diff options
Diffstat (limited to 'ext/js')
42 files changed, 609 insertions, 498 deletions
| diff --git a/ext/js/display/display-anki.js b/ext/js/display/display-anki.js index 574e90ee..81791285 100644 --- a/ext/js/display/display-anki.js +++ b/ext/js/display/display-anki.js @@ -20,6 +20,7 @@ import {EventListenerCollection, deferPromise} from '../core.js';  import {AnkiNoteBuilder} from '../data/anki-note-builder.js';  import {AnkiUtil} from '../data/anki-util.js';  import {PopupMenu} from '../dom/popup-menu.js'; +import {querySelectorNotNull} from '../dom/query-selector.js';  import {yomitan} from '../yomitan.js';  export class DisplayAnki { @@ -91,7 +92,7 @@ export class DisplayAnki {              ['term', ['term-kanji', 'term-kana']]          ]);          /** @type {HTMLElement} */ -        this._menuContainer = /** @type {HTMLElement} */ (document.querySelector('#popup-menus')); +        this._menuContainer = querySelectorNotNull(document, '#popup-menus');          /** @type {(event: MouseEvent) => void} */          this._onShowTagsBind = this._onShowTags.bind(this);          /** @type {(event: MouseEvent) => void} */ @@ -827,7 +828,8 @@ export class DisplayAnki {          button.hidden = disabled;          button.dataset.noteIds = allNoteIds.join(' '); -        const badge = /** @type {?HTMLElement} */ (button.querySelector('.action-button-badge')); +        /** @type {?HTMLElement} */ +        const badge = button.querySelector('.action-button-badge');          if (badge !== null) {              const badgeData = badge.dataset;              if (allNoteIds.length > 1) { @@ -866,13 +868,17 @@ export class DisplayAnki {          const noteIds = this._getNodeNoteIds(node);          if (noteIds.length === 0) { return; } -        const menuContainerNode = /** @type {HTMLElement} */ (this._display.displayGenerator.instantiateTemplate('view-note-button-popup-menu')); -        const menuBodyNode = /** @type {HTMLElement} */ (menuContainerNode.querySelector('.popup-menu-body')); +        /** @type {HTMLElement} */ +        const menuContainerNode = this._display.displayGenerator.instantiateTemplate('view-note-button-popup-menu'); +        /** @type {HTMLElement} */ +        const menuBodyNode = querySelectorNotNull(menuContainerNode, '.popup-menu-body');          for (let i = 0, ii = noteIds.length; i < ii; ++i) {              const noteId = noteIds[i]; -            const item = /** @type {HTMLElement} */ (this._display.displayGenerator.instantiateTemplate('view-note-button-popup-menu-item')); -            const label = /** @type {Element} */ (item.querySelector('.popup-menu-item-label')); +            /** @type {HTMLElement} */ +            const item = this._display.displayGenerator.instantiateTemplate('view-note-button-popup-menu-item'); +            /** @type {Element} */ +            const label = querySelectorNotNull(item, '.popup-menu-item-label');              label.textContent = `Note ${i + 1}: ${noteId}`;              item.dataset.menuAction = 'viewNote';              item.dataset.noteIds = `${noteId}`; diff --git a/ext/js/display/display-audio.js b/ext/js/display/display-audio.js index 3576decb..1f279030 100644 --- a/ext/js/display/display-audio.js +++ b/ext/js/display/display-audio.js @@ -18,6 +18,7 @@  import {EventListenerCollection} from '../core.js';  import {PopupMenu} from '../dom/popup-menu.js'; +import {querySelectorNotNull} from '../dom/query-selector.js';  import {AudioSystem} from '../media/audio-system.js';  import {yomitan} from '../yomitan.js'; @@ -45,7 +46,7 @@ export class DisplayAudio {          /** @type {Map<string, import('display-audio').CacheItem>} */          this._cache = new Map();          /** @type {Element} */ -        this._menuContainer = /** @type {Element} */ (document.querySelector('#popup-menus')); +        this._menuContainer = querySelectorNotNull(document, '#popup-menus');          /** @type {import('core').TokenObject} */          this._entriesToken = {};          /** @type {Set<PopupMenu>} */ @@ -715,7 +716,8 @@ export class DisplayAudio {              button.dataset.potentialAvailableAudioCount = `${potentialAvailableAudioCount}`;          } -        const badge = /** @type {?HTMLElement} */ (button.querySelector('.action-button-badge')); +        /** @type {?HTMLElement} */ +        const badge = button.querySelector('.action-button-badge');          if (badge === null) { return; }          const badgeData = badge.dataset; @@ -804,7 +806,8 @@ export class DisplayAudio {      _createMenu(sourceButton, term, reading) {          // Create menu          const menuContainerNode = /** @type {HTMLElement} */ (this._display.displayGenerator.instantiateTemplate('audio-button-popup-menu')); -        const menuBodyNode = /** @type {HTMLElement} */ (menuContainerNode.querySelector('.popup-menu-body')); +        /** @type {HTMLElement} */ +        const menuBodyNode = querySelectorNotNull(menuContainerNode, '.popup-menu-body');          menuContainerNode.dataset.term = term;          menuContainerNode.dataset.reading = reading; @@ -837,7 +840,8 @@ export class DisplayAudio {                  const existingNode = this._getOrCreateMenuItem(currentItems, index, subIndex);                  const node = existingNode !== null ? existingNode : /** @type {HTMLElement} */ (displayGenerator.instantiateTemplate('audio-button-popup-menu-item')); -                const labelNode = /** @type {HTMLElement} */ (node.querySelector('.popup-menu-item-audio-button .popup-menu-item-label')); +                /** @type {HTMLElement} */ +                const labelNode = querySelectorNotNull(node, '.popup-menu-item-audio-button .popup-menu-item-label');                  let label = name;                  if (!nameUnique) {                      label = `${label} ${nameIndex + 1}`; @@ -847,11 +851,13 @@ export class DisplayAudio {                  if (typeof subName === 'string' && subName.length > 0) { label += `: ${subName}`; }                  labelNode.textContent = label; -                const cardButton = /** @type {HTMLElement} */ (node.querySelector('.popup-menu-item-set-primary-audio-button')); +                /** @type {HTMLElement} */ +                const cardButton = querySelectorNotNull(node, '.popup-menu-item-set-primary-audio-button');                  cardButton.hidden = !downloadable;                  if (valid !== null) { -                    const icon = /** @type {HTMLElement} */ (node.querySelector('.popup-menu-item-audio-button .popup-menu-item-icon')); +                    /** @type {HTMLElement} */ +                    const icon = querySelectorNotNull(node, '.popup-menu-item-audio-button .popup-menu-item-icon');                      icon.dataset.icon = valid ? 'checkmark' : 'cross';                      showIcons = true;                  } diff --git a/ext/js/display/display-notification.js b/ext/js/display/display-notification.js index d1cfa537..a0e9e35c 100644 --- a/ext/js/display/display-notification.js +++ b/ext/js/display/display-notification.js @@ -17,6 +17,7 @@   */  import {EventListenerCollection} from '../core.js'; +import {querySelectorNotNull} from '../dom/query-selector.js';  export class DisplayNotification {      /** @@ -29,9 +30,9 @@ export class DisplayNotification {          /** @type {HTMLElement} */          this._node = node;          /** @type {HTMLElement} */ -        this._body = /** @type {HTMLElement} */ (node.querySelector('.footer-notification-body')); +        this._body = querySelectorNotNull(node, '.footer-notification-body');          /** @type {HTMLElement} */ -        this._closeButton = /** @type {HTMLElement} */ (node.querySelector('.footer-notification-close-button')); +        this._closeButton = querySelectorNotNull(node, '.footer-notification-close-button');          /** @type {EventListenerCollection} */          this._eventListeners = new EventListenerCollection();          /** @type {?import('core').Timeout} */ diff --git a/ext/js/display/display-profile-selection.js b/ext/js/display/display-profile-selection.js index c5cb7d06..f2ebd3e4 100644 --- a/ext/js/display/display-profile-selection.js +++ b/ext/js/display/display-profile-selection.js @@ -18,6 +18,7 @@  import {EventListenerCollection, generateId} from '../core.js';  import {PanelElement} from '../dom/panel-element.js'; +import {querySelectorNotNull} from '../dom/query-selector.js';  import {yomitan} from '../yomitan.js';  export class DisplayProfileSelection { @@ -28,12 +29,14 @@ export class DisplayProfileSelection {          /** @type {import('./display.js').Display} */          this._display = display;          /** @type {HTMLElement} */ -        this._profielList = /** @type {HTMLElement} */ (document.querySelector('#profile-list')); +        this._profielList = querySelectorNotNull(document, '#profile-list');          /** @type {HTMLButtonElement} */ -        this._profileButton = /** @type {HTMLButtonElement} */ (document.querySelector('#profile-button')); +        this._profileButton = querySelectorNotNull(document, '#profile-button'); +        /** @type {HTMLElement} */ +        const profilePanelElement = querySelectorNotNull(document, '#profile-panel');          /** @type {PanelElement} */          this._profilePanel = new PanelElement({ -            node: /** @type {HTMLElement} */ (document.querySelector('#profile-panel')), +            node: profilePanelElement,              closingAnimationDuration: 375 // Milliseconds; includes buffer          });          /** @type {boolean} */ @@ -98,9 +101,11 @@ export class DisplayProfileSelection {          for (let i = 0, ii = profiles.length; i < ii; ++i) {              const {name} = profiles[i];              const entry = displayGenerator.createProfileListItem(); -            const radio = /** @type {HTMLInputElement} */ (entry.querySelector('.profile-entry-is-default-radio')); +            /** @type {HTMLInputElement} */ +            const radio = querySelectorNotNull(entry, '.profile-entry-is-default-radio');              radio.checked = (i === profileCurrent); -            const nameNode = /** @type {Element} */ (entry.querySelector('.profile-list-item-name')); +            /** @type {Element} */ +            const nameNode = querySelectorNotNull(entry, '.profile-list-item-name');              nameNode.textContent = name;              fragment.appendChild(entry);              this._eventListeners.addEventListener(radio, 'change', this._onProfileRadioChange.bind(this, i), false); diff --git a/ext/js/display/display.js b/ext/js/display/display.js index 9c2d97f2..b6a818ba 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -22,6 +22,7 @@ import {ThemeController} from '../app/theme-controller.js';  import {FrameEndpoint} from '../comm/frame-endpoint.js';  import {DynamicProperty, EventDispatcher, EventListenerCollection, clone, deepEqual, invokeMessageHandler, log, promiseTimeout} from '../core.js';  import {PopupMenu} from '../dom/popup-menu.js'; +import {querySelectorNotNull} from '../dom/query-selector.js';  import {ScrollElement} from '../dom/scroll-element.js';  import {HotkeyHelpController} from '../input/hotkey-help-controller.js';  import {TextScanner} from '../language/text-scanner.js'; @@ -62,7 +63,7 @@ export class Display extends EventDispatcher {          /** @type {import('../input/hotkey-handler.js').HotkeyHandler} */          this._hotkeyHandler = hotkeyHandler;          /** @type {HTMLElement} */ -        this._container = /** @type {HTMLElement} */ (document.querySelector('#dictionary-entries')); +        this._container = querySelectorNotNull(document, '#dictionary-entries');          /** @type {import('dictionary').DictionaryEntry[]} */          this._dictionaryEntries = [];          /** @type {HTMLElement[]} */ @@ -116,7 +117,7 @@ export class Display extends EventDispatcher {          /** @type {number} */          this._queryOffset = 0;          /** @type {HTMLElement} */ -        this._progressIndicator = /** @type {HTMLElement} */ (document.querySelector('#progress-indicator')); +        this._progressIndicator = querySelectorNotNull(document, '#progress-indicator');          /** @type {?import('core').Timeout} */          this._progressIndicatorTimer = null;          /** @type {DynamicProperty<boolean>} */ @@ -126,24 +127,24 @@ export class Display extends EventDispatcher {          /** @type {?boolean} */          this._queryParserVisibleOverride = null;          /** @type {HTMLElement} */ -        this._queryParserContainer = /** @type {HTMLElement} */ (document.querySelector('#query-parser-container')); +        this._queryParserContainer = querySelectorNotNull(document, '#query-parser-container');          /** @type {QueryParser} */          this._queryParser = new QueryParser({              getSearchContext: this._getSearchContext.bind(this),              japaneseUtil          });          /** @type {HTMLElement} */ -        this._contentScrollElement = /** @type {HTMLElement} */ (document.querySelector('#content-scroll')); +        this._contentScrollElement = querySelectorNotNull(document, '#content-scroll');          /** @type {HTMLElement} */ -        this._contentScrollBodyElement = /** @type {HTMLElement} */ (document.querySelector('#content-body')); +        this._contentScrollBodyElement = querySelectorNotNull(document, '#content-body');          /** @type {ScrollElement} */          this._windowScroll = new ScrollElement(this._contentScrollElement); -        /** @type {HTMLButtonElement} */ -        this._closeButton = /** @type {HTMLButtonElement} */ (document.querySelector('#close-button')); -        /** @type {HTMLButtonElement} */ -        this._navigationPreviousButton = /** @type {HTMLButtonElement} */ (document.querySelector('#navigate-previous-button')); -        /** @type {HTMLButtonElement} */ -        this._navigationNextButton = /** @type {HTMLButtonElement} */ (document.querySelector('#navigate-next-button')); +        /** @type {?HTMLButtonElement} */ +        this._closeButton = document.querySelector('#close-button'); +        /** @type {?HTMLButtonElement} */ +        this._navigationPreviousButton = document.querySelector('#navigate-previous-button'); +        /** @type {?HTMLButtonElement} */ +        this._navigationNextButton = document.querySelector('#navigate-next-button');          /** @type {?Frontend} */          this._frontend = null;          /** @type {?Promise<void>} */ @@ -171,7 +172,7 @@ export class Display extends EventDispatcher {          /** @type {?import('./display-notification.js').DisplayNotification} */          this._tagNotification = null;          /** @type {HTMLElement} */ -        this._footerNotificationContainer = /** @type {HTMLElement} */ (document.querySelector('#content-footer')); +        this._footerNotificationContainer = querySelectorNotNull(document, '#content-footer');          /** @type {OptionToggleHotkeyHandler} */          this._optionToggleHotkeyHandler = new OptionToggleHotkeyHandler(this);          /** @type {ElementOverflowController} */ @@ -179,7 +180,7 @@ export class Display extends EventDispatcher {          /** @type {boolean} */          this._frameVisible = (pageType === 'search');          /** @type {HTMLElement} */ -        this._menuContainer = /** @type {HTMLElement} */ (document.querySelector('#popup-menus')); +        this._menuContainer = querySelectorNotNull(document, '#popup-menus');          /** @type {(event: MouseEvent) => void} */          this._onEntryClickBind = this._onEntryClick.bind(this);          /** @type {(event: MouseEvent) => void} */ @@ -1041,7 +1042,8 @@ export class Display extends EventDispatcher {          const node = /** @type {HTMLElement} */ (e.currentTarget);          const menuContainerNode = /** @type {HTMLElement} */ (this._displayGenerator.instantiateTemplate('dictionary-entry-popup-menu')); -        const menuBodyNode = /** @type {HTMLElement} */ (menuContainerNode.querySelector('.popup-menu-body')); +        /** @type {HTMLElement} */ +        const menuBodyNode = querySelectorNotNull(menuContainerNode, '.popup-menu-body');          /**           * @param {string} menuAction @@ -1049,7 +1051,9 @@ export class Display extends EventDispatcher {           */          const addItem = (menuAction, label) => {              const item = /** @type {HTMLElement} */ (this._displayGenerator.instantiateTemplate('dictionary-entry-popup-menu-item')); -            /** @type {HTMLElement} */ (item.querySelector('.popup-menu-item-label')).textContent = label; +            /** @type {HTMLElement} */ +            const labelElement = querySelectorNotNull(item, '.popup-menu-item-label'); +            labelElement.textContent = label;              item.dataset.menuAction = menuAction;              menuBodyNode.appendChild(item);          }; @@ -1291,7 +1295,8 @@ export class Display extends EventDispatcher {      /** */      _setContentExtensionUnloaded() { -        const errorExtensionUnloaded = /** @type {?HTMLElement} */ (document.querySelector('#error-extension-unloaded')); +        /** @type {?HTMLElement} */ +        const errorExtensionUnloaded = document.querySelector('#error-extension-unloaded');          if (this._container !== null) {              this._container.hidden = true; @@ -1323,7 +1328,8 @@ export class Display extends EventDispatcher {       * @param {boolean} visible       */      _setNoContentVisible(visible) { -        const noResults = /** @type {?HTMLElement} */ (document.querySelector('#no-results')); +        /** @type {?HTMLElement} */ +        const noResults = document.querySelector('#no-results');          if (noResults !== null) {              noResults.hidden = !visible; diff --git a/ext/js/display/query-parser.js b/ext/js/display/query-parser.js index 03b54fd5..e71c7251 100644 --- a/ext/js/display/query-parser.js +++ b/ext/js/display/query-parser.js @@ -17,6 +17,7 @@   */  import {EventDispatcher, log} from '../core.js'; +import {querySelectorNotNull} from '../dom/query-selector.js';  import {TextScanner} from '../language/text-scanner.js';  import {yomitan} from '../yomitan.js'; @@ -50,11 +51,11 @@ export class QueryParser extends EventDispatcher {          /** @type {import('api').ParseTextResult} */          this._parseResults = [];          /** @type {HTMLElement} */ -        this._queryParser = /** @type {HTMLElement} */ (document.querySelector('#query-parser-content')); +        this._queryParser = querySelectorNotNull(document, '#query-parser-content');          /** @type {HTMLElement} */ -        this._queryParserModeContainer = /** @type {HTMLElement} */ (document.querySelector('#query-parser-mode-container')); +        this._queryParserModeContainer = querySelectorNotNull(document, '#query-parser-mode-container');          /** @type {HTMLSelectElement} */ -        this._queryParserModeSelect = /** @type {HTMLSelectElement} */ (document.querySelector('#query-parser-mode-select')); +        this._queryParserModeSelect = querySelectorNotNull(document, '#query-parser-mode-select');          /** @type {TextScanner} */          this._textScanner = new TextScanner({              node: this._queryParser, diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index 2778c4cd..b512a16d 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -19,6 +19,7 @@  import * as wanakana from '../../lib/wanakana.js';  import {ClipboardMonitor} from '../comm/clipboard-monitor.js';  import {EventListenerCollection, invokeMessageHandler} from '../core.js'; +import {querySelectorNotNull} from '../dom/query-selector.js';  import {yomitan} from '../yomitan.js';  export class SearchDisplayController { @@ -42,17 +43,17 @@ export class SearchDisplayController {          /** @type {import('./search-persistent-state-controller.js').SearchPersistentStateController} */          this._searchPersistentStateController = searchPersistentStateController;          /** @type {HTMLButtonElement} */ -        this._searchButton = /** @type {HTMLButtonElement} */ (document.querySelector('#search-button')); +        this._searchButton = querySelectorNotNull(document, '#search-button');          /** @type {HTMLButtonElement} */ -        this._searchBackButton = /** @type {HTMLButtonElement} */ (document.querySelector('#search-back-button')); +        this._searchBackButton = querySelectorNotNull(document, '#search-back-button');          /** @type {HTMLTextAreaElement} */ -        this._queryInput = /** @type {HTMLTextAreaElement} */ (document.querySelector('#search-textbox')); +        this._queryInput = querySelectorNotNull(document, '#search-textbox');          /** @type {HTMLElement} */ -        this._introElement = /** @type {HTMLElement} */ (document.querySelector('#intro')); +        this._introElement = querySelectorNotNull(document, '#intro');          /** @type {HTMLInputElement} */ -        this._clipboardMonitorEnableCheckbox = /** @type {HTMLInputElement} */ (document.querySelector('#clipboard-monitor-enable')); +        this._clipboardMonitorEnableCheckbox = querySelectorNotNull(document, '#clipboard-monitor-enable');          /** @type {HTMLInputElement} */ -        this._wanakanaEnableCheckbox = /** @type {HTMLInputElement} */ (document.querySelector('#wanakana-enable')); +        this._wanakanaEnableCheckbox = querySelectorNotNull(document, '#wanakana-enable');          /** @type {EventListenerCollection} */          this._queryInputEvents = new EventListenerCollection();          /** @type {boolean} */ diff --git a/ext/js/dom/popup-menu.js b/ext/js/dom/popup-menu.js index 78394c93..33cdd1ae 100644 --- a/ext/js/dom/popup-menu.js +++ b/ext/js/dom/popup-menu.js @@ -17,6 +17,7 @@   */  import {EventDispatcher, EventListenerCollection} from '../core.js'; +import {querySelectorNotNull} from './query-selector.js';  /**   * @augments EventDispatcher<import('popup-menu').EventType> @@ -33,9 +34,9 @@ export class PopupMenu extends EventDispatcher {          /** @type {HTMLElement} */          this._containerNode = containerNode;          /** @type {HTMLElement} */ -        this._node = /** @type {HTMLElement} */ (containerNode.querySelector('.popup-menu')); +        this._node = querySelectorNotNull(containerNode, '.popup-menu');          /** @type {HTMLElement} */ -        this._bodyNode = /** @type {HTMLElement} */ (containerNode.querySelector('.popup-menu-body')); +        this._bodyNode = querySelectorNotNull(containerNode, '.popup-menu-body');          /** @type {boolean} */          this._isClosed = false;          /** @type {EventListenerCollection} */ diff --git a/ext/js/dom/query-selector.js b/ext/js/dom/query-selector.js new file mode 100644 index 00000000..e881211d --- /dev/null +++ b/ext/js/dom/query-selector.js @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023  Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <https://www.gnu.org/licenses/>. + */ + +import {ExtensionError} from '../core/extension-error.js'; + +/** + * @param {Element|Document|DocumentFragment} element + * @param {string} selector + * @returns {ExtensionError} + */ +function createError(element, selector) { +    const error = new ExtensionError(`Performing querySelectorNotNull(element, ${JSON.stringify(selector)}) returned null`); +    error.data = {element, selector}; +    return error; +} + +/** + * @template {Element} T + * @param {Element|Document|DocumentFragment} element + * @param {string} selector + * @returns {T} + * @throws {Error} + */ +export function querySelectorNotNull(element, selector) { +    /** @type {?T} */ +    const result = element.querySelector(selector); +    if (result === null) { throw createError(element, selector); } +    return result; +} 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(); |