diff --git a/.eslintrc.json b/.eslintrc.json index af9f22b469..68bc0795df 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -670,7 +670,7 @@ "ext/js/general/text-source-map.js", "ext/js/language/deinflector.js", "ext/js/dictionary/dictionary-database.js", - "ext/js/language/sandbox/dictionary-data-util.js", + "ext/js/dictionary/dictionary-data-util.js", "ext/js/language/sandbox/japanese-util.js", "ext/js/language/translator.js", "ext/js/media/audio-downloader.js", diff --git a/dev/jsconfig.json b/dev/jsconfig.json index a754006890..c791b5c0d2 100644 --- a/dev/jsconfig.json +++ b/dev/jsconfig.json @@ -66,7 +66,7 @@ "../ext/js/language/deinflector.js", "../ext/js/dictionary/dictionary-importer.js", "../ext/js/dictionary/dictionary-database.js", - "../ext/js/language/sandbox/dictionary-data-util.js", + "../ext/js/dictionary/dictionary-data-util.js", "../ext/js/language/sandbox/japanese-util.js", "../ext/js/language/translator.js", "../ext/js/media/media-util.js", diff --git a/ext/data/schemas/dictionary-term-meta-bank-v3-schema.json b/ext/data/schemas/dictionary-term-meta-bank-v3-schema.json index 995c456aff..89709a9ea8 100644 --- a/ext/data/schemas/dictionary-term-meta-bank-v3-schema.json +++ b/ext/data/schemas/dictionary-term-meta-bank-v3-schema.json @@ -40,8 +40,8 @@ }, { "type": "string", - "enum": ["freq", "pitch"], - "description": "Type of data. \"freq\" corresponds to frequency information; \"pitch\" corresponds to pitch information." + "enum": ["freq", "pitch", "ipa"], + "description": "Type of data. \"freq\" corresponds to frequency information; \"pitch\" corresponds to pitch information. \"ipa\" corresponds to IPA transcription." }, { "description": "Data for the term." @@ -61,7 +61,7 @@ "description": "Frequency information for the term." }, { - "type": ["object"], + "type": "object", "required": [ "reading", "frequency" @@ -89,7 +89,7 @@ {}, {"const": "pitch"}, { - "type": ["object"], + "type": "object", "description": "Pitch accent information for the term.", "required": [ "reading", @@ -164,6 +164,54 @@ } } ] + }, + { + "minItems": 3, + "maxItems": 3, + "items": [ + {}, + {"const": "ipa"}, + { + "type": ["object"], + "description": "IPA transcription information for the term.", + "required": [ + "reading", + "transcriptions" + ], + "additionalProperties": false, + "properties": { + "reading": { + "type": "string", + "description": "Reading for the term." + }, + "transcriptions": { + "type": "array", + "description": "List of different IPA transcription information for the term and reading combination.", + "items": { + "type": "object", + "required": [ + "ipa" + ], + "additionalProperties": false, + "properties": { + "ipa": { + "type": "string", + "description": "IPA transcription for the term." + }, + "tags": { + "type": "array", + "description": "List of tags for this IPA transcription.", + "items": { + "type": "string", + "description": "Tag for this IPA transcription." + } + } + } + } + } + } + } + ] } ] } diff --git a/ext/data/templates/default-anki-field-templates.handlebars b/ext/data/templates/default-anki-field-templates.handlebars index d94f6d709f..f23b9d0be9 100644 --- a/ext/data/templates/default-anki-field-templates.handlebars +++ b/ext/data/templates/default-anki-field-templates.handlebars @@ -229,6 +229,27 @@ {{/inline}} {{! End Pitch Accents }} +{{#*inline "phonetic-transcriptions"}} + {{~#if (op ">" definition.phoneticTranscriptions.length 0)~}} + + {{~/if~}} +{{/inline}} + {{#*inline "clipboard-image"}} {{~#if (hasMedia "clipboardImage")~}} diff --git a/ext/js/accessibility/accessibility-controller.js b/ext/js/accessibility/accessibility-controller.js index 8250b36945..2b352948e8 100644 --- a/ext/js/accessibility/accessibility-controller.js +++ b/ext/js/accessibility/accessibility-controller.js @@ -16,19 +16,14 @@ * along with this program. If not, see . */ +import {isContentScriptRegistered, registerContentScript, unregisterContentScript} from '../background/script-manager.js'; import {log} from '../core.js'; /** * This class controls the registration of accessibility handlers. */ export class AccessibilityController { - /** - * Creates a new instance. - * @param {import('../background/script-manager.js').ScriptManager} scriptManager An instance of the `ScriptManager` class. - */ - constructor(scriptManager) { - /** @type {import('../background/script-manager.js').ScriptManager} */ - this._scriptManager = scriptManager; + constructor() { /** @type {?import('core').TokenObject} */ this._updateGoogleDocsAccessibilityToken = null; /** @type {?Promise} */ @@ -90,19 +85,17 @@ export class AccessibilityController { const id = 'googleDocsAccessibility'; try { if (forceGoogleDocsHtmlRenderingAny) { - if (await this._scriptManager.isContentScriptRegistered(id)) { return; } + if (await isContentScriptRegistered(id)) { return; } /** @type {import('script-manager').RegistrationDetails} */ const details = { allFrames: true, - matchAboutBlank: true, matches: ['*://docs.google.com/*'], - urlMatches: '^[^:]*://docs\\.google\\.com/[\\w\\W]*$', runAt: 'document_start', js: ['js/accessibility/google-docs.js'] }; - await this._scriptManager.registerContentScript(id, details); + await registerContentScript(id, details); } else { - await this._scriptManager.unregisterContentScript(id); + await unregisterContentScript(id); } } catch (e) { log.error(e); diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index b68b55f340..b093ec33fe 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventListenerCollection, invokeMessageHandler, log, promiseAnimationFrame} from '../core.js'; +import {EventListenerCollection, log, promiseAnimationFrame} from '../core.js'; +import {createApiMap, invokeApiMapHandler} from '../core/api-map.js'; import {DocumentUtil} from '../dom/document-util.js'; import {TextSourceElement} from '../dom/text-source-element.js'; import {TextSourceRange} from '../dom/text-source-range.js'; @@ -106,12 +107,12 @@ export class Frontend { this._optionsContextOverride = null; /* eslint-disable no-multi-spaces */ - /** @type {import('core').MessageHandlerMap} */ - this._runtimeMessageHandlers = new Map(/** @type {import('core').MessageHandlerMapInit} */ ([ - ['Frontend.requestReadyBroadcast', this._onMessageRequestFrontendReadyBroadcast.bind(this)], - ['Frontend.setAllVisibleOverride', this._onApiSetAllVisibleOverride.bind(this)], - ['Frontend.clearAllVisibleOverride', this._onApiClearAllVisibleOverride.bind(this)] - ])); + /** @type {import('application').ApiMap} */ + this._runtimeApiMap = createApiMap([ + ['frontendRequestReadyBroadcast', this._onMessageRequestFrontendReadyBroadcast.bind(this)], + ['frontendSetAllVisibleOverride', this._onApiSetAllVisibleOverride.bind(this)], + ['frontendClearAllVisibleOverride', this._onApiClearAllVisibleOverride.bind(this)] + ]); this._hotkeyHandler.registerActions([ ['scanSelectedText', this._onActionScanSelectedText.bind(this)], @@ -239,9 +240,7 @@ export class Frontend { // Message handlers - /** - * @param {import('frontend').FrontendRequestReadyBroadcastParams} params - */ + /** @type {import('application').ApiHandler<'frontendRequestReadyBroadcast'>} */ _onMessageRequestFrontendReadyBroadcast({frameId}) { this._signalFrontendReady(frameId); } @@ -313,10 +312,7 @@ export class Frontend { }; } - /** - * @param {{value: boolean, priority: number, awaitFrame: boolean}} params - * @returns {Promise} - */ + /** @type {import('application').ApiHandler<'frontendSetAllVisibleOverride'>} */ async _onApiSetAllVisibleOverride({value, priority, awaitFrame}) { const result = await this._popupFactory.setAllVisibleOverride(value, priority); if (awaitFrame) { @@ -325,10 +321,7 @@ export class Frontend { return result; } - /** - * @param {{token: import('core').TokenString}} params - * @returns {Promise} - */ + /** @type {import('application').ApiHandler<'frontendClearAllVisibleOverride'>} */ async _onApiClearAllVisibleOverride({token}) { return await this._popupFactory.clearAllVisibleOverride(token); } @@ -342,11 +335,9 @@ export class Frontend { this._updatePopupPosition(); } - /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ - _onRuntimeMessage({action, params}, sender, callback) { - const messageHandler = this._runtimeMessageHandlers.get(action); - if (typeof messageHandler === 'undefined') { return false; } - return invokeMessageHandler(messageHandler, params, callback, sender); + /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ + _onRuntimeMessage({action, params}, _sender, callback) { + return invokeApiMapHandler(this._runtimeApiMap, action, params, [], callback); } /** @@ -827,12 +818,12 @@ export class Frontend { * @param {?number} targetFrameId */ _signalFrontendReady(targetFrameId) { - /** @type {import('frontend').FrontendReadyDetails} */ - const params = {frameId: this._frameId}; + /** @type {import('application').ApiMessageNoFrameId<'frontendReady'>} */ + const message = {action: 'frontendReady', params: {frameId: this._frameId}}; if (targetFrameId === null) { - yomitan.api.broadcastTab('frontendReady', params); + yomitan.api.broadcastTab(message); } else { - yomitan.api.sendMessageToFrame(targetFrameId, 'frontendReady', params); + yomitan.api.sendMessageToFrame(targetFrameId, message); } } @@ -853,11 +844,11 @@ export class Frontend { } chrome.runtime.onMessage.removeListener(onMessage); }; - /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ + /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ const onMessage = (message, _sender, sendResponse) => { try { - const {action, params} = message; - if (action === 'frontendReady' && /** @type {import('frontend').FrontendReadyDetails} */ (params).frameId === frameId) { + const {action} = message; + if (action === 'frontendReady' && message.params.frameId === frameId) { cleanup(); resolve(); sendResponse(); @@ -876,7 +867,7 @@ export class Frontend { } chrome.runtime.onMessage.addListener(onMessage); - yomitan.api.broadcastTab('Frontend.requestReadyBroadcast', {frameId: this._frameId}); + yomitan.api.broadcastTab({action: 'frontendRequestReadyBroadcast', params: {frameId: this._frameId}}); }); } diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index b4be6c174d..68d4e0c89a 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -40,7 +40,7 @@ import {MediaUtil} from '../media/media-util.js'; import {ClipboardReaderProxy, DictionaryDatabaseProxy, OffscreenProxy, TranslatorProxy} from './offscreen-proxy.js'; import {ProfileConditionsUtil} from './profile-conditions-util.js'; import {RequestBuilder} from './request-builder.js'; -import {ScriptManager} from './script-manager.js'; +import {injectStylesheet} from './script-manager.js'; /** * This class controls the core logic of the extension, including API calls @@ -110,10 +110,8 @@ export class Backend { }); /** @type {OptionsUtil} */ this._optionsUtil = new OptionsUtil(); - /** @type {ScriptManager} */ - this._scriptManager = new ScriptManager(); /** @type {AccessibilityController} */ - this._accessibilityController = new AccessibilityController(this._scriptManager); + this._accessibilityController = new AccessibilityController(); /** @type {?number} */ this._searchPopupTabId = null; @@ -301,8 +299,8 @@ export class Backend { this._clipboardMonitor.on('change', this._onClipboardTextChange.bind(this)); - this._sendMessageAllTabsIgnoreResponse('Yomitan.backendReady', {}); - this._sendMessageIgnoreResponse({action: 'Yomitan.backendReady', params: {}}); + this._sendMessageAllTabsIgnoreResponse({action: 'applicationBackendReady'}); + this._sendMessageIgnoreResponse({action: 'applicationBackendReady'}); } catch (e) { log.error(e); throw e; @@ -406,7 +404,7 @@ export class Backend { * @param {chrome.tabs.ZoomChangeInfo} event */ _onZoomChange({tabId, oldZoomFactor, newZoomFactor}) { - this._sendMessageTabIgnoreResponse(tabId, {action: 'Yomitan.zoomChanged', params: {oldZoomFactor, newZoomFactor}}, {}); + this._sendMessageTabIgnoreResponse(tabId, {action: 'applicationZoomChanged', params: {oldZoomFactor, newZoomFactor}}, {}); } /** @@ -429,7 +427,8 @@ export class Backend { /** @type {import('api').ApiHandler<'requestBackendReadySignal'>} */ _onApiRequestBackendReadySignal(_params, sender) { // tab ID isn't set in background (e.g. browser_action) - const data = {action: 'Yomitan.backendReady', params: {}}; + /** @type {import('application').ApiMessage<'applicationBackendReady'>} */ + const data = {action: 'applicationBackendReady'}; if (typeof sender.tab === 'undefined') { this._sendMessageIgnoreResponse(data); return false; @@ -611,30 +610,30 @@ export class Backend { } /** @type {import('api').ApiHandler<'sendMessageToFrame'>} */ - _onApiSendMessageToFrame({frameId: targetFrameId, action, params}, sender) { + _onApiSendMessageToFrame({frameId: targetFrameId, message}, sender) { if (!sender) { return false; } const {tab} = sender; if (!tab) { return false; } const {id} = tab; if (typeof id !== 'number') { return false; } - const frameId = sender.frameId; - /** @type {import('extension').ChromeRuntimeMessageWithFrameId} */ - const message = {action, params, frameId}; - this._sendMessageTabIgnoreResponse(id, message, {frameId: targetFrameId}); + const {frameId} = sender; + /** @type {import('application').ApiMessageAny} */ + const message2 = {...message, frameId}; + this._sendMessageTabIgnoreResponse(id, message2, {frameId: targetFrameId}); return true; } /** @type {import('api').ApiHandler<'broadcastTab'>} */ - _onApiBroadcastTab({action, params}, sender) { + _onApiBroadcastTab({message}, sender) { if (!sender) { return false; } const {tab} = sender; if (!tab) { return false; } const {id} = tab; if (typeof id !== 'number') { return false; } - const frameId = sender.frameId; - /** @type {import('extension').ChromeRuntimeMessageWithFrameId} */ - const message = {action, params, frameId}; - this._sendMessageTabIgnoreResponse(id, message, {}); + const {frameId} = sender; + /** @type {import('application').ApiMessageAny} */ + const message2 = {...message, frameId}; + this._sendMessageTabIgnoreResponse(id, message2, {}); return true; } @@ -650,7 +649,7 @@ export class Backend { async _onApiInjectStylesheet({type, value}, sender) { const {frameId, tab} = sender; if (typeof tab !== 'object' || tab === null || typeof tab.id !== 'number') { throw new Error('Invalid tab'); } - return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false); + return await injectStylesheet(type, value, tab.id, frameId, false); } /** @type {import('api').ApiHandler<'getStylesheetContent'>} */ @@ -1096,7 +1095,7 @@ export class Backend { await this._sendMessageTabPromise( id, - {action: 'SearchDisplayController.setMode', params: {mode: 'popup'}}, + {action: 'searchDisplayControllerSetMode', params: {mode: 'popup'}}, {frameId: 0} ); @@ -1116,7 +1115,7 @@ export class Backend { try { const mode = await this._sendMessageTabPromise( id, - {action: 'SearchDisplayController.getMode', params: {}}, + {action: 'searchDisplayControllerGetMode'}, {frameId: 0} ); return mode === 'popup'; @@ -1196,7 +1195,7 @@ export class Backend { async _updateSearchQuery(tabId, text, animate) { await this._sendMessageTabPromise( tabId, - {action: 'SearchDisplayController.updateSearchQuery', params: {text, animate}}, + {action: 'searchDisplayControllerUpdateSearchQuery', params: {text, animate}}, {frameId: 0} ); } @@ -1227,7 +1226,7 @@ export class Backend { this._accessibilityController.update(this._getOptionsFull(false)); - this._sendMessageAllTabsIgnoreResponse('Yomitan.optionsUpdated', {source}); + this._sendMessageAllTabsIgnoreResponse({action: 'applicationOptionsUpdated', params: {source}}); } /** @@ -1635,7 +1634,7 @@ export class Backend { try { const response = await this._sendMessageTabPromise( tabId, - {action: 'Yomitan.getUrl', params: {}}, + {action: 'applicationGetUrl'}, {frameId: 0} ); const url = typeof response === 'object' && response !== null ? /** @type {import('core').SerializableObject} */ (response).url : void 0; @@ -1806,14 +1805,14 @@ export class Backend { return new Promise((resolve, reject) => { /** @type {?import('core').Timeout} */ let timer = null; - /** @type {?import('extension').ChromeRuntimeOnMessageCallback} */ + /** @type {?import('extension').ChromeRuntimeOnMessageCallback} */ let onMessage = (message, sender) => { if ( !sender.tab || sender.tab.id !== tabId || sender.frameId !== frameId || !(typeof message === 'object' && message !== null) || - /** @type {import('core').SerializableObject} */ (message).action !== 'yomitanReady' + message.action !== 'applicationReady' ) { return; } @@ -1834,7 +1833,7 @@ export class Backend { chrome.runtime.onMessage.addListener(onMessage); - this._sendMessageTabPromise(tabId, {action: 'Yomitan.isReady'}, {frameId}) + this._sendMessageTabPromise(tabId, {action: 'applicationIsReady'}, {frameId}) .then( (value) => { if (!value) { return; } @@ -1893,7 +1892,8 @@ export class Backend { } /** - * @param {{action: string, params: import('core').SerializableObject}} message + * @template {import('application').ApiNames} TName + * @param {import('application').ApiMessage} message */ _sendMessageIgnoreResponse(message) { const callback = () => this._checkLastError(chrome.runtime.lastError); @@ -1902,7 +1902,7 @@ export class Backend { /** * @param {number} tabId - * @param {{action: string, params?: import('core').SerializableObject, frameId?: number}} message + * @param {import('application').ApiMessageAny} message * @param {chrome.tabs.MessageSendOptions} options */ _sendMessageTabIgnoreResponse(tabId, message, options) { @@ -1911,25 +1911,25 @@ export class Backend { } /** - * @param {string} action - * @param {import('core').SerializableObject} params + * @param {import('application').ApiMessageAny} message */ - _sendMessageAllTabsIgnoreResponse(action, params) { + _sendMessageAllTabsIgnoreResponse(message) { const callback = () => this._checkLastError(chrome.runtime.lastError); chrome.tabs.query({}, (tabs) => { for (const tab of tabs) { const {id} = tab; if (typeof id !== 'number') { continue; } - chrome.tabs.sendMessage(id, {action, params}, callback); + chrome.tabs.sendMessage(id, message, callback); } }); } /** + * @template {import('application').ApiNames} TName * @param {number} tabId - * @param {{action: string, params?: import('core').SerializableObject}} message + * @param {import('application').ApiMessage} message * @param {chrome.tabs.MessageSendOptions} options - * @returns {Promise} + * @returns {Promise>} */ _sendMessageTabPromise(tabId, message, options) { return new Promise((resolve, reject) => { @@ -1938,7 +1938,7 @@ export class Backend { */ const callback = (response) => { try { - resolve(this._getMessageResponseResult(response)); + resolve(/** @type {import('application').ApiReturn} */ (this._getMessageResponseResult(response))); } catch (error) { reject(error); } @@ -1961,11 +1961,11 @@ export class Backend { if (typeof response !== 'object' || response === null) { throw new Error('Tab did not respond'); } - const responseError = /** @type {import('core').SerializedError|undefined} */ (/** @type {import('core').SerializableObject} */ (response).error); + const responseError = /** @type {import('core').Response} */ (response).error; if (typeof responseError === 'object' && responseError !== null) { throw ExtensionError.deserialize(responseError); } - return /** @type {import('core').SerializableObject} */ (response).result; + return /** @type {import('core').Response} */ (response).result; } /** @@ -2000,7 +2000,7 @@ export class Backend { let token = null; try { if (typeof tabId === 'number' && typeof frameId === 'number') { - const action = 'Frontend.setAllVisibleOverride'; + const action = 'frontendSetAllVisibleOverride'; const params = {value: false, priority: 0, awaitFrame: true}; token = await this._sendMessageTabPromise(tabId, {action, params}, {frameId}); } @@ -2017,7 +2017,7 @@ export class Backend { }); } finally { if (token !== null) { - const action = 'Frontend.clearAllVisibleOverride'; + const action = 'frontendClearAllVisibleOverride'; const params = {token}; try { await this._sendMessageTabPromise(tabId, {action, params}, {frameId}); @@ -2382,7 +2382,7 @@ export class Backend { */ _triggerDatabaseUpdated(type, cause) { this._translator.clearDatabaseCaches(); - this._sendMessageAllTabsIgnoreResponse('Yomitan.databaseUpdated', {type, cause}); + this._sendMessageAllTabsIgnoreResponse({action: 'applicationDatabaseUpdated', params: {type, cause}}); } /** diff --git a/ext/js/background/script-manager.js b/ext/js/background/script-manager.js index 98f67bb0b8..1142121fad 100644 --- a/ext/js/background/script-manager.js +++ b/ext/js/background/script-manager.js @@ -16,419 +16,139 @@ * along with this program. If not, see . */ -import {isObject} from '../core.js'; - /** - * This class is used to manage script injection into content tabs. + * Injects a stylesheet into a tab. + * @param {'file'|'code'} type The type of content to inject; either 'file' or 'code'. + * @param {string} content The content to inject. + * - If type is `'file'`, this argument should be a path to a file. + * - If type is `'code'`, this argument should be the CSS content. + * @param {number} tabId The id of the tab to inject into. + * @param {number|undefined} frameId The id of the frame to inject into. + * @param {boolean} allFrames Whether or not the stylesheet should be injected into all frames. + * @returns {Promise} */ -export class ScriptManager { - /** - * Creates a new instance of the class. - */ - constructor() { - /** @type {Map} */ - this._contentScriptRegistrations = new Map(); - } - - /** - * Injects a stylesheet into a tab. - * @param {'file'|'code'} type The type of content to inject; either 'file' or 'code'. - * @param {string} content The content to inject. - * If type is 'file', this argument should be a path to a file. - * If type is 'code', this argument should be the CSS content. - * @param {number} tabId The id of the tab to inject into. - * @param {number|undefined} frameId The id of the frame to inject into. - * @param {boolean} allFrames Whether or not the stylesheet should be injected into all frames. - * @returns {Promise} - */ - injectStylesheet(type, content, tabId, frameId, allFrames) { - if (isObject(chrome.scripting) && typeof chrome.scripting.insertCSS === 'function') { - return this._injectStylesheetMV3(type, content, tabId, frameId, allFrames); - } else { - return Promise.reject(new Error('Stylesheet injection not supported')); - } - } - - /** - * Injects a script into a tab. - * @param {string} file The path to a file to inject. - * @param {number} tabId The id of the tab to inject into. - * @param {number|undefined} frameId The id of the frame to inject into. - * @param {boolean} allFrames Whether or not the script should be injected into all frames. - * @returns {Promise<{frameId: number|undefined, result: unknown}>} The id of the frame and the result of the script injection. - */ - injectScript(file, tabId, frameId, allFrames) { - if (isObject(chrome.scripting) && typeof chrome.scripting.executeScript === 'function') { - return this._injectScriptMV3(file, tabId, frameId, allFrames); - } else { - return Promise.reject(new Error('Script injection not supported')); - } - } - - /** - * Checks whether or not a content script is registered. - * @param {string} id The identifier used with a call to `registerContentScript`. - * @returns {Promise} `true` if a script is registered, `false` otherwise. - */ - async isContentScriptRegistered(id) { - if (this._contentScriptRegistrations.has(id)) { - return true; - } - if (isObject(chrome.scripting) && typeof chrome.scripting.getRegisteredContentScripts === 'function') { - const scripts = await new Promise((resolve, reject) => { - chrome.scripting.getRegisteredContentScripts({ids: [id]}, (result) => { - const e = chrome.runtime.lastError; - if (e) { - reject(new Error(e.message)); - } else { - resolve(result); - } - }); - }); - for (const script of scripts) { - if (script.id === id) { - return true; - } +export function injectStylesheet(type, content, tabId, frameId, allFrames) { + return new Promise((resolve, reject) => { + /** @type {chrome.scripting.InjectionTarget} */ + const target = { + tabId, + allFrames + }; + /** @type {chrome.scripting.CSSInjection} */ + const details = ( + type === 'file' ? + {origin: 'AUTHOR', files: [content], target} : + {origin: 'USER', css: content, target} + ); + if (!allFrames && typeof frameId === 'number') { + details.target.frameIds = [frameId]; + } + chrome.scripting.insertCSS(details, () => { + const e = chrome.runtime.lastError; + if (e) { + reject(new Error(e.message)); + } else { + resolve(); } - } - return false; - } - - /** - * Registers a dynamic content script. - * Note: if the fallback handler is used and the 'webNavigation' permission isn't granted, - * there is a possibility that the script can be injected more than once due to the events used. - * Therefore, a reentrant check may need to be performed by the content script. - * @param {string} id A unique identifier for the registration. - * @param {import('script-manager').RegistrationDetails} details The script registration details. - * @throws An error is thrown if the id is already in use. - */ - async registerContentScript(id, details) { - if (await this.isContentScriptRegistered(id)) { - throw new Error('Registration already exists'); - } - - if (isObject(chrome.scripting) && typeof chrome.scripting.registerContentScripts === 'function') { - const details2 = this._createContentScriptRegistrationOptionsChrome(details, id); - await /** @type {Promise} */ (new Promise((resolve, reject) => { - chrome.scripting.registerContentScripts([details2], () => { - const e = chrome.runtime.lastError; - if (e) { - reject(new Error(e.message)); - } else { - resolve(); - } - }); - })); - this._contentScriptRegistrations.set(id, null); - return; - } - - // Fallback - this._registerContentScriptFallback(id, details); - } + }); + }); +} - /** - * Unregisters a previously registered content script. - * @param {string} id The identifier passed to a previous call to `registerContentScript`. - * @returns {Promise} `true` if the content script was unregistered, `false` otherwise. - */ - async unregisterContentScript(id) { - if (isObject(chrome.scripting) && typeof chrome.scripting.unregisterContentScripts === 'function') { - this._contentScriptRegistrations.delete(id); - try { - await this._unregisterContentScriptMV3(id); - return true; - } catch (e) { - return false; +/** + * Checks whether or not a content script is registered. + * @param {string} id The identifier used with a call to `registerContentScript`. + * @returns {Promise} `true` if a script is registered, `false` otherwise. + */ +export async function isContentScriptRegistered(id) { + const scripts = await new Promise((resolve, reject) => { + chrome.scripting.getRegisteredContentScripts({ids: [id]}, (result) => { + const e = chrome.runtime.lastError; + if (e) { + reject(new Error(e.message)); + } else { + resolve(result); } + }); + }); + for (const script of scripts) { + if (script.id === id) { + return true; } - - // Fallback - const registration = this._contentScriptRegistrations.get(id); - if (typeof registration === 'undefined') { return false; } - this._contentScriptRegistrations.delete(id); - if (registration !== null && typeof registration.unregister === 'function') { - await registration.unregister(); - } - return true; } + return false; +} - /** - * Gets the optional permissions required to register a content script. - * @returns {string[]} An array of the required permissions, which may be empty. - */ - getRequiredContentScriptRegistrationPermissions() { - if (isObject(chrome.scripting) && typeof chrome.scripting.registerContentScripts === 'function') { - return []; - } - - // Fallback - return ['webNavigation']; +/** + * Registers a dynamic content script. + * Note: if the fallback handler is used and the 'webNavigation' permission isn't granted, + * there is a possibility that the script can be injected more than once due to the events used. + * Therefore, a reentrant check may need to be performed by the content script. + * @param {string} id A unique identifier for the registration. + * @param {import('script-manager').RegistrationDetails} details The script registration details. + * @throws An error is thrown if the id is already in use. + */ +export async function registerContentScript(id, details) { + if (await isContentScriptRegistered(id)) { + throw new Error('Registration already exists'); } - // Private - - /** - * @param {'file'|'code'} type - * @param {string} content - * @param {number} tabId - * @param {number|undefined} frameId - * @param {boolean} allFrames - * @returns {Promise} - */ - _injectStylesheetMV3(type, content, tabId, frameId, allFrames) { - return new Promise((resolve, reject) => { - /** @type {chrome.scripting.InjectionTarget} */ - const target = { - tabId, - allFrames - }; - /** @type {chrome.scripting.CSSInjection} */ - const details = ( - type === 'file' ? - {origin: 'AUTHOR', files: [content], target} : - {origin: 'USER', css: content, target} - ); - if (!allFrames && typeof frameId === 'number') { - details.target.frameIds = [frameId]; + const details2 = createContentScriptRegistrationOptions(details, id); + await /** @type {Promise} */ (new Promise((resolve, reject) => { + chrome.scripting.registerContentScripts([details2], () => { + const e = chrome.runtime.lastError; + if (e) { + reject(new Error(e.message)); + } else { + resolve(); } - chrome.scripting.insertCSS(details, () => { - const e = chrome.runtime.lastError; - if (e) { - reject(new Error(e.message)); - } else { - resolve(); - } - }); }); - } + })); +} - /** - * @param {string} file - * @param {number} tabId - * @param {number|undefined} frameId - * @param {boolean} allFrames - * @returns {Promise<{frameId: number|undefined, result: unknown}>} The id of the frame and the result of the script injection. - */ - _injectScriptMV3(file, tabId, frameId, allFrames) { - return new Promise((resolve, reject) => { - /** @type {chrome.scripting.ScriptInjection} */ - const details = { - injectImmediately: true, - files: [file], - target: {tabId, allFrames} - }; - if (!allFrames && typeof frameId === 'number') { - details.target.frameIds = [frameId]; +/** + * Unregisters a previously registered content script. + * @param {string} id The identifier passed to a previous call to `registerContentScript`. + * @returns {Promise} + */ +export async function unregisterContentScript(id) { + return new Promise((resolve, reject) => { + chrome.scripting.unregisterContentScripts({ids: [id]}, () => { + const e = chrome.runtime.lastError; + if (e) { + reject(new Error(e.message)); + } else { + resolve(); } - chrome.scripting.executeScript(details, (results) => { - const e = chrome.runtime.lastError; - if (e) { - reject(new Error(e.message)); - } else { - const {frameId: frameId2, result} = results[0]; - resolve({frameId: frameId2, result}); - } - }); }); - } - - /** - * @param {string} id - * @returns {Promise} - */ - _unregisterContentScriptMV3(id) { - return new Promise((resolve, reject) => { - chrome.scripting.unregisterContentScripts({ids: [id]}, () => { - const e = chrome.runtime.lastError; - if (e) { - reject(new Error(e.message)); - } else { - resolve(); - } - }); - }); - } - - /** - * @param {import('script-manager').RegistrationDetails} details - * @returns {browser.contentScripts.RegisteredContentScriptOptions} - */ - _createContentScriptRegistrationOptionsFirefox(details) { - const {css, js, matchAboutBlank} = details; - /** @type {browser.contentScripts.RegisteredContentScriptOptions} */ - const options = {}; - if (typeof matchAboutBlank !== 'undefined') { - options.matchAboutBlank = matchAboutBlank; - } - if (Array.isArray(css)) { - options.css = css.map((file) => ({file})); - } - if (Array.isArray(js)) { - options.js = js.map((file) => ({file})); - } - this._initializeContentScriptRegistrationOptionsGeneric(details, options); - return options; - } - - /** - * @param {import('script-manager').RegistrationDetails} details - * @param {string} id - * @returns {chrome.scripting.RegisteredContentScript} - */ - _createContentScriptRegistrationOptionsChrome(details, id) { - const {css, js} = details; - /** @type {chrome.scripting.RegisteredContentScript} */ - const options = { - id: id, - persistAcrossSessions: true - }; - if (Array.isArray(css)) { - options.css = [...css]; - } - if (Array.isArray(js)) { - options.js = [...js]; - } - this._initializeContentScriptRegistrationOptionsGeneric(details, options); - return options; - } + }); +} - /** - * @param {import('script-manager').RegistrationDetails} details - * @param {chrome.scripting.RegisteredContentScript|browser.contentScripts.RegisteredContentScriptOptions} options - */ - _initializeContentScriptRegistrationOptionsGeneric(details, options) { - const {allFrames, excludeMatches, matches, runAt} = details; - if (typeof allFrames !== 'undefined') { - options.allFrames = allFrames; - } - if (Array.isArray(excludeMatches)) { - options.excludeMatches = [...excludeMatches]; - } - if (Array.isArray(matches)) { - options.matches = [...matches]; - } - if (typeof runAt !== 'undefined') { - options.runAt = runAt; - } +/** + * @param {import('script-manager').RegistrationDetails} details + * @param {string} id + * @returns {chrome.scripting.RegisteredContentScript} + */ +function createContentScriptRegistrationOptions(details, id) { + const {css, js, allFrames, matches, runAt} = details; + /** @type {chrome.scripting.RegisteredContentScript} */ + const options = { + id: id, + persistAcrossSessions: true + }; + if (Array.isArray(css)) { + options.css = [...css]; } - - /** - * @param {string[]} array - * @param {boolean} firefoxConvention - * @returns {string[]|browser.extensionTypes.ExtensionFileOrCode[]} - */ - _convertFileArray(array, firefoxConvention) { - return firefoxConvention ? array.map((file) => ({file})) : [...array]; + if (Array.isArray(js)) { + options.js = [...js]; } - - /** - * @param {string} id - * @param {import('script-manager').RegistrationDetails} details - */ - _registerContentScriptFallback(id, details) { - const {allFrames, css, js, matchAboutBlank, runAt, urlMatches} = details; - /** @type {import('script-manager').ContentScriptInjectionDetails} */ - const details2 = {allFrames, css, js, matchAboutBlank, runAt, urlRegex: /** @type {?RegExp} */ (null)}; - /** @type {() => Promise} */ - let unregister; - const webNavigationEvent = this._getWebNavigationEvent(runAt); - if (typeof webNavigationEvent === 'object' && webNavigationEvent !== null) { - /** - * @param {chrome.webNavigation.WebNavigationFramedCallbackDetails} details - */ - const onTabCommitted = ({url, tabId, frameId}) => { - this._injectContentScript(true, details2, null, url, tabId, frameId); - }; - const filter = {url: [{urlMatches}]}; - webNavigationEvent.addListener(onTabCommitted, filter); - unregister = async () => webNavigationEvent.removeListener(onTabCommitted); - } else { - /** - * @param {number} tabId - * @param {chrome.tabs.TabChangeInfo} changeInfo - * @param {chrome.tabs.Tab} tab - */ - const onTabUpdated = (tabId, {status}, {url}) => { - if (typeof status === 'string' && typeof url === 'string') { - this._injectContentScript(false, details2, status, url, tabId, void 0); - } - }; - try { - // Firefox - /** @type {browser.tabs.UpdateFilter} */ - const extraParameters = {urls: [urlMatches], properties: ['status']}; - browser.tabs.onUpdated.addListener( - /** @type {(tabId: number, changeInfo: browser.tabs._OnUpdatedChangeInfo, tab: browser.tabs.Tab) => void} */ (onTabUpdated), - extraParameters - ); - } catch (e) { - // Chrome - details2.urlRegex = new RegExp(urlMatches); - chrome.tabs.onUpdated.addListener(onTabUpdated); - } - unregister = async () => chrome.tabs.onUpdated.removeListener(onTabUpdated); - } - this._contentScriptRegistrations.set(id, {unregister}); + if (typeof allFrames !== 'undefined') { + options.allFrames = allFrames; } - - /** - * @param {import('script-manager').RunAt} runAt - * @returns {?(chrome.webNavigation.WebNavigationFramedEvent|chrome.webNavigation.WebNavigationTransitionalEvent)} - */ - _getWebNavigationEvent(runAt) { - const {webNavigation} = chrome; - if (!isObject(webNavigation)) { return null; } - switch (runAt) { - case 'document_start': - return webNavigation.onCommitted; - case 'document_end': - return webNavigation.onDOMContentLoaded; - default: // 'document_idle': - return webNavigation.onCompleted; - } + if (Array.isArray(matches)) { + options.matches = [...matches]; } - - /** - * @param {boolean} isWebNavigation - * @param {import('script-manager').ContentScriptInjectionDetails} details - * @param {?string} status - * @param {string} url - * @param {number} tabId - * @param {number|undefined} frameId - */ - async _injectContentScript(isWebNavigation, details, status, url, tabId, frameId) { - const {urlRegex} = details; - if (urlRegex !== null && !urlRegex.test(url)) { return; } - - let {allFrames, css, js, runAt} = details; - - if (isWebNavigation) { - if (allFrames) { - allFrames = false; - } else { - if (frameId !== 0) { return; } - } - } else { - if (runAt === 'document_start') { - if (status !== 'loading') { return; } - } else { // 'document_end', 'document_idle' - if (status !== 'complete') { return; } - } - } - - const promises = []; - if (Array.isArray(css)) { - for (const file of css) { - promises.push(this.injectStylesheet('file', file, tabId, frameId, allFrames)); - } - } - if (Array.isArray(js)) { - for (const file of js) { - promises.push(this.injectScript(file, tabId, frameId, allFrames)); - } - } - await Promise.all(promises); + if (typeof runAt !== 'undefined') { + options.runAt = runAt; } + return options; } diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index c23515383e..423115f18c 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -156,21 +156,19 @@ export class API { /** * @param {import('api').ApiParam<'sendMessageToFrame', 'frameId'>} frameId - * @param {import('api').ApiParam<'sendMessageToFrame', 'action'>} action - * @param {import('api').ApiParam<'sendMessageToFrame', 'params'>} [params] + * @param {import('api').ApiParam<'sendMessageToFrame', 'message'>} message * @returns {Promise>} */ - sendMessageToFrame(frameId, action, params) { - return this._invoke('sendMessageToFrame', {frameId, action, params}); + sendMessageToFrame(frameId, message) { + return this._invoke('sendMessageToFrame', {frameId, message}); } /** - * @param {import('api').ApiParam<'broadcastTab', 'action'>} action - * @param {import('api').ApiParam<'broadcastTab', 'params'>} params + * @param {import('api').ApiParam<'broadcastTab', 'message'>} message * @returns {Promise>} */ - broadcastTab(action, params) { - return this._invoke('broadcastTab', {action, params}); + broadcastTab(message) { + return this._invoke('broadcastTab', {message}); } /** diff --git a/ext/js/comm/frame-client.js b/ext/js/comm/frame-client.js index 5e997622fc..cb591ca995 100644 --- a/ext/js/comm/frame-client.js +++ b/ext/js/comm/frame-client.js @@ -110,14 +110,14 @@ export class FrameClient { contentWindow.postMessage({action, params}, targetOrigin); }; - /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ + /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ const onMessage = (message) => { onMessageInner(message); return false; }; /** - * @param {import('extension').ChromeRuntimeMessageWithFrameId} message + * @param {import('application').ApiMessageAny} message */ const onMessageInner = async (message) => { try { @@ -130,7 +130,7 @@ export class FrameClient { switch (action) { case 'frameEndpointReady': { - const {secret} = /** @type {import('frame-client').FrameEndpointReadyDetails} */ (params); + const {secret} = params; const token = generateId(16); tokenMap.set(secret, token); postMessage('frameEndpointConnect', {secret, token, hostFrameId}); @@ -138,7 +138,7 @@ export class FrameClient { break; case 'frameEndpointConnected': { - const {secret, token} = /** @type {import('frame-client').FrameEndpointConnectedDetails} */ (params); + const {secret, token} = params; const frameId = message.frameId; const token2 = tokenMap.get(secret); if (typeof token2 !== 'undefined' && token === token2 && typeof frameId === 'number') { diff --git a/ext/js/comm/frame-endpoint.js b/ext/js/comm/frame-endpoint.js index c338e143c4..4c5f58c106 100644 --- a/ext/js/comm/frame-endpoint.js +++ b/ext/js/comm/frame-endpoint.js @@ -41,7 +41,7 @@ export class FrameEndpoint { } /** @type {import('frame-client').FrameEndpointReadyDetails} */ const details = {secret: this._secret}; - yomitan.api.broadcastTab('frameEndpointReady', details); + yomitan.api.broadcastTab({action: 'frameEndpointReady', params: details}); } /** @@ -83,6 +83,6 @@ export class FrameEndpoint { this._eventListeners.removeAllEventListeners(); /** @type {import('frame-client').FrameEndpointConnectedDetails} */ const details = {secret, token}; - yomitan.api.sendMessageToFrame(hostFrameId, 'frameEndpointConnected', details); + yomitan.api.sendMessageToFrame(hostFrameId, {action: 'frameEndpointConnected', params: details}); } } diff --git a/ext/js/data/sandbox/anki-note-data-creator.js b/ext/js/data/sandbox/anki-note-data-creator.js index 9d93b49789..c0a1186980 100644 --- a/ext/js/data/sandbox/anki-note-data-creator.js +++ b/ext/js/data/sandbox/anki-note-data-creator.js @@ -55,6 +55,8 @@ export class AnkiNoteDataCreator { const context2 = this.createCachedValue(this._getPublicContext.bind(this, context)); const pitches = this.createCachedValue(this._getPitches.bind(this, dictionaryEntry)); const pitchCount = this.createCachedValue(this._getPitchCount.bind(this, pitches)); + const phoneticTranscriptions = this.createCachedValue(this._getPhoneticTranscriptions.bind(this, dictionaryEntry)); + if (typeof media !== 'object' || media === null || Array.isArray(media)) { media = { audio: void 0, @@ -82,6 +84,7 @@ export class AnkiNoteDataCreator { get uniqueReadings() { return self.getCachedValue(uniqueReadings); }, get pitches() { return self.getCachedValue(pitches); }, get pitchCount() { return self.getCachedValue(pitchCount); }, + get phoneticTranscriptions() { return self.getCachedValue(phoneticTranscriptions); }, get context() { return self.getCachedValue(context2); }, media, dictionaryEntry @@ -193,7 +196,11 @@ export class AnkiNoteDataCreator { for (const {dictionary, pronunciations} of DictionaryDataUtil.getGroupedPronunciations(dictionaryEntry)) { /** @type {import('anki-templates').Pitch[]} */ const pitches = []; - for (const {terms, reading, position, nasalPositions, devoicePositions, tags, exclusiveTerms, exclusiveReadings} of pronunciations) { + for (const groupedPronunciation of pronunciations) { + const {pronunciation} = groupedPronunciation; + if (pronunciation.type !== 'pitch-accent') { continue; } + const {position, nasalPositions, devoicePositions, tags} = pronunciation; + const {terms, reading, exclusiveTerms, exclusiveReadings} = groupedPronunciation; pitches.push({ expressions: terms, reading, @@ -211,6 +218,35 @@ export class AnkiNoteDataCreator { return results; } + /** + * @param {import('dictionary').DictionaryEntry} dictionaryEntry + * @returns {import('anki-templates').TranscriptionGroup[]} + */ + _getPhoneticTranscriptions(dictionaryEntry) { + const results = []; + if (dictionaryEntry.type === 'term') { + for (const {dictionary, pronunciations} of DictionaryDataUtil.getGroupedPronunciations(dictionaryEntry)) { + const phoneticTranscriptions = []; + for (const groupedPronunciation of pronunciations) { + const {pronunciation} = groupedPronunciation; + if (pronunciation.type !== 'phonetic-transcription') { continue; } + const {ipa, tags} = pronunciation; + const {terms, reading, exclusiveTerms, exclusiveReadings} = groupedPronunciation; + phoneticTranscriptions.push({ + expressions: terms, + reading, + ipa, + tags, + exclusiveExpressions: exclusiveTerms, + exclusiveReadings + }); + } + results.push({dictionary, phoneticTranscriptions}); + } + } + return results; + } + /** * @param {import('anki-templates-internal').CachedValue} cachedPitches * @returns {number} @@ -353,6 +389,7 @@ export class AnkiNoteDataCreator { const expressions = this.createCachedValue(this._getTermExpressions.bind(this, dictionaryEntry)); const frequencies = this.createCachedValue(this._getTermFrequencies.bind(this, dictionaryEntry)); const pitches = this.createCachedValue(this._getTermPitches.bind(this, dictionaryEntry)); + const phoneticTranscriptions = this.createCachedValue(this._getTermPhoneticTranscriptions.bind(this, dictionaryEntry)); const glossary = this.createCachedValue(this._getTermGlossaryArray.bind(this, dictionaryEntry, type)); const cloze = this.createCachedValue(this._getCloze.bind(this, dictionaryEntry, context)); const furiganaSegments = this.createCachedValue(this._getTermFuriganaSegments.bind(this, dictionaryEntry, type)); @@ -389,6 +426,7 @@ export class AnkiNoteDataCreator { get definitions() { return self.getCachedValue(commonInfo).definitions; }, get frequencies() { return self.getCachedValue(frequencies); }, get pitches() { return self.getCachedValue(pitches); }, + get phoneticTranscriptions() { return self.getCachedValue(phoneticTranscriptions); }, sourceTermExactMatchCount, url, get cloze() { return self.getCachedValue(cloze); }, @@ -485,15 +523,16 @@ export class AnkiNoteDataCreator { /** * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry - * @returns {import('anki-templates').TermPronunciation[]} + * @returns {import('anki-templates').TermPitchAccent[]} */ _getTermPitches(dictionaryEntry) { // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; const results = []; const {headwords} = dictionaryEntry; - for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pitches} of dictionaryEntry.pronunciations) { + for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations} of dictionaryEntry.pronunciations) { const {term, reading} = headwords[headwordIndex]; + const pitches = DictionaryDataUtil.getPronunciationsOfType(pronunciations, 'pitch-accent'); const cachedPitches = this.createCachedValue(this._getTermPitchesInner.bind(this, pitches)); results.push({ index: results.length, @@ -512,8 +551,8 @@ export class AnkiNoteDataCreator { } /** - * @param {import('dictionary').TermPitch[]} pitches - * @returns {import('anki-templates').TermPitch[]} + * @param {import('dictionary').PitchAccent[]} pitches + * @returns {import('anki-templates').PitchAccent[]} */ _getTermPitchesInner(pitches) { // eslint-disable-next-line @typescript-eslint/no-this-alias @@ -529,6 +568,52 @@ export class AnkiNoteDataCreator { return results; } + /** + * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry + * @returns {import('anki-templates').TermPhoneticTranscription[]} + */ + _getTermPhoneticTranscriptions(dictionaryEntry) { + const results = []; + const {headwords} = dictionaryEntry; + for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations} of dictionaryEntry.pronunciations) { + const {term, reading} = headwords[headwordIndex]; + const phoneticTranscriptions = DictionaryDataUtil.getPronunciationsOfType(pronunciations, 'phonetic-transcription'); + const termPhoneticTranscriptions = this._getTermPhoneticTranscriptionsInner(phoneticTranscriptions); + results.push({ + index: results.length, + expressionIndex: headwordIndex, + dictionary, + dictionaryOrder: { + index: dictionaryIndex, + priority: dictionaryPriority + }, + expression: term, + reading, + get phoneticTranscriptions() { return termPhoneticTranscriptions; } + }); + } + + return results; + } + + /** + * @param {import('dictionary').PhoneticTranscription[]} phoneticTranscriptions + * @returns {import('anki-templates').PhoneticTranscription[]} + */ + _getTermPhoneticTranscriptionsInner(phoneticTranscriptions) { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const self = this; + const results = []; + for (const {ipa, tags} of phoneticTranscriptions) { + const cachedTags = this.createCachedValue(this._convertTags.bind(this, tags)); + results.push({ + ipa, + get tags() { return self.getCachedValue(cachedTags); } + }); + } + return results; + } + /** * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry * @returns {import('anki-templates').TermHeadword[]} @@ -592,16 +677,17 @@ export class AnkiNoteDataCreator { /** * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry * @param {number} i - * @returns {import('anki-templates').TermPronunciation[]} + * @returns {import('anki-templates').TermPitchAccent[]} */ _getTermExpressionPitches(dictionaryEntry, i) { // eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; const results = []; - const {headwords, pronunciations} = dictionaryEntry; - for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pitches} of pronunciations) { + const {headwords, pronunciations: termPronunciations} = dictionaryEntry; + for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations} of termPronunciations) { if (headwordIndex !== i) { continue; } const {term, reading} = headwords[headwordIndex]; + const pitches = DictionaryDataUtil.getPronunciationsOfType(pronunciations, 'pitch-accent'); const cachedPitches = this.createCachedValue(this._getTermPitchesInner.bind(this, pitches)); results.push({ index: results.length, diff --git a/ext/js/dictionary/dictionary-data-util.js b/ext/js/dictionary/dictionary-data-util.js index a54b043bbf..50ae4b1194 100644 --- a/ext/js/dictionary/dictionary-data-util.js +++ b/ext/js/dictionary/dictionary-data-util.js @@ -135,7 +135,7 @@ export class DictionaryDataUtil { * @returns {import('dictionary-data-util').DictionaryGroupedPronunciations[]} */ static getGroupedPronunciations(dictionaryEntry) { - const {headwords, pronunciations} = dictionaryEntry; + const {headwords, pronunciations: termPronunciations} = dictionaryEntry; const allTerms = new Set(); const allReadings = new Set(); @@ -146,23 +146,20 @@ export class DictionaryDataUtil { /** @type {Map} */ const groupedPronunciationsMap = new Map(); - for (const {headwordIndex, dictionary, pitches} of pronunciations) { + for (const {headwordIndex, dictionary, pronunciations} of termPronunciations) { const {term, reading} = headwords[headwordIndex]; let dictionaryGroupedPronunciationList = groupedPronunciationsMap.get(dictionary); if (typeof dictionaryGroupedPronunciationList === 'undefined') { dictionaryGroupedPronunciationList = []; groupedPronunciationsMap.set(dictionary, dictionaryGroupedPronunciationList); } - for (const {position, nasalPositions, devoicePositions, tags} of pitches) { - let groupedPronunciation = this._findExistingGroupedPronunciation(reading, position, nasalPositions, devoicePositions, tags, dictionaryGroupedPronunciationList); + for (const pronunciation of pronunciations) { + let groupedPronunciation = this._findExistingGroupedPronunciation(reading, pronunciation, dictionaryGroupedPronunciationList); if (groupedPronunciation === null) { groupedPronunciation = { + pronunciation, terms: new Set(), - reading, - position, - nasalPositions, - devoicePositions, - tags + reading }; dictionaryGroupedPronunciationList.push(groupedPronunciation); } @@ -177,28 +174,43 @@ export class DictionaryDataUtil { /** @type {import('dictionary-data-util').GroupedPronunciation[]} */ const pronunciations2 = []; for (const groupedPronunciation of dictionaryGroupedPronunciationList) { - const {terms, reading, position, nasalPositions, devoicePositions, tags} = groupedPronunciation; + const {pronunciation, terms, reading} = groupedPronunciation; const exclusiveTerms = !this._areSetsEqual(terms, allTerms) ? this._getSetIntersection(terms, allTerms) : []; const exclusiveReadings = []; if (multipleReadings) { exclusiveReadings.push(reading); } pronunciations2.push({ + pronunciation, terms: [...terms], reading, - position, - nasalPositions, - devoicePositions, - tags, exclusiveTerms, exclusiveReadings }); } + results2.push({dictionary, pronunciations: pronunciations2}); } return results2; } + /** + * @template {import('dictionary').PronunciationType} T + * @param {import('dictionary').Pronunciation[]} pronunciations + * @param {T} type + * @returns {import('dictionary').PronunciationGeneric[]} + */ + static getPronunciationsOfType(pronunciations, type) { + /** @type {import('dictionary').PronunciationGeneric[]} */ + const results = []; + for (const pronunciation of pronunciations) { + if (pronunciation.type !== type) { continue; } + // This is type safe, but for some reason the cast is needed. + results.push(/** @type {import('dictionary').PronunciationGeneric} */ (pronunciation)); + } + return results; + } + /** * @param {import('dictionary').Tag[]|import('anki-templates').Tag[]} termTags * @returns {import('dictionary-data-util').TermFrequencyType} @@ -288,26 +300,49 @@ export class DictionaryDataUtil { /** * @param {string} reading - * @param {number} position - * @param {number[]} nasalPositions - * @param {number[]} devoicePositions - * @param {import('dictionary').Tag[]} tags + * @param {import('dictionary').Pronunciation} pronunciation * @param {import('dictionary-data-util').GroupedPronunciationInternal[]} groupedPronunciationList * @returns {?import('dictionary-data-util').GroupedPronunciationInternal} */ - static _findExistingGroupedPronunciation(reading, position, nasalPositions, devoicePositions, tags, groupedPronunciationList) { - for (const pitchInfo of groupedPronunciationList) { - if ( - pitchInfo.reading === reading && - pitchInfo.position === position && - this._areArraysEqual(pitchInfo.nasalPositions, nasalPositions) && - this._areArraysEqual(pitchInfo.devoicePositions, devoicePositions) && - this._areTagListsEqual(pitchInfo.tags, tags) - ) { - return pitchInfo; + static _findExistingGroupedPronunciation(reading, pronunciation, groupedPronunciationList) { + const existingGroupedPronunciation = groupedPronunciationList.find((groupedPronunciation) => { + return groupedPronunciation.reading === reading && this._arePronunciationsEquivalent(groupedPronunciation, pronunciation); + }); + + return existingGroupedPronunciation || null; + } + + /** + * @param {import('dictionary-data-util').GroupedPronunciationInternal} groupedPronunciation + * @param {import('dictionary').Pronunciation} pronunciation2 + * @returns {boolean} + */ + static _arePronunciationsEquivalent({pronunciation: pronunciation1}, pronunciation2) { + if ( + pronunciation1.type !== pronunciation2.type || + !this._areTagListsEqual(pronunciation1.tags, pronunciation2.tags) + ) { + return false; + } + switch (pronunciation1.type) { + case 'pitch-accent': + { + // This cast is valid based on the type check at the start of the function. + const pitchAccent2 = /** @type {import('dictionary').PitchAccent} */ (pronunciation2); + return ( + pronunciation1.position === pitchAccent2.position && + this._areArraysEqual(pronunciation1.nasalPositions, pitchAccent2.nasalPositions) && + this._areArraysEqual(pronunciation1.devoicePositions, pitchAccent2.devoicePositions) + ); + } + case 'phonetic-transcription': + { + // This cast is valid based on the type check at the start of the function. + const phoneticTranscription2 = /** @type {import('dictionary').PhoneticTranscription} */ (pronunciation2); + return pronunciation1.ipa === phoneticTranscription2.ipa; } } - return null; + return true; } /** diff --git a/ext/js/dictionary/dictionary-database.js b/ext/js/dictionary/dictionary-database.js index 45c5c6fdb9..02db6322a0 100644 --- a/ext/js/dictionary/dictionary-database.js +++ b/ext/js/dictionary/dictionary-database.js @@ -627,6 +627,8 @@ export class DictionaryDatabase { return {index, term, mode, data, dictionary}; case 'pitch': return {index, term, mode, data, dictionary}; + case 'ipa': + return {index, term, mode, data, dictionary}; default: throw new Error(`Unknown mode: ${mode}`); } diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js index b91d0ce917..3a2a562115 100644 --- a/ext/js/display/display-generator.js +++ b/ext/js/display/display-generator.js @@ -626,7 +626,7 @@ export class DisplayGenerator { n1.appendChild(tag); let hasTags = false; - for (const {tags} of pronunciations) { + for (const {pronunciation: {tags}} of pronunciations) { if (tags.length > 0) { hasTags = true; break; @@ -645,8 +645,52 @@ export class DisplayGenerator { * @returns {HTMLElement} */ _createPronunciation(details) { + const {pronunciation} = details; + switch (pronunciation.type) { + case 'pitch-accent': + return this._createPronunciationPitchAccent(pronunciation, details); + case 'phonetic-transcription': + return this._createPronunciationPhoneticTranscription(pronunciation, details); + } + } + + + /** + * @param {import('dictionary').PhoneticTranscription} pronunciation + * @param {import('dictionary-data-util').GroupedPronunciation} details + * @returns {HTMLElement} + */ + _createPronunciationPhoneticTranscription(pronunciation, details) { + const {ipa, tags} = pronunciation; + const {exclusiveTerms, exclusiveReadings} = details; + + const node = this._instantiate('pronunciation'); + + node.dataset.tagCount = `${tags.length}`; + + let n = this._querySelector(node, '.pronunciation-tag-list'); + this._appendMultiple(n, this._createTag.bind(this), tags); + + n = this._querySelector(node, '.pronunciation-disambiguation-list'); + this._createPronunciationDisambiguations(n, exclusiveTerms, exclusiveReadings); + + n = this._querySelector(node, '.pronunciation-text-container'); + + this._setTextContent(n, ipa); + + return node; + } + + /** + * @param {import('dictionary').PitchAccent} pitchAccent + * @param {import('dictionary-data-util').GroupedPronunciation} details + * @returns {HTMLElement} + */ + _createPronunciationPitchAccent(pitchAccent, details) { const jp = this._japaneseUtil; - const {reading, position, nasalPositions, devoicePositions, tags, exclusiveTerms, exclusiveReadings} = details; + + const {position, nasalPositions, devoicePositions, tags} = pitchAccent; + const {reading, exclusiveTerms, exclusiveReadings} = details; const morae = jp.getKanaMorae(reading); const node = this._instantiate('pronunciation'); @@ -666,6 +710,7 @@ export class DisplayGenerator { n.appendChild(this._pronunciationGenerator.createPronunciationDownstepPosition(position)); n = this._querySelector(node, '.pronunciation-text-container'); + n.lang = 'ja'; n.appendChild(this._pronunciationGenerator.createPronunciationText(morae, position, nasalPositions, devoicePositions)); @@ -954,20 +999,21 @@ export class DisplayGenerator { /** * @param {string} reading - * @param {import('dictionary').TermPronunciation[]} pronunciations + * @param {import('dictionary').TermPronunciation[]} termPronunciations * @param {string[]} wordClasses * @param {number} headwordIndex * @returns {?string} */ - _getPronunciationCategories(reading, pronunciations, wordClasses, headwordIndex) { - if (pronunciations.length === 0) { return null; } + _getPronunciationCategories(reading, termPronunciations, wordClasses, headwordIndex) { + if (termPronunciations.length === 0) { return null; } const isVerbOrAdjective = DictionaryDataUtil.isNonNounVerbOrAdjective(wordClasses); /** @type {Set} */ const categories = new Set(); - for (const pronunciation of pronunciations) { - if (pronunciation.headwordIndex !== headwordIndex) { continue; } - for (const {position} of pronunciation.pitches) { - const category = this._japaneseUtil.getPitchCategory(reading, position, isVerbOrAdjective); + for (const termPronunciation of termPronunciations) { + if (termPronunciation.headwordIndex !== headwordIndex) { continue; } + for (const pronunciation of termPronunciation.pronunciations) { + if (pronunciation.type !== 'pitch-accent') { continue; } + const category = this._japaneseUtil.getPitchCategory(reading, pronunciation.position, isVerbOrAdjective); if (category !== null) { categories.add(category); } diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index 482afd56c1..6767d201d2 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -18,7 +18,8 @@ import * as wanakana from '../../lib/wanakana.js'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; -import {EventListenerCollection, invokeMessageHandler} from '../core.js'; +import {EventListenerCollection} from '../core.js'; +import {createApiMap, invokeApiMapHandler} from '../core/api-map.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; import {yomitan} from '../yomitan.js'; @@ -75,8 +76,12 @@ export class SearchDisplayController { getText: yomitan.api.clipboardGet.bind(yomitan.api) } }); - /** @type {import('core').MessageHandlerMap} */ - this._messageHandlers = new Map(); + /** @type {import('application').ApiMap} */ + this._apiMap = createApiMap([ + ['searchDisplayControllerGetMode', this._onMessageGetMode.bind(this)], + ['searchDisplayControllerSetMode', this._onMessageSetMode.bind(this)], + ['searchDisplayControllerUpdateSearchQuery', this._onExternalSearchUpdate.bind(this)] + ]); } /** */ @@ -94,13 +99,6 @@ export class SearchDisplayController { this._display.hotkeyHandler.registerActions([ ['focusSearchBox', this._onActionFocusSearchBox.bind(this)] ]); - /* eslint-disable no-multi-spaces */ - this._registerMessageHandlers([ - ['SearchDisplayController.getMode', this._onMessageGetMode.bind(this)], - ['SearchDisplayController.setMode', this._onMessageSetMode.bind(this)], - ['SearchDisplayController.updateSearchQuery', this._onExternalSearchUpdate.bind(this)] - ]); - /* eslint-enable no-multi-spaces */ this._updateClipboardMonitorEnabled(); @@ -140,32 +138,21 @@ export class SearchDisplayController { // Messages - /** - * @param {{mode: import('display').SearchMode}} details - */ + /** @type {import('application').ApiHandler<'searchDisplayControllerSetMode'>} */ _onMessageSetMode({mode}) { this.setMode(mode); } - /** - * @returns {import('display').SearchMode} - */ + /** @type {import('application').ApiHandler<'searchDisplayControllerGetMode'>} */ _onMessageGetMode() { return this._searchPersistentStateController.mode; } // Private - /** - * @param {{action: string, params?: import('core').SerializableObject}} message - * @param {chrome.runtime.MessageSender} sender - * @param {(response?: unknown) => void} callback - * @returns {boolean} - */ - _onMessage({action, params}, sender, callback) { - const messageHandler = this._messageHandlers.get(action); - if (typeof messageHandler === 'undefined') { return false; } - return invokeMessageHandler(messageHandler, params, callback, sender); + /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ + _onMessage({action, params}, _sender, callback) { + return invokeApiMapHandler(this._apiMap, action, params, [], callback); } /** @@ -284,9 +271,7 @@ export class SearchDisplayController { this._clipboardMonitor.setPreviousText(selection !== null ? selection.toString().trim() : ''); } - /** - * @param {{text: string, animate?: boolean}} details - */ + /** @type {import('application').ApiHandler<'searchDisplayControllerUpdateSearchQuery'>} */ _onExternalSearchUpdate({text, animate = true}) { const options = this._display.getOptions(); if (options === null) { return; } @@ -548,15 +533,6 @@ export class SearchDisplayController { } } - /** - * @param {import('core').MessageHandlerMapInit} handlers - */ - _registerMessageHandlers(handlers) { - for (const [name, handlerInfo] of handlers) { - this._messageHandlers.set(name, handlerInfo); - } - } - /** * @param {?Element} element * @returns {boolean} diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 179ce9a717..36ed8b4358 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -982,7 +982,7 @@ export class Translator { case 'pitch': { if (data.reading !== reading) { continue; } - /** @type {import('dictionary').TermPitch[]} */ + /** @type {import('dictionary').PitchAccent[]} */ const pitches = []; for (const {position, tags, nasal, devoice} of data.pitches) { /** @type {import('dictionary').Tag[]} */ @@ -992,7 +992,13 @@ export class Translator { } const nasalPositions = this._toNumberArray(nasal); const devoicePositions = this._toNumberArray(devoice); - pitches.push({position, nasalPositions, devoicePositions, tags: tags2}); + pitches.push({ + type: 'pitch-accent', + position, + nasalPositions, + devoicePositions, + tags: tags2 + }); } for (const {pronunciations, headwordIndex} of targets) { pronunciations.push(this._createTermPronunciation( @@ -1006,6 +1012,34 @@ export class Translator { } } break; + case 'ipa': + { + if (data.reading !== reading) { continue; } + /** @type {import('dictionary').PhoneticTranscription[]} */ + const phoneticTranscriptions = []; + for (const {ipa, tags} of data.transcriptions) { + /** @type {import('dictionary').Tag[]} */ + const tags2 = []; + if (Array.isArray(tags)) { + tagAggregator.addTags(tags2, dictionary, tags); + } + phoneticTranscriptions.push({ + type: 'phonetic-transcription', + ipa, + tags: tags2 + }); + } + for (const {pronunciations, headwordIndex} of targets) { + pronunciations.push(this._createTermPronunciation( + pronunciations.length, + headwordIndex, + dictionary, + dictionaryIndex, + dictionaryPriority, + phoneticTranscriptions + )); + } + } } } } @@ -1359,11 +1393,11 @@ export class Translator { * @param {string} dictionary * @param {number} dictionaryIndex * @param {number} dictionaryPriority - * @param {import('dictionary').TermPitch[]} pitches + * @param {import('dictionary').Pronunciation[]} pronunciations * @returns {import('dictionary').TermPronunciation} */ - _createTermPronunciation(index, headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pitches) { - return {index, headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pitches}; + _createTermPronunciation(index, headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations) { + return {index, headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations}; } /** diff --git a/ext/js/pages/settings/anki-controller.js b/ext/js/pages/settings/anki-controller.js index 6f357680d0..aea94b652b 100644 --- a/ext/js/pages/settings/anki-controller.js +++ b/ext/js/pages/settings/anki-controller.js @@ -145,6 +145,7 @@ export class AnkiController { 'pitch-accents', 'pitch-accent-graphs', 'pitch-accent-positions', + 'phonetic-transcriptions', 'reading', 'screenshot', 'search-query', diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js index 57725bcbb1..158102393c 100644 --- a/ext/js/templates/sandbox/anki-template-renderer.js +++ b/ext/js/templates/sandbox/anki-template-renderer.js @@ -543,12 +543,13 @@ export class AnkiTemplateRenderer { const [data] = /** @type {[data: import('anki-templates').NoteData]} */ (args); const {dictionaryEntry} = data; if (dictionaryEntry.type !== 'term') { return []; } - const {pronunciations, headwords} = dictionaryEntry; + const {pronunciations: termPronunciations, headwords} = dictionaryEntry; /** @type {Set} */ const categories = new Set(); - for (const {headwordIndex, pitches} of pronunciations) { + for (const {headwordIndex, pronunciations} of termPronunciations) { const {reading, wordClasses} = headwords[headwordIndex]; const isVerbOrAdjective = DictionaryDataUtil.isNonNounVerbOrAdjective(wordClasses); + const pitches = DictionaryDataUtil.getPronunciationsOfType(pronunciations, 'pitch-accent'); for (const {position} of pitches) { const category = this._japaneseUtil.getPitchCategory(reading, position, isVerbOrAdjective); if (category !== null) { diff --git a/ext/js/yomitan.js b/ext/js/yomitan.js index 2fbe99cc33..621e9cf029 100644 --- a/ext/js/yomitan.js +++ b/ext/js/yomitan.js @@ -18,11 +18,14 @@ import {API} from './comm/api.js'; import {CrossFrameAPI} from './comm/cross-frame-api.js'; -import {EventDispatcher, deferPromise, invokeMessageHandler, log} from './core.js'; +import {EventDispatcher, deferPromise, log} from './core.js'; +import {createApiMap, invokeApiMapHandler} from './core/api-map.js'; import {ExtensionError} from './core/extension-error.js'; -// Set up chrome alias if it's not available (Edge Legacy) -if ((() => { +/** + * @returns {boolean} + */ +function checkChromeNotAvailable() { let hasChrome = false; let hasBrowser = false; try { @@ -36,7 +39,10 @@ if ((() => { // NOP } return (hasBrowser && !hasChrome); -})()) { +} + +// Set up chrome alias if it's not available (Edge Legacy) +if (checkChromeNotAvailable()) { // @ts-expect-error - objects should have roughly the same interface // eslint-disable-next-line no-global-assign chrome = browser; @@ -90,15 +96,15 @@ export class Yomitan extends EventDispatcher { this._isBackendReadyPromiseResolve = resolve; /* eslint-disable no-multi-spaces */ - /** @type {import('core').MessageHandlerMap} */ - this._messageHandlers = new Map(/** @type {import('core').MessageHandlerMapInit} */ ([ - ['Yomitan.isReady', this._onMessageIsReady.bind(this)], - ['Yomitan.backendReady', this._onMessageBackendReady.bind(this)], - ['Yomitan.getUrl', this._onMessageGetUrl.bind(this)], - ['Yomitan.optionsUpdated', this._onMessageOptionsUpdated.bind(this)], - ['Yomitan.databaseUpdated', this._onMessageDatabaseUpdated.bind(this)], - ['Yomitan.zoomChanged', this._onMessageZoomChanged.bind(this)] - ])); + /** @type {import('application').ApiMap} */ + this._apiMap = createApiMap([ + ['applicationIsReady', this._onMessageIsReady.bind(this)], + ['applicationBackendReady', this._onMessageBackendReady.bind(this)], + ['applicationGetUrl', this._onMessageGetUrl.bind(this)], + ['applicationOptionsUpdated', this._onMessageOptionsUpdated.bind(this)], + ['applicationDatabaseUpdated', this._onMessageDatabaseUpdated.bind(this)], + ['applicationZoomChanged', this._onMessageZoomChanged.bind(this)] + ]); /* eslint-enable no-multi-spaces */ } @@ -166,7 +172,7 @@ export class Yomitan extends EventDispatcher { */ ready() { this._isReady = true; - this.sendMessage({action: 'yomitanReady'}); + this.sendMessage({action: 'applicationReady'}); } /** @@ -178,6 +184,7 @@ export class Yomitan extends EventDispatcher { return this._extensionUrlBase !== null && url.startsWith(this._extensionUrlBase); } + // TODO : this function needs type safety /** * Runs `chrome.runtime.sendMessage()` with additional exception handling events. * @param {import('extension').ChromeRuntimeSendMessageArgs} args The arguments to be passed to `chrome.runtime.sendMessage()`. @@ -216,55 +223,41 @@ export class Yomitan extends EventDispatcher { return location.href; } - /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ - _onMessage({action, params}, sender, callback) { - const messageHandler = this._messageHandlers.get(action); - if (typeof messageHandler === 'undefined') { return false; } - return invokeMessageHandler(messageHandler, params, callback, sender); + /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ + _onMessage({action, params}, _sender, callback) { + return invokeApiMapHandler(this._apiMap, action, params, [], callback); } - /** - * @returns {boolean} - */ + /** @type {import('application').ApiHandler<'applicationIsReady'>} */ _onMessageIsReady() { return this._isReady; } - /** - * @returns {void} - */ + /** @type {import('application').ApiHandler<'applicationBackendReady'>} */ _onMessageBackendReady() { if (this._isBackendReadyPromiseResolve === null) { return; } this._isBackendReadyPromiseResolve(); this._isBackendReadyPromiseResolve = null; } - /** - * @returns {{url: string}} - */ + /** @type {import('application').ApiHandler<'applicationGetUrl'>} */ _onMessageGetUrl() { return {url: this._getUrl()}; } - /** - * @param {{source: string}} params - */ + /** @type {import('application').ApiHandler<'applicationOptionsUpdated'>} */ _onMessageOptionsUpdated({source}) { if (source !== 'background') { this.trigger('optionsUpdated', {source}); } } - /** - * @param {{type: string, cause: string}} params - */ + /** @type {import('application').ApiHandler<'applicationDatabaseUpdated'>} */ _onMessageDatabaseUpdated({type, cause}) { this.trigger('databaseUpdated', {type, cause}); } - /** - * @param {{oldZoomFactor: number, newZoomFactor: number}} params - */ + /** @type {import('application').ApiHandler<'applicationZoomChanged'>} */ _onMessageZoomChanged({oldZoomFactor, newZoomFactor}) { this.trigger('zoomChanged', {oldZoomFactor, newZoomFactor}); } diff --git a/package-lock.json b/package-lock.json index d7c62cabb0..c8a5bad528 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,6 +54,9 @@ "ts-json-schema-generator": "^1.5.0", "typescript": "5.3.3", "vitest": "^0.34.6" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@aashutoshrathi/word-wrap": { diff --git a/test/data/anki-note-builder-test-results.json b/test/data/anki-note-builder-test-results.json index 49542e39db..86bffc6a0e 100644 --- a/test/data/anki-note-builder-test-results.json +++ b/test/data/anki-note-builder-test-results.json @@ -79,6 +79,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "だ", "screenshot": "", "search-query": "fullQuery", @@ -109,6 +110,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ダース", "screenshot": "", "search-query": "fullQuery", @@ -144,6 +146,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -174,6 +177,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -204,6 +208,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -234,6 +239,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -264,6 +270,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "だ", "screenshot": "", "search-query": "fullQuery", @@ -294,6 +301,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ダース", "screenshot": "", "search-query": "fullQuery", @@ -329,6 +337,7 @@ "pitch-accents": "
", "pitch-accent-graphs": "
", "pitch-accent-positions": "
  1. [0]
  2. [3]
", + "phonetic-transcriptions": "
    ", "reading": "うちこむ", "screenshot": "", "search-query": "fullQuery", @@ -359,6 +368,7 @@ "pitch-accents": "
    ", "pitch-accent-graphs": "
    ", "pitch-accent-positions": "
    1. [0]
    2. [3]
    ", + "phonetic-transcriptions": "
      ", "reading": "ぶちこむ", "screenshot": "", "search-query": "fullQuery", @@ -389,6 +399,7 @@ "pitch-accents": "
      ", "pitch-accent-graphs": "
      ", "pitch-accent-positions": "
      1. [0]
      2. [3]
      ", + "phonetic-transcriptions": "
        ", "reading": "うちこむ", "screenshot": "", "search-query": "fullQuery", @@ -419,6 +430,7 @@ "pitch-accents": "
        ", "pitch-accent-graphs": "
        ", "pitch-accent-positions": "
        1. [0]
        2. [3]
        ", + "phonetic-transcriptions": "
          ", "reading": "ぶちこむ", "screenshot": "", "search-query": "fullQuery", @@ -449,6 +461,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -479,6 +492,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -509,6 +523,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -539,6 +554,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -569,6 +585,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "だ", "screenshot": "", "search-query": "fullQuery", @@ -599,6 +616,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ダース", "screenshot": "", "search-query": "fullQuery", @@ -634,6 +652,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "がぞう", "screenshot": "", "search-query": "fullQuery", @@ -669,6 +688,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "だ", "screenshot": "", "search-query": "fullQuery", @@ -704,6 +724,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ダース", "screenshot": "", "search-query": "fullQuery", @@ -739,6 +760,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -769,6 +791,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -804,6 +827,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -834,6 +858,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -869,6 +894,7 @@ "pitch-accents": "
          ", "pitch-accent-graphs": "
          ", "pitch-accent-positions": "
          1. [0]
          2. [3]
          ", + "phonetic-transcriptions": "
            ", "reading": "うちこむ", "screenshot": "", "search-query": "fullQuery", @@ -899,6 +925,7 @@ "pitch-accents": "
            ", "pitch-accent-graphs": "
            ", "pitch-accent-positions": "
            1. [0]
            2. [3]
            ", + "phonetic-transcriptions": "
              ", "reading": "うちこむ", "screenshot": "", "search-query": "fullQuery", @@ -929,6 +956,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -959,6 +987,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -994,6 +1023,7 @@ "pitch-accents": "
              ", "pitch-accent-graphs": "
              ", "pitch-accent-positions": "
              1. [0]
              2. [3]
              ", + "phonetic-transcriptions": "
                ", "reading": "ぶちこむ", "screenshot": "", "search-query": "fullQuery", @@ -1024,6 +1054,7 @@ "pitch-accents": "
                ", "pitch-accent-graphs": "
                ", "pitch-accent-positions": "
                1. [0]
                2. [3]
                ", + "phonetic-transcriptions": "
                  ", "reading": "ぶちこむ", "screenshot": "", "search-query": "fullQuery", @@ -1054,6 +1085,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -1084,6 +1116,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -1119,6 +1152,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "がぞう", "screenshot": "", "search-query": "fullQuery", @@ -1166,6 +1200,7 @@ "pitch-accents": "
                  ", "pitch-accent-graphs": "
                  ", "pitch-accent-positions": "
                  1. [0]
                  2. [3]
                  ", + "phonetic-transcriptions": "
                    ", "reading": "うちこむ", "screenshot": "", "search-query": "fullQuery", @@ -1196,6 +1231,7 @@ "pitch-accents": "
                    ", "pitch-accent-graphs": "
                    ", "pitch-accent-positions": "
                    1. [0]
                    2. [3]
                    ", + "phonetic-transcriptions": "
                      ", "reading": "ぶちこむ", "screenshot": "", "search-query": "fullQuery", @@ -1226,6 +1262,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -1256,6 +1293,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -1286,6 +1324,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "だ", "screenshot": "", "search-query": "fullQuery", @@ -1316,6 +1355,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ダース", "screenshot": "", "search-query": "fullQuery", @@ -1351,6 +1391,7 @@ "pitch-accents": "
                      1. (うちこむ only)
                      2. (うちこむ only)
                      3. (ぶちこむ only)
                      4. (ぶちこむ only)
                      ", "pitch-accent-graphs": "
                      1. (うちこむ only)
                      2. (うちこむ only)
                      3. (ぶちこむ only)
                      4. (ぶちこむ only)
                      ", "pitch-accent-positions": "
                      1. (うちこむ only) [0]
                      2. (うちこむ only) [3]
                      3. (ぶちこむ only) [0]
                      4. (ぶちこむ only) [3]
                      ", + "phonetic-transcriptions": "
                        ", "reading": "うちこむ、ぶちこむ", "screenshot": "", "search-query": "fullQuery", @@ -1381,6 +1422,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ、ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -1411,6 +1453,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "だ", "screenshot": "", "search-query": "fullQuery", @@ -1441,6 +1484,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ダース", "screenshot": "", "search-query": "fullQuery", @@ -1476,6 +1520,7 @@ "pitch-accents": "
                        ", "pitch-accent-graphs": "
                        ", "pitch-accent-positions": "
                        1. [0]
                        2. [3]
                        ", + "phonetic-transcriptions": "
                          ", "reading": "うちこむ", "screenshot": "", "search-query": "fullQuery", @@ -1506,6 +1551,7 @@ "pitch-accents": "
                          ", "pitch-accent-graphs": "
                          ", "pitch-accent-positions": "
                          1. [0]
                          2. [3]
                          ", + "phonetic-transcriptions": "
                            ", "reading": "ぶちこむ", "screenshot": "", "search-query": "fullQuery", @@ -1536,6 +1582,7 @@ "pitch-accents": "
                            ", "pitch-accent-graphs": "
                            ", "pitch-accent-positions": "
                            1. [0]
                            2. [3]
                            ", + "phonetic-transcriptions": "
                              ", "reading": "うちこむ", "screenshot": "", "search-query": "fullQuery", @@ -1566,6 +1613,7 @@ "pitch-accents": "
                              ", "pitch-accent-graphs": "
                              ", "pitch-accent-positions": "
                              1. [0]
                              2. [3]
                              ", + "phonetic-transcriptions": "
                                ", "reading": "ぶちこむ", "screenshot": "", "search-query": "fullQuery", @@ -1596,6 +1644,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -1626,6 +1675,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -1656,6 +1706,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -1686,6 +1737,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -1716,6 +1768,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "だ", "screenshot": "", "search-query": "fullQuery", @@ -1746,6 +1799,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ダース", "screenshot": "", "search-query": "fullQuery", @@ -1781,6 +1835,7 @@ "pitch-accents": "
                                ", "pitch-accent-graphs": "
                                ", "pitch-accent-positions": "
                                1. [0]
                                2. [3]
                                ", + "phonetic-transcriptions": "
                                  ", "reading": "うちこむ", "screenshot": "", "search-query": "fullQuery", @@ -1811,6 +1866,7 @@ "pitch-accents": "
                                  ", "pitch-accent-graphs": "
                                  ", "pitch-accent-positions": "
                                  1. [0]
                                  2. [3]
                                  ", + "phonetic-transcriptions": "
                                    ", "reading": "ぶちこむ", "screenshot": "", "search-query": "fullQuery", @@ -1841,6 +1897,7 @@ "pitch-accents": "
                                    ", "pitch-accent-graphs": "
                                    ", "pitch-accent-positions": "
                                    1. [0]
                                    2. [3]
                                    ", + "phonetic-transcriptions": "
                                      ", "reading": "うちこむ", "screenshot": "", "search-query": "fullQuery", @@ -1871,6 +1928,7 @@ "pitch-accents": "
                                      ", "pitch-accent-graphs": "
                                      ", "pitch-accent-positions": "
                                      1. [0]
                                      2. [3]
                                      ", + "phonetic-transcriptions": "
                                        ", "reading": "ぶちこむ", "screenshot": "", "search-query": "fullQuery", @@ -1901,6 +1959,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -1931,6 +1990,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -1961,6 +2021,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -1991,6 +2052,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -2021,6 +2083,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "だ", "screenshot": "", "search-query": "fullQuery", @@ -2051,6 +2114,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ダース", "screenshot": "", "search-query": "fullQuery", @@ -2086,6 +2150,7 @@ "pitch-accents": "
                                        ", "pitch-accent-graphs": "
                                        ", "pitch-accent-positions": "
                                        1. [0]
                                        2. [3]
                                        ", + "phonetic-transcriptions": "
                                          ", "reading": "うちこむ", "screenshot": "", "search-query": "fullQuery", @@ -2116,6 +2181,7 @@ "pitch-accents": "
                                          ", "pitch-accent-graphs": "
                                          ", "pitch-accent-positions": "
                                          1. [0]
                                          2. [3]
                                          ", + "phonetic-transcriptions": "
                                            ", "reading": "ぶちこむ", "screenshot": "", "search-query": "fullQuery", @@ -2146,6 +2212,7 @@ "pitch-accents": "
                                            ", "pitch-accent-graphs": "
                                            ", "pitch-accent-positions": "
                                            1. [0]
                                            2. [3]
                                            ", + "phonetic-transcriptions": "
                                              ", "reading": "うちこむ", "screenshot": "", "search-query": "fullQuery", @@ -2176,6 +2243,7 @@ "pitch-accents": "
                                              ", "pitch-accent-graphs": "
                                              ", "pitch-accent-positions": "
                                              1. [0]
                                              2. [3]
                                              ", + "phonetic-transcriptions": "
                                                ", "reading": "ぶちこむ", "screenshot": "", "search-query": "fullQuery", @@ -2206,6 +2274,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -2236,6 +2305,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -2266,6 +2336,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ", "screenshot": "", "search-query": "fullQuery", @@ -2296,6 +2367,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -2326,6 +2398,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "だ", "screenshot": "", "search-query": "fullQuery", @@ -2356,6 +2429,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "ダース", "screenshot": "", "search-query": "fullQuery", @@ -2391,6 +2465,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "よむ", "screenshot": "", "search-query": "fullQuery", @@ -2426,6 +2501,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "つよみ", "screenshot": "", "search-query": "fullQuery", @@ -2461,6 +2537,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "よむ", "screenshot": "", "search-query": "fullQuery", @@ -2496,6 +2573,7 @@ "pitch-accents": "
                                                1. (うちこむ only)
                                                2. (うちこむ only)
                                                3. (ぶちこむ only)
                                                4. (ぶちこむ only)
                                                ", "pitch-accent-graphs": "
                                                1. (うちこむ only)
                                                2. (うちこむ only)
                                                3. (ぶちこむ only)
                                                4. (ぶちこむ only)
                                                ", "pitch-accent-positions": "
                                                1. (うちこむ only) [0]
                                                2. (うちこむ only) [3]
                                                3. (ぶちこむ only) [0]
                                                4. (ぶちこむ only) [3]
                                                ", + "phonetic-transcriptions": "
                                                  ", "reading": "うちこむ、ぶちこむ", "screenshot": "", "search-query": "fullQuery", @@ -2526,6 +2604,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "うつ、ぶつ", "screenshot": "", "search-query": "fullQuery", @@ -2561,6 +2640,7 @@ "pitch-accents": "
                                                  ", "pitch-accent-graphs": "
                                                  ", "pitch-accent-positions": "
                                                  1. [2]
                                                  2. [2]
                                                  3. [0]
                                                  ", + "phonetic-transcriptions": "
                                                    ", "reading": "おてまえ", "screenshot": "", "search-query": "fullQuery", @@ -2596,6 +2676,7 @@ "pitch-accents": "", "pitch-accent-graphs": "", "pitch-accent-positions": "[3]", + "phonetic-transcriptions": "
                                                      ", "reading": "ばんごう", "screenshot": "", "search-query": "fullQuery", @@ -2631,6 +2712,7 @@ "pitch-accents": "", "pitch-accent-graphs": "", "pitch-accent-positions": "[0]", + "phonetic-transcriptions": "
                                                        ", "reading": "ちゅうごし", "screenshot": "", "search-query": "fullQuery", @@ -2666,6 +2748,7 @@ "pitch-accents": "", "pitch-accent-graphs": "", "pitch-accent-positions": "[0]", + "phonetic-transcriptions": "
                                                          ", "reading": "しょぎょう", "screenshot": "", "search-query": "fullQuery", @@ -2701,6 +2784,7 @@ "pitch-accents": "", "pitch-accent-graphs": "", "pitch-accent-positions": "[4]", + "phonetic-transcriptions": "
                                                            ", "reading": "どぼくこうじ", "screenshot": "", "search-query": "fullQuery", @@ -2712,6 +2796,42 @@ } ] }, + { + "name": "Test pronunciations 6 - phonetic transcriptions", + "results": [ + { + "audio": "", + "clipboard-image": "", + "clipboard-text": "", + "cloze-body": "好き", + "cloze-prefix": "cloze-prefix", + "cloze-suffix": "cloze-suffix", + "conjugation": "", + "dictionary": "Test Dictionary 2", + "document-title": "title", + "expression": "好き", + "frequencies": "", + "furigana": "き", + "furigana-plain": "好[す]き", + "glossary": "
                                                            (adj-na, n, Test Dictionary 2) suki definition
                                                            ", + "glossary-brief": "
                                                            suki definition
                                                            ", + "glossary-no-dictionary": "
                                                            (adj-na, n) suki definition
                                                            ", + "part-of-speech": "Unknown", + "pitch-accents": "No pitch accent data", + "pitch-accent-graphs": "No pitch accent data", + "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "
                                                            • (東京) [sɨᵝkʲi]
                                                            ", + "reading": "すき", + "screenshot": "", + "search-query": "fullQuery", + "selection-text": "", + "sentence": "cloze-prefix好きcloze-suffix", + "sentence-furigana": "cloze-prefix好きcloze-suffix", + "tags": "adj-na, n", + "url": "url:" + } + ] + }, { "name": "Structured content test", "results": [ @@ -2736,6 +2856,7 @@ "pitch-accents": "No pitch accent data", "pitch-accent-graphs": "No pitch accent data", "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", "reading": "こうぞう", "screenshot": "", "search-query": "fullQuery", diff --git a/test/data/dictionaries/valid-dictionary1/term_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_bank_1.json index 14f66d951c..ce4290bdff 100644 --- a/test/data/dictionaries/valid-dictionary1/term_bank_1.json +++ b/test/data/dictionaries/valid-dictionary1/term_bank_1.json @@ -18,6 +18,7 @@ ["中腰", "ちゅうごし", "n", "n", 1, ["chuugoshi definition"], 11, ""], ["所業", "しょぎょう", "n", "n", 1, ["shogyouu definition"], 12, ""], ["土木工事", "どぼくこうじ", "n", "n", 1, ["dobokukouji definition"], 13, ""], + ["好き", "すき", "adj-na n", "", 1, ["suki definition"], 14, ""], [ "内容", "ないよう", "n", "n", 35, [ diff --git a/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json index 069ea16b30..562966482f 100644 --- a/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json +++ b/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json @@ -108,5 +108,15 @@ {"position": 4, "devoice": 3} ] } + ], + [ + "好き", + "ipa", + { + "reading": "すき", + "transcriptions": [ + {"ipa": "[sɨᵝkʲi]", "tags": ["東京"]} + ] + } ] ] \ No newline at end of file diff --git a/test/data/translator-test-inputs.json b/test/data/translator-test-inputs.json index 5afb6a6013..ec7f1a1121 100644 --- a/test/data/translator-test-inputs.json +++ b/test/data/translator-test-inputs.json @@ -330,6 +330,13 @@ "text": "土木工事", "options": "default" }, + { + "name": "Test pronunciations 6 - phonetic transcriptions", + "func": "findTerms", + "mode": "split", + "text": "好き", + "options": "default" + }, { "name": "Structured content test", "func": "findTerms", diff --git a/test/data/translator-test-results-note-data1.json b/test/data/translator-test-results-note-data1.json index 34f7c21ab5..1342a63fed 100644 --- a/test/data/translator-test-results-note-data1.json +++ b/test/data/translator-test-results-note-data1.json @@ -151,6 +151,7 @@ "uniqueReadings": [], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -313,6 +314,7 @@ "uniqueReadings": [], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -595,6 +597,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -626,6 +629,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -908,6 +912,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -939,6 +944,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -1213,6 +1219,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -1248,6 +1255,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -1517,6 +1525,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -1552,6 +1561,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -1821,6 +1831,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -1856,6 +1867,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -2125,6 +2137,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -2160,6 +2173,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -2433,6 +2447,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -2464,6 +2479,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -2746,6 +2762,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -2777,6 +2794,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -3103,6 +3121,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "うちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -3176,6 +3208,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -3497,6 +3535,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "ぶちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -3570,6 +3622,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -3891,6 +3949,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "うちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -3964,6 +4036,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -4285,6 +4363,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "ぶちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -4358,6 +4450,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -4629,6 +4727,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -4664,6 +4763,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -4935,6 +5035,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -4970,6 +5071,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -5241,6 +5343,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -5276,6 +5379,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -5547,6 +5651,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -5582,6 +5687,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -5855,6 +5961,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -5886,6 +5993,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -6168,6 +6276,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -6199,6 +6308,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -6320,6 +6430,7 @@ ], "frequencies": [], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -6351,6 +6462,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -6629,6 +6741,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -6660,6 +6773,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -6947,6 +7061,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -6978,6 +7093,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -7252,6 +7368,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -7287,6 +7404,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -7556,6 +7674,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -7591,6 +7710,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -7865,6 +7985,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -7900,6 +8021,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -8169,6 +8291,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -8204,6 +8327,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -8530,6 +8654,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "うちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -8603,6 +8741,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -8924,6 +9068,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "うちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -8997,6 +9155,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -9268,6 +9432,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -9303,6 +9468,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -9574,6 +9740,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -9609,6 +9776,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -9935,6 +10103,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "ぶちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -10008,6 +10190,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -10329,6 +10517,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "ぶちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -10402,6 +10604,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -10673,6 +10881,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -10708,6 +10917,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -10979,6 +11189,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -11014,6 +11225,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -11135,6 +11347,7 @@ ], "frequencies": [], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -11166,6 +11379,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -11545,6 +11759,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "うちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -11600,6 +11828,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -11962,6 +12196,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "ぶちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -12017,6 +12265,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -12329,6 +12583,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -12354,6 +12609,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -12666,6 +12922,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -12691,6 +12948,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -12968,6 +13226,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -12993,6 +13252,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -13279,6 +13539,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -13304,6 +13565,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -13954,6 +14216,32 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "うちこむ", + "phoneticTranscriptions": [] + }, + { + "index": 1, + "expressionIndex": 1, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "ぶちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 2, "url": "url:", "cloze": { @@ -14042,6 +14330,12 @@ } ], "pitchCount": 4, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -14586,6 +14880,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 2, "url": "url:", "cloze": { @@ -14612,6 +14907,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -14882,6 +15178,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -14907,6 +15204,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -15186,6 +15484,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -15211,6 +15510,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -15541,6 +15841,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "うちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -15614,6 +15928,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -15939,6 +16259,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "ぶちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -16012,6 +16346,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -16337,6 +16677,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "うちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -16410,6 +16764,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -16735,6 +17095,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "ぶちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -16808,6 +17182,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -17079,6 +17459,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -17114,6 +17495,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -17385,6 +17767,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -17420,6 +17803,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -17691,6 +18075,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -17726,6 +18111,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -17997,6 +18383,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -18032,6 +18419,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -18305,6 +18693,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -18336,6 +18725,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -18618,6 +19008,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -18649,6 +19040,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -18975,11 +19367,25 @@ ] } ], - "sourceTermExactMatchCount": 1, - "url": "url:", - "cloze": { - "sentence": "", - "prefix": "", + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "うちこむ", + "phoneticTranscriptions": [] + } + ], + "sourceTermExactMatchCount": 1, + "url": "url:", + "cloze": { + "sentence": "", + "prefix": "", "body": "", "suffix": "" }, @@ -19048,6 +19454,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -19369,6 +19781,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "ぶちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -19442,6 +19868,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -19763,6 +20195,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "うちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -19836,6 +20282,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -20157,6 +20609,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "ぶちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -20230,6 +20696,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -20501,6 +20973,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -20536,6 +21009,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -20807,6 +21281,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -20842,6 +21317,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -21113,6 +21589,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -21148,6 +21625,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -21419,6 +21897,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -21454,6 +21933,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -21727,6 +22207,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -21758,6 +22239,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -22040,6 +22522,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -22071,6 +22554,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -22397,6 +22881,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "うちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -22470,6 +22968,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -22791,6 +23295,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "ぶちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -22864,6 +23382,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -23185,6 +23709,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "うちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -23258,6 +23796,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -23579,6 +24123,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "ぶちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -23652,6 +24210,12 @@ } ], "pitchCount": 2, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -23923,6 +24487,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -23958,6 +24523,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -24229,6 +24795,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -24264,6 +24831,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -24535,6 +25103,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -24570,6 +25139,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -24841,6 +25411,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -24876,6 +25447,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -25149,6 +25721,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -25180,6 +25753,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -25462,6 +26036,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -25493,6 +26068,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -25610,6 +26186,7 @@ ], "frequencies": [], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -25645,6 +26222,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -25760,6 +26338,7 @@ ], "frequencies": [], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -25795,6 +26374,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -25912,6 +26492,7 @@ ], "frequencies": [], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -25947,6 +26528,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -26597,6 +27179,32 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "うちこむ", + "phoneticTranscriptions": [] + }, + { + "index": 1, + "expressionIndex": 1, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "打ち込む", + "reading": "ぶちこむ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -26685,6 +27293,12 @@ } ], "pitchCount": 4, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -27229,6 +27843,7 @@ } ], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 0, "url": "url:", "cloze": { @@ -27255,6 +27870,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", @@ -27444,6 +28060,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "お手前", + "reading": "おてまえ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -27563,6 +28193,12 @@ } ], "pitchCount": 3, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -27672,6 +28308,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "番号", + "reading": "ばんごう", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -27723,6 +28373,12 @@ } ], "pitchCount": 1, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -27832,6 +28488,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "中腰", + "reading": "ちゅうごし", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -27883,6 +28553,12 @@ } ], "pitchCount": 1, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -27992,6 +28668,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "所業", + "reading": "しょぎょう", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -28043,6 +28733,12 @@ } ], "pitchCount": 1, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -28152,6 +28848,20 @@ ] } ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "土木工事", + "reading": "どぼくこうじ", + "phoneticTranscriptions": [] + } + ], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -28203,6 +28913,220 @@ } ], "pitchCount": 1, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [] + } + ], + "context": { + "query": "query", + "fullQuery": "fullQuery", + "document": { + "title": "title" + } + }, + "media": {} + } + ] + }, + { + "name": "Test pronunciations 6 - phonetic transcriptions", + "noteDataList": [ + { + "marker": "{marker}", + "definition": { + "type": "term", + "id": 20, + "source": "好き", + "rawSource": "好き", + "sourceTerm": "好き", + "reasons": [], + "score": 1, + "isPrimary": true, + "sequence": 14, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "dictionaryNames": [ + "Test Dictionary 2" + ], + "expression": "好き", + "reading": "すき", + "expressions": [ + { + "sourceTerm": "好き", + "expression": "好き", + "reading": "すき", + "termTags": [], + "frequencies": [], + "pitches": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "好き", + "reading": "すき", + "pitches": [] + } + ], + "furiganaSegments": [ + { + "text": "好", + "furigana": "す" + }, + { + "text": "き", + "furigana": "" + } + ], + "termFrequency": "normal", + "wordClasses": [] + } + ], + "glossary": [ + "suki definition" + ], + "definitionTags": [ + { + "name": "adj-na", + "category": "default", + "notes": "", + "order": 0, + "score": 0, + "dictionary": "Test Dictionary 2", + "redundant": false + }, + { + "name": "n", + "category": "partOfSpeech", + "notes": "noun", + "order": 0, + "score": 0, + "dictionary": "Test Dictionary 2", + "redundant": false + } + ], + "termTags": [], + "frequencies": [], + "pitches": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "好き", + "reading": "すき", + "pitches": [] + } + ], + "phoneticTranscriptions": [ + { + "index": 0, + "expressionIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "expression": "好き", + "reading": "すき", + "phoneticTranscriptions": [ + { + "ipa": "[sɨᵝkʲi]", + "tags": [ + { + "name": "東京", + "category": "default", + "notes": "", + "order": 0, + "score": 0, + "dictionary": "Test Dictionary 2", + "redundant": false + } + ] + } + ] + } + ], + "sourceTermExactMatchCount": 1, + "url": "url:", + "cloze": { + "sentence": "", + "prefix": "", + "body": "", + "suffix": "" + }, + "furiganaSegments": [ + { + "text": "好", + "furigana": "す" + }, + { + "text": "き", + "furigana": "" + } + ] + }, + "glossaryLayoutMode": "default", + "compactTags": false, + "group": false, + "merge": false, + "modeTermKanji": false, + "modeTermKana": false, + "modeKanji": false, + "compactGlossaries": false, + "uniqueExpressions": [ + "好き" + ], + "uniqueReadings": [ + "すき" + ], + "pitches": [ + { + "dictionary": "Test Dictionary 2", + "pitches": [] + } + ], + "pitchCount": 0, + "phoneticTranscriptions": [ + { + "dictionary": "Test Dictionary 2", + "phoneticTranscriptions": [ + { + "expressions": [ + "好き" + ], + "reading": "すき", + "ipa": "[sɨᵝkʲi]", + "tags": [ + { + "name": "東京", + "category": "default", + "order": 0, + "score": 0, + "content": [], + "dictionaries": [ + "Test Dictionary 2" + ], + "redundant": false + } + ], + "exclusiveExpressions": [], + "exclusiveReadings": [] + } + ] + } + ], "context": { "query": "query", "fullQuery": "fullQuery", @@ -28221,7 +29145,7 @@ "marker": "{marker}", "definition": { "type": "term", - "id": 21, + "id": 22, "source": "構造", "rawSource": "構造", "sourceTerm": "構造", @@ -28322,6 +29246,7 @@ ], "frequencies": [], "pitches": [], + "phoneticTranscriptions": [], "sourceTermExactMatchCount": 1, "url": "url:", "cloze": { @@ -28353,6 +29278,7 @@ ], "pitches": [], "pitchCount": 0, + "phoneticTranscriptions": [], "context": { "query": "query", "fullQuery": "fullQuery", diff --git a/test/data/translator-test-results.json b/test/data/translator-test-results.json index 0a7155b885..50d97775c0 100644 --- a/test/data/translator-test-results.json +++ b/test/data/translator-test-results.json @@ -1740,14 +1740,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -1927,14 +1929,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -2114,14 +2118,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -2301,14 +2307,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -4637,14 +4645,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -4824,14 +4834,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -5351,14 +5363,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -5538,14 +5552,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -6820,14 +6836,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -7055,14 +7073,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -8194,14 +8214,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -8215,14 +8237,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -9227,14 +9251,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -9418,14 +9444,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -9609,14 +9637,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -9800,14 +9830,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -11000,14 +11032,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -11187,14 +11221,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -11374,14 +11410,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -11561,14 +11599,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -12761,14 +12801,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -12948,14 +12990,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -13135,14 +13179,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -13322,14 +13368,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -15012,14 +15060,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -15033,14 +15083,16 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], "tags": [] }, { + "type": "pitch-accent", "position": 3, "nasalPositions": [], "devoicePositions": [], @@ -15674,8 +15726,9 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 2, "nasalPositions": [], "devoicePositions": [], @@ -15696,6 +15749,7 @@ ] }, { + "type": "pitch-accent", "position": 2, "nasalPositions": [], "devoicePositions": [], @@ -15716,6 +15770,7 @@ ] }, { + "type": "pitch-accent", "position": 0, "nasalPositions": [], "devoicePositions": [], @@ -15820,8 +15875,9 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 3, "nasalPositions": [ 3 @@ -15914,8 +15970,9 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [ 3 @@ -16008,8 +16065,9 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 0, "nasalPositions": [ 2 @@ -16102,8 +16160,9 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "pitches": [ + "pronunciations": [ { + "type": "pitch-accent", "position": 4, "nasalPositions": [], "devoicePositions": [ @@ -16118,6 +16177,118 @@ } ] }, + { + "name": "Test pronunciations 6 - phonetic transcriptions", + "originalTextLength": 2, + "dictionaryEntries": [ + { + "type": "term", + "isPrimary": true, + "inflections": [], + "score": 1, + "frequencyOrder": 0, + "dictionaryIndex": 0, + "dictionaryPriority": 0, + "sourceTermExactMatchCount": 1, + "maxTransformedTextLength": 2, + "headwords": [ + { + "index": 0, + "term": "好き", + "reading": "すき", + "sources": [ + { + "originalText": "好き", + "transformedText": "好き", + "deinflectedText": "好き", + "matchType": "exact", + "matchSource": "term", + "isPrimary": true + } + ], + "tags": [], + "wordClasses": [] + } + ], + "definitions": [ + { + "index": 0, + "headwordIndices": [ + 0 + ], + "dictionary": "Test Dictionary 2", + "dictionaryIndex": 0, + "dictionaryPriority": 0, + "id": 20, + "score": 1, + "frequencyOrder": 0, + "sequences": [ + 14 + ], + "isPrimary": true, + "tags": [ + { + "name": "adj-na", + "category": "default", + "order": 0, + "score": 0, + "content": [], + "dictionaries": [ + "Test Dictionary 2" + ], + "redundant": false + }, + { + "name": "n", + "category": "partOfSpeech", + "order": 0, + "score": 0, + "content": [ + "noun" + ], + "dictionaries": [ + "Test Dictionary 2" + ], + "redundant": false + } + ], + "entries": [ + "suki definition" + ] + } + ], + "pronunciations": [ + { + "index": 0, + "headwordIndex": 0, + "dictionary": "Test Dictionary 2", + "dictionaryIndex": 0, + "dictionaryPriority": 0, + "pronunciations": [ + { + "type": "phonetic-transcription", + "ipa": "[sɨᵝkʲi]", + "tags": [ + { + "name": "東京", + "category": "default", + "order": 0, + "score": 0, + "content": [], + "dictionaries": [ + "Test Dictionary 2" + ], + "redundant": false + } + ] + } + ] + } + ], + "frequencies": [] + } + ] + }, { "name": "Structured content test", "originalTextLength": 2, @@ -16189,7 +16360,7 @@ "dictionary": "Test Dictionary 2", "dictionaryIndex": 0, "dictionaryPriority": 0, - "id": 21, + "id": 22, "score": 35, "frequencyOrder": 0, "sequences": [ diff --git a/test/database.test.js b/test/database.test.js index 7c3d560698..f5d2c307de 100644 --- a/test/database.test.js +++ b/test/database.test.js @@ -164,8 +164,8 @@ async function testDatabase1() { kanjiMeta: {total: 6, freq: 6}, media: {total: 6}, tagMeta: {total: 15}, - termMeta: {total: 38, freq: 31, pitch: 7}, - terms: {total: 22} + termMeta: {total: 39, freq: 31, pitch: 7, ipa: 1}, + terms: {total: 23} } }; @@ -192,8 +192,8 @@ async function testDatabase1() { true ); expect(counts).toStrictEqual({ - counts: [{kanji: 2, kanjiMeta: 6, terms: 22, termMeta: 38, tagMeta: 15, media: 6}], - total: {kanji: 2, kanjiMeta: 6, terms: 22, termMeta: 38, tagMeta: 15, media: 6} + counts: [{kanji: 2, kanjiMeta: 6, terms: 23, termMeta: 39, tagMeta: 15, media: 6}], + total: {kanji: 2, kanjiMeta: 6, terms: 23, termMeta: 39, tagMeta: 15, media: 6} }); // Test find* functions diff --git a/test/utilities/anki.js b/test/utilities/anki.js index 4b73f6b90a..aa6c83d289 100644 --- a/test/utilities/anki.js +++ b/test/utilities/anki.js @@ -75,6 +75,7 @@ function getFieldMarkers(type) { 'pitch-accents', 'pitch-accent-graphs', 'pitch-accent-positions', + 'phonetic-transcriptions', 'reading', 'screenshot', 'search-query', diff --git a/types/ext/anki-templates.d.ts b/types/ext/anki-templates.d.ts index 5c40f40633..098873e622 100644 --- a/types/ext/anki-templates.d.ts +++ b/types/ext/anki-templates.d.ts @@ -76,6 +76,7 @@ export type NoteData = { readonly uniqueReadings: string[]; readonly pitches: PitchGroup[]; readonly pitchCount: number; + readonly phoneticTranscriptions: TranscriptionGroup[]; readonly context: Context; media: Media; readonly dictionaryEntry: Dictionary.DictionaryEntry; @@ -97,6 +98,20 @@ export type Pitch = { exclusiveReadings: string[]; }; +export type TranscriptionGroup = { + dictionary: string; + phoneticTranscriptions: Transcription[]; +}; + +export type Transcription = { + expressions: string[]; + reading: string; + ipa: string; + tags: Dictionary.Tag[]; + exclusiveExpressions: string[]; + exclusiveReadings: string[]; +}; + /** * For legacy reasons, {@link Pitch} has a custom tag type that resembles {@link Dictionary.Tag}. */ @@ -175,7 +190,8 @@ export type TermDictionaryEntry = { readonly termTags?: Tag[]; readonly definitions?: TermDefinition[]; readonly frequencies: TermFrequency[]; - readonly pitches: TermPronunciation[]; + readonly pitches: TermPitchAccent[]; + readonly phoneticTranscriptions: TermPhoneticTranscription[]; sourceTermExactMatchCount: number; url: string; readonly cloze: Cloze; @@ -225,7 +241,7 @@ export type TermFrequency = { frequency: number | string; }; -export type TermPronunciation = { +export type TermPitchAccent = { index: number; expressionIndex: number; dictionary: string; @@ -235,14 +251,32 @@ export type TermPronunciation = { }; expression: string; reading: string; - readonly pitches: TermPitch[]; + readonly pitches: PitchAccent[]; }; -export type TermPitch = { +export type PitchAccent = { position: number; tags: Tag[]; }; +export type TermPhoneticTranscription = { + index: number; + expressionIndex: number; + dictionary: string; + dictionaryOrder: { + index: number; + priority: number; + }; + expression: string; + reading: string; + readonly phoneticTranscriptions: PhoneticTranscription[]; +}; + +export type PhoneticTranscription = { + ipa: string; + tags: Tag[]; +}; + export type TermFrequencyType = DictionaryDataUtil.TermFrequencyType; export type TermHeadword = { @@ -251,7 +285,7 @@ export type TermHeadword = { reading: string; readonly termTags: Tag[]; readonly frequencies: TermFrequency[]; - readonly pitches: TermPronunciation[]; + readonly pitches: TermPitchAccent[]; readonly furiganaSegments: FuriganaSegment[]; readonly termFrequency: TermFrequencyType; wordClasses: string[]; diff --git a/types/ext/api.d.ts b/types/ext/api.d.ts index ad3aa22cb0..46dfbdc295 100644 --- a/types/ext/api.d.ts +++ b/types/ext/api.d.ts @@ -31,6 +31,7 @@ import type * as Settings from './settings'; import type * as SettingsModifications from './settings-modifications'; import type * as Translation from './translation'; import type * as Translator from './translator'; +import type {ApiMessageNoFrameIdAny as ApplicationApiMessageNoFrameIdAny} from './application'; import type { ApiMap as BaseApiMap, ApiMapInit as BaseApiMapInit, @@ -220,15 +221,13 @@ type ApiSurface = { sendMessageToFrame: { params: { frameId: number; - action: string; - params?: Core.SerializableObject; + message: ApplicationApiMessageNoFrameIdAny; }; return: boolean; }; broadcastTab: { params: { - action: string; - params?: Core.SerializableObject; + message: ApplicationApiMessageNoFrameIdAny; }; return: boolean; }; diff --git a/types/ext/application.d.ts b/types/ext/application.d.ts new file mode 100644 index 0000000000..ac594abcae --- /dev/null +++ b/types/ext/application.d.ts @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import type {TokenString} from './core'; +import type {SearchMode} from './display'; +import type {FrameEndpointReadyDetails, FrameEndpointConnectedDetails} from './frame-client'; +import type {DatabaseUpdateType, DatabaseUpdateCause} from './backend'; +import type { + ApiMap as BaseApiMap, + ApiHandler as BaseApiHandler, + ApiParams as BaseApiParams, + ApiNames as BaseApiNames, + ApiReturn as BaseApiReturn, +} from './api-map'; + +export type ApiSurface = { + searchDisplayControllerGetMode: { + params: void; + return: SearchMode; + }; + searchDisplayControllerSetMode: { + params: { + mode: SearchMode; + }; + return: void; + }; + searchDisplayControllerUpdateSearchQuery: { + params: { + text: string; + animate?: boolean; + }; + return: void; + }; + applicationReady: { + params: void; + return: void; + }; + applicationIsReady: { + params: void; + return: boolean; + }; + applicationBackendReady: { + params: void; + return: void; + }; + applicationGetUrl: { + params: void; + return: { + url: string; + }; + }; + applicationOptionsUpdated: { + params: { + source: string; + }; + return: void; + }; + applicationDatabaseUpdated: { + params: { + type: DatabaseUpdateType; + cause: DatabaseUpdateCause; + }; + return: void; + }; + applicationZoomChanged: { + params: { + oldZoomFactor: number; + newZoomFactor: number; + }; + return: void; + }; + frontendRequestReadyBroadcast: { + params: { + frameId: number; + }; + return: void; + }; + frontendSetAllVisibleOverride: { + params: { + value: boolean; + priority: number; + awaitFrame: boolean; + }; + return: TokenString; + }; + frontendClearAllVisibleOverride: { + params: { + token: TokenString; + }; + return: boolean; + }; + frontendReady: { + params: { + frameId: number; + }; + return: void; + }; + frameEndpointReady: { + params: FrameEndpointReadyDetails; + return: void; + }; + frameEndpointConnected: { + params: FrameEndpointConnectedDetails; + return: void; + }; +}; + +export type ApiParams = BaseApiParams; + +export type ApiNames = BaseApiNames; + +export type ApiMessageNoFrameId = ( + ApiParams extends void ? + {action: TName, params?: never} : + {action: TName, params: ApiParams} +); + +export type ApiMessage = ApiMessageNoFrameId & { + /** + * The origin frameId that sent this message. + * If sent from the backend, this value will be undefined. + */ + frameId?: number; +}; + +export type ApiMessageNoFrameIdAny = {[name in ApiNames]: ApiMessageNoFrameId}[ApiNames]; + +export type ApiMessageAny = {[name in ApiNames]: ApiMessage}[ApiNames]; + +export type ApiMap = BaseApiMap; + +export type ApiHandler = BaseApiHandler; + +export type ApiReturn = BaseApiReturn; diff --git a/types/ext/dictionary-data-util.d.ts b/types/ext/dictionary-data-util.d.ts index b78e643956..4ab06f11b8 100644 --- a/types/ext/dictionary-data-util.d.ts +++ b/types/ext/dictionary-data-util.d.ts @@ -64,21 +64,15 @@ export type KanjiFrequency = { export type TermFrequencyType = 'popular' | 'rare' | 'normal'; export type GroupedPronunciationInternal = { + pronunciation: Dictionary.Pronunciation; terms: Set; reading: string; - position: number; - nasalPositions: number[]; - devoicePositions: number[]; - tags: Dictionary.Tag[]; }; export type GroupedPronunciation = { + pronunciation: Dictionary.Pronunciation; terms: string[]; reading: string; - position: number; - nasalPositions: number[]; - devoicePositions: number[]; - tags: Dictionary.Tag[]; exclusiveTerms: string[]; exclusiveReadings: string[]; }; diff --git a/types/ext/dictionary-data.d.ts b/types/ext/dictionary-data.d.ts index b194c190cd..0e0edd5c46 100644 --- a/types/ext/dictionary-data.d.ts +++ b/types/ext/dictionary-data.d.ts @@ -125,7 +125,7 @@ export type GenericFrequencyData = string | number | { export type TermMetaArray = TermMeta[]; -export type TermMeta = TermMetaFrequency | TermMetaPitch; +export type TermMeta = TermMetaFrequency | TermMetaPitch | TermMetaPhonetic; export type TermMetaFrequencyDataWithReading = { reading: string; @@ -154,6 +154,20 @@ export type TermMetaPitch = [ data: TermMetaPitchData, ]; +export type TermMetaPhonetic = [ + expression: string, + mode: 'ipa', + data: TermMetaPhoneticData, +]; + +export type TermMetaPhoneticData = { + reading: string; + transcriptions: { + ipa: string; + tags?: string[]; + }[]; +}; + export type KanjiMetaArray = KanjiMeta[]; export type KanjiMeta = KanjiMetaFrequency; diff --git a/types/ext/dictionary-database.d.ts b/types/ext/dictionary-database.d.ts index 3202ef6017..3cf68543cf 100644 --- a/types/ext/dictionary-database.d.ts +++ b/types/ext/dictionary-database.d.ts @@ -102,7 +102,7 @@ export type Tag = { dictionary: string; }; -export type DatabaseTermMeta = DatabaseTermMetaFrequency | DatabaseTermMetaPitch; +export type DatabaseTermMeta = DatabaseTermMetaFrequency | DatabaseTermMetaPitch | DatabaseTermMetaPhoneticData; export type DatabaseTermMetaFrequency = { expression: string; @@ -118,12 +118,19 @@ export type DatabaseTermMetaPitch = { dictionary: string; }; +export type DatabaseTermMetaPhoneticData = { + expression: string; + mode: 'ipa'; + data: DictionaryData.TermMetaPhoneticData; + dictionary: string; +}; + export type TermMetaFrequencyDataWithReading = { reading: string; frequency: DictionaryData.GenericFrequencyData; }; -export type TermMeta = TermMetaFrequency | TermMetaPitch; +export type TermMeta = TermMetaFrequency | TermMetaPitch | TermMetaPhoneticData; export type TermMetaType = TermMeta['mode']; @@ -136,13 +143,21 @@ export type TermMetaFrequency = { }; export type TermMetaPitch = { + mode: 'pitch'; index: number; term: string; - mode: 'pitch'; data: DictionaryData.TermMetaPitchData; dictionary: string; }; +export type TermMetaPhoneticData = { + mode: 'ipa'; + index: number; + term: string; + data: DictionaryData.TermMetaPhoneticData; + dictionary: string; +}; + export type DatabaseKanjiMeta = DatabaseKanjiMetaFrequency; export type DatabaseKanjiMetaFrequency = { diff --git a/types/ext/dictionary.d.ts b/types/ext/dictionary.d.ts index 3e90dec0e1..7c348e7f5d 100644 --- a/types/ext/dictionary.d.ts +++ b/types/ext/dictionary.d.ts @@ -365,15 +365,21 @@ export type TermPronunciation = { */ dictionaryPriority: number; /** - * The pitch accent representations for the term. + * The pronunciations for the term. */ - pitches: TermPitch[]; + pronunciations: Pronunciation[]; }; +export type Pronunciation = PitchAccent | PhoneticTranscription; + /** * Pitch accent information for a term, represented as the position of the downstep. */ -export type TermPitch = { +export type PitchAccent = { + /** + * Type of the pronunciation, for disambiguation between union type members. + */ + type: 'pitch-accent'; /** * Position of the downstep, as a number of mora. */ @@ -392,6 +398,25 @@ export type TermPitch = { tags: Tag[]; }; +export type PhoneticTranscription = { + /** + * Type of the pronunciation, for disambiguation between union type members. + */ + type: 'phonetic-transcription'; + /** + * An IPA transcription. + */ + ipa: string; + /** + * Tags for the IPA transcription. + */ + tags: Tag[]; +}; + +export type PronunciationType = Pronunciation['type']; + +export type PronunciationGeneric = Extract; + /** * Frequency information corresponds to how frequently a term appears in a corpus, * which can be a number of occurrences or an overall rank. diff --git a/types/ext/extension.d.ts b/types/ext/extension.d.ts index 1c86a4ca84..5a24456669 100644 --- a/types/ext/extension.d.ts +++ b/types/ext/extension.d.ts @@ -56,16 +56,7 @@ export type ContentOrigin = { frameId?: number; }; -export type ChromeRuntimeMessage = { - action: string; - params?: Core.SerializableObject; -}; - -export type ChromeRuntimeMessageWithFrameId = ChromeRuntimeMessage & { - frameId?: number; -}; - -export type ChromeRuntimeOnMessageCallback = ( +export type ChromeRuntimeOnMessageCallback = ( message: TMessage, sender: chrome.runtime.MessageSender, sendResponse: ChromeRuntimeMessageSendResponseFunction, diff --git a/types/ext/frontend.d.ts b/types/ext/frontend.d.ts index 73b24dc39e..4cc8d03b2d 100644 --- a/types/ext/frontend.d.ts +++ b/types/ext/frontend.d.ts @@ -48,14 +48,6 @@ export type ConstructorDetails = { export type PageType = 'web' | 'popup' | 'search'; -export type FrontendRequestReadyBroadcastParams = { - frameId: number; -}; - export type GetPopupInfoResult = { popupId: string | null; }; - -export type FrontendReadyDetails = { - frameId: number; -}; diff --git a/types/ext/script-manager.d.ts b/types/ext/script-manager.d.ts index 57b9ee0601..66a5c20fcc 100644 --- a/types/ext/script-manager.d.ts +++ b/types/ext/script-manager.d.ts @@ -21,26 +21,12 @@ export type RunAt = 'document_start' | 'document_end' | 'document_idle'; export type RegistrationDetails = { /** Same as `matches` in the `content_scripts` manifest key. */ matches: string[]; - - /** Regex match pattern to use as a fallback when native content script registration isn't supported. */ - /** Should be equivalent to `matches`. */ - urlMatches: string; - /** Same as `run_at` in the `content_scripts` manifest key. */ runAt: RunAt; - - /** Same as `exclude_matches` in the `content_scripts` manifest key. */ - excludeMatches?: string[]; - - /** Same as `match_about_blank` in the `content_scripts` manifest key. */ - matchAboutBlank: boolean; - /** Same as `all_frames` in the `content_scripts` manifest key. */ allFrames: boolean; - /** List of CSS paths. */ css?: string[]; - /** List of script paths. */ js?: string[]; };