diff options
Diffstat (limited to 'ext/js/pages')
33 files changed, 492 insertions, 451 deletions
| diff --git a/ext/js/pages/action-popup-main.js b/ext/js/pages/action-popup-main.js index 94b9b356..f8dd865f 100644 --- a/ext/js/pages/action-popup-main.js +++ b/ext/js/pages/action-popup-main.js @@ -17,6 +17,7 @@   */  import {PermissionsUtil} from '../data/permissions-util.js'; +import {querySelectorNotNull} from '../dom/query-selector.js';  import {HotkeyHelpController} from '../input/hotkey-help-controller.js';  import {yomitan} from '../yomitan.js'; @@ -57,7 +58,9 @@ export class DisplayController {              this._setupOptions(primaryProfile);          } -        /** @type {HTMLElement} */ (document.querySelector('.action-select-profile')).hidden = (profiles.length <= 1); +        /** @type {HTMLElement} */ +        const profileSelect = querySelectorNotNull(document, '.action-select-profile'); +        profileSelect.hidden = (profiles.length <= 1);          this._updateProfileSelect(profiles, profileCurrent); @@ -207,8 +210,10 @@ export class DisplayController {       * @param {number} profileCurrent       */      _updateProfileSelect(profiles, profileCurrent) { -        const select = /** @type {HTMLSelectElement} */ (document.querySelector('#profile-select')); -        const optionGroup = /** @type {HTMLElement} */ (document.querySelector('#profile-select-option-group')); +        /** @type {HTMLSelectElement} */ +        const select = querySelectorNotNull(document, '#profile-select'); +        /** @type {HTMLElement} */ +        const optionGroup = querySelectorNotNull(document, '#profile-select-option-group');          const fragment = document.createDocumentFragment();          for (let i = 0, ii = profiles.length; i < ii; ++i) {              const {name} = profiles[i]; diff --git a/ext/js/pages/info-main.js b/ext/js/pages/info-main.js index f71d64c3..7445354f 100644 --- a/ext/js/pages/info-main.js +++ b/ext/js/pages/info-main.js @@ -18,6 +18,7 @@  import {log, promiseTimeout} from '../core.js';  import {DocumentFocusController} from '../dom/document-focus-controller.js'; +import {querySelectorNotNull} from '../dom/query-selector.js';  import {yomitan} from '../yomitan.js';  import {BackupController} from './settings/backup-controller.js';  import {SettingsController} from './settings/settings-controller.js'; @@ -69,15 +70,27 @@ function getOperatingSystemDisplayName(os) {          const {name, version} = manifest;          const {browser, platform: {os}} = await yomitan.api.getEnvironmentInfo(); -        const thisVersionLink = /** @type {HTMLLinkElement} */ (document.querySelector('#release-notes-this-version-link')); +        /** @type {HTMLLinkElement} */ +        const thisVersionLink = querySelectorNotNull(document, '#release-notes-this-version-link');          const {hrefFormat} = thisVersionLink.dataset;          thisVersionLink.href = typeof hrefFormat === 'string' ? hrefFormat.replace(/\{version\}/g, version) : ''; -        /** @type {HTMLElement} */ (document.querySelector('#version')).textContent = `${name} ${version}`; -        /** @type {HTMLElement} */ (document.querySelector('#browser')).textContent = getBrowserDisplayName(browser); -        /** @type {HTMLElement} */ (document.querySelector('#platform')).textContent = getOperatingSystemDisplayName(os); -        /** @type {HTMLElement} */ (document.querySelector('#language')).textContent = `${language}`; -        /** @type {HTMLElement} */ (document.querySelector('#user-agent')).textContent = userAgent; +        /** @type {HTMLElement} */ +        const versionElement = querySelectorNotNull(document, '#version'); +        /** @type {HTMLElement} */ +        const browserElement = querySelectorNotNull(document, '#browser'); +        /** @type {HTMLElement} */ +        const platformElement = querySelectorNotNull(document, '#platform'); +        /** @type {HTMLElement} */ +        const languageElement = querySelectorNotNull(document, '#language'); +        /** @type {HTMLElement} */ +        const userAgentElement = querySelectorNotNull(document, '#user-agent'); + +        versionElement.textContent = `${name} ${version}`; +        browserElement.textContent = getBrowserDisplayName(browser); +        platformElement.textContent = getOperatingSystemDisplayName(os); +        languageElement.textContent = `${language}`; +        userAgentElement.textContent = userAgent;          (async () => {              let ankiConnectVersion = null; @@ -87,9 +100,16 @@ function getOperatingSystemDisplayName(os) {                  // NOP              } -            /** @type {HTMLElement} */ (document.querySelector('#anki-connect-version')).textContent = (ankiConnectVersion !== null ? `${ankiConnectVersion}` : 'Unknown'); -            /** @type {HTMLElement} */ (document.querySelector('#anki-connect-version-container')).dataset.hasError = `${ankiConnectVersion === null}`; -            /** @type {HTMLElement} */ (document.querySelector('#anki-connect-version-unknown-message')).hidden = (ankiConnectVersion !== null); +            /** @type {HTMLElement} */ +            const ankiVersionElement = querySelectorNotNull(document, '#anki-connect-version'); +            /** @type {HTMLElement} */ +            const ankiVersionContainerElement = querySelectorNotNull(document, '#anki-connect-version-container'); +            /** @type {HTMLElement} */ +            const ankiVersionUnknownElement = querySelectorNotNull(document, '#anki-connect-version-unknown-message'); + +            ankiVersionElement.textContent = (ankiConnectVersion !== null ? `${ankiConnectVersion}` : 'Unknown'); +            ankiVersionContainerElement.dataset.hasError = `${ankiConnectVersion === null}`; +            ankiVersionUnknownElement.hidden = (ankiConnectVersion !== null);          })();          (async () => { @@ -115,8 +135,12 @@ function getOperatingSystemDisplayName(os) {                  fragment.appendChild(node);              } -            /** @type {HTMLElement} */ (document.querySelector('#installed-dictionaries-none')).hidden = (dictionaryInfos.length !== 0); -            const container = /** @type {HTMLElement} */ (document.querySelector('#installed-dictionaries')); +            /** @type {HTMLElement} */ +            const noneElement = querySelectorNotNull(document, '#installed-dictionaries-none'); + +            noneElement.hidden = (dictionaryInfos.length !== 0); +            /** @type {HTMLElement} */ +            const container = querySelectorNotNull(document, '#installed-dictionaries');              container.textContent = '';              container.appendChild(fragment);          })(); diff --git a/ext/js/pages/permissions-main.js b/ext/js/pages/permissions-main.js index 064e9240..58dae310 100644 --- a/ext/js/pages/permissions-main.js +++ b/ext/js/pages/permissions-main.js @@ -18,6 +18,7 @@  import {log, promiseTimeout} from '../core.js';  import {DocumentFocusController} from '../dom/document-focus-controller.js'; +import {querySelectorNotNull} from '../dom/query-selector.js';  import {yomitan} from '../yomitan.js';  import {ExtensionContentController} from './common/extension-content-controller.js';  import {ModalController} from './settings/modal-controller.js'; @@ -99,11 +100,12 @@ function setupPermissionsToggles() {          setupEnvironmentInfo(); -        /** @type {[HTMLInputElement, HTMLInputElement]} */ -        const permissionsCheckboxes = [ -            /** @type {HTMLInputElement} */ (document.querySelector('#permission-checkbox-allow-in-private-windows')), -            /** @type {HTMLInputElement} */ (document.querySelector('#permission-checkbox-allow-file-url-access')) -        ]; +        /** @type {HTMLInputElement} */ +        const permissionCheckbox1 = querySelectorNotNull(document, '#permission-checkbox-allow-in-private-windows'); +        /** @type {HTMLInputElement} */ +        const permissionCheckbox2 = querySelectorNotNull(document, '#permission-checkbox-allow-file-url-access'); +        /** @type {HTMLInputElement[]} */ +        const permissionsCheckboxes = [permissionCheckbox1, permissionCheckbox2];          const permissions = await Promise.all([              isAllowedIncognitoAccess(), diff --git a/ext/js/pages/settings/anki-controller.js b/ext/js/pages/settings/anki-controller.js index 737cc04a..f470d9fa 100644 --- a/ext/js/pages/settings/anki-controller.js +++ b/ext/js/pages/settings/anki-controller.js @@ -20,6 +20,7 @@ import {AnkiConnect} from '../../comm/anki-connect.js';  import {EventListenerCollection, log} from '../../core.js';  import {ExtensionError} from '../../core/extension-error.js';  import {AnkiUtil} from '../../data/anki-util.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {SelectorObserver} from '../../dom/selector-observer.js';  import {ObjectPropertyAccessor} from '../../general/object-property-accessor.js';  import {yomitan} from '../../yomitan.js'; @@ -45,26 +46,27 @@ export class AnkiController {          this._stringComparer = new Intl.Collator(); // Locale does not matter          /** @type {?Promise<import('anki-controller').AnkiData>} */          this._getAnkiDataPromise = null; -        /** @type {?HTMLElement} */ -        this._ankiErrorContainer = null; -        /** @type {?HTMLElement} */ -        this._ankiErrorMessageNode = null; +        /** @type {HTMLElement} */ +        this._ankiErrorMessageNode = querySelectorNotNull(document, '#anki-error-message'); +        const ankiErrorMessageNodeDefaultContent = this._ankiErrorMessageNode.textContent;          /** @type {string} */ -        this._ankiErrorMessageNodeDefaultContent = ''; -        /** @type {?HTMLElement} */ -        this._ankiErrorMessageDetailsNode = null; -        /** @type {?HTMLElement} */ -        this._ankiErrorMessageDetailsContainer = null; -        /** @type {?HTMLElement} */ -        this._ankiErrorMessageDetailsToggle = null; -        /** @type {?HTMLElement} */ -        this._ankiErrorInvalidResponseInfo = null; -        /** @type {?HTMLElement} */ -        this._ankiCardPrimary = null; +        this._ankiErrorMessageNodeDefaultContent = typeof ankiErrorMessageNodeDefaultContent === 'string' ? ankiErrorMessageNodeDefaultContent : ''; +        /** @type {HTMLElement} */ +        this._ankiErrorMessageDetailsNode = querySelectorNotNull(document, '#anki-error-message-details'); +        /** @type {HTMLElement} */ +        this._ankiErrorMessageDetailsContainer = querySelectorNotNull(document, '#anki-error-message-details-container'); +        /** @type {HTMLElement} */ +        this._ankiErrorMessageDetailsToggle = querySelectorNotNull(document, '#anki-error-message-details-toggle'); +        /** @type {HTMLElement} */ +        this._ankiErrorInvalidResponseInfo = querySelectorNotNull(document, '#anki-error-invalid-response-info'); +        /** @type {HTMLElement} */ +        this._ankiCardPrimary = querySelectorNotNull(document, '#anki-card-primary');          /** @type {?Error} */          this._ankiError = null;          /** @type {?import('core').TokenObject} */          this._validateFieldsToken = null; +        /** @type {?HTMLInputElement} */ +        this._ankiEnableCheckbox = document.querySelector('[data-setting="anki.enable"]');      }      /** @type {import('./settings-controller.js').SettingsController} */ @@ -74,19 +76,11 @@ export class AnkiController {      /** */      async prepare() { -        this._ankiErrorContainer = /** @type {HTMLElement} */ (document.querySelector('#anki-error')); -        this._ankiErrorMessageNode = /** @type {HTMLElement} */ (document.querySelector('#anki-error-message')); -        const ankiErrorMessageNodeDefaultContent = this._ankiErrorMessageNode.textContent; -        this._ankiErrorMessageNodeDefaultContent = typeof ankiErrorMessageNodeDefaultContent === 'string' ? ankiErrorMessageNodeDefaultContent : ''; -        this._ankiErrorMessageDetailsNode = /** @type {HTMLElement} */ (document.querySelector('#anki-error-message-details')); -        this._ankiErrorMessageDetailsContainer = /** @type {HTMLElement} */ (document.querySelector('#anki-error-message-details-container')); -        this._ankiErrorMessageDetailsToggle = /** @type {HTMLElement} */ (document.querySelector('#anki-error-message-details-toggle')); -        this._ankiErrorInvalidResponseInfo = /** @type {HTMLElement} */ (document.querySelector('#anki-error-invalid-response-info')); -        this._ankiEnableCheckbox = /** @type {?HTMLInputElement} */ (document.querySelector('[data-setting="anki.enable"]')); -        this._ankiCardPrimary = /** @type {HTMLElement} */ (document.querySelector('#anki-card-primary')); -        const ankiApiKeyInput = /** @type {HTMLElement} */ (document.querySelector('#anki-api-key-input')); +        /** @type {HTMLElement} */ +        const ankiApiKeyInput = querySelectorNotNull(document, '#anki-api-key-input');          const ankiCardPrimaryTypeRadios = /** @type {NodeListOf<HTMLInputElement>} */ (document.querySelectorAll('input[type=radio][name=anki-card-primary-type]')); -        const ankiErrorLog = /** @type {HTMLElement} */ (document.querySelector('#anki-error-log')); +        /** @type {HTMLElement} */ +        const ankiErrorLog = querySelectorNotNull(document, '#anki-error-log');          this._setupFieldMenus(); @@ -439,9 +433,6 @@ export class AnkiController {      /** */      _hideAnkiError() {          const ankiErrorMessageNode = /** @type {HTMLElement} */ (this._ankiErrorMessageNode); -        if (this._ankiErrorContainer !== null) { -            this._ankiErrorContainer.hidden = true; -        }          /** @type {HTMLElement} */ (this._ankiErrorMessageDetailsContainer).hidden = true;          /** @type {HTMLElement} */ (this._ankiErrorMessageDetailsToggle).hidden = true;          /** @type {HTMLElement} */ (this._ankiErrorInvalidResponseInfo).hidden = true; @@ -472,9 +463,6 @@ export class AnkiController {          details += `${error.stack}`.trimRight();          /** @type {HTMLElement} */ (this._ankiErrorMessageDetailsNode).textContent = details; -        if (this._ankiErrorContainer !== null) { -            this._ankiErrorContainer.hidden = false; -        }          /** @type {HTMLElement} */ (this._ankiErrorMessageDetailsContainer).hidden = true;          /** @type {HTMLElement} */ (this._ankiErrorInvalidResponseInfo).hidden = (errorString.indexOf('Invalid response') < 0);          /** @type {HTMLElement} */ (this._ankiErrorMessageDetailsToggle).hidden = false; @@ -534,7 +522,8 @@ export class AnkiController {       * @param {?Error} error       */      _setAnkiNoteViewerStatus(visible, error) { -        const node = /** @type {HTMLElement} */ (document.querySelector('#test-anki-note-viewer-results')); +        /** @type {HTMLElement} */ +        const node = querySelectorNotNull(document, '#test-anki-note-viewer-results');          if (visible) {              const success = (error === null);              node.textContent = success ? 'Success!' : error.message; @@ -608,8 +597,12 @@ class AnkiCardController {          const cardOptions = this._getCardOptions(ankiOptions, this._cardType);          if (cardOptions === null) { return; }          const {deck, model, fields} = cardOptions; -        this._deckController.prepare(/** @type {HTMLSelectElement} */ (this._node.querySelector('.anki-card-deck')), deck); -        this._modelController.prepare(/** @type {HTMLSelectElement} */ (this._node.querySelector('.anki-card-model')), model); +        /** @type {HTMLSelectElement} */ +        const deckControllerSelect = querySelectorNotNull(this._node, '.anki-card-deck'); +        /** @type {HTMLSelectElement} */ +        const modelControllerSelect = querySelectorNotNull(this._node, '.anki-card-model'); +        this._deckController.prepare(deckControllerSelect, deck); +        this._modelController.prepare(modelControllerSelect, model);          this._fields = fields;          this._ankiCardFieldsContainer = this._node.querySelector('.anki-card-fields'); @@ -752,7 +745,8 @@ class AnkiCardController {      _setFieldMarker(element, marker) {          const container = element.closest('.anki-card-field-value-container');          if (container === null) { return; } -        const input = /** @type {HTMLInputElement} */ (container.querySelector('.anki-card-field-value')); +        /** @type {HTMLInputElement} */ +        const input = querySelectorNotNull(container, '.anki-card-field-value');          input.value = `{${marker}}`;          input.dispatchEvent(new Event('change'));      } @@ -780,15 +774,19 @@ class AnkiCardController {          for (const [fieldName, fieldValue] of Object.entries(this._fields)) {              const content = this._settingsController.instantiateTemplateFragment('anki-card-field'); -            const fieldNameContainerNode = /** @type {HTMLElement} */ (content.querySelector('.anki-card-field-name-container')); +            /** @type {HTMLElement} */ +            const fieldNameContainerNode = querySelectorNotNull(content, '.anki-card-field-name-container');              fieldNameContainerNode.dataset.index = `${index}`; -            const fieldNameNode = /** @type {HTMLElement} */ (content.querySelector('.anki-card-field-name')); +            /** @type {HTMLElement} */ +            const fieldNameNode = querySelectorNotNull(content, '.anki-card-field-name');              fieldNameNode.textContent = fieldName; -            const valueContainer = /** @type {HTMLElement} */ (content.querySelector('.anki-card-field-value-container')); +            /** @type {HTMLElement} */ +            const valueContainer = querySelectorNotNull(content, '.anki-card-field-value-container');              valueContainer.dataset.index = `${index}`; -            const inputField = /** @type {HTMLInputElement} */ (content.querySelector('.anki-card-field-value')); +            /** @type {HTMLInputElement} */ +            const inputField = querySelectorNotNull(content, '.anki-card-field-value');              inputField.value = fieldValue;              inputField.dataset.setting = ObjectPropertyAccessor.getPathString(['anki', this._cardType, 'fields', fieldName]);              this._validateFieldPermissions(inputField, index, false); @@ -798,7 +796,8 @@ class AnkiCardController {              this._fieldEventListeners.addEventListener(inputField, 'settingChanged', this._onFieldSettingChanged.bind(this, index), false);              this._validateField(inputField, index); -            const menuButton = /** @type {?HTMLElement} */ (content.querySelector('.anki-card-field-value-menu-button')); +            /** @type {?HTMLElement} */ +            const menuButton = content.querySelector('.anki-card-field-value-menu-button');              if (menuButton !== null) {                  if (typeof this._cardMenu !== 'undefined') {                      menuButton.dataset.menu = this._cardMenu; diff --git a/ext/js/pages/settings/anki-templates-controller.js b/ext/js/pages/settings/anki-templates-controller.js index 89848ef3..03fda9ca 100644 --- a/ext/js/pages/settings/anki-templates-controller.js +++ b/ext/js/pages/settings/anki-templates-controller.js @@ -18,6 +18,7 @@  import {ExtensionError} from '../../core/extension-error.js';  import {AnkiNoteBuilder} from '../../data/anki-note-builder.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {JapaneseUtil} from '../../language/sandbox/japanese-util.js';  import {yomitan} from '../../yomitan.js'; @@ -40,14 +41,16 @@ export class AnkiTemplatesController {          this._cachedDictionaryEntryText = null;          /** @type {?string} */          this._defaultFieldTemplates = null; -        /** @type {?HTMLTextAreaElement} */ -        this._fieldTemplatesTextarea = null; -        /** @type {?HTMLElement} */ -        this._compileResultInfo = null; -        /** @type {?HTMLInputElement} */ -        this._renderFieldInput = null; -        /** @type {?HTMLElement} */ -        this._renderResult = null; +        /** @type {HTMLTextAreaElement} */ +        this._fieldTemplatesTextarea = querySelectorNotNull(document, '#anki-card-templates-textarea'); +        /** @type {HTMLElement} */ +        this._compileResultInfo = querySelectorNotNull(document, '#anki-card-templates-compile-result'); +        /** @type {HTMLInputElement} */ +        this._renderFieldInput = querySelectorNotNull(document, '#anki-card-templates-test-field-input'); +        /** @type {HTMLInputElement} */ +        this._renderTextInput = querySelectorNotNull(document, '#anki-card-templates-test-text-input'); +        /** @type {HTMLElement} */ +        this._renderResult = querySelectorNotNull(document, '#anki-card-templates-render-result');          /** @type {?import('./modal.js').Modal} */          this._fieldTemplateResetModal = null;          /** @type {AnkiNoteBuilder} */ @@ -58,15 +61,14 @@ export class AnkiTemplatesController {      async prepare() {          this._defaultFieldTemplates = await yomitan.api.getDefaultAnkiFieldTemplates(); -        this._fieldTemplatesTextarea = /** @type {HTMLTextAreaElement} */ (document.querySelector('#anki-card-templates-textarea')); -        this._compileResultInfo = /** @type {HTMLElement} */ (document.querySelector('#anki-card-templates-compile-result')); -        this._renderFieldInput = /** @type {HTMLInputElement} */ (document.querySelector('#anki-card-templates-test-field-input')); -        this._renderTextInput = /** @type {HTMLInputElement} */ (document.querySelector('#anki-card-templates-test-text-input')); -        this._renderResult = /** @type {HTMLElement} */ (document.querySelector('#anki-card-templates-render-result')); -        const menuButton = /** @type {HTMLButtonElement} */ (document.querySelector('#anki-card-templates-test-field-menu-button')); -        const testRenderButton = /** @type {HTMLButtonElement} */ (document.querySelector('#anki-card-templates-test-render-button')); -        const resetButton = /** @type {HTMLButtonElement} */ (document.querySelector('#anki-card-templates-reset-button')); -        const resetConfirmButton = /** @type {HTMLButtonElement} */ (document.querySelector('#anki-card-templates-reset-button-confirm')); +        /** @type {HTMLButtonElement} */ +        const menuButton = querySelectorNotNull(document, '#anki-card-templates-test-field-menu-button'); +        /** @type {HTMLButtonElement} */ +        const testRenderButton = querySelectorNotNull(document, '#anki-card-templates-test-render-button'); +        /** @type {HTMLButtonElement} */ +        const resetButton = querySelectorNotNull(document, '#anki-card-templates-reset-button'); +        /** @type {HTMLButtonElement} */ +        const resetConfirmButton = querySelectorNotNull(document, '#anki-card-templates-reset-button-confirm');          this._fieldTemplateResetModal = this._modalController.getModal('anki-card-templates-reset');          this._fieldTemplatesTextarea.addEventListener('change', this._onChanged.bind(this), false); diff --git a/ext/js/pages/settings/audio-controller.js b/ext/js/pages/settings/audio-controller.js index 0a3f9454..af05ee70 100644 --- a/ext/js/pages/settings/audio-controller.js +++ b/ext/js/pages/settings/audio-controller.js @@ -17,6 +17,7 @@   */  import {EventDispatcher, EventListenerCollection} from '../../core.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {AudioSystem} from '../../media/audio-system.js';  /** @@ -35,14 +36,14 @@ export class AudioController extends EventDispatcher {          this._modalController = modalController;          /** @type {AudioSystem} */          this._audioSystem = new AudioSystem(); -        /** @type {?HTMLElement} */ -        this._audioSourceContainer = null; -        /** @type {?HTMLButtonElement} */ -        this._audioSourceAddButton = null; +        /** @type {HTMLElement} */ +        this._audioSourceContainer = querySelectorNotNull(document, '#audio-source-list'); +        /** @type {HTMLButtonElement} */ +        this._audioSourceAddButton = querySelectorNotNull(document, '#audio-source-add');          /** @type {AudioSourceEntry[]} */          this._audioSourceEntries = []; -        /** @type {?HTMLInputElement} */ -        this._voiceTestTextInput = null; +        /** @type {HTMLInputElement} */ +        this._voiceTestTextInput = querySelectorNotNull(document, '#text-to-speech-voice-test-text');          /** @type {import('audio-controller').VoiceInfo[]} */          this._voices = [];      } @@ -61,11 +62,9 @@ export class AudioController extends EventDispatcher {      async prepare() {          this._audioSystem.prepare(); -        this._voiceTestTextInput = /** @type {HTMLInputElement} */ (document.querySelector('#text-to-speech-voice-test-text')); -        this._audioSourceContainer = /** @type {HTMLElement} */ (document.querySelector('#audio-source-list')); -        this._audioSourceAddButton = /** @type {HTMLButtonElement} */ (document.querySelector('#audio-source-add'));          this._audioSourceContainer.textContent = ''; -        const testButton = /** @type {HTMLButtonElement} */ (document.querySelector('#text-to-speech-voice-test')); +        /** @type {HTMLButtonElement} */ +        const testButton = querySelectorNotNull(document, '#text-to-speech-voice-test');          this._audioSourceAddButton.addEventListener('click', this._onAddAudioSource.bind(this), false); @@ -270,12 +269,12 @@ class AudioSourceEntry {          this._node = node;          /** @type {EventListenerCollection} */          this._eventListeners = new EventListenerCollection(); -        /** @type {?HTMLSelectElement} */ -        this._typeSelect = null; -        /** @type {?HTMLInputElement} */ -        this._urlInput = null; -        /** @type {?HTMLSelectElement} */ -        this._voiceSelect = null; +        /** @type {HTMLSelectElement} */ +        this._typeSelect = querySelectorNotNull(this._node, '.audio-source-type-select'); +        /** @type {HTMLInputElement} */ +        this._urlInput = querySelectorNotNull(this._node, '.audio-source-parameter-container[data-field=url] .audio-source-parameter'); +        /** @type {HTMLSelectElement} */ +        this._voiceSelect = querySelectorNotNull(this._node, '.audio-source-parameter-container[data-field=voice] .audio-source-parameter');      }      /** @type {number} */ @@ -296,10 +295,8 @@ class AudioSourceEntry {      prepare() {          this._updateTypeParameter(); -        const menuButton = /** @type {HTMLButtonElement} */ (this._node.querySelector('.audio-source-menu-button')); -        this._typeSelect = /** @type {HTMLSelectElement} */ (this._node.querySelector('.audio-source-type-select')); -        this._urlInput = /** @type {HTMLInputElement} */ (this._node.querySelector('.audio-source-parameter-container[data-field=url] .audio-source-parameter')); -        this._voiceSelect = /** @type {HTMLSelectElement} */ (this._node.querySelector('.audio-source-parameter-container[data-field=voice] .audio-source-parameter')); +        /** @type {HTMLButtonElement} */ +        const menuButton = querySelectorNotNull(this._node, '.audio-source-menu-button');          this._typeSelect.value = this._type;          this._urlInput.value = this._url; @@ -389,7 +386,8 @@ class AudioSourceEntry {                  break;          } -        const helpNode = /** @type {?HTMLElement} */ (menu.bodyNode.querySelector('.popup-menu-item[data-menu-action=help]')); +        /** @type {?HTMLElement} */ +        const helpNode = menu.bodyNode.querySelector('.popup-menu-item[data-menu-action=help]');          if (helpNode !== null) {              helpNode.hidden = !hasHelp;          } diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 95433d1c..94f85416 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -20,6 +20,7 @@ import {Dexie} from '../../../lib/dexie.js';  import {isObject, log} from '../../core.js';  import {OptionsUtil} from '../../data/options-util.js';  import {ArrayBufferUtil} from '../../data/sandbox/array-buffer-util.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {yomitan} from '../../yomitan.js';  import {DictionaryController} from './dictionary-controller.js'; @@ -243,7 +244,8 @@ export class BackupController {       */      _showSettingsImportError(error) {          log.error(error); -        const element = /** @type {HTMLElement} */ (document.querySelector('#settings-import-error-message')); +        /** @type {HTMLElement} */ +        const element = querySelectorNotNull(document, '#settings-import-error-message');          element.textContent = `${error}`;          if (this._settingsImportErrorModal !== null) {              this._settingsImportErrorModal.setVisible(true); @@ -480,7 +482,8 @@ export class BackupController {      /** */      _onSettingsImportClick() { -        const element = /** @type {HTMLElement} */ (document.querySelector('#settings-import-file')); +        /** @type {HTMLElement} */ +        const element = querySelectorNotNull(document, '#settings-import-file');          element.click();      } @@ -538,7 +541,8 @@ export class BackupController {       * @param {boolean} [isWarning]       */      _databaseExportImportErrorMessage(message, isWarning=false) { -        const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report')); +        /** @type {HTMLElement} */ +        const errorMessageContainer = querySelectorNotNull(document, '#db-ops-error-report');          errorMessageContainer.style.display = 'block';          errorMessageContainer.textContent = message; @@ -557,7 +561,8 @@ export class BackupController {      _databaseExportProgressCallback({totalRows, completedRows, done}) {          // eslint-disable-next-line no-console          console.log(`Progress: ${completedRows} of ${totalRows} rows completed`); -        const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report')); +        /** @type {HTMLElement} */ +        const messageContainer = querySelectorNotNull(document, '#db-ops-progress-report');          messageContainer.style.display = 'block';          messageContainer.textContent = `Export Progress: ${completedRows} of ${totalRows} rows completed`; @@ -589,7 +594,8 @@ export class BackupController {              return;          } -        const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report')); +        /** @type {HTMLElement} */ +        const errorMessageContainer = querySelectorNotNull(document, '#db-ops-error-report');          errorMessageContainer.style.display = 'none';          const date = new Date(Date.now()); @@ -619,7 +625,8 @@ export class BackupController {      _databaseImportProgressCallback({totalRows, completedRows, done}) {          // eslint-disable-next-line no-console          console.log(`Progress: ${completedRows} of ${totalRows} rows completed`); -        const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report')); +        /** @type {HTMLElement} */ +        const messageContainer = querySelectorNotNull(document, '#db-ops-progress-report');          messageContainer.style.display = 'block';          messageContainer.style.color = '#4169e1';          messageContainer.textContent = `Import Progress: ${completedRows} of ${totalRows} rows completed`; @@ -645,7 +652,9 @@ export class BackupController {      /** */      _onSettingsImportDatabaseClick() { -        /** @type {HTMLElement} */ (document.querySelector('#settings-import-db')).click(); +        /** @type {HTMLElement} */ +        const element = querySelectorNotNull(document, '#settings-import-db'); +        element.click();      }      /** @@ -658,7 +667,8 @@ export class BackupController {              return;          } -        const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report')); +        /** @type {HTMLElement} */ +        const errorMessageContainer = querySelectorNotNull(document, '#db-ops-error-report');          errorMessageContainer.style.display = 'none';          const element = /** @type {HTMLInputElement} */ (e.currentTarget); @@ -675,7 +685,8 @@ export class BackupController {          } catch (error) {              // eslint-disable-next-line no-console              console.log(error); -            const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report')); +            /** @type {HTMLElement} */ +            const messageContainer = querySelectorNotNull(document, '#db-ops-progress-report');              messageContainer.style.color = 'red';              this._databaseExportImportErrorMessage('Encountered errors when importing. Please restart the browser and try again. If it continues to fail, reinstall Yomitan and import dictionaries one-by-one.');          } finally { diff --git a/ext/js/pages/settings/collapsible-dictionary-controller.js b/ext/js/pages/settings/collapsible-dictionary-controller.js index 355292ad..cff3ad20 100644 --- a/ext/js/pages/settings/collapsible-dictionary-controller.js +++ b/ext/js/pages/settings/collapsible-dictionary-controller.js @@ -17,6 +17,7 @@   */  import {EventListenerCollection} from '../../core.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {yomitan} from '../../yomitan.js';  export class CollapsibleDictionaryController { @@ -32,8 +33,8 @@ export class CollapsibleDictionaryController {          this._dictionaryInfoMap = new Map();          /** @type {EventListenerCollection} */          this._eventListeners = new EventListenerCollection(); -        /** @type {?HTMLElement} */ -        this._container = null; +        /** @type {HTMLElement} */ +        this._container = querySelectorNotNull(document, '#collapsible-dictionary-list');          /** @type {HTMLSelectElement[]} */          this._selects = [];          /** @type {?HTMLSelectElement} */ @@ -42,8 +43,6 @@ export class CollapsibleDictionaryController {      /** */      async prepare() { -        this._container = /** @type {HTMLElement} */ (document.querySelector('#collapsible-dictionary-list')); -          await this._onDatabaseUpdated();          yomitan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); @@ -150,13 +149,17 @@ export class CollapsibleDictionaryController {          const node = this._settingsController.instantiateTemplate('collapsible-dictionary-item');          fragment.appendChild(node); -        const nameNode = /** @type {HTMLElement} */ (node.querySelector('.dictionary-title')); +        /** @type {HTMLElement} */ +        const nameNode = querySelectorNotNull(node, '.dictionary-title');          nameNode.textContent = dictionary; -        const versionNode = /** @type {HTMLElement} */ (node.querySelector('.dictionary-version')); +        /** @type {HTMLElement} */ +        const versionNode = querySelectorNotNull(node, '.dictionary-version');          versionNode.textContent = version; -        return /** @type {HTMLSelectElement} */ (node.querySelector('.definitions-collapsible')); +        /** @type {HTMLSelectElement} */ +        const select = querySelectorNotNull(node, '.definitions-collapsible'); +        return select;      }      /** */ diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index de63b200..0d84ccbf 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -17,6 +17,7 @@   */  import {EventListenerCollection, log} from '../../core.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {DictionaryWorker} from '../../language/dictionary-worker.js';  import {yomitan} from '../../yomitan.js'; @@ -41,21 +42,21 @@ class DictionaryEntry {          /** @type {ChildNode[]} */          this._nodes = [...fragment.childNodes];          /** @type {HTMLInputElement} */ -        this._enabledCheckbox = /** @type {HTMLInputElement} */ (fragment.querySelector('.dictionary-enabled')); +        this._enabledCheckbox = querySelectorNotNull(fragment, '.dictionary-enabled');          /** @type {HTMLInputElement} */ -        this._priorityInput = /** @type {HTMLInputElement} */ (fragment.querySelector('.dictionary-priority')); +        this._priorityInput = querySelectorNotNull(fragment, '.dictionary-priority');          /** @type {HTMLButtonElement} */ -        this._menuButton = /** @type {HTMLButtonElement} */ (fragment.querySelector('.dictionary-menu-button')); +        this._menuButton = querySelectorNotNull(fragment, '.dictionary-menu-button');          /** @type {HTMLButtonElement} */ -        this._outdatedButton = /** @type {HTMLButtonElement} */ (fragment.querySelector('.dictionary-outdated-button')); +        this._outdatedButton = querySelectorNotNull(fragment, '.dictionary-outdated-button');          /** @type {HTMLButtonElement} */ -        this._integrityButton = /** @type {HTMLButtonElement} */ (fragment.querySelector('.dictionary-integrity-button')); +        this._integrityButton = querySelectorNotNull(fragment, '.dictionary-integrity-button');          /** @type {HTMLElement} */ -        this._titleNode = /** @type {HTMLElement} */ (fragment.querySelector('.dictionary-title')); +        this._titleNode = querySelectorNotNull(fragment, '.dictionary-title');          /** @type {HTMLElement} */ -        this._versionNode = /** @type {HTMLElement} */ (fragment.querySelector('.dictionary-version')); +        this._versionNode = querySelectorNotNull(fragment, '.dictionary-version');          /** @type {HTMLElement} */ -        this._titleContainer = /** @type {HTMLElement} */ (fragment.querySelector('.dictionary-item-title-container')); +        this._titleContainer = querySelectorNotNull(fragment, '.dictionary-item-title-container');      }      /** @type {string} */ @@ -168,12 +169,25 @@ class DictionaryEntry {          const modal = this._dictionaryController.modalController.getModal('dictionary-details');          if (modal === null) { return; } -        /** @type {HTMLElement} */ (modal.node.querySelector('.dictionary-title')).textContent = title; -        /** @type {HTMLElement} */ (modal.node.querySelector('.dictionary-version')).textContent = `rev.${revision}`; -        /** @type {HTMLElement} */ (modal.node.querySelector('.dictionary-outdated-notification')).hidden = (version >= 3); -        /** @type {HTMLElement} */ (modal.node.querySelector('.dictionary-counts')).textContent = this._counts !== null ? JSON.stringify(this._counts, null, 4) : ''; -        /** @type {HTMLInputElement} */ (modal.node.querySelector('.dictionary-prefix-wildcard-searches-supported')).checked = prefixWildcardsSupported; -        this._setupDetails(/** @type {HTMLElement} */ (modal.node.querySelector('.dictionary-details-table'))); +        /** @type {HTMLElement} */ +        const titleElement = querySelectorNotNull(modal.node, '.dictionary-title'); +        /** @type {HTMLElement} */ +        const versionElement = querySelectorNotNull(modal.node, '.dictionary-version'); +        /** @type {HTMLElement} */ +        const outdateElement = querySelectorNotNull(modal.node, '.dictionary-outdated-notification'); +        /** @type {HTMLElement} */ +        const countsElement = querySelectorNotNull(modal.node, '.dictionary-counts'); +        /** @type {HTMLInputElement} */ +        const wildcardSupportedElement = querySelectorNotNull(modal.node, '.dictionary-prefix-wildcard-searches-supported'); +        /** @type {HTMLElement} */ +        const detailsTableElement = querySelectorNotNull(modal.node, '.dictionary-details-table'); + +        titleElement.textContent = title; +        versionElement.textContent = `rev.${revision}`; +        outdateElement.hidden = (version >= 3); +        countsElement.textContent = this._counts !== null ? JSON.stringify(this._counts, null, 4) : ''; +        wildcardSupportedElement.checked = prefixWildcardsSupported; +        this._setupDetails(detailsTableElement);          modal.setVisible(true);      } @@ -200,8 +214,14 @@ class DictionaryEntry {              const details = /** @type {HTMLElement} */ (this._dictionaryController.instantiateTemplate('dictionary-details-entry'));              details.dataset.type = key; -            /** @type {HTMLElement} */ (details.querySelector('.dictionary-details-entry-label')).textContent = `${label}:`; -            /** @type {HTMLElement} */ (details.querySelector('.dictionary-details-entry-info')).textContent = info; + +            /** @type {HTMLElement} */ +            const labelElement = querySelectorNotNull(details, '.dictionary-details-entry-label'); +            /** @type {HTMLElement} */ +            const infoElement = querySelectorNotNull(details, '.dictionary-details-entry-info'); + +            labelElement.textContent = `${label}:`; +            infoElement.textContent = info;              fragment.appendChild(details);              any = true; @@ -241,8 +261,10 @@ class DictionaryEntry {          const count = this._dictionaryController.dictionaryOptionCount;          const modal = this._dictionaryController.modalController.getModal('dictionary-move-location');          if (modal === null) { return; } -        const input = /** @type {HTMLInputElement} */ (modal.node.querySelector('#dictionary-move-location')); -        const titleNode = /** @type {HTMLElement} */ (modal.node.querySelector('.dictionary-title')); +        /** @type {HTMLInputElement} */ +        const input = querySelectorNotNull(modal.node, '#dictionary-move-location'); +        /** @type {HTMLElement} */ +        const titleNode = querySelectorNotNull(modal.node, '.dictionary-title');          modal.node.dataset.index = `${this._index}`;          titleNode.textContent = title; @@ -284,9 +306,11 @@ class DictionaryExtraInfo {              this._nodes.push(node);          } -        const dictionaryIntegrityButton = /** @type {HTMLButtonElement} */ (fragment.querySelector('.dictionary-integrity-button')); +        /** @type {HTMLButtonElement} */ +        const dictionaryIntegrityButton = querySelectorNotNull(fragment, '.dictionary-integrity-button'); -        this._setTitle(fragment.querySelector('.dictionary-total-count')); +        const titleNode = fragment.querySelector('.dictionary-total-count'); +        this._setTitle(titleNode);          this._eventListeners.addEventListener(dictionaryIntegrityButton, 'click', this._onIntegrityButtonClick.bind(this), false);          container.appendChild(fragment); @@ -315,11 +339,13 @@ class DictionaryExtraInfo {          const modal = this._parent.modalController.getModal('dictionary-extra-data');          if (modal === null) { return; } -        const dictionaryCounts = /** @type {HTMLElement} */ (modal.node.querySelector('.dictionary-counts')); +        /** @type {HTMLElement} */ +        const dictionaryCounts = querySelectorNotNull(modal.node, '.dictionary-counts');          const info = {counts: this._totalCounts, remainders: this._remainders};          dictionaryCounts.textContent = JSON.stringify(info, null, 4); -        this._setTitle(modal.node.querySelector('.dictionary-total-count')); +        const titleNode = modal.node.querySelector('.dictionary-total-count'); +        this._setTitle(titleNode);          modal.setVisible(true);      } @@ -354,22 +380,22 @@ export class DictionaryController {          this._databaseStateToken = null;          /** @type {boolean} */          this._checkingIntegrity = false; -        /** @type {?HTMLButtonElement} */ -        this._checkIntegrityButton = null; -        /** @type {?HTMLElement} */ -        this._dictionaryEntryContainer = null; -        /** @type {?HTMLElement} */ -        this._dictionaryInstallCountNode = null; -        /** @type {?HTMLElement} */ -        this._dictionaryEnabledCountNode = null; +        /** @type {HTMLButtonElement} */ +        this._checkIntegrityButton = querySelectorNotNull(document, '#dictionary-check-integrity'); +        /** @type {HTMLElement} */ +        this._dictionaryEntryContainer = querySelectorNotNull(document, '#dictionary-list'); +        /** @type {HTMLElement} */ +        this._dictionaryInstallCountNode = querySelectorNotNull(document, '#dictionary-install-count'); +        /** @type {HTMLElement} */ +        this._dictionaryEnabledCountNode = querySelectorNotNull(document, '#dictionary-enabled-count');          /** @type {?NodeListOf<HTMLElement>} */          this._noDictionariesInstalledWarnings = null;          /** @type {?NodeListOf<HTMLElement>} */          this._noDictionariesEnabledWarnings = null;          /** @type {?import('./modal.js').Modal} */          this._deleteDictionaryModal = null; -        /** @type {?HTMLInputElement} */ -        this._allCheckbox = null; +        /** @type {HTMLInputElement} */ +        this._allCheckbox = querySelectorNotNull(document, '#all-dictionaries-enabled');          /** @type {?DictionaryExtraInfo} */          this._extraInfo = null;          /** @type {boolean} */ @@ -388,16 +414,13 @@ export class DictionaryController {      /** */      async prepare() { -        this._checkIntegrityButton = /** @type {HTMLButtonElement} */ (document.querySelector('#dictionary-check-integrity')); -        this._dictionaryEntryContainer = /** @type {HTMLElement} */ (document.querySelector('#dictionary-list')); -        this._dictionaryInstallCountNode = /** @type {HTMLElement} */ (document.querySelector('#dictionary-install-count')); -        this._dictionaryEnabledCountNode = /** @type {HTMLElement} */ (document.querySelector('#dictionary-enabled-count'));          this._noDictionariesInstalledWarnings = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.no-dictionaries-installed-warning'));          this._noDictionariesEnabledWarnings = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.no-dictionaries-enabled-warning'));          this._deleteDictionaryModal = this._modalController.getModal('dictionary-confirm-delete'); -        this._allCheckbox = /** @type {HTMLInputElement} */ (document.querySelector('#all-dictionaries-enabled')); -        const dictionaryDeleteButton = /** @type {HTMLButtonElement} */ (document.querySelector('#dictionary-confirm-delete-button')); -        const dictionaryMoveButton = /** @type {HTMLButtonElement} */ (document.querySelector('#dictionary-move-button')); +        /** @type {HTMLButtonElement} */ +        const dictionaryDeleteButton = querySelectorNotNull(document, '#dictionary-confirm-delete-button'); +        /** @type {HTMLButtonElement} */ +        const dictionaryMoveButton = querySelectorNotNull(document, '#dictionary-move-button');          yomitan.on('databaseUpdated', this._onDatabaseUpdated.bind(this));          this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); @@ -420,7 +443,8 @@ export class DictionaryController {          if (this._isDeleting) { return; }          const modal = /** @type {import('./modal.js').Modal} */ (this._deleteDictionaryModal);          modal.node.dataset.dictionaryTitle = dictionaryTitle; -        const nameElement = /** @type {Element} */ (modal.node.querySelector('#dictionary-confirm-delete-name')); +        /** @type {Element} */ +        const nameElement = querySelectorNotNull(modal.node, '#dictionary-confirm-delete-name');          nameElement.textContent = dictionaryTitle;          modal.setVisible(true);      } @@ -696,7 +720,9 @@ export class DictionaryController {          if (typeof index !== 'number') { return; }          const indexNumber = Number.parseInt(index, 10); -        const targetString = /** @type {HTMLInputElement} */ (document.querySelector('#dictionary-move-location')).value; +        /** @type {HTMLInputElement} */ +        const targetStringInput = querySelectorNotNull(document, '#dictionary-move-location'); +        const targetString = targetStringInput.value;          const target = Number.parseInt(targetString, 10) - 1;          if (!Number.isFinite(target) || !Number.isFinite(indexNumber) || indexNumber === target) { return; } diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index d1255e11..35b7c461 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -18,6 +18,7 @@  import {log} from '../../core.js';  import {ExtensionError} from '../../core/extension-error.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {DictionaryWorker} from '../../language/dictionary-worker.js';  import {yomitan} from '../../yomitan.js';  import {DictionaryController} from './dictionary-controller.js'; @@ -37,22 +38,18 @@ export class DictionaryImportController {          this._statusFooter = statusFooter;          /** @type {boolean} */          this._modifying = false; -        /** @type {?HTMLButtonElement} */ -        this._purgeButton = null; -        /** @type {?HTMLButtonElement} */ -        this._purgeConfirmButton = null; -        /** @type {?HTMLButtonElement} */ -        this._importFileButton = null; -        /** @type {?HTMLInputElement} */ -        this._importFileInput = null; +        /** @type {HTMLButtonElement} */ +        this._purgeButton = querySelectorNotNull(document, '#dictionary-delete-all-button'); +        /** @type {HTMLButtonElement} */ +        this._purgeConfirmButton = querySelectorNotNull(document, '#dictionary-confirm-delete-all-button'); +        /** @type {HTMLButtonElement} */ +        this._importFileButton = querySelectorNotNull(document, '#dictionary-import-file-button'); +        /** @type {HTMLInputElement} */ +        this._importFileInput = querySelectorNotNull(document, '#dictionary-import-file-input');          /** @type {?import('./modal.js').Modal} */          this._purgeConfirmModal = null; -        /** @type {?HTMLElement} */ -        this._errorContainer = null; -        /** @type {?HTMLElement} */ -        this._spinner = null; -        /** @type {?HTMLElement} */ -        this._purgeNotification = null; +        /** @type {HTMLElement} */ +        this._errorContainer = querySelectorNotNull(document, '#dictionary-error');          /** @type {[originalMessage: string, newMessage: string][]} */          this._errorToStringOverrides = [              [ @@ -68,14 +65,7 @@ export class DictionaryImportController {      /** */      async prepare() { -        this._purgeButton = /** @type {HTMLButtonElement} */ (document.querySelector('#dictionary-delete-all-button')); -        this._purgeConfirmButton = /** @type {HTMLButtonElement} */ (document.querySelector('#dictionary-confirm-delete-all-button')); -        this._importFileButton = /** @type {HTMLButtonElement} */ (document.querySelector('#dictionary-import-file-button')); -        this._importFileInput = /** @type {HTMLInputElement} */ (document.querySelector('#dictionary-import-file-input'));          this._purgeConfirmModal = this._modalController.getModal('dictionary-confirm-delete-all'); -        this._errorContainer = /** @type {HTMLElement} */ (document.querySelector('#dictionary-error')); -        this._spinner = /** @type {HTMLElement} */ (document.querySelector('#dictionary-spinner')); -        this._purgeNotification = /** @type {HTMLElement} */ (document.querySelector('#dictionary-delete-all-status'));          this._purgeButton.addEventListener('click', this._onPurgeButtonClick.bind(this), false);          this._purgeConfirmButton.addEventListener('click', this._onPurgeConfirmButtonClick.bind(this), false); @@ -123,14 +113,11 @@ export class DictionaryImportController {      async _purgeDatabase() {          if (this._modifying) { return; } -        const purgeNotification = this._purgeNotification;          const prevention = this._preventPageExit();          try {              this._setModifying(true);              this._hideErrors(); -            this._setSpinnerVisible(true); -            if (purgeNotification !== null) { purgeNotification.hidden = false; }              await yomitan.api.purgeDatabase();              const errors = await this._clearDictionarySettings(); @@ -142,8 +129,6 @@ export class DictionaryImportController {              this._showErrors([error instanceof Error ? error : new Error(`${error}`)]);          } finally {              prevention.end(); -            if (purgeNotification !== null) { purgeNotification.hidden = true; } -            this._setSpinnerVisible(false);              this._setModifying(false);              this._triggerStorageChanged();          } @@ -156,7 +141,6 @@ export class DictionaryImportController {          if (this._modifying) { return; }          const statusFooter = this._statusFooter; -        const importInfo = /** @type {HTMLElement} */ (document.querySelector('#dictionary-import-info'));          const progressSelector = '.dictionary-import-progress';          const progressContainers = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll(`#dictionaries-modal ${progressSelector}`));          const progressBars = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll(`${progressSelector} .progress-bar`)); @@ -168,7 +152,6 @@ export class DictionaryImportController {          try {              this._setModifying(true);              this._hideErrors(); -            this._setSpinnerVisible(true);              for (const progress of progressContainers) { progress.hidden = false; } @@ -204,11 +187,6 @@ export class DictionaryImportController {              const fileCount = files.length;              for (let i = 0; i < fileCount; ++i) { -                if (importInfo !== null && fileCount > 1) { -                    importInfo.hidden = false; -                    importInfo.textContent = `(${i + 1} of ${fileCount})`; -                } -                  statusPrefix = `Importing dictionary${fileCount > 1 ? ` (${i + 1} of ${fileCount})` : ''}`;                  onProgress({                      stepIndex: -1, @@ -226,11 +204,6 @@ export class DictionaryImportController {              prevention.end();              for (const progress of progressContainers) { progress.hidden = true; }              if (statusFooter !== null) { statusFooter.setTaskActive(progressSelector, false); } -            if (importInfo !== null) { -                importInfo.textContent = ''; -                importInfo.hidden = true; -            } -            this._setSpinnerVisible(false);              this._setModifying(false);              this._triggerStorageChanged();          } @@ -313,15 +286,6 @@ export class DictionaryImportController {      }      /** -     * @param {boolean} visible -     */ -    _setSpinnerVisible(visible) { -        if (this._spinner !== null) { -            this._spinner.hidden = !visible; -        } -    } - -    /**       * @returns {import('settings-controller').PageExitPrevention}       */      _preventPageExit() { diff --git a/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js b/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js index d36d965a..e92d9e93 100644 --- a/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js +++ b/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js @@ -17,6 +17,7 @@   */  import {EventListenerCollection, isObject} from '../../core.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {HotkeyUtil} from '../../input/hotkey-util.js';  import {yomitan} from '../../yomitan.js';  import {KeyboardMouseInputField} from './keyboard-mouse-input-field.js'; @@ -28,12 +29,12 @@ export class ExtensionKeyboardShortcutController {      constructor(settingsController) {          /** @type {import('./settings-controller.js').SettingsController} */          this._settingsController = settingsController; -        /** @type {?HTMLButtonElement} */ -        this._resetButton = null; -        /** @type {?HTMLButtonElement} */ -        this._clearButton = null; -        /** @type {?HTMLElement} */ -        this._listContainer = null; +        /** @type {HTMLButtonElement} */ +        this._resetButton = querySelectorNotNull(document, '#extension-hotkey-list-reset-all'); +        /** @type {HTMLButtonElement} */ +        this._clearButton = querySelectorNotNull(document, '#extension-hotkey-list-clear-all'); +        /** @type {HTMLElement} */ +        this._listContainer = querySelectorNotNull(document, '#extension-hotkey-list');          /** @type {HotkeyUtil} */          this._hotkeyUtil = new HotkeyUtil();          /** @type {?import('environment').OperatingSystem} */ @@ -49,10 +50,6 @@ export class ExtensionKeyboardShortcutController {      /** */      async prepare() { -        this._resetButton = /** @type {HTMLButtonElement} */ (document.querySelector('#extension-hotkey-list-reset-all')); -        this._clearButton = /** @type {HTMLButtonElement} */ (document.querySelector('#extension-hotkey-list-clear-all')); -        this._listContainer = /** @type {HTMLElement} */ (document.querySelector('#extension-hotkey-list')); -          const canResetCommands = this.canResetCommands();          const canModifyCommands = this.canModifyCommands();          this._resetButton.hidden = !canResetCommands; @@ -277,11 +274,14 @@ class ExtensionKeyboardShortcutHotkeyEntry {      /** */      prepare() { -        const label = /** @type {HTMLElement} */ (this._node.querySelector('.settings-item-label')); +        /** @type {HTMLElement} */ +        const label = querySelectorNotNull(this._node, '.settings-item-label');          label.textContent = this._description || this._name; -        const button = /** @type {HTMLButtonElement} */ (this._node.querySelector('.extension-hotkey-list-item-button')); -        const input = /** @type {HTMLInputElement} */ (this._node.querySelector('input')); +        /** @type {HTMLButtonElement} */ +        const button = querySelectorNotNull(this._node, '.extension-hotkey-list-item-button'); +        /** @type {HTMLInputElement} */ +        const input = querySelectorNotNull(this._node, 'input');          this._input = input; diff --git a/ext/js/pages/settings/keyboard-shortcuts-controller.js b/ext/js/pages/settings/keyboard-shortcuts-controller.js index ad16b0e9..0734faa1 100644 --- a/ext/js/pages/settings/keyboard-shortcuts-controller.js +++ b/ext/js/pages/settings/keyboard-shortcuts-controller.js @@ -18,6 +18,7 @@  import {EventListenerCollection} from '../../core.js';  import {DocumentUtil} from '../../dom/document-util.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {ObjectPropertyAccessor} from '../../general/object-property-accessor.js';  import {yomitan} from '../../yomitan.js';  import {KeyboardMouseInputField} from './keyboard-mouse-input-field.js'; @@ -33,18 +34,18 @@ export class KeyboardShortcutController {          this._entries = [];          /** @type {?import('environment').OperatingSystem} */          this._os = null; -        /** @type {?HTMLButtonElement} */ -        this._addButton = null; -        /** @type {?HTMLButtonElement} */ -        this._resetButton = null; -        /** @type {?HTMLElement} */ -        this._listContainer = null; -        /** @type {?HTMLElement} */ -        this._emptyIndicator = null; +        /** @type {HTMLButtonElement} */ +        this._addButton = querySelectorNotNull(document, '#hotkey-list-add'); +        /** @type {HTMLButtonElement} */ +        this._resetButton = querySelectorNotNull(document, '#hotkey-list-reset'); +        /** @type {HTMLElement} */ +        this._listContainer = querySelectorNotNull(document, '#hotkey-list'); +        /** @type {HTMLElement} */ +        this._emptyIndicator = querySelectorNotNull(document, '#hotkey-list-empty');          /** @type {Intl.Collator} */          this._stringComparer = new Intl.Collator('en-US'); // Invariant locale -        /** @type {?HTMLElement} */ -        this._scrollContainer = null; +        /** @type {HTMLElement} */ +        this._scrollContainer = querySelectorNotNull(document, '#keyboard-shortcuts-modal .modal-body');          /** @type {Map<string, import('keyboard-shortcut-controller').ActionDetails>} */          this._actionDetails = new Map([              ['',                                 {scopes: new Set()}], @@ -81,12 +82,6 @@ export class KeyboardShortcutController {          const {platform: {os}} = await yomitan.api.getEnvironmentInfo();          this._os = os; -        this._addButton = /** @type {HTMLButtonElement} */ (document.querySelector('#hotkey-list-add')); -        this._resetButton = /** @type {HTMLButtonElement} */ (document.querySelector('#hotkey-list-reset')); -        this._listContainer = /** @type {HTMLElement} */ (document.querySelector('#hotkey-list')); -        this._emptyIndicator = /** @type {HTMLElement} */ (document.querySelector('#hotkey-list-empty')); -        this._scrollContainer = /** @type {HTMLElement} */ (document.querySelector('#keyboard-shortcuts-modal .modal-body')); -          this._addButton.addEventListener('click', this._onAddClick.bind(this));          this._resetButton.addEventListener('click', this._onResetClick.bind(this));          this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); @@ -283,12 +278,18 @@ class KeyboardShortcutHotkeyEntry {      prepare() {          const node = this._node; -        const menuButton = /** @type {HTMLButtonElement} */ (node.querySelector('.hotkey-list-item-button')); -        const input = /** @type {HTMLInputElement} */ (node.querySelector('.hotkey-list-item-input')); -        const action = /** @type {HTMLSelectElement} */ (node.querySelector('.hotkey-list-item-action')); -        const enabledToggle = /** @type {HTMLInputElement} */ (node.querySelector('.hotkey-list-item-enabled')); -        const scopesButton = /** @type {HTMLButtonElement} */ (node.querySelector('.hotkey-list-item-scopes-button')); -        const enabledButton = /** @type {HTMLButtonElement} */ (node.querySelector('.hotkey-list-item-enabled-button')); +        /** @type {HTMLButtonElement} */ +        const menuButton = querySelectorNotNull(node, '.hotkey-list-item-button'); +        /** @type {HTMLInputElement} */ +        const input = querySelectorNotNull(node, '.hotkey-list-item-input'); +        /** @type {HTMLSelectElement} */ +        const action = querySelectorNotNull(node, '.hotkey-list-item-action'); +        /** @type {HTMLInputElement} */ +        const enabledToggle = querySelectorNotNull(node, '.hotkey-list-item-enabled'); +        /** @type {HTMLButtonElement} */ +        const scopesButton = querySelectorNotNull(node, '.hotkey-list-item-scopes-button'); +        /** @type {HTMLButtonElement} */ +        const enabledButton = querySelectorNotNull(node, '.hotkey-list-item-enabled-button');          this._actionSelect = action;          this._enabledButton = enabledButton; @@ -333,7 +334,8 @@ class KeyboardShortcutHotkeyEntry {          const {action} = this._data;          const {menu} = e.detail; -        const resetArgument = /** @type {HTMLElement} */ (menu.bodyNode.querySelector('.popup-menu-item[data-menu-action="resetArgument"]')); +        /** @type {HTMLElement} */ +        const resetArgument = querySelectorNotNull(menu.bodyNode, '.popup-menu-item[data-menu-action="resetArgument"]');          const details = this._parent.getActionDetails(action);          const argumentDetails = typeof details !== 'undefined' ? details.argument : void 0; @@ -646,7 +648,8 @@ class KeyboardShortcutHotkeyEntry {              if (scope === null) { continue; }              menuItem.hidden = !(validScopes === null || validScopes.has(scope)); -            const checkbox = /** @type {HTMLInputElement} */ (menuItem.querySelector('.hotkey-scope-checkbox')); +            /** @type {HTMLInputElement} */ +            const checkbox = querySelectorNotNull(menuItem, '.hotkey-scope-checkbox');              if (checkbox !== null) {                  checkbox.checked = scopes.includes(scope);                  this._scopeMenuEventListeners.addEventListener(checkbox, 'change', this._onScopeCheckboxChange.bind(this), false); diff --git a/ext/js/pages/settings/mecab-controller.js b/ext/js/pages/settings/mecab-controller.js index 4e2b02c6..9c55c9a0 100644 --- a/ext/js/pages/settings/mecab-controller.js +++ b/ext/js/pages/settings/mecab-controller.js @@ -16,23 +16,21 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {yomitan} from '../../yomitan.js';  export class MecabController {      constructor() { -        /** @type {?HTMLButtonElement} */ -        this._testButton = null; -        /** @type {?HTMLElement} */ -        this._resultsContainer = null; +        /** @type {HTMLButtonElement} */ +        this._testButton = querySelectorNotNull(document, '#test-mecab-button'); +        /** @type {HTMLElement} */ +        this._resultsContainer = querySelectorNotNull(document, '#test-mecab-results');          /** @type {boolean} */          this._testActive = false;      }      /** */      prepare() { -        this._testButton = /** @type {HTMLButtonElement} */ (document.querySelector('#test-mecab-button')); -        this._resultsContainer = /** @type {HTMLElement} */ (document.querySelector('#test-mecab-results')); -          this._testButton.addEventListener('click', this._onTestButtonClick.bind(this), false);      } diff --git a/ext/js/pages/settings/modal.js b/ext/js/pages/settings/modal.js index 21a6e705..17a4605d 100644 --- a/ext/js/pages/settings/modal.js +++ b/ext/js/pages/settings/modal.js @@ -37,7 +37,8 @@ export class Modal extends PanelElement {      prepare() {          const node = this.node;          this._contentNode = node.querySelector('.modal-content'); -        let dimmerNode = /** @type {?HTMLElement} */ (node.querySelector('.modal-content-dimmer')); +        /** @type {?HTMLElement} */ +        let dimmerNode = node.querySelector('.modal-content-dimmer');          if (dimmerNode === null) { dimmerNode = node; }          dimmerNode.addEventListener('mousedown', this._onModalContainerMouseDown.bind(this), false);          dimmerNode.addEventListener('mouseup', this._onModalContainerMouseUp.bind(this), false); diff --git a/ext/js/pages/settings/nested-popups-controller.js b/ext/js/pages/settings/nested-popups-controller.js index c01986ab..7eb78148 100644 --- a/ext/js/pages/settings/nested-popups-controller.js +++ b/ext/js/pages/settings/nested-popups-controller.js @@ -17,6 +17,7 @@   */  import {DocumentUtil} from '../../dom/document-util.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  export class NestedPopupsController {      /** @@ -27,20 +28,16 @@ export class NestedPopupsController {          this._settingsController = settingsController;          /** @type {number} */          this._popupNestingMaxDepth = 0; -        /** @type {?HTMLInputElement} */ -        this._nestedPopupsEnabled = null; -        /** @type {?HTMLInputElement} */ -        this._nestedPopupsCount = null; -        /** @type {?HTMLElement} */ -        this._nestedPopupsEnabledMoreOptions = null; +        /** @type {HTMLInputElement} */ +        this._nestedPopupsEnabled = querySelectorNotNull(document, '#nested-popups-enabled'); +        /** @type {HTMLInputElement} */ +        this._nestedPopupsCount = querySelectorNotNull(document, '#nested-popups-count'); +        /** @type {HTMLElement} */ +        this._nestedPopupsEnabledMoreOptions = querySelectorNotNull(document, '#nested-popups-enabled-more-options');      }      /** */      async prepare() { -        this._nestedPopupsEnabled = /** @type {HTMLInputElement} */ (document.querySelector('#nested-popups-enabled')); -        this._nestedPopupsCount = /** @type {HTMLInputElement} */ (document.querySelector('#nested-popups-count')); -        this._nestedPopupsEnabledMoreOptions = /** @type {HTMLElement} */ (document.querySelector('#nested-popups-enabled-more-options')); -          const options = await this._settingsController.getOptions();          const optionsContext = this._settingsController.getOptionsContext(); diff --git a/ext/js/pages/settings/permissions-origin-controller.js b/ext/js/pages/settings/permissions-origin-controller.js index a4271f92..6dacced8 100644 --- a/ext/js/pages/settings/permissions-origin-controller.js +++ b/ext/js/pages/settings/permissions-origin-controller.js @@ -17,6 +17,7 @@   */  import {EventListenerCollection} from '../../core.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  export class PermissionsOriginController {      /** @@ -25,16 +26,16 @@ export class PermissionsOriginController {      constructor(settingsController) {          /** @type {import('./settings-controller.js').SettingsController} */          this._settingsController = settingsController; -        /** @type {?HTMLElement} */ -        this._originContainer = null; -        /** @type {?HTMLElement} */ -        this._originEmpty = null; +        /** @type {HTMLElement} */ +        this._originContainer = querySelectorNotNull(document, '#permissions-origin-list'); +        /** @type {HTMLElement} */ +        this._originEmpty = querySelectorNotNull(document, '#permissions-origin-list-empty');          /** @type {?NodeListOf<HTMLInputElement>} */          this._originToggleNodes = null; -        /** @type {?HTMLInputElement} */ -        this._addOriginInput = null; -        /** @type {?HTMLElement} */ -        this._errorContainer = null; +        /** @type {HTMLInputElement} */ +        this._addOriginInput = querySelectorNotNull(document, '#permissions-origin-new-input'); +        /** @type {HTMLElement} */ +        this._errorContainer = querySelectorNotNull(document, '#permissions-origin-list-error');          /** @type {ChildNode[]} */          this._originContainerChildren = [];          /** @type {EventListenerCollection} */ @@ -43,12 +44,9 @@ export class PermissionsOriginController {      /** */      async prepare() { -        this._originContainer = /** @type {HTMLElement} */ (document.querySelector('#permissions-origin-list')); -        this._originEmpty = /** @type {HTMLElement} */ (document.querySelector('#permissions-origin-list-empty'));          this._originToggleNodes = /** @type {NodeListOf<HTMLInputElement>} */ (document.querySelectorAll('.permissions-origin-toggle')); -        this._addOriginInput = /** @type {HTMLInputElement} */ (document.querySelector('#permissions-origin-new-input')); -        this._errorContainer = /** @type {HTMLElement} */ (document.querySelector('#permissions-origin-list-error')); -        const addButton = /** @type {HTMLButtonElement} */ (document.querySelector('#permissions-origin-add')); +        /** @type {HTMLButtonElement} */ +        const addButton = querySelectorNotNull(document, '#permissions-origin-add');          for (const node of this._originToggleNodes) {              node.addEventListener('change', this._onOriginToggleChange.bind(this), false); @@ -87,8 +85,10 @@ export class PermissionsOriginController {          for (const origin of originsSet) {              if (excludeOrigins.has(origin)) { continue; }              const node = this._settingsController.instantiateTemplateFragment('permissions-origin'); -            const input = /** @type {HTMLInputElement} */ (node.querySelector('.permissions-origin-input')); -            const menuButton = /** @type {HTMLElement} */ (node.querySelector('.permissions-origin-button')); +            /** @type {HTMLInputElement} */ +            const input = querySelectorNotNull(node, '.permissions-origin-input'); +            /** @type {HTMLElement} */ +            const menuButton = querySelectorNotNull(node, '.permissions-origin-button');              input.value = origin;              this._eventListeners.addEventListener(menuButton, 'menuClose', this._onOriginMenuClose.bind(this, origin), false);              this._originContainerChildren.push(...node.childNodes); diff --git a/ext/js/pages/settings/persistent-storage-controller.js b/ext/js/pages/settings/persistent-storage-controller.js index e85bfc6b..7386edd7 100644 --- a/ext/js/pages/settings/persistent-storage-controller.js +++ b/ext/js/pages/settings/persistent-storage-controller.js @@ -17,22 +17,23 @@   */  import {isObject} from '../../core.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {yomitan} from '../../yomitan.js';  export class PersistentStorageController {      constructor() { -        /** @type {?HTMLInputElement} */ -        this._persistentStorageCheckbox = null; +        /** @type {HTMLInputElement} */ +        this._persistentStorageCheckbox = querySelectorNotNull(document, '#storage-persistent-checkbox');      }      /** */      async prepare() { -        this._persistentStorageCheckbox = /** @type {HTMLInputElement} */ (document.querySelector('#storage-persistent-checkbox'));          this._persistentStorageCheckbox.addEventListener('change', this._onPersistentStorageCheckboxChange.bind(this), false);          if (!this._isPersistentStorageSupported()) { return; } -        const info = /** @type {?HTMLElement} */ (document.querySelector('#storage-persistent-info')); +        /** @type {?HTMLElement} */ +        const info = document.querySelector('#storage-persistent-info');          if (info !== null) { info.hidden = false; }          const isStoragePeristent = await this.isStoragePeristent(); @@ -77,7 +78,8 @@ export class PersistentStorageController {          this._updateCheckbox(isStoragePeristent); -        const node = /** @type {?HTMLElement} */ (document.querySelector('#storage-persistent-fail-warning')); +        /** @type {?HTMLElement} */ +        const node = document.querySelector('#storage-persistent-fail-warning');          if (node !== null) { node.hidden = isStoragePeristent; }          yomitan.trigger('storageChanged'); diff --git a/ext/js/pages/settings/popup-preview-controller.js b/ext/js/pages/settings/popup-preview-controller.js index 7239ca17..d8bc9850 100644 --- a/ext/js/pages/settings/popup-preview-controller.js +++ b/ext/js/pages/settings/popup-preview-controller.js @@ -16,6 +16,8 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {querySelectorNotNull} from '../../dom/query-selector.js'; +  export class PopupPreviewController {      /**       * @param {import('./settings-controller.js').SettingsController} settingsController @@ -25,25 +27,20 @@ export class PopupPreviewController {          this._settingsController = settingsController;          /** @type {string} */          this._targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, ''); -        /** @type {?HTMLIFrameElement} */ -        this._frame = null; -        /** @type {?HTMLTextAreaElement} */ -        this._customCss = null; -        /** @type {?HTMLTextAreaElement} */ -        this._customOuterCss = null; -        /** @type {?HTMLElement} */ -        this._previewFrameContainer = null; +        /** @type {HTMLIFrameElement} */ +        this._frame = querySelectorNotNull(document, '#popup-preview-frame'); +        /** @type {HTMLTextAreaElement} */ +        this._customCss = querySelectorNotNull(document, '#custom-popup-css'); +        /** @type {HTMLTextAreaElement} */ +        this._customOuterCss = querySelectorNotNull(document, '#custom-popup-outer-css'); +        /** @type {HTMLElement} */ +        this._previewFrameContainer = querySelectorNotNull(document, '.preview-frame-container');      }      /** */      async prepare() {          if (new URLSearchParams(location.search).get('popup-preview') === 'false') { return; } -        this._frame = /** @type {HTMLIFrameElement} */ (document.querySelector('#popup-preview-frame')); -        this._customCss = /** @type {HTMLTextAreaElement} */ (document.querySelector('#custom-popup-css')); -        this._customOuterCss = /** @type {HTMLTextAreaElement} */ (document.querySelector('#custom-popup-outer-css')); -        this._previewFrameContainer = /** @type {HTMLElement} */ (document.querySelector('.preview-frame-container')); -          this._customCss.addEventListener('input', this._onCustomCssChange.bind(this), false);          this._customCss.addEventListener('settingChanged', this._onCustomCssChange.bind(this), false);          this._customOuterCss.addEventListener('input', this._onCustomOuterCssChange.bind(this), false); diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js index 60d264fa..e5cc6580 100644 --- a/ext/js/pages/settings/popup-preview-frame.js +++ b/ext/js/pages/settings/popup-preview-frame.js @@ -18,6 +18,7 @@  import * as wanakana from '../../../lib/wanakana.js';  import {Frontend} from '../../app/frontend.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {TextSourceRange} from '../../dom/text-source-range.js';  import {yomitan} from '../../yomitan.js'; @@ -49,10 +50,10 @@ export class PopupPreviewFrame {          this._textSource = null;          /** @type {?import('settings').OptionsContext} */          this._optionsContext = null; -        /** @type {?HTMLElement} */ -        this._exampleText = null; -        /** @type {?HTMLInputElement} */ -        this._exampleTextInput = null; +        /** @type {HTMLElement} */ +        this._exampleText = querySelectorNotNull(document, '#example-text'); +        /** @type {HTMLInputElement} */ +        this._exampleTextInput = querySelectorNotNull(document, '#example-text-input');          /** @type {string} */          this._targetOrigin = chrome.runtime.getURL('/').replace(/\/$/, ''); @@ -67,9 +68,6 @@ export class PopupPreviewFrame {      /** */      async prepare() { -        this._exampleText = /** @type {HTMLElement} */ (document.querySelector('#example-text')); -        this._exampleTextInput = /** @type {HTMLInputElement} */ (document.querySelector('#example-text-input')); -          if (this._exampleTextInput !== null && typeof wanakana !== 'undefined') {              wanakana.bind(this._exampleTextInput);          } @@ -77,7 +75,8 @@ export class PopupPreviewFrame {          window.addEventListener('message', this._onMessage.bind(this), false);          // Setup events -        const darkThemeCheckbox = /** @type {HTMLInputElement} */ (document.querySelector('#theme-dark-checkbox')); +        /** @type {HTMLInputElement} */ +        const darkThemeCheckbox = querySelectorNotNull(document, '#theme-dark-checkbox');          darkThemeCheckbox.addEventListener('change', this._onThemeDarkCheckboxChanged.bind(this), false);          this._exampleText.addEventListener('click', this._onExampleTextClick.bind(this), false);          this._exampleTextInput.addEventListener('blur', this._onExampleTextInputBlur.bind(this), false); diff --git a/ext/js/pages/settings/popup-window-controller.js b/ext/js/pages/settings/popup-window-controller.js index e1a5456b..0d56dc58 100644 --- a/ext/js/pages/settings/popup-window-controller.js +++ b/ext/js/pages/settings/popup-window-controller.js @@ -16,12 +16,14 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {yomitan} from '../../yomitan.js';  export class PopupWindowController {      /** */      prepare() { -        const testLink = /** @type {HTMLElement} */ (document.querySelector('#test-window-open-link')); +        /** @type {HTMLElement} */ +        const testLink = querySelectorNotNull(document, '#test-window-open-link');          testLink.addEventListener('click', this._onTestWindowOpenLinkClick.bind(this), false);      } diff --git a/ext/js/pages/settings/profile-conditions-ui.js b/ext/js/pages/settings/profile-conditions-ui.js index 96aef83f..e02d2585 100644 --- a/ext/js/pages/settings/profile-conditions-ui.js +++ b/ext/js/pages/settings/profile-conditions-ui.js @@ -18,6 +18,7 @@  import {EventDispatcher, EventListenerCollection} from '../../core.js';  import {DocumentUtil} from '../../dom/document-util.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {KeyboardMouseInputField} from './keyboard-mouse-input-field.js';  /** @@ -33,10 +34,10 @@ export class ProfileConditionsUI extends EventDispatcher {          this._settingsController = settingsController;          /** @type {?import('environment').OperatingSystem} */          this._os = null; -        /** @type {?HTMLElement} */ -        this._conditionGroupsContainer = null; -        /** @type {?HTMLElement} */ -        this._addConditionGroupButton = null; +        /** @type {HTMLElement} */ +        this._conditionGroupsContainer = querySelectorNotNull(document, '#profile-condition-groups'); +        /** @type {HTMLElement} */ +        this._addConditionGroupButton = querySelectorNotNull(document, '#profile-add-condition-group');          /** @type {ProfileConditionGroupUI[]} */          this._children = [];          /** @type {EventListenerCollection} */ @@ -139,8 +140,6 @@ export class ProfileConditionsUI extends EventDispatcher {          const {conditionGroups} = profiles[profileIndex];          this._profileIndex = profileIndex; -        this._conditionGroupsContainer = /** @type {HTMLElement} */ (document.querySelector('#profile-condition-groups')); -        this._addConditionGroupButton = /** @type {HTMLElement} */ (document.querySelector('#profile-add-condition-group'));          for (let i = 0, ii = conditionGroups.length; i < ii; ++i) {              this._addConditionGroup(conditionGroups[i], i); @@ -157,9 +156,6 @@ export class ProfileConditionsUI extends EventDispatcher {              child.cleanup();          }          this._children = []; - -        this._conditionGroupsContainer = null; -        this._addConditionGroupButton = null;      }      /** @@ -354,7 +350,7 @@ export class ProfileConditionsUI extends EventDispatcher {          const child = new ProfileConditionGroupUI(this, index);          child.prepare(conditionGroup);          this._children.push(child); -        /** @type {HTMLElement} */ (this._conditionGroupsContainer).appendChild(child.node); +        this._conditionGroupsContainer.appendChild(child.node);          return child;      } @@ -460,9 +456,9 @@ class ProfileConditionGroupUI {          /** @type {HTMLElement} */          this._node = /** @type {HTMLElement} */ (this._parent.instantiateTemplate('profile-condition-group'));          /** @type {HTMLElement} */ -        this._conditionContainer = /** @type {HTMLElement} */ (this._node.querySelector('.profile-condition-list')); +        this._conditionContainer = querySelectorNotNull(this._node, '.profile-condition-list');          /** @type {HTMLElement} */ -        this._addConditionButton = /** @type {HTMLElement} */ (this._node.querySelector('.profile-condition-add-button')); +        this._addConditionButton = querySelectorNotNull(this._node, '.profile-condition-add-button');          /** @type {ProfileConditionUI[]} */          this._children = [];          /** @type {EventListenerCollection} */ @@ -621,23 +617,23 @@ class ProfileConditionUI {          /** @type {HTMLElement} */          this._node = this._parent.parent.instantiateTemplate('profile-condition');          /** @type {HTMLSelectElement} */ -        this._typeInput = /** @type {HTMLSelectElement} */ (this._node.querySelector('.profile-condition-type')); +        this._typeInput = querySelectorNotNull(this._node, '.profile-condition-type');          /** @type {HTMLSelectElement} */ -        this._operatorInput = /** @type {HTMLSelectElement} */ (this._node.querySelector('.profile-condition-operator')); +        this._operatorInput = querySelectorNotNull(this._node, '.profile-condition-operator');          /** @type {HTMLButtonElement} */ -        this._removeButton = /** @type {HTMLButtonElement} */ (this._node.querySelector('.profile-condition-remove')); +        this._removeButton = querySelectorNotNull(this._node, '.profile-condition-remove');          /** @type {HTMLButtonElement} */ -        this._mouseButton = /** @type {HTMLButtonElement} */ (this._node.querySelector('.mouse-button')); +        this._mouseButton = querySelectorNotNull(this._node, '.mouse-button');          /** @type {HTMLElement} */ -        this._mouseButtonContainer = /** @type {HTMLElement} */ (this._node.querySelector('.mouse-button-container')); +        this._mouseButtonContainer = querySelectorNotNull(this._node, '.mouse-button-container');          /** @type {HTMLButtonElement} */ -        this._menuButton = /** @type {HTMLButtonElement} */ (this._node.querySelector('.profile-condition-menu-button')); +        this._menuButton = querySelectorNotNull(this._node, '.profile-condition-menu-button');          /** @type {HTMLElement} */ -        this._typeOptionContainer = /** @type {HTMLElement} */ (this._typeInput.querySelector('optgroup')); +        this._typeOptionContainer = querySelectorNotNull(this._typeInput, 'optgroup');          /** @type {HTMLElement} */ -        this._operatorOptionContainer = /** @type {HTMLElement} */ (this._operatorInput.querySelector('optgroup')); +        this._operatorOptionContainer = querySelectorNotNull(this._operatorInput, 'optgroup');          /** @type {HTMLInputElement} */ -        this._valueInput = /** @type {HTMLInputElement} */ (this._node.querySelector('.profile-condition-input')); +        this._valueInput = querySelectorNotNull(this._node, '.profile-condition-input');          /** @type {string} */          this._value = '';          /** @type {?KeyboardMouseInputField} */ @@ -777,7 +773,8 @@ class ProfileConditionUI {       */      _onMenuOpen(e) {          const bodyNode = e.detail.menu.bodyNode; -        const deleteGroup = /** @type {HTMLElement} */ (bodyNode.querySelector('.popup-menu-item[data-menu-action="deleteGroup"]')); +        /** @type {HTMLElement} */ +        const deleteGroup = querySelectorNotNull(bodyNode, '.popup-menu-item[data-menu-action="deleteGroup"]');          if (deleteGroup !== null) {              deleteGroup.hidden = (this._parent.childCount <= 1);          } diff --git a/ext/js/pages/settings/profile-controller.js b/ext/js/pages/settings/profile-controller.js index c82223b8..6e0710a8 100644 --- a/ext/js/pages/settings/profile-controller.js +++ b/ext/js/pages/settings/profile-controller.js @@ -17,6 +17,7 @@   */  import {clone, EventListenerCollection} from '../../core.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {yomitan} from '../../yomitan.js';  import {ProfileConditionsUI} from './profile-conditions-ui.js'; @@ -34,24 +35,24 @@ export class ProfileController {          this._profileConditionsUI = new ProfileConditionsUI(settingsController);          /** @type {?number} */          this._profileConditionsIndex = null; -        /** @type {?HTMLSelectElement} */ -        this._profileActiveSelect = null; -        /** @type {?HTMLSelectElement} */ -        this._profileTargetSelect = null; -        /** @type {?HTMLSelectElement} */ -        this._profileCopySourceSelect = null; -        /** @type {?HTMLElement} */ -        this._removeProfileNameElement = null; -        /** @type {?HTMLButtonElement} */ -        this._profileAddButton = null; -        /** @type {?HTMLButtonElement} */ -        this._profileRemoveConfirmButton = null; -        /** @type {?HTMLButtonElement} */ -        this._profileCopyConfirmButton = null; -        /** @type {?HTMLElement} */ -        this._profileEntryListContainer = null; -        /** @type {?HTMLElement} */ -        this._profileConditionsProfileName = null; +        /** @type {HTMLSelectElement} */ +        this._profileActiveSelect = querySelectorNotNull(document, '#profile-active-select'); +        /** @type {HTMLSelectElement} */ +        this._profileTargetSelect = querySelectorNotNull(document, '#profile-target-select'); +        /** @type {HTMLSelectElement} */ +        this._profileCopySourceSelect = querySelectorNotNull(document, '#profile-copy-source-select'); +        /** @type {HTMLElement} */ +        this._removeProfileNameElement = querySelectorNotNull(document, '#profile-remove-name'); +        /** @type {HTMLButtonElement} */ +        this._profileAddButton = querySelectorNotNull(document, '#profile-add-button'); +        /** @type {HTMLButtonElement} */ +        this._profileRemoveConfirmButton = querySelectorNotNull(document, '#profile-remove-confirm-button'); +        /** @type {HTMLButtonElement} */ +        this._profileCopyConfirmButton = querySelectorNotNull(document, '#profile-copy-confirm-button'); +        /** @type {HTMLElement} */ +        this._profileEntryListContainer = querySelectorNotNull(document, '#profile-entry-list'); +        /** @type {HTMLElement} */ +        this._profileConditionsProfileName = querySelectorNotNull(document, '#profile-conditions-profile-name');          /** @type {?import('./modal.js').Modal} */          this._profileRemoveModal = null;          /** @type {?import('./modal.js').Modal} */ @@ -83,15 +84,6 @@ export class ProfileController {          const {platform: {os}} = await yomitan.api.getEnvironmentInfo();          this._profileConditionsUI.os = os; -        this._profileActiveSelect = /** @type {HTMLSelectElement} */ (document.querySelector('#profile-active-select')); -        this._profileTargetSelect = /** @type {HTMLSelectElement} */ (document.querySelector('#profile-target-select')); -        this._profileCopySourceSelect = /** @type {HTMLSelectElement} */ (document.querySelector('#profile-copy-source-select')); -        this._removeProfileNameElement = /** @type {HTMLElement} */ (document.querySelector('#profile-remove-name')); -        this._profileAddButton = /** @type {HTMLButtonElement} */ (document.querySelector('#profile-add-button')); -        this._profileRemoveConfirmButton = /** @type {HTMLButtonElement} */ (document.querySelector('#profile-remove-confirm-button')); -        this._profileCopyConfirmButton = /** @type {HTMLButtonElement} */ (document.querySelector('#profile-copy-confirm-button')); -        this._profileEntryListContainer = /** @type {HTMLElement} */ (document.querySelector('#profile-entry-list')); -        this._profileConditionsProfileName = /** @type {HTMLElement} */ (document.querySelector('#profile-conditions-profile-name'));          this._profileRemoveModal = this._modalController.getModal('profile-remove');          this._profileCopyModal = this._modalController.getModal('profile-copy');          this._profileConditionsModal = this._modalController.getModal('profile-conditions'); @@ -662,15 +654,15 @@ class ProfileEntry {          /** @type {number} */          this._index = index;          /** @type {HTMLInputElement} */ -        this._isDefaultRadio = /** @type {HTMLInputElement} */ (node.querySelector('.profile-entry-is-default-radio')); +        this._isDefaultRadio = querySelectorNotNull(node, '.profile-entry-is-default-radio');          /** @type {HTMLInputElement} */ -        this._nameInput = /** @type {HTMLInputElement} */ (node.querySelector('.profile-entry-name-input')); +        this._nameInput = querySelectorNotNull(node, '.profile-entry-name-input');          /** @type {HTMLElement} */ -        this._countLink = /** @type {HTMLElement} */ (node.querySelector('.profile-entry-condition-count-link')); +        this._countLink = querySelectorNotNull(node, '.profile-entry-condition-count-link');          /** @type {HTMLElement} */ -        this._countText = /** @type {HTMLElement} */ (node.querySelector('.profile-entry-condition-count')); +        this._countText = querySelectorNotNull(node, '.profile-entry-condition-count');          /** @type {HTMLButtonElement} */ -        this._menuButton = /** @type {HTMLButtonElement} */ (node.querySelector('.profile-entry-menu-button')); +        this._menuButton = querySelectorNotNull(node, '.profile-entry-menu-button');          /** @type {EventListenerCollection} */          this._eventListeners = new EventListenerCollection();      } diff --git a/ext/js/pages/settings/scan-inputs-controller.js b/ext/js/pages/settings/scan-inputs-controller.js index 53423bdc..eb526863 100644 --- a/ext/js/pages/settings/scan-inputs-controller.js +++ b/ext/js/pages/settings/scan-inputs-controller.js @@ -18,6 +18,7 @@  import {EventListenerCollection} from '../../core.js';  import {DocumentUtil} from '../../dom/document-util.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {yomitan} from '../../yomitan.js';  import {KeyboardMouseInputField} from './keyboard-mouse-input-field.js'; @@ -30,10 +31,10 @@ export class ScanInputsController {          this._settingsController = settingsController;          /** @type {?import('environment').OperatingSystem} */          this._os = null; -        /** @type {?HTMLElement} */ -        this._container = null; -        /** @type {?HTMLButtonElement} */ -        this._addButton = null; +        /** @type {HTMLElement} */ +        this._container = querySelectorNotNull(document, '#scan-input-list'); +        /** @type {HTMLButtonElement} */ +        this._addButton = querySelectorNotNull(document, '#scan-input-add');          /** @type {?NodeListOf<HTMLElement>} */          this._scanningInputCountNodes = null;          /** @type {ScanInputField[]} */ @@ -45,8 +46,6 @@ export class ScanInputsController {          const {platform: {os}} = await yomitan.api.getEnvironmentInfo();          this._os = os; -        this._container = /** @type {HTMLElement} */ (document.querySelector('#scan-input-list')); -        this._addButton = /** @type {HTMLButtonElement} */ (document.querySelector('#scan-input-add'));          this._scanningInputCountNodes = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.scanning-input-count'));          this._addButton.addEventListener('click', this._onAddButtonClick.bind(this), false); @@ -157,7 +156,8 @@ export class ScanInputsController {          // Scroll to bottom          const button = /** @type {HTMLElement} */ (e.currentTarget);          const modalContainer = /** @type {HTMLElement} */ (button.closest('.modal')); -        const scrollContainer = /** @type {HTMLElement} */ (modalContainer.querySelector('.modal-body')); +        /** @type {HTMLElement} */ +        const scrollContainer = querySelectorNotNull(modalContainer, '.modal-body');          scrollContainer.scrollTop = scrollContainer.scrollHeight;      } @@ -265,12 +265,16 @@ class ScanInputField {          const {include, exclude, options: {showAdvanced}} = scanningInput;          const node = /** @type {HTMLElement} */ (this._parent.instantiateTemplate('scan-input')); -        const includeInputNode = /** @type {HTMLInputElement} */ (node.querySelector('.scan-input-field[data-property=include]')); -        const includeMouseButton = /** @type {HTMLButtonElement} */ (node.querySelector('.mouse-button[data-property=include]')); -        const excludeInputNode = /** @type {HTMLInputElement} */ (node.querySelector('.scan-input-field[data-property=exclude]')); -        const excludeMouseButton = /** @type {HTMLButtonElement} */ (node.querySelector('.mouse-button[data-property=exclude]')); -        const removeButton = /** @type {HTMLButtonElement} */ (node.querySelector('.scan-input-remove')); -        const menuButton = /** @type {HTMLButtonElement} */ (node.querySelector('.scanning-input-menu-button')); +        /** @type {HTMLInputElement} */ +        const includeInputNode = querySelectorNotNull(node, '.scan-input-field[data-property=include]'); +        /** @type {HTMLButtonElement} */ +        const includeMouseButton = querySelectorNotNull(node, '.mouse-button[data-property=include]'); +        /** @type {HTMLInputElement} */ +        const excludeInputNode = querySelectorNotNull(node, '.scan-input-field[data-property=exclude]'); +        /** @type {HTMLButtonElement} */ +        const excludeMouseButton = querySelectorNotNull(node, '.mouse-button[data-property=exclude]'); +        /** @type {HTMLButtonElement} */ +        const menuButton = querySelectorNotNull(node, '.scanning-input-menu-button');          node.dataset.showAdvanced = `${showAdvanced}`; @@ -285,13 +289,8 @@ class ScanInputField {          this._eventListeners.on(this._includeInputField, 'change', this._onIncludeValueChange.bind(this));          this._eventListeners.on(this._excludeInputField, 'change', this._onExcludeValueChange.bind(this)); -        if (removeButton !== null) { -            this._eventListeners.addEventListener(removeButton, 'click', this._onRemoveClick.bind(this)); -        } -        if (menuButton !== null) { -            this._eventListeners.addEventListener(menuButton, 'menuOpen', this._onMenuOpen.bind(this)); -            this._eventListeners.addEventListener(menuButton, 'menuClose', this._onMenuClose.bind(this)); -        } +        this._eventListeners.addEventListener(menuButton, 'menuOpen', this._onMenuOpen.bind(this)); +        this._eventListeners.addEventListener(menuButton, 'menuClose', this._onMenuClose.bind(this));          this._updateDataSettingTargets();      } @@ -341,8 +340,10 @@ class ScanInputField {       */      _onMenuOpen(e) {          const bodyNode = e.detail.menu.bodyNode; -        const showAdvanced = /** @type {?HTMLElement} */ (bodyNode.querySelector('.popup-menu-item[data-menu-action="showAdvanced"]')); -        const hideAdvanced = /** @type {?HTMLElement} */ (bodyNode.querySelector('.popup-menu-item[data-menu-action="hideAdvanced"]')); +        /** @type {?HTMLElement} */ +        const showAdvanced = bodyNode.querySelector('.popup-menu-item[data-menu-action="showAdvanced"]'); +        /** @type {?HTMLElement} */ +        const hideAdvanced = bodyNode.querySelector('.popup-menu-item[data-menu-action="hideAdvanced"]');          const advancedVisible = (this._node !== null && this._node.dataset.showAdvanced === 'true');          if (showAdvanced !== null) {              showAdvanced.hidden = advancedVisible; diff --git a/ext/js/pages/settings/scan-inputs-simple-controller.js b/ext/js/pages/settings/scan-inputs-simple-controller.js index 8d52af61..ddb68825 100644 --- a/ext/js/pages/settings/scan-inputs-simple-controller.js +++ b/ext/js/pages/settings/scan-inputs-simple-controller.js @@ -16,6 +16,7 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {HotkeyUtil} from '../../input/hotkey-util.js';  import {yomitan} from '../../yomitan.js';  import {ScanInputsController} from './scan-inputs-controller.js'; @@ -27,10 +28,10 @@ export class ScanInputsSimpleController {      constructor(settingsController) {          /** @type {import('./settings-controller.js').SettingsController} */          this._settingsController = settingsController; -        /** @type {?HTMLInputElement} */ -        this._middleMouseButtonScan = null; -        /** @type {?HTMLSelectElement} */ -        this._mainScanModifierKeyInput = null; +        /** @type {HTMLInputElement} */ +        this._middleMouseButtonScan = querySelectorNotNull(document, '#middle-mouse-button-scan'); +        /** @type {HTMLSelectElement} */ +        this._mainScanModifierKeyInput = querySelectorNotNull(document, '#main-scan-modifier-key');          /** @type {boolean} */          this._mainScanModifierKeyInputHasOther = false;          /** @type {HotkeyUtil} */ @@ -39,9 +40,6 @@ export class ScanInputsSimpleController {      /** */      async prepare() { -        this._middleMouseButtonScan = /** @type {HTMLInputElement} */ (document.querySelector('#middle-mouse-button-scan')); -        this._mainScanModifierKeyInput = /** @type {HTMLSelectElement} */ (document.querySelector('#main-scan-modifier-key')); -          const {platform: {os}} = await yomitan.api.getEnvironmentInfo();          this._hotkeyUtil.os = os; diff --git a/ext/js/pages/settings/secondary-search-dictionary-controller.js b/ext/js/pages/settings/secondary-search-dictionary-controller.js index b20bd475..f24f6ea3 100644 --- a/ext/js/pages/settings/secondary-search-dictionary-controller.js +++ b/ext/js/pages/settings/secondary-search-dictionary-controller.js @@ -17,6 +17,7 @@   */  import {EventListenerCollection} from '../../core.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {yomitan} from '../../yomitan.js';  export class SecondarySearchDictionaryController { @@ -32,14 +33,12 @@ export class SecondarySearchDictionaryController {          this._dictionaryInfoMap = new Map();          /** @type {EventListenerCollection} */          this._eventListeners = new EventListenerCollection(); -        /** @type {?HTMLElement} */ -        this._container = null; +        /** @type {HTMLElement} */ +        this._container = querySelectorNotNull(document, '#secondary-search-dictionary-list');      }      /** */      async prepare() { -        this._container = /** @type {HTMLElement} */ (document.querySelector('#secondary-search-dictionary-list')); -          await this._onDatabaseUpdated();          yomitan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); @@ -83,13 +82,16 @@ export class SecondarySearchDictionaryController {              const node = /** @type {HTMLElement} */ (this._settingsController.instantiateTemplate('secondary-search-dictionary'));              fragment.appendChild(node); -            const nameNode = /** @type {HTMLElement} */ (node.querySelector('.dictionary-title')); +            /** @type {HTMLElement} */ +            const nameNode = querySelectorNotNull(node, '.dictionary-title');              nameNode.textContent = name; -            const versionNode = /** @type {HTMLElement} */ (node.querySelector('.dictionary-version')); +            /** @type {HTMLElement} */ +            const versionNode = querySelectorNotNull(node, '.dictionary-version');              versionNode.textContent = `rev.${dictionaryInfo.revision}`; -            const toggle = /** @type {HTMLElement} */ (node.querySelector('.dictionary-allow-secondary-searches')); +            /** @type {HTMLElement} */ +            const toggle = querySelectorNotNull(node, '.dictionary-allow-secondary-searches');              toggle.dataset.setting = `dictionaries[${i}].allowSecondarySearches`;              this._eventListeners.addEventListener(toggle, 'settingChanged', this._onEnabledChanged.bind(this, node), false);          } diff --git a/ext/js/pages/settings/sentence-termination-characters-controller.js b/ext/js/pages/settings/sentence-termination-characters-controller.js index 80c4cdbe..7fd90b28 100644 --- a/ext/js/pages/settings/sentence-termination-characters-controller.js +++ b/ext/js/pages/settings/sentence-termination-characters-controller.js @@ -17,6 +17,7 @@   */  import {EventListenerCollection} from '../../core.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  export class SentenceTerminationCharactersController {      /** @@ -27,16 +28,16 @@ export class SentenceTerminationCharactersController {          this._settingsController = settingsController;          /** @type {SentenceTerminationCharacterEntry[]} */          this._entries = []; -        /** @type {?HTMLButtonElement} */ -        this._addButton = null; -        /** @type {?HTMLButtonElement} */ -        this._resetButton = null; -        /** @type {?HTMLElement} */ -        this._listTable = null; -        /** @type {?HTMLElement} */ -        this._listContainer = null; -        /** @type {?HTMLElement} */ -        this._emptyIndicator = null; +        /** @type {HTMLButtonElement} */ +        this._addButton = querySelectorNotNull(document, '#sentence-termination-character-list-add'); +        /** @type {HTMLButtonElement} */ +        this._resetButton = querySelectorNotNull(document, '#sentence-termination-character-list-reset'); +        /** @type {HTMLElement} */ +        this._listTable = querySelectorNotNull(document, '#sentence-termination-character-list-table'); +        /** @type {HTMLElement} */ +        this._listContainer = querySelectorNotNull(document, '#sentence-termination-character-list'); +        /** @type {HTMLElement} */ +        this._emptyIndicator = querySelectorNotNull(document, '#sentence-termination-character-list-empty');      }      /** @type {import('./settings-controller.js').SettingsController} */ @@ -46,12 +47,6 @@ export class SentenceTerminationCharactersController {      /** */      async prepare() { -        this._addButton = /** @type {HTMLButtonElement} */ (document.querySelector('#sentence-termination-character-list-add')); -        this._resetButton = /** @type {HTMLButtonElement} */ (document.querySelector('#sentence-termination-character-list-reset')); -        this._listTable = /** @type {HTMLElement} */ (document.querySelector('#sentence-termination-character-list-table')); -        this._listContainer = /** @type {HTMLElement} */ (document.querySelector('#sentence-termination-character-list')); -        this._emptyIndicator = /** @type {HTMLElement} */ (document.querySelector('#sentence-termination-character-list-empty')); -          this._addButton.addEventListener('click', this._onAddClick.bind(this));          this._resetButton.addEventListener('click', this._onResetClick.bind(this));          this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); @@ -209,13 +204,20 @@ class SentenceTerminationCharacterEntry {          const {enabled, character1, character2, includeCharacterAtStart, includeCharacterAtEnd} = this._data;          const node = this._node; -        const enabledToggle = /** @type {HTMLInputElement} */ (node.querySelector('.sentence-termination-character-enabled')); -        const typeSelect = /** @type {HTMLSelectElement} */ (node.querySelector('.sentence-termination-character-type')); -        const character1Input = /** @type {HTMLInputElement} */ (node.querySelector('.sentence-termination-character-input1')); -        const character2Input = /** @type {HTMLInputElement} */ (node.querySelector('.sentence-termination-character-input2')); -        const includeAtStartCheckbox = /** @type {HTMLInputElement} */ (node.querySelector('.sentence-termination-character-include-at-start')); -        const includeAtEndheckbox = /** @type {HTMLInputElement} */ (node.querySelector('.sentence-termination-character-include-at-end')); -        const menuButton = /** @type {HTMLButtonElement} */ (node.querySelector('.sentence-termination-character-entry-button')); +        /** @type {HTMLInputElement} */ +        const enabledToggle = querySelectorNotNull(node, '.sentence-termination-character-enabled'); +        /** @type {HTMLSelectElement} */ +        const typeSelect = querySelectorNotNull(node, '.sentence-termination-character-type'); +        /** @type {HTMLInputElement} */ +        const character1Input = querySelectorNotNull(node, '.sentence-termination-character-input1'); +        /** @type {HTMLInputElement} */ +        const character2Input = querySelectorNotNull(node, '.sentence-termination-character-input2'); +        /** @type {HTMLInputElement} */ +        const includeAtStartCheckbox = querySelectorNotNull(node, '.sentence-termination-character-include-at-start'); +        /** @type {HTMLInputElement} */ +        const includeAtEndheckbox = querySelectorNotNull(node, '.sentence-termination-character-include-at-end'); +        /** @type {HTMLButtonElement} */ +        const menuButton = querySelectorNotNull(node, '.sentence-termination-character-entry-button');          this._character1Input = character1Input;          this._character2Input = character2Input; diff --git a/ext/js/pages/settings/settings-display-controller.js b/ext/js/pages/settings/settings-display-controller.js index 16e6cfae..e575a1cb 100644 --- a/ext/js/pages/settings/settings-display-controller.js +++ b/ext/js/pages/settings/settings-display-controller.js @@ -18,6 +18,7 @@  import {DocumentUtil} from '../../dom/document-util.js';  import {PopupMenu} from '../../dom/popup-menu.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {SelectorObserver} from '../../dom/selector-observer.js';  export class SettingsDisplayController { @@ -30,10 +31,10 @@ export class SettingsDisplayController {          this._settingsController = settingsController;          /** @type {import('./modal-controller.js').ModalController} */          this._modalController = modalController; -        /** @type {?HTMLElement} */ -        this._contentNode = null; -        /** @type {?HTMLElement} */ -        this._menuContainer = null; +        /** @type {HTMLElement} */ +        this._contentNode = querySelectorNotNull(document, '.content'); +        /** @type {HTMLElement} */ +        this._menuContainer = querySelectorNotNull(document, '#popup-menus');          /** @type {(event: MouseEvent) => void} */          this._onMoreToggleClickBind = this._onMoreToggleClick.bind(this);          /** @type {(event: MouseEvent) => void} */ @@ -42,9 +43,6 @@ export class SettingsDisplayController {      /** */      prepare() { -        this._contentNode = /** @type {HTMLElement} */ (document.querySelector('.content')); -        this._menuContainer = /** @type {HTMLElement} */ (document.querySelector('#popup-menus')); -          const onFabButtonClick = this._onFabButtonClick.bind(this);          for (const fabButton of /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.fab-button'))) {              fabButton.addEventListener('click', onFabButtonClick, false); @@ -156,7 +154,8 @@ export class SettingsDisplayController {          const container = this._getMoreContainer(node);          if (container === null) { return; } -        const more = /** @type {?HTMLElement} */ (container.querySelector('.more')); +        /** @type {?HTMLElement} */ +        const more = container.querySelector('.more');          if (more === null) { return; }          const moreVisible = more.hidden; diff --git a/ext/js/pages/settings/settings-main.js b/ext/js/pages/settings/settings-main.js index 1b9723e8..3f0dac3f 100644 --- a/ext/js/pages/settings/settings-main.js +++ b/ext/js/pages/settings/settings-main.js @@ -18,6 +18,7 @@  import {log} from '../../core.js';  import {DocumentFocusController} from '../../dom/document-focus-controller.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {yomitan} from '../../yomitan.js';  import {ExtensionContentController} from '../common/extension-content-controller.js';  import {AnkiController} from './anki-controller.js'; @@ -65,7 +66,9 @@ async function setupGenericSettingsController(genericSettingController) {          const extensionContentController = new ExtensionContentController();          extensionContentController.prepare(); -        const statusFooter = new StatusFooter(/** @type {HTMLElement} */ (document.querySelector('.status-footer-container'))); +        /** @type {HTMLElement} */ +        const statusFooterElement = querySelectorNotNull(document, '.status-footer-container'); +        const statusFooter = new StatusFooter(statusFooterElement);          statusFooter.prepare();          /** @type {?number} */ diff --git a/ext/js/pages/settings/sort-frequency-dictionary-controller.js b/ext/js/pages/settings/sort-frequency-dictionary-controller.js index e7759d95..2c56f023 100644 --- a/ext/js/pages/settings/sort-frequency-dictionary-controller.js +++ b/ext/js/pages/settings/sort-frequency-dictionary-controller.js @@ -16,6 +16,7 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {yomitan} from '../../yomitan.js';  export class SortFrequencyDictionaryController { @@ -25,25 +26,20 @@ export class SortFrequencyDictionaryController {      constructor(settingsController) {          /** @type {import('./settings-controller.js').SettingsController} */          this._settingsController = settingsController; -        /** @type {?HTMLSelectElement} */ -        this._sortFrequencyDictionarySelect = null; -        /** @type {?HTMLSelectElement} */ -        this._sortFrequencyDictionaryOrderSelect = null; -        /** @type {?HTMLButtonElement} */ -        this._sortFrequencyDictionaryOrderAutoButton = null; -        /** @type {?HTMLElement} */ -        this._sortFrequencyDictionaryOrderContainerNode = null; +        /** @type {HTMLSelectElement} */ +        this._sortFrequencyDictionarySelect = querySelectorNotNull(document, '#sort-frequency-dictionary'); +        /** @type {HTMLSelectElement} */ +        this._sortFrequencyDictionaryOrderSelect = querySelectorNotNull(document, '#sort-frequency-dictionary-order'); +        /** @type {HTMLButtonElement} */ +        this._sortFrequencyDictionaryOrderAutoButton = querySelectorNotNull(document, '#sort-frequency-dictionary-order-auto'); +        /** @type {HTMLElement} */ +        this._sortFrequencyDictionaryOrderContainerNode = querySelectorNotNull(document, '#sort-frequency-dictionary-order-container');          /** @type {?import('core').TokenObject} */          this._getDictionaryInfoToken = null;      }      /** */      async prepare() { -        this._sortFrequencyDictionarySelect = /** @type {HTMLSelectElement} */ (document.querySelector('#sort-frequency-dictionary')); -        this._sortFrequencyDictionaryOrderSelect = /** @type {HTMLSelectElement} */ (document.querySelector('#sort-frequency-dictionary-order')); -        this._sortFrequencyDictionaryOrderAutoButton = /** @type {HTMLButtonElement} */ (document.querySelector('#sort-frequency-dictionary-order-auto')); -        this._sortFrequencyDictionaryOrderContainerNode = /** @type {HTMLElement} */ (document.querySelector('#sort-frequency-dictionary-order-container')); -          await this._onDatabaseUpdated();          yomitan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); diff --git a/ext/js/pages/settings/status-footer.js b/ext/js/pages/settings/status-footer.js index a8f1a8c4..4830dbd5 100644 --- a/ext/js/pages/settings/status-footer.js +++ b/ext/js/pages/settings/status-footer.js @@ -17,6 +17,7 @@   */  import {PanelElement} from '../../dom/panel-element.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  export class StatusFooter extends PanelElement {      /** @@ -28,12 +29,13 @@ export class StatusFooter extends PanelElement {              closingAnimationDuration: 375 // Milliseconds; includes buffer          });          /** @type {HTMLElement} */ -        this._body = /** @type {HTMLElement} */ (node.querySelector('.status-footer')); +        this._body = querySelectorNotNull(node, '.status-footer');      }      /** */      prepare() { -        const closeButton = /** @type {HTMLElement} */ (this._body.querySelector('.status-footer-header-close')); +        /** @type {HTMLElement} */ +        const closeButton = querySelectorNotNull(this._body, '.status-footer-header-close');          this.on('closeCompleted', this._onCloseCompleted.bind(this));          closeButton.addEventListener('click', this._onCloseClick.bind(this), false);      } diff --git a/ext/js/pages/settings/storage-controller.js b/ext/js/pages/settings/storage-controller.js index 7f323b48..16e03786 100644 --- a/ext/js/pages/settings/storage-controller.js +++ b/ext/js/pages/settings/storage-controller.js @@ -16,6 +16,7 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {querySelectorNotNull} from '../../dom/query-selector.js';  import {yomitan} from '../../yomitan.js';  export class StorageController { @@ -53,7 +54,8 @@ export class StorageController {          this._storageUseInfiniteNodes = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.storage-use-infinite'));          this._storageUseValidNodes = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.storage-use-valid'));          this._storageUseInvalidNodes = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.storage-use-invalid')); -        const storageRefreshButton = /** @type {HTMLButtonElement} */ (document.querySelector('#storage-refresh')); +        /** @type {HTMLButtonElement} */ +        const storageRefreshButton = querySelectorNotNull(document, '#storage-refresh');          storageRefreshButton.addEventListener('click', this._onStorageRefreshButtonClick.bind(this), false);          yomitan.on('storageChanged', this._onStorageChanged.bind(this)); diff --git a/ext/js/pages/settings/translation-text-replacements-controller.js b/ext/js/pages/settings/translation-text-replacements-controller.js index 050db8d1..a54c3dd9 100644 --- a/ext/js/pages/settings/translation-text-replacements-controller.js +++ b/ext/js/pages/settings/translation-text-replacements-controller.js @@ -17,6 +17,7 @@   */  import {EventListenerCollection} from '../../core.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js';  export class TranslationTextReplacementsController {      /** @@ -25,16 +26,16 @@ export class TranslationTextReplacementsController {      constructor(settingsController) {          /** @type {import('./settings-controller.js').SettingsController} */          this._settingsController = settingsController; -        /** @type {?HTMLElement} */ -        this._entryContainer = null; +        /** @type {HTMLElement} */ +        this._entryContainer = querySelectorNotNull(document, '#translation-text-replacement-list');          /** @type {TranslationTextReplacementsEntry[]} */          this._entries = [];      }      /** */      async prepare() { -        this._entryContainer = /** @type {HTMLElement} */ (document.querySelector('#translation-text-replacement-list')); -        const addButton = /** @type {HTMLButtonElement} */ (document.querySelector('#translation-text-replacement-add')); +        /** @type {HTMLButtonElement} */ +        const addButton = querySelectorNotNull(document, '#translation-text-replacement-add');          addButton.addEventListener('click', this._onAdd.bind(this), false);          this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); @@ -179,12 +180,18 @@ class TranslationTextReplacementsEntry {      /** */      prepare() { -        const patternInput = /** @type {HTMLInputElement} */ (this._node.querySelector('.translation-text-replacement-pattern')); -        const replacementInput = /** @type {HTMLInputElement} */ (this._node.querySelector('.translation-text-replacement-replacement')); -        const ignoreCaseToggle = /** @type {HTMLInputElement} */ (this._node.querySelector('.translation-text-replacement-pattern-ignore-case')); -        const menuButton = /** @type {HTMLInputElement} */ (this._node.querySelector('.translation-text-replacement-button')); -        const testInput = /** @type {HTMLInputElement} */ (this._node.querySelector('.translation-text-replacement-test-input')); -        const testOutput = /** @type {HTMLInputElement} */ (this._node.querySelector('.translation-text-replacement-test-output')); +        /** @type {HTMLInputElement} */ +        const patternInput = querySelectorNotNull(this._node, '.translation-text-replacement-pattern'); +        /** @type {HTMLInputElement} */ +        const replacementInput = querySelectorNotNull(this._node, '.translation-text-replacement-replacement'); +        /** @type {HTMLInputElement} */ +        const ignoreCaseToggle = querySelectorNotNull(this._node, '.translation-text-replacement-pattern-ignore-case'); +        /** @type {HTMLInputElement} */ +        const menuButton = querySelectorNotNull(this._node, '.translation-text-replacement-button'); +        /** @type {HTMLInputElement} */ +        const testInput = querySelectorNotNull(this._node, '.translation-text-replacement-test-input'); +        /** @type {HTMLInputElement} */ +        const testOutput = querySelectorNotNull(this._node, '.translation-text-replacement-test-output');          this._patternInput = patternInput;          this._replacementInput = replacementInput; @@ -221,8 +228,12 @@ class TranslationTextReplacementsEntry {      _onMenuOpen(e) {          const bodyNode = e.detail.menu.bodyNode;          const testVisible = this._isTestVisible(); -        /** @type {HTMLElement} */ (bodyNode.querySelector('[data-menu-action=showTest]')).hidden = testVisible; -        /** @type {HTMLElement} */ (bodyNode.querySelector('[data-menu-action=hideTest]')).hidden = !testVisible; +        /** @type {HTMLElement} */ +        const element1 = querySelectorNotNull(bodyNode, '[data-menu-action=showTest]'); +        /** @type {HTMLElement} */ +        const element2 = querySelectorNotNull(bodyNode, '[data-menu-action=hideTest]'); +        element1.hidden = testVisible; +        element2.hidden = !testVisible;      }      /** diff --git a/ext/js/pages/welcome-main.js b/ext/js/pages/welcome-main.js index c034aae1..d208e996 100644 --- a/ext/js/pages/welcome-main.js +++ b/ext/js/pages/welcome-main.js @@ -18,6 +18,7 @@  import {log} from '../core.js';  import {DocumentFocusController} from '../dom/document-focus-controller.js'; +import {querySelectorNotNull} from '../dom/query-selector.js';  import {yomitan} from '../yomitan.js';  import {ExtensionContentController} from './common/extension-content-controller.js';  import {DictionaryController} from './settings/dictionary-controller.js'; @@ -55,7 +56,9 @@ async function setupGenericSettingsController(genericSettingController) {          const extensionContentController = new ExtensionContentController();          extensionContentController.prepare(); -        const statusFooter = new StatusFooter(/** @type {HTMLElement} */ (document.querySelector('.status-footer-container'))); +        /** @type {HTMLElement} */ +        const statusFooterElement = querySelectorNotNull(document, '.status-footer-container'); +        const statusFooter = new StatusFooter(statusFooterElement);          statusFooter.prepare();          await yomitan.prepare(); |