diff options
Diffstat (limited to 'ext/js/data/options-util.js')
-rw-r--r-- | ext/js/data/options-util.js | 185 |
1 files changed, 165 insertions, 20 deletions
diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index 3858cb55..e8723f1f 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -22,20 +22,29 @@ import {JsonSchema} from './json-schema.js'; export class OptionsUtil { constructor() { + /** @type {?TemplatePatcher} */ this._templatePatcher = null; + /** @type {?JsonSchema} */ this._optionsSchema = null; } + /** */ async prepare() { - const schema = await this._fetchAsset('/data/schemas/options-schema.json', true); + const schema = /** @type {import('json-schema').Schema} */ (await this._fetchJson('/data/schemas/options-schema.json')); this._optionsSchema = new JsonSchema(schema); } - async update(options, targetVersion=null) { + /** + * @param {unknown} optionsInput + * @param {?number} [targetVersion] + * @returns {Promise<import('settings').Options>} + */ + async update(optionsInput, targetVersion=null) { // Invalid options - if (!isObject(options)) { - options = {}; - } + let options = /** @type {{[key: string]: unknown}} */ ( + typeof optionsInput === 'object' && optionsInput !== null && !Array.isArray(optionsInput) ? + optionsInput : {} + ); // Check for legacy options let defaultProfileOptions = {}; @@ -50,7 +59,7 @@ export class OptionsUtil { } // Remove invalid profiles - const profiles = options.profiles; + const profiles = /** @type {unknown[]} */ (options.profiles); for (let i = profiles.length - 1; i >= 0; --i) { if (!isObject(profiles[i])) { profiles.splice(i, 1); @@ -87,12 +96,12 @@ export class OptionsUtil { options = await this._applyUpdates(options, this._getVersionUpdates(targetVersion)); // Validation - options = this._optionsSchema.getValidValueOrDefault(options); - - // Result - return options; + return /** @type {import('settings').Options} */ (/** @type {JsonSchema} */ (this._optionsSchema).getValidValueOrDefault(options)); } + /** + * @returns {Promise<import('settings').Options>} + */ async load() { let options; try { @@ -121,6 +130,10 @@ export class OptionsUtil { return options; } + /** + * @param {import('settings').Options} options + * @returns {Promise<void>} + */ save(options) { return new Promise((resolve, reject) => { chrome.storage.local.set({options: JSON.stringify(options)}, () => { @@ -134,23 +147,36 @@ export class OptionsUtil { }); } + /** + * @returns {import('settings').Options} + */ getDefault() { - const optionsVersion = this._getVersionUpdates().length; - const options = this._optionsSchema.getValidValueOrDefault(); + const optionsVersion = this._getVersionUpdates(null).length; + const options = /** @type {import('settings').Options} */ (/** @type {JsonSchema} */ (this._optionsSchema).getValidValueOrDefault()); options.version = optionsVersion; return options; } + /** + * @param {import('settings').Options} options + * @returns {import('settings').Options} + */ createValidatingProxy(options) { - return this._optionsSchema.createProxy(options); + return /** @type {import('settings').Options} */ (/** @type {JsonSchema} */ (this._optionsSchema).createProxy(options)); } + /** + * @param {import('settings').Options} options + */ validate(options) { - return this._optionsSchema.validate(options); + /** @type {JsonSchema} */ (this._optionsSchema).validate(options); } // Legacy profile updating + /** + * @returns {(?import('options-util').LegacyUpdateFunction)[]} + */ _legacyProfileUpdateGetUpdates() { return [ null, @@ -242,6 +268,9 @@ export class OptionsUtil { ]; } + /** + * @returns {import('options-util').LegacyOptions} + */ _legacyProfileUpdateGetDefaults() { return { general: { @@ -341,9 +370,17 @@ export class OptionsUtil { }; } + /** + * @param {import('options-util').IntermediateOptions} options + * @returns {import('options-util').IntermediateOptions} + */ _legacyProfileUpdateAssignDefaults(options) { const defaults = this._legacyProfileUpdateGetDefaults(); + /** + * @param {import('options-util').IntermediateOptions} target + * @param {import('core').UnknownObject} source + */ const combine = (target, source) => { for (const key in source) { if (!Object.prototype.hasOwnProperty.call(target, key)) { @@ -362,6 +399,10 @@ export class OptionsUtil { return options; } + /** + * @param {import('options-util').IntermediateOptions} options + * @returns {import('options-util').IntermediateOptions} + */ _legacyProfileUpdateUpdateVersion(options) { const updates = this._legacyProfileUpdateGetUpdates(); this._legacyProfileUpdateAssignDefaults(options); @@ -384,6 +425,10 @@ export class OptionsUtil { // Private + /** + * @param {import('options-util').IntermediateOptions} options + * @param {string} modificationsUrl + */ async _applyAnkiFieldTemplatesPatch(options, modificationsUrl) { let patch = null; for (const {options: profileOptions} of options.profiles) { @@ -391,18 +436,22 @@ export class OptionsUtil { if (fieldTemplates === null) { continue; } if (patch === null) { - const content = await this._fetchAsset(modificationsUrl); + const content = await this._fetchText(modificationsUrl); if (this._templatePatcher === null) { this._templatePatcher = new TemplatePatcher(); } patch = this._templatePatcher.parsePatch(content); } - profileOptions.anki.fieldTemplates = this._templatePatcher.applyPatch(fieldTemplates, patch); + profileOptions.anki.fieldTemplates = /** @type {TemplatePatcher} */ (this._templatePatcher).applyPatch(fieldTemplates, patch); } } - async _fetchAsset(url, json=false) { + /** + * @param {string} url + * @returns {Promise<Response>} + */ + async _fetchGeneric(url) { url = chrome.runtime.getURL(url); const response = await fetch(url, { method: 'GET', @@ -415,9 +464,31 @@ export class OptionsUtil { if (!response.ok) { throw new Error(`Failed to fetch ${url}: ${response.status}`); } - return await (json ? response.json() : response.text()); + return response; + } + + /** + * @param {string} url + * @returns {Promise<string>} + */ + async _fetchText(url) { + const response = await this._fetchGeneric(url); + return await response.text(); + } + + /** + * @param {string} url + * @returns {Promise<unknown>} + */ + async _fetchJson(url) { + const response = await this._fetchGeneric(url); + return await response.json(); } + /** + * @param {string} string + * @returns {number} + */ _getStringHashCode(string) { let hashCode = 0; @@ -431,6 +502,11 @@ export class OptionsUtil { return hashCode; } + /** + * @param {import('options-util').IntermediateOptions} options + * @param {import('options-util').ModernUpdate[]} updates + * @returns {Promise<import('settings').Options>} + */ async _applyUpdates(options, updates) { const targetVersion = updates.length; let currentVersion = options.version; @@ -449,6 +525,10 @@ export class OptionsUtil { return options; } + /** + * @param {?number} targetVersion + * @returns {import('options-util').ModernUpdate[]} + */ _getVersionUpdates(targetVersion) { const result = [ {async: false, update: this._updateVersion1.bind(this)}, @@ -479,6 +559,9 @@ export class OptionsUtil { return result; } + /** + * @type {import('options-util').ModernUpdateFunctionSync} + */ _updateVersion1(options) { // Version 1 changes: // Added options.global.database.prefixWildcardsSupported = false. @@ -490,6 +573,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionSync} + */ _updateVersion2(options) { // Version 2 changes: // Legacy profile update process moved into this upgrade function. @@ -502,6 +588,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionAsync} + */ async _updateVersion3(options) { // Version 3 changes: // Pitch accent Anki field templates added. @@ -509,6 +598,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionAsync} + */ async _updateVersion4(options) { // Version 4 changes: // Options conditions converted to string representations. @@ -594,6 +686,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionSync} + */ _updateVersion5(options) { // Version 5 changes: // Removed legacy version number from profile options. @@ -603,6 +698,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionAsync} + */ async _updateVersion6(options) { // Version 6 changes: // Updated handlebars templates to include "conjugation" definition. @@ -625,6 +723,10 @@ export class OptionsUtil { return options; } + /** + * @param {string} templates + * @returns {string} + */ _updateVersion6AnkiTemplatesCompactTags(templates) { const rawPattern1 = '{{~#if definitionTags~}}<i>({{#each definitionTags}}{{name}}{{#unless @last}}, {{/unless}}{{/each}})</i> {{/if~}}'; const pattern1 = new RegExp(`((\r?\n)?[ \t]*)${escapeRegExp(rawPattern1)}`, 'g'); @@ -649,6 +751,9 @@ export class OptionsUtil { return templates; } + /** + * @type {import('options-util').ModernUpdateFunctionSync} + */ _updateVersion7(options) { // Version 7 changes: // Added general.maximumClipboardSearchLength. @@ -666,6 +771,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionAsync} + */ async _updateVersion8(options) { // Version 8 changes: // Added translation.textReplacements. @@ -755,6 +863,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionSync} + */ _updateVersion9(options) { // Version 9 changes: // Added general.frequencyDisplayMode. @@ -766,6 +877,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionAsync} + */ async _updateVersion10(options) { // Version 10 changes: // Removed global option useSettingsV2. @@ -803,6 +917,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionSync} + */ _updateVersion11(options) { // Version 11 changes: // Changed dictionaries to an array. @@ -827,6 +944,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionAsync} + */ async _updateVersion12(options) { // Version 12 changes: // Changed sentenceParsing.enableTerminationCharacters to sentenceParsing.terminationCharacterMode. @@ -841,7 +961,7 @@ export class OptionsUtil { delete sentenceParsing.enableTerminationCharacters; const {sources, customSourceUrl, customSourceType, textToSpeechVoice} = audio; - audio.sources = sources.map((type) => { + audio.sources = /** @type {string[]} */ (sources).map((type) => { switch (type) { case 'text-to-speech': case 'text-to-speech-reading': @@ -859,6 +979,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionAsync} + */ async _updateVersion13(options) { // Version 13 changes: // Handlebars templates updated to use formatGlossary. @@ -874,6 +997,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionSync} + */ _updateVersion14(options) { // Version 14 changes: // Added accessibility options. @@ -885,6 +1011,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionSync} + */ _updateVersion15(options) { // Version 15 changes: // Added general.sortFrequencyDictionary. @@ -896,6 +1025,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionSync} + */ _updateVersion16(options) { // Version 16 changes: // Added scanning.matchTypePrefix. @@ -905,12 +1037,16 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionSync} + */ _updateVersion17(options) { // Version 17 changes: // Added vertical sentence punctuation to terminationCharacters. const additions = ['︒', '︕', '︖', '︙']; for (const profile of options.profiles) { - const {terminationCharacters} = profile.options.sentenceParsing; + /** @type {import('settings').SentenceParsingTerminationCharacterOption[]} */ + const terminationCharacters = profile.options.sentenceParsing.terminationCharacters; const newAdditions = []; for (const character of additions) { if (terminationCharacters.findIndex((value) => (value.character1 === character && value.character2 === null)) < 0) { @@ -930,6 +1066,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionSync} + */ _updateVersion18(options) { // Version 18 changes: // general.popupTheme's 'default' value changed to 'light' @@ -952,6 +1091,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionSync} + */ _updateVersion19(options) { // Version 19 changes: // Added anki.noteGuiMode. @@ -979,6 +1121,9 @@ export class OptionsUtil { return options; } + /** + * @type {import('options-util').ModernUpdateFunctionSync} + */ _updateVersion20(options) { // Version 20 changes: // Added anki.downloadTimeout. |