diff options
60 files changed, 396 insertions, 274 deletions
diff --git a/.eslintrc.json b/.eslintrc.json index aea94cde..fbb47626 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -658,9 +658,8 @@ }, { "files": [ - "ext/js/core.js", "ext/js/core/extension-error.js", - "ext/js/yomitan.js", + "ext/js/application.js", "ext/js/accessibility/accessibility-controller.js", "ext/js/background/backend.js", "ext/js/background/offscreen.js", diff --git a/ext/js/app/content-script-main.js b/ext/js/app/content-script-main.js index c0bea73c..d77f1fa0 100644 --- a/ext/js/app/content-script-main.js +++ b/ext/js/app/content-script-main.js @@ -16,29 +16,31 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import {Application} from '../application.js'; import {log} from '../core/logger.js'; import {HotkeyHandler} from '../input/hotkey-handler.js'; -import {yomitan} from '../yomitan.js'; import {Frontend} from './frontend.js'; import {PopupFactory} from './popup-factory.js'; /** Entry point. */ async function main() { try { - await yomitan.prepare(); + const application = new Application(); + await application.prepare(); - const {tabId, frameId} = await yomitan.api.frameInformationGet(); + const {tabId, frameId} = await application.api.frameInformationGet(); if (typeof frameId !== 'number') { throw new Error('Failed to get frameId'); } const hotkeyHandler = new HotkeyHandler(); - hotkeyHandler.prepare(); + hotkeyHandler.prepare(application.crossFrame); - const popupFactory = new PopupFactory(frameId); + const popupFactory = new PopupFactory(application, frameId); popupFactory.prepare(); const frontend = new Frontend({ + application, tabId, frameId, popupFactory, @@ -54,7 +56,7 @@ async function main() { }); await frontend.prepare(); - yomitan.ready(); + application.ready(); } catch (e) { log.error(e); } diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index d1c32b03..de1c5a46 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -25,7 +25,6 @@ import {TextSourceElement} from '../dom/text-source-element.js'; import {TextSourceGenerator} from '../dom/text-source-generator.js'; import {TextSourceRange} from '../dom/text-source-range.js'; import {TextScanner} from '../language/text-scanner.js'; -import {yomitan} from '../yomitan.js'; /** * This is the main class responsible for scanning and handling webpage content. @@ -36,6 +35,7 @@ export class Frontend { * @param {import('frontend').ConstructorDetails} details Details about how to set up the instance. */ constructor({ + application, pageType, popupFactory, depth, @@ -49,6 +49,8 @@ export class Frontend { childrenSupported = true, hotkeyHandler }) { + /** @type {import('../application.js').Application} */ + this._application = application; /** @type {import('frontend').PageType} */ this._pageType = pageType; /** @type {import('./popup-factory.js').PopupFactory} */ @@ -89,6 +91,7 @@ export class Frontend { this._textSourceGenerator = new TextSourceGenerator(); /** @type {TextScanner} */ this._textScanner = new TextScanner({ + api: application.api, node: window, ignoreElements: this._ignoreElements.bind(this), ignorePoint: this._ignorePoint.bind(this), @@ -157,7 +160,7 @@ export class Frontend { async prepare() { await this.updateOptions(); try { - const {zoomFactor} = await yomitan.api.getZoom(); + const {zoomFactor} = await this._application.api.getZoom(); this._pageZoomFactor = zoomFactor; } catch (e) { // Ignore exceptions which may occur due to being on an unsupported page (e.g. about:blank) @@ -174,16 +177,16 @@ export class Frontend { visualViewport.addEventListener('resize', this._onVisualViewportResize.bind(this)); } - yomitan.on('optionsUpdated', this.updateOptions.bind(this)); - yomitan.on('zoomChanged', this._onZoomChanged.bind(this)); - yomitan.on('closePopups', this._onClosePopups.bind(this)); + this._application.on('optionsUpdated', this.updateOptions.bind(this)); + this._application.on('zoomChanged', this._onZoomChanged.bind(this)); + this._application.on('closePopups', this._onClosePopups.bind(this)); chrome.runtime.onMessage.addListener(this._onRuntimeMessage.bind(this)); this._textScanner.on('clear', this._onTextScannerClear.bind(this)); this._textScanner.on('searched', this._onSearched.bind(this)); /* eslint-disable no-multi-spaces */ - yomitan.crossFrame.registerHandlers([ + this._application.crossFrame.registerHandlers([ ['frontendClosePopup', this._onApiClosePopup.bind(this)], ['frontendCopySelection', this._onApiCopySelection.bind(this)], ['frontendGetSelectionText', this._onApiGetSelectionText.bind(this)], @@ -230,7 +233,7 @@ export class Frontend { try { await this._updateOptionsInternal(); } catch (e) { - if (!yomitan.webExtension.unloaded) { + if (!this._application.webExtension.unloaded) { throw e; } } @@ -372,7 +375,7 @@ export class Frontend { const scanningOptions = /** @type {import('settings').ProfileOptions} */ (this._options).scanning; if (error !== null) { - if (yomitan.webExtension.unloaded) { + if (this._application.webExtension.unloaded) { if (textSource !== null && !passive) { this._showExtensionUnloaded(textSource); } @@ -461,7 +464,7 @@ export class Frontend { */ async _updateOptionsInternal() { const optionsContext = await this._getOptionsContext(); - const options = await yomitan.api.optionsGet(optionsContext); + const options = await this._application.api.optionsGet(optionsContext); const {scanning: scanningOptions, sentenceParsing: sentenceParsingOptions} = options; this._options = options; @@ -609,7 +612,7 @@ export class Frontend { return await this._getDefaultPopup(); } - const {popupId} = await yomitan.crossFrame.invoke(targetFrameId, 'frontendGetPopupInfo', void 0); + const {popupId} = await this._application.crossFrame.invoke(targetFrameId, 'frontendGetPopupInfo', void 0); if (popupId === null) { return null; } @@ -659,7 +662,7 @@ export class Frontend { try { return this._popup !== null && await this._popup.containsPoint(x, y); } catch (e) { - if (!yomitan.webExtension.unloaded) { + if (!this._application.webExtension.unloaded) { throw e; } return false; @@ -746,7 +749,7 @@ export class Frontend { Promise.resolve() ); this._lastShowPromise.catch((error) => { - if (yomitan.webExtension.unloaded) { return; } + if (this._application.webExtension.unloaded) { return; } log.error(error); }); return this._lastShowPromise; @@ -811,9 +814,9 @@ export class Frontend { /** @type {import('application').ApiMessageNoFrameId<'frontendReady'>} */ const message = {action: 'frontendReady', params: {frameId: this._frameId}}; if (targetFrameId === null) { - yomitan.api.broadcastTab(message); + this._application.api.broadcastTab(message); } else { - yomitan.api.sendMessageToFrame(targetFrameId, message); + this._application.api.sendMessageToFrame(targetFrameId, message); } } @@ -857,7 +860,7 @@ export class Frontend { } chrome.runtime.onMessage.addListener(onMessage); - yomitan.api.broadcastTab({action: 'frontendRequestReadyBroadcast', params: {frameId: this._frameId}}); + this._application.api.broadcastTab({action: 'frontendRequestReadyBroadcast', params: {frameId: this._frameId}}); }); } @@ -892,7 +895,7 @@ export class Frontend { let documentTitle = document.title; if (this._useProxyPopup && this._parentFrameId !== null) { try { - ({url, documentTitle} = await yomitan.crossFrame.invoke(this._parentFrameId, 'frontendGetPageInfo', void 0)); + ({url, documentTitle} = await this._application.crossFrame.invoke(this._parentFrameId, 'frontendGetPageInfo', void 0)); } catch (e) { // NOP } diff --git a/ext/js/app/popup-factory.js b/ext/js/app/popup-factory.js index f9eec913..1b7d21db 100644 --- a/ext/js/app/popup-factory.js +++ b/ext/js/app/popup-factory.js @@ -18,7 +18,6 @@ import {FrameOffsetForwarder} from '../comm/frame-offset-forwarder.js'; import {generateId} from '../core/utilities.js'; -import {yomitan} from '../yomitan.js'; import {PopupProxy} from './popup-proxy.js'; import {PopupWindow} from './popup-window.js'; import {Popup} from './popup.js'; @@ -29,13 +28,16 @@ import {Popup} from './popup.js'; export class PopupFactory { /** * Creates a new instance. + * @param {import('../application.js').Application} application * @param {number} frameId The frame ID of the host frame. */ - constructor(frameId) { + constructor(application, frameId) { + /** @type {import('../application.js').Application} */ + this._application = application; /** @type {number} */ this._frameId = frameId; /** @type {FrameOffsetForwarder} */ - this._frameOffsetForwarder = new FrameOffsetForwarder(frameId); + this._frameOffsetForwarder = new FrameOffsetForwarder(application.crossFrame, frameId); /** @type {Map<string, import('popup').PopupAny>} */ this._popups = new Map(); /** @type {Map<string, {popup: import('popup').PopupAny, token: string}[]>} */ @@ -48,7 +50,7 @@ export class PopupFactory { prepare() { this._frameOffsetForwarder.prepare(); /* eslint-disable no-multi-spaces */ - yomitan.crossFrame.registerHandlers([ + this._application.crossFrame.registerHandlers([ ['popupFactoryGetOrCreatePopup', this._onApiGetOrCreatePopup.bind(this)], ['popupFactorySetOptionsContext', this._onApiSetOptionsContext.bind(this)], ['popupFactoryHide', this._onApiHide.bind(this)], @@ -119,6 +121,7 @@ export class PopupFactory { id = generateId(16); } const popup = new PopupWindow({ + application: this._application, id, depth, frameId: this._frameId @@ -131,6 +134,7 @@ export class PopupFactory { id = generateId(16); } const popup = new Popup({ + application: this._application, id, depth, frameId: this._frameId, @@ -152,7 +156,7 @@ export class PopupFactory { } const useFrameOffsetForwarder = (parentPopupId === null); /** @type {{id: string, depth: number, frameId: number}} */ - const info = await yomitan.crossFrame.invoke(frameId, 'popupFactoryGetOrCreatePopup', /** @type {import('popup-factory').GetOrCreatePopupDetails} */ ({ + const info = await this._application.crossFrame.invoke(frameId, 'popupFactoryGetOrCreatePopup', /** @type {import('popup-factory').GetOrCreatePopupDetails} */ ({ id, parentPopupId, frameId, @@ -160,6 +164,7 @@ export class PopupFactory { })); id = info.id; const popup = new PopupProxy({ + application: this._application, id, depth: info.depth, frameId: info.frameId, diff --git a/ext/js/app/popup-proxy.js b/ext/js/app/popup-proxy.js index 856ec086..3632b8cb 100644 --- a/ext/js/app/popup-proxy.js +++ b/ext/js/app/popup-proxy.js @@ -18,7 +18,6 @@ import {EventDispatcher} from '../core/event-dispatcher.js'; import {log} from '../core/logger.js'; -import {yomitan} from '../yomitan.js'; /** * This class is a proxy for a Popup that is hosted in a different frame. @@ -31,12 +30,15 @@ export class PopupProxy extends EventDispatcher { * @param {import('popup').PopupProxyConstructorDetails} details Details about how to set up the instance. */ constructor({ + application, id, depth, frameId, frameOffsetForwarder }) { super(); + /** @type {import('../application.js').Application} */ + this._application = application; /** @type {string} */ this._id = id; /** @type {number} */ @@ -305,7 +307,7 @@ export class PopupProxy extends EventDispatcher { * @returns {Promise<import('cross-frame-api').ApiReturn<TName>>} */ _invoke(action, params) { - return yomitan.crossFrame.invoke(this._frameId, action, params); + return this._application.crossFrame.invoke(this._frameId, action, params); } /** @@ -320,7 +322,7 @@ export class PopupProxy extends EventDispatcher { try { return await this._invoke(action, params); } catch (e) { - if (!yomitan.webExtension.unloaded) { throw e; } + if (!this._application.webExtension.unloaded) { throw e; } return defaultReturnValue; } } diff --git a/ext/js/app/popup-window.js b/ext/js/app/popup-window.js index 7a0b6af4..32c4d67b 100644 --- a/ext/js/app/popup-window.js +++ b/ext/js/app/popup-window.js @@ -17,7 +17,6 @@ */ import {EventDispatcher} from '../core/event-dispatcher.js'; -import {yomitan} from '../yomitan.js'; /** * This class represents a popup that is hosted in a new native window. @@ -29,11 +28,14 @@ export class PopupWindow extends EventDispatcher { * @param {import('popup').PopupWindowConstructorDetails} details Details about how to set up the instance. */ constructor({ + application, id, depth, frameId }) { super(); + /** @type {import('../application.js').Application} */ + this._application = application; /** @type {string} */ this._id = id; /** @type {number} */ @@ -142,7 +144,7 @@ export class PopupWindow extends EventDispatcher { * @returns {Promise<boolean>} `true` if the popup is visible, `false` otherwise. */ async isVisible() { - return (this._popupTabId !== null && await yomitan.api.isTabSearchPopup(this._popupTabId)); + return (this._popupTabId !== null && await this._application.api.isTabSearchPopup(this._popupTabId)); } /** @@ -274,7 +276,7 @@ export class PopupWindow extends EventDispatcher { * @returns {Promise<import('display').DirectApiReturn<TName>|undefined>} */ async _invoke(open, action, params) { - if (yomitan.webExtension.unloaded) { + if (this._application.webExtension.unloaded) { return void 0; } @@ -283,14 +285,14 @@ export class PopupWindow extends EventDispatcher { const frameId = 0; if (this._popupTabId !== null) { try { - return /** @type {import('display').DirectApiReturn<TName>} */ (await yomitan.crossFrame.invokeTab( + return /** @type {import('display').DirectApiReturn<TName>} */ (await this._application.crossFrame.invokeTab( this._popupTabId, frameId, 'displayPopupMessage2', message )); } catch (e) { - if (yomitan.webExtension.unloaded) { + if (this._application.webExtension.unloaded) { open = false; } } @@ -301,10 +303,10 @@ export class PopupWindow extends EventDispatcher { return void 0; } - const {tabId} = await yomitan.api.getOrCreateSearchPopup({focus: 'ifCreated'}); + const {tabId} = await this._application.api.getOrCreateSearchPopup({focus: 'ifCreated'}); this._popupTabId = tabId; - return /** @type {import('display').DirectApiReturn<TName>} */ (await yomitan.crossFrame.invokeTab( + return /** @type {import('display').DirectApiReturn<TName>} */ (await this._application.crossFrame.invokeTab( this._popupTabId, frameId, 'displayPopupMessage2', diff --git a/ext/js/app/popup.js b/ext/js/app/popup.js index c741e8f1..08ff0661 100644 --- a/ext/js/app/popup.js +++ b/ext/js/app/popup.js @@ -24,7 +24,6 @@ import {ExtensionError} from '../core/extension-error.js'; import {deepEqual} from '../core/utilities.js'; import {DocumentUtil} from '../dom/document-util.js'; import {loadStyle} from '../dom/style-util.js'; -import {yomitan} from '../yomitan.js'; import {ThemeController} from './theme-controller.js'; /** @@ -37,12 +36,15 @@ export class Popup extends EventDispatcher { * @param {import('popup').PopupConstructorDetails} details The details used to construct the new instance. */ constructor({ + application, id, depth, frameId, childrenSupported }) { super(); + /** @type {import('../application.js').Application} */ + this._application = application; /** @type {string} */ this._id = id; /** @type {number} */ @@ -206,7 +208,7 @@ export class Popup extends EventDispatcher { this._frame.addEventListener('scroll', (e) => e.stopPropagation()); this._frame.addEventListener('load', this._onFrameLoad.bind(this)); this._visible.on('change', this._onVisibleChange.bind(this)); - yomitan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this)); + this._application.on('extensionUnloaded', this._onExtensionUnloaded.bind(this)); this._onVisibleChange({value: this.isVisibleSync()}); this._themeController.prepare(); } @@ -362,7 +364,7 @@ export class Popup extends EventDispatcher { useWebExtensionApi = false; parentNode = this._shadow; } - const node = await loadStyle('yomitan-popup-outer-user-stylesheet', 'code', css, useWebExtensionApi, parentNode); + const node = await loadStyle(this._application, 'yomitan-popup-outer-user-stylesheet', 'code', css, useWebExtensionApi, parentNode); this.trigger('customOuterCssChanged', {node, useWebExtensionApi, inShadow}); } @@ -575,7 +577,7 @@ export class Popup extends EventDispatcher { useWebExtensionApi = false; parentNode = this._shadow; } - await loadStyle('yomitan-popup-outer-stylesheet', fileType, '/css/popup-outer.css', useWebExtensionApi, parentNode); + await loadStyle(this._application, 'yomitan-popup-outer-stylesheet', fileType, '/css/popup-outer.css', useWebExtensionApi, parentNode); } /** @@ -697,7 +699,7 @@ export class Popup extends EventDispatcher { /** @type {import('display').DirectApiMessage<TName>} */ const message = {action, params}; const wrappedMessage = this._frameClient.createMessage(message); - return /** @type {import('display').DirectApiReturn<TName>} */ (await yomitan.crossFrame.invoke( + return /** @type {import('display').DirectApiReturn<TName>} */ (await this._application.crossFrame.invoke( this._frameClient.frameId, 'displayPopupMessage1', /** @type {import('display').DirectApiFrameClientMessageAny} */ (wrappedMessage) @@ -714,7 +716,7 @@ export class Popup extends EventDispatcher { try { return await this._invoke(action, params); } catch (e) { - if (!yomitan.webExtension.unloaded) { throw e; } + if (!this._application.webExtension.unloaded) { throw e; } return void 0; } } @@ -1008,7 +1010,7 @@ export class Popup extends EventDispatcher { */ async _setOptionsContext(optionsContext) { this._optionsContext = optionsContext; - const options = await yomitan.api.optionsGet(optionsContext); + const options = await this._application.api.optionsGet(optionsContext); const {general} = options; this._themeController.theme = general.popupTheme; this._themeController.outerTheme = general.popupOuterTheme; diff --git a/ext/js/yomitan.js b/ext/js/application.js index 33afac27..87bd0e86 100644 --- a/ext/js/yomitan.js +++ b/ext/js/application.js @@ -55,7 +55,7 @@ if (checkChromeNotAvailable()) { * The Yomitan class is a core component through which various APIs are handled and invoked. * @augments EventDispatcher<import('application').Events> */ -export class Yomitan extends EventDispatcher { +export class Application extends EventDispatcher { /** * Creates a new instance. The instance should not be used until it has been fully prepare()'d. */ @@ -158,7 +158,7 @@ export class Yomitan extends EventDispatcher { await this._webExtension.sendMessagePromise({action: 'requestBackendReadySignal'}); await this._isBackendReadyPromise; - this._crossFrame = new CrossFrameAPI(); + this._crossFrame = new CrossFrameAPI(this._api); await this._crossFrame.prepare(); log.on('log', this._onForwardLog.bind(this)); @@ -253,8 +253,3 @@ export class Yomitan extends EventDispatcher { } } } - -/** - * The default Yomitan class instance. - */ -export const yomitan = new Yomitan(); diff --git a/ext/js/background/background-main.js b/ext/js/background/background-main.js index f5871a14..b63b4396 100644 --- a/ext/js/background/background-main.js +++ b/ext/js/background/background-main.js @@ -16,14 +16,15 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import {yomitan} from '../yomitan.js'; +import {Application} from '../application.js'; import {Backend} from './backend.js'; /** Entry point. */ async function main() { - yomitan.prepare(true); + const application = new Application(); + application.prepare(true); - const backend = new Backend(yomitan.webExtension); + const backend = new Backend(application.webExtension); await backend.prepare(); } diff --git a/ext/js/comm/cross-frame-api.js b/ext/js/comm/cross-frame-api.js index fca7c84d..eb9bed38 100644 --- a/ext/js/comm/cross-frame-api.js +++ b/ext/js/comm/cross-frame-api.js @@ -22,7 +22,6 @@ import {EventListenerCollection} from '../core/event-listener-collection.js'; import {ExtensionError} from '../core/extension-error.js'; import {parseJson} from '../core/json.js'; import {log} from '../core/logger.js'; -import {yomitan} from '../yomitan.js'; /** * @augments EventDispatcher<import('cross-frame-api').CrossFrameAPIPortEvents> @@ -290,7 +289,12 @@ export class CrossFrameAPIPort extends EventDispatcher { } export class CrossFrameAPI { - constructor() { + /** + * @param {import('../comm/api.js').API} api + */ + constructor(api) { + /** @type {import('../comm/api.js').API} */ + this._api = api; /** @type {number} */ this._ackTimeout = 3000; // 3 seconds /** @type {number} */ @@ -310,7 +314,7 @@ export class CrossFrameAPI { /** */ async prepare() { chrome.runtime.onConnect.addListener(this._onConnect.bind(this)); - ({tabId: this._tabId = null, frameId: this._frameId = null} = await yomitan.api.frameInformationGet()); + ({tabId: this._tabId = null, frameId: this._frameId = null} = await this._api.frameInformationGet()); } /** @@ -411,7 +415,7 @@ export class CrossFrameAPI { * @returns {Promise<CrossFrameAPIPort>} */ async _createCommPort(otherTabId, otherFrameId) { - await yomitan.api.openCrossFramePort(otherTabId, otherFrameId); + await this._api.openCrossFramePort(otherTabId, otherFrameId); const tabPorts = this._commPorts.get(otherTabId); if (typeof tabPorts !== 'undefined') { diff --git a/ext/js/comm/frame-ancestry-handler.js b/ext/js/comm/frame-ancestry-handler.js index 31739654..3e58d57b 100644 --- a/ext/js/comm/frame-ancestry-handler.js +++ b/ext/js/comm/frame-ancestry-handler.js @@ -17,7 +17,6 @@ */ import {generateId} from '../core/utilities.js'; -import {yomitan} from '../yomitan.js'; /** * This class is used to return the ancestor frame IDs for the current frame. @@ -28,9 +27,12 @@ import {yomitan} from '../yomitan.js'; export class FrameAncestryHandler { /** * Creates a new instance. + * @param {import('../comm/cross-frame-api.js').CrossFrameAPI} crossFrameApi * @param {number} frameId The frame ID of the current frame the instance is instantiated in. */ - constructor(frameId) { + constructor(crossFrameApi, frameId) { + /** @type {import('../comm/cross-frame-api.js').CrossFrameAPI} */ + this._crossFrameApi = crossFrameApi; /** @type {number} */ this._frameId = frameId; /** @type {boolean} */ @@ -59,7 +61,7 @@ export class FrameAncestryHandler { prepare() { if (this._isPrepared) { return; } window.addEventListener('message', this._onWindowMessage.bind(this), false); - yomitan.crossFrame.registerHandlers([ + this._crossFrameApi.registerHandlers([ ['frameAncestryHandlerRequestFrameInfoResponse', this._onFrameAncestryHandlerRequestFrameInfoResponse.bind(this)] ]); this._isPrepared = true; @@ -211,7 +213,7 @@ export class FrameAncestryHandler { const more = (window !== parent); try { - const response = await yomitan.crossFrame.invoke(originFrameId, 'frameAncestryHandlerRequestFrameInfoResponse', {uniqueId, frameId, nonce, more}); + const response = await this._crossFrameApi.invoke(originFrameId, 'frameAncestryHandlerRequestFrameInfoResponse', {uniqueId, frameId, nonce, more}); if (response === null) { return; } const nonce2 = response.nonce; if (typeof nonce2 !== 'string') { return; } diff --git a/ext/js/comm/frame-endpoint.js b/ext/js/comm/frame-endpoint.js index 0008417d..d2002d2e 100644 --- a/ext/js/comm/frame-endpoint.js +++ b/ext/js/comm/frame-endpoint.js @@ -18,10 +18,14 @@ import {EventListenerCollection} from '../core/event-listener-collection.js'; import {generateId} from '../core/utilities.js'; -import {yomitan} from '../yomitan.js'; export class FrameEndpoint { - constructor() { + /** + * @param {import('../comm/api.js').API} api + */ + constructor(api) { + /** @type {import('../comm/api.js').API} */ + this._api = api; /** @type {string} */ this._secret = generateId(16); /** @type {?string} */ @@ -42,7 +46,7 @@ export class FrameEndpoint { } /** @type {import('frame-client').FrameEndpointReadyDetails} */ const details = {secret: this._secret}; - yomitan.api.broadcastTab({action: 'frameEndpointReady', params: details}); + this._api.broadcastTab({action: 'frameEndpointReady', params: details}); } /** @@ -84,6 +88,6 @@ export class FrameEndpoint { this._eventListeners.removeAllEventListeners(); /** @type {import('frame-client').FrameEndpointConnectedDetails} */ const details = {secret, token}; - yomitan.api.sendMessageToFrame(hostFrameId, {action: 'frameEndpointConnected', params: details}); + this._api.sendMessageToFrame(hostFrameId, {action: 'frameEndpointConnected', params: details}); } } diff --git a/ext/js/comm/frame-offset-forwarder.js b/ext/js/comm/frame-offset-forwarder.js index a1f249e2..fe1ff98a 100644 --- a/ext/js/comm/frame-offset-forwarder.js +++ b/ext/js/comm/frame-offset-forwarder.js @@ -16,18 +16,20 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import {yomitan} from '../yomitan.js'; import {FrameAncestryHandler} from './frame-ancestry-handler.js'; export class FrameOffsetForwarder { /** + * @param {import('../comm/cross-frame-api.js').CrossFrameAPI} crossFrameApi * @param {number} frameId */ - constructor(frameId) { + constructor(crossFrameApi, frameId) { + /** @type {import('../comm/cross-frame-api.js').CrossFrameAPI} */ + this._crossFrameApi = crossFrameApi; /** @type {number} */ this._frameId = frameId; /** @type {FrameAncestryHandler} */ - this._frameAncestryHandler = new FrameAncestryHandler(frameId); + this._frameAncestryHandler = new FrameAncestryHandler(crossFrameApi, frameId); } /** @@ -35,7 +37,7 @@ export class FrameOffsetForwarder { */ prepare() { this._frameAncestryHandler.prepare(); - yomitan.crossFrame.registerHandlers([ + this._crossFrameApi.registerHandlers([ ['frameOffsetForwarderGetChildFrameRect', this._onMessageGetChildFrameRect.bind(this)] ]); } @@ -55,7 +57,7 @@ export class FrameOffsetForwarder { /** @type {Promise<?import('frame-offset-forwarder').ChildFrameRect>[]} */ const promises = []; for (const frameId of ancestorFrameIds) { - promises.push(yomitan.crossFrame.invoke(frameId, 'frameOffsetForwarderGetChildFrameRect', {frameId: childFrameId})); + promises.push(this._crossFrameApi.invoke(frameId, 'frameOffsetForwarderGetChildFrameRect', {frameId: childFrameId})); childFrameId = frameId; } diff --git a/ext/js/data/anki-note-builder.js b/ext/js/data/anki-note-builder.js index 5bb943c2..a6e1aafc 100644 --- a/ext/js/data/anki-note-builder.js +++ b/ext/js/data/anki-note-builder.js @@ -19,15 +19,17 @@ import {ExtensionError} from '../core/extension-error.js'; import {deferPromise} from '../core/utilities.js'; import {convertHiraganaToKatakana, convertKatakanaToHiragana} from '../language/japanese.js'; -import {yomitan} from '../yomitan.js'; import {cloneFieldMarkerPattern, getRootDeckName} from './anki-util.js'; export class AnkiNoteBuilder { /** * Initiate an instance of AnkiNoteBuilder. + * @param {import('anki-note-builder').MinimalApi} api * @param {import('../templates/template-renderer-proxy.js').TemplateRendererProxy|import('../templates/sandbox/template-renderer.js').TemplateRenderer} templateRenderer */ - constructor(templateRenderer) { + constructor(api, templateRenderer) { + /** @type {import('anki-note-builder').MinimalApi} */ + this._api = api; /** @type {RegExp} */ this._markerPattern = cloneFieldMarkerPattern(true); /** @type {import('../templates/template-renderer-proxy.js').TemplateRendererProxy|import('../templates/sandbox/template-renderer.js').TemplateRenderer} */ @@ -431,7 +433,7 @@ export class AnkiNoteBuilder { // Inject media const selectionText = injectSelectionText ? this._getSelectionText() : null; - const injectedMedia = await yomitan.api.injectAnkiNoteMedia( + const injectedMedia = await this._api.injectAnkiNoteMedia( timestamp, dictionaryEntryDetails, audioDetails, @@ -483,7 +485,7 @@ export class AnkiNoteBuilder { async _getTextFurigana(entries, optionsContext, scanLength) { const results = []; for (const {text, readingMode} of entries) { - const parseResults = await yomitan.api.parseText(text, optionsContext, scanLength, true, false); + const parseResults = await this._api.parseText(text, optionsContext, scanLength, true, false); let data = null; for (const {source, content} of parseResults) { if (source !== 'scanning-parser') { continue; } diff --git a/ext/js/display/display-anki.js b/ext/js/display/display-anki.js index 68d28d33..c19cfa22 100644 --- a/ext/js/display/display-anki.js +++ b/ext/js/display/display-anki.js @@ -24,7 +24,6 @@ import {isNoteDataValid} from '../data/anki-util.js'; import {PopupMenu} from '../dom/popup-menu.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; import {TemplateRendererProxy} from '../templates/template-renderer-proxy.js'; -import {yomitan} from '../yomitan.js'; export class DisplayAnki { /** @@ -41,7 +40,7 @@ export class DisplayAnki { /** @type {?string} */ this._ankiFieldTemplatesDefault = null; /** @type {AnkiNoteBuilder} */ - this._ankiNoteBuilder = new AnkiNoteBuilder(new TemplateRendererProxy()); + this._ankiNoteBuilder = new AnkiNoteBuilder(display.application.api, new TemplateRendererProxy()); /** @type {?import('./display-notification.js').DisplayNotification} */ this._errorNotification = null; /** @type {?EventListenerCollection} */ @@ -487,7 +486,7 @@ export class DisplayAnki { let noteId = null; let addNoteOkay = false; try { - noteId = await yomitan.api.addAnkiNote(note); + noteId = await this._display.application.api.addAnkiNote(note); addNoteOkay = true; } catch (e) { allErrors.length = 0; @@ -500,7 +499,7 @@ export class DisplayAnki { } else { if (this._suspendNewCards) { try { - await yomitan.api.suspendAnkiCardsForNote(noteId); + await this._display.application.api.suspendAnkiCardsForNote(noteId); } catch (e) { allErrors.push(toError(e)); } @@ -605,7 +604,7 @@ export class DisplayAnki { templates = this._ankiFieldTemplatesDefault; if (typeof templates === 'string') { return templates; } - templates = await yomitan.api.getDefaultAnkiFieldTemplates(); + templates = await this._display.application.api.getDefaultAnkiFieldTemplates(); this._ankiFieldTemplatesDefault = templates; return templates; } @@ -639,12 +638,12 @@ export class DisplayAnki { let ankiError = null; try { if (forceCanAddValue !== null) { - if (!await yomitan.api.isAnkiConnected()) { + if (!await this._display.application.api.isAnkiConnected()) { throw new Error('Anki not connected'); } infos = this._getAnkiNoteInfoForceValue(notes, forceCanAddValue); } else { - infos = await yomitan.api.getAnkiNoteInfo(notes, fetchAdditionalInfo); + infos = await this._display.application.api.getAnkiNoteInfo(notes, fetchAdditionalInfo); } } catch (e) { infos = this._getAnkiNoteInfoForceValue(notes, false); @@ -853,7 +852,7 @@ export class DisplayAnki { const noteIds = this._getNodeNoteIds(node); if (noteIds.length === 0) { return; } try { - await yomitan.api.noteView(noteIds[0], this._noteGuiMode, false); + await this._display.application.api.noteView(noteIds[0], this._noteGuiMode, false); } catch (e) { const displayErrors = ( toError(e).message === 'Mode not supported' ? diff --git a/ext/js/display/display-audio.js b/ext/js/display/display-audio.js index 8cbfc83f..4acd6494 100644 --- a/ext/js/display/display-audio.js +++ b/ext/js/display/display-audio.js @@ -20,7 +20,6 @@ import {EventListenerCollection} from '../core/event-listener-collection.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'; export class DisplayAudio { /** @@ -676,7 +675,7 @@ export class DisplayAudio { */ async _getTermAudioInfoList(source, term, reading) { const sourceData = this._getSourceData(source); - const infoList = await yomitan.api.getTermAudioInfoList(sourceData, term, reading); + const infoList = await this._display.application.api.getTermAudioInfoList(sourceData, term, reading); return infoList.map((info) => ({info, audioPromise: null, audioResolved: false, audio: null})); } diff --git a/ext/js/display/display-content-manager.js b/ext/js/display/display-content-manager.js index 4465ce3e..81742279 100644 --- a/ext/js/display/display-content-manager.js +++ b/ext/js/display/display-content-manager.js @@ -18,7 +18,6 @@ import {EventListenerCollection} from '../core/event-listener-collection.js'; import {base64ToArrayBuffer} from '../data/sandbox/array-buffer-util.js'; -import {yomitan} from '../yomitan.js'; /** * The content manager which is used when generating HTML display content. @@ -140,7 +139,7 @@ export class DisplayContentManager { */ async _getMediaData(path, dictionary) { const token = this._token; - const datas = await yomitan.api.getMedia([{path, dictionary}]); + const datas = await this._display.application.api.getMedia([{path, dictionary}]); if (token === this._token && datas.length > 0) { const data = datas[0]; const buffer = base64ToArrayBuffer(data.content); diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js index 01f6f38b..fdfe3d4a 100644 --- a/ext/js/display/display-generator.js +++ b/ext/js/display/display-generator.js @@ -21,7 +21,6 @@ import {isObject} from '../core/utilities.js'; import {getDisambiguations, getGroupedPronunciations, getTermFrequency, groupKanjiFrequencies, groupTermFrequencies, groupTermTags, isNonNounVerbOrAdjective} from '../dictionary/dictionary-data-util.js'; import {HtmlTemplateCollection} from '../dom/html-template-collection.js'; import {distributeFurigana, getKanaMorae, getPitchCategory, isCodePointKanji, isStringPartiallyJapanese} from '../language/japanese.js'; -import {yomitan} from '../yomitan.js'; import {createPronunciationDownstepPosition, createPronunciationGraph, createPronunciationText} from './sandbox/pronunciation-generator.js'; import {StructuredContentGenerator} from './sandbox/structured-content-generator.js'; @@ -40,9 +39,11 @@ export class DisplayGenerator { this._structuredContentGenerator = new StructuredContentGenerator(this._contentManager, document); } - /** */ - async prepare() { - const html = await yomitan.api.getDisplayTemplatesHtml(); + /** + * @param {import('../comm/api.js').API} api + */ + async prepare(api) { + const html = await api.getDisplayTemplatesHtml(); this._templates.load(html); this.updateHotkeys(); } diff --git a/ext/js/display/display-profile-selection.js b/ext/js/display/display-profile-selection.js index b61b49d5..3df79b74 100644 --- a/ext/js/display/display-profile-selection.js +++ b/ext/js/display/display-profile-selection.js @@ -20,7 +20,6 @@ import {EventListenerCollection} from '../core/event-listener-collection.js'; import {generateId} from '../core/utilities.js'; import {PanelElement} from '../dom/panel-element.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; -import {yomitan} from '../yomitan.js'; export class DisplayProfileSelection { /** @@ -50,7 +49,7 @@ export class DisplayProfileSelection { /** */ async prepare() { - yomitan.on('optionsUpdated', this._onOptionsUpdated.bind(this)); + this._display.application.on('optionsUpdated', this._onOptionsUpdated.bind(this)); this._profileButton.addEventListener('click', this._onProfileButtonClick.bind(this), false); this._profileListNeedsUpdate = true; } @@ -92,7 +91,7 @@ export class DisplayProfileSelection { /** */ async _updateProfileList() { this._profileListNeedsUpdate = false; - const options = await yomitan.api.optionsGetFull(); + const options = await this._display.application.api.optionsGetFull(); this._eventListeners.removeAllEventListeners(); const displayGenerator = this._display.displayGenerator; @@ -138,7 +137,7 @@ export class DisplayProfileSelection { scope: 'global', optionsContext: null }; - await yomitan.api.modifySettings([modification], this._source); + await this._display.application.api.modifySettings([modification], this._source); this._setProfilePanelVisible(false); } } diff --git a/ext/js/display/display.js b/ext/js/display/display.js index c7a2775d..4114cc45 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -32,7 +32,6 @@ import {ScrollElement} from '../dom/scroll-element.js'; import {TextSourceGenerator} from '../dom/text-source-generator.js'; import {HotkeyHelpController} from '../input/hotkey-help-controller.js'; import {TextScanner} from '../language/text-scanner.js'; -import {yomitan} from '../yomitan.js'; import {DisplayContentManager} from './display-content-manager.js'; import {DisplayGenerator} from './display-generator.js'; import {DisplayHistory} from './display-history.js'; @@ -46,14 +45,17 @@ import {QueryParser} from './query-parser.js'; */ export class Display extends EventDispatcher { /** + * @param {import('../application.js').Application} application * @param {number|undefined} tabId * @param {number|undefined} frameId * @param {import('display').DisplayPageType} pageType * @param {import('../dom/document-focus-controller.js').DocumentFocusController} documentFocusController * @param {import('../input/hotkey-handler.js').HotkeyHandler} hotkeyHandler */ - constructor(tabId, frameId, pageType, documentFocusController, hotkeyHandler) { + constructor(application, tabId, frameId, pageType, documentFocusController, hotkeyHandler) { super(); + /** @type {import('../application.js').Application} */ + this._application = application; /** @type {number|undefined} */ this._tabId = tabId; /** @type {number|undefined} */ @@ -131,6 +133,7 @@ export class Display extends EventDispatcher { this._textSourceGenerator = new TextSourceGenerator(); /** @type {QueryParser} */ this._queryParser = new QueryParser({ + api: application.api, getSearchContext: this._getSearchContext.bind(this), textSourceGenerator: this._textSourceGenerator }); @@ -163,7 +166,7 @@ export class Display extends EventDispatcher { /** @type {boolean} */ this._childrenSupported = true; /** @type {?FrameEndpoint} */ - this._frameEndpoint = (pageType === 'popup' ? new FrameEndpoint() : null); + this._frameEndpoint = (pageType === 'popup' ? new FrameEndpoint(this._application.api) : null); /** @type {?import('environment').Browser} */ this._browser = null; /** @type {?HTMLTextAreaElement} */ @@ -224,6 +227,11 @@ export class Display extends EventDispatcher { /* eslint-enable no-multi-spaces */ } + /** @type {import('../application.js').Application} */ + get application() { + return this._application; + } + /** @type {DisplayGenerator} */ get displayGenerator() { return this._displayGenerator; @@ -307,7 +315,7 @@ export class Display extends EventDispatcher { // State setup const {documentElement} = document; - const {browser} = await yomitan.api.getEnvironmentInfo(); + const {browser} = await this._application.api.getEnvironmentInfo(); this._browser = browser; if (documentElement !== null) { @@ -315,8 +323,8 @@ export class Display extends EventDispatcher { } // Prepare - await this._hotkeyHelpController.prepare(); - await this._displayGenerator.prepare(); + await this._hotkeyHelpController.prepare(this._application.api); + await this._displayGenerator.prepare(this._application.api); this._queryParser.prepare(); this._history.prepare(); this._optionToggleHotkeyHandler.prepare(); @@ -325,8 +333,8 @@ export class Display extends EventDispatcher { this._history.on('stateChanged', this._onStateChanged.bind(this)); this._queryParser.on('searched', this._onQueryParserSearch.bind(this)); this._progressIndicatorVisible.on('change', this._onProgressIndicatorVisibleChanged.bind(this)); - yomitan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this)); - yomitan.crossFrame.registerHandlers([ + this._application.on('extensionUnloaded', this._onExtensionUnloaded.bind(this)); + this._application.crossFrame.registerHandlers([ ['displayPopupMessage1', this._onDisplayPopupMessage1.bind(this)], ['displayPopupMessage2', this._onDisplayPopupMessage2.bind(this)] ]); @@ -384,7 +392,7 @@ export class Display extends EventDispatcher { * @param {Error} error */ onError(error) { - if (yomitan.webExtension.unloaded) { return; } + if (this._application.webExtension.unloaded) { return; } log.error(error); } @@ -412,7 +420,7 @@ export class Display extends EventDispatcher { /** */ async updateOptions() { - const options = await yomitan.api.optionsGet(this.getOptionsContext()); + const options = await this._application.api.optionsGet(this.getOptionsContext()); const {scanning: scanningOptions, sentenceParsing: sentenceParsingOptions} = options; this._options = options; @@ -586,7 +594,7 @@ export class Display extends EventDispatcher { if (typeof this._contentOriginTabId !== 'number' || typeof this._contentOriginFrameId !== 'number') { throw new Error('No content origin is assigned'); } - return await yomitan.crossFrame.invokeTab(this._contentOriginTabId, this._contentOriginFrameId, action, params); + return await this._application.crossFrame.invokeTab(this._contentOriginTabId, this._contentOriginFrameId, action, params); } /** @@ -599,7 +607,7 @@ export class Display extends EventDispatcher { if (this._parentFrameId === null || this._parentFrameId === this._frameId) { throw new Error('Invalid parent frame'); } - return await yomitan.crossFrame.invoke(this._parentFrameId, action, params); + return await this._application.crossFrame.invoke(this._parentFrameId, action, params); } /** @@ -721,7 +729,7 @@ export class Display extends EventDispatcher { /** @type {import('display').WindowApiHandler<'displayExtensionUnloaded'>} */ _onMessageExtensionUnloaded() { - yomitan.webExtension.triggerUnloaded(); + this._application.webExtension.triggerUnloaded(); } // Private @@ -900,7 +908,7 @@ export class Display extends EventDispatcher { const element = /** @type {Element} */ (e.currentTarget); let query = element.textContent; if (query === null) { query = ''; } - const dictionaryEntries = await yomitan.api.kanjiFind(query, optionsContext); + const dictionaryEntries = await this._application.api.kanjiFind(query, optionsContext); /** @type {import('display').ContentDetails} */ const details = { focus: false, @@ -1136,7 +1144,7 @@ export class Display extends EventDispatcher { */ async _findDictionaryEntries(isKanji, source, wildcardsEnabled, optionsContext) { if (isKanji) { - const dictionaryEntries = await yomitan.api.kanjiFind(source, optionsContext); + const dictionaryEntries = await this._application.api.kanjiFind(source, optionsContext); return dictionaryEntries; } else { /** @type {import('api').FindTermsDetails} */ @@ -1155,7 +1163,7 @@ export class Display extends EventDispatcher { } } - const {dictionaryEntries} = await yomitan.api.termsFind(source, findDetails, optionsContext); + const {dictionaryEntries} = await this._application.api.termsFind(source, findDetails, optionsContext); return dictionaryEntries; } } @@ -1640,7 +1648,7 @@ export class Display extends EventDispatcher { /** */ _closePopups() { - yomitan.triggerClosePopups(); + this._application.triggerClosePopups(); } /** @@ -1711,11 +1719,12 @@ export class Display extends EventDispatcher { import('../app/frontend.js') ]); - const popupFactory = new PopupFactory(this._frameId); + const popupFactory = new PopupFactory(this._application, this._frameId); popupFactory.prepare(); /** @type {import('frontend').ConstructorDetails} */ const setupNestedPopupsOptions = { + application: this._application, useProxyPopup, parentPopupId, parentFrameId, @@ -1828,6 +1837,7 @@ export class Display extends EventDispatcher { if (this._contentTextScanner === null) { this._contentTextScanner = new TextScanner({ + api: this._application.api, node: window, getSearchContext: this._getSearchContext.bind(this), searchTerms: true, @@ -1888,7 +1898,7 @@ export class Display extends EventDispatcher { * @param {import('text-scanner').SearchedEventDetails} details */ _onContentTextScannerSearched({type, dictionaryEntries, sentence, textSource, optionsContext, error}) { - if (error !== null && !yomitan.webExtension.unloaded) { + if (error !== null && !this._application.webExtension.unloaded) { log.error(error); } diff --git a/ext/js/display/option-toggle-hotkey-handler.js b/ext/js/display/option-toggle-hotkey-handler.js index d9065e7f..b2f48a3e 100644 --- a/ext/js/display/option-toggle-hotkey-handler.js +++ b/ext/js/display/option-toggle-hotkey-handler.js @@ -16,10 +16,9 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import {generateId} from '../core/utilities.js'; import {ExtensionError} from '../core/extension-error.js'; import {toError} from '../core/to-error.js'; -import {yomitan} from '../yomitan.js'; +import {generateId} from '../core/utilities.js'; export class OptionToggleHotkeyHandler { /** @@ -72,7 +71,7 @@ export class OptionToggleHotkeyHandler { try { const optionsContext = this._display.getOptionsContext(); - const getSettingsResponse = (await yomitan.api.getSettings([{ + const getSettingsResponse = (await this._display.application.api.getSettings([{ scope: 'profile', path, optionsContext @@ -97,7 +96,7 @@ export class OptionToggleHotkeyHandler { value, optionsContext }; - const modifySettingsResponse = (await yomitan.api.modifySettings([modification], this._source))[0]; + const modifySettingsResponse = (await this._display.application.api.modifySettings([modification], this._source))[0]; const {error: modifySettingsError} = modifySettingsResponse; if (typeof modifySettingsError !== 'undefined') { throw ExtensionError.deserialize(modifySettingsError); diff --git a/ext/js/display/popup-main.js b/ext/js/display/popup-main.js index 870e039e..2ca2fcd3 100644 --- a/ext/js/display/popup-main.js +++ b/ext/js/display/popup-main.js @@ -16,10 +16,10 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import {Application} from '../application.js'; import {log} from '../core/logger.js'; import {DocumentFocusController} from '../dom/document-focus-controller.js'; import {HotkeyHandler} from '../input/hotkey-handler.js'; -import {yomitan} from '../yomitan.js'; import {DisplayAnki} from './display-anki.js'; import {DisplayAudio} from './display-audio.js'; import {DisplayProfileSelection} from './display-profile-selection.js'; @@ -32,14 +32,15 @@ async function main() { const documentFocusController = new DocumentFocusController(); documentFocusController.prepare(); - await yomitan.prepare(); + const application = new Application(); + await application.prepare(); - const {tabId, frameId} = await yomitan.api.frameInformationGet(); + const {tabId, frameId} = await application.api.frameInformationGet(); const hotkeyHandler = new HotkeyHandler(); - hotkeyHandler.prepare(); + hotkeyHandler.prepare(application.crossFrame); - const display = new Display(tabId, frameId, 'popup', documentFocusController, hotkeyHandler); + const display = new Display(application, tabId, frameId, 'popup', documentFocusController, hotkeyHandler); await display.prepare(); const displayAudio = new DisplayAudio(display); @@ -58,7 +59,7 @@ async function main() { document.documentElement.dataset.loaded = 'true'; - yomitan.ready(); + application.ready(); } catch (e) { log.error(e); } diff --git a/ext/js/display/query-parser.js b/ext/js/display/query-parser.js index 178bb110..daa298d2 100644 --- a/ext/js/display/query-parser.js +++ b/ext/js/display/query-parser.js @@ -21,7 +21,6 @@ import {log} from '../core/logger.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; import {convertHiraganaToKatakana, convertKatakanaToHiragana, isStringEntirelyKana} from '../language/japanese.js'; import {TextScanner} from '../language/text-scanner.js'; -import {yomitan} from '../yomitan.js'; /** * @augments EventDispatcher<import('query-parser').Events> @@ -30,8 +29,10 @@ export class QueryParser extends EventDispatcher { /** * @param {import('display').QueryParserConstructorDetails} details */ - constructor({getSearchContext, textSourceGenerator}) { + constructor({api, getSearchContext, textSourceGenerator}) { super(); + /** @type {import('../comm/api.js').API} */ + this._api = api; /** @type {import('display').GetSearchContextCallback} */ this._getSearchContext = getSearchContext; /** @type {string} */ @@ -58,6 +59,7 @@ export class QueryParser extends EventDispatcher { this._queryParserModeSelect = querySelectorNotNull(document, '#query-parser-mode-select'); /** @type {TextScanner} */ this._textScanner = new TextScanner({ + api, node: this._queryParser, getSearchContext, searchTerms: true, @@ -128,7 +130,7 @@ export class QueryParser extends EventDispatcher { /** @type {?import('core').TokenObject} */ const token = {}; this._setTextToken = token; - this._parseResults = await yomitan.api.parseText(text, this._getOptionsContext(), this._scanLength, this._useInternalParser, this._useMecabParser); + this._parseResults = await this._api.parseText(text, this._getOptionsContext(), this._scanLength, this._useInternalParser, this._useMecabParser); if (this._setTextToken !== token) { return; } this._refreshSelectedParser(); @@ -214,7 +216,7 @@ export class QueryParser extends EventDispatcher { scope: 'profile', optionsContext }; - yomitan.api.modifySettings([modification], 'search'); + this._api.modifySettings([modification], 'search'); } /** diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index ff4340c1..49c69520 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -21,7 +21,6 @@ import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; import {createApiMap, invokeApiMapHandler} from '../core/api-map.js'; import {EventListenerCollection} from '../core/event-listener-collection.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; -import {yomitan} from '../yomitan.js'; export class SearchDisplayController { /** @@ -71,7 +70,7 @@ export class SearchDisplayController { /** @type {ClipboardMonitor} */ this._clipboardMonitor = new ClipboardMonitor({ clipboardReader: { - getText: yomitan.api.clipboardGet.bind(yomitan.api) + getText: this._display.application.api.clipboardGet.bind(this._display.application.api) } }); /** @type {import('application').ApiMap} */ @@ -89,7 +88,7 @@ export class SearchDisplayController { this._searchPersistentStateController.on('modeChange', this._onModeChange.bind(this)); chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); - yomitan.on('optionsUpdated', this._onOptionsUpdated.bind(this)); + this._display.application.on('optionsUpdated', this._onOptionsUpdated.bind(this)); this._display.on('optionsUpdated', this._onDisplayOptionsUpdated.bind(this)); this._display.on('contentUpdateStart', this._onContentUpdateStart.bind(this)); @@ -297,7 +296,7 @@ export class SearchDisplayController { scope: 'profile', optionsContext: this._display.getOptionsContext() }; - yomitan.api.modifySettings([modification], 'search'); + this._display.application.api.modifySettings([modification], 'search'); } /** @@ -430,7 +429,7 @@ export class SearchDisplayController { scope: 'profile', optionsContext: this._display.getOptionsContext() }; - await yomitan.api.modifySettings([modification], 'search'); + await this._display.application.api.modifySettings([modification], 'search'); } /** */ diff --git a/ext/js/display/search-main.js b/ext/js/display/search-main.js index dedad163..dc4f1b7e 100644 --- a/ext/js/display/search-main.js +++ b/ext/js/display/search-main.js @@ -16,10 +16,10 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import {Application} from '../application.js'; import {log} from '../core/logger.js'; import {DocumentFocusController} from '../dom/document-focus-controller.js'; import {HotkeyHandler} from '../input/hotkey-handler.js'; -import {yomitan} from '../yomitan.js'; import {DisplayAnki} from './display-anki.js'; import {DisplayAudio} from './display-audio.js'; import {Display} from './display.js'; @@ -30,6 +30,8 @@ import {SearchPersistentStateController} from './search-persistent-state-control /** Entry point. */ async function main() { try { + const application = new Application(); + const documentFocusController = new DocumentFocusController('#search-textbox'); documentFocusController.prepare(); @@ -39,14 +41,14 @@ async function main() { const searchActionPopupController = new SearchActionPopupController(searchPersistentStateController); searchActionPopupController.prepare(); - await yomitan.prepare(); + await application.prepare(); - const {tabId, frameId} = await yomitan.api.frameInformationGet(); + const {tabId, frameId} = await application.api.frameInformationGet(); const hotkeyHandler = new HotkeyHandler(); - hotkeyHandler.prepare(); + hotkeyHandler.prepare(application.crossFrame); - const display = new Display(tabId, frameId, 'search', documentFocusController, hotkeyHandler); + const display = new Display(application, tabId, frameId, 'search', documentFocusController, hotkeyHandler); await display.prepare(); const displayAudio = new DisplayAudio(display); @@ -62,7 +64,7 @@ async function main() { document.documentElement.dataset.loaded = 'true'; - yomitan.ready(); + application.ready(); } catch (e) { log.error(e); } diff --git a/ext/js/dom/style-util.js b/ext/js/dom/style-util.js index ac20e655..e5046e5c 100644 --- a/ext/js/dom/style-util.js +++ b/ext/js/dom/style-util.js @@ -15,8 +15,6 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import {yomitan} from '../yomitan.js'; - /** @type {Map<string, ?HTMLStyleElement|HTMLLinkElement>} */ const injectedStylesheets = new Map(); /** @type {WeakMap<Node, Map<string, ?HTMLStyleElement|HTMLLinkElement>>} */ @@ -54,6 +52,7 @@ function setInjectedStylesheet(id, parentNode, value) { } /** + * @param {import('../application.js').Application} application * @param {string} id * @param {'code'|'file'|'file-content'} type * @param {string} value @@ -62,8 +61,8 @@ function setInjectedStylesheet(id, parentNode, value) { * @returns {Promise<?HTMLStyleElement|HTMLLinkElement>} * @throws {Error} */ -export async function loadStyle(id, type, value, useWebExtensionApi = false, parentNode = null) { - if (useWebExtensionApi && yomitan.isExtensionUrl(window.location.href)) { +export async function loadStyle(application, id, type, value, useWebExtensionApi = false, parentNode = null) { + if (useWebExtensionApi && application.isExtensionUrl(window.location.href)) { // Permissions error will occur if trying to use the WebExtension API to inject into an extension page useWebExtensionApi = false; } @@ -79,7 +78,7 @@ export async function loadStyle(id, type, value, useWebExtensionApi = false, par } if (type === 'file-content') { - value = await yomitan.api.getStylesheetContent(value); + value = await application.api.getStylesheetContent(value); type = 'code'; useWebExtensionApi = false; } @@ -91,7 +90,7 @@ export async function loadStyle(id, type, value, useWebExtensionApi = false, par } setInjectedStylesheet(id, parentNode, null); - await yomitan.api.injectStylesheet(type, value); + await application.api.injectStylesheet(type, value); return null; } diff --git a/ext/js/input/hotkey-handler.js b/ext/js/input/hotkey-handler.js index 3b40a86d..9caedcc2 100644 --- a/ext/js/input/hotkey-handler.js +++ b/ext/js/input/hotkey-handler.js @@ -19,7 +19,6 @@ import {EventDispatcher} from '../core/event-dispatcher.js'; import {EventListenerCollection} from '../core/event-listener-collection.js'; import {DocumentUtil} from '../dom/document-util.js'; -import {yomitan} from '../yomitan.js'; /** * Class which handles hotkey events and actions. @@ -47,11 +46,12 @@ export class HotkeyHandler extends EventDispatcher { /** * Begins listening to key press events in order to detect hotkeys. + * @param {import('../comm/cross-frame-api.js').CrossFrameAPI} crossFrameApi */ - prepare() { + prepare(crossFrameApi) { this._isPrepared = true; this._updateEventHandlers(); - yomitan.crossFrame.registerHandlers([ + crossFrameApi.registerHandlers([ ['hotkeyHandlerForwardHotkey', this._onMessageForwardHotkey.bind(this)] ]); } diff --git a/ext/js/input/hotkey-help-controller.js b/ext/js/input/hotkey-help-controller.js index 4c4f56d5..a75ab9db 100644 --- a/ext/js/input/hotkey-help-controller.js +++ b/ext/js/input/hotkey-help-controller.js @@ -16,9 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import {isObject} from '../core/utilities.js'; import {parseJson} from '../core/json.js'; -import {yomitan} from '../yomitan.js'; +import {isObject} from '../core/utilities.js'; import {HotkeyUtil} from './hotkey-util.js'; export class HotkeyHelpController { @@ -34,10 +33,10 @@ export class HotkeyHelpController { } /** - * @returns {Promise<void>} + * @param {import('../comm/api.js').API} api */ - async prepare() { - const {platform: {os}} = await yomitan.api.getEnvironmentInfo(); + async prepare(api) { + const {platform: {os}} = await api.getEnvironmentInfo(); this._hotkeyUtil.os = os; await this._setupGlobalCommands(this._globalActionHotkeys); } diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index 6228a82c..d78c4c74 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -22,7 +22,6 @@ import {log} from '../core/logger.js'; import {clone} from '../core/utilities.js'; import {DocumentUtil} from '../dom/document-util.js'; import {TextSourceElement} from '../dom/text-source-element.js'; -import {yomitan} from '../yomitan.js'; /** * @augments EventDispatcher<import('text-scanner').Events> @@ -32,6 +31,7 @@ export class TextScanner extends EventDispatcher { * @param {import('text-scanner').ConstructorDetails} details */ constructor({ + api, node, getSearchContext, ignoreElements = null, @@ -43,6 +43,8 @@ export class TextScanner extends EventDispatcher { textSourceGenerator }) { super(); + /** @type {import('../comm/api.js').API} */ + this._api = api; /** @type {HTMLElement|Window} */ this._node = node; /** @type {import('text-scanner').GetSearchContextCallback} */ @@ -1204,7 +1206,7 @@ export class TextScanner extends EventDispatcher { /** @type {import('api').FindTermsDetails} */ const details = {}; if (this._matchTypePrefix) { details.matchType = 'prefix'; } - const {dictionaryEntries, originalTextLength} = await yomitan.api.termsFind(searchText, details, optionsContext); + const {dictionaryEntries, originalTextLength} = await this._api.termsFind(searchText, details, optionsContext); if (dictionaryEntries.length === 0) { return null; } textSource.setEndOffset(originalTextLength, false, layoutAwareScan); @@ -1236,7 +1238,7 @@ export class TextScanner extends EventDispatcher { const searchText = this.getTextSourceContent(textSource, 1, layoutAwareScan); if (searchText.length === 0) { return null; } - const dictionaryEntries = await yomitan.api.kanjiFind(searchText, optionsContext); + const dictionaryEntries = await this._api.kanjiFind(searchText, optionsContext); if (dictionaryEntries.length === 0) { return null; } textSource.setEndOffset(1, false, layoutAwareScan); @@ -1564,7 +1566,7 @@ export class TextScanner extends EventDispatcher { */ async _hasJapanese(text) { try { - return await yomitan.api.textHasJapaneseCharacters(text); + return await this._api.textHasJapaneseCharacters(text); } catch (e) { return false; } diff --git a/ext/js/pages/action-popup-main.js b/ext/js/pages/action-popup-main.js index 86201e83..6d2c85ab 100644 --- a/ext/js/pages/action-popup-main.js +++ b/ext/js/pages/action-popup-main.js @@ -16,13 +16,18 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import {Application} from '../application.js'; import {getAllPermissions, hasRequiredPermissionsForOptions} 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'; class DisplayController { - constructor() { + /** + * @param {import('../comm/api.js').API} api + */ + constructor(api) { + /** @type {import('../comm/api.js').API} */ + this._api = api; /** @type {?import('settings').Options} */ this._optionsFull = null; } @@ -36,7 +41,7 @@ class DisplayController { this._setupButtonEvents('.action-open-search', 'openSearchPage', chrome.runtime.getURL('/search.html'), this._onSearchClick.bind(this)); this._setupButtonEvents('.action-open-info', 'openInfoPage', chrome.runtime.getURL('/info.html')); - const optionsFull = await yomitan.api.optionsGetFull(); + const optionsFull = await this._api.optionsGetFull(); this._optionsFull = optionsFull; this._setupHotkeys(); @@ -108,7 +113,7 @@ class DisplayController { const result = customHandler(e); if (typeof result !== 'undefined') { return; } } - yomitan.api.commandExec(command, {mode: e.ctrlKey ? 'newTab' : 'existingOrNewTab'}); + this._api.commandExec(command, {mode: e.ctrlKey ? 'newTab' : 'existingOrNewTab'}); e.preventDefault(); }; /** @@ -116,7 +121,7 @@ class DisplayController { */ const onAuxClick = (e) => { if (e.button !== 1) { return; } - yomitan.api.commandExec(command, {mode: 'newTab'}); + this._api.commandExec(command, {mode: 'newTab'}); e.preventDefault(); }; node.addEventListener('click', onClick, false); @@ -180,7 +185,7 @@ class DisplayController { */ _setupOptions({options}) { const extensionEnabled = options.general.enable; - const onToggleChanged = () => yomitan.api.commandExec('toggleTextScanning'); + const onToggleChanged = () => this._api.commandExec('toggleTextScanning'); for (const toggle of /** @type {NodeListOf<HTMLInputElement>} */ (document.querySelectorAll('#enable-search,#enable-search2'))) { toggle.checked = extensionEnabled; toggle.addEventListener('change', onToggleChanged, false); @@ -192,7 +197,7 @@ class DisplayController { /** */ async _setupHotkeys() { const hotkeyHelpController = new HotkeyHelpController(); - await hotkeyHelpController.prepare(); + await hotkeyHelpController.prepare(this._api); const {profiles, profileCurrent} = /** @type {import('settings').Options} */ (this._optionsFull); const primaryProfile = (profileCurrent >= 0 && profileCurrent < profiles.length) ? profiles[profileCurrent] : null; @@ -250,7 +255,7 @@ class DisplayController { scope: 'global', optionsContext: null }; - await yomitan.api.modifySettings([modification], 'action-popup'); + await this._api.modifySettings([modification], 'action-popup'); } /** @@ -258,7 +263,7 @@ class DisplayController { */ async _updateDictionariesEnabledWarnings(options) { const noDictionariesEnabledWarnings = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.no-dictionaries-enabled-warning')); - const dictionaries = await yomitan.api.getDictionaryInfo(); + const dictionaries = await this._api.getDictionaryInfo(); const enabledDictionaries = new Set(); for (const {name, enabled} of options.dictionaries) { @@ -295,21 +300,22 @@ class DisplayController { /** @returns {Promise<boolean>} */ async _isSafari() { - const {browser} = await yomitan.api.getEnvironmentInfo(); + const {browser} = await this._api.getEnvironmentInfo(); return browser === 'safari'; } } /** Entry point. */ async function main() { - await yomitan.prepare(); + const application = new Application(); + await application.prepare(); - yomitan.api.logIndicatorClear(); + application.api.logIndicatorClear(); - const displayController = new DisplayController(); + const displayController = new DisplayController(application.api); displayController.prepare(); - yomitan.ready(); + application.ready(); } await main(); diff --git a/ext/js/pages/info-main.js b/ext/js/pages/info-main.js index dd55ab4b..ca5094b1 100644 --- a/ext/js/pages/info-main.js +++ b/ext/js/pages/info-main.js @@ -16,11 +16,11 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import {Application} from '../application.js'; import {log} from '../core/logger.js'; import {promiseTimeout} from '../core/utilities.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'; @@ -57,11 +57,13 @@ function getOperatingSystemDisplayName(os) { } } -/** */ -async function showAnkiConnectInfo() { +/** + * @param {import('../comm/api.js').API} api + */ +async function showAnkiConnectInfo(api) { let ankiConnectVersion = null; try { - ankiConnectVersion = await yomitan.api.getAnkiConnectVersion(); + ankiConnectVersion = await api.getAnkiConnectVersion(); } catch (e) { // NOP } @@ -78,11 +80,13 @@ async function showAnkiConnectInfo() { ankiVersionUnknownElement.hidden = (ankiConnectVersion !== null); } -/** */ -async function showDictionaryInfo() { +/** + * @param {import('../comm/api.js').API} api + */ +async function showDictionaryInfo(api) { let dictionaryInfos; try { - dictionaryInfos = await yomitan.api.getDictionaryInfo(); + dictionaryInfos = await api.getDictionaryInfo(); } catch (e) { return; } @@ -121,11 +125,12 @@ async function main() { const manifest = chrome.runtime.getManifest(); const language = chrome.i18n.getUILanguage(); - await yomitan.prepare(); + const application = new Application(); + await application.prepare(); const {userAgent} = navigator; const {name, version} = manifest; - const {browser, platform: {os}} = await yomitan.api.getEnvironmentInfo(); + const {browser, platform: {os}} = await application.api.getEnvironmentInfo(); /** @type {HTMLLinkElement} */ const thisVersionLink = querySelectorNotNull(document, '#release-notes-this-version-link'); @@ -149,10 +154,10 @@ async function main() { languageElement.textContent = `${language}`; userAgentElement.textContent = userAgent; - showAnkiConnectInfo(); - showDictionaryInfo(); + showAnkiConnectInfo(application.api); + showDictionaryInfo(application.api); - const settingsController = new SettingsController(); + const settingsController = new SettingsController(application); await settingsController.prepare(); const backupController = new BackupController(settingsController, null); diff --git a/ext/js/pages/permissions-main.js b/ext/js/pages/permissions-main.js index 38135689..1659bea5 100644 --- a/ext/js/pages/permissions-main.js +++ b/ext/js/pages/permissions-main.js @@ -16,11 +16,11 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import {Application} from '../application.js'; import {log} from '../core/logger.js'; import {promiseTimeout} from '../core/utilities.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'; import {PermissionsOriginController} from './settings/permissions-origin-controller.js'; @@ -30,11 +30,11 @@ import {SettingsController} from './settings/settings-controller.js'; import {SettingsDisplayController} from './settings/settings-display-controller.js'; /** - * @returns {Promise<void>} + * @param {import('../comm/api.js').API} api */ -async function setupEnvironmentInfo() { +async function setupEnvironmentInfo(api) { const {manifest_version: manifestVersion} = chrome.runtime.getManifest(); - const {browser, platform} = await yomitan.api.getEnvironmentInfo(); + const {browser, platform} = await api.getEnvironmentInfo(); document.documentElement.dataset.browser = browser; document.documentElement.dataset.os = platform.os; document.documentElement.dataset.manifestVersion = `${manifestVersion}`; @@ -90,6 +90,8 @@ function setupPermissionsToggles() { /** Entry point. */ async function main() { try { + const application = new Application(); + const documentFocusController = new DocumentFocusController(); documentFocusController.prepare(); @@ -98,9 +100,9 @@ async function main() { setupPermissionsToggles(); - await yomitan.prepare(); + await application.prepare(); - setupEnvironmentInfo(); + setupEnvironmentInfo(application.api); /** @type {HTMLInputElement} */ const permissionCheckbox1 = querySelectorNotNull(document, '#permission-checkbox-allow-in-private-windows'); @@ -121,7 +123,7 @@ async function main() { const modalController = new ModalController(); modalController.prepare(); - const settingsController = new SettingsController(); + const settingsController = new SettingsController(application); await settingsController.prepare(); const permissionsToggleController = new PermissionsToggleController(settingsController); @@ -130,7 +132,7 @@ async function main() { const permissionsOriginController = new PermissionsOriginController(settingsController); permissionsOriginController.prepare(); - const persistentStorageController = new PersistentStorageController(); + const persistentStorageController = new PersistentStorageController(application); persistentStorageController.prepare(); await promiseTimeout(100); diff --git a/ext/js/pages/settings/anki-controller.js b/ext/js/pages/settings/anki-controller.js index 09ab3c03..ae6a71db 100644 --- a/ext/js/pages/settings/anki-controller.js +++ b/ext/js/pages/settings/anki-controller.js @@ -26,7 +26,6 @@ import {getRequiredPermissionsForAnkiFieldValue, hasPermissions, setPermissionsG 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'; export class AnkiController { /** @@ -510,7 +509,7 @@ export class AnkiController { let noteId = null; for (const query of queries) { - const notes = await yomitan.api.findAnkiNotes(query); + const notes = await this._settingsController.application.api.findAnkiNotes(query); if (notes.length > 0) { noteId = notes[0]; break; @@ -521,7 +520,7 @@ export class AnkiController { throw new Error('Could not find a note to test with'); } - await yomitan.api.noteView(noteId, mode, false); + await this._settingsController.application.api.noteView(noteId, mode, false); } /** diff --git a/ext/js/pages/settings/anki-templates-controller.js b/ext/js/pages/settings/anki-templates-controller.js index 869c9e16..332102cf 100644 --- a/ext/js/pages/settings/anki-templates-controller.js +++ b/ext/js/pages/settings/anki-templates-controller.js @@ -21,7 +21,6 @@ import {toError} from '../../core/to-error.js'; import {AnkiNoteBuilder} from '../../data/anki-note-builder.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {TemplateRendererProxy} from '../../templates/template-renderer-proxy.js'; -import {yomitan} from '../../yomitan.js'; export class AnkiTemplatesController { /** @@ -55,12 +54,12 @@ export class AnkiTemplatesController { /** @type {?import('./modal.js').Modal} */ this._fieldTemplateResetModal = null; /** @type {AnkiNoteBuilder} */ - this._ankiNoteBuilder = new AnkiNoteBuilder(new TemplateRendererProxy()); + this._ankiNoteBuilder = new AnkiNoteBuilder(settingsController.application.api, new TemplateRendererProxy()); } /** */ async prepare() { - this._defaultFieldTemplates = await yomitan.api.getDefaultAnkiFieldTemplates(); + this._defaultFieldTemplates = await this._settingsController.application.api.getDefaultAnkiFieldTemplates(); /** @type {HTMLButtonElement} */ const menuButton = querySelectorNotNull(document, '#anki-card-templates-test-field-menu-button'); @@ -205,7 +204,7 @@ export class AnkiTemplatesController { */ async _getDictionaryEntry(text, optionsContext) { if (this._cachedDictionaryEntryText !== text) { - const {dictionaryEntries} = await yomitan.api.termsFind(text, {}, optionsContext); + const {dictionaryEntries} = await this._settingsController.application.api.termsFind(text, {}, optionsContext); if (dictionaryEntries.length === 0) { return null; } this._cachedDictionaryEntryValue = dictionaryEntries[0]; diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 053cc96b..79733c4d 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -25,7 +25,6 @@ import {OptionsUtil} from '../../data/options-util.js'; import {getAllPermissions} from '../../data/permissions-util.js'; import {arrayBufferUtf8Decode} 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'; export class BackupController { @@ -134,8 +133,8 @@ export class BackupController { */ async _getSettingsExportData(date) { const optionsFull = await this._settingsController.getOptionsFull(); - const environment = await yomitan.api.getEnvironmentInfo(); - const fieldTemplatesDefault = await yomitan.api.getDefaultAnkiFieldTemplates(); + const environment = await this._settingsController.application.api.getEnvironmentInfo(); + const fieldTemplatesDefault = await this._settingsController.application.api.getDefaultAnkiFieldTemplates(); const permissions = await getAllPermissions(); // Format options @@ -644,10 +643,10 @@ export class BackupController { * @param {File} file */ async _importDatabase(databaseName, file) { - await yomitan.api.purgeDatabase(); + await this._settingsController.application.api.purgeDatabase(); await Dexie.import(file, {progressCallback: this._databaseImportProgressCallback}); - yomitan.api.triggerDatabaseUpdated('dictionary', 'import'); - yomitan.triggerStorageChanged(); + this._settingsController.application.api.triggerDatabaseUpdated('dictionary', 'import'); + this._settingsController.application.triggerStorageChanged(); } /** */ diff --git a/ext/js/pages/settings/collapsible-dictionary-controller.js b/ext/js/pages/settings/collapsible-dictionary-controller.js index e6930049..5ba61e0c 100644 --- a/ext/js/pages/settings/collapsible-dictionary-controller.js +++ b/ext/js/pages/settings/collapsible-dictionary-controller.js @@ -18,7 +18,6 @@ import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js'; export class CollapsibleDictionaryController { /** @@ -45,7 +44,7 @@ export class CollapsibleDictionaryController { async prepare() { await this._onDatabaseUpdated(); - yomitan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); + this._settingsController.application.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); this._settingsController.on('dictionarySettingsReordered', this._onDictionarySettingsReordered.bind(this)); } diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index 10dfdcdc..1d3c1730 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -20,7 +20,6 @@ import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {log} from '../../core/logger.js'; import {DictionaryWorker} from '../../dictionary/dictionary-worker.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js'; class DictionaryEntry { /** @@ -437,7 +436,7 @@ export class DictionaryController { /** @type {HTMLButtonElement} */ const dictionaryMoveButton = querySelectorNotNull(document, '#dictionary-move-button'); - yomitan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); + this._settingsController.application.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); this._allCheckbox.addEventListener('change', this._onAllCheckboxChange.bind(this), false); dictionaryDeleteButton.addEventListener('click', this._onDictionaryConfirmDelete.bind(this), false); @@ -917,7 +916,7 @@ export class DictionaryController { */ async _deleteDictionaryInternal(dictionaryTitle, onProgress) { await new DictionaryWorker().deleteDictionary(dictionaryTitle, onProgress); - yomitan.api.triggerDatabaseUpdated('dictionary', 'delete'); + this._settingsController.application.api.triggerDatabaseUpdated('dictionary', 'delete'); } /** @@ -947,7 +946,7 @@ export class DictionaryController { /** */ _triggerStorageChanged() { - yomitan.triggerStorageChanged(); + this._settingsController.application.triggerStorageChanged(); } /** */ diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index 183c0ccd..0484001d 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -21,7 +21,6 @@ import {log} from '../../core/logger.js'; import {toError} from '../../core/to-error.js'; import {DictionaryWorker} from '../../dictionary/dictionary-worker.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js'; import {DictionaryController} from './dictionary-controller.js'; export class DictionaryImportController { @@ -120,7 +119,7 @@ export class DictionaryImportController { this._setModifying(true); this._hideErrors(); - await yomitan.api.purgeDatabase(); + await this._settingsController.application.api.purgeDatabase(); const errors = await this._clearDictionarySettings(); if (errors.length > 0) { @@ -236,7 +235,7 @@ export class DictionaryImportController { async _importDictionary(file, importDetails, onProgress) { const archiveContent = await this._readFile(file); const {result, errors} = await new DictionaryWorker().importDictionary(archiveContent, importDetails, onProgress); - yomitan.api.triggerDatabaseUpdated('dictionary', 'import'); + this._settingsController.application.api.triggerDatabaseUpdated('dictionary', 'import'); const errors2 = await this._addDictionarySettings(result.sequenced, result.title); if (errors.length > 0) { @@ -399,6 +398,6 @@ export class DictionaryImportController { /** */ _triggerStorageChanged() { - yomitan.triggerStorageChanged(); + this._settingsController.application.triggerStorageChanged(); } } diff --git a/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js b/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js index 61330bb8..61eefffa 100644 --- a/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js +++ b/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js @@ -20,7 +20,6 @@ import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {isObject} from '../../core/utilities.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'; export class ExtensionKeyboardShortcutController { @@ -63,7 +62,7 @@ export class ExtensionKeyboardShortcutController { this._clearButton.addEventListener('click', this._onClearClick.bind(this)); } - const {platform: {os}} = await yomitan.api.getEnvironmentInfo(); + const {platform: {os}} = await this._settingsController.application.api.getEnvironmentInfo(); this._os = os; this._hotkeyUtil.os = os; diff --git a/ext/js/pages/settings/keyboard-shortcuts-controller.js b/ext/js/pages/settings/keyboard-shortcuts-controller.js index 396b0cc2..9392f768 100644 --- a/ext/js/pages/settings/keyboard-shortcuts-controller.js +++ b/ext/js/pages/settings/keyboard-shortcuts-controller.js @@ -20,7 +20,6 @@ import {EventListenerCollection} from '../../core/event-listener-collection.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'; export class KeyboardShortcutController { @@ -81,7 +80,7 @@ export class KeyboardShortcutController { /** */ async prepare() { - const {platform: {os}} = await yomitan.api.getEnvironmentInfo(); + const {platform: {os}} = await this._settingsController.application.api.getEnvironmentInfo(); this._os = os; this._addButton.addEventListener('click', this._onAddClick.bind(this)); diff --git a/ext/js/pages/settings/mecab-controller.js b/ext/js/pages/settings/mecab-controller.js index dec2be68..ba2f6166 100644 --- a/ext/js/pages/settings/mecab-controller.js +++ b/ext/js/pages/settings/mecab-controller.js @@ -18,10 +18,14 @@ import {toError} from '../../core/to-error.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js'; export class MecabController { - constructor() { + /** + * @param {import('../../comm/api.js').API} api + */ + constructor(api) { + /** @type {import('../../comm/api.js').API} */ + this._api = api; /** @type {HTMLButtonElement} */ this._testButton = querySelectorNotNull(document, '#test-mecab-button'); /** @type {HTMLElement} */ @@ -55,7 +59,7 @@ export class MecabController { /** @type {HTMLButtonElement} */ (this._testButton).disabled = true; resultsContainer.textContent = ''; resultsContainer.hidden = true; - await yomitan.api.testMecab(); + await this._api.testMecab(); this._setStatus('Connection was successful', false); } catch (e) { this._setStatus(toError(e).message, true); diff --git a/ext/js/pages/settings/persistent-storage-controller.js b/ext/js/pages/settings/persistent-storage-controller.js index baffa969..8b7726dd 100644 --- a/ext/js/pages/settings/persistent-storage-controller.js +++ b/ext/js/pages/settings/persistent-storage-controller.js @@ -18,14 +18,23 @@ import {isObject} from '../../core/utilities.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js'; export class PersistentStorageController { - constructor() { + /** + * @param {import('../../application.js').Application} application + */ + constructor(application) { + /** @type {import('../../application.js').Application} */ + this._application = application; /** @type {HTMLInputElement} */ this._persistentStorageCheckbox = querySelectorNotNull(document, '#storage-persistent-checkbox'); } + /** @type {import('../../application.js').Application} */ + get application() { + return this._application; + } + /** */ async prepare() { this._persistentStorageCheckbox.addEventListener('change', this._onPersistentStorageCheckboxChange.bind(this), false); @@ -82,7 +91,7 @@ export class PersistentStorageController { const node = document.querySelector('#storage-persistent-fail-warning'); if (node !== null) { node.hidden = isStoragePeristent; } - yomitan.triggerStorageChanged(); + this._application.triggerStorageChanged(); } /** diff --git a/ext/js/pages/settings/popup-preview-frame-main.js b/ext/js/pages/settings/popup-preview-frame-main.js index e3d7d0ec..fd08bf1d 100644 --- a/ext/js/pages/settings/popup-preview-frame-main.js +++ b/ext/js/pages/settings/popup-preview-frame-main.js @@ -17,17 +17,18 @@ */ import {PopupFactory} from '../../app/popup-factory.js'; +import {Application} from '../../application.js'; import {log} from '../../core/logger.js'; import {HotkeyHandler} from '../../input/hotkey-handler.js'; -import {yomitan} from '../../yomitan.js'; import {PopupPreviewFrame} from './popup-preview-frame.js'; /** Entry point. */ async function main() { try { - await yomitan.prepare(); + const application = new Application(); + await application.prepare(); - const {tabId, frameId} = await yomitan.api.frameInformationGet(); + const {tabId, frameId} = await application.api.frameInformationGet(); if (typeof tabId === 'undefined') { throw new Error('Failed to get tabId'); } @@ -36,12 +37,12 @@ async function main() { } const hotkeyHandler = new HotkeyHandler(); - hotkeyHandler.prepare(); + hotkeyHandler.prepare(application.crossFrame); - const popupFactory = new PopupFactory(frameId); + const popupFactory = new PopupFactory(application, frameId); popupFactory.prepare(); - const preview = new PopupPreviewFrame(tabId, frameId, popupFactory, hotkeyHandler); + const preview = new PopupPreviewFrame(application, tabId, frameId, popupFactory, hotkeyHandler); await preview.prepare(); document.documentElement.dataset.loaded = 'true'; diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js index 7a899641..e9cfa541 100644 --- a/ext/js/pages/settings/popup-preview-frame.js +++ b/ext/js/pages/settings/popup-preview-frame.js @@ -20,16 +20,18 @@ 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'; export class PopupPreviewFrame { /** + * @param {import('../../application.js').Application} application * @param {number} tabId * @param {number} frameId * @param {import('../../app/popup-factory.js').PopupFactory} popupFactory * @param {import('../../input/hotkey-handler.js').HotkeyHandler} hotkeyHandler */ - constructor(tabId, frameId, popupFactory, hotkeyHandler) { + constructor(application, tabId, frameId, popupFactory, hotkeyHandler) { + /** @type {import('../../application.js').Application} */ + this._application = application; /** @type {number} */ this._tabId = tabId; /** @type {number} */ @@ -86,11 +88,12 @@ export class PopupPreviewFrame { // Overwrite API functions /** @type {?(optionsContext: import('settings').OptionsContext) => Promise<import('settings').ProfileOptions>} */ - this._apiOptionsGetOld = yomitan.api.optionsGet.bind(yomitan.api); - yomitan.api.optionsGet = this._apiOptionsGet.bind(this); + this._apiOptionsGetOld = this._application.api.optionsGet.bind(this._application.api); + this._application.api.optionsGet = this._apiOptionsGet.bind(this); // Overwrite frontend this._frontend = new Frontend({ + application: this._application, tabId: this._tabId, frameId: this._frameId, popupFactory: this._popupFactory, diff --git a/ext/js/pages/settings/popup-window-controller.js b/ext/js/pages/settings/popup-window-controller.js index 1b767f77..62879f3b 100644 --- a/ext/js/pages/settings/popup-window-controller.js +++ b/ext/js/pages/settings/popup-window-controller.js @@ -17,9 +17,16 @@ */ import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js'; export class PopupWindowController { + /** + * @param {import('../../comm/api.js').API} api + */ + constructor(api) { + /** @type {import('../../comm/api.js').API} */ + this._api = api; + } + /** */ prepare() { /** @type {HTMLElement} */ @@ -39,6 +46,6 @@ export class PopupWindowController { /** */ async _testWindowOpen() { - await yomitan.api.getOrCreateSearchPopup({focus: true}); + await this._api.getOrCreateSearchPopup({focus: true}); } } diff --git a/ext/js/pages/settings/profile-controller.js b/ext/js/pages/settings/profile-controller.js index 73926a69..c5ccbe7d 100644 --- a/ext/js/pages/settings/profile-controller.js +++ b/ext/js/pages/settings/profile-controller.js @@ -19,7 +19,6 @@ import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {clone} from '../../core/utilities.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js'; import {ProfileConditionsUI} from './profile-conditions-ui.js'; export class ProfileController { @@ -82,7 +81,7 @@ export class ProfileController { /** */ async prepare() { - const {platform: {os}} = await yomitan.api.getEnvironmentInfo(); + const {platform: {os}} = await this._settingsController.application.api.getEnvironmentInfo(); this._profileConditionsUI.os = os; this._profileRemoveModal = this._modalController.getModal('profile-remove'); diff --git a/ext/js/pages/settings/scan-inputs-controller.js b/ext/js/pages/settings/scan-inputs-controller.js index 2dfa3de3..f1547fe4 100644 --- a/ext/js/pages/settings/scan-inputs-controller.js +++ b/ext/js/pages/settings/scan-inputs-controller.js @@ -19,7 +19,6 @@ import {EventListenerCollection} from '../../core/event-listener-collection.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'; export class ScanInputsController { @@ -43,7 +42,7 @@ export class ScanInputsController { /** */ async prepare() { - const {platform: {os}} = await yomitan.api.getEnvironmentInfo(); + const {platform: {os}} = await this._settingsController.application.api.getEnvironmentInfo(); this._os = os; this._scanningInputCountNodes = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.scanning-input-count')); diff --git a/ext/js/pages/settings/scan-inputs-simple-controller.js b/ext/js/pages/settings/scan-inputs-simple-controller.js index e4f34c99..b09a3a76 100644 --- a/ext/js/pages/settings/scan-inputs-simple-controller.js +++ b/ext/js/pages/settings/scan-inputs-simple-controller.js @@ -18,7 +18,6 @@ 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'; export class ScanInputsSimpleController { @@ -40,7 +39,7 @@ export class ScanInputsSimpleController { /** */ async prepare() { - const {platform: {os}} = await yomitan.api.getEnvironmentInfo(); + const {platform: {os}} = await this._settingsController.application.api.getEnvironmentInfo(); this._hotkeyUtil.os = os; this._mainScanModifierKeyInputHasOther = false; diff --git a/ext/js/pages/settings/secondary-search-dictionary-controller.js b/ext/js/pages/settings/secondary-search-dictionary-controller.js index 592f5eeb..c708bf65 100644 --- a/ext/js/pages/settings/secondary-search-dictionary-controller.js +++ b/ext/js/pages/settings/secondary-search-dictionary-controller.js @@ -18,7 +18,6 @@ import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js'; export class SecondarySearchDictionaryController { /** @@ -41,7 +40,7 @@ export class SecondarySearchDictionaryController { async prepare() { await this._onDatabaseUpdated(); - yomitan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); + this._settingsController.application.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); this._settingsController.on('dictionarySettingsReordered', this._onDictionarySettingsReordered.bind(this)); } diff --git a/ext/js/pages/settings/settings-controller.js b/ext/js/pages/settings/settings-controller.js index 49fa9c9e..3f389271 100644 --- a/ext/js/pages/settings/settings-controller.js +++ b/ext/js/pages/settings/settings-controller.js @@ -22,14 +22,18 @@ import {generateId, isObject} from '../../core/utilities.js'; import {OptionsUtil} from '../../data/options-util.js'; import {getAllPermissions} from '../../data/permissions-util.js'; import {HtmlTemplateCollection} from '../../dom/html-template-collection.js'; -import {yomitan} from '../../yomitan.js'; /** * @augments EventDispatcher<import('settings-controller').Events> */ export class SettingsController extends EventDispatcher { - constructor() { + /** + * @param {import('../../application.js').Application} application + */ + constructor(application) { super(); + /** @type {import('../../application.js').Application} */ + this._application = application; /** @type {number} */ this._profileIndex = 0; /** @type {string} */ @@ -43,6 +47,11 @@ export class SettingsController extends EventDispatcher { this._templates.load(document); } + /** @type {import('../../application.js').Application} */ + get application() { + return this._application; + } + /** @type {string} */ get source() { return this._source; @@ -60,7 +69,7 @@ export class SettingsController extends EventDispatcher { /** */ async prepare() { - yomitan.on('optionsUpdated', this._onOptionsUpdated.bind(this)); + this._application.on('optionsUpdated', this._onOptionsUpdated.bind(this)); if (this._canObservePermissionsChanges()) { chrome.permissions.onAdded.addListener(this._onPermissionsChanged.bind(this)); chrome.permissions.onRemoved.addListener(this._onPermissionsChanged.bind(this)); @@ -82,14 +91,14 @@ export class SettingsController extends EventDispatcher { */ async getOptions() { const optionsContext = this.getOptionsContext(); - return await yomitan.api.optionsGet(optionsContext); + return await this._application.api.optionsGet(optionsContext); } /** * @returns {Promise<import('settings').Options>} */ async getOptionsFull() { - return await yomitan.api.optionsGetFull(); + return await this._application.api.optionsGetFull(); } /** @@ -97,7 +106,7 @@ export class SettingsController extends EventDispatcher { */ async setAllSettings(value) { const profileIndex = value.profileCurrent; - await yomitan.api.setAllSettings(value, this._source); + await this._application.api.setAllSettings(value, this._source); this._setProfileIndex(profileIndex, true); } @@ -171,7 +180,7 @@ export class SettingsController extends EventDispatcher { * @returns {Promise<import('dictionary-importer').Summary[]>} */ async getDictionaryInfo() { - return await yomitan.api.getDictionaryInfo(); + return await this._application.api.getDictionaryInfo(); } /** @@ -279,7 +288,7 @@ export class SettingsController extends EventDispatcher { this._modifyOptionsScope(target2); return target2; }); - return await yomitan.api.getSettings(targets2); + return await this._application.api.getSettings(targets2); } /** @@ -294,7 +303,7 @@ export class SettingsController extends EventDispatcher { this._modifyOptionsScope(target2); return target2; }); - return await yomitan.api.modifySettings(targets2, this._source); + return await this._application.api.modifySettings(targets2, this._source); } /** diff --git a/ext/js/pages/settings/settings-main.js b/ext/js/pages/settings/settings-main.js index 7e458043..c3391173 100644 --- a/ext/js/pages/settings/settings-main.js +++ b/ext/js/pages/settings/settings-main.js @@ -16,10 +16,10 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import {Application} from '../../application.js'; import {log} from '../../core/logger.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'; import {AnkiTemplatesController} from './anki-templates-controller.js'; @@ -78,7 +78,8 @@ async function main() { document.documentElement.dataset.loadingStalled = 'true'; }, 1000); - await yomitan.prepare(); + const application = new Application(); + await application.prepare(); if (prepareTimer !== null) { clearTimeout(prepareTimer); @@ -91,10 +92,10 @@ async function main() { const modalController = new ModalController(); modalController.prepare(); - const settingsController = new SettingsController(); + const settingsController = new SettingsController(application); await settingsController.prepare(); - const persistentStorageController = new PersistentStorageController(); + const persistentStorageController = new PersistentStorageController(application); persistentStorageController.prepare(); const storageController = new StorageController(persistentStorageController); @@ -154,10 +155,10 @@ async function main() { const extensionKeyboardShortcutController = new ExtensionKeyboardShortcutController(settingsController); extensionKeyboardShortcutController.prepare(); - const popupWindowController = new PopupWindowController(); + const popupWindowController = new PopupWindowController(application.api); popupWindowController.prepare(); - const mecabController = new MecabController(); + const mecabController = new MecabController(application.api); mecabController.prepare(); const collapsibleDictionaryController = new CollapsibleDictionaryController(settingsController); diff --git a/ext/js/pages/settings/sort-frequency-dictionary-controller.js b/ext/js/pages/settings/sort-frequency-dictionary-controller.js index f5b230f0..c8e9918b 100644 --- a/ext/js/pages/settings/sort-frequency-dictionary-controller.js +++ b/ext/js/pages/settings/sort-frequency-dictionary-controller.js @@ -17,7 +17,6 @@ */ import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js'; export class SortFrequencyDictionaryController { /** @@ -42,7 +41,7 @@ export class SortFrequencyDictionaryController { async prepare() { await this._onDatabaseUpdated(); - yomitan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); + this._settingsController.application.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); this._sortFrequencyDictionarySelect.addEventListener('change', this._onSortFrequencyDictionarySelectChange.bind(this)); this._sortFrequencyDictionaryOrderSelect.addEventListener('change', this._onSortFrequencyDictionaryOrderSelectChange.bind(this)); @@ -157,7 +156,7 @@ export class SortFrequencyDictionaryController { const lessCommonTerms = ['行なう', '論じる', '過す', '行方', '人口', '猫', '犬', '滝', '理', '暁']; const terms = [...moreCommonTerms, ...lessCommonTerms]; - const frequencies = await yomitan.api.getTermFrequencies( + const frequencies = await this._settingsController.application.api.getTermFrequencies( terms.map((term) => ({term, reading: null})), [dictionary] ); diff --git a/ext/js/pages/settings/storage-controller.js b/ext/js/pages/settings/storage-controller.js index 6be1fe24..9a3aa23a 100644 --- a/ext/js/pages/settings/storage-controller.js +++ b/ext/js/pages/settings/storage-controller.js @@ -17,7 +17,6 @@ */ import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js'; export class StorageController { /** @@ -61,7 +60,7 @@ export class StorageController { const storageRefreshButton = querySelectorNotNull(document, '#storage-refresh'); storageRefreshButton.addEventListener('click', this._onStorageRefreshButtonClick.bind(this), false); - yomitan.on('storageChanged', this._onStorageChanged.bind(this)); + this._persistentStorageController.application.on('storageChanged', this._onStorageChanged.bind(this)); this._updateStats(); } diff --git a/ext/js/pages/welcome-main.js b/ext/js/pages/welcome-main.js index 35472ec2..030d2826 100644 --- a/ext/js/pages/welcome-main.js +++ b/ext/js/pages/welcome-main.js @@ -16,10 +16,10 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import {Application} from '../application.js'; import {log} from '../core/logger.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'; import {DictionaryImportController} from './settings/dictionary-import-controller.js'; @@ -31,10 +31,12 @@ import {SettingsController} from './settings/settings-controller.js'; import {SettingsDisplayController} from './settings/settings-display-controller.js'; import {StatusFooter} from './settings/status-footer.js'; -/** */ -async function setupEnvironmentInfo() { +/** + * @param {import('../comm/api.js').API} api + */ +async function setupEnvironmentInfo(api) { const {manifest_version: manifestVersion} = chrome.runtime.getManifest(); - const {browser, platform} = await yomitan.api.getEnvironmentInfo(); + const {browser, platform} = await api.getEnvironmentInfo(); document.documentElement.dataset.browser = browser; document.documentElement.dataset.os = platform.os; document.documentElement.dataset.manifestVersion = `${manifestVersion}`; @@ -62,9 +64,10 @@ async function main() { const statusFooter = new StatusFooter(statusFooterElement); statusFooter.prepare(); - await yomitan.prepare(); + const application = new Application(); + await application.prepare(); - setupEnvironmentInfo(); + setupEnvironmentInfo(application.api); chrome.storage.session.get({'needsCustomTemplatesWarning': false}).then((result) => { if (result.needsCustomTemplatesWarning) { @@ -78,7 +81,7 @@ async function main() { const modalController = new ModalController(); modalController.prepare(); - const settingsController = new SettingsController(); + const settingsController = new SettingsController(application); await settingsController.prepare(); const dictionaryController = new DictionaryController(settingsController, modalController, statusFooter); diff --git a/test/utilities/anki.js b/test/utilities/anki.js index 69f4ce8b..d0e095bd 100644 --- a/test/utilities/anki.js +++ b/test/utilities/anki.js @@ -144,7 +144,8 @@ export async function getTemplateRenderResults(dictionaryEntries, type, mode, te } break; } - const ankiNoteBuilder = new AnkiNoteBuilder(ankiTemplateRenderer.templateRenderer); + const api = new MinimalApi(); + const ankiNoteBuilder = new AnkiNoteBuilder(api, ankiTemplateRenderer.templateRenderer); const context = { url: 'url:', sentence: { @@ -186,3 +187,19 @@ export async function getTemplateRenderResults(dictionaryEntries, type, mode, te return results; } + +class MinimalApi { + /** + * @type {import('anki-note-builder.js').MinimalApi['injectAnkiNoteMedia']} + */ + async injectAnkiNoteMedia() { + throw new Error('Not supported'); + } + + /** + * @type {import('anki-note-builder.js').MinimalApi['parseText']} + */ + async parseText() { + throw new Error('Not supported'); + } +} diff --git a/types/ext/anki-note-builder.d.ts b/types/ext/anki-note-builder.d.ts index 092978ed..8aec3342 100644 --- a/types/ext/anki-note-builder.d.ts +++ b/types/ext/anki-note-builder.d.ts @@ -23,6 +23,7 @@ import type * as Dictionary from './dictionary'; import type * as Extension from './extension'; import type * as Settings from './settings'; import type * as TemplateRenderer from './template-renderer'; +import type * as Api from './api'; export type CreateNoteDetails = { dictionaryEntry: Dictionary.DictionaryEntry; @@ -118,3 +119,22 @@ export type BatchedRequestData = { reject: (reason?: unknown) => void; marker: string; }; + +export type MinimalApi = { + injectAnkiNoteMedia( + timestamp: Api.ApiParam<'injectAnkiNoteMedia', 'timestamp'>, + definitionDetails: Api.ApiParam<'injectAnkiNoteMedia', 'definitionDetails'>, + audioDetails: Api.ApiParam<'injectAnkiNoteMedia', 'audioDetails'>, + screenshotDetails: Api.ApiParam<'injectAnkiNoteMedia', 'screenshotDetails'>, + clipboardDetails: Api.ApiParam<'injectAnkiNoteMedia', 'clipboardDetails'>, + dictionaryMediaDetails: Api.ApiParam<'injectAnkiNoteMedia', 'dictionaryMediaDetails'>, + ): Promise<Api.ApiReturn<'injectAnkiNoteMedia'>>; + + parseText( + text: Api.ApiParam<'parseText', 'text'>, + optionsContext: Api.ApiParam<'parseText', 'optionsContext'>, + scanLength: Api.ApiParam<'parseText', 'scanLength'>, + useInternalParser: Api.ApiParam<'parseText', 'useInternalParser'>, + useMecabParser: Api.ApiParam<'parseText', 'useMecabParser'>, + ): Promise<Api.ApiReturn<'parseText'>>; +}; diff --git a/types/ext/display.d.ts b/types/ext/display.d.ts index da24af75..351cf067 100644 --- a/types/ext/display.d.ts +++ b/types/ext/display.d.ts @@ -18,6 +18,7 @@ import type {DisplayContentManager} from '../../ext/js/display/display-content-manager'; import type {HotkeyHelpController} from '../../ext/js/input/hotkey-help-controller'; import type {TextSourceGenerator} from '../../ext/js/dom/text-source-generator'; +import type {API} from '../../ext/js/comm/api'; import type * as Dictionary from './dictionary'; import type * as Extension from './extension'; import type * as Settings from './settings'; @@ -127,6 +128,7 @@ export type SearchMode = null | 'popup' | 'action-popup'; export type GetSearchContextCallback = TextScannerTypes.GetSearchContextCallbackSync; export type QueryParserConstructorDetails = { + api: API; getSearchContext: GetSearchContextCallback; textSourceGenerator: TextSourceGenerator; }; diff --git a/types/ext/frontend.d.ts b/types/ext/frontend.d.ts index 53a849a2..17f3d121 100644 --- a/types/ext/frontend.d.ts +++ b/types/ext/frontend.d.ts @@ -17,9 +17,12 @@ import type {PopupFactory} from '../../ext/js/app/popup-factory'; import type {HotkeyHandler} from '../../ext/js/input/hotkey-handler'; +import type {Application} from '../../ext/js/application'; /** Details about how to set up the instance. */ export type ConstructorDetails = { + /** The main application instance. */ + application: Application; /** The type of page, one of 'web', 'popup', or 'search'. */ pageType: PageType; /** A PopupFactory instance to use for generating popups. */ diff --git a/types/ext/popup.d.ts b/types/ext/popup.d.ts index 4246e24e..1ea25c15 100644 --- a/types/ext/popup.d.ts +++ b/types/ext/popup.d.ts @@ -19,6 +19,7 @@ import type {Popup} from '../../ext/js/app/popup'; import type {PopupProxy} from '../../ext/js/app/popup-proxy'; import type {PopupWindow} from '../../ext/js/app/popup-window'; import type {FrameOffsetForwarder} from '../../ext/js/comm/frame-offset-forwarder'; +import type {Application} from '../../ext/js/application'; import type * as DocumentUtil from './document-util'; import type * as Settings from './settings'; import type {EventNames, EventArgument as BaseEventArgument} from './core'; @@ -92,6 +93,8 @@ export type ValidSize = { }; export type PopupConstructorDetails = { + /** The main application instance. */ + application: Application; /** The ID of the popup. */ id: string; /** The depth of the popup. */ @@ -103,6 +106,8 @@ export type PopupConstructorDetails = { }; export type PopupWindowConstructorDetails = { + /** The main application instance. */ + application: Application; /** The ID of the popup. */ id: string; /** The depth of the popup. */ @@ -112,6 +117,8 @@ export type PopupWindowConstructorDetails = { }; export type PopupProxyConstructorDetails = { + /** The main application instance. */ + application: Application; /** The ID of the popup. */ id: string; /** The depth of the popup. */ diff --git a/types/ext/text-scanner.d.ts b/types/ext/text-scanner.d.ts index 3e1cb6c2..4253d6cc 100644 --- a/types/ext/text-scanner.d.ts +++ b/types/ext/text-scanner.d.ts @@ -17,6 +17,7 @@ import type {TextScanner} from '../../ext/js/language/text-scanner'; import type {TextSourceGenerator} from '../../ext/js/dom/text-source-generator'; +import type {API} from '../../ext/js/comm/api'; import type * as Dictionary from './dictionary'; import type * as Display from './display'; import type * as Input from './input'; @@ -138,6 +139,7 @@ export type GetSearchContextCallbackSync = () => SearchContext; export type GetSearchContextCallbackAsync = () => Promise<SearchContext>; export type ConstructorDetails = { + api: API; node: HTMLElement | Window; getSearchContext: GetSearchContextCallback; ignoreElements?: (() => Element[]) | null; |