summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2023-12-22 20:23:12 -0500
committerGitHub <noreply@github.com>2023-12-23 01:23:12 +0000
commit33886cf39bd8c128296834a6825992f8b8341fe7 (patch)
tree1d5e8245f1d372d8ba89f93e4d0c9787c50d4468 /ext
parentf320e2dd5d7fe7c0ada7ec9634bd060c73cc3986 (diff)
API type safety (#425)
* Improve API type safety * Update init * Update type names * Simplify init
Diffstat (limited to 'ext')
-rw-r--r--ext/js/accessibility/google-docs.js11
-rw-r--r--ext/js/background/backend.js103
-rw-r--r--ext/js/comm/api.js337
-rw-r--r--ext/js/display/query-parser.js4
4 files changed, 203 insertions, 252 deletions
diff --git a/ext/js/accessibility/google-docs.js b/ext/js/accessibility/google-docs.js
index 27841b6d..4bc398ff 100644
--- a/ext/js/accessibility/google-docs.js
+++ b/ext/js/accessibility/google-docs.js
@@ -24,10 +24,11 @@
self.googleDocsAccessibilitySetup = true;
/**
- * @template [TReturn=unknown]
- * @param {string} action
- * @param {import('core').SerializableObject} params
- * @returns {Promise<TReturn>}
+ * @template {import('api').ApiNames} TAction
+ * @template {import('api').ApiParams<TAction>} TParams
+ * @param {TAction} action
+ * @param {TParams} params
+ * @returns {Promise<import('api').ApiReturn<TAction>>}
*/
const invokeApi = (action, params) => {
return new Promise((resolve, reject) => {
@@ -45,7 +46,7 @@
};
const optionsContext = {depth: 0, url: location.href};
- /** @type {import('api').OptionsGetResult} */
+ /** @type {import('api').ApiReturn<'optionsGet'>} */
let options;
try {
options = await invokeApi('optionsGet', {optionsContext});
diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js
index c62685b2..f7cad1e6 100644
--- a/ext/js/background/backend.js
+++ b/ext/js/background/backend.js
@@ -22,7 +22,8 @@ import {AnkiConnect} from '../comm/anki-connect.js';
import {ClipboardMonitor} from '../comm/clipboard-monitor.js';
import {ClipboardReader} from '../comm/clipboard-reader.js';
import {Mecab} from '../comm/mecab.js';
-import {clone, deferPromise, invokeMessageHandler, isObject, log, promiseTimeout} from '../core.js';
+import {clone, deferPromise, isObject, log, promiseTimeout} from '../core.js';
+import {createApiMap, invokeApiMapHandler} from '../core/api-map.js';
import {ExtensionError} from '../core/extension-error.js';
import {readResponseJson} from '../core/json.js';
import {AnkiUtil} from '../data/anki-util.js';
@@ -146,8 +147,8 @@ export class Backend {
this._permissionsUtil = new PermissionsUtil();
/* eslint-disable no-multi-spaces */
- /** @type {import('core').MessageHandlerMap} */
- this._messageHandlers = new Map(/** @type {import('core').MessageHandlerMapInit} */ ([
+ /** @type {import('api').ApiMap} */
+ this._apiMap = createApiMap([
['requestBackendReadySignal', this._onApiRequestBackendReadySignal.bind(this)],
['optionsGet', this._onApiOptionsGet.bind(this)],
['optionsGetFull', this._onApiOptionsGetFull.bind(this)],
@@ -190,7 +191,7 @@ export class Backend {
['findAnkiNotes', this._onApiFindAnkiNotes.bind(this)],
['loadExtensionScripts', this._onApiLoadExtensionScripts.bind(this)],
['openCrossFramePort', this._onApiOpenCrossFramePort.bind(this)]
- ]));
+ ]);
/* eslint-enable no-multi-spaces */
/** @type {Map<string, (params?: import('core').SerializableObject) => void>} */
@@ -369,7 +370,7 @@ export class Backend {
});
}
- /** @type {import('extension').ChromeRuntimeOnMessageCallback} */
+ /** @type {import('extension').ChromeRuntimeOnMessageCallback<import('api').MessageAny>} */
_onMessageWrapper(message, sender, sendResponse) {
if (this._isPrepared) {
return this._onMessage(message, sender, sendResponse);
@@ -392,15 +393,13 @@ export class Backend {
}
/**
- * @param {{action: string, params?: import('core').SerializableObject}} message
+ * @param {import('api').MessageAny} message
* @param {chrome.runtime.MessageSender} sender
* @param {(response?: unknown) => void} callback
* @returns {boolean}
*/
_onMessage({action, params}, sender, callback) {
- const messageHandler = this._messageHandlers.get(action);
- if (typeof messageHandler === 'undefined') { return false; }
- return invokeMessageHandler(messageHandler, params, callback, sender);
+ return invokeApiMapHandler(this._apiMap, action, params, [sender], callback);
}
/**
@@ -427,7 +426,7 @@ export class Backend {
// Message handlers
- /** @type {import('api').Handler<import('api').RequestBackendReadySignalDetails, import('api').RequestBackendReadySignalResult, true>} */
+ /** @type {import('api').ApiHandler<'requestBackendReadySignal'>} */
_onApiRequestBackendReadySignal(_params, sender) {
// tab ID isn't set in background (e.g. browser_action)
const data = {action: 'Yomitan.backendReady', params: {}};
@@ -443,17 +442,17 @@ export class Backend {
}
}
- /** @type {import('api').Handler<import('api').OptionsGetDetails, import('api').OptionsGetResult>} */
+ /** @type {import('api').ApiHandler<'optionsGet'>} */
_onApiOptionsGet({optionsContext}) {
return this._getProfileOptions(optionsContext, false);
}
- /** @type {import('api').Handler<import('api').OptionsGetFullDetails, import('api').OptionsGetFullResult>} */
+ /** @type {import('api').ApiHandler<'optionsGetFull'>} */
_onApiOptionsGetFull() {
return this._getOptionsFull(false);
}
- /** @type {import('api').Handler<import('api').KanjiFindDetails, import('api').KanjiFindResult>} */
+ /** @type {import('api').ApiHandler<'kanjiFind'>} */
async _onApiKanjiFind({text, optionsContext}) {
const options = this._getProfileOptions(optionsContext, false);
const {general: {maxResults}} = options;
@@ -463,7 +462,7 @@ export class Backend {
return dictionaryEntries;
}
- /** @type {import('api').Handler<import('api').TermsFindDetails, import('api').TermsFindResult>} */
+ /** @type {import('api').ApiHandler<'termsFind'>} */
async _onApiTermsFind({text, details, optionsContext}) {
const options = this._getProfileOptions(optionsContext, false);
const {general: {resultOutputMode: mode, maxResults}} = options;
@@ -473,7 +472,7 @@ export class Backend {
return {dictionaryEntries, originalTextLength};
}
- /** @type {import('api').Handler<import('api').ParseTextDetails, import('api').ParseTextResult>} */
+ /** @type {import('api').ApiHandler<'parseText'>} */
async _onApiParseText({text, optionsContext, scanLength, useInternalParser, useMecabParser}) {
const [internalResults, mecabResults] = await Promise.all([
(useInternalParser ? this._textParseScanning(text, scanLength, optionsContext) : null),
@@ -506,22 +505,22 @@ export class Backend {
return results;
}
- /** @type {import('api').Handler<import('api').GetAnkiConnectVersionDetails, import('api').GetAnkiConnectVersionResult>} */
+ /** @type {import('api').ApiHandler<'getAnkiConnectVersion'>} */
async _onApiGetAnkiConnectVersion() {
return await this._anki.getVersion();
}
- /** @type {import('api').Handler<import('api').IsAnkiConnectedDetails, import('api').IsAnkiConnectedResult>} */
+ /** @type {import('api').ApiHandler<'isAnkiConnected'>} */
async _onApiIsAnkiConnected() {
return await this._anki.isConnected();
}
- /** @type {import('api').Handler<import('api').AddAnkiNoteDetails, import('api').AddAnkiNoteResult>} */
+ /** @type {import('api').ApiHandler<'addAnkiNote'>} */
async _onApiAddAnkiNote({note}) {
return await this._anki.addNote(note);
}
- /** @type {import('api').Handler<import('api').GetAnkiNoteInfoDetails, import('api').GetAnkiNoteInfoResult>} */
+ /** @type {import('api').ApiHandler<'getAnkiNoteInfo'>} */
async _onApiGetAnkiNoteInfo({notes, fetchAdditionalInfo}) {
/** @type {import('anki').NoteInfoWrapper[]} */
const results = [];
@@ -558,7 +557,7 @@ export class Backend {
return results;
}
- /** @type {import('api').Handler<import('api').InjectAnkiNoteMediaDetails, import('api').InjectAnkiNoteMediaResult>} */
+ /** @type {import('api').ApiHandler<'injectAnkiNoteMedia'>} */
async _onApiInjectAnkiNoteMedia({timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails}) {
return await this._injectAnkNoteMedia(
this._anki,
@@ -571,7 +570,7 @@ export class Backend {
);
}
- /** @type {import('api').Handler<import('api').NoteViewDetails, import('api').NoteViewResult>} */
+ /** @type {import('api').ApiHandler<'noteView'>} */
async _onApiNoteView({noteId, mode, allowFallback}) {
if (mode === 'edit') {
try {
@@ -590,7 +589,7 @@ export class Backend {
return 'browse';
}
- /** @type {import('api').Handler<import('api').SuspendAnkiCardsForNoteDetails, import('api').SuspendAnkiCardsForNoteResult>} */
+ /** @type {import('api').ApiHandler<'suspendAnkiCardsForNote'>} */
async _onApiSuspendAnkiCardsForNote({noteId}) {
const cardIds = await this._anki.findCardsForNote(noteId);
const count = cardIds.length;
@@ -601,17 +600,17 @@ export class Backend {
return count;
}
- /** @type {import('api').Handler<import('api').CommandExecDetails, import('api').CommandExecResult>} */
+ /** @type {import('api').ApiHandler<'commandExec'>} */
_onApiCommandExec({command, params}) {
return this._runCommand(command, params);
}
- /** @type {import('api').Handler<import('api').GetTermAudioInfoListDetails, import('api').GetTermAudioInfoListResult>} */
+ /** @type {import('api').ApiHandler<'getTermAudioInfoList'>} */
async _onApiGetTermAudioInfoList({source, term, reading}) {
return await this._audioDownloader.getTermAudioInfoList(source, term, reading);
}
- /** @type {import('api').Handler<import('api').SendMessageToFrameDetails, import('api').SendMessageToFrameResult, true>} */
+ /** @type {import('api').ApiHandler<'sendMessageToFrame'>} */
_onApiSendMessageToFrame({frameId: targetFrameId, action, params}, sender) {
if (!sender) { return false; }
const {tab} = sender;
@@ -625,7 +624,7 @@ export class Backend {
return true;
}
- /** @type {import('api').Handler<import('api').BroadcastTabDetails, import('api').BroadcastTabResult, true>} */
+ /** @type {import('api').ApiHandler<'broadcastTab'>} */
_onApiBroadcastTab({action, params}, sender) {
if (!sender) { return false; }
const {tab} = sender;
@@ -639,7 +638,7 @@ export class Backend {
return true;
}
- /** @type {import('api').Handler<import('api').FrameInformationGetDetails, import('api').FrameInformationGetResult, true>} */
+ /** @type {import('api').ApiHandler<'frameInformationGet'>} */
_onApiFrameInformationGet(_params, sender) {
const tab = sender.tab;
const tabId = tab ? tab.id : void 0;
@@ -647,14 +646,14 @@ export class Backend {
return Promise.resolve({tabId, frameId});
}
- /** @type {import('api').Handler<import('api').InjectStylesheetDetails, import('api').InjectStylesheetResult, true>} */
+ /** @type {import('api').ApiHandler<'injectStylesheet'>} */
async _onApiInjectStylesheet({type, value}, sender) {
const {frameId, tab} = sender;
if (typeof tab !== 'object' || tab === null || typeof tab.id !== 'number') { throw new Error('Invalid tab'); }
return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false);
}
- /** @type {import('api').Handler<import('api').GetStylesheetContentDetails, import('api').GetStylesheetContentResult>} */
+ /** @type {import('api').ApiHandler<'getStylesheetContent'>} */
async _onApiGetStylesheetContent({url}) {
if (!url.startsWith('/') || url.startsWith('//') || !url.endsWith('.css')) {
throw new Error('Invalid URL');
@@ -662,22 +661,22 @@ export class Backend {
return await this._fetchText(url);
}
- /** @type {import('api').Handler<import('api').GetEnvironmentInfoDetails, import('api').GetEnvironmentInfoResult>} */
+ /** @type {import('api').ApiHandler<'getEnvironmentInfo'>} */
_onApiGetEnvironmentInfo() {
return this._environment.getInfo();
}
- /** @type {import('api').Handler<import('api').ClipboardGetDetails, import('api').ClipboardGetResult>} */
+ /** @type {import('api').ApiHandler<'clipboardGet'>} */
async _onApiClipboardGet() {
return this._clipboardReader.getText(false);
}
- /** @type {import('api').Handler<import('api').GetDisplayTemplatesHtmlDetails, import('api').GetDisplayTemplatesHtmlResult>} */
+ /** @type {import('api').ApiHandler<'getDisplayTemplatesHtml'>} */
async _onApiGetDisplayTemplatesHtml() {
return await this._fetchText('/display-templates.html');
}
- /** @type {import('api').Handler<import('api').GetZoomDetails, import('api').GetZoomResult, true>} */
+ /** @type {import('api').ApiHandler<'getZoom'>} */
_onApiGetZoom(_params, sender) {
return new Promise((resolve, reject) => {
if (!sender || !sender.tab) {
@@ -707,45 +706,45 @@ export class Backend {
});
}
- /** @type {import('api').Handler<import('api').GetDefaultAnkiFieldTemplatesDetails, import('api').GetDefaultAnkiFieldTemplatesResult>} */
+ /** @type {import('api').ApiHandler<'getDefaultAnkiFieldTemplates'>} */
_onApiGetDefaultAnkiFieldTemplates() {
return /** @type {string} */ (this._defaultAnkiFieldTemplates);
}
- /** @type {import('api').Handler<import('api').GetDictionaryInfoDetails, import('api').GetDictionaryInfoResult>} */
+ /** @type {import('api').ApiHandler<'getDictionaryInfo'>} */
async _onApiGetDictionaryInfo() {
return await this._dictionaryDatabase.getDictionaryInfo();
}
- /** @type {import('api').Handler<import('api').PurgeDatabaseDetails, import('api').PurgeDatabaseResult>} */
+ /** @type {import('api').ApiHandler<'purgeDatabase'>} */
async _onApiPurgeDatabase() {
await this._dictionaryDatabase.purge();
this._triggerDatabaseUpdated('dictionary', 'purge');
}
- /** @type {import('api').Handler<import('api').GetMediaDetails, import('api').GetMediaResult>} */
+ /** @type {import('api').ApiHandler<'getMedia'>} */
async _onApiGetMedia({targets}) {
return await this._getNormalizedDictionaryDatabaseMedia(targets);
}
- /** @type {import('api').Handler<import('api').LogDetails, import('api').LogResult>} */
+ /** @type {import('api').ApiHandler<'log'>} */
_onApiLog({error, level, context}) {
log.log(ExtensionError.deserialize(error), level, context);
}
- /** @type {import('api').Handler<import('api').LogIndicatorClearDetails, import('api').LogIndicatorClearResult>} */
+ /** @type {import('api').ApiHandler<'logIndicatorClear'>} */
_onApiLogIndicatorClear() {
if (this._logErrorLevel === null) { return; }
this._logErrorLevel = null;
this._updateBadge();
}
- /** @type {import('api').Handler<import('api').ModifySettingsDetails, import('api').ModifySettingsResult>} */
+ /** @type {import('api').ApiHandler<'modifySettings'>} */
_onApiModifySettings({targets, source}) {
return this._modifySettings(targets, source);
}
- /** @type {import('api').Handler<import('api').GetSettingsDetails, import('api').GetSettingsResult>} */
+ /** @type {import('api').ApiHandler<'getSettings'>} */
_onApiGetSettings({targets}) {
const results = [];
for (const target of targets) {
@@ -759,14 +758,14 @@ export class Backend {
return results;
}
- /** @type {import('api').Handler<import('api').SetAllSettingsDetails, import('api').SetAllSettingsResult>} */
+ /** @type {import('api').ApiHandler<'setAllSettings'>} */
async _onApiSetAllSettings({value, source}) {
this._optionsUtil.validate(value);
this._options = clone(value);
await this._saveOptions(source);
}
- /** @type {import('api').Handler<import('api').GetOrCreateSearchPopupDetails, import('api').GetOrCreateSearchPopupResult>} */
+ /** @type {import('api').ApiHandlerNoExtraArgs<'getOrCreateSearchPopup'>} */
async _onApiGetOrCreateSearchPopup({focus = false, text}) {
const {tab, created} = await this._getOrCreateSearchPopupWrapper();
if (focus === true || (focus === 'ifCreated' && created)) {
@@ -782,19 +781,19 @@ export class Backend {
return {tabId: typeof id === 'number' ? id : null, windowId: tab.windowId};
}
- /** @type {import('api').Handler<import('api').IsTabSearchPopupDetails, import('api').IsTabSearchPopupResult>} */
+ /** @type {import('api').ApiHandler<'isTabSearchPopup'>} */
async _onApiIsTabSearchPopup({tabId}) {
const baseUrl = chrome.runtime.getURL('/search.html');
const tab = typeof tabId === 'number' ? await this._checkTabUrl(tabId, (url) => url !== null && url.startsWith(baseUrl)) : null;
return (tab !== null);
}
- /** @type {import('api').Handler<import('api').TriggerDatabaseUpdatedDetails, import('api').TriggerDatabaseUpdatedResult>} */
+ /** @type {import('api').ApiHandler<'triggerDatabaseUpdated'>} */
_onApiTriggerDatabaseUpdated({type, cause}) {
this._triggerDatabaseUpdated(type, cause);
}
- /** @type {import('api').Handler<import('api').TestMecabDetails, import('api').TestMecabResult>} */
+ /** @type {import('api').ApiHandler<'testMecab'>} */
async _onApiTestMecab() {
if (!this._mecab.isEnabled()) {
throw new Error('MeCab not enabled');
@@ -831,22 +830,22 @@ export class Backend {
return true;
}
- /** @type {import('api').Handler<import('api').TextHasJapaneseCharactersDetails, import('api').TextHasJapaneseCharactersResult>} */
+ /** @type {import('api').ApiHandler<'textHasJapaneseCharacters'>} */
_onApiTextHasJapaneseCharacters({text}) {
return this._japaneseUtil.isStringPartiallyJapanese(text);
}
- /** @type {import('api').Handler<import('api').GetTermFrequenciesDetails, import('api').GetTermFrequenciesResult>} */
+ /** @type {import('api').ApiHandler<'getTermFrequencies'>} */
async _onApiGetTermFrequencies({termReadingList, dictionaries}) {
return await this._translator.getTermFrequencies(termReadingList, dictionaries);
}
- /** @type {import('api').Handler<import('api').FindAnkiNotesDetails, import('api').FindAnkiNotesResult>} */
+ /** @type {import('api').ApiHandler<'findAnkiNotes'>} */
async _onApiFindAnkiNotes({query}) {
return await this._anki.findNotes(query);
}
- /** @type {import('api').Handler<import('api').LoadExtensionScriptsDetails, import('api').LoadExtensionScriptsResult, true>} */
+ /** @type {import('api').ApiHandler<'loadExtensionScripts'>} */
async _onApiLoadExtensionScripts({files}, sender) {
if (!sender || !sender.tab) { throw new Error('Invalid sender'); }
const tabId = sender.tab.id;
@@ -857,7 +856,7 @@ export class Backend {
}
}
- /** @type {import('api').Handler<import('api').OpenCrossFramePortDetails, import('api').OpenCrossFramePortResult, true>} */
+ /** @type {import('api').ApiHandler<'openCrossFramePort'>} */
_onApiOpenCrossFramePort({targetTabId, targetFrameId}, sender) {
const sourceTabId = (sender && sender.tab ? sender.tab.id : null);
if (typeof sourceTabId !== 'number') {
@@ -2062,7 +2061,7 @@ export class Backend {
* @param {?import('api').InjectAnkiNoteMediaScreenshotDetails} screenshotDetails
* @param {?import('api').InjectAnkiNoteMediaClipboardDetails} clipboardDetails
* @param {import('api').InjectAnkiNoteMediaDictionaryMediaDetails[]} dictionaryMediaDetails
- * @returns {Promise<import('api').InjectAnkiNoteMediaResult>}
+ * @returns {Promise<import('api').ApiReturn<'injectAnkiNoteMedia'>>}
*/
async _injectAnkNoteMedia(ankiConnect, timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails) {
let screenshotFileName = null;
diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js
index de19650d..f2d4d545 100644
--- a/ext/js/comm/api.js
+++ b/ext/js/comm/api.js
@@ -28,395 +28,344 @@ export class API {
}
/**
- * @param {import('api').OptionsGetDetails['optionsContext']} optionsContext
- * @returns {Promise<import('api').OptionsGetResult>}
+ * @param {import('api').ApiParam<'optionsGet', 'optionsContext'>} optionsContext
+ * @returns {Promise<import('api').ApiReturn<'optionsGet'>>}
*/
optionsGet(optionsContext) {
- /** @type {import('api').OptionsGetDetails} */
- const details = {optionsContext};
- return this._invoke('optionsGet', details);
+ return this._invoke('optionsGet', {optionsContext});
}
/**
- * @returns {Promise<import('api').OptionsGetFullResult>}
+ * @returns {Promise<import('api').ApiReturn<'optionsGetFull'>>}
*/
optionsGetFull() {
- return this._invoke('optionsGetFull');
+ return this._invoke('optionsGetFull', void 0);
}
/**
- * @param {import('api').TermsFindDetails['text']} text
- * @param {import('api').TermsFindDetails['details']} details
- * @param {import('api').TermsFindDetails['optionsContext']} optionsContext
- * @returns {Promise<import('api').TermsFindResult>}
+ * @param {import('api').ApiParam<'termsFind', 'text'>} text
+ * @param {import('api').ApiParam<'termsFind', 'details'>} details
+ * @param {import('api').ApiParam<'termsFind', 'optionsContext'>} optionsContext
+ * @returns {Promise<import('api').ApiReturn<'termsFind'>>}
*/
termsFind(text, details, optionsContext) {
- /** @type {import('api').TermsFindDetails} */
- const details2 = {text, details, optionsContext};
- return this._invoke('termsFind', details2);
+ return this._invoke('termsFind', {text, details, optionsContext});
}
/**
- * @param {import('api').ParseTextDetails['text']} text
- * @param {import('api').ParseTextDetails['optionsContext']} optionsContext
- * @param {import('api').ParseTextDetails['scanLength']} scanLength
- * @param {import('api').ParseTextDetails['useInternalParser']} useInternalParser
- * @param {import('api').ParseTextDetails['useMecabParser']} useMecabParser
- * @returns {Promise<import('api').ParseTextResult>}
+ * @param {import('api').ApiParam<'parseText', 'text'>} text
+ * @param {import('api').ApiParam<'parseText', 'optionsContext'>} optionsContext
+ * @param {import('api').ApiParam<'parseText', 'scanLength'>} scanLength
+ * @param {import('api').ApiParam<'parseText', 'useInternalParser'>} useInternalParser
+ * @param {import('api').ApiParam<'parseText', 'useMecabParser'>} useMecabParser
+ * @returns {Promise<import('api').ApiReturn<'parseText'>>}
*/
parseText(text, optionsContext, scanLength, useInternalParser, useMecabParser) {
- /** @type {import('api').ParseTextDetails} */
- const details = {text, optionsContext, scanLength, useInternalParser, useMecabParser};
- return this._invoke('parseText', details);
+ return this._invoke('parseText', {text, optionsContext, scanLength, useInternalParser, useMecabParser});
}
/**
- * @param {import('api').KanjiFindDetails['text']} text
- * @param {import('api').KanjiFindDetails['optionsContext']} optionsContext
- * @returns {Promise<import('api').KanjiFindResult>}
+ * @param {import('api').ApiParam<'kanjiFind', 'text'>} text
+ * @param {import('api').ApiParam<'kanjiFind', 'optionsContext'>} optionsContext
+ * @returns {Promise<import('api').ApiReturn<'kanjiFind'>>}
*/
kanjiFind(text, optionsContext) {
- /** @type {import('api').KanjiFindDetails} */
- const details = {text, optionsContext};
- return this._invoke('kanjiFind', details);
+ return this._invoke('kanjiFind', {text, optionsContext});
}
/**
- * @returns {Promise<import('api').IsAnkiConnectedResult>}
+ * @returns {Promise<import('api').ApiReturn<'isAnkiConnected'>>}
*/
isAnkiConnected() {
- return this._invoke('isAnkiConnected');
+ return this._invoke('isAnkiConnected', void 0);
}
/**
- * @returns {Promise<import('api').GetAnkiConnectVersionResult>}
+ * @returns {Promise<import('api').ApiReturn<'getAnkiConnectVersion'>>}
*/
getAnkiConnectVersion() {
- return this._invoke('getAnkiConnectVersion');
+ return this._invoke('getAnkiConnectVersion', void 0);
}
/**
- * @param {import('api').AddAnkiNoteDetails['note']} note
- * @returns {Promise<import('api').AddAnkiNoteResult>}
+ * @param {import('api').ApiParam<'addAnkiNote', 'note'>} note
+ * @returns {Promise<import('api').ApiReturn<'addAnkiNote'>>}
*/
addAnkiNote(note) {
- /** @type {import('api').AddAnkiNoteDetails} */
- const details = {note};
- return this._invoke('addAnkiNote', details);
+ return this._invoke('addAnkiNote', {note});
}
/**
- * @param {import('api').GetAnkiNoteInfoDetails['notes']} notes
- * @param {import('api').GetAnkiNoteInfoDetails['fetchAdditionalInfo']} fetchAdditionalInfo
- * @returns {Promise<import('api').GetAnkiNoteInfoResult>}
+ * @param {import('api').ApiParam<'getAnkiNoteInfo', 'notes'>} notes
+ * @param {import('api').ApiParam<'getAnkiNoteInfo', 'fetchAdditionalInfo'>} fetchAdditionalInfo
+ * @returns {Promise<import('api').ApiReturn<'getAnkiNoteInfo'>>}
*/
getAnkiNoteInfo(notes, fetchAdditionalInfo) {
- /** @type {import('api').GetAnkiNoteInfoDetails} */
- const details = {notes, fetchAdditionalInfo};
- return this._invoke('getAnkiNoteInfo', details);
+ return this._invoke('getAnkiNoteInfo', {notes, fetchAdditionalInfo});
}
/**
- * @param {import('api').InjectAnkiNoteMediaDetails['timestamp']} timestamp
- * @param {import('api').InjectAnkiNoteMediaDetails['definitionDetails']} definitionDetails
- * @param {import('api').InjectAnkiNoteMediaDetails['audioDetails']} audioDetails
- * @param {import('api').InjectAnkiNoteMediaDetails['screenshotDetails']} screenshotDetails
- * @param {import('api').InjectAnkiNoteMediaDetails['clipboardDetails']} clipboardDetails
- * @param {import('api').InjectAnkiNoteMediaDetails['dictionaryMediaDetails']} dictionaryMediaDetails
- * @returns {Promise<import('api').InjectAnkiNoteMediaResult>}
+ * @param {import('api').ApiParam<'injectAnkiNoteMedia', 'timestamp'>} timestamp
+ * @param {import('api').ApiParam<'injectAnkiNoteMedia', 'definitionDetails'>} definitionDetails
+ * @param {import('api').ApiParam<'injectAnkiNoteMedia', 'audioDetails'>} audioDetails
+ * @param {import('api').ApiParam<'injectAnkiNoteMedia', 'screenshotDetails'>} screenshotDetails
+ * @param {import('api').ApiParam<'injectAnkiNoteMedia', 'clipboardDetails'>} clipboardDetails
+ * @param {import('api').ApiParam<'injectAnkiNoteMedia', 'dictionaryMediaDetails'>} dictionaryMediaDetails
+ * @returns {Promise<import('api').ApiReturn<'injectAnkiNoteMedia'>>}
*/
injectAnkiNoteMedia(timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails) {
- /** @type {import('api').InjectAnkiNoteMediaDetails} */
- const details = {timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails};
- return this._invoke('injectAnkiNoteMedia', details);
+ return this._invoke('injectAnkiNoteMedia', {timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails});
}
/**
- * @param {import('api').NoteViewDetails['noteId']} noteId
- * @param {import('api').NoteViewDetails['mode']} mode
- * @param {import('api').NoteViewDetails['allowFallback']} allowFallback
- * @returns {Promise<import('api').NoteViewResult>}
+ * @param {import('api').ApiParam<'noteView', 'noteId'>} noteId
+ * @param {import('api').ApiParam<'noteView', 'mode'>} mode
+ * @param {import('api').ApiParam<'noteView', 'allowFallback'>} allowFallback
+ * @returns {Promise<import('api').ApiReturn<'noteView'>>}
*/
noteView(noteId, mode, allowFallback) {
- /** @type {import('api').NoteViewDetails} */
- const details = {noteId, mode, allowFallback};
- return this._invoke('noteView', details);
+ return this._invoke('noteView', {noteId, mode, allowFallback});
}
/**
- * @param {import('api').SuspendAnkiCardsForNoteDetails['noteId']} noteId
- * @returns {Promise<import('api').SuspendAnkiCardsForNoteResult>}
+ * @param {import('api').ApiParam<'suspendAnkiCardsForNote', 'noteId'>} noteId
+ * @returns {Promise<import('api').ApiReturn<'suspendAnkiCardsForNote'>>}
*/
suspendAnkiCardsForNote(noteId) {
- /** @type {import('api').SuspendAnkiCardsForNoteDetails} */
- const details = {noteId};
- return this._invoke('suspendAnkiCardsForNote', details);
+ return this._invoke('suspendAnkiCardsForNote', {noteId});
}
/**
- * @param {import('api').GetTermAudioInfoListDetails['source']} source
- * @param {import('api').GetTermAudioInfoListDetails['term']} term
- * @param {import('api').GetTermAudioInfoListDetails['reading']} reading
- * @returns {Promise<import('api').GetTermAudioInfoListResult>}
+ * @param {import('api').ApiParam<'getTermAudioInfoList', 'source'>} source
+ * @param {import('api').ApiParam<'getTermAudioInfoList', 'term'>} term
+ * @param {import('api').ApiParam<'getTermAudioInfoList', 'reading'>} reading
+ * @returns {Promise<import('api').ApiReturn<'getTermAudioInfoList'>>}
*/
getTermAudioInfoList(source, term, reading) {
- /** @type {import('api').GetTermAudioInfoListDetails} */
- const details = {source, term, reading};
- return this._invoke('getTermAudioInfoList', details);
+ return this._invoke('getTermAudioInfoList', {source, term, reading});
}
/**
- * @param {import('api').CommandExecDetails['command']} command
- * @param {import('api').CommandExecDetails['params']} [params]
- * @returns {Promise<import('api').CommandExecResult>}
+ * @param {import('api').ApiParam<'commandExec', 'command'>} command
+ * @param {import('api').ApiParam<'commandExec', 'params'>} [params]
+ * @returns {Promise<import('api').ApiReturn<'commandExec'>>}
*/
commandExec(command, params) {
- /** @type {import('api').CommandExecDetails} */
- const details = {command, params};
- return this._invoke('commandExec', details);
+ return this._invoke('commandExec', {command, params});
}
/**
- * @param {import('api').SendMessageToFrameDetails['frameId']} frameId
- * @param {import('api').SendMessageToFrameDetails['action']} action
- * @param {import('api').SendMessageToFrameDetails['params']} [params]
- * @returns {Promise<import('api').SendMessageToFrameResult>}
+ * @param {import('api').ApiParam<'sendMessageToFrame', 'frameId'>} frameId
+ * @param {import('api').ApiParam<'sendMessageToFrame', 'action'>} action
+ * @param {import('api').ApiParam<'sendMessageToFrame', 'params'>} [params]
+ * @returns {Promise<import('api').ApiReturn<'sendMessageToFrame'>>}
*/
sendMessageToFrame(frameId, action, params) {
- /** @type {import('api').SendMessageToFrameDetails} */
- const details = {frameId, action, params};
- return this._invoke('sendMessageToFrame', details);
+ return this._invoke('sendMessageToFrame', {frameId, action, params});
}
/**
- * @param {import('api').BroadcastTabDetails['action']} action
- * @param {import('api').BroadcastTabDetails['params']} params
- * @returns {Promise<import('api').BroadcastTabResult>}
+ * @param {import('api').ApiParam<'broadcastTab', 'action'>} action
+ * @param {import('api').ApiParam<'broadcastTab', 'params'>} params
+ * @returns {Promise<import('api').ApiReturn<'broadcastTab'>>}
*/
broadcastTab(action, params) {
- /** @type {import('api').BroadcastTabDetails} */
- const details = {action, params};
- return this._invoke('broadcastTab', details);
+ return this._invoke('broadcastTab', {action, params});
}
/**
- * @returns {Promise<import('api').FrameInformationGetResult>}
+ * @returns {Promise<import('api').ApiReturn<'frameInformationGet'>>}
*/
frameInformationGet() {
- return this._invoke('frameInformationGet');
+ return this._invoke('frameInformationGet', void 0);
}
/**
- * @param {import('api').InjectStylesheetDetails['type']} type
- * @param {import('api').InjectStylesheetDetails['value']} value
- * @returns {Promise<import('api').InjectStylesheetResult>}
+ * @param {import('api').ApiParam<'injectStylesheet', 'type'>} type
+ * @param {import('api').ApiParam<'injectStylesheet', 'value'>} value
+ * @returns {Promise<import('api').ApiReturn<'injectStylesheet'>>}
*/
injectStylesheet(type, value) {
- /** @type {import('api').InjectStylesheetDetails} */
- const details = {type, value};
- return this._invoke('injectStylesheet', details);
+ return this._invoke('injectStylesheet', {type, value});
}
/**
- * @param {import('api').GetStylesheetContentDetails['url']} url
- * @returns {Promise<import('api').GetStylesheetContentResult>}
+ * @param {import('api').ApiParam<'getStylesheetContent', 'url'>} url
+ * @returns {Promise<import('api').ApiReturn<'getStylesheetContent'>>}
*/
getStylesheetContent(url) {
- /** @type {import('api').GetStylesheetContentDetails} */
- const details = {url};
- return this._invoke('getStylesheetContent', details);
+ return this._invoke('getStylesheetContent', {url});
}
/**
- * @returns {Promise<import('api').GetEnvironmentInfoResult>}
+ * @returns {Promise<import('api').ApiReturn<'getEnvironmentInfo'>>}
*/
getEnvironmentInfo() {
- return this._invoke('getEnvironmentInfo');
+ return this._invoke('getEnvironmentInfo', void 0);
}
/**
- * @returns {Promise<import('api').ClipboardGetResult>}
+ * @returns {Promise<import('api').ApiReturn<'clipboardGet'>>}
*/
clipboardGet() {
- return this._invoke('clipboardGet');
+ return this._invoke('clipboardGet', void 0);
}
/**
- * @returns {Promise<import('api').GetDisplayTemplatesHtmlResult>}
+ * @returns {Promise<import('api').ApiReturn<'getDisplayTemplatesHtml'>>}
*/
getDisplayTemplatesHtml() {
- return this._invoke('getDisplayTemplatesHtml');
+ return this._invoke('getDisplayTemplatesHtml', void 0);
}
/**
- * @returns {Promise<import('api').GetZoomResult>}
+ * @returns {Promise<import('api').ApiReturn<'getZoom'>>}
*/
getZoom() {
- return this._invoke('getZoom');
+ return this._invoke('getZoom', void 0);
}
/**
- * @returns {Promise<import('api').GetDefaultAnkiFieldTemplatesResult>}
+ * @returns {Promise<import('api').ApiReturn<'getDefaultAnkiFieldTemplates'>>}
*/
getDefaultAnkiFieldTemplates() {
- return this._invoke('getDefaultAnkiFieldTemplates');
+ return this._invoke('getDefaultAnkiFieldTemplates', void 0);
}
/**
- * @returns {Promise<import('api').GetDictionaryInfoResult>}
+ * @returns {Promise<import('api').ApiReturn<'getDictionaryInfo'>>}
*/
getDictionaryInfo() {
- return this._invoke('getDictionaryInfo');
+ return this._invoke('getDictionaryInfo', void 0);
}
/**
- * @returns {Promise<import('api').PurgeDatabaseResult>}
+ * @returns {Promise<import('api').ApiReturn<'purgeDatabase'>>}
*/
purgeDatabase() {
- return this._invoke('purgeDatabase');
+ return this._invoke('purgeDatabase', void 0);
}
/**
- * @param {import('api').GetMediaDetails['targets']} targets
- * @returns {Promise<import('api').GetMediaResult>}
+ * @param {import('api').ApiParam<'getMedia', 'targets'>} targets
+ * @returns {Promise<import('api').ApiReturn<'getMedia'>>}
*/
getMedia(targets) {
- /** @type {import('api').GetMediaDetails} */
- const details = {targets};
- return this._invoke('getMedia', details);
+ return this._invoke('getMedia', {targets});
}
/**
- * @param {import('api').LogDetails['error']} error
- * @param {import('api').LogDetails['level']} level
- * @param {import('api').LogDetails['context']} context
- * @returns {Promise<import('api').LogResult>}
+ * @param {import('api').ApiParam<'log', 'error'>} error
+ * @param {import('api').ApiParam<'log', 'level'>} level
+ * @param {import('api').ApiParam<'log', 'context'>} context
+ * @returns {Promise<import('api').ApiReturn<'log'>>}
*/
log(error, level, context) {
- /** @type {import('api').LogDetails} */
- const details = {error, level, context};
- return this._invoke('log', details);
+ return this._invoke('log', {error, level, context});
}
/**
- * @returns {Promise<import('api').LogIndicatorClearResult>}
+ * @returns {Promise<import('api').ApiReturn<'logIndicatorClear'>>}
*/
logIndicatorClear() {
- return this._invoke('logIndicatorClear');
+ return this._invoke('logIndicatorClear', void 0);
}
/**
- * @param {import('api').ModifySettingsDetails['targets']} targets
- * @param {import('api').ModifySettingsDetails['source']} source
- * @returns {Promise<import('api').ModifySettingsResult>}
+ * @param {import('api').ApiParam<'modifySettings', 'targets'>} targets
+ * @param {import('api').ApiParam<'modifySettings', 'source'>} source
+ * @returns {Promise<import('api').ApiReturn<'modifySettings'>>}
*/
modifySettings(targets, source) {
- const details = {targets, source};
- return this._invoke('modifySettings', details);
+ return this._invoke('modifySettings', {targets, source});
}
/**
- * @param {import('api').GetSettingsDetails['targets']} targets
- * @returns {Promise<import('api').GetSettingsResult>}
+ * @param {import('api').ApiParam<'getSettings', 'targets'>} targets
+ * @returns {Promise<import('api').ApiReturn<'getSettings'>>}
*/
getSettings(targets) {
- /** @type {import('api').GetSettingsDetails} */
- const details = {targets};
- return this._invoke('getSettings', details);
+ return this._invoke('getSettings', {targets});
}
/**
- * @param {import('api').SetAllSettingsDetails['value']} value
- * @param {import('api').SetAllSettingsDetails['source']} source
- * @returns {Promise<import('api').SetAllSettingsResult>}
+ * @param {import('api').ApiParam<'setAllSettings', 'value'>} value
+ * @param {import('api').ApiParam<'setAllSettings', 'source'>} source
+ * @returns {Promise<import('api').ApiReturn<'setAllSettings'>>}
*/
setAllSettings(value, source) {
- /** @type {import('api').SetAllSettingsDetails} */
- const details = {value, source};
- return this._invoke('setAllSettings', details);
+ return this._invoke('setAllSettings', {value, source});
}
/**
- * @param {import('api').GetOrCreateSearchPopupDetails} details
- * @returns {Promise<import('api').GetOrCreateSearchPopupResult>}
+ * @param {import('api').ApiParams<'getOrCreateSearchPopup'>} details
+ * @returns {Promise<import('api').ApiReturn<'getOrCreateSearchPopup'>>}
*/
getOrCreateSearchPopup(details) {
return this._invoke('getOrCreateSearchPopup', details);
}
/**
- * @param {import('api').IsTabSearchPopupDetails['tabId']} tabId
- * @returns {Promise<import('api').IsTabSearchPopupResult>}
+ * @param {import('api').ApiParam<'isTabSearchPopup', 'tabId'>} tabId
+ * @returns {Promise<import('api').ApiReturn<'isTabSearchPopup'>>}
*/
isTabSearchPopup(tabId) {
- /** @type {import('api').IsTabSearchPopupDetails} */
- const details = {tabId};
- return this._invoke('isTabSearchPopup', details);
+ return this._invoke('isTabSearchPopup', {tabId});
}
/**
- * @param {import('api').TriggerDatabaseUpdatedDetails['type']} type
- * @param {import('api').TriggerDatabaseUpdatedDetails['cause']} cause
- * @returns {Promise<import('api').TriggerDatabaseUpdatedResult>}
+ * @param {import('api').ApiParam<'triggerDatabaseUpdated', 'type'>} type
+ * @param {import('api').ApiParam<'triggerDatabaseUpdated', 'cause'>} cause
+ * @returns {Promise<import('api').ApiReturn<'triggerDatabaseUpdated'>>}
*/
triggerDatabaseUpdated(type, cause) {
- /** @type {import('api').TriggerDatabaseUpdatedDetails} */
- const details = {type, cause};
- return this._invoke('triggerDatabaseUpdated', details);
+ return this._invoke('triggerDatabaseUpdated', {type, cause});
}
/**
- * @returns {Promise<import('api').TestMecabResult>}
+ * @returns {Promise<import('api').ApiReturn<'testMecab'>>}
*/
testMecab() {
- return this._invoke('testMecab');
+ return this._invoke('testMecab', void 0);
}
/**
- * @param {import('api').TextHasJapaneseCharactersDetails['text']} text
- * @returns {Promise<import('api').TextHasJapaneseCharactersResult>}
+ * @param {import('api').ApiParam<'textHasJapaneseCharacters', 'text'>} text
+ * @returns {Promise<import('api').ApiReturn<'textHasJapaneseCharacters'>>}
*/
textHasJapaneseCharacters(text) {
- /** @type {import('api').TextHasJapaneseCharactersDetails} */
- const details = {text};
- return this._invoke('textHasJapaneseCharacters', details);
+ return this._invoke('textHasJapaneseCharacters', {text});
}
/**
- * @param {import('api').GetTermFrequenciesDetails['termReadingList']} termReadingList
- * @param {import('api').GetTermFrequenciesDetails['dictionaries']} dictionaries
- * @returns {Promise<import('api').GetTermFrequenciesResult>}
+ * @param {import('api').ApiParam<'getTermFrequencies', 'termReadingList'>} termReadingList
+ * @param {import('api').ApiParam<'getTermFrequencies', 'dictionaries'>} dictionaries
+ * @returns {Promise<import('api').ApiReturn<'getTermFrequencies'>>}
*/
getTermFrequencies(termReadingList, dictionaries) {
- /** @type {import('api').GetTermFrequenciesDetails} */
- const details = {termReadingList, dictionaries};
- return this._invoke('getTermFrequencies', details);
+ return this._invoke('getTermFrequencies', {termReadingList, dictionaries});
}
/**
- * @param {import('api').FindAnkiNotesDetails['query']} query
- * @returns {Promise<import('api').FindAnkiNotesResult>}
+ * @param {import('api').ApiParam<'findAnkiNotes', 'query'>} query
+ * @returns {Promise<import('api').ApiReturn<'findAnkiNotes'>>}
*/
findAnkiNotes(query) {
- /** @type {import('api').FindAnkiNotesDetails} */
- const details = {query};
- return this._invoke('findAnkiNotes', details);
+ return this._invoke('findAnkiNotes', {query});
}
/**
- * @param {import('api').LoadExtensionScriptsDetails['files']} files
- * @returns {Promise<import('api').LoadExtensionScriptsResult>}
+ * @param {import('api').ApiParam<'loadExtensionScripts', 'files'>} files
+ * @returns {Promise<import('api').ApiReturn<'loadExtensionScripts'>>}
*/
loadExtensionScripts(files) {
- /** @type {import('api').LoadExtensionScriptsDetails} */
- const details = {files};
- return this._invoke('loadExtensionScripts', details);
+ return this._invoke('loadExtensionScripts', {files});
}
/**
- * @param {import('api').OpenCrossFramePortDetails['targetTabId']} targetTabId
- * @param {import('api').OpenCrossFramePortDetails['targetFrameId']} targetFrameId
- * @returns {Promise<import('api').OpenCrossFramePortResult>}
+ * @param {import('api').ApiParam<'openCrossFramePort', 'targetTabId'>} targetTabId
+ * @param {import('api').ApiParam<'openCrossFramePort', 'targetFrameId'>} targetFrameId
+ * @returns {Promise<import('api').ApiReturn<'openCrossFramePort'>>}
*/
openCrossFramePort(targetTabId, targetFrameId) {
return this._invoke('openCrossFramePort', {targetTabId, targetFrameId});
@@ -425,12 +374,14 @@ export class API {
// Utilities
/**
- * @template [TReturn=unknown]
- * @param {string} action
- * @param {import('core').SerializableObject} [params]
- * @returns {Promise<TReturn>}
+ * @template {import('api').ApiNames} TAction
+ * @template {import('api').ApiParams<TAction>} TParams
+ * @param {TAction} action
+ * @param {TParams} params
+ * @returns {Promise<import('api').ApiReturn<TAction>>}
*/
- _invoke(action, params = {}) {
+ _invoke(action, params) {
+ /** @type {import('api').MessageAny} */
const data = {action, params};
return new Promise((resolve, reject) => {
try {
diff --git a/ext/js/display/query-parser.js b/ext/js/display/query-parser.js
index 3e59098f..0e7e1e1a 100644
--- a/ext/js/display/query-parser.js
+++ b/ext/js/display/query-parser.js
@@ -48,7 +48,7 @@ export class QueryParser extends EventDispatcher {
this._useInternalParser = true;
/** @type {boolean} */
this._useMecabParser = false;
- /** @type {import('api').ParseTextResult} */
+ /** @type {import('api').ParseTextResultItem[]} */
this._parseResults = [];
/** @type {HTMLElement} */
this._queryParser = querySelectorNotNull(document, '#query-parser-content');
@@ -252,7 +252,7 @@ export class QueryParser extends EventDispatcher {
/**
* @param {HTMLSelectElement} select
- * @param {import('api').ParseTextResult} parseResults
+ * @param {import('api').ParseTextResultItem[]} parseResults
* @param {?string} selectedParser
*/
_updateParserModeSelect(select, parseResults, selectedParser) {