From 2dbb24ea0416cb83185b6f92624bd9b6e937eade Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 26 Jan 2020 21:01:00 -0500 Subject: Improve error messages when Interface server is invalid --- ext/bg/js/settings/anki.js | 8 +++++++- ext/bg/settings.html | 9 ++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/anki.js b/ext/bg/js/settings/anki.js index 9adb2f2a..3b0912d4 100644 --- a/ext/bg/js/settings/anki.js +++ b/ext/bg/js/settings/anki.js @@ -35,9 +35,15 @@ function _ankiSetError(error) { const node = document.querySelector('#anki-error'); if (!node) { return; } if (error) { + const errorString = `${error}`; node.hidden = false; - node.textContent = `${error}`; + node.textContent = errorString; _ankiSetErrorData(node, error); + + const node2 = document.querySelector('#anki-invalid-response-error'); + if (node2 !== null) { + node2.hidden = (errorString.indexOf('Invalid response') < 0); + } } else { node.hidden = true; node.textContent = ''; diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 3e06d4b5..8c787aff 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -760,6 +760,13 @@ + +
@@ -771,7 +778,7 @@
- +
-- cgit v1.2.3 From 02e0e24153eb95b45057cfc92cadb6563b229cc1 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 27 Jan 2020 23:28:01 +0200 Subject: change jpod101 kana check to detect katakana --- ext/bg/js/audio.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 36ac413b..d1e4af9e 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -22,7 +22,7 @@ const audioUrlBuilders = new Map([ let kana = definition.reading; let kanji = definition.expression; - if (!kana && wanakana.isHiragana(kanji)) { + if (!kana && jpIsStringEntirelyKana(kanji)) { kana = kanji; kanji = null; } -- cgit v1.2.3 From 1d9332cb69d76818a1f6a422ceafc3a463c48d7c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 28 Jan 2020 18:58:14 -0500 Subject: Simplify getSequencedDefinitions and dictTermsMergeBySequence --- ext/bg/js/dictionary.js | 20 ++++++++++++-------- ext/bg/js/translator.js | 16 ++++++++-------- 2 files changed, 20 insertions(+), 16 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 67128725..7d6bf7ba 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -159,11 +159,14 @@ function dictTermsGroup(definitions, dictionaries) { } function dictTermsMergeBySequence(definitions, mainDictionary) { - const definitionsBySequence = {'-1': []}; + const sequencedDefinitions = new Map(); + const nonSequencedDefinitions = []; for (const definition of definitions) { - if (mainDictionary === definition.dictionary && definition.sequence >= 0) { - if (!definitionsBySequence[definition.sequence]) { - definitionsBySequence[definition.sequence] = { + const sequence = definition.sequence; + if (mainDictionary === definition.dictionary && sequence >= 0) { + sequencedDefinition = sequencedDefinitions.get(sequence); + if (typeof sequencedDefinition === 'undefined') { + sequencedDefinition = { reasons: definition.reasons, score: Number.MIN_SAFE_INTEGER, expression: new Set(), @@ -173,15 +176,16 @@ function dictTermsMergeBySequence(definitions, mainDictionary) { dictionary: definition.dictionary, definitions: [] }; + sequencedDefinitions.set(sequence, sequencedDefinition); + } else { + sequencedDefinition.score = Math.max(sequencedDefinition.score, definition.score); } - const score = Math.max(definitionsBySequence[definition.sequence].score, definition.score); - definitionsBySequence[definition.sequence].score = score; } else { - definitionsBySequence['-1'].push(definition); + nonSequencedDefinitions.push(definition); } } - return definitionsBySequence; + return [sequencedDefinitions, nonSequencedDefinitions]; } function dictTermsMergeByGloss(result, definitions, appendTo, mergedIndices) { diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index dfec54ac..d32f8c67 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -48,14 +48,14 @@ class Translator { } async getSequencedDefinitions(definitions, mainDictionary) { - const definitionsBySequence = dictTermsMergeBySequence(definitions, mainDictionary); - const defaultDefinitions = definitionsBySequence['-1']; - - const sequenceList = Object.keys(definitionsBySequence).map((v) => Number(v)).filter((v) => v >= 0); - const sequencedDefinitions = sequenceList.map((key) => ({ - definitions: definitionsBySequence[key], - rawDefinitions: [] - })); + const [definitionsBySequence, defaultDefinitions] = dictTermsMergeBySequence(definitions, mainDictionary); + + const sequenceList = []; + const sequencedDefinitions = []; + for (const [key, value] of definitionsBySequence.entries()) { + sequenceList.push(key); + sequencedDefinitions.push({definitions: value, rawDefinitions: []}); + } for (const definition of await this.database.findTermsBySequenceBulk(sequenceList, mainDictionary)) { sequencedDefinitions[definition.index].rawDefinitions.push(definition); -- cgit v1.2.3 From daf038544873a9c59511c73b49475c80cb0dc4d1 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 1 Feb 2020 10:34:13 -0500 Subject: Add declaration --- ext/bg/js/dictionary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 7d6bf7ba..1dd88e28 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -164,7 +164,7 @@ function dictTermsMergeBySequence(definitions, mainDictionary) { for (const definition of definitions) { const sequence = definition.sequence; if (mainDictionary === definition.dictionary && sequence >= 0) { - sequencedDefinition = sequencedDefinitions.get(sequence); + let sequencedDefinition = sequencedDefinitions.get(sequence); if (typeof sequencedDefinition === 'undefined') { sequencedDefinition = { reasons: definition.reasons, -- cgit v1.2.3 From 5541aae2012785894fbb52b2767cce5be9a6f5ba Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 1 Feb 2020 20:35:21 -0500 Subject: Assign valid score during construction --- ext/bg/js/dictionary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 1dd88e28..48f65d6c 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -168,7 +168,7 @@ function dictTermsMergeBySequence(definitions, mainDictionary) { if (typeof sequencedDefinition === 'undefined') { sequencedDefinition = { reasons: definition.reasons, - score: Number.MIN_SAFE_INTEGER, + score: definition.score, expression: new Set(), reading: new Set(), expressions: new Map(), -- cgit v1.2.3 From 566012f228452a4113e83f76d1c53be1f1f41643 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 1 Feb 2020 11:40:17 -0500 Subject: Use await --- ext/bg/js/settings/dictionaries.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index ed171ae9..545e128d 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -427,7 +427,7 @@ async function onDictionaryMainChanged(e) { const optionsContext = getOptionsContext(); const options = await getOptionsMutable(optionsContext); options.general.mainDictionary = value; - settingsSaveOptions(); + await settingsSaveOptions(); } -- cgit v1.2.3 From 748cd27ad09526e4ec8fd309c6a85fdba1e2462c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 1 Feb 2020 11:20:50 -0500 Subject: Remove argument from onDictionaryOptionsChanged --- ext/bg/js/settings/dictionaries.js | 8 ++++++-- ext/bg/js/settings/main.js | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index 545e128d..31f47bfd 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -361,12 +361,16 @@ async function dictSettingsInitialize() { const optionsContext = getOptionsContext(); const options = await getOptionsMutable(optionsContext); - onDictionaryOptionsChanged(options); + onDictionaryOptionsChanged(); onDatabaseUpdated(options); } -async function onDictionaryOptionsChanged(options) { +async function onDictionaryOptionsChanged() { if (dictionaryUI === null) { return; } + + const optionsContext = getOptionsContext(); + const options = await getOptionsMutable(optionsContext); + dictionaryUI.setOptionsDictionaries(options.dictionaries); const optionsFull = await apiOptionsGetFull(); diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 3bf65eda..6e162ffc 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -167,7 +167,7 @@ async function formWrite(options) { await ankiTemplatesUpdateValue(); await onAnkiOptionsChanged(options); - await onDictionaryOptionsChanged(options); + await onDictionaryOptionsChanged(); formUpdateVisibility(options); } -- cgit v1.2.3 From 55047def9dad485b4ad6e118c5d9eaed5d09af0c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 1 Feb 2020 11:40:09 -0500 Subject: Separate the functionality of updateMainDictionarySelect One function now updates the options, and another handles updating the value. The options are no longer mutated due to updating this Enable search when clicking glossary entries +
@@ -492,6 +492,10 @@
+
+ +
+
diff --git a/ext/mixed/css/display-dark.css b/ext/mixed/css/display-dark.css index 088fc741..c9cd9f90 100644 --- a/ext/mixed/css/display-dark.css +++ b/ext/mixed/css/display-dark.css @@ -38,6 +38,7 @@ body { background-color: #1e1e1e; color: #d4d4d4; } .tag[data-category=dictionary] { background-color: #9057ad; } .tag[data-category=frequency] { background-color: #489148; } .tag[data-category=partOfSpeech] { background-color: #565656; } +.tag[data-category=search] { background-color: #69696e; } .term-reasons { color: #888888; } diff --git a/ext/mixed/css/display-default.css b/ext/mixed/css/display-default.css index 69141c9d..6eee43c4 100644 --- a/ext/mixed/css/display-default.css +++ b/ext/mixed/css/display-default.css @@ -38,6 +38,7 @@ body { background-color: #ffffff; color: #333333; } .tag[data-category=dictionary] { background-color: #aa66cc; } .tag[data-category=frequency] { background-color: #5cb85c; } .tag[data-category=partOfSpeech] { background-color: #565656; } +.tag[data-category=search] { background-color: #8a8a91; } .term-reasons { color: #777777; } diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index fefd500f..3a66cec3 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -227,6 +227,10 @@ button.action-button { margin-left: 0.375em; } +html:root:not([data-enable-search-tags=true]) .tag[data-category=search] { + display: none; +} + .entry-header2, .entry-header3 { display: inline; diff --git a/ext/mixed/display-templates.html b/ext/mixed/display-templates.html index 6c611be9..6fcf4c74 100644 --- a/ext/mixed/display-templates.html +++ b/ext/mixed/display-templates.html @@ -77,5 +77,6 @@ + diff --git a/ext/mixed/js/display-generator.js b/ext/mixed/js/display-generator.js index b2dc373b..c90e693a 100644 --- a/ext/mixed/js/display-generator.js +++ b/ext/mixed/js/display-generator.js @@ -111,7 +111,11 @@ class DisplayGenerator { // Fallback termTags = details.termTags; } + const searchQueries = [details.expression, details.reading] + .filter((x) => !!x) + .map((x) => ({query: x})); DisplayGenerator._appendMultiple(tagContainer, this.createTag.bind(this), termTags); + DisplayGenerator._appendMultiple(tagContainer, this.createSearchTag.bind(this), searchQueries); DisplayGenerator._appendMultiple(frequencyContainer, this.createFrequencyTag.bind(this), details.frequencies); return node; @@ -270,6 +274,16 @@ class DisplayGenerator { return node; } + createSearchTag(details) { + const node = DisplayGenerator._instantiateTemplate(this._tagSearchTemplate); + + node.textContent = details.query; + + node.dataset.query = details.query; + + return node; + } + createFrequencyTag(details) { const node = DisplayGenerator._instantiateTemplate(this._tagFrequencyTemplate); @@ -311,6 +325,7 @@ class DisplayGenerator { 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'); } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 70ef8ec3..54cda0eb 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -257,6 +257,7 @@ class Display { data.ankiEnabled = `${options.anki.enable}`; data.audioEnabled = `${options.audio.enable}`; data.compactGlossaries = `${options.general.compactGlossaries}`; + data.enableSearchTags = `${options.scanning.enableSearchTags}`; data.debug = `${options.general.debugInfo}`; } @@ -312,9 +313,9 @@ class Display { this.addEventListeners('.action-play-audio', 'click', this.onAudioPlay.bind(this)); this.addEventListeners('.kanji-link', 'click', this.onKanjiLookup.bind(this)); if (this.options.scanning.enablePopupSearch) { - this.addEventListeners('.term-glossary-item', 'mouseup', this.onGlossaryMouseUp.bind(this)); - this.addEventListeners('.term-glossary-item', 'mousedown', this.onGlossaryMouseDown.bind(this)); - this.addEventListeners('.term-glossary-item', 'mousemove', this.onGlossaryMouseMove.bind(this)); + this.addEventListeners('.term-glossary-item, .tag', 'mouseup', this.onGlossaryMouseUp.bind(this)); + this.addEventListeners('.term-glossary-item, .tag', 'mousedown', this.onGlossaryMouseDown.bind(this)); + this.addEventListeners('.term-glossary-item, .tag', 'mousemove', this.onGlossaryMouseMove.bind(this)); } } else { Display.clearEventListeners(this.eventListeners); -- cgit v1.2.3 From f23e0c9c95a7c0803694ea91b0ea55079e8f52d6 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 1 Feb 2020 20:51:30 -0500 Subject: Validate Deinflector.ruleTypes --- ext/bg/js/deinflector.js | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/deinflector.js b/ext/bg/js/deinflector.js index 33b2a8b3..e2ced965 100644 --- a/ext/bg/js/deinflector.js +++ b/ext/bg/js/deinflector.js @@ -76,17 +76,19 @@ class Deinflector { const ruleTypes = Deinflector.ruleTypes; let value = 0; for (const rule of rules) { - value |= ruleTypes[rule]; + const ruleBits = ruleTypes.get(rule); + if (typeof ruleBits === 'undefined') { continue; } + value |= ruleBits; } return value; } } -Deinflector.ruleTypes = { - 'v1': 0b0000001, // Verb ichidan - 'v5': 0b0000010, // Verb godan - 'vs': 0b0000100, // Verb suru - 'vk': 0b0001000, // Verb kuru - 'adj-i': 0b0010000, // Adjective i - 'iru': 0b0100000 // Intermediate -iru endings for progressive or perfect tense -}; +Deinflector.ruleTypes = new Map([ + ['v1', 0b0000001], // Verb ichidan + ['v5', 0b0000010], // Verb godan + ['vs', 0b0000100], // Verb suru + ['vk', 0b0001000], // Verb kuru + ['adj-i', 0b0010000], // Adjective i + ['iru', 0b0100000] // Intermediate -iru endings for progressive or perfect tense +]); -- cgit v1.2.3 From 0b474751b5fb994d402caf3d0515d95680684b60 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 26 Jan 2020 11:06:03 -0500 Subject: Add simplified isObject test --- ext/bg/js/json-schema.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 5d596a8b..2b7c9f27 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -131,19 +131,19 @@ class JsonSchemaProxyHandler { case 'object': { const properties = schema.properties; - if (properties !== null && typeof properties === 'object' && !Array.isArray(properties)) { + if (JsonSchemaProxyHandler.isObject(properties)) { if (Object.prototype.hasOwnProperty.call(properties, property)) { return properties[property]; } } const additionalProperties = schema.additionalProperties; - return (additionalProperties !== null && typeof additionalProperties === 'object' && !Array.isArray(additionalProperties)) ? additionalProperties : null; + return JsonSchemaProxyHandler.isObject(additionalProperties) ? additionalProperties : null; } case 'array': { const items = schema.items; - return (items !== null && typeof items === 'object' && !Array.isArray(items)) ? items : null; + return JsonSchemaProxyHandler.isObject(items) ? items : null; } default: return null; @@ -399,6 +399,10 @@ class JsonSchemaProxyHandler { return value; } + + static isObject(value) { + return typeof value === 'object' && value !== null && !Array.isArray(value); + } } class JsonSchema { -- cgit v1.2.3 From 6595715f7cdf1c4d0de743443b88eba05d7d6ae1 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 26 Jan 2020 10:57:52 -0500 Subject: Add support for allOf, anyOf, oneOf, and not --- ext/bg/js/json-schema.js | 64 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 2b7c9f27..8129b6d2 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -151,6 +151,70 @@ class JsonSchemaProxyHandler { } static validate(value, schema) { + let result = JsonSchemaProxyHandler.validateSingleSchema(value, schema); + if (result !== null) { return result; } + + result = JsonSchemaProxyHandler.validateAllOf(value, schema); + if (result !== null) { return result; } + + result = JsonSchemaProxyHandler.validateAnyOf(value, schema); + if (result !== null) { return result; } + + result = JsonSchemaProxyHandler.validateOneOf(value, schema); + if (result !== null) { return result; } + + result = JsonSchemaProxyHandler.validateNoneOf(value, schema); + if (result !== null) { return result; } + + return null; + } + + static validateAllOf(value, schema) { + const subSchemas = schema.allOf; + if (!Array.isArray(subSchemas)) { return null; } + + for (let i = 0; i < subSchemas.length; ++i) { + const result = JsonSchemaProxyHandler.validate(value, subSchemas[i]); + if (result !== null) { return `allOf[${i}] schema didn't match: ${result}`; } + } + return null; + } + + static validateAnyOf(value, schema) { + const subSchemas = schema.anyOf; + if (!Array.isArray(subSchemas)) { return null; } + + for (let i = 0; i < subSchemas.length; ++i) { + const result = JsonSchemaProxyHandler.validate(value, subSchemas[i]); + if (result === null) { return null; } + } + return '0 anyOf schemas matched'; + } + + static validateOneOf(value, schema) { + const subSchemas = schema.oneOf; + if (!Array.isArray(subSchemas)) { return null; } + + let count = 0; + for (let i = 0; i < subSchemas.length; ++i) { + const result = JsonSchemaProxyHandler.validate(value, subSchemas[i]); + if (result === null) { ++count; } + } + return count === 1 ? null : `${count} oneOf schemas matched`; + } + + static validateNoneOf(value, schema) { + const subSchemas = schema.not; + if (!Array.isArray(subSchemas)) { return null; } + + for (let i = 0; i < subSchemas.length; ++i) { + const result = JsonSchemaProxyHandler.validate(value, subSchemas[i]); + if (result === null) { return `not[${i}] schema matched`; } + } + return null; + } + + static validateSingleSchema(value, schema) { const type = JsonSchemaProxyHandler.getValueType(value); const schemaType = schema.type; if (!JsonSchemaProxyHandler.isValueTypeAny(value, type, schemaType)) { -- cgit v1.2.3 From 203216986e82d8b6c654979791d1ff5172ba83ca Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 26 Jan 2020 11:13:13 -0500 Subject: Add support for conditionals --- ext/bg/js/json-schema.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 8129b6d2..ab4a4817 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -154,6 +154,9 @@ class JsonSchemaProxyHandler { let result = JsonSchemaProxyHandler.validateSingleSchema(value, schema); if (result !== null) { return result; } + result = JsonSchemaProxyHandler.validateConditional(value, schema); + if (result !== null) { return result; } + result = JsonSchemaProxyHandler.validateAllOf(value, schema); if (result !== null) { return result; } @@ -169,6 +172,25 @@ class JsonSchemaProxyHandler { return null; } + static validateConditional(value, schema) { + const ifCondition = schema.if; + if (!JsonSchemaProxyHandler.isObject(ifCondition)) { return null; } + + const thenSchema = schema.then; + if (JsonSchemaProxyHandler.isObject(thenSchema)) { + const result = JsonSchemaProxyHandler.validate(value, thenSchema); + if (result !== null) { return `then conditional didn't match: ${result}`; } + } + + const elseSchema = schema.else; + if (JsonSchemaProxyHandler.isObject(elseSchema)) { + const result = JsonSchemaProxyHandler.validate(value, thenSchema); + if (result !== null) { return `else conditional didn't match: ${result}`; } + } + + return null; + } + static validateAllOf(value, schema) { const subSchemas = schema.allOf; if (!Array.isArray(subSchemas)) { return null; } -- cgit v1.2.3 From a844698f153773d5255724ba48a00851418af009 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 26 Jan 2020 15:45:31 -0500 Subject: Return unconstrained schema when additionalProperties is true/undefined --- ext/bg/js/json-schema.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index ab4a4817..7a7f2489 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -138,7 +138,13 @@ class JsonSchemaProxyHandler { } const additionalProperties = schema.additionalProperties; - return JsonSchemaProxyHandler.isObject(additionalProperties) ? additionalProperties : null; + if (additionalProperties === false) { + return null; + } if (JsonSchemaProxyHandler.isObject(additionalProperties)) { + return additionalProperties; + } else { + return JsonSchemaProxyHandler._unconstrainedSchema; + } } case 'array': { @@ -491,6 +497,8 @@ class JsonSchemaProxyHandler { } } +JsonSchemaProxyHandler._unconstrainedSchema = {}; + class JsonSchema { static createProxy(target, schema) { return new Proxy(target, new JsonSchemaProxyHandler(schema)); -- cgit v1.2.3 From 980a1ddf745032a790a043e8040cdb7ec5b110ad Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 26 Jan 2020 12:34:27 -0500 Subject: Improve support for array schemas --- ext/bg/js/json-schema.js | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 7a7f2489..f32176fd 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -149,7 +149,23 @@ class JsonSchemaProxyHandler { case 'array': { const items = schema.items; - return JsonSchemaProxyHandler.isObject(items) ? items : null; + if (JsonSchemaProxyHandler.isObject(items)) { + return items; + } + if (Array.isArray(items)) { + if (property >= 0 && property < items.length) { + return items[property]; + } + } + + const additionalItems = schema.additionalItems; + if (additionalItems === false) { + return null; + } else if (JsonSchemaProxyHandler.isObject(additionalItems)) { + return additionalItems; + } else { + return JsonSchemaProxyHandler._unconstrainedSchema; + } } default: return null; @@ -322,6 +338,18 @@ class JsonSchemaProxyHandler { return 'Array length too long'; } + for (let i = 0, ii = value.length; i < ii; ++i) { + const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, i, value); + if (propertySchema === null) { + return `No schema found for array[${i}]`; + } + + const error = JsonSchemaProxyHandler.validate(value[i], propertySchema); + if (error !== null) { + return error; + } + } + return null; } -- cgit v1.2.3 From 31dbeab67ce79a9c46053ce124ad3924f71b67cc Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 26 Jan 2020 14:59:37 -0500 Subject: Add validate on JsonSchema --- ext/bg/js/json-schema.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index f32176fd..61bd3d43 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -532,6 +532,10 @@ class JsonSchema { return new Proxy(target, new JsonSchemaProxyHandler(schema)); } + static validate(value, schema) { + return JsonSchemaProxyHandler.validate(value, schema); + } + static getValidValueOrDefault(schema, value) { return JsonSchemaProxyHandler.getValidValueOrDefault(schema, value); } -- cgit v1.2.3 From 52b623b5cdb1963aa1fb65228f9377e147708959 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 26 Jan 2020 15:57:39 -0500 Subject: Improve getPropertySchema's type detection --- ext/bg/js/json-schema.js | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 61bd3d43..9b651f46 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -64,7 +64,7 @@ class JsonSchemaProxyHandler { } } - const propertySchema = JsonSchemaProxyHandler.getPropertySchema(this._schema, property); + const propertySchema = JsonSchemaProxyHandler.getPropertySchema(this._schema, property, target); if (propertySchema === null) { return; } @@ -86,7 +86,7 @@ class JsonSchemaProxyHandler { } } - const propertySchema = JsonSchemaProxyHandler.getPropertySchema(this._schema, property); + const propertySchema = JsonSchemaProxyHandler.getPropertySchema(this._schema, property, target); if (propertySchema === null) { throw new Error(`Property ${property} not supported`); } @@ -122,11 +122,8 @@ class JsonSchemaProxyHandler { throw new Error('construct not supported'); } - static getPropertySchema(schema, property) { - const type = schema.type; - if (Array.isArray(type)) { - throw new Error(`Ambiguous property type for ${property}`); - } + static getPropertySchema(schema, property, value) { + const type = JsonSchemaProxyHandler.getSchemaOrValueType(schema, value); switch (type) { case 'object': { @@ -172,6 +169,29 @@ class JsonSchemaProxyHandler { } } + static getSchemaOrValueType(schema, value) { + const type = schema.type; + + if (Array.isArray(type)) { + if (typeof value !== 'undefined') { + const valueType = JsonSchemaProxyHandler.getValueType(value); + if (type.indexOf(valueType) >= 0) { + return valueType; + } + } + throw new Error(`Ambiguous property type for ${property}`); + } + + if (typeof type === 'undefined') { + if (typeof value !== 'undefined') { + return JsonSchemaProxyHandler.getValueType(value); + } + throw new Error(`No property type for ${property}`); + } + + return type; + } + static validate(value, schema) { let result = JsonSchemaProxyHandler.validateSingleSchema(value, schema); if (result !== null) { return result; } @@ -376,7 +396,7 @@ class JsonSchemaProxyHandler { } for (const property of properties) { - const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, property); + const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, property, value); if (propertySchema === null) { return `No schema found for ${property}`; } @@ -492,14 +512,14 @@ class JsonSchemaProxyHandler { for (const property of required) { properties.delete(property); - const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, property); + const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, property, value); if (propertySchema === null) { continue; } value[property] = JsonSchemaProxyHandler.getValidValueOrDefault(propertySchema, value[property]); } } for (const property of properties) { - const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, property); + const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, property, value); if (propertySchema === null) { Reflect.deleteProperty(value, property); } else { @@ -512,7 +532,7 @@ class JsonSchemaProxyHandler { static populateArrayDefaults(value, schema) { for (let i = 0, ii = value.length; i < ii; ++i) { - const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, i); + const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, i, value); if (propertySchema === null) { continue; } value[i] = JsonSchemaProxyHandler.getValidValueOrDefault(propertySchema, value[i]); } -- cgit v1.2.3 From 0171d86b28e3a4373a2deabb4a4a8cf738ca2743 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 1 Feb 2020 22:20:47 -0500 Subject: Fix maxLength check --- ext/bg/js/json-schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 9b651f46..29873b52 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -339,7 +339,7 @@ class JsonSchemaProxyHandler { return 'String length too short'; } - const maxLength = schema.minLength; + const maxLength = schema.maxLength; if (typeof maxLength === 'number' && value.length > maxLength) { return 'String length too long'; } -- cgit v1.2.3 From 36e641e00168b09e81aad7e234399d06ce12e619 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 1 Feb 2020 22:57:27 -0500 Subject: getSchemaOrValueType return null --- ext/bg/js/json-schema.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 29873b52..c20cb502 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -179,14 +179,14 @@ class JsonSchemaProxyHandler { return valueType; } } - throw new Error(`Ambiguous property type for ${property}`); + return null; } if (typeof type === 'undefined') { if (typeof value !== 'undefined') { return JsonSchemaProxyHandler.getValueType(value); } - throw new Error(`No property type for ${property}`); + return null; } return type; -- cgit v1.2.3 From 964db7410863a5b840b101884c3c522389dd4e80 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 2 Feb 2020 00:13:46 -0500 Subject: Update schema validation to throw errors --- ext/bg/js/json-schema.js | 158 ++++++++++++++++++++++------------------------- 1 file changed, 74 insertions(+), 84 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index c20cb502..65fbce41 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -93,10 +93,7 @@ class JsonSchemaProxyHandler { value = JsonSchema.isolate(value); - const error = JsonSchemaProxyHandler.validate(value, propertySchema); - if (error !== null) { - throw new Error(`Invalid value: ${error}`); - } + JsonSchemaProxyHandler.validate(value, propertySchema); target[property] = value; return true; @@ -193,184 +190,173 @@ class JsonSchemaProxyHandler { } static validate(value, schema) { - let result = JsonSchemaProxyHandler.validateSingleSchema(value, schema); - if (result !== null) { return result; } - - result = JsonSchemaProxyHandler.validateConditional(value, schema); - if (result !== null) { return result; } - - result = JsonSchemaProxyHandler.validateAllOf(value, schema); - if (result !== null) { return result; } - - result = JsonSchemaProxyHandler.validateAnyOf(value, schema); - if (result !== null) { return result; } - - result = JsonSchemaProxyHandler.validateOneOf(value, schema); - if (result !== null) { return result; } - - result = JsonSchemaProxyHandler.validateNoneOf(value, schema); - if (result !== null) { return result; } - - return null; + JsonSchemaProxyHandler.validateSingleSchema(value, schema); + JsonSchemaProxyHandler.validateConditional(value, schema); + JsonSchemaProxyHandler.validateAllOf(value, schema); + JsonSchemaProxyHandler.validateAnyOf(value, schema); + JsonSchemaProxyHandler.validateOneOf(value, schema); + JsonSchemaProxyHandler.validateNoneOf(value, schema); } static validateConditional(value, schema) { const ifCondition = schema.if; - if (!JsonSchemaProxyHandler.isObject(ifCondition)) { return null; } + if (!JsonSchemaProxyHandler.isObject(ifCondition)) { return; } const thenSchema = schema.then; if (JsonSchemaProxyHandler.isObject(thenSchema)) { - const result = JsonSchemaProxyHandler.validate(value, thenSchema); - if (result !== null) { return `then conditional didn't match: ${result}`; } + JsonSchemaProxyHandler.validate(value, thenSchema); } const elseSchema = schema.else; if (JsonSchemaProxyHandler.isObject(elseSchema)) { - const result = JsonSchemaProxyHandler.validate(value, thenSchema); - if (result !== null) { return `else conditional didn't match: ${result}`; } + JsonSchemaProxyHandler.validate(value, thenSchema); } - - return null; } static validateAllOf(value, schema) { const subSchemas = schema.allOf; - if (!Array.isArray(subSchemas)) { return null; } + if (!Array.isArray(subSchemas)) { return; } for (let i = 0; i < subSchemas.length; ++i) { - const result = JsonSchemaProxyHandler.validate(value, subSchemas[i]); - if (result !== null) { return `allOf[${i}] schema didn't match: ${result}`; } + JsonSchemaProxyHandler.validate(value, subSchemas[i]); } - return null; } static validateAnyOf(value, schema) { const subSchemas = schema.anyOf; - if (!Array.isArray(subSchemas)) { return null; } + if (!Array.isArray(subSchemas)) { return; } for (let i = 0; i < subSchemas.length; ++i) { - const result = JsonSchemaProxyHandler.validate(value, subSchemas[i]); - if (result === null) { return null; } + try { + JsonSchemaProxyHandler.validate(value, subSchemas[i]); + return; + } catch (e) { + // NOP + } } - return '0 anyOf schemas matched'; + + throw new JsonSchemaValidationError('0 anyOf schemas matched', value, schema); } static validateOneOf(value, schema) { const subSchemas = schema.oneOf; - if (!Array.isArray(subSchemas)) { return null; } + if (!Array.isArray(subSchemas)) { return; } let count = 0; for (let i = 0; i < subSchemas.length; ++i) { - const result = JsonSchemaProxyHandler.validate(value, subSchemas[i]); - if (result === null) { ++count; } + try { + JsonSchemaProxyHandler.validate(value, subSchemas[i]); + ++count; + } catch (e) { + // NOP + } + } + + if (count !== 1) { + throw new JsonSchemaValidationError(`${count} oneOf schemas matched`, value, schema); } - return count === 1 ? null : `${count} oneOf schemas matched`; } static validateNoneOf(value, schema) { const subSchemas = schema.not; - if (!Array.isArray(subSchemas)) { return null; } + if (!Array.isArray(subSchemas)) { return; } for (let i = 0; i < subSchemas.length; ++i) { - const result = JsonSchemaProxyHandler.validate(value, subSchemas[i]); - if (result === null) { return `not[${i}] schema matched`; } + try { + JsonSchemaProxyHandler.validate(value, subSchemas[i]); + } catch (e) { + continue; + } + throw new JsonSchemaValidationError(`not[${i}] schema matched`, value, schema); } - return null; } static validateSingleSchema(value, schema) { const type = JsonSchemaProxyHandler.getValueType(value); const schemaType = schema.type; if (!JsonSchemaProxyHandler.isValueTypeAny(value, type, schemaType)) { - return `Value type ${type} does not match schema type ${schemaType}`; + throw new JsonSchemaValidationError(`Value type ${type} does not match schema type ${schemaType}`, value, schema); } const schemaEnum = schema.enum; if (Array.isArray(schemaEnum) && !JsonSchemaProxyHandler.valuesAreEqualAny(value, schemaEnum)) { - return 'Invalid enum value'; + throw new JsonSchemaValidationError('Invalid enum value', value, schema); } switch (type) { case 'number': - return JsonSchemaProxyHandler.validateNumber(value, schema); + JsonSchemaProxyHandler.validateNumber(value, schema); + break; case 'string': - return JsonSchemaProxyHandler.validateString(value, schema); + JsonSchemaProxyHandler.validateString(value, schema); + break; case 'array': - return JsonSchemaProxyHandler.validateArray(value, schema); + JsonSchemaProxyHandler.validateArray(value, schema); + break; case 'object': - return JsonSchemaProxyHandler.validateObject(value, schema); - default: - return null; + JsonSchemaProxyHandler.validateObject(value, schema); + break; } } static validateNumber(value, schema) { const multipleOf = schema.multipleOf; if (typeof multipleOf === 'number' && Math.floor(value / multipleOf) * multipleOf !== value) { - return `Number is not a multiple of ${multipleOf}`; + throw new JsonSchemaValidationError(`Number is not a multiple of ${multipleOf}`, value, schema); } const minimum = schema.minimum; if (typeof minimum === 'number' && value < minimum) { - return `Number is less than ${minimum}`; + throw new JsonSchemaValidationError(`Number is less than ${minimum}`, value, schema); } const exclusiveMinimum = schema.exclusiveMinimum; if (typeof exclusiveMinimum === 'number' && value <= exclusiveMinimum) { - return `Number is less than or equal to ${exclusiveMinimum}`; + throw new JsonSchemaValidationError(`Number is less than or equal to ${exclusiveMinimum}`, value, schema); } const maximum = schema.maximum; if (typeof maximum === 'number' && value > maximum) { - return `Number is greater than ${maximum}`; + throw new JsonSchemaValidationError(`Number is greater than ${maximum}`, value, schema); } const exclusiveMaximum = schema.exclusiveMaximum; if (typeof exclusiveMaximum === 'number' && value >= exclusiveMaximum) { - return `Number is greater than or equal to ${exclusiveMaximum}`; + throw new JsonSchemaValidationError(`Number is greater than or equal to ${exclusiveMaximum}`, value, schema); } - - return null; } static validateString(value, schema) { const minLength = schema.minLength; if (typeof minLength === 'number' && value.length < minLength) { - return 'String length too short'; + throw new JsonSchemaValidationError('String length too short', value, schema); } const maxLength = schema.maxLength; if (typeof maxLength === 'number' && value.length > maxLength) { - return 'String length too long'; + throw new JsonSchemaValidationError('String length too long', value, schema); } - - return null; } static validateArray(value, schema) { const minItems = schema.minItems; if (typeof minItems === 'number' && value.length < minItems) { - return 'Array length too short'; + throw new JsonSchemaValidationError('Array length too short', value, schema); } const maxItems = schema.maxItems; if (typeof maxItems === 'number' && value.length > maxItems) { - return 'Array length too long'; + throw new JsonSchemaValidationError('Array length too long', value, schema); } for (let i = 0, ii = value.length; i < ii; ++i) { const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, i, value); if (propertySchema === null) { - return `No schema found for array[${i}]`; + throw new JsonSchemaValidationError(`No schema found for array[${i}]`, value, schema); } - const error = JsonSchemaProxyHandler.validate(value[i], propertySchema); - if (error !== null) { - return error; - } + JsonSchemaProxyHandler.validate(value[i], propertySchema); } - - return null; } static validateObject(value, schema) { @@ -380,33 +366,28 @@ class JsonSchemaProxyHandler { if (Array.isArray(required)) { for (const property of required) { if (!properties.has(property)) { - return `Missing property ${property}`; + throw new JsonSchemaValidationError(`Missing property ${property}`, value, schema); } } } const minProperties = schema.minProperties; if (typeof minProperties === 'number' && properties.length < minProperties) { - return 'Not enough object properties'; + throw new JsonSchemaValidationError('Not enough object properties', value, schema); } const maxProperties = schema.maxProperties; if (typeof maxProperties === 'number' && properties.length > maxProperties) { - return 'Too many object properties'; + throw new JsonSchemaValidationError('Too many object properties', value, schema); } for (const property of properties) { const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, property, value); if (propertySchema === null) { - return `No schema found for ${property}`; - } - const error = JsonSchemaProxyHandler.validate(value[property], propertySchema); - if (error !== null) { - return error; + throw new JsonSchemaValidationError(`No schema found for ${property}`, value, schema); } + JsonSchemaProxyHandler.validate(value[property], propertySchema); } - - return null; } static isValueTypeAny(value, type, schemaTypes) { @@ -547,6 +528,15 @@ class JsonSchemaProxyHandler { JsonSchemaProxyHandler._unconstrainedSchema = {}; +class JsonSchemaValidationError extends Error { + constructor(message, value, schema, path) { + super(message); + this.value = value; + this.schema = schema; + this.path = path; + } +} + class JsonSchema { static createProxy(target, schema) { return new Proxy(target, new JsonSchemaProxyHandler(schema)); -- cgit v1.2.3 From 7c9fe2c6cf52e61620ff36853fa0dee1b93594f5 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 2 Feb 2020 10:17:16 -0500 Subject: Fix conditional logic --- ext/bg/js/json-schema.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 65fbce41..97429211 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -199,17 +199,19 @@ class JsonSchemaProxyHandler { } static validateConditional(value, schema) { - const ifCondition = schema.if; - if (!JsonSchemaProxyHandler.isObject(ifCondition)) { return; } + const ifSchema = schema.if; + if (!JsonSchemaProxyHandler.isObject(ifSchema)) { return; } - const thenSchema = schema.then; - if (JsonSchemaProxyHandler.isObject(thenSchema)) { + let okay = true; + try { JsonSchemaProxyHandler.validate(value, thenSchema); + } catch (e) { + okay = false; } - const elseSchema = schema.else; - if (JsonSchemaProxyHandler.isObject(elseSchema)) { - JsonSchemaProxyHandler.validate(value, thenSchema); + const nextSchema = okay ? schema.then : schema.else; + if (JsonSchemaProxyHandler.isObject(nextSchema)) { + JsonSchemaProxyHandler.validate(value, nextSchema); } } -- cgit v1.2.3 From fca5c7515160bcae84fd3dc3284ec54babe75c72 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 2 Feb 2020 10:35:41 -0500 Subject: Fix ifSchema --- ext/bg/js/json-schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 97429211..43ba0c1d 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -204,7 +204,7 @@ class JsonSchemaProxyHandler { let okay = true; try { - JsonSchemaProxyHandler.validate(value, thenSchema); + JsonSchemaProxyHandler.validate(value, ifSchema, info); } catch (e) { okay = false; } -- cgit v1.2.3 From 3bef380e3be0a641ec5095ef73ddb5c8d48a342c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 2 Feb 2020 10:47:30 -0500 Subject: Add improved error information when validation fails --- ext/bg/js/json-schema.js | 165 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 114 insertions(+), 51 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 43ba0c1d..49f1e082 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -93,7 +93,7 @@ class JsonSchemaProxyHandler { value = JsonSchema.isolate(value); - JsonSchemaProxyHandler.validate(value, propertySchema); + JsonSchemaProxyHandler.validate(value, propertySchema, new JsonSchemaTraversalInfo(value, propertySchema)); target[property] = value; return true; @@ -189,206 +189,244 @@ class JsonSchemaProxyHandler { return type; } - static validate(value, schema) { - JsonSchemaProxyHandler.validateSingleSchema(value, schema); - JsonSchemaProxyHandler.validateConditional(value, schema); - JsonSchemaProxyHandler.validateAllOf(value, schema); - JsonSchemaProxyHandler.validateAnyOf(value, schema); - JsonSchemaProxyHandler.validateOneOf(value, schema); - JsonSchemaProxyHandler.validateNoneOf(value, schema); + static validate(value, schema, info) { + JsonSchemaProxyHandler.validateSingleSchema(value, schema, info); + JsonSchemaProxyHandler.validateConditional(value, schema, info); + JsonSchemaProxyHandler.validateAllOf(value, schema, info); + JsonSchemaProxyHandler.validateAnyOf(value, schema, info); + JsonSchemaProxyHandler.validateOneOf(value, schema, info); + JsonSchemaProxyHandler.validateNoneOf(value, schema, info); } - static validateConditional(value, schema) { + static validateConditional(value, schema, info) { const ifSchema = schema.if; if (!JsonSchemaProxyHandler.isObject(ifSchema)) { return; } let okay = true; + info.schemaPush('if', ifSchema); try { JsonSchemaProxyHandler.validate(value, ifSchema, info); } catch (e) { okay = false; } + info.schemaPop(); const nextSchema = okay ? schema.then : schema.else; if (JsonSchemaProxyHandler.isObject(nextSchema)) { - JsonSchemaProxyHandler.validate(value, nextSchema); + info.schemaPush(okay ? 'then' : 'else', nextSchema); + JsonSchemaProxyHandler.validate(value, nextSchema, info); + info.schemaPop(); } } - static validateAllOf(value, schema) { + static validateAllOf(value, schema, info) { const subSchemas = schema.allOf; if (!Array.isArray(subSchemas)) { return; } + info.schemaPush('allOf', subSchemas); for (let i = 0; i < subSchemas.length; ++i) { - JsonSchemaProxyHandler.validate(value, subSchemas[i]); + const subSchema = subSchemas[i]; + info.schemaPush(i, subSchema); + JsonSchemaProxyHandler.validate(value, subSchema, info); + info.schemaPop(); } + info.schemaPop(); } - static validateAnyOf(value, schema) { + static validateAnyOf(value, schema, info) { const subSchemas = schema.anyOf; if (!Array.isArray(subSchemas)) { return; } + info.schemaPush('anyOf', subSchemas); for (let i = 0; i < subSchemas.length; ++i) { + const subSchema = subSchemas[i]; + info.schemaPush(i, subSchema); try { - JsonSchemaProxyHandler.validate(value, subSchemas[i]); + JsonSchemaProxyHandler.validate(value, subSchema, info); return; } catch (e) { // NOP } + info.schemaPop(); } - throw new JsonSchemaValidationError('0 anyOf schemas matched', value, schema); + throw new JsonSchemaValidationError('0 anyOf schemas matched', value, schema, info); + // info.schemaPop(); // Unreachable } - static validateOneOf(value, schema) { + static validateOneOf(value, schema, info) { const subSchemas = schema.oneOf; if (!Array.isArray(subSchemas)) { return; } + info.schemaPush('oneOf', subSchemas); let count = 0; for (let i = 0; i < subSchemas.length; ++i) { + const subSchema = subSchemas[i]; + info.schemaPush(i, subSchema); try { - JsonSchemaProxyHandler.validate(value, subSchemas[i]); + JsonSchemaProxyHandler.validate(value, subSchema, info); ++count; } catch (e) { // NOP } + info.schemaPop(); } if (count !== 1) { - throw new JsonSchemaValidationError(`${count} oneOf schemas matched`, value, schema); + throw new JsonSchemaValidationError(`${count} oneOf schemas matched`, value, schema, info); } + + info.schemaPop(); } - static validateNoneOf(value, schema) { + static validateNoneOf(value, schema, info) { const subSchemas = schema.not; if (!Array.isArray(subSchemas)) { return; } + info.schemaPush('not', subSchemas); for (let i = 0; i < subSchemas.length; ++i) { + const subSchema = subSchemas[i]; + info.schemaPush(i, subSchema); try { - JsonSchemaProxyHandler.validate(value, subSchemas[i]); + JsonSchemaProxyHandler.validate(value, subSchema, info); } catch (e) { + info.schemaPop(); continue; } - throw new JsonSchemaValidationError(`not[${i}] schema matched`, value, schema); + throw new JsonSchemaValidationError(`not[${i}] schema matched`, value, schema, info); } + info.schemaPop(); } - static validateSingleSchema(value, schema) { + static validateSingleSchema(value, schema, info) { const type = JsonSchemaProxyHandler.getValueType(value); const schemaType = schema.type; if (!JsonSchemaProxyHandler.isValueTypeAny(value, type, schemaType)) { - throw new JsonSchemaValidationError(`Value type ${type} does not match schema type ${schemaType}`, value, schema); + throw new JsonSchemaValidationError(`Value type ${type} does not match schema type ${schemaType}`, value, schema, info); } const schemaEnum = schema.enum; if (Array.isArray(schemaEnum) && !JsonSchemaProxyHandler.valuesAreEqualAny(value, schemaEnum)) { - throw new JsonSchemaValidationError('Invalid enum value', value, schema); + throw new JsonSchemaValidationError('Invalid enum value', value, schema, info); } switch (type) { case 'number': - JsonSchemaProxyHandler.validateNumber(value, schema); + JsonSchemaProxyHandler.validateNumber(value, schema, info); break; case 'string': - JsonSchemaProxyHandler.validateString(value, schema); + JsonSchemaProxyHandler.validateString(value, schema, info); break; case 'array': - JsonSchemaProxyHandler.validateArray(value, schema); + JsonSchemaProxyHandler.validateArray(value, schema, info); break; case 'object': - JsonSchemaProxyHandler.validateObject(value, schema); + JsonSchemaProxyHandler.validateObject(value, schema, info); break; } } - static validateNumber(value, schema) { + static validateNumber(value, schema, info) { const multipleOf = schema.multipleOf; if (typeof multipleOf === 'number' && Math.floor(value / multipleOf) * multipleOf !== value) { - throw new JsonSchemaValidationError(`Number is not a multiple of ${multipleOf}`, value, schema); + throw new JsonSchemaValidationError(`Number is not a multiple of ${multipleOf}`, value, schema, info); } const minimum = schema.minimum; if (typeof minimum === 'number' && value < minimum) { - throw new JsonSchemaValidationError(`Number is less than ${minimum}`, value, schema); + throw new JsonSchemaValidationError(`Number is less than ${minimum}`, value, schema, info); } const exclusiveMinimum = schema.exclusiveMinimum; if (typeof exclusiveMinimum === 'number' && value <= exclusiveMinimum) { - throw new JsonSchemaValidationError(`Number is less than or equal to ${exclusiveMinimum}`, value, schema); + throw new JsonSchemaValidationError(`Number is less than or equal to ${exclusiveMinimum}`, value, schema, info); } const maximum = schema.maximum; if (typeof maximum === 'number' && value > maximum) { - throw new JsonSchemaValidationError(`Number is greater than ${maximum}`, value, schema); + throw new JsonSchemaValidationError(`Number is greater than ${maximum}`, value, schema, info); } const exclusiveMaximum = schema.exclusiveMaximum; if (typeof exclusiveMaximum === 'number' && value >= exclusiveMaximum) { - throw new JsonSchemaValidationError(`Number is greater than or equal to ${exclusiveMaximum}`, value, schema); + throw new JsonSchemaValidationError(`Number is greater than or equal to ${exclusiveMaximum}`, value, schema, info); } } - static validateString(value, schema) { + static validateString(value, schema, info) { const minLength = schema.minLength; if (typeof minLength === 'number' && value.length < minLength) { - throw new JsonSchemaValidationError('String length too short', value, schema); + throw new JsonSchemaValidationError('String length too short', value, schema, info); } const maxLength = schema.maxLength; if (typeof maxLength === 'number' && value.length > maxLength) { - throw new JsonSchemaValidationError('String length too long', value, schema); + throw new JsonSchemaValidationError('String length too long', value, schema, info); } } - static validateArray(value, schema) { + static validateArray(value, schema, info) { const minItems = schema.minItems; if (typeof minItems === 'number' && value.length < minItems) { - throw new JsonSchemaValidationError('Array length too short', value, schema); + throw new JsonSchemaValidationError('Array length too short', value, schema, info); } const maxItems = schema.maxItems; if (typeof maxItems === 'number' && value.length > maxItems) { - throw new JsonSchemaValidationError('Array length too long', value, schema); + throw new JsonSchemaValidationError('Array length too long', value, schema, info); } for (let i = 0, ii = value.length; i < ii; ++i) { const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, i, value); if (propertySchema === null) { - throw new JsonSchemaValidationError(`No schema found for array[${i}]`, value, schema); + throw new JsonSchemaValidationError(`No schema found for array[${i}]`, value, schema, info); } - JsonSchemaProxyHandler.validate(value[i], propertySchema); + const propertyValue = value[i]; + + info.valuePush(i, propertyValue); + info.schemaPush(i, propertySchema); + JsonSchemaProxyHandler.validate(propertyValue, propertySchema, info); + info.schemaPop(); + info.valuePop(); } } - static validateObject(value, schema) { + static validateObject(value, schema, info) { const properties = new Set(Object.getOwnPropertyNames(value)); const required = schema.required; if (Array.isArray(required)) { for (const property of required) { if (!properties.has(property)) { - throw new JsonSchemaValidationError(`Missing property ${property}`, value, schema); + throw new JsonSchemaValidationError(`Missing property ${property}`, value, schema, info); } } } const minProperties = schema.minProperties; if (typeof minProperties === 'number' && properties.length < minProperties) { - throw new JsonSchemaValidationError('Not enough object properties', value, schema); + throw new JsonSchemaValidationError('Not enough object properties', value, schema, info); } const maxProperties = schema.maxProperties; if (typeof maxProperties === 'number' && properties.length > maxProperties) { - throw new JsonSchemaValidationError('Too many object properties', value, schema); + throw new JsonSchemaValidationError('Too many object properties', value, schema, info); } for (const property of properties) { const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, property, value); if (propertySchema === null) { - throw new JsonSchemaValidationError(`No schema found for ${property}`, value, schema); + throw new JsonSchemaValidationError(`No schema found for ${property}`, value, schema, info); } - JsonSchemaProxyHandler.validate(value[property], propertySchema); + + const propertyValue = value[property]; + + info.valuePush(property, propertyValue); + info.schemaPush(property, propertySchema); + JsonSchemaProxyHandler.validate(propertyValue, propertySchema, info); + info.schemaPop(); + info.valuePop(); } } @@ -530,12 +568,37 @@ class JsonSchemaProxyHandler { JsonSchemaProxyHandler._unconstrainedSchema = {}; +class JsonSchemaTraversalInfo { + constructor(value, schema) { + this.valuePath = []; + this.schemaPath = []; + this.valuePush([null, value]); + this.schemaPush([null, schema]); + } + + valuePush(path, value) { + this.valuePath.push([path, value]); + } + + valuePop() { + this.valuePath.pop(); + } + + schemaPush(path, schema) { + this.schemaPath.push([path, schema]); + } + + schemaPop() { + this.schemaPath.pop(); + } +} + class JsonSchemaValidationError extends Error { - constructor(message, value, schema, path) { + constructor(message, value, schema, info) { super(message); this.value = value; this.schema = schema; - this.path = path; + this.info = info; } } @@ -545,7 +608,7 @@ class JsonSchema { } static validate(value, schema) { - return JsonSchemaProxyHandler.validate(value, schema); + return JsonSchemaProxyHandler.validate(value, schema, new JsonSchemaTraversalInfo(value, schema)); } static getValidValueOrDefault(schema, value) { -- cgit v1.2.3 From ea808024d70e95b94dca7f846e7910573fed8466 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 2 Feb 2020 11:04:38 -0500 Subject: Fix missing else --- ext/bg/js/json-schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 49f1e082..0ca1183e 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -134,7 +134,7 @@ class JsonSchemaProxyHandler { const additionalProperties = schema.additionalProperties; if (additionalProperties === false) { return null; - } if (JsonSchemaProxyHandler.isObject(additionalProperties)) { + } else if (JsonSchemaProxyHandler.isObject(additionalProperties)) { return additionalProperties; } else { return JsonSchemaProxyHandler._unconstrainedSchema; -- cgit v1.2.3 From b1fc9c024ae84ed603d7f7bec54fadc73a39f1b8 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 2 Feb 2020 11:13:26 -0500 Subject: Update how property schemas are returned --- ext/bg/js/json-schema.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 0ca1183e..8db5411c 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -126,8 +126,9 @@ class JsonSchemaProxyHandler { { const properties = schema.properties; if (JsonSchemaProxyHandler.isObject(properties)) { - if (Object.prototype.hasOwnProperty.call(properties, property)) { - return properties[property]; + const propertySchema = properties[property]; + if (JsonSchemaProxyHandler.isObject(propertySchema)) { + return propertySchema; } } @@ -148,7 +149,10 @@ class JsonSchemaProxyHandler { } if (Array.isArray(items)) { if (property >= 0 && property < items.length) { - return items[property]; + const propertySchema = items[property]; + if (JsonSchemaProxyHandler.isObject(propertySchema)) { + return propertySchema; + } } } -- cgit v1.2.3 From fff1e67a5e52cb104c77069903f975e114d7a835 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 2 Feb 2020 11:18:13 -0500 Subject: Improve schema path when using getPropertySchema --- ext/bg/js/json-schema.js | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 8db5411c..ad6372df 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -119,7 +119,7 @@ class JsonSchemaProxyHandler { throw new Error('construct not supported'); } - static getPropertySchema(schema, property, value) { + static getPropertySchema(schema, property, value, path=null) { const type = JsonSchemaProxyHandler.getSchemaOrValueType(schema, value); switch (type) { case 'object': @@ -128,6 +128,7 @@ class JsonSchemaProxyHandler { if (JsonSchemaProxyHandler.isObject(properties)) { const propertySchema = properties[property]; if (JsonSchemaProxyHandler.isObject(propertySchema)) { + if (path !== null) { path.push(['properties', properties], [property, propertySchema]); } return propertySchema; } } @@ -136,9 +137,12 @@ class JsonSchemaProxyHandler { if (additionalProperties === false) { return null; } else if (JsonSchemaProxyHandler.isObject(additionalProperties)) { + if (path !== null) { path.push(['additionalProperties', additionalProperties]); } return additionalProperties; } else { - return JsonSchemaProxyHandler._unconstrainedSchema; + const result = JsonSchemaProxyHandler._unconstrainedSchema; + if (path !== null) { path.push([null, result]); } + return result; } } case 'array': @@ -151,6 +155,7 @@ class JsonSchemaProxyHandler { if (property >= 0 && property < items.length) { const propertySchema = items[property]; if (JsonSchemaProxyHandler.isObject(propertySchema)) { + if (path !== null) { path.push(['items', items], [property, propertySchema]); } return propertySchema; } } @@ -160,9 +165,12 @@ class JsonSchemaProxyHandler { if (additionalItems === false) { return null; } else if (JsonSchemaProxyHandler.isObject(additionalItems)) { + if (path !== null) { path.push(['additionalItems', additionalItems]); } return additionalItems; } else { - return JsonSchemaProxyHandler._unconstrainedSchema; + const result = JsonSchemaProxyHandler._unconstrainedSchema; + if (path !== null) { path.push([null, result]); } + return result; } } default: @@ -381,18 +389,19 @@ class JsonSchemaProxyHandler { } for (let i = 0, ii = value.length; i < ii; ++i) { - const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, i, value); + const schemaPath = []; + const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, i, value, schemaPath); if (propertySchema === null) { throw new JsonSchemaValidationError(`No schema found for array[${i}]`, value, schema, info); } const propertyValue = value[i]; + for (const [p, s] of schemaPath) { info.schemaPush(p, s); } info.valuePush(i, propertyValue); - info.schemaPush(i, propertySchema); JsonSchemaProxyHandler.validate(propertyValue, propertySchema, info); - info.schemaPop(); info.valuePop(); + for (let i = 0; i < schemaPath.length; ++i) { info.schemaPop(); } } } @@ -419,18 +428,19 @@ class JsonSchemaProxyHandler { } for (const property of properties) { - const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, property, value); + const schemaPath = []; + const propertySchema = JsonSchemaProxyHandler.getPropertySchema(schema, property, value, schemaPath); if (propertySchema === null) { throw new JsonSchemaValidationError(`No schema found for ${property}`, value, schema, info); } const propertyValue = value[property]; + for (const [p, s] of schemaPath) { info.schemaPush(p, s); } info.valuePush(property, propertyValue); - info.schemaPush(property, propertySchema); JsonSchemaProxyHandler.validate(propertyValue, propertySchema, info); - info.schemaPop(); info.valuePop(); + for (let i = 0; i < schemaPath.length; ++i) { info.schemaPop(); } } } -- cgit v1.2.3 From 3c28c7dd7cdbf4af91b0b4044f03e0877569e3b8 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 2 Feb 2020 11:22:22 -0500 Subject: Fix init --- ext/bg/js/json-schema.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index ad6372df..3cf24c35 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -586,8 +586,8 @@ class JsonSchemaTraversalInfo { constructor(value, schema) { this.valuePath = []; this.schemaPath = []; - this.valuePush([null, value]); - this.schemaPush([null, schema]); + this.valuePush(null, value); + this.schemaPush(null, schema); } valuePush(path, value) { -- cgit v1.2.3 From e8701cb950edd4c90f6c911e340db3af32194113 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 2 Feb 2020 14:53:32 -0500 Subject: Hide anki-invalid-response-error when there is no error --- ext/bg/js/settings/anki.js | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/anki.js b/ext/bg/js/settings/anki.js index 3b0912d4..f55989fc 100644 --- a/ext/bg/js/settings/anki.js +++ b/ext/bg/js/settings/anki.js @@ -33,20 +33,27 @@ function _ankiSpinnerShow(show) { function _ankiSetError(error) { const node = document.querySelector('#anki-error'); - if (!node) { return; } + const node2 = document.querySelector('#anki-invalid-response-error'); if (error) { const errorString = `${error}`; - node.hidden = false; - node.textContent = errorString; - _ankiSetErrorData(node, error); + if (node !== null) { + node.hidden = false; + node.textContent = errorString; + _ankiSetErrorData(node, error); + } - const node2 = document.querySelector('#anki-invalid-response-error'); if (node2 !== null) { node2.hidden = (errorString.indexOf('Invalid response') < 0); } } else { - node.hidden = true; - node.textContent = ''; + if (node !== null) { + node.hidden = true; + node.textContent = ''; + } + + if (node2 !== null) { + node2.hidden = true; + } } } -- cgit v1.2.3 From 4b17e79cb82c7c4348ada090f95ea484effe36c2 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 26 Jan 2020 15:06:42 -0500 Subject: Add schemas for dictionary data --- ext/bg/data/dictionary-index-schema.json | 40 ++++++++++++++++++ ext/bg/data/dictionary-kanji-bank-v1-schema.json | 33 +++++++++++++++ ext/bg/data/dictionary-kanji-bank-v3-schema.json | 44 ++++++++++++++++++++ .../data/dictionary-kanji-meta-bank-v3-schema.json | 25 +++++++++++ ext/bg/data/dictionary-tag-bank-v3-schema.json | 32 +++++++++++++++ ext/bg/data/dictionary-term-bank-v1-schema.json | 36 ++++++++++++++++ ext/bg/data/dictionary-term-bank-v3-schema.json | 48 ++++++++++++++++++++++ .../data/dictionary-term-meta-bank-v3-schema.json | 25 +++++++++++ 8 files changed, 283 insertions(+) create mode 100644 ext/bg/data/dictionary-index-schema.json create mode 100644 ext/bg/data/dictionary-kanji-bank-v1-schema.json create mode 100644 ext/bg/data/dictionary-kanji-bank-v3-schema.json create mode 100644 ext/bg/data/dictionary-kanji-meta-bank-v3-schema.json create mode 100644 ext/bg/data/dictionary-tag-bank-v3-schema.json create mode 100644 ext/bg/data/dictionary-term-bank-v1-schema.json create mode 100644 ext/bg/data/dictionary-term-bank-v3-schema.json create mode 100644 ext/bg/data/dictionary-term-meta-bank-v3-schema.json (limited to 'ext/bg') diff --git a/ext/bg/data/dictionary-index-schema.json b/ext/bg/data/dictionary-index-schema.json new file mode 100644 index 00000000..9865fcc1 --- /dev/null +++ b/ext/bg/data/dictionary-index-schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "description": "Index file containing information about the data contained in the dictionary.", + "required": [ + "title", + "revision" + ], + "properties": { + "title": { + "type": "string", + "description": "Title of the dictionary." + }, + "revision": { + "type": "string", + "description": "Revision of the dictionary. This value is only used for displaying information." + }, + "sequenced": { + "type": "boolean", + "default": false, + "description": "Whether or not this dictionary can be used as the primary dictionary. Primary dictionaries typically contain term/expression definitions." + }, + "format": { + "type": "integer", + "description": "Format of data found in the JSON data files." + }, + "version": { + "type": "integer", + "description": "Alias for format." + } + }, + "anyOf": [ + { + "required": ["format"] + }, + { + "required": ["version"] + } + ] +} \ No newline at end of file diff --git a/ext/bg/data/dictionary-kanji-bank-v1-schema.json b/ext/bg/data/dictionary-kanji-bank-v1-schema.json new file mode 100644 index 00000000..6dad5a7a --- /dev/null +++ b/ext/bg/data/dictionary-kanji-bank-v1-schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "description": "Data file containing kanji information.", + "additionalItems": { + "type": "array", + "description": "Information about a single kanji character.", + "minItems": 4, + "items": [ + { + "type": "string", + "description": "Kanji character.", + "minLength": 1 + }, + { + "type": "string", + "description": "String of space-separated onyomi readings for the kanji character. An empty string is treated as no readings." + }, + { + "type": "string", + "description": "String of space-separated kunyomi readings for the kanji character. An empty string is treated as no readings." + }, + { + "type": "string", + "description": "String of space-separated tags for the kanji character. An empty string is treated as no tags." + } + ], + "additionalItems": { + "type": "string", + "description": "A meaning for the kanji character." + } + } +} \ No newline at end of file diff --git a/ext/bg/data/dictionary-kanji-bank-v3-schema.json b/ext/bg/data/dictionary-kanji-bank-v3-schema.json new file mode 100644 index 00000000..a5b82039 --- /dev/null +++ b/ext/bg/data/dictionary-kanji-bank-v3-schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "description": "Data file containing kanji information.", + "additionalItems": { + "type": "array", + "description": "Information about a single kanji character.", + "minItems": 6, + "items": [ + { + "type": "string", + "description": "Kanji character.", + "minLength": 1 + }, + { + "type": "string", + "description": "String of space-separated onyomi readings for the kanji character. An empty string is treated as no readings." + }, + { + "type": "string", + "description": "String of space-separated kunyomi readings for the kanji character. An empty string is treated as no readings." + }, + { + "type": "string", + "description": "String of space-separated tags for the kanji character. An empty string is treated as no tags." + }, + { + "type": "array", + "description": "Array of meanings for the kanji character.", + "items": { + "type": "string", + "description": "A meaning for the kanji character." + } + }, + { + "type": "object", + "description": "Various stats for the kanji character.", + "additionalProperties": { + "type": "string" + } + } + ] + } +} \ No newline at end of file diff --git a/ext/bg/data/dictionary-kanji-meta-bank-v3-schema.json b/ext/bg/data/dictionary-kanji-meta-bank-v3-schema.json new file mode 100644 index 00000000..62479026 --- /dev/null +++ b/ext/bg/data/dictionary-kanji-meta-bank-v3-schema.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "description": "Custom metadata for kanji characters.", + "additionalItems": { + "type": "array", + "description": "Metadata about a single kanji character.", + "minItems": 3, + "items": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "string", + "enum": ["freq"], + "description": "Type of data. \"freq\" corresponds to frequency information." + }, + { + "type": ["string", "number"], + "description": "Data for the character." + } + ] + } +} \ No newline at end of file diff --git a/ext/bg/data/dictionary-tag-bank-v3-schema.json b/ext/bg/data/dictionary-tag-bank-v3-schema.json new file mode 100644 index 00000000..ee5ca64d --- /dev/null +++ b/ext/bg/data/dictionary-tag-bank-v3-schema.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "description": "Data file containing tag information for terms and kanji.", + "additionalItems": { + "type": "array", + "description": "Information about a single tag.", + "minItems": 5, + "items": [ + { + "type": "string", + "description": "Tag name." + }, + { + "type": "string", + "description": "Category for the tag." + }, + { + "type": "number", + "description": "Sorting order for the tag." + }, + { + "type": "string", + "description": "Notes for the tag." + }, + { + "type": "number", + "description": "Score used to determine popularity. Negative values are more rare and positive values are more frequent. This score is also used to sort search results." + } + ] + } +} \ No newline at end of file diff --git a/ext/bg/data/dictionary-term-bank-v1-schema.json b/ext/bg/data/dictionary-term-bank-v1-schema.json new file mode 100644 index 00000000..6ffb26e6 --- /dev/null +++ b/ext/bg/data/dictionary-term-bank-v1-schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "description": "Data file containing term and expression information.", + "additionalItems": { + "type": "array", + "description": "Information about a single term/expression.", + "minItems": 5, + "items": [ + { + "type": "string", + "description": "Term or expression." + }, + { + "type": "string", + "description": "Reading of the term/expression, or an empty string if the reading is the same as the term/expression." + }, + { + "type": ["string", "null"], + "description": "String of space-separated tags for the definition. An empty string is treated as no tags." + }, + { + "type": "string", + "description": "String of space-separated rule identifiers for the definition which is used to validate delinflection. Valid rule identifiers are: v1: ichidan verb; v5: godan verb; vs: suru verb; vk: kuru verb; adj-i: i-adjective. An empty string corresponds to words which aren't inflected, such as nouns." + }, + { + "type": "number", + "description": "Score used to determine popularity. Negative values are more rare and positive values are more frequent. This score is also used to sort search results." + } + ], + "additionalItems": { + "type": "string", + "description": "Single definition for the term/expression." + } + } +} \ No newline at end of file diff --git a/ext/bg/data/dictionary-term-bank-v3-schema.json b/ext/bg/data/dictionary-term-bank-v3-schema.json new file mode 100644 index 00000000..bb982e36 --- /dev/null +++ b/ext/bg/data/dictionary-term-bank-v3-schema.json @@ -0,0 +1,48 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "description": "Data file containing term and expression information.", + "additionalItems": { + "type": "array", + "description": "Information about a single term/expression.", + "minItems": 8, + "items": [ + { + "type": "string", + "description": "Term or expression." + }, + { + "type": "string", + "description": "Reading of the term/expression, or an empty string if the reading is the same as the term/expression." + }, + { + "type": ["string", "null"], + "description": "String of space-separated tags for the definition. An empty string is treated as no tags." + }, + { + "type": "string", + "description": "String of space-separated rule identifiers for the definition which is used to validate delinflection. Valid rule identifiers are: v1: ichidan verb; v5: godan verb; vs: suru verb; vk: kuru verb; adj-i: i-adjective. An empty string corresponds to words which aren't inflected, such as nouns." + }, + { + "type": "number", + "description": "Score used to determine popularity. Negative values are more rare and positive values are more frequent. This score is also used to sort search results." + }, + { + "type": "array", + "description": "Array of definitions for the term/expression.", + "items": { + "type": "string", + "description": "Single definition for the term/expression." + } + }, + { + "type": "integer", + "description": "Sequence number for the term/expression. Terms/expressions with the same sequence number can be shown together when the \"resultOutputMode\" option is set to \"merge\"." + }, + { + "type": "string", + "description": "String of space-separated tags for the term/expression. An empty string is treated as no tags." + } + ] + } +} \ No newline at end of file diff --git a/ext/bg/data/dictionary-term-meta-bank-v3-schema.json b/ext/bg/data/dictionary-term-meta-bank-v3-schema.json new file mode 100644 index 00000000..1cc0557f --- /dev/null +++ b/ext/bg/data/dictionary-term-meta-bank-v3-schema.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "array", + "description": "Custom metadata for terms/expressions.", + "additionalItems": { + "type": "array", + "description": "Metadata about a single term/expression.", + "minItems": 3, + "items": [ + { + "type": "string", + "description": "Term or expression." + }, + { + "type": "string", + "enum": ["freq"], + "description": "Type of data. \"freq\" corresponds to frequency information." + }, + { + "type": ["string", "number"], + "description": "Data for the term/expression." + } + ] + } +} \ No newline at end of file -- cgit v1.2.3 From 165959ef068905485a044a06bb281109d88d5679 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 8 Feb 2020 20:45:30 -0500 Subject: Move japanese.js into bg --- ext/bg/background.html | 2 +- ext/bg/js/japanese.js | 454 +++++++++++++++++++++++++++++++++++++++++++++++ ext/bg/search.html | 2 +- ext/bg/settings.html | 2 +- ext/mixed/js/japanese.js | 454 ----------------------------------------------- 5 files changed, 457 insertions(+), 457 deletions(-) create mode 100644 ext/bg/js/japanese.js delete mode 100644 ext/mixed/js/japanese.js (limited to 'ext/bg') diff --git a/ext/bg/background.html b/ext/bg/background.html index af87eddb..e6d32593 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -31,6 +31,7 @@ + @@ -39,7 +40,6 @@ - diff --git a/ext/bg/js/japanese.js b/ext/bg/js/japanese.js new file mode 100644 index 00000000..0da822d7 --- /dev/null +++ b/ext/bg/js/japanese.js @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2016-2020 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +const JP_HALFWIDTH_KATAKANA_MAPPING = new Map([ + ['ヲ', 'ヲヺ-'], + ['ァ', 'ァ--'], + ['ィ', 'ィ--'], + ['ゥ', 'ゥ--'], + ['ェ', 'ェ--'], + ['ォ', 'ォ--'], + ['ャ', 'ャ--'], + ['ュ', 'ュ--'], + ['ョ', 'ョ--'], + ['ッ', 'ッ--'], + ['ー', 'ー--'], + ['ア', 'ア--'], + ['イ', 'イ--'], + ['ウ', 'ウヴ-'], + ['エ', 'エ--'], + ['オ', 'オ--'], + ['カ', 'カガ-'], + ['キ', 'キギ-'], + ['ク', 'クグ-'], + ['ケ', 'ケゲ-'], + ['コ', 'コゴ-'], + ['サ', 'サザ-'], + ['シ', 'シジ-'], + ['ス', 'スズ-'], + ['セ', 'セゼ-'], + ['ソ', 'ソゾ-'], + ['タ', 'タダ-'], + ['チ', 'チヂ-'], + ['ツ', 'ツヅ-'], + ['テ', 'テデ-'], + ['ト', 'トド-'], + ['ナ', 'ナ--'], + ['ニ', 'ニ--'], + ['ヌ', 'ヌ--'], + ['ネ', 'ネ--'], + ['ノ', 'ノ--'], + ['ハ', 'ハバパ'], + ['ヒ', 'ヒビピ'], + ['フ', 'フブプ'], + ['ヘ', 'ヘベペ'], + ['ホ', 'ホボポ'], + ['マ', 'マ--'], + ['ミ', 'ミ--'], + ['ム', 'ム--'], + ['メ', 'メ--'], + ['モ', 'モ--'], + ['ヤ', 'ヤ--'], + ['ユ', 'ユ--'], + ['ヨ', 'ヨ--'], + ['ラ', 'ラ--'], + ['リ', 'リ--'], + ['ル', 'ル--'], + ['レ', 'レ--'], + ['ロ', 'ロ--'], + ['ワ', 'ワ--'], + ['ン', 'ン--'] +]); + +const JP_HIRAGANA_RANGE = [0x3040, 0x309f]; +const JP_KATAKANA_RANGE = [0x30a0, 0x30ff]; +const JP_KANA_RANGES = [JP_HIRAGANA_RANGE, JP_KATAKANA_RANGE]; + +const JP_CJK_COMMON_RANGE = [0x4e00, 0x9fff]; +const JP_CJK_RARE_RANGE = [0x3400, 0x4dbf]; +const JP_CJK_RANGES = [JP_CJK_COMMON_RANGE, JP_CJK_RARE_RANGE]; + +const JP_ITERATION_MARK_CHAR_CODE = 0x3005; + +// Japanese character ranges, roughly ordered in order of expected frequency +const JP_JAPANESE_RANGES = [ + JP_HIRAGANA_RANGE, + JP_KATAKANA_RANGE, + + JP_CJK_COMMON_RANGE, + JP_CJK_RARE_RANGE, + + [0xff66, 0xff9f], // Halfwidth katakana + + [0x30fb, 0x30fc], // Katakana punctuation + [0xff61, 0xff65], // Kana punctuation + [0x3000, 0x303f], // CJK punctuation + + [0xff10, 0xff19], // Fullwidth numbers + [0xff21, 0xff3a], // Fullwidth upper case Latin letters + [0xff41, 0xff5a], // Fullwidth lower case Latin letters + + [0xff01, 0xff0f], // Fullwidth punctuation 1 + [0xff1a, 0xff1f], // Fullwidth punctuation 2 + [0xff3b, 0xff3f], // Fullwidth punctuation 3 + [0xff5b, 0xff60], // Fullwidth punctuation 4 + [0xffe0, 0xffee], // Currency markers +]; + + +// Helper functions + +function _jpIsCharCodeInRanges(charCode, ranges) { + for (const [min, max] of ranges) { + if (charCode >= min && charCode <= max) { + return true; + } + } + return false; +} + + +// Character code testing functions + +function jpIsCharCodeKanji(charCode) { + return _jpIsCharCodeInRanges(charCode, JP_CJK_RANGES); +} + +function jpIsCharCodeKana(charCode) { + return _jpIsCharCodeInRanges(charCode, JP_KANA_RANGES); +} + +function jpIsCharCodeJapanese(charCode) { + return _jpIsCharCodeInRanges(charCode, JP_JAPANESE_RANGES); +} + + +// String testing functions + +function jpIsStringEntirelyKana(str) { + if (str.length === 0) { return false; } + for (let i = 0, ii = str.length; i < ii; ++i) { + if (!jpIsCharCodeKana(str.charCodeAt(i))) { + return false; + } + } + return true; +} + +function jpIsStringPartiallyJapanese(str) { + if (str.length === 0) { return false; } + for (let i = 0, ii = str.length; i < ii; ++i) { + if (jpIsCharCodeJapanese(str.charCodeAt(i))) { + return true; + } + } + return false; +} + + +// Conversion functions + +function jpKatakanaToHiragana(text) { + let result = ''; + for (const c of text) { + if (wanakana.isKatakana(c)) { + result += wanakana.toHiragana(c); + } else { + result += c; + } + } + + return result; +} + +function jpHiraganaToKatakana(text) { + let result = ''; + for (const c of text) { + if (wanakana.isHiragana(c)) { + result += wanakana.toKatakana(c); + } else { + result += c; + } + } + + return result; +} + +function jpToRomaji(text) { + return wanakana.toRomaji(text); +} + +function jpConvertReading(expressionFragment, readingFragment, readingMode) { + switch (readingMode) { + case 'hiragana': + return jpKatakanaToHiragana(readingFragment || ''); + case 'katakana': + return jpHiraganaToKatakana(readingFragment || ''); + case 'romaji': + if (readingFragment) { + return jpToRomaji(readingFragment); + } else { + if (jpIsStringEntirelyKana(expressionFragment)) { + return jpToRomaji(expressionFragment); + } + } + return readingFragment; + case 'none': + return null; + default: + return readingFragment; + } +} + +function jpDistributeFurigana(expression, reading) { + const fallback = [{furigana: reading, text: expression}]; + if (!reading) { + return fallback; + } + + let isAmbiguous = false; + const segmentize = (reading, groups) => { + if (groups.length === 0 || isAmbiguous) { + return []; + } + + const group = groups[0]; + if (group.mode === 'kana') { + if (jpKatakanaToHiragana(reading).startsWith(jpKatakanaToHiragana(group.text))) { + const readingLeft = reading.substring(group.text.length); + const segs = segmentize(readingLeft, groups.splice(1)); + if (segs) { + return [{text: group.text}].concat(segs); + } + } + } else { + let foundSegments = null; + for (let i = reading.length; i >= group.text.length; --i) { + const readingUsed = reading.substring(0, i); + const readingLeft = reading.substring(i); + const segs = segmentize(readingLeft, groups.slice(1)); + if (segs) { + if (foundSegments !== null) { + // more than one way to segmentize the tail, mark as ambiguous + isAmbiguous = true; + return null; + } + foundSegments = [{text: group.text, furigana: readingUsed}].concat(segs); + } + // there is only one way to segmentize the last non-kana group + if (groups.length === 1) { + break; + } + } + return foundSegments; + } + }; + + const groups = []; + let modePrev = null; + for (const c of expression) { + const charCode = c.charCodeAt(0); + const modeCurr = jpIsCharCodeKanji(charCode) || charCode === JP_ITERATION_MARK_CHAR_CODE ? 'kanji' : 'kana'; + if (modeCurr === modePrev) { + groups[groups.length - 1].text += c; + } else { + groups.push({mode: modeCurr, text: c}); + modePrev = modeCurr; + } + } + + const segments = segmentize(reading, groups); + if (segments && !isAmbiguous) { + return segments; + } + return fallback; +} + +function jpDistributeFuriganaInflected(expression, reading, source) { + const output = []; + + let stemLength = 0; + const shortest = Math.min(source.length, expression.length); + const sourceHiragana = jpKatakanaToHiragana(source); + const expressionHiragana = jpKatakanaToHiragana(expression); + while (stemLength < shortest && sourceHiragana[stemLength] === expressionHiragana[stemLength]) { + ++stemLength; + } + const offset = source.length - stemLength; + + const stemExpression = source.substring(0, source.length - offset); + const stemReading = reading.substring( + 0, + offset === 0 ? reading.length : reading.length - expression.length + stemLength + ); + for (const segment of jpDistributeFurigana(stemExpression, stemReading)) { + output.push(segment); + } + + if (stemLength !== source.length) { + output.push({text: source.substring(stemLength)}); + } + + return output; +} + +function jpConvertHalfWidthKanaToFullWidth(text, sourceMapping) { + let result = ''; + const ii = text.length; + const hasSourceMapping = Array.isArray(sourceMapping); + + for (let i = 0; i < ii; ++i) { + const c = text[i]; + const mapping = JP_HALFWIDTH_KATAKANA_MAPPING.get(c); + if (typeof mapping !== 'string') { + result += c; + continue; + } + + let index = 0; + switch (text.charCodeAt(i + 1)) { + case 0xff9e: // dakuten + index = 1; + break; + case 0xff9f: // handakuten + index = 2; + break; + } + + let c2 = mapping[index]; + if (index > 0) { + if (c2 === '-') { // invalid + index = 0; + c2 = mapping[0]; + } else { + ++i; + } + } + + if (hasSourceMapping && index > 0) { + index = result.length; + const v = sourceMapping.splice(index + 1, 1)[0]; + sourceMapping[index] += v; + } + result += c2; + } + + return result; +} + +function jpConvertNumericTofullWidth(text) { + let result = ''; + for (let i = 0, ii = text.length; i < ii; ++i) { + let c = text.charCodeAt(i); + if (c >= 0x30 && c <= 0x39) { // ['0', '9'] + c += 0xff10 - 0x30; // 0xff10 = '0' full width + result += String.fromCharCode(c); + } else { + result += text[i]; + } + } + return result; +} + +function jpConvertAlphabeticToKana(text, sourceMapping) { + let part = ''; + let result = ''; + const ii = text.length; + + if (sourceMapping.length === ii) { + sourceMapping.length = ii; + sourceMapping.fill(1); + } + + for (let i = 0; i < ii; ++i) { + // Note: 0x61 is the character code for 'a' + let c = text.charCodeAt(i); + if (c >= 0x41 && c <= 0x5a) { // ['A', 'Z'] + c += (0x61 - 0x41); + } else if (c >= 0x61 && c <= 0x7a) { // ['a', 'z'] + // NOP; c += (0x61 - 0x61); + } else if (c >= 0xff21 && c <= 0xff3a) { // ['A', 'Z'] fullwidth + c += (0x61 - 0xff21); + } else if (c >= 0xff41 && c <= 0xff5a) { // ['a', 'z'] fullwidth + c += (0x61 - 0xff41); + } else if (c === 0x2d || c === 0xff0d) { // '-' or fullwidth dash + c = 0x2d; // '-' + } else { + if (part.length > 0) { + result += jpToHiragana(part, sourceMapping, result.length); + part = ''; + } + result += text[i]; + continue; + } + part += String.fromCharCode(c); + } + + if (part.length > 0) { + result += jpToHiragana(part, sourceMapping, result.length); + } + return result; +} + +function jpToHiragana(text, sourceMapping, sourceMappingStart) { + const result = wanakana.toHiragana(text); + + // Generate source mapping + if (Array.isArray(sourceMapping)) { + if (typeof sourceMappingStart !== 'number') { sourceMappingStart = 0; } + let i = 0; + let resultPos = 0; + const ii = text.length; + while (i < ii) { + // Find smallest matching substring + let iNext = i + 1; + let resultPosNext = result.length; + while (iNext < ii) { + const t = wanakana.toHiragana(text.substring(0, iNext)); + if (t === result.substring(0, t.length)) { + resultPosNext = t.length; + break; + } + ++iNext; + } + + // Merge characters + const removals = iNext - i - 1; + if (removals > 0) { + let sum = 0; + const vs = sourceMapping.splice(sourceMappingStart + 1, removals); + for (const v of vs) { sum += v; } + sourceMapping[sourceMappingStart] += sum; + } + ++sourceMappingStart; + + // Empty elements + const additions = resultPosNext - resultPos - 1; + for (let j = 0; j < additions; ++j) { + sourceMapping.splice(sourceMappingStart, 0, 0); + ++sourceMappingStart; + } + + i = iNext; + resultPos = resultPosNext; + } + } + + return result; +} diff --git a/ext/bg/search.html b/ext/bg/search.html index 74afbb68..bb7ac095 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -75,6 +75,7 @@ + @@ -82,7 +83,6 @@ - diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 77bcc359..3480b124 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -1084,12 +1084,12 @@ - + diff --git a/ext/mixed/js/japanese.js b/ext/mixed/js/japanese.js deleted file mode 100644 index 0da822d7..00000000 --- a/ext/mixed/js/japanese.js +++ /dev/null @@ -1,454 +0,0 @@ -/* - * Copyright (C) 2016-2020 Alex Yatskov - * Author: Alex Yatskov - * - * 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 . - */ - - -const JP_HALFWIDTH_KATAKANA_MAPPING = new Map([ - ['ヲ', 'ヲヺ-'], - ['ァ', 'ァ--'], - ['ィ', 'ィ--'], - ['ゥ', 'ゥ--'], - ['ェ', 'ェ--'], - ['ォ', 'ォ--'], - ['ャ', 'ャ--'], - ['ュ', 'ュ--'], - ['ョ', 'ョ--'], - ['ッ', 'ッ--'], - ['ー', 'ー--'], - ['ア', 'ア--'], - ['イ', 'イ--'], - ['ウ', 'ウヴ-'], - ['エ', 'エ--'], - ['オ', 'オ--'], - ['カ', 'カガ-'], - ['キ', 'キギ-'], - ['ク', 'クグ-'], - ['ケ', 'ケゲ-'], - ['コ', 'コゴ-'], - ['サ', 'サザ-'], - ['シ', 'シジ-'], - ['ス', 'スズ-'], - ['セ', 'セゼ-'], - ['ソ', 'ソゾ-'], - ['タ', 'タダ-'], - ['チ', 'チヂ-'], - ['ツ', 'ツヅ-'], - ['テ', 'テデ-'], - ['ト', 'トド-'], - ['ナ', 'ナ--'], - ['ニ', 'ニ--'], - ['ヌ', 'ヌ--'], - ['ネ', 'ネ--'], - ['ノ', 'ノ--'], - ['ハ', 'ハバパ'], - ['ヒ', 'ヒビピ'], - ['フ', 'フブプ'], - ['ヘ', 'ヘベペ'], - ['ホ', 'ホボポ'], - ['マ', 'マ--'], - ['ミ', 'ミ--'], - ['ム', 'ム--'], - ['メ', 'メ--'], - ['モ', 'モ--'], - ['ヤ', 'ヤ--'], - ['ユ', 'ユ--'], - ['ヨ', 'ヨ--'], - ['ラ', 'ラ--'], - ['リ', 'リ--'], - ['ル', 'ル--'], - ['レ', 'レ--'], - ['ロ', 'ロ--'], - ['ワ', 'ワ--'], - ['ン', 'ン--'] -]); - -const JP_HIRAGANA_RANGE = [0x3040, 0x309f]; -const JP_KATAKANA_RANGE = [0x30a0, 0x30ff]; -const JP_KANA_RANGES = [JP_HIRAGANA_RANGE, JP_KATAKANA_RANGE]; - -const JP_CJK_COMMON_RANGE = [0x4e00, 0x9fff]; -const JP_CJK_RARE_RANGE = [0x3400, 0x4dbf]; -const JP_CJK_RANGES = [JP_CJK_COMMON_RANGE, JP_CJK_RARE_RANGE]; - -const JP_ITERATION_MARK_CHAR_CODE = 0x3005; - -// Japanese character ranges, roughly ordered in order of expected frequency -const JP_JAPANESE_RANGES = [ - JP_HIRAGANA_RANGE, - JP_KATAKANA_RANGE, - - JP_CJK_COMMON_RANGE, - JP_CJK_RARE_RANGE, - - [0xff66, 0xff9f], // Halfwidth katakana - - [0x30fb, 0x30fc], // Katakana punctuation - [0xff61, 0xff65], // Kana punctuation - [0x3000, 0x303f], // CJK punctuation - - [0xff10, 0xff19], // Fullwidth numbers - [0xff21, 0xff3a], // Fullwidth upper case Latin letters - [0xff41, 0xff5a], // Fullwidth lower case Latin letters - - [0xff01, 0xff0f], // Fullwidth punctuation 1 - [0xff1a, 0xff1f], // Fullwidth punctuation 2 - [0xff3b, 0xff3f], // Fullwidth punctuation 3 - [0xff5b, 0xff60], // Fullwidth punctuation 4 - [0xffe0, 0xffee], // Currency markers -]; - - -// Helper functions - -function _jpIsCharCodeInRanges(charCode, ranges) { - for (const [min, max] of ranges) { - if (charCode >= min && charCode <= max) { - return true; - } - } - return false; -} - - -// Character code testing functions - -function jpIsCharCodeKanji(charCode) { - return _jpIsCharCodeInRanges(charCode, JP_CJK_RANGES); -} - -function jpIsCharCodeKana(charCode) { - return _jpIsCharCodeInRanges(charCode, JP_KANA_RANGES); -} - -function jpIsCharCodeJapanese(charCode) { - return _jpIsCharCodeInRanges(charCode, JP_JAPANESE_RANGES); -} - - -// String testing functions - -function jpIsStringEntirelyKana(str) { - if (str.length === 0) { return false; } - for (let i = 0, ii = str.length; i < ii; ++i) { - if (!jpIsCharCodeKana(str.charCodeAt(i))) { - return false; - } - } - return true; -} - -function jpIsStringPartiallyJapanese(str) { - if (str.length === 0) { return false; } - for (let i = 0, ii = str.length; i < ii; ++i) { - if (jpIsCharCodeJapanese(str.charCodeAt(i))) { - return true; - } - } - return false; -} - - -// Conversion functions - -function jpKatakanaToHiragana(text) { - let result = ''; - for (const c of text) { - if (wanakana.isKatakana(c)) { - result += wanakana.toHiragana(c); - } else { - result += c; - } - } - - return result; -} - -function jpHiraganaToKatakana(text) { - let result = ''; - for (const c of text) { - if (wanakana.isHiragana(c)) { - result += wanakana.toKatakana(c); - } else { - result += c; - } - } - - return result; -} - -function jpToRomaji(text) { - return wanakana.toRomaji(text); -} - -function jpConvertReading(expressionFragment, readingFragment, readingMode) { - switch (readingMode) { - case 'hiragana': - return jpKatakanaToHiragana(readingFragment || ''); - case 'katakana': - return jpHiraganaToKatakana(readingFragment || ''); - case 'romaji': - if (readingFragment) { - return jpToRomaji(readingFragment); - } else { - if (jpIsStringEntirelyKana(expressionFragment)) { - return jpToRomaji(expressionFragment); - } - } - return readingFragment; - case 'none': - return null; - default: - return readingFragment; - } -} - -function jpDistributeFurigana(expression, reading) { - const fallback = [{furigana: reading, text: expression}]; - if (!reading) { - return fallback; - } - - let isAmbiguous = false; - const segmentize = (reading, groups) => { - if (groups.length === 0 || isAmbiguous) { - return []; - } - - const group = groups[0]; - if (group.mode === 'kana') { - if (jpKatakanaToHiragana(reading).startsWith(jpKatakanaToHiragana(group.text))) { - const readingLeft = reading.substring(group.text.length); - const segs = segmentize(readingLeft, groups.splice(1)); - if (segs) { - return [{text: group.text}].concat(segs); - } - } - } else { - let foundSegments = null; - for (let i = reading.length; i >= group.text.length; --i) { - const readingUsed = reading.substring(0, i); - const readingLeft = reading.substring(i); - const segs = segmentize(readingLeft, groups.slice(1)); - if (segs) { - if (foundSegments !== null) { - // more than one way to segmentize the tail, mark as ambiguous - isAmbiguous = true; - return null; - } - foundSegments = [{text: group.text, furigana: readingUsed}].concat(segs); - } - // there is only one way to segmentize the last non-kana group - if (groups.length === 1) { - break; - } - } - return foundSegments; - } - }; - - const groups = []; - let modePrev = null; - for (const c of expression) { - const charCode = c.charCodeAt(0); - const modeCurr = jpIsCharCodeKanji(charCode) || charCode === JP_ITERATION_MARK_CHAR_CODE ? 'kanji' : 'kana'; - if (modeCurr === modePrev) { - groups[groups.length - 1].text += c; - } else { - groups.push({mode: modeCurr, text: c}); - modePrev = modeCurr; - } - } - - const segments = segmentize(reading, groups); - if (segments && !isAmbiguous) { - return segments; - } - return fallback; -} - -function jpDistributeFuriganaInflected(expression, reading, source) { - const output = []; - - let stemLength = 0; - const shortest = Math.min(source.length, expression.length); - const sourceHiragana = jpKatakanaToHiragana(source); - const expressionHiragana = jpKatakanaToHiragana(expression); - while (stemLength < shortest && sourceHiragana[stemLength] === expressionHiragana[stemLength]) { - ++stemLength; - } - const offset = source.length - stemLength; - - const stemExpression = source.substring(0, source.length - offset); - const stemReading = reading.substring( - 0, - offset === 0 ? reading.length : reading.length - expression.length + stemLength - ); - for (const segment of jpDistributeFurigana(stemExpression, stemReading)) { - output.push(segment); - } - - if (stemLength !== source.length) { - output.push({text: source.substring(stemLength)}); - } - - return output; -} - -function jpConvertHalfWidthKanaToFullWidth(text, sourceMapping) { - let result = ''; - const ii = text.length; - const hasSourceMapping = Array.isArray(sourceMapping); - - for (let i = 0; i < ii; ++i) { - const c = text[i]; - const mapping = JP_HALFWIDTH_KATAKANA_MAPPING.get(c); - if (typeof mapping !== 'string') { - result += c; - continue; - } - - let index = 0; - switch (text.charCodeAt(i + 1)) { - case 0xff9e: // dakuten - index = 1; - break; - case 0xff9f: // handakuten - index = 2; - break; - } - - let c2 = mapping[index]; - if (index > 0) { - if (c2 === '-') { // invalid - index = 0; - c2 = mapping[0]; - } else { - ++i; - } - } - - if (hasSourceMapping && index > 0) { - index = result.length; - const v = sourceMapping.splice(index + 1, 1)[0]; - sourceMapping[index] += v; - } - result += c2; - } - - return result; -} - -function jpConvertNumericTofullWidth(text) { - let result = ''; - for (let i = 0, ii = text.length; i < ii; ++i) { - let c = text.charCodeAt(i); - if (c >= 0x30 && c <= 0x39) { // ['0', '9'] - c += 0xff10 - 0x30; // 0xff10 = '0' full width - result += String.fromCharCode(c); - } else { - result += text[i]; - } - } - return result; -} - -function jpConvertAlphabeticToKana(text, sourceMapping) { - let part = ''; - let result = ''; - const ii = text.length; - - if (sourceMapping.length === ii) { - sourceMapping.length = ii; - sourceMapping.fill(1); - } - - for (let i = 0; i < ii; ++i) { - // Note: 0x61 is the character code for 'a' - let c = text.charCodeAt(i); - if (c >= 0x41 && c <= 0x5a) { // ['A', 'Z'] - c += (0x61 - 0x41); - } else if (c >= 0x61 && c <= 0x7a) { // ['a', 'z'] - // NOP; c += (0x61 - 0x61); - } else if (c >= 0xff21 && c <= 0xff3a) { // ['A', 'Z'] fullwidth - c += (0x61 - 0xff21); - } else if (c >= 0xff41 && c <= 0xff5a) { // ['a', 'z'] fullwidth - c += (0x61 - 0xff41); - } else if (c === 0x2d || c === 0xff0d) { // '-' or fullwidth dash - c = 0x2d; // '-' - } else { - if (part.length > 0) { - result += jpToHiragana(part, sourceMapping, result.length); - part = ''; - } - result += text[i]; - continue; - } - part += String.fromCharCode(c); - } - - if (part.length > 0) { - result += jpToHiragana(part, sourceMapping, result.length); - } - return result; -} - -function jpToHiragana(text, sourceMapping, sourceMappingStart) { - const result = wanakana.toHiragana(text); - - // Generate source mapping - if (Array.isArray(sourceMapping)) { - if (typeof sourceMappingStart !== 'number') { sourceMappingStart = 0; } - let i = 0; - let resultPos = 0; - const ii = text.length; - while (i < ii) { - // Find smallest matching substring - let iNext = i + 1; - let resultPosNext = result.length; - while (iNext < ii) { - const t = wanakana.toHiragana(text.substring(0, iNext)); - if (t === result.substring(0, t.length)) { - resultPosNext = t.length; - break; - } - ++iNext; - } - - // Merge characters - const removals = iNext - i - 1; - if (removals > 0) { - let sum = 0; - const vs = sourceMapping.splice(sourceMappingStart + 1, removals); - for (const v of vs) { sum += v; } - sourceMapping[sourceMappingStart] += sum; - } - ++sourceMappingStart; - - // Empty elements - const additions = resultPosNext - resultPos - 1; - for (let j = 0; j < additions; ++j) { - sourceMapping.splice(sourceMappingStart, 0, 0); - ++sourceMappingStart; - } - - i = iNext; - resultPos = resultPosNext; - } - } - - return result; -} -- cgit v1.2.3 From f85f92c665736c759aa9d325d6d36f236fc21485 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 8 Feb 2020 20:48:47 -0500 Subject: Remove trailing comma --- ext/bg/js/japanese.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/japanese.js b/ext/bg/js/japanese.js index 0da822d7..c45c0958 100644 --- a/ext/bg/js/japanese.js +++ b/ext/bg/js/japanese.js @@ -108,7 +108,7 @@ const JP_JAPANESE_RANGES = [ [0xff1a, 0xff1f], // Fullwidth punctuation 2 [0xff3b, 0xff3f], // Fullwidth punctuation 3 [0xff5b, 0xff60], // Fullwidth punctuation 4 - [0xffe0, 0xffee], // Currency markers + [0xffe0, 0xffee] // Currency markers ]; -- cgit v1.2.3 From d5708de4eed15567e14e0c1fd4998561eee1680e Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 1 Feb 2020 14:39:26 -0500 Subject: Fix undefined reject --- ext/bg/js/backend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index eeab68a5..43ee81c3 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -708,7 +708,7 @@ class Backend { } try { - const tabWindow = await new Promise((resolve) => { + const tabWindow = await new Promise((resolve, reject) => { chrome.windows.get(tab.windowId, {}, (tabWindow) => { const e = chrome.runtime.lastError; if (e) { reject(e); } -- cgit v1.2.3 From ddc7c71e4f9da861d9d2bd56e60804ee9e70f621 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sat, 25 Jan 2020 17:54:07 +0200 Subject: add support for native popup windows --- ext/bg/js/backend.js | 54 +++++++++++++++++++++++++++++++++++++--------------- ext/bg/js/context.js | 4 ++-- 2 files changed, 41 insertions(+), 17 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 43ee81c3..23811e9d 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -33,6 +33,7 @@ class Backend { this.isPreparedPromise = new Promise((resolve) => (this.isPreparedResolve = resolve)); this.clipboardPasteTarget = document.querySelector('#clipboard-paste-target'); + this.popupWindow = null; this.apiForwarder = new BackendApiForwarder(); } @@ -565,23 +566,46 @@ class Backend { // Command handlers async _onCommandSearch(params) { - const url = chrome.runtime.getURL('/bg/search.html'); - if (!(params && params.newTab)) { - try { - const tab = await Backend._findTab(1000, (url2) => ( - url2 !== null && - url2.startsWith(url) && - (url2.length === url.length || url2[url.length] === '?' || url2[url.length] === '#') - )); - if (tab !== null) { - await Backend._focusTab(tab); - return; + const {mode, query} = params || {}; + + const optionsContext = {depth: 0}; + const options = await this.getOptions(optionsContext); + const {popupWidth, popupHeight} = options.general; + + const baseUrl = chrome.runtime.getURL('/bg/search.html'); + const queryString = (query && query.length > 0) ? `?query=${encodeURIComponent(query)}` : ''; + const url = baseUrl + queryString; + + switch (mode) { + case 'sameTab': + try { + const tab = await Backend._findTab(1000, (url2) => ( + url2 !== null && + url2.startsWith(url) && + (url2.length === url.length || url2[url.length] === '?' || url2[url.length] === '#') + )); + if (tab !== null) { + await Backend._focusTab(tab); + return; + } + } catch (e) { + // NOP } - } catch (e) { - // NOP - } + chrome.tabs.create({url}); + return; + case 'newTab': + chrome.tabs.create({url}); + return; + case 'popup': + if (this.popupWindow !== null) { + chrome.windows.remove(this.popupWindow.id); + } + chrome.windows.create( + {url, width: popupWidth, height: popupHeight, type: 'popup'}, + (popupWindow) => { this.popupWindow = popupWindow; } + ); + return; } - chrome.tabs.create({url}); } _onCommandHelp() { diff --git a/ext/bg/js/context.js b/ext/bg/js/context.js index 834174bf..a0e5adc9 100644 --- a/ext/bg/js/context.js +++ b/ext/bg/js/context.js @@ -30,12 +30,12 @@ function setupButtonEvents(selector, command, url) { for (const node of nodes) { node.addEventListener('click', (e) => { if (e.button !== 0) { return; } - apiCommandExec(command, {newTab: e.ctrlKey}); + apiCommandExec(command, {mode: e.ctrlKey ? 'newTab' : 'sameTab'}); e.preventDefault(); }, false); node.addEventListener('auxclick', (e) => { if (e.button !== 1) { return; } - apiCommandExec(command, {newTab: true}); + apiCommandExec(command, {mode: 'newTab'}); e.preventDefault(); }, false); -- cgit v1.2.3 From 679e42c21ccc3ab778f4b26406b353769e171878 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sat, 25 Jan 2020 18:11:19 +0200 Subject: move apiClipboardGet Firefox handling to Backend --- ext/bg/js/backend.js | 31 ++++++++++++++++++++++++------- ext/bg/js/search.js | 44 +++++++------------------------------------- 2 files changed, 31 insertions(+), 44 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 23811e9d..2fb7711f 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -522,13 +522,30 @@ class Backend { } async _onApiClipboardGet() { - const clipboardPasteTarget = this.clipboardPasteTarget; - clipboardPasteTarget.value = ''; - clipboardPasteTarget.focus(); - document.execCommand('paste'); - const result = clipboardPasteTarget.value; - clipboardPasteTarget.value = ''; - return result; + /* + Notes: + document.execCommand('paste') doesn't work on Firefox. + This may be a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1603985 + Therefore, navigator.clipboard.readText() is used on Firefox. + + navigator.clipboard.readText() can't be used in Chrome for two reasons: + * Requires page to be focused, else it rejects with an exception. + * When the page is focused, Chrome will request clipboard permission, despite already + being an extension with clipboard permissions. It effectively asks for the + non-extension permission for clipboard access. + */ + const browser = await Backend._getBrowser(); + if (browser === 'firefox' || browser === 'firefox-mobile') { + return await navigator.clipboard.readText(); + } else { + const clipboardPasteTarget = this.clipboardPasteTarget; + clipboardPasteTarget.value = ''; + clipboardPasteTarget.focus(); + document.execCommand('paste'); + const result = clipboardPasteTarget.value; + clipboardPasteTarget.value = ''; + return result; + } } async _onApiGetDisplayTemplatesHtml() { diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index f5c641a8..9508346e 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -36,8 +36,6 @@ class DisplaySearch extends Display { this.introVisible = true; this.introAnimationTimer = null; - this.isFirefox = false; - this.clipboardMonitorTimerId = null; this.clipboardMonitorTimerToken = null; this.clipboardInterval = 250; @@ -53,7 +51,6 @@ class DisplaySearch extends Display { async prepare() { try { await this.initialize(); - this.isFirefox = await DisplaySearch._isFirefox(); if (this.search !== null) { this.search.addEventListener('click', (e) => this.onSearch(e), false); @@ -250,13 +247,18 @@ class DisplaySearch extends Display { startClipboardMonitor() { // The token below is used as a unique identifier to ensure that a new clipboard monitor - // hasn't been started during the await call. The check below the await this.getClipboardText() + // hasn't been started during the await call. The check below the await apiClipboardGet() // call will exit early if the reference has changed. const token = {}; const intervalCallback = async () => { this.clipboardMonitorTimerId = null; - let text = await this.getClipboardText(); + let text = null; + try { + text = await apiClipboardGet(); + } catch (e) { + // NOP + } if (this.clipboardMonitorTimerToken !== token) { return; } if ( @@ -288,27 +290,6 @@ class DisplaySearch extends Display { } } - async getClipboardText() { - /* - Notes: - apiClipboardGet doesn't work on Firefox because document.execCommand('paste') - results in an empty string on the web extension background page. - This may be a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1603985 - Therefore, navigator.clipboard.readText() is used on Firefox. - - navigator.clipboard.readText() can't be used in Chrome for two reasons: - * Requires page to be focused, else it rejects with an exception. - * When the page is focused, Chrome will request clipboard permission, despite already - being an extension with clipboard permissions. It effectively asks for the - non-extension permission for clipboard access. - */ - try { - return this.isFirefox ? await navigator.clipboard.readText() : await apiClipboardGet(); - } catch (e) { - return null; - } - } - isWanakanaEnabled() { return this.wanakanaEnable !== null && this.wanakanaEnable.checked; } @@ -399,17 +380,6 @@ class DisplaySearch extends Display { const match = /^[^?#]*\?(?:[^&#]*&)?query=([^&#]*)/.exec(url); return match !== null ? decodeURIComponent(match[1]) : null; } - - static async _isFirefox() { - const {browser} = await apiGetEnvironmentInfo(); - switch (browser) { - case 'firefox': - case 'firefox-mobile': - return true; - default: - return false; - } - } } DisplaySearch.onKeyDownIgnoreKeys = { -- cgit v1.2.3 From f29abfc5115b06281c18467d7cf7b43bf133da82 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sat, 25 Jan 2020 18:18:40 +0200 Subject: use correct optionsContext --- ext/bg/js/backend.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 2fb7711f..3cb9ce1d 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -585,8 +585,7 @@ class Backend { async _onCommandSearch(params) { const {mode, query} = params || {}; - const optionsContext = {depth: 0}; - const options = await this.getOptions(optionsContext); + const options = await this.getOptions(this.optionsContext); const {popupWidth, popupHeight} = options.general; const baseUrl = chrome.runtime.getURL('/bg/search.html'); -- cgit v1.2.3 From 939ad42dacfeff566497f3c2f8e9c64d59b8168d Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sat, 25 Jan 2020 19:00:36 +0200 Subject: add global clipboard monitor that spawns popups TODO: refactor the search page clipboard monitor and popup clipboard monitor to use a common ClipboardMonitor class --- ext/bg/data/options-schema.json | 5 +++++ ext/bg/js/backend.js | 17 +++++++++++++++++ ext/bg/js/options.js | 1 + ext/bg/js/settings/main.js | 1 + ext/bg/settings.html | 4 ++++ 5 files changed, 28 insertions(+) (limited to 'ext/bg') diff --git a/ext/bg/data/options-schema.json b/ext/bg/data/options-schema.json index 7e12481d..d6207952 100644 --- a/ext/bg/data/options-schema.json +++ b/ext/bg/data/options-schema.json @@ -79,6 +79,7 @@ "type": "object", "required": [ "enable", + "enableClipboardPopups", "resultOutputMode", "debugInfo", "maxResults", @@ -111,6 +112,10 @@ "type": "boolean", "default": true }, + "enableClipboardPopups": { + "type": "boolean", + "default": false + }, "resultOutputMode": { "type": "string", "enum": ["group", "merge", "split"], diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 3cb9ce1d..407dc965 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -34,6 +34,9 @@ class Backend { this.clipboardPasteTarget = document.querySelector('#clipboard-paste-target'); this.popupWindow = null; + this.clipboardPopupTimerId = null; + this.clipboardInterval = 250; + this.clipboardPreviousText = null; this.apiForwarder = new BackendApiForwarder(); } @@ -122,6 +125,20 @@ class Backend { } else { this.mecab.stopListener(); } + + window.clearInterval(this.clipboardPopupTimerId); + if (options.general.enableClipboardPopups) { + this.clipboardPopupTimerId = setInterval(() => { + this._onApiClipboardGet() + .then((result) => { + if (this.clipboardPreviousText === result) { + return; + } + this._onCommandSearch({mode: 'popup', query: result}); + this.clipboardPreviousText = result; + }); + }, this.clipboardInterval); + } } async getOptionsSchema() { diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 78508059..97032660 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -266,6 +266,7 @@ function profileOptionsCreateDefaults() { return { general: { enable: true, + enableClipboardPopups: false, resultOutputMode: 'group', debugInfo: false, maxResults: 32, diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 4492cd42..ad3459f0 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -28,6 +28,7 @@ function getOptionsFullMutable() { async function formRead(options) { options.general.enable = $('#enable').prop('checked'); + options.general.enableClipboardPopups = $('#enable-clipboard-popups').prop('checked'); options.general.showGuide = $('#show-usage-guide').prop('checked'); options.general.compactTags = $('#compact-tags').prop('checked'); options.general.compactGlossaries = $('#compact-glossaries').prop('checked'); diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 3480b124..b0fcec2b 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -134,6 +134,10 @@
+
+ +
+
-- cgit v1.2.3 From 222f869c842b13d4bc7464a8fa2cceff6f64fa00 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 26 Jan 2020 02:11:48 +0200 Subject: fix search page hotkey --- ext/bg/js/backend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 407dc965..d80e7713 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -600,7 +600,7 @@ class Backend { // Command handlers async _onCommandSearch(params) { - const {mode, query} = params || {}; + const {mode, query} = params || {mode: 'sameTab'}; const options = await this.getOptions(this.optionsContext); const {popupWidth, popupHeight} = options.general; -- cgit v1.2.3 From 37a922adc0721520dac2f376b85ed2c8b5e42e69 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 26 Jan 2020 02:13:01 +0200 Subject: fix settings page checkbox --- ext/bg/js/settings/main.js | 1 + 1 file changed, 1 insertion(+) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index ad3459f0..55472a7b 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -105,6 +105,7 @@ async function formRead(options) { async function formWrite(options) { $('#enable').prop('checked', options.general.enable); + $('#enable-clipboard-popups').prop('checked', options.general.enableClipboardPopups); $('#show-usage-guide').prop('checked', options.general.showGuide); $('#compact-tags').prop('checked', options.general.compactTags); $('#compact-glossaries').prop('checked', options.general.compactGlossaries); -- cgit v1.2.3 From c685fd0e5f90c32911a225f0aaa68f5ff9fdfb7b Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 26 Jan 2020 03:02:33 +0200 Subject: extract ClipboardMonitor from DisplaySearch --- ext/bg/js/clipboard-monitor.js | 78 ++++++++++++++++++++++++++++++++++++++++++ ext/bg/js/search.js | 78 ++++++++++-------------------------------- ext/bg/search.html | 1 + 3 files changed, 97 insertions(+), 60 deletions(-) create mode 100644 ext/bg/js/clipboard-monitor.js (limited to 'ext/bg') diff --git a/ext/bg/js/clipboard-monitor.js b/ext/bg/js/clipboard-monitor.js new file mode 100644 index 00000000..579cdc56 --- /dev/null +++ b/ext/bg/js/clipboard-monitor.js @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +class ClipboardMonitor { + constructor() { + this.timerId = null; + this.timerToken = null; + this.interval = 250; + this.previousText = null; + } + + onClipboardText(_text) { + throw new Error('Override me'); + } + + start() { + // The token below is used as a unique identifier to ensure that a new clipboard monitor + // hasn't been started during the await call. The check below the await apiClipboardGet() + // call will exit early if the reference has changed. + const token = {}; + const intervalCallback = async () => { + this.timerId = null; + + let text = null; + try { + text = await apiClipboardGet(); + } catch (e) { + // NOP + } + if (this.timerToken !== token) { return; } + + if ( + typeof text === 'string' && + (text = text.trim()).length > 0 && + text !== this.previousText + ) { + this.previousText = text; + if (jpIsStringPartiallyJapanese(text)) { + this.onClipboardText(text); + } + } + + this.timerId = setTimeout(intervalCallback, this.interval); + }; + + this.timerToken = token; + + intervalCallback(); + } + + stop() { + this.timerToken = null; + if (this.timerId !== null) { + clearTimeout(this.timerId); + this.timerId = null; + } + } + + setPreviousText(text) { + this.previousText = text; + } +} diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 9508346e..e32ba46e 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -36,10 +36,7 @@ class DisplaySearch extends Display { this.introVisible = true; this.introAnimationTimer = null; - this.clipboardMonitorTimerId = null; - this.clipboardMonitorTimerToken = null; - this.clipboardInterval = 250; - this.clipboardPreviousText = null; + this.clipboardMonitor = new ClipboardMonitor(); } static create() { @@ -93,7 +90,7 @@ class DisplaySearch extends Display { if (this.clipboardMonitorEnable !== null) { if (this.options.general.enableClipboardMonitor === true) { this.clipboardMonitorEnable.checked = true; - this.startClipboardMonitor(); + this.clipboardMonitor.start(); } else { this.clipboardMonitorEnable.checked = false; } @@ -103,7 +100,7 @@ class DisplaySearch extends Display { {permissions: ['clipboardRead']}, (granted) => { if (granted) { - this.startClipboardMonitor(); + this.clipboardMonitor.start(); apiOptionsSet({general: {enableClipboardMonitor: true}}, this.getOptionsContext()); } else { e.target.checked = false; @@ -111,16 +108,18 @@ class DisplaySearch extends Display { } ); } else { - this.stopClipboardMonitor(); + this.clipboardMonitor.stop(); apiOptionsSet({general: {enableClipboardMonitor: false}}, this.getOptionsContext()); } }); } window.addEventListener('popstate', (e) => this.onPopState(e)); + window.addEventListener('copy', (e) => this.onCopy(e)); + + this.clipboardMonitor.onClipboardText = (text) => this.onClipboardText(text); this.updateSearchButton(); - this.initClipboardMonitor(); } catch (e) { this.onError(e); } @@ -199,6 +198,17 @@ class DisplaySearch extends Display { } } + onCopy() { + // ignore copy from search page + this.clipboardMonitor.setPreviousText(document.getSelection().toString().trim()); + } + + onClipboardText(text) { + this.setQuery(this.isWanakanaEnabled() ? window.wanakana.toKana(text) : text); + window.history.pushState(null, '', `${window.location.pathname}?query=${encodeURIComponent(text)}`); + this.onSearchQueryUpdated(this.query.value, true); + } + async onSearchQueryUpdated(query, animate) { try { const details = {}; @@ -238,58 +248,6 @@ class DisplaySearch extends Display { this.queryParser.setOptions(this.options); } - initClipboardMonitor() { - // ignore copy from search page - window.addEventListener('copy', () => { - this.clipboardPreviousText = document.getSelection().toString().trim(); - }); - } - - startClipboardMonitor() { - // The token below is used as a unique identifier to ensure that a new clipboard monitor - // hasn't been started during the await call. The check below the await apiClipboardGet() - // call will exit early if the reference has changed. - const token = {}; - const intervalCallback = async () => { - this.clipboardMonitorTimerId = null; - - let text = null; - try { - text = await apiClipboardGet(); - } catch (e) { - // NOP - } - if (this.clipboardMonitorTimerToken !== token) { return; } - - if ( - typeof text === 'string' && - (text = text.trim()).length > 0 && - text !== this.clipboardPreviousText - ) { - this.clipboardPreviousText = text; - if (jpIsStringPartiallyJapanese(text)) { - this.setQuery(this.isWanakanaEnabled() ? window.wanakana.toKana(text) : text); - window.history.pushState(null, '', `${window.location.pathname}?query=${encodeURIComponent(text)}`); - this.onSearchQueryUpdated(this.query.value, true); - } - } - - this.clipboardMonitorTimerId = setTimeout(intervalCallback, this.clipboardInterval); - }; - - this.clipboardMonitorTimerToken = token; - - intervalCallback(); - } - - stopClipboardMonitor() { - this.clipboardMonitorTimerToken = null; - if (this.clipboardMonitorTimerId !== null) { - clearTimeout(this.clipboardMonitorTimerId); - this.clipboardMonitorTimerId = null; - } - } - isWanakanaEnabled() { return this.wanakanaEnable !== null && this.wanakanaEnable.checked; } diff --git a/ext/bg/search.html b/ext/bg/search.html index bb7ac095..bb555e92 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -87,6 +87,7 @@ + -- cgit v1.2.3 From 90a5d795708898fba940f77358269354e96355a7 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 26 Jan 2020 03:31:31 +0200 Subject: use ClipboardMonitor in Backend --- ext/bg/background.html | 1 + ext/bg/js/api.js | 4 ++++ ext/bg/js/backend.js | 25 +++++++++++-------------- 3 files changed, 16 insertions(+), 14 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/background.html b/ext/bg/background.html index e6d32593..11023221 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -26,6 +26,7 @@ + diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 285b8016..f4be7a0c 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -29,6 +29,10 @@ function apiGetDisplayTemplatesHtml() { return _apiInvoke('getDisplayTemplatesHtml'); } +function apiClipboardGet() { + return _apiInvoke('clipboardGet'); +} + function _apiInvoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index d80e7713..fa1660c2 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -22,6 +22,7 @@ class Backend { this.translator = new Translator(); this.anki = new AnkiNull(); this.mecab = new Mecab(); + this.clipboardMonitor = new ClipboardMonitor(); this.options = null; this.optionsSchema = null; this.optionsContext = { @@ -33,10 +34,8 @@ class Backend { this.isPreparedPromise = new Promise((resolve) => (this.isPreparedResolve = resolve)); this.clipboardPasteTarget = document.querySelector('#clipboard-paste-target'); + this.popupWindow = null; - this.clipboardPopupTimerId = null; - this.clipboardInterval = 250; - this.clipboardPreviousText = null; this.apiForwarder = new BackendApiForwarder(); } @@ -71,6 +70,8 @@ class Backend { this.isPreparedResolve(); this.isPreparedResolve = null; this.isPreparedPromise = null; + + this.clipboardMonitor.onClipboardText = (text) => this._onClipboardText(text); } onOptionsUpdated(source) { @@ -101,6 +102,10 @@ class Backend { } } + _onClipboardText(text) { + this._onCommandSearch({mode: 'popup', query: text}); + } + _onZoomChange({tabId, oldZoomFactor, newZoomFactor}) { const callback = () => this.checkLastError(chrome.runtime.lastError); chrome.tabs.sendMessage(tabId, {action: 'zoomChanged', params: {oldZoomFactor, newZoomFactor}}, callback); @@ -126,18 +131,10 @@ class Backend { this.mecab.stopListener(); } - window.clearInterval(this.clipboardPopupTimerId); if (options.general.enableClipboardPopups) { - this.clipboardPopupTimerId = setInterval(() => { - this._onApiClipboardGet() - .then((result) => { - if (this.clipboardPreviousText === result) { - return; - } - this._onCommandSearch({mode: 'popup', query: result}); - this.clipboardPreviousText = result; - }); - }, this.clipboardInterval); + this.clipboardMonitor.start(); + } else { + this.clipboardMonitor.stop(); } } -- cgit v1.2.3 From 91682dd63334da7acf458a320fbd48bbf7dff766 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 26 Jan 2020 04:16:02 +0200 Subject: add permission check to formRead --- ext/bg/js/settings/main.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 55472a7b..cf0f08db 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -28,7 +28,22 @@ function getOptionsFullMutable() { async function formRead(options) { options.general.enable = $('#enable').prop('checked'); - options.general.enableClipboardPopups = $('#enable-clipboard-popups').prop('checked'); + const enableClipboardPopups = $('#enable-clipboard-popups').prop('checked'); + if (enableClipboardPopups) { + options.general.enableClipboardPopups = await new Promise((resolve, _reject) => { + chrome.permissions.request( + {permissions: ['clipboardRead']}, + (granted) => { + if (!granted) { + $('#enable-clipboard-popups').prop('checked', false); + } + resolve(granted); + } + ); + }); + } else { + options.general.enableClipboardPopups = false; + } options.general.showGuide = $('#show-usage-guide').prop('checked'); options.general.compactTags = $('#compact-tags').prop('checked'); options.general.compactGlossaries = $('#compact-glossaries').prop('checked'); -- cgit v1.2.3 From d7f0369281f8e9f036d87be392828e81d8187e74 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 2 Feb 2020 14:41:41 +0200 Subject: use Promise --- ext/bg/js/backend.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index fa1660c2..06010a95 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -630,10 +630,10 @@ class Backend { if (this.popupWindow !== null) { chrome.windows.remove(this.popupWindow.id); } - chrome.windows.create( + this.popupWindow = await new Promise((resolve) => chrome.windows.create( {url, width: popupWidth, height: popupHeight, type: 'popup'}, - (popupWindow) => { this.popupWindow = popupWindow; } - ); + resolve + )); return; } } -- cgit v1.2.3 From 8d56d6ffcb0f02e1edaf4193e182e95bfe7d22c5 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 2 Feb 2020 14:58:31 +0200 Subject: handle closing already closed popup --- ext/bg/js/backend.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 06010a95..a9f2385b 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -628,7 +628,8 @@ class Backend { return; case 'popup': if (this.popupWindow !== null) { - chrome.windows.remove(this.popupWindow.id); + const callback = () => this.checkLastError(chrome.runtime.lastError); + chrome.windows.remove(this.popupWindow.id, callback); } this.popupWindow = await new Promise((resolve) => chrome.windows.create( {url, width: popupWidth, height: popupHeight, type: 'popup'}, -- cgit v1.2.3 From c16c38638b4ce39f64add0d6e430a21e44d8ec01 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 2 Feb 2020 16:08:19 +0200 Subject: hide search input in native popups --- ext/bg/js/backend.js | 6 ++++-- ext/bg/js/search.js | 37 ++++++++++++++++++------------------- ext/bg/search.html | 34 ++++++++++++++++++---------------- ext/mixed/css/display.css | 4 ++++ 4 files changed, 44 insertions(+), 37 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index a9f2385b..bdb8a76a 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -603,8 +603,10 @@ class Backend { const {popupWidth, popupHeight} = options.general; const baseUrl = chrome.runtime.getURL('/bg/search.html'); - const queryString = (query && query.length > 0) ? `?query=${encodeURIComponent(query)}` : ''; - const url = baseUrl + queryString; + const queryParams = {mode}; + if (query && query.length > 0) { queryParams.query = query; } + const queryString = new URLSearchParams(queryParams).toString(); + const url = `${baseUrl}?${queryString}`; switch (mode) { case 'sameTab': diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index e32ba46e..ea4ab235 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -53,6 +53,8 @@ class DisplaySearch extends Display { this.search.addEventListener('click', (e) => this.onSearch(e), false); } if (this.query !== null) { + const {query='', mode=''} = DisplaySearch.parseQueryStringFromLocation(window.location.href); + document.documentElement.dataset.searchMode = mode; this.query.addEventListener('input', () => this.onSearchInput(), false); if (this.wanakanaEnable !== null) { @@ -63,7 +65,6 @@ class DisplaySearch extends Display { this.wanakanaEnable.checked = false; } this.wanakanaEnable.addEventListener('change', (e) => { - const query = DisplaySearch.getSearchQueryFromLocation(window.location.href) || ''; if (e.target.checked) { window.wanakana.bind(this.query); this.setQuery(window.wanakana.toKana(query)); @@ -77,15 +78,12 @@ class DisplaySearch extends Display { }); } - const query = DisplaySearch.getSearchQueryFromLocation(window.location.href); - if (query !== null) { - if (this.isWanakanaEnabled()) { - this.setQuery(window.wanakana.toKana(query)); - } else { - this.setQuery(query); - } - this.onSearchQueryUpdated(this.query.value, false); + if (this.isWanakanaEnabled()) { + this.setQuery(window.wanakana.toKana(query)); + } else { + this.setQuery(query); } + this.onSearchQueryUpdated(this.query.value, false); } if (this.clipboardMonitorEnable !== null) { if (this.options.general.enableClipboardMonitor === true) { @@ -162,13 +160,12 @@ class DisplaySearch extends Display { } onPopState() { - const query = DisplaySearch.getSearchQueryFromLocation(window.location.href) || ''; - if (this.query !== null) { - if (this.isWanakanaEnabled()) { - this.setQuery(window.wanakana.toKana(query)); - } else { - this.setQuery(query); - } + const {query='', mode=''} = DisplaySearch.parseQueryStringFromLocation(window.location.href); + document.documentElement.dataset.searchMode = mode; + if (this.isWanakanaEnabled()) { + this.setQuery(window.wanakana.toKana(query)); + } else { + this.setQuery(query); } this.onSearchQueryUpdated(this.query.value, false); @@ -334,9 +331,11 @@ class DisplaySearch extends Display { } } - static getSearchQueryFromLocation(url) { - const match = /^[^?#]*\?(?:[^&#]*&)?query=([^&#]*)/.exec(url); - return match !== null ? decodeURIComponent(match[1]) : null; + static parseQueryStringFromLocation(url) { + const parsedUrl = new URL(url); + const parsedSearch = new URLSearchParams(parsedUrl.search); + return Array.from(parsedSearch.entries()) + .reduce((a, [k, v]) => Object.assign({}, a, {[k]: v}), {}); } } diff --git a/ext/bg/search.html b/ext/bg/search.html index bb555e92..10e5aa8e 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -25,23 +25,25 @@

Search your installed dictionaries by entering a Japanese expression into the field below.

-
- - - - - - - - -
+
+
+ + + + + + + + +
-
- - - - -
+
+ + + + +
+
diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 3a66cec3..62e62243 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -136,6 +136,10 @@ 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 { + display: none; +} + /* * Entries -- cgit v1.2.3 From 8a295c4bb06b88bb135a34a3f692ac631a713f50 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Wed, 5 Feb 2020 01:41:57 +0200 Subject: fix constant usage from wrong scope --- ext/bg/js/search.js | 1 + 1 file changed, 1 insertion(+) (limited to 'ext/bg') diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index ea4ab235..34dac15b 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -65,6 +65,7 @@ class DisplaySearch extends Display { this.wanakanaEnable.checked = false; } this.wanakanaEnable.addEventListener('change', (e) => { + const {query=''} = DisplaySearch.parseQueryStringFromLocation(window.location.href); if (e.target.checked) { window.wanakana.bind(this.query); this.setQuery(window.wanakana.toKana(query)); -- cgit v1.2.3 From 722a2a4bce08ae2e69d7abb1c5d09842bd29ebfa Mon Sep 17 00:00:00 2001 From: siikamiika Date: Wed, 5 Feb 2020 13:26:25 +0200 Subject: disable internal clipboard monitor in native popup --- ext/bg/js/search.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 34dac15b..feadefe4 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -49,11 +49,12 @@ class DisplaySearch extends Display { try { await this.initialize(); + const {query='', mode=''} = DisplaySearch.parseQueryStringFromLocation(window.location.href); + if (this.search !== null) { this.search.addEventListener('click', (e) => this.onSearch(e), false); } if (this.query !== null) { - const {query='', mode=''} = DisplaySearch.parseQueryStringFromLocation(window.location.href); document.documentElement.dataset.searchMode = mode; this.query.addEventListener('input', () => this.onSearchInput(), false); @@ -86,7 +87,7 @@ class DisplaySearch extends Display { } this.onSearchQueryUpdated(this.query.value, false); } - if (this.clipboardMonitorEnable !== null) { + if (this.clipboardMonitorEnable !== null && mode !== 'popup') { if (this.options.general.enableClipboardMonitor === true) { this.clipboardMonitorEnable.checked = true; this.clipboardMonitor.start(); -- cgit v1.2.3 From 9fbd47e4eaeee2f1121cdeae08dbc5439b247498 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 9 Feb 2020 21:06:59 +0200 Subject: rename sameTab --> existingOrNewTab --- ext/bg/js/backend.js | 4 ++-- ext/bg/js/context.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index bdb8a76a..f94463aa 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -597,7 +597,7 @@ class Backend { // Command handlers async _onCommandSearch(params) { - const {mode, query} = params || {mode: 'sameTab'}; + const {mode, query} = params || {mode: 'existingOrNewTab'}; const options = await this.getOptions(this.optionsContext); const {popupWidth, popupHeight} = options.general; @@ -609,7 +609,7 @@ class Backend { const url = `${baseUrl}?${queryString}`; switch (mode) { - case 'sameTab': + case 'existingOrNewTab': try { const tab = await Backend._findTab(1000, (url2) => ( url2 !== null && diff --git a/ext/bg/js/context.js b/ext/bg/js/context.js index a0e5adc9..37adb6b7 100644 --- a/ext/bg/js/context.js +++ b/ext/bg/js/context.js @@ -30,7 +30,7 @@ function setupButtonEvents(selector, command, url) { for (const node of nodes) { node.addEventListener('click', (e) => { if (e.button !== 0) { return; } - apiCommandExec(command, {mode: e.ctrlKey ? 'newTab' : 'sameTab'}); + apiCommandExec(command, {mode: e.ctrlKey ? 'newTab' : 'existingOrNewTab'}); e.preventDefault(); }, false); node.addEventListener('auxclick', (e) => { -- cgit v1.2.3 From 1797edc7d88f63872c3a593179d9dd5c7b3c0b47 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 9 Feb 2020 21:11:35 +0200 Subject: check chrome.windows support before using --- ext/bg/js/backend.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index f94463aa..6d23b695 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -629,6 +629,10 @@ class Backend { chrome.tabs.create({url}); return; case 'popup': + if (!isObject(chrome.windows)) { + // chrome.windows not supported (e.g. on Firefox mobile) + return; + } if (this.popupWindow !== null) { const callback = () => this.checkLastError(chrome.runtime.lastError); chrome.windows.remove(this.popupWindow.id, callback); -- cgit v1.2.3 From 4e59c2d55684b5a0b1d9edc580dd4c43bfc46211 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 9 Feb 2020 21:28:40 +0200 Subject: hide native popup option for firefox mobile --- ext/bg/css/settings.css | 14 ++++++++++++++ ext/bg/settings.html | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/css/settings.css b/ext/bg/css/settings.css index 815a88fa..d686e8f8 100644 --- a/ext/bg/css/settings.css +++ b/ext/bg/css/settings.css @@ -222,6 +222,20 @@ html:root[data-operating-system=openbsd] [data-show-for-operating-system~=openbs display: initial; } +html:root[data-browser=edge] [data-hide-for-browser~=edge], +html:root[data-browser=chrome] [data-hide-for-browser~=chrome], +html:root[data-browser=firefox] [data-hide-for-browser~=firefox], +html:root[data-browser=firefox-mobile] [data-hide-for-browser~=firefox-mobile], +html:root[data-operating-system=mac] [data-hide-for-operating-system~=mac], +html:root[data-operating-system=win] [data-hide-for-operating-system~=win], +html:root[data-operating-system=android] [data-hide-for-operating-system~=android], +html:root[data-operating-system=cros] [data-hide-for-operating-system~=cros], +html:root[data-operating-system=linux] [data-hide-for-operating-system~=linux], +html:root[data-operating-system=openbsd] [data-hide-for-operating-system~=openbsd] { + display: none; +} + + @media screen and (max-width: 740px) { .col-xs-6 { float: none; diff --git a/ext/bg/settings.html b/ext/bg/settings.html index b0fcec2b..57616873 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -134,7 +134,7 @@ -
+
-- cgit v1.2.3 From 21bad6c6e380c9c0dbd03f82563a1570bf22963c Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 9 Feb 2020 21:47:11 +0200 Subject: simplify setQuery kana conversion --- ext/bg/js/search.js | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index feadefe4..1baee904 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -69,22 +69,17 @@ class DisplaySearch extends Display { const {query=''} = DisplaySearch.parseQueryStringFromLocation(window.location.href); if (e.target.checked) { window.wanakana.bind(this.query); - this.setQuery(window.wanakana.toKana(query)); apiOptionsSet({general: {enableWanakana: true}}, this.getOptionsContext()); } else { window.wanakana.unbind(this.query); - this.setQuery(query); apiOptionsSet({general: {enableWanakana: false}}, this.getOptionsContext()); } + this.setQuery(query); this.onSearchQueryUpdated(this.query.value, false); }); } - if (this.isWanakanaEnabled()) { - this.setQuery(window.wanakana.toKana(query)); - } else { - this.setQuery(query); - } + this.setQuery(query); this.onSearchQueryUpdated(this.query.value, false); } if (this.clipboardMonitorEnable !== null && mode !== 'popup') { @@ -164,12 +159,7 @@ class DisplaySearch extends Display { onPopState() { const {query='', mode=''} = DisplaySearch.parseQueryStringFromLocation(window.location.href); document.documentElement.dataset.searchMode = mode; - if (this.isWanakanaEnabled()) { - this.setQuery(window.wanakana.toKana(query)); - } else { - this.setQuery(query); - } - + this.setQuery(query); this.onSearchQueryUpdated(this.query.value, false); } @@ -203,7 +193,7 @@ class DisplaySearch extends Display { } onClipboardText(text) { - this.setQuery(this.isWanakanaEnabled() ? window.wanakana.toKana(text) : text); + this.setQuery(text); window.history.pushState(null, '', `${window.location.pathname}?query=${encodeURIComponent(text)}`); this.onSearchQueryUpdated(this.query.value, true); } @@ -256,8 +246,9 @@ class DisplaySearch extends Display { } setQuery(query) { - this.query.value = query; - this.queryParser.setText(query); + const interpretedQuery = this.isWanakanaEnabled() ? window.wanakana.toKana(query) : query; + this.query.value = interpretedQuery; + this.queryParser.setText(interpretedQuery); } setIntroVisible(visible, animate) { -- cgit v1.2.3 From d4e74a05723b1b4347c51606bcee61dc72e34e51 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 9 Feb 2020 22:16:52 +0200 Subject: fix existing tab focus --- ext/bg/js/backend.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 6d23b695..9eb1d9ca 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -597,7 +597,7 @@ class Backend { // Command handlers async _onCommandSearch(params) { - const {mode, query} = params || {mode: 'existingOrNewTab'}; + const {mode='existingOrNewTab', query} = params || {}; const options = await this.getOptions(this.optionsContext); const {popupWidth, popupHeight} = options.general; @@ -613,11 +613,14 @@ class Backend { try { const tab = await Backend._findTab(1000, (url2) => ( url2 !== null && - url2.startsWith(url) && - (url2.length === url.length || url2[url.length] === '?' || url2[url.length] === '#') + url2.startsWith(baseUrl) && + (url2.length === baseUrl.length || url2[baseUrl.length] === '?' || url2[baseUrl.length] === '#') )); if (tab !== null) { await Backend._focusTab(tab); + if (queryParams.query) { + await new Promise((resolve) => chrome.tabs.update(tab.id, {url}, resolve)); + } return; } } catch (e) { -- cgit v1.2.3 From 4508efb9a695a97f19cf99ccb6155c55f9be5f0d Mon Sep 17 00:00:00 2001 From: siikamiika Date: Sun, 9 Feb 2020 22:41:20 +0200 Subject: stop clipboard monitor before starting it again --- ext/bg/js/clipboard-monitor.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'ext/bg') diff --git a/ext/bg/js/clipboard-monitor.js b/ext/bg/js/clipboard-monitor.js index 579cdc56..b4a27fa2 100644 --- a/ext/bg/js/clipboard-monitor.js +++ b/ext/bg/js/clipboard-monitor.js @@ -30,6 +30,8 @@ class ClipboardMonitor { } start() { + this.stop(); + // The token below is used as a unique identifier to ensure that a new clipboard monitor // hasn't been started during the await call. The check below the await apiClipboardGet() // call will exit early if the reference has changed. -- cgit v1.2.3 From 56f1f8384dba7da6f1373768129bd37c24147520 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 10 Feb 2020 00:09:29 +0200 Subject: use parseUrl in Backend --- ext/bg/js/backend.js | 12 +++++++----- ext/bg/js/search.js | 13 +++---------- ext/mixed/js/core.js | 8 ++++++++ 3 files changed, 18 insertions(+), 15 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 9eb1d9ca..9565a8d9 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -608,14 +608,16 @@ class Backend { const queryString = new URLSearchParams(queryParams).toString(); const url = `${baseUrl}?${queryString}`; + const isTabMatch = (url2) => { + if (url2 === null || !url2.startsWith(baseUrl)) { return false; } + const {baseUrl: baseUrl2, queryParams: queryParams2} = parseUrl(url2); + return baseUrl2 === baseUrl && (queryParams2.mode === mode || (!queryParams2.mode && mode === 'existingOrNewTab')); + }; + switch (mode) { case 'existingOrNewTab': try { - const tab = await Backend._findTab(1000, (url2) => ( - url2 !== null && - url2.startsWith(baseUrl) && - (url2.length === baseUrl.length || url2[baseUrl.length] === '?' || url2[baseUrl.length] === '#') - )); + const tab = await Backend._findTab(1000, isTabMatch); if (tab !== null) { await Backend._focusTab(tab); if (queryParams.query) { diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 1baee904..b6a1e66a 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -49,7 +49,7 @@ class DisplaySearch extends Display { try { await this.initialize(); - const {query='', mode=''} = DisplaySearch.parseQueryStringFromLocation(window.location.href); + const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); if (this.search !== null) { this.search.addEventListener('click', (e) => this.onSearch(e), false); @@ -66,7 +66,7 @@ class DisplaySearch extends Display { this.wanakanaEnable.checked = false; } this.wanakanaEnable.addEventListener('change', (e) => { - const {query=''} = DisplaySearch.parseQueryStringFromLocation(window.location.href); + const {queryParams: {query=''}} = parseUrl(window.location.href); if (e.target.checked) { window.wanakana.bind(this.query); apiOptionsSet({general: {enableWanakana: true}}, this.getOptionsContext()); @@ -157,7 +157,7 @@ class DisplaySearch extends Display { } onPopState() { - const {query='', mode=''} = DisplaySearch.parseQueryStringFromLocation(window.location.href); + const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); document.documentElement.dataset.searchMode = mode; this.setQuery(query); this.onSearchQueryUpdated(this.query.value, false); @@ -323,13 +323,6 @@ class DisplaySearch extends Display { document.title = `${text} - Yomichan Search`; } } - - static parseQueryStringFromLocation(url) { - const parsedUrl = new URL(url); - const parsedSearch = new URLSearchParams(parsedUrl.search); - return Array.from(parsedSearch.entries()) - .reduce((a, [k, v]) => Object.assign({}, a, {[k]: v}), {}); - } } DisplaySearch.onKeyDownIgnoreKeys = { diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index 0142d594..ca9e98e5 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -128,6 +128,14 @@ function stringReverse(string) { return string.split('').reverse().join('').replace(/([\uDC00-\uDFFF])([\uD800-\uDBFF])/g, '$2$1'); } +function parseUrl(url) { + const parsedUrl = new URL(url); + const baseUrl = `${parsedUrl.origin}${parsedUrl.pathname}`; + const queryParams = Array.from(parsedUrl.searchParams.entries()) + .reduce((a, [k, v]) => Object.assign({}, a, {[k]: v}), {}); + return {baseUrl, queryParams}; +} + /* * Async utilities -- cgit v1.2.3 From 89729d8c20e8d1113e640a46f448a9734da1fe56 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 10 Feb 2020 00:39:05 +0200 Subject: reuse existing popup window --- ext/bg/js/backend.js | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 9565a8d9..adfb4f10 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -614,17 +614,21 @@ class Backend { return baseUrl2 === baseUrl && (queryParams2.mode === mode || (!queryParams2.mode && mode === 'existingOrNewTab')); }; + const openInTab = async () => { + const tab = await Backend._findTab(1000, isTabMatch); + if (tab !== null) { + await Backend._focusTab(tab); + if (queryParams.query) { + await new Promise((resolve) => chrome.tabs.update(tab.id, {url}, resolve)); + } + return true; + } + }; + switch (mode) { case 'existingOrNewTab': try { - const tab = await Backend._findTab(1000, isTabMatch); - if (tab !== null) { - await Backend._focusTab(tab); - if (queryParams.query) { - await new Promise((resolve) => chrome.tabs.update(tab.id, {url}, resolve)); - } - return; - } + if (await openInTab()) { return; } } catch (e) { // NOP } @@ -634,18 +638,23 @@ class Backend { chrome.tabs.create({url}); return; case 'popup': - if (!isObject(chrome.windows)) { + try { // chrome.windows not supported (e.g. on Firefox mobile) - return; - } - if (this.popupWindow !== null) { - const callback = () => this.checkLastError(chrome.runtime.lastError); - chrome.windows.remove(this.popupWindow.id, callback); + if (!isObject(chrome.windows)) { return; } + if (await openInTab()) { return; } + // if the previous popup is open in an invalid state, close it + if (this.popupWindow !== null) { + const callback = () => this.checkLastError(chrome.runtime.lastError); + chrome.windows.remove(this.popupWindow.id, callback); + } + // open new popup + this.popupWindow = await new Promise((resolve) => chrome.windows.create( + {url, width: popupWidth, height: popupHeight, type: 'popup'}, + resolve + )); + } catch (e) { + // NOP } - this.popupWindow = await new Promise((resolve) => chrome.windows.create( - {url, width: popupWidth, height: popupHeight, type: 'popup'}, - resolve - )); return; } } -- cgit v1.2.3 From 460d306f60fa745368c8249e4bc4bdb0d0448f25 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 10 Feb 2020 01:16:06 +0200 Subject: update popup search with chrome.tabs.sendMessage --- ext/bg/js/backend.js | 4 +++- ext/bg/js/search.js | 23 ++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index adfb4f10..668d1fb7 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -619,7 +619,9 @@ class Backend { if (tab !== null) { await Backend._focusTab(tab); if (queryParams.query) { - await new Promise((resolve) => chrome.tabs.update(tab.id, {url}, resolve)); + await new Promise((resolve) => chrome.tabs.sendMessage( + tab.id, {action: 'searchQueryUpdate', params: {query: queryParams.query}}, resolve + )); } return true; } diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index b6a1e66a..37c96934 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -109,10 +109,12 @@ class DisplaySearch extends Display { }); } + chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); + window.addEventListener('popstate', (e) => this.onPopState(e)); window.addEventListener('copy', (e) => this.onCopy(e)); - this.clipboardMonitor.onClipboardText = (text) => this.onClipboardText(text); + this.clipboardMonitor.onClipboardText = (text) => this.onExternalSearchUpdate(text); this.updateSearchButton(); } catch (e) { @@ -163,6 +165,15 @@ class DisplaySearch extends Display { this.onSearchQueryUpdated(this.query.value, false); } + onRuntimeMessage({action, params}, sender, callback) { + const handler = DisplaySearch._runtimeMessageHandlers.get(action); + if (typeof handler !== 'function') { return false; } + + const result = handler(this, params, sender); + callback(result); + return false; + } + onKeyDown(e) { const key = Display.getKeyFromEvent(e); const ignoreKeys = DisplaySearch.onKeyDownIgnoreKeys; @@ -192,9 +203,11 @@ class DisplaySearch extends Display { this.clipboardMonitor.setPreviousText(document.getSelection().toString().trim()); } - onClipboardText(text) { + onExternalSearchUpdate(text) { this.setQuery(text); - window.history.pushState(null, '', `${window.location.pathname}?query=${encodeURIComponent(text)}`); + const url = new URL(window.location.href); + url.searchParams.set('query', text); + window.history.pushState(null, '', url.toString()); this.onSearchQueryUpdated(this.query.value, true); } @@ -340,4 +353,8 @@ DisplaySearch.onKeyDownIgnoreKeys = { 'Shift': [] }; +DisplaySearch._runtimeMessageHandlers = new Map([ + ['searchQueryUpdate', (self, {query}) => { self.onExternalSearchUpdate(query); }] +]); + DisplaySearch.instance = DisplaySearch.create(); -- cgit v1.2.3 From 14b9f4a82781b9a5044e22437f6b0b02af67a120 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 10 Feb 2020 01:31:47 +0200 Subject: preserve search page mode on manual search --- ext/bg/js/search.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 37c96934..4da27513 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -152,9 +152,13 @@ class DisplaySearch extends Display { e.preventDefault(); const query = this.query.value; + this.queryParser.setText(query); - const queryString = query.length > 0 ? `?query=${encodeURIComponent(query)}` : ''; - window.history.pushState(null, '', `${window.location.pathname}${queryString}`); + + const url = new URL(window.location.href); + url.searchParams.set('query', query); + window.history.pushState(null, '', url.toString()); + this.onSearchQueryUpdated(query, true); } -- cgit v1.2.3 From 6c63a17d669b57fdbc1a5a71daf89c6c95e7d5ef Mon Sep 17 00:00:00 2001 From: siikamiika Date: Thu, 6 Feb 2020 04:00:02 +0200 Subject: query parser html templates --- build_tmpl.sh | 2 - build_tmpl_auto.sh | 16 ------ ext/bg/background.html | 1 - ext/bg/js/api.js | 4 ++ ext/bg/js/backend.js | 6 +++ ext/bg/js/search-query-parser-generator.js | 78 ++++++++++++++++++++++++++++++ ext/bg/js/search-query-parser.js | 60 +++++++---------------- ext/bg/js/templates.js | 55 --------------------- ext/bg/query-parser-templates.html | 12 +++++ ext/bg/search.html | 7 +-- ext/bg/settings.html | 1 - ext/mixed/css/display.css | 4 +- ext/mixed/js/api.js | 4 ++ ext/mixed/js/template-handler.js | 53 ++++++++++++++++++++ tmpl/query-parser.html | 27 ----------- 15 files changed, 180 insertions(+), 150 deletions(-) delete mode 100755 build_tmpl.sh delete mode 100755 build_tmpl_auto.sh create mode 100644 ext/bg/js/search-query-parser-generator.js delete mode 100644 ext/bg/js/templates.js create mode 100644 ext/bg/query-parser-templates.html create mode 100644 ext/mixed/js/template-handler.js delete mode 100644 tmpl/query-parser.html (limited to 'ext/bg') diff --git a/build_tmpl.sh b/build_tmpl.sh deleted file mode 100755 index e91f8de8..00000000 --- a/build_tmpl.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -handlebars tmpl/*.html -f ext/bg/js/templates.js diff --git a/build_tmpl_auto.sh b/build_tmpl_auto.sh deleted file mode 100755 index 98065cb7..00000000 --- a/build_tmpl_auto.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -DIRECTORY_TO_OBSERVE="tmpl" -BUILD_SCRIPT="build_tmpl.sh" - -function block_for_change { - inotifywait -e modify,move,create,delete $DIRECTORY_TO_OBSERVE -} - -function build { - bash $BUILD_SCRIPT -} - -build -while block_for_change; do - build -done diff --git a/ext/bg/background.html b/ext/bg/background.html index 11023221..7fd1c477 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -37,7 +37,6 @@ - diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index f4be7a0c..cd6a9d18 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -33,6 +33,10 @@ function apiClipboardGet() { return _apiInvoke('clipboardGet'); } +function apiGetQueryParserTemplatesHtml() { + return _apiInvoke('getQueryParserTemplatesHtml'); +} + function _apiInvoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 668d1fb7..529055d2 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -567,6 +567,11 @@ class Backend { return await requestText(url, 'GET'); } + async _onApiGetQueryParserTemplatesHtml() { + const url = chrome.runtime.getURL('/bg/query-parser-templates.html'); + return await requestText(url, 'GET'); + } + _onApiGetZoom(params, sender) { if (!sender || !sender.tab) { return Promise.reject(new Error('Invalid tab')); @@ -854,6 +859,7 @@ Backend._messageHandlers = new Map([ ['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)] ]); diff --git a/ext/bg/js/search-query-parser-generator.js b/ext/bg/js/search-query-parser-generator.js new file mode 100644 index 00000000..67a1ccad --- /dev/null +++ b/ext/bg/js/search-query-parser-generator.js @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2020 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +class QueryParserGenerator { + constructor() { + this._templateHandler = null; + this._initialize(); + } + + async _initialize() { + const html = await apiGetQueryParserTemplatesHtml(); + this._templateHandler = new TemplateHandler(html); + } + + createParseResult(terms, preview=false) { + const fragment = document.createDocumentFragment(); + for (const term of terms) { + const termContainer = this._templateHandler.instantiate(preview ? 'term-preview' : 'term'); + for (const segment of term) { + if (!segment.text.trim()) { continue; } + if (!segment.reading || !segment.reading.trim()) { + termContainer.appendChild(this.createSegmentText(segment.text)); + } else { + termContainer.appendChild(this.createSegment(segment)); + } + } + fragment.appendChild(termContainer); + } + return fragment; + } + + createSegment(segment) { + const segmentContainer = this._templateHandler.instantiate('segment'); + 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; + return segmentContainer; + } + + createSegmentText(text) { + const fragment = document.createDocumentFragment(); + for (const chr of text) { + const charContainer = this._templateHandler.instantiate('char'); + charContainer.innerText = chr; + fragment.appendChild(charContainer); + } + return fragment; + } + + createParserSelect(parseResults, selectedParser) { + const selectContainer = this._templateHandler.instantiate('select'); + for (const parseResult of parseResults) { + const optionContainer = this._templateHandler.instantiate('select-option'); + optionContainer.value = parseResult.id; + optionContainer.innerText = parseResult.name; + optionContainer.defaultSelected = selectedParser === parseResult.id; + selectContainer.appendChild(optionContainer); + } + return selectContainer; + } +} diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index e8e6d11f..3a93c7e7 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -19,14 +19,16 @@ class QueryParser extends TextScanner { constructor(search) { - super(document.querySelector('#query-parser'), [], [], []); + super(document.querySelector('#query-parser-content'), [], [], []); this.search = search; this.parseResults = []; this.selectedParser = null; - this.queryParser = document.querySelector('#query-parser'); - this.queryParserSelect = document.querySelector('#query-parser-select'); + this.queryParser = document.querySelector('#query-parser-content'); + this.queryParserSelect = document.querySelector('#query-parser-select-container'); + + this.queryParserGenerator = new QueryParserGenerator(); } onError(error) { @@ -64,7 +66,7 @@ class QueryParser extends TextScanner { const selectedParser = e.target.value; this.selectedParser = selectedParser; apiOptionsSet({parsing: {selectedParser}}, this.search.getOptionsContext()); - this.renderParseResult(this.getParseResult()); + this.renderParseResult(); } getMouseEventListeners() { @@ -113,13 +115,13 @@ class QueryParser extends TextScanner { async setText(text) { this.search.setSpinnerVisible(true); - await this.setPreview(text); + this.setPreview(text); this.parseResults = await this.parseText(text); this.refreshSelectedParser(); this.renderParserSelect(); - await this.renderParseResult(); + this.renderParseResult(); this.search.setSpinnerVisible(false); } @@ -146,57 +148,29 @@ class QueryParser extends TextScanner { return results; } - async setPreview(text) { + setPreview(text) { const previewTerms = []; for (let i = 0, ii = text.length; i < ii; i += 2) { const tempText = text.substring(i, i + 2); - previewTerms.push([{text: tempText.split('')}]); + previewTerms.push([{text: tempText}]); } - this.queryParser.innerHTML = await apiTemplateRender('query-parser.html', { - terms: previewTerms, - preview: true - }); + this.queryParser.textContent = ''; + this.queryParser.appendChild(this.queryParserGenerator.createParseResult(previewTerms, true)); } renderParserSelect() { this.queryParserSelect.innerHTML = ''; if (this.parseResults.length > 1) { - const select = document.createElement('select'); - select.classList.add('form-control'); - for (const parseResult of this.parseResults) { - const option = document.createElement('option'); - option.value = parseResult.id; - option.innerText = parseResult.name; - option.defaultSelected = this.selectedParser === parseResult.id; - select.appendChild(option); - } + const select = this.queryParserGenerator.createParserSelect(this.parseResults, this.selectedParser); select.addEventListener('change', this.onParserChange.bind(this)); this.queryParserSelect.appendChild(select); } } - async renderParseResult() { + renderParseResult() { const parseResult = this.getParseResult(); - if (!parseResult) { - this.queryParser.innerHTML = ''; - return; - } - - this.queryParser.innerHTML = await apiTemplateRender( - 'query-parser.html', - {terms: QueryParser.processParseResultForDisplay(parseResult.parsedText)} - ); - } - - static processParseResultForDisplay(result) { - return result.map((term) => { - return term.filter((part) => part.text.trim()).map((part) => { - return { - text: part.text.split(''), - reading: part.reading, - raw: !part.reading || !part.reading.trim() - }; - }); - }); + this.queryParser.textContent = ''; + if (!parseResult) { return; } + this.queryParser.appendChild(this.queryParserGenerator.createParseResult(parseResult.parsedText)); } } diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js deleted file mode 100644 index 2f65be31..00000000 --- a/ext/bg/js/templates.js +++ /dev/null @@ -1,55 +0,0 @@ -(function() { - var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; -templates['query-parser.html'] = template({"1":function(container,depth0,helpers,partials,data) { - var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}); - - return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.preview : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(4, data, 0),"data":data})) != null ? stack1 : "") - + ((stack1 = helpers.each.call(alias1,depth0,{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + ""; -},"2":function(container,depth0,helpers,partials,data) { - return ""; -},"4":function(container,depth0,helpers,partials,data) { - return ""; -},"6":function(container,depth0,helpers,partials,data) { - var stack1; - - return ((stack1 = container.invokePartial(partials.part,depth0,{"name":"part","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); -},"8":function(container,depth0,helpers,partials,data) { - var stack1; - - return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.raw : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.program(12, data, 0),"data":data})) != null ? stack1 : ""); -},"9":function(container,depth0,helpers,partials,data) { - var stack1; - - return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.text : depth0),{"name":"each","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); -},"10":function(container,depth0,helpers,partials,data) { - return "" - + container.escapeExpression(container.lambda(depth0, depth0)) - + ""; -},"12":function(container,depth0,helpers,partials,data) { - var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}); - - return "" - + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.text : depth0),{"name":"each","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + "" - + container.escapeExpression(((helper = (helper = helpers.reading || (depth0 != null ? depth0.reading : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"reading","hash":{},"data":data}) : helper))) - + ""; -},"14":function(container,depth0,helpers,partials,data,blockParams,depths) { - var stack1; - - return ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"preview":(depths[1] != null ? depths[1].preview : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); -},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) { - var stack1; - - return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.terms : depth0),{"name":"each","hash":{},"fn":container.program(14, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : ""); -},"main_d": function(fn, props, container, depth0, data, blockParams, depths) { - - var decorators = container.decorators; - - fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"args":["term"],"data":data}) || fn; - fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(8, data, 0, blockParams, depths),"inverse":container.noop,"args":["part"],"data":data}) || fn; - return fn; - } - -,"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true}); -})(); \ No newline at end of file diff --git a/ext/bg/query-parser-templates.html b/ext/bg/query-parser-templates.html new file mode 100644 index 00000000..f7f6d76c --- /dev/null +++ b/ext/bg/query-parser-templates.html @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/ext/bg/search.html b/ext/bg/search.html index 10e5aa8e..d6336826 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -48,8 +48,8 @@
-
-
+
+

@@ -78,7 +78,6 @@ - @@ -87,7 +86,9 @@ + + diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 57616873..b048a36c 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -1097,7 +1097,6 @@ - diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 62e62243..2714de72 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -127,12 +127,12 @@ html:root[data-yomichan-page=float] .navigation-header:not([hidden])~.navigation user-select: none; } -#query-parser { +#query-parser-content { margin-top: 0.5em; font-size: 2em; } -#query-parser[data-term-spacing=true] .query-parser-term { +#query-parser-content[data-term-spacing=true] .query-parser-term { margin-right: 0.2em; } diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 5ec93b01..0b1e7e4f 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -105,6 +105,10 @@ function apiGetDisplayTemplatesHtml() { return _apiInvoke('getDisplayTemplatesHtml'); } +function apiGetQueryParserTemplatesHtml() { + return _apiInvoke('getQueryParserTemplatesHtml'); +} + function apiGetZoom() { return _apiInvoke('getZoom'); } diff --git a/ext/mixed/js/template-handler.js b/ext/mixed/js/template-handler.js new file mode 100644 index 00000000..86e2414f --- /dev/null +++ b/ext/mixed/js/template-handler.js @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2020 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +class TemplateHandler { + constructor(html) { + this._templates = new Map(); + this._html = html; + this._doc = null; + + this._initialize(); + } + + _initialize() { + this._doc = new DOMParser().parseFromString(this._html, 'text/html'); + for (const template of this._doc.querySelectorAll('template')) { + this._setTemplate(template); + } + } + + _setTemplate(template) { + const idMatch = template.id.match(/^([a-z-]+)-template$/); + if (!idMatch) { + throw new Error(`Invalid template ID: ${template.id}`); + } + this._templates.set(idMatch[1], template); + } + + instantiate(name) { + const template = this._templates.get(name); + return document.importNode(template.content.firstChild, true); + } + + instantiateFragment(name) { + const template = this._templates.get(name); + return document.importNode(template.content, true); + } +} diff --git a/tmpl/query-parser.html b/tmpl/query-parser.html deleted file mode 100644 index db98b5ff..00000000 --- a/tmpl/query-parser.html +++ /dev/null @@ -1,27 +0,0 @@ -{{~#*inline "term"~}} -{{~#if preview~}} - -{{~else~}} - -{{~/if~}} -{{~#each this~}} -{{> part }} -{{~/each~}} - -{{~/inline~}} - -{{~#*inline "part"~}} -{{~#if raw~}} -{{~#each text~}} -{{this}} -{{~/each~}} -{{~else~}} -{{~#each text~}} -{{this}} -{{~/each~}}{{reading}} -{{~/if~}} -{{~/inline~}} - -{{~#each terms~}} -{{> term preview=../preview }} -{{~/each~}} -- cgit v1.2.3 From b7f347ff4f18a7f64267eb0d1ba1b84cbdfc4220 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Thu, 6 Feb 2020 04:25:45 +0200 Subject: remove newline from template --- ext/bg/query-parser-templates.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/query-parser-templates.html b/ext/bg/query-parser-templates.html index f7f6d76c..7cab16a9 100644 --- a/ext/bg/query-parser-templates.html +++ b/ext/bg/query-parser-templates.html @@ -5,8 +5,7 @@ - + -- cgit v1.2.3 From 9c98c631afde843f8b5ed4c54fc1f4ff0ec3a00a Mon Sep 17 00:00:00 2001 From: siikamiika Date: Mon, 10 Feb 2020 22:09:23 +0200 Subject: fix async issues --- ext/bg/js/search-query-parser-generator.js | 3 +-- ext/bg/js/search-query-parser.js | 4 ++++ ext/bg/js/search.js | 2 ++ 3 files changed, 7 insertions(+), 2 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/search-query-parser-generator.js b/ext/bg/js/search-query-parser-generator.js index 67a1ccad..8d71890b 100644 --- a/ext/bg/js/search-query-parser-generator.js +++ b/ext/bg/js/search-query-parser-generator.js @@ -20,10 +20,9 @@ class QueryParserGenerator { constructor() { this._templateHandler = null; - this._initialize(); } - async _initialize() { + async prepare() { const html = await apiGetQueryParserTemplatesHtml(); this._templateHandler = new TemplateHandler(html); } diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 3a93c7e7..f648fdd4 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -31,6 +31,10 @@ class QueryParser extends TextScanner { this.queryParserGenerator = new QueryParserGenerator(); } + async prepare() { + await this.queryParserGenerator.prepare(); + } + onError(error) { logError(error, false); } diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 4da27513..6641255f 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -49,6 +49,8 @@ class DisplaySearch extends Display { try { await this.initialize(); + await this.queryParser.prepare(); + const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); if (this.search !== null) { -- cgit v1.2.3 From 4629fb3639ec28e6c514b4a364b71e4192575b9b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 1 Feb 2020 15:00:34 -0500 Subject: Change no-undef from off to error --- .eslintrc.json | 37 +++++++++++++++++++++++++++++-- ext/bg/js/anki.js | 1 + ext/bg/js/audio.js | 1 + ext/bg/js/backend.js | 8 +++++++ ext/bg/js/context.js | 1 + ext/bg/js/database.js | 1 + ext/bg/js/dictionary.js | 1 + ext/bg/js/handlebars.js | 1 + ext/bg/js/japanese.js | 1 + ext/bg/js/options.js | 1 + ext/bg/js/search-frontend.js | 1 + ext/bg/js/search-query-parser.js | 1 + ext/bg/js/search.js | 3 +++ ext/bg/js/settings/anki-templates.js | 3 +++ ext/bg/js/settings/anki.js | 3 +++ ext/bg/js/settings/audio-ui.js | 1 + ext/bg/js/settings/audio.js | 2 ++ ext/bg/js/settings/backup.js | 4 ++++ ext/bg/js/settings/conditions-ui.js | 1 + ext/bg/js/settings/dictionaries.js | 5 +++++ ext/bg/js/settings/main.js | 8 +++++++ ext/bg/js/settings/popup-preview-frame.js | 1 + ext/bg/js/settings/profiles.js | 4 ++++ ext/bg/js/settings/storage.js | 1 + ext/bg/js/translator.js | 6 +++++ ext/mixed/js/audio.js | 1 + ext/mixed/js/display-generator.js | 1 + ext/mixed/js/display.js | 5 +++++ ext/mixed/js/text-scanner.js | 1 + 29 files changed, 103 insertions(+), 2 deletions(-) (limited to 'ext/bg') diff --git a/.eslintrc.json b/.eslintrc.json index 81fe517f..9b1a19b3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -23,7 +23,8 @@ "no-case-declarations": "error", "no-const-assign": "error", "no-constant-condition": "off", - "no-undef": "off", + "no-global-assign": "error", + "no-undef": "error", "no-unused-vars": ["error", {"vars": "local", "args": "after-used", "argsIgnorePattern": "^_", "caughtErrors": "none"}], "no-unused-expressions": "error", "no-var": "error", @@ -32,5 +33,37 @@ "quotes": ["error", "single", "avoid-escape"], "require-atomic-updates": "off", "semi": "error" - } + }, + "overrides": [ + { + "files": ["*.js"], + "excludedFiles": ["ext/mixed/js/core.js"], + "globals": { + "yomichan": "readonly", + "errorToJson": "readonly", + "jsonToError": "readonly", + "logError": "readonly", + "isObject": "readonly", + "hasOwn": "readonly", + "toIterable": "readonly", + "stringReverse": "readonly", + "promiseTimeout": "readonly", + "stringReplaceAsync": "readonly", + "EventDispatcher": "readonly", + "EXTENSION_IS_BROWSER_EDGE": "readonly" + } + }, + { + "files": ["ext/mixed/js/core.js"], + "globals": { + "chrome": "writable" + } + }, + { + "files": ["ext/bg/js/settings/*.js"], + "env": { + "jquery": true + } + } + ] } diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index 10a07061..39c6ad51 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global requestJson*/ /* * AnkiConnect diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index d1e4af9e..6389528b 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global jpIsStringEntirelyKana, audioGetFromSources*/ const audioUrlBuilders = new Map([ ['jpod101', async (definition) => { diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 529055d2..1a1dc735 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -16,6 +16,14 @@ * along with this program. If not, see . */ +/*global optionsSave, utilIsolate +conditionsTestValue, profileConditionsDescriptor, profileOptionsGetDefaultFieldTemplates +handlebarsRenderDynamic, handlebarsRenderStatic +requestText, requestJson, optionsLoad +dictConfigured, dictTermsSort, dictEnabledSet, dictNoteFormat +audioGetUrl, audioInject +jpConvertReading, jpDistributeFuriganaInflected, jpKatakanaToHiragana +Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema*/ class Backend { constructor() { diff --git a/ext/bg/js/context.js b/ext/bg/js/context.js index 37adb6b7..bec964fb 100644 --- a/ext/bg/js/context.js +++ b/ext/bg/js/context.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global apiCommandExec, apiGetEnvironmentInfo, apiOptionsGet*/ function showExtensionInfo() { const node = document.getElementById('extension-info'); diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index e87cc64b..b54d832c 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global dictFieldSplit, dictTagSanitize, JSZip*/ class Database { constructor() { diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 48f65d6c..e03dece0 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global utilSetEqual, utilSetIntersection, apiTemplateRender*/ function dictEnabledSet(options) { const dictionaries = {}; diff --git a/ext/bg/js/handlebars.js b/ext/bg/js/handlebars.js index 62f89ee4..e8cb67eb 100644 --- a/ext/bg/js/handlebars.js +++ b/ext/bg/js/handlebars.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global jpIsCharCodeKanji, jpDistributeFurigana, Handlebars*/ function handlebarsEscape(text) { return Handlebars.Utils.escapeExpression(text); diff --git a/ext/bg/js/japanese.js b/ext/bg/js/japanese.js index c45c0958..e8a6fa08 100644 --- a/ext/bg/js/japanese.js +++ b/ext/bg/js/japanese.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global wanakana*/ const JP_HALFWIDTH_KATAKANA_MAPPING = new Map([ ['ヲ', 'ヲヺ-'], diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 97032660..f9db99a2 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global utilStringHashCode*/ /* * Generic options functions diff --git a/ext/bg/js/search-frontend.js b/ext/bg/js/search-frontend.js index e453ccef..509c4009 100644 --- a/ext/bg/js/search-frontend.js +++ b/ext/bg/js/search-frontend.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global apiOptionsGet*/ async function searchFrontendSetup() { const optionsContext = { diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index f648fdd4..ff0dbaef 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global apiTermsFind, apiOptionsSet, apiTextParse, apiTextParseMecab, apiTemplateRender, TextScanner*/ class QueryParser extends TextScanner { constructor(search) { diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 6641255f..312d1de4 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -16,6 +16,9 @@ * along with this program. If not, see . */ +/*global jpIsStringPartiallyJapanese, apiOptionsSet, apiTermsFind, apiClipboardGet, apiGetEnvironmentInfo +Display, QueryParser*/ + class DisplaySearch extends Display { constructor() { super(document.querySelector('#spinner'), document.querySelector('#content')); diff --git a/ext/bg/js/settings/anki-templates.js b/ext/bg/js/settings/anki-templates.js index 5e74358f..2e80e334 100644 --- a/ext/bg/js/settings/anki-templates.js +++ b/ext/bg/js/settings/anki-templates.js @@ -16,6 +16,9 @@ * along with this program. If not, see . */ +/*global getOptionsContext, getOptionsMutable, settingsSaveOptions +profileOptionsGetDefaultFieldTemplates, ankiGetFieldMarkers, ankiGetFieldMarkersHtml, dictFieldFormat +apiOptionsGet, apiTermsFind*/ function onAnkiFieldTemplatesReset(e) { e.preventDefault(); diff --git a/ext/bg/js/settings/anki.js b/ext/bg/js/settings/anki.js index f55989fc..4263fc51 100644 --- a/ext/bg/js/settings/anki.js +++ b/ext/bg/js/settings/anki.js @@ -16,6 +16,9 @@ * along with this program. If not, see . */ +/*global getOptionsContext, getOptionsMutable, settingsSaveOptions +utilBackgroundIsolate, utilAnkiGetDeckNames, utilAnkiGetModelNames, utilAnkiGetModelFieldNames +onFormOptionsChanged*/ // Private diff --git a/ext/bg/js/settings/audio-ui.js b/ext/bg/js/settings/audio-ui.js index 711c2291..e918609e 100644 --- a/ext/bg/js/settings/audio-ui.js +++ b/ext/bg/js/settings/audio-ui.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global toIterable*/ class AudioSourceUI { static instantiateTemplate(templateSelector) { diff --git a/ext/bg/js/settings/audio.js b/ext/bg/js/settings/audio.js index cff3f521..588d9a11 100644 --- a/ext/bg/js/settings/audio.js +++ b/ext/bg/js/settings/audio.js @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +/*global getOptionsContext, getOptionsMutable, settingsSaveOptions +AudioSourceUI, audioGetTextToSpeechVoice*/ let audioSourceUI = null; diff --git a/ext/bg/js/settings/backup.js b/ext/bg/js/settings/backup.js index becdc568..6d1f28e9 100644 --- a/ext/bg/js/settings/backup.js +++ b/ext/bg/js/settings/backup.js @@ -16,6 +16,10 @@ * along with this program. If not, see . */ +/*global apiOptionsGetFull, apiGetEnvironmentInfo +utilBackend, utilIsolate, utilBackgroundIsolate, utilReadFileArrayBuffer +optionsGetDefault, optionsUpdateVersion +profileOptionsGetDefaultFieldTemplates*/ // Exporting diff --git a/ext/bg/js/settings/conditions-ui.js b/ext/bg/js/settings/conditions-ui.js index 4d041451..5a271321 100644 --- a/ext/bg/js/settings/conditions-ui.js +++ b/ext/bg/js/settings/conditions-ui.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global conditionsNormalizeOptionValue*/ class ConditionsUI { static instantiateTemplate(templateSelector) { diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index 0d827f49..c80aac73 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -16,6 +16,11 @@ * along with this program. If not, see . */ +/*global getOptionsContext, getOptionsMutable, getOptionsFullMutable, settingsSaveOptions, apiOptionsGetFull +utilBackgroundIsolate, utilDatabaseDeleteDictionary, utilDatabaseGetDictionaryInfo, utilDatabaseGetDictionaryCounts +utilDatabasePurge, utilDatabaseImport +storageUpdateStats, storageEstimate +PageExitPrevention*/ let dictionaryUI = null; diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index cf0f08db..1ba4a7ef 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -16,6 +16,14 @@ * along with this program. If not, see . */ +/*global getOptionsContext, apiOptionsSave +utilBackend, utilIsolate, utilBackgroundIsolate +ankiErrorShown, ankiFieldsToDict +ankiTemplatesUpdateValue, onAnkiOptionsChanged, onDictionaryOptionsChanged +appearanceInitialize, audioSettingsInitialize, profileOptionsSetup, dictSettingsInitialize +ankiInitialize, ankiTemplatesInitialize, storageInfoInitialize +*/ + function getOptionsMutable(optionsContext) { return utilBackend().getOptions( utilBackgroundIsolate(optionsContext) diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 37a4b416..042f335f 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global apiOptionsGet, Popup, PopupProxyHost, Frontend, TextSourceRange*/ class SettingsPopupPreview { constructor() { diff --git a/ext/bg/js/settings/profiles.js b/ext/bg/js/settings/profiles.js index c4e68b53..3e589809 100644 --- a/ext/bg/js/settings/profiles.js +++ b/ext/bg/js/settings/profiles.js @@ -16,6 +16,10 @@ * along with this program. If not, see . */ +/*global getOptionsMutable, getOptionsFullMutable, settingsSaveOptions, apiOptionsGetFull +utilBackgroundIsolate, formWrite +conditionsClearCaches, ConditionsUI, profileConditionsDescriptor*/ + let currentProfileIndex = 0; let profileConditionsContainer = null; diff --git a/ext/bg/js/settings/storage.js b/ext/bg/js/settings/storage.js index 6c10f665..cbe1bb4d 100644 --- a/ext/bg/js/settings/storage.js +++ b/ext/bg/js/settings/storage.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global apiGetEnvironmentInfo*/ function storageBytesToLabeledString(size) { const base = 1000; diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index d32f8c67..81c2464b 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -16,6 +16,12 @@ * along with this program. If not, see . */ +/*global requestJson +dictTermsMergeBySequence, dictTagBuildSource, dictTermsMergeByGloss, dictTermsSort, dictTagsSort +dictEnabledSet, dictTermsGroup, dictTermsCompressTags, dictTermsUndupe, dictTagSanitize +jpDistributeFurigana, jpConvertHalfWidthKanaToFullWidth, jpConvertNumericTofullWidth +jpConvertAlphabeticToKana, jpHiraganaToKatakana, jpKatakanaToHiragana, jpIsCharCodeJapanese +Database, Deinflector*/ class Translator { constructor() { diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js index b0c5fa82..76a3e7da 100644 --- a/ext/mixed/js/audio.js +++ b/ext/mixed/js/audio.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global apiAudioGetUrl*/ class TextToSpeechAudio { constructor(text, voice) { diff --git a/ext/mixed/js/display-generator.js b/ext/mixed/js/display-generator.js index d5ab9dbc..3617e546 100644 --- a/ext/mixed/js/display-generator.js +++ b/ext/mixed/js/display-generator.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global apiGetDisplayTemplatesHtml*/ class DisplayGenerator { constructor() { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index a6cfe848..b18e275d 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -16,6 +16,11 @@ * along with this program. If not, see . */ +/*global docRangeFromPoint, docSentenceExtract +apiKanjiFind, apiTermsFind, apiNoteView, apiOptionsGet, apiDefinitionsAddable, apiDefinitionAdd +apiScreenshotGet, apiForward +audioPrepareTextToSpeech, audioGetFromSources +DisplayGenerator, WindowScroll, DisplayContext, DOM*/ class Display { constructor(spinner, container) { diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index 88f1e27a..e6da1e5e 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global docRangeFromPoint, TextSourceRange, DOM*/ class TextScanner { constructor(node, ignoreNodes, ignoreElements, ignorePoints) { -- cgit v1.2.3 From ab9d2b38e04c9909d0a7f4cbb28fe069a6515b2a Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 1 Feb 2020 17:27:48 -0500 Subject: Remove redundant declaration --- ext/bg/js/settings/audio-ui.js | 2 -- 1 file changed, 2 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/audio-ui.js b/ext/bg/js/settings/audio-ui.js index e918609e..555380b4 100644 --- a/ext/bg/js/settings/audio-ui.js +++ b/ext/bg/js/settings/audio-ui.js @@ -16,8 +16,6 @@ * along with this program. If not, see . */ -/*global toIterable*/ - class AudioSourceUI { static instantiateTemplate(templateSelector) { const template = document.querySelector(templateSelector); -- cgit v1.2.3 From e2ac478cb7635cbd15d0033bd2c486689a7b4e67 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 11 Feb 2020 22:19:47 -0500 Subject: Define more globals --- .eslintrc.json | 1 + ext/bg/js/backend.js | 2 +- ext/bg/js/clipboard-monitor.js | 1 + ext/bg/js/search-query-parser-generator.js | 1 + ext/bg/js/search-query-parser.js | 2 +- ext/bg/js/search.js | 2 +- ext/bg/js/settings/dictionaries.js | 2 +- ext/fg/js/document.js | 1 + ext/fg/js/float.js | 1 + ext/fg/js/frontend-initialize.js | 1 + ext/fg/js/frontend.js | 1 + ext/fg/js/popup-nested.js | 1 + ext/fg/js/popup-proxy-host.js | 1 + ext/fg/js/popup-proxy.js | 1 + ext/fg/js/popup.js | 1 + 15 files changed, 15 insertions(+), 4 deletions(-) (limited to 'ext/bg') diff --git a/.eslintrc.json b/.eslintrc.json index 9b1a19b3..f502479d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -49,6 +49,7 @@ "stringReverse": "readonly", "promiseTimeout": "readonly", "stringReplaceAsync": "readonly", + "parseUrl": "readonly", "EventDispatcher": "readonly", "EXTENSION_IS_BROWSER_EDGE": "readonly" } diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 1a1dc735..7b2ec46d 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -23,7 +23,7 @@ requestText, requestJson, optionsLoad dictConfigured, dictTermsSort, dictEnabledSet, dictNoteFormat audioGetUrl, audioInject jpConvertReading, jpDistributeFuriganaInflected, jpKatakanaToHiragana -Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema*/ +Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/ class Backend { constructor() { diff --git a/ext/bg/js/clipboard-monitor.js b/ext/bg/js/clipboard-monitor.js index b4a27fa2..c2f41385 100644 --- a/ext/bg/js/clipboard-monitor.js +++ b/ext/bg/js/clipboard-monitor.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global apiClipboardGet, jpIsStringPartiallyJapanese*/ class ClipboardMonitor { constructor() { diff --git a/ext/bg/js/search-query-parser-generator.js b/ext/bg/js/search-query-parser-generator.js index 8d71890b..f842644e 100644 --- a/ext/bg/js/search-query-parser-generator.js +++ b/ext/bg/js/search-query-parser-generator.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global apiGetQueryParserTemplatesHtml, TemplateHandler*/ class QueryParserGenerator { constructor() { diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index ff0dbaef..49c0bb34 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -/*global apiTermsFind, apiOptionsSet, apiTextParse, apiTextParseMecab, apiTemplateRender, TextScanner*/ +/*global apiTermsFind, apiOptionsSet, apiTextParse, apiTextParseMecab, apiTemplateRender, TextScanner, QueryParserGenerator*/ class QueryParser extends TextScanner { constructor(search) { diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 312d1de4..50ec146f 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -17,7 +17,7 @@ */ /*global jpIsStringPartiallyJapanese, apiOptionsSet, apiTermsFind, apiClipboardGet, apiGetEnvironmentInfo -Display, QueryParser*/ +Display, QueryParser, ClipboardMonitor*/ class DisplaySearch extends Display { constructor() { diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index c80aac73..fb459404 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -/*global getOptionsContext, getOptionsMutable, getOptionsFullMutable, settingsSaveOptions, apiOptionsGetFull +/*global getOptionsContext, getOptionsMutable, getOptionsFullMutable, settingsSaveOptions, apiOptionsGetFull, apiOptionsGet utilBackgroundIsolate, utilDatabaseDeleteDictionary, utilDatabaseGetDictionaryInfo, utilDatabaseGetDictionaryCounts utilDatabasePurge, utilDatabaseImport storageUpdateStats, storageEstimate diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js index 71654b29..7284cdd1 100644 --- a/ext/fg/js/document.js +++ b/ext/fg/js/document.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global TextSourceElement, TextSourceRange, DOM*/ const REGEX_TRANSPARENT_COLOR = /rgba\s*\([^)]*,\s*0(?:\.0+)?\s*\)/; diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 8d61d8f6..d31b8336 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global popupNestedInitialize, Display*/ class DisplayFloat extends Display { constructor() { diff --git a/ext/fg/js/frontend-initialize.js b/ext/fg/js/frontend-initialize.js index 9c923fea..c32e97d4 100644 --- a/ext/fg/js/frontend-initialize.js +++ b/ext/fg/js/frontend-initialize.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global PopupProxyHost, PopupProxy, Frontend*/ async function main() { const data = window.frontendInitializationData || {}; diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 2286bf19..3611d44e 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global apiGetZoom, apiOptionsGet, apiTermsFind, apiKanjiFind, docSentenceExtract, TextScanner*/ class Frontend extends TextScanner { constructor(popup, ignoreNodes) { diff --git a/ext/fg/js/popup-nested.js b/ext/fg/js/popup-nested.js index 3f3c945e..3e5f5b80 100644 --- a/ext/fg/js/popup-nested.js +++ b/ext/fg/js/popup-nested.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global apiOptionsGet*/ let popupNestedInitialized = false; diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index 427172c6..98729796 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global apiFrameInformationGet, FrontendApiReceiver, Popup*/ class PopupProxyHost { constructor() { diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index 63aa6bbe..db6dffb1 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global FrontendApiSender*/ class PopupProxy { constructor(depth, parentId, parentFrameId, url) { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index e7dae93e..0b142dda 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +/*global apiInjectStylesheet*/ class Popup { constructor(id, depth, frameIdPromise) { -- cgit v1.2.3 From c8e7a1543ee52943157ff54b2a9983ad20deaa15 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 11 Feb 2020 22:25:48 -0500 Subject: Remove unused --- ext/bg/js/search-query-parser.js | 2 +- ext/bg/js/search.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 49c0bb34..3d38e6e8 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -/*global apiTermsFind, apiOptionsSet, apiTextParse, apiTextParseMecab, apiTemplateRender, TextScanner, QueryParserGenerator*/ +/*global apiTermsFind, apiOptionsSet, apiTextParse, apiTextParseMecab, TextScanner, QueryParserGenerator*/ class QueryParser extends TextScanner { constructor(search) { diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 50ec146f..93bcfa53 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -16,8 +16,7 @@ * along with this program. If not, see . */ -/*global jpIsStringPartiallyJapanese, apiOptionsSet, apiTermsFind, apiClipboardGet, apiGetEnvironmentInfo -Display, QueryParser, ClipboardMonitor*/ +/*global apiOptionsSet, apiTermsFind, Display, QueryParser, ClipboardMonitor*/ class DisplaySearch extends Display { constructor() { -- cgit v1.2.3 From df37acd17f9459c17185552f11dc4cc424e01958 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Thu, 13 Feb 2020 01:59:26 +0200 Subject: rename display initialize methods to prepare --- ext/bg/js/search.js | 2 +- ext/fg/js/float.js | 30 +++++++++++++++--------------- ext/mixed/js/display.js | 24 ++++++++++++------------ 3 files changed, 28 insertions(+), 28 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 6641255f..86ed675a 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -47,7 +47,7 @@ class DisplaySearch extends Display { async prepare() { try { - await this.initialize(); + await super.prepare(); await this.queryParser.prepare(); diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 8d61d8f6..3766d5a4 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -33,6 +33,20 @@ class DisplayFloat extends Display { window.addEventListener('message', (e) => this.onMessage(e), false); } + async prepare(options, popupInfo, url, childrenSupported, scale) { + await super.prepare(options); + + const {id, depth, parentFrameId} = popupInfo; + this.optionsContext.depth = depth; + this.optionsContext.url = url; + + if (childrenSupported) { + popupNestedInitialize(id, depth, parentFrameId, url); + } + + this.setContentScale(scale); + } + onError(error) { if (this._orphaned) { this.setContent('orphaned'); @@ -92,20 +106,6 @@ class DisplayFloat extends Display { setContentScale(scale) { document.body.style.fontSize = `${scale}em`; } - - async initialize(options, popupInfo, url, childrenSupported, scale) { - await super.initialize(options); - - const {id, depth, parentFrameId} = popupInfo; - this.optionsContext.depth = depth; - this.optionsContext.url = url; - - if (childrenSupported) { - popupNestedInitialize(id, depth, parentFrameId, url); - } - - this.setContentScale(scale); - } } DisplayFloat._onKeyDownHandlers = new Map([ @@ -122,7 +122,7 @@ DisplayFloat._messageHandlers = new Map([ ['setContent', (self, {type, details}) => self.setContent(type, details)], ['clearAutoPlayTimer', (self) => self.clearAutoPlayTimer()], ['setCustomCss', (self, {css}) => self.setCustomCss(css)], - ['initialize', (self, {options, popupInfo, url, childrenSupported, scale}) => self.initialize(options, popupInfo, url, childrenSupported, scale)], + ['initialize', (self, {options, popupInfo, url, childrenSupported, scale}) => self.prepare(options, popupInfo, url, childrenSupported, scale)], ['setContentScale', (self, {scale}) => self.setContentScale(scale)] ]); diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index cee63d9b..8dea625d 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -43,6 +43,16 @@ class Display { this.setInteractive(true); } + async prepare(options=null) { + await this.displayGenerator.prepare(); + await this.updateOptions(options); + yomichan.on('optionsUpdate', () => this.updateOptions(null)); + } + + isPrepared() { + return this.options !== null; + } + onError(_error) { throw new Error('Override me'); } @@ -238,16 +248,6 @@ class Display { throw new Error('Override me'); } - isInitialized() { - return this.options !== null; - } - - async initialize(options=null) { - await this.displayGenerator.prepare(); - await this.updateOptions(options); - yomichan.on('optionsUpdate', () => this.updateOptions(null)); - } - async updateOptions(options) { this.options = options ? options : await apiOptionsGet(this.getOptionsContext()); this.updateDocumentOptions(this.options); @@ -358,7 +358,7 @@ class Display { async setContentTerms(definitions, context, token) { if (!context) { throw new Error('Context expected'); } - if (!this.isInitialized()) { return; } + if (!this.isPrepared()) { return; } this.setEventListenersActive(false); @@ -419,7 +419,7 @@ class Display { async setContentKanji(definitions, context, token) { if (!context) { throw new Error('Context expected'); } - if (!this.isInitialized()) { return; } + if (!this.isPrepared()) { return; } this.setEventListenersActive(false); -- cgit v1.2.3 From d7e1ef01d8af4a315a31364eb5138e24a132ea1e Mon Sep 17 00:00:00 2001 From: siikamiika Date: Thu, 13 Feb 2020 16:26:45 +0200 Subject: use Promise.all to await dependencies --- ext/bg/js/search.js | 6 +++--- ext/mixed/js/display.js | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 86ed675a..bb27205c 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -47,9 +47,9 @@ class DisplaySearch extends Display { async prepare() { try { - await super.prepare(); - - await this.queryParser.prepare(); + const superPromise = super.prepare(); + const queryParserPromise = this.queryParser.prepare(); + await Promise.all([superPromise, queryParserPromise]); const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index b524fe34..d49c205e 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -44,8 +44,9 @@ class Display { } async prepare(options=null) { - await this.displayGenerator.prepare(); - await this.updateOptions(options); + const displayGeneratorPromise = this.displayGenerator.prepare(); + const updateOptionsPromise = this.updateOptions(options); + await Promise.all([displayGeneratorPromise, updateOptionsPromise]); yomichan.on('optionsUpdate', () => this.updateOptions(null)); } -- cgit v1.2.3 From e645296b1b5e993355844f2cb2b026b87d05759e Mon Sep 17 00:00:00 2001 From: siikamiika Date: Fri, 14 Feb 2020 01:12:28 +0200 Subject: fix Map set in Translator.buildTermFrequencies --- ext/bg/js/translator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index 81c2464b..6d5dd50a 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -460,7 +460,7 @@ class Translator { termList = []; expressionsUnique.push(expression); termsUnique.push(termList); - termsUniqueMap[expression] = termList; + termsUniqueMap.set(expression, termList); } termList.push(term); -- cgit v1.2.3 From e3c871bc004d9ca612d908f0f3848cdcfc286cd3 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 13 Feb 2020 20:18:15 -0500 Subject: Remove unused handlebarsRenderStatic --- ext/bg/js/api.js | 4 ++-- ext/bg/js/backend.js | 10 +++------- ext/bg/js/dictionary.js | 2 +- ext/bg/js/handlebars.js | 5 ----- ext/mixed/js/api.js | 4 ---- 5 files changed, 6 insertions(+), 19 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index cd6a9d18..4f1595c7 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -17,8 +17,8 @@ */ -function apiTemplateRender(template, data, dynamic) { - return _apiInvoke('templateRender', {data, template, dynamic}); +function apiTemplateRender(template, data) { + return _apiInvoke('templateRender', {data, template}); } function apiAudioGetUrl(definition, source, optionsContext) { diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 7b2ec46d..da50bade 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -18,7 +18,7 @@ /*global optionsSave, utilIsolate conditionsTestValue, profileConditionsDescriptor, profileOptionsGetDefaultFieldTemplates -handlebarsRenderDynamic, handlebarsRenderStatic +handlebarsRenderDynamic requestText, requestJson, optionsLoad dictConfigured, dictTermsSort, dictEnabledSet, dictNoteFormat audioGetUrl, audioInject @@ -459,12 +459,8 @@ class Backend { return this.anki.guiBrowse(`nid:${noteId}`); } - async _onApiTemplateRender({template, data, dynamic}) { - return ( - dynamic ? - handlebarsRenderDynamic(template, data) : - handlebarsRenderStatic(template, data) - ); + async _onApiTemplateRender({template, data}) { + return handlebarsRenderDynamic(template, data); } async _onApiCommandExec({command, params}) { diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index e03dece0..491632a0 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -335,7 +335,7 @@ async function dictFieldFormat(field, definition, mode, options, templates, exce } data.marker = marker; try { - return await apiTemplateRender(templates, data, true); + return await apiTemplateRender(templates, data); } catch (e) { if (exceptions) { exceptions.push(e); } return `{${marker}-render-error}`; diff --git a/ext/bg/js/handlebars.js b/ext/bg/js/handlebars.js index e8cb67eb..b1443447 100644 --- a/ext/bg/js/handlebars.js +++ b/ext/bg/js/handlebars.js @@ -135,11 +135,6 @@ function handlebarsRegisterHelpers() { } } -function handlebarsRenderStatic(name, data) { - handlebarsRegisterHelpers(); - return Handlebars.templates[name](data).trim(); -} - function handlebarsRenderDynamic(template, data) { handlebarsRegisterHelpers(); const cache = handlebarsRenderDynamic._cache; diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 0b1e7e4f..8eea0628 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -65,10 +65,6 @@ function apiNoteView(noteId) { return _apiInvoke('noteView', {noteId}); } -function apiTemplateRender(template, data, dynamic) { - return _apiInvoke('templateRender', {data, template, dynamic}); -} - function apiAudioGetUrl(definition, source, optionsContext) { return _apiInvoke('audioGetUrl', {definition, source, optionsContext}); } -- cgit v1.2.3 From 0e6b75438a88456eb0a19bf0fc54bae5d2ec4485 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 13 Feb 2020 20:24:54 -0500 Subject: Use Map for Translator.tagCache --- ext/bg/js/translator.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index 6d5dd50a..3471cb01 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -27,7 +27,7 @@ class Translator { constructor() { this.database = null; this.deinflector = null; - this.tagCache = {}; + this.tagCache = new Map(); } async prepare() { @@ -44,12 +44,12 @@ class Translator { } async purgeDatabase() { - this.tagCache = {}; + this.tagCache.clear(); await this.database.purge(); } async deleteDictionary(dictionaryName) { - this.tagCache = {}; + this.tagCache.clear(); await this.database.deleteDictionary(dictionaryName); } @@ -537,22 +537,22 @@ class Translator { async getTagMetaList(names, title) { const tagMetaList = []; - const cache = ( - hasOwn(this.tagCache, title) ? - this.tagCache[title] : - (this.tagCache[title] = {}) - ); + let cache = this.tagCache.get(title); + if (typeof cache === 'undefined') { + cache = new Map(); + this.tagCache.set(title, cache); + } for (const name of names) { const base = Translator.getNameBase(name); - if (hasOwn(cache, base)) { - tagMetaList.push(cache[base]); - } else { - const tagMeta = await this.database.findTagForTitle(base, title); - cache[base] = tagMeta; - tagMetaList.push(tagMeta); + let tagMeta = cache.get(base); + if (typeof tagMeta === 'undefined') { + tagMeta = await this.database.findTagForTitle(base, title); + cache.set(base, tagMeta); } + + tagMetaList.push(tagMeta); } return tagMetaList; -- cgit v1.2.3 From 4dd4926672b4db39721c10a3941246ab1225f988 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 14 Feb 2020 20:51:10 -0500 Subject: Remove unused apiGetDisplayTemplatesHtml and apiGetQueryParserTemplatesHtml --- ext/bg/js/api.js | 8 -------- 1 file changed, 8 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 4f1595c7..0c244ffa 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -25,18 +25,10 @@ function apiAudioGetUrl(definition, source, optionsContext) { return _apiInvoke('audioGetUrl', {definition, source, optionsContext}); } -function apiGetDisplayTemplatesHtml() { - return _apiInvoke('getDisplayTemplatesHtml'); -} - function apiClipboardGet() { return _apiInvoke('clipboardGet'); } -function apiGetQueryParserTemplatesHtml() { - return _apiInvoke('getQueryParserTemplatesHtml'); -} - function _apiInvoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { -- cgit v1.2.3 From faf15c08aa6777d1ae2d112c55659e08d53156c6 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 14 Feb 2020 22:34:44 -0500 Subject: Rename optionsUpdate event to optionsUpdated Past tense better indicates that the options were changed, but no data is being included as part of the event. It is also more consistent with the other event names the yomichan object currently provides. --- ext/bg/js/backend.js | 2 +- ext/bg/js/settings/main.js | 4 ++-- ext/fg/js/frontend.js | 2 +- ext/mixed/js/core.js | 6 +++--- ext/mixed/js/display.js | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index da50bade..d1a34f82 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -88,7 +88,7 @@ class Backend { const callback = () => this.checkLastError(chrome.runtime.lastError); chrome.tabs.query({}, (tabs) => { for (const tab of tabs) { - chrome.tabs.sendMessage(tab.id, {action: 'optionsUpdate', params: {source}}, callback); + chrome.tabs.sendMessage(tab.id, {action: 'optionsUpdated', params: {source}}, callback); } }); } diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 1ba4a7ef..c6683427 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -242,7 +242,7 @@ async function settingsSaveOptions() { await apiOptionsSave(source); } -async function onOptionsUpdate({source}) { +async function onOptionsUpdated({source}) { const thisSource = await settingsGetSource(); if (source === thisSource) { return; } @@ -274,7 +274,7 @@ async function onReady() { storageInfoInitialize(); - yomichan.on('optionsUpdate', onOptionsUpdate); + yomichan.on('optionsUpdated', onOptionsUpdated); } $(document).ready(() => onReady()); diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index a620fb03..c3050514 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -56,7 +56,7 @@ class Frontend extends TextScanner { } yomichan.on('orphaned', () => this.onOrphaned()); - yomichan.on('optionsUpdate', () => this.updateOptions()); + yomichan.on('optionsUpdated', () => this.updateOptions()); yomichan.on('zoomChanged', (e) => this.onZoomChanged(e)); chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index c273bb87..2dd9cf40 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -248,7 +248,7 @@ const yomichan = (() => { this._messageHandlers = new Map([ ['getUrl', this._onMessageGetUrl.bind(this)], - ['optionsUpdate', this._onMessageOptionsUpdate.bind(this)], + ['optionsUpdated', this._onMessageOptionsUpdated.bind(this)], ['zoomChanged', this._onMessageZoomChanged.bind(this)] ]); @@ -276,8 +276,8 @@ const yomichan = (() => { return {url: window.location.href}; } - _onMessageOptionsUpdate({source}) { - this.trigger('optionsUpdate', {source}); + _onMessageOptionsUpdated({source}) { + this.trigger('optionsUpdated', {source}); } _onMessageZoomChanged({oldZoomFactor, newZoomFactor}) { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 90b7aaf3..045ce906 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -52,7 +52,7 @@ class Display { const displayGeneratorPromise = this.displayGenerator.prepare(); const updateOptionsPromise = this.updateOptions(options); await Promise.all([displayGeneratorPromise, updateOptionsPromise]); - yomichan.on('optionsUpdate', () => this.updateOptions(null)); + yomichan.on('optionsUpdated', () => this.updateOptions(null)); } onError(_error) { -- cgit v1.2.3 From 070ae70f7cd635f2b8742a6c406d1ef7b09ccc51 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 16:33:48 -0500 Subject: Create EventListenerCollection class --- .eslintrc.json | 1 + ext/bg/js/settings/dictionaries.js | 24 ++++------------ ext/mixed/js/core.js | 23 +++++++++++++++ ext/mixed/js/display.js | 59 ++++++++++++++++++-------------------- ext/mixed/js/text-scanner.js | 24 ++++------------ 5 files changed, 64 insertions(+), 67 deletions(-) (limited to 'ext/bg') diff --git a/.eslintrc.json b/.eslintrc.json index d4ae215b..bda308e2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -51,6 +51,7 @@ "stringReplaceAsync": "readonly", "parseUrl": "readonly", "EventDispatcher": "readonly", + "EventListenerCollection": "readonly", "EXTENSION_IS_BROWSER_EDGE": "readonly" } }, diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index fb459404..adad76fb 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -179,7 +179,7 @@ class SettingsDictionaryEntryUI { this.dictionaryInfo = dictionaryInfo; this.optionsDictionary = optionsDictionary; this.counts = null; - this.eventListeners = []; + this.eventListeners = new EventListenerCollection(); this.isDeleting = false; this.content = content; @@ -198,10 +198,10 @@ class SettingsDictionaryEntryUI { this.applyValues(); - this.addEventListener(this.enabledCheckbox, 'change', (e) => this.onEnabledChanged(e), false); - this.addEventListener(this.allowSecondarySearchesCheckbox, 'change', (e) => this.onAllowSecondarySearchesChanged(e), false); - this.addEventListener(this.priorityInput, 'change', (e) => this.onPriorityChanged(e), false); - this.addEventListener(this.deleteButton, 'click', (e) => this.onDeleteButtonClicked(e), false); + 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); } cleanup() { @@ -212,7 +212,7 @@ class SettingsDictionaryEntryUI { this.content = null; } this.dictionaryInfo = null; - this.clearEventListeners(); + this.eventListeners.removeAllEventListeners(); } setCounts(counts) { @@ -229,18 +229,6 @@ class SettingsDictionaryEntryUI { this.parent.save(); } - addEventListener(node, type, listener, options) { - node.addEventListener(type, listener, options); - this.eventListeners.push([node, type, listener, options]); - } - - clearEventListeners() { - for (const [node, type, listener, options] of this.eventListeners) { - node.removeEventListener(type, listener, options); - } - this.eventListeners = []; - } - applyValues() { this.enabledCheckbox.checked = this.optionsDictionary.enabled; this.allowSecondarySearchesCheckbox.checked = this.optionsDictionary.allowSecondarySearches; diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index b6ecb48b..330a30fb 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -236,6 +236,29 @@ class EventDispatcher { } } +class EventListenerCollection { + constructor() { + this._eventListeners = []; + } + + get size() { + return this._eventListeners.length; + } + + addEventListener(node, type, listener, options) { + node.addEventListener(type, listener, options); + this._eventListeners.push([node, type, listener, options]); + } + + removeAllEventListeners() { + if (this._eventListeners.length === 0) { return; } + for (const [node, type, listener, options] of this._eventListeners) { + node.removeEventListener(type, listener, options); + } + this._eventListeners = []; + } +} + /* * Default message handlers diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index e914b082..8113260c 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -35,8 +35,8 @@ class Display { this.audioCache = new Map(); this.styleNode = null; - this.eventListeners = []; - this.persistentEventListeners = []; + this.eventListeners = new EventListenerCollection(); + this.persistentEventListeners = new EventListenerCollection(); this.interactive = false; this.eventListenersActive = false; this.clickScanPrevent = false; @@ -301,13 +301,23 @@ class Display { this.interactive = interactive; if (interactive) { - Display.addEventListener(this.persistentEventListeners, document, 'keydown', this.onKeyDown.bind(this), false); - Display.addEventListener(this.persistentEventListeners, document, 'wheel', this.onWheel.bind(this), {passive: false}); - Display.addEventListener(this.persistentEventListeners, document.querySelector('.action-previous'), 'click', this.onSourceTermView.bind(this)); - Display.addEventListener(this.persistentEventListeners, document.querySelector('.action-next'), 'click', this.onNextTermView.bind(this)); - Display.addEventListener(this.persistentEventListeners, document.querySelector('.navigation-header'), 'wheel', this.onHistoryWheel.bind(this), {passive: false}); + const actionPrevious = document.querySelector('.action-previous'); + const actionNext = document.querySelector('.action-next'); + 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}); + if (actionPrevious !== null) { + this.persistentEventListeners.addEventListener(actionPrevious, 'click', this.onSourceTermView.bind(this)); + } + 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}); + } } else { - Display.clearEventListeners(this.persistentEventListeners); + this.persistentEventListeners.removeAllEventListeners(); } this.setEventListenersActive(this.eventListenersActive); } @@ -318,23 +328,23 @@ class Display { this.eventListenersActive = active; if (active) { - this.addEventListeners('.action-add-note', 'click', this.onNoteAdd.bind(this)); - this.addEventListeners('.action-view-note', 'click', this.onNoteView.bind(this)); - this.addEventListeners('.action-play-audio', 'click', this.onAudioPlay.bind(this)); - this.addEventListeners('.kanji-link', 'click', this.onKanjiLookup.bind(this)); + this.addMultipleEventListeners('.action-add-note', 'click', this.onNoteAdd.bind(this)); + this.addMultipleEventListeners('.action-view-note', 'click', this.onNoteView.bind(this)); + this.addMultipleEventListeners('.action-play-audio', 'click', this.onAudioPlay.bind(this)); + this.addMultipleEventListeners('.kanji-link', 'click', this.onKanjiLookup.bind(this)); if (this.options.scanning.enablePopupSearch) { - this.addEventListeners('.term-glossary-item, .tag', 'mouseup', this.onGlossaryMouseUp.bind(this)); - this.addEventListeners('.term-glossary-item, .tag', 'mousedown', this.onGlossaryMouseDown.bind(this)); - this.addEventListeners('.term-glossary-item, .tag', 'mousemove', this.onGlossaryMouseMove.bind(this)); + this.addMultipleEventListeners('.term-glossary-item, .tag', 'mouseup', this.onGlossaryMouseUp.bind(this)); + this.addMultipleEventListeners('.term-glossary-item, .tag', 'mousedown', this.onGlossaryMouseDown.bind(this)); + this.addMultipleEventListeners('.term-glossary-item, .tag', 'mousemove', this.onGlossaryMouseMove.bind(this)); } } else { - Display.clearEventListeners(this.eventListeners); + this.eventListeners.removeAllEventListeners(); } } - addEventListeners(selector, type, listener, options) { + addMultipleEventListeners(selector, type, listener, options) { for (const node of this.container.querySelectorAll(selector)) { - Display.addEventListener(this.eventListeners, node, type, listener, options); + this.eventListeners.addEventListener(node, type, listener, options); } } @@ -793,19 +803,6 @@ class Display { return -1; } - static addEventListener(eventListeners, object, type, listener, options) { - if (object === null) { return; } - object.addEventListener(type, listener, options); - eventListeners.push([object, type, listener, options]); - } - - static clearEventListeners(eventListeners) { - for (const [object, type, listener, options] of eventListeners) { - object.removeEventListener(type, listener, options); - } - eventListeners.length = 0; - } - static getElementTop(element) { const elementRect = element.getBoundingClientRect(); const documentRect = document.documentElement.getBoundingClientRect(); diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index e6da1e5e..aa10bbaf 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -31,7 +31,7 @@ class TextScanner { this.options = null; this.enabled = false; - this.eventListeners = []; + this.eventListeners = new EventListenerCollection(); this.primaryTouchIdentifier = null; this.preventNextContextMenu = false; @@ -229,7 +229,7 @@ class TextScanner { } } else { if (this.enabled) { - this.clearEventListeners(); + this.eventListeners.removeAllEventListeners(); this.enabled = false; } this.onSearchClear(false); @@ -237,13 +237,13 @@ class TextScanner { } hookEvents() { - let eventListeners = this.getMouseEventListeners(); + let eventListenerInfos = this.getMouseEventListeners(); if (this.options.scanning.touchInputEnabled) { - eventListeners = eventListeners.concat(this.getTouchEventListeners()); + eventListenerInfos = eventListenerInfos.concat(this.getTouchEventListeners()); } - for (const [node, type, listener, options] of eventListeners) { - this.addEventListener(node, type, listener, options); + for (const [node, type, listener, options] of eventListenerInfos) { + this.eventListeners.addEventListener(node, type, listener, options); } } @@ -268,18 +268,6 @@ class TextScanner { ]; } - addEventListener(node, type, listener, options) { - node.addEventListener(type, listener, options); - this.eventListeners.push([node, type, listener, options]); - } - - clearEventListeners() { - for (const [node, type, listener, options] of this.eventListeners) { - node.removeEventListener(type, listener, options); - } - this.eventListeners = []; - } - setOptions(options) { this.options = options; this.setEnabled(this.options.general.enable); -- cgit v1.2.3 From dcd243c9e9f3af3fedb0bb0db9795f377f175587 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 17:27:55 -0500 Subject: Update how popups are created --- ext/bg/js/settings/popup-preview-frame.js | 2 +- ext/fg/js/frontend-initialize.js | 11 ++++-- ext/fg/js/popup-proxy-host.js | 64 +++++++++++++++++++++---------- ext/fg/js/popup-proxy.js | 6 +-- ext/fg/js/popup.js | 8 ++++ 5 files changed, 62 insertions(+), 29 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 042f335f..8fd06222 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -50,7 +50,7 @@ class SettingsPopupPreview { const popupHost = new PopupProxyHost(); await popupHost.prepare(); - const popup = popupHost.createPopup(null, 0); + const popup = popupHost.getOrCreatePopup(); popup.setChildrenSupported(false); this.frontend = new Frontend(popup); diff --git a/ext/fg/js/frontend-initialize.js b/ext/fg/js/frontend-initialize.js index c32e97d4..54b874f2 100644 --- a/ext/fg/js/frontend-initialize.js +++ b/ext/fg/js/frontend-initialize.js @@ -22,13 +22,16 @@ async function main() { const data = window.frontendInitializationData || {}; const {id, depth=0, parentFrameId, ignoreNodes, url, proxy=false} = data; - let popupHost = null; - if (!proxy) { - popupHost = new PopupProxyHost(); + let popup; + if (proxy) { + popup = new PopupProxy(null, depth + 1, id, parentFrameId, url); + } else { + const popupHost = new PopupProxyHost(); await popupHost.prepare(); + + popup = popupHost.getOrCreatePopup(); } - const popup = proxy ? new PopupProxy(depth + 1, id, parentFrameId, url) : popupHost.createPopup(null, depth); const frontend = new Frontend(popup, ignoreNodes); await frontend.prepare(); } diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index 98729796..e55801ff 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -34,7 +34,7 @@ class PopupProxyHost { if (typeof frameId !== 'number') { return; } this._apiReceiver = new FrontendApiReceiver(`popup-proxy-host#${frameId}`, new Map([ - ['createNestedPopup', ({parentId}) => this._onApiCreateNestedPopup(parentId)], + ['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)], @@ -47,14 +47,51 @@ class PopupProxyHost { ])); } - createPopup(parentId, depth) { - return this._createPopupInternal(parentId, depth).popup; + getOrCreatePopup(id=null, parentId=null) { + // Find by existing id + if (id !== null) { + const popup = this._popups.get(id); + if (typeof popup !== 'undefined') { + return popup; + } + } + + // Find by existing parent id + let parent = null; + if (parentId !== null) { + parent = this._popups.get(parentId); + if (typeof parent !== 'undefined') { + const popup = parent.child; + if (popup !== null) { + return popup; + } + } else { + parent = null; + } + } + + // New unique id + if (id === null) { + id = this._nextId++; + } + + // Create new popup + const depth = (parent !== null ? parent.depth + 1 : 0); + const popup = new Popup(id, depth, this._frameIdPromise); + if (parent !== null) { + popup.setParent(parent); + } + this._popups.set(id, popup); + return popup; } // Message handlers - async _onApiCreateNestedPopup(parentId) { - return this._createPopupInternal(parentId, 0).id; + async _onApiGetOrCreatePopup(id, parentId) { + const popup = this.getOrCreatePopup(id, parentId); + return { + id: popup.id + }; } async _onApiSetOptions(id, options) { @@ -106,25 +143,10 @@ class PopupProxyHost { // Private functions - _createPopupInternal(parentId, depth) { - const parent = (typeof parentId === 'string' && this._popups.has(parentId) ? this._popups.get(parentId) : null); - const id = `${this._nextId}`; - if (parent !== null) { - depth = parent.depth + 1; - } - ++this._nextId; - const popup = new Popup(id, depth, this._frameIdPromise); - if (parent !== null) { - popup.setParent(parent); - } - this._popups.set(id, popup); - return {popup, id}; - } - _getPopup(id) { const popup = this._popups.get(id); if (typeof popup === 'undefined') { - throw new Error('Invalid popup ID'); + throw new Error(`Invalid popup ID ${id}`); } return popup; } diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index db6dffb1..093cdd2e 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -19,10 +19,10 @@ /*global FrontendApiSender*/ class PopupProxy { - constructor(depth, parentId, parentFrameId, url) { + constructor(id, depth, parentId, parentFrameId, url) { this._parentId = parentId; this._parentFrameId = parentFrameId; - this._id = null; + this._id = id; this._idPromise = null; this._depth = depth; this._url = url; @@ -113,7 +113,7 @@ class PopupProxy { } async _getPopupIdAsync() { - const id = await this._invokeHostApi('createNestedPopup', {parentId: this._parentId}); + const {id} = await this._invokeHostApi('getOrCreatePopup', {id: this._id, parentId: this._parentId}); this._id = id; return id; } diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 0fc40475..8d4d2b14 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -49,10 +49,18 @@ class Popup { // Public properties + get id() { + return this._id; + } + get parent() { return this._parent; } + get child() { + return this._child; + } + get depth() { return this._depth; } -- cgit v1.2.3 From 2c3f510010ca2910b8c227a9888e2c3584840402 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 13:13:04 -0500 Subject: Allow apiInjectStylesheet to inject a URL --- ext/bg/js/backend.js | 23 ++++++++++++++++------- ext/fg/js/popup.js | 2 +- ext/mixed/js/api.js | 4 ++-- 3 files changed, 19 insertions(+), 10 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index d1a34f82..eeed841c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -499,19 +499,28 @@ class Backend { return Promise.resolve({frameId}); } - _onApiInjectStylesheet({css}, sender) { + _onApiInjectStylesheet({type, value}, sender) { if (!sender.tab) { return Promise.reject(new Error('Invalid tab')); } const tabId = sender.tab.id; const frameId = sender.frameId; - const details = { - code: css, - runAt: 'document_start', - cssOrigin: 'user', - allFrames: false - }; + const details = ( + type === 'file' ? + { + file: value, + runAt: 'document_start', + cssOrigin: 'author', + allFrames: false + } : + { + code: value, + runAt: 'document_start', + cssOrigin: 'user', + allFrames: false + } + ); if (typeof frameId === 'number') { details.frameId = frameId; } diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 45203c03..970c5343 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -167,7 +167,7 @@ class Popup { } else { if (!css) { return; } try { - await apiInjectStylesheet(css); + await apiInjectStylesheet('code', css); this._stylesheetInjectedViaApi = true; } catch (e) { // NOP diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 86bdc73c..14900ecf 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -89,8 +89,8 @@ function apiFrameInformationGet() { return _apiInvoke('frameInformationGet'); } -function apiInjectStylesheet(css) { - return _apiInvoke('injectStylesheet', {css}); +function apiInjectStylesheet(type, value) { + return _apiInvoke('injectStylesheet', {type, value}); } function apiGetEnvironmentInfo() { -- cgit v1.2.3 From b6a50e234cf4fc6179725082f9f53ddd7325ba01 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 13:29:55 -0500 Subject: Change parameter name --- ext/bg/js/settings/popup-preview-frame.js | 2 +- ext/fg/js/popup.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 8fd06222..a7863b3a 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -137,7 +137,7 @@ class SettingsPopupPreview { setCustomOuterCss(css) { if (this.frontend === null) { return; } - this.frontend.popup.setCustomOuterCss(css, true); + this.frontend.popup.setCustomOuterCss(css, false); } async updateSearch() { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 970c5343..6fbab62b 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -158,11 +158,11 @@ class Popup { this._container.dataset.yomichanSiteColor = this._getSiteColor(); } - async setCustomOuterCss(css, injectDirectly) { + async setCustomOuterCss(css, useWebExtensionApi) { // Cannot repeatedly inject stylesheets using web extension APIs since there is no way to remove them. if (this._stylesheetInjectedViaApi) { return; } - if (injectDirectly || Popup._isOnExtensionPage()) { + if (!useWebExtensionApi || Popup._isOnExtensionPage()) { Popup.injectOuterStylesheet(css); } else { if (!css) { return; } -- cgit v1.2.3 From d3aefdc4e3f78b87b99bff576137f530ac7ac163 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 13:37:03 -0500 Subject: Override setCustomOuterCss instead of Popup.injectOuterStylesheet --- ext/bg/js/settings/popup-preview-frame.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index a7863b3a..8c354bf7 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -22,7 +22,8 @@ class SettingsPopupPreview { constructor() { this.frontend = null; this.apiOptionsGetOld = apiOptionsGet; - this.popupInjectOuterStylesheetOld = Popup.injectOuterStylesheet; + this.popup = null; + this.popupSetCustomOuterCssOld = null; this.popupShown = false; this.themeChangeTimeout = null; this.textSource = null; @@ -50,19 +51,19 @@ class SettingsPopupPreview { const popupHost = new PopupProxyHost(); await popupHost.prepare(); - const popup = popupHost.getOrCreatePopup(); - popup.setChildrenSupported(false); + this.popup = popupHost.getOrCreatePopup(); + this.popup.setChildrenSupported(false); - this.frontend = new Frontend(popup); + this.popupSetCustomOuterCssOld = this.popup.setCustomOuterCss; + this.popup.setCustomOuterCss = (...args) => this.popupSetCustomOuterCss(...args); + + this.frontend = new Frontend(this.popup); this.frontend.setEnabled = function () {}; this.frontend.searchClear = function () {}; await this.frontend.prepare(); - // Overwrite popup - Popup.injectOuterStylesheet = (...args) => this.popupInjectOuterStylesheet(...args); - // Update search this.updateSearch(); } @@ -83,9 +84,9 @@ class SettingsPopupPreview { return options; } - popupInjectOuterStylesheet(...args) { + async popupSetCustomOuterCss(...args) { // This simulates the stylesheet priorities when injecting using the web extension API. - const result = this.popupInjectOuterStylesheetOld(...args); + const result = await this.popupSetCustomOuterCssOld.call(this.popup, ...args); const outerStylesheet = Popup.outerStylesheet; const node = document.querySelector('#client-css'); -- cgit v1.2.3 From 9fd6ee382d35fb5fcfc3e6d0f4fab711b37b693e Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 13:48:06 -0500 Subject: Create more generic function for injecting stylesheets --- ext/bg/js/settings/popup-preview-frame.js | 5 +- ext/fg/js/popup.js | 132 +++++++++++++++++++----------- 2 files changed, 88 insertions(+), 49 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 8c354bf7..e900d4e2 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -88,10 +88,9 @@ class SettingsPopupPreview { // This simulates the stylesheet priorities when injecting using the web extension API. const result = await this.popupSetCustomOuterCssOld.call(this.popup, ...args); - const outerStylesheet = Popup.outerStylesheet; const node = document.querySelector('#client-css'); - if (node !== null && outerStylesheet !== null) { - node.parentNode.insertBefore(outerStylesheet, node); + if (node !== null && result !== null) { + node.parentNode.insertBefore(result, node); } return result; diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 6fbab62b..d799e371 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -31,7 +31,6 @@ class Popup { this._visible = false; this._visibleOverride = null; this._options = null; - this._stylesheetInjectedViaApi = false; this._contentScale = 1.0; this._containerSizeContentScale = null; @@ -159,20 +158,12 @@ class Popup { } async setCustomOuterCss(css, useWebExtensionApi) { - // Cannot repeatedly inject stylesheets using web extension APIs since there is no way to remove them. - if (this._stylesheetInjectedViaApi) { return; } - - if (!useWebExtensionApi || Popup._isOnExtensionPage()) { - Popup.injectOuterStylesheet(css); - } else { - if (!css) { return; } - try { - await apiInjectStylesheet('code', css); - this._stylesheetInjectedViaApi = true; - } catch (e) { - // NOP - } - } + return await Popup._injectStylesheet( + 'yomichan-popup-outer-user-stylesheet', + 'code', + css, + useWebExtensionApi + ); } setChildrenSupported(value) { @@ -187,26 +178,6 @@ class Popup { return this._container.getBoundingClientRect(); } - static injectOuterStylesheet(css) { - if (Popup.outerStylesheet === null) { - if (!css) { return; } - Popup.outerStylesheet = document.createElement('style'); - Popup.outerStylesheet.id = 'yomichan-popup-outer-stylesheet'; - } - - const outerStylesheet = Popup.outerStylesheet; - if (css) { - outerStylesheet.textContent = css; - - const par = document.head; - if (par && outerStylesheet.parentNode !== par) { - par.appendChild(outerStylesheet); - } - } else { - outerStylesheet.textContent = ''; - } - } - // Private functions _inject() { @@ -248,7 +219,11 @@ class Popup { }); this._observeFullscreen(true); this._onFullscreenChanged(); - this.setCustomOuterCss(this._options.general.customPopupOuterCss, false); + try { + this.setCustomOuterCss(this._options.general.customPopupOuterCss, true); + } catch (e) { + // NOP + } }); } @@ -526,15 +501,6 @@ class Popup { ]; } - static _isOnExtensionPage() { - try { - const url = chrome.runtime.getURL('/'); - return window.location.href.substring(0, url.length) === url; - } catch (e) { - // NOP - } - } - static _getViewport(useVisualViewport) { const visualViewport = window.visualViewport; if (visualViewport !== null && typeof visualViewport === 'object') { @@ -567,6 +533,80 @@ class Popup { bottom: window.innerHeight }; } + + static _isOnExtensionPage() { + try { + const url = chrome.runtime.getURL('/'); + return window.location.href.substring(0, url.length) === url; + } catch (e) { + // NOP + } + } + + static async _injectStylesheet(id, type, value, useWebExtensionApi) { + const injectedStylesheets = Popup._injectedStylesheets; + + if (Popup._isOnExtensionPage()) { + // Permissions error will occur if trying to use the WebExtension API to inject + // into an extension page. + useWebExtensionApi = false; + } + + let styleNode = injectedStylesheets.get(id); + if (typeof styleNode !== 'undefined') { + if (styleNode === null) { + // Previously injected via WebExtension API + throw new Error(`Stylesheet with id ${id} has already been injected using the WebExtension API`); + } + } else { + styleNode = null; + } + + if (useWebExtensionApi) { + // Inject via WebExtension API + if (styleNode !== null && styleNode.parentNode !== null) { + styleNode.parentNode.removeChild(styleNode); + } + + await apiInjectStylesheet(type, value); + + injectedStylesheets.set(id, null); + return null; + } + + // Create node in document + const parentNode = document.head; + if (parentNode === null) { + throw new Error('No parent node'); + } + + // Create or reuse node + const isFile = (type === 'file'); + const tagName = isFile ? 'link' : 'style'; + if (styleNode === null || styleNode.nodeName.toLowerCase() !== tagName) { + if (styleNode !== null && styleNode.parentNode !== null) { + styleNode.parentNode.removeChild(styleNode); + } + styleNode = document.createElement(tagName); + styleNode.id = id; + } + + // Update node style + if (isFile) { + styleNode.rel = value; + } else { + styleNode.textContent = value; + } + + // Update parent + if (styleNode.parentNode !== parentNode) { + parentNode.appendChild(styleNode); + } + + // Add to map + injectedStylesheets.set(id, styleNode); + return styleNode; + } } -Popup.outerStylesheet = null; +Popup._injectedStylesheets = new Map(); -- cgit v1.2.3 From e173a71ba631533a2a0a64ffe4d8883d7300802e Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 14:34:49 -0500 Subject: Fix CSS injection on about:blank pages --- ext/bg/js/backend.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index eeed841c..458ea483 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -512,13 +512,15 @@ class Backend { file: value, runAt: 'document_start', cssOrigin: 'author', - allFrames: false + allFrames: false, + matchAboutBlank: true } : { code: value, runAt: 'document_start', cssOrigin: 'user', - allFrames: false + allFrames: false, + matchAboutBlank: true } ); if (typeof frameId === 'number') { -- cgit v1.2.3 From aee16c443195ff8ab2b0f5f5e8551e44895d48a1 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 16 Feb 2020 23:41:17 -0500 Subject: Check origin on window messages --- ext/bg/js/settings/popup-preview-frame.js | 3 +++ ext/bg/js/settings/popup-preview.js | 8 +++++--- ext/fg/js/popup.js | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index e900d4e2..890b8c96 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -27,6 +27,7 @@ class SettingsPopupPreview { this.popupShown = false; this.themeChangeTimeout = null; this.textSource = null; + this._targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, ''); } static create() { @@ -97,6 +98,8 @@ class SettingsPopupPreview { } onMessage(e) { + if (e.origin !== this._targetOrigin) { return; } + const {action, params} = e.data; const handler = SettingsPopupPreview._messageHandlers.get(action); if (typeof handler !== 'function') { return; } diff --git a/ext/bg/js/settings/popup-preview.js b/ext/bg/js/settings/popup-preview.js index 0d20471e..d1d2ff5e 100644 --- a/ext/bg/js/settings/popup-preview.js +++ b/ext/bg/js/settings/popup-preview.js @@ -40,20 +40,22 @@ function showAppearancePreview() { window.wanakana.bind(text[0]); + const targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, ''); + text.on('input', () => { const action = 'setText'; const params = {text: text.val()}; - frame.contentWindow.postMessage({action, params}, '*'); + frame.contentWindow.postMessage({action, params}, targetOrigin); }); customCss.on('input', () => { const action = 'setCustomCss'; const params = {css: customCss.val()}; - frame.contentWindow.postMessage({action, params}, '*'); + frame.contentWindow.postMessage({action, params}, targetOrigin); }); customOuterCss.on('input', () => { const action = 'setCustomOuterCss'; const params = {css: customOuterCss.val()}; - frame.contentWindow.postMessage({action, params}, '*'); + frame.contentWindow.postMessage({action, params}, targetOrigin); }); container.append(frame); diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 59c46ab8..900e7325 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -33,6 +33,7 @@ class Popup { this._options = null; this._contentScale = 1.0; this._containerSizeContentScale = null; + this._targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, ''); this._container = document.createElement('iframe'); this._container.className = 'yomichan-float'; @@ -349,7 +350,7 @@ class Popup { _invokeApi(action, params={}) { if (this._container.contentWindow) { - this._container.contentWindow.postMessage({action, params}, '*'); + this._container.contentWindow.postMessage({action, params}, this._targetOrigin); } } -- cgit v1.2.3 From 0f46e3a093e7f0c07ad310d8c17e2582bdfd2741 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 17 Feb 2020 11:02:21 -0500 Subject: Use a token to ensure that messages are coming from Yomichan --- ext/bg/js/backend.js | 9 ++++++++- ext/fg/js/float.js | 50 ++++++++++++++++++++++++++++++++++++++++++++------ ext/fg/js/popup.js | 15 +++++++++++---- ext/mixed/js/api.js | 4 ++++ 4 files changed, 67 insertions(+), 11 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 458ea483..a4d085e0 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -46,6 +46,8 @@ class Backend { this.popupWindow = null; this.apiForwarder = new BackendApiForwarder(); + + this.messageToken = yomichan.generateId(16); } async prepare() { @@ -614,6 +616,10 @@ class Backend { }); } + async _onApiGetMessageToken() { + return this.messageToken; + } + // Command handlers async _onCommandSearch(params) { @@ -875,7 +881,8 @@ Backend._messageHandlers = new Map([ ['clipboardGet', (self, ...args) => self._onApiClipboardGet(...args)], ['getDisplayTemplatesHtml', (self, ...args) => self._onApiGetDisplayTemplatesHtml(...args)], ['getQueryParserTemplatesHtml', (self, ...args) => self._onApiGetQueryParserTemplatesHtml(...args)], - ['getZoom', (self, ...args) => self._onApiGetZoom(...args)] + ['getZoom', (self, ...args) => self._onApiGetZoom(...args)], + ['getMessageToken', (self, ...args) => self._onApiGetMessageToken(...args)] ]); Backend._commandHandlers = new Map([ diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 440a9731..8f21a9c5 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -/*global popupNestedInitialize, apiForward, Display*/ +/*global popupNestedInitialize, apiForward, apiGetMessageToken, Display*/ class DisplayFloat extends Display { constructor() { @@ -30,6 +30,8 @@ class DisplayFloat extends Display { this._orphaned = false; this._prepareInvoked = false; + this._messageToken = null; + this._messageTokenPromise = null; yomichan.on('orphaned', () => this.onOrphaned()); window.addEventListener('message', (e) => this.onMessage(e), false); @@ -75,11 +77,23 @@ class DisplayFloat extends Display { } onMessage(e) { - const {action, params} = e.data; - const handler = DisplayFloat._messageHandlers.get(action); - if (typeof handler !== 'function') { return; } - - handler(this, params); + const data = e.data; + if (typeof data !== 'object' || data === null) { return; } // Invalid data + + const token = data.token; + if (typeof token !== 'string') { return; } // Invalid data + + if (this._messageToken === null) { + // Async + this.getMessageToken() + .then( + () => { this.handleAction(token, data); }, + () => {} + ); + } else { + // Sync + this.handleAction(token, data); + } } onKeyDown(e) { @@ -94,6 +108,30 @@ class DisplayFloat extends Display { return super.onKeyDown(e); } + async getMessageToken() { + // this._messageTokenPromise is used to ensure that only one call to apiGetMessageToken is made. + if (this._messageTokenPromise === null) { + this._messageTokenPromise = apiGetMessageToken(); + } + const messageToken = await this._messageTokenPromise; + if (this._messageToken === null) { + this._messageToken = messageToken; + } + this._messageTokenPromise = null; + } + + handleAction(token, {action, params}) { + if (token !== this._messageToken) { + // Invalid token + return; + } + + const handler = DisplayFloat._messageHandlers.get(action); + if (typeof handler !== 'function') { return; } + + handler(this, params); + } + getOptionsContext() { return this.optionsContext; } diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 900e7325..4927f4bd 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -/*global apiInjectStylesheet*/ +/*global apiInjectStylesheet, apiGetMessageToken*/ class Popup { constructor(id, depth, frameIdPromise) { @@ -34,6 +34,7 @@ class Popup { this._contentScale = 1.0; this._containerSizeContentScale = null; this._targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, ''); + this._messageToken = null; this._container = document.createElement('iframe'); this._container.className = 'yomichan-float'; @@ -198,6 +199,10 @@ class Popup { // NOP } + if (this._messageToken === null) { + this._messageToken = await apiGetMessageToken(); + } + return new Promise((resolve) => { const parentFrameId = (typeof this._frameId === 'number' ? this._frameId : null); this._container.setAttribute('src', chrome.runtime.getURL('/fg/float.html')); @@ -349,9 +354,11 @@ class Popup { } _invokeApi(action, params={}) { - if (this._container.contentWindow) { - this._container.contentWindow.postMessage({action, params}, this._targetOrigin); - } + const token = this._messageToken; + const contentWindow = this._container.contentWindow; + if (token === null || contentWindow === null) { return; } + + contentWindow.postMessage({action, params, token}, this._targetOrigin); } static _getFullscreenElement() { diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 14900ecf..7ea68d59 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -113,6 +113,10 @@ function apiGetZoom() { return _apiInvoke('getZoom'); } +function apiGetMessageToken() { + return _apiInvoke('getMessageToken'); +} + function _apiInvoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { -- cgit v1.2.3 From a8b1e40a1e34178d9907b04f3a8e16a21b9eb42a Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 19 Feb 2020 19:59:24 -0500 Subject: Close to prevent hangs --- ext/bg/js/database.js | 6 ++++++ test/test-database.js | 2 ++ 2 files changed, 8 insertions(+) (limited to 'ext/bg') diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index b54d832c..02d59c83 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -96,6 +96,12 @@ class Database { } } + async close() { + this.validate(); + this.db.close(); + this.db = null; + } + async purge() { this.validate(); diff --git a/test/test-database.js b/test/test-database.js index d66e2137..cefa297c 100644 --- a/test/test-database.js +++ b/test/test-database.js @@ -130,6 +130,8 @@ async function testDatabase1() { // Cleanup await cleanup(); } + + await database.close(); } async function testDatabaseEmpty1(database) { -- cgit v1.2.3 From 212e5428e81ad98f8af64eb3a6d2ab9d5b93f645 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 17 Feb 2020 16:16:08 -0500 Subject: Mark private functions in database.js --- ext/bg/js/database.js | 174 ++++++++++++++++++++++++++------------------------ 1 file changed, 89 insertions(+), 85 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 02d59c83..7d3019bf 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -23,14 +23,16 @@ class Database { this.db = null; } + // Public + async prepare() { if (this.db !== null) { throw new Error('Database already initialized'); } try { - this.db = await Database.open('dict', 5, (db, transaction, oldVersion) => { - Database.upgrade(db, transaction, oldVersion, [ + this.db = await Database._open('dict', 5, (db, transaction, oldVersion) => { + Database._upgrade(db, transaction, oldVersion, [ { version: 2, stores: { @@ -103,17 +105,17 @@ class Database { } async purge() { - this.validate(); + this._validate(); this.db.close(); - await Database.deleteDatabase(this.db.name); + await Database._deleteDatabase(this.db.name); this.db = null; await this.prepare(); } async deleteDictionary(dictionaryName, onProgress, progressSettings) { - this.validate(); + this._validate(); const targets = [ ['dictionaries', 'title'], @@ -140,14 +142,14 @@ class Database { const dbObjectStore = dbTransaction.objectStore(objectStoreName); const dbIndex = dbObjectStore.index(index); const only = IDBKeyRange.only(dictionaryName); - promises.push(Database.deleteValues(dbObjectStore, dbIndex, only, onProgress, progressData, progressRate)); + promises.push(Database._deleteValues(dbObjectStore, dbIndex, only, onProgress, progressData, progressRate)); } await Promise.all(promises); } async findTermsBulk(termList, titles, wildcard) { - this.validate(); + this._validate(); const promises = []; const visited = {}; @@ -155,7 +157,7 @@ class Database { const processRow = (row, index) => { if (titles.includes(row.dictionary) && !hasOwn(visited, row.id)) { visited[row.id] = true; - results.push(Database.createTerm(row, index)); + results.push(Database._createTerm(row, index)); } }; @@ -171,8 +173,8 @@ class Database { const term = prefixWildcard ? stringReverse(termList[i]) : termList[i]; const query = useWildcard ? IDBKeyRange.bound(term, `${term}\uffff`, false, false) : IDBKeyRange.only(term); promises.push( - Database.getAll(dbIndex1, query, i, processRow), - Database.getAll(dbIndex2, query, i, processRow) + Database._getAll(dbIndex1, query, i, processRow), + Database._getAll(dbIndex2, query, i, processRow) ); } @@ -182,13 +184,13 @@ class Database { } async findTermsExactBulk(termList, readingList, titles) { - this.validate(); + this._validate(); const promises = []; const results = []; const processRow = (row, index) => { if (row.reading === readingList[index] && titles.includes(row.dictionary)) { - results.push(Database.createTerm(row, index)); + results.push(Database._createTerm(row, index)); } }; @@ -198,7 +200,7 @@ class Database { for (let i = 0; i < termList.length; ++i) { const only = IDBKeyRange.only(termList[i]); - promises.push(Database.getAll(dbIndex, only, i, processRow)); + promises.push(Database._getAll(dbIndex, only, i, processRow)); } await Promise.all(promises); @@ -207,13 +209,13 @@ class Database { } async findTermsBySequenceBulk(sequenceList, mainDictionary) { - this.validate(); + this._validate(); const promises = []; const results = []; const processRow = (row, index) => { if (row.dictionary === mainDictionary) { - results.push(Database.createTerm(row, index)); + results.push(Database._createTerm(row, index)); } }; @@ -223,7 +225,7 @@ class Database { for (let i = 0; i < sequenceList.length; ++i) { const only = IDBKeyRange.only(sequenceList[i]); - promises.push(Database.getAll(dbIndex, only, i, processRow)); + promises.push(Database._getAll(dbIndex, only, i, processRow)); } await Promise.all(promises); @@ -232,51 +234,26 @@ class Database { } async findTermMetaBulk(termList, titles) { - return this.findGenericBulk('termMeta', 'expression', termList, titles, Database.createTermMeta); + return this._findGenericBulk('termMeta', 'expression', termList, titles, Database._createTermMeta); } async findKanjiBulk(kanjiList, titles) { - return this.findGenericBulk('kanji', 'character', kanjiList, titles, Database.createKanji); + return this._findGenericBulk('kanji', 'character', kanjiList, titles, Database._createKanji); } async findKanjiMetaBulk(kanjiList, titles) { - return this.findGenericBulk('kanjiMeta', 'character', kanjiList, titles, Database.createKanjiMeta); - } - - async findGenericBulk(tableName, indexName, indexValueList, titles, createResult) { - this.validate(); - - const promises = []; - const results = []; - const processRow = (row, index) => { - if (titles.includes(row.dictionary)) { - results.push(createResult(row, index)); - } - }; - - const dbTransaction = this.db.transaction([tableName], 'readonly'); - const dbTerms = dbTransaction.objectStore(tableName); - const dbIndex = dbTerms.index(indexName); - - for (let i = 0; i < indexValueList.length; ++i) { - const only = IDBKeyRange.only(indexValueList[i]); - promises.push(Database.getAll(dbIndex, only, i, processRow)); - } - - await Promise.all(promises); - - return results; + return this._findGenericBulk('kanjiMeta', 'character', kanjiList, titles, Database._createKanjiMeta); } async findTagForTitle(name, title) { - this.validate(); + this._validate(); let result = null; const dbTransaction = this.db.transaction(['tagMeta'], 'readonly'); const dbTerms = dbTransaction.objectStore('tagMeta'); const dbIndex = dbTerms.index('name'); const only = IDBKeyRange.only(name); - await Database.getAll(dbIndex, only, null, (row) => { + await Database._getAll(dbIndex, only, null, (row) => { if (title === row.dictionary) { result = row; } @@ -286,19 +263,19 @@ class Database { } async getDictionaryInfo() { - this.validate(); + this._validate(); const results = []; const dbTransaction = this.db.transaction(['dictionaries'], 'readonly'); const dbDictionaries = dbTransaction.objectStore('dictionaries'); - await Database.getAll(dbDictionaries, null, null, (info) => results.push(info)); + await Database._getAll(dbDictionaries, null, null, (info) => results.push(info)); return results; } async getDictionaryCounts(dictionaryNames, getTotal) { - this.validate(); + this._validate(); const objectStoreNames = [ 'kanji', @@ -319,7 +296,7 @@ class Database { // Query is required for Edge, otherwise index.count throws an exception. const query1 = IDBKeyRange.lowerBound('', false); - const totalPromise = getTotal ? Database.getCounts(targets, query1) : null; + const totalPromise = getTotal ? Database._getCounts(targets, query1) : null; const counts = []; const countPromises = []; @@ -327,7 +304,7 @@ class Database { counts.push(null); const index = i; const query2 = IDBKeyRange.only(dictionaryNames[i]); - const countPromise = Database.getCounts(targets, query2).then((v) => counts[index] = v); + const countPromise = Database._getCounts(targets, query2).then((v) => counts[index] = v); countPromises.push(countPromise); } await Promise.all(countPromises); @@ -340,7 +317,7 @@ class Database { } async importDictionary(archive, progressCallback, details) { - this.validate(); + this._validate(); const errors = []; const prefixWildcardsSupported = details.prefixWildcardsSupported; @@ -357,7 +334,7 @@ class Database { const count = Math.min(maxTransactionLength, items.length - i); const transaction = db.transaction([objectStoreName], 'readwrite'); const objectStore = transaction.objectStore(objectStoreName); - await Database.bulkAdd(objectStore, items, i, count); + await Database._bulkAdd(objectStore, items, i, count); } catch (e) { errors.push(e); } @@ -373,7 +350,7 @@ class Database { const dbCountTransaction = db.transaction(['dictionaries'], 'readonly'); const dbIndex = dbCountTransaction.objectStore('dictionaries').index('title'); const only = IDBKeyRange.only(summary.title); - const count = await Database.getCount(dbIndex, only); + const count = await Database._getCount(dbIndex, only); if (count > 0) { throw new Error('Dictionary is already imported'); @@ -381,7 +358,7 @@ class Database { const transaction = db.transaction(['dictionaries'], 'readwrite'); const objectStore = transaction.objectStore('dictionaries'); - await Database.bulkAdd(objectStore, [summary], 0, 1); + await Database._bulkAdd(objectStore, [summary], 0, 1); }; const termDataLoaded = async (summary, entries, total, current) => { @@ -500,7 +477,7 @@ class Database { await bulkAdd('tagMeta', rows, total, current); }; - const result = await Database.importDictionaryZip( + const result = await Database._importDictionaryZip( archive, indexDataLoaded, termDataLoaded, @@ -514,13 +491,40 @@ class Database { return {result, errors}; } - validate() { + // Private + + _validate() { if (this.db === null) { throw new Error('Database not initialized'); } } - static async importDictionaryZip( + async _findGenericBulk(tableName, indexName, indexValueList, titles, createResult) { + this._validate(); + + const promises = []; + const results = []; + const processRow = (row, index) => { + if (titles.includes(row.dictionary)) { + results.push(createResult(row, index)); + } + }; + + const dbTransaction = this.db.transaction([tableName], 'readonly'); + const dbTerms = dbTransaction.objectStore(tableName); + const dbIndex = dbTerms.index(indexName); + + for (let i = 0; i < indexValueList.length; ++i) { + const only = IDBKeyRange.only(indexValueList[i]); + promises.push(Database._getAll(dbIndex, only, i, processRow)); + } + + await Promise.all(promises); + + return results; + } + + static async _importDictionaryZip( archive, indexDataLoaded, termDataLoaded, @@ -610,7 +614,7 @@ class Database { return summary; } - static createTerm(row, index) { + static _createTerm(row, index) { return { index, expression: row.expression, @@ -626,7 +630,7 @@ class Database { }; } - static createKanji(row, index) { + static _createKanji(row, index) { return { index, character: row.character, @@ -639,20 +643,20 @@ class Database { }; } - static createTermMeta({expression, mode, data, dictionary}, index) { + static _createTermMeta({expression, mode, data, dictionary}, index) { return {expression, mode, data, dictionary, index}; } - static createKanjiMeta({character, mode, data, dictionary}, index) { + static _createKanjiMeta({character, mode, data, dictionary}, index) { return {character, mode, data, dictionary, index}; } - static getAll(dbIndex, query, context, processRow) { - const fn = typeof dbIndex.getAll === 'function' ? Database.getAllFast : Database.getAllUsingCursor; + static _getAll(dbIndex, query, context, processRow) { + const fn = typeof dbIndex.getAll === 'function' ? Database._getAllFast : Database._getAllUsingCursor; return fn(dbIndex, query, context, processRow); } - static getAllFast(dbIndex, query, context, processRow) { + static _getAllFast(dbIndex, query, context, processRow) { return new Promise((resolve, reject) => { const request = dbIndex.getAll(query); request.onerror = (e) => reject(e); @@ -665,7 +669,7 @@ class Database { }); } - static getAllUsingCursor(dbIndex, query, context, processRow) { + static _getAllUsingCursor(dbIndex, query, context, processRow) { return new Promise((resolve, reject) => { const request = dbIndex.openCursor(query, 'next'); request.onerror = (e) => reject(e); @@ -681,18 +685,18 @@ class Database { }); } - static getCounts(targets, query) { + static _getCounts(targets, query) { const countPromises = []; const counts = {}; for (const [objectStoreName, index] of targets) { const n = objectStoreName; - const countPromise = Database.getCount(index, query).then((count) => counts[n] = count); + const countPromise = Database._getCount(index, query).then((count) => counts[n] = count); countPromises.push(countPromise); } return Promise.all(countPromises).then(() => counts); } - static getCount(dbIndex, query) { + static _getCount(dbIndex, query) { return new Promise((resolve, reject) => { const request = dbIndex.count(query); request.onerror = (e) => reject(e); @@ -700,12 +704,12 @@ class Database { }); } - static getAllKeys(dbIndex, query) { - const fn = typeof dbIndex.getAllKeys === 'function' ? Database.getAllKeysFast : Database.getAllKeysUsingCursor; + static _getAllKeys(dbIndex, query) { + const fn = typeof dbIndex.getAllKeys === 'function' ? Database._getAllKeysFast : Database._getAllKeysUsingCursor; return fn(dbIndex, query); } - static getAllKeysFast(dbIndex, query) { + static _getAllKeysFast(dbIndex, query) { return new Promise((resolve, reject) => { const request = dbIndex.getAllKeys(query); request.onerror = (e) => reject(e); @@ -713,7 +717,7 @@ class Database { }); } - static getAllKeysUsingCursor(dbIndex, query) { + static _getAllKeysUsingCursor(dbIndex, query) { return new Promise((resolve, reject) => { const primaryKeys = []; const request = dbIndex.openKeyCursor(query, 'next'); @@ -730,9 +734,9 @@ class Database { }); } - static async deleteValues(dbObjectStore, dbIndex, query, onProgress, progressData, progressRate) { + static async _deleteValues(dbObjectStore, dbIndex, query, onProgress, progressData, progressRate) { const hasProgress = (typeof onProgress === 'function'); - const count = await Database.getCount(dbIndex, query); + const count = await Database._getCount(dbIndex, query); ++progressData.storesProcesed; progressData.count += count; if (hasProgress) { @@ -751,16 +755,16 @@ class Database { ); const promises = []; - const primaryKeys = await Database.getAllKeys(dbIndex, query); + const primaryKeys = await Database._getAllKeys(dbIndex, query); for (const key of primaryKeys) { - const promise = Database.deleteValue(dbObjectStore, key).then(onValueDeleted); + const promise = Database._deleteValue(dbObjectStore, key).then(onValueDeleted); promises.push(promise); } await Promise.all(promises); } - static deleteValue(dbObjectStore, key) { + static _deleteValue(dbObjectStore, key) { return new Promise((resolve, reject) => { const request = dbObjectStore.delete(key); request.onerror = (e) => reject(e); @@ -768,7 +772,7 @@ class Database { }); } - static bulkAdd(objectStore, items, start, count) { + static _bulkAdd(objectStore, items, start, count) { return new Promise((resolve, reject) => { if (start + count > items.length) { count = items.length - start; @@ -796,7 +800,7 @@ class Database { }); } - static open(name, version, onUpgradeNeeded) { + static _open(name, version, onUpgradeNeeded) { return new Promise((resolve, reject) => { const request = window.indexedDB.open(name, version * 10); @@ -814,7 +818,7 @@ class Database { }); } - static upgrade(db, transaction, oldVersion, upgrades) { + static _upgrade(db, transaction, oldVersion, upgrades) { for (const {version, stores} of upgrades) { if (oldVersion >= version) { continue; } @@ -824,13 +828,13 @@ class Database { const objectStoreNames = transaction.objectStoreNames || db.objectStoreNames; const objectStore = ( - Database.listContains(objectStoreNames, objectStoreName) ? + Database._listContains(objectStoreNames, objectStoreName) ? transaction.objectStore(objectStoreName) : db.createObjectStore(objectStoreName, primaryKey) ); for (const indexName of indices) { - if (Database.listContains(objectStore.indexNames, indexName)) { continue; } + if (Database._listContains(objectStore.indexNames, indexName)) { continue; } objectStore.createIndex(indexName, indexName, {}); } @@ -838,7 +842,7 @@ class Database { } } - static deleteDatabase(dbName) { + static _deleteDatabase(dbName) { return new Promise((resolve, reject) => { const request = indexedDB.deleteDatabase(dbName); request.onerror = (e) => reject(e); @@ -846,7 +850,7 @@ class Database { }); } - static listContains(list, value) { + static _listContains(list, value) { for (let i = 0, ii = list.length; i < ii; ++i) { if (list[i] === value) { return true; } } -- cgit v1.2.3 From 177bca4865f615dff5618aff13248777d34081a6 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 17 Feb 2020 18:17:12 -0500 Subject: Create new dictionary import function --- ext/bg/js/database.js | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) (limited to 'ext/bg') diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 7d3019bf..51e8d359 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -316,6 +316,169 @@ class Database { return result; } + async importDictionaryNew(archiveSource, onProgress, details) { + this._validate(); + const db = this.db; + const hasOnProgress = (typeof onProgress === 'function'); + + // Read archive + const archive = await JSZip.loadAsync(archiveSource); + + // Read and validate index + const indexFile = archive.files['index.json']; + if (!indexFile) { + throw new Error('No dictionary index found in archive'); + } + + const index = JSON.parse(await indexFile.async('string')); + + const dictionaryTitle = index.title; + const version = index.format || index.version; + + if (!dictionaryTitle || !index.revision) { + throw new Error('Unrecognized dictionary format'); + } + + // Verify database is not already imported + if (await this._dictionaryExists(dictionaryTitle)) { + throw new Error('Dictionary is already imported'); + } + + // Data format converters + const convertTermBankEntry = (entry) => { + if (version === 1) { + const [expression, reading, definitionTags, rules, score, ...glossary] = entry; + return {expression, reading, definitionTags, rules, score, glossary}; + } else { + const [expression, reading, definitionTags, rules, score, glossary, sequence, termTags] = entry; + return {expression, reading, definitionTags, rules, score, glossary, sequence, termTags}; + } + }; + + const convertTermMetaBankEntry = (entry) => { + const [expression, mode, data] = entry; + return {expression, mode, data}; + }; + + const convertKanjiBankEntry = (entry) => { + if (version === 1) { + const [character, onyomi, kunyomi, tags, ...meanings] = entry; + return {character, onyomi, kunyomi, tags, meanings}; + } else { + const [character, onyomi, kunyomi, tags, meanings, stats] = entry; + return {character, onyomi, kunyomi, tags, meanings, stats}; + } + }; + + const convertKanjiMetaBankEntry = (entry) => { + const [character, mode, data] = entry; + return {character, mode, data}; + }; + + const convertTagBankEntry = (entry) => { + const [name, category, order, notes, score] = entry; + return {name, category, order, notes, score}; + }; + + // Archive file reading + const readFileSequence = async (fileNameFormat, convertEntry) => { + const results = []; + for (let i = 1; true; ++i) { + const fileName = fileNameFormat.replace(/\?/, `${i}`); + const file = archive.files[fileName]; + if (!file) { break; } + + const entries = JSON.parse(await file.async('string')); + for (let entry of entries) { + entry = convertEntry(entry); + entry.dictionary = dictionaryTitle; + results.push(entry); + } + } + return results; + }; + + // Load data + const termList = await readFileSequence('term_bank_?.json', convertTermBankEntry); + const termMetaList = await readFileSequence('term_meta_bank_?.json', convertTermMetaBankEntry); + const kanjiList = await readFileSequence('kanji_bank_?.json', convertKanjiBankEntry); + const kanjiMetaList = await readFileSequence('kanji_meta_bank_?.json', convertKanjiMetaBankEntry); + const tagList = await readFileSequence('tag_bank_?.json', convertTagBankEntry); + + // Old tags + const indexTagMeta = index.tagMeta; + if (typeof indexTagMeta === 'object' && indexTagMeta !== null) { + for (const name of Object.keys(indexTagMeta)) { + const {category, order, notes, score} = indexTagMeta[name]; + tagList.push({name, category, order, notes, score}); + } + } + + // Prefix wildcard support + const prefixWildcardsSupported = !!details.prefixWildcardsSupported; + if (prefixWildcardsSupported) { + for (const entry of termList) { + entry.expressionReverse = stringReverse(entry.expression); + entry.readingReverse = stringReverse(entry.reading); + } + } + + // Add dictionary + const summary = { + title: dictionaryTitle, + revision: index.revision, + sequenced: index.sequenced, + version, + prefixWildcardsSupported + }; + + { + const transaction = db.transaction(['dictionaries'], 'readwrite'); + const objectStore = transaction.objectStore('dictionaries'); + await Database._bulkAdd(objectStore, [summary], 0, 1); + } + + // Add data + const errors = []; + const total = ( + termList.length + + termMetaList.length + + kanjiList.length + + kanjiMetaList.length + + tagList.length + ); + let loadedCount = 0; + const maxTransactionLength = 1000; + + const bulkAdd = async (objectStoreName, entries) => { + const ii = entries.length; + for (let i = 0; i < ii; i += maxTransactionLength) { + const count = Math.min(maxTransactionLength, ii - i); + + try { + const transaction = db.transaction([objectStoreName], 'readwrite'); + const objectStore = transaction.objectStore(objectStoreName); + await Database._bulkAdd(objectStore, entries, i, count); + } catch (e) { + errors.push(e); + } + + loadedCount += count; + if (hasOnProgress) { + onProgress(total, loadedCount); + } + } + }; + + await bulkAdd('terms', termList); + await bulkAdd('termMeta', termMetaList); + await bulkAdd('kanji', kanjiList); + await bulkAdd('kanjiMeta', kanjiMetaList); + await bulkAdd('tagMeta', tagList); + + return {result: summary, errors}; + } + async importDictionary(archive, progressCallback, details) { this._validate(); @@ -499,6 +662,15 @@ class Database { } } + async _dictionaryExists(title) { + const db = this.db; + const dbCountTransaction = db.transaction(['dictionaries'], 'readonly'); + const dbIndex = dbCountTransaction.objectStore('dictionaries').index('title'); + const only = IDBKeyRange.only(title); + const count = await Database._getCount(dbIndex, only); + return count > 0; + } + async _findGenericBulk(tableName, indexName, indexValueList, titles, createResult) { this._validate(); -- cgit v1.2.3 From d620f40448928d3bd3dc8bbafd95bedd177c9975 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 17 Feb 2020 18:19:11 -0500 Subject: Remove old dictionary import functions --- ext/bg/js/database.js | 269 +------------------------------------------------- 1 file changed, 2 insertions(+), 267 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 51e8d359..f85ac7f3 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -/*global dictFieldSplit, dictTagSanitize, JSZip*/ +/*global dictFieldSplit, JSZip*/ class Database { constructor() { @@ -316,7 +316,7 @@ class Database { return result; } - async importDictionaryNew(archiveSource, onProgress, details) { + async importDictionary(archiveSource, onProgress, details) { this._validate(); const db = this.db; const hasOnProgress = (typeof onProgress === 'function'); @@ -479,181 +479,6 @@ class Database { return {result: summary, errors}; } - async importDictionary(archive, progressCallback, details) { - this._validate(); - - const errors = []; - const prefixWildcardsSupported = details.prefixWildcardsSupported; - - const maxTransactionLength = 1000; - const bulkAdd = async (objectStoreName, items, total, current) => { - const db = this.db; - for (let i = 0; i < items.length; i += maxTransactionLength) { - if (progressCallback) { - progressCallback(total, current + i / items.length); - } - - try { - const count = Math.min(maxTransactionLength, items.length - i); - const transaction = db.transaction([objectStoreName], 'readwrite'); - const objectStore = transaction.objectStore(objectStoreName); - await Database._bulkAdd(objectStore, items, i, count); - } catch (e) { - errors.push(e); - } - } - }; - - const indexDataLoaded = async (summary) => { - if (summary.version > 3) { - throw new Error('Unsupported dictionary version'); - } - - const db = this.db; - const dbCountTransaction = db.transaction(['dictionaries'], 'readonly'); - const dbIndex = dbCountTransaction.objectStore('dictionaries').index('title'); - const only = IDBKeyRange.only(summary.title); - const count = await Database._getCount(dbIndex, only); - - if (count > 0) { - throw new Error('Dictionary is already imported'); - } - - const transaction = db.transaction(['dictionaries'], 'readwrite'); - const objectStore = transaction.objectStore('dictionaries'); - await Database._bulkAdd(objectStore, [summary], 0, 1); - }; - - const termDataLoaded = async (summary, entries, total, current) => { - const rows = []; - if (summary.version === 1) { - for (const [expression, reading, definitionTags, rules, score, ...glossary] of entries) { - rows.push({ - expression, - reading, - definitionTags, - rules, - score, - glossary, - dictionary: summary.title - }); - } - } else { - for (const [expression, reading, definitionTags, rules, score, glossary, sequence, termTags] of entries) { - rows.push({ - expression, - reading, - definitionTags, - rules, - score, - glossary, - sequence, - termTags, - dictionary: summary.title - }); - } - } - - if (prefixWildcardsSupported) { - for (const row of rows) { - row.expressionReverse = stringReverse(row.expression); - row.readingReverse = stringReverse(row.reading); - } - } - - await bulkAdd('terms', rows, total, current); - }; - - const termMetaDataLoaded = async (summary, entries, total, current) => { - const rows = []; - for (const [expression, mode, data] of entries) { - rows.push({ - expression, - mode, - data, - dictionary: summary.title - }); - } - - await bulkAdd('termMeta', rows, total, current); - }; - - const kanjiDataLoaded = async (summary, entries, total, current) => { - const rows = []; - if (summary.version === 1) { - for (const [character, onyomi, kunyomi, tags, ...meanings] of entries) { - rows.push({ - character, - onyomi, - kunyomi, - tags, - meanings, - dictionary: summary.title - }); - } - } else { - for (const [character, onyomi, kunyomi, tags, meanings, stats] of entries) { - rows.push({ - character, - onyomi, - kunyomi, - tags, - meanings, - stats, - dictionary: summary.title - }); - } - } - - await bulkAdd('kanji', rows, total, current); - }; - - const kanjiMetaDataLoaded = async (summary, entries, total, current) => { - const rows = []; - for (const [character, mode, data] of entries) { - rows.push({ - character, - mode, - data, - dictionary: summary.title - }); - } - - await bulkAdd('kanjiMeta', rows, total, current); - }; - - const tagDataLoaded = async (summary, entries, total, current) => { - const rows = []; - for (const [name, category, order, notes, score] of entries) { - const row = dictTagSanitize({ - name, - category, - order, - notes, - score, - dictionary: summary.title - }); - - rows.push(row); - } - - await bulkAdd('tagMeta', rows, total, current); - }; - - const result = await Database._importDictionaryZip( - archive, - indexDataLoaded, - termDataLoaded, - termMetaDataLoaded, - kanjiDataLoaded, - kanjiMetaDataLoaded, - tagDataLoaded, - details - ); - - return {result, errors}; - } - // Private _validate() { @@ -696,96 +521,6 @@ class Database { return results; } - static async _importDictionaryZip( - archive, - indexDataLoaded, - termDataLoaded, - termMetaDataLoaded, - kanjiDataLoaded, - kanjiMetaDataLoaded, - tagDataLoaded, - details - ) { - const zip = await JSZip.loadAsync(archive); - - const indexFile = zip.files['index.json']; - if (!indexFile) { - throw new Error('No dictionary index found in archive'); - } - - const index = JSON.parse(await indexFile.async('string')); - if (!index.title || !index.revision) { - throw new Error('Unrecognized dictionary format'); - } - - const summary = { - title: index.title, - revision: index.revision, - sequenced: index.sequenced, - version: index.format || index.version, - prefixWildcardsSupported: !!details.prefixWildcardsSupported - }; - - await indexDataLoaded(summary); - - const buildTermBankName = (index) => `term_bank_${index + 1}.json`; - const buildTermMetaBankName = (index) => `term_meta_bank_${index + 1}.json`; - const buildKanjiBankName = (index) => `kanji_bank_${index + 1}.json`; - const buildKanjiMetaBankName = (index) => `kanji_meta_bank_${index + 1}.json`; - const buildTagBankName = (index) => `tag_bank_${index + 1}.json`; - - const countBanks = (namer) => { - let count = 0; - while (zip.files[namer(count)]) { - ++count; - } - - return count; - }; - - const termBankCount = countBanks(buildTermBankName); - const termMetaBankCount = countBanks(buildTermMetaBankName); - const kanjiBankCount = countBanks(buildKanjiBankName); - const kanjiMetaBankCount = countBanks(buildKanjiMetaBankName); - const tagBankCount = countBanks(buildTagBankName); - - let bankLoadedCount = 0; - let bankTotalCount = - termBankCount + - termMetaBankCount + - kanjiBankCount + - kanjiMetaBankCount + - tagBankCount; - - if (tagDataLoaded && index.tagMeta) { - const bank = []; - for (const name in index.tagMeta) { - const tag = index.tagMeta[name]; - bank.push([name, tag.category, tag.order, tag.notes, tag.score]); - } - - tagDataLoaded(summary, bank, ++bankTotalCount, bankLoadedCount++); - } - - const loadBank = async (summary, namer, count, callback) => { - if (callback) { - for (let i = 0; i < count; ++i) { - const bankFile = zip.files[namer(i)]; - const bank = JSON.parse(await bankFile.async('string')); - await callback(summary, bank, bankTotalCount, bankLoadedCount++); - } - } - }; - - await loadBank(summary, buildTermBankName, termBankCount, termDataLoaded); - await loadBank(summary, buildTermMetaBankName, termMetaBankCount, termMetaDataLoaded); - await loadBank(summary, buildKanjiBankName, kanjiBankCount, kanjiDataLoaded); - await loadBank(summary, buildKanjiMetaBankName, kanjiMetaBankCount, kanjiMetaDataLoaded); - await loadBank(summary, buildTagBankName, tagBankCount, tagDataLoaded); - - return summary; - } - static _createTerm(row, index) { return { index, -- cgit v1.2.3 From 086fefc9215b0b4d2a63ad371b1e3302f32d725b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 17 Feb 2020 19:43:44 -0500 Subject: Validate data before importing --- ext/bg/js/database.js | 53 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index f85ac7f3..c42b9d89 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -16,11 +16,12 @@ * along with this program. If not, see . */ -/*global dictFieldSplit, JSZip*/ +/*global dictFieldSplit, requestJson, JsonSchema, JSZip*/ class Database { constructor() { this.db = null; + this._schemas = new Map(); } // Public @@ -332,6 +333,9 @@ class Database { const index = JSON.parse(await indexFile.async('string')); + const indexSchema = await this._getSchema('/bg/data/dictionary-index-schema.json'); + JsonSchema.validate(index, indexSchema); + const dictionaryTitle = index.title; const version = index.format || index.version; @@ -381,7 +385,7 @@ class Database { }; // Archive file reading - const readFileSequence = async (fileNameFormat, convertEntry) => { + const readFileSequence = async (fileNameFormat, convertEntry, schema) => { const results = []; for (let i = 1; true; ++i) { const fileName = fileNameFormat.replace(/\?/, `${i}`); @@ -389,6 +393,8 @@ class Database { if (!file) { break; } const entries = JSON.parse(await file.async('string')); + JsonSchema.validate(entries, schema); + for (let entry of entries) { entry = convertEntry(entry); entry.dictionary = dictionaryTitle; @@ -398,12 +404,16 @@ class Database { return results; }; + // Load schemas + const dataBankSchemaPaths = this.constructor._getDataBankSchemaPaths(version); + const dataBankSchemas = await Promise.all(dataBankSchemaPaths.map((path) => this._getSchema(path))); + // Load data - const termList = await readFileSequence('term_bank_?.json', convertTermBankEntry); - const termMetaList = await readFileSequence('term_meta_bank_?.json', convertTermMetaBankEntry); - const kanjiList = await readFileSequence('kanji_bank_?.json', convertKanjiBankEntry); - const kanjiMetaList = await readFileSequence('kanji_meta_bank_?.json', convertKanjiMetaBankEntry); - const tagList = await readFileSequence('tag_bank_?.json', convertTagBankEntry); + const termList = await readFileSequence('term_bank_?.json', convertTermBankEntry, dataBankSchemas[0]); + const termMetaList = await readFileSequence('term_meta_bank_?.json', convertTermMetaBankEntry, dataBankSchemas[1]); + const kanjiList = await readFileSequence('kanji_bank_?.json', convertKanjiBankEntry, dataBankSchemas[2]); + const kanjiMetaList = await readFileSequence('kanji_meta_bank_?.json', convertKanjiMetaBankEntry, dataBankSchemas[3]); + const tagList = await readFileSequence('tag_bank_?.json', convertTagBankEntry, dataBankSchemas[4]); // Old tags const indexTagMeta = index.tagMeta; @@ -487,6 +497,35 @@ class Database { } } + async _getSchema(fileName) { + let schemaPromise = this._schemas.get(fileName); + if (typeof schemaPromise !== 'undefined') { + return schemaPromise; + } + + schemaPromise = requestJson(chrome.runtime.getURL(fileName), 'GET'); + this._schemas.set(fileName, schemaPromise); + return schemaPromise; + } + + static _getDataBankSchemaPaths(version) { + const termBank = ( + version === 1 ? + '/bg/data/dictionary-term-bank-v1-schema.json' : + '/bg/data/dictionary-term-bank-v3-schema.json' + ); + const termMetaBank = '/bg/data/dictionary-term-meta-bank-v3-schema.json'; + const kanjiBank = ( + version === 1 ? + '/bg/data/dictionary-kanji-bank-v1-schema.json' : + '/bg/data/dictionary-kanji-bank-v3-schema.json' + ); + const kanjiMetaBank = '/bg/data/dictionary-kanji-meta-bank-v3-schema.json'; + const tagBank = '/bg/data/dictionary-tag-bank-v3-schema.json'; + + return [termBank, termMetaBank, kanjiBank, kanjiMetaBank, tagBank]; + } + async _dictionaryExists(title) { const db = this.db; const dbCountTransaction = db.transaction(['dictionaries'], 'readonly'); -- cgit v1.2.3 From 62d4f68412687fc27340f3807258d09d1a6c4b10 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 17 Feb 2020 19:46:08 -0500 Subject: Define old tagMeta format in index file --- ext/bg/data/dictionary-index-schema.json | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'ext/bg') diff --git a/ext/bg/data/dictionary-index-schema.json b/ext/bg/data/dictionary-index-schema.json index 9865fcc1..fa1a696b 100644 --- a/ext/bg/data/dictionary-index-schema.json +++ b/ext/bg/data/dictionary-index-schema.json @@ -27,6 +27,33 @@ "version": { "type": "integer", "description": "Alias for format." + }, + "tagMeta": { + "type": "object", + "description": "Tag information for terms and kanji. This object is obsolete and individual tag files should be used instead.", + "additionalProperties": { + "type": "object", + "description": "Information about a single tag. The object key is the name of the tag.", + "properties": { + "category": { + "type": "string", + "description": "Category for the tag." + }, + "order": { + "type": "number", + "description": "Sorting order for the tag." + }, + "notes": { + "type": "string", + "description": "Notes for the tag." + }, + "score": { + "type": "number", + "description": "Score used to determine popularity. Negative values are more rare and positive values are more frequent. This score is also used to sort search results." + } + }, + "additionalProperties": false + } } }, "anyOf": [ -- cgit v1.2.3 From 86d0fb664361251287e6327bc10c6d56c6af5c8c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 17 Feb 2020 19:46:22 -0500 Subject: Require format to be 1, 2, or 3 --- ext/bg/data/dictionary-index-schema.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/data/dictionary-index-schema.json b/ext/bg/data/dictionary-index-schema.json index fa1a696b..9311f14c 100644 --- a/ext/bg/data/dictionary-index-schema.json +++ b/ext/bg/data/dictionary-index-schema.json @@ -22,11 +22,13 @@ }, "format": { "type": "integer", - "description": "Format of data found in the JSON data files." + "description": "Format of data found in the JSON data files.", + "enum": [1, 2, 3] }, "version": { "type": "integer", - "description": "Alias for format." + "description": "Alias for format.", + "enum": [1, 2, 3] }, "tagMeta": { "type": "object", -- cgit v1.2.3 From d76ab91f83db048aa907e0483123feb437336c22 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 22 Feb 2020 12:45:50 -0500 Subject: Update validate function name in close --- ext/bg/js/database.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index c42b9d89..a5919868 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -100,7 +100,7 @@ class Database { } async close() { - this.validate(); + this._validate(); this.db.close(); this.db = null; } -- cgit v1.2.3 From 77a3dadd0b128f4421e16c3465fbf609c483241b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 22 Feb 2020 13:25:28 -0500 Subject: Make schema errors have more information --- ext/bg/js/database.js | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index a5919868..453cf15f 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -326,7 +326,8 @@ class Database { const archive = await JSZip.loadAsync(archiveSource); // Read and validate index - const indexFile = archive.files['index.json']; + const indexFileName = 'index.json'; + const indexFile = archive.files[indexFileName]; if (!indexFile) { throw new Error('No dictionary index found in archive'); } @@ -334,7 +335,7 @@ class Database { const index = JSON.parse(await indexFile.async('string')); const indexSchema = await this._getSchema('/bg/data/dictionary-index-schema.json'); - JsonSchema.validate(index, indexSchema); + Database._validateJsonSchema(index, indexSchema, indexFileName); const dictionaryTitle = index.title; const version = index.format || index.version; @@ -393,7 +394,7 @@ class Database { if (!file) { break; } const entries = JSON.parse(await file.async('string')); - JsonSchema.validate(entries, schema); + Database._validateJsonSchema(entries, schema, fileName); for (let entry of entries) { entry = convertEntry(entry); @@ -508,6 +509,42 @@ class Database { return schemaPromise; } + static _validateJsonSchema(value, schema, fileName) { + try { + JsonSchema.validate(value, schema); + } catch (e) { + throw Database._formatSchemaError(e, fileName); + } + } + + static _formatSchemaError(e, fileName) { + const valuePathString = Database._getSchemaErrorPathString(e.info.valuePath, 'dictionary'); + const schemaPathString = Database._getSchemaErrorPathString(e.info.schemaPath, 'schema'); + + const e2 = new Error(`Dictionary has invalid data in '${fileName}' for value '${valuePathString}', validated against '${schemaPathString}': ${e.message}`); + e2.data = e; + + return e2; + } + + static _getSchemaErrorPathString(infoList, base='') { + let result = base; + for (const [part] of infoList) { + switch (typeof part) { + case 'string': + if (result.length > 0) { + result += '.'; + } + result += part; + break; + case 'number': + result += `[${part}]`; + break; + } + } + return result; + } + static _getDataBankSchemaPaths(version) { const termBank = ( version === 1 ? -- cgit v1.2.3 From 452eec8a881f0fcbdb2d5c75ae8253b158812d10 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 12:27:18 -0500 Subject: Use Map --- ext/bg/js/settings/dictionaries.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index adad76fb..427f47f0 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -491,15 +491,18 @@ function dictionaryErrorsShow(errors) { dialog.textContent = ''; if (errors !== null && errors.length > 0) { - const uniqueErrors = {}; + const uniqueErrors = new Map(); for (let e of errors) { console.error(e); e = dictionaryErrorToString(e); - uniqueErrors[e] = hasOwn(uniqueErrors, e) ? uniqueErrors[e] + 1 : 1; + let count = uniqueErrors.get(e); + if (typeof count === 'undefined') { + count = 0; + } + uniqueErrors.set(e, count + 1); } - for (const e in uniqueErrors) { - const count = uniqueErrors[e]; + for (const [e, count] of uniqueErrors.entries()) { const div = document.createElement('p'); if (count > 1) { div.textContent = `${e} `; -- cgit v1.2.3 From 886278b19de3693f75bb6c7775da8db86a8dd008 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 12:30:07 -0500 Subject: Use Set --- ext/bg/js/translator.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index 3471cb01..b850c6e4 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -400,16 +400,12 @@ class Translator { async findKanji(text, options) { const dictionaries = dictEnabledSet(options); const titles = Object.keys(dictionaries); - const kanjiUnique = {}; - const kanjiList = []; + const kanjiUnique = new Set(); for (const c of text) { - if (!hasOwn(kanjiUnique, c)) { - kanjiList.push(c); - kanjiUnique[c] = true; - } + kanjiUnique.add(c); } - const definitions = await this.database.findKanjiBulk(kanjiList, titles); + const definitions = await this.database.findKanjiBulk([...kanjiUnique], titles); if (definitions.length === 0) { return definitions; } -- cgit v1.2.3 From 1f62dfa572f2eff2a130ef5db7b12bdc2dc7db27 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 12:31:11 -0500 Subject: Use Set --- ext/bg/js/database.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 453cf15f..db59f982 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -153,11 +153,11 @@ class Database { this._validate(); const promises = []; - const visited = {}; + const visited = new Set(); const results = []; const processRow = (row, index) => { - if (titles.includes(row.dictionary) && !hasOwn(visited, row.id)) { - visited[row.id] = true; + if (titles.includes(row.dictionary) && !visited.has(row.id)) { + visited.add(row.id); results.push(Database._createTerm(row, index)); } }; -- cgit v1.2.3 From cae8ed276799f0171aa8a5d77b908b4a751c98e9 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 12:32:45 -0500 Subject: Use Array.isArray instead of hasOwn --- ext/bg/js/audio.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 6389528b..0ecfd5c4 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -168,10 +168,8 @@ async function audioInject(definition, fields, sources, optionsContext) { } try { - let audioSourceDefinition = definition; - if (hasOwn(definition, 'expressions')) { - audioSourceDefinition = definition.expressions[0]; - } + const expressions = definition.expressions; + const audioSourceDefinition = Array.isArray(expressions) ? expressions[0] : definition; const {url} = await audioGetFromSources(audioSourceDefinition, sources, optionsContext, true); if (url !== null) { -- cgit v1.2.3 From bc94970a46171c2cd0f5feb272811487e2e1e110 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 12:45:18 -0500 Subject: Use Map --- ext/bg/js/dictionary.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 491632a0..afdd71a0 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -123,7 +123,7 @@ function dictTermsCompressTags(definitions) { } function dictTermsGroup(definitions, dictionaries) { - const groups = {}; + const groups = new Map(); for (const definition of definitions) { const key = [definition.source, definition.expression]; key.push(...definition.reasons); @@ -132,26 +132,27 @@ function dictTermsGroup(definitions, dictionaries) { } const keyString = key.toString(); - if (hasOwn(groups, keyString)) { - groups[keyString].push(definition); - } else { - groups[keyString] = [definition]; + let groupDefinitions = groups.get(keyString); + if (typeof groupDefinitions === 'undefined') { + groupDefinitions = []; + groups.set(keyString, groupDefinitions); } + + groupDefinitions.push(definition); } const results = []; - for (const key in groups) { - const groupDefs = groups[key]; - const firstDef = groupDefs[0]; - dictTermsSort(groupDefs, dictionaries); + for (const groupDefinitions of groups.values()) { + const firstDef = groupDefinitions[0]; + dictTermsSort(groupDefinitions, dictionaries); results.push({ - definitions: groupDefs, + definitions: groupDefinitions, expression: firstDef.expression, reading: firstDef.reading, furiganaSegments: firstDef.furiganaSegments, reasons: firstDef.reasons, termTags: firstDef.termTags, - score: groupDefs.reduce((p, v) => v.score > p ? v.score : p, Number.MIN_SAFE_INTEGER), + score: groupDefinitions.reduce((p, v) => v.score > p ? v.score : p, Number.MIN_SAFE_INTEGER), source: firstDef.source }); } -- cgit v1.2.3 From 5587116baef8e243412eb9e5ad9690284944a7a3 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 12:55:21 -0500 Subject: Simplify spread --- ext/bg/js/dictionary.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index afdd71a0..52302e9b 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -125,8 +125,7 @@ function dictTermsCompressTags(definitions) { function dictTermsGroup(definitions, dictionaries) { const groups = new Map(); for (const definition of definitions) { - const key = [definition.source, definition.expression]; - key.push(...definition.reasons); + const key = [definition.source, definition.expression, ...definition.reasons]; if (definition.reading) { key.push(definition.reading); } -- cgit v1.2.3 From a4bdffbd9def55cdc8a8a5058be39d9eea42b5e9 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 12:55:35 -0500 Subject: Use Map --- ext/bg/js/dictionary.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 52302e9b..febb27cc 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -79,20 +79,16 @@ function dictTermsSort(definitions, dictionaries=null) { } function dictTermsUndupe(definitions) { - const definitionGroups = {}; + const definitionGroups = new Map(); for (const definition of definitions) { - const definitionExisting = definitionGroups[definition.id]; - if (!hasOwn(definitionGroups, definition.id) || definition.expression.length > definitionExisting.expression.length) { - definitionGroups[definition.id] = definition; + const id = definition.id; + const definitionExisting = definitionGroups.get(id); + if (typeof definitionExisting === 'undefined' || definition.expression.length > definitionExisting.expression.length) { + definitionGroups.set(id, definition); } } - const definitionsUnique = []; - for (const key in definitionGroups) { - definitionsUnique.push(definitionGroups[key]); - } - - return definitionsUnique; + return [...definitionGroups.values()]; } function dictTermsCompressTags(definitions) { -- cgit v1.2.3 From 163211ade323f09ad9de7861e1eeab8d344bacb8 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 13:12:03 -0500 Subject: Use Map --- ext/bg/js/translator.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index b850c6e4..a864712a 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -506,28 +506,29 @@ class Translator { const names = Object.keys(items); const tagMetaList = await this.getTagMetaList(names, title); - const stats = {}; + const statsGroups = new Map(); for (let i = 0; i < names.length; ++i) { const name = names[i]; const meta = tagMetaList[i]; if (meta === null) { continue; } const category = meta.category; - const group = ( - hasOwn(stats, category) ? - stats[category] : - (stats[category] = []) - ); + let group = statsGroups.get(category); + if (typeof group === 'undefined') { + group = []; + statsGroups.set(category, group); + } const stat = Object.assign({}, meta, {name, value: items[name]}); group.push(dictTagSanitize(stat)); } + const stats = {}; const sortCompare = (a, b) => a.notes - b.notes; - for (const category in stats) { - stats[category].sort(sortCompare); + for (const [category, group] of statsGroups.entries()) { + group.sort(sortCompare); + stats[category] = group; } - return stats; } -- cgit v1.2.3 From cc2e21cd866fd02120c593f6a5ad9b5840c0edbb Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 15:01:21 -0500 Subject: Use Map for dictEnabledSet --- ext/bg/js/database.js | 24 ++++++++++++------------ ext/bg/js/dictionary.js | 27 +++++++++++++++------------ ext/bg/js/translator.js | 37 ++++++++++++++++--------------------- 3 files changed, 43 insertions(+), 45 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index db59f982..1f6810cf 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -149,14 +149,14 @@ class Database { await Promise.all(promises); } - async findTermsBulk(termList, titles, wildcard) { + async findTermsBulk(termList, dictionaries, wildcard) { this._validate(); const promises = []; const visited = new Set(); const results = []; const processRow = (row, index) => { - if (titles.includes(row.dictionary) && !visited.has(row.id)) { + if (dictionaries.has(row.dictionary) && !visited.has(row.id)) { visited.add(row.id); results.push(Database._createTerm(row, index)); } @@ -184,13 +184,13 @@ class Database { return results; } - async findTermsExactBulk(termList, readingList, titles) { + async findTermsExactBulk(termList, readingList, dictionaries) { this._validate(); const promises = []; const results = []; const processRow = (row, index) => { - if (row.reading === readingList[index] && titles.includes(row.dictionary)) { + if (row.reading === readingList[index] && dictionaries.has(row.dictionary)) { results.push(Database._createTerm(row, index)); } }; @@ -234,16 +234,16 @@ class Database { return results; } - async findTermMetaBulk(termList, titles) { - return this._findGenericBulk('termMeta', 'expression', termList, titles, Database._createTermMeta); + async findTermMetaBulk(termList, dictionaries) { + return this._findGenericBulk('termMeta', 'expression', termList, dictionaries, Database._createTermMeta); } - async findKanjiBulk(kanjiList, titles) { - return this._findGenericBulk('kanji', 'character', kanjiList, titles, Database._createKanji); + async findKanjiBulk(kanjiList, dictionaries) { + return this._findGenericBulk('kanji', 'character', kanjiList, dictionaries, Database._createKanji); } - async findKanjiMetaBulk(kanjiList, titles) { - return this._findGenericBulk('kanjiMeta', 'character', kanjiList, titles, Database._createKanjiMeta); + async findKanjiMetaBulk(kanjiList, dictionaries) { + return this._findGenericBulk('kanjiMeta', 'character', kanjiList, dictionaries, Database._createKanjiMeta); } async findTagForTitle(name, title) { @@ -572,13 +572,13 @@ class Database { return count > 0; } - async _findGenericBulk(tableName, indexName, indexValueList, titles, createResult) { + async _findGenericBulk(tableName, indexName, indexValueList, dictionaries, createResult) { this._validate(); const promises = []; const results = []; const processRow = (row, index) => { - if (titles.includes(row.dictionary)) { + if (dictionaries.has(row.dictionary)) { results.push(createResult(row, index)); } }; diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index febb27cc..d9a7a865 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -19,15 +19,17 @@ /*global utilSetEqual, utilSetIntersection, apiTemplateRender*/ function dictEnabledSet(options) { - const dictionaries = {}; - for (const title in options.dictionaries) { - const dictionary = options.dictionaries[title]; - if (dictionary.enabled) { - dictionaries[title] = dictionary; - } + 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 + }); } - - return dictionaries; + return enabledDictionaryMap; } function dictConfigured(options) { @@ -58,10 +60,11 @@ function dictTermsSort(definitions, dictionaries=null) { return definitions.sort((v1, v2) => { let i; if (dictionaries !== null) { - i = ( - ((dictionaries[v2.dictionary] || {}).priority || 0) - - ((dictionaries[v1.dictionary] || {}).priority || 0) - ); + const dictionaryInfo1 = dictionaries.get(v1.dictionary); + const dictionaryInfo2 = dictionaries.get(v2.dictionary); + const priority1 = typeof dictionaryInfo1 !== 'undefined' ? dictionaryInfo1.priority || 0 : 0; + const priority2 = typeof dictionaryInfo2 !== 'undefined' ? dictionaryInfo2.priority || 0 : 0; + i = priority2 - priority1; if (i !== 0) { return i; } } diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index a864712a..c7017328 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -85,7 +85,7 @@ class Translator { } } - const definitions = await this.database.findTermsExactBulk(expressionList, readingList, secondarySearchTitles); + const definitions = await this.database.findTermsExactBulk(expressionList, readingList, new Set(secondarySearchTitles)); for (const definition of definitions) { const definitionTags = await this.expandTags(definition.definitionTags, definition.dictionary); definitionTags.push(dictTagBuildSource(definition.dictionary)); @@ -156,11 +156,10 @@ class Translator { async findTermsGrouped(text, details, options) { const dictionaries = dictEnabledSet(options); - const titles = Object.keys(dictionaries); const [definitions, length] = await this.findTermsInternal(text, dictionaries, details, options); const definitionsGrouped = dictTermsGroup(definitions, dictionaries); - await this.buildTermMeta(definitionsGrouped, titles); + await this.buildTermMeta(definitionsGrouped, dictionaries); if (options.general.compactTags) { for (const definition of definitionsGrouped) { @@ -174,7 +173,6 @@ class Translator { async findTermsMerged(text, details, options) { const dictionaries = dictEnabledSet(options); const secondarySearchTitles = Object.keys(options.dictionaries).filter((dict) => options.dictionaries[dict].allowSecondarySearches); - const titles = Object.keys(dictionaries); const [definitions, length] = await this.findTermsInternal(text, dictionaries, details, options); const {sequencedDefinitions, defaultDefinitions} = await this.getSequencedDefinitions(definitions, options.general.mainDictionary); const definitionsMerged = []; @@ -198,7 +196,7 @@ class Translator { definitionsMerged.push(groupedDefinition); } - await this.buildTermMeta(definitionsMerged, titles); + await this.buildTermMeta(definitionsMerged, dictionaries); if (options.general.compactTags) { for (const definition of definitionsMerged) { @@ -211,10 +209,9 @@ class Translator { async findTermsSplit(text, details, options) { const dictionaries = dictEnabledSet(options); - const titles = Object.keys(dictionaries); const [definitions, length] = await this.findTermsInternal(text, dictionaries, details, options); - await this.buildTermMeta(definitions, titles); + await this.buildTermMeta(definitions, dictionaries); return [definitions, length]; } @@ -225,11 +222,10 @@ class Translator { return [[], 0]; } - const titles = Object.keys(dictionaries); const deinflections = ( details.wildcard ? - await this.findTermWildcard(text, titles, details.wildcard) : - await this.findTermDeinflections(text, titles, options) + await this.findTermWildcard(text, dictionaries, details.wildcard) : + await this.findTermDeinflections(text, dictionaries, options) ); let definitions = []; @@ -271,8 +267,8 @@ class Translator { return [definitions, length]; } - async findTermWildcard(text, titles, wildcard) { - const definitions = await this.database.findTermsBulk([text], titles, wildcard); + async findTermWildcard(text, dictionaries, wildcard) { + const definitions = await this.database.findTermsBulk([text], dictionaries, wildcard); if (definitions.length === 0) { return []; } @@ -287,7 +283,7 @@ class Translator { }]; } - async findTermDeinflections(text, titles, options) { + async findTermDeinflections(text, dictionaries, options) { const deinflections = this.getAllDeinflections(text, options); if (deinflections.length === 0) { @@ -309,7 +305,7 @@ class Translator { deinflectionArray.push(deinflection); } - const definitions = await this.database.findTermsBulk(uniqueDeinflectionTerms, titles, null); + const definitions = await this.database.findTermsBulk(uniqueDeinflectionTerms, dictionaries, null); for (const definition of definitions) { const definitionRules = Deinflector.rulesToRuleFlags(definition.rules); @@ -399,13 +395,12 @@ class Translator { async findKanji(text, options) { const dictionaries = dictEnabledSet(options); - const titles = Object.keys(dictionaries); const kanjiUnique = new Set(); for (const c of text) { kanjiUnique.add(c); } - const definitions = await this.database.findKanjiBulk([...kanjiUnique], titles); + const definitions = await this.database.findKanjiBulk([...kanjiUnique], dictionaries); if (definitions.length === 0) { return definitions; } @@ -425,12 +420,12 @@ class Translator { definition.stats = stats; } - await this.buildKanjiMeta(definitions, titles); + await this.buildKanjiMeta(definitions, dictionaries); return definitions; } - async buildTermMeta(definitions, titles) { + async buildTermMeta(definitions, dictionaries) { const terms = []; for (const definition of definitions) { if (definition.expressions) { @@ -464,7 +459,7 @@ class Translator { term.frequencies = []; } - const metas = await this.database.findTermMetaBulk(expressionsUnique, titles); + const metas = await this.database.findTermMetaBulk(expressionsUnique, dictionaries); for (const {expression, mode, data, dictionary, index} of metas) { switch (mode) { case 'freq': @@ -476,14 +471,14 @@ class Translator { } } - async buildKanjiMeta(definitions, titles) { + async buildKanjiMeta(definitions, dictionaries) { const kanjiList = []; for (const definition of definitions) { kanjiList.push(definition.character); definition.frequencies = []; } - const metas = await this.database.findKanjiMetaBulk(kanjiList, titles); + const metas = await this.database.findKanjiMetaBulk(kanjiList, dictionaries); for (const {character, mode, data, dictionary, index} of metas) { switch (mode) { case 'freq': -- cgit v1.2.3 From 1c828d205c7cb8fd095b70ee9cd70f14ac5b044d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 15:01:44 -0500 Subject: Remove unused dictRowsSort --- ext/bg/js/dictionary.js | 14 -------------- 1 file changed, 14 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index d9a7a865..afdcc6b9 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -42,20 +42,6 @@ function dictConfigured(options) { return false; } -function dictRowsSort(rows, options) { - return rows.sort((ra, rb) => { - const pa = (options.dictionaries[ra.title] || {}).priority || 0; - const pb = (options.dictionaries[rb.title] || {}).priority || 0; - if (pa > pb) { - return -1; - } else if (pa < pb) { - return 1; - } else { - return 0; - } - }); -} - function dictTermsSort(definitions, dictionaries=null) { return definitions.sort((v1, v2) => { let i; -- cgit v1.2.3 From b6718dc4a402c07680b4f1e894a632373068350d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 15:09:59 -0500 Subject: Use findTerms instead of findTermsInternal --- ext/bg/js/backend.js | 7 ++++--- ext/bg/js/translator.js | 11 +++++++++-- 2 files changed, 13 insertions(+), 5 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index a4d085e0..16dffc85 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -319,7 +319,8 @@ class Backend { async _onApiTermsFind({text, details, optionsContext}) { const options = await this.getOptions(optionsContext); - const [definitions, length] = await this.translator.findTerms(text, details, options); + const mode = options.general.resultOutputMode; + const [definitions, length] = await this.translator.findTerms(mode, text, details, options); definitions.splice(options.general.maxResults); return {length, definitions}; } @@ -329,9 +330,9 @@ class Backend { const results = []; while (text.length > 0) { const term = []; - const [definitions, sourceLength] = await this.translator.findTermsInternal( + const [definitions, sourceLength] = await this.translator.findTerms( + 'simple', text.substring(0, options.scanning.length), - dictEnabledSet(options), {}, options ); diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index c7017328..17b9e4ff 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -141,14 +141,16 @@ class Translator { return result; } - async findTerms(text, details, options) { - switch (options.general.resultOutputMode) { + async findTerms(mode, text, details, options) { + switch (mode) { case 'group': return await this.findTermsGrouped(text, details, options); case 'merge': return await this.findTermsMerged(text, details, options); case 'split': return await this.findTermsSplit(text, details, options); + case 'simple': + return await this.findTermsSimple(text, details, options); default: return [[], 0]; } @@ -216,6 +218,11 @@ class Translator { return [definitions, length]; } + async findTermsSimple(text, details, options) { + const dictionaries = dictEnabledSet(options); + return await this.findTermsInternal(text, dictionaries, details, options); + } + async findTermsInternal(text, dictionaries, details, options) { text = Translator.getSearchableText(text, options); if (text.length === 0) { -- cgit v1.2.3 From 5e8b408a2317e8280a9c0e84681d304b6c8167f1 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 15:25:22 -0500 Subject: Change secondarySearchTitles to secondarySearchDictionaries --- ext/bg/js/dictionary.js | 3 ++- ext/bg/js/translator.js | 19 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index afdcc6b9..4a0b46cb 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -26,7 +26,8 @@ function dictEnabledSet(options) { const dictionary = optionsDictionaries[title]; if (!dictionary.enabled) { continue; } enabledDictionaryMap.set(title, { - priority: dictionary.priority || 0 + priority: dictionary.priority || 0, + allowSecondarySearches: !!dictionary.allowSecondarySearches }); } return enabledDictionaryMap; diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index 17b9e4ff..0a3d535f 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -70,8 +70,8 @@ class Translator { return {sequencedDefinitions, defaultDefinitions}; } - async getMergedSecondarySearchResults(text, expressionsMap, secondarySearchTitles) { - if (secondarySearchTitles.length === 0) { + async getMergedSecondarySearchResults(text, expressionsMap, secondarySearchDictionaries) { + if (secondarySearchDictionaries.length === 0) { return []; } @@ -85,7 +85,7 @@ class Translator { } } - const definitions = await this.database.findTermsExactBulk(expressionList, readingList, new Set(secondarySearchTitles)); + const definitions = await this.database.findTermsExactBulk(expressionList, readingList, secondarySearchDictionaries); for (const definition of definitions) { const definitionTags = await this.expandTags(definition.definitionTags, definition.dictionary); definitionTags.push(dictTagBuildSource(definition.dictionary)); @@ -101,7 +101,7 @@ class Translator { return definitions; } - async getMergedDefinition(text, dictionaries, sequencedDefinition, defaultDefinitions, secondarySearchTitles, mergedByTermIndices) { + async getMergedDefinition(text, dictionaries, sequencedDefinition, defaultDefinitions, secondarySearchDictionaries, mergedByTermIndices) { const result = sequencedDefinition.definitions; const rawDefinitionsBySequence = sequencedDefinition.rawDefinitions; @@ -114,7 +114,7 @@ class Translator { } const definitionsByGloss = dictTermsMergeByGloss(result, rawDefinitionsBySequence); - const secondarySearchResults = await this.getMergedSecondarySearchResults(text, result.expressions, secondarySearchTitles); + const secondarySearchResults = await this.getMergedSecondarySearchResults(text, result.expressions, secondarySearchDictionaries); dictTermsMergeByGloss(result, defaultDefinitions.concat(secondarySearchResults), definitionsByGloss, mergedByTermIndices); @@ -174,7 +174,12 @@ class Translator { async findTermsMerged(text, details, options) { const dictionaries = dictEnabledSet(options); - const secondarySearchTitles = Object.keys(options.dictionaries).filter((dict) => options.dictionaries[dict].allowSecondarySearches); + const secondarySearchDictionaries = new Map(); + for (const [title, dictionary] of dictionaries.entries()) { + if (!dictionary.allowSecondarySearches) { continue; } + secondarySearchDictionaries.set(title, dictionary); + } + const [definitions, length] = await this.findTermsInternal(text, dictionaries, details, options); const {sequencedDefinitions, defaultDefinitions} = await this.getSequencedDefinitions(definitions, options.general.mainDictionary); const definitionsMerged = []; @@ -186,7 +191,7 @@ class Translator { dictionaries, sequencedDefinition, defaultDefinitions, - secondarySearchTitles, + secondarySearchDictionaries, mergedByTermIndices ); definitionsMerged.push(result); -- cgit v1.2.3 From 89af73b61b04c9bf9042aec6bad9539492a4233c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 16:17:11 -0500 Subject: Use Map --- ext/bg/js/dictionary.js | 27 ++++++++++++++------------- ext/bg/js/translator.js | 3 +-- 2 files changed, 15 insertions(+), 15 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 4a0b46cb..5a85bb5b 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -175,10 +175,10 @@ function dictTermsMergeBySequence(definitions, mainDictionary) { return [sequencedDefinitions, nonSequencedDefinitions]; } -function dictTermsMergeByGloss(result, definitions, appendTo, mergedIndices) { - const definitionsByGloss = appendTo || {}; +function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices=null) { + const definitionsByGloss = appendTo !== null ? appendTo : new Map(); for (const [index, definition] of definitions.entries()) { - if (appendTo) { + if (appendTo !== null) { let match = false; for (const expression of result.expressions.keys()) { if (definition.expression === expression) { @@ -196,14 +196,15 @@ function dictTermsMergeByGloss(result, definitions, appendTo, mergedIndices) { if (!match) { continue; - } else if (mergedIndices) { + } else if (mergedIndices !== null) { mergedIndices.add(index); } } const gloss = JSON.stringify(definition.glossary.concat(definition.dictionary)); - if (!definitionsByGloss[gloss]) { - definitionsByGloss[gloss] = { + let glossDefinition = definitionsByGloss.get(gloss); + if (typeof glossDefinition === 'undefined') { + glossDefinition = { expression: new Set(), reading: new Set(), definitionTags: [], @@ -214,21 +215,22 @@ function dictTermsMergeByGloss(result, definitions, appendTo, mergedIndices) { id: definition.id, dictionary: definition.dictionary }; + definitionsByGloss.set(gloss, glossDefinition); } - definitionsByGloss[gloss].expression.add(definition.expression); - definitionsByGloss[gloss].reading.add(definition.reading); + glossDefinition.expression.add(definition.expression); + glossDefinition.reading.add(definition.reading); result.expression.add(definition.expression); result.reading.add(definition.reading); for (const tag of definition.definitionTags) { - if (!definitionsByGloss[gloss].definitionTags.find((existingTag) => existingTag.name === tag.name)) { - definitionsByGloss[gloss].definitionTags.push(tag); + if (!glossDefinition.definitionTags.find((existingTag) => existingTag.name === tag.name)) { + glossDefinition.definitionTags.push(tag); } } - if (!appendTo) { + if (appendTo === null) { // result->expressions[ Expression1[ Reading1[ Tag1, Tag2 ] ], Expression2, ... ] if (!result.expressions.has(definition.expression)) { result.expressions.set(definition.expression, new Map()); @@ -245,8 +247,7 @@ function dictTermsMergeByGloss(result, definitions, appendTo, mergedIndices) { } } - for (const gloss in definitionsByGloss) { - const definition = definitionsByGloss[gloss]; + for (const definition of definitionsByGloss.values()) { definition.only = []; if (!utilSetEqual(definition.expression, result.expression)) { for (const expression of utilSetIntersection(definition.expression, result.expression)) { diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index 0a3d535f..9a69e30e 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -118,8 +118,7 @@ class Translator { dictTermsMergeByGloss(result, defaultDefinitions.concat(secondarySearchResults), definitionsByGloss, mergedByTermIndices); - for (const gloss in definitionsByGloss) { - const definition = definitionsByGloss[gloss]; + for (const definition of definitionsByGloss.values()) { dictTagsSort(definition.definitionTags); result.definitions.push(definition); } -- cgit v1.2.3 From 50a171bc75f3329fec9d0aa24c4838f675a1b66d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 17:00:01 -0500 Subject: Simplify update of mergedIndices --- ext/bg/js/dictionary.js | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 5a85bb5b..532d17c7 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -178,26 +178,15 @@ function dictTermsMergeBySequence(definitions, mainDictionary) { function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices=null) { const definitionsByGloss = appendTo !== null ? appendTo : new Map(); for (const [index, definition] of definitions.entries()) { - if (appendTo !== null) { - let match = false; - for (const expression of result.expressions.keys()) { - if (definition.expression === expression) { - for (const reading of result.expressions.get(expression).keys()) { - if (definition.reading === reading) { - match = true; - break; - } - } - } - if (match) { - break; - } - } - - if (!match) { - continue; - } else if (mergedIndices !== null) { + if (mergedIndices !== null) { + const expressionMap = result.expressions.get(definition.expression); + if ( + typeof expressionMap !== 'undefined' && + typeof expressionMap.get(definition.reading) !== 'undefined' + ) { mergedIndices.add(index); + } else { + continue; } } -- cgit v1.2.3 From 45c685d00a7b98c33118dc50efd780e5be483936 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 17:05:09 -0500 Subject: Add variables for expression and reading --- ext/bg/js/dictionary.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 532d17c7..3a4a58a1 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -178,11 +178,13 @@ function dictTermsMergeBySequence(definitions, mainDictionary) { function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices=null) { const definitionsByGloss = appendTo !== null ? appendTo : new Map(); for (const [index, definition] of definitions.entries()) { + const {expression, reading} = definition; + if (mergedIndices !== null) { - const expressionMap = result.expressions.get(definition.expression); + const expressionMap = result.expressions.get(expression); if ( typeof expressionMap !== 'undefined' && - typeof expressionMap.get(definition.reading) !== 'undefined' + typeof expressionMap.get(reading) !== 'undefined' ) { mergedIndices.add(index); } else { @@ -207,11 +209,11 @@ function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices definitionsByGloss.set(gloss, glossDefinition); } - glossDefinition.expression.add(definition.expression); - glossDefinition.reading.add(definition.reading); + glossDefinition.expression.add(expression); + glossDefinition.reading.add(reading); - result.expression.add(definition.expression); - result.reading.add(definition.reading); + result.expression.add(expression); + result.reading.add(reading); for (const tag of definition.definitionTags) { if (!glossDefinition.definitionTags.find((existingTag) => existingTag.name === tag.name)) { @@ -221,16 +223,16 @@ function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices if (appendTo === null) { // result->expressions[ Expression1[ Reading1[ Tag1, Tag2 ] ], Expression2, ... ] - if (!result.expressions.has(definition.expression)) { - result.expressions.set(definition.expression, new Map()); + if (!result.expressions.has(expression)) { + result.expressions.set(expression, new Map()); } - if (!result.expressions.get(definition.expression).has(definition.reading)) { - result.expressions.get(definition.expression).set(definition.reading, []); + if (!result.expressions.get(expression).has(reading)) { + result.expressions.get(expression).set(reading, []); } for (const tag of definition.termTags) { - if (!result.expressions.get(definition.expression).get(definition.reading).find((existingTag) => existingTag.name === tag.name)) { - result.expressions.get(definition.expression).get(definition.reading).push(tag); + if (!result.expressions.get(expression).get(reading).find((existingTag) => existingTag.name === tag.name)) { + result.expressions.get(expression).get(reading).push(tag); } } } @@ -251,7 +253,7 @@ function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices } return definitionsByGloss; -} + } function dictTagBuildSource(name) { return dictTagSanitize({name, category: 'dictionary', order: 100}); -- cgit v1.2.3 From 69b5007842e08a8fda4016712a5046f81799e638 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 17:15:24 -0500 Subject: Reduce number of redundant Map.get calls in dictTermsMergeByGloss --- ext/bg/js/dictionary.js | 31 ++++++++++++++++++++++++------- ext/bg/js/translator.js | 3 ++- 2 files changed, 26 insertions(+), 8 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 3a4a58a1..786546ea 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -222,17 +222,34 @@ function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices } if (appendTo === null) { - // result->expressions[ Expression1[ Reading1[ Tag1, Tag2 ] ], Expression2, ... ] - if (!result.expressions.has(expression)) { - result.expressions.set(expression, new Map()); + /* + Data layout: + result.expressions = new Map([ + [expression, new Map([ + [reading, new Map([ + [tagName, tagInfo], + ... + ])], + ... + ])], + ... + ]); + */ + let readingMap = result.expressions.get(expression); + if (typeof readingMap === 'undefined') { + readingMap = new Map(); + result.expressions.set(expression, readingMap); } - if (!result.expressions.get(expression).has(reading)) { - result.expressions.get(expression).set(reading, []); + + let termTagsMap = readingMap.get(reading); + if (typeof termTagsMap === 'undefined') { + termTagsMap = new Map(); + readingMap.set(reading, termTagsMap); } for (const tag of definition.termTags) { - if (!result.expressions.get(expression).get(reading).find((existingTag) => existingTag.name === tag.name)) { - result.expressions.get(expression).get(reading).push(tag); + if (!termTagsMap.has(tag.name)) { + termTagsMap.set(tag.name, tag); } } } diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index 9a69e30e..6cf07d93 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -127,7 +127,8 @@ class Translator { const expressions = []; for (const [expression, readingMap] of result.expressions.entries()) { - for (const [reading, termTags] of readingMap.entries()) { + for (const [reading, termTagsMap] of readingMap.entries()) { + const termTags = [...termTagsMap.values()]; const score = termTags.map((tag) => tag.score).reduce((p, v) => p + v, 0); expressions.push(Translator.createExpression(expression, reading, dictTagsSort(termTags), Translator.scoreToTermFrequency(score))); } -- cgit v1.2.3 From 19fb7dacb4c951906c5ce3094711f436b0e06c02 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 17:23:29 -0500 Subject: Assign result properties to variables to reduce number of lookups --- ext/bg/js/dictionary.js | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 786546ea..1bc3c93c 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -177,11 +177,17 @@ function dictTermsMergeBySequence(definitions, mainDictionary) { function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices=null) { const definitionsByGloss = appendTo !== null ? appendTo : new Map(); + + const resultExpressionsMap = result.expressions; + const resultExpressionSet = result.expression; + const resultReadingSet = result.reading; + const resultSource = result.source; + for (const [index, definition] of definitions.entries()) { const {expression, reading} = definition; if (mergedIndices !== null) { - const expressionMap = result.expressions.get(expression); + const expressionMap = resultExpressionsMap.get(expression); if ( typeof expressionMap !== 'undefined' && typeof expressionMap.get(reading) !== 'undefined' @@ -200,7 +206,7 @@ function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices reading: new Set(), definitionTags: [], glossary: definition.glossary, - source: result.source, + source: resultSource, reasons: [], score: definition.score, id: definition.id, @@ -212,8 +218,8 @@ function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices glossDefinition.expression.add(expression); glossDefinition.reading.add(reading); - result.expression.add(expression); - result.reading.add(reading); + resultExpressionSet.add(expression); + resultReadingSet.add(reading); for (const tag of definition.definitionTags) { if (!glossDefinition.definitionTags.find((existingTag) => existingTag.name === tag.name)) { @@ -235,10 +241,10 @@ function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices ... ]); */ - let readingMap = result.expressions.get(expression); + let readingMap = resultExpressionsMap.get(expression); if (typeof readingMap === 'undefined') { readingMap = new Map(); - result.expressions.set(expression, readingMap); + resultExpressionsMap.set(expression, readingMap); } let termTagsMap = readingMap.get(reading); @@ -257,13 +263,13 @@ function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices for (const definition of definitionsByGloss.values()) { definition.only = []; - if (!utilSetEqual(definition.expression, result.expression)) { - for (const expression of utilSetIntersection(definition.expression, result.expression)) { + if (!utilSetEqual(definition.expression, resultExpressionSet)) { + for (const expression of utilSetIntersection(definition.expression, resultExpressionSet)) { definition.only.push(expression); } } - if (!utilSetEqual(definition.reading, result.reading)) { - for (const reading of utilSetIntersection(definition.reading, result.reading)) { + if (!utilSetEqual(definition.reading, resultReadingSet)) { + for (const reading of utilSetIntersection(definition.reading, resultReadingSet)) { definition.only.push(reading); } } -- cgit v1.2.3 From f143632f28d3bc2f89144958ff49e9e0b1932c96 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 17:31:08 -0500 Subject: Simplify only assignment --- ext/bg/js/dictionary.js | 43 +++++++++++++++++++++++++++++++++---------- ext/bg/js/util.js | 26 -------------------------- 2 files changed, 33 insertions(+), 36 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index 1bc3c93c..bffa7afa 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -/*global utilSetEqual, utilSetIntersection, apiTemplateRender*/ +/*global apiTemplateRender*/ function dictEnabledSet(options) { const enabledDictionaryMap = new Map(); @@ -145,6 +145,30 @@ function dictTermsGroup(definitions, dictionaries) { return dictTermsSort(results); } +function dictAreSetsEqual(set1, set2) { + if (set1.size !== set2.size) { + return false; + } + + for (const value of set1) { + if (!set2.has(value)) { + return false; + } + } + + return true; +} + +function dictGetSetIntersection(set1, set2) { + const result = []; + for (const value of set1) { + if (set2.has(value)) { + result.push(value); + } + } + return result; +} + function dictTermsMergeBySequence(definitions, mainDictionary) { const sequencedDefinitions = new Map(); const nonSequencedDefinitions = []; @@ -262,16 +286,15 @@ function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices } for (const definition of definitionsByGloss.values()) { - definition.only = []; - if (!utilSetEqual(definition.expression, resultExpressionSet)) { - for (const expression of utilSetIntersection(definition.expression, resultExpressionSet)) { - definition.only.push(expression); - } + const only = []; + const expressionSet = definition.expression; + const readingSet = definition.reading; + definition.only = only; + if (!dictAreSetsEqual(expressionSet, resultExpressionSet)) { + only.push(...dictGetSetIntersection(expressionSet, resultExpressionSet)); } - if (!utilSetEqual(definition.reading, resultReadingSet)) { - for (const reading of utilSetIntersection(definition.reading, resultReadingSet)) { - definition.only.push(reading); - } + if (!dictAreSetsEqual(readingSet, resultReadingSet)) { + only.push(...dictGetSetIntersection(readingSet, resultReadingSet)); } } diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index 333e814b..9ebd2ac4 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -59,32 +59,6 @@ function utilBackgroundFunctionIsolate(func) { return backgroundPage.utilFunctionIsolate(func); } -function utilSetEqual(setA, setB) { - if (setA.size !== setB.size) { - return false; - } - - for (const value of setA) { - if (!setB.has(value)) { - return false; - } - } - - return true; -} - -function utilSetIntersection(setA, setB) { - return new Set( - [...setA].filter((value) => setB.has(value)) - ); -} - -function utilSetDifference(setA, setB) { - return new Set( - [...setA].filter((value) => !setB.has(value)) - ); -} - function utilStringHashCode(string) { let hashCode = 0; -- cgit v1.2.3 From 54bb702b843ae9a312704f27e282bf340e8197fc Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 22 Feb 2020 11:30:24 -0500 Subject: Updates for PR feedback --- ext/bg/js/dictionary.js | 10 +++++----- ext/bg/js/translator.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js index bffa7afa..f5c5b21b 100644 --- a/ext/bg/js/dictionary.js +++ b/ext/bg/js/dictionary.js @@ -49,8 +49,8 @@ function dictTermsSort(definitions, dictionaries=null) { if (dictionaries !== null) { const dictionaryInfo1 = dictionaries.get(v1.dictionary); const dictionaryInfo2 = dictionaries.get(v2.dictionary); - const priority1 = typeof dictionaryInfo1 !== 'undefined' ? dictionaryInfo1.priority || 0 : 0; - const priority2 = typeof dictionaryInfo2 !== 'undefined' ? dictionaryInfo2.priority || 0 : 0; + const priority1 = typeof dictionaryInfo1 !== 'undefined' ? dictionaryInfo1.priority : 0; + const priority2 = typeof dictionaryInfo2 !== 'undefined' ? dictionaryInfo2.priority : 0; i = priority2 - priority1; if (i !== 0) { return i; } } @@ -254,7 +254,7 @@ function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices if (appendTo === null) { /* Data layout: - result.expressions = new Map([ + resultExpressionsMap = new Map([ [expression, new Map([ [reading, new Map([ [tagName, tagInfo], @@ -289,17 +289,17 @@ function dictTermsMergeByGloss(result, definitions, appendTo=null, mergedIndices const only = []; const expressionSet = definition.expression; const readingSet = definition.reading; - definition.only = only; if (!dictAreSetsEqual(expressionSet, resultExpressionSet)) { only.push(...dictGetSetIntersection(expressionSet, resultExpressionSet)); } if (!dictAreSetsEqual(readingSet, resultReadingSet)) { only.push(...dictGetSetIntersection(readingSet, resultReadingSet)); } + definition.only = only; } return definitionsByGloss; - } +} function dictTagBuildSource(name) { return dictTagSanitize({name, category: 'dictionary', order: 100}); diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index 6cf07d93..a675a9f7 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -71,7 +71,7 @@ class Translator { } async getMergedSecondarySearchResults(text, expressionsMap, secondarySearchDictionaries) { - if (secondarySearchDictionaries.length === 0) { + if (secondarySearchDictionaries.size === 0) { return []; } -- cgit v1.2.3 From 1944f7a2157fd63fd04beb374ee5d9fb797e2531 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 19:46:20 -0500 Subject: Fix indent --- ext/bg/js/page-exit-prevention.js | 74 +++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 37 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/page-exit-prevention.js b/ext/bg/js/page-exit-prevention.js index 3a320db3..be06c495 100644 --- a/ext/bg/js/page-exit-prevention.js +++ b/ext/bg/js/page-exit-prevention.js @@ -18,43 +18,43 @@ class PageExitPrevention { - constructor() { - } - - start() { - PageExitPrevention._addInstance(this); - } - - end() { - PageExitPrevention._removeInstance(this); - } - - static _addInstance(instance) { - const size = PageExitPrevention._instances.size; - PageExitPrevention._instances.set(instance, true); - if (size === 0) { - window.addEventListener('beforeunload', PageExitPrevention._onBeforeUnload); - } - } - - static _removeInstance(instance) { - if ( - PageExitPrevention._instances.delete(instance) && - PageExitPrevention._instances.size === 0 - ) { - window.removeEventListener('beforeunload', PageExitPrevention._onBeforeUnload); - } - } - - static _onBeforeUnload(e) { - if (PageExitPrevention._instances.size === 0) { - return; - } - - e.preventDefault(); - e.returnValue = ''; - return ''; - } + constructor() { + } + + start() { + PageExitPrevention._addInstance(this); + } + + end() { + PageExitPrevention._removeInstance(this); + } + + static _addInstance(instance) { + const size = PageExitPrevention._instances.size; + PageExitPrevention._instances.set(instance, true); + if (size === 0) { + window.addEventListener('beforeunload', PageExitPrevention._onBeforeUnload); + } + } + + static _removeInstance(instance) { + if ( + PageExitPrevention._instances.delete(instance) && + PageExitPrevention._instances.size === 0 + ) { + window.removeEventListener('beforeunload', PageExitPrevention._onBeforeUnload); + } + } + + static _onBeforeUnload(e) { + if (PageExitPrevention._instances.size === 0) { + return; + } + + e.preventDefault(); + e.returnValue = ''; + return ''; + } } PageExitPrevention._instances = new Map(); -- cgit v1.2.3 From 6d75637ace341b79a6a12c7854b13fb1c279498f Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 19:48:02 -0500 Subject: Fix brace style issues --- ext/bg/js/backend.js | 21 +++++++++++++++------ ext/fg/js/document.js | 3 +-- 2 files changed, 16 insertions(+), 8 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 16dffc85..7c71f82c 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -801,8 +801,11 @@ class Backend { await new Promise((resolve, reject) => { chrome.tabs.update(tab.id, {active: true}, () => { const e = chrome.runtime.lastError; - if (e) { reject(e); } - else { resolve(); } + if (e) { + reject(e); + } else { + resolve(); + } }); }); @@ -815,16 +818,22 @@ class Backend { const tabWindow = await new Promise((resolve, reject) => { chrome.windows.get(tab.windowId, {}, (tabWindow) => { const e = chrome.runtime.lastError; - if (e) { reject(e); } - else { resolve(tabWindow); } + if (e) { + reject(e); + } else { + resolve(tabWindow); + } }); }); if (!tabWindow.focused) { await new Promise((resolve, reject) => { chrome.windows.update(tab.windowId, {focused: true}, () => { const e = chrome.runtime.lastError; - if (e) { reject(e); } - else { resolve(); } + if (e) { + reject(e); + } else { + resolve(); + } }); }); } diff --git a/ext/fg/js/document.js b/ext/fg/js/document.js index 7284cdd1..ea9ac965 100644 --- a/ext/fg/js/document.js +++ b/ext/fg/js/document.js @@ -192,8 +192,7 @@ function docSentenceExtract(source, extent) { if (terminators.includes(c)) { endPos = i + 1; break; - } - else if (c in quotesBwd) { + } else if (c in quotesBwd) { endPos = i; break; } -- cgit v1.2.3 From aa76113e756391da64d84d368c3b103c873519cb Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 19:49:07 -0500 Subject: Fix block padding --- ext/bg/js/settings/backup.js | 1 - ext/mixed/js/audio.js | 2 -- 2 files changed, 3 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/backup.js b/ext/bg/js/settings/backup.js index 6d1f28e9..f4d622a4 100644 --- a/ext/bg/js/settings/backup.js +++ b/ext/bg/js/settings/backup.js @@ -163,7 +163,6 @@ async function _showSettingsImportWarnings(warnings) { sanitize: e.currentTarget.dataset.importSanitize === 'true' }); modalNode.modal('hide'); - }; const onModalHide = () => { complete({result: false}); diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js index fe5982dd..b5a025be 100644 --- a/ext/mixed/js/audio.js +++ b/ext/mixed/js/audio.js @@ -54,7 +54,6 @@ class TextToSpeechAudio { speechSynthesis.cancel(); speechSynthesis.speak(this._utterance); - } catch (e) { // NOP } @@ -82,7 +81,6 @@ class TextToSpeechAudio { return new TextToSpeechAudio(text, voice); } - } function audioGetFromUrl(url, willDownload) { -- cgit v1.2.3 From c3e7280365a78f648ab15c8460cabfb276e077d4 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 19:49:20 -0500 Subject: Simplify ternary --- ext/bg/js/search-query-parser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 3d38e6e8..8c434990 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -59,7 +59,7 @@ class QueryParser extends TextScanner { this.search.setContent('terms', {definitions, context: { focus: false, - disableHistory: cause === 'mouse' ? true : false, + disableHistory: cause === 'mouse', sentence: {text: searchText, offset: 0}, url: window.location.href }}); -- cgit v1.2.3 From 56567903a5935a7e57e7a8134e3c118a2188babb Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 15 Feb 2020 19:49:56 -0500 Subject: Fix unnamed functions --- ext/bg/js/settings/popup-preview-frame.js | 4 ++-- ext/bg/js/util.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/popup-preview-frame.js b/ext/bg/js/settings/popup-preview-frame.js index 890b8c96..aa2b6100 100644 --- a/ext/bg/js/settings/popup-preview-frame.js +++ b/ext/bg/js/settings/popup-preview-frame.js @@ -60,8 +60,8 @@ class SettingsPopupPreview { this.frontend = new Frontend(this.popup); - this.frontend.setEnabled = function () {}; - this.frontend.searchClear = function () {}; + this.frontend.setEnabled = () => {}; + this.frontend.searchClear = () => {}; await this.frontend.prepare(); diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index 9ebd2ac4..5ce4b08c 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -33,7 +33,7 @@ function utilIsolate(value) { } function utilFunctionIsolate(func) { - return function (...args) { + return function isolatedFunction(...args) { try { args = args.map((v) => utilIsolate(v)); return func.call(this, ...args); -- cgit v1.2.3 From c0d91bffc461c61af9d35aecb7c92e15648382b2 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 17 Feb 2020 15:21:30 -0500 Subject: Add no-shadow --- .eslintrc.json | 1 + ext/bg/js/backend.js | 32 ++++++++++++++++---------------- ext/bg/js/database.js | 4 ++-- ext/bg/js/japanese.js | 12 ++++++------ ext/bg/js/json-schema.js | 2 +- ext/bg/js/search.js | 4 ++-- ext/bg/js/settings/dictionaries.js | 2 +- ext/mixed/js/core.js | 6 +++--- 8 files changed, 32 insertions(+), 31 deletions(-) (limited to 'ext/bg') diff --git a/.eslintrc.json b/.eslintrc.json index b21c00f4..cfee7be8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -26,6 +26,7 @@ "no-const-assign": "error", "no-constant-condition": "off", "no-global-assign": "error", + "no-shadow": ["error", {"builtinGlobals": false}], "no-undef": "error", "no-unneeded-ternary": "error", "no-unused-vars": ["error", {"vars": "local", "args": "after-used", "argsIgnorePattern": "^_", "caughtErrors": "none"}], diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 7c71f82c..2691b7d9 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -274,18 +274,18 @@ class Backend { const node = nodes.pop(); for (const key of Object.keys(node.obj)) { const path = node.path.concat(key); - const obj = node.obj[key]; - if (obj !== null && typeof obj === 'object') { - nodes.unshift({obj, path}); + const obj2 = node.obj[key]; + if (obj2 !== null && typeof obj2 === 'object') { + nodes.unshift({obj: obj2, path}); } else { - valuePaths.push([obj, path]); + valuePaths.push([obj2, path]); } } } return valuePaths; } - function modifyOption(path, value, options) { + function modifyOption(path, value) { let pivot = options; for (const key of path.slice(0, -1)) { if (!hasOwn(pivot, key)) { @@ -298,7 +298,7 @@ class Backend { } for (const [value, path] of getValuePaths(changedOptions)) { - modifyOption(path, value, options); + modifyOption(path, value); } await this._onApiOptionsSave({source}); @@ -340,9 +340,9 @@ class Backend { dictTermsSort(definitions); const {expression, reading} = definitions[0]; const source = text.substring(0, sourceLength); - for (const {text, furigana} of jpDistributeFuriganaInflected(expression, reading, source)) { - const reading = jpConvertReading(text, furigana, options.parsing.readingMode); - term.push({text, reading}); + for (const {text: text2, furigana} of jpDistributeFuriganaInflected(expression, reading, source)) { + const reading2 = jpConvertReading(text2, furigana, options.parsing.readingMode); + term.push({text: text2, reading: reading2}); } text = text.substring(source.length); } else { @@ -365,17 +365,17 @@ class Backend { for (const {expression, reading, source} of parsedLine) { const term = []; if (expression !== null && reading !== null) { - for (const {text, furigana} of jpDistributeFuriganaInflected( + for (const {text: text2, furigana} of jpDistributeFuriganaInflected( expression, jpKatakanaToHiragana(reading), source )) { - const reading = jpConvertReading(text, furigana, options.parsing.readingMode); - term.push({text, reading}); + const reading2 = jpConvertReading(text2, furigana, options.parsing.readingMode); + term.push({text: text2, reading: reading2}); } } else { - const reading = jpConvertReading(source, null, options.parsing.readingMode); - term.push({text: source, reading}); + const reading2 = jpConvertReading(source, null, options.parsing.readingMode); + term.push({text: source, reading: reading2}); } result.push(term); } @@ -816,12 +816,12 @@ class Backend { try { const tabWindow = await new Promise((resolve, reject) => { - chrome.windows.get(tab.windowId, {}, (tabWindow) => { + chrome.windows.get(tab.windowId, {}, (value) => { const e = chrome.runtime.lastError; if (e) { reject(e); } else { - resolve(tabWindow); + resolve(value); } }); }); diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 1f6810cf..558f3ceb 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -809,9 +809,9 @@ class Database { for (const objectStoreName of objectStoreNames) { const {primaryKey, indices} = stores[objectStoreName]; - const objectStoreNames = transaction.objectStoreNames || db.objectStoreNames; + const objectStoreNames2 = transaction.objectStoreNames || db.objectStoreNames; const objectStore = ( - Database._listContains(objectStoreNames, objectStoreName) ? + Database._listContains(objectStoreNames2, objectStoreName) ? transaction.objectStore(objectStoreName) : db.createObjectStore(objectStoreName, primaryKey) ); diff --git a/ext/bg/js/japanese.js b/ext/bg/js/japanese.js index e8a6fa08..abb32da4 100644 --- a/ext/bg/js/japanese.js +++ b/ext/bg/js/japanese.js @@ -224,15 +224,15 @@ function jpDistributeFurigana(expression, reading) { } let isAmbiguous = false; - const segmentize = (reading, groups) => { + const segmentize = (reading2, groups) => { if (groups.length === 0 || isAmbiguous) { return []; } const group = groups[0]; if (group.mode === 'kana') { - if (jpKatakanaToHiragana(reading).startsWith(jpKatakanaToHiragana(group.text))) { - const readingLeft = reading.substring(group.text.length); + if (jpKatakanaToHiragana(reading2).startsWith(jpKatakanaToHiragana(group.text))) { + const readingLeft = reading2.substring(group.text.length); const segs = segmentize(readingLeft, groups.splice(1)); if (segs) { return [{text: group.text}].concat(segs); @@ -240,9 +240,9 @@ function jpDistributeFurigana(expression, reading) { } } else { let foundSegments = null; - for (let i = reading.length; i >= group.text.length; --i) { - const readingUsed = reading.substring(0, i); - const readingLeft = reading.substring(i); + for (let i = reading2.length; i >= group.text.length; --i) { + const readingUsed = reading2.substring(0, i); + const readingLeft = reading2.substring(i); const segs = segmentize(readingLeft, groups.slice(1)); if (segs) { if (foundSegments !== null) { diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js index 3cf24c35..58f804fd 100644 --- a/ext/bg/js/json-schema.js +++ b/ext/bg/js/json-schema.js @@ -401,7 +401,7 @@ class JsonSchemaProxyHandler { info.valuePush(i, propertyValue); JsonSchemaProxyHandler.validate(propertyValue, propertySchema, info); info.valuePop(); - for (let i = 0; i < schemaPath.length; ++i) { info.schemaPop(); } + for (let j = 0, jj = schemaPath.length; j < jj; ++j) { info.schemaPop(); } } } diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 76a62b97..98e167ad 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -70,7 +70,7 @@ class DisplaySearch extends Display { this.wanakanaEnable.checked = false; } this.wanakanaEnable.addEventListener('change', (e) => { - const {queryParams: {query=''}} = parseUrl(window.location.href); + const {queryParams: {query: query2=''}} = parseUrl(window.location.href); if (e.target.checked) { window.wanakana.bind(this.query); apiOptionsSet({general: {enableWanakana: true}}, this.getOptionsContext()); @@ -78,7 +78,7 @@ class DisplaySearch extends Display { window.wanakana.unbind(this.query); apiOptionsSet({general: {enableWanakana: false}}, this.getOptionsContext()); } - this.setQuery(query); + this.setQuery(query2); this.onSearchQueryUpdated(this.query.value, false); }); } diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index 427f47f0..70a22a16 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -166,7 +166,7 @@ class SettingsDictionaryListUI { delete n.dataset.dict; $(n).modal('hide'); - const index = this.dictionaryEntries.findIndex((e) => e.dictionaryInfo.title === title); + const index = this.dictionaryEntries.findIndex((entry) => entry.dictionaryInfo.title === title); if (index >= 0) { this.dictionaryEntries[index].deleteDictionary(); } diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index 330a30fb..83813796 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -160,9 +160,9 @@ function promiseTimeout(delay, resolveValue) { const resolve = (value) => complete(promiseResolve, value); const reject = (value) => complete(promiseReject, value); - const promise = new Promise((resolve, reject) => { - promiseResolve = resolve; - promiseReject = reject; + const promise = new Promise((resolve2, reject2) => { + promiseResolve = resolve2; + promiseReject = reject2; }); timer = window.setTimeout(() => { timer = null; -- cgit v1.2.3 From 53220af68eabdda27b35224056f3bd589e8c4785 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 23 Feb 2020 11:49:52 -0500 Subject: Don't use innerHTML --- ext/bg/js/search-query-parser.js | 2 +- ext/fg/js/source.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 8c434990..0d4aaa50 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -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/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'); -- cgit v1.2.3 From 5a5c18371ca6df5473f9d5ef9a391acde6c2e168 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 23 Feb 2020 11:58:17 -0500 Subject: Use textContent instead of innerText --- ext/bg/js/audio.js | 2 +- ext/bg/js/search-query-parser-generator.js | 6 +++--- ext/fg/js/document.js | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 0ecfd5c4..d300570b 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/'); } 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/fg/js/document.js b/ext/fg/js/document.js index ea9ac965..1a3e8791 100644 --- a/ext/fg/js/document.js +++ b/ext/fg/js/document.js @@ -50,7 +50,7 @@ function docImposterCreate(element, isTextarea) { const imposter = document.createElement('div'); const imposterStyle = imposter.style; - imposter.innerText = element.value; + imposter.textContent = element.value; for (let i = 0, ii = elementStyle.length; i < ii; ++i) { const property = elementStyle[i]; -- cgit v1.2.3 From 76976439669d31484b116df3ea4d9bbcbb485e32 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 23 Feb 2020 11:59:57 -0500 Subject: Reject with a consistent error --- ext/bg/js/backend.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'ext/bg') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 2691b7d9..e3bf7bda 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -802,7 +802,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 +819,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 +830,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(); } -- cgit v1.2.3 From 15f79bc78b69ee808b99983861e21594ba30bb10 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 23 Feb 2020 21:12:43 -0500 Subject: Fix popupScalingFactor being limited to integer values --- ext/bg/js/settings/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/bg') diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index c6683427..d1ad2c6b 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(); -- cgit v1.2.3