summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/js/display/display-anki.js18
-rw-r--r--ext/js/display/display-audio.js18
-rw-r--r--ext/js/display/display-notification.js5
-rw-r--r--ext/js/display/display-profile-selection.js15
-rw-r--r--ext/js/display/display.js40
-rw-r--r--ext/js/display/query-parser.js7
-rw-r--r--ext/js/display/search-display-controller.js13
-rw-r--r--ext/js/dom/popup-menu.js5
-rw-r--r--ext/js/dom/query-selector.js43
-rw-r--r--ext/js/pages/action-popup-main.js11
-rw-r--r--ext/js/pages/info-main.js46
-rw-r--r--ext/js/pages/permissions-main.js12
-rw-r--r--ext/js/pages/settings/anki-controller.js83
-rw-r--r--ext/js/pages/settings/anki-templates-controller.js36
-rw-r--r--ext/js/pages/settings/audio-controller.js40
-rw-r--r--ext/js/pages/settings/backup-controller.js29
-rw-r--r--ext/js/pages/settings/collapsible-dictionary-controller.js17
-rw-r--r--ext/js/pages/settings/dictionary-controller.js108
-rw-r--r--ext/js/pages/settings/dictionary-import-controller.js58
-rw-r--r--ext/js/pages/settings/extension-keyboard-shortcuts-controller.js26
-rw-r--r--ext/js/pages/settings/keyboard-shortcuts-controller.js51
-rw-r--r--ext/js/pages/settings/mecab-controller.js12
-rw-r--r--ext/js/pages/settings/modal.js3
-rw-r--r--ext/js/pages/settings/nested-popups-controller.js17
-rw-r--r--ext/js/pages/settings/permissions-origin-controller.js30
-rw-r--r--ext/js/pages/settings/persistent-storage-controller.js12
-rw-r--r--ext/js/pages/settings/popup-preview-controller.js23
-rw-r--r--ext/js/pages/settings/popup-preview-frame.js15
-rw-r--r--ext/js/pages/settings/popup-window-controller.js4
-rw-r--r--ext/js/pages/settings/profile-conditions-ui.js41
-rw-r--r--ext/js/pages/settings/profile-controller.js56
-rw-r--r--ext/js/pages/settings/scan-inputs-controller.js45
-rw-r--r--ext/js/pages/settings/scan-inputs-simple-controller.js12
-rw-r--r--ext/js/pages/settings/secondary-search-dictionary-controller.js16
-rw-r--r--ext/js/pages/settings/sentence-termination-characters-controller.js48
-rw-r--r--ext/js/pages/settings/settings-display-controller.js15
-rw-r--r--ext/js/pages/settings/settings-main.js5
-rw-r--r--ext/js/pages/settings/sort-frequency-dictionary-controller.js22
-rw-r--r--ext/js/pages/settings/status-footer.js6
-rw-r--r--ext/js/pages/settings/storage-controller.js4
-rw-r--r--ext/js/pages/settings/translation-text-replacements-controller.js35
-rw-r--r--ext/js/pages/welcome-main.js5
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();