aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/js/background/backend.js38
-rw-r--r--ext/js/background/offscreen-proxy.js4
-rw-r--r--ext/js/background/offscreen.js4
-rw-r--r--ext/js/background/profile-conditions-util.js662
-rw-r--r--ext/js/comm/anki-connect.js4
-rw-r--r--ext/js/comm/clipboard-reader.js4
-rw-r--r--ext/js/data/anki-note-builder.js6
-rw-r--r--ext/js/data/anki-util.js117
-rw-r--r--ext/js/data/permissions-util.js191
-rw-r--r--ext/js/data/sandbox/anki-note-data-creator.js1523
-rw-r--r--ext/js/data/sandbox/array-buffer-util.js93
-rw-r--r--ext/js/data/sandbox/string-util.js89
-rw-r--r--ext/js/dictionary/dictionary-data-util.js696
-rw-r--r--ext/js/dictionary/dictionary-importer.js8
-rw-r--r--ext/js/display/display-anki.js4
-rw-r--r--ext/js/display/display-content-manager.js4
-rw-r--r--ext/js/display/display-generator.js26
-rw-r--r--ext/js/display/sandbox/pronunciation-generator.js378
-rw-r--r--ext/js/dom/dom-text-scanner.js6
-rw-r--r--ext/js/dom/text-source-element.js6
-rw-r--r--ext/js/general/regex-util.js130
-rw-r--r--ext/js/language/translator.js4
-rw-r--r--ext/js/media/audio-downloader.js4
-rw-r--r--ext/js/media/media-util.js211
-rw-r--r--ext/js/pages/action-popup-main.js8
-rw-r--r--ext/js/pages/settings/anki-controller.js23
-rw-r--r--ext/js/pages/settings/backup-controller.js7
-rw-r--r--ext/js/pages/settings/permissions-origin-controller.js5
-rw-r--r--ext/js/pages/settings/permissions-toggle-controller.js13
-rw-r--r--ext/js/pages/settings/recommended-permissions-controller.js5
-rw-r--r--ext/js/pages/settings/settings-controller.js11
-rw-r--r--ext/js/templates/sandbox/anki-template-renderer-content-manager.js1
-rw-r--r--ext/js/templates/sandbox/anki-template-renderer.js22
-rw-r--r--test/dictionary-data.test.js9
-rw-r--r--test/dictionary-data.write.js6
-rw-r--r--test/fixtures/translator-test.js21
-rw-r--r--test/profile-conditions-util.test.js10
-rw-r--r--test/utilities/anki.js6
38 files changed, 2138 insertions, 2221 deletions
diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js
index 0773dc4b..b95626f5 100644
--- a/ext/js/background/backend.js
+++ b/ext/js/background/backend.js
@@ -26,19 +26,19 @@ import {ExtensionError} from '../core/extension-error.js';
import {readResponseJson} from '../core/json.js';
import {log} from '../core/logger.js';
import {clone, deferPromise, isObject, promiseTimeout} from '../core/utilities.js';
-import {AnkiUtil} from '../data/anki-util.js';
+import {isNoteDataValid} from '../data/anki-util.js';
import {OptionsUtil} from '../data/options-util.js';
-import {PermissionsUtil} from '../data/permissions-util.js';
-import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js';
+import {getAllPermissions, hasPermissions, hasRequiredPermissionsForOptions} from '../data/permissions-util.js';
+import {arrayBufferToBase64} from '../data/sandbox/array-buffer-util.js';
import {DictionaryDatabase} from '../dictionary/dictionary-database.js';
import {Environment} from '../extension/environment.js';
import {ObjectPropertyAccessor} from '../general/object-property-accessor.js';
import {distributeFuriganaInflected, isCodePointJapanese, isStringPartiallyJapanese, convertKatakanaToHiragana as jpConvertKatakanaToHiragana} from '../language/japanese.js';
import {Translator} from '../language/translator.js';
import {AudioDownloader} from '../media/audio-downloader.js';
-import {MediaUtil} from '../media/media-util.js';
+import {getFileExtensionFromAudioMediaType, getFileExtensionFromImageMediaType} from '../media/media-util.js';
import {ClipboardReaderProxy, DictionaryDatabaseProxy, OffscreenProxy, TranslatorProxy} from './offscreen-proxy.js';
-import {ProfileConditionsUtil} from './profile-conditions-util.js';
+import {createSchema, normalizeContext} from './profile-conditions-util.js';
import {RequestBuilder} from './request-builder.js';
import {injectStylesheet} from './script-manager.js';
@@ -95,8 +95,6 @@ export class Backend {
this._options = null;
/** @type {import('../data/json-schema.js').JsonSchema[]} */
this._profileConditionsSchemaCache = [];
- /** @type {ProfileConditionsUtil} */
- this._profileConditionsUtil = new ProfileConditionsUtil();
/** @type {?string} */
this._defaultAnkiFieldTemplates = null;
/** @type {RequestBuilder} */
@@ -138,8 +136,6 @@ export class Backend {
this._logErrorLevel = null;
/** @type {?chrome.permissions.Permissions} */
this._permissions = null;
- /** @type {PermissionsUtil} */
- this._permissionsUtil = new PermissionsUtil();
/** @type {Map<string, (() => void)[]>} */
this._applicationReadyHandlers = new Map();
@@ -259,7 +255,7 @@ export class Backend {
try {
this._prepareInternalSync();
- this._permissions = await this._permissionsUtil.getAllPermissions();
+ this._permissions = await getAllPermissions();
this._defaultBrowserActionTitle = await this._getBrowserIconTitle();
this._badgePrepareDelayTimer = setTimeout(() => {
this._badgePrepareDelayTimer = null;
@@ -545,7 +541,7 @@ export class Backend {
for (let i = 0; i < notes.length; ++i) {
const note = notes[i];
let canAdd = canAddArray[i];
- const valid = AnkiUtil.isNoteDataValid(note);
+ const valid = isNoteDataValid(note);
if (!valid) { canAdd = false; }
const info = {canAdd, valid, noteIds: null};
results.push(info);
@@ -815,7 +811,7 @@ export class Backend {
let permissionsOkay = false;
try {
- permissionsOkay = await this._permissionsUtil.hasPermissions({permissions: ['nativeMessaging']});
+ permissionsOkay = await hasPermissions({permissions: ['nativeMessaging']});
} catch (e) {
// NOP
}
@@ -1302,7 +1298,7 @@ export class Backend {
* @returns {?import('settings').Profile}
*/
_getProfileFromContext(options, optionsContext) {
- const normalizedOptionsContext = this._profileConditionsUtil.normalizeContext(optionsContext);
+ const normalizedOptionsContext = normalizeContext(optionsContext);
let index = 0;
for (const profile of options.profiles) {
@@ -1312,7 +1308,7 @@ export class Backend {
if (index < this._profileConditionsSchemaCache.length) {
schema = this._profileConditionsSchemaCache[index];
} else {
- schema = this._profileConditionsUtil.createSchema(conditionGroups);
+ schema = createSchema(conditionGroups);
this._profileConditionsSchemaCache.push(schema);
}
@@ -2128,7 +2124,7 @@ export class Backend {
return null;
}
- let extension = contentType !== null ? MediaUtil.getFileExtensionFromAudioMediaType(contentType) : null;
+ let extension = contentType !== null ? getFileExtensionFromAudioMediaType(contentType) : null;
if (extension === null) { extension = '.mp3'; }
let fileName = this._generateAnkiNoteMediaFileName('yomitan_audio', extension, timestamp, definitionDetails);
fileName = fileName.replace(/\]/g, '');
@@ -2147,7 +2143,7 @@ export class Backend {
const dataUrl = await this._getScreenshot(tabId, frameId, format, quality);
const {mediaType, data} = this._getDataUrlInfo(dataUrl);
- const extension = MediaUtil.getFileExtensionFromImageMediaType(mediaType);
+ const extension = getFileExtensionFromImageMediaType(mediaType);
if (extension === null) {
throw new Error('Unknown media type for screenshot image');
}
@@ -2169,7 +2165,7 @@ export class Backend {
}
const {mediaType, data} = this._getDataUrlInfo(dataUrl);
- const extension = MediaUtil.getFileExtensionFromImageMediaType(mediaType);
+ const extension = getFileExtensionFromImageMediaType(mediaType);
if (extension === null) {
throw new Error('Unknown media type for clipboard image');
}
@@ -2215,7 +2211,7 @@ export class Backend {
let fileName = null;
if (media !== null) {
const {content, mediaType} = media;
- const extension = MediaUtil.getFileExtensionFromImageMediaType(mediaType);
+ const extension = getFileExtensionFromImageMediaType(mediaType);
fileName = this._generateAnkiNoteMediaFileName(
`yomitan_dictionary_media_${i + 1}`,
extension !== null ? extension : '',
@@ -2611,7 +2607,7 @@ export class Backend {
* @returns {Promise<void>}
*/
async _checkPermissions() {
- this._permissions = await this._permissionsUtil.getAllPermissions();
+ this._permissions = await getAllPermissions();
this._updateBadge();
}
@@ -2628,7 +2624,7 @@ export class Backend {
*/
_hasRequiredPermissionsForSettings(options) {
if (!this._canObservePermissionsChanges()) { return true; }
- return this._permissions === null || this._permissionsUtil.hasRequiredPermissionsForOptions(this._permissions, options);
+ return this._permissions === null || hasRequiredPermissionsForOptions(this._permissions, options);
}
/**
@@ -2663,7 +2659,7 @@ export class Backend {
const results = [];
for (const item of await this._dictionaryDatabase.getMedia(targets)) {
const {content, dictionary, height, mediaType, path, width} = item;
- const content2 = ArrayBufferUtil.arrayBufferToBase64(content);
+ const content2 = arrayBufferToBase64(content);
results.push({content: content2, dictionary, height, mediaType, path, width});
}
return results;
diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js
index 80ff31c0..716deddd 100644
--- a/ext/js/background/offscreen-proxy.js
+++ b/ext/js/background/offscreen-proxy.js
@@ -18,7 +18,7 @@
import {ExtensionError} from '../core/extension-error.js';
import {isObject} from '../core/utilities.js';
-import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js';
+import {base64ToArrayBuffer} from '../data/sandbox/array-buffer-util.js';
export class OffscreenProxy {
/**
@@ -144,7 +144,7 @@ export class DictionaryDatabaseProxy {
*/
async getMedia(targets) {
const serializedMedia = /** @type {import('dictionary-database').Media<string>[]} */ (await this._offscreen.sendMessagePromise({action: 'databaseGetMediaOffscreen', params: {targets}}));
- const media = serializedMedia.map((m) => ({...m, content: ArrayBufferUtil.base64ToArrayBuffer(m.content)}));
+ const media = serializedMedia.map((m) => ({...m, content: base64ToArrayBuffer(m.content)}));
return media;
}
}
diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js
index ef05508a..b203e326 100644
--- a/ext/js/background/offscreen.js
+++ b/ext/js/background/offscreen.js
@@ -18,7 +18,7 @@
import {ClipboardReader} from '../comm/clipboard-reader.js';
import {createApiMap, invokeApiMapHandler} from '../core/api-map.js';
-import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js';
+import {arrayBufferToBase64} from '../data/sandbox/array-buffer-util.js';
import {DictionaryDatabase} from '../dictionary/dictionary-database.js';
import {Translator} from '../language/translator.js';
@@ -110,7 +110,7 @@ export class Offscreen {
/** @type {import('offscreen').ApiHandler<'databaseGetMediaOffscreen'>} */
async _getMediaHandler({targets}) {
const media = await this._dictionaryDatabase.getMedia(targets);
- const serializedMedia = media.map((m) => ({...m, content: ArrayBufferUtil.arrayBufferToBase64(m.content)}));
+ const serializedMedia = media.map((m) => ({...m, content: arrayBufferToBase64(m.content)}));
return serializedMedia;
}
diff --git a/ext/js/background/profile-conditions-util.js b/ext/js/background/profile-conditions-util.js
index f3be226d..e2d58725 100644
--- a/ext/js/background/profile-conditions-util.js
+++ b/ext/js/background/profile-conditions-util.js
@@ -18,379 +18,369 @@
import {JsonSchema} from '../data/json-schema.js';
+/** @type {RegExp} */
+const splitPattern = /[,;\s]+/;
+/** @type {Map<string, {operators: Map<string, import('profile-conditions-util').CreateSchemaFunction>}>} */
+const descriptors = new Map([
+ [
+ 'popupLevel',
+ {
+ operators: new Map(/** @type {import('profile-conditions-util').OperatorMapArray} */ ([
+ ['equal', createSchemaPopupLevelEqual.bind(this)],
+ ['notEqual', createSchemaPopupLevelNotEqual.bind(this)],
+ ['lessThan', createSchemaPopupLevelLessThan.bind(this)],
+ ['greaterThan', createSchemaPopupLevelGreaterThan.bind(this)],
+ ['lessThanOrEqual', createSchemaPopupLevelLessThanOrEqual.bind(this)],
+ ['greaterThanOrEqual', createSchemaPopupLevelGreaterThanOrEqual.bind(this)]
+ ]))
+ }
+ ],
+ [
+ 'url',
+ {
+ operators: new Map(/** @type {import('profile-conditions-util').OperatorMapArray} */ ([
+ ['matchDomain', createSchemaUrlMatchDomain.bind(this)],
+ ['matchRegExp', createSchemaUrlMatchRegExp.bind(this)]
+ ]))
+ }
+ ],
+ [
+ 'modifierKeys',
+ {
+ operators: new Map(/** @type {import('profile-conditions-util').OperatorMapArray} */ ([
+ ['are', createSchemaModifierKeysAre.bind(this)],
+ ['areNot', createSchemaModifierKeysAreNot.bind(this)],
+ ['include', createSchemaModifierKeysInclude.bind(this)],
+ ['notInclude', createSchemaModifierKeysNotInclude.bind(this)]
+ ]))
+ }
+ ],
+ [
+ 'flags',
+ {
+ operators: new Map(/** @type {import('profile-conditions-util').OperatorMapArray} */ ([
+ ['are', createSchemaFlagsAre.bind(this)],
+ ['areNot', createSchemaFlagsAreNot.bind(this)],
+ ['include', createSchemaFlagsInclude.bind(this)],
+ ['notInclude', createSchemaFlagsNotInclude.bind(this)]
+ ]))
+ }
+ ]
+]);
+
/**
- * Utility class to help processing profile conditions.
+ * Creates a new JSON schema descriptor for the given set of condition groups.
+ * @param {import('settings').ProfileConditionGroup[]} conditionGroups An array of condition groups.
+ * For a profile match, all of the items must return successfully in at least one of the groups.
+ * @returns {JsonSchema} A new `JsonSchema` object.
*/
-export class ProfileConditionsUtil {
- /**
- * Creates a new instance.
- */
- constructor() {
- /** @type {RegExp} */
- this._splitPattern = /[,;\s]+/;
- /** @type {Map<string, {operators: Map<string, import('profile-conditions-util').CreateSchemaFunction>}>} */
- this._descriptors = new Map([
- [
- 'popupLevel',
- {
- operators: new Map(/** @type {import('profile-conditions-util').OperatorMapArray} */ ([
- ['equal', this._createSchemaPopupLevelEqual.bind(this)],
- ['notEqual', this._createSchemaPopupLevelNotEqual.bind(this)],
- ['lessThan', this._createSchemaPopupLevelLessThan.bind(this)],
- ['greaterThan', this._createSchemaPopupLevelGreaterThan.bind(this)],
- ['lessThanOrEqual', this._createSchemaPopupLevelLessThanOrEqual.bind(this)],
- ['greaterThanOrEqual', this._createSchemaPopupLevelGreaterThanOrEqual.bind(this)]
- ]))
- }
- ],
- [
- 'url',
- {
- operators: new Map(/** @type {import('profile-conditions-util').OperatorMapArray} */ ([
- ['matchDomain', this._createSchemaUrlMatchDomain.bind(this)],
- ['matchRegExp', this._createSchemaUrlMatchRegExp.bind(this)]
- ]))
- }
- ],
- [
- 'modifierKeys',
- {
- operators: new Map(/** @type {import('profile-conditions-util').OperatorMapArray} */ ([
- ['are', this._createSchemaModifierKeysAre.bind(this)],
- ['areNot', this._createSchemaModifierKeysAreNot.bind(this)],
- ['include', this._createSchemaModifierKeysInclude.bind(this)],
- ['notInclude', this._createSchemaModifierKeysNotInclude.bind(this)]
- ]))
- }
- ],
- [
- 'flags',
- {
- operators: new Map(/** @type {import('profile-conditions-util').OperatorMapArray} */ ([
- ['are', this._createSchemaFlagsAre.bind(this)],
- ['areNot', this._createSchemaFlagsAreNot.bind(this)],
- ['include', this._createSchemaFlagsInclude.bind(this)],
- ['notInclude', this._createSchemaFlagsNotInclude.bind(this)]
- ]))
- }
- ]
- ]);
- }
+export function createSchema(conditionGroups) {
+ const anyOf = [];
+ for (const {conditions} of conditionGroups) {
+ const allOf = [];
+ for (const {type, operator, value} of conditions) {
+ const conditionDescriptor = descriptors.get(type);
+ if (typeof conditionDescriptor === 'undefined') { continue; }
- /**
- * Creates a new JSON schema descriptor for the given set of condition groups.
- * @param {import('settings').ProfileConditionGroup[]} conditionGroups An array of condition groups.
- * For a profile match, all of the items must return successfully in at least one of the groups.
- * @returns {JsonSchema} A new `JsonSchema` object.
- */
- createSchema(conditionGroups) {
- const anyOf = [];
- for (const {conditions} of conditionGroups) {
- const allOf = [];
- for (const {type, operator, value} of conditions) {
- const conditionDescriptor = this._descriptors.get(type);
- if (typeof conditionDescriptor === 'undefined') { continue; }
+ const createSchema2 = conditionDescriptor.operators.get(operator);
+ if (typeof createSchema2 === 'undefined') { continue; }
- const createSchema = conditionDescriptor.operators.get(operator);
- if (typeof createSchema === 'undefined') { continue; }
-
- const schema = createSchema(value);
- allOf.push(schema);
- }
- switch (allOf.length) {
- case 0: break;
- case 1: anyOf.push(allOf[0]); break;
- default: anyOf.push({allOf}); break;
- }
+ const schema = createSchema2(value);
+ allOf.push(schema);
}
- let schema;
- switch (anyOf.length) {
- case 0: schema = {}; break;
- case 1: schema = anyOf[0]; break;
- default: schema = {anyOf}; break;
+ switch (allOf.length) {
+ case 0: break;
+ case 1: anyOf.push(allOf[0]); break;
+ default: anyOf.push({allOf}); break;
}
- return new JsonSchema(schema);
}
+ let schema;
+ switch (anyOf.length) {
+ case 0: schema = {}; break;
+ case 1: schema = anyOf[0]; break;
+ default: schema = {anyOf}; break;
+ }
+ return new JsonSchema(schema);
+}
- /**
- * Creates a normalized version of the context object to test,
- * assigning dependent fields as needed.
- * @param {import('settings').OptionsContext} context A context object which is used during schema validation.
- * @returns {import('profile-conditions-util').NormalizedOptionsContext} A normalized context object.
- */
- normalizeContext(context) {
- const normalizedContext = /** @type {import('profile-conditions-util').NormalizedOptionsContext} */ (Object.assign({}, context));
- const {url} = normalizedContext;
- if (typeof url === 'string') {
- try {
- normalizedContext.domain = new URL(url).hostname;
- } catch (e) {
- // NOP
- }
- }
- const {flags} = normalizedContext;
- if (!Array.isArray(flags)) {
- normalizedContext.flags = [];
+/**
+ * Creates a normalized version of the context object to test,
+ * assigning dependent fields as needed.
+ * @param {import('settings').OptionsContext} context A context object which is used during schema validation.
+ * @returns {import('profile-conditions-util').NormalizedOptionsContext} A normalized context object.
+ */
+export function normalizeContext(context) {
+ const normalizedContext = /** @type {import('profile-conditions-util').NormalizedOptionsContext} */ (Object.assign({}, context));
+ const {url} = normalizedContext;
+ if (typeof url === 'string') {
+ try {
+ normalizedContext.domain = new URL(url).hostname;
+ } catch (e) {
+ // NOP
}
- return normalizedContext;
}
-
- // Private
-
- /**
- * @param {string} value
- * @returns {string[]}
- */
- _split(value) {
- return value.split(this._splitPattern);
+ const {flags} = normalizedContext;
+ if (!Array.isArray(flags)) {
+ normalizedContext.flags = [];
}
+ return normalizedContext;
+}
- /**
- * @param {string} value
- * @returns {number}
- */
- _stringToNumber(value) {
- const number = Number.parseFloat(value);
- return Number.isFinite(number) ? number : 0;
- }
+// Private
- // popupLevel schema creation functions
+/**
+ * @param {string} value
+ * @returns {string[]}
+ */
+function split(value) {
+ return value.split(splitPattern);
+}
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaPopupLevelEqual(value) {
- const number = this._stringToNumber(value);
- return {
- required: ['depth'],
- properties: {
- depth: {const: number}
- }
- };
- }
+/**
+ * @param {string} value
+ * @returns {number}
+ */
+function stringToNumber(value) {
+ const number = Number.parseFloat(value);
+ return Number.isFinite(number) ? number : 0;
+}
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaPopupLevelNotEqual(value) {
- return {
- not: {
- anyOf: [this._createSchemaPopupLevelEqual(value)]
- }
- };
- }
+// popupLevel schema creation functions
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaPopupLevelLessThan(value) {
- const number = this._stringToNumber(value);
- return {
- required: ['depth'],
- properties: {
- depth: {type: 'number', exclusiveMaximum: number}
- }
- };
- }
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaPopupLevelEqual(value) {
+ const number = stringToNumber(value);
+ return {
+ required: ['depth'],
+ properties: {
+ depth: {const: number}
+ }
+ };
+}
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaPopupLevelGreaterThan(value) {
- const number = this._stringToNumber(value);
- return {
- required: ['depth'],
- properties: {
- depth: {type: 'number', exclusiveMinimum: number}
- }
- };
- }
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaPopupLevelNotEqual(value) {
+ return {
+ not: {
+ anyOf: [createSchemaPopupLevelEqual(value)]
+ }
+ };
+}
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaPopupLevelLessThanOrEqual(value) {
- const number = this._stringToNumber(value);
- return {
- required: ['depth'],
- properties: {
- depth: {type: 'number', maximum: number}
- }
- };
- }
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaPopupLevelLessThan(value) {
+ const number = stringToNumber(value);
+ return {
+ required: ['depth'],
+ properties: {
+ depth: {type: 'number', exclusiveMaximum: number}
+ }
+ };
+}
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaPopupLevelGreaterThanOrEqual(value) {
- const number = this._stringToNumber(value);
- return {
- required: ['depth'],
- properties: {
- depth: {type: 'number', minimum: number}
- }
- };
- }
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaPopupLevelGreaterThan(value) {
+ const number = stringToNumber(value);
+ return {
+ required: ['depth'],
+ properties: {
+ depth: {type: 'number', exclusiveMinimum: number}
+ }
+ };
+}
- // url schema creation functions
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaPopupLevelLessThanOrEqual(value) {
+ const number = stringToNumber(value);
+ return {
+ required: ['depth'],
+ properties: {
+ depth: {type: 'number', maximum: number}
+ }
+ };
+}
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaUrlMatchDomain(value) {
- const oneOf = [];
- for (let domain of this._split(value)) {
- if (domain.length === 0) { continue; }
- domain = domain.toLowerCase();
- oneOf.push({const: domain});
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaPopupLevelGreaterThanOrEqual(value) {
+ const number = stringToNumber(value);
+ return {
+ required: ['depth'],
+ properties: {
+ depth: {type: 'number', minimum: number}
}
- return {
- required: ['domain'],
- properties: {
- domain: {oneOf}
- }
- };
- }
+ };
+}
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaUrlMatchRegExp(value) {
- return {
- required: ['url'],
- properties: {
- url: {type: 'string', pattern: value, patternFlags: 'i'}
- }
- };
+// url schema creation functions
+
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaUrlMatchDomain(value) {
+ const oneOf = [];
+ for (let domain of split(value)) {
+ if (domain.length === 0) { continue; }
+ domain = domain.toLowerCase();
+ oneOf.push({const: domain});
}
+ return {
+ required: ['domain'],
+ properties: {
+ domain: {oneOf}
+ }
+ };
+}
- // modifierKeys schema creation functions
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaUrlMatchRegExp(value) {
+ return {
+ required: ['url'],
+ properties: {
+ url: {type: 'string', pattern: value, patternFlags: 'i'}
+ }
+ };
+}
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaModifierKeysAre(value) {
- return this._createSchemaArrayCheck('modifierKeys', value, true, false);
- }
+// modifierKeys schema creation functions
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaModifierKeysAreNot(value) {
- return {
- not: {
- anyOf: [this._createSchemaArrayCheck('modifierKeys', value, true, false)]
- }
- };
- }
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaModifierKeysAre(value) {
+ return createSchemaArrayCheck('modifierKeys', value, true, false);
+}
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaModifierKeysInclude(value) {
- return this._createSchemaArrayCheck('modifierKeys', value, false, false);
- }
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaModifierKeysAreNot(value) {
+ return {
+ not: {
+ anyOf: [createSchemaArrayCheck('modifierKeys', value, true, false)]
+ }
+ };
+}
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaModifierKeysNotInclude(value) {
- return this._createSchemaArrayCheck('modifierKeys', value, false, true);
- }
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaModifierKeysInclude(value) {
+ return createSchemaArrayCheck('modifierKeys', value, false, false);
+}
- // modifierKeys schema creation functions
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaModifierKeysNotInclude(value) {
+ return createSchemaArrayCheck('modifierKeys', value, false, true);
+}
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaFlagsAre(value) {
- return this._createSchemaArrayCheck('flags', value, true, false);
- }
+// modifierKeys schema creation functions
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaFlagsAreNot(value) {
- return {
- not: {
- anyOf: [this._createSchemaArrayCheck('flags', value, true, false)]
- }
- };
- }
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaFlagsAre(value) {
+ return createSchemaArrayCheck('flags', value, true, false);
+}
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaFlagsInclude(value) {
- return this._createSchemaArrayCheck('flags', value, false, false);
- }
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaFlagsAreNot(value) {
+ return {
+ not: {
+ anyOf: [createSchemaArrayCheck('flags', value, true, false)]
+ }
+ };
+}
- /**
- * @param {string} value
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaFlagsNotInclude(value) {
- return this._createSchemaArrayCheck('flags', value, false, true);
- }
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaFlagsInclude(value) {
+ return createSchemaArrayCheck('flags', value, false, false);
+}
- // Generic
+/**
+ * @param {string} value
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaFlagsNotInclude(value) {
+ return createSchemaArrayCheck('flags', value, false, true);
+}
- /**
- * @param {string} key
- * @param {string} value
- * @param {boolean} exact
- * @param {boolean} none
- * @returns {import('ext/json-schema').Schema}
- */
- _createSchemaArrayCheck(key, value, exact, none) {
- /** @type {import('ext/json-schema').Schema[]} */
- const containsList = [];
- for (const item of this._split(value)) {
- if (item.length === 0) { continue; }
- containsList.push({
- contains: {
- const: item
- }
- });
- }
- const containsListCount = containsList.length;
- /** @type {import('ext/json-schema').Schema} */
- const schema = {
- type: 'array'
- };
- if (exact) {
- schema.maxItems = containsListCount;
- }
- if (none) {
- if (containsListCount > 0) {
- schema.not = {anyOf: containsList};
- }
- } else {
- schema.minItems = containsListCount;
- if (containsListCount > 0) {
- schema.allOf = containsList;
+// Generic
+
+/**
+ * @param {string} key
+ * @param {string} value
+ * @param {boolean} exact
+ * @param {boolean} none
+ * @returns {import('ext/json-schema').Schema}
+ */
+function createSchemaArrayCheck(key, value, exact, none) {
+ /** @type {import('ext/json-schema').Schema[]} */
+ const containsList = [];
+ for (const item of split(value)) {
+ if (item.length === 0) { continue; }
+ containsList.push({
+ contains: {
+ const: item
}
+ });
+ }
+ const containsListCount = containsList.length;
+ /** @type {import('ext/json-schema').Schema} */
+ const schema = {
+ type: 'array'
+ };
+ if (exact) {
+ schema.maxItems = containsListCount;
+ }
+ if (none) {
+ if (containsListCount > 0) {
+ schema.not = {anyOf: containsList};
+ }
+ } else {
+ schema.minItems = containsListCount;
+ if (containsListCount > 0) {
+ schema.allOf = containsList;
}
- return {
- required: [key],
- properties: {
- [key]: schema
- }
- };
}
+ return {
+ required: [key],
+ properties: {
+ [key]: schema
+ }
+ };
}
diff --git a/ext/js/comm/anki-connect.js b/ext/js/comm/anki-connect.js
index f16471ce..7cb2d071 100644
--- a/ext/js/comm/anki-connect.js
+++ b/ext/js/comm/anki-connect.js
@@ -18,7 +18,7 @@
import {ExtensionError} from '../core/extension-error.js';
import {parseJson} from '../core/json.js';
-import {AnkiUtil} from '../data/anki-util.js';
+import {getRootDeckName} from '../data/anki-util.js';
/**
* This class controls communication with Anki via the AnkiConnect plugin.
@@ -499,7 +499,7 @@ export class AnkiConnect {
query = `"deck:${this._escapeQuery(note.deckName)}" `;
break;
case 'deck-root':
- query = `"deck:${this._escapeQuery(AnkiUtil.getRootDeckName(note.deckName))}" `;
+ query = `"deck:${this._escapeQuery(getRootDeckName(note.deckName))}" `;
break;
}
query += this._fieldsToQuery(note.fields);
diff --git a/ext/js/comm/clipboard-reader.js b/ext/js/comm/clipboard-reader.js
index 2ac41cb9..b040d6ca 100644
--- a/ext/js/comm/clipboard-reader.js
+++ b/ext/js/comm/clipboard-reader.js
@@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import {MediaUtil} from '../media/media-util.js';
+import {getFileExtensionFromImageMediaType} from '../media/media-util.js';
/**
* Class which can read text and images from the clipboard.
@@ -130,7 +130,7 @@ export class ClipboardReader {
for (const item of items) {
for (const type of item.types) {
- if (!MediaUtil.getFileExtensionFromImageMediaType(type)) { continue; }
+ if (!getFileExtensionFromImageMediaType(type)) { continue; }
try {
const blob = await item.getType(type);
return await this._readFileAsDataURL(blob);
diff --git a/ext/js/data/anki-note-builder.js b/ext/js/data/anki-note-builder.js
index 815e7f3f..5bb943c2 100644
--- a/ext/js/data/anki-note-builder.js
+++ b/ext/js/data/anki-note-builder.js
@@ -20,7 +20,7 @@ import {ExtensionError} from '../core/extension-error.js';
import {deferPromise} from '../core/utilities.js';
import {convertHiraganaToKatakana, convertKatakanaToHiragana} from '../language/japanese.js';
import {yomitan} from '../yomitan.js';
-import {AnkiUtil} from './anki-util.js';
+import {cloneFieldMarkerPattern, getRootDeckName} from './anki-util.js';
export class AnkiNoteBuilder {
/**
@@ -29,7 +29,7 @@ export class AnkiNoteBuilder {
*/
constructor(templateRenderer) {
/** @type {RegExp} */
- this._markerPattern = AnkiUtil.cloneFieldMarkerPattern(true);
+ this._markerPattern = cloneFieldMarkerPattern(true);
/** @type {import('../templates/template-renderer-proxy.js').TemplateRendererProxy|import('../templates/sandbox/template-renderer.js').TemplateRenderer} */
this._templateRenderer = templateRenderer;
/** @type {import('anki-note-builder').BatchedRequestGroup[]} */
@@ -64,7 +64,7 @@ export class AnkiNoteBuilder {
let duplicateScopeCheckChildren = false;
if (duplicateScope === 'deck-root') {
duplicateScope = 'deck';
- duplicateScopeDeckName = AnkiUtil.getRootDeckName(deckName);
+ duplicateScopeDeckName = getRootDeckName(deckName);
duplicateScopeCheckChildren = true;
}
diff --git a/ext/js/data/anki-util.js b/ext/js/data/anki-util.js
index 57684887..123e5d2f 100644
--- a/ext/js/data/anki-util.js
+++ b/ext/js/data/anki-util.js
@@ -18,72 +18,67 @@
import {isObject} from '../core/utilities.js';
+/** @type {RegExp} @readonly */
+const markerPattern = /\{([\w-]+)\}/g;
+
/**
- * This class has some general utility functions for working with Anki data.
+ * Gets the root deck name of a full deck name. If the deck is a root deck,
+ * the same name is returned. Nested decks are separated using '::'.
+ * @param {string} deckName A string of the deck name.
+ * @returns {string} A string corresponding to the name of the root deck.
*/
-export class AnkiUtil {
- /** @type {RegExp} @readonly */
- static _markerPattern = /\{([\w-]+)\}/g;
-
- /**
- * Gets the root deck name of a full deck name. If the deck is a root deck,
- * the same name is returned. Nested decks are separated using '::'.
- * @param {string} deckName A string of the deck name.
- * @returns {string} A string corresponding to the name of the root deck.
- */
- static getRootDeckName(deckName) {
- const index = deckName.indexOf('::');
- return index >= 0 ? deckName.substring(0, index) : deckName;
- }
+export function getRootDeckName(deckName) {
+ const index = deckName.indexOf('::');
+ return index >= 0 ? deckName.substring(0, index) : deckName;
+}
- /**
- * Checks whether or not any marker is contained in a string.
- * @param {string} string A string to check.
- * @returns {boolean} `true` if the text contains an Anki field marker, `false` otherwise.
- */
- static stringContainsAnyFieldMarker(string) {
- const result = this._markerPattern.test(string);
- this._markerPattern.lastIndex = 0;
- return result;
- }
+/**
+ * Checks whether or not any marker is contained in a string.
+ * @param {string} string A string to check.
+ * @returns {boolean} `true` if the text contains an Anki field marker, `false` otherwise.
+ */
+export function stringContainsAnyFieldMarker(string) {
+ const result = markerPattern.test(string);
+ markerPattern.lastIndex = 0;
+ return result;
+}
- /**
- * Gets a list of all markers that are contained in a string.
- * @param {string} string A string to check.
- * @returns {string[]} An array of marker strings.
- */
- static getFieldMarkers(string) {
- const pattern = this._markerPattern;
- const markers = [];
- while (true) {
- const match = pattern.exec(string);
- if (match === null) { break; }
- markers.push(match[1]);
- }
- return markers;
+/**
+ * Gets a list of all markers that are contained in a string.
+ * @param {string} string A string to check.
+ * @returns {string[]} An array of marker strings.
+ */
+export function getFieldMarkers(string) {
+ const pattern = markerPattern;
+ const markers = [];
+ while (true) {
+ const match = pattern.exec(string);
+ if (match === null) { break; }
+ markers.push(match[1]);
}
+ return markers;
+}
- /**
- * Returns a regular expression which can be used to find markers in a string.
- * @param {boolean} global Whether or not the regular expression should have the global flag.
- * @returns {RegExp} A new `RegExp` instance.
- */
- static cloneFieldMarkerPattern(global) {
- return new RegExp(this._markerPattern.source, global ? 'g' : '');
- }
+/**
+ * Returns a regular expression which can be used to find markers in a string.
+ * @param {boolean} global Whether or not the regular expression should have the global flag.
+ * @returns {RegExp} A new `RegExp` instance.
+ */
+export function cloneFieldMarkerPattern(global) {
+ return new RegExp(markerPattern.source, global ? 'g' : '');
+}
- /**
- * Checks whether or not a note object is valid.
- * @param {import('anki').Note} note A note object to check.
- * @returns {boolean} `true` if the note is valid, `false` otherwise.
- */
- static isNoteDataValid(note) {
- if (!isObject(note)) { return false; }
- const {fields, deckName, modelName} = note;
- return (
- typeof deckName === 'string' &&
- typeof modelName === 'string' &&
- Object.entries(fields).length > 0
- );
- }
+/**
+ * Checks whether or not a note object is valid.
+ * @param {import('anki').Note} note A note object to check.
+ * @returns {boolean} `true` if the note is valid, `false` otherwise.
+ */
+export function isNoteDataValid(note) {
+ if (!isObject(note)) { return false; }
+ const {fields, deckName, modelName} = note;
+ return (
+ typeof deckName === 'string' &&
+ typeof modelName === 'string' &&
+ Object.entries(fields).length > 0
+ );
}
diff --git a/ext/js/data/permissions-util.js b/ext/js/data/permissions-util.js
index 43aaa87c..837b6d5f 100644
--- a/ext/js/data/permissions-util.js
+++ b/ext/js/data/permissions-util.js
@@ -16,123 +16,128 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import {AnkiUtil} from './anki-util.js';
+import {getFieldMarkers} from './anki-util.js';
-export class PermissionsUtil {
- constructor() {
- /** @type {Set<string>} */
- this._ankiFieldMarkersRequiringClipboardPermission = new Set([
- 'clipboard-image',
- 'clipboard-text'
- ]);
+/**
+ * This function returns whether an Anki field marker might require clipboard permissions.
+ * This is speculative and may not guarantee that the field marker actually does require the permission,
+ * as the custom handlebars template is not deeply inspected.
+ * @param {string} marker
+ * @returns {boolean}
+ */
+function ankiFieldMarkerMayUseClipboard(marker) {
+ switch (marker) {
+ case 'clipboard-image':
+ case 'clipboard-text':
+ return true;
+ default:
+ return false;
}
+}
+
+/**
+ * @param {chrome.permissions.Permissions} permissions
+ * @returns {Promise<boolean>}
+ */
+export function hasPermissions(permissions) {
+ return new Promise((resolve, reject) => chrome.permissions.contains(permissions, (result) => {
+ const e = chrome.runtime.lastError;
+ if (e) {
+ reject(new Error(e.message));
+ } else {
+ resolve(result);
+ }
+ }));
+}
- /**
- * @param {chrome.permissions.Permissions} permissions
- * @returns {Promise<boolean>}
- */
- hasPermissions(permissions) {
- return new Promise((resolve, reject) => chrome.permissions.contains(permissions, (result) => {
+/**
+ * @param {chrome.permissions.Permissions} permissions
+ * @param {boolean} shouldHave
+ * @returns {Promise<boolean>}
+ */
+export function setPermissionsGranted(permissions, shouldHave) {
+ return (
+ shouldHave ?
+ new Promise((resolve, reject) => chrome.permissions.request(permissions, (result) => {
const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
resolve(result);
}
- }));
- }
-
- /**
- * @param {chrome.permissions.Permissions} permissions
- * @param {boolean} shouldHave
- * @returns {Promise<boolean>}
- */
- setPermissionsGranted(permissions, shouldHave) {
- return (
- shouldHave ?
- new Promise((resolve, reject) => chrome.permissions.request(permissions, (result) => {
- const e = chrome.runtime.lastError;
- if (e) {
- reject(new Error(e.message));
- } else {
- resolve(result);
- }
- })) :
- new Promise((resolve, reject) => chrome.permissions.remove(permissions, (result) => {
- const e = chrome.runtime.lastError;
- if (e) {
- reject(new Error(e.message));
- } else {
- resolve(!result);
- }
- }))
- );
- }
-
- /**
- * @returns {Promise<chrome.permissions.Permissions>}
- */
- getAllPermissions() {
- return new Promise((resolve, reject) => chrome.permissions.getAll((result) => {
+ })) :
+ new Promise((resolve, reject) => chrome.permissions.remove(permissions, (result) => {
const e = chrome.runtime.lastError;
if (e) {
reject(new Error(e.message));
} else {
- resolve(result);
+ resolve(!result);
}
- }));
- }
+ }))
+ );
+}
- /**
- * @param {string} fieldValue
- * @returns {string[]}
- */
- getRequiredPermissionsForAnkiFieldValue(fieldValue) {
- const markers = AnkiUtil.getFieldMarkers(fieldValue);
- const markerPermissions = this._ankiFieldMarkersRequiringClipboardPermission;
- for (const marker of markers) {
- if (markerPermissions.has(marker)) {
- return ['clipboardRead'];
- }
+/**
+ * @returns {Promise<chrome.permissions.Permissions>}
+ */
+export function getAllPermissions() {
+ return new Promise((resolve, reject) => chrome.permissions.getAll((result) => {
+ const e = chrome.runtime.lastError;
+ if (e) {
+ reject(new Error(e.message));
+ } else {
+ resolve(result);
+ }
+ }));
+}
+
+/**
+ * @param {string} fieldValue
+ * @returns {string[]}
+ */
+export function getRequiredPermissionsForAnkiFieldValue(fieldValue) {
+ const markers = getFieldMarkers(fieldValue);
+ for (const marker of markers) {
+ if (ankiFieldMarkerMayUseClipboard(marker)) {
+ return ['clipboardRead'];
}
- return [];
}
+ return [];
+}
- /**
- * @param {chrome.permissions.Permissions} permissions
- * @param {import('settings').ProfileOptions} options
- * @returns {boolean}
- */
- hasRequiredPermissionsForOptions(permissions, options) {
- const permissionsSet = new Set(permissions.permissions);
+/**
+ * @param {chrome.permissions.Permissions} permissions
+ * @param {import('settings').ProfileOptions} options
+ * @returns {boolean}
+ */
+export function hasRequiredPermissionsForOptions(permissions, options) {
+ const permissionsSet = new Set(permissions.permissions);
- if (!permissionsSet.has('nativeMessaging')) {
- if (options.parsing.enableMecabParser) {
- return false;
- }
+ if (!permissionsSet.has('nativeMessaging')) {
+ if (options.parsing.enableMecabParser) {
+ return false;
}
+ }
- if (!permissionsSet.has('clipboardRead')) {
- if (options.clipboard.enableBackgroundMonitor || options.clipboard.enableSearchPageMonitor) {
- return false;
- }
- const fieldMarkersRequiringClipboardPermission = this._ankiFieldMarkersRequiringClipboardPermission;
- const fieldsList = [
- options.anki.terms.fields,
- options.anki.kanji.fields
- ];
- for (const fields of fieldsList) {
- for (const fieldValue of Object.values(fields)) {
- const markers = AnkiUtil.getFieldMarkers(fieldValue);
- for (const marker of markers) {
- if (fieldMarkersRequiringClipboardPermission.has(marker)) {
- return false;
- }
+ if (!permissionsSet.has('clipboardRead')) {
+ if (options.clipboard.enableBackgroundMonitor || options.clipboard.enableSearchPageMonitor) {
+ return false;
+ }
+ const fieldsList = [
+ options.anki.terms.fields,
+ options.anki.kanji.fields
+ ];
+ for (const fields of fieldsList) {
+ for (const fieldValue of Object.values(fields)) {
+ const markers = getFieldMarkers(fieldValue);
+ for (const marker of markers) {
+ if (ankiFieldMarkerMayUseClipboard(marker)) {
+ return false;
}
}
}
}
-
- return true;
}
+
+ return true;
}
diff --git a/ext/js/data/sandbox/anki-note-data-creator.js b/ext/js/data/sandbox/anki-note-data-creator.js
index fc787a66..51679662 100644
--- a/ext/js/data/sandbox/anki-note-data-creator.js
+++ b/ext/js/data/sandbox/anki-note-data-creator.js
@@ -16,867 +16,842 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import {DictionaryDataUtil} from '../../dictionary/dictionary-data-util.js';
+import {getDisambiguations, getGroupedPronunciations, getPronunciationsOfType, getTermFrequency, groupTermTags} from '../../dictionary/dictionary-data-util.js';
import {distributeFurigana} from '../../language/japanese.js';
/**
- * This class is used to convert the internal dictionary entry format to the
- * format used by Anki, for backwards compatibility.
- */
-export class AnkiNoteDataCreator {
- /**
- * Creates a compatibility representation of the specified data.
- * @param {string} marker The marker that is being used for template rendering.
- * @param {import('anki-templates-internal').CreateDetails} details Information which is used to generate the data.
- * @returns {import('anki-templates').NoteData} An object used for rendering Anki templates.
- */
- create(marker, {
- dictionaryEntry,
- resultOutputMode,
- mode,
- glossaryLayoutMode,
- compactTags,
- context,
- media
- }) {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const self = this;
- const definition = this.createCachedValue(this._getDefinition.bind(this, dictionaryEntry, context, resultOutputMode));
- const uniqueExpressions = this.createCachedValue(this._getUniqueExpressions.bind(this, dictionaryEntry));
- const uniqueReadings = this.createCachedValue(this._getUniqueReadings.bind(this, dictionaryEntry));
- 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,
- screenshot: void 0,
- clipboardImage: void 0,
- clipboardText: void 0,
- selectionText: void 0,
- textFurigana: [],
- dictionaryMedia: {}
- };
- }
- /** @type {import('anki-templates').NoteData} */
- const result = {
- marker,
- get definition() { return self.getCachedValue(definition); },
- glossaryLayoutMode,
- compactTags,
- group: (resultOutputMode === 'group'),
- merge: (resultOutputMode === 'merge'),
- modeTermKanji: (mode === 'term-kanji'),
- modeTermKana: (mode === 'term-kana'),
- modeKanji: (mode === 'kanji'),
- compactGlossaries: (glossaryLayoutMode === 'compact'),
- get uniqueExpressions() { return self.getCachedValue(uniqueExpressions); },
- 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
+ * Creates a compatibility representation of the specified data.
+ * @param {string} marker The marker that is being used for template rendering.
+ * @param {import('anki-templates-internal').CreateDetails} details Information which is used to generate the data.
+ * @returns {import('anki-templates').NoteData} An object used for rendering Anki templates.
+ */
+export function createAnkiNoteData(marker, {
+ dictionaryEntry,
+ resultOutputMode,
+ mode,
+ glossaryLayoutMode,
+ compactTags,
+ context,
+ media
+}) {
+ const definition = createCachedValue(getDefinition.bind(null, dictionaryEntry, context, resultOutputMode));
+ const uniqueExpressions = createCachedValue(getUniqueExpressions.bind(null, dictionaryEntry));
+ const uniqueReadings = createCachedValue(getUniqueReadings.bind(null, dictionaryEntry));
+ const context2 = createCachedValue(getPublicContext.bind(null, context));
+ const pitches = createCachedValue(getPitches.bind(null, dictionaryEntry));
+ const pitchCount = createCachedValue(getPitchCount.bind(null, pitches));
+ const phoneticTranscriptions = createCachedValue(getPhoneticTranscriptions.bind(null, dictionaryEntry));
+
+ if (typeof media !== 'object' || media === null || Array.isArray(media)) {
+ media = {
+ audio: void 0,
+ screenshot: void 0,
+ clipboardImage: void 0,
+ clipboardText: void 0,
+ selectionText: void 0,
+ textFurigana: [],
+ dictionaryMedia: {}
};
- Object.defineProperty(result, 'dictionaryEntry', {
- configurable: false,
- enumerable: false,
- writable: false,
- value: dictionaryEntry
- });
- return result;
}
+ /** @type {import('anki-templates').NoteData} */
+ const result = {
+ marker,
+ get definition() { return getCachedValue(definition); },
+ glossaryLayoutMode,
+ compactTags,
+ group: (resultOutputMode === 'group'),
+ merge: (resultOutputMode === 'merge'),
+ modeTermKanji: (mode === 'term-kanji'),
+ modeTermKana: (mode === 'term-kana'),
+ modeKanji: (mode === 'kanji'),
+ compactGlossaries: (glossaryLayoutMode === 'compact'),
+ get uniqueExpressions() { return getCachedValue(uniqueExpressions); },
+ get uniqueReadings() { return getCachedValue(uniqueReadings); },
+ get pitches() { return getCachedValue(pitches); },
+ get pitchCount() { return getCachedValue(pitchCount); },
+ get phoneticTranscriptions() { return getCachedValue(phoneticTranscriptions); },
+ get context() { return getCachedValue(context2); },
+ media,
+ dictionaryEntry
+ };
+ Object.defineProperty(result, 'dictionaryEntry', {
+ configurable: false,
+ enumerable: false,
+ writable: false,
+ value: dictionaryEntry
+ });
+ return result;
+}
- /**
- * Creates a deferred-evaluation value.
- * @template [T=unknown]
- * @param {() => T} getter The function to invoke to get the return value.
- * @returns {import('anki-templates-internal').CachedValue<T>} An object which can be passed into `getCachedValue`.
- */
- createCachedValue(getter) {
- return {getter, hasValue: false, value: void 0};
- }
+/**
+ * Creates a deferred-evaluation value.
+ * @template [T=unknown]
+ * @param {() => T} getter The function to invoke to get the return value.
+ * @returns {import('anki-templates-internal').CachedValue<T>} An object which can be passed into `getCachedValue`.
+ */
+export function createCachedValue(getter) {
+ return {getter, hasValue: false, value: void 0};
+}
- /**
- * Gets the value of a cached object.
- * @template [T=unknown]
- * @param {import('anki-templates-internal').CachedValue<T>} item An object that was returned from `createCachedValue`.
- * @returns {T} The result of evaluating the getter, which is cached after the first invocation.
- */
- getCachedValue(item) {
- if (item.hasValue) { return /** @type {T} */ (item.value); }
- const value = item.getter();
- item.value = value;
- item.hasValue = true;
- return value;
- }
+/**
+ * Gets the value of a cached object.
+ * @template [T=unknown]
+ * @param {import('anki-templates-internal').CachedValue<T>} item An object that was returned from `createCachedValue`.
+ * @returns {T} The result of evaluating the getter, which is cached after the first invocation.
+ */
+export function getCachedValue(item) {
+ if (item.hasValue) { return /** @type {T} */ (item.value); }
+ const value = item.getter();
+ item.value = value;
+ item.hasValue = true;
+ return value;
+}
- // Private
+// Private
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @returns {?import('dictionary').TermSource}
- */
- _getPrimarySource(dictionaryEntry) {
- for (const headword of dictionaryEntry.headwords) {
- for (const source of headword.sources) {
- if (source.isPrimary) { return source; }
- }
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {?import('dictionary').TermSource}
+ */
+function getPrimarySource(dictionaryEntry) {
+ for (const headword of dictionaryEntry.headwords) {
+ for (const source of headword.sources) {
+ if (source.isPrimary) { return source; }
}
- return null;
}
+ return null;
+}
- /**
- * @param {import('dictionary').DictionaryEntry} dictionaryEntry
- * @returns {string[]}
- */
- _getUniqueExpressions(dictionaryEntry) {
- if (dictionaryEntry.type === 'term') {
- const results = new Set();
- for (const {term} of dictionaryEntry.headwords) {
- results.add(term);
- }
- return [...results];
- } else {
- return [];
+/**
+ * @param {import('dictionary').DictionaryEntry} dictionaryEntry
+ * @returns {string[]}
+ */
+function getUniqueExpressions(dictionaryEntry) {
+ if (dictionaryEntry.type === 'term') {
+ const results = new Set();
+ for (const {term} of dictionaryEntry.headwords) {
+ results.add(term);
}
+ return [...results];
+ } else {
+ return [];
}
+}
- /**
- * @param {import('dictionary').DictionaryEntry} dictionaryEntry
- * @returns {string[]}
- */
- _getUniqueReadings(dictionaryEntry) {
- if (dictionaryEntry.type === 'term') {
- const results = new Set();
- for (const {reading} of dictionaryEntry.headwords) {
- results.add(reading);
- }
- return [...results];
- } else {
- return [];
+/**
+ * @param {import('dictionary').DictionaryEntry} dictionaryEntry
+ * @returns {string[]}
+ */
+function getUniqueReadings(dictionaryEntry) {
+ if (dictionaryEntry.type === 'term') {
+ const results = new Set();
+ for (const {reading} of dictionaryEntry.headwords) {
+ results.add(reading);
}
+ return [...results];
+ } else {
+ return [];
}
+}
- /**
- * @param {import('anki-templates-internal').Context} context
- * @returns {import('anki-templates').Context}
- */
- _getPublicContext(context) {
- let {documentTitle, query, fullQuery} = context;
- if (typeof documentTitle !== 'string') { documentTitle = ''; }
- return {
- query,
- fullQuery,
- document: {
- title: documentTitle
- }
- };
- }
+/**
+ * @param {import('anki-templates-internal').Context} context
+ * @returns {import('anki-templates').Context}
+ */
+function getPublicContext(context) {
+ let {documentTitle, query, fullQuery} = context;
+ if (typeof documentTitle !== 'string') { documentTitle = ''; }
+ return {
+ query,
+ fullQuery,
+ document: {
+ title: documentTitle
+ }
+ };
+}
- /**
- * @param {import('dictionary').DictionaryEntry} dictionaryEntry
- * @returns {import('anki-templates').PitchGroup[]}
- */
- _getPitches(dictionaryEntry) {
- /** @type {import('anki-templates').PitchGroup[]} */
- const results = [];
- if (dictionaryEntry.type === 'term') {
- for (const {dictionary, pronunciations} of DictionaryDataUtil.getGroupedPronunciations(dictionaryEntry)) {
- /** @type {import('anki-templates').Pitch[]} */
- const pitches = [];
- 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,
- position,
- nasalPositions,
- devoicePositions,
- tags: this._convertPitchTags(tags),
- exclusiveExpressions: exclusiveTerms,
- exclusiveReadings
- });
- }
- results.push({dictionary, pitches});
+/**
+ * @param {import('dictionary').DictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').PitchGroup[]}
+ */
+function getPitches(dictionaryEntry) {
+ /** @type {import('anki-templates').PitchGroup[]} */
+ const results = [];
+ if (dictionaryEntry.type === 'term') {
+ for (const {dictionary, pronunciations} of getGroupedPronunciations(dictionaryEntry)) {
+ /** @type {import('anki-templates').Pitch[]} */
+ const pitches = [];
+ 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,
+ position,
+ nasalPositions,
+ devoicePositions,
+ tags: convertPitchTags(tags),
+ exclusiveExpressions: exclusiveTerms,
+ exclusiveReadings
+ });
}
+ results.push({dictionary, pitches});
}
- return results;
}
+ 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});
+/**
+ * @param {import('dictionary').DictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').TranscriptionGroup[]}
+ */
+function getPhoneticTranscriptions(dictionaryEntry) {
+ const results = [];
+ if (dictionaryEntry.type === 'term') {
+ for (const {dictionary, pronunciations} of 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;
}
+ return results;
+}
- /**
- * @param {import('anki-templates-internal').CachedValue<import('anki-templates').PitchGroup[]>} cachedPitches
- * @returns {number}
- */
- _getPitchCount(cachedPitches) {
- const pitches = this.getCachedValue(cachedPitches);
- return pitches.reduce((i, v) => i + v.pitches.length, 0);
- }
+/**
+ * @param {import('anki-templates-internal').CachedValue<import('anki-templates').PitchGroup[]>} cachedPitches
+ * @returns {number}
+ */
+function getPitchCount(cachedPitches) {
+ const pitches = getCachedValue(cachedPitches);
+ return pitches.reduce((i, v) => i + v.pitches.length, 0);
+}
- /**
- * @param {import('dictionary').DictionaryEntry} dictionaryEntry
- * @param {import('anki-templates-internal').Context} context
- * @param {import('settings').ResultOutputMode} resultOutputMode
- * @returns {import('anki-templates').DictionaryEntry}
- */
- _getDefinition(dictionaryEntry, context, resultOutputMode) {
- switch (dictionaryEntry.type) {
- case 'term':
- return this._getTermDefinition(dictionaryEntry, context, resultOutputMode);
- case 'kanji':
- return this._getKanjiDefinition(dictionaryEntry, context);
- default:
- return /** @type {import('anki-templates').UnknownDictionaryEntry} */ ({});
- }
+/**
+ * @param {import('dictionary').DictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates-internal').Context} context
+ * @param {import('settings').ResultOutputMode} resultOutputMode
+ * @returns {import('anki-templates').DictionaryEntry}
+ */
+function getDefinition(dictionaryEntry, context, resultOutputMode) {
+ switch (dictionaryEntry.type) {
+ case 'term':
+ return getTermDefinition(dictionaryEntry, context, resultOutputMode);
+ case 'kanji':
+ return getKanjiDefinition(dictionaryEntry, context);
+ default:
+ return /** @type {import('anki-templates').UnknownDictionaryEntry} */ ({});
}
+}
- /**
- * @param {import('dictionary').KanjiDictionaryEntry} dictionaryEntry
- * @param {import('anki-templates-internal').Context} context
- * @returns {import('anki-templates').KanjiDictionaryEntry}
- */
- _getKanjiDefinition(dictionaryEntry, context) {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const self = this;
-
- const {character, dictionary, onyomi, kunyomi, definitions} = dictionaryEntry;
+/**
+ * @param {import('dictionary').KanjiDictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates-internal').Context} context
+ * @returns {import('anki-templates').KanjiDictionaryEntry}
+ */
+function getKanjiDefinition(dictionaryEntry, context) {
+ const {character, dictionary, onyomi, kunyomi, definitions} = dictionaryEntry;
+
+ let {url} = context;
+ if (typeof url !== 'string') { url = ''; }
+
+ const stats = createCachedValue(getKanjiStats.bind(null, dictionaryEntry));
+ const tags = createCachedValue(convertTags.bind(null, dictionaryEntry.tags));
+ const frequencies = createCachedValue(getKanjiFrequencies.bind(null, dictionaryEntry));
+ const cloze = createCachedValue(getCloze.bind(null, dictionaryEntry, context));
+
+ return {
+ type: 'kanji',
+ character,
+ dictionary,
+ onyomi,
+ kunyomi,
+ glossary: definitions,
+ get tags() { return getCachedValue(tags); },
+ get stats() { return getCachedValue(stats); },
+ get frequencies() { return getCachedValue(frequencies); },
+ url,
+ get cloze() { return getCachedValue(cloze); }
+ };
+}
- let {url} = context;
- if (typeof url !== 'string') { url = ''; }
+/**
+ * @param {import('dictionary').KanjiDictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').KanjiStatGroups}
+ */
+function getKanjiStats(dictionaryEntry) {
+ /** @type {import('anki-templates').KanjiStatGroups} */
+ const results = {};
+ for (const [key, value] of Object.entries(dictionaryEntry.stats)) {
+ results[key] = value.map(convertKanjiStat);
+ }
+ return results;
+}
- const stats = this.createCachedValue(this._getKanjiStats.bind(this, dictionaryEntry));
- const tags = this.createCachedValue(this._convertTags.bind(this, dictionaryEntry.tags));
- const frequencies = this.createCachedValue(this._getKanjiFrequencies.bind(this, dictionaryEntry));
- const cloze = this.createCachedValue(this._getCloze.bind(this, dictionaryEntry, context));
+/**
+ * @param {import('dictionary').KanjiStat} kanjiStat
+ * @returns {import('anki-templates').KanjiStat}
+ */
+function convertKanjiStat({name, category, content, order, score, dictionary, value}) {
+ return {
+ name,
+ category,
+ notes: content,
+ order,
+ score,
+ dictionary,
+ value
+ };
+}
- return {
- type: 'kanji',
- character,
+/**
+ * @param {import('dictionary').KanjiDictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').KanjiFrequency[]}
+ */
+function getKanjiFrequencies(dictionaryEntry) {
+ /** @type {import('anki-templates').KanjiFrequency[]} */
+ const results = [];
+ for (const {index, dictionary, dictionaryIndex, dictionaryPriority, character, frequency, displayValue} of dictionaryEntry.frequencies) {
+ results.push({
+ index,
dictionary,
- onyomi,
- kunyomi,
- glossary: definitions,
- get tags() { return self.getCachedValue(tags); },
- get stats() { return self.getCachedValue(stats); },
- get frequencies() { return self.getCachedValue(frequencies); },
- url,
- get cloze() { return self.getCachedValue(cloze); }
- };
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ character,
+ frequency: displayValue !== null ? displayValue : frequency
+ });
}
+ return results;
+}
- /**
- * @param {import('dictionary').KanjiDictionaryEntry} dictionaryEntry
- * @returns {import('anki-templates').KanjiStatGroups}
- */
- _getKanjiStats(dictionaryEntry) {
- /** @type {import('anki-templates').KanjiStatGroups} */
- const results = {};
- const convertKanjiStatBind = this._convertKanjiStat.bind(this);
- for (const [key, value] of Object.entries(dictionaryEntry.stats)) {
- results[key] = value.map(convertKanjiStatBind);
- }
- return results;
- }
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates-internal').Context} context
+ * @param {import('settings').ResultOutputMode} resultOutputMode
+ * @returns {import('anki-templates').TermDictionaryEntry}
+ */
+function getTermDefinition(dictionaryEntry, context, resultOutputMode) {
+ /** @type {import('anki-templates').TermDictionaryEntryType} */
+ let type = 'term';
+ switch (resultOutputMode) {
+ case 'group': type = 'termGrouped'; break;
+ case 'merge': type = 'termMerged'; break;
+ }
+
+ const {inflectionRuleChainCandidates, score, dictionaryIndex, dictionaryPriority, sourceTermExactMatchCount, definitions} = dictionaryEntry;
+
+ let {url} = context;
+ if (typeof url !== 'string') { url = ''; }
+
+ const primarySource = getPrimarySource(dictionaryEntry);
+
+ const dictionaryNames = createCachedValue(getTermDictionaryNames.bind(null, dictionaryEntry));
+ const commonInfo = createCachedValue(getTermDictionaryEntryCommonInfo.bind(null, dictionaryEntry, type));
+ const termTags = createCachedValue(getTermTags.bind(null, dictionaryEntry, type));
+ const expressions = createCachedValue(getTermExpressions.bind(null, dictionaryEntry));
+ const frequencies = createCachedValue(getTermFrequencies.bind(null, dictionaryEntry));
+ const pitches = createCachedValue(getTermPitches.bind(null, dictionaryEntry));
+ const phoneticTranscriptions = createCachedValue(getTermPhoneticTranscriptions.bind(null, dictionaryEntry));
+ const glossary = createCachedValue(getTermGlossaryArray.bind(null, dictionaryEntry, type));
+ const cloze = createCachedValue(getCloze.bind(null, dictionaryEntry, context));
+ const furiganaSegments = createCachedValue(getTermFuriganaSegments.bind(null, dictionaryEntry, type));
+ const sequence = createCachedValue(getTermDictionaryEntrySequence.bind(null, dictionaryEntry));
+
+ return {
+ type,
+ id: (type === 'term' && definitions.length > 0 ? definitions[0].id : void 0),
+ source: (primarySource !== null ? primarySource.transformedText : null),
+ rawSource: (primarySource !== null ? primarySource.originalText : null),
+ sourceTerm: (type !== 'termMerged' ? (primarySource !== null ? primarySource.deinflectedText : null) : void 0),
+ inflectionRuleChainCandidates,
+ score,
+ isPrimary: (type === 'term' ? dictionaryEntry.isPrimary : void 0),
+ get sequence() { return getCachedValue(sequence); },
+ get dictionary() { return getCachedValue(dictionaryNames)[0]; },
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ get dictionaryNames() { return getCachedValue(dictionaryNames); },
+ get expression() {
+ const {uniqueTerms} = getCachedValue(commonInfo);
+ return (type === 'term' || type === 'termGrouped' ? uniqueTerms[0] : uniqueTerms);
+ },
+ get reading() {
+ const {uniqueReadings} = getCachedValue(commonInfo);
+ return (type === 'term' || type === 'termGrouped' ? uniqueReadings[0] : uniqueReadings);
+ },
+ get expressions() { return getCachedValue(expressions); },
+ get glossary() { return getCachedValue(glossary); },
+ get definitionTags() { return type === 'term' ? getCachedValue(commonInfo).definitionTags : void 0; },
+ get termTags() { return getCachedValue(termTags); },
+ get definitions() { return getCachedValue(commonInfo).definitions; },
+ get frequencies() { return getCachedValue(frequencies); },
+ get pitches() { return getCachedValue(pitches); },
+ get phoneticTranscriptions() { return getCachedValue(phoneticTranscriptions); },
+ sourceTermExactMatchCount,
+ url,
+ get cloze() { return getCachedValue(cloze); },
+ get furiganaSegments() { return getCachedValue(furiganaSegments); }
+ };
+}
- /**
- * @param {import('dictionary').KanjiStat} kanjiStat
- * @returns {import('anki-templates').KanjiStat}
- */
- _convertKanjiStat({name, category, content, order, score, dictionary, value}) {
- return {
- name,
- category,
- notes: content,
- order,
- score,
- dictionary,
- value
- };
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {string[]}
+ */
+function getTermDictionaryNames(dictionaryEntry) {
+ const dictionaryNames = new Set();
+ for (const {dictionary} of dictionaryEntry.definitions) {
+ dictionaryNames.add(dictionary);
}
+ return [...dictionaryNames];
+}
- /**
- * @param {import('dictionary').KanjiDictionaryEntry} dictionaryEntry
- * @returns {import('anki-templates').KanjiFrequency[]}
- */
- _getKanjiFrequencies(dictionaryEntry) {
- /** @type {import('anki-templates').KanjiFrequency[]} */
- const results = [];
- for (const {index, dictionary, dictionaryIndex, dictionaryPriority, character, frequency, displayValue} of dictionaryEntry.frequencies) {
- results.push({
- index,
- dictionary,
- dictionaryOrder: {
- index: dictionaryIndex,
- priority: dictionaryPriority
- },
- character,
- frequency: displayValue !== null ? displayValue : frequency
- });
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates').TermDictionaryEntryType} type
+ * @returns {import('anki-templates').TermDictionaryEntryCommonInfo}
+ */
+function getTermDictionaryEntryCommonInfo(dictionaryEntry, type) {
+ const merged = (type === 'termMerged');
+ const hasDefinitions = (type !== 'term');
+
+ /** @type {Set<string>} */
+ const allTermsSet = new Set();
+ /** @type {Set<string>} */
+ const allReadingsSet = new Set();
+ for (const {term, reading} of dictionaryEntry.headwords) {
+ allTermsSet.add(term);
+ allReadingsSet.add(reading);
+ }
+ const uniqueTerms = [...allTermsSet];
+ const uniqueReadings = [...allReadingsSet];
+
+ /** @type {import('anki-templates').TermDefinition[]} */
+ const definitions = [];
+ /** @type {import('anki-templates').Tag[]} */
+ const definitionTags = [];
+ for (const {tags, headwordIndices, entries, dictionary, sequences} of dictionaryEntry.definitions) {
+ const definitionTags2 = [];
+ for (const tag of tags) {
+ definitionTags.push(convertTag(tag));
+ definitionTags2.push(convertTag(tag));
}
- return results;
+ if (!hasDefinitions) { continue; }
+ const only = merged ? getDisambiguations(dictionaryEntry.headwords, headwordIndices, allTermsSet, allReadingsSet) : void 0;
+ definitions.push({
+ sequence: sequences[0],
+ dictionary,
+ glossary: entries,
+ definitionTags: definitionTags2,
+ only
+ });
}
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @param {import('anki-templates-internal').Context} context
- * @param {import('settings').ResultOutputMode} resultOutputMode
- * @returns {import('anki-templates').TermDictionaryEntry}
- */
- _getTermDefinition(dictionaryEntry, context, resultOutputMode) {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const self = this;
-
- /** @type {import('anki-templates').TermDictionaryEntryType} */
- let type = 'term';
- switch (resultOutputMode) {
- case 'group': type = 'termGrouped'; break;
- case 'merge': type = 'termMerged'; break;
- }
+ return {
+ uniqueTerms,
+ uniqueReadings,
+ definitionTags,
+ definitions: hasDefinitions ? definitions : void 0
+ };
+}
- const {inflectionRuleChainCandidates, score, dictionaryIndex, dictionaryPriority, sourceTermExactMatchCount, definitions} = dictionaryEntry;
-
- let {url} = context;
- if (typeof url !== 'string') { url = ''; }
-
- const primarySource = this._getPrimarySource(dictionaryEntry);
-
- const dictionaryNames = this.createCachedValue(this._getTermDictionaryNames.bind(this, dictionaryEntry));
- const commonInfo = this.createCachedValue(this._getTermDictionaryEntryCommonInfo.bind(this, dictionaryEntry, type));
- const termTags = this.createCachedValue(this._getTermTags.bind(this, dictionaryEntry, type));
- 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));
- const sequence = this.createCachedValue(this._getTermDictionaryEntrySequence.bind(this, dictionaryEntry));
-
- return {
- type,
- id: (type === 'term' && definitions.length > 0 ? definitions[0].id : void 0),
- source: (primarySource !== null ? primarySource.transformedText : null),
- rawSource: (primarySource !== null ? primarySource.originalText : null),
- sourceTerm: (type !== 'termMerged' ? (primarySource !== null ? primarySource.deinflectedText : null) : void 0),
- inflectionRuleChainCandidates,
- score,
- isPrimary: (type === 'term' ? dictionaryEntry.isPrimary : void 0),
- get sequence() { return self.getCachedValue(sequence); },
- get dictionary() { return self.getCachedValue(dictionaryNames)[0]; },
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').TermFrequency[]}
+ */
+function getTermFrequencies(dictionaryEntry) {
+ const results = [];
+ const {headwords} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, hasReading, frequency, displayValue} of dictionaryEntry.frequencies) {
+ const {term, reading} = headwords[headwordIndex];
+ results.push({
+ index: results.length,
+ expressionIndex: headwordIndex,
+ dictionary,
dictionaryOrder: {
index: dictionaryIndex,
priority: dictionaryPriority
},
- get dictionaryNames() { return self.getCachedValue(dictionaryNames); },
- get expression() {
- const {uniqueTerms} = self.getCachedValue(commonInfo);
- return (type === 'term' || type === 'termGrouped' ? uniqueTerms[0] : uniqueTerms);
- },
- get reading() {
- const {uniqueReadings} = self.getCachedValue(commonInfo);
- return (type === 'term' || type === 'termGrouped' ? uniqueReadings[0] : uniqueReadings);
- },
- get expressions() { return self.getCachedValue(expressions); },
- get glossary() { return self.getCachedValue(glossary); },
- get definitionTags() { return type === 'term' ? self.getCachedValue(commonInfo).definitionTags : void 0; },
- get termTags() { return self.getCachedValue(termTags); },
- 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); },
- get furiganaSegments() { return self.getCachedValue(furiganaSegments); }
- };
+ expression: term,
+ reading,
+ hasReading,
+ frequency: displayValue !== null ? displayValue : frequency
+ });
}
+ return results;
+}
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @returns {string[]}
- */
- _getTermDictionaryNames(dictionaryEntry) {
- const dictionaryNames = new Set();
- for (const {dictionary} of dictionaryEntry.definitions) {
- dictionaryNames.add(dictionary);
- }
- return [...dictionaryNames];
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').TermPitchAccent[]}
+ */
+function getTermPitches(dictionaryEntry) {
+ const results = [];
+ const {headwords} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations} of dictionaryEntry.pronunciations) {
+ const {term, reading} = headwords[headwordIndex];
+ const pitches = getPronunciationsOfType(pronunciations, 'pitch-accent');
+ const cachedPitches = createCachedValue(getTermPitchesInner.bind(null, pitches));
+ results.push({
+ index: results.length,
+ expressionIndex: headwordIndex,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ expression: term,
+ reading,
+ get pitches() { return getCachedValue(cachedPitches); }
+ });
}
+ return results;
+}
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @param {import('anki-templates').TermDictionaryEntryType} type
- * @returns {import('anki-templates').TermDictionaryEntryCommonInfo}
- */
- _getTermDictionaryEntryCommonInfo(dictionaryEntry, type) {
- const merged = (type === 'termMerged');
- const hasDefinitions = (type !== 'term');
-
- /** @type {Set<string>} */
- const allTermsSet = new Set();
- /** @type {Set<string>} */
- const allReadingsSet = new Set();
- for (const {term, reading} of dictionaryEntry.headwords) {
- allTermsSet.add(term);
- allReadingsSet.add(reading);
- }
- const uniqueTerms = [...allTermsSet];
- const uniqueReadings = [...allReadingsSet];
-
- /** @type {import('anki-templates').TermDefinition[]} */
- const definitions = [];
- /** @type {import('anki-templates').Tag[]} */
- const definitionTags = [];
- for (const {tags, headwordIndices, entries, dictionary, sequences} of dictionaryEntry.definitions) {
- const definitionTags2 = [];
- for (const tag of tags) {
- definitionTags.push(this._convertTag(tag));
- definitionTags2.push(this._convertTag(tag));
- }
- if (!hasDefinitions) { continue; }
- const only = merged ? DictionaryDataUtil.getDisambiguations(dictionaryEntry.headwords, headwordIndices, allTermsSet, allReadingsSet) : void 0;
- definitions.push({
- sequence: sequences[0],
- dictionary,
- glossary: entries,
- definitionTags: definitionTags2,
- only
- });
- }
-
- return {
- uniqueTerms,
- uniqueReadings,
- definitionTags,
- definitions: hasDefinitions ? definitions : void 0
- };
+/**
+ * @param {import('dictionary').PitchAccent[]} pitches
+ * @returns {import('anki-templates').PitchAccent[]}
+ */
+function getTermPitchesInner(pitches) {
+ const results = [];
+ for (const {position, tags} of pitches) {
+ const cachedTags = createCachedValue(convertTags.bind(null, tags));
+ results.push({
+ position,
+ get tags() { return getCachedValue(cachedTags); }
+ });
}
+ return results;
+}
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @returns {import('anki-templates').TermFrequency[]}
- */
- _getTermFrequencies(dictionaryEntry) {
- const results = [];
- const {headwords} = dictionaryEntry;
- for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, hasReading, frequency, displayValue} of dictionaryEntry.frequencies) {
- const {term, reading} = headwords[headwordIndex];
- results.push({
- index: results.length,
- expressionIndex: headwordIndex,
- dictionary,
- dictionaryOrder: {
- index: dictionaryIndex,
- priority: dictionaryPriority
- },
- expression: term,
- reading,
- hasReading,
- frequency: displayValue !== null ? displayValue : frequency
- });
- }
- return results;
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').TermPhoneticTranscription[]}
+ */
+function getTermPhoneticTranscriptions(dictionaryEntry) {
+ const results = [];
+ const {headwords} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, pronunciations} of dictionaryEntry.pronunciations) {
+ const {term, reading} = headwords[headwordIndex];
+ const phoneticTranscriptions = getPronunciationsOfType(pronunciations, 'phonetic-transcription');
+ const termPhoneticTranscriptions = getTermPhoneticTranscriptionsInner(phoneticTranscriptions);
+ results.push({
+ index: results.length,
+ expressionIndex: headwordIndex,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ expression: term,
+ reading,
+ get phoneticTranscriptions() { return termPhoneticTranscriptions; }
+ });
}
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @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, 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,
- expressionIndex: headwordIndex,
- dictionary,
- dictionaryOrder: {
- index: dictionaryIndex,
- priority: dictionaryPriority
- },
- expression: term,
- reading,
- get pitches() { return self.getCachedValue(cachedPitches); }
- });
- }
- return results;
- }
+ return results;
+}
- /**
- * @param {import('dictionary').PitchAccent[]} pitches
- * @returns {import('anki-templates').PitchAccent[]}
- */
- _getTermPitchesInner(pitches) {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const self = this;
- const results = [];
- for (const {position, tags} of pitches) {
- const cachedTags = this.createCachedValue(this._convertTags.bind(this, tags));
- results.push({
- position,
- get tags() { return self.getCachedValue(cachedTags); }
- });
- }
- return results;
+/**
+ * @param {import('dictionary').PhoneticTranscription[]} phoneticTranscriptions
+ * @returns {import('anki-templates').PhoneticTranscription[]}
+ */
+function getTermPhoneticTranscriptionsInner(phoneticTranscriptions) {
+ const results = [];
+ for (const {ipa, tags} of phoneticTranscriptions) {
+ const cachedTags = createCachedValue(convertTags.bind(null, tags));
+ results.push({
+ ipa,
+ get tags() { return getCachedValue(cachedTags); }
+ });
}
+ 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').TermDictionaryEntry} dictionaryEntry
+ * @returns {import('anki-templates').TermHeadword[]}
+ */
+function getTermExpressions(dictionaryEntry) {
+ const results = [];
+ const {headwords} = dictionaryEntry;
+ for (let i = 0, ii = headwords.length; i < ii; ++i) {
+ const {term, reading, tags, sources: [{deinflectedText}], wordClasses} = headwords[i];
+ const termTags = createCachedValue(convertTags.bind(null, tags));
+ const frequencies = createCachedValue(getTermExpressionFrequencies.bind(null, dictionaryEntry, i));
+ const pitches = createCachedValue(getTermExpressionPitches.bind(null, dictionaryEntry, i));
+ const termFrequency = createCachedValue(getTermExpressionTermFrequency.bind(null, termTags));
+ const furiganaSegments = createCachedValue(getTermHeadwordFuriganaSegments.bind(null, term, reading));
+ const item = {
+ sourceTerm: deinflectedText,
+ expression: term,
+ reading,
+ get termTags() { return getCachedValue(termTags); },
+ get frequencies() { return getCachedValue(frequencies); },
+ get pitches() { return getCachedValue(pitches); },
+ get furiganaSegments() { return getCachedValue(furiganaSegments); },
+ get termFrequency() { return getCachedValue(termFrequency); },
+ wordClasses
+ };
+ results.push(item);
}
+ 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
+ * @param {number} i
+ * @returns {import('anki-templates').TermFrequency[]}
+ */
+function getTermExpressionFrequencies(dictionaryEntry, i) {
+ const results = [];
+ const {headwords, frequencies} = dictionaryEntry;
+ for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, hasReading, frequency, displayValue} of frequencies) {
+ if (headwordIndex !== i) { continue; }
+ const {term, reading} = headwords[headwordIndex];
+ results.push({
+ index: results.length,
+ expressionIndex: headwordIndex,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ expression: term,
+ reading,
+ hasReading,
+ frequency: displayValue !== null ? displayValue : frequency
+ });
}
+ return results;
+}
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @returns {import('anki-templates').TermHeadword[]}
- */
- _getTermExpressions(dictionaryEntry) {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const self = this;
- const results = [];
- const {headwords} = dictionaryEntry;
- for (let i = 0, ii = headwords.length; i < ii; ++i) {
- const {term, reading, tags, sources: [{deinflectedText}], wordClasses} = headwords[i];
- const termTags = this.createCachedValue(this._convertTags.bind(this, tags));
- const frequencies = this.createCachedValue(this._getTermExpressionFrequencies.bind(this, dictionaryEntry, i));
- const pitches = this.createCachedValue(this._getTermExpressionPitches.bind(this, dictionaryEntry, i));
- const termFrequency = this.createCachedValue(this._getTermExpressionTermFrequency.bind(this, termTags));
- const furiganaSegments = this.createCachedValue(this._getTermHeadwordFuriganaSegments.bind(this, term, reading));
- const item = {
- sourceTerm: deinflectedText,
- expression: term,
- reading,
- get termTags() { return self.getCachedValue(termTags); },
- get frequencies() { return self.getCachedValue(frequencies); },
- get pitches() { return self.getCachedValue(pitches); },
- get furiganaSegments() { return self.getCachedValue(furiganaSegments); },
- get termFrequency() { return self.getCachedValue(termFrequency); },
- wordClasses
- };
- results.push(item);
- }
- return results;
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @param {number} i
+ * @returns {import('anki-templates').TermPitchAccent[]}
+ */
+function getTermExpressionPitches(dictionaryEntry, i) {
+ const results = [];
+ 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 = getPronunciationsOfType(pronunciations, 'pitch-accent');
+ const cachedPitches = createCachedValue(getTermPitchesInner.bind(null, pitches));
+ results.push({
+ index: results.length,
+ expressionIndex: headwordIndex,
+ dictionary,
+ dictionaryOrder: {
+ index: dictionaryIndex,
+ priority: dictionaryPriority
+ },
+ expression: term,
+ reading,
+ get pitches() { return getCachedValue(cachedPitches); }
+ });
}
+ return results;
+}
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @param {number} i
- * @returns {import('anki-templates').TermFrequency[]}
- */
- _getTermExpressionFrequencies(dictionaryEntry, i) {
- const results = [];
- const {headwords, frequencies} = dictionaryEntry;
- for (const {headwordIndex, dictionary, dictionaryIndex, dictionaryPriority, hasReading, frequency, displayValue} of frequencies) {
- if (headwordIndex !== i) { continue; }
- const {term, reading} = headwords[headwordIndex];
- results.push({
- index: results.length,
- expressionIndex: headwordIndex,
- dictionary,
- dictionaryOrder: {
- index: dictionaryIndex,
- priority: dictionaryPriority
- },
- expression: term,
- reading,
- hasReading,
- frequency: displayValue !== null ? displayValue : frequency
- });
- }
- return results;
- }
+/**
+ * @param {import('anki-templates-internal').CachedValue<import('anki-templates').Tag[]>} cachedTermTags
+ * @returns {import('anki-templates').TermFrequencyType}
+ */
+function getTermExpressionTermFrequency(cachedTermTags) {
+ const termTags = getCachedValue(cachedTermTags);
+ return getTermFrequency(termTags);
+}
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @param {number} i
- * @returns {import('anki-templates').TermPitchAccent[]}
- */
- _getTermExpressionPitches(dictionaryEntry, i) {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const self = this;
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates').TermDictionaryEntryType} type
+ * @returns {import('dictionary-data').TermGlossary[]|undefined}
+ */
+function getTermGlossaryArray(dictionaryEntry, type) {
+ if (type === 'term') {
const results = [];
- 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,
- expressionIndex: headwordIndex,
- dictionary,
- dictionaryOrder: {
- index: dictionaryIndex,
- priority: dictionaryPriority
- },
- expression: term,
- reading,
- get pitches() { return self.getCachedValue(cachedPitches); }
- });
+ for (const {entries} of dictionaryEntry.definitions) {
+ results.push(...entries);
}
return results;
}
+ return void 0;
+}
- /**
- * @param {import('anki-templates-internal').CachedValue<import('anki-templates').Tag[]>} cachedTermTags
- * @returns {import('anki-templates').TermFrequencyType}
- */
- _getTermExpressionTermFrequency(cachedTermTags) {
- const termTags = this.getCachedValue(cachedTermTags);
- return DictionaryDataUtil.getTermFrequency(termTags);
- }
-
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @param {import('anki-templates').TermDictionaryEntryType} type
- * @returns {import('dictionary-data').TermGlossary[]|undefined}
- */
- _getTermGlossaryArray(dictionaryEntry, type) {
- if (type === 'term') {
- const results = [];
- for (const {entries} of dictionaryEntry.definitions) {
- results.push(...entries);
- }
- return results;
- }
- return void 0;
- }
-
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @param {import('anki-templates').TermDictionaryEntryType} type
- * @returns {import('anki-templates').Tag[]|undefined}
- */
- _getTermTags(dictionaryEntry, type) {
- if (type !== 'termMerged') {
- const results = [];
- for (const {tag} of DictionaryDataUtil.groupTermTags(dictionaryEntry)) {
- results.push(this._convertTag(tag));
- }
- return results;
- }
- return void 0;
- }
-
- /**
- * @param {import('dictionary').Tag[]} tags
- * @returns {import('anki-templates').Tag[]}
- */
- _convertTags(tags) {
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates').TermDictionaryEntryType} type
+ * @returns {import('anki-templates').Tag[]|undefined}
+ */
+function getTermTags(dictionaryEntry, type) {
+ if (type !== 'termMerged') {
const results = [];
- for (const tag of tags) {
- results.push(this._convertTag(tag));
+ for (const {tag} of groupTermTags(dictionaryEntry)) {
+ results.push(convertTag(tag));
}
return results;
}
+ return void 0;
+}
- /**
- * @param {import('dictionary').Tag} tag
- * @returns {import('anki-templates').Tag}
- */
- _convertTag({name, category, content, order, score, dictionaries, redundant}) {
- return {
- name,
- category,
- notes: (content.length > 0 ? content[0] : ''),
- order,
- score,
- dictionary: (dictionaries.length > 0 ? dictionaries[0] : ''),
- redundant
- };
+/**
+ * @param {import('dictionary').Tag[]} tags
+ * @returns {import('anki-templates').Tag[]}
+ */
+function convertTags(tags) {
+ const results = [];
+ for (const tag of tags) {
+ results.push(convertTag(tag));
}
+ return results;
+}
- /**
- * @param {import('dictionary').Tag[]} tags
- * @returns {import('anki-templates').PitchTag[]}
- */
- _convertPitchTags(tags) {
- const results = [];
- for (const tag of tags) {
- results.push(this._convertPitchTag(tag));
- }
- return results;
- }
+/**
+ * @param {import('dictionary').Tag} tag
+ * @returns {import('anki-templates').Tag}
+ */
+function convertTag({name, category, content, order, score, dictionaries, redundant}) {
+ return {
+ name,
+ category,
+ notes: (content.length > 0 ? content[0] : ''),
+ order,
+ score,
+ dictionary: (dictionaries.length > 0 ? dictionaries[0] : ''),
+ redundant
+ };
+}
- /**
- * @param {import('dictionary').Tag} tag
- * @returns {import('anki-templates').PitchTag}
- */
- _convertPitchTag({name, category, content, order, score, dictionaries, redundant}) {
- return {
- name,
- category,
- order,
- score,
- content: [...content],
- dictionaries: [...dictionaries],
- redundant
- };
+/**
+ * @param {import('dictionary').Tag[]} tags
+ * @returns {import('anki-templates').PitchTag[]}
+ */
+function convertPitchTags(tags) {
+ const results = [];
+ for (const tag of tags) {
+ results.push(convertPitchTag(tag));
}
+ return results;
+}
- /**
- * @param {import('dictionary').DictionaryEntry} dictionaryEntry
- * @param {import('anki-templates-internal').Context} context
- * @returns {import('anki-templates').Cloze}
- */
- _getCloze(dictionaryEntry, context) {
- let originalText = '';
- switch (dictionaryEntry.type) {
- case 'term':
- {
- const primarySource = this._getPrimarySource(dictionaryEntry);
- if (primarySource !== null) { originalText = primarySource.originalText; }
- }
- break;
- case 'kanji':
- originalText = dictionaryEntry.character;
- break;
- }
-
- const {sentence} = context;
- let text;
- let offset;
- if (typeof sentence === 'object' && sentence !== null) {
- ({text, offset} = sentence);
- }
- if (typeof text !== 'string') { text = ''; }
- if (typeof offset !== 'number') { offset = 0; }
-
- return {
- sentence: text,
- prefix: text.substring(0, offset),
- body: text.substring(offset, offset + originalText.length),
- suffix: text.substring(offset + originalText.length)
- };
- }
+/**
+ * @param {import('dictionary').Tag} tag
+ * @returns {import('anki-templates').PitchTag}
+ */
+function convertPitchTag({name, category, content, order, score, dictionaries, redundant}) {
+ return {
+ name,
+ category,
+ order,
+ score,
+ content: [...content],
+ dictionaries: [...dictionaries],
+ redundant
+ };
+}
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @param {import('anki-templates').TermDictionaryEntryType} type
- * @returns {import('anki-templates').FuriganaSegment[]|undefined}
- */
- _getTermFuriganaSegments(dictionaryEntry, type) {
- if (type === 'term') {
- for (const {term, reading} of dictionaryEntry.headwords) {
- return this._getTermHeadwordFuriganaSegments(term, reading);
+/**
+ * @param {import('dictionary').DictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates-internal').Context} context
+ * @returns {import('anki-templates').Cloze}
+ */
+function getCloze(dictionaryEntry, context) {
+ let originalText = '';
+ switch (dictionaryEntry.type) {
+ case 'term':
+ {
+ const primarySource = getPrimarySource(dictionaryEntry);
+ if (primarySource !== null) { originalText = primarySource.originalText; }
}
+ break;
+ case 'kanji':
+ originalText = dictionaryEntry.character;
+ break;
+ }
+
+ const {sentence} = context;
+ let text;
+ let offset;
+ if (typeof sentence === 'object' && sentence !== null) {
+ ({text, offset} = sentence);
+ }
+ if (typeof text !== 'string') { text = ''; }
+ if (typeof offset !== 'number') { offset = 0; }
+
+ return {
+ sentence: text,
+ prefix: text.substring(0, offset),
+ body: text.substring(offset, offset + originalText.length),
+ suffix: text.substring(offset + originalText.length)
+ };
+}
+
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @param {import('anki-templates').TermDictionaryEntryType} type
+ * @returns {import('anki-templates').FuriganaSegment[]|undefined}
+ */
+function getTermFuriganaSegments(dictionaryEntry, type) {
+ if (type === 'term') {
+ for (const {term, reading} of dictionaryEntry.headwords) {
+ return getTermHeadwordFuriganaSegments(term, reading);
}
- return void 0;
}
+ return void 0;
+}
- /**
- * @param {string} term
- * @param {string} reading
- * @returns {import('anki-templates').FuriganaSegment[]}
- */
- _getTermHeadwordFuriganaSegments(term, reading) {
- /** @type {import('anki-templates').FuriganaSegment[]} */
- const result = [];
- for (const {text, reading: reading2} of distributeFurigana(term, reading)) {
- result.push({text, furigana: reading2});
- }
- return result;
+/**
+ * @param {string} term
+ * @param {string} reading
+ * @returns {import('anki-templates').FuriganaSegment[]}
+ */
+function getTermHeadwordFuriganaSegments(term, reading) {
+ /** @type {import('anki-templates').FuriganaSegment[]} */
+ const result = [];
+ for (const {text, reading: reading2} of distributeFurigana(term, reading)) {
+ result.push({text, furigana: reading2});
}
+ return result;
+}
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @returns {number}
- */
- _getTermDictionaryEntrySequence(dictionaryEntry) {
- let hasSequence = false;
- let mainSequence = -1;
- if (!dictionaryEntry.isPrimary) { return mainSequence; }
- for (const {sequences} of dictionaryEntry.definitions) {
- const sequence = sequences[0];
- if (!hasSequence) {
- mainSequence = sequence;
- hasSequence = true;
- if (mainSequence === -1) { break; }
- } else if (mainSequence !== sequence) {
- mainSequence = -1;
- break;
- }
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {number}
+ */
+function getTermDictionaryEntrySequence(dictionaryEntry) {
+ let hasSequence = false;
+ let mainSequence = -1;
+ if (!dictionaryEntry.isPrimary) { return mainSequence; }
+ for (const {sequences} of dictionaryEntry.definitions) {
+ const sequence = sequences[0];
+ if (!hasSequence) {
+ mainSequence = sequence;
+ hasSequence = true;
+ if (mainSequence === -1) { break; }
+ } else if (mainSequence !== sequence) {
+ mainSequence = -1;
+ break;
}
- return mainSequence;
}
+ return mainSequence;
}
diff --git a/ext/js/data/sandbox/array-buffer-util.js b/ext/js/data/sandbox/array-buffer-util.js
index 1857ec74..487fcd24 100644
--- a/ext/js/data/sandbox/array-buffer-util.js
+++ b/ext/js/data/sandbox/array-buffer-util.js
@@ -17,61 +17,56 @@
*/
/**
- * Class containing generic ArrayBuffer utility functions.
+ * Decodes the contents of an ArrayBuffer using UTF8.
+ * @param {ArrayBuffer} arrayBuffer The input ArrayBuffer.
+ * @returns {string} A UTF8-decoded string.
*/
-export class ArrayBufferUtil {
- /**
- * Decodes the contents of an ArrayBuffer using UTF8.
- * @param {ArrayBuffer} arrayBuffer The input ArrayBuffer.
- * @returns {string} A UTF8-decoded string.
- */
- static arrayBufferUtf8Decode(arrayBuffer) {
- try {
- return new TextDecoder('utf-8').decode(arrayBuffer);
- } catch (e) {
- return decodeURIComponent(escape(this.arrayBufferToBinaryString(arrayBuffer)));
- }
+export function arrayBufferUtf8Decode(arrayBuffer) {
+ try {
+ return new TextDecoder('utf-8').decode(arrayBuffer);
+ } catch (e) {
+ return decodeURIComponent(escape(arrayBufferToBinaryString(arrayBuffer)));
}
+}
- /**
- * Converts the contents of an ArrayBuffer to a base64 string.
- * @param {ArrayBuffer} arrayBuffer The input ArrayBuffer.
- * @returns {string} A base64 string representing the binary content.
- */
- static arrayBufferToBase64(arrayBuffer) {
- return btoa(this.arrayBufferToBinaryString(arrayBuffer));
- }
+/**
+ * Converts the contents of an ArrayBuffer to a base64 string.
+ * @param {ArrayBuffer} arrayBuffer The input ArrayBuffer.
+ * @returns {string} A base64 string representing the binary content.
+ */
+export function arrayBufferToBase64(arrayBuffer) {
+ return btoa(arrayBufferToBinaryString(arrayBuffer));
+}
- /**
- * Converts the contents of an ArrayBuffer to a binary string.
- * @param {ArrayBuffer} arrayBuffer The input ArrayBuffer.
- * @returns {string} A string representing the binary content.
- */
- static arrayBufferToBinaryString(arrayBuffer) {
- const bytes = new Uint8Array(arrayBuffer);
- try {
- return String.fromCharCode(...bytes);
- } catch (e) {
- let binary = '';
- for (let i = 0, ii = bytes.byteLength; i < ii; ++i) {
- binary += String.fromCharCode(bytes[i]);
- }
- return binary;
+/**
+ * Converts the contents of an ArrayBuffer to a binary string.
+ * @param {ArrayBuffer} arrayBuffer The input ArrayBuffer.
+ * @returns {string} A string representing the binary content.
+ */
+export function arrayBufferToBinaryString(arrayBuffer) {
+ const bytes = new Uint8Array(arrayBuffer);
+ try {
+ return String.fromCharCode(...bytes);
+ } catch (e) {
+ let binary = '';
+ for (let i = 0, ii = bytes.byteLength; i < ii; ++i) {
+ binary += String.fromCharCode(bytes[i]);
}
+ return binary;
}
+}
- /**
- * Converts a base64 string to an ArrayBuffer.
- * @param {string} content The binary content string encoded in base64.
- * @returns {ArrayBuffer} A new `ArrayBuffer` object corresponding to the specified content.
- */
- static base64ToArrayBuffer(content) {
- const binaryContent = atob(content);
- const length = binaryContent.length;
- const array = new Uint8Array(length);
- for (let i = 0; i < length; ++i) {
- array[i] = binaryContent.charCodeAt(i);
- }
- return array.buffer;
+/**
+ * Converts a base64 string to an ArrayBuffer.
+ * @param {string} content The binary content string encoded in base64.
+ * @returns {ArrayBuffer} A new `ArrayBuffer` object corresponding to the specified content.
+ */
+export function base64ToArrayBuffer(content) {
+ const binaryContent = atob(content);
+ const length = binaryContent.length;
+ const array = new Uint8Array(length);
+ for (let i = 0; i < length; ++i) {
+ array[i] = binaryContent.charCodeAt(i);
}
+ return array.buffer;
}
diff --git a/ext/js/data/sandbox/string-util.js b/ext/js/data/sandbox/string-util.js
index 3bc1c549..45e52f08 100644
--- a/ext/js/data/sandbox/string-util.js
+++ b/ext/js/data/sandbox/string-util.js
@@ -17,59 +17,54 @@
*/
/**
- * Class containing generic string utility functions.
+ * Reads code points from a string in the forward direction.
+ * @param {string} text The text to read the code points from.
+ * @param {number} position The index of the first character to read.
+ * @param {number} count The number of code points to read.
+ * @returns {string} The code points from the string.
*/
-export class StringUtil {
- /**
- * Reads code points from a string in the forward direction.
- * @param {string} text The text to read the code points from.
- * @param {number} position The index of the first character to read.
- * @param {number} count The number of code points to read.
- * @returns {string} The code points from the string.
- */
- static readCodePointsForward(text, position, count) {
- const textLength = text.length;
- let result = '';
- for (; count > 0; --count) {
- const char = text[position];
- result += char;
- if (++position >= textLength) { break; }
- const charCode = char.charCodeAt(0);
- if (charCode >= 0xd800 && charCode < 0xdc00) { // charCode is a high surrogate code
- const char2 = text[position];
- const charCode2 = char2.charCodeAt(0);
- if (charCode2 >= 0xdc00 && charCode2 < 0xe000) { // charCode2 is a low surrogate code
- result += char2;
- if (++position >= textLength) { break; }
- }
+export function readCodePointsForward(text, position, count) {
+ const textLength = text.length;
+ let result = '';
+ for (; count > 0; --count) {
+ const char = text[position];
+ result += char;
+ if (++position >= textLength) { break; }
+ const charCode = char.charCodeAt(0);
+ if (charCode >= 0xd800 && charCode < 0xdc00) { // charCode is a high surrogate code
+ const char2 = text[position];
+ const charCode2 = char2.charCodeAt(0);
+ if (charCode2 >= 0xdc00 && charCode2 < 0xe000) { // charCode2 is a low surrogate code
+ result += char2;
+ if (++position >= textLength) { break; }
}
}
- return result;
}
+ return result;
+}
- /**
- * Reads code points from a string in the backward direction.
- * @param {string} text The text to read the code points from.
- * @param {number} position The index of the first character to read.
- * @param {number} count The number of code points to read.
- * @returns {string} The code points from the string.
- */
- static readCodePointsBackward(text, position, count) {
- let result = '';
- for (; count > 0; --count) {
- const char = text[position];
- result = char + result;
- if (--position < 0) { break; }
- const charCode = char.charCodeAt(0);
- if (charCode >= 0xdc00 && charCode < 0xe000) { // charCode is a low surrogate code
- const char2 = text[position];
- const charCode2 = char2.charCodeAt(0);
- if (charCode2 >= 0xd800 && charCode2 < 0xdc00) { // charCode2 is a high surrogate code
- result = char2 + result;
- if (--position < 0) { break; }
- }
+/**
+ * Reads code points from a string in the backward direction.
+ * @param {string} text The text to read the code points from.
+ * @param {number} position The index of the first character to read.
+ * @param {number} count The number of code points to read.
+ * @returns {string} The code points from the string.
+ */
+export function readCodePointsBackward(text, position, count) {
+ let result = '';
+ for (; count > 0; --count) {
+ const char = text[position];
+ result = char + result;
+ if (--position < 0) { break; }
+ const charCode = char.charCodeAt(0);
+ if (charCode >= 0xdc00 && charCode < 0xe000) { // charCode is a low surrogate code
+ const char2 = text[position];
+ const charCode2 = char2.charCodeAt(0);
+ if (charCode2 >= 0xd800 && charCode2 < 0xdc00) { // charCode2 is a high surrogate code
+ result = char2 + result;
+ if (--position < 0) { break; }
}
}
- return result;
}
+ return result;
}
diff --git a/ext/js/dictionary/dictionary-data-util.js b/ext/js/dictionary/dictionary-data-util.js
index 9b49c7af..a2a106cc 100644
--- a/ext/js/dictionary/dictionary-data-util.js
+++ b/ext/js/dictionary/dictionary-data-util.js
@@ -16,411 +16,409 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-export class DictionaryDataUtil {
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @returns {import('dictionary-data-util').TagGroup[]}
- */
- static groupTermTags(dictionaryEntry) {
- const {headwords} = dictionaryEntry;
- const headwordCount = headwords.length;
- const uniqueCheck = (headwordCount > 1);
- const resultsIndexMap = new Map();
- const results = [];
- for (let i = 0; i < headwordCount; ++i) {
- const {tags} = headwords[i];
- for (const tag of tags) {
- if (uniqueCheck) {
- const {name, category, content, dictionaries} = tag;
- const key = this._createMapKey([name, category, content, dictionaries]);
- const index = resultsIndexMap.get(key);
- if (typeof index !== 'undefined') {
- const existingItem = results[index];
- existingItem.headwordIndices.push(i);
- continue;
- }
- resultsIndexMap.set(key, results.length);
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {import('dictionary-data-util').TagGroup[]}
+ */
+export function groupTermTags(dictionaryEntry) {
+ const {headwords} = dictionaryEntry;
+ const headwordCount = headwords.length;
+ const uniqueCheck = (headwordCount > 1);
+ const resultsIndexMap = new Map();
+ const results = [];
+ for (let i = 0; i < headwordCount; ++i) {
+ const {tags} = headwords[i];
+ for (const tag of tags) {
+ if (uniqueCheck) {
+ const {name, category, content, dictionaries} = tag;
+ const key = createMapKey([name, category, content, dictionaries]);
+ const index = resultsIndexMap.get(key);
+ if (typeof index !== 'undefined') {
+ const existingItem = results[index];
+ existingItem.headwordIndices.push(i);
+ continue;
}
-
- const item = {tag, headwordIndices: [i]};
- results.push(item);
+ resultsIndexMap.set(key, results.length);
}
+
+ const item = {tag, headwordIndices: [i]};
+ results.push(item);
}
- return results;
}
+ return results;
+}
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @returns {import('dictionary-data-util').DictionaryFrequency<import('dictionary-data-util').TermFrequency>[]}
- */
- static groupTermFrequencies(dictionaryEntry) {
- const {headwords, frequencies: sourceFrequencies} = dictionaryEntry;
-
- /** @type {import('dictionary-data-util').TermFrequenciesMap1} */
- const map1 = new Map();
- for (const {headwordIndex, dictionary, hasReading, frequency, displayValue} of sourceFrequencies) {
- const {term, reading} = headwords[headwordIndex];
-
- let map2 = map1.get(dictionary);
- if (typeof map2 === 'undefined') {
- map2 = new Map();
- map1.set(dictionary, map2);
- }
-
- const readingKey = hasReading ? reading : null;
- const key = this._createMapKey([term, readingKey]);
- let frequencyData = map2.get(key);
- if (typeof frequencyData === 'undefined') {
- frequencyData = {term, reading: readingKey, values: new Map()};
- map2.set(key, frequencyData);
- }
-
- frequencyData.values.set(this._createMapKey([frequency, displayValue]), {frequency, displayValue});
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {import('dictionary-data-util').DictionaryFrequency<import('dictionary-data-util').TermFrequency>[]}
+ */
+export function groupTermFrequencies(dictionaryEntry) {
+ const {headwords, frequencies: sourceFrequencies} = dictionaryEntry;
+
+ /** @type {import('dictionary-data-util').TermFrequenciesMap1} */
+ const map1 = new Map();
+ for (const {headwordIndex, dictionary, hasReading, frequency, displayValue} of sourceFrequencies) {
+ const {term, reading} = headwords[headwordIndex];
+
+ let map2 = map1.get(dictionary);
+ if (typeof map2 === 'undefined') {
+ map2 = new Map();
+ map1.set(dictionary, map2);
}
- const results = [];
- for (const [dictionary, map2] of map1.entries()) {
- const frequencies = [];
- for (const {term, reading, values} of map2.values()) {
- frequencies.push({
- term,
- reading,
- values: [...values.values()]
- });
- }
- results.push({dictionary, frequencies});
+ const readingKey = hasReading ? reading : null;
+ const key = createMapKey([term, readingKey]);
+ let frequencyData = map2.get(key);
+ if (typeof frequencyData === 'undefined') {
+ frequencyData = {term, reading: readingKey, values: new Map()};
+ map2.set(key, frequencyData);
}
- return results;
- }
- /**
- * @param {import('dictionary').KanjiFrequency[]} sourceFrequencies
- * @returns {import('dictionary-data-util').DictionaryFrequency<import('dictionary-data-util').KanjiFrequency>[]}
- */
- static groupKanjiFrequencies(sourceFrequencies) {
- /** @type {import('dictionary-data-util').KanjiFrequenciesMap1} */
- const map1 = new Map();
- for (const {dictionary, character, frequency, displayValue} of sourceFrequencies) {
- let map2 = map1.get(dictionary);
- if (typeof map2 === 'undefined') {
- map2 = new Map();
- map1.set(dictionary, map2);
- }
-
- let frequencyData = map2.get(character);
- if (typeof frequencyData === 'undefined') {
- frequencyData = {character, values: new Map()};
- map2.set(character, frequencyData);
- }
+ frequencyData.values.set(createMapKey([frequency, displayValue]), {frequency, displayValue});
+ }
- frequencyData.values.set(this._createMapKey([frequency, displayValue]), {frequency, displayValue});
+ const results = [];
+ for (const [dictionary, map2] of map1.entries()) {
+ const frequencies = [];
+ for (const {term, reading, values} of map2.values()) {
+ frequencies.push({
+ term,
+ reading,
+ values: [...values.values()]
+ });
}
-
- const results = [];
- for (const [dictionary, map2] of map1.entries()) {
- const frequencies = [];
- for (const {character, values} of map2.values()) {
- frequencies.push({
- character,
- values: [...values.values()]
- });
- }
- results.push({dictionary, frequencies});
- }
- return results;
+ results.push({dictionary, frequencies});
}
+ return results;
+}
- /**
- * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
- * @returns {import('dictionary-data-util').DictionaryGroupedPronunciations[]}
- */
- static getGroupedPronunciations(dictionaryEntry) {
- const {headwords, pronunciations: termPronunciations} = dictionaryEntry;
-
- const allTerms = new Set();
- const allReadings = new Set();
- for (const {term, reading} of headwords) {
- allTerms.add(term);
- allReadings.add(reading);
+/**
+ * @param {import('dictionary').KanjiFrequency[]} sourceFrequencies
+ * @returns {import('dictionary-data-util').DictionaryFrequency<import('dictionary-data-util').KanjiFrequency>[]}
+ */
+export function groupKanjiFrequencies(sourceFrequencies) {
+ /** @type {import('dictionary-data-util').KanjiFrequenciesMap1} */
+ const map1 = new Map();
+ for (const {dictionary, character, frequency, displayValue} of sourceFrequencies) {
+ let map2 = map1.get(dictionary);
+ if (typeof map2 === 'undefined') {
+ map2 = new Map();
+ map1.set(dictionary, map2);
}
- /** @type {Map<string, import('dictionary-data-util').GroupedPronunciationInternal[]>} */
- const groupedPronunciationsMap = new Map();
- 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 pronunciation of pronunciations) {
- let groupedPronunciation = this._findExistingGroupedPronunciation(reading, pronunciation, dictionaryGroupedPronunciationList);
- if (groupedPronunciation === null) {
- groupedPronunciation = {
- pronunciation,
- terms: new Set(),
- reading
- };
- dictionaryGroupedPronunciationList.push(groupedPronunciation);
- }
- groupedPronunciation.terms.add(term);
- }
+ let frequencyData = map2.get(character);
+ if (typeof frequencyData === 'undefined') {
+ frequencyData = {character, values: new Map()};
+ map2.set(character, frequencyData);
}
- /** @type {import('dictionary-data-util').DictionaryGroupedPronunciations[]} */
- const results2 = [];
- const multipleReadings = (allReadings.size > 1);
- for (const [dictionary, dictionaryGroupedPronunciationList] of groupedPronunciationsMap.entries()) {
- /** @type {import('dictionary-data-util').GroupedPronunciation[]} */
- const pronunciations2 = [];
- for (const groupedPronunciation of dictionaryGroupedPronunciationList) {
- 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,
- exclusiveTerms,
- exclusiveReadings
- });
- }
-
- results2.push({dictionary, pronunciations: pronunciations2});
- }
- return results2;
+ frequencyData.values.set(createMapKey([frequency, displayValue]), {frequency, displayValue});
}
- /**
- * @template {import('dictionary').PronunciationType} T
- * @param {import('dictionary').Pronunciation[]} pronunciations
- * @param {T} type
- * @returns {import('dictionary').PronunciationGeneric<T>[]}
- */
- static getPronunciationsOfType(pronunciations, type) {
- /** @type {import('dictionary').PronunciationGeneric<T>[]} */
- 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<T>} */ (pronunciation));
+ const results = [];
+ for (const [dictionary, map2] of map1.entries()) {
+ const frequencies = [];
+ for (const {character, values} of map2.values()) {
+ frequencies.push({
+ character,
+ values: [...values.values()]
+ });
}
- return results;
+ results.push({dictionary, frequencies});
}
+ return results;
+}
- /**
- * @param {import('dictionary').Tag[]|import('anki-templates').Tag[]} termTags
- * @returns {import('dictionary-data-util').TermFrequencyType}
- */
- static getTermFrequency(termTags) {
- let totalScore = 0;
- for (const {score} of termTags) {
- totalScore += score;
- }
- if (totalScore > 0) {
- return 'popular';
- } else if (totalScore < 0) {
- return 'rare';
- } else {
- return 'normal';
- }
+/**
+ * @param {import('dictionary').TermDictionaryEntry} dictionaryEntry
+ * @returns {import('dictionary-data-util').DictionaryGroupedPronunciations[]}
+ */
+export function getGroupedPronunciations(dictionaryEntry) {
+ const {headwords, pronunciations: termPronunciations} = dictionaryEntry;
+
+ const allTerms = new Set();
+ const allReadings = new Set();
+ for (const {term, reading} of headwords) {
+ allTerms.add(term);
+ allReadings.add(reading);
}
- /**
- * @param {import('dictionary').TermHeadword[]} headwords
- * @param {number[]} headwordIndices
- * @param {Set<string>} allTermsSet
- * @param {Set<string>} allReadingsSet
- * @returns {string[]}
- */
- static getDisambiguations(headwords, headwordIndices, allTermsSet, allReadingsSet) {
- if (allTermsSet.size <= 1 && allReadingsSet.size <= 1) { return []; }
-
- /** @type {Set<string>} */
- const terms = new Set();
- /** @type {Set<string>} */
- const readings = new Set();
- for (const headwordIndex of headwordIndices) {
- const {term, reading} = headwords[headwordIndex];
- terms.add(term);
- readings.add(reading);
+ /** @type {Map<string, import('dictionary-data-util').GroupedPronunciationInternal[]>} */
+ const groupedPronunciationsMap = new Map();
+ 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);
}
-
- /** @type {string[]} */
- const disambiguations = [];
- const addTerms = !this._areSetsEqual(terms, allTermsSet);
- const addReadings = !this._areSetsEqual(readings, allReadingsSet);
- if (addTerms) {
- disambiguations.push(...this._getSetIntersection(terms, allTermsSet));
- }
- if (addReadings) {
- if (addTerms) {
- for (const term of terms) {
- readings.delete(term);
- }
+ for (const pronunciation of pronunciations) {
+ let groupedPronunciation = findExistingGroupedPronunciation(reading, pronunciation, dictionaryGroupedPronunciationList);
+ if (groupedPronunciation === null) {
+ groupedPronunciation = {
+ pronunciation,
+ terms: new Set(),
+ reading
+ };
+ dictionaryGroupedPronunciationList.push(groupedPronunciation);
}
- disambiguations.push(...this._getSetIntersection(readings, allReadingsSet));
+ groupedPronunciation.terms.add(term);
}
- return disambiguations;
}
- /**
- * @param {string[]} wordClasses
- * @returns {boolean}
- */
- static isNonNounVerbOrAdjective(wordClasses) {
- let isVerbOrAdjective = false;
- let isSuruVerb = false;
- let isNoun = false;
- for (const wordClass of wordClasses) {
- switch (wordClass) {
- case 'v1':
- case 'v5':
- case 'vk':
- case 'vz':
- case 'adj-i':
- isVerbOrAdjective = true;
- break;
- case 'vs':
- isVerbOrAdjective = true;
- isSuruVerb = true;
- // falls through
- case 'n':
- isNoun = true;
- break;
+ /** @type {import('dictionary-data-util').DictionaryGroupedPronunciations[]} */
+ const results2 = [];
+ const multipleReadings = (allReadings.size > 1);
+ for (const [dictionary, dictionaryGroupedPronunciationList] of groupedPronunciationsMap.entries()) {
+ /** @type {import('dictionary-data-util').GroupedPronunciation[]} */
+ const pronunciations2 = [];
+ for (const groupedPronunciation of dictionaryGroupedPronunciationList) {
+ const {pronunciation, terms, reading} = groupedPronunciation;
+ const exclusiveTerms = !areSetsEqual(terms, allTerms) ? getSetIntersection(terms, allTerms) : [];
+ const exclusiveReadings = [];
+ if (multipleReadings) {
+ exclusiveReadings.push(reading);
}
+ pronunciations2.push({
+ pronunciation,
+ terms: [...terms],
+ reading,
+ exclusiveTerms,
+ exclusiveReadings
+ });
}
- return isVerbOrAdjective && !(isSuruVerb && isNoun);
+
+ results2.push({dictionary, pronunciations: pronunciations2});
}
+ return results2;
+}
- // Private
+/**
+ * @template {import('dictionary').PronunciationType} T
+ * @param {import('dictionary').Pronunciation[]} pronunciations
+ * @param {T} type
+ * @returns {import('dictionary').PronunciationGeneric<T>[]}
+ */
+export function getPronunciationsOfType(pronunciations, type) {
+ /** @type {import('dictionary').PronunciationGeneric<T>[]} */
+ 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<T>} */ (pronunciation));
+ }
+ return results;
+}
- /**
- * @param {string} reading
- * @param {import('dictionary').Pronunciation} pronunciation
- * @param {import('dictionary-data-util').GroupedPronunciationInternal[]} groupedPronunciationList
- * @returns {?import('dictionary-data-util').GroupedPronunciationInternal}
- */
- static _findExistingGroupedPronunciation(reading, pronunciation, groupedPronunciationList) {
- const existingGroupedPronunciation = groupedPronunciationList.find((groupedPronunciation) => {
- return groupedPronunciation.reading === reading && this._arePronunciationsEquivalent(groupedPronunciation, pronunciation);
- });
+/**
+ * @param {import('dictionary').Tag[]|import('anki-templates').Tag[]} termTags
+ * @returns {import('dictionary-data-util').TermFrequencyType}
+ */
+export function getTermFrequency(termTags) {
+ let totalScore = 0;
+ for (const {score} of termTags) {
+ totalScore += score;
+ }
+ if (totalScore > 0) {
+ return 'popular';
+ } else if (totalScore < 0) {
+ return 'rare';
+ } else {
+ return 'normal';
+ }
+}
- return existingGroupedPronunciation || null;
+/**
+ * @param {import('dictionary').TermHeadword[]} headwords
+ * @param {number[]} headwordIndices
+ * @param {Set<string>} allTermsSet
+ * @param {Set<string>} allReadingsSet
+ * @returns {string[]}
+ */
+export function getDisambiguations(headwords, headwordIndices, allTermsSet, allReadingsSet) {
+ if (allTermsSet.size <= 1 && allReadingsSet.size <= 1) { return []; }
+
+ /** @type {Set<string>} */
+ const terms = new Set();
+ /** @type {Set<string>} */
+ const readings = new Set();
+ for (const headwordIndex of headwordIndices) {
+ const {term, reading} = headwords[headwordIndex];
+ terms.add(term);
+ readings.add(reading);
}
- /**
- * @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;
+ /** @type {string[]} */
+ const disambiguations = [];
+ const addTerms = !areSetsEqual(terms, allTermsSet);
+ const addReadings = !areSetsEqual(readings, allReadingsSet);
+ if (addTerms) {
+ disambiguations.push(...getSetIntersection(terms, allTermsSet));
+ }
+ if (addReadings) {
+ if (addTerms) {
+ for (const term of terms) {
+ readings.delete(term);
}
}
- return true;
+ disambiguations.push(...getSetIntersection(readings, allReadingsSet));
}
+ return disambiguations;
+}
- /**
- * @template [T=unknown]
- * @param {T[]} array1
- * @param {T[]} array2
- * @returns {boolean}
- */
- static _areArraysEqual(array1, array2) {
- const ii = array1.length;
- if (ii !== array2.length) { return false; }
- for (let i = 0; i < ii; ++i) {
- if (array1[i] !== array2[i]) { return false; }
+/**
+ * @param {string[]} wordClasses
+ * @returns {boolean}
+ */
+export function isNonNounVerbOrAdjective(wordClasses) {
+ let isVerbOrAdjective = false;
+ let isSuruVerb = false;
+ let isNoun = false;
+ for (const wordClass of wordClasses) {
+ switch (wordClass) {
+ case 'v1':
+ case 'v5':
+ case 'vk':
+ case 'vz':
+ case 'adj-i':
+ isVerbOrAdjective = true;
+ break;
+ case 'vs':
+ isVerbOrAdjective = true;
+ isSuruVerb = true;
+ // falls through
+ case 'n':
+ isNoun = true;
+ break;
}
- return true;
}
+ return isVerbOrAdjective && !(isSuruVerb && isNoun);
+}
- /**
- * @param {import('dictionary').Tag[]} tagList1
- * @param {import('dictionary').Tag[]} tagList2
- * @returns {boolean}
- */
- static _areTagListsEqual(tagList1, tagList2) {
- const ii = tagList1.length;
- if (tagList2.length !== ii) { return false; }
-
- for (let i = 0; i < ii; ++i) {
- const tag1 = tagList1[i];
- const tag2 = tagList2[i];
- if (tag1.name !== tag2.name || !this._areArraysEqual(tag1.dictionaries, tag2.dictionaries)) {
- return false;
- }
+// Private
+
+/**
+ * @param {string} reading
+ * @param {import('dictionary').Pronunciation} pronunciation
+ * @param {import('dictionary-data-util').GroupedPronunciationInternal[]} groupedPronunciationList
+ * @returns {?import('dictionary-data-util').GroupedPronunciationInternal}
+ */
+function findExistingGroupedPronunciation(reading, pronunciation, groupedPronunciationList) {
+ const existingGroupedPronunciation = groupedPronunciationList.find((groupedPronunciation) => {
+ return groupedPronunciation.reading === reading && arePronunciationsEquivalent(groupedPronunciation, pronunciation);
+ });
+
+ return existingGroupedPronunciation || null;
+}
+
+/**
+ * @param {import('dictionary-data-util').GroupedPronunciationInternal} groupedPronunciation
+ * @param {import('dictionary').Pronunciation} pronunciation2
+ * @returns {boolean}
+ */
+function arePronunciationsEquivalent({pronunciation: pronunciation1}, pronunciation2) {
+ if (
+ pronunciation1.type !== pronunciation2.type ||
+ !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 &&
+ areArraysEqual(pronunciation1.nasalPositions, pitchAccent2.nasalPositions) &&
+ 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 true;
+}
- return true;
+/**
+ * @template [T=unknown]
+ * @param {T[]} array1
+ * @param {T[]} array2
+ * @returns {boolean}
+ */
+function areArraysEqual(array1, array2) {
+ const ii = array1.length;
+ if (ii !== array2.length) { return false; }
+ for (let i = 0; i < ii; ++i) {
+ if (array1[i] !== array2[i]) { return false; }
}
+ return true;
+}
- /**
- * @template [T=unknown]
- * @param {Set<T>} set1
- * @param {Set<T>} set2
- * @returns {boolean}
- */
- static _areSetsEqual(set1, set2) {
- if (set1.size !== set2.size) {
+/**
+ * @param {import('dictionary').Tag[]} tagList1
+ * @param {import('dictionary').Tag[]} tagList2
+ * @returns {boolean}
+ */
+function areTagListsEqual(tagList1, tagList2) {
+ const ii = tagList1.length;
+ if (tagList2.length !== ii) { return false; }
+
+ for (let i = 0; i < ii; ++i) {
+ const tag1 = tagList1[i];
+ const tag2 = tagList2[i];
+ if (tag1.name !== tag2.name || !areArraysEqual(tag1.dictionaries, tag2.dictionaries)) {
return false;
}
+ }
- for (const value of set1) {
- if (!set2.has(value)) {
- return false;
- }
- }
+ return true;
+}
- return true;
+/**
+ * @template [T=unknown]
+ * @param {Set<T>} set1
+ * @param {Set<T>} set2
+ * @returns {boolean}
+ */
+function areSetsEqual(set1, set2) {
+ if (set1.size !== set2.size) {
+ return false;
}
- /**
- * @template [T=unknown]
- * @param {Set<T>} set1
- * @param {Set<T>} set2
- * @returns {T[]}
- */
- static _getSetIntersection(set1, set2) {
- const result = [];
- for (const value of set1) {
- if (set2.has(value)) {
- result.push(value);
- }
+ for (const value of set1) {
+ if (!set2.has(value)) {
+ return false;
}
- return result;
}
- /**
- * @param {unknown[]} array
- * @returns {string}
- */
- static _createMapKey(array) {
- return JSON.stringify(array);
+ return true;
+}
+
+/**
+ * @template [T=unknown]
+ * @param {Set<T>} set1
+ * @param {Set<T>} set2
+ * @returns {T[]}
+ */
+function getSetIntersection(set1, set2) {
+ const result = [];
+ for (const value of set1) {
+ if (set2.has(value)) {
+ result.push(value);
+ }
}
+ return result;
+}
+
+/**
+ * @param {unknown[]} array
+ * @returns {string}
+ */
+function createMapKey(array) {
+ return JSON.stringify(array);
}
diff --git a/ext/js/dictionary/dictionary-importer.js b/ext/js/dictionary/dictionary-importer.js
index 0feed8b2..eed7d9dd 100644
--- a/ext/js/dictionary/dictionary-importer.js
+++ b/ext/js/dictionary/dictionary-importer.js
@@ -24,11 +24,11 @@ import {
ZipReader as ZipReader0,
configure
} from '../../lib/zip.js';
-import {stringReverse} from '../core/utilities.js';
import {ExtensionError} from '../core/extension-error.js';
import {parseJson} from '../core/json.js';
import {toError} from '../core/to-error.js';
-import {MediaUtil} from '../media/media-util.js';
+import {stringReverse} from '../core/utilities.js';
+import {getFileExtensionFromImageMediaType, getImageMediaTypeFromFileName} from '../media/media-util.js';
const ajvSchemas = /** @type {import('dictionary-importer').CompiledSchemaValidators} */ (/** @type {unknown} */ (ajvSchemas0));
const BlobWriter = /** @type {typeof import('@zip.js/zip.js').BlobWriter} */ (/** @type {unknown} */ (BlobWriter0));
@@ -560,7 +560,7 @@ export class DictionaryImporter {
// Check if already added
let mediaData = media.get(path);
if (typeof mediaData !== 'undefined') {
- if (MediaUtil.getFileExtensionFromImageMediaType(mediaData.mediaType) === null) {
+ if (getFileExtensionFromImageMediaType(mediaData.mediaType) === null) {
throw createError('Media file is not a valid image');
}
return mediaData;
@@ -575,7 +575,7 @@ export class DictionaryImporter {
// Load file content
let content = await (await this._getData(file, new BlobWriter())).arrayBuffer();
- const mediaType = MediaUtil.getImageMediaTypeFromFileName(path);
+ const mediaType = getImageMediaTypeFromFileName(path);
if (mediaType === null) {
throw createError('Could not determine media type for image');
}
diff --git a/ext/js/display/display-anki.js b/ext/js/display/display-anki.js
index 5433142d..68d28d33 100644
--- a/ext/js/display/display-anki.js
+++ b/ext/js/display/display-anki.js
@@ -20,7 +20,7 @@ import {EventListenerCollection} from '../core/event-listener-collection.js';
import {toError} from '../core/to-error.js';
import {deferPromise} from '../core/utilities.js';
import {AnkiNoteBuilder} from '../data/anki-note-builder.js';
-import {AnkiUtil} from '../data/anki-util.js';
+import {isNoteDataValid} from '../data/anki-util.js';
import {PopupMenu} from '../dom/popup-menu.js';
import {querySelectorNotNull} from '../dom/query-selector.js';
import {TemplateRendererProxy} from '../templates/template-renderer-proxy.js';
@@ -676,7 +676,7 @@ export class DisplayAnki {
_getAnkiNoteInfoForceValue(notes, canAdd) {
const results = [];
for (const note of notes) {
- const valid = AnkiUtil.isNoteDataValid(note);
+ const valid = isNoteDataValid(note);
results.push({canAdd, valid, noteIds: null});
}
return results;
diff --git a/ext/js/display/display-content-manager.js b/ext/js/display/display-content-manager.js
index d13dffb3..4465ce3e 100644
--- a/ext/js/display/display-content-manager.js
+++ b/ext/js/display/display-content-manager.js
@@ -17,7 +17,7 @@
*/
import {EventListenerCollection} from '../core/event-listener-collection.js';
-import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js';
+import {base64ToArrayBuffer} from '../data/sandbox/array-buffer-util.js';
import {yomitan} from '../yomitan.js';
/**
@@ -143,7 +143,7 @@ export class DisplayContentManager {
const datas = await yomitan.api.getMedia([{path, dictionary}]);
if (token === this._token && datas.length > 0) {
const data = datas[0];
- const buffer = ArrayBufferUtil.base64ToArrayBuffer(data.content);
+ const buffer = base64ToArrayBuffer(data.content);
const blob = new Blob([buffer], {type: data.mediaType});
const url = URL.createObjectURL(blob);
return {data, url};
diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js
index eef58bb0..01f6f38b 100644
--- a/ext/js/display/display-generator.js
+++ b/ext/js/display/display-generator.js
@@ -18,11 +18,11 @@
import {ExtensionError} from '../core/extension-error.js';
import {isObject} from '../core/utilities.js';
-import {DictionaryDataUtil} from '../dictionary/dictionary-data-util.js';
+import {getDisambiguations, getGroupedPronunciations, getTermFrequency, groupKanjiFrequencies, groupTermFrequencies, groupTermTags, isNonNounVerbOrAdjective} from '../dictionary/dictionary-data-util.js';
import {HtmlTemplateCollection} from '../dom/html-template-collection.js';
import {distributeFurigana, getKanaMorae, getPitchCategory, isCodePointKanji, isStringPartiallyJapanese} from '../language/japanese.js';
import {yomitan} from '../yomitan.js';
-import {PronunciationGenerator} from './sandbox/pronunciation-generator.js';
+import {createPronunciationDownstepPosition, createPronunciationGraph, createPronunciationText} from './sandbox/pronunciation-generator.js';
import {StructuredContentGenerator} from './sandbox/structured-content-generator.js';
export class DisplayGenerator {
@@ -38,8 +38,6 @@ export class DisplayGenerator {
this._templates = new HtmlTemplateCollection();
/** @type {StructuredContentGenerator} */
this._structuredContentGenerator = new StructuredContentGenerator(this._contentManager, document);
- /** @type {PronunciationGenerator} */
- this._pronunciationGenerator = new PronunciationGenerator();
}
/** */
@@ -73,10 +71,10 @@ export class DisplayGenerator {
const headwordTagsContainer = this._querySelector(node, '.headword-list-tag-list');
const {headwords, type, inflectionRuleChainCandidates, definitions, frequencies, pronunciations} = dictionaryEntry;
- const groupedPronunciations = DictionaryDataUtil.getGroupedPronunciations(dictionaryEntry);
+ const groupedPronunciations = getGroupedPronunciations(dictionaryEntry);
const pronunciationCount = groupedPronunciations.reduce((i, v) => i + v.pronunciations.length, 0);
- const groupedFrequencies = DictionaryDataUtil.groupTermFrequencies(dictionaryEntry);
- const termTags = DictionaryDataUtil.groupTermTags(dictionaryEntry);
+ const groupedFrequencies = groupTermFrequencies(dictionaryEntry);
+ const termTags = groupTermTags(dictionaryEntry);
/** @type {Set<string>} */
const uniqueTerms = new Set();
@@ -166,7 +164,7 @@ export class DisplayGenerator {
const dictionaryIndicesContainer = this._querySelector(node, '.kanji-dictionary-indices');
this._setTextContent(glyphContainer, dictionaryEntry.character, 'ja');
- const groupedFrequencies = DictionaryDataUtil.groupKanjiFrequencies(dictionaryEntry.frequencies);
+ const groupedFrequencies = groupKanjiFrequencies(dictionaryEntry.frequencies);
const dictionaryTag = this._createDictionaryTag(dictionaryEntry.dictionary);
@@ -334,7 +332,7 @@ export class DisplayGenerator {
node.dataset.isPrimary = `${isPrimaryAny}`;
node.dataset.readingIsSame = `${reading === term}`;
- node.dataset.frequency = DictionaryDataUtil.getTermFrequency(tags);
+ node.dataset.frequency = getTermFrequency(tags);
node.dataset.matchTypes = [...matchTypes].join(' ');
node.dataset.matchSources = [...matchSources].join(' ');
@@ -415,7 +413,7 @@ export class DisplayGenerator {
*/
_createTermDefinition(definition, dictionaryTag, headwords, uniqueTerms, uniqueReadings) {
const {dictionary, tags, headwordIndices, entries} = definition;
- const disambiguations = DictionaryDataUtil.getDisambiguations(headwords, headwordIndices, uniqueTerms, uniqueReadings);
+ const disambiguations = getDisambiguations(headwords, headwordIndices, uniqueTerms, uniqueReadings);
const node = this._instantiate('definition-item');
@@ -742,15 +740,15 @@ export class DisplayGenerator {
this._createPronunciationDisambiguations(n, exclusiveTerms, exclusiveReadings);
n = this._querySelector(node, '.pronunciation-downstep-notation-container');
- n.appendChild(this._pronunciationGenerator.createPronunciationDownstepPosition(position));
+ n.appendChild(createPronunciationDownstepPosition(position));
n = this._querySelector(node, '.pronunciation-text-container');
n.lang = 'ja';
- n.appendChild(this._pronunciationGenerator.createPronunciationText(morae, position, nasalPositions, devoicePositions));
+ n.appendChild(createPronunciationText(morae, position, nasalPositions, devoicePositions));
n = this._querySelector(node, '.pronunciation-graph-container');
- n.appendChild(this._pronunciationGenerator.createPronunciationGraph(morae, position));
+ n.appendChild(createPronunciationGraph(morae, position));
return node;
}
@@ -1040,7 +1038,7 @@ export class DisplayGenerator {
*/
_getPronunciationCategories(reading, termPronunciations, wordClasses, headwordIndex) {
if (termPronunciations.length === 0) { return null; }
- const isVerbOrAdjective = DictionaryDataUtil.isNonNounVerbOrAdjective(wordClasses);
+ const isVerbOrAdjective = isNonNounVerbOrAdjective(wordClasses);
/** @type {Set<import('japanese-util').PitchCategory>} */
const categories = new Set();
for (const termPronunciation of termPronunciations) {
diff --git a/ext/js/display/sandbox/pronunciation-generator.js b/ext/js/display/sandbox/pronunciation-generator.js
index 45631e74..373ec830 100644
--- a/ext/js/display/sandbox/pronunciation-generator.js
+++ b/ext/js/display/sandbox/pronunciation-generator.js
@@ -18,221 +18,219 @@
import {getKanaDiacriticInfo, isMoraPitchHigh} from '../../language/japanese.js';
-export class PronunciationGenerator {
- /**
- * @param {string[]} morae
- * @param {number} downstepPosition
- * @param {number[]} nasalPositions
- * @param {number[]} devoicePositions
- * @returns {HTMLSpanElement}
- */
- createPronunciationText(morae, downstepPosition, nasalPositions, devoicePositions) {
- const nasalPositionsSet = nasalPositions.length > 0 ? new Set(nasalPositions) : null;
- const devoicePositionsSet = devoicePositions.length > 0 ? new Set(devoicePositions) : null;
- const container = document.createElement('span');
- container.className = 'pronunciation-text';
- for (let i = 0, ii = morae.length; i < ii; ++i) {
- const i1 = i + 1;
- const mora = morae[i];
- const highPitch = isMoraPitchHigh(i, downstepPosition);
- const highPitchNext = isMoraPitchHigh(i1, downstepPosition);
- const nasal = nasalPositionsSet !== null && nasalPositionsSet.has(i1);
- const devoice = devoicePositionsSet !== null && devoicePositionsSet.has(i1);
-
- const n1 = document.createElement('span');
- n1.className = 'pronunciation-mora';
- n1.dataset.position = `${i}`;
- n1.dataset.pitch = highPitch ? 'high' : 'low';
- n1.dataset.pitchNext = highPitchNext ? 'high' : 'low';
-
- const characterNodes = [];
- for (const character of mora) {
- const n2 = document.createElement('span');
- n2.className = 'pronunciation-character';
- n2.textContent = character;
- n1.appendChild(n2);
- characterNodes.push(n2);
- }
-
- if (devoice) {
- n1.dataset.devoice = 'true';
- const n3 = document.createElement('span');
- n3.className = 'pronunciation-devoice-indicator';
- n1.appendChild(n3);
- }
- if (nasal && characterNodes.length > 0) {
- n1.dataset.nasal = 'true';
-
- const group = document.createElement('span');
- group.className = 'pronunciation-character-group';
+/**
+ * @param {string[]} morae
+ * @param {number} downstepPosition
+ * @param {number[]} nasalPositions
+ * @param {number[]} devoicePositions
+ * @returns {HTMLSpanElement}
+ */
+export function createPronunciationText(morae, downstepPosition, nasalPositions, devoicePositions) {
+ const nasalPositionsSet = nasalPositions.length > 0 ? new Set(nasalPositions) : null;
+ const devoicePositionsSet = devoicePositions.length > 0 ? new Set(devoicePositions) : null;
+ const container = document.createElement('span');
+ container.className = 'pronunciation-text';
+ for (let i = 0, ii = morae.length; i < ii; ++i) {
+ const i1 = i + 1;
+ const mora = morae[i];
+ const highPitch = isMoraPitchHigh(i, downstepPosition);
+ const highPitchNext = isMoraPitchHigh(i1, downstepPosition);
+ const nasal = nasalPositionsSet !== null && nasalPositionsSet.has(i1);
+ const devoice = devoicePositionsSet !== null && devoicePositionsSet.has(i1);
- const n2 = characterNodes[0];
- const character = /** @type {string} */ (n2.textContent);
+ const n1 = document.createElement('span');
+ n1.className = 'pronunciation-mora';
+ n1.dataset.position = `${i}`;
+ n1.dataset.pitch = highPitch ? 'high' : 'low';
+ n1.dataset.pitchNext = highPitchNext ? 'high' : 'low';
+
+ const characterNodes = [];
+ for (const character of mora) {
+ const n2 = document.createElement('span');
+ n2.className = 'pronunciation-character';
+ n2.textContent = character;
+ n1.appendChild(n2);
+ characterNodes.push(n2);
+ }
- const characterInfo = getKanaDiacriticInfo(character);
- if (characterInfo !== null) {
- n1.dataset.originalText = mora;
- n2.dataset.originalText = character;
- n2.textContent = characterInfo.character;
- }
+ if (devoice) {
+ n1.dataset.devoice = 'true';
+ const n3 = document.createElement('span');
+ n3.className = 'pronunciation-devoice-indicator';
+ n1.appendChild(n3);
+ }
+ if (nasal && characterNodes.length > 0) {
+ n1.dataset.nasal = 'true';
- let n3 = document.createElement('span');
- n3.className = 'pronunciation-nasal-diacritic';
- n3.textContent = '\u309a'; // Combining handakuten
- group.appendChild(n3);
+ const group = document.createElement('span');
+ group.className = 'pronunciation-character-group';
- n3 = document.createElement('span');
- n3.className = 'pronunciation-nasal-indicator';
- group.appendChild(n3);
+ const n2 = characterNodes[0];
+ const character = /** @type {string} */ (n2.textContent);
- /** @type {ParentNode} */ (n2.parentNode).replaceChild(group, n2);
- group.insertBefore(n2, group.firstChild);
+ const characterInfo = getKanaDiacriticInfo(character);
+ if (characterInfo !== null) {
+ n1.dataset.originalText = mora;
+ n2.dataset.originalText = character;
+ n2.textContent = characterInfo.character;
}
- const line = document.createElement('span');
- line.className = 'pronunciation-mora-line';
- n1.appendChild(line);
+ let n3 = document.createElement('span');
+ n3.className = 'pronunciation-nasal-diacritic';
+ n3.textContent = '\u309a'; // Combining handakuten
+ group.appendChild(n3);
- container.appendChild(n1);
- }
- return container;
- }
+ n3 = document.createElement('span');
+ n3.className = 'pronunciation-nasal-indicator';
+ group.appendChild(n3);
- /**
- * @param {string[]} morae
- * @param {number} downstepPosition
- * @returns {SVGSVGElement}
- */
- createPronunciationGraph(morae, downstepPosition) {
- const ii = morae.length;
-
- const svgns = 'http://www.w3.org/2000/svg';
- const svg = document.createElementNS(svgns, 'svg');
- svg.setAttribute('xmlns', svgns);
- svg.setAttribute('class', 'pronunciation-graph');
- svg.setAttribute('focusable', 'false');
- svg.setAttribute('viewBox', `0 0 ${50 * (ii + 1)} 100`);
-
- if (ii <= 0) { return svg; }
-
- const path1 = document.createElementNS(svgns, 'path');
- svg.appendChild(path1);
-
- const path2 = document.createElementNS(svgns, 'path');
- svg.appendChild(path2);
-
- const pathPoints = [];
- for (let i = 0; i < ii; ++i) {
- const highPitch = isMoraPitchHigh(i, downstepPosition);
- const highPitchNext = isMoraPitchHigh(i + 1, downstepPosition);
- const x = i * 50 + 25;
- const y = highPitch ? 25 : 75;
- if (highPitch && !highPitchNext) {
- this._addGraphDotDownstep(svg, svgns, x, y);
- } else {
- this._addGraphDot(svg, svgns, x, y);
- }
- pathPoints.push(`${x} ${y}`);
+ /** @type {ParentNode} */ (n2.parentNode).replaceChild(group, n2);
+ group.insertBefore(n2, group.firstChild);
}
- path1.setAttribute('class', 'pronunciation-graph-line');
- path1.setAttribute('d', `M${pathPoints.join(' L')}`);
+ const line = document.createElement('span');
+ line.className = 'pronunciation-mora-line';
+ n1.appendChild(line);
+
+ container.appendChild(n1);
+ }
+ return container;
+}
- pathPoints.splice(0, ii - 1);
- {
- const highPitch = isMoraPitchHigh(ii, downstepPosition);
- const x = ii * 50 + 25;
- const y = highPitch ? 25 : 75;
- this._addGraphTriangle(svg, svgns, x, y);
- pathPoints.push(`${x} ${y}`);
+/**
+ * @param {string[]} morae
+ * @param {number} downstepPosition
+ * @returns {SVGSVGElement}
+ */
+export function createPronunciationGraph(morae, downstepPosition) {
+ const ii = morae.length;
+
+ const svgns = 'http://www.w3.org/2000/svg';
+ const svg = document.createElementNS(svgns, 'svg');
+ svg.setAttribute('xmlns', svgns);
+ svg.setAttribute('class', 'pronunciation-graph');
+ svg.setAttribute('focusable', 'false');
+ svg.setAttribute('viewBox', `0 0 ${50 * (ii + 1)} 100`);
+
+ if (ii <= 0) { return svg; }
+
+ const path1 = document.createElementNS(svgns, 'path');
+ svg.appendChild(path1);
+
+ const path2 = document.createElementNS(svgns, 'path');
+ svg.appendChild(path2);
+
+ const pathPoints = [];
+ for (let i = 0; i < ii; ++i) {
+ const highPitch = isMoraPitchHigh(i, downstepPosition);
+ const highPitchNext = isMoraPitchHigh(i + 1, downstepPosition);
+ const x = i * 50 + 25;
+ const y = highPitch ? 25 : 75;
+ if (highPitch && !highPitchNext) {
+ addGraphDotDownstep(svg, svgns, x, y);
+ } else {
+ addGraphDot(svg, svgns, x, y);
}
+ pathPoints.push(`${x} ${y}`);
+ }
- path2.setAttribute('class', 'pronunciation-graph-line-tail');
- path2.setAttribute('d', `M${pathPoints.join(' L')}`);
+ path1.setAttribute('class', 'pronunciation-graph-line');
+ path1.setAttribute('d', `M${pathPoints.join(' L')}`);
- return svg;
+ pathPoints.splice(0, ii - 1);
+ {
+ const highPitch = isMoraPitchHigh(ii, downstepPosition);
+ const x = ii * 50 + 25;
+ const y = highPitch ? 25 : 75;
+ addGraphTriangle(svg, svgns, x, y);
+ pathPoints.push(`${x} ${y}`);
}
- /**
- * @param {number} downstepPosition
- * @returns {HTMLSpanElement}
- */
- createPronunciationDownstepPosition(downstepPosition) {
- const downstepPositionString = `${downstepPosition}`;
+ path2.setAttribute('class', 'pronunciation-graph-line-tail');
+ path2.setAttribute('d', `M${pathPoints.join(' L')}`);
- const n1 = document.createElement('span');
- n1.className = 'pronunciation-downstep-notation';
- n1.dataset.downstepPosition = downstepPositionString;
+ return svg;
+}
- let n2 = document.createElement('span');
- n2.className = 'pronunciation-downstep-notation-prefix';
- n2.textContent = '[';
- n1.appendChild(n2);
+/**
+ * @param {number} downstepPosition
+ * @returns {HTMLSpanElement}
+ */
+export function createPronunciationDownstepPosition(downstepPosition) {
+ const downstepPositionString = `${downstepPosition}`;
- n2 = document.createElement('span');
- n2.className = 'pronunciation-downstep-notation-number';
- n2.textContent = downstepPositionString;
- n1.appendChild(n2);
+ const n1 = document.createElement('span');
+ n1.className = 'pronunciation-downstep-notation';
+ n1.dataset.downstepPosition = downstepPositionString;
- n2 = document.createElement('span');
- n2.className = 'pronunciation-downstep-notation-suffix';
- n2.textContent = ']';
- n1.appendChild(n2);
+ let n2 = document.createElement('span');
+ n2.className = 'pronunciation-downstep-notation-prefix';
+ n2.textContent = '[';
+ n1.appendChild(n2);
- return n1;
- }
+ n2 = document.createElement('span');
+ n2.className = 'pronunciation-downstep-notation-number';
+ n2.textContent = downstepPositionString;
+ n1.appendChild(n2);
- // Private
+ n2 = document.createElement('span');
+ n2.className = 'pronunciation-downstep-notation-suffix';
+ n2.textContent = ']';
+ n1.appendChild(n2);
- /**
- * @param {Element} container
- * @param {string} svgns
- * @param {number} x
- * @param {number} y
- */
- _addGraphDot(container, svgns, x, y) {
- container.appendChild(this._createGraphCircle(svgns, 'pronunciation-graph-dot', x, y, '15'));
- }
+ return n1;
+}
- /**
- * @param {Element} container
- * @param {string} svgns
- * @param {number} x
- * @param {number} y
- */
- _addGraphDotDownstep(container, svgns, x, y) {
- container.appendChild(this._createGraphCircle(svgns, 'pronunciation-graph-dot-downstep1', x, y, '15'));
- container.appendChild(this._createGraphCircle(svgns, 'pronunciation-graph-dot-downstep2', x, y, '5'));
- }
+// Private
- /**
- * @param {Element} container
- * @param {string} svgns
- * @param {number} x
- * @param {number} y
- */
- _addGraphTriangle(container, svgns, x, y) {
- const node = document.createElementNS(svgns, 'path');
- node.setAttribute('class', 'pronunciation-graph-triangle');
- node.setAttribute('d', 'M0 13 L15 -13 L-15 -13 Z');
- node.setAttribute('transform', `translate(${x},${y})`);
- container.appendChild(node);
- }
+/**
+ * @param {Element} container
+ * @param {string} svgns
+ * @param {number} x
+ * @param {number} y
+ */
+function addGraphDot(container, svgns, x, y) {
+ container.appendChild(createGraphCircle(svgns, 'pronunciation-graph-dot', x, y, '15'));
+}
- /**
- * @param {string} svgns
- * @param {string} className
- * @param {number} x
- * @param {number} y
- * @param {string} radius
- * @returns {Element}
- */
- _createGraphCircle(svgns, className, x, y, radius) {
- const node = document.createElementNS(svgns, 'circle');
- node.setAttribute('class', className);
- node.setAttribute('cx', `${x}`);
- node.setAttribute('cy', `${y}`);
- node.setAttribute('r', radius);
- return node;
- }
+/**
+ * @param {Element} container
+ * @param {string} svgns
+ * @param {number} x
+ * @param {number} y
+ */
+function addGraphDotDownstep(container, svgns, x, y) {
+ container.appendChild(createGraphCircle(svgns, 'pronunciation-graph-dot-downstep1', x, y, '15'));
+ container.appendChild(createGraphCircle(svgns, 'pronunciation-graph-dot-downstep2', x, y, '5'));
+}
+
+/**
+ * @param {Element} container
+ * @param {string} svgns
+ * @param {number} x
+ * @param {number} y
+ */
+function addGraphTriangle(container, svgns, x, y) {
+ const node = document.createElementNS(svgns, 'path');
+ node.setAttribute('class', 'pronunciation-graph-triangle');
+ node.setAttribute('d', 'M0 13 L15 -13 L-15 -13 Z');
+ node.setAttribute('transform', `translate(${x},${y})`);
+ container.appendChild(node);
+}
+
+/**
+ * @param {string} svgns
+ * @param {string} className
+ * @param {number} x
+ * @param {number} y
+ * @param {string} radius
+ * @returns {Element}
+ */
+function createGraphCircle(svgns, className, x, y, radius) {
+ const node = document.createElementNS(svgns, 'circle');
+ node.setAttribute('class', className);
+ node.setAttribute('cx', `${x}`);
+ node.setAttribute('cy', `${y}`);
+ node.setAttribute('r', radius);
+ return node;
}
diff --git a/ext/js/dom/dom-text-scanner.js b/ext/js/dom/dom-text-scanner.js
index d605aeca..5caa32f7 100644
--- a/ext/js/dom/dom-text-scanner.js
+++ b/ext/js/dom/dom-text-scanner.js
@@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import {StringUtil} from '../data/sandbox/string-util.js';
+import {readCodePointsBackward, readCodePointsForward} from '../data/sandbox/string-util.js';
/**
* A class used to scan text in a document.
@@ -172,7 +172,7 @@ export class DOMTextScanner {
if (resetOffset) { this._offset = 0; }
while (this._offset < nodeValueLength) {
- const char = StringUtil.readCodePointsForward(nodeValue, this._offset, 1);
+ const char = readCodePointsForward(nodeValue, this._offset, 1);
this._offset += char.length;
const charAttributes = DOMTextScanner.getCharacterAttributes(char, preserveNewlines, preserveWhitespace);
if (this._checkCharacterForward(char, charAttributes)) { break; }
@@ -201,7 +201,7 @@ export class DOMTextScanner {
if (resetOffset) { this._offset = nodeValueLength; }
while (this._offset > 0) {
- const char = StringUtil.readCodePointsBackward(nodeValue, this._offset - 1, 1);
+ const char = readCodePointsBackward(nodeValue, this._offset - 1, 1);
this._offset -= char.length;
const charAttributes = DOMTextScanner.getCharacterAttributes(char, preserveNewlines, preserveWhitespace);
if (this._checkCharacterBackward(char, charAttributes)) { break; }
diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js
index 477295e6..8727a4e1 100644
--- a/ext/js/dom/text-source-element.js
+++ b/ext/js/dom/text-source-element.js
@@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import {StringUtil} from '../data/sandbox/string-util.js';
+import {readCodePointsBackward, readCodePointsForward} from '../data/sandbox/string-util.js';
import {DocumentUtil} from './document-util.js';
/**
@@ -118,7 +118,7 @@ export class TextSourceElement {
const offset = fromEnd ? this._endOffset : this._startOffset;
length = Math.min(this._fullContent.length - offset, length);
if (length > 0) {
- length = StringUtil.readCodePointsForward(this._fullContent, offset, length).length;
+ length = readCodePointsForward(this._fullContent, offset, length).length;
}
this._endOffset = offset + length;
this._content = this._fullContent.substring(this._startOffset, this._endOffset);
@@ -133,7 +133,7 @@ export class TextSourceElement {
setStartOffset(length) {
length = Math.min(this._startOffset, length);
if (length > 0) {
- length = StringUtil.readCodePointsBackward(this._fullContent, this._startOffset - 1, length).length;
+ length = readCodePointsBackward(this._fullContent, this._startOffset - 1, length).length;
}
this._startOffset -= length;
this._content = this._fullContent.substring(this._startOffset, this._endOffset);
diff --git a/ext/js/general/regex-util.js b/ext/js/general/regex-util.js
index 301b1fcf..f6eca3b6 100644
--- a/ext/js/general/regex-util.js
+++ b/ext/js/general/regex-util.js
@@ -16,82 +16,76 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+/** @type {RegExp} @readonly */
+const matchReplacementPattern = /\$(?:\$|&|`|'|(\d\d?)|<([^>]*)>)/g;
/**
- * This class provides some general utility functions for regular expressions.
+ * Applies string.replace using a regular expression and replacement string as arguments.
+ * A source map of the changes is also maintained.
+ * @param {string} text A string of the text to replace.
+ * @param {import('./text-source-map.js').TextSourceMap} sourceMap An instance of `TextSourceMap` which corresponds to `text`.
+ * @param {RegExp} pattern A regular expression to use as the replacement.
+ * @param {string} replacement A replacement string that follows the format of the standard
+ * JavaScript regular expression replacement string.
+ * @returns {string} A new string with the pattern replacements applied and the source map updated.
*/
-export class RegexUtil {
- /** @type {RegExp} @readonly */
- static _matchReplacementPattern = /\$(?:\$|&|`|'|(\d\d?)|<([^>]*)>)/g;
+export function applyTextReplacement(text, sourceMap, pattern, replacement) {
+ const isGlobal = pattern.global;
+ if (isGlobal) { pattern.lastIndex = 0; }
+ for (let loop = true; loop; loop = isGlobal) {
+ const match = pattern.exec(text);
+ if (match === null) { break; }
- /**
- * Applies string.replace using a regular expression and replacement string as arguments.
- * A source map of the changes is also maintained.
- * @param {string} text A string of the text to replace.
- * @param {import('./text-source-map.js').TextSourceMap} sourceMap An instance of `TextSourceMap` which corresponds to `text`.
- * @param {RegExp} pattern A regular expression to use as the replacement.
- * @param {string} replacement A replacement string that follows the format of the standard
- * JavaScript regular expression replacement string.
- * @returns {string} A new string with the pattern replacements applied and the source map updated.
- */
- static applyTextReplacement(text, sourceMap, pattern, replacement) {
- const isGlobal = pattern.global;
- if (isGlobal) { pattern.lastIndex = 0; }
- for (let loop = true; loop; loop = isGlobal) {
- const match = pattern.exec(text);
- if (match === null) { break; }
+ const matchText = match[0];
+ const index = match.index;
+ const actualReplacement = applyMatchReplacement(replacement, match);
+ const actualReplacementLength = actualReplacement.length;
+ const delta = actualReplacementLength - (matchText.length > 0 ? matchText.length : -1);
- const matchText = match[0];
- const index = match.index;
- const actualReplacement = this.applyMatchReplacement(replacement, match);
- const actualReplacementLength = actualReplacement.length;
- const delta = actualReplacementLength - (matchText.length > 0 ? matchText.length : -1);
+ text = `${text.substring(0, index)}${actualReplacement}${text.substring(index + matchText.length)}`;
+ pattern.lastIndex += delta;
- text = `${text.substring(0, index)}${actualReplacement}${text.substring(index + matchText.length)}`;
- pattern.lastIndex += delta;
-
- if (actualReplacementLength > 0) {
- sourceMap.insert(index, ...(new Array(actualReplacementLength).fill(0)));
- sourceMap.combine(index - 1 + actualReplacementLength, matchText.length);
- } else {
- sourceMap.combine(index, matchText.length);
- }
+ if (actualReplacementLength > 0) {
+ sourceMap.insert(index, ...(new Array(actualReplacementLength).fill(0)));
+ sourceMap.combine(index - 1 + actualReplacementLength, matchText.length);
+ } else {
+ sourceMap.combine(index, matchText.length);
}
- return text;
}
+ return text;
+}
- /**
- * Applies the replacement string for a given regular expression match.
- * @param {string} replacement The replacement string that follows the format of the standard
- * JavaScript regular expression replacement string.
- * @param {RegExpMatchArray} match A match object returned from RegExp.match.
- * @returns {string} A new string with the pattern replacement applied.
- */
- static applyMatchReplacement(replacement, match) {
- const pattern = this._matchReplacementPattern;
- pattern.lastIndex = 0;
- return replacement.replace(pattern, (g0, g1, g2) => {
- if (typeof g1 !== 'undefined') {
- const matchIndex = Number.parseInt(g1, 10);
- if (matchIndex >= 1 && matchIndex <= match.length) {
- return match[matchIndex];
- }
- } else if (typeof g2 !== 'undefined') {
- const {groups} = match;
- if (typeof groups === 'object' && groups !== null && Object.prototype.hasOwnProperty.call(groups, g2)) {
- return groups[g2];
- }
- } else {
- let {index} = match;
- if (typeof index !== 'number') { index = 0; }
- switch (g0) {
- case '$': return '$';
- case '&': return match[0];
- case '`': return replacement.substring(0, index);
- case '\'': return replacement.substring(index + g0.length);
- }
+/**
+ * Applies the replacement string for a given regular expression match.
+ * @param {string} replacement The replacement string that follows the format of the standard
+ * JavaScript regular expression replacement string.
+ * @param {RegExpMatchArray} match A match object returned from RegExp.match.
+ * @returns {string} A new string with the pattern replacement applied.
+ */
+export function applyMatchReplacement(replacement, match) {
+ const pattern = matchReplacementPattern;
+ pattern.lastIndex = 0;
+ return replacement.replace(pattern, (g0, g1, g2) => {
+ if (typeof g1 !== 'undefined') {
+ const matchIndex = Number.parseInt(g1, 10);
+ if (matchIndex >= 1 && matchIndex <= match.length) {
+ return match[matchIndex];
}
- return g0;
- });
- }
+ } else if (typeof g2 !== 'undefined') {
+ const {groups} = match;
+ if (typeof groups === 'object' && groups !== null && Object.prototype.hasOwnProperty.call(groups, g2)) {
+ return groups[g2];
+ }
+ } else {
+ let {index} = match;
+ if (typeof index !== 'number') { index = 0; }
+ switch (g0) {
+ case '$': return '$';
+ case '&': return match[0];
+ case '`': return replacement.substring(0, index);
+ case '\'': return replacement.substring(index + g0.length);
+ }
+ }
+ return g0;
+ });
}
diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js
index 9d2f18e0..334eb5b7 100644
--- a/ext/js/language/translator.js
+++ b/ext/js/language/translator.js
@@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import {RegexUtil} from '../general/regex-util.js';
+import {applyTextReplacement} from '../general/regex-util.js';
import {TextSourceMap} from '../general/text-source-map.js';
import {convertAlphabeticToKana} from './japanese-wanakana.js';
import {collapseEmphaticSequences, convertHalfWidthKanaToFullWidth, convertHiraganaToKatakana, convertKatakanaToHiragana, convertNumericToFullWidth, isCodePointJapanese} from './japanese.js';
@@ -506,7 +506,7 @@ export class Translator {
*/
_applyTextReplacements(text, sourceMap, replacements) {
for (const {pattern, replacement} of replacements) {
- text = RegexUtil.applyTextReplacement(text, sourceMap, pattern, replacement);
+ text = applyTextReplacement(text, sourceMap, pattern, replacement);
}
return text;
}
diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js
index b4f63b96..968c9353 100644
--- a/ext/js/media/audio-downloader.js
+++ b/ext/js/media/audio-downloader.js
@@ -20,7 +20,7 @@ import {RequestBuilder} from '../background/request-builder.js';
import {ExtensionError} from '../core/extension-error.js';
import {readResponseJson} from '../core/json.js';
import {JsonSchema} from '../data/json-schema.js';
-import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js';
+import {arrayBufferToBase64} from '../data/sandbox/array-buffer-util.js';
import {NativeSimpleDOMParser} from '../dom/native-simple-dom-parser.js';
import {SimpleDOMParser} from '../dom/simple-dom-parser.js';
import {isStringEntirelyKana} from '../language/japanese.js';
@@ -358,7 +358,7 @@ export class AudioDownloader {
throw new Error('Could not retrieve audio');
}
- const data = ArrayBufferUtil.arrayBufferToBase64(arrayBuffer);
+ const data = arrayBufferToBase64(arrayBuffer);
const contentType = response.headers.get('Content-Type');
return {data, contentType};
}
diff --git a/ext/js/media/media-util.js b/ext/js/media/media-util.js
index 1f9f2d0b..3e492e42 100644
--- a/ext/js/media/media-util.js
+++ b/ext/js/media/media-util.js
@@ -17,119 +17,114 @@
*/
/**
- * MediaUtil is a class containing helper methods related to media processing.
+ * Gets the file extension of a file path. URL search queries and hash
+ * fragments are not handled.
+ * @param {string} path The path to the file.
+ * @returns {string} The file extension, including the '.', or an empty string
+ * if there is no file extension.
*/
-export class MediaUtil {
- /**
- * Gets the file extension of a file path. URL search queries and hash
- * fragments are not handled.
- * @param {string} path The path to the file.
- * @returns {string} The file extension, including the '.', or an empty string
- * if there is no file extension.
- */
- static getFileNameExtension(path) {
- const match = /\.[^./\\]*$/.exec(path);
- return match !== null ? match[0] : '';
- }
+export function getFileNameExtension(path) {
+ const match = /\.[^./\\]*$/.exec(path);
+ return match !== null ? match[0] : '';
+}
- /**
- * Gets an image file's media type using a file path.
- * @param {string} path The path to the file.
- * @returns {?string} The media type string if it can be determined from the file path,
- * otherwise `null`.
- */
- static getImageMediaTypeFromFileName(path) {
- switch (this.getFileNameExtension(path).toLowerCase()) {
- case '.apng':
- return 'image/apng';
- case '.bmp':
- return 'image/bmp';
- case '.gif':
- return 'image/gif';
- case '.ico':
- case '.cur':
- return 'image/x-icon';
- case '.jpg':
- case '.jpeg':
- case '.jfif':
- case '.pjpeg':
- case '.pjp':
- return 'image/jpeg';
- case '.png':
- return 'image/png';
- case '.svg':
- return 'image/svg+xml';
- case '.tif':
- case '.tiff':
- return 'image/tiff';
- case '.webp':
- return 'image/webp';
- default:
- return null;
- }
+/**
+ * Gets an image file's media type using a file path.
+ * @param {string} path The path to the file.
+ * @returns {?string} The media type string if it can be determined from the file path,
+ * otherwise `null`.
+ */
+export function getImageMediaTypeFromFileName(path) {
+ switch (getFileNameExtension(path).toLowerCase()) {
+ case '.apng':
+ return 'image/apng';
+ case '.bmp':
+ return 'image/bmp';
+ case '.gif':
+ return 'image/gif';
+ case '.ico':
+ case '.cur':
+ return 'image/x-icon';
+ case '.jpg':
+ case '.jpeg':
+ case '.jfif':
+ case '.pjpeg':
+ case '.pjp':
+ return 'image/jpeg';
+ case '.png':
+ return 'image/png';
+ case '.svg':
+ return 'image/svg+xml';
+ case '.tif':
+ case '.tiff':
+ return 'image/tiff';
+ case '.webp':
+ return 'image/webp';
+ default:
+ return null;
}
+}
- /**
- * Gets the file extension for a corresponding media type.
- * @param {string} mediaType The media type to use.
- * @returns {?string} A file extension including the dot for the media type,
- * otherwise `null`.
- */
- static getFileExtensionFromImageMediaType(mediaType) {
- switch (mediaType) {
- case 'image/apng':
- return '.apng';
- case 'image/bmp':
- return '.bmp';
- case 'image/gif':
- return '.gif';
- case 'image/x-icon':
- return '.ico';
- case 'image/jpeg':
- return '.jpeg';
- case 'image/png':
- return '.png';
- case 'image/svg+xml':
- return '.svg';
- case 'image/tiff':
- return '.tiff';
- case 'image/webp':
- return '.webp';
- default:
- return null;
- }
+/**
+ * Gets the file extension for a corresponding media type.
+ * @param {string} mediaType The media type to use.
+ * @returns {?string} A file extension including the dot for the media type,
+ * otherwise `null`.
+ */
+export function getFileExtensionFromImageMediaType(mediaType) {
+ switch (mediaType) {
+ case 'image/apng':
+ return '.apng';
+ case 'image/bmp':
+ return '.bmp';
+ case 'image/gif':
+ return '.gif';
+ case 'image/x-icon':
+ return '.ico';
+ case 'image/jpeg':
+ return '.jpeg';
+ case 'image/png':
+ return '.png';
+ case 'image/svg+xml':
+ return '.svg';
+ case 'image/tiff':
+ return '.tiff';
+ case 'image/webp':
+ return '.webp';
+ default:
+ return null;
}
+}
- /**
- * Gets the file extension for a corresponding media type.
- * @param {string} mediaType The media type to use.
- * @returns {?string} A file extension including the dot for the media type,
- * otherwise `null`.
- */
- static getFileExtensionFromAudioMediaType(mediaType) {
- switch (mediaType) {
- case 'audio/aac':
- return '.aac';
- case 'audio/mpeg':
- case 'audio/mp3':
- return '.mp3';
- case 'audio/mp4':
- return '.mp4';
- case 'audio/ogg':
- case 'audio/vorbis':
- return '.ogg';
- case 'audio/vnd.wav':
- case 'audio/wave':
- case 'audio/wav':
- case 'audio/x-wav':
- case 'audio/x-pn-wav':
- return '.wav';
- case 'audio/flac':
- return '.flac';
- case 'audio/webm':
- return '.webm';
- default:
- return null;
- }
+/**
+ * Gets the file extension for a corresponding media type.
+ * @param {string} mediaType The media type to use.
+ * @returns {?string} A file extension including the dot for the media type,
+ * otherwise `null`.
+ */
+export function getFileExtensionFromAudioMediaType(mediaType) {
+ switch (mediaType) {
+ case 'audio/aac':
+ return '.aac';
+ case 'audio/mpeg':
+ case 'audio/mp3':
+ return '.mp3';
+ case 'audio/mp4':
+ return '.mp4';
+ case 'audio/ogg':
+ case 'audio/vorbis':
+ return '.ogg';
+ case 'audio/vnd.wav':
+ case 'audio/wave':
+ case 'audio/wav':
+ case 'audio/x-wav':
+ case 'audio/x-pn-wav':
+ return '.wav';
+ case 'audio/flac':
+ return '.flac';
+ case 'audio/webm':
+ return '.webm';
+ default:
+ return null;
}
}
diff --git a/ext/js/pages/action-popup-main.js b/ext/js/pages/action-popup-main.js
index b978d989..86201e83 100644
--- a/ext/js/pages/action-popup-main.js
+++ b/ext/js/pages/action-popup-main.js
@@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import {PermissionsUtil} from '../data/permissions-util.js';
+import {getAllPermissions, hasRequiredPermissionsForOptions} from '../data/permissions-util.js';
import {querySelectorNotNull} from '../dom/query-selector.js';
import {HotkeyHelpController} from '../input/hotkey-help-controller.js';
import {yomitan} from '../yomitan.js';
@@ -25,8 +25,6 @@ class DisplayController {
constructor() {
/** @type {?import('settings').Options} */
this._optionsFull = null;
- /** @type {PermissionsUtil} */
- this._permissionsUtil = new PermissionsUtil();
}
/** */
@@ -286,8 +284,8 @@ class DisplayController {
* @param {import('settings').ProfileOptions} options
*/
async _updatePermissionsWarnings(options) {
- const permissions = await this._permissionsUtil.getAllPermissions();
- if (this._permissionsUtil.hasRequiredPermissionsForOptions(permissions, options)) { return; }
+ const permissions = await getAllPermissions();
+ if (hasRequiredPermissionsForOptions(permissions, options)) { return; }
const warnings = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.action-open-permissions,.permissions-required-warning'));
for (const node of warnings) {
diff --git a/ext/js/pages/settings/anki-controller.js b/ext/js/pages/settings/anki-controller.js
index d1faf491..09ab3c03 100644
--- a/ext/js/pages/settings/anki-controller.js
+++ b/ext/js/pages/settings/anki-controller.js
@@ -21,7 +21,8 @@ import {EventListenerCollection} from '../../core/event-listener-collection.js';
import {ExtensionError} from '../../core/extension-error.js';
import {log} from '../../core/logger.js';
import {toError} from '../../core/to-error.js';
-import {AnkiUtil} from '../../data/anki-util.js';
+import {stringContainsAnyFieldMarker} from '../../data/anki-util.js';
+import {getRequiredPermissionsForAnkiFieldValue, hasPermissions, setPermissionsGranted} from '../../data/permissions-util.js';
import {querySelectorNotNull} from '../../dom/query-selector.js';
import {SelectorObserver} from '../../dom/selector-observer.js';
import {ObjectPropertyAccessor} from '../../general/object-property-accessor.js';
@@ -211,7 +212,7 @@ export class AnkiController {
* @returns {string[]}
*/
getRequiredPermissions(fieldValue) {
- return this._settingsController.permissionsUtil.getRequiredPermissionsForAnkiFieldValue(fieldValue);
+ return getRequiredPermissionsForAnkiFieldValue(fieldValue);
}
// Private
@@ -738,7 +739,7 @@ class AnkiCardController {
*/
_validateField(node, index) {
let valid = (node.dataset.hasPermissions !== 'false');
- if (valid && index === 0 && !AnkiUtil.stringContainsAnyFieldMarker(node.value)) {
+ if (valid && index === 0 && !stringContainsAnyFieldMarker(node.value)) {
valid = false;
}
node.dataset.invalid = `${!valid}`;
@@ -936,7 +937,7 @@ class AnkiCardController {
*/
async _requestPermissions(permissions) {
try {
- await this._settingsController.permissionsUtil.setPermissionsGranted({permissions}, true);
+ await setPermissionsGranted({permissions}, true);
} catch (e) {
log.error(e);
}
@@ -952,12 +953,12 @@ class AnkiCardController {
const permissions = this._ankiController.getRequiredPermissions(fieldValue);
if (permissions.length > 0) {
node.dataset.requiredPermission = permissions.join(' ');
- const hasPermissions = await (
+ const hasPermissions2 = await (
request ?
- this._settingsController.permissionsUtil.setPermissionsGranted({permissions}, true) :
- this._settingsController.permissionsUtil.hasPermissions({permissions})
+ setPermissionsGranted({permissions}, true) :
+ hasPermissions({permissions})
);
- node.dataset.hasPermissions = `${hasPermissions}`;
+ node.dataset.hasPermissions = `${hasPermissions2}`;
} else {
delete node.dataset.requiredPermission;
delete node.dataset.hasPermissions;
@@ -977,15 +978,15 @@ class AnkiCardController {
if (typeof requiredPermission !== 'string') { continue; }
const requiredPermissionArray = (requiredPermission.length === 0 ? [] : requiredPermission.split(' '));
- let hasPermissions = true;
+ let hasPermissions2 = true;
for (const permission of requiredPermissionArray) {
if (!permissionsSet.has(permission)) {
- hasPermissions = false;
+ hasPermissions2 = false;
break;
}
}
- inputField.dataset.hasPermissions = `${hasPermissions}`;
+ inputField.dataset.hasPermissions = `${hasPermissions2}`;
this._validateField(inputField, i);
}
}
diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js
index f2eccd1e..053cc96b 100644
--- a/ext/js/pages/settings/backup-controller.js
+++ b/ext/js/pages/settings/backup-controller.js
@@ -22,7 +22,8 @@ import {log} from '../../core/logger.js';
import {toError} from '../../core/to-error.js';
import {isObject} from '../../core/utilities.js';
import {OptionsUtil} from '../../data/options-util.js';
-import {ArrayBufferUtil} from '../../data/sandbox/array-buffer-util.js';
+import {getAllPermissions} from '../../data/permissions-util.js';
+import {arrayBufferUtf8Decode} from '../../data/sandbox/array-buffer-util.js';
import {querySelectorNotNull} from '../../dom/query-selector.js';
import {yomitan} from '../../yomitan.js';
import {DictionaryController} from './dictionary-controller.js';
@@ -135,7 +136,7 @@ export class BackupController {
const optionsFull = await this._settingsController.getOptionsFull();
const environment = await yomitan.api.getEnvironmentInfo();
const fieldTemplatesDefault = await yomitan.api.getDefaultAnkiFieldTemplates();
- const permissions = await this._settingsController.permissionsUtil.getAllPermissions();
+ const permissions = await getAllPermissions();
// Format options
for (const {options} of optionsFull.profiles) {
@@ -425,7 +426,7 @@ export class BackupController {
async _importSettingsFile(file) {
if (this._optionsUtil === null) { throw new Error('OptionsUtil invalid'); }
- const dataString = ArrayBufferUtil.arrayBufferUtf8Decode(await this._readFileArrayBuffer(file));
+ const dataString = arrayBufferUtf8Decode(await this._readFileArrayBuffer(file));
/** @type {import('backup-controller').BackupData} */
const data = parseJson(dataString);
diff --git a/ext/js/pages/settings/permissions-origin-controller.js b/ext/js/pages/settings/permissions-origin-controller.js
index a0f23af6..9447e3cc 100644
--- a/ext/js/pages/settings/permissions-origin-controller.js
+++ b/ext/js/pages/settings/permissions-origin-controller.js
@@ -18,6 +18,7 @@
import {EventListenerCollection} from '../../core/event-listener-collection.js';
import {toError} from '../../core/to-error.js';
+import {getAllPermissions, setPermissionsGranted} from '../../data/permissions-util.js';
import {querySelectorNotNull} from '../../dom/query-selector.js';
export class PermissionsOriginController {
@@ -140,7 +141,7 @@ export class PermissionsOriginController {
/** */
async _updatePermissions() {
- const permissions = await this._settingsController.permissionsUtil.getAllPermissions();
+ const permissions = await getAllPermissions();
this._onPermissionsChanged({permissions});
}
@@ -152,7 +153,7 @@ export class PermissionsOriginController {
async _setOriginPermissionEnabled(origin, enabled) {
let added = false;
try {
- added = await this._settingsController.permissionsUtil.setPermissionsGranted({origins: [origin]}, enabled);
+ added = await setPermissionsGranted({origins: [origin]}, enabled);
} catch (e) {
const errorContainer = /** @type {HTMLElement} */ (this._errorContainer);
errorContainer.hidden = false;
diff --git a/ext/js/pages/settings/permissions-toggle-controller.js b/ext/js/pages/settings/permissions-toggle-controller.js
index c775aa12..25204dce 100644
--- a/ext/js/pages/settings/permissions-toggle-controller.js
+++ b/ext/js/pages/settings/permissions-toggle-controller.js
@@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import {getAllPermissions, hasPermissions, setPermissionsGranted} from '../../data/permissions-util.js';
import {ObjectPropertyAccessor} from '../../general/object-property-accessor.js';
export class PermissionsToggleController {
@@ -85,11 +86,11 @@ export class PermissionsToggleController {
toggle.checked = valuePre;
const permissions = this._getRequiredPermissions(toggle);
try {
- value = await this._settingsController.permissionsUtil.setPermissionsGranted({permissions}, value);
+ value = await setPermissionsGranted({permissions}, value);
} catch (error) {
value = valuePre;
try {
- value = await this._settingsController.permissionsUtil.hasPermissions({permissions});
+ value = await hasPermissions({permissions});
} catch (error2) {
// NOP
}
@@ -111,13 +112,13 @@ export class PermissionsToggleController {
const permissionsSet = new Set(typeof permissions2 !== 'undefined' ? permissions2 : []);
for (const toggle of /** @type {NodeListOf<HTMLInputElement>} */ (this._toggles)) {
const {permissionsSetting} = toggle.dataset;
- const hasPermissions = this._hasAll(permissionsSet, this._getRequiredPermissions(toggle));
+ const hasPermissions2 = this._hasAll(permissionsSet, this._getRequiredPermissions(toggle));
if (typeof permissionsSetting === 'string') {
- const valid = !toggle.checked || hasPermissions;
+ const valid = !toggle.checked || hasPermissions2;
this._setToggleValid(toggle, valid);
} else {
- toggle.checked = hasPermissions;
+ toggle.checked = hasPermissions2;
}
}
}
@@ -134,7 +135,7 @@ export class PermissionsToggleController {
/** */
async _updateValidity() {
- const permissions = await this._settingsController.permissionsUtil.getAllPermissions();
+ const permissions = await getAllPermissions();
this._onPermissionsChanged({permissions});
}
diff --git a/ext/js/pages/settings/recommended-permissions-controller.js b/ext/js/pages/settings/recommended-permissions-controller.js
index 84a4ef10..b7bb1ea8 100644
--- a/ext/js/pages/settings/recommended-permissions-controller.js
+++ b/ext/js/pages/settings/recommended-permissions-controller.js
@@ -18,6 +18,7 @@
import {EventListenerCollection} from '../../core/event-listener-collection.js';
import {toError} from '../../core/to-error.js';
+import {getAllPermissions, setPermissionsGranted} from '../../data/permissions-util.js';
export class RecommendedPermissionsController {
/**
@@ -77,7 +78,7 @@ export class RecommendedPermissionsController {
/** */
async _updatePermissions() {
- const permissions = await this._settingsController.permissionsUtil.getAllPermissions();
+ const permissions = await getAllPermissions();
this._onPermissionsChanged({permissions});
}
@@ -89,7 +90,7 @@ export class RecommendedPermissionsController {
async _setOriginPermissionEnabled(origin, enabled) {
let added = false;
try {
- added = await this._settingsController.permissionsUtil.setPermissionsGranted({origins: [origin]}, enabled);
+ added = await setPermissionsGranted({origins: [origin]}, enabled);
} catch (e) {
if (this._errorContainer !== null) {
this._errorContainer.hidden = false;
diff --git a/ext/js/pages/settings/settings-controller.js b/ext/js/pages/settings/settings-controller.js
index 25f5e8ad..49fa9c9e 100644
--- a/ext/js/pages/settings/settings-controller.js
+++ b/ext/js/pages/settings/settings-controller.js
@@ -20,7 +20,7 @@ import {EventDispatcher} from '../../core/event-dispatcher.js';
import {EventListenerCollection} from '../../core/event-listener-collection.js';
import {generateId, isObject} from '../../core/utilities.js';
import {OptionsUtil} from '../../data/options-util.js';
-import {PermissionsUtil} from '../../data/permissions-util.js';
+import {getAllPermissions} from '../../data/permissions-util.js';
import {HtmlTemplateCollection} from '../../dom/html-template-collection.js';
import {yomitan} from '../../yomitan.js';
@@ -41,8 +41,6 @@ export class SettingsController extends EventDispatcher {
/** @type {HtmlTemplateCollection} */
this._templates = new HtmlTemplateCollection();
this._templates.load(document);
- /** @type {PermissionsUtil} */
- this._permissionsUtil = new PermissionsUtil();
}
/** @type {string} */
@@ -60,11 +58,6 @@ export class SettingsController extends EventDispatcher {
this._setProfileIndex(value, true);
}
- /** @type {PermissionsUtil} */
- get permissionsUtil() {
- return this._permissionsUtil;
- }
-
/** */
async prepare() {
yomitan.on('optionsUpdated', this._onOptionsUpdated.bind(this));
@@ -338,7 +331,7 @@ export class SettingsController extends EventDispatcher {
const eventName = 'permissionsChanged';
if (!this.hasListeners(eventName)) { return; }
- const permissions = await this._permissionsUtil.getAllPermissions();
+ const permissions = await getAllPermissions();
this.trigger(eventName, {permissions});
}
diff --git a/ext/js/templates/sandbox/anki-template-renderer-content-manager.js b/ext/js/templates/sandbox/anki-template-renderer-content-manager.js
index 932b6ab7..664746bf 100644
--- a/ext/js/templates/sandbox/anki-template-renderer-content-manager.js
+++ b/ext/js/templates/sandbox/anki-template-renderer-content-manager.js
@@ -24,7 +24,6 @@ export class AnkiTemplateRendererContentManager {
* Creates a new instance of the class.
* @param {import('./template-renderer-media-provider.js').TemplateRendererMediaProvider} mediaProvider The media provider for the object.
* @param {import('anki-templates').NoteData} data The data object passed to the Handlebars template renderer.
- * See AnkiNoteDataCreator.create's return value for structure information.
*/
constructor(mediaProvider, data) {
/** @type {import('./template-renderer-media-provider.js').TemplateRendererMediaProvider} */
diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js
index 52087336..26d3f336 100644
--- a/ext/js/templates/sandbox/anki-template-renderer.js
+++ b/ext/js/templates/sandbox/anki-template-renderer.js
@@ -17,9 +17,9 @@
*/
import {Handlebars} from '../../../lib/handlebars.js';
-import {AnkiNoteDataCreator} from '../../data/sandbox/anki-note-data-creator.js';
-import {DictionaryDataUtil} from '../../dictionary/dictionary-data-util.js';
-import {PronunciationGenerator} from '../../display/sandbox/pronunciation-generator.js';
+import {createAnkiNoteData} from '../../data/sandbox/anki-note-data-creator.js';
+import {getPronunciationsOfType, isNonNounVerbOrAdjective} from '../../dictionary/dictionary-data-util.js';
+import {createPronunciationDownstepPosition, createPronunciationGraph, createPronunciationText} from '../../display/sandbox/pronunciation-generator.js';
import {StructuredContentGenerator} from '../../display/sandbox/structured-content-generator.js';
import {CssStyleApplier} from '../../dom/sandbox/css-style-applier.js';
import {convertHiraganaToKatakana, convertKatakanaToHiragana, distributeFurigana, getKanaMorae, getPitchCategory, isMoraPitchHigh} from '../../language/japanese.js';
@@ -44,12 +44,8 @@ export class AnkiTemplateRenderer {
this._structuredContentDatasetKeyIgnorePattern = /^sc([^a-z]|$)/;
/** @type {TemplateRenderer} */
this._templateRenderer = new TemplateRenderer();
- /** @type {AnkiNoteDataCreator} */
- this._ankiNoteDataCreator = new AnkiNoteDataCreator();
/** @type {TemplateRendererMediaProvider} */
this._mediaProvider = new TemplateRendererMediaProvider();
- /** @type {PronunciationGenerator} */
- this._pronunciationGenerator = new PronunciationGenerator();
/** @type {?(Map<string, unknown>[])} */
this._stateStack = null;
/** @type {?import('anki-note-builder').Requirement[]} */
@@ -104,7 +100,7 @@ export class AnkiTemplateRenderer {
]);
/* eslint-enable no-multi-spaces */
this._templateRenderer.registerDataType('ankiNote', {
- modifier: ({marker, commonData}) => this._ankiNoteDataCreator.create(marker, commonData),
+ modifier: ({marker, commonData}) => createAnkiNoteData(marker, commonData),
composeData: ({marker}, commonData) => ({marker, commonData})
});
this._templateRenderer.setRenderCallbacks(
@@ -550,8 +546,8 @@ export class AnkiTemplateRenderer {
const categories = new Set();
for (const {headwordIndex, pronunciations} of termPronunciations) {
const {reading, wordClasses} = headwords[headwordIndex];
- const isVerbOrAdjective = DictionaryDataUtil.isNonNounVerbOrAdjective(wordClasses);
- const pitches = DictionaryDataUtil.getPronunciationsOfType(pronunciations, 'pitch-accent');
+ const isVerbOrAdjective = isNonNounVerbOrAdjective(wordClasses);
+ const pitches = getPronunciationsOfType(pronunciations, 'pitch-accent');
for (const {position} of pitches) {
const category = getPitchCategory(reading, position, isVerbOrAdjective);
if (category !== null) {
@@ -737,11 +733,11 @@ export class AnkiTemplateRenderer {
switch (format) {
case 'text':
- return this._getPronunciationHtml(this._pronunciationGenerator.createPronunciationText(morae, downstepPosition, nasalPositions, devoicePositions));
+ return this._getPronunciationHtml(createPronunciationText(morae, downstepPosition, nasalPositions, devoicePositions));
case 'graph':
- return this._getPronunciationHtml(this._pronunciationGenerator.createPronunciationGraph(morae, downstepPosition));
+ return this._getPronunciationHtml(createPronunciationGraph(morae, downstepPosition));
case 'position':
- return this._getPronunciationHtml(this._pronunciationGenerator.createPronunciationDownstepPosition(downstepPosition));
+ return this._getPronunciationHtml(createPronunciationDownstepPosition(downstepPosition));
default:
return '';
}
diff --git a/test/dictionary-data.test.js b/test/dictionary-data.test.js
index 438e1e97..9f8ba6f0 100644
--- a/test/dictionary-data.test.js
+++ b/test/dictionary-data.test.js
@@ -53,7 +53,10 @@ describe('Dictionary data', () => {
expected3: expectedResults3[i]
}));
describe.each(testCases)('Test %#: $data.name', ({data, expected1, expected2, expected3}) => {
- test('Test', async ({translator, ankiNoteDataCreator, expect}) => {
+ test('Test', async ({window, translator, expect}) => {
+ // The window property needs to be referenced for it to be initialized.
+ // It is needed for DOM access for structured content.
+ void window;
switch (data.func) {
case 'findTerms':
{
@@ -62,7 +65,7 @@ describe('Dictionary data', () => {
const options = createFindOptions(dictionaryName, optionsPresets, data.options);
const {dictionaryEntries, originalTextLength} = await translator.findTerms(mode, text, options);
const renderResults = mode !== 'simple' ? await getTemplateRenderResults(dictionaryEntries, 'terms', mode, template, expect) : null;
- const noteDataList = mode !== 'simple' ? dictionaryEntries.map((dictionaryEntry) => createTestAnkiNoteData(ankiNoteDataCreator, dictionaryEntry, mode)) : null;
+ const noteDataList = mode !== 'simple' ? dictionaryEntries.map((dictionaryEntry) => createTestAnkiNoteData(dictionaryEntry, mode)) : null;
expect.soft(originalTextLength).toStrictEqual(expected1.originalTextLength);
expect.soft(dictionaryEntries).toStrictEqual(expected1.dictionaryEntries);
expect.soft(noteDataList).toEqual(expected2.noteDataList);
@@ -76,7 +79,7 @@ describe('Dictionary data', () => {
const options = createFindOptions(dictionaryName, optionsPresets, data.options);
const dictionaryEntries = await translator.findKanji(text, options);
const renderResults = await getTemplateRenderResults(dictionaryEntries, 'kanji', 'split', template, expect);
- const noteDataList = dictionaryEntries.map((dictionaryEntry) => createTestAnkiNoteData(ankiNoteDataCreator, dictionaryEntry, 'split'));
+ const noteDataList = dictionaryEntries.map((dictionaryEntry) => createTestAnkiNoteData(dictionaryEntry, 'split'));
expect.soft(dictionaryEntries).toStrictEqual(expected1.dictionaryEntries);
expect.soft(noteDataList).toEqual(expected2.noteDataList);
expect.soft(renderResults).toStrictEqual(expected3.results);
diff --git a/test/dictionary-data.write.js b/test/dictionary-data.write.js
index bdf635c8..d88bd3cd 100644
--- a/test/dictionary-data.write.js
+++ b/test/dictionary-data.write.js
@@ -35,7 +35,7 @@ const dirname = path.dirname(fileURLToPath(import.meta.url));
const dictionaryName = 'Test Dictionary 2';
const test = await createTranslatorTest(void 0, path.join(dirname, 'data/dictionaries/valid-dictionary1'), dictionaryName);
-test('Write dictionary data expected data', async ({translator, ankiNoteDataCreator, expect}) => {
+test('Write dictionary data expected data', async ({translator, expect}) => {
const testInputsFilePath = path.join(dirname, 'data/translator-test-inputs.json');
/** @type {import('test/translator').TranslatorTestInputs} */
const {optionsPresets, tests} = parseJson(readFileSync(testInputsFilePath, {encoding: 'utf8'}));
@@ -63,7 +63,7 @@ test('Write dictionary data expected data', async ({translator, ankiNoteDataCrea
const options = createFindOptions(dictionaryName, optionsPresets, data.options);
const {dictionaryEntries, originalTextLength} = await translator.findTerms(mode, text, options);
const renderResults = mode !== 'simple' ? await getTemplateRenderResults(dictionaryEntries, 'terms', mode, template, null) : null;
- const noteDataList = mode !== 'simple' ? dictionaryEntries.map((dictionaryEntry) => createTestAnkiNoteData(ankiNoteDataCreator, dictionaryEntry, mode)) : null;
+ const noteDataList = mode !== 'simple' ? dictionaryEntries.map((dictionaryEntry) => createTestAnkiNoteData(dictionaryEntry, mode)) : null;
actualResults1.push({name, originalTextLength, dictionaryEntries});
actualResults2.push({name, noteDataList});
actualResults3.push({name, results: renderResults});
@@ -76,7 +76,7 @@ test('Write dictionary data expected data', async ({translator, ankiNoteDataCrea
const options = createFindOptions(dictionaryName, optionsPresets, data.options);
const dictionaryEntries = await translator.findKanji(text, options);
const renderResults = await getTemplateRenderResults(dictionaryEntries, 'kanji', 'split', template, null);
- const noteDataList = dictionaryEntries.map((dictionaryEntry) => createTestAnkiNoteData(ankiNoteDataCreator, dictionaryEntry, 'split'));
+ const noteDataList = dictionaryEntries.map((dictionaryEntry) => createTestAnkiNoteData(dictionaryEntry, 'split'));
actualResults1.push({name, dictionaryEntries});
actualResults2.push({name, noteDataList});
actualResults3.push({name, results: renderResults});
diff --git a/test/fixtures/translator-test.js b/test/fixtures/translator-test.js
index 58247b70..d1b3de8b 100644
--- a/test/fixtures/translator-test.js
+++ b/test/fixtures/translator-test.js
@@ -23,7 +23,6 @@ import {dirname, join} from 'path';
import {expect, vi} from 'vitest';
import {parseJson} from '../../dev/json.js';
import {createDictionaryArchive} from '../../dev/util.js';
-import {AnkiNoteDataCreator} from '../../ext/js/data/sandbox/anki-note-data-creator.js';
import {DictionaryDatabase} from '../../ext/js/dictionary/dictionary-database.js';
import {DictionaryImporter} from '../../ext/js/dictionary/dictionary-importer.js';
import {Translator} from '../../ext/js/language/translator.js';
@@ -42,7 +41,7 @@ vi.stubGlobal('chrome', chrome);
/**
* @param {string} dictionaryDirectory
* @param {string} dictionaryName
- * @returns {Promise<{translator: Translator, ankiNoteDataCreator: AnkiNoteDataCreator}>}
+ * @returns {Promise<Translator>}
*/
async function createTranslatorContext(dictionaryDirectory, dictionaryName) {
// Dictionary
@@ -69,31 +68,23 @@ async function createTranslatorContext(dictionaryDirectory, dictionaryName) {
const deinflectionReasons = parseJson(readFileSync(languageTransformDescriptorPath, {encoding: 'utf8'}));
translator.prepare(deinflectionReasons);
- // Assign properties
- const ankiNoteDataCreator = new AnkiNoteDataCreator();
- return {translator, ankiNoteDataCreator};
+ return translator;
}
/**
* @param {string|undefined} htmlFilePath
* @param {string} dictionaryDirectory
* @param {string} dictionaryName
- * @returns {Promise<import('vitest').TestAPI<{window: import('jsdom').DOMWindow, translator: Translator, ankiNoteDataCreator: AnkiNoteDataCreator}>>}
+ * @returns {Promise<import('vitest').TestAPI<{window: import('jsdom').DOMWindow, translator: Translator}>>}
*/
export async function createTranslatorTest(htmlFilePath, dictionaryDirectory, dictionaryName) {
const test = createDomTest(htmlFilePath);
- const {translator, ankiNoteDataCreator} = await createTranslatorContext(dictionaryDirectory, dictionaryName);
- /** @type {import('vitest').TestAPI<{window: import('jsdom').DOMWindow, translator: Translator, ankiNoteDataCreator: AnkiNoteDataCreator}>} */
+ const translator = await createTranslatorContext(dictionaryDirectory, dictionaryName);
+ /** @type {import('vitest').TestAPI<{window: import('jsdom').DOMWindow, translator: Translator}>} */
const result = test.extend({
window: async ({window}, use) => { await use(window); },
// eslint-disable-next-line no-empty-pattern
- translator: async ({}, use) => { await use(translator); },
- ankiNoteDataCreator: async ({window}, use) => {
- // The window property needs to be referenced for it to be initialized.
- // It is needed for DOM access for structured content.
- void window;
- await use(ankiNoteDataCreator);
- }
+ translator: async ({}, use) => { await use(translator); }
});
return result;
}
diff --git a/test/profile-conditions-util.test.js b/test/profile-conditions-util.test.js
index 7af5f223..fcd53939 100644
--- a/test/profile-conditions-util.test.js
+++ b/test/profile-conditions-util.test.js
@@ -17,7 +17,7 @@
*/
import {describe, expect, test} from 'vitest';
-import {ProfileConditionsUtil} from '../ext/js/background/profile-conditions-util.js';
+import {createSchema, normalizeContext} from '../ext/js/background/profile-conditions-util.js';
/** */
function testNormalizeContext() {
@@ -50,8 +50,7 @@ function testNormalizeContext() {
];
test.each(data)('normalize-context-test-%#', ({context, expected}) => {
- const profileConditionsUtil = new ProfileConditionsUtil();
- const actual = profileConditionsUtil.normalizeContext(context);
+ const actual = normalizeContext(context);
expect(actual).toStrictEqual(expected);
});
});
@@ -1101,14 +1100,13 @@ function testSchemas() {
/* eslint-enable no-multi-spaces */
test.each(data)('schemas-test-%#', ({conditionGroups, expectedSchema, inputs}) => {
- const profileConditionsUtil = new ProfileConditionsUtil();
- const schema = profileConditionsUtil.createSchema(conditionGroups);
+ const schema = createSchema(conditionGroups);
if (typeof expectedSchema !== 'undefined') {
expect(schema.schema).toStrictEqual(expectedSchema);
}
if (Array.isArray(inputs)) {
for (const {expected, context} of inputs) {
- const normalizedContext = profileConditionsUtil.normalizeContext(context);
+ const normalizedContext = normalizeContext(context);
const actual = schema.isValid(normalizedContext);
expect(actual).toStrictEqual(expected);
}
diff --git a/test/utilities/anki.js b/test/utilities/anki.js
index e30d578f..69f4ce8b 100644
--- a/test/utilities/anki.js
+++ b/test/utilities/anki.js
@@ -16,16 +16,16 @@
*/
import {AnkiNoteBuilder} from '../../ext/js/data/anki-note-builder.js';
+import {createAnkiNoteData} from '../../ext/js/data/sandbox/anki-note-data-creator.js';
import {AnkiTemplateRenderer} from '../../ext/js/templates/sandbox/anki-template-renderer.js';
/**
- * @param {import('../../ext/js/data/sandbox/anki-note-data-creator.js').AnkiNoteDataCreator} ankiNoteDataCreator
* @param {import('dictionary').DictionaryEntry} dictionaryEntry
* @param {import('settings').ResultOutputMode} mode
* @returns {import('anki-templates').NoteData}
* @throws {Error}
*/
-export function createTestAnkiNoteData(ankiNoteDataCreator, dictionaryEntry, mode) {
+export function createTestAnkiNoteData(dictionaryEntry, mode) {
const marker = '{marker}';
/** @type {import('anki-templates-internal').CreateDetails} */
const data = {
@@ -43,7 +43,7 @@ export function createTestAnkiNoteData(ankiNoteDataCreator, dictionaryEntry, mod
},
media: {}
};
- return ankiNoteDataCreator.create(marker, data);
+ return createAnkiNoteData(marker, data);
}
/**