From ab1152132d9354991f8e112002c56ff1465f33a3 Mon Sep 17 00:00:00 2001 From: Austin Yu <52673267+austinyu12@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:05:30 -0700 Subject: [PATCH 01/33] Search page displays queried sentence even when parser is off. (#1522) * parsing skipped when parsers off * removed comments --- ext/js/display/query-parser.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ext/js/display/query-parser.js b/ext/js/display/query-parser.js index 344383fb26..cf487a3d35 100644 --- a/ext/js/display/query-parser.js +++ b/ext/js/display/query-parser.js @@ -133,6 +133,9 @@ export class QueryParser extends EventDispatcher { this._text = text; this._setPreview(text); + if (this._useInternalParser === false && this._useMecabParser === false) { + return; + } /** @type {?import('core').TokenObject} */ const token = {}; this._setTextToken = token; From 72a34770db642ab23b6e16ca6fecf6adf222d873 Mon Sep 17 00:00:00 2001 From: James Maa Date: Fri, 25 Oct 2024 11:05:54 -0700 Subject: [PATCH 02/33] Support default setting overrides on different languages (#1368) * WIP * Update only when there are setting overrides * Display overrides * Add schema validation * Fix types * --wip-- [skip ci] * --wip-- [skip ci] * Changes * Unneeded changes * Fix types * Render all mod types * Fix french text replacements * Address comments * Add languages with spaces --- .eslintrc.json | 19 + ext/data/recommended-settings.json | 406 ++++++++++++++++++ .../schemas/recommended-settings-schema.json | 180 ++++++++ .../recommended-settings-controller.js | 179 ++++++++ ext/js/pages/settings/settings-controller.js | 12 + ext/js/pages/settings/settings-main.js | 3 + ext/js/pages/welcome-main.js | 4 + ext/templates-modals.html | 24 ++ ext/templates-settings.html | 17 + test/data/json.json | 11 + types/ext/settings-controller.d.ts | 10 + 11 files changed, 865 insertions(+) create mode 100644 ext/data/recommended-settings.json create mode 100644 ext/data/schemas/recommended-settings-schema.json create mode 100644 ext/js/pages/settings/recommended-settings-controller.js diff --git a/.eslintrc.json b/.eslintrc.json index faee1c51d0..64e8525893 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -694,6 +694,25 @@ } }] } + }, + { + "files": [ + "ext/data/recommended-settings.json" + ], + "rules": { + "jsonc/sort-keys": ["error", { + "pathPattern": ".*", + "order": [ + "modification", + "description" + ] + }, { + "pathPattern": ".*", + "order": { + "type": "asc" + } + }] + } } ] } diff --git a/ext/data/recommended-settings.json b/ext/data/recommended-settings.json new file mode 100644 index 0000000000..c492a2540a --- /dev/null +++ b/ext/data/recommended-settings.json @@ -0,0 +1,406 @@ +{ + "da": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "de": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "el": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "en": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "es": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "fi": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "fr": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + }, + { + "modification": { + "action": "set", + "path": "translation.textReplacements.groups", + "value": [ + [ + { + "pattern": "l'", + "ignoreCase": true, + "replacement": "" + }, + { + "pattern": "j'", + "ignoreCase": true, + "replacement": "" + }, + { + "pattern": "d'", + "ignoreCase": true, + "replacement": "" + } + ] + ] + }, + "description": "Separating the l', j', d' from the word." + } + ], + "hu": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "id": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "it": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "mn": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "nl": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "pl": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "pt": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "ro": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "ru": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "sh": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "sq": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "sv": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "tr": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "vi": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ] +} diff --git a/ext/data/schemas/recommended-settings-schema.json b/ext/data/schemas/recommended-settings-schema.json new file mode 100644 index 0000000000..2c8054244b --- /dev/null +++ b/ext/data/schemas/recommended-settings-schema.json @@ -0,0 +1,180 @@ +{ + "$id": "recommendedSetttings", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Contains data for recommended default options overrides by language.", + "type": "object", + "$defs": { + "path": { + "type": "string", + "minLength": 2 + }, + "value": { + "anyOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "object" + } + ] + }, + "description": { + "type": "string", + "minLength": 2 + } + }, + "patternProperties": { + "^.{2,}$": { + "title": "Language", + "type": "array", + "items": { + "title": "Modification", + "type": "object", + "oneOf": [ + { + "type": "object", + "title": "ModificationSet", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "set" + }, + "path": { + "$ref": "#/$defs/path" + }, + "value": { + "$ref": "#/$defs/value" + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + }, + { + "type": "object", + "title": "ModificationDelete", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "delete" + }, + "path": { + "$ref": "#/$defs/path" + }, + "value": { + "$ref": "#/$defs/value" + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + }, + { + "type": "object", + "title": "ModificationSwap", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "swap" + }, + "path1": { + "$ref": "#/$defs/path" + }, + "path2": { + "$ref": "#/$defs/path" + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + }, + { + "type": "object", + "title": "ModificationSplice", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "splice" + }, + "path": { + "$ref": "#/$defs/path" + }, + "start": { + "type": "number" + }, + "deleteCount": { + "type": "number" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/$defs/value" + } + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + }, + { + "type": "object", + "title": "ModificationPush", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "push" + }, + "path": { + "$ref": "#/$defs/path" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/$defs/value" + } + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + } + ] + } + } + } +} diff --git a/ext/js/pages/settings/recommended-settings-controller.js b/ext/js/pages/settings/recommended-settings-controller.js new file mode 100644 index 0000000000..65ab53bde9 --- /dev/null +++ b/ext/js/pages/settings/recommended-settings-controller.js @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2024 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import {log} from '../../core/log.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js'; + +export class RecommendedSettingsController { + /** + * @param {import('./settings-controller.js').SettingsController} settingsController + */ + constructor(settingsController) { + /** @type {import('./settings-controller.js').SettingsController} */ + this._settingsController = settingsController; + /** @type {HTMLElement} */ + this._recommendedSettingsModal = querySelectorNotNull(document, '#recommended-settings-modal'); + /** @type {HTMLInputElement} */ + this._languageSelect = querySelectorNotNull(document, '#language-select'); + /** @type {HTMLInputElement} */ + this._applyButton = querySelectorNotNull(document, '#recommended-settings-apply-button'); + /** @type {Map} */ + this._recommendedSettings = new Map(); + } + + /** */ + async prepare() { + this._languageSelect.addEventListener('change', this._onLanguageSelectChanged.bind(this), false); + this._applyButton.addEventListener('click', this._onApplyButtonClicked.bind(this), false); + } + + /** + * @param {Event} _e + */ + _onLanguageSelectChanged(_e) { + const setLanguage = this._languageSelect.value; + if (typeof setLanguage !== 'string') { return; } + + const recommendedSettings = this._settingsController.getRecommendedSettings(setLanguage); + if (typeof recommendedSettings !== 'undefined') { + const settingsList = querySelectorNotNull(document, '#recommended-settings-list'); + settingsList.innerHTML = ''; + this._recommendedSettings = new Map(); + + for (const [index, setting] of recommendedSettings.entries()) { + this._recommendedSettings.set(index.toString(), setting); + + const {description} = setting; + const template = this._settingsController.instantiateTemplate('recommended-settings-list-item'); + + // Render label + this._renderLabel(template, setting); + + // Render description + const descriptionElement = querySelectorNotNull(template, '.settings-item-description'); + if (description !== 'undefined') { + descriptionElement.textContent = description; + } + + // Render checkbox + const checkbox = /** @type {HTMLInputElement} */ (querySelectorNotNull(template, 'input[type="checkbox"]')); + checkbox.value = index.toString(); + + settingsList.append(template); + } + this._recommendedSettingsModal.hidden = false; + } + } + + /** + * @param {MouseEvent} e + */ + _onApplyButtonClicked(e) { + e.preventDefault(); + /** @type {NodeListOf} */ + const enabledCheckboxes = querySelectorNotNull(document, '#recommended-settings-list').querySelectorAll('input[type="checkbox"]:checked'); + if (enabledCheckboxes.length > 0) { + const modifications = []; + for (const checkbox of enabledCheckboxes) { + const index = checkbox.value; + const setting = this._recommendedSettings.get(index); + if (typeof setting === 'undefined') { continue; } + modifications.push(setting.modification); + } + void this._settingsController.modifyProfileSettings(modifications).then( + (results) => { + results.map((result) => { + if (Object.hasOwn(result, 'error')) { + log.error(new Error(`Failed to apply recommended setting: ${JSON.stringify(result)}`)); + } + }); + }, + ); + void this._settingsController.refresh(); + } + this._recommendedSettingsModal.hidden = true; + } + + /** + * @param {Element} template + * @param {import('settings-controller').RecommendedSetting} setting + */ + _renderLabel(template, setting) { + const label = querySelectorNotNull(template, '.settings-item-label'); + + const {modification} = setting; + switch (modification.action) { + case 'set': { + const {path, value} = modification; + const pathCodeElement = document.createElement('code'); + pathCodeElement.textContent = path; + const valueCodeElement = document.createElement('code'); + valueCodeElement.textContent = JSON.stringify(value, null, 2); + + label.appendChild(document.createTextNode('Setting ')); + label.appendChild(pathCodeElement); + label.appendChild(document.createTextNode(' = ')); + label.appendChild(valueCodeElement); + break; + } + case 'delete': { + const {path} = modification; + const pathCodeElement = document.createElement('code'); + pathCodeElement.textContent = path; + + label.appendChild(document.createTextNode('Deleting ')); + label.appendChild(pathCodeElement); + break; + } + case 'swap': { + const {path1, path2} = modification; + const path1CodeElement = document.createElement('code'); + path1CodeElement.textContent = path1; + const path2CodeElement = document.createElement('code'); + path2CodeElement.textContent = path2; + + label.appendChild(document.createTextNode('Swapping ')); + label.appendChild(path1CodeElement); + label.appendChild(document.createTextNode(' and ')); + label.appendChild(path2CodeElement); + break; + } + case 'splice': { + const {path, start, deleteCount, items} = modification; + const pathCodeElement = document.createElement('code'); + pathCodeElement.textContent = path; + + label.appendChild(document.createTextNode('Splicing ')); + label.appendChild(pathCodeElement); + label.appendChild(document.createTextNode(` at ${start} deleting ${deleteCount} items and inserting ${items.length} items`)); + break; + } + case 'push': { + const {path, items} = modification; + const pathCodeElement = document.createElement('code'); + pathCodeElement.textContent = path; + + label.appendChild(document.createTextNode(`Pushing ${items.length} items to `)); + label.appendChild(pathCodeElement); + break; + } + default: { + log.error(new Error(`Unknown modification: ${modification}`)); + } + } + } +} diff --git a/ext/js/pages/settings/settings-controller.js b/ext/js/pages/settings/settings-controller.js index ee44f875f3..ca55c2b0ee 100644 --- a/ext/js/pages/settings/settings-controller.js +++ b/ext/js/pages/settings/settings-controller.js @@ -18,6 +18,7 @@ import {EventDispatcher} from '../../core/event-dispatcher.js'; import {EventListenerCollection} from '../../core/event-listener-collection.js'; +import {fetchJson} from '../../core/fetch-utilities.js'; import {isObjectNotArray} from '../../core/object-utilities.js'; import {generateId} from '../../core/utilities.js'; import {OptionsUtil} from '../../data/options-util.js'; @@ -45,6 +46,8 @@ export class SettingsController extends EventDispatcher { this._pageExitPreventionEventListeners = new EventListenerCollection(); /** @type {HtmlTemplateCollection} */ this._templates = new HtmlTemplateCollection(); + /** @type {import('settings-controller').RecommendedSettingsByLanguage} */ + this._recommendedSettingsByLanguage = {}; } /** @type {import('../../application.js').Application} */ @@ -75,6 +78,7 @@ export class SettingsController extends EventDispatcher { /** */ async prepare() { await this._templates.loadFromFiles(['/templates-settings.html']); + this._recommendedSettingsByLanguage = await fetchJson('/data/recommended-settings.json'); this._application.on('optionsUpdated', this._onOptionsUpdated.bind(this)); if (this._canObservePermissionsChanges()) { chrome.permissions.onAdded.addListener(this._onPermissionsChanged.bind(this)); @@ -182,6 +186,14 @@ export class SettingsController extends EventDispatcher { return await this.modifyProfileSettings([{action: 'set', path, value}]); } + /** + * @param {string} language + * @returns {import('settings-controller').RecommendedSetting[]} + */ + getRecommendedSettings(language) { + return this._recommendedSettingsByLanguage[language]; + } + /** * @returns {Promise} */ diff --git a/ext/js/pages/settings/settings-main.js b/ext/js/pages/settings/settings-main.js index f070c03876..4a7925bf68 100644 --- a/ext/js/pages/settings/settings-main.js +++ b/ext/js/pages/settings/settings-main.js @@ -40,6 +40,7 @@ import {PersistentStorageController} from './persistent-storage-controller.js'; import {PopupPreviewController} from './popup-preview-controller.js'; import {PopupWindowController} from './popup-window-controller.js'; import {ProfileController} from './profile-controller.js'; +import {RecommendedSettingsController} from './recommended-settings-controller.js'; import {ScanInputsController} from './scan-inputs-controller.js'; import {ScanInputsSimpleController} from './scan-inputs-simple-controller.js'; import {SecondarySearchDictionaryController} from './secondary-search-dictionary-controller.js'; @@ -174,6 +175,8 @@ await Application.main(true, async (application) => { const sortFrequencyDictionaryController = new SortFrequencyDictionaryController(settingsController); preparePromises.push(sortFrequencyDictionaryController.prepare()); + const recommendedSettingsController = new RecommendedSettingsController(settingsController); + preparePromises.push(recommendedSettingsController.prepare()); await Promise.all(preparePromises); diff --git a/ext/js/pages/welcome-main.js b/ext/js/pages/welcome-main.js index ae00c0c637..c4d75ee8c3 100644 --- a/ext/js/pages/welcome-main.js +++ b/ext/js/pages/welcome-main.js @@ -26,6 +26,7 @@ import {GenericSettingController} from './settings/generic-setting-controller.js import {LanguagesController} from './settings/languages-controller.js'; import {ModalController} from './settings/modal-controller.js'; import {RecommendedPermissionsController} from './settings/recommended-permissions-controller.js'; +import {RecommendedSettingsController} from './settings/recommended-settings-controller.js'; import {ScanInputsSimpleController} from './settings/scan-inputs-simple-controller.js'; import {SettingsController} from './settings/settings-controller.js'; import {SettingsDisplayController} from './settings/settings-display-controller.js'; @@ -105,6 +106,9 @@ await Application.main(true, async (application) => { const languagesController = new LanguagesController(settingsController); preparePromises.push(languagesController.prepare()); + const recommendedSettingsController = new RecommendedSettingsController(settingsController); + preparePromises.push(recommendedSettingsController.prepare()); + await Promise.all(preparePromises); document.documentElement.dataset.loaded = 'true'; diff --git a/ext/templates-modals.html b/ext/templates-modals.html index 6f1a3a02b2..4b0a088ddb 100644 --- a/ext/templates-modals.html +++ b/ext/templates-modals.html @@ -335,6 +335,30 @@

Pronunciation Dictionaries

+ + + + + + diff --git a/test/data/json.json b/test/data/json.json index 9fdda0b86b..321bff6483 100644 --- a/test/data/json.json +++ b/test/data/json.json @@ -98,6 +98,11 @@ "typeFile": "types/test/json.d.ts", "type": "AjvSchema" }, + { + "path": "ext/data/schemas/recommended-settings-schema.json", + "typeFile": "types/test/json.d.ts", + "type": "AjvSchema" + }, { "path": "test/data/translator-test-inputs.json", "typeFile": "types/test/translator.d.ts", @@ -192,6 +197,12 @@ "typeFile": "types/ext/dictionary-recommended.d.ts", "type": "RecommendedDictionaries", "schema": "ext/data/schemas/recommended-dictionaries-schema.json" + }, + { + "path": "ext/data/recommended-settings.json", + "typeFile": "types/ext/settings-controller.d.ts", + "type": "RecommendedSettingsByLanguage", + "schema": "ext/data/schemas/recommended-settings-schema.json" } ] } diff --git a/types/ext/settings-controller.d.ts b/types/ext/settings-controller.d.ts index e12651ebea..bbb393fbb7 100644 --- a/types/ext/settings-controller.d.ts +++ b/types/ext/settings-controller.d.ts @@ -22,6 +22,7 @@ import type * as Core from './core'; import type * as Settings from './settings'; import type * as SettingsModifications from './settings-modifications'; import type {EventNames, EventArgument as BaseEventArgument} from './core'; +import type {Modification} from './settings-modifications'; export type PageExitPrevention = { end: () => void; @@ -57,3 +58,12 @@ export type SettingsModification = THasScope extends export type SettingsExtraFields = THasScope extends true ? null : SettingsModifications.OptionsScope; export type ModifyResult = Core.Response; + +export type RecommendedSetting = { + modification: Modification; + description: string; +}; + +export type RecommendedSettingsByLanguage = { + [key: string]: RecommendedSetting[]; +}; From 019c45838a2669ac63d9f3cf1c9bf981b144e2e1 Mon Sep 17 00:00:00 2001 From: marv Date: Sat, 26 Oct 2024 11:03:29 -0700 Subject: [PATCH 03/33] Revert "Support default setting overrides on different languages (#1368)" (#1530) This reverts commit 72a34770db642ab23b6e16ca6fecf6adf222d873. --- .eslintrc.json | 19 - ext/data/recommended-settings.json | 406 ------------------ .../schemas/recommended-settings-schema.json | 180 -------- .../recommended-settings-controller.js | 179 -------- ext/js/pages/settings/settings-controller.js | 12 - ext/js/pages/settings/settings-main.js | 3 - ext/js/pages/welcome-main.js | 4 - ext/templates-modals.html | 24 -- ext/templates-settings.html | 17 - test/data/json.json | 11 - types/ext/settings-controller.d.ts | 10 - 11 files changed, 865 deletions(-) delete mode 100644 ext/data/recommended-settings.json delete mode 100644 ext/data/schemas/recommended-settings-schema.json delete mode 100644 ext/js/pages/settings/recommended-settings-controller.js diff --git a/.eslintrc.json b/.eslintrc.json index 64e8525893..faee1c51d0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -694,25 +694,6 @@ } }] } - }, - { - "files": [ - "ext/data/recommended-settings.json" - ], - "rules": { - "jsonc/sort-keys": ["error", { - "pathPattern": ".*", - "order": [ - "modification", - "description" - ] - }, { - "pathPattern": ".*", - "order": { - "type": "asc" - } - }] - } } ] } diff --git a/ext/data/recommended-settings.json b/ext/data/recommended-settings.json deleted file mode 100644 index c492a2540a..0000000000 --- a/ext/data/recommended-settings.json +++ /dev/null @@ -1,406 +0,0 @@ -{ - "da": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "de": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "el": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "en": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "es": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "fi": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "fr": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - }, - { - "modification": { - "action": "set", - "path": "translation.textReplacements.groups", - "value": [ - [ - { - "pattern": "l'", - "ignoreCase": true, - "replacement": "" - }, - { - "pattern": "j'", - "ignoreCase": true, - "replacement": "" - }, - { - "pattern": "d'", - "ignoreCase": true, - "replacement": "" - } - ] - ] - }, - "description": "Separating the l', j', d' from the word." - } - ], - "hu": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "id": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "it": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "mn": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "nl": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "pl": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "pt": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "ro": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "ru": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "sh": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "sq": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "sv": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "tr": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ], - "vi": [ - { - "modification": { - "action": "set", - "path": "scanning.scanResolution", - "value": "word" - }, - "description": "Scan text one word at a time (as opposed to one character)." - }, - { - "modification": { - "action": "set", - "path": "translation.searchResolution", - "value": "word" - }, - "description": "Lookup whole words in the dictionary." - } - ] -} diff --git a/ext/data/schemas/recommended-settings-schema.json b/ext/data/schemas/recommended-settings-schema.json deleted file mode 100644 index 2c8054244b..0000000000 --- a/ext/data/schemas/recommended-settings-schema.json +++ /dev/null @@ -1,180 +0,0 @@ -{ - "$id": "recommendedSetttings", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "Contains data for recommended default options overrides by language.", - "type": "object", - "$defs": { - "path": { - "type": "string", - "minLength": 2 - }, - "value": { - "anyOf": [ - { - "type": "string", - "minLength": 1 - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "array" - }, - { - "type": "object" - } - ] - }, - "description": { - "type": "string", - "minLength": 2 - } - }, - "patternProperties": { - "^.{2,}$": { - "title": "Language", - "type": "array", - "items": { - "title": "Modification", - "type": "object", - "oneOf": [ - { - "type": "object", - "title": "ModificationSet", - "properties": { - "modification": { - "type": "object", - "properties": { - "action": { - "type": "string", - "const": "set" - }, - "path": { - "$ref": "#/$defs/path" - }, - "value": { - "$ref": "#/$defs/value" - } - } - }, - "description": { - "$ref": "#/$defs/description" - } - } - }, - { - "type": "object", - "title": "ModificationDelete", - "properties": { - "modification": { - "type": "object", - "properties": { - "action": { - "type": "string", - "const": "delete" - }, - "path": { - "$ref": "#/$defs/path" - }, - "value": { - "$ref": "#/$defs/value" - } - } - }, - "description": { - "$ref": "#/$defs/description" - } - } - }, - { - "type": "object", - "title": "ModificationSwap", - "properties": { - "modification": { - "type": "object", - "properties": { - "action": { - "type": "string", - "const": "swap" - }, - "path1": { - "$ref": "#/$defs/path" - }, - "path2": { - "$ref": "#/$defs/path" - } - } - }, - "description": { - "$ref": "#/$defs/description" - } - } - }, - { - "type": "object", - "title": "ModificationSplice", - "properties": { - "modification": { - "type": "object", - "properties": { - "action": { - "type": "string", - "const": "splice" - }, - "path": { - "$ref": "#/$defs/path" - }, - "start": { - "type": "number" - }, - "deleteCount": { - "type": "number" - }, - "items": { - "type": "array", - "items": { - "$ref": "#/$defs/value" - } - } - } - }, - "description": { - "$ref": "#/$defs/description" - } - } - }, - { - "type": "object", - "title": "ModificationPush", - "properties": { - "modification": { - "type": "object", - "properties": { - "action": { - "type": "string", - "const": "push" - }, - "path": { - "$ref": "#/$defs/path" - }, - "items": { - "type": "array", - "items": { - "$ref": "#/$defs/value" - } - } - } - }, - "description": { - "$ref": "#/$defs/description" - } - } - } - ] - } - } - } -} diff --git a/ext/js/pages/settings/recommended-settings-controller.js b/ext/js/pages/settings/recommended-settings-controller.js deleted file mode 100644 index 65ab53bde9..0000000000 --- a/ext/js/pages/settings/recommended-settings-controller.js +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (C) 2024 Yomitan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import {log} from '../../core/log.js'; -import {querySelectorNotNull} from '../../dom/query-selector.js'; - -export class RecommendedSettingsController { - /** - * @param {import('./settings-controller.js').SettingsController} settingsController - */ - constructor(settingsController) { - /** @type {import('./settings-controller.js').SettingsController} */ - this._settingsController = settingsController; - /** @type {HTMLElement} */ - this._recommendedSettingsModal = querySelectorNotNull(document, '#recommended-settings-modal'); - /** @type {HTMLInputElement} */ - this._languageSelect = querySelectorNotNull(document, '#language-select'); - /** @type {HTMLInputElement} */ - this._applyButton = querySelectorNotNull(document, '#recommended-settings-apply-button'); - /** @type {Map} */ - this._recommendedSettings = new Map(); - } - - /** */ - async prepare() { - this._languageSelect.addEventListener('change', this._onLanguageSelectChanged.bind(this), false); - this._applyButton.addEventListener('click', this._onApplyButtonClicked.bind(this), false); - } - - /** - * @param {Event} _e - */ - _onLanguageSelectChanged(_e) { - const setLanguage = this._languageSelect.value; - if (typeof setLanguage !== 'string') { return; } - - const recommendedSettings = this._settingsController.getRecommendedSettings(setLanguage); - if (typeof recommendedSettings !== 'undefined') { - const settingsList = querySelectorNotNull(document, '#recommended-settings-list'); - settingsList.innerHTML = ''; - this._recommendedSettings = new Map(); - - for (const [index, setting] of recommendedSettings.entries()) { - this._recommendedSettings.set(index.toString(), setting); - - const {description} = setting; - const template = this._settingsController.instantiateTemplate('recommended-settings-list-item'); - - // Render label - this._renderLabel(template, setting); - - // Render description - const descriptionElement = querySelectorNotNull(template, '.settings-item-description'); - if (description !== 'undefined') { - descriptionElement.textContent = description; - } - - // Render checkbox - const checkbox = /** @type {HTMLInputElement} */ (querySelectorNotNull(template, 'input[type="checkbox"]')); - checkbox.value = index.toString(); - - settingsList.append(template); - } - this._recommendedSettingsModal.hidden = false; - } - } - - /** - * @param {MouseEvent} e - */ - _onApplyButtonClicked(e) { - e.preventDefault(); - /** @type {NodeListOf} */ - const enabledCheckboxes = querySelectorNotNull(document, '#recommended-settings-list').querySelectorAll('input[type="checkbox"]:checked'); - if (enabledCheckboxes.length > 0) { - const modifications = []; - for (const checkbox of enabledCheckboxes) { - const index = checkbox.value; - const setting = this._recommendedSettings.get(index); - if (typeof setting === 'undefined') { continue; } - modifications.push(setting.modification); - } - void this._settingsController.modifyProfileSettings(modifications).then( - (results) => { - results.map((result) => { - if (Object.hasOwn(result, 'error')) { - log.error(new Error(`Failed to apply recommended setting: ${JSON.stringify(result)}`)); - } - }); - }, - ); - void this._settingsController.refresh(); - } - this._recommendedSettingsModal.hidden = true; - } - - /** - * @param {Element} template - * @param {import('settings-controller').RecommendedSetting} setting - */ - _renderLabel(template, setting) { - const label = querySelectorNotNull(template, '.settings-item-label'); - - const {modification} = setting; - switch (modification.action) { - case 'set': { - const {path, value} = modification; - const pathCodeElement = document.createElement('code'); - pathCodeElement.textContent = path; - const valueCodeElement = document.createElement('code'); - valueCodeElement.textContent = JSON.stringify(value, null, 2); - - label.appendChild(document.createTextNode('Setting ')); - label.appendChild(pathCodeElement); - label.appendChild(document.createTextNode(' = ')); - label.appendChild(valueCodeElement); - break; - } - case 'delete': { - const {path} = modification; - const pathCodeElement = document.createElement('code'); - pathCodeElement.textContent = path; - - label.appendChild(document.createTextNode('Deleting ')); - label.appendChild(pathCodeElement); - break; - } - case 'swap': { - const {path1, path2} = modification; - const path1CodeElement = document.createElement('code'); - path1CodeElement.textContent = path1; - const path2CodeElement = document.createElement('code'); - path2CodeElement.textContent = path2; - - label.appendChild(document.createTextNode('Swapping ')); - label.appendChild(path1CodeElement); - label.appendChild(document.createTextNode(' and ')); - label.appendChild(path2CodeElement); - break; - } - case 'splice': { - const {path, start, deleteCount, items} = modification; - const pathCodeElement = document.createElement('code'); - pathCodeElement.textContent = path; - - label.appendChild(document.createTextNode('Splicing ')); - label.appendChild(pathCodeElement); - label.appendChild(document.createTextNode(` at ${start} deleting ${deleteCount} items and inserting ${items.length} items`)); - break; - } - case 'push': { - const {path, items} = modification; - const pathCodeElement = document.createElement('code'); - pathCodeElement.textContent = path; - - label.appendChild(document.createTextNode(`Pushing ${items.length} items to `)); - label.appendChild(pathCodeElement); - break; - } - default: { - log.error(new Error(`Unknown modification: ${modification}`)); - } - } - } -} diff --git a/ext/js/pages/settings/settings-controller.js b/ext/js/pages/settings/settings-controller.js index ca55c2b0ee..ee44f875f3 100644 --- a/ext/js/pages/settings/settings-controller.js +++ b/ext/js/pages/settings/settings-controller.js @@ -18,7 +18,6 @@ import {EventDispatcher} from '../../core/event-dispatcher.js'; import {EventListenerCollection} from '../../core/event-listener-collection.js'; -import {fetchJson} from '../../core/fetch-utilities.js'; import {isObjectNotArray} from '../../core/object-utilities.js'; import {generateId} from '../../core/utilities.js'; import {OptionsUtil} from '../../data/options-util.js'; @@ -46,8 +45,6 @@ export class SettingsController extends EventDispatcher { this._pageExitPreventionEventListeners = new EventListenerCollection(); /** @type {HtmlTemplateCollection} */ this._templates = new HtmlTemplateCollection(); - /** @type {import('settings-controller').RecommendedSettingsByLanguage} */ - this._recommendedSettingsByLanguage = {}; } /** @type {import('../../application.js').Application} */ @@ -78,7 +75,6 @@ export class SettingsController extends EventDispatcher { /** */ async prepare() { await this._templates.loadFromFiles(['/templates-settings.html']); - this._recommendedSettingsByLanguage = await fetchJson('/data/recommended-settings.json'); this._application.on('optionsUpdated', this._onOptionsUpdated.bind(this)); if (this._canObservePermissionsChanges()) { chrome.permissions.onAdded.addListener(this._onPermissionsChanged.bind(this)); @@ -186,14 +182,6 @@ export class SettingsController extends EventDispatcher { return await this.modifyProfileSettings([{action: 'set', path, value}]); } - /** - * @param {string} language - * @returns {import('settings-controller').RecommendedSetting[]} - */ - getRecommendedSettings(language) { - return this._recommendedSettingsByLanguage[language]; - } - /** * @returns {Promise} */ diff --git a/ext/js/pages/settings/settings-main.js b/ext/js/pages/settings/settings-main.js index 4a7925bf68..f070c03876 100644 --- a/ext/js/pages/settings/settings-main.js +++ b/ext/js/pages/settings/settings-main.js @@ -40,7 +40,6 @@ import {PersistentStorageController} from './persistent-storage-controller.js'; import {PopupPreviewController} from './popup-preview-controller.js'; import {PopupWindowController} from './popup-window-controller.js'; import {ProfileController} from './profile-controller.js'; -import {RecommendedSettingsController} from './recommended-settings-controller.js'; import {ScanInputsController} from './scan-inputs-controller.js'; import {ScanInputsSimpleController} from './scan-inputs-simple-controller.js'; import {SecondarySearchDictionaryController} from './secondary-search-dictionary-controller.js'; @@ -175,8 +174,6 @@ await Application.main(true, async (application) => { const sortFrequencyDictionaryController = new SortFrequencyDictionaryController(settingsController); preparePromises.push(sortFrequencyDictionaryController.prepare()); - const recommendedSettingsController = new RecommendedSettingsController(settingsController); - preparePromises.push(recommendedSettingsController.prepare()); await Promise.all(preparePromises); diff --git a/ext/js/pages/welcome-main.js b/ext/js/pages/welcome-main.js index c4d75ee8c3..ae00c0c637 100644 --- a/ext/js/pages/welcome-main.js +++ b/ext/js/pages/welcome-main.js @@ -26,7 +26,6 @@ import {GenericSettingController} from './settings/generic-setting-controller.js import {LanguagesController} from './settings/languages-controller.js'; import {ModalController} from './settings/modal-controller.js'; import {RecommendedPermissionsController} from './settings/recommended-permissions-controller.js'; -import {RecommendedSettingsController} from './settings/recommended-settings-controller.js'; import {ScanInputsSimpleController} from './settings/scan-inputs-simple-controller.js'; import {SettingsController} from './settings/settings-controller.js'; import {SettingsDisplayController} from './settings/settings-display-controller.js'; @@ -106,9 +105,6 @@ await Application.main(true, async (application) => { const languagesController = new LanguagesController(settingsController); preparePromises.push(languagesController.prepare()); - const recommendedSettingsController = new RecommendedSettingsController(settingsController); - preparePromises.push(recommendedSettingsController.prepare()); - await Promise.all(preparePromises); document.documentElement.dataset.loaded = 'true'; diff --git a/ext/templates-modals.html b/ext/templates-modals.html index 4b0a088ddb..6f1a3a02b2 100644 --- a/ext/templates-modals.html +++ b/ext/templates-modals.html @@ -335,30 +335,6 @@

Pronunciation Dictionaries

- - - - - - diff --git a/test/data/json.json b/test/data/json.json index 321bff6483..9fdda0b86b 100644 --- a/test/data/json.json +++ b/test/data/json.json @@ -98,11 +98,6 @@ "typeFile": "types/test/json.d.ts", "type": "AjvSchema" }, - { - "path": "ext/data/schemas/recommended-settings-schema.json", - "typeFile": "types/test/json.d.ts", - "type": "AjvSchema" - }, { "path": "test/data/translator-test-inputs.json", "typeFile": "types/test/translator.d.ts", @@ -197,12 +192,6 @@ "typeFile": "types/ext/dictionary-recommended.d.ts", "type": "RecommendedDictionaries", "schema": "ext/data/schemas/recommended-dictionaries-schema.json" - }, - { - "path": "ext/data/recommended-settings.json", - "typeFile": "types/ext/settings-controller.d.ts", - "type": "RecommendedSettingsByLanguage", - "schema": "ext/data/schemas/recommended-settings-schema.json" } ] } diff --git a/types/ext/settings-controller.d.ts b/types/ext/settings-controller.d.ts index bbb393fbb7..e12651ebea 100644 --- a/types/ext/settings-controller.d.ts +++ b/types/ext/settings-controller.d.ts @@ -22,7 +22,6 @@ import type * as Core from './core'; import type * as Settings from './settings'; import type * as SettingsModifications from './settings-modifications'; import type {EventNames, EventArgument as BaseEventArgument} from './core'; -import type {Modification} from './settings-modifications'; export type PageExitPrevention = { end: () => void; @@ -58,12 +57,3 @@ export type SettingsModification = THasScope extends export type SettingsExtraFields = THasScope extends true ? null : SettingsModifications.OptionsScope; export type ModifyResult = Core.Response; - -export type RecommendedSetting = { - modification: Modification; - description: string; -}; - -export type RecommendedSettingsByLanguage = { - [key: string]: RecommendedSetting[]; -}; From b5237116f0e5ae05a08608a460a8f2305e7a1836 Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Sun, 27 Oct 2024 13:12:50 -0400 Subject: [PATCH 04/33] Fix UTF16 and UTF32 characters breaking cloze data due to the use of substring (#1533) * Fix UTF16 and UTF32 characters breaking cloze data due to the use of substring * Reduce potential for future footguns * Redefine textChars instead of changing type of text --- ext/js/data/anki-note-data-creator.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ext/js/data/anki-note-data-creator.js b/ext/js/data/anki-note-data-creator.js index 0a401a0c4e..43d1252e3a 100644 --- a/ext/js/data/anki-note-data-creator.js +++ b/ext/js/data/anki-note-data-creator.js @@ -968,18 +968,19 @@ function getCloze(dictionaryEntry, context) { } if (typeof text !== 'string') { text = ''; } if (typeof offset !== 'number') { offset = 0; } + const textChars = [...text]; const textSegments = []; - for (const {text: text2, reading: reading2} of distributeFuriganaInflected(term, reading, text.substring(offset, offset + originalText.length))) { + for (const {text: text2, reading: reading2} of distributeFuriganaInflected(term, reading, textChars.slice(offset, offset + originalText.length).join(''))) { textSegments.push(reading2.length > 0 ? reading2 : text2); } return { - sentence: text, - prefix: text.substring(0, offset), - body: text.substring(offset, offset + originalText.length), + sentence: textChars.join(''), + prefix: textChars.slice(0, offset).join(''), + body: textChars.slice(offset, offset + originalText.length).join(''), bodyKana: textSegments.join(''), - suffix: text.substring(offset + originalText.length), + suffix: textChars.slice(offset + originalText.length).join(''), }; } From 8e6ce426963046b02b78951877fc74ba358779b8 Mon Sep 17 00:00:00 2001 From: marv Date: Sun, 27 Oct 2024 10:14:15 -0700 Subject: [PATCH 05/33] Bump Dev Dependencies (#1528) * Bump stuff * Bump stuff and revert ts to fix ts expect error * Bump micromatch because vuln * Remove dependency --- .vscode/settings.json | 3 +- ext/js/core/utilities.js | 1 + ext/js/dictionary/dictionary-data-util.js | 1 + package-lock.json | 1020 +++++++++++++-------- package.json | 14 +- test/utilities/anki.js | 2 + 6 files changed, 655 insertions(+), 386 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e1bf61a8c3..e8d5635b00 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -38,5 +38,6 @@ ], "url": "/ext/data/schemas/recommended-dictionaries-schema.json" } - ] + ], + "typescript.tsdk": "node_modules\\typescript\\lib" } diff --git a/ext/js/core/utilities.js b/ext/js/core/utilities.js index 42b118843b..ec29a30364 100644 --- a/ext/js/core/utilities.js +++ b/ext/js/core/utilities.js @@ -242,6 +242,7 @@ export function deferPromise() { let resolve; /** @type {((reason?: import('core').RejectionReason) => void)|undefined} */ let reject; + /** @type {Promise} */ const promise = new Promise((resolve2, reject2) => { resolve = resolve2; reject = reject2; diff --git a/ext/js/dictionary/dictionary-data-util.js b/ext/js/dictionary/dictionary-data-util.js index 296f4ed95e..8ff1b625a8 100644 --- a/ext/js/dictionary/dictionary-data-util.js +++ b/ext/js/dictionary/dictionary-data-util.js @@ -145,6 +145,7 @@ export function groupKanjiFrequencies(sourceFrequencies) { export function getGroupedPronunciations(dictionaryEntry) { const {headwords, pronunciations: termPronunciations} = dictionaryEntry; + /** @type {Set} */ const allTerms = new Set(); const allReadings = new Set(); /** @type {Map} */ diff --git a/package-lock.json b/package-lock.json index 5507d346e8..26aa8e8724 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "license": "GPL-3.0-or-later", "dependencies": { + "@typescript-eslint/typescript-estree": "^8.11.0", "@zip.js/zip.js": "^2.7.45", "dexie": "^3.2.5", "dexie-export-import": "^4.1.2", @@ -32,14 +33,14 @@ "@types/node": "20.11.5", "@types/wanakana": "^5.3.0", "@types/zip.js": "^2.0.33", - "@typescript-eslint/eslint-plugin": "^7.0.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", "@vitest/coverage-v8": "1.2.2", - "ajv": "^8.14.0", + "ajv": "^8.17.1", "css": "^3.0.0", "dotenv": "^16.4.5", "esbuild": "^0.21.4", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-import": "^2.29.1", @@ -48,7 +49,7 @@ "eslint-plugin-no-unsanitized": "^4.0.2", "eslint-plugin-sonarjs": "^0.24.0", "eslint-plugin-unicorn": "^51.0.1", - "eslint-plugin-unused-imports": "^3.1.0", + "eslint-plugin-unused-imports": "^3.2.0", "eslint-plugin-vitest": "0.3.22", "fake-indexeddb": "^5.0.2", "handlebars": "^4.7.8", @@ -61,9 +62,9 @@ "prettier": "^3.2.5", "stylelint": "^16.2.1", "stylelint-config-recommended": "^14.0.0", - "ts-json-schema-generator": "^1.5.0", + "ts-json-schema-generator": "^1.5.1", "typescript": "^5.4.5", - "vitest": "1.2.2" + "vitest": "^1.2.2" }, "engines": { "node": ">=20.0.0" @@ -990,10 +991,11 @@ "dev": true }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -1011,12 +1013,14 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -1038,10 +1042,12 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", - "dev": true + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -1166,7 +1172,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1179,7 +1184,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, "engines": { "node": ">= 8" } @@ -1188,7 +1192,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1785,32 +1788,31 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.0.tgz", - "integrity": "sha512-M72SJ0DkcQVmmsbqlzc6EJgb/3Oz2Wdm6AyESB4YkGgCxP8u5jt5jn4/OBMPK3HLOxcttZq5xbBBU7e2By4SZQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", "dev": true, + "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.0.0", - "@typescript-eslint/type-utils": "7.0.0", - "@typescript-eslint/utils": "7.0.0", - "@typescript-eslint/visitor-keys": "7.0.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.56.0" }, "peerDependenciesMeta": { @@ -1820,16 +1822,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.0.tgz", - "integrity": "sha512-IxTStwhNDPO07CCrYuAqjuJ3Xf5MrMaNgbAZPxFXAUpAtwqFxiuItxUaVtP/SJQeCdJjwDGh9/lMOluAndkKeg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.0.0", - "@typescript-eslint/visitor-keys": "7.0.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1837,12 +1840,13 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.0.tgz", - "integrity": "sha512-9ZIJDqagK1TTs4W9IyeB2sH/s1fFhN9958ycW8NRTg1vXGzzH5PQNzq6KbsbVGMT+oyyfa17DfchHDidcmf5cg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", "dev": true, + "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1850,22 +1854,23 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.0.tgz", - "integrity": "sha512-JzsOzhJJm74aQ3c9um/aDryHgSHfaX8SHFIu9x4Gpik/+qxLvxUylhTsO9abcNu39JIdhY2LgYrFxTii3IajLA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.0.0", - "@typescript-eslint/visitor-keys": "7.0.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1878,21 +1883,19 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.0.tgz", - "integrity": "sha512-kuPZcPAdGcDBAyqDn/JVeJVhySvpkxzfXjJq1X1BFSTYo1TTuo4iyb937u457q4K0In84p6u2VHQGaFnv7VYqg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.0.0", - "@typescript-eslint/types": "7.0.0", - "@typescript-eslint/typescript-estree": "7.0.0", - "semver": "^7.5.4" + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1903,16 +1906,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.0.tgz", - "integrity": "sha512-JZP0uw59PRHp7sHQl3aF/lFgwOW2rgNVnXUksj1d932PMita9wFBd3621vHQRDvHwPsSY9FMAAHVc8gTvLYY4w==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.0.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -1924,15 +1928,17 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1944,26 +1950,27 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.56.0" }, "peerDependenciesMeta": { "typescript": { @@ -1971,6 +1978,111 @@ } } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/scope-manager": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", @@ -1989,18 +2101,19 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.0.tgz", - "integrity": "sha512-FIM8HPxj1P2G7qfrpiXvbHeHypgo2mFpFGoh5I73ZlqmJOsloSa1x0ZyXCer43++P1doxCgNqIOLqmZR6SOT8g==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.0.0", - "@typescript-eslint/utils": "7.0.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2016,16 +2129,17 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.0.tgz", - "integrity": "sha512-IxTStwhNDPO07CCrYuAqjuJ3Xf5MrMaNgbAZPxFXAUpAtwqFxiuItxUaVtP/SJQeCdJjwDGh9/lMOluAndkKeg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.0.0", - "@typescript-eslint/visitor-keys": "7.0.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2033,12 +2147,13 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.0.tgz", - "integrity": "sha512-9ZIJDqagK1TTs4W9IyeB2sH/s1fFhN9958ycW8NRTg1vXGzzH5PQNzq6KbsbVGMT+oyyfa17DfchHDidcmf5cg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", "dev": true, + "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2046,22 +2161,23 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.0.tgz", - "integrity": "sha512-JzsOzhJJm74aQ3c9um/aDryHgSHfaX8SHFIu9x4Gpik/+qxLvxUylhTsO9abcNu39JIdhY2LgYrFxTii3IajLA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "7.0.0", - "@typescript-eslint/visitor-keys": "7.0.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2074,21 +2190,19 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.0.tgz", - "integrity": "sha512-kuPZcPAdGcDBAyqDn/JVeJVhySvpkxzfXjJq1X1BFSTYo1TTuo4iyb937u457q4K0In84p6u2VHQGaFnv7VYqg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.0.0", - "@typescript-eslint/types": "7.0.0", - "@typescript-eslint/typescript-estree": "7.0.0", - "semver": "^7.5.4" + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2099,16 +2213,17 @@ } }, "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.0.tgz", - "integrity": "sha512-JZP0uw59PRHp7sHQl3aF/lFgwOW2rgNVnXUksj1d932PMita9wFBd3621vHQRDvHwPsSY9FMAAHVc8gTvLYY4w==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.0.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || >=20.0.0" }, "funding": { "type": "opencollective", @@ -2120,15 +2235,17 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -2153,22 +2270,22 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", - "dev": true, + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz", + "integrity": "sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==", + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -2180,20 +2297,50 @@ } } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/types": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz", + "integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==", + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz", + "integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.11.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -2229,6 +2376,61 @@ "eslint": "^7.0.0 || ^8.0.0" } }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", @@ -2441,15 +2643,16 @@ } }, "node_modules/ajv": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.14.0.tgz", - "integrity": "sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -3275,12 +3478,13 @@ } }, "node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/comment-parser": { @@ -4060,16 +4264,18 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -4374,10 +4580,11 @@ } }, "node_modules/eslint-plugin-unused-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.1.0.tgz", - "integrity": "sha512-9l1YFCzXKkw1qtAru1RWUtG2EVDZY0a0eChKXcL+EZ5jitG7qxdctu4RnvhOJHv4xfmUf7h+JJPINlVpGhZMrw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.2.0.tgz", + "integrity": "sha512-6uXyn6xdINEpxE1MtDjxQsyXB37lfyO2yKGVVgtD7WEWQGORSOZjgrD6hBhvGv4/SO+TOlS+UnC6JppRqbuwGQ==", "dev": true, + "license": "MIT", "dependencies": { "eslint-rule-composer": "^0.3.0" }, @@ -4447,7 +4654,6 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -4611,7 +4817,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -4627,7 +4832,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -4647,6 +4851,13 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -4660,7 +4871,6 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "dependencies": { "reusify": "^1.0.4" } @@ -6354,15 +6564,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/lint-staged/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "engines": { - "node": ">=18" - } - }, "node_modules/listr2": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.1.tgz", @@ -6777,16 +6978,15 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, "engines": { "node": ">= 8" } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", - "dev": true, + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -7666,7 +7866,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -8057,7 +8256,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -8146,7 +8344,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "funding": [ { "type": "github", @@ -8242,7 +8439,6 @@ "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, "bin": { "semver": "bin/semver.js" }, @@ -9203,7 +9399,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", - "dev": true, "engines": { "node": ">=16" }, @@ -9212,18 +9407,19 @@ } }, "node_modules/ts-json-schema-generator": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-1.5.0.tgz", - "integrity": "sha512-RkiaJ6YxGc5EWVPfyHxszTmpGxX8HC2XBvcFlAl1zcvpOG4tjjh+eXioStXJQYTvr9MoK8zCOWzAUlko3K0DiA==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-1.5.1.tgz", + "integrity": "sha512-apX5qG2+NA66j7b4AJm8q/DpdTeOsjfh7A3LpKsUiil0FepkNwtN28zYgjrsiiya2/OPhsr/PSjX5FUYg79rCg==", "dev": true, + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.12", - "commander": "^11.0.0", + "@types/json-schema": "^7.0.15", + "commander": "^12.0.0", "glob": "^8.0.3", "json5": "^2.2.3", "normalize-path": "^3.0.0", "safe-stable-stringify": "^2.4.3", - "typescript": "~5.3.2" + "typescript": "~5.4.2" }, "bin": { "ts-json-schema-generator": "bin/ts-json-schema-generator" @@ -9272,19 +9468,6 @@ "node": ">=10" } }, - "node_modules/ts-json-schema-generator/node_modules/typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -9430,7 +9613,7 @@ "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9741,6 +9924,7 @@ "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.2.tgz", "integrity": "sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==", "dev": true, + "license": "MIT", "dependencies": { "@vitest/expect": "1.2.2", "@vitest/runner": "1.2.2", @@ -10718,9 +10902,9 @@ } }, "@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true }, "@html-validate/stylish": { @@ -10733,12 +10917,12 @@ } }, "@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } @@ -10750,9 +10934,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz", - "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "dev": true }, "@isaacs/cliui": { @@ -10850,7 +11034,6 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, "requires": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -10859,14 +11042,12 @@ "@nodelib/fs.stat": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" }, "@nodelib/fs.walk": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -11302,79 +11483,74 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.0.0.tgz", - "integrity": "sha512-M72SJ0DkcQVmmsbqlzc6EJgb/3Oz2Wdm6AyESB4YkGgCxP8u5jt5jn4/OBMPK3HLOxcttZq5xbBBU7e2By4SZQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", "dev": true, "requires": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "7.0.0", - "@typescript-eslint/type-utils": "7.0.0", - "@typescript-eslint/utils": "7.0.0", - "@typescript-eslint/visitor-keys": "7.0.0", - "debug": "^4.3.4", + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "dependencies": { "@typescript-eslint/scope-manager": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.0.tgz", - "integrity": "sha512-IxTStwhNDPO07CCrYuAqjuJ3Xf5MrMaNgbAZPxFXAUpAtwqFxiuItxUaVtP/SJQeCdJjwDGh9/lMOluAndkKeg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, "requires": { - "@typescript-eslint/types": "7.0.0", - "@typescript-eslint/visitor-keys": "7.0.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" } }, "@typescript-eslint/types": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.0.tgz", - "integrity": "sha512-9ZIJDqagK1TTs4W9IyeB2sH/s1fFhN9958ycW8NRTg1vXGzzH5PQNzq6KbsbVGMT+oyyfa17DfchHDidcmf5cg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.0.tgz", - "integrity": "sha512-JzsOzhJJm74aQ3c9um/aDryHgSHfaX8SHFIu9x4Gpik/+qxLvxUylhTsO9abcNu39JIdhY2LgYrFxTii3IajLA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dev": true, "requires": { - "@typescript-eslint/types": "7.0.0", - "@typescript-eslint/visitor-keys": "7.0.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" } }, "@typescript-eslint/utils": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.0.tgz", - "integrity": "sha512-kuPZcPAdGcDBAyqDn/JVeJVhySvpkxzfXjJq1X1BFSTYo1TTuo4iyb937u457q4K0In84p6u2VHQGaFnv7VYqg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.0.0", - "@typescript-eslint/types": "7.0.0", - "@typescript-eslint/typescript-estree": "7.0.0", - "semver": "^7.5.4" + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" } }, "@typescript-eslint/visitor-keys": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.0.tgz", - "integrity": "sha512-JZP0uw59PRHp7sHQl3aF/lFgwOW2rgNVnXUksj1d932PMita9wFBd3621vHQRDvHwPsSY9FMAAHVc8gTvLYY4w==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", "dev": true, "requires": { - "@typescript-eslint/types": "7.0.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" } }, "brace-expansion": { @@ -11387,9 +11563,9 @@ } }, "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -11398,16 +11574,78 @@ } }, "@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + } + }, + "@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "@typescript-eslint/scope-manager": { @@ -11421,72 +11659,69 @@ } }, "@typescript-eslint/type-utils": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.0.0.tgz", - "integrity": "sha512-FIM8HPxj1P2G7qfrpiXvbHeHypgo2mFpFGoh5I73ZlqmJOsloSa1x0ZyXCer43++P1doxCgNqIOLqmZR6SOT8g==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "7.0.0", - "@typescript-eslint/utils": "7.0.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^1.3.0" }, "dependencies": { "@typescript-eslint/scope-manager": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.0.0.tgz", - "integrity": "sha512-IxTStwhNDPO07CCrYuAqjuJ3Xf5MrMaNgbAZPxFXAUpAtwqFxiuItxUaVtP/SJQeCdJjwDGh9/lMOluAndkKeg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", "dev": true, "requires": { - "@typescript-eslint/types": "7.0.0", - "@typescript-eslint/visitor-keys": "7.0.0" + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" } }, "@typescript-eslint/types": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.0.0.tgz", - "integrity": "sha512-9ZIJDqagK1TTs4W9IyeB2sH/s1fFhN9958ycW8NRTg1vXGzzH5PQNzq6KbsbVGMT+oyyfa17DfchHDidcmf5cg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.0.0.tgz", - "integrity": "sha512-JzsOzhJJm74aQ3c9um/aDryHgSHfaX8SHFIu9x4Gpik/+qxLvxUylhTsO9abcNu39JIdhY2LgYrFxTii3IajLA==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dev": true, "requires": { - "@typescript-eslint/types": "7.0.0", - "@typescript-eslint/visitor-keys": "7.0.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" } }, "@typescript-eslint/utils": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.0.0.tgz", - "integrity": "sha512-kuPZcPAdGcDBAyqDn/JVeJVhySvpkxzfXjJq1X1BFSTYo1TTuo4iyb937u457q4K0In84p6u2VHQGaFnv7VYqg==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "7.0.0", - "@typescript-eslint/types": "7.0.0", - "@typescript-eslint/typescript-estree": "7.0.0", - "semver": "^7.5.4" + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" } }, "@typescript-eslint/visitor-keys": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.0.0.tgz", - "integrity": "sha512-JZP0uw59PRHp7sHQl3aF/lFgwOW2rgNVnXUksj1d932PMita9wFBd3621vHQRDvHwPsSY9FMAAHVc8gTvLYY4w==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", "dev": true, "requires": { - "@typescript-eslint/types": "7.0.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" } }, "brace-expansion": { @@ -11499,9 +11734,9 @@ } }, "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -11516,35 +11751,46 @@ "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", - "dev": true, + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.11.0.tgz", + "integrity": "sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==", "requires": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", + "@typescript-eslint/types": "8.11.0", + "@typescript-eslint/visitor-keys": "8.11.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" }, "dependencies": { + "@typescript-eslint/types": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.11.0.tgz", + "integrity": "sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==" + }, + "@typescript-eslint/visitor-keys": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.11.0.tgz", + "integrity": "sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==", + "requires": { + "@typescript-eslint/types": "8.11.0", + "eslint-visitor-keys": "^3.4.3" + } + }, "brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "requires": { "balanced-match": "^1.0.0" } }, "minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dev": true, + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "requires": { "brace-expansion": "^2.0.1" } @@ -11564,6 +11810,42 @@ "@typescript-eslint/types": "6.21.0", "@typescript-eslint/typescript-estree": "6.21.0", "semver": "^7.5.4" + }, + "dependencies": { + "@typescript-eslint/typescript-estree": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } } }, "@typescript-eslint/visitor-keys": { @@ -11718,15 +12000,15 @@ } }, "ajv": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.14.0.tgz", - "integrity": "sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" + "require-from-string": "^2.0.2" } }, "ansi-escapes": { @@ -12296,9 +12578,9 @@ } }, "commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true }, "comment-parser": { @@ -12884,16 +13166,16 @@ "dev": true }, "eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -13146,9 +13428,9 @@ } }, "eslint-plugin-unused-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.1.0.tgz", - "integrity": "sha512-9l1YFCzXKkw1qtAru1RWUtG2EVDZY0a0eChKXcL+EZ5jitG7qxdctu4RnvhOJHv4xfmUf7h+JJPINlVpGhZMrw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.2.0.tgz", + "integrity": "sha512-6uXyn6xdINEpxE1MtDjxQsyXB37lfyO2yKGVVgtD7WEWQGORSOZjgrD6hBhvGv4/SO+TOlS+UnC6JppRqbuwGQ==", "dev": true, "requires": { "eslint-rule-composer": "^0.3.0" @@ -13182,8 +13464,7 @@ "eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" }, "espree": { "version": "9.6.1", @@ -13288,7 +13569,6 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -13301,7 +13581,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -13320,6 +13599,12 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "dev": true + }, "fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -13330,7 +13615,6 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, "requires": { "reusify": "^1.0.4" } @@ -14534,12 +14818,6 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true - }, - "commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true } } }, @@ -14833,14 +15111,12 @@ "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", - "dev": true, + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "requires": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -15441,8 +15717,7 @@ "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, "quick-lru": { "version": "5.1.1", @@ -15725,8 +16000,7 @@ "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, "rfdc": { "version": "1.3.1", @@ -15792,7 +16066,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "requires": { "queue-microtask": "^1.2.2" } @@ -15857,8 +16130,7 @@ "semver": { "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==" }, "sentence-case": { "version": "3.0.4", @@ -16592,22 +16864,21 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", - "dev": true, "requires": {} }, "ts-json-schema-generator": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-1.5.0.tgz", - "integrity": "sha512-RkiaJ6YxGc5EWVPfyHxszTmpGxX8HC2XBvcFlAl1zcvpOG4tjjh+eXioStXJQYTvr9MoK8zCOWzAUlko3K0DiA==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-1.5.1.tgz", + "integrity": "sha512-apX5qG2+NA66j7b4AJm8q/DpdTeOsjfh7A3LpKsUiil0FepkNwtN28zYgjrsiiya2/OPhsr/PSjX5FUYg79rCg==", "dev": true, "requires": { - "@types/json-schema": "^7.0.12", - "commander": "^11.0.0", + "@types/json-schema": "^7.0.15", + "commander": "^12.0.0", "glob": "^8.0.3", "json5": "^2.2.3", "normalize-path": "^3.0.0", "safe-stable-stringify": "^2.4.3", - "typescript": "~5.3.2" + "typescript": "~5.4.2" }, "dependencies": { "brace-expansion": { @@ -16640,12 +16911,6 @@ "requires": { "brace-expansion": "^2.0.1" } - }, - "typescript": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", - "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true } } }, @@ -16759,8 +17024,7 @@ "typescript": { "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==" }, "ua-parser-js": { "version": "0.7.37", diff --git a/package.json b/package.json index ec074190bb..045b96b772 100644 --- a/package.json +++ b/package.json @@ -70,14 +70,14 @@ "@types/node": "20.11.5", "@types/wanakana": "^5.3.0", "@types/zip.js": "^2.0.33", - "@typescript-eslint/eslint-plugin": "^7.0.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", "@vitest/coverage-v8": "1.2.2", - "ajv": "^8.14.0", + "ajv": "^8.17.1", "css": "^3.0.0", "dotenv": "^16.4.5", "esbuild": "^0.21.4", - "eslint": "^8.57.0", + "eslint": "^8.57.1", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-header": "^3.1.1", "eslint-plugin-import": "^2.29.1", @@ -86,7 +86,7 @@ "eslint-plugin-no-unsanitized": "^4.0.2", "eslint-plugin-sonarjs": "^0.24.0", "eslint-plugin-unicorn": "^51.0.1", - "eslint-plugin-unused-imports": "^3.1.0", + "eslint-plugin-unused-imports": "^3.2.0", "eslint-plugin-vitest": "0.3.22", "fake-indexeddb": "^5.0.2", "handlebars": "^4.7.8", @@ -99,9 +99,9 @@ "prettier": "^3.2.5", "stylelint": "^16.2.1", "stylelint-config-recommended": "^14.0.0", - "ts-json-schema-generator": "^1.5.0", + "ts-json-schema-generator": "^1.5.1", "typescript": "^5.4.5", - "vitest": "1.2.2" + "vitest": "^1.2.2" }, "dependencies": { "@zip.js/zip.js": "^2.7.45", diff --git a/test/utilities/anki.js b/test/utilities/anki.js index ed7b6c603f..2e6d8a69c9 100644 --- a/test/utilities/anki.js +++ b/test/utilities/anki.js @@ -42,6 +42,7 @@ function createTestFields(type) { */ export function createTestAnkiNoteData(dictionaryEntry, mode, styles = '') { const marker = '{marker}'; + /** @type {Map} */ const dictionaryStylesMap = new Map(); if (styles !== '') { dictionaryStylesMap.set('Test Dictionary 2', styles); @@ -104,6 +105,7 @@ export async function getTemplateRenderResults(dictionaryEntries, mode, template query: 'query', fullQuery: 'fullQuery', }; + /** @type {Map} */ const dictionaryStylesMap = new Map(); if (styles) { dictionaryStylesMap.set('Test Dictionary 2', styles); From 086e043856ad54cf13cb65f9ba4c63afe8a22cc3 Mon Sep 17 00:00:00 2001 From: marv Date: Sun, 27 Oct 2024 10:22:28 -0700 Subject: [PATCH 06/33] Playwright Comment on Flaky Tests (#1531) * Playwright Comment on Flaky Tests * More specific regex * Fix if statement --- .github/workflows/playwright_comment.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/playwright_comment.yml b/.github/workflows/playwright_comment.yml index a118f980b6..992ffcea5f 100644 --- a/.github/workflows/playwright_comment.yml +++ b/.github/workflows/playwright_comment.yml @@ -47,6 +47,7 @@ jobs: cat ./master-screenshots-outcome >> $GITHUB_OUTPUT echo "$EOF" >> $GITHUB_OUTPUT echo "FAILED=$(grep -c '^ *[0-9] failed$' $GITHUB_OUTPUT)" >> $GITHUB_OUTPUT + echo "FLAKY=$(grep -c '^ *[0-9] flaky$' $GITHUB_OUTPUT)" >> $GITHUB_OUTPUT # this is required because github.event.workflow_run.pull_requests is not available for PRs from forks - name: "Get PR information" @@ -64,16 +65,15 @@ jobs: message: | :heavy_exclamation_mark: Could not fetch screenshots from master branch, so had nothing to make a visual comparison against; please check the "master-screenshots" step in the workflow run and rerun it before merging. - - name: "[Comment] Success: No visual differences introduced by this PR" + - name: "[Comment] Warning: Flaky tests caused by this PR; please check the playwright report" uses: mshick/add-pr-comment@dd126dd8c253650d181ad9538d8b4fa218fc31e8 # pin@v2 - if: steps.playwright.outputs.MASTER_SCREENSHOTS_OUTCOME != 'failure' && steps.playwright.outputs.FAILED == 0 + if: steps.playwright.outputs.MASTER_SCREENSHOTS_OUTCOME != 'failure' && steps.playwright.outputs.FLAKY != 0 with: issue: ${{ steps.source-run-info.outputs.pullRequestNumber }} message: | - :heavy_check_mark: No visual differences introduced by this PR. + :warning: Flaky tests caused by this PR; please check the playwright report. View Playwright Report (note: open the "playwright-report" artifact) - update-only: true - name: "[Comment] Warning: Visual differences introduced by this PR" uses: mshick/add-pr-comment@dd126dd8c253650d181ad9538d8b4fa218fc31e8 # pin@v2 @@ -84,3 +84,14 @@ jobs: :warning: Visual differences introduced by this PR; please validate if they are desirable. View Playwright Report (note: open the "playwright-report" artifact) + + - name: "[Comment] Success: No visual differences introduced by this PR" + uses: mshick/add-pr-comment@dd126dd8c253650d181ad9538d8b4fa218fc31e8 # pin@v2 + if: steps.playwright.outputs.MASTER_SCREENSHOTS_OUTCOME != 'failure' && steps.playwright.outputs.FAILED == 0 + with: + issue: ${{ steps.source-run-info.outputs.pullRequestNumber }} + message: | + :heavy_check_mark: No visual differences introduced by this PR. + + View Playwright Report (note: open the "playwright-report" artifact) + update-only: true From e79b3c79cde16b5345721049de80e1ceb42a7a9e Mon Sep 17 00:00:00 2001 From: marv Date: Sun, 27 Oct 2024 11:26:00 -0700 Subject: [PATCH 07/33] Fix Flaky Test Comment (#1536) --- .github/workflows/playwright_comment.yml | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/.github/workflows/playwright_comment.yml b/.github/workflows/playwright_comment.yml index 992ffcea5f..8b3d5bb69b 100644 --- a/.github/workflows/playwright_comment.yml +++ b/.github/workflows/playwright_comment.yml @@ -65,33 +65,23 @@ jobs: message: | :heavy_exclamation_mark: Could not fetch screenshots from master branch, so had nothing to make a visual comparison against; please check the "master-screenshots" step in the workflow run and rerun it before merging. - - name: "[Comment] Warning: Flaky tests caused by this PR; please check the playwright report" + - name: "[Comment] Warning: Flaky tests or visual differences caused by this PR; please check the playwright report" uses: mshick/add-pr-comment@dd126dd8c253650d181ad9538d8b4fa218fc31e8 # pin@v2 - if: steps.playwright.outputs.MASTER_SCREENSHOTS_OUTCOME != 'failure' && steps.playwright.outputs.FLAKY != 0 + if: steps.playwright.outputs.MASTER_SCREENSHOTS_OUTCOME != 'failure' && (steps.playwright.outputs.FLAKY != 0 || steps.playwright.outputs.FAILED != 0) with: issue: ${{ steps.source-run-info.outputs.pullRequestNumber }} message: | - :warning: Flaky tests caused by this PR; please check the playwright report. - - View Playwright Report (note: open the "playwright-report" artifact) - - - name: "[Comment] Warning: Visual differences introduced by this PR" - uses: mshick/add-pr-comment@dd126dd8c253650d181ad9538d8b4fa218fc31e8 # pin@v2 - if: steps.playwright.outputs.MASTER_SCREENSHOTS_OUTCOME != 'failure' && steps.playwright.outputs.FAILED != 0 - with: - issue: ${{ steps.source-run-info.outputs.pullRequestNumber }} - message: | - :warning: Visual differences introduced by this PR; please validate if they are desirable. + :warning: Flaky tests or visual differences caused by this PR; please check the playwright report. View Playwright Report (note: open the "playwright-report" artifact) - name: "[Comment] Success: No visual differences introduced by this PR" uses: mshick/add-pr-comment@dd126dd8c253650d181ad9538d8b4fa218fc31e8 # pin@v2 - if: steps.playwright.outputs.MASTER_SCREENSHOTS_OUTCOME != 'failure' && steps.playwright.outputs.FAILED == 0 + if: steps.playwright.outputs.MASTER_SCREENSHOTS_OUTCOME != 'failure' && steps.playwright.outputs.FLAKY == 0 && steps.playwright.outputs.FAILED == 0 with: issue: ${{ steps.source-run-info.outputs.pullRequestNumber }} message: | :heavy_check_mark: No visual differences introduced by this PR. View Playwright Report (note: open the "playwright-report" artifact) - update-only: true + update-only: true \ No newline at end of file From f462278af1f6345edeb724898566db2e7a06bf56 Mon Sep 17 00:00:00 2001 From: James Maa Date: Sun, 27 Oct 2024 13:05:48 -0700 Subject: [PATCH 08/33] Add local Playwright testing instructions to CONTRIBUTING.md (#1537) * Add local Playwright testing instructions to CONTRIBUTING.md Signed-off-by: James Maa * Update CONTRIBUTING.md Signed-off-by: James Maa --------- Signed-off-by: James Maa --- CONTRIBUTING.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3d3cd3ee39..1ba5aab7e4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,6 +30,14 @@ Unit tests, integration tests, and various other tests can be executed by runnin Other individual tests can be looked up in the [package.json](package.json) file, and the source for specific tests can be found in the [test](test) directory +### Playwright + +Steps to run [playwright](https://playwright.dev/) tests locally: + +1. Run `npx playwright install` to install the headless browsers +2. Copy the dictionary test data located in the `dictionaries` branch to a directory named `dictionaries` via `git clone --branch dictionaries git@github.com:yomidevs/yomitan.git dictionaries` ([source](https://github.com/yomidevs/yomitan/blob/086e043856ad54cf13cb65f9ba4c63afe8a22cc3/.github/workflows/playwright.yml#L52-L57)). +3. Now you can run `npx playwright test`. The first run might produce some benign errors complaining about `Error: A snapshot doesn't exist at ...writing actual.`, but subsequent runs should succeed. + ## Building By default, the development repository is configured for Chrome, and the [ext](ext) directory can be directly From 139ad7df43dd64acb6dd23f088f978cc5b0988a4 Mon Sep 17 00:00:00 2001 From: James Maa Date: Mon, 28 Oct 2024 11:53:52 -0700 Subject: [PATCH 09/33] Upgrade publish-edge (#1521) * Upgrade publish-edge * Hardcode value * Bump version * Undo hardcode --- .github/workflows/publish-edge.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-edge.yml b/.github/workflows/publish-edge.yml index 88a4118cdf..f3fc289981 100644 --- a/.github/workflows/publish-edge.yml +++ b/.github/workflows/publish-edge.yml @@ -15,11 +15,10 @@ jobs: fileName: "*" - name: Publish on Microsoft Edge Addons - uses: wdzeng/edge-addon@v1 + uses: wdzeng/edge-addon@v2 id: webStorePublish with: product-id: 18e6c4cd-6383-4f38-95e9-92a629f60817 zip-path: yomitan-edge.zip client-id: ${{ secrets.EDGE_CLIENT_ID }} - client-secret: ${{ secrets.EDGE_CLIENT_SECRET }} - access-token-url: ${{ secrets.EDGE_ACCESS_TOKEN_URL }} \ No newline at end of file + api-key: ${{ secrets.EDGE_API_KEY }} \ No newline at end of file From 7ed53c16d69d720b0e1139e14ef36dbdc6a93d15 Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:58:39 -0400 Subject: [PATCH 10/33] Allow scanning without moving mouse from clicking (#1538) * Allow scanning without moving mouse from clicking * Fix clicking or tapping to hide popup --- ext/js/display/display.js | 2 - ext/js/display/query-parser.js | 1 - ext/js/language/text-scanner.js | 68 ++------------------------------- types/ext/text-scanner.d.ts | 2 - 4 files changed, 4 insertions(+), 69 deletions(-) diff --git a/ext/js/display/display.js b/ext/js/display/display.js index 3f6d893a8d..af81bed84d 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -2011,8 +2011,6 @@ export class Display extends EventDispatcher { getSearchContext: this._getSearchContext.bind(this), searchTerms: true, searchKanji: false, - searchOnClick: true, - searchOnClickOnly: true, textSourceGenerator: this._textSourceGenerator, }); this._contentTextScanner.includeSelector = '.click-scannable,.click-scannable *'; diff --git a/ext/js/display/query-parser.js b/ext/js/display/query-parser.js index cf487a3d35..1221cca319 100644 --- a/ext/js/display/query-parser.js +++ b/ext/js/display/query-parser.js @@ -66,7 +66,6 @@ export class QueryParser extends EventDispatcher { getSearchContext, searchTerms: true, searchKanji: false, - searchOnClick: true, textSourceGenerator, }); /** @type {?(import('../language/ja/japanese-wanakana.js'))} */ diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index 58c6b1f294..ea059be856 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -39,8 +39,6 @@ export class TextScanner extends EventDispatcher { ignorePoint = null, searchTerms = false, searchKanji = false, - searchOnClick = false, - searchOnClickOnly = false, textSourceGenerator, }) { super(); @@ -58,10 +56,6 @@ export class TextScanner extends EventDispatcher { this._searchTerms = searchTerms; /** @type {boolean} */ this._searchKanji = searchKanji; - /** @type {boolean} */ - this._searchOnClick = searchOnClick; - /** @type {boolean} */ - this._searchOnClickOnly = searchOnClickOnly; /** @type {import('../dom/text-source-generator').TextSourceGenerator} */ this._textSourceGenerator = textSourceGenerator; @@ -662,7 +656,6 @@ export class TextScanner extends EventDispatcher { switch (e.button) { case 0: // Primary - if (this._searchOnClick) { this._resetPreventNextClickScan(); } this._scanTimerClear(); this._triggerClear('mousedown'); break; @@ -674,6 +667,8 @@ export class TextScanner extends EventDispatcher { } break; } + + this._onMouseMove(e); } /** */ @@ -694,28 +689,7 @@ export class TextScanner extends EventDispatcher { return false; } - if (this._searchOnClick) { - this._onSearchClick(e); - } - } - - /** - * @param {MouseEvent} e - */ - _onSearchClick(e) { - const preventNextClickScan = this._preventNextClickScan; - this._preventNextClickScan = false; - if (this._preventNextClickScanTimer !== null) { - clearTimeout(this._preventNextClickScanTimer); - this._preventNextClickScanTimer = null; - } - - if (preventNextClickScan) { return; } - - const modifiers = getActiveModifiersAndButtons(e); - const modifierKeys = getActiveModifiers(e); - const inputInfo = this._createInputInfo(null, 'mouse', 'click', false, modifiers, modifierKeys); - void this._searchAt(e.clientX, e.clientY, inputInfo); + this._onMouseMove(e); } /** */ @@ -1141,9 +1115,7 @@ export class TextScanner extends EventDispatcher { const capture = true; /** @type {import('event-listener-collection').AddEventListenerArgs[]} */ let eventListenerInfos; - if (this._searchOnClickOnly) { - eventListenerInfos = this._getMouseClickOnlyEventListeners(capture); - } else if (this._arePointerEventsSupported()) { + if (this._arePointerEventsSupported()) { eventListenerInfos = this._getPointerEventListeners(capture); } else { eventListenerInfos = [...this._getMouseEventListeners(capture)]; @@ -1154,9 +1126,6 @@ export class TextScanner extends EventDispatcher { eventListenerInfos.push(...this._getTouchEventListeners(capture)); } } - if (this._searchOnClick) { - eventListenerInfos.push(...this._getMouseClickOnlyEventListeners2(capture)); - } eventListenerInfos.push(this._getSelectionChangeCheckUserSelectionListener()); @@ -1223,35 +1192,6 @@ export class TextScanner extends EventDispatcher { ]; } - /** - * @param {boolean} capture - * @returns {import('event-listener-collection').AddEventListenerArgs[]} - */ - _getMouseClickOnlyEventListeners(capture) { - return [ - [this._node, 'click', this._onClick.bind(this), capture], - ]; - } - - /** - * @param {boolean} capture - * @returns {import('event-listener-collection').AddEventListenerArgs[]} - */ - _getMouseClickOnlyEventListeners2(capture) { - const {documentElement} = document; - /** @type {import('event-listener-collection').AddEventListenerArgs[]} */ - const entries = [ - [document, 'selectionchange', this._onSelectionChange.bind(this)], - ]; - if (documentElement !== null) { - entries.push([documentElement, 'mousedown', this._onSearchClickMouseDown.bind(this), capture]); - if (this._touchInputEnabled) { - entries.push([documentElement, 'touchstart', this._onSearchClickTouchStart.bind(this), {passive: true, capture}]); - } - } - return entries; - } - /** * @returns {import('event-listener-collection').AddEventListenerArgs} */ diff --git a/types/ext/text-scanner.d.ts b/types/ext/text-scanner.d.ts index 36cff9a4ae..859bfca270 100644 --- a/types/ext/text-scanner.d.ts +++ b/types/ext/text-scanner.d.ts @@ -154,8 +154,6 @@ export type ConstructorDetails = { ignorePoint?: ((x: number, y: number) => Promise) | null; searchTerms?: boolean; searchKanji?: boolean; - searchOnClick?: boolean; - searchOnClickOnly?: boolean; textSourceGenerator: TextSourceGenerator; }; From e3f279c345503f2f23c6bd51d0315d3bc681ced0 Mon Sep 17 00:00:00 2001 From: marv Date: Tue, 29 Oct 2024 00:11:45 -0700 Subject: [PATCH 11/33] Use Playwright Report Summary Action (#1543) * Use Playwright Report Summary Action * Move comment logic * Use output summary * Remove if statement * Remove comments --- .github/workflows/playwright.yml | 6 ++++++ .github/workflows/playwright_comment.yml | 23 +++++++++++++++-------- playwright.config.js | 5 ++++- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index a792076a98..924d2481ee 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -106,3 +106,9 @@ jobs: name: playwright-output path: playwright-output if: github.event_name == 'pull_request' + + - uses: actions/upload-artifact@v4 + with: + name: playwright-results-json + path: playwright-results.json + if: github.event_name == 'pull_request' diff --git a/.github/workflows/playwright_comment.yml b/.github/workflows/playwright_comment.yml index 8b3d5bb69b..5ae1adf853 100644 --- a/.github/workflows/playwright_comment.yml +++ b/.github/workflows/playwright_comment.yml @@ -35,6 +35,19 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} run_id: ${{ github.event.workflow_run.id }} name: master-screenshots-outcome + - name: Grab playwright-results-json from PR run + uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # pin@v2 + continue-on-error: true + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + run_id: ${{ github.event.workflow_run.id }} + name: playwright-results-json + + - name: Generate summary from playwright-results.json (expected to fail to comment) + id: playwright-summary + uses: daun/playwright-report-summary@v3 + with: + report-file: playwright-results.json - name: Load artifacts into environment variables id: playwright @@ -70,18 +83,12 @@ jobs: if: steps.playwright.outputs.MASTER_SCREENSHOTS_OUTCOME != 'failure' && (steps.playwright.outputs.FLAKY != 0 || steps.playwright.outputs.FAILED != 0) with: issue: ${{ steps.source-run-info.outputs.pullRequestNumber }} - message: | - :warning: Flaky tests or visual differences caused by this PR; please check the playwright report. - - View Playwright Report (note: open the "playwright-report" artifact) + message: ${{ steps.playwright-summary.outputs.summary }} - name: "[Comment] Success: No visual differences introduced by this PR" uses: mshick/add-pr-comment@dd126dd8c253650d181ad9538d8b4fa218fc31e8 # pin@v2 if: steps.playwright.outputs.MASTER_SCREENSHOTS_OUTCOME != 'failure' && steps.playwright.outputs.FLAKY == 0 && steps.playwright.outputs.FAILED == 0 with: issue: ${{ steps.source-run-info.outputs.pullRequestNumber }} - message: | - :heavy_check_mark: No visual differences introduced by this PR. - - View Playwright Report (note: open the "playwright-report" artifact) + message: ${{ steps.playwright-summary.outputs.summary }} update-only: true \ No newline at end of file diff --git a/playwright.config.js b/playwright.config.js index acfceb1d19..dfbec44b67 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -47,7 +47,10 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : void 0, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + reporter: [ + ['html'], + ['json', {outputFile: 'playwright-results.json'}], + ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ From 0fd70090fb755ba067465c69d8c3bedeb587a69d Mon Sep 17 00:00:00 2001 From: Khai Truong <56820749+khaitruong922@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:23:16 +0700 Subject: [PATCH 12/33] Sort entries by primary reading (#1497) * Search term by exact reading when clicking on gloss link * lint * update typing * type * sort by reading match * fix lint and test * add test * fix typing * rename * refactor * refactor * rename to primary reading * remove extract reading implementation --- ext/js/background/backend.js | 8 +- ext/js/display/display.js | 13 +- ext/js/language/translator.js | 68 ++- test/data/anki-note-builder-test-results.json | 178 +++++++ test/data/database-test-cases.json | 6 +- .../valid-dictionary1/term_bank_1.json | 4 +- test/data/translator-test-inputs.json | 26 + .../translator-test-results-note-data1.json | 458 ++++++++++++++++++ test/data/translator-test-results.json | 450 +++++++++++++++++ test/utilities/translator.js | 2 + types/ext/api.d.ts | 1 + types/ext/dictionary.d.ts | 4 + types/ext/translation.d.ts | 4 + types/test/translator.d.ts | 1 + 14 files changed, 1188 insertions(+), 35 deletions(-) diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 8fe408ee9a..ceb736c40b 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -1463,7 +1463,9 @@ export class Backend { /** @type {import('translator').FindTermsMode} */ const mode = 'simple'; const options = this._getProfileOptions(optionsContext, false); - const details = {matchType: /** @type {import('translation').FindTermsMatchType} */ ('exact'), deinflect: true}; + + /** @type {import('api').FindTermsDetails} */ + const details = {matchType: 'exact', deinflect: true}; const findTermsOptions = this._getTranslatorFindTermsOptions(mode, details, options); /** @type {import('api').ParseTextLine[]} */ const results = []; @@ -2455,9 +2457,10 @@ export class Backend { * @returns {import('translation').FindTermsOptions} An options object. */ _getTranslatorFindTermsOptions(mode, details, options) { - let {matchType, deinflect} = details; + let {matchType, deinflect, primaryReading} = details; if (typeof matchType !== 'string') { matchType = /** @type {import('translation').FindTermsMatchType} */ ('exact'); } if (typeof deinflect !== 'boolean') { deinflect = true; } + if (typeof primaryReading !== 'string') { primaryReading = ''; } const enabledDictionaryMap = this._getTranslatorEnabledDictionaryMap(options); const { general: {mainDictionary, sortFrequencyDictionary, sortFrequencyDictionaryOrder, language}, @@ -2484,6 +2487,7 @@ export class Backend { return { matchType, deinflect, + primaryReading, mainDictionary, sortFrequencyDictionary, sortFrequencyDictionaryOrder, diff --git a/ext/js/display/display.js b/ext/js/display/display.js index af81bed84d..14b943e22e 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -1265,14 +1265,15 @@ export class Display extends EventDispatcher { /** * @param {boolean} isKanji * @param {string} source + * @param {string} primaryReading * @param {boolean} wildcardsEnabled * @param {import('settings').OptionsContext} optionsContext * @returns {Promise} */ - async _findDictionaryEntries(isKanji, source, wildcardsEnabled, optionsContext) { + async _findDictionaryEntries(isKanji, source, primaryReading, wildcardsEnabled, optionsContext) { /** @type {import('dictionary').DictionaryEntry[]} */ let dictionaryEntries = []; - const {findDetails, source: source2} = this._getFindDetails(source, wildcardsEnabled); + const {findDetails, source: source2} = this._getFindDetails(source, primaryReading, wildcardsEnabled); if (isKanji) { dictionaryEntries = await this._application.api.kanjiFind(source, optionsContext); if (dictionaryEntries.length > 0) { return dictionaryEntries; } @@ -1289,12 +1290,13 @@ export class Display extends EventDispatcher { /** * @param {string} source + * @param {string} primaryReading * @param {boolean} wildcardsEnabled * @returns {{findDetails: import('api').FindTermsDetails, source: string}} */ - _getFindDetails(source, wildcardsEnabled) { + _getFindDetails(source, primaryReading, wildcardsEnabled) { /** @type {import('api').FindTermsDetails} */ - const findDetails = {}; + const findDetails = {primaryReading}; if (wildcardsEnabled) { const match = /^([*\uff0a]*)([\w\W]*?)([*\uff0a]*)$/.exec(source); if (match !== null) { @@ -1327,6 +1329,7 @@ export class Display extends EventDispatcher { if (query === null) { query = ''; } let queryFull = urlSearchParams.get('full'); queryFull = (queryFull !== null ? queryFull : query); + const primaryReading = urlSearchParams.get('primary_reading') ?? ''; const queryOffsetString = urlSearchParams.get('offset'); let queryOffset = 0; if (queryOffsetString !== null) { @@ -1358,7 +1361,7 @@ export class Display extends EventDispatcher { let {dictionaryEntries} = content; if (!Array.isArray(dictionaryEntries)) { - dictionaryEntries = hasEnabledDictionaries && lookup && query.length > 0 ? await this._findDictionaryEntries(type === 'kanji', query, wildcardsEnabled, optionsContext) : []; + dictionaryEntries = hasEnabledDictionaries && lookup && query.length > 0 ? await this._findDictionaryEntries(type === 'kanji', query, primaryReading, wildcardsEnabled, optionsContext) : []; if (this._setContentToken !== token) { return; } content.dictionaryEntries = dictionaryEntries; changeHistory = true; diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 21719da3d1..c5ce979074 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -76,13 +76,13 @@ export class Translator { * @returns {Promise<{dictionaryEntries: import('dictionary').TermDictionaryEntry[], originalTextLength: number}>} An object containing dictionary entries and the length of the original source text. */ async findTerms(mode, text, options) { - const {enabledDictionaryMap, excludeDictionaryDefinitions, sortFrequencyDictionary, sortFrequencyDictionaryOrder, language} = options; + const {enabledDictionaryMap, excludeDictionaryDefinitions, sortFrequencyDictionary, sortFrequencyDictionaryOrder, language, primaryReading} = options; const tagAggregator = new TranslatorTagAggregator(); - let {dictionaryEntries, originalTextLength} = await this._findTermsInternal(text, options, tagAggregator); + let {dictionaryEntries, originalTextLength} = await this._findTermsInternal(text, options, tagAggregator, primaryReading); switch (mode) { case 'group': - dictionaryEntries = this._groupDictionaryEntriesByHeadword(language, dictionaryEntries, tagAggregator); + dictionaryEntries = this._groupDictionaryEntriesByHeadword(language, dictionaryEntries, tagAggregator, primaryReading); break; case 'merge': dictionaryEntries = await this._getRelatedDictionaryEntries(dictionaryEntries, options, tagAggregator); @@ -217,9 +217,10 @@ export class Translator { * @param {string} text * @param {import('translation').FindTermsOptions} options * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading * @returns {Promise<{dictionaryEntries: import('translation-internal').TermDictionaryEntry[], originalTextLength: number}>} */ - async _findTermsInternal(text, options, tagAggregator) { + async _findTermsInternal(text, options, tagAggregator, primaryReading) { const {removeNonJapaneseCharacters, enabledDictionaryMap} = options; if (removeNonJapaneseCharacters && (['ja', 'zh', 'yue'].includes(options.language))) { text = this._getJapaneseChineseOnlyText(text); @@ -230,16 +231,17 @@ export class Translator { const deinflections = await this._getDeinflections(text, options); - return this._getDictionaryEntries(deinflections, enabledDictionaryMap, tagAggregator); + return this._getDictionaryEntries(deinflections, enabledDictionaryMap, tagAggregator, primaryReading); } /** * @param {import('translation-internal').DatabaseDeinflection[]} deinflections * @param {import('translation').TermEnabledDictionaryMap} enabledDictionaryMap * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading * @returns {{dictionaryEntries: import('translation-internal').TermDictionaryEntry[], originalTextLength: number}} */ - _getDictionaryEntries(deinflections, enabledDictionaryMap, tagAggregator) { + _getDictionaryEntries(deinflections, enabledDictionaryMap, tagAggregator, primaryReading) { let originalTextLength = 0; /** @type {import('translation-internal').TermDictionaryEntry[]} */ const dictionaryEntries = []; @@ -261,13 +263,13 @@ export class Translator { continue; } if (transformedText.length > existingTransformedLength) { - dictionaryEntries.splice(existingIndex, 1, this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, true, enabledDictionaryMap, tagAggregator)); + dictionaryEntries.splice(existingIndex, 1, this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, true, enabledDictionaryMap, tagAggregator, primaryReading)); } else { this._mergeInflectionRuleChains(existingEntry, inflectionRuleChainCandidates); this._mergeTextProcessorRuleChains(existingEntry, textProcessorRuleChainCandidates); } } else { - const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, true, enabledDictionaryMap, tagAggregator); + const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, true, enabledDictionaryMap, tagAggregator, primaryReading); dictionaryEntries.push(dictionaryEntry); ids.add(id); } @@ -680,7 +682,7 @@ export class Translator { * @returns {Promise} */ async _getRelatedDictionaryEntries(dictionaryEntries, options, tagAggregator) { - const {mainDictionary, enabledDictionaryMap, language} = options; + const {mainDictionary, enabledDictionaryMap, language, primaryReading} = options; /** @type {import('translator').SequenceQuery[]} */ const sequenceList = []; /** @type {import('translation-internal').DictionaryEntryGroup[]} */ @@ -711,20 +713,20 @@ export class Translator { if (sequenceList.length > 0) { const secondarySearchDictionaryMap = this._getSecondarySearchDictionaryMap(enabledDictionaryMap); - await this._addRelatedDictionaryEntries(groupedDictionaryEntries, ungroupedDictionaryEntriesMap, sequenceList, enabledDictionaryMap, tagAggregator); + await this._addRelatedDictionaryEntries(groupedDictionaryEntries, ungroupedDictionaryEntriesMap, sequenceList, enabledDictionaryMap, tagAggregator, primaryReading); for (const group of groupedDictionaryEntries) { this._sortTermDictionaryEntriesById(group.dictionaryEntries); } if (ungroupedDictionaryEntriesMap.size > 0 || secondarySearchDictionaryMap.size > 0) { - await this._addSecondaryRelatedDictionaryEntries(language, groupedDictionaryEntries, ungroupedDictionaryEntriesMap, enabledDictionaryMap, secondarySearchDictionaryMap, tagAggregator); + await this._addSecondaryRelatedDictionaryEntries(language, groupedDictionaryEntries, ungroupedDictionaryEntriesMap, enabledDictionaryMap, secondarySearchDictionaryMap, tagAggregator, primaryReading); } } const newDictionaryEntries = []; for (const group of groupedDictionaryEntries) { - newDictionaryEntries.push(this._createGroupedDictionaryEntry(language, group.dictionaryEntries, true, tagAggregator)); + newDictionaryEntries.push(this._createGroupedDictionaryEntry(language, group.dictionaryEntries, true, tagAggregator, primaryReading)); } - newDictionaryEntries.push(...this._groupDictionaryEntriesByHeadword(language, ungroupedDictionaryEntriesMap.values(), tagAggregator)); + newDictionaryEntries.push(...this._groupDictionaryEntriesByHeadword(language, ungroupedDictionaryEntriesMap.values(), tagAggregator, primaryReading)); return newDictionaryEntries; } @@ -734,8 +736,9 @@ export class Translator { * @param {import('translator').SequenceQuery[]} sequenceList * @param {import('translation').TermEnabledDictionaryMap} enabledDictionaryMap * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading */ - async _addRelatedDictionaryEntries(groupedDictionaryEntries, ungroupedDictionaryEntriesMap, sequenceList, enabledDictionaryMap, tagAggregator) { + async _addRelatedDictionaryEntries(groupedDictionaryEntries, ungroupedDictionaryEntriesMap, sequenceList, enabledDictionaryMap, tagAggregator, primaryReading) { const databaseEntries = await this._database.findTermsBySequenceBulk(sequenceList); for (const databaseEntry of databaseEntries) { const {dictionaryEntries, ids} = groupedDictionaryEntries[databaseEntry.index]; @@ -743,7 +746,7 @@ export class Translator { if (ids.has(id)) { continue; } const {term} = databaseEntry; - const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, term, term, term, [], [], false, enabledDictionaryMap, tagAggregator); + const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, term, term, term, [], [], false, enabledDictionaryMap, tagAggregator, primaryReading); dictionaryEntries.push(dictionaryEntry); ids.add(id); ungroupedDictionaryEntriesMap.delete(id); @@ -757,8 +760,9 @@ export class Translator { * @param {import('translation').TermEnabledDictionaryMap} enabledDictionaryMap * @param {import('translation').TermEnabledDictionaryMap} secondarySearchDictionaryMap * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading */ - async _addSecondaryRelatedDictionaryEntries(language, groupedDictionaryEntries, ungroupedDictionaryEntriesMap, enabledDictionaryMap, secondarySearchDictionaryMap, tagAggregator) { + async _addSecondaryRelatedDictionaryEntries(language, groupedDictionaryEntries, ungroupedDictionaryEntriesMap, enabledDictionaryMap, secondarySearchDictionaryMap, tagAggregator, primaryReading) { // Prepare grouping info /** @type {import('dictionary-database').TermExactRequest[]} */ const termList = []; @@ -816,7 +820,7 @@ export class Translator { for (const {ids, dictionaryEntries} of target.groups) { if (ids.has(id)) { continue; } - const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, sourceText, sourceText, sourceText, [], [], false, enabledDictionaryMap, tagAggregator); + const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, sourceText, sourceText, sourceText, [], [], false, enabledDictionaryMap, tagAggregator, primaryReading); dictionaryEntries.push(dictionaryEntry); ids.add(id); ungroupedDictionaryEntriesMap.delete(id); @@ -828,9 +832,10 @@ export class Translator { * @param {string} language * @param {Iterable} dictionaryEntries * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading * @returns {import('translation-internal').TermDictionaryEntry[]} */ - _groupDictionaryEntriesByHeadword(language, dictionaryEntries, tagAggregator) { + _groupDictionaryEntriesByHeadword(language, dictionaryEntries, tagAggregator, primaryReading) { /** @type {Map} */ const groups = new Map(); const readingNormalizer = this._readingNormalizers.get(language); @@ -848,7 +853,7 @@ export class Translator { const newDictionaryEntries = []; for (const groupDictionaryEntries of groups.values()) { - newDictionaryEntries.push(this._createGroupedDictionaryEntry(language, groupDictionaryEntries, false, tagAggregator)); + newDictionaryEntries.push(this._createGroupedDictionaryEntry(language, groupDictionaryEntries, false, tagAggregator, primaryReading)); } return newDictionaryEntries; } @@ -1668,12 +1673,13 @@ export class Translator { * @param {string} dictionaryAlias * @param {number} dictionaryPriority * @param {number} sourceTermExactMatchCount + * @param {boolean} matchPrimaryReading * @param {number} maxOriginalTextLength * @param {import('dictionary').TermHeadword[]} headwords * @param {import('dictionary').TermDefinition[]} definitions * @returns {import('translation-internal').TermDictionaryEntry} */ - _createTermDictionaryEntry(isPrimary, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, score, dictionaryIndex, dictionaryAlias, dictionaryPriority, sourceTermExactMatchCount, maxOriginalTextLength, headwords, definitions) { + _createTermDictionaryEntry(isPrimary, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, score, dictionaryIndex, dictionaryAlias, dictionaryPriority, sourceTermExactMatchCount, matchPrimaryReading, maxOriginalTextLength, headwords, definitions) { return { type: 'term', isPrimary, @@ -1685,6 +1691,7 @@ export class Translator { dictionaryAlias, dictionaryPriority, sourceTermExactMatchCount, + matchPrimaryReading, maxOriginalTextLength, headwords, definitions, @@ -1703,9 +1710,10 @@ export class Translator { * @param {boolean} isPrimary * @param {Map} enabledDictionaryMap * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading * @returns {import('translation-internal').TermDictionaryEntry} */ - _createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, isPrimary, enabledDictionaryMap, tagAggregator) { + _createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, isPrimary, enabledDictionaryMap, tagAggregator, primaryReading) { const { matchType, matchSource, @@ -1723,6 +1731,7 @@ export class Translator { // Cast is safe because getDeinflections filters out deinflection definitions const contentDefinitions = /** @type {import('dictionary-data').TermGlossaryContent[]} */ (definitions); const reading = (rawReading.length > 0 ? rawReading : term); + const matchPrimaryReading = primaryReading.length > 0 && reading === primaryReading; const {index: dictionaryIndex, priority: dictionaryPriority} = this._getDictionaryOrder(dictionary, enabledDictionaryMap); const dictionaryAlias = this._getDictionaryAlias(dictionary, enabledDictionaryMap); const sourceTermExactMatchCount = (isPrimary && deinflectedText === term ? 1 : 0); @@ -1747,6 +1756,7 @@ export class Translator { dictionaryAlias, dictionaryPriority, sourceTermExactMatchCount, + matchPrimaryReading, maxOriginalTextLength, [this._createTermHeadword(0, term, reading, [source], headwordTagGroups, rules)], [this._createTermDefinition(0, [0], dictionary, dictionaryIndex, dictionaryAlias, dictionaryPriority, id, score, [sequence], isPrimary, definitionTagGroups, contentDefinitions)], @@ -1758,9 +1768,10 @@ export class Translator { * @param {import('translation-internal').TermDictionaryEntry[]} dictionaryEntries * @param {boolean} checkDuplicateDefinitions * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading * @returns {import('translation-internal').TermDictionaryEntry} */ - _createGroupedDictionaryEntry(language, dictionaryEntries, checkDuplicateDefinitions, tagAggregator) { + _createGroupedDictionaryEntry(language, dictionaryEntries, checkDuplicateDefinitions, tagAggregator, primaryReading) { // Headwords are generated before sorting, so that the order of dictionaryEntries can be maintained const definitionEntries = []; /** @type {Map} */ @@ -1820,7 +1831,11 @@ export class Translator { const headwordsArray = [...headwords.values()]; let sourceTermExactMatchCount = 0; - for (const {sources} of headwordsArray) { + let matchPrimaryReading = false; + for (const {sources, reading} of headwordsArray) { + if (primaryReading.length > 0 && reading === primaryReading) { + matchPrimaryReading = true; + } for (const source of sources) { if (source.isPrimary && source.matchSource === 'term') { ++sourceTermExactMatchCount; @@ -1838,6 +1853,7 @@ export class Translator { dictionaryAlias, dictionaryPriority, sourceTermExactMatchCount, + matchPrimaryReading, maxOriginalTextLength, headwordsArray, definitions, @@ -2017,8 +2033,12 @@ export class Translator { * @returns {number} */ const compareFunction = (v1, v2) => { + // Sort by reading match + let i = (v2.matchPrimaryReading ? 1 : 0) - (v1.matchPrimaryReading ? 1 : 0); + if (i !== 0) { return i; } + // Sort by length of source term - let i = v2.maxOriginalTextLength - v1.maxOriginalTextLength; + i = v2.maxOriginalTextLength - v1.maxOriginalTextLength; if (i !== 0) { return i; } // Sort by length of the shortest text processing chain diff --git a/test/data/anki-note-builder-test-results.json b/test/data/anki-note-builder-test-results.json index a7e954152d..b496d0b880 100644 --- a/test/data/anki-note-builder-test-results.json +++ b/test/data/anki-note-builder-test-results.json @@ -4606,5 +4606,183 @@ "url": "url:" } ] + }, + { + "name": "Find terms using primary reading 1", + "results": [ + { + "audio": "", + "clipboard-image": "", + "clipboard-text": "", + "cloze-body": "自重", + "cloze-body-kana": "じちょう", + "cloze-prefix": "cloze-prefix", + "cloze-suffix": "cloze-suffix", + "conjugation": "", + "dictionary": "Test Dictionary 2", + "dictionary-alias": "termsDictAlias", + "document-title": "title", + "expression": "自重", + "frequencies": "", + "frequency-harmonic-rank": "9999999", + "frequency-harmonic-occurrence": "0", + "frequency-average-rank": "9999999", + "frequency-average-occurrence": "0", + "furigana": "自重じちょう", + "furigana-plain": "自重[じちょう]", + "glossary": "
(n, termsDictAlias) jichou definition
", + "glossary-brief": "
jichou definition
", + "glossary-no-dictionary": "
(n) jichou definition
", + "glossary-first": "
(n, termsDictAlias) jichou definition
", + "glossary-first-brief": "
jichou definition
", + "glossary-first-no-dictionary": "
(n) jichou definition
", + "part-of-speech": "Noun", + "pitch-accents": "", + "pitch-accent-graphs": "", + "pitch-accent-graphs-jj": "", + "pitch-accent-positions": "", + "pitch-accent-categories": "", + "phonetic-transcriptions": "", + "reading": "じちょう", + "screenshot": "", + "search-query": "fullQuery", + "popup-selection-text": "", + "sentence": "cloze-prefix自重cloze-suffix", + "sentence-furigana": "cloze-prefix自重cloze-suffix", + "tags": "n", + "url": "url:" + }, + { + "audio": "", + "clipboard-image": "", + "clipboard-text": "", + "cloze-body": "自重", + "cloze-body-kana": "じじゅう", + "cloze-prefix": "cloze-prefix", + "cloze-suffix": "cloze-suffix", + "conjugation": "", + "dictionary": "Test Dictionary 2", + "dictionary-alias": "termsDictAlias", + "document-title": "title", + "expression": "自重", + "frequencies": "", + "frequency-harmonic-rank": "9999999", + "frequency-harmonic-occurrence": "0", + "frequency-average-rank": "9999999", + "frequency-average-occurrence": "0", + "furigana": "自重じじゅう", + "furigana-plain": "自重[じじゅう]", + "glossary": "
(n, termsDictAlias) jijuu definition
", + "glossary-brief": "
jijuu definition
", + "glossary-no-dictionary": "
(n) jijuu definition
", + "glossary-first": "
(n, termsDictAlias) jijuu definition
", + "glossary-first-brief": "
jijuu definition
", + "glossary-first-no-dictionary": "
(n) jijuu definition
", + "part-of-speech": "Noun", + "pitch-accents": "", + "pitch-accent-graphs": "", + "pitch-accent-graphs-jj": "", + "pitch-accent-positions": "", + "pitch-accent-categories": "", + "phonetic-transcriptions": "", + "reading": "じじゅう", + "screenshot": "", + "search-query": "fullQuery", + "popup-selection-text": "", + "sentence": "cloze-prefix自重cloze-suffix", + "sentence-furigana": "cloze-prefix自重cloze-suffix", + "tags": "n", + "url": "url:" + } + ] + }, + { + "name": "Find terms using primary reading 2", + "results": [ + { + "audio": "", + "clipboard-image": "", + "clipboard-text": "", + "cloze-body": "自重", + "cloze-body-kana": "じじゅう", + "cloze-prefix": "cloze-prefix", + "cloze-suffix": "cloze-suffix", + "conjugation": "", + "dictionary": "Test Dictionary 2", + "dictionary-alias": "termsDictAlias", + "document-title": "title", + "expression": "自重", + "frequencies": "", + "frequency-harmonic-rank": "9999999", + "frequency-harmonic-occurrence": "0", + "frequency-average-rank": "9999999", + "frequency-average-occurrence": "0", + "furigana": "自重じじゅう", + "furigana-plain": "自重[じじゅう]", + "glossary": "
(n, termsDictAlias) jijuu definition
", + "glossary-brief": "
jijuu definition
", + "glossary-no-dictionary": "
(n) jijuu definition
", + "glossary-first": "
(n, termsDictAlias) jijuu definition
", + "glossary-first-brief": "
jijuu definition
", + "glossary-first-no-dictionary": "
(n) jijuu definition
", + "part-of-speech": "Noun", + "pitch-accents": "", + "pitch-accent-graphs": "", + "pitch-accent-graphs-jj": "", + "pitch-accent-positions": "", + "pitch-accent-categories": "", + "phonetic-transcriptions": "", + "reading": "じじゅう", + "screenshot": "", + "search-query": "fullQuery", + "popup-selection-text": "", + "sentence": "cloze-prefix自重cloze-suffix", + "sentence-furigana": "cloze-prefix自重cloze-suffix", + "tags": "n", + "url": "url:" + }, + { + "audio": "", + "clipboard-image": "", + "clipboard-text": "", + "cloze-body": "自重", + "cloze-body-kana": "じちょう", + "cloze-prefix": "cloze-prefix", + "cloze-suffix": "cloze-suffix", + "conjugation": "", + "dictionary": "Test Dictionary 2", + "dictionary-alias": "termsDictAlias", + "document-title": "title", + "expression": "自重", + "frequencies": "", + "frequency-harmonic-rank": "9999999", + "frequency-harmonic-occurrence": "0", + "frequency-average-rank": "9999999", + "frequency-average-occurrence": "0", + "furigana": "自重じちょう", + "furigana-plain": "自重[じちょう]", + "glossary": "
(n, termsDictAlias) jichou definition
", + "glossary-brief": "
jichou definition
", + "glossary-no-dictionary": "
(n) jichou definition
", + "glossary-first": "
(n, termsDictAlias) jichou definition
", + "glossary-first-brief": "
jichou definition
", + "glossary-first-no-dictionary": "
(n) jichou definition
", + "part-of-speech": "Noun", + "pitch-accents": "", + "pitch-accent-graphs": "", + "pitch-accent-graphs-jj": "", + "pitch-accent-positions": "", + "pitch-accent-categories": "", + "phonetic-transcriptions": "", + "reading": "じちょう", + "screenshot": "", + "search-query": "fullQuery", + "popup-selection-text": "", + "sentence": "cloze-prefix自重cloze-suffix", + "sentence-furigana": "cloze-prefix自重cloze-suffix", + "tags": "n", + "url": "url:" + } + ] } ] diff --git a/test/data/database-test-cases.json b/test/data/database-test-cases.json index 6d0cbe2f59..1595ffc4ae 100644 --- a/test/data/database-test-cases.json +++ b/test/data/database-test-cases.json @@ -28,7 +28,7 @@ "ipa": 1 }, "terms": { - "total": 31 + "total": 33 } } }, @@ -37,7 +37,7 @@ { "kanji": 2, "kanjiMeta": 6, - "terms": 31, + "terms": 33, "termMeta": 39, "tagMeta": 15, "media": 6 @@ -46,7 +46,7 @@ "total": { "kanji": 2, "kanjiMeta": 6, - "terms": 31, + "terms": 33, "termMeta": 39, "tagMeta": 15, "media": 6 diff --git a/test/data/dictionaries/valid-dictionary1/term_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_bank_1.json index 0a635b84d6..7c3e005a40 100644 --- a/test/data/dictionaries/valid-dictionary1/term_bank_1.json +++ b/test/data/dictionaries/valid-dictionary1/term_bank_1.json @@ -345,5 +345,7 @@ ["English", "", "n", "n", 1, ["English definition"], 19, ""], ["language", "", "n", "n", 1, ["language definition"], 20, ""], ["USB", "ユーエスビー", "n", "n", 1, ["USB definition"], 21, ""], - ["마시다", "", "v", "v", 1, ["masida definition"], 22, ""] + ["마시다", "", "v", "v", 1, ["masida definition"], 22, ""], + ["自重", "じちょう", "n", "n", 1, ["jichou definition"], 23, ""], + ["自重", "じじゅう", "n", "n", 2, ["jijuu definition"], 24, ""] ] diff --git a/test/data/translator-test-inputs.json b/test/data/translator-test-inputs.json index 25115e9c23..bceb9d68eb 100644 --- a/test/data/translator-test-inputs.json +++ b/test/data/translator-test-inputs.json @@ -513,6 +513,32 @@ "searchResolution": "word" } ] + }, + { + "name": "Find terms using primary reading 1", + "func": "findTerms", + "mode": "split", + "text": "自重", + "options": [ + "default", + { + "type": "terms", + "primaryReading": "じちょう" + } + ] + }, + { + "name": "Find terms using primary reading 2", + "func": "findTerms", + "mode": "split", + "text": "自重", + "options": [ + "default", + { + "type": "terms", + "primaryReading": "じじゅう" + } + ] } ] } diff --git a/test/data/translator-test-results-note-data1.json b/test/data/translator-test-results-note-data1.json index e43042a0a8..dedba5601e 100644 --- a/test/data/translator-test-results-note-data1.json +++ b/test/data/translator-test-results-note-data1.json @@ -35095,5 +35095,463 @@ "media": {} } ] + }, + { + "name": "Find terms using primary reading 1", + "noteDataList": [ + { + "marker": "{marker}", + "definition": { + "type": "term", + "id": 31, + "source": "自重", + "rawSource": "自重", + "sourceTerm": "自重", + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 1, + "isPrimary": true, + "sequence": 23, + "dictionary": "Test Dictionary 2", + "dictionaryAlias": "termsDictAlias", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "dictionaryNames": [ + "Test Dictionary 2" + ], + "expression": "自重", + "reading": "じちょう", + "expressions": [ + { + "sourceTerm": "自重", + "expression": "自重", + "reading": "じちょう", + "termTags": [], + "frequencies": [], + "pitches": [], + "furiganaSegments": [ + { + "text": "自重", + "furigana": "じちょう" + } + ], + "termFrequency": "normal", + "wordClasses": [ + "n" + ] + } + ], + "glossary": [ + "jichou definition" + ], + "glossaryScopedStyles": ".yomitan-glossary ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "dictScopedStyles": ".yomitan-glossary [data-dictionary=\"Test Dictionary 2\"] ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "definitionTags": [ + { + "name": "n", + "category": "partOfSpeech", + "notes": "noun", + "order": 0, + "score": 0, + "dictionary": "Test Dictionary 2", + "redundant": false + } + ], + "termTags": [], + "frequencies": [], + "frequencyHarmonic": -1, + "frequencyAverage": -1, + "pitches": [], + "phoneticTranscriptions": [], + "sourceTermExactMatchCount": 1, + "url": "url:", + "cloze": { + "sentence": "", + "prefix": "", + "body": "", + "bodyKana": "", + "suffix": "" + }, + "furiganaSegments": [ + { + "text": "自重", + "furigana": "じちょう" + } + ] + }, + "glossaryLayoutMode": "default", + "compactTags": false, + "group": false, + "merge": false, + "modeTermKanji": false, + "modeTermKana": false, + "modeKanji": false, + "compactGlossaries": false, + "uniqueExpressions": [ + "自重" + ], + "uniqueReadings": [ + "じちょう" + ], + "pitches": [], + "pitchCount": 0, + "phoneticTranscriptions": [], + "context": { + "query": "query", + "fullQuery": "fullQuery", + "document": { + "title": "title" + } + }, + "media": {} + }, + { + "marker": "{marker}", + "definition": { + "type": "term", + "id": 32, + "source": "自重", + "rawSource": "自重", + "sourceTerm": "自重", + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 2, + "isPrimary": true, + "sequence": 24, + "dictionary": "Test Dictionary 2", + "dictionaryAlias": "termsDictAlias", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "dictionaryNames": [ + "Test Dictionary 2" + ], + "expression": "自重", + "reading": "じじゅう", + "expressions": [ + { + "sourceTerm": "自重", + "expression": "自重", + "reading": "じじゅう", + "termTags": [], + "frequencies": [], + "pitches": [], + "furiganaSegments": [ + { + "text": "自重", + "furigana": "じじゅう" + } + ], + "termFrequency": "normal", + "wordClasses": [ + "n" + ] + } + ], + "glossary": [ + "jijuu definition" + ], + "glossaryScopedStyles": ".yomitan-glossary ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "dictScopedStyles": ".yomitan-glossary [data-dictionary=\"Test Dictionary 2\"] ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "definitionTags": [ + { + "name": "n", + "category": "partOfSpeech", + "notes": "noun", + "order": 0, + "score": 0, + "dictionary": "Test Dictionary 2", + "redundant": false + } + ], + "termTags": [], + "frequencies": [], + "frequencyHarmonic": -1, + "frequencyAverage": -1, + "pitches": [], + "phoneticTranscriptions": [], + "sourceTermExactMatchCount": 1, + "url": "url:", + "cloze": { + "sentence": "", + "prefix": "", + "body": "", + "bodyKana": "", + "suffix": "" + }, + "furiganaSegments": [ + { + "text": "自重", + "furigana": "じじゅう" + } + ] + }, + "glossaryLayoutMode": "default", + "compactTags": false, + "group": false, + "merge": false, + "modeTermKanji": false, + "modeTermKana": false, + "modeKanji": false, + "compactGlossaries": false, + "uniqueExpressions": [ + "自重" + ], + "uniqueReadings": [ + "じじゅう" + ], + "pitches": [], + "pitchCount": 0, + "phoneticTranscriptions": [], + "context": { + "query": "query", + "fullQuery": "fullQuery", + "document": { + "title": "title" + } + }, + "media": {} + } + ] + }, + { + "name": "Find terms using primary reading 2", + "noteDataList": [ + { + "marker": "{marker}", + "definition": { + "type": "term", + "id": 32, + "source": "自重", + "rawSource": "自重", + "sourceTerm": "自重", + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 2, + "isPrimary": true, + "sequence": 24, + "dictionary": "Test Dictionary 2", + "dictionaryAlias": "termsDictAlias", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "dictionaryNames": [ + "Test Dictionary 2" + ], + "expression": "自重", + "reading": "じじゅう", + "expressions": [ + { + "sourceTerm": "自重", + "expression": "自重", + "reading": "じじゅう", + "termTags": [], + "frequencies": [], + "pitches": [], + "furiganaSegments": [ + { + "text": "自重", + "furigana": "じじゅう" + } + ], + "termFrequency": "normal", + "wordClasses": [ + "n" + ] + } + ], + "glossary": [ + "jijuu definition" + ], + "glossaryScopedStyles": ".yomitan-glossary ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "dictScopedStyles": ".yomitan-glossary [data-dictionary=\"Test Dictionary 2\"] ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "definitionTags": [ + { + "name": "n", + "category": "partOfSpeech", + "notes": "noun", + "order": 0, + "score": 0, + "dictionary": "Test Dictionary 2", + "redundant": false + } + ], + "termTags": [], + "frequencies": [], + "frequencyHarmonic": -1, + "frequencyAverage": -1, + "pitches": [], + "phoneticTranscriptions": [], + "sourceTermExactMatchCount": 1, + "url": "url:", + "cloze": { + "sentence": "", + "prefix": "", + "body": "", + "bodyKana": "", + "suffix": "" + }, + "furiganaSegments": [ + { + "text": "自重", + "furigana": "じじゅう" + } + ] + }, + "glossaryLayoutMode": "default", + "compactTags": false, + "group": false, + "merge": false, + "modeTermKanji": false, + "modeTermKana": false, + "modeKanji": false, + "compactGlossaries": false, + "uniqueExpressions": [ + "自重" + ], + "uniqueReadings": [ + "じじゅう" + ], + "pitches": [], + "pitchCount": 0, + "phoneticTranscriptions": [], + "context": { + "query": "query", + "fullQuery": "fullQuery", + "document": { + "title": "title" + } + }, + "media": {} + }, + { + "marker": "{marker}", + "definition": { + "type": "term", + "id": 31, + "source": "自重", + "rawSource": "自重", + "sourceTerm": "自重", + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 1, + "isPrimary": true, + "sequence": 23, + "dictionary": "Test Dictionary 2", + "dictionaryAlias": "termsDictAlias", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "dictionaryNames": [ + "Test Dictionary 2" + ], + "expression": "自重", + "reading": "じちょう", + "expressions": [ + { + "sourceTerm": "自重", + "expression": "自重", + "reading": "じちょう", + "termTags": [], + "frequencies": [], + "pitches": [], + "furiganaSegments": [ + { + "text": "自重", + "furigana": "じちょう" + } + ], + "termFrequency": "normal", + "wordClasses": [ + "n" + ] + } + ], + "glossary": [ + "jichou definition" + ], + "glossaryScopedStyles": ".yomitan-glossary ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "dictScopedStyles": ".yomitan-glossary [data-dictionary=\"Test Dictionary 2\"] ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "definitionTags": [ + { + "name": "n", + "category": "partOfSpeech", + "notes": "noun", + "order": 0, + "score": 0, + "dictionary": "Test Dictionary 2", + "redundant": false + } + ], + "termTags": [], + "frequencies": [], + "frequencyHarmonic": -1, + "frequencyAverage": -1, + "pitches": [], + "phoneticTranscriptions": [], + "sourceTermExactMatchCount": 1, + "url": "url:", + "cloze": { + "sentence": "", + "prefix": "", + "body": "", + "bodyKana": "", + "suffix": "" + }, + "furiganaSegments": [ + { + "text": "自重", + "furigana": "じちょう" + } + ] + }, + "glossaryLayoutMode": "default", + "compactTags": false, + "group": false, + "merge": false, + "modeTermKanji": false, + "modeTermKana": false, + "modeKanji": false, + "compactGlossaries": false, + "uniqueExpressions": [ + "自重" + ], + "uniqueReadings": [ + "じちょう" + ], + "pitches": [], + "pitchCount": 0, + "phoneticTranscriptions": [], + "context": { + "query": "query", + "fullQuery": "fullQuery", + "document": { + "title": "title" + } + }, + "media": {} + } + ] } ] diff --git a/test/data/translator-test-results.json b/test/data/translator-test-results.json index 1cd5b4ba9d..bd09e49945 100644 --- a/test/data/translator-test-results.json +++ b/test/data/translator-test-results.json @@ -314,6 +314,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -494,6 +495,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -693,6 +695,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -874,6 +877,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -1055,6 +1059,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -1236,6 +1241,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -1417,6 +1423,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -1597,6 +1604,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -1796,6 +1804,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -2002,6 +2011,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -2208,6 +2218,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -2414,6 +2425,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -2625,6 +2637,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -2811,6 +2824,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -2997,6 +3011,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -3183,6 +3198,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -3364,6 +3380,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -3544,6 +3561,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -3743,6 +3761,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -3866,6 +3885,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -4052,6 +4072,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 3, "headwords": [ { @@ -4247,6 +4268,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -4433,6 +4455,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -4614,6 +4637,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -4801,6 +4825,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -4982,6 +5007,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -5169,6 +5195,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -5375,6 +5402,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -5586,6 +5614,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -5772,6 +5801,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -5959,6 +5989,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -6165,6 +6196,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -6376,6 +6408,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -6562,6 +6595,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -6749,6 +6783,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 3, "headwords": [ { @@ -6882,6 +6917,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -6949,6 +6985,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -7016,6 +7053,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -7083,6 +7121,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -7155,6 +7194,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -7227,6 +7267,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -7299,6 +7340,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -7371,6 +7413,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -7438,6 +7481,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -7505,6 +7549,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -7578,6 +7623,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -7833,6 +7879,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -8093,6 +8140,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -8328,6 +8376,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -8558,6 +8607,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -8738,6 +8788,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -8937,6 +8988,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 2, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -9424,6 +9476,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 2, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -9857,6 +9910,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -10037,6 +10091,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -10257,6 +10312,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -10484,6 +10540,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -10711,6 +10768,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -10938,6 +10996,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -11149,6 +11208,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -11335,6 +11395,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -11521,6 +11582,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -11707,6 +11769,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -11888,6 +11951,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -12068,6 +12132,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -12269,6 +12334,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 10, "headwords": [ { @@ -12477,6 +12543,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 10, "headwords": [ { @@ -12685,6 +12752,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 10, "headwords": [ { @@ -12893,6 +12961,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 10, "headwords": [ { @@ -13106,6 +13175,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 5, "headwords": [ { @@ -13294,6 +13364,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 5, "headwords": [ { @@ -13482,6 +13553,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 5, "headwords": [ { @@ -13670,6 +13742,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 5, "headwords": [ { @@ -13853,6 +13926,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -14035,6 +14109,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -14236,6 +14311,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -14444,6 +14520,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -14652,6 +14729,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -14860,6 +14938,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -15073,6 +15152,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 6, "headwords": [ { @@ -15261,6 +15341,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 6, "headwords": [ { @@ -15449,6 +15530,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 6, "headwords": [ { @@ -15637,6 +15719,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 6, "headwords": [ { @@ -15820,6 +15903,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 3, "headwords": [ { @@ -16002,6 +16086,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 3, "headwords": [ { @@ -16208,6 +16293,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -16323,6 +16409,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 5, "headwords": [ { @@ -16447,6 +16534,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 7, "headwords": [ { @@ -16560,6 +16648,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -17047,6 +17136,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -17486,6 +17576,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 3, "headwords": [ { @@ -17646,6 +17737,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -17752,6 +17844,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -17858,6 +17951,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -17964,6 +18058,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -18070,6 +18165,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -18193,6 +18289,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -18340,6 +18437,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 5, "headwords": [ { @@ -18426,6 +18524,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -18498,6 +18597,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 7, "headwords": [ { @@ -18586,6 +18686,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 3, "headwords": [ { @@ -18674,6 +18775,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -18857,6 +18959,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -19046,6 +19149,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -19229,6 +19333,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -19418,6 +19523,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -19535,6 +19641,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -19719,6 +19826,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -19925,6 +20033,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 7, "headwords": [ { @@ -20011,6 +20120,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 7, "headwords": [ { @@ -20099,6 +20209,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 8, "headwords": [ { @@ -20192,6 +20303,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -20278,6 +20390,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 7, "headwords": [ { @@ -20341,5 +20454,342 @@ "frequencies": [] } ] + }, + { + "name": "Find term using primary reading 1", + "originalTextLength": 2, + "dictionaryEntries": [ + { + "type": "term", + "isPrimary": true, + "textProcessorRuleChainCandidates": [ + [] + ], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 1, + "frequencyOrder": 0, + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "sourceTermExactMatchCount": 1, + "matchPrimaryReading": true, + "maxOriginalTextLength": 2, + "headwords": [ + { + "index": 0, + "term": "自重", + "reading": "じちょう", + "sources": [ + { + "originalText": "自重", + "transformedText": "自重", + "deinflectedText": "自重", + "matchType": "exact", + "matchSource": "term", + "isPrimary": true + } + ], + "tags": [], + "wordClasses": [ + "n" + ] + } + ], + "definitions": [ + { + "index": 0, + "headwordIndices": [ + 0 + ], + "dictionary": "Test Dictionary 2", + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "id": 31, + "score": 1, + "frequencyOrder": 0, + "sequences": [ + 23 + ], + "isPrimary": true, + "tags": [ + { + "name": "n", + "category": "partOfSpeech", + "order": 0, + "score": 0, + "content": [ + "noun" + ], + "dictionaries": [ + "Test Dictionary 2" + ], + "redundant": false + } + ], + "entries": [ + "jichou definition" + ] + } + ], + "pronunciations": [], + "frequencies": [] + }, + { + "type": "term", + "isPrimary": true, + "textProcessorRuleChainCandidates": [ + [] + ], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 2, + "frequencyOrder": 0, + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, + "maxOriginalTextLength": 2, + "headwords": [ + { + "index": 0, + "term": "自重", + "reading": "じじゅう", + "sources": [ + { + "originalText": "自重", + "transformedText": "自重", + "deinflectedText": "自重", + "matchType": "exact", + "matchSource": "term", + "isPrimary": true + } + ], + "tags": [], + "wordClasses": [ + "n" + ] + } + ], + "definitions": [ + { + "index": 0, + "headwordIndices": [ + 0 + ], + "dictionary": "Test Dictionary 2", + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "id": 32, + "score": 2, + "frequencyOrder": 0, + "sequences": [ + 24 + ], + "isPrimary": true, + "tags": [ + { + "name": "n", + "category": "partOfSpeech", + "order": 0, + "score": 0, + "content": [ + "noun" + ], + "dictionaries": [ + "Test Dictionary 2" + ], + "redundant": false + } + ], + "entries": [ + "jijuu definition" + ] + } + ], + "pronunciations": [], + "frequencies": [] + } + ] + }, + { + "name": "Find term using primary reading 2", + "originalTextLength": 2, + "dictionaryEntries": [ + + { + "type": "term", + "isPrimary": true, + "textProcessorRuleChainCandidates": [ + [] + ], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 2, + "frequencyOrder": 0, + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "sourceTermExactMatchCount": 1, + "matchPrimaryReading": true, + "maxOriginalTextLength": 2, + "headwords": [ + { + "index": 0, + "term": "自重", + "reading": "じじゅう", + "sources": [ + { + "originalText": "自重", + "transformedText": "自重", + "deinflectedText": "自重", + "matchType": "exact", + "matchSource": "term", + "isPrimary": true + } + ], + "tags": [], + "wordClasses": [ + "n" + ] + } + ], + "definitions": [ + { + "index": 0, + "headwordIndices": [ + 0 + ], + "dictionary": "Test Dictionary 2", + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "id": 32, + "score": 2, + "frequencyOrder": 0, + "sequences": [ + 24 + ], + "isPrimary": true, + "tags": [ + { + "name": "n", + "category": "partOfSpeech", + "order": 0, + "score": 0, + "content": [ + "noun" + ], + "dictionaries": [ + "Test Dictionary 2" + ], + "redundant": false + } + ], + "entries": [ + "jijuu definition" + ] + } + ], + "pronunciations": [], + "frequencies": [] + }, + { + "type": "term", + "isPrimary": true, + "textProcessorRuleChainCandidates": [ + [] + ], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 1, + "frequencyOrder": 0, + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, + "maxOriginalTextLength": 2, + "headwords": [ + { + "index": 0, + "term": "自重", + "reading": "じちょう", + "sources": [ + { + "originalText": "自重", + "transformedText": "自重", + "deinflectedText": "自重", + "matchType": "exact", + "matchSource": "term", + "isPrimary": true + } + ], + "tags": [], + "wordClasses": [ + "n" + ] + } + ], + "definitions": [ + { + "index": 0, + "headwordIndices": [ + 0 + ], + "dictionary": "Test Dictionary 2", + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "id": 31, + "score": 1, + "frequencyOrder": 0, + "sequences": [ + 23 + ], + "isPrimary": true, + "tags": [ + { + "name": "n", + "category": "partOfSpeech", + "order": 0, + "score": 0, + "content": [ + "noun" + ], + "dictionaries": [ + "Test Dictionary 2" + ], + "redundant": false + } + ], + "entries": [ + "jichou definition" + ] + } + ], + "pronunciations": [], + "frequencies": [] + } + ] } ] diff --git a/test/utilities/translator.js b/test/utilities/translator.js index e288c8df2e..314b1a680b 100644 --- a/test/utilities/translator.js +++ b/test/utilities/translator.js @@ -124,6 +124,7 @@ export function createFindTermsOptions(dictionaryName, optionsPresets, optionsAr sortFrequencyDictionary, sortFrequencyDictionaryOrder, removeNonJapaneseCharacters, + primaryReading, excludeDictionaryDefinitions, searchResolution, language, @@ -136,6 +137,7 @@ export function createFindTermsOptions(dictionaryName, optionsPresets, optionsAr sortFrequencyDictionary: typeof sortFrequencyDictionary !== 'undefined' ? sortFrequencyDictionary : null, sortFrequencyDictionaryOrder: typeof sortFrequencyDictionaryOrder !== 'undefined' ? sortFrequencyDictionaryOrder : 'ascending', removeNonJapaneseCharacters: typeof removeNonJapaneseCharacters !== 'undefined' ? removeNonJapaneseCharacters : false, + primaryReading: typeof primaryReading !== 'undefined' ? primaryReading : '', textReplacements, enabledDictionaryMap, excludeDictionaryDefinitions: Array.isArray(excludeDictionaryDefinitions) ? new Set(excludeDictionaryDefinitions) : null, diff --git a/types/ext/api.d.ts b/types/ext/api.d.ts index 46be7938eb..45996c48c2 100644 --- a/types/ext/api.d.ts +++ b/types/ext/api.d.ts @@ -48,6 +48,7 @@ import type { export type FindTermsDetails = { matchType?: Translation.FindTermsMatchType; deinflect?: boolean; + primaryReading?: string; }; export type ParseTextResultItem = { diff --git a/types/ext/dictionary.d.ts b/types/ext/dictionary.d.ts index ac9b7940ca..5a6fef4ab7 100644 --- a/types/ext/dictionary.d.ts +++ b/types/ext/dictionary.d.ts @@ -247,6 +247,10 @@ export type TermDictionaryEntry = { * The number of primary sources that had an exact text match for the term. */ sourceTermExactMatchCount: number; + /** + * Whether the term reading matched the primary reading. + */ + matchPrimaryReading: boolean; /** * The maximum length of the original text for all primary sources. */ diff --git a/types/ext/translation.d.ts b/types/ext/translation.d.ts index 4d60507ce3..a24fb744bd 100644 --- a/types/ext/translation.d.ts +++ b/types/ext/translation.d.ts @@ -68,6 +68,10 @@ export type FindTermsOptions = { * Whether or not deinflection should be performed. */ deinflect: boolean; + /** + * The reading which will be sorted to the top of the results. + */ + primaryReading: string; /** * The name of the primary dictionary to search. */ diff --git a/types/test/translator.d.ts b/types/test/translator.d.ts index efd5cc3fcf..2d18b75d13 100644 --- a/types/test/translator.d.ts +++ b/types/test/translator.d.ts @@ -44,6 +44,7 @@ export type FindTermsOptionsPreset = { sortFrequencyDictionary?: string | null; sortFrequencyDictionaryOrder?: FindTermsSortOrder; removeNonJapaneseCharacters?: boolean; + primaryReading?: string; textReplacements?: (FindTermsTextReplacement[] | null)[]; enabledDictionaryMap?: [key: string, value: FindTermDictionary][]; excludeDictionaryDefinitions?: string[] | null; From 87ef6fdfad2cd8ccc30242a1562a190e3021302b Mon Sep 17 00:00:00 2001 From: James Maa Date: Tue, 29 Oct 2024 12:53:36 -0700 Subject: [PATCH 13/33] Change playwright dictionary loading timeout (#1548) Change timeout --- test/playwright/integration.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/playwright/integration.spec.js b/test/playwright/integration.spec.js index b9f5170002..ca4ef694df 100644 --- a/test/playwright/integration.spec.js +++ b/test/playwright/integration.spec.js @@ -73,7 +73,7 @@ test('anki add', async ({context, page, extensionId}) => { mimeType: 'application/x-zip', buffer: Buffer.from(dictionary), }); - await expect(page.locator('id=dictionaries')).toHaveText('Dictionaries (1 installed, 1 enabled)', {timeout: 5 * 60 * 1000}); + await expect(page.locator('id=dictionaries')).toHaveText('Dictionaries (1 installed, 1 enabled)', {timeout: 1 * 60 * 1000}); // Connect to anki await page.locator('.toggle', {has: page.locator('[data-setting="anki.enable"]')}).click(); From c4b2d3d187c8dcd21f8adea160bc39ebb663e604 Mon Sep 17 00:00:00 2001 From: Olga <137053456+ImenaOphelia@users.noreply.github.com> Date: Tue, 29 Oct 2024 21:35:04 +0100 Subject: [PATCH 14/33] Change Polish Example to an Infinitive (#1549) --- ext/js/language/language-descriptors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/js/language/language-descriptors.js b/ext/js/language/language-descriptors.js index bdaebd61be..ef71d5f912 100644 --- a/ext/js/language/language-descriptors.js +++ b/ext/js/language/language-descriptors.js @@ -261,7 +261,7 @@ const languageDescriptors = [ iso: 'pl', iso639_3: 'pol', name: 'Polish', - exampleText: 'czytacie', + exampleText: 'czytać', textPreprocessors: capitalizationPreprocessors, }, { From 66309bc9d986446f51036e5576d154b10f80c9f4 Mon Sep 17 00:00:00 2001 From: Olga <137053456+ImenaOphelia@users.noreply.github.com> Date: Wed, 30 Oct 2024 20:12:23 +0100 Subject: [PATCH 15/33] Change Examples to Infinitives (#1550) --- ext/js/language/language-descriptors.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ext/js/language/language-descriptors.js b/ext/js/language/language-descriptors.js index ef71d5f912..a07437b711 100644 --- a/ext/js/language/language-descriptors.js +++ b/ext/js/language/language-descriptors.js @@ -78,7 +78,7 @@ const languageDescriptors = [ iso: 'de', iso639_3: 'deu', name: 'German', - exampleText: 'gelesen', + exampleText: 'lesen', textPreprocessors: { ...capitalizationPreprocessors, eszettPreprocessor, @@ -142,7 +142,7 @@ const languageDescriptors = [ iso: 'grc', iso639_3: 'grc', name: 'Ancient Greek', - exampleText: 'γράφω', + exampleText: 'γράφω', /* 'to write' */ textPreprocessors: { ...capitalizationPreprocessors, removeAlphabeticDiacritics, @@ -165,7 +165,7 @@ const languageDescriptors = [ iso: 'id', iso639_3: 'ind', name: 'Indonesian', - exampleText: 'membaca', + exampleText: 'baca', textPreprocessors: capitalizationPreprocessors, }, { @@ -182,7 +182,7 @@ const languageDescriptors = [ iso: 'la', iso639_3: 'lat', name: 'Latin', - exampleText: 'legere', + exampleText: 'legō', textPreprocessors: { ...capitalizationPreprocessors, removeAlphabeticDiacritics, @@ -275,7 +275,7 @@ const languageDescriptors = [ iso: 'ro', iso639_3: 'ron', name: 'Romanian', - exampleText: 'citit', + exampleText: 'citi', textPreprocessors: { ...capitalizationPreprocessors, removeAlphabeticDiacritics, @@ -307,7 +307,7 @@ const languageDescriptors = [ iso: 'sh', iso639_3: 'hbs', name: 'Serbo-Croatian', - exampleText: 'čitaše', + exampleText: 'čìtati', textPreprocessors: { ...capitalizationPreprocessors, removeSerboCroatianAccentMarks, @@ -317,7 +317,7 @@ const languageDescriptors = [ iso: 'sq', iso639_3: 'sqi', name: 'Albanian', - exampleText: 'ndihmojme', + exampleText: 'ndihmoj', /* 'to help' */ textPreprocessors: capitalizationPreprocessors, languageTransforms: albanianTransforms, }, @@ -349,14 +349,14 @@ const languageDescriptors = [ iso: 'tr', iso639_3: 'tur', name: 'Turkish', - exampleText: 'okuyor', + exampleText: 'okumak', textPreprocessors: capitalizationPreprocessors, }, { iso: 'uk', iso639_3: 'ukr', name: 'Ukrainian', - exampleText: 'читаєте', + exampleText: 'читати', textPreprocessors: capitalizationPreprocessors, }, { From 4b1dfbe2d451adda21de571b8f940f4bb26433f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 11:28:18 -0700 Subject: [PATCH 16/33] Bump the minor group with 4 updates (#1554) Bumps the minor group with 4 updates: [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action), [github/codeql-action](https://github.com/github/codeql-action), [softprops/action-gh-release](https://github.com/softprops/action-gh-release) and [actions/dependency-review-action](https://github.com/actions/dependency-review-action). Updates `lycheeverse/lychee-action` from 2.0.0 to 2.0.2 - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v2.0.0...v2.0.2) Updates `github/codeql-action` from 3.26.12 to 3.27.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Commits](https://github.com/github/codeql-action/compare/v3.26.12...v3.27.0) Updates `softprops/action-gh-release` from 2.0.8 to 2.0.9 - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/c062e08bd532815e2082a85e87e3ef29c3e6d191...e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8) Updates `actions/dependency-review-action` from 4.3.4 to 4.4.0 - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/5a2ce3f5b92ee19cbb1541a4984c76d921601d7c...4081bf99e2866ebe428fc0477b69eb4fcda7220a) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor - dependency-name: softprops/action-gh-release dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/broken-links.yml | 2 +- .github/workflows/codeql.yml | 6 +++--- .github/workflows/create-prerelease-on-tag.yml | 2 +- .github/workflows/dependency-review.yml | 2 +- .github/workflows/scorecard.yml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/broken-links.yml b/.github/workflows/broken-links.yml index 4c3eeeb2da..8b8f1cfbd6 100644 --- a/.github/workflows/broken-links.yml +++ b/.github/workflows/broken-links.yml @@ -21,7 +21,7 @@ jobs: run: npm ci - name: Build Legal run: npm run license-report:html - - uses: lycheeverse/lychee-action@v2.0.0 + - uses: lycheeverse/lychee-action@v2.0.2 with: fail: true jobSummary: false diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index aab0563064..a8a2334d1c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -51,7 +51,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3.26.12 + uses: github/codeql-action/init@v3.27.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -65,7 +65,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3.26.12 + uses: github/codeql-action/autobuild@v3.27.0 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -78,6 +78,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3.26.12 + uses: github/codeql-action/analyze@v3.27.0 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/create-prerelease-on-tag.yml b/.github/workflows/create-prerelease-on-tag.yml index 753b3082d8..bedfa6dd56 100644 --- a/.github/workflows/create-prerelease-on-tag.yml +++ b/.github/workflows/create-prerelease-on-tag.yml @@ -40,7 +40,7 @@ jobs: - name: Release id: release - uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # pin@v2 + uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8 # pin@v2 with: generate_release_notes: true prerelease: true diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index bf2dcfbae9..5edc997f09 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -24,4 +24,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: 'Dependency Review' - uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4 + uses: actions/dependency-review-action@4081bf99e2866ebe428fc0477b69eb4fcda7220a # v4.4.0 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 22cb6238b8..4439f5de9e 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@572cc5268d94f11b89e12e7a166cf93275856072 # v2.22.12 + uses: github/codeql-action/upload-sarif@48c3e2675613624ea7978e5d132169f97bc3b578 # v2.22.12 with: sarif_file: results.sarif From 7c198d4a67dfec71e9edd1c11227c994a2a9b6c7 Mon Sep 17 00:00:00 2001 From: James Maa Date: Fri, 1 Nov 2024 11:35:56 -0700 Subject: [PATCH 17/33] =?UTF-8?q?Reapply=20"Support=20default=20setting=20?= =?UTF-8?q?overrides=20on=20different=20languages=20(#1=E2=80=A6=20(#1535)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reapply "Support default setting overrides on different languages (#1368)" (#1530) This reverts commit 019c45838a2669ac63d9f3cf1c9bf981b144e2e1. * Make recommended settings lazy-load --- .eslintrc.json | 19 + ext/data/recommended-settings.json | 406 ++++++++++++++++++ .../schemas/recommended-settings-schema.json | 180 ++++++++ .../recommended-settings-controller.js | 194 +++++++++ ext/js/pages/settings/settings-main.js | 3 + ext/js/pages/welcome-main.js | 4 + ext/templates-modals.html | 24 ++ ext/templates-settings.html | 17 + test/data/json.json | 11 + types/ext/settings-controller.d.ts | 10 + 10 files changed, 868 insertions(+) create mode 100644 ext/data/recommended-settings.json create mode 100644 ext/data/schemas/recommended-settings-schema.json create mode 100644 ext/js/pages/settings/recommended-settings-controller.js diff --git a/.eslintrc.json b/.eslintrc.json index faee1c51d0..64e8525893 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -694,6 +694,25 @@ } }] } + }, + { + "files": [ + "ext/data/recommended-settings.json" + ], + "rules": { + "jsonc/sort-keys": ["error", { + "pathPattern": ".*", + "order": [ + "modification", + "description" + ] + }, { + "pathPattern": ".*", + "order": { + "type": "asc" + } + }] + } } ] } diff --git a/ext/data/recommended-settings.json b/ext/data/recommended-settings.json new file mode 100644 index 0000000000..c492a2540a --- /dev/null +++ b/ext/data/recommended-settings.json @@ -0,0 +1,406 @@ +{ + "da": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "de": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "el": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "en": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "es": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "fi": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "fr": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + }, + { + "modification": { + "action": "set", + "path": "translation.textReplacements.groups", + "value": [ + [ + { + "pattern": "l'", + "ignoreCase": true, + "replacement": "" + }, + { + "pattern": "j'", + "ignoreCase": true, + "replacement": "" + }, + { + "pattern": "d'", + "ignoreCase": true, + "replacement": "" + } + ] + ] + }, + "description": "Separating the l', j', d' from the word." + } + ], + "hu": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "id": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "it": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "mn": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "nl": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "pl": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "pt": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "ro": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "ru": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "sh": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "sq": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "sv": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "tr": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "vi": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ] +} diff --git a/ext/data/schemas/recommended-settings-schema.json b/ext/data/schemas/recommended-settings-schema.json new file mode 100644 index 0000000000..2c8054244b --- /dev/null +++ b/ext/data/schemas/recommended-settings-schema.json @@ -0,0 +1,180 @@ +{ + "$id": "recommendedSetttings", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Contains data for recommended default options overrides by language.", + "type": "object", + "$defs": { + "path": { + "type": "string", + "minLength": 2 + }, + "value": { + "anyOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "object" + } + ] + }, + "description": { + "type": "string", + "minLength": 2 + } + }, + "patternProperties": { + "^.{2,}$": { + "title": "Language", + "type": "array", + "items": { + "title": "Modification", + "type": "object", + "oneOf": [ + { + "type": "object", + "title": "ModificationSet", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "set" + }, + "path": { + "$ref": "#/$defs/path" + }, + "value": { + "$ref": "#/$defs/value" + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + }, + { + "type": "object", + "title": "ModificationDelete", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "delete" + }, + "path": { + "$ref": "#/$defs/path" + }, + "value": { + "$ref": "#/$defs/value" + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + }, + { + "type": "object", + "title": "ModificationSwap", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "swap" + }, + "path1": { + "$ref": "#/$defs/path" + }, + "path2": { + "$ref": "#/$defs/path" + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + }, + { + "type": "object", + "title": "ModificationSplice", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "splice" + }, + "path": { + "$ref": "#/$defs/path" + }, + "start": { + "type": "number" + }, + "deleteCount": { + "type": "number" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/$defs/value" + } + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + }, + { + "type": "object", + "title": "ModificationPush", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "push" + }, + "path": { + "$ref": "#/$defs/path" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/$defs/value" + } + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + } + ] + } + } + } +} diff --git a/ext/js/pages/settings/recommended-settings-controller.js b/ext/js/pages/settings/recommended-settings-controller.js new file mode 100644 index 0000000000..6cc21e14ce --- /dev/null +++ b/ext/js/pages/settings/recommended-settings-controller.js @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2024 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import {fetchJson} from '../../core/fetch-utilities.js'; +import {log} from '../../core/log.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js'; + +export class RecommendedSettingsController { + /** + * @param {import('./settings-controller.js').SettingsController} settingsController + */ + constructor(settingsController) { + /** @type {import('./settings-controller.js').SettingsController} */ + this._settingsController = settingsController; + /** @type {HTMLElement} */ + this._recommendedSettingsModal = querySelectorNotNull(document, '#recommended-settings-modal'); + /** @type {HTMLInputElement} */ + this._languageSelect = querySelectorNotNull(document, '#language-select'); + /** @type {HTMLInputElement} */ + this._applyButton = querySelectorNotNull(document, '#recommended-settings-apply-button'); + /** @type {Map} */ + this._recommendedSettings = new Map(); + } + + /** */ + async prepare() { + this._languageSelect.addEventListener('change', this._onLanguageSelectChanged.bind(this), false); + this._applyButton.addEventListener('click', this._onApplyButtonClicked.bind(this), false); + } + + /** + * @param {Event} _e + */ + async _onLanguageSelectChanged(_e) { + const setLanguage = this._languageSelect.value; + if (typeof setLanguage !== 'string') { return; } + + const recommendedSettings = await this._getRecommendedSettings(setLanguage); + if (typeof recommendedSettings !== 'undefined') { + const settingsList = querySelectorNotNull(document, '#recommended-settings-list'); + settingsList.innerHTML = ''; + this._recommendedSettings = new Map(); + + for (const [index, setting] of recommendedSettings.entries()) { + this._recommendedSettings.set(index.toString(), setting); + + const {description} = setting; + const template = this._settingsController.instantiateTemplate('recommended-settings-list-item'); + + // Render label + this._renderLabel(template, setting); + + // Render description + const descriptionElement = querySelectorNotNull(template, '.settings-item-description'); + if (description !== 'undefined') { + descriptionElement.textContent = description; + } + + // Render checkbox + const checkbox = /** @type {HTMLInputElement} */ (querySelectorNotNull(template, 'input[type="checkbox"]')); + checkbox.value = index.toString(); + + settingsList.append(template); + } + this._recommendedSettingsModal.hidden = false; + } + } + + /** + * + * @param {string} language + * @returns {Promise} + */ + async _getRecommendedSettings(language) { + if (typeof this._recommendedSettingsByLanguage === 'undefined') { + /** @type {import('settings-controller').RecommendedSettingsByLanguage} */ + this._recommendedSettingsByLanguage = await fetchJson('/data/recommended-settings.json'); + } + + return this._recommendedSettingsByLanguage[language]; + } + + /** + * @param {MouseEvent} e + */ + _onApplyButtonClicked(e) { + e.preventDefault(); + /** @type {NodeListOf} */ + const enabledCheckboxes = querySelectorNotNull(document, '#recommended-settings-list').querySelectorAll('input[type="checkbox"]:checked'); + if (enabledCheckboxes.length > 0) { + const modifications = []; + for (const checkbox of enabledCheckboxes) { + const index = checkbox.value; + const setting = this._recommendedSettings.get(index); + if (typeof setting === 'undefined') { continue; } + modifications.push(setting.modification); + } + void this._settingsController.modifyProfileSettings(modifications).then( + (results) => { + results.map((result) => { + if (Object.hasOwn(result, 'error')) { + log.error(new Error(`Failed to apply recommended setting: ${JSON.stringify(result)}`)); + } + }); + }, + ); + void this._settingsController.refresh(); + } + this._recommendedSettingsModal.hidden = true; + } + + /** + * @param {Element} template + * @param {import('settings-controller').RecommendedSetting} setting + */ + _renderLabel(template, setting) { + const label = querySelectorNotNull(template, '.settings-item-label'); + + const {modification} = setting; + switch (modification.action) { + case 'set': { + const {path, value} = modification; + const pathCodeElement = document.createElement('code'); + pathCodeElement.textContent = path; + const valueCodeElement = document.createElement('code'); + valueCodeElement.textContent = JSON.stringify(value, null, 2); + + label.appendChild(document.createTextNode('Setting ')); + label.appendChild(pathCodeElement); + label.appendChild(document.createTextNode(' = ')); + label.appendChild(valueCodeElement); + break; + } + case 'delete': { + const {path} = modification; + const pathCodeElement = document.createElement('code'); + pathCodeElement.textContent = path; + + label.appendChild(document.createTextNode('Deleting ')); + label.appendChild(pathCodeElement); + break; + } + case 'swap': { + const {path1, path2} = modification; + const path1CodeElement = document.createElement('code'); + path1CodeElement.textContent = path1; + const path2CodeElement = document.createElement('code'); + path2CodeElement.textContent = path2; + + label.appendChild(document.createTextNode('Swapping ')); + label.appendChild(path1CodeElement); + label.appendChild(document.createTextNode(' and ')); + label.appendChild(path2CodeElement); + break; + } + case 'splice': { + const {path, start, deleteCount, items} = modification; + const pathCodeElement = document.createElement('code'); + pathCodeElement.textContent = path; + + label.appendChild(document.createTextNode('Splicing ')); + label.appendChild(pathCodeElement); + label.appendChild(document.createTextNode(` at ${start} deleting ${deleteCount} items and inserting ${items.length} items`)); + break; + } + case 'push': { + const {path, items} = modification; + const pathCodeElement = document.createElement('code'); + pathCodeElement.textContent = path; + + label.appendChild(document.createTextNode(`Pushing ${items.length} items to `)); + label.appendChild(pathCodeElement); + break; + } + default: { + log.error(new Error(`Unknown modification: ${modification}`)); + } + } + } +} diff --git a/ext/js/pages/settings/settings-main.js b/ext/js/pages/settings/settings-main.js index f070c03876..4a7925bf68 100644 --- a/ext/js/pages/settings/settings-main.js +++ b/ext/js/pages/settings/settings-main.js @@ -40,6 +40,7 @@ import {PersistentStorageController} from './persistent-storage-controller.js'; import {PopupPreviewController} from './popup-preview-controller.js'; import {PopupWindowController} from './popup-window-controller.js'; import {ProfileController} from './profile-controller.js'; +import {RecommendedSettingsController} from './recommended-settings-controller.js'; import {ScanInputsController} from './scan-inputs-controller.js'; import {ScanInputsSimpleController} from './scan-inputs-simple-controller.js'; import {SecondarySearchDictionaryController} from './secondary-search-dictionary-controller.js'; @@ -174,6 +175,8 @@ await Application.main(true, async (application) => { const sortFrequencyDictionaryController = new SortFrequencyDictionaryController(settingsController); preparePromises.push(sortFrequencyDictionaryController.prepare()); + const recommendedSettingsController = new RecommendedSettingsController(settingsController); + preparePromises.push(recommendedSettingsController.prepare()); await Promise.all(preparePromises); diff --git a/ext/js/pages/welcome-main.js b/ext/js/pages/welcome-main.js index ae00c0c637..c4d75ee8c3 100644 --- a/ext/js/pages/welcome-main.js +++ b/ext/js/pages/welcome-main.js @@ -26,6 +26,7 @@ import {GenericSettingController} from './settings/generic-setting-controller.js import {LanguagesController} from './settings/languages-controller.js'; import {ModalController} from './settings/modal-controller.js'; import {RecommendedPermissionsController} from './settings/recommended-permissions-controller.js'; +import {RecommendedSettingsController} from './settings/recommended-settings-controller.js'; import {ScanInputsSimpleController} from './settings/scan-inputs-simple-controller.js'; import {SettingsController} from './settings/settings-controller.js'; import {SettingsDisplayController} from './settings/settings-display-controller.js'; @@ -105,6 +106,9 @@ await Application.main(true, async (application) => { const languagesController = new LanguagesController(settingsController); preparePromises.push(languagesController.prepare()); + const recommendedSettingsController = new RecommendedSettingsController(settingsController); + preparePromises.push(recommendedSettingsController.prepare()); + await Promise.all(preparePromises); document.documentElement.dataset.loaded = 'true'; diff --git a/ext/templates-modals.html b/ext/templates-modals.html index 6f1a3a02b2..4b0a088ddb 100644 --- a/ext/templates-modals.html +++ b/ext/templates-modals.html @@ -335,6 +335,30 @@

Pronunciation Dictionaries

+ + + + + + diff --git a/test/data/json.json b/test/data/json.json index 9fdda0b86b..321bff6483 100644 --- a/test/data/json.json +++ b/test/data/json.json @@ -98,6 +98,11 @@ "typeFile": "types/test/json.d.ts", "type": "AjvSchema" }, + { + "path": "ext/data/schemas/recommended-settings-schema.json", + "typeFile": "types/test/json.d.ts", + "type": "AjvSchema" + }, { "path": "test/data/translator-test-inputs.json", "typeFile": "types/test/translator.d.ts", @@ -192,6 +197,12 @@ "typeFile": "types/ext/dictionary-recommended.d.ts", "type": "RecommendedDictionaries", "schema": "ext/data/schemas/recommended-dictionaries-schema.json" + }, + { + "path": "ext/data/recommended-settings.json", + "typeFile": "types/ext/settings-controller.d.ts", + "type": "RecommendedSettingsByLanguage", + "schema": "ext/data/schemas/recommended-settings-schema.json" } ] } diff --git a/types/ext/settings-controller.d.ts b/types/ext/settings-controller.d.ts index e12651ebea..bbb393fbb7 100644 --- a/types/ext/settings-controller.d.ts +++ b/types/ext/settings-controller.d.ts @@ -22,6 +22,7 @@ import type * as Core from './core'; import type * as Settings from './settings'; import type * as SettingsModifications from './settings-modifications'; import type {EventNames, EventArgument as BaseEventArgument} from './core'; +import type {Modification} from './settings-modifications'; export type PageExitPrevention = { end: () => void; @@ -57,3 +58,12 @@ export type SettingsModification = THasScope extends export type SettingsExtraFields = THasScope extends true ? null : SettingsModifications.OptionsScope; export type ModifyResult = Core.Response; + +export type RecommendedSetting = { + modification: Modification; + description: string; +}; + +export type RecommendedSettingsByLanguage = { + [key: string]: RecommendedSetting[]; +}; From 92bac4e73082fa5b524c5ad49e7b541529f8422d Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Sun, 3 Nov 2024 16:02:29 -0500 Subject: [PATCH 18/33] Add radical normalization preprocessor to Japanese, Chinese, and Cantonese (#1559) * Add radical normalization to japanese * Add radical normalization to chinese * Add CJK Strokes Range * Add to yue * Move normalizeRadicalCharacters to CJK-util * Fix tests --- .eslintrc.json | 1 + ext/js/language/CJK-util.js | 39 +++++++++++++++++++++++++ ext/js/language/language-descriptors.js | 8 +++++ types/ext/language-descriptors.d.ts | 13 +++++++-- 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 64e8525893..1eb1278c19 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -560,6 +560,7 @@ "ext/js/dom/css-style-applier.js", "ext/js/language/CJK-util.js", "ext/js/language/ja/japanese.js", + "ext/js/language/text-processors.js", "ext/js/language/text-utilities.js", "ext/js/templates/anki-template-renderer-content-manager.js", "ext/js/templates/anki-template-renderer.js", diff --git a/ext/js/language/CJK-util.js b/ext/js/language/CJK-util.js index 5c59afb58b..5c56468261 100644 --- a/ext/js/language/CJK-util.js +++ b/ext/js/language/CJK-util.js @@ -15,6 +15,8 @@ * along with this program. If not, see . */ +import {basicTextProcessorOptions} from './text-processors.js'; + /** @type {import('CJK-util').CodepointRange} */ const CJK_UNIFIED_IDEOGRAPHS_RANGE = [0x4e00, 0x9fff]; /** @type {import('CJK-util').CodepointRange} */ @@ -94,3 +96,40 @@ export function isCodePointInRanges(codePoint, ranges) { } return false; } + +/** @type {import('CJK-util').CodepointRange} */ +export const KANGXI_RADICALS_RANGE = [0x2f00, 0x2fdf]; + +/** @type {import('CJK-util').CodepointRange} */ +export const CJK_RADICALS_SUPPLEMENT_RANGE = [0x2e80, 0x2eff]; + +/** @type {import('CJK-util').CodepointRange} */ +export const CJK_STROKES_RANGE = [0x31c0, 0x31ef]; + +/** @type {import('CJK-util').CodepointRange[]} */ +export const CJK_RADICALS_RANGES = [ + KANGXI_RADICALS_RANGE, + CJK_RADICALS_SUPPLEMENT_RANGE, + CJK_STROKES_RANGE, +]; + +/** + * @param {string} text + * @returns {string} + */ +export function normalizeRadicals(text) { + let result = ''; + for (let i = 0; i < text.length; i++) { + const codePoint = text[i].codePointAt(0); + result += codePoint && (isCodePointInRanges(codePoint, CJK_RADICALS_RANGES)) ? text[i].normalize('NFKD') : text[i]; + } + return result; +} + +/** @type {import('language').TextProcessor} */ +export const normalizeRadicalCharacters = { + name: 'Normalize radical characters', + description: '⼀ → 一 (U+2F00 → U+4E00)', + options: basicTextProcessorOptions, + process: (str, setting) => (setting ? normalizeRadicals(str) : str), +}; diff --git a/ext/js/language/language-descriptors.js b/ext/js/language/language-descriptors.js index a07437b711..02e2f35e1a 100644 --- a/ext/js/language/language-descriptors.js +++ b/ext/js/language/language-descriptors.js @@ -16,6 +16,7 @@ */ import {removeArabicScriptDiacritics} from './ar/arabic-text-preprocessors.js'; +import {normalizeRadicalCharacters} from './CJK-util.js'; import {eszettPreprocessor} from './de/german-text-preprocessors.js'; import {germanTransforms} from './de/german-transforms.js'; import {englishTransforms} from './en/english-transforms.js'; @@ -212,6 +213,7 @@ const languageDescriptors = [ convertHalfWidthCharacters, alphabeticToHiragana, normalizeCombiningCharacters, + normalizeRadicalCharacters, alphanumericWidthVariants, convertHiraganaToKatakana, collapseEmphaticSequences, @@ -374,6 +376,9 @@ const languageDescriptors = [ iso639_3: 'yue', name: 'Cantonese', exampleText: '讀', + textPreprocessors: { + normalizeRadicalCharacters, + }, }, { iso: 'zh', @@ -382,6 +387,9 @@ const languageDescriptors = [ exampleText: '读', isTextLookupWorthy: isStringPartiallyChinese, readingNormalizer: normalizePinyin, + textPreprocessors: { + normalizeRadicalCharacters, + }, }, ]; diff --git a/types/ext/language-descriptors.d.ts b/types/ext/language-descriptors.d.ts index 62643e0c75..199ec4b587 100644 --- a/types/ext/language-descriptors.d.ts +++ b/types/ext/language-descriptors.d.ts @@ -135,6 +135,7 @@ type AllTextProcessors = { convertHalfWidthCharacters: TextProcessor; alphabeticToHiragana: TextProcessor; normalizeCombiningCharacters: TextProcessor; + normalizeRadicalCharacters: TextProcessor; alphanumericWidthVariants: BidirectionalConversionPreprocessor; convertHiraganaToKatakana: BidirectionalConversionPreprocessor; collapseEmphaticSequences: TextProcessor<[collapseEmphatic: boolean, collapseEmphaticFull: boolean]>; @@ -200,6 +201,14 @@ type AllTextProcessors = { normalizeDiacritics: TextProcessor<'old' | 'new' | 'off'>; }; }; - yue: Record; - zh: Record; + yue: { + pre: { + normalizeRadicalCharacters: TextProcessor; + }; + }; + zh: { + pre: { + normalizeRadicalCharacters: TextProcessor; + }; + }; }; From d09cd38d8fc57a2a747106209e1b50c7e5cee8b5 Mon Sep 17 00:00:00 2001 From: Khai Truong <56820749+khaitruong922@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:12:14 +0700 Subject: [PATCH 19/33] Update dictionary will preserve its settings (#1553) * Fix updating dictionaries resets their positioning * preserve settings when update dictionary * cleanup * cleanup pr * lint * Add profile id * Revert "Add profile id" This reverts commit b33bc945b483dcc219e3d1f2c9f96d4f408ca34f. * Reapply "Add profile id" This reverts commit 4d984d0a7a3ea30e055ed55616c15e01fe8390c8. * lint * don't need to handle id collision * update for loop syntax --- ext/js/data/options-util.js | 11 +++++ .../pages/settings/dictionary-controller.js | 19 +++++++- .../settings/dictionary-import-controller.js | 46 ++++++++++++++----- ext/js/pages/settings/profile-controller.js | 3 +- test/options-util.test.js | 3 +- types/ext/settings-controller.d.ts | 5 ++ types/ext/settings.d.ts | 1 + 7 files changed, 73 insertions(+), 15 deletions(-) diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index ad49ab7886..28aca32475 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -564,6 +564,7 @@ export class OptionsUtil { this._updateVersion50, this._updateVersion51, this._updateVersion52, + this._updateVersion53, ]; /* eslint-enable @typescript-eslint/unbound-method */ if (typeof targetVersion === 'number' && targetVersion < result.length) { @@ -1498,6 +1499,16 @@ export class OptionsUtil { } } + /** + * - Added profile id + * @type {import('options-util').UpdateFunction} + */ + async _updateVersion53(options) { + for (let i = 0; i < options.profiles.length; i++) { + options.profiles[i].id = `profile-${i}`; + } + } + /** * @param {string} url * @returns {Promise} diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index c45c01f148..a56720937a 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -1139,9 +1139,24 @@ export class DictionaryController { downloadUrl = downloadUrl ?? dictionaryInfo.downloadUrl; if (typeof downloadUrl !== 'string') { throw new Error('Attempted to update dictionary without download URL'); } - await this._deleteDictionary(dictionaryTitle); + const options = await this._settingsController.getOptionsFull(); + const {profiles} = options; + + /** @type {import('settings-controller.js').ProfilesDictionarySettings} */ + const profilesDictionarySettings = {}; + + for (const profile of profiles) { + const dictionaries = profile.options.dictionaries; + for (let i = 0; i < dictionaries.length; ++i) { + if (dictionaries[i].name === dictionaryTitle) { + profilesDictionarySettings[profile.id] = {...dictionaries[i], index: i}; + break; + } + } + } - this._settingsController.trigger('importDictionaryFromUrl', {url: downloadUrl}); + await this._deleteDictionary(dictionaryTitle); + this._settingsController.trigger('importDictionaryFromUrl', {url: downloadUrl, profilesDictionarySettings}); } /** diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index f2e0623550..ed358ff9b3 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -127,6 +127,7 @@ export class DictionaryImportController { const onProgress = importProgressTracker.onProgress.bind(importProgressTracker); await this._importDictionaries( this._generateFilesFromUrls([url], onProgress), + null, importProgressTracker, ); void this._recommendedDictionaryQueue.shift(); @@ -251,8 +252,8 @@ export class DictionaryImportController { /** * @param {import('settings-controller').EventArgument<'importDictionaryFromUrl'>} details */ - _onEventImportDictionaryFromUrl({url}) { - void this.importFilesFromURLs(url); + _onEventImportDictionaryFromUrl({url, profilesDictionarySettings}) { + void this.importFilesFromURLs(url, profilesDictionarySettings); } /** */ @@ -311,6 +312,7 @@ export class DictionaryImportController { const importProgressTracker = new ImportProgressTracker(this._getFileImportSteps(), fileArray.length); void this._importDictionaries( this._arrayToAsyncGenerator(fileArray), + null, importProgressTracker, ); } @@ -416,6 +418,7 @@ export class DictionaryImportController { node.value = ''; void this._importDictionaries( this._arrayToAsyncGenerator(files2), + null, new ImportProgressTracker(this._getFileImportSteps(), files2.length), ); } @@ -424,19 +427,21 @@ export class DictionaryImportController { async _onImportFromURL() { const text = this._importURLText.value.trim(); if (!text) { return; } - await this.importFilesFromURLs(text); + await this.importFilesFromURLs(text, null); } /** * @param {string} text + * @param {import('settings-controller').ProfilesDictionarySettings} profilesDictionarySettings */ - async importFilesFromURLs(text) { + async importFilesFromURLs(text, profilesDictionarySettings) { const urls = text.split('\n'); const importProgressTracker = new ImportProgressTracker(this._getUrlImportSteps(), urls.length); const onProgress = importProgressTracker.onProgress.bind(importProgressTracker); void this._importDictionaries( this._generateFilesFromUrls(urls, onProgress), + profilesDictionarySettings, importProgressTracker, ); } @@ -518,9 +523,10 @@ export class DictionaryImportController { /** * @param {AsyncGenerator} dictionaries + * @param {import('settings-controller').ProfilesDictionarySettings} profilesDictionarySettings * @param {ImportProgressTracker} importProgressTracker */ - async _importDictionaries(dictionaries, importProgressTracker) { + async _importDictionaries(dictionaries, profilesDictionarySettings, importProgressTracker) { if (this._modifying) { return; } const statusFooter = this._statusFooter; @@ -557,6 +563,7 @@ export class DictionaryImportController { ...errors, ...(await this._importDictionaryFromZip( file, + profilesDictionarySettings, importDetails, onProgress, ) ?? []), @@ -613,18 +620,19 @@ export class DictionaryImportController { /** * @param {File} file + * @param {import('settings-controller').ProfilesDictionarySettings} profilesDictionarySettings * @param {import('dictionary-importer').ImportDetails} importDetails * @param {import('dictionary-worker').ImportProgressCallback} onProgress * @returns {Promise} */ - async _importDictionaryFromZip(file, importDetails, onProgress) { + async _importDictionaryFromZip(file, profilesDictionarySettings, importDetails, onProgress) { const archiveContent = await this._readFile(file); const {result, errors} = await new DictionaryWorker().importDictionary(archiveContent, importDetails, onProgress); if (!result) { return errors; } - const errors2 = await this._addDictionarySettings(result); + const errors2 = await this._addDictionarySettings(result, profilesDictionarySettings); await this._settingsController.application.api.triggerDatabaseUpdated('dictionary', 'import'); @@ -638,9 +646,10 @@ export class DictionaryImportController { /** * @param {import('dictionary-importer').Summary} summary + * @param {import('settings-controller').ProfilesDictionarySettings} profilesDictionarySettings * @returns {Promise} */ - async _addDictionarySettings(summary) { + async _addDictionarySettings(summary, profilesDictionarySettings) { const {title, sequenced, styles} = summary; let optionsFull; // Workaround Firefox bug sometimes causing getOptionsFull to fail @@ -659,11 +668,26 @@ export class DictionaryImportController { const targets = []; const profileCount = optionsFull.profiles.length; for (let i = 0; i < profileCount; ++i) { - const {options} = optionsFull.profiles[i]; + const {options, id: profileId} = optionsFull.profiles[i]; const enabled = profileIndex === i; - const value = DictionaryController.createDefaultDictionarySettings(title, enabled, styles); + const defaultSettings = DictionaryController.createDefaultDictionarySettings(title, enabled, styles); const path1 = `profiles[${i}].options.dictionaries`; - targets.push({action: 'push', path: path1, items: [value]}); + + if (profilesDictionarySettings === null || typeof profilesDictionarySettings[profileId] === 'undefined') { + targets.push({action: 'push', path: path1, items: [defaultSettings]}); + } else { + const {index, ...currentSettings} = profilesDictionarySettings[profileId]; + targets.push({ + action: 'splice', + path: path1, + start: index, + items: [{ + ...currentSettings, + name: title, + }], + deleteCount: 0, + }); + } if (sequenced && options.general.mainDictionary === '') { const path2 = `profiles[${i}].options.general.mainDictionary`; diff --git a/ext/js/pages/settings/profile-controller.js b/ext/js/pages/settings/profile-controller.js index 558b45bedc..11f5a9ea00 100644 --- a/ext/js/pages/settings/profile-controller.js +++ b/ext/js/pages/settings/profile-controller.js @@ -17,7 +17,7 @@ */ import {EventListenerCollection} from '../../core/event-listener-collection.js'; -import {clone} from '../../core/utilities.js'; +import {clone, generateId} from '../../core/utilities.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {ProfileConditionsUI} from './profile-conditions-ui.js'; @@ -184,6 +184,7 @@ export class ProfileController { // Create new profile const newProfile = clone(profile); newProfile.name = this._createCopyName(profile.name, this._profiles, 100); + newProfile.id = generateId(16); // Update state const index = this._profiles.length; diff --git a/test/options-util.test.js b/test/options-util.test.js index d4ac27f663..3f37f8ee80 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -565,6 +565,7 @@ function createOptionsUpdatedTestData1() { return { profiles: [ { + id: 'profile-0', name: 'Default', options: createProfileOptionsUpdatedTestData1(), conditionGroups: [ @@ -644,7 +645,7 @@ function createOptionsUpdatedTestData1() { }, ], profileCurrent: 0, - version: 52, + version: 53, global: { database: { prefixWildcardsSupported: false, diff --git a/types/ext/settings-controller.d.ts b/types/ext/settings-controller.d.ts index bbb393fbb7..d87ca5d4a0 100644 --- a/types/ext/settings-controller.d.ts +++ b/types/ext/settings-controller.d.ts @@ -28,6 +28,10 @@ export type PageExitPrevention = { end: () => void; }; +type ProfileDictionarySettings = Settings.DictionaryOptions & {index: number}; + +export type ProfilesDictionarySettings = {[profileId: string]: ProfileDictionarySettings} | null; + export type Events = { optionsChanged: { options: Settings.ProfileOptions; @@ -42,6 +46,7 @@ export type Events = { }; importDictionaryFromUrl: { url: string; + profilesDictionarySettings: ProfilesDictionarySettings; }; dictionaryEnabled: Record; scanInputsChanged: { diff --git a/types/ext/settings.d.ts b/types/ext/settings.d.ts index ceffe1e00f..2d03f0f1c3 100644 --- a/types/ext/settings.d.ts +++ b/types/ext/settings.d.ts @@ -70,6 +70,7 @@ export type GlobalDatabaseOptions = { }; export type Profile = { + id: string; name: string; conditionGroups: ProfileConditionGroup[]; options: ProfileOptions; From 37ba526de4d696eddb7113aec8fc39b1163a3984 Mon Sep 17 00:00:00 2001 From: James Maa Date: Mon, 4 Nov 2024 09:35:08 -0800 Subject: [PATCH 20/33] Fix example text popup not updating correctly when scan resolution is set to `word` (#1527) * Add option to disallowExpandStartOffset * Types --- ext/js/app/frontend.js | 2 +- ext/js/dom/dom-text-scanner.js | 8 ++++---- ext/js/dom/text-source-range.js | 3 ++- ext/js/language/text-scanner.js | 16 +++++++++------- types/ext/text-scanner.d.ts | 2 +- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index 830f50fd14..e58828df13 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -224,7 +224,7 @@ export class Frontend { */ async setTextSource(textSource) { this._textScanner.setCurrentTextSource(null); - await this._textScanner.search(textSource); + await this._textScanner.search(textSource, null, false, true); } /** diff --git a/ext/js/dom/dom-text-scanner.js b/ext/js/dom/dom-text-scanner.js index 8171a37140..8413c79b0d 100644 --- a/ext/js/dom/dom-text-scanner.js +++ b/ext/js/dom/dom-text-scanner.js @@ -137,11 +137,11 @@ export class DOMTextScanner { if (nodeType === TEXT_NODE) { lastNode = node; - if (!( - forward ? + const shouldContinueScanning = forward ? this._seekTextNodeForward(/** @type {Text} */ (node), resetOffset) : - this._seekTextNodeBackward(/** @type {Text} */ (node), resetOffset) - )) { + this._seekTextNodeBackward(/** @type {Text} */ (node), resetOffset); + + if (!shouldContinueScanning) { // Length reached break; } diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js index 4450d32845..18f49cd733 100644 --- a/ext/js/dom/text-source-range.js +++ b/ext/js/dom/text-source-range.js @@ -166,7 +166,8 @@ export class TextSourceRange { */ setStartOffset(length, layoutAwareScan, stopAtWordBoundary = false) { if (this._disallowExpandSelection) { return 0; } - const state = new DOMTextScanner(this._range.startContainer, this._range.startOffset, !layoutAwareScan, layoutAwareScan, stopAtWordBoundary).seek(-length); + let state = new DOMTextScanner(this._range.startContainer, this._range.startOffset, !layoutAwareScan, layoutAwareScan, stopAtWordBoundary); + state = state.seek(-length); this._range.setStart(state.node, state.offset); this._rangeStartOffset = this._range.startOffset; this._content = state.content + this._content; diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index ea059be856..15e82162a4 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -425,12 +425,13 @@ export class TextScanner extends EventDispatcher { /** * @param {import('text-source').TextSource} textSource - * @param {import('text-scanner').InputInfoDetail} [inputDetail] - * @param {boolean} showEmpty + * @param {import('text-scanner').InputInfoDetail?} [inputDetail] + * @param {boolean} showEmpty shows a "No results found" popup if no results are found + * @param {boolean} disallowExpandStartOffset disallows expanding the start offset of the range */ - async search(textSource, inputDetail, showEmpty = false) { + async search(textSource, inputDetail, showEmpty = false, disallowExpandStartOffset = false) { const inputInfo = this._createInputInfo(null, 'script', 'script', true, [], [], inputDetail); - await this._search(textSource, this._searchTerms, this._searchKanji, inputInfo, showEmpty); + await this._search(textSource, this._searchTerms, this._searchKanji, inputInfo, showEmpty, disallowExpandStartOffset); } // Private @@ -455,8 +456,9 @@ export class TextScanner extends EventDispatcher { * @param {boolean} searchKanji * @param {import('text-scanner').InputInfo} inputInfo * @param {boolean} showEmpty shows a "No results found" popup if no results are found + * @param {boolean} disallowExpandStartOffset disallows expanding the start offset of the range */ - async _search(textSource, searchTerms, searchKanji, inputInfo, showEmpty = false) { + async _search(textSource, searchTerms, searchKanji, inputInfo, showEmpty = false, disallowExpandStartOffset = false) { try { const isAltText = textSource instanceof TextSourceElement; if (inputInfo.pointerType === 'touch') { @@ -479,7 +481,7 @@ export class TextScanner extends EventDispatcher { null ); - if (this._scanResolution === 'word') { + if (this._scanResolution === 'word' && !disallowExpandStartOffset) { // Move the start offset to the beginning of the word textSource.setStartOffset(this._scanLength, this._layoutAwareScan, true); } @@ -1497,7 +1499,7 @@ export class TextScanner extends EventDispatcher { * @param {boolean} passive * @param {import('input').Modifier[]} modifiers * @param {import('input').ModifierKey[]} modifierKeys - * @param {import('text-scanner').InputInfoDetail} [detail] + * @param {import('text-scanner').InputInfoDetail?} [detail] * @returns {import('text-scanner').InputInfo} */ _createInputInfo(input, pointerType, eventType, passive, modifiers, modifierKeys, detail) { diff --git a/types/ext/text-scanner.d.ts b/types/ext/text-scanner.d.ts index 859bfca270..54ab47838e 100644 --- a/types/ext/text-scanner.d.ts +++ b/types/ext/text-scanner.d.ts @@ -104,7 +104,7 @@ export type InputInfo = { passive: boolean; modifiers: Input.Modifier[]; modifierKeys: Input.ModifierKey[]; - detail: InputInfoDetail | undefined; + detail: InputInfoDetail | undefined | null; }; export type InputInfoDetail = { From afd0b677de777c7e060787c4e9f5e56ada59a0cd Mon Sep 17 00:00:00 2001 From: Khai Truong <56820749+khaitruong922@users.noreply.github.com> Date: Tue, 5 Nov 2024 01:33:01 +0700 Subject: [PATCH 21/33] Set editing profile index correctly when deleting profile (#1560) * handle editing profile index correctly when delete profile * lint * simplify index calculation * rename variables * Revert "rename variables" This reverts commit 4d81d24415703a732dee19a1e2dd525c288d190b. --- ext/js/pages/settings/profile-controller.js | 6 ++++-- ext/js/pages/settings/settings-controller.js | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ext/js/pages/settings/profile-controller.js b/ext/js/pages/settings/profile-controller.js index 11f5a9ea00..84dcb19ef5 100644 --- a/ext/js/pages/settings/profile-controller.js +++ b/ext/js/pages/settings/profile-controller.js @@ -259,8 +259,10 @@ export class ProfileController { this._updateProfileSelectOptions(); // Update profile index - if (settingsProfileIndex === profileIndex) { - this._settingsController.profileIndex = profileCurrentNew; + if (settingsProfileIndex >= profileIndex) { + this._settingsController.profileIndex = settingsProfileIndex - 1; + } else { + this._settingsController.refreshProfileIndex(); } // Modify settings diff --git a/ext/js/pages/settings/settings-controller.js b/ext/js/pages/settings/settings-controller.js index ee44f875f3..09057818f5 100644 --- a/ext/js/pages/settings/settings-controller.js +++ b/ext/js/pages/settings/settings-controller.js @@ -67,6 +67,11 @@ export class SettingsController extends EventDispatcher { this._setProfileIndex(value, true); } + /** */ + refreshProfileIndex() { + this._setProfileIndex(this._profileIndex, true); + } + /** @type {HtmlTemplateCollection} */ get templates() { return this._templates; From 093a2f370558ea576ddef9b99d5afa9e0386ee06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 12:09:47 -0800 Subject: [PATCH 22/33] Bump CodSpeedHQ/action from 2 to 3 (#1297) Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 2 to 3. - [Release notes](https://github.com/codspeedhq/action/releases) - [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codspeedhq/action/compare/v2...v3) --- updated-dependencies: - dependency-name: CodSpeedHQ/action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: marv --- .github/workflows/bench.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index b365d4567d..e8805d5f85 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -26,7 +26,7 @@ jobs: run: npm run build:libs - name: Run Benchmarks - uses: CodSpeedHQ/action@v2 + uses: CodSpeedHQ/action@v3 with: token: ${{ secrets.CODSPEED_TOKEN }} run: npm run bench From 37513b12c690735b0655f4ab01cbf1a1d450209e Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Mon, 4 Nov 2024 20:36:19 -0500 Subject: [PATCH 23/33] Restore searchOnClick for search page (#1565) --- ext/js/display/display.js | 2 + ext/js/display/query-parser.js | 1 + ext/js/language/text-scanner.js | 67 ++++++++++++++++++++++++++++++++- types/ext/text-scanner.d.ts | 2 + 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/ext/js/display/display.js b/ext/js/display/display.js index 14b943e22e..610f659396 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -2014,6 +2014,8 @@ export class Display extends EventDispatcher { getSearchContext: this._getSearchContext.bind(this), searchTerms: true, searchKanji: false, + searchOnClick: true, + searchOnClickOnly: true, textSourceGenerator: this._textSourceGenerator, }); this._contentTextScanner.includeSelector = '.click-scannable,.click-scannable *'; diff --git a/ext/js/display/query-parser.js b/ext/js/display/query-parser.js index 1221cca319..cf487a3d35 100644 --- a/ext/js/display/query-parser.js +++ b/ext/js/display/query-parser.js @@ -66,6 +66,7 @@ export class QueryParser extends EventDispatcher { getSearchContext, searchTerms: true, searchKanji: false, + searchOnClick: true, textSourceGenerator, }); /** @type {?(import('../language/ja/japanese-wanakana.js'))} */ diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index 15e82162a4..f76bdf5251 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -39,6 +39,8 @@ export class TextScanner extends EventDispatcher { ignorePoint = null, searchTerms = false, searchKanji = false, + searchOnClick = false, + searchOnClickOnly = false, textSourceGenerator, }) { super(); @@ -56,6 +58,10 @@ export class TextScanner extends EventDispatcher { this._searchTerms = searchTerms; /** @type {boolean} */ this._searchKanji = searchKanji; + /** @type {boolean} */ + this._searchOnClick = searchOnClick; + /** @type {boolean} */ + this._searchOnClickOnly = searchOnClickOnly; /** @type {import('../dom/text-source-generator').TextSourceGenerator} */ this._textSourceGenerator = textSourceGenerator; @@ -658,6 +664,7 @@ export class TextScanner extends EventDispatcher { switch (e.button) { case 0: // Primary + if (this._searchOnClick) { this._resetPreventNextClickScan(); } this._scanTimerClear(); this._triggerClear('mousedown'); break; @@ -691,9 +698,33 @@ export class TextScanner extends EventDispatcher { return false; } + if (this._searchOnClick) { + this._onSearchClick(e); + return; + } + this._onMouseMove(e); } + /** + * @param {MouseEvent} e + */ + _onSearchClick(e) { + const preventNextClickScan = this._preventNextClickScan; + this._preventNextClickScan = false; + if (this._preventNextClickScanTimer !== null) { + clearTimeout(this._preventNextClickScanTimer); + this._preventNextClickScanTimer = null; + } + + if (preventNextClickScan) { return; } + + const modifiers = getActiveModifiersAndButtons(e); + const modifierKeys = getActiveModifiers(e); + const inputInfo = this._createInputInfo(null, 'mouse', 'click', false, modifiers, modifierKeys); + void this._searchAt(e.clientX, e.clientY, inputInfo); + } + /** */ _onAuxClick() { this._preventNextContextMenu = false; @@ -1117,7 +1148,9 @@ export class TextScanner extends EventDispatcher { const capture = true; /** @type {import('event-listener-collection').AddEventListenerArgs[]} */ let eventListenerInfos; - if (this._arePointerEventsSupported()) { + if (this._searchOnClickOnly) { + eventListenerInfos = this._getMouseClickOnlyEventListeners(capture); + } else if (this._arePointerEventsSupported()) { eventListenerInfos = this._getPointerEventListeners(capture); } else { eventListenerInfos = [...this._getMouseEventListeners(capture)]; @@ -1128,6 +1161,9 @@ export class TextScanner extends EventDispatcher { eventListenerInfos.push(...this._getTouchEventListeners(capture)); } } + if (this._searchOnClick) { + eventListenerInfos.push(...this._getMouseClickOnlyEventListeners2(capture)); + } eventListenerInfos.push(this._getSelectionChangeCheckUserSelectionListener()); @@ -1194,6 +1230,35 @@ export class TextScanner extends EventDispatcher { ]; } + /** + * @param {boolean} capture + * @returns {import('event-listener-collection').AddEventListenerArgs[]} + */ + _getMouseClickOnlyEventListeners(capture) { + return [ + [this._node, 'click', this._onClick.bind(this), capture], + ]; + } + + /** + * @param {boolean} capture + * @returns {import('event-listener-collection').AddEventListenerArgs[]} + */ + _getMouseClickOnlyEventListeners2(capture) { + const {documentElement} = document; + /** @type {import('event-listener-collection').AddEventListenerArgs[]} */ + const entries = [ + [document, 'selectionchange', this._onSelectionChange.bind(this)], + ]; + if (documentElement !== null) { + entries.push([documentElement, 'mousedown', this._onSearchClickMouseDown.bind(this), capture]); + if (this._touchInputEnabled) { + entries.push([documentElement, 'touchstart', this._onSearchClickTouchStart.bind(this), {passive: true, capture}]); + } + } + return entries; + } + /** * @returns {import('event-listener-collection').AddEventListenerArgs} */ diff --git a/types/ext/text-scanner.d.ts b/types/ext/text-scanner.d.ts index 54ab47838e..dd733e371d 100644 --- a/types/ext/text-scanner.d.ts +++ b/types/ext/text-scanner.d.ts @@ -154,6 +154,8 @@ export type ConstructorDetails = { ignorePoint?: ((x: number, y: number) => Promise) | null; searchTerms?: boolean; searchKanji?: boolean; + searchOnClick?: boolean; + searchOnClickOnly?: boolean; textSourceGenerator: TextSourceGenerator; }; From dc174a9a7e69271f93f531b75cf240f6b24fd9e4 Mon Sep 17 00:00:00 2001 From: Khai Truong <56820749+khaitruong922@users.noreply.github.com> Date: Wed, 6 Nov 2024 01:38:56 +0700 Subject: [PATCH 24/33] Update dictionary will update alias if alias is the same as previous title (#1568) Update dictionary alias if it is the same as title --- ext/js/pages/settings/dictionary-import-controller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index ed358ff9b3..1b1b335136 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -676,7 +676,8 @@ export class DictionaryImportController { if (profilesDictionarySettings === null || typeof profilesDictionarySettings[profileId] === 'undefined') { targets.push({action: 'push', path: path1, items: [defaultSettings]}); } else { - const {index, ...currentSettings} = profilesDictionarySettings[profileId]; + const {index, alias, name, ...currentSettings} = profilesDictionarySettings[profileId]; + const newAlias = alias === name ? title : alias; targets.push({ action: 'splice', path: path1, @@ -684,6 +685,7 @@ export class DictionaryImportController { items: [{ ...currentSettings, name: title, + alias: newAlias, }], deleteCount: 0, }); From e7cfac47aa0bdcf617b8688de0d571d75ef2f547 Mon Sep 17 00:00:00 2001 From: James Maa Date: Tue, 5 Nov 2024 10:49:42 -0800 Subject: [PATCH 25/33] Add support page (#1562) * Add support page * Comments --- ext/info.html | 10 ++++---- ext/js/pages/support-main.js | 41 +++++++++++++++++++++++++++++++++ ext/support.html | 44 ++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 ext/js/pages/support-main.js create mode 100644 ext/support.html diff --git a/ext/info.html b/ext/info.html index d663a93bef..7c85b80be3 100644 --- a/ext/info.html +++ b/ext/info.html @@ -66,11 +66,13 @@
diff --git a/ext/js/pages/support-main.js b/ext/js/pages/support-main.js new file mode 100644 index 0000000000..5f35b7960f --- /dev/null +++ b/ext/js/pages/support-main.js @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import {ThemeController} from '../app/theme-controller.js'; +import {Application} from '../application.js'; +import {SettingsController} from './settings/settings-controller.js'; + +await Application.main(true, async (application) => { + const settingsController = new SettingsController(application); + await settingsController.prepare(); + + /** @type {ThemeController} */ + const themeController = new ThemeController(document.documentElement); + themeController.prepare(); + const optionsFull = await application.api.optionsGetFull(); + const {profiles, profileCurrent} = optionsFull; + const defaultProfile = (profileCurrent >= 0 && profileCurrent < profiles.length) ? profiles[profileCurrent] : null; + if (defaultProfile !== null) { + themeController.theme = defaultProfile.options.general.popupTheme; + themeController.siteOverride = true; + themeController.updateTheme(); + } + + document.body.hidden = false; + + document.documentElement.dataset.loaded = 'true'; +}); diff --git a/ext/support.html b/ext/support.html new file mode 100644 index 0000000000..00bcf3e819 --- /dev/null +++ b/ext/support.html @@ -0,0 +1,44 @@ + + + + + + Support Yomitan + + + + + + + + + + + + + + +
+
+
+ + + +

Support Yomitan ❤️

+ +

Here are some ways to support Yomitan:

+
+
+ +
+
+
+
+
+ + + From 3ad619139ebeeb7aa697653ceb44a9d9ce8009d5 Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:20:26 -0500 Subject: [PATCH 26/33] Force scan resolution "character" for ja, zh, yue, and ko (#1542) * Force scan resolution "character" for ja, zh, yue, ko * Split out visibility modifiers * Hide scan resolution setting for jp, zh, yue, ko * Remove unused visibility modifiers css from search-settings --------- Signed-off-by: Kuuuube <61125188+Kuuuube@users.noreply.github.com> --- ext/css/search-settings.css | 6 ------ ext/css/settings.css | 12 ------------ ext/css/visibility-modifiers.css | 15 +++++++++++++++ ext/js/language/text-scanner.js | 5 ++++- ext/settings.html | 3 ++- 5 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 ext/css/visibility-modifiers.css diff --git a/ext/css/search-settings.css b/ext/css/search-settings.css index b145745675..5693c5f2f7 100644 --- a/ext/css/search-settings.css +++ b/ext/css/search-settings.css @@ -384,12 +384,6 @@ button.icon-button.modal-header-button>.icon-button-inner>.icon { .settings-item-children.settings-item-children-group .settings-item-children { padding-left: 0; } -:root:not([data-advanced=true]) .advanced-only { - display: none; -} -:root:not([data-advanced=false]) .basic-only { - display: none; -} .settings-item.settings-item-button, a.settings-item.settings-item-button { cursor: pointer; diff --git a/ext/css/settings.css b/ext/css/settings.css index 7e6c4bc158..65f05dcf16 100644 --- a/ext/css/settings.css +++ b/ext/css/settings.css @@ -638,18 +638,6 @@ a.heading-link-light { .settings-item-children.settings-item-children-group .settings-item-children { padding-left: 0; } -:root:not([data-debug=true]) .debug-only { - display: none; -} -:root:not([data-advanced=true]) .advanced-only { - display: none; -} -:root:not([data-advanced=false]) .basic-only { - display: none; -} -:root:not([data-language=ja]):not([data-language=zh]):not([data-language=yue]) .jpzhyue-only { - display: none; -} .settings-item.settings-item-button, a.settings-item.settings-item-button { cursor: pointer; diff --git a/ext/css/visibility-modifiers.css b/ext/css/visibility-modifiers.css new file mode 100644 index 0000000000..f461b9c11f --- /dev/null +++ b/ext/css/visibility-modifiers.css @@ -0,0 +1,15 @@ +:root:not([data-debug=true]) .debug-only { + display: none; +} +:root:not([data-advanced=true]) .advanced-only { + display: none; +} +:root:not([data-advanced=false]) .basic-only { + display: none; +} +:root:not([data-language=ja]):not([data-language=zh]):not([data-language=yue]) .jpzhyue-only { + display: none; +} +:root:is([data-language=ja], [data-language=zh], [data-language=yue], [data-language=ko]) .not-jpzhyueko { + display: none; +} diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index f76bdf5251..4af6d00e93 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -24,6 +24,8 @@ import {clone} from '../core/utilities.js'; import {anyNodeMatchesSelector, everyNodeMatchesSelector, getActiveModifiers, getActiveModifiersAndButtons, isPointInSelection} from '../dom/document-util.js'; import {TextSourceElement} from '../dom/text-source-element.js'; +const SCAN_RESOLUTION_EXCLUDED_LANGUAGES = new Set(['ja', 'zh', 'yue', 'ko']); + /** * @augments EventDispatcher */ @@ -487,7 +489,8 @@ export class TextScanner extends EventDispatcher { null ); - if (this._scanResolution === 'word' && !disallowExpandStartOffset) { + if (this._scanResolution === 'word' && (!disallowExpandStartOffset || + (this._language === null || !SCAN_RESOLUTION_EXCLUDED_LANGUAGES.has(this._language)))) { // Move the start offset to the beginning of the word textSource.setStartOffset(this._scanLength, this._layoutAwareScan, true); } diff --git a/ext/settings.html b/ext/settings.html index cffffc8f38..99b39dc293 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -13,6 +13,7 @@ + @@ -380,7 +381,7 @@

Yomitan Settings

-
+
Scan resolution
From 7110544f29a77f2ccdcafb9d6e3cb77c6a904245 Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Tue, 5 Nov 2024 18:57:23 -0500 Subject: [PATCH 27/33] Fix "Import dictionaries from URLs" textarea not showing newlines on firefox (#1574) --- ext/css/settings.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/css/settings.css b/ext/css/settings.css index 65f05dcf16..52582d34ca 100644 --- a/ext/css/settings.css +++ b/ext/css/settings.css @@ -2414,7 +2414,7 @@ input[type=number].dictionary-priority { #dictionary-import-url-text { width: 100%; height: 4em; - white-space: nowrap; + text-wrap: nowrap; resize: none; } From c95b9388a650702278a5a864541d08f7d113162a Mon Sep 17 00:00:00 2001 From: Khai Truong <56820749+khaitruong922@users.noreply.github.com> Date: Thu, 7 Nov 2024 01:44:52 +0700 Subject: [PATCH 28/33] Allow queueing dictionary update & delete together (#1561) * dictionary update queue * naming * update conditions * lint * remove unneeded async await * allow queueing delete and update in 1 queue * await database update before running next task * remove cmt * naming * Rename var * move await logic to correct place * use deferPromise * move promise to _deleteDictionaryInternal * removeconsole.log * preserve update button after database update --- .../pages/settings/dictionary-controller.js | 140 +++++++++++++----- .../settings/dictionary-import-controller.js | 17 ++- types/ext/dictionary-controller.d.ts | 29 ++++ types/ext/settings-controller.d.ts | 3 + 4 files changed, 149 insertions(+), 40 deletions(-) create mode 100644 types/ext/dictionary-controller.d.ts diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index a56720937a..9e2d79e830 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -20,6 +20,7 @@ import * as ajvSchemas0 from '../../../lib/validate-schemas.js'; import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {readResponseJson} from '../../core/json.js'; import {log} from '../../core/log.js'; +import {deferPromise} from '../../core/utilities.js'; import {compareRevisions} from '../../dictionary/dictionary-data-util.js'; import {DictionaryWorker} from '../../dictionary/dictionary-worker.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; @@ -32,14 +33,17 @@ class DictionaryEntry { * @param {DocumentFragment} fragment * @param {number} index * @param {import('dictionary-importer').Summary} dictionaryInfo + * @param {string | null} updateDownloadUrl */ - constructor(dictionaryController, fragment, index, dictionaryInfo) { + constructor(dictionaryController, fragment, index, dictionaryInfo, updateDownloadUrl) { /** @type {DictionaryController} */ this._dictionaryController = dictionaryController; /** @type {number} */ this._index = index; /** @type {import('dictionary-importer').Summary} */ this._dictionaryInfo = dictionaryInfo; + /** @type {string | null} */ + this._updateDownloadUrl = updateDownloadUrl; /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); /** @type {?import('dictionary-database').DictionaryCountGroup} */ @@ -86,6 +90,7 @@ class DictionaryEntry { this._outdatedButton.hidden = (version >= 3); this._priorityInput.dataset.setting = `dictionaries[${index}].priority`; this._enabledCheckbox.dataset.setting = `dictionaries[${index}].enabled`; + this._showUpdatesAvailableButton(); this._eventListeners.addEventListener(this._enabledCheckbox, 'settingChanged', this._onEnabledChanged.bind(this), false); this._eventListeners.addEventListener(this._menuButton, 'menuOpen', this._onMenuOpen.bind(this), false); this._eventListeners.addEventListener(this._menuButton, 'menuClose', this._onMenuClose.bind(this), false); @@ -122,6 +127,11 @@ class DictionaryEntry { this._enabledCheckbox.checked = value; } + /** */ + hideUpdatesAvailableButton() { + this._updatesAvailable.hidden = true; + } + /** * @returns {Promise} */ @@ -147,13 +157,29 @@ class DictionaryEntry { const downloadUrl = latestDownloadUrl ?? currentDownloadUrl; - this._updatesAvailable.dataset.downloadUrl = downloadUrl; - this._updatesAvailable.hidden = false; + this._updateDownloadUrl = downloadUrl; + this._showUpdatesAvailableButton(); return true; } + /** + * @returns {string | null} + */ + get updateDownloadUrl() { + return this._updateDownloadUrl; + } + // Private + /** */ + _showUpdatesAvailableButton() { + if (this._updateDownloadUrl === null || this._dictionaryController.isDictionaryInTaskQueue(this.dictionaryTitle)) { + return; + } + this._updatesAvailable.dataset.downloadUrl = this._updateDownloadUrl; + this._updatesAvailable.hidden = false; + } + /** * @param {import('popup-menu').MenuOpenEvent} e */ @@ -161,7 +187,8 @@ class DictionaryEntry { const bodyNode = e.detail.menu.bodyNode; const count = this._dictionaryController.dictionaryOptionCount; this._setMenuActionEnabled(bodyNode, 'moveTo', count > 1); - this._setMenuActionEnabled(bodyNode, 'delete', !this._dictionaryController.isDictionaryInDeleteQueue(this.dictionaryTitle)); + const deleteDisabled = this._dictionaryController.isDictionaryInTaskQueue(this.dictionaryTitle); + this._setMenuActionEnabled(bodyNode, 'delete', !deleteDisabled); } /** @@ -504,10 +531,12 @@ export class DictionaryController { this._allCheckbox = querySelectorNotNull(document, '#all-dictionaries-enabled'); /** @type {?DictionaryExtraInfo} */ this._extraInfo = null; + /** @type {import('dictionary-controller.js').DictionaryTask[]} */ + this._dictionaryTaskQueue = []; /** @type {boolean} */ - this._isDeleting = false; - /** @type {string[]} */ - this._dictionaryDeleteQueue = []; + this._isTaskQueueRunning = false; + /** @type {(() => void) | null} */ + this._onDictionariesUpdate = null; } /** @type {import('./modal-controller.js').ModalController} */ @@ -738,6 +767,10 @@ export class DictionaryController { this._dictionaries = dictionaries; await this._updateEntries(); + + if (this._onDictionariesUpdate) { + this._onDictionariesUpdate(); + } } /** */ @@ -754,7 +787,10 @@ export class DictionaryController { if (dictionaries === null) { return; } this._updateMainDictionarySelectOptions(dictionaries); + /** @type {Map} */ + const dictionaryUpdateDownloadUrlMap = new Map(); for (const entry of this._dictionaryEntries) { + dictionaryUpdateDownloadUrlMap.set(entry.dictionaryTitle, entry.updateDownloadUrl); entry.cleanup(); } this._dictionaryEntries = []; @@ -784,8 +820,9 @@ export class DictionaryController { for (let i = 0, ii = dictionaryOptionsArray.length; i < ii; ++i) { const {name} = dictionaryOptionsArray[i]; const dictionaryInfo = dictionaryInfoMap.get(name); + const updateDownloadUrl = dictionaryUpdateDownloadUrlMap.get(name) ?? null; if (typeof dictionaryInfo === 'undefined') { continue; } - this._createDictionaryEntry(i, dictionaryInfo); + this._createDictionaryEntry(i, dictionaryInfo, updateDownloadUrl); } } @@ -842,11 +879,12 @@ export class DictionaryController { const modal = /** @type {import('./modal.js').Modal} */ (this._deleteDictionaryModal); modal.setVisible(false); - const title = modal.node.dataset.dictionaryTitle; - if (typeof title !== 'string') { return; } + const dictionaryTitle = modal.node.dataset.dictionaryTitle; + if (typeof dictionaryTitle !== 'string') { return; } delete modal.node.dataset.dictionaryTitle; - void this._enqueueDictionaryDelete(title); + void this._enqueueTask({type: 'delete', dictionaryTitle}); + this._hideUpdatesAvailableButton(dictionaryTitle); } /** @@ -858,12 +896,25 @@ export class DictionaryController { const modal = /** @type {import('./modal.js').Modal} */ (this._updateDictionaryModal); modal.setVisible(false); - const title = modal.node.dataset.dictionaryTitle; + const dictionaryTitle = modal.node.dataset.dictionaryTitle; const downloadUrl = modal.node.dataset.downloadUrl; - if (typeof title !== 'string') { return; } + if (typeof dictionaryTitle !== 'string') { return; } delete modal.node.dataset.dictionaryTitle; - void this._updateDictionary(title, downloadUrl); + void this._enqueueTask({type: 'update', dictionaryTitle, downloadUrl}); + this._hideUpdatesAvailableButton(dictionaryTitle); + } + + /** + * @param {string} dictionaryTitle + */ + _hideUpdatesAvailableButton(dictionaryTitle) { + for (const entry of this._dictionaryEntries) { + if (entry.dictionaryTitle === dictionaryTitle) { + entry.hideUpdatesAvailableButton(); + break; + } + } } /** @@ -954,7 +1005,7 @@ export class DictionaryController { /** */ async _checkForUpdates() { - if (this._dictionaries === null || this._checkingIntegrity || this._checkingUpdates || this._isDeleting) { return; } + if (this._dictionaries === null || this._checkingIntegrity || this._checkingUpdates || this._isTaskQueueRunning) { return; } let hasUpdates; try { this._checkingUpdates = true; @@ -977,7 +1028,7 @@ export class DictionaryController { /** */ async _checkIntegrity() { - if (this._dictionaries === null || this._checkingIntegrity || this._checkingUpdates || this._isDeleting) { return; } + if (this._dictionaries === null || this._checkingIntegrity || this._checkingUpdates || this._isTaskQueueRunning) { return; } try { this._checkingIntegrity = true; @@ -1033,11 +1084,12 @@ export class DictionaryController { /** * @param {number} index * @param {import('dictionary-importer').Summary} dictionaryInfo + * @param {string|null} updateDownloadUrl */ - _createDictionaryEntry(index, dictionaryInfo) { + _createDictionaryEntry(index, dictionaryInfo, updateDownloadUrl) { const fragment = this.instantiateTemplateFragment('dictionary'); - const entry = new DictionaryEntry(this, fragment, index, dictionaryInfo); + const entry = new DictionaryEntry(this, fragment, index, dictionaryInfo, updateDownloadUrl); this._dictionaryEntries.push(entry); entry.prepare(); @@ -1053,30 +1105,41 @@ export class DictionaryController { * @param {string} dictionaryTitle * @returns {boolean} */ - isDictionaryInDeleteQueue(dictionaryTitle) { - return this._dictionaryDeleteQueue.includes(dictionaryTitle); + isDictionaryInTaskQueue(dictionaryTitle) { + return this._dictionaryTaskQueue.some((task) => task.dictionaryTitle === dictionaryTitle); } /** - * @param {string} dictionaryTitle + * @param {import('dictionary-controller.js').DictionaryTask} task */ - async _enqueueDictionaryDelete(dictionaryTitle) { - if (this.isDictionaryInDeleteQueue(dictionaryTitle)) { return; } - this._dictionaryDeleteQueue.push(dictionaryTitle); - if (this._isDeleting) { return; } - while (this._dictionaryDeleteQueue.length > 0) { - const title = this._dictionaryDeleteQueue[0]; - if (!title) { continue; } - await this._deleteDictionary(title); - void this._dictionaryDeleteQueue.shift(); + _enqueueTask(task) { + if (this.isDictionaryInTaskQueue(task.dictionaryTitle)) { return; } + this._dictionaryTaskQueue.push(task); + void this._runTaskQueue(); + } + + + /** */ + async _runTaskQueue() { + if (this._isTaskQueueRunning) { return; } + this._isTaskQueueRunning = true; + while (this._dictionaryTaskQueue.length > 0) { + const task = this._dictionaryTaskQueue[0]; + if (task.type === 'delete') { + await this._deleteDictionary(task.dictionaryTitle); + } else if (task.type === 'update') { + await this._updateDictionary(task.dictionaryTitle, task.downloadUrl); + } + void this._dictionaryTaskQueue.shift(); } + this._isTaskQueueRunning = false; } /** * @param {string} dictionaryTitle */ async _deleteDictionary(dictionaryTitle) { - if (this._isDeleting || this._checkingIntegrity) { return; } + if (this._checkingIntegrity) { return; } const index = this._dictionaryEntries.findIndex((entry) => entry.dictionaryTitle === dictionaryTitle); if (index < 0) { return; } @@ -1089,7 +1152,6 @@ export class DictionaryController { const statusLabels = /** @type {NodeListOf} */ (document.querySelectorAll(`${progressSelector} .progress-status`)); const prevention = this._settingsController.preventPageExit(); try { - this._isDeleting = true; this._setButtonsEnabled(false); /** @@ -1115,6 +1177,7 @@ export class DictionaryController { await this._deleteDictionaryInternal(dictionaryTitle, onProgress); await this._deleteDictionarySettings(dictionaryTitle); + this._onDictionariesUpdate = null; } catch (e) { log.error(e); } finally { @@ -1122,7 +1185,6 @@ export class DictionaryController { for (const progress of progressContainers) { progress.hidden = true; } if (statusFooter !== null) { statusFooter.setTaskActive(progressSelector, false); } this._setButtonsEnabled(true); - this._isDeleting = false; this._triggerStorageChanged(); } } @@ -1132,7 +1194,7 @@ export class DictionaryController { * @param {string|undefined} downloadUrl */ async _updateDictionary(dictionaryTitle, downloadUrl) { - if (this._checkingIntegrity || this._checkingUpdates || this._isDeleting || this._dictionaries === null) { return; } + if (this._checkingIntegrity || this._checkingUpdates || this._dictionaries === null) { return; } const dictionaryInfo = this._dictionaries.find((entry) => entry.title === dictionaryTitle); if (typeof dictionaryInfo === 'undefined') { throw new Error('Dictionary not found'); } @@ -1156,7 +1218,10 @@ export class DictionaryController { } await this._deleteDictionary(dictionaryTitle); - this._settingsController.trigger('importDictionaryFromUrl', {url: downloadUrl, profilesDictionarySettings}); + /** @type {import('core').DeferredPromiseDetails} */ + const {promise: importPromise, resolve} = deferPromise(); + this._settingsController.trigger('importDictionaryFromUrl', {url: downloadUrl, profilesDictionarySettings, onImportDone: resolve}); + await importPromise; } /** @@ -1175,7 +1240,12 @@ export class DictionaryController { */ async _deleteDictionaryInternal(dictionaryTitle, onProgress) { await new DictionaryWorker().deleteDictionary(dictionaryTitle, onProgress); + /** @type {import('core').DeferredPromiseDetails} */ + const {promise: dictionariesUpdatePromise, resolve} = deferPromise(); + this._onDictionariesUpdate = resolve; void this._settingsController.application.api.triggerDatabaseUpdated('dictionary', 'delete'); + await dictionariesUpdatePromise; + this._onDictionariesUpdate = null; } /** diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index 1b1b335136..d8e803f019 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -128,6 +128,7 @@ export class DictionaryImportController { await this._importDictionaries( this._generateFilesFromUrls([url], onProgress), null, + null, importProgressTracker, ); void this._recommendedDictionaryQueue.shift(); @@ -252,8 +253,8 @@ export class DictionaryImportController { /** * @param {import('settings-controller').EventArgument<'importDictionaryFromUrl'>} details */ - _onEventImportDictionaryFromUrl({url, profilesDictionarySettings}) { - void this.importFilesFromURLs(url, profilesDictionarySettings); + _onEventImportDictionaryFromUrl({url, profilesDictionarySettings, onImportDone}) { + void this.importFilesFromURLs(url, profilesDictionarySettings, onImportDone); } /** */ @@ -313,6 +314,7 @@ export class DictionaryImportController { void this._importDictionaries( this._arrayToAsyncGenerator(fileArray), null, + null, importProgressTracker, ); } @@ -419,6 +421,7 @@ export class DictionaryImportController { void this._importDictionaries( this._arrayToAsyncGenerator(files2), null, + null, new ImportProgressTracker(this._getFileImportSteps(), files2.length), ); } @@ -427,14 +430,15 @@ export class DictionaryImportController { async _onImportFromURL() { const text = this._importURLText.value.trim(); if (!text) { return; } - await this.importFilesFromURLs(text, null); + await this.importFilesFromURLs(text, null, null); } /** * @param {string} text * @param {import('settings-controller').ProfilesDictionarySettings} profilesDictionarySettings + * @param {import('settings-controller').ImportDictionaryDoneCallback} onImportDone */ - async importFilesFromURLs(text, profilesDictionarySettings) { + async importFilesFromURLs(text, profilesDictionarySettings, onImportDone) { const urls = text.split('\n'); const importProgressTracker = new ImportProgressTracker(this._getUrlImportSteps(), urls.length); @@ -442,6 +446,7 @@ export class DictionaryImportController { void this._importDictionaries( this._generateFilesFromUrls(urls, onProgress), profilesDictionarySettings, + onImportDone, importProgressTracker, ); } @@ -524,9 +529,10 @@ export class DictionaryImportController { /** * @param {AsyncGenerator} dictionaries * @param {import('settings-controller').ProfilesDictionarySettings} profilesDictionarySettings + * @param {import('settings-controller').ImportDictionaryDoneCallback} onImportDone * @param {ImportProgressTracker} importProgressTracker */ - async _importDictionaries(dictionaries, profilesDictionarySettings, importProgressTracker) { + async _importDictionaries(dictionaries, profilesDictionarySettings, onImportDone, importProgressTracker) { if (this._modifying) { return; } const statusFooter = this._statusFooter; @@ -578,6 +584,7 @@ export class DictionaryImportController { if (statusFooter !== null) { statusFooter.setTaskActive(progressSelector, false); } this._setModifying(false); this._triggerStorageChanged(); + if (onImportDone) { onImportDone(); } } } diff --git a/types/ext/dictionary-controller.d.ts b/types/ext/dictionary-controller.d.ts new file mode 100644 index 0000000000..a024a7dd19 --- /dev/null +++ b/types/ext/dictionary-controller.d.ts @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023-2024 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +type DictionaryDeleteTask = { + type: 'delete'; + dictionaryTitle: string; +}; + +type DictionaryUpdateTask = { + type: 'update'; + dictionaryTitle: string; + downloadUrl: string | undefined; +}; + +export type DictionaryTask = DictionaryDeleteTask | DictionaryUpdateTask; diff --git a/types/ext/settings-controller.d.ts b/types/ext/settings-controller.d.ts index d87ca5d4a0..6bbea19c10 100644 --- a/types/ext/settings-controller.d.ts +++ b/types/ext/settings-controller.d.ts @@ -32,6 +32,8 @@ type ProfileDictionarySettings = Settings.DictionaryOptions & {index: number}; export type ProfilesDictionarySettings = {[profileId: string]: ProfileDictionarySettings} | null; +export type ImportDictionaryDoneCallback = (() => void) | null; + export type Events = { optionsChanged: { options: Settings.ProfileOptions; @@ -47,6 +49,7 @@ export type Events = { importDictionaryFromUrl: { url: string; profilesDictionarySettings: ProfilesDictionarySettings; + onImportDone: ImportDictionaryDoneCallback; }; dictionaryEnabled: Record; scanInputsChanged: { From adaf0665217fe6dc1b60699baa5b831de704b05b Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:18:00 -0500 Subject: [PATCH 29/33] Show Anki card flags (#1571) * Add support for cardsInfo from ankiconnect api * Add cardsInfo to NoteInfo * Normalize cardInfo data * Add cardsInfo into notesInfo with a single extra request * Add flag button * Populate flag data in button * Add flag notification on click * Update option text * Add flag icon * Set flag names * Add color to flags * Fix gradient direction * Fix bad space * Remove no flag from flagnames * Allow flagsIndicatorIcon to be null * Clean up variable naming * Clarify behavior on tags and flags setting info * Use array for gradient slices instead of string to avoid weird comma * Rename displayTags to displayTagsAndFlags * Add description to _normalizeCardInfoArray --- ext/css/material.css | 1 + ext/data/schemas/options-schema.json | 4 +- ext/images/flag.svg | 1 + ext/js/background/backend.js | 23 ++++- ext/js/comm/anki-connect.js | 51 ++++++++++ ext/js/data/options-util.js | 12 +++ ext/js/display/display-anki.js | 141 +++++++++++++++++++++++++-- ext/settings.html | 10 +- ext/templates-display.html | 3 + test/options-util.test.js | 4 +- types/ext/anki.d.ts | 7 ++ types/ext/display-anki.d.ts | 6 ++ types/ext/settings.d.ts | 4 +- 13 files changed, 247 insertions(+), 20 deletions(-) create mode 100644 ext/images/flag.svg diff --git a/ext/css/material.css b/ext/css/material.css index 9013365f6c..2854ab3d1b 100644 --- a/ext/css/material.css +++ b/ext/css/material.css @@ -278,6 +278,7 @@ body { .icon[data-icon=clipboard] { --icon-image: url(/images/clipboard.svg); } .icon[data-icon=key] { --icon-image: url(/images/key.svg); } .icon[data-icon=tag] { --icon-image: url(/images/tag.svg); } +.icon[data-icon=flag] { --icon-image: url(/images/flag.svg); } .icon[data-icon=accessibility] { --icon-image: url(/images/accessibility.svg); } .icon[data-icon=connection] { --icon-image: url(/images/connection.svg); } .icon[data-icon=external-link] { --icon-image: url(/images/external-link.svg); } diff --git a/ext/data/schemas/options-schema.json b/ext/data/schemas/options-schema.json index acbee8eeec..26d921cd50 100644 --- a/ext/data/schemas/options-schema.json +++ b/ext/data/schemas/options-schema.json @@ -927,7 +927,7 @@ "checkForDuplicates", "fieldTemplates", "suspendNewCards", - "displayTags", + "displayTagsAndFlags", "noteGuiMode", "apiKey", "downloadTimeout" @@ -1046,7 +1046,7 @@ "type": "boolean", "default": false }, - "displayTags": { + "displayTagsAndFlags": { "type": "string", "enum": ["never", "always", "non-standard"], "default": "never" diff --git a/ext/images/flag.svg b/ext/images/flag.svg new file mode 100644 index 0000000000..940da218bc --- /dev/null +++ b/ext/images/flag.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index ceb736c40b..e8925ebab4 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -613,7 +613,7 @@ export class Backend { } const noteIds = isDuplicate ? duplicateNoteIds[originalIndices.indexOf(i)] : null; - const noteInfos = (fetchAdditionalInfo && noteIds !== null && noteIds.length > 0) ? await this._anki.notesInfo(noteIds) : []; + const noteInfos = (fetchAdditionalInfo && noteIds !== null && noteIds.length > 0) ? await this._notesCardsInfo(noteIds) : []; const info = { canAdd: valid, @@ -628,6 +628,27 @@ export class Backend { return results; } + /** + * @param {number[]} noteIds + * @returns {Promise<(?import('anki').NoteInfo)[]>} + */ + async _notesCardsInfo(noteIds) { + const notesInfo = await this._anki.notesInfo(noteIds); + /** @type {number[]} */ + // @ts-expect-error - ts is not smart enough to realize that filtering !!x removes null and undefined + const cardIds = notesInfo.flatMap((x) => x?.cards).filter((x) => !!x); + const cardsInfo = await this._anki.cardsInfo(cardIds); + for (let i = 0; i < notesInfo.length; i++) { + if (notesInfo[i] !== null) { + const cardInfo = cardsInfo.find((x) => x?.noteId === notesInfo[i]?.noteId); + if (cardInfo) { + notesInfo[i]?.cardsInfo.push(cardInfo); + } + } + } + return notesInfo; + } + /** @type {import('api').ApiHandler<'injectAnkiNoteMedia'>} */ async _onApiInjectAnkiNoteMedia({timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails}) { return await this._injectAnkNoteMedia( diff --git a/ext/js/comm/anki-connect.js b/ext/js/comm/anki-connect.js index 4baff406d5..468321a8c5 100644 --- a/ext/js/comm/anki-connect.js +++ b/ext/js/comm/anki-connect.js @@ -180,6 +180,17 @@ export class AnkiConnect { return this._normalizeNoteInfoArray(result); } + /** + * @param {import('anki').CardId[]} cardIds + * @returns {Promise<(?import('anki').CardInfo)[]>} + */ + async cardsInfo(cardIds) { + if (!this._enabled) { return []; } + await this._checkVersion(); + const result = await this._invoke('cardsInfo', {cards: cardIds}); + return this._normalizeCardInfoArray(result); + } + /** * @returns {Promise} */ @@ -655,6 +666,46 @@ export class AnkiConnect { fields: fields2, modelName, cards: cards2, + cardsInfo: [], + }; + result2.push(item2); + } + return result2; + } + + /** + * Transforms raw AnkiConnect data into the CardInfo type. + * @param {unknown} result + * @returns {(?import('anki').CardInfo)[]} + * @throws {Error} + */ + _normalizeCardInfoArray(result) { + if (!Array.isArray(result)) { + throw this._createUnexpectedResultError('array', result, ''); + } + /** @type {(?import('anki').CardInfo)[]} */ + const result2 = []; + for (let i = 0, ii = result.length; i < ii; ++i) { + const item = /** @type {unknown} */ (result[i]); + if (item === null || typeof item !== 'object') { + throw this._createError(`Unexpected result type at index ${i}: expected Cards.CardInfo, received ${this._getTypeName(item)}`, result); + } + const {cardId} = /** @type {{[key: string]: unknown}} */ (item); + if (typeof cardId !== 'number') { + result2.push(null); + continue; + } + const {note, flags} = /** @type {{[key: string]: unknown}} */ (item); + if (typeof note !== 'number') { + result2.push(null); + continue; + } + + /** @type {import('anki').CardInfo} */ + const item2 = { + noteId: note, + cardId, + flags: typeof flags === 'number' ? flags : 0, }; result2.push(item2); } diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index 28aca32475..98417e43e5 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -565,6 +565,7 @@ export class OptionsUtil { this._updateVersion51, this._updateVersion52, this._updateVersion53, + this._updateVersion54, ]; /* eslint-enable @typescript-eslint/unbound-method */ if (typeof targetVersion === 'number' && targetVersion < result.length) { @@ -1509,6 +1510,17 @@ export class OptionsUtil { } } + /** + * - Renamed anki.displayTags to anki.displayTagsAndFlags + * @type {import('options-util').UpdateFunction} + */ + async _updateVersion54(options) { + for (const profile of options.profiles) { + profile.options.anki.displayTagsAndFlags = profile.options.anki.displayTags; + delete profile.options.anki.displayTags; + } + } + /** * @param {string} url * @returns {Promise} diff --git a/ext/js/display/display-anki.js b/ext/js/display/display-anki.js index b3b05408e3..1ce12b09ab 100644 --- a/ext/js/display/display-anki.js +++ b/ext/js/display/display-anki.js @@ -49,6 +49,8 @@ export class DisplayAnki { this._errorNotificationEventListeners = null; /** @type {?import('./display-notification.js').DisplayNotification} */ this._tagsNotification = null; + /** @type {?import('./display-notification.js').DisplayNotification} */ + this._flagsNotification = null; /** @type {?Promise} */ this._updateSaveButtonsPromise = null; /** @type {?import('core').TokenObject} */ @@ -69,8 +71,8 @@ export class DisplayAnki { this._resultOutputMode = 'split'; /** @type {import('settings').GlossaryLayoutMode} */ this._glossaryLayoutMode = 'default'; - /** @type {import('settings').AnkiDisplayTags} */ - this._displayTags = 'never'; + /** @type {import('settings').AnkiDisplayTagsAndFlags} */ + this._displayTagsAndFlags = 'never'; /** @type {import('settings').AnkiDuplicateScope} */ this._duplicateScope = 'collection'; /** @type {boolean} */ @@ -103,6 +105,8 @@ export class DisplayAnki { /** @type {(event: MouseEvent) => void} */ this._onShowTagsBind = this._onShowTags.bind(this); /** @type {(event: MouseEvent) => void} */ + this._onShowFlagsBind = this._onShowFlags.bind(this); + /** @type {(event: MouseEvent) => void} */ this._onNoteSaveBind = this._onNoteSave.bind(this); /** @type {(event: MouseEvent) => void} */ this._onViewNotesButtonClickBind = this._onViewNotesButtonClick.bind(this); @@ -206,7 +210,7 @@ export class DisplayAnki { duplicateBehavior, suspendNewCards, checkForDuplicates, - displayTags, + displayTagsAndFlags, kanji, terms, noteGuiMode, @@ -221,7 +225,7 @@ export class DisplayAnki { this._compactTags = compactTags; this._resultOutputMode = resultOutputMode; this._glossaryLayoutMode = glossaryLayoutMode; - this._displayTags = displayTags; + this._displayTagsAndFlags = displayTagsAndFlags; this._duplicateScope = duplicateScope; this._duplicateScopeCheckAllModels = duplicateScopeCheckAllModels; this._duplicateBehavior = duplicateBehavior; @@ -260,6 +264,9 @@ export class DisplayAnki { for (const node of element.querySelectorAll('.action-button[data-action=view-tags]')) { eventListeners.addEventListener(node, 'click', this._onShowTagsBind); } + for (const node of element.querySelectorAll('.action-button[data-action=view-flags]')) { + eventListeners.addEventListener(node, 'click', this._onShowFlagsBind); + } for (const node of element.querySelectorAll('.action-button[data-action=save-note]')) { eventListeners.addEventListener(node, 'click', this._onNoteSaveBind); } @@ -304,6 +311,16 @@ export class DisplayAnki { this._showTagsNotification(tags); } + /** + * @param {MouseEvent} e + */ + _onShowFlags(e) { + e.preventDefault(); + const element = /** @type {HTMLElement} */ (e.currentTarget); + const flags = element.title; + this._showFlagsNotification(flags); + } + /** * @param {number} index * @param {import('display-anki').CreateMode} mode @@ -323,6 +340,15 @@ export class DisplayAnki { return entry !== null ? entry.querySelector('.action-button[data-action=view-tags]') : null; } + /** + * @param {number} index + * @returns {?HTMLButtonElement} + */ + _flagsIndicatorFind(index) { + const entry = this._getEntry(index); + return entry !== null ? entry.querySelector('.action-button[data-action=view-flags]') : null; + } + /** * @param {number} index * @returns {?HTMLElement} @@ -429,7 +455,7 @@ export class DisplayAnki { * @param {import('display-anki').DictionaryEntryDetails[]} dictionaryEntryDetails */ _updateSaveButtons(dictionaryEntryDetails) { - const displayTags = this._displayTags; + const displayTagsAndFlags = this._displayTagsAndFlags; for (let i = 0, ii = dictionaryEntryDetails.length; i < ii; ++i) { /** @type {?Set} */ let allNoteIds = null; @@ -457,8 +483,9 @@ export class DisplayAnki { } } - if (displayTags !== 'never' && Array.isArray(noteInfos)) { + if (displayTagsAndFlags !== 'never' && Array.isArray(noteInfos)) { this._setupTagsIndicator(i, noteInfos); + this._setupFlagsIndicator(i, noteInfos); } } @@ -483,7 +510,7 @@ export class DisplayAnki { displayTags.add(tag); } } - if (this._displayTags === 'non-standard') { + if (this._displayTagsAndFlags === 'non-standard') { for (const tag of this._noteTags) { displayTags.delete(tag); } @@ -508,6 +535,104 @@ export class DisplayAnki { this._tagsNotification.open(); } + /** + * @param {number} i + * @param {(?import('anki').NoteInfo)[]} noteInfos + */ + _setupFlagsIndicator(i, noteInfos) { + const flagsIndicator = this._flagsIndicatorFind(i); + if (flagsIndicator === null) { + return; + } + + /** @type {Set} */ + const displayFlags = new Set(); + for (const item of noteInfos) { + if (item === null) { continue; } + for (const cardInfo of item.cardsInfo) { + if (cardInfo.flags !== 0) { + displayFlags.add(this._getFlagName(cardInfo.flags)); + } + } + } + + if (displayFlags.size > 0) { + flagsIndicator.disabled = false; + flagsIndicator.hidden = false; + flagsIndicator.title = `Card flags: ${[...displayFlags].join(', ')}`; + /** @type {HTMLElement | null} */ + const flagsIndicatorIcon = flagsIndicator.querySelector('.action-icon'); + if (flagsIndicatorIcon !== null && flagsIndicator instanceof HTMLElement) { + flagsIndicatorIcon.style.background = this._getFlagColor(displayFlags); + } + } + } + + /** + * @param {number} flag + * @returns {string} + */ + _getFlagName(flag) { + /** @type {Record} */ + const flagNamesDict = { + 1: 'Red', + 2: 'Orange', + 3: 'Green', + 4: 'Blue', + 5: 'Pink', + 6: 'Turquoise', + 7: 'Purple', + }; + if (flag in flagNamesDict) { + return flagNamesDict[flag]; + } + return ''; + } + + /** + * @param {Set} flags + * @returns {string} + */ + _getFlagColor(flags) { + /** @type {Record} */ + const flagColorsDict = { + Red: {red: 248, green: 113, blue: 113}, + Orange: {red: 253, green: 186, blue: 116}, + Green: {red: 134, green: 239, blue: 172}, + Blue: {red: 96, green: 165, blue: 250}, + Pink: {red: 240, green: 171, blue: 252}, + Turquoise: {red: 94, green: 234, blue: 212}, + Purple: {red: 192, green: 132, blue: 252}, + }; + + const gradientSliceSize = 100 / flags.size; + let currentGradientPercent = 0; + + const gradientSlices = []; + for (const flag of flags) { + const flagColor = flagColorsDict[flag]; + gradientSlices.push( + 'rgb(' + flagColor.red + ',' + flagColor.green + ',' + flagColor.blue + ') ' + currentGradientPercent + '%', + 'rgb(' + flagColor.red + ',' + flagColor.green + ',' + flagColor.blue + ') ' + (currentGradientPercent + gradientSliceSize) + '%', + ); + currentGradientPercent += gradientSliceSize; + } + + return 'linear-gradient(to right,' + gradientSlices.join(',') + ')'; + } + + /** + * @param {string} message + */ + _showFlagsNotification(message) { + if (this._flagsNotification === null) { + this._flagsNotification = this._display.createNotification(true); + } + + this._flagsNotification.setContent(message); + this._flagsNotification.open(); + } + /** * @param {import('display-anki').CreateMode} mode */ @@ -733,7 +858,7 @@ export class DisplayAnki { * @returns {Promise} */ async _getDictionaryEntryDetails(dictionaryEntries) { - const fetchAdditionalInfo = (this._displayTags !== 'never'); + const fetchAdditionalInfo = (this._displayTagsAndFlags !== 'never'); const notePromises = []; const noteTargets = []; diff --git a/ext/settings.html b/ext/settings.html index 99b39dc293..9c0e114fdf 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -1961,12 +1961,12 @@

Yomitan Settings

- Show card tags + Show card tags and flags (?)
- @@ -1975,10 +1975,10 @@

Yomitan Settings