diff --git a/.eslintrc.json b/.eslintrc.json index 9a0402edb6..0fa398264c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -594,7 +594,7 @@ "error", "block", { - "pattern": " \\* Copyright \\(C\\) (\\d+-)?2023 Yomitan Authors(\n \\* Copyright \\(C\\) (\\d+-)?2022 Yomichan Authors)?\n \\*\n \\* This program is free software: you can redistribute it and/or modify\n \\* it under the terms of the GNU General Public License as published by\n \\* the Free Software Foundation, either version 3 of the License, or\n \\* \\(at your option\\) any later version\\.\n \\*\n \\* This program is distributed in the hope that it will be useful,\n \\* but WITHOUT ANY WARRANTY; without even the implied warranty of\n \\* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE\\. See the\n \\* GNU General Public License for more details\\.\n \\*\n \\* You should have received a copy of the GNU General Public License\n \\* along with this program\\. If not, see \\.\n " + "pattern": " \\* Copyright \\(C\\) (2023-)?2024 Yomitan Authors(\n \\* Copyright \\(C\\) (20(16|17|18|19|20|21)-)?2022 Yomichan Authors)?\n \\*\n \\* This program is free software: you can redistribute it and/or modify\n \\* it under the terms of the GNU General Public License as published by\n \\* the Free Software Foundation, either version 3 of the License, or\n \\* \\(at your option\\) any later version\\.\n \\*\n \\* This program is distributed in the hope that it will be useful,\n \\* but WITHOUT ANY WARRANTY; without even the implied warranty of\n \\* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE\\. See the\n \\* GNU General Public License for more details\\.\n \\*\n \\* You should have received a copy of the GNU General Public License\n \\* along with this program\\. If not, see \\.\n " } ] } diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index fa6a4923c3..2aa209746b 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -17,4 +17,4 @@ assignees: '' (e.g. 20.11.1.1) **Exported settings file** -(If you think it might be relevant, create a settings export file using the _Export Settings_ button on the settings page; zip or rename the resulting file to .txt to attach to the issue.) +(If you think it might be relevant, create a settings export file using the _Export Settings_ button on the settings page and attach it to this issue.) diff --git a/README.md b/README.md index 59d07d9561..c260180a55 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,32 @@ -# Yomitan +# Yomitan +[![Chrome Release]()](https://chrome.google.com/webstore/detail/yomitan/likgccmbimhjbgkjambclfkhldnlhbnn) +[![Firefox Release]()](https://addons.mozilla.org/en-US/firefox/addon/yomitan/) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/themoeway/yomitan/badge)](https://securityscorecards.dev/viewer/?uri=github.com/themoeway/yomitan) +[![Discord](https://dcbadge.vercel.app/api/server/UGNPMDE7zC?style=flat)](https://discord.gg/UGNPMDE7zC) -[![Chrome Release (Stable)]()](https://chrome.google.com/webstore/detail/yomitan/likgccmbimhjbgkjambclfkhldnlhbnn) [![Firefox Release (Stable)]()](https://addons.mozilla.org/en-US/firefox/addon/yomitan/) -[![Chrome Release (Testing)]()](https://chrome.google.com/webstore/detail/yomitan-development-build/glnaenfapkkecknnmginabpmgkenenml) [![Firefox Release (Testing)]()](https://github.com/themoeway/yomitan/releases) -[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/themoeway/yomitan/badge)](https://securityscorecards.dev/viewer/?uri=github.com/themoeway/yomitan) [![Discord Server](https://dcbadge.vercel.app/api/server/UGNPMDE7zC?style=flat)](https://discord.gg/UGNPMDE7zC) +:wave: **This project is a community fork of Yomichan** (which was [sunset](https://foosoft.net/posts/sunsetting-the-yomichan-project/) by its owner on Feb 26 2023). We have made a number of foundational changes to ensure **the project stays alive, works on latest browser versions, and is easy to contribute to**: -## Project Introduction - -:wave: **This project is a community fork of Yomichan** (which was [sunset](https://foosoft.net/posts/sunsetting-the-yomichan-project/) by its owner on Feb 26 2023). - -We have made a number of foundational changes to ensure **the project stays alive, works on latest browser versions, and is easy to contribute to**: - -- Completed the Manifest V2 → Manifest V3 transition, which is required to submit a new extension to the Chrome webstore. It will also be long-term required for usage of the extension, as [Manifest V2 extensions will start being disabled as early as June 2024](https://developer.chrome.com/blog/resuming-the-transition-to-mv3/). -- Switched to using ECMAScript modules and npm-sourced dependencies to make for a more modern coding and packaging experience. -- Implemented an end-to-end CI/CD pipeline to make it easy to rapidly iterate and deploy new versions. -- Switched to standard testing frameworks, vitest and playwright, to make it easier to develop more comprehensive tests, and detect regressions. +- Completed the Manifest V2 → V3 transition, [read why here!](https://developer.chrome.com/blog/resuming-the-transition-to-mv3/). +- Switched to using ECMAScript modules and npm-sourced dependencies. +- Implemented an end-to-end CI/CD pipeline. +- Switched to standard testing frameworks, vitest and playwrights. In addition, we are beginning to make important bug fixes and minor enhancements: -- Improve dictionary import speed by 2x~10x or more (depending on the dictionary) -- Fix UI regressions on modern browser versions, like [the popup being too small](https://github.com/themoeway/yomitan/pull/228) -- Add functionality to import/export multiple dictionaries, to make your data more portable across machines -- And [more](https://github.com/themoeway/yomitan/pulls?q=is%3Apr+is%3Amerged+-label%3Aarea%2Fdependencies+-label%3Akind%2Fmeta) +- Improve dictionary import speeds by 2x~10x. +- Add functionality to import/export multiple dictionaries, enabling portability across devices. +- And [more](https://github.com/themoeway/yomitan/pulls?q=is%3Apr+is%3Amerged+-label%3Aarea%2Fdependencies+-label%3Akind%2Fmeta). Since the owner requested forks be uniquely named, we have chosen a new name, _yomitan_. (_-tan_ is an honorific used for anthropomorphic moe characters.) While we've made some substantial changes, the majority of the extension's functionality is thanks to hard work of foosoft and numerous other open source contributors from 2016-2023. -Since this is a distributed effort, we **highly welcome new contributors**! Feel free to browse the issue tracker, and you can find us on [TheMoeWay Discord](https://discord.gg/UGNPMDE7zC) at [#yomitan-development](https://discord.com/channels/617136488840429598/1081538711742844980). +## Contributing -## Tool Introduction +Since this is a distributed effort, we **highly welcome new contributors**! Feel free to browse the [issue tracker](https://github.com/themoeway/yomitan/issues), and read our [contributing guidelines](./CONTRIBUTING.md). You can also find us on [TheMoeWay Discord](https://discord.gg/UGNPMDE7zC) at [#yomitan-development](https://discord.com/channels/617136488840429598/1081538711742844980). + +## What's Yomitan? Yomitan turns your web browser into a tool for building Japanese language literacy by helping you to decipher texts -which would be otherwise too difficult tackle. This extension is similar to -[10ten Japanese Reader (formerly Rikaichamp)](https://addons.mozilla.org/en-US/firefox/addon/10ten-ja-reader/) for Firefox and -[Rikaikun](https://chrome.google.com/webstore/detail/rikaikun/jipdnfibhldikgcjhfnomkfpcebammhp?hl=en) for Chrome, but it -stands apart in its goal of being an all-encompassing learning tool as opposed to a mere browser-based dictionary. +which would be otherwise too difficult tackle. This extension is similar to [10ten Japanese Reader (formerly Rikaichamp)](https://addons.mozilla.org/en-US/firefox/addon/10ten-ja-reader/) for Firefox and [Rikaikun](https://chrome.google.com/webstore/detail/rikaikun/jipdnfibhldikgcjhfnomkfpcebammhp?hl=en) for Chrome, but it stands apart in its goal of being an all-encompassing learning tool as opposed to a mere browser-based dictionary. Yomitan provides advanced features not available in other browser-based dictionaries: @@ -49,39 +43,20 @@ Yomitan provides advanced features not available in other browser-based dictiona [![Dictionary options](img/ss-dictionaries-thumb.png)](img/ss-dictionaries.png) [![Anki options](img/ss-anki-thumb.png)](img/ss-anki.png) -## Table of Contents - -- [Project Introduction](#project-introduction) -- [Tool Introduction](#tool-introduction) -- [Installation](#installation) -- [Migrating from Yomichan](#migrating-from-yomichan) - - [Exporting Data](#exporting-data) - - [Custom Templates](#custom-templates) -- [Dictionaries](#dictionaries) -- [Basic Usage](#basic-usage) - - [Importing Dictionaries](#importing-dictionaries) - - [Importing and Exporting Personal Configuration](#importing-and-exporting-personal-configuration) -- [Custom Dictionaries](#custom-dictionaries) -- [Anki Integration](#anki-integration) - - [Flashcard Configuration](#flashcard-configuration) - - [Flashcard Creation](#flashcard-creation) -- [Keyboard Shortcuts](#keyboard-shortcuts) -- [Advanced Options](#advanced-options) - - [Parse sentences using MeCab](#parse-sentences-using-mecab) -- [Frequently Asked Questions](#frequently-asked-questions) -- [Licenses](#licenses) -- [Third-Party Libraries](#third-party-libraries) +## Helpful information + +- [Migrating from Yomichan (legacy)](./docs/yomichan-migration.md#migrating-from-yomichan) +- [Importing standardised and custom dictionaries](./docs/dictionaries.md#dictionaries) +- [Anki integration and flashcards creation](./docs/anki-integration.md#anki-integration) +- [Advanced options, including MeCab](./docs/advanced-options.md#advanced-options) +- [Frequently asked questions](./docs/faq.md#frequently-asked-questions) +- [Keyboard shortcuts](./docs/keyboard-shortcuts.md) ## Installation -Yomitan comes in two flavors: _stable_ and _testing_. Over the years, this extension has evolved to contain many -complex features which have become increasingly difficult to test across different browsers, versions, and environments. -New changes are initially introduced into the _testing_ version, and after some time spent ensuring that they are -relatively bug free, they will be promoted to the _stable_ version. If you are technically savvy and don't mind -submitting issues on GitHub, try the _testing_ version; otherwise, the _stable_ version will be your best bet. +Yomitan comes in two flavors: _stable_ and _testing_. New changes are initially introduced into the _testing_ version, and after some time spent ensuring that they are relatively bug free, they will be promoted to the _stable_ version. If you are technically savvy and don't mind submitting issues on GitHub, try the _testing_ version; otherwise, the _stable_ version will be your best bet. - **Google Chrome** - - [stable](https://chrome.google.com/webstore/detail/yomitan/likgccmbimhjbgkjambclfkhldnlhbnn) - [testing](https://chrome.google.com/webstore/detail/yomitan-development-build/glnaenfapkkecknnmginabpmgkenenml) @@ -89,323 +64,36 @@ submitting issues on GitHub, try the _testing_ version; otherwise, the _stable_ - [stable](https://addons.mozilla.org/en-US/firefox/addon/yomitan/) - [testing](https://github.com/themoeway/yomitan/releases) ※ -※ NOTE: Unlike Chrome, Firefox does not allow extensions meant for testing to be hosted in the marketplace. +※ Unlike Chrome, Firefox does not allow extensions meant for testing to be hosted in the marketplace. You will have to download a desired version and side-load it yourself. You only need to do this once and will get updates automatically. -## Migrating from Yomichan - -### Exporting Data - -If you are an existing user of Yomichan, you can export your dictionary collection and settings such that they can be imported into Yomitan to reflect your setup exactly as it was. - -You can export your settings from Yomichan's Settings page. Go to the `Backup` section and click on `Export Settings`. - -Yomichan doesn't have first-class support to export the dictionary collection. Please follow the instructions provided in the following link to export your data: -https://github.com/themoeway/yomichan-data-exporter#steps-to-export-the-data - -You can then import the exported files into Yomitan from the `Backup` section of the `Settings` page. Please see [the section on importing dictionaries](#importing-dictionaries) further below for more explicit steps. - -### Custom Templates - -If you do not use custom templates for Anki note creation, this section can be skipped. - -Due to security concerns, an alternate implementation of Handlebars is being used which behaves slightly differently. -This revealed a bug in four of Yomitan's template helpers, which have now been fixed in the default templates. If your -custom templates use the following helpers, please ensure their use matches the corrected forms. - -| Helper | Example | Corrected | -| ---------------- | ------------------------------------------------------------- | ------------------------------------ | -| `formatGlossary` | `{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}` | `{{formatGlossary ../dictionary .}}` | -| `furigana` | `{{#furigana}}{{{definition}}}{{/furigana}}` | `{{furigana definition}}` | -| `furiganaPlain` | `{{~#furiganaPlain}}{{{.}}}{{/furiganaPlain~}}` | `{{~furiganaPlain .~}}` | -| `dumpObject` | `{{#dumpObject}}{{{.}}}{{/dumpObject}}` | `{{dumpObject .}}` | - -Authors of custom templates may be interested to know that other helpers previously used and documented in the block -form (e.g. `{{#set "key" "value"}}{{/set}}`), while not broken by this change, may also be replaced with the less verbose -form (e.g. `{{set "key" "value"}}`). The default templates and helper documentation have been changed to reflect this. - -## Dictionaries - -There are several free Japanese dictionaries available for Yomitan, with two of them having glossaries available in -different languages. You must download and import the dictionaries you wish to use in order to enable Yomitan -definition lookups. If you have proprietary EPWING dictionaries that you would like to use, check the [Yomitan -Import](https://github.com/themoeway/yomitan-import) page to learn how to convert and import them into Yomitan. - -Be aware that non-English dictionaries contain fewer entries than their English counterparts. Even if your primary -language is not English, you may consider also importing the English version for better coverage. - -- [Jitendex](https://github.com/stephenmk/Jitendex) - Jitendex is an improved version of JMdict for Yomitan. It features better formatting and some other improvements, and is actively being improved by its author. -- [JMdict](https://github.com/themoeway/jmdict-yomitan#jmdict-for-yomitan-1) - There are daily automatically updated builds of JMdict for Yomitan available in this repository. It is available in multiple languages and formats, but we recommend installing the more modern Jitendex for English users. -- [JMnedict](https://github.com/themoeway/jmdict-yomitan#jmnedict-for-yomitan) - JMnedict is a dictionary that lists readings of person/place/organization names and other proper nouns. -- [KANJIDIC](https://github.com/themoeway/jmdict-yomitan#kanjidic-for-yomitan) - KANJIDIC is an English dictionary listing readings, meanings, and other info about kanji characters. - ## Basic Usage -1. Click the _Yomitan_ button in the browser bar to open the quick-actions popup. - - - - - The _cog_ button will open the Settings page. - - The _magnifying glass_ button will open the Search page. - - The _question mark_ button will open the Information page. - - The _profile_ button will appear when multiple profiles exist, allowing the current profile to be quickly changed. - -2. Import the dictionaries you wish to use for term and kanji searches. If you do not have any dictionaries installed - or enabled, Yomitan will warn you that it is not ready for use by displaying an orange exclamation mark over its - icon. This exclamation mark will disappear once you have installed and enabled at least one dictionary. - - - -3. Webpage text can be scanned by moving the cursor while holding a modifier key, which is Shift - by default. If definitions are found for the text at the cursor position, a popup window containing term definitions - will open. This window can be dismissed by clicking anywhere outside of it. - - - -4. Click on the _speaker_ button to hear the term pronounced by a native speaker. If an audio sample is - not available, you will hear a short click instead. You can configure the sources used to retrieve audio samples in - the options page. - -5. Click on individual kanji in the term definition results to view additional information about those characters, - including stroke order diagrams, readings, meanings, as well as other useful data. - - - -### Importing Dictionaries - -You can import individual dictionaries from the settings page as described above. - -Yomitan also supports exporting and importing your entire collection of dictionaries. - -#### Importing a Dictionary Collection - -- Go to Yomitan's Settings page (Click on the extension's icon then click on the cog icon from the popup) -- Click `Import Dictionary Collection` and select the database file you want to import -- Wait for the import to finish then turn all the dictionaries back on from the `Dictionaries > Configure installed and enabled dictionaries` section -- Refresh the browser tab to see the dictionaries in effect - -#### Exporting the Dictionary Collection - -- Click `Export Dictionary Collection` from the backup section of Yomitan's settings page -- It will show you a progress report as it exports the data then initiates a - download for a file named something like `yomitan-dictionaries-YYYY-MM-DD-HH-mm-ss.json` - (e.g. `yomitan-dictionaries-2023-07-05-02-42-04.json`) - -### Importing and Exporting Personal Configuration - -Note that you can also similarly export and import your Yomitan settings from the `Backup` section of the Settings page. - -You should be able to replicate your exact Yomitan setup across devices by exporting your settings and dictionary collection from the source device then importing those from the destination. - -## Custom Dictionaries - -Yomitan supports the use of custom dictionaries, including the esoteric but popular -[EPWING](https://ja.wikipedia.org/wiki/EPWING) format. They were often utilized in portable electronic dictionaries -similar to the ones pictured below. These dictionaries are often sought after by language learners for their correctness -and excellent coverage of the Japanese language. - -Unfortunately, as most of the dictionaries released in this format are proprietary, they are unable to be bundled with -Yomitan. Instead, you will need to procure these dictionaries yourself and import them using [Yomitan -Import](https://github.com/themoeway/yomitan-import). Check the project page for additional details. - -![Pocket EPWING dictionaries](img/epwing-devices.jpg) - -## Anki Integration - -Yomitan features automatic flashcard creation for [Anki](https://apps.ankiweb.net/), a free application designed to help you -retain knowledge. This feature requires the prior installation of an Anki plugin called [AnkiConnect](https://foosoft.net/projects/anki-connect). -Check the respective project page for more information about how to set up this software. - -### Flashcard Configuration - -Before flashcards can be automatically created, you must configure the templates used to create term and/or kanji notes. -If you are unfamiliar with Anki deck and model management, this would be a good time to reference the [Anki -Manual](https://docs.ankiweb.net/#/). In short, you must specify what information should be included in the -flashcards that Yomitan creates through AnkiConnect. +1. Click the yomitan icon _Yomitan_ button in the browser bar to open the quick-actions popup. -Flashcard fields can be configured with the following steps: - -1. Open the Yomitan options page and scroll down to the section labeled _Anki Options_. -2. Tick the checkbox labeled _Enable Anki integration_ (Anki must be running with [AnkiConnect](https://foosoft.net/projects/anki-connect) installed). -3. Select the type of template to configure by clicking on either the _Terms_ or _Kanji_ tabs. -4. Select the Anki deck and model to use for new creating new flashcards of this type. -5. Fill the model fields with markers corresponding to the information you wish to include (several can be used at - once). Advanced users can also configure the actual [Handlebars](https://handlebarsjs.com/) templates used to create - the flashcard contents (this is strictly optional). - - #### Markers for Term Cards - - | Marker | Description | - | -------------------------- | ------------------------------------------------------------------------------------------------------------------------ | - | `{audio}` | Audio sample of a native speaker's pronunciation in MP3 format (if available). | - | `{clipboard-image}` | An image which is stored in the system clipboard, if present. | - | `{clipboard-text}` | Text which is stored in the system clipboard, if present. | - | `{cloze-body}` | Raw, inflected term as it appeared before being reduced to dictionary form by Yomitan. | - | `{cloze-prefix}` | Fragment of the containing `{sentence}` starting at the beginning of `{sentence}` until the beginning of `{cloze-body}`. | - | `{cloze-suffix}` | Fragment of the containing `{sentence}` starting at the end of `{cloze-body}` until the end of `{sentence}`. | - | `{conjugation}` | Conjugation path from the raw inflected term to the source term. | - | `{dictionary}` | Name of the dictionary from which the card is being created (unavailable in _grouped_ mode). | - | `{document-title}` | Title of the web page that the term appeared in. | - | `{expression}` | Term expressed as kanji (will be displayed in kana if kanji is not available). | - | `{frequencies}` | Frequency information for the term. | - | `{furigana}` | Term expressed as kanji with furigana displayed above it (e.g. 日本語にほんご). | - | `{furigana-plain}` | Term expressed as kanji with furigana displayed next to it in brackets (e.g. 日本語[にほんご]). | - | `{glossary}` | List of definitions for the term (output format depends on whether running in _grouped_ mode). | - | `{glossary-brief}` | List of definitions for the term in a more compact format. | - | `{glossary-no-dictionary}` | List of definitions for the term, except the dictionary tag is omitted. | - | `{part-of-speech}` | Part of speech information for the term. | - | `{pitch-accents}` | List of pitch accent downstep notations for the term. | - | `{pitch-accent-graphs}` | List of pitch accent graphs for the term. | - | `{pitch-accent-positions}` | List of accent downstep positions for the term as a number. | - | `{reading}` | Kana reading for the term (empty for terms where the expression is the reading). | - | `{screenshot}` | Screenshot of the web page taken at the time the term was added. | - | `{search-query}` | The full search query shown on the search page. | - | `{selection-text}` | The selected text on the search page or popup. | - | `{sentence}` | Sentence, quote, or phrase that the term appears in from the source content. | - | `{sentence-furigana}` | Sentence, quote, or phrase that the term appears in from the source content, with furigana added. | - | `{tags}` | Grammar and usage tags providing information about the term (unavailable in _grouped_ mode). | - | `{url}` | Address of the web page in which the term appeared in. | - - #### Markers for Kanji Cards - - | Marker | Description | - | --------------------- | ------------------------------------------------------------------------------------------------------------------------ | - | `{character}` | Unicode glyph representing the current kanji. | - | `{clipboard-image}` | An image which is stored in the system clipboard, if present. | - | `{clipboard-text}` | Text which is stored in the system clipboard, if present. | - | `{cloze-body}` | Raw, inflected parent term as it appeared before being reduced to dictionary form by Yomitan. | - | `{cloze-prefix}` | Fragment of the containing `{sentence}` starting at the beginning of `{sentence}` until the beginning of `{cloze-body}`. | - | `{cloze-suffix}` | Fragment of the containing `{sentence}` starting at the end of `{cloze-body}` until the end of `{sentence}`. | - | `{dictionary}` | Name of the dictionary from which the card is being created. | - | `{document-title}` | Title of the web page that the kanji appeared in. | - | `{frequencies}` | Frequency information for the kanji. | - | `{glossary}` | List of definitions for the kanji. | - | `{kunyomi}` | Kunyomi (Japanese reading) for the kanji expressed as katakana. | - | `{onyomi}` | Onyomi (Chinese reading) for the kanji expressed as hiragana. | - | `{screenshot}` | Screenshot of the web page taken at the time the kanji was added. | - | `{search-query}` | The full search query shown on the search page. | - | `{selection-text}` | The selected text on the search page or popup. | - | `{sentence}` | Sentence, quote, or phrase that the character appears in from the source content. | - | `{sentence-furigana}` | Sentence, quote, or phrase that the character appears in from the source content, with furigana added. | - | `{stroke-count}` | Number of strokes that the kanji character has. | - | `{url}` | Address of the web page in which the kanji appeared in. | - -When creating your model for Yomitan, _make sure that you pick a unique field to be first_; fields that will -contain `{expression}` or `{character}` are ideal candidates for this. Anki does not allow duplicate flashcards to be -added to a deck by default; it uses the first field in the model to check for duplicates. For example, if you have `{reading}` -configured to be the first field in your model and はし is already in your deck, you will not -be able to create a flashcard for はし because they share the same reading. - -### Flashcard Creation - -Once Yomitan is configured, it becomes trivial to create new flashcards with a single click. You will see the following -icons next to term definitions: - -- Clicking ![](img/btn-add-expression.png) adds the current expression as kanji (e.g. 食べる). -- Clicking ![](img/btn-add-reading.png) adds the current expression as hiragana or katakana (e.g. たべる). - -Below are some troubleshooting tips you can try if you are unable to create new flashcards: - -- Individual icons will appear grayed out if a flashcard cannot be created for the current definition (e.g. it already exists in the deck). -- If all of the buttons appear grayed out, then you should double-check your deck and model configuration settings. -- If no icons appear at all, make sure that Anki is running in the background and that [AnkiConnect](https://foosoft.net/projects/anki-connect) has been installed. - -## Keyboard Shortcuts - -The following shortcuts are globally available: - -| Shortcut | Action | -| ---------------------------------- | ------------------------ | -| Alt + Insert | Open search page. | -| Alt + Delete | Toggle extension on/off. | - -The following shortcuts are available on search results: - -| Shortcut | Action | -| -------------------------------- | --------------------------------------- | -| Esc | Cancel current search. | -| Alt + PgUp | Page up through results. | -| Alt + PgDn | Page down through results. | -| Alt + End | Go to last result. | -| Alt + Home | Go to first result. | -| Alt + Up | Go to previous result. | -| Alt + Down | Go to next result. | -| Alt + b | Go to back to source term. | -| Alt + e | Add current term as expression to Anki. | -| Alt + r | Add current term as reading to Anki. | -| Alt + p | Play audio for current term. | -| Alt + k | Add current kanji to Anki. | - -## Advanced Options - -Click the `Advanced` toggle switch in the bottom left corner of the Settings page to enable advanced options. - -### Parse sentences using MeCab - -[MeCab](https://taku910.github.io/mecab/) is a third-party program which uses its own dictionaries and parsing algorithm to decompose sentences into individual words. MeCab may provide more accurate parsing results than Yomitan's internal parser. - -In order for Yomitan to use it, both MeCab and a native messaging component must be installed. -A setup guide can be found [here](https://github.com/themoeway/yomitan-mecab-installer/blob/master/README.md). - -## Frequently Asked Questions - -**I can't scan text in Firefox!** - -In Firefox's Manifest V3, host permissions are treated as opt-in. For Yomitan to work properly, the recommended permissions -must be explicitly set. In the Yomitan welcome page, go to the `Recommended Permissions (Important)` section and check `Enable recommended permissions`. - -**I'm having problems importing dictionaries in Firefox, what do I do?** - -Yomitan uses the cross-browser IndexedDB system for storing imported dictionary data into your user profile. Although -everything "just works" in Chrome, depending on settings, Firefox users can run into problems due to browser bugs. -Yomitan catches errors and tries to offer suggestions about how to work around Firefox issues, but in general at least -one of the following solutions should work for you: - -- Make sure you have cookies enabled. It appears that disabling them also disables IndexedDB for some reason. You - can still have cookies be disabled on other sites; just make sure to add the Yomitan extension to the whitelist of - whatever tool you are using to restrict cookies. You can get the extension "URL" by looking at the address bar when - you have the search page open. -- Make sure that you have sufficient disk space available on the drive Firefox uses to store your user profile. - Firefox limits the amount of space that can be used by IndexedDB to a small fraction of the disk space actually - available on your computer. -- Make sure that you have history set to "Remember history" enabled in your privacy settings. When this option is - set to "Never remember history", IndexedDB access is once again disabled for an inexplicable reason. -- As a last resort, try using the [Refresh Firefox](https://support.mozilla.org/en-US/kb/reset-preferences-fix-problems) - feature to reset your user profile. It appears that the Firefox profile system can corrupt itself preventing - IndexedDB from being accessible to Yomitan. - -**Will you add support for online dictionaries?** + yomitan main popup -Online dictionaries will not be implemented because it is not possible to support them in a robust way. In order to -perform Japanese deinflection, Yomitan must execute dozens of database queries for every single word. Factoring in -network latency and the fragility of web scraping, it would not be possible to maintain a good and consistent user -experience. + - The cog _cog_ button will open the Settings page. + - The magnifying glass _magnifying glass_ button will open the Search page. + - The question mark symbol _question mark_ button will open the Information page. + - The profile icon _profile_ button will appear when multiple profiles exist, allowing the current profile to be quickly changed. -**Is it possible to use Yomitan with files saved locally on my computer with Chrome?** +2. Import the dictionaries you wish to use for term and kanji searches, head over to the [the dictionary docs](./docs/dictionaries.md) to get set up! If you do not have any dictionaries installed or enabled, Yomitan will warn you that it is not ready for use by displaying an orange exclamation mark over its icon. This exclamation mark will disappear once you have installed and enabled at least one dictionary. -In order to use Yomitan with local files in Chrome, you must first tick the _Allow access to file URLs_ checkbox -for Yomitan on the extensions page. Due to the restrictions placed on browser addons in the WebExtensions model, it -will likely never be possible to use Yomitan with PDF files. + custom dictionaries list -**Is it possible to delete individual dictionaries without purging the database?** +3. Webpage text can be scanned by moving the cursor while holding a modifier key, which is Shift by default. If definitions are found for the text at the cursor position, a popup window containing term definitions will open. This window can be dismissed by clicking anywhere outside of it. -Yomitan is able to delete individual dictionaries, but keep in mind that this process can be _very_ slow and can -cause the browser to become unresponsive. The time it takes to delete a single dictionary can sometimes be roughly -the same as the time it originally took to import, which can be significant for certain large dictionaries. + popup with search terms -**Why aren't EPWING dictionaries bundled with Yomitan?** +4. Click on the loudspeaker icon _speaker_ button to hear the term pronounced by a native speaker. If an audio sample is not available, you will hear a short click instead. You can configure the sources used to retrieve audio samples in the options page. -The vast majority of EPWING dictionaries are proprietary, so they are unfortunately not able to be included in -this extension due to copyright reasons. +5. Click on individual kanji in the term definition results to view additional information about those characters, including stroke order diagrams, readings, meanings, as well as other useful data. -**When are you going to add support for $MYLANGUAGE?** + popup with kanji details -Developing Yomitan requires a decent understanding of Japanese sentence structure and grammar, and other languages -are likely to have their own unique set of rules for syntax, grammar, inflection, and so on. Supporting additional -languages would not only require many additional changes to the codebase, it would also incur significant maintenance -overhead and knowledge demands for the developers. Therefore, suggestions and contributions for supporting -new languages will be declined, allowing Yomitan's focus to remain Japanese-centric. +To further enhance your Yomitan experience, it's worth [integrating with Anki](./docs/anki-integration.md), a spaced-repetition flashcard program to help solidify the words you encounter. ## Licenses diff --git a/dev/bin/build-libs.js b/dev/bin/build-libs.js index 07d27188a5..a614407eab 100644 --- a/dev/bin/build-libs.js +++ b/dev/bin/build-libs.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/dev/bin/build.js b/dev/bin/build.js index 5d7e4f0dda..190964d564 100644 --- a/dev/bin/build.js +++ b/dev/bin/build.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/dev/bin/dictionary-validate.js b/dev/bin/dictionary-validate.js index dc01815ef0..e7c3562e09 100644 --- a/dev/bin/dictionary-validate.js +++ b/dev/bin/dictionary-validate.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/dev/bin/generate-css-json.js b/dev/bin/generate-css-json.js index 57419b415a..8b77016a7b 100644 --- a/dev/bin/generate-css-json.js +++ b/dev/bin/generate-css-json.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/dev/bin/schema-validate.js b/dev/bin/schema-validate.js index bbd5ad5fb1..74a42444bd 100644 --- a/dev/bin/schema-validate.js +++ b/dev/bin/schema-validate.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/dev/build-libs.js b/dev/build-libs.js index 10720010cc..15ab3c8d9d 100644 --- a/dev/build-libs.js +++ b/dev/build-libs.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/dev/data-error.js b/dev/data-error.js index 0ab2d35429..5659245b81 100644 --- a/dev/data-error.js +++ b/dev/data-error.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/dev/data/display-pronunciation-overrides.css b/dev/data/display-pronunciation-overrides.css index 7bba55e54c..c0a4da805b 100644 --- a/dev/data/display-pronunciation-overrides.css +++ b/dev/data/display-pronunciation-overrides.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/dev/data/structured-content-overrides.css b/dev/data/structured-content-overrides.css index c0b14202e5..46859b1bfe 100644 --- a/dev/data/structured-content-overrides.css +++ b/dev/data/structured-content-overrides.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/dev/dictionary-validate.js b/dev/dictionary-validate.js index 51edcc50a5..18bba99ee1 100644 --- a/dev/dictionary-validate.js +++ b/dev/dictionary-validate.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -23,6 +23,7 @@ import {performance} from 'perf_hooks'; import {fileURLToPath} from 'url'; import {parseJson} from './json.js'; import {createJsonSchema} from './schema-validate.js'; +import {toError} from './to-error.js'; const dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -39,35 +40,33 @@ function readSchema(relativeFileName) { /** * @param {import('dev/schema-validate').ValidateMode} mode * @param {import('jszip')} zip - * @param {string} fileNameFormat - * @param {import('dev/dictionary-validate').Schema} schema + * @param {import('dev/dictionary-validate').SchemasDetails} schemasDetails */ -async function validateDictionaryBanks(mode, zip, fileNameFormat, schema) { - let jsonSchema; - try { - jsonSchema = createJsonSchema(mode, schema); - } catch (e) { - const e2 = e instanceof Error ? e : new Error(`${e}`); - e2.message += `\n(in file ${fileNameFormat})}`; - throw e2; - } - let index = 1; - while (true) { - const fileName = fileNameFormat.replace(/\?/, `${index}`); +async function validateDictionaryBanks(mode, zip, schemasDetails) { + for (const [fileName, file] of Object.entries(zip.files)) { + for (const [fileNameFormat, schema] of schemasDetails) { + if (!fileNameFormat.test(fileName)) { continue; } - const file = zip.files[fileName]; - if (!file) { break; } + let jsonSchema; + try { + jsonSchema = createJsonSchema(mode, schema); + } catch (e) { + const e2 = toError(e); + e2.message += `\n(in file ${fileName})}`; + throw e2; + } - const data = parseJson(await file.async('string')); - try { - jsonSchema.validate(data); - } catch (e) { - const e2 = e instanceof Error ? e : new Error(`${e}`); - e2.message += `\n(in file ${fileName})}`; - throw e2; - } + const data = parseJson(await file.async('string')); - ++index; + try { + jsonSchema.validate(data); + } catch (e) { + const e2 = toError(e); + e2.message += `\n(in file ${fileName})}`; + throw e2; + } + break; + } } } @@ -92,16 +91,21 @@ export async function validateDictionary(mode, archive, schemas) { const jsonSchema = createJsonSchema(mode, schemas.index); jsonSchema.validate(index); } catch (e) { - const e2 = e instanceof Error ? e : new Error(`${e}`); + const e2 = toError(e); e2.message += `\n(in file ${indexFileName})}`; throw e2; } - await validateDictionaryBanks(mode, archive, 'term_bank_?.json', version === 1 ? schemas.termBankV1 : schemas.termBankV3); - await validateDictionaryBanks(mode, archive, 'term_meta_bank_?.json', schemas.termMetaBankV3); - await validateDictionaryBanks(mode, archive, 'kanji_bank_?.json', version === 1 ? schemas.kanjiBankV1 : schemas.kanjiBankV3); - await validateDictionaryBanks(mode, archive, 'kanji_meta_bank_?.json', schemas.kanjiMetaBankV3); - await validateDictionaryBanks(mode, archive, 'tag_bank_?.json', schemas.tagBankV3); + /** @type {import('dev/dictionary-validate').SchemasDetails} */ + const schemasDetails = [ + [/^term_bank_(\d+)\.json$/, version === 1 ? schemas.termBankV1 : schemas.termBankV3], + [/^term_meta_bank_(\d+)\.json$/, schemas.termMetaBankV3], + [/^kanji_bank_(\d+)\.json$/, version === 1 ? schemas.kanjiBankV1 : schemas.kanjiBankV3], + [/^kanji_meta_bank_(\d+)\.json$/, schemas.kanjiMetaBankV3], + [/^tag_bank_(\d+)\.json$/, schemas.tagBankV3] + ]; + + await validateDictionaryBanks(mode, archive, schemasDetails); } /** diff --git a/dev/generate-css-json.js b/dev/generate-css-json.js index 1a2aaba37b..307288315b 100644 --- a/dev/generate-css-json.js +++ b/dev/generate-css-json.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/dev/json.js b/dev/json.js index a76edfcd61..550f15645f 100644 --- a/dev/json.js +++ b/dev/json.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/dev/lib/dexie.js b/dev/lib/dexie.js index 81a62b28c3..834260e001 100644 --- a/dev/lib/dexie.js +++ b/dev/lib/dexie.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/dev/lib/handlebars.js b/dev/lib/handlebars.js index 5b57efdd63..c99a3b13dd 100644 --- a/dev/lib/handlebars.js +++ b/dev/lib/handlebars.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/dev/lib/parse5.js b/dev/lib/parse5.js index c7b2c83893..03249db475 100644 --- a/dev/lib/parse5.js +++ b/dev/lib/parse5.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/dev/lib/ucs2length.js b/dev/lib/ucs2length.js index 3b370493a4..ce9027de52 100644 --- a/dev/lib/ucs2length.js +++ b/dev/lib/ucs2length.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/dev/lib/wanakana.js b/dev/lib/wanakana.js index dca707296e..b2679cec2a 100644 --- a/dev/lib/wanakana.js +++ b/dev/lib/wanakana.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/dev/lib/z-worker.js b/dev/lib/z-worker.js index f6a95ed3b1..142ed8fc31 100644 --- a/dev/lib/z-worker.js +++ b/dev/lib/z-worker.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/dev/lib/zip.js b/dev/lib/zip.js index b6e8545191..007b4285e0 100644 --- a/dev/lib/zip.js +++ b/dev/lib/zip.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/dev/manifest-util.js b/dev/manifest-util.js index 1802e1397a..6ab817909c 100644 --- a/dev/manifest-util.js +++ b/dev/manifest-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/dev/schema-validate.js b/dev/schema-validate.js index d4e977e122..d1ffcf8202 100644 --- a/dev/schema-validate.js +++ b/dev/schema-validate.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/dev/to-error.js b/dev/to-error.js new file mode 100644 index 0000000000..4cda477ea8 --- /dev/null +++ b/dev/to-error.js @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2023-2024 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +export {toError} from '../ext/js/core/to-error.js'; diff --git a/dev/util.js b/dev/util.js index ff1c012046..89bd95dae2 100644 --- a/dev/util.js +++ b/dev/util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/docs/advanced-options.md b/docs/advanced-options.md new file mode 100644 index 0000000000..f1740029cb --- /dev/null +++ b/docs/advanced-options.md @@ -0,0 +1,10 @@ +## Advanced Options + +Click the `Advanced` toggle switch in the bottom left corner of the Settings page to enable advanced options. + +### Parse sentences using MeCab + +[MeCab](https://taku910.github.io/mecab/) is a third-party program which uses its own dictionaries and parsing algorithm to decompose sentences into individual words. MeCab may provide more accurate parsing results than Yomitan's internal parser. + +In order for Yomitan to use it, both MeCab and a native messaging component must be installed. +A setup guide can be found [here](https://github.com/themoeway/yomitan-mecab-installer/blob/master/README.md). diff --git a/docs/anki-integration.md b/docs/anki-integration.md new file mode 100644 index 0000000000..151137d479 --- /dev/null +++ b/docs/anki-integration.md @@ -0,0 +1,99 @@ +## Anki Integration + +Yomitan features automatic flashcard creation for [Anki](https://apps.ankiweb.net/), a free application designed to help you +retain knowledge. This feature requires the prior installation of an Anki plugin called [AnkiConnect](https://foosoft.net/projects/anki-connect). +Check the respective project page for more information about how to set up this software. + +### Flashcard Configuration + +Before flashcards can be automatically created, you must configure the templates used to create term and/or kanji notes. +If you are unfamiliar with Anki deck and model management, this would be a good time to reference the [Anki +Manual](https://docs.ankiweb.net/#/). In short, you must specify what information should be included in the +flashcards that Yomitan creates through AnkiConnect. + +Flashcard fields can be configured with the following steps: + +1. Open the Yomitan options page and scroll down to the section labeled _Anki Options_. +2. Tick the checkbox labeled _Enable Anki integration_ (Anki must be running with [AnkiConnect](https://foosoft.net/projects/anki-connect) installed). +3. Select the type of template to configure by clicking on either the _Terms_ or _Kanji_ tabs. +4. Select the Anki deck and model to use for new creating new flashcards of this type. +5. Fill the model fields with markers corresponding to the information you wish to include (several can be used at + once). Advanced users can also configure the actual [Handlebars](https://handlebarsjs.com/) templates used to create + the flashcard contents (this is strictly optional). + + #### Markers for Term Cards + + | Marker | Description | + | -------------------------- | ------------------------------------------------------------------------------------------------------------------------ | + | `{audio}` | Audio sample of a native speaker's pronunciation in MP3 format (if available). | + | `{clipboard-image}` | An image which is stored in the system clipboard, if present. | + | `{clipboard-text}` | Text which is stored in the system clipboard, if present. | + | `{cloze-body}` | Raw, inflected term as it appeared before being reduced to dictionary form by Yomitan. | + | `{cloze-prefix}` | Fragment of the containing `{sentence}` starting at the beginning of `{sentence}` until the beginning of `{cloze-body}`. | + | `{cloze-suffix}` | Fragment of the containing `{sentence}` starting at the end of `{cloze-body}` until the end of `{sentence}`. | + | `{conjugation}` | Conjugation path from the raw inflected term to the source term. | + | `{dictionary}` | Name of the dictionary from which the card is being created (unavailable in _grouped_ mode). | + | `{document-title}` | Title of the web page that the term appeared in. | + | `{expression}` | Term expressed as kanji (will be displayed in kana if kanji is not available). | + | `{frequencies}` | Frequency information for the term. | + | `{furigana}` | Term expressed as kanji with furigana displayed above it (e.g. 日本語にほんご). | + | `{furigana-plain}` | Term expressed as kanji with furigana displayed next to it in brackets (e.g. 日本語[にほんご]). | + | `{glossary}` | List of definitions for the term (output format depends on whether running in _grouped_ mode). | + | `{glossary-brief}` | List of definitions for the term in a more compact format. | + | `{glossary-no-dictionary}` | List of definitions for the term, except the dictionary tag is omitted. | + | `{part-of-speech}` | Part of speech information for the term. | + | `{pitch-accents}` | List of pitch accent downstep notations for the term. | + | `{pitch-accent-graphs}` | List of pitch accent graphs for the term. | + | `{pitch-accent-positions}` | List of accent downstep positions for the term as a number. | + | `{reading}` | Kana reading for the term (empty for terms where the expression is the reading). | + | `{screenshot}` | Screenshot of the web page taken at the time the term was added. | + | `{search-query}` | The full search query shown on the search page. | + | `{selection-text}` | The selected text on the search page or popup. | + | `{sentence}` | Sentence, quote, or phrase that the term appears in from the source content. | + | `{sentence-furigana}` | Sentence, quote, or phrase that the term appears in from the source content, with furigana added. | + | `{tags}` | Grammar and usage tags providing information about the term (unavailable in _grouped_ mode). | + | `{url}` | Address of the web page in which the term appeared in. | + + #### Markers for Kanji Cards + + | Marker | Description | + | --------------------- | ------------------------------------------------------------------------------------------------------------------------ | + | `{character}` | Unicode glyph representing the current kanji. | + | `{clipboard-image}` | An image which is stored in the system clipboard, if present. | + | `{clipboard-text}` | Text which is stored in the system clipboard, if present. | + | `{cloze-body}` | Raw, inflected parent term as it appeared before being reduced to dictionary form by Yomitan. | + | `{cloze-prefix}` | Fragment of the containing `{sentence}` starting at the beginning of `{sentence}` until the beginning of `{cloze-body}`. | + | `{cloze-suffix}` | Fragment of the containing `{sentence}` starting at the end of `{cloze-body}` until the end of `{sentence}`. | + | `{dictionary}` | Name of the dictionary from which the card is being created. | + | `{document-title}` | Title of the web page that the kanji appeared in. | + | `{frequencies}` | Frequency information for the kanji. | + | `{glossary}` | List of definitions for the kanji. | + | `{kunyomi}` | Kunyomi (Japanese reading) for the kanji expressed as katakana. | + | `{onyomi}` | Onyomi (Chinese reading) for the kanji expressed as hiragana. | + | `{screenshot}` | Screenshot of the web page taken at the time the kanji was added. | + | `{search-query}` | The full search query shown on the search page. | + | `{selection-text}` | The selected text on the search page or popup. | + | `{sentence}` | Sentence, quote, or phrase that the character appears in from the source content. | + | `{sentence-furigana}` | Sentence, quote, or phrase that the character appears in from the source content, with furigana added. | + | `{stroke-count}` | Number of strokes that the kanji character has. | + | `{url}` | Address of the web page in which the kanji appeared in. | + +When creating your model for Yomitan, _make sure that you pick a unique field to be first_; fields that will +contain `{expression}` or `{character}` are ideal candidates for this. Anki does not allow duplicate flashcards to be +added to a deck by default; it uses the first field in the model to check for duplicates. For example, if you have `{reading}` +configured to be the first field in your model and はし is already in your deck, you will not +be able to create a flashcard for はし because they share the same reading. + +### Flashcard Creation + +Once Yomitan is configured, it becomes trivial to create new flashcards with a single click. You will see the following +icons next to term definitions: + +- Clicking ![](../img/btn-add-expression.png) adds the current expression as kanji (e.g. 食べる). +- Clicking ![](../img/btn-add-reading.png) adds the current expression as hiragana or katakana (e.g. たべる). + +Below are some troubleshooting tips you can try if you are unable to create new flashcards: + +- Individual icons will appear grayed out if a flashcard cannot be created for the current definition (e.g. it already exists in the deck). +- If all of the buttons appear grayed out, then you should double-check your deck and model configuration settings. +- If no icons appear at all, make sure that Anki is running in the background and that [AnkiConnect](https://foosoft.net/projects/anki-connect) has been installed. diff --git a/docs/dictionaries.md b/docs/dictionaries.md new file mode 100644 index 0000000000..73307c3760 --- /dev/null +++ b/docs/dictionaries.md @@ -0,0 +1,51 @@ +## Dictionaries + +There are several free Japanese dictionaries available for Yomitan, with two of them having glossaries available in +different languages. You must download and import the dictionaries you wish to use in order to enable Yomitan +definition lookups. If you have proprietary EPWING dictionaries that you would like to use, check the [Yomitan +Import](https://github.com/themoeway/yomitan-import) page to learn how to convert and import them into Yomitan. + +Be aware that non-English dictionaries contain fewer entries than their English counterparts. Even if your primary +language is not English, you may consider also importing the English version for better coverage. + +- [Jitendex](https://github.com/stephenmk/Jitendex) - Jitendex is an improved version of JMdict for Yomitan. It features better formatting and some other improvements, and is actively being improved by its author. +- [JMdict](https://github.com/themoeway/jmdict-yomitan#jmdict-for-yomitan-1) - There are daily automatically updated builds of JMdict for Yomitan available in this repository. It is available in multiple languages and formats, but we recommend installing the more modern Jitendex for English users. +- [JMnedict](https://github.com/themoeway/jmdict-yomitan#jmnedict-for-yomitan) - JMnedict is a dictionary that lists readings of person/place/organization names and other proper nouns. +- [KANJIDIC](https://github.com/themoeway/jmdict-yomitan#kanjidic-for-yomitan) - KANJIDIC is an English dictionary listing readings, meanings, and other info about kanji characters. + +### Importing Dictionaries + +Yomitan also supports exporting and importing your entire collection of dictionaries. + +#### Importing a Dictionary Collection + +- Go to Yomitan's settings page (click on the extension's icon then click on the cog icon from the popup) +- Click `Import Dictionary Collection` and select the database file you want to import +- Wait for the import to finish then turn all the dictionaries back on from the `Dictionaries > Configure installed and enabled dictionaries` section +- Refresh the browser tab to see the dictionaries in effect + +#### Exporting the Dictionary Collection + +- Click `Export Dictionary Collection` from the backup section of Yomitan's settings page +- It will show you a progress report as it exports the data then initiates a + download for a file named something like `yomitan-dictionaries-YYYY-MM-DD-HH-mm-ss.json` + (e.g. `yomitan-dictionaries-2023-07-05-02-42-04.json`) + +### Importing and Exporting Personal Configuration + +Note that you can also similarly export and import your Yomitan settings from the `Backup` section of the Settings page. + +You should be able to replicate your exact Yomitan setup across devices by exporting your settings and dictionary collection from the source device then importing those from the destination. + +## Custom Dictionaries + +Yomitan supports the use of custom dictionaries, including the esoteric but popular +[EPWING](https://ja.wikipedia.org/wiki/EPWING) format. They were often utilized in portable electronic dictionaries +similar to the ones pictured below. These dictionaries are often sought after by language learners for their correctness +and excellent coverage of the Japanese language. + +Unfortunately, as most of the dictionaries released in this format are proprietary, they are unable to be bundled with +Yomitan. Instead, you will need to procure these dictionaries yourself and import them using [Yomitan +Import](https://github.com/themoeway/yomitan-import). Check the project page for additional details. + +![Pocket EPWING dictionaries](../img/epwing-devices.jpg) diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000000..a972b0b9c0 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,58 @@ +## Frequently Asked Questions + +**I can't scan text in Firefox!** + +In Firefox's Manifest V3, host permissions are treated as opt-in. For Yomitan to work properly, the recommended permissions +must be explicitly set. In the Yomitan welcome page, go to the `Recommended Permissions (Important)` section and check `Enable recommended permissions`. + +**I'm having problems importing dictionaries in Firefox, what do I do?** + +Yomitan uses the cross-browser IndexedDB system for storing imported dictionary data into your user profile. Although +everything "just works" in Chrome, depending on settings, Firefox users can run into problems due to browser bugs. +Yomitan catches errors and tries to offer suggestions about how to work around Firefox issues, but in general at least +one of the following solutions should work for you: + +- Make sure you have cookies enabled. It appears that disabling them also disables IndexedDB for some reason. You + can still have cookies be disabled on other sites; just make sure to add the Yomitan extension to the whitelist of + whatever tool you are using to restrict cookies. You can get the extension "URL" by looking at the address bar when + you have the search page open. +- Make sure that you have sufficient disk space available on the drive Firefox uses to store your user profile. + Firefox limits the amount of space that can be used by IndexedDB to a small fraction of the disk space actually + available on your computer. +- Make sure that you have history set to "Remember history" enabled in your privacy settings. When this option is + set to "Never remember history", IndexedDB access is once again disabled for an inexplicable reason. +- As a last resort, try using the [Refresh Firefox](https://support.mozilla.org/en-US/kb/reset-preferences-fix-problems) + feature to reset your user profile. It appears that the Firefox profile system can corrupt itself preventing + IndexedDB from being accessible to Yomitan. + +**Will you add support for online dictionaries?** + +Online dictionaries will not be implemented because it is not possible to support them in a robust way. In order to +perform Japanese deinflection, Yomitan must execute dozens of database queries for every single word. Factoring in +network latency and the fragility of web scraping, it would not be possible to maintain a good and consistent user +experience. + +**Is it possible to use Yomitan with files saved locally on my computer with Chrome?** + +In order to use Yomitan with local files in Chrome, you must first tick the _Allow access to file URLs_ checkbox +for Yomitan on the extensions page. Due to the restrictions placed on browser addons in the WebExtensions model, it +will likely never be possible to use Yomitan with PDF files. + +**Is it possible to delete individual dictionaries without purging the database?** + +Yomitan is able to delete individual dictionaries, but keep in mind that this process can be _very_ slow and can +cause the browser to become unresponsive. The time it takes to delete a single dictionary can sometimes be roughly +the same as the time it originally took to import, which can be significant for certain large dictionaries. + +**Why aren't EPWING dictionaries bundled with Yomitan?** + +The vast majority of EPWING dictionaries are proprietary, so they are unfortunately not able to be included in +this extension due to copyright reasons. + +**When are you going to add support for $MYLANGUAGE?** + +Developing Yomitan requires a decent understanding of Japanese sentence structure and grammar, and other languages +are likely to have their own unique set of rules for syntax, grammar, inflection, and so on. Supporting additional +languages would not only require many additional changes to the codebase, it would also incur significant maintenance +overhead and knowledge demands for the developers. Therefore, suggestions and contributions for supporting +new languages will be declined, allowing Yomitan's focus to remain Japanese-centric. diff --git a/docs/keyboard-shortcuts.md b/docs/keyboard-shortcuts.md new file mode 100644 index 0000000000..bf12a84af4 --- /dev/null +++ b/docs/keyboard-shortcuts.md @@ -0,0 +1,25 @@ +## Keyboard Shortcuts + +The following shortcuts are globally available: + +| Shortcut | Action | +| ---------------------------------- | ------------------------ | +| Alt + Insert | Open search page. | +| Alt + Delete | Toggle extension on/off. | + +The following shortcuts are available on search results: + +| Shortcut | Action | +| -------------------------------- | --------------------------------------- | +| Esc | Cancel current search. | +| Alt + PgUp | Page up through results. | +| Alt + PgDn | Page down through results. | +| Alt + End | Go to last result. | +| Alt + Home | Go to first result. | +| Alt + Up | Go to previous result. | +| Alt + Down | Go to next result. | +| Alt + B | Go to back to source term. | +| Alt + E | Add current term as expression to Anki. | +| Alt + R | Add current term as reading to Anki. | +| Alt + P | Play audio for current term. | +| Alt + K | Add current kanji to Anki. | diff --git a/docs/yomichan-migration.md b/docs/yomichan-migration.md new file mode 100644 index 0000000000..291c296600 --- /dev/null +++ b/docs/yomichan-migration.md @@ -0,0 +1,31 @@ +## Migrating from Yomichan + +### Exporting Data + +If you are an existing user of Yomichan, you can export your dictionary collection and settings such that they can be imported into Yomitan to reflect your setup exactly as it was. + +You can export your settings from Yomichan's Settings page. Go to the `Backup` section and click on `Export Settings`. + +Yomichan doesn't have first-class support to export the dictionary collection. Please follow the instructions provided in the following link to export your data: +https://github.com/themoeway/yomichan-data-exporter#steps-to-export-the-data + +You can then import the exported files into Yomitan from the `Backup` section of the `Settings` page. Please see [the section on importing dictionaries](#importing-dictionaries) further below for more explicit steps. + +### Custom Templates + +If you do not use custom templates for Anki note creation, this section can be skipped. + +Due to security concerns, an alternate implementation of Handlebars is being used which behaves slightly differently. +This revealed a bug in four of Yomitan's template helpers, which have now been fixed in the default templates. If your +custom templates use the following helpers, please ensure their use matches the corrected forms. + +| Helper | Example | Corrected | +| ---------------- | ------------------------------------------------------------- | ------------------------------------ | +| `formatGlossary` | `{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}` | `{{formatGlossary ../dictionary .}}` | +| `furigana` | `{{#furigana}}{{{definition}}}{{/furigana}}` | `{{furigana definition}}` | +| `furiganaPlain` | `{{~#furiganaPlain}}{{{.}}}{{/furiganaPlain~}}` | `{{~furiganaPlain .~}}` | +| `dumpObject` | `{{#dumpObject}}{{{.}}}{{/dumpObject}}` | `{{dumpObject .}}` | + +Authors of custom templates may be interested to know that other helpers previously used and documented in the block +form (e.g. `{{#set "key" "value"}}{{/set}}`), while not broken by this change, may also be replaced with the less verbose +form (e.g. `{{set "key" "value"}}`). The default templates and helper documentation have been changed to reflect this. diff --git a/ext/css/action-popup.css b/ext/css/action-popup.css index a82d66bfc6..b3f7d5dfc2 100644 --- a/ext/css/action-popup.css +++ b/ext/css/action-popup.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/css/background.css b/ext/css/background.css index ab28302565..0c8fe728d9 100644 --- a/ext/css/background.css +++ b/ext/css/background.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/css/display-pronunciation.css b/ext/css/display-pronunciation.css index cc067633e3..6c4a8019e3 100644 --- a/ext/css/display-pronunciation.css +++ b/ext/css/display-pronunciation.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/css/display.css b/ext/css/display.css index d9d6583a4b..25d3f500da 100644 --- a/ext/css/display.css +++ b/ext/css/display.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -825,17 +825,39 @@ button.action-button:active { } /* Inflections */ -.inflection-list { - display: inline-block; +.inflection-rule-chains { + padding-inline-start: 0; + list-style-type: none; +} +.inflection-rule-chain { color: var(--reason-text-color); } -.inflection-list:empty { +.inflection-rule-chain:empty { display: none; } -.inflection-list>.inflection+.inflection-separator+.inflection::before { +.inflection-rule-chain>.inflection+.inflection-separator+.inflection::before { content: var(--inflection-separator); padding: 0 0.25em; } +.inflection-source-icon { + display: inline-block; + white-space: nowrap; + text-align: center; + width: 1.4em; + margin-right: 0.2em; +} +.inflection-source-icon[data-inflection-source='dictionary']::after { + content: '📖'; +} +.inflection-source-icon[data-inflection-source='algorithm']::after { + content: '🧩'; +} +.inflection-source-icon[data-inflection-source='both'] { + width: 2.8em; +} +.inflection-source-icon[data-inflection-source='both']::after { + content: '🧩📖'; +} /* Headwords */ diff --git a/ext/css/material.css b/ext/css/material.css index 58358176a8..a976a60e2f 100644 --- a/ext/css/material.css +++ b/ext/css/material.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/css/permissions.css b/ext/css/permissions.css index abdbf89871..b72ad90bc5 100644 --- a/ext/css/permissions.css +++ b/ext/css/permissions.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/css/popup-outer.css b/ext/css/popup-outer.css index 495790ca05..3555e76a2f 100644 --- a/ext/css/popup-outer.css +++ b/ext/css/popup-outer.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/css/popup-preview.css b/ext/css/popup-preview.css index f3d7316611..2f5a03a8d4 100644 --- a/ext/css/popup-preview.css +++ b/ext/css/popup-preview.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/css/search.css b/ext/css/search.css index ed2e9892bf..98fa13e316 100644 --- a/ext/css/search.css +++ b/ext/css/search.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/css/settings.css b/ext/css/settings.css index 06fca0e401..e6e9442821 100644 --- a/ext/css/settings.css +++ b/ext/css/settings.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/css/structured-content.css b/ext/css/structured-content.css index 7fcfd0e2a8..7a3b55f413 100644 --- a/ext/css/structured-content.css +++ b/ext/css/structured-content.css @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/data/schemas/dictionary-term-bank-v3-schema.json b/ext/data/schemas/dictionary-term-bank-v3-schema.json index f4b4faa5fc..066229c304 100644 --- a/ext/data/schemas/dictionary-term-bank-v3-schema.json +++ b/ext/data/schemas/dictionary-term-bank-v3-schema.json @@ -320,6 +320,9 @@ "enum": ["start", "end", "left", "right", "center", "justify", "justify-all", "match-parent"], "default": "start" }, + "textShadow": { + "type": "string" + }, "margin": { "type": "string" }, @@ -397,7 +400,7 @@ }, { "type": "string", - "description": "String of space-separated rule identifiers for the definition which is used to validate delinflection. Valid rule identifiers are: v1: ichidan verb; v5: godan verb; vs: suru verb; vk: kuru verb; adj-i: i-adjective. An empty string corresponds to words which aren't inflected, such as nouns." + "description": "String of space-separated rule identifiers for the definition which is used to validate deinflection. An empty string should be used for words which aren't inflected." }, { "type": "number", @@ -532,6 +535,24 @@ } } ] + }, + { + "type": "array", + "description": "Deinflection of the term to an uninflected term.", + "items": [ + { + "type": "string", + "description": "The uninflected term." + }, + { + "type": "array", + "description": "A chain of inflection rules that produced the inflected term", + "items": { + "type": "string", + "description": "A single inflection rule." + } + } + ] } ] } diff --git a/ext/data/schemas/options-schema.json b/ext/data/schemas/options-schema.json index 65c4102e93..24f3a6b0e4 100644 --- a/ext/data/schemas/options-schema.json +++ b/ext/data/schemas/options-schema.json @@ -822,7 +822,9 @@ "priority", "enabled", "allowSecondarySearches", - "definitionsCollapsible" + "definitionsCollapsible", + "partsOfSpeechFilter", + "useDeinflections" ], "properties": { "name": { @@ -845,6 +847,14 @@ "type": "string", "enum": ["not-collapsible", "expanded", "collapsed", "force-collapsed", "force-expanded"], "default": "not-collapsible" + }, + "partsOfSpeechFilter": { + "type": "boolean", + "default": true + }, + "useDeinflections": { + "type": "boolean", + "default": true } } } diff --git a/ext/data/templates/anki-field-templates-upgrade-v24.handlebars b/ext/data/templates/anki-field-templates-upgrade-v24.handlebars new file mode 100644 index 0000000000..2288737cc2 --- /dev/null +++ b/ext/data/templates/anki-field-templates-upgrade-v24.handlebars @@ -0,0 +1,52 @@ +{{#*inline "phonetic-transcriptions"}} + {{~#if (op ">" definition.phoneticTranscriptions.length 0)~}} +
    + {{~#each definition.phoneticTranscriptions~}} + {{~#each phoneticTranscriptions~}} +
  • + {{~set "any" false~}} + {{~#each tags~}} + {{~#if (get "any")}}, {{else}}({{/if~}} + {{name}} + {{~set "any" true~}} + {{~/each~}} + {{~#if (get "any")}}) {{/if~}} + {{ipa~}} +
  • + {{~/each~}} + {{~/each~}} +
+ {{~/if~}} +{{/inline}} + +{{<<<<<<<}} +{{#*inline "conjugation"}} + {{~#if (op ">" definition.inflectionRuleChainCandidates.length 0)~}} + {{~set "multiple" false~}} + {{~#if (op ">" definition.inflectionRuleChainCandidates.length 1)~}} + {{~set "multiple" true~}} + {{~/if~}} + {{~#if (get "multiple")~}}
    {{/if~}} + {{~#each definition.inflectionRuleChainCandidates~}} + {{~#if (op ">" inflectionRules.length 0)~}} + {{~#if (get "multiple")~}}
  • {{/if~}} + {{~#each inflectionRules~}} + {{~#if (op ">" @index 0)}} « {{/if~}} + {{.}} + {{~/each~}} + {{~#if (get "multiple")~}}
  • {{/if~}} + {{~/if~}} + {{~/each~}} + {{~#if (get "multiple")~}}
{{/if~}} + {{~/if~}} +{{/inline}} +{{=======}} +{{#*inline "conjugation"}} + {{~#if definition.reasons~}} + {{~#each definition.reasons~}} + {{~#if (op ">" @index 0)}} « {{/if~}} + {{.}} + {{~/each~}} + {{~/if~}} +{{/inline}} +{{>>>>>>>>}} diff --git a/ext/data/templates/default-anki-field-templates.handlebars b/ext/data/templates/default-anki-field-templates.handlebars index f23b9d0be9..818677cedf 100644 --- a/ext/data/templates/default-anki-field-templates.handlebars +++ b/ext/data/templates/default-anki-field-templates.handlebars @@ -261,11 +261,23 @@ {{/inline}} {{#*inline "conjugation"}} - {{~#if definition.reasons~}} - {{~#each definition.reasons~}} - {{~#if (op ">" @index 0)}} « {{/if~}} - {{.}} - {{~/each~}} + {{~#if (op ">" definition.inflectionRuleChainCandidates.length 0)~}} + {{~set "multiple" false~}} + {{~#if (op ">" definition.inflectionRuleChainCandidates.length 1)~}} + {{~set "multiple" true~}} + {{~/if~}} + {{~#if (get "multiple")~}}
    {{/if~}} + {{~#each definition.inflectionRuleChainCandidates~}} + {{~#if (op ">" inflectionRules.length 0)~}} + {{~#if (get "multiple")~}}
  • {{/if~}} + {{~#each inflectionRules~}} + {{~#if (op ">" @index 0)}} « {{/if~}} + {{.}} + {{~/each~}} + {{~#if (get "multiple")~}}
  • {{/if~}} + {{~/if~}} + {{~/each~}} + {{~#if (get "multiple")~}}
{{/if~}} {{~/if~}} {{/inline}} diff --git a/ext/display-templates.html b/ext/display-templates.html index ed0037bb6d..a50cea3b9d 100644 --- a/ext/display-templates.html +++ b/ext/display-templates.html @@ -32,7 +32,7 @@
-
+
    @@ -77,6 +77,7 @@ + diff --git a/ext/js/accessibility/accessibility-controller.js b/ext/js/accessibility/accessibility-controller.js index 4dced51be9..b27858931c 100644 --- a/ext/js/accessibility/accessibility-controller.js +++ b/ext/js/accessibility/accessibility-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -17,7 +17,7 @@ */ import {isContentScriptRegistered, registerContentScript, unregisterContentScript} from '../background/script-manager.js'; -import {log} from '../core.js'; +import {log} from '../core/logger.js'; /** * This class controls the registration of accessibility handlers. diff --git a/ext/js/accessibility/google-docs-util.js b/ext/js/accessibility/google-docs-util.js index cc6cbd698d..34a5dd5555 100644 --- a/ext/js/accessibility/google-docs-util.js +++ b/ext/js/accessibility/google-docs-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/accessibility/google-docs-xray.js b/ext/js/accessibility/google-docs-xray.js index ef293933a7..f128c05875 100644 --- a/ext/js/accessibility/google-docs-xray.js +++ b/ext/js/accessibility/google-docs-xray.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/ext/js/accessibility/google-docs.js b/ext/js/accessibility/google-docs.js index 00266bb693..4af786977d 100644 --- a/ext/js/accessibility/google-docs.js +++ b/ext/js/accessibility/google-docs.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/ext/js/app/content-script-main.js b/ext/js/app/content-script-main.js index 7b76fda0e9..c0bea73cf9 100644 --- a/ext/js/app/content-script-main.js +++ b/ext/js/app/content-script-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {log} from '../core.js'; +import {log} from '../core/logger.js'; import {HotkeyHandler} from '../input/hotkey-handler.js'; import {yomitan} from '../yomitan.js'; import {Frontend} from './frontend.js'; diff --git a/ext/js/app/content-script-wrapper.js b/ext/js/app/content-script-wrapper.js index 795d3d8b9b..4d23e85bb6 100644 --- a/ext/js/app/content-script-wrapper.js +++ b/ext/js/app/content-script-wrapper.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index 9dafde7a19..837364ad58 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,8 +16,10 @@ * along with this program. If not, see . */ -import {EventListenerCollection, log, promiseAnimationFrame} from '../core.js'; import {createApiMap, invokeApiMapHandler} from '../core/api-map.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; +import {log} from '../core/logger.js'; +import {promiseAnimationFrame} from '../core/utilities.js'; import {DocumentUtil} from '../dom/document-util.js'; import {TextSourceElement} from '../dom/text-source-element.js'; import {TextSourceRange} from '../dom/text-source-range.js'; @@ -224,7 +226,7 @@ export class Frontend { try { await this._updateOptionsInternal(); } catch (e) { - if (!yomitan.isExtensionUnloaded) { + if (!yomitan.webExtension.unloaded) { throw e; } } @@ -366,7 +368,7 @@ export class Frontend { const scanningOptions = /** @type {import('settings').ProfileOptions} */ (this._options).scanning; if (error !== null) { - if (yomitan.isExtensionUnloaded) { + if (yomitan.webExtension.unloaded) { if (textSource !== null && !passive) { this._showExtensionUnloaded(textSource); } @@ -653,7 +655,7 @@ export class Frontend { try { return this._popup !== null && await this._popup.containsPoint(x, y); } catch (e) { - if (!yomitan.isExtensionUnloaded) { + if (!yomitan.webExtension.unloaded) { throw e; } return false; @@ -740,7 +742,7 @@ export class Frontend { Promise.resolve() ); this._lastShowPromise.catch((error) => { - if (yomitan.isExtensionUnloaded) { return; } + if (yomitan.webExtension.unloaded) { return; } log.error(error); }); return this._lastShowPromise; @@ -864,7 +866,6 @@ export class Frontend { case 'web': return preventMiddleMouseOptions.onWebPages; case 'popup': return preventMiddleMouseOptions.onPopupPages; case 'search': return preventMiddleMouseOptions.onSearchPages; - default: return false; } } diff --git a/ext/js/app/popup-factory.js b/ext/js/app/popup-factory.js index 0d8cabd429..f9eec913cf 100644 --- a/ext/js/app/popup-factory.js +++ b/ext/js/app/popup-factory.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -17,7 +17,7 @@ */ import {FrameOffsetForwarder} from '../comm/frame-offset-forwarder.js'; -import {generateId} from '../core.js'; +import {generateId} from '../core/utilities.js'; import {yomitan} from '../yomitan.js'; import {PopupProxy} from './popup-proxy.js'; import {PopupWindow} from './popup-window.js'; diff --git a/ext/js/app/popup-proxy.js b/ext/js/app/popup-proxy.js index e581be82e5..856ec086cd 100644 --- a/ext/js/app/popup-proxy.js +++ b/ext/js/app/popup-proxy.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventDispatcher, log} from '../core.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; +import {log} from '../core/logger.js'; import {yomitan} from '../yomitan.js'; /** @@ -319,7 +320,7 @@ export class PopupProxy extends EventDispatcher { try { return await this._invoke(action, params); } catch (e) { - if (!yomitan.isExtensionUnloaded) { throw e; } + if (!yomitan.webExtension.unloaded) { throw e; } return defaultReturnValue; } } diff --git a/ext/js/app/popup-window.js b/ext/js/app/popup-window.js index a696885af6..7a0b6af4d5 100644 --- a/ext/js/app/popup-window.js +++ b/ext/js/app/popup-window.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventDispatcher} from '../core.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; import {yomitan} from '../yomitan.js'; /** @@ -274,7 +274,7 @@ export class PopupWindow extends EventDispatcher { * @returns {Promise|undefined>} */ async _invoke(open, action, params) { - if (yomitan.isExtensionUnloaded) { + if (yomitan.webExtension.unloaded) { return void 0; } @@ -290,7 +290,7 @@ export class PopupWindow extends EventDispatcher { message )); } catch (e) { - if (yomitan.isExtensionUnloaded) { + if (yomitan.webExtension.unloaded) { open = false; } } diff --git a/ext/js/app/popup.js b/ext/js/app/popup.js index 0f7fbd8701..c741e8f156 100644 --- a/ext/js/app/popup.js +++ b/ext/js/app/popup.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -17,8 +17,11 @@ */ import {FrameClient} from '../comm/frame-client.js'; -import {DynamicProperty, EventDispatcher, EventListenerCollection, deepEqual} from '../core.js'; +import {DynamicProperty} from '../core/dynamic-property.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; import {ExtensionError} from '../core/extension-error.js'; +import {deepEqual} from '../core/utilities.js'; import {DocumentUtil} from '../dom/document-util.js'; import {loadStyle} from '../dom/style-util.js'; import {yomitan} from '../yomitan.js'; @@ -711,7 +714,7 @@ export class Popup extends EventDispatcher { try { return await this._invoke(action, params); } catch (e) { - if (!yomitan.isExtensionUnloaded) { throw e; } + if (!yomitan.webExtension.unloaded) { throw e; } return void 0; } } diff --git a/ext/js/app/theme-controller.js b/ext/js/app/theme-controller.js index 8b88c834de..559a6e57d1 100644 --- a/ext/js/app/theme-controller.js +++ b/ext/js/app/theme-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 481567b55f..b61f27b108 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -22,10 +22,11 @@ import {AnkiConnect} from '../comm/anki-connect.js'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; import {ClipboardReader} from '../comm/clipboard-reader.js'; import {Mecab} from '../comm/mecab.js'; -import {clone, deferPromise, isObject, log, promiseTimeout} from '../core.js'; import {createApiMap, invokeApiMapHandler} from '../core/api-map.js'; import {ExtensionError} from '../core/extension-error.js'; import {readResponseJson} from '../core/json.js'; +import {log} from '../core/logger.js'; +import {clone, deferPromise, isObject, promiseTimeout} from '../core/utilities.js'; import {AnkiUtil} from '../data/anki-util.js'; import {OptionsUtil} from '../data/options-util.js'; import {PermissionsUtil} from '../data/permissions-util.js'; @@ -48,9 +49,11 @@ import {injectStylesheet} from './script-manager.js'; */ export class Backend { /** - * Creates a new instance. + * @param {import('../extension/web-extension.js').WebExtension} webExtension */ - constructor() { + constructor(webExtension) { + /** @type {import('../extension/web-extension.js').WebExtension} */ + this._webExtension = webExtension; /** @type {JapaneseUtil} */ this._japaneseUtil = new JapaneseUtil(wanakana); /** @type {Environment} */ @@ -79,7 +82,7 @@ export class Backend { }); } else { /** @type {?OffscreenProxy} */ - this._offscreen = new OffscreenProxy(); + this._offscreen = new OffscreenProxy(webExtension); /** @type {DictionaryDatabase|DictionaryDatabaseProxy} */ this._dictionaryDatabase = new DictionaryDatabaseProxy(this._offscreen); /** @type {Translator|TranslatorProxy} */ @@ -143,10 +146,13 @@ export class Backend { this._permissions = null; /** @type {PermissionsUtil} */ this._permissionsUtil = new PermissionsUtil(); + /** @type {Map void)[]>} */ + this._applicationReadyHandlers = new Map(); /* eslint-disable no-multi-spaces */ /** @type {import('api').ApiMap} */ this._apiMap = createApiMap([ + ['applicationReady', this._onApiApplicationReady.bind(this)], ['requestBackendReadySignal', this._onApiRequestBackendReadySignal.bind(this)], ['optionsGet', this._onApiOptionsGet.bind(this)], ['optionsGetFull', this._onApiOptionsGetFull.bind(this)], @@ -424,6 +430,21 @@ export class Backend { // Message handlers + /** @type {import('api').ApiHandler<'applicationReady'>} */ + _onApiApplicationReady(_params, sender) { + const {tab, frameId} = sender; + if (!tab || typeof frameId !== 'number') { return; } + const {id} = tab; + if (typeof id !== 'number') { return; } + const key = `${id}:${frameId}`; + const handlers = this._applicationReadyHandlers.get(key); + if (typeof handlers === 'undefined') { return; } + for (const handler of handlers) { + handler(); + } + this._applicationReadyHandlers.delete(key); + } + /** @type {import('api').ApiHandler<'requestBackendReadySignal'>} */ _onApiRequestBackendReadySignal(_params, sender) { // tab ID isn't set in background (e.g. browser_action) @@ -1805,18 +1826,8 @@ export class Backend { return new Promise((resolve, reject) => { /** @type {?import('core').Timeout} */ let timer = null; - /** @type {?import('extension').ChromeRuntimeOnMessageCallback} */ - let onMessage = (message, sender) => { - if ( - !sender.tab || - sender.tab.id !== tabId || - sender.frameId !== frameId || - !(typeof message === 'object' && message !== null) || - message.action !== 'applicationReady' - ) { - return; - } + const readyHandler = () => { cleanup(); resolve(); }; @@ -1825,13 +1836,10 @@ export class Backend { clearTimeout(timer); timer = null; } - if (onMessage !== null) { - chrome.runtime.onMessage.removeListener(onMessage); - onMessage = null; - } + this._removeApplicationReadyHandler(tabId, frameId, readyHandler); }; - chrome.runtime.onMessage.addListener(onMessage); + this._addApplicationReadyHandler(tabId, frameId, readyHandler); this._sendMessageTabPromise(tabId, {action: 'applicationIsReady'}, {frameId}) .then( @@ -1896,8 +1904,7 @@ export class Backend { * @param {import('application').ApiMessage} message */ _sendMessageIgnoreResponse(message) { - const callback = () => this._checkLastError(chrome.runtime.lastError); - chrome.runtime.sendMessage(message, callback); + this._webExtension.sendMessageIgnoreResponse(message); } /** @@ -2427,7 +2434,9 @@ export class Backend { enabledDictionaryMap.set(mainDictionary, { index: enabledDictionaryMap.size, priority: 0, - allowSecondarySearches: false + allowSecondarySearches: false, + partsOfSpeechFilter: true, + useDeinflections: true }); excludeDictionaryDefinitions = new Set(); excludeDictionaryDefinitions.add(mainDictionary); @@ -2473,10 +2482,13 @@ export class Backend { const enabledDictionaryMap = new Map(); for (const dictionary of options.dictionaries) { if (!dictionary.enabled) { continue; } - enabledDictionaryMap.set(dictionary.name, { + const {name, priority, allowSecondarySearches, partsOfSpeechFilter, useDeinflections} = dictionary; + enabledDictionaryMap.set(name, { index: enabledDictionaryMap.size, - priority: dictionary.priority, - allowSecondarySearches: dictionary.allowSecondarySearches + priority, + allowSecondarySearches, + partsOfSpeechFilter, + useDeinflections }); } return enabledDictionaryMap; @@ -2629,14 +2641,14 @@ export class Backend { } /** + * Only request this permission for Firefox versions >= 77. + * https://bugzilla.mozilla.org/show_bug.cgi?id=1630413 * @returns {Promise} */ async _requestPersistentStorage() { try { if (await navigator.storage.persisted()) { return; } - // Only request this permission for Firefox versions >= 77. - // https://bugzilla.mozilla.org/show_bug.cgi?id=1630413 const {vendor, version} = await browser.runtime.getBrowserInfo(); if (vendor !== 'Mozilla') { return; } @@ -2680,4 +2692,38 @@ export class Backend { return defaultValue; } } + + /** + * @param {number} tabId + * @param {number} frameId + * @param {() => void} handler + */ + _addApplicationReadyHandler(tabId, frameId, handler) { + const key = `${tabId}:${frameId}`; + let handlers = this._applicationReadyHandlers.get(key); + if (typeof handlers === 'undefined') { + handlers = []; + this._applicationReadyHandlers.set(key, handlers); + } + handlers.push(handler); + } + + /** + * @param {number} tabId + * @param {number} frameId + * @param {() => void} handler + * @returns {boolean} + */ + _removeApplicationReadyHandler(tabId, frameId, handler) { + const key = `${tabId}:${frameId}`; + const handlers = this._applicationReadyHandlers.get(key); + if (typeof handlers === 'undefined') { return false; } + const index = handlers.indexOf(handler); + if (index < 0) { return false; } + handlers.splice(index, 1); + if (handlers.length === 0) { + this._applicationReadyHandlers.delete(key); + } + return true; + } } diff --git a/ext/js/background/background-main.js b/ext/js/background/background-main.js index 29c256d32a..f5871a1423 100644 --- a/ext/js/background/background-main.js +++ b/ext/js/background/background-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -23,7 +23,7 @@ import {Backend} from './backend.js'; async function main() { yomitan.prepare(true); - const backend = new Backend(); + const backend = new Backend(yomitan.webExtension); await backend.prepare(); } diff --git a/ext/js/background/offscreen-main.js b/ext/js/background/offscreen-main.js index aac5b8f22b..67d5fa838a 100644 --- a/ext/js/background/offscreen-main.js +++ b/ext/js/background/offscreen-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js index 2335b67396..555c3abcab 100644 --- a/ext/js/background/offscreen-proxy.js +++ b/ext/js/background/offscreen-proxy.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,12 +16,17 @@ * along with this program. If not, see . */ -import {isObject} from '../core.js'; import {ExtensionError} from '../core/extension-error.js'; +import {isObject} from '../core/utilities.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; export class OffscreenProxy { - constructor() { + /** + * @param {import('../extension/web-extension.js').WebExtension} webExtension + */ + constructor(webExtension) { + /** @type {import('../extension/web-extension.js').WebExtension} */ + this._webExtension = webExtension; /** @type {?Promise} */ this._creatingOffscreen = null; } @@ -76,16 +81,9 @@ export class OffscreenProxy { * @param {import('offscreen').ApiMessage} message * @returns {Promise>} */ - sendMessagePromise(message) { - return new Promise((resolve, reject) => { - chrome.runtime.sendMessage(message, (response) => { - try { - resolve(this._getMessageResponseResult(response)); - } catch (error) { - reject(error); - } - }); - }); + async sendMessagePromise(message) { + const response = await this._webExtension.sendMessagePromise(message); + return this._getMessageResponseResult(/** @type {import('core').Response>} */ (response)); } /** diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 2320471c65..470ea0e26d 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/background/profile-conditions-util.js b/ext/js/background/profile-conditions-util.js index 8d477094f5..f3be226d23 100644 --- a/ext/js/background/profile-conditions-util.js +++ b/ext/js/background/profile-conditions-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/background/request-builder.js b/ext/js/background/request-builder.js index 23f10ed33b..b23f16af5a 100644 --- a/ext/js/background/request-builder.js +++ b/ext/js/background/request-builder.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/background/script-manager.js b/ext/js/background/script-manager.js index 353c50e3d2..84213452a5 100644 --- a/ext/js/background/script-manager.js +++ b/ext/js/background/script-manager.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/comm/anki-connect.js b/ext/js/comm/anki-connect.js index fa5543d585..f16471ce8e 100644 --- a/ext/js/comm/anki-connect.js +++ b/ext/js/comm/anki-connect.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index 423115f18c..2e1e882606 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -20,11 +20,11 @@ import {ExtensionError} from '../core/extension-error.js'; export class API { /** - * @param {import('../yomitan.js').Yomitan} yomitan + * @param {import('../extension/web-extension.js').WebExtension} webExtension */ - constructor(yomitan) { - /** @type {import('../yomitan.js').Yomitan} */ - this._yomitan = yomitan; + constructor(webExtension) { + /** @type {import('../extension/web-extension.js').WebExtension} */ + this._webExtension = webExtension; } /** @@ -375,13 +375,15 @@ export class API { const data = {action, params}; return new Promise((resolve, reject) => { try { - this._yomitan.sendMessage(data, (response) => { - this._checkLastError(chrome.runtime.lastError); + this._webExtension.sendMessage(data, (response) => { + this._webExtension.getLastError(); if (response !== null && typeof response === 'object') { - if (typeof response.error !== 'undefined') { - reject(ExtensionError.deserialize(response.error)); + const {error} = /** @type {import('core').UnknownObject} */ (response); + if (typeof error !== 'undefined') { + reject(ExtensionError.deserialize(/** @type {import('core').SerializedError} */ (error))); } else { - resolve(response.result); + const {result} = /** @type {import('core').UnknownObject} */ (response); + resolve(/** @type {import('api').ApiReturn} */ (result)); } } else { const message = response === null ? 'Unexpected null response' : `Unexpected response of type ${typeof response}`; @@ -393,11 +395,4 @@ export class API { } }); } - - /** - * @param {chrome.runtime.LastError|undefined} _ignore - */ - _checkLastError(_ignore) { - // NOP - } } diff --git a/ext/js/comm/clipboard-monitor.js b/ext/js/comm/clipboard-monitor.js index b870a7a00f..a1ea33620a 100644 --- a/ext/js/comm/clipboard-monitor.js +++ b/ext/js/comm/clipboard-monitor.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventDispatcher} from '../core.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; /** * @augments EventDispatcher diff --git a/ext/js/comm/clipboard-reader.js b/ext/js/comm/clipboard-reader.js index a6d1c0688e..2ac41cb9ac 100644 --- a/ext/js/comm/clipboard-reader.js +++ b/ext/js/comm/clipboard-reader.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/comm/cross-frame-api.js b/ext/js/comm/cross-frame-api.js index 3569c037ab..fca7c84d7f 100644 --- a/ext/js/comm/cross-frame-api.js +++ b/ext/js/comm/cross-frame-api.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,10 +16,12 @@ * along with this program. If not, see . */ -import {EventDispatcher, EventListenerCollection, log} from '../core.js'; import {extendApiMap, invokeApiMapHandler} from '../core/api-map.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; import {ExtensionError} from '../core/extension-error.js'; import {parseJson} from '../core/json.js'; +import {log} from '../core/logger.js'; import {yomitan} from '../yomitan.js'; /** diff --git a/ext/js/comm/frame-ancestry-handler.js b/ext/js/comm/frame-ancestry-handler.js index e715cedc91..3173965454 100644 --- a/ext/js/comm/frame-ancestry-handler.js +++ b/ext/js/comm/frame-ancestry-handler.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {generateId} from '../core.js'; +import {generateId} from '../core/utilities.js'; import {yomitan} from '../yomitan.js'; /** diff --git a/ext/js/comm/frame-client.js b/ext/js/comm/frame-client.js index cb591ca995..62db1edde9 100644 --- a/ext/js/comm/frame-client.js +++ b/ext/js/comm/frame-client.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {deferPromise, generateId, isObject} from '../core.js'; +import {deferPromise, generateId, isObject} from '../core/utilities.js'; export class FrameClient { constructor() { diff --git a/ext/js/comm/frame-endpoint.js b/ext/js/comm/frame-endpoint.js index 4c5f58c106..0008417d08 100644 --- a/ext/js/comm/frame-endpoint.js +++ b/ext/js/comm/frame-endpoint.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventListenerCollection, generateId} from '../core.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; +import {generateId} from '../core/utilities.js'; import {yomitan} from '../yomitan.js'; export class FrameEndpoint { diff --git a/ext/js/comm/frame-offset-forwarder.js b/ext/js/comm/frame-offset-forwarder.js index 570f3e8825..a1f249e2b1 100644 --- a/ext/js/comm/frame-offset-forwarder.js +++ b/ext/js/comm/frame-offset-forwarder.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/comm/mecab.js b/ext/js/comm/mecab.js index 6d0f3f40d0..1ff3f06606 100644 --- a/ext/js/comm/mecab.js +++ b/ext/js/comm/mecab.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../core.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; +import {toError} from '../core/to-error.js'; /** * This class is used to connect Yomitan to a native component that is @@ -235,7 +236,7 @@ export class Mecab { try { await this._setupPortPromise; } catch (e) { - throw new Error(e instanceof Error ? e.message : `${e}`); + throw toError(e); } } diff --git a/ext/js/core.js b/ext/js/core.js deleted file mode 100644 index 726b037c43..0000000000 --- a/ext/js/core.js +++ /dev/null @@ -1,737 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2019-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import {ExtensionError} from './core/extension-error.js'; - -/** - * Checks whether a given value is a non-array object. - * @param {unknown} value The value to check. - * @returns {boolean} `true` if the value is an object and not an array, `false` otherwise. - */ -export function isObject(value) { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -/** - * Converts any string into a form that can be passed into the RegExp constructor. - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions - * @param {string} string The string to convert to a valid regular expression. - * @returns {string} The escaped string. - */ -export function escapeRegExp(string) { - return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); -} - -/** - * Reverses a string. - * @param {string} string The string to reverse. - * @returns {string} The returned string, which retains proper UTF-16 surrogate pair order. - */ -export function stringReverse(string) { - return [...string].reverse().join(''); -} - -/** - * Creates a deep clone of an object or value. This is similar to `parseJson(JSON.stringify(value))`. - * @template [T=unknown] - * @param {T} value The value to clone. - * @returns {T} A new clone of the value. - * @throws An error if the value is circular and cannot be cloned. - */ -export function clone(value) { - if (value === null) { return /** @type {T} */ (null); } - switch (typeof value) { - case 'boolean': - case 'number': - case 'string': - case 'bigint': - case 'symbol': - case 'undefined': - return value; - default: - return cloneInternal(value, new Set()); - } -} - -/** - * @template [T=unknown] - * @param {T} value - * @param {Set} visited - * @returns {T} - * @throws {Error} - */ -function cloneInternal(value, visited) { - if (value === null) { return /** @type {T} */ (null); } - switch (typeof value) { - case 'boolean': - case 'number': - case 'string': - case 'bigint': - case 'symbol': - case 'undefined': - return value; - case 'object': - return /** @type {T} */ ( - Array.isArray(value) ? - cloneArray(value, visited) : - cloneObject(/** @type {import('core').SerializableObject} */ (value), visited) - ); - default: - throw new Error(`Cannot clone object of type ${typeof value}`); - } -} - -/** - * @param {unknown[]} value - * @param {Set} visited - * @returns {unknown[]} - * @throws {Error} - */ -function cloneArray(value, visited) { - if (visited.has(value)) { throw new Error('Circular'); } - try { - visited.add(value); - const result = []; - for (const item of value) { - result.push(cloneInternal(item, visited)); - } - return result; - } finally { - visited.delete(value); - } -} - -/** - * @param {import('core').SerializableObject} value - * @param {Set} visited - * @returns {import('core').SerializableObject} - * @throws {Error} - */ -function cloneObject(value, visited) { - if (visited.has(value)) { throw new Error('Circular'); } - try { - visited.add(value); - /** @type {import('core').SerializableObject} */ - const result = {}; - for (const key in value) { - if (Object.prototype.hasOwnProperty.call(value, key)) { - result[key] = cloneInternal(value[key], visited); - } - } - return result; - } finally { - visited.delete(value); - } -} - -/** - * Checks if an object or value is deeply equal to another object or value. - * @param {unknown} value1 The first value to check. - * @param {unknown} value2 The second value to check. - * @returns {boolean} `true` if the values are the same object, or deeply equal without cycles. `false` otherwise. - */ -export function deepEqual(value1, value2) { - if (value1 === value2) { return true; } - - const type = typeof value1; - if (typeof value2 !== type) { return false; } - - switch (type) { - case 'object': - case 'function': - return deepEqualInternal(value1, value2, new Set()); - default: - return false; - } -} - -/** - * @param {unknown} value1 - * @param {unknown} value2 - * @param {Set} visited1 - * @returns {boolean} - */ -function deepEqualInternal(value1, value2, visited1) { - if (value1 === value2) { return true; } - - const type = typeof value1; - if (typeof value2 !== type) { return false; } - - switch (type) { - case 'object': - case 'function': - { - if (value1 === null || value2 === null) { return false; } - const array = Array.isArray(value1); - if (array !== Array.isArray(value2)) { return false; } - if (visited1.has(value1)) { return false; } - visited1.add(value1); - return ( - array ? - areArraysEqual(/** @type {unknown[]} */ (value1), /** @type {unknown[]} */ (value2), visited1) : - areObjectsEqual(/** @type {import('core').UnknownObject} */ (value1), /** @type {import('core').UnknownObject} */ (value2), visited1) - ); - } - default: - return false; - } -} - -/** - * @param {import('core').UnknownObject} value1 - * @param {import('core').UnknownObject} value2 - * @param {Set} visited1 - * @returns {boolean} - */ -function areObjectsEqual(value1, value2, visited1) { - const keys1 = Object.keys(value1); - const keys2 = Object.keys(value2); - if (keys1.length !== keys2.length) { return false; } - - const keys1Set = new Set(keys1); - for (const key of keys2) { - if (!keys1Set.has(key) || !deepEqualInternal(value1[key], value2[key], visited1)) { return false; } - } - - return true; -} - -/** - * @param {unknown[]} value1 - * @param {unknown[]} value2 - * @param {Set} visited1 - * @returns {boolean} - */ -function areArraysEqual(value1, value2, visited1) { - const length = value1.length; - if (length !== value2.length) { return false; } - - for (let i = 0; i < length; ++i) { - if (!deepEqualInternal(value1[i], value2[i], visited1)) { return false; } - } - - return true; -} - -/** - * Creates a new base-16 (lower case) string of a sequence of random bytes of the given length. - * @param {number} length The number of bytes the string represents. The returned string's length will be twice as long. - * @returns {string} A string of random characters. - */ -export function generateId(length) { - const array = new Uint8Array(length); - crypto.getRandomValues(array); - let id = ''; - for (const value of array) { - id += value.toString(16).padStart(2, '0'); - } - return id; -} - -/** - * Creates an unresolved promise that can be resolved later, outside the promise's executor function. - * @template [T=unknown] - * @returns {import('core').DeferredPromiseDetails} An object `{promise, resolve, reject}`, containing the promise and the resolve/reject functions. - */ -export function deferPromise() { - /** @type {((value: T) => void)|undefined} */ - let resolve; - /** @type {((reason?: import('core').RejectionReason) => void)|undefined} */ - let reject; - const promise = new Promise((resolve2, reject2) => { - resolve = resolve2; - reject = reject2; - }); - return { - promise, - resolve: /** @type {(value: T) => void} */ (resolve), - reject: /** @type {(reason?: import('core').RejectionReason) => void} */ (reject) - }; -} - -/** - * Creates a promise that is resolved after a set delay. - * @param {number} delay How many milliseconds until the promise should be resolved. If 0, the promise is immediately resolved. - * @returns {Promise} A promise with two additional properties: `resolve` and `reject`, which can be used to complete the promise early. - */ -export function promiseTimeout(delay) { - return delay <= 0 ? Promise.resolve() : new Promise((resolve) => { setTimeout(resolve, delay); }); -} - -/** - * Creates a promise that will resolve after the next animation frame, using `requestAnimationFrame`. - * @param {number} [timeout] A maximum duration (in milliseconds) to wait until the promise resolves. If null or omitted, no timeout is used. - * @returns {Promise<{time: number, timeout: boolean}>} A promise that is resolved with `{time, timeout}`, where `time` is the timestamp from `requestAnimationFrame`, - * and `timeout` is a boolean indicating whether the cause was a timeout or not. - * @throws The promise throws an error if animation is not supported in this context, such as in a service worker. - */ -export function promiseAnimationFrame(timeout) { - return new Promise((resolve, reject) => { - if (typeof cancelAnimationFrame !== 'function' || typeof requestAnimationFrame !== 'function') { - reject(new Error('Animation not supported in this context')); - return; - } - - /** @type {?import('core').Timeout} */ - let timer = null; - /** @type {?number} */ - let frameRequest = null; - /** - * @param {number} time - */ - const onFrame = (time) => { - frameRequest = null; - if (timer !== null) { - clearTimeout(timer); - timer = null; - } - resolve({time, timeout: false}); - }; - const onTimeout = () => { - timer = null; - if (frameRequest !== null) { - // eslint-disable-next-line no-undef - cancelAnimationFrame(frameRequest); - frameRequest = null; - } - resolve({time: performance.now(), timeout: true}); - }; - - // eslint-disable-next-line no-undef - frameRequest = requestAnimationFrame(onFrame); - if (typeof timeout === 'number') { - timer = setTimeout(onTimeout, timeout); - } - }); -} - -/** - * The following typedef is required because the JSDoc `implements` tag doesn't work with `import()`. - * https://github.com/microsoft/TypeScript/issues/49905 - * @typedef {import('core').EventDispatcherOffGeneric} EventDispatcherOffGeneric - */ - -/** - * Base class controls basic event dispatching. - * @template {import('core').EventSurface} TSurface - * @implements {EventDispatcherOffGeneric} - */ -export class EventDispatcher { - /** - * Creates a new instance. - */ - constructor() { - /** @type {Map, import('core').EventHandlerAny[]>} */ - this._eventMap = new Map(); - } - - /** - * Triggers an event with the given name and specified argument. - * @template {import('core').EventNames} TName - * @param {TName} eventName The string representing the event's name. - * @param {import('core').EventArgument} details The argument passed to the callback functions. - * @returns {boolean} `true` if any callbacks were registered, `false` otherwise. - */ - trigger(eventName, details) { - const callbacks = this._eventMap.get(eventName); - if (typeof callbacks === 'undefined') { return false; } - - for (const callback of callbacks) { - callback(details); - } - return true; - } - - /** - * Adds a single event listener to a specific event. - * @template {import('core').EventNames} TName - * @param {TName} eventName The string representing the event's name. - * @param {import('core').EventHandler} callback The event listener callback to add. - */ - on(eventName, callback) { - let callbacks = this._eventMap.get(eventName); - if (typeof callbacks === 'undefined') { - callbacks = []; - this._eventMap.set(eventName, callbacks); - } - callbacks.push(callback); - } - - /** - * Removes a single event listener from a specific event. - * @template {import('core').EventNames} TName - * @param {TName} eventName The string representing the event's name. - * @param {import('core').EventHandler} callback The event listener callback to add. - * @returns {boolean} `true` if the callback was removed, `false` otherwise. - */ - off(eventName, callback) { - const callbacks = this._eventMap.get(eventName); - if (typeof callbacks === 'undefined') { return false; } - - const ii = callbacks.length; - for (let i = 0; i < ii; ++i) { - if (callbacks[i] === callback) { - callbacks.splice(i, 1); - if (callbacks.length === 0) { - this._eventMap.delete(eventName); - } - return true; - } - } - return false; - } - - /** - * Checks if an event has any listeners. - * @template {import('core').EventNames} TName - * @param {TName} eventName The string representing the event's name. - * @returns {boolean} `true` if the event has listeners, `false` otherwise. - */ - hasListeners(eventName) { - const callbacks = this._eventMap.get(eventName); - return (typeof callbacks !== 'undefined' && callbacks.length > 0); - } -} - -/** - * Class which stores event listeners added to various objects, making it easy to remove them in bulk. - */ -export class EventListenerCollection { - /** - * Creates a new instance. - */ - constructor() { - /** @type {import('event-listener-collection').EventListenerDetails[]} */ - this._eventListeners = []; - } - - /** - * Returns the number of event listeners that are currently in the object. - * @type {number} - */ - get size() { - return this._eventListeners.length; - } - - /** - * Adds an event listener using `object.addEventListener`. The listener will later be removed using `object.removeEventListener`. - * @param {import('event-listener-collection').EventTarget} target The object to add the event listener to. - * @param {string} type The name of the event. - * @param {EventListener | EventListenerObject | import('event-listener-collection').EventListenerFunction} listener The callback listener. - * @param {AddEventListenerOptions | boolean} [options] Options for the event. - */ - addEventListener(target, type, listener, options) { - target.addEventListener(type, listener, options); - this._eventListeners.push({type: 'removeEventListener', target, eventName: type, listener, options}); - } - - /** - * Adds an event listener using `object.addListener`. The listener will later be removed using `object.removeListener`. - * @template {import('event-listener-collection').EventListenerFunction} TCallback - * @template [TArgs=unknown] - * @param {import('event-listener-collection').ExtensionEvent} target The object to add the event listener to. - * @param {TCallback} callback The callback. - * @param {TArgs[]} args The extra argument array passed to the `addListener`/`removeListener` function. - */ - addListener(target, callback, ...args) { - target.addListener(callback, ...args); - this._eventListeners.push({type: 'removeListener', target, callback, args}); - } - - /** - * Adds an event listener using `object.on`. The listener will later be removed using `object.off`. - * @template {import('core').EventSurface} TSurface - * @template {import('core').EventNames} TName - * @param {EventDispatcher} target The object to add the event listener to. - * @param {TName} eventName The string representing the event's name. - * @param {import('core').EventHandler} callback The event listener callback to add. - */ - on(target, eventName, callback) { - target.on(eventName, callback); - this._eventListeners.push({type: 'off', eventName, target, callback}); - } - - /** - * Removes all event listeners added to objects for this instance and clears the internal list of event listeners. - */ - removeAllEventListeners() { - if (this._eventListeners.length === 0) { return; } - for (const item of this._eventListeners) { - switch (item.type) { - case 'removeEventListener': - item.target.removeEventListener(item.eventName, item.listener, item.options); - break; - case 'removeListener': - item.target.removeListener(item.callback, ...item.args); - break; - case 'off': - item.target.off(item.eventName, item.callback); - break; - } - } - this._eventListeners = []; - } -} - -/** - * Class representing a generic value with an override stack. - * Changes can be observed by listening to the 'change' event. - * @template [T=unknown] - * @augments EventDispatcher> - */ -export class DynamicProperty extends EventDispatcher { - /** - * Creates a new instance with the specified value. - * @param {T} value The value to assign. - */ - constructor(value) { - super(); - /** @type {T} */ - this._value = value; - /** @type {T} */ - this._defaultValue = value; - /** @type {{value: T, priority: number, token: string}[]} */ - this._overrides = []; - } - - /** - * Gets the default value for the property, which is assigned to the - * public value property when no overrides are present. - * @type {T} - */ - get defaultValue() { - return this._defaultValue; - } - - /** - * Assigns the default value for the property. If no overrides are present - * and if the value is different than the current default value, - * the 'change' event will be triggered. - * @param {T} value The value to assign. - */ - set defaultValue(value) { - this._defaultValue = value; - if (this._overrides.length === 0) { this._updateValue(); } - } - - /** - * Gets the current value for the property, taking any overrides into account. - * @type {T} - */ - get value() { - return this._value; - } - - /** - * Gets the number of overrides added to the property. - * @type {number} - */ - get overrideCount() { - return this._overrides.length; - } - - /** - * Adds an override value with the specified priority to the override stack. - * Values with higher priority will take precedence over those with lower. - * For tie breaks, the override value added first will take precedence. - * If the newly added override has the highest priority of all overrides - * and if the override value is different from the current value, - * the 'change' event will be fired. - * @param {T} value The override value to assign. - * @param {number} [priority] The priority value to use, as a number. - * @returns {import('core').TokenString} A string token which can be passed to the clearOverride function - * to remove the override. - */ - setOverride(value, priority = 0) { - const overridesCount = this._overrides.length; - let i = 0; - for (; i < overridesCount; ++i) { - if (priority > this._overrides[i].priority) { break; } - } - const token = generateId(16); - this._overrides.splice(i, 0, {value, priority, token}); - if (i === 0) { this._updateValue(); } - return token; - } - - /** - * Removes a specific override value. If the removed override - * had the highest priority, and the new value is different from - * the previous value, the 'change' event will be fired. - * @param {import('core').TokenString} token The token for the corresponding override which is to be removed. - * @returns {boolean} `true` if an override was returned, `false` otherwise. - */ - clearOverride(token) { - for (let i = 0, ii = this._overrides.length; i < ii; ++i) { - if (this._overrides[i].token === token) { - this._overrides.splice(i, 1); - if (i === 0) { this._updateValue(); } - return true; - } - } - return false; - } - - /** - * Updates the current value using the current overrides and default value. - * If the new value differs from the previous value, the 'change' event will be fired. - */ - _updateValue() { - const value = this._overrides.length > 0 ? this._overrides[0].value : this._defaultValue; - if (this._value === value) { return; } - this._value = value; - this.trigger('change', {value}); - } -} - -/** - * This class handles logging of messages to the console and triggering - * an event for log calls. - * @augments EventDispatcher - */ -export class Logger extends EventDispatcher { - /** - * Creates a new instance. - */ - constructor() { - super(); - /** @type {string} */ - this._extensionName = 'Yomitan'; - try { - const {name, version} = chrome.runtime.getManifest(); - this._extensionName = `${name} ${version}`; - } catch (e) { - // NOP - } - } - - /** - * Logs a generic error. This will trigger the 'log' event with the same arguments as the function invocation. - * @param {unknown} error The error to log. This is typically an `Error` or `Error`-like object. - * @param {import('log').LogLevel} level The level to log at. Values include `'info'`, `'debug'`, `'warn'`, and `'error'`. - * Other values will be logged at a non-error level. - * @param {?import('log').LogContext} [context] An optional context object for the error which should typically include a `url` field. - */ - log(error, level, context = null) { - if (typeof context !== 'object' || context === null) { - context = {url: location.href}; - } - - let errorString; - try { - if (typeof error === 'string') { - errorString = error; - } else { - errorString = ( - typeof error === 'object' && error !== null ? - error.toString() : - `${error}` - ); - if (/^\[object \w+\]$/.test(errorString)) { - errorString = JSON.stringify(error); - } - } - } catch (e) { - errorString = `${error}`; - } - - let errorStack; - try { - errorStack = ( - error instanceof Error ? - (typeof error.stack === 'string' ? error.stack.trimEnd() : '') : - '' - ); - } catch (e) { - errorStack = ''; - } - - let errorData; - try { - if (error instanceof ExtensionError) { - errorData = error.data; - } - } catch (e) { - // NOP - } - - if (errorStack.startsWith(errorString)) { - errorString = errorStack; - } else if (errorStack.length > 0) { - errorString += `\n${errorStack}`; - } - - let message = `${this._extensionName} has encountered a problem.`; - message += `\nOriginating URL: ${context.url}\n`; - message += errorString; - if (typeof errorData !== 'undefined') { - message += `\nData: ${JSON.stringify(errorData, null, 4)}`; - } - message += '\n\nIssues can be reported at https://github.com/themoeway/yomitan/issues'; - - /* eslint-disable no-console */ - switch (level) { - case 'info': console.info(message); break; - case 'debug': console.debug(message); break; - case 'warn': console.warn(message); break; - case 'error': console.error(message); break; - default: console.log(message); break; - } - /* eslint-enable no-console */ - - this.trigger('log', {error, level, context}); - } - - /** - * Logs a warning. This function invokes `log` internally. - * @param {unknown} error The error to log. This is typically an `Error` or `Error`-like object. - * @param {?import('log').LogContext} context An optional context object for the error which should typically include a `url` field. - */ - warn(error, context = null) { - this.log(error, 'warn', context); - } - - /** - * Logs an error. This function invokes `log` internally. - * @param {unknown} error The error to log. This is typically an `Error` or `Error`-like object. - * @param {?import('log').LogContext} context An optional context object for the error which should typically include a `url` field. - */ - error(error, context = null) { - this.log(error, 'error', context); - } - - /** - * @param {import('log').LogLevel} errorLevel - * @returns {import('log').LogErrorLevelValue} - */ - getLogErrorLevelValue(errorLevel) { - switch (errorLevel) { - case 'log': - case 'info': - case 'debug': - return 0; - case 'warn': return 1; - case 'error': return 2; - } - } -} - -/** - * This object is the default logger used by the runtime. - */ -export const log = new Logger(); diff --git a/ext/js/core/api-map.js b/ext/js/core/api-map.js index 09be80354b..727c573089 100644 --- a/ext/js/core/api-map.js +++ b/ext/js/core/api-map.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/ext/js/core/dynamic-property.js b/ext/js/core/dynamic-property.js new file mode 100644 index 0000000000..5d8b4716d9 --- /dev/null +++ b/ext/js/core/dynamic-property.js @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023-2024 Yomitan Authors + * Copyright (C) 2019-2022 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import {EventDispatcher} from './event-dispatcher.js'; +import {generateId} from './utilities.js'; + +/** + * Class representing a generic value with an override stack. + * Changes can be observed by listening to the 'change' event. + * @template [T=unknown] + * @augments EventDispatcher> + */ +export class DynamicProperty extends EventDispatcher { + /** + * Creates a new instance with the specified value. + * @param {T} value The value to assign. + */ + constructor(value) { + super(); + /** @type {T} */ + this._value = value; + /** @type {T} */ + this._defaultValue = value; + /** @type {{value: T, priority: number, token: string}[]} */ + this._overrides = []; + } + + /** + * Gets the default value for the property, which is assigned to the + * public value property when no overrides are present. + * @type {T} + */ + get defaultValue() { + return this._defaultValue; + } + + /** + * Assigns the default value for the property. If no overrides are present + * and if the value is different than the current default value, + * the 'change' event will be triggered. + * @param {T} value The value to assign. + */ + set defaultValue(value) { + this._defaultValue = value; + if (this._overrides.length === 0) { this._updateValue(); } + } + + /** + * Gets the current value for the property, taking any overrides into account. + * @type {T} + */ + get value() { + return this._value; + } + + /** + * Gets the number of overrides added to the property. + * @type {number} + */ + get overrideCount() { + return this._overrides.length; + } + + /** + * Adds an override value with the specified priority to the override stack. + * Values with higher priority will take precedence over those with lower. + * For tie breaks, the override value added first will take precedence. + * If the newly added override has the highest priority of all overrides + * and if the override value is different from the current value, + * the 'change' event will be fired. + * @param {T} value The override value to assign. + * @param {number} [priority] The priority value to use, as a number. + * @returns {import('core').TokenString} A string token which can be passed to the clearOverride function + * to remove the override. + */ + setOverride(value, priority = 0) { + const overridesCount = this._overrides.length; + let i = 0; + for (; i < overridesCount; ++i) { + if (priority > this._overrides[i].priority) { break; } + } + const token = generateId(16); + this._overrides.splice(i, 0, {value, priority, token}); + if (i === 0) { this._updateValue(); } + return token; + } + + /** + * Removes a specific override value. If the removed override + * had the highest priority, and the new value is different from + * the previous value, the 'change' event will be fired. + * @param {import('core').TokenString} token The token for the corresponding override which is to be removed. + * @returns {boolean} `true` if an override was returned, `false` otherwise. + */ + clearOverride(token) { + for (let i = 0, ii = this._overrides.length; i < ii; ++i) { + if (this._overrides[i].token === token) { + this._overrides.splice(i, 1); + if (i === 0) { this._updateValue(); } + return true; + } + } + return false; + } + + /** + * Updates the current value using the current overrides and default value. + * If the new value differs from the previous value, the 'change' event will be fired. + */ + _updateValue() { + const value = this._overrides.length > 0 ? this._overrides[0].value : this._defaultValue; + if (this._value === value) { return; } + this._value = value; + this.trigger('change', {value}); + } +} diff --git a/ext/js/core/event-dispatcher.js b/ext/js/core/event-dispatcher.js new file mode 100644 index 0000000000..75f2fdd33f --- /dev/null +++ b/ext/js/core/event-dispatcher.js @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2023-2024 Yomitan Authors + * Copyright (C) 2019-2022 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * The following typedef is required because the JSDoc `implements` tag doesn't work with `import()`. + * https://github.com/microsoft/TypeScript/issues/49905 + * @typedef {import('core').EventDispatcherOffGeneric} EventDispatcherOffGeneric + */ + +/** + * Base class controls basic event dispatching. + * @template {import('core').EventSurface} TSurface + * @implements {EventDispatcherOffGeneric} + */ +export class EventDispatcher { + /** + * Creates a new instance. + */ + constructor() { + /** @type {Map, import('core').EventHandlerAny[]>} */ + this._eventMap = new Map(); + } + + /** + * Triggers an event with the given name and specified argument. + * @template {import('core').EventNames} TName + * @param {TName} eventName The string representing the event's name. + * @param {import('core').EventArgument} details The argument passed to the callback functions. + * @returns {boolean} `true` if any callbacks were registered, `false` otherwise. + */ + trigger(eventName, details) { + const callbacks = this._eventMap.get(eventName); + if (typeof callbacks === 'undefined') { return false; } + + for (const callback of callbacks) { + callback(details); + } + return true; + } + + /** + * Adds a single event listener to a specific event. + * @template {import('core').EventNames} TName + * @param {TName} eventName The string representing the event's name. + * @param {import('core').EventHandler} callback The event listener callback to add. + */ + on(eventName, callback) { + let callbacks = this._eventMap.get(eventName); + if (typeof callbacks === 'undefined') { + callbacks = []; + this._eventMap.set(eventName, callbacks); + } + callbacks.push(callback); + } + + /** + * Removes a single event listener from a specific event. + * @template {import('core').EventNames} TName + * @param {TName} eventName The string representing the event's name. + * @param {import('core').EventHandler} callback The event listener callback to add. + * @returns {boolean} `true` if the callback was removed, `false` otherwise. + */ + off(eventName, callback) { + const callbacks = this._eventMap.get(eventName); + if (typeof callbacks === 'undefined') { return false; } + + const ii = callbacks.length; + for (let i = 0; i < ii; ++i) { + if (callbacks[i] === callback) { + callbacks.splice(i, 1); + if (callbacks.length === 0) { + this._eventMap.delete(eventName); + } + return true; + } + } + return false; + } + + /** + * Checks if an event has any listeners. + * @template {import('core').EventNames} TName + * @param {TName} eventName The string representing the event's name. + * @returns {boolean} `true` if the event has listeners, `false` otherwise. + */ + hasListeners(eventName) { + const callbacks = this._eventMap.get(eventName); + return (typeof callbacks !== 'undefined' && callbacks.length > 0); + } +} diff --git a/ext/js/core/event-listener-collection.js b/ext/js/core/event-listener-collection.js new file mode 100644 index 0000000000..87d18a90c6 --- /dev/null +++ b/ext/js/core/event-listener-collection.js @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2023-2024 Yomitan Authors + * Copyright (C) 2019-2022 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Class which stores event listeners added to various objects, making it easy to remove them in bulk. + */ +export class EventListenerCollection { + /** + * Creates a new instance. + */ + constructor() { + /** @type {import('event-listener-collection').EventListenerDetails[]} */ + this._eventListeners = []; + } + + /** + * Returns the number of event listeners that are currently in the object. + * @type {number} + */ + get size() { + return this._eventListeners.length; + } + + /** + * Adds an event listener using `object.addEventListener`. The listener will later be removed using `object.removeEventListener`. + * @param {import('event-listener-collection').EventTarget} target The object to add the event listener to. + * @param {string} type The name of the event. + * @param {EventListener | EventListenerObject | import('event-listener-collection').EventListenerFunction} listener The callback listener. + * @param {AddEventListenerOptions | boolean} [options] Options for the event. + */ + addEventListener(target, type, listener, options) { + target.addEventListener(type, listener, options); + this._eventListeners.push({type: 'removeEventListener', target, eventName: type, listener, options}); + } + + /** + * Adds an event listener using `object.addListener`. The listener will later be removed using `object.removeListener`. + * @template {import('event-listener-collection').EventListenerFunction} TCallback + * @template [TArgs=unknown] + * @param {import('event-listener-collection').ExtensionEvent} target The object to add the event listener to. + * @param {TCallback} callback The callback. + * @param {TArgs[]} args The extra argument array passed to the `addListener`/`removeListener` function. + */ + addListener(target, callback, ...args) { + target.addListener(callback, ...args); + this._eventListeners.push({type: 'removeListener', target, callback, args}); + } + + /** + * Adds an event listener using `object.on`. The listener will later be removed using `object.off`. + * @template {import('core').EventSurface} TSurface + * @template {import('core').EventNames} TName + * @param {import('./event-dispatcher.js').EventDispatcher} target The object to add the event listener to. + * @param {TName} eventName The string representing the event's name. + * @param {import('core').EventHandler} callback The event listener callback to add. + */ + on(target, eventName, callback) { + target.on(eventName, callback); + this._eventListeners.push({type: 'off', eventName, target, callback}); + } + + /** + * Removes all event listeners added to objects for this instance and clears the internal list of event listeners. + */ + removeAllEventListeners() { + if (this._eventListeners.length === 0) { return; } + for (const item of this._eventListeners) { + switch (item.type) { + case 'removeEventListener': + item.target.removeEventListener(item.eventName, item.listener, item.options); + break; + case 'removeListener': + item.target.removeListener(item.callback, ...item.args); + break; + case 'off': + item.target.off(item.eventName, item.callback); + break; + } + } + this._eventListeners = []; + } +} diff --git a/ext/js/core/extension-error.js b/ext/js/core/extension-error.js index f1485a74d6..ab1c9dd73e 100644 --- a/ext/js/core/extension-error.js +++ b/ext/js/core/extension-error.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/ext/js/core/json.js b/ext/js/core/json.js index 0ebe28879f..c7d8fbfd1d 100644 --- a/ext/js/core/json.js +++ b/ext/js/core/json.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/ext/js/core/logger.js b/ext/js/core/logger.js new file mode 100644 index 0000000000..165e1ae255 --- /dev/null +++ b/ext/js/core/logger.js @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2023-2024 Yomitan Authors + * Copyright (C) 2019-2022 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import {EventDispatcher} from './event-dispatcher.js'; +import {ExtensionError} from './extension-error.js'; + +/** + * This class handles logging of messages to the console and triggering + * an event for log calls. + * @augments EventDispatcher + */ +export class Logger extends EventDispatcher { + /** + * Creates a new instance. + */ + constructor() { + super(); + /** @type {string} */ + this._extensionName = 'Yomitan'; + try { + const {name, version} = chrome.runtime.getManifest(); + this._extensionName = `${name} ${version}`; + } catch (e) { + // NOP + } + } + + /** + * Logs a generic error. This will trigger the 'log' event with the same arguments as the function invocation. + * @param {unknown} error The error to log. This is typically an `Error` or `Error`-like object. + * @param {import('log').LogLevel} level The level to log at. Values include `'info'`, `'debug'`, `'warn'`, and `'error'`. + * Other values will be logged at a non-error level. + * @param {?import('log').LogContext} [context] An optional context object for the error which should typically include a `url` field. + */ + log(error, level, context = null) { + if (typeof context !== 'object' || context === null) { + context = {url: location.href}; + } + + let errorString; + try { + if (typeof error === 'string') { + errorString = error; + } else { + errorString = ( + typeof error === 'object' && error !== null ? + error.toString() : + `${error}` + ); + if (/^\[object \w+\]$/.test(errorString)) { + errorString = JSON.stringify(error); + } + } + } catch (e) { + errorString = `${error}`; + } + + let errorStack; + try { + errorStack = ( + error instanceof Error ? + (typeof error.stack === 'string' ? error.stack.trimEnd() : '') : + '' + ); + } catch (e) { + errorStack = ''; + } + + let errorData; + try { + if (error instanceof ExtensionError) { + errorData = error.data; + } + } catch (e) { + // NOP + } + + if (errorStack.startsWith(errorString)) { + errorString = errorStack; + } else if (errorStack.length > 0) { + errorString += `\n${errorStack}`; + } + + let message = `${this._extensionName} has encountered a problem.`; + message += `\nOriginating URL: ${context.url}\n`; + message += errorString; + if (typeof errorData !== 'undefined') { + message += `\nData: ${JSON.stringify(errorData, null, 4)}`; + } + message += '\n\nIssues can be reported at https://github.com/themoeway/yomitan/issues'; + + /* eslint-disable no-console */ + switch (level) { + case 'log': console.log(message); break; + case 'info': console.info(message); break; + case 'debug': console.debug(message); break; + case 'warn': console.warn(message); break; + case 'error': console.error(message); break; + } + /* eslint-enable no-console */ + + this.trigger('log', {error, level, context}); + } + + /** + * Logs a warning. This function invokes `log` internally. + * @param {unknown} error The error to log. This is typically an `Error` or `Error`-like object. + * @param {?import('log').LogContext} context An optional context object for the error which should typically include a `url` field. + */ + warn(error, context = null) { + this.log(error, 'warn', context); + } + + /** + * Logs an error. This function invokes `log` internally. + * @param {unknown} error The error to log. This is typically an `Error` or `Error`-like object. + * @param {?import('log').LogContext} context An optional context object for the error which should typically include a `url` field. + */ + error(error, context = null) { + this.log(error, 'error', context); + } + + /** + * @param {import('log').LogLevel} errorLevel + * @returns {import('log').LogErrorLevelValue} + */ + getLogErrorLevelValue(errorLevel) { + switch (errorLevel) { + case 'log': + case 'info': + case 'debug': + return 0; + case 'warn': return 1; + case 'error': return 2; + } + } +} + +/** + * This object is the default logger used by the runtime. + */ +export const log = new Logger(); diff --git a/ext/js/core/to-error.js b/ext/js/core/to-error.js new file mode 100644 index 0000000000..c662929c0d --- /dev/null +++ b/ext/js/core/to-error.js @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2023-2024 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Utility function to convert an unknown value to an error. + * This is useful for try-catch situations where the catch parameter has the `unknown` type. + * @param {unknown} value + * @returns {Error} + */ +export function toError(value) { + return value instanceof Error ? value : new Error(`${value}`); +} diff --git a/ext/js/core/utilities.js b/ext/js/core/utilities.js new file mode 100644 index 0000000000..1fc2da42ba --- /dev/null +++ b/ext/js/core/utilities.js @@ -0,0 +1,319 @@ +/* + * Copyright (C) 2023-2024 Yomitan Authors + * Copyright (C) 2019-2022 Yomichan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * Checks whether a given value is a non-array object. + * @param {unknown} value The value to check. + * @returns {boolean} `true` if the value is an object and not an array, `false` otherwise. + */ +export function isObject(value) { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +/** + * Converts any string into a form that can be passed into the RegExp constructor. + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + * @param {string} string The string to convert to a valid regular expression. + * @returns {string} The escaped string. + */ +export function escapeRegExp(string) { + return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); +} + +/** + * Reverses a string. + * @param {string} string The string to reverse. + * @returns {string} The returned string, which retains proper UTF-16 surrogate pair order. + */ +export function stringReverse(string) { + return [...string].reverse().join(''); +} + +/** + * Creates a deep clone of an object or value. This is similar to `parseJson(JSON.stringify(value))`. + * @template [T=unknown] + * @param {T} value The value to clone. + * @returns {T} A new clone of the value. + * @throws An error if the value is circular and cannot be cloned. + */ +export function clone(value) { + if (value === null) { return /** @type {T} */ (null); } + switch (typeof value) { + case 'boolean': + case 'number': + case 'string': + case 'bigint': + case 'symbol': + case 'undefined': + return value; + default: + return cloneInternal(value, new Set()); + } +} + +/** + * @template [T=unknown] + * @param {T} value + * @param {Set} visited + * @returns {T} + * @throws {Error} + */ +function cloneInternal(value, visited) { + if (value === null) { return /** @type {T} */ (null); } + switch (typeof value) { + case 'boolean': + case 'number': + case 'string': + case 'bigint': + case 'symbol': + case 'undefined': + return value; + case 'object': + return /** @type {T} */ ( + Array.isArray(value) ? + cloneArray(value, visited) : + cloneObject(/** @type {import('core').SerializableObject} */ (value), visited) + ); + default: + throw new Error(`Cannot clone object of type ${typeof value}`); + } +} + +/** + * @param {unknown[]} value + * @param {Set} visited + * @returns {unknown[]} + * @throws {Error} + */ +function cloneArray(value, visited) { + if (visited.has(value)) { throw new Error('Circular'); } + try { + visited.add(value); + const result = []; + for (const item of value) { + result.push(cloneInternal(item, visited)); + } + return result; + } finally { + visited.delete(value); + } +} + +/** + * @param {import('core').SerializableObject} value + * @param {Set} visited + * @returns {import('core').SerializableObject} + * @throws {Error} + */ +function cloneObject(value, visited) { + if (visited.has(value)) { throw new Error('Circular'); } + try { + visited.add(value); + /** @type {import('core').SerializableObject} */ + const result = {}; + for (const key in value) { + if (Object.prototype.hasOwnProperty.call(value, key)) { + result[key] = cloneInternal(value[key], visited); + } + } + return result; + } finally { + visited.delete(value); + } +} + +/** + * Checks if an object or value is deeply equal to another object or value. + * @param {unknown} value1 The first value to check. + * @param {unknown} value2 The second value to check. + * @returns {boolean} `true` if the values are the same object, or deeply equal without cycles. `false` otherwise. + */ +export function deepEqual(value1, value2) { + if (value1 === value2) { return true; } + + const type = typeof value1; + if (typeof value2 !== type) { return false; } + + switch (type) { + case 'object': + case 'function': + return deepEqualInternal(value1, value2, new Set()); + default: + return false; + } +} + +/** + * @param {unknown} value1 + * @param {unknown} value2 + * @param {Set} visited1 + * @returns {boolean} + */ +function deepEqualInternal(value1, value2, visited1) { + if (value1 === value2) { return true; } + + const type = typeof value1; + if (typeof value2 !== type) { return false; } + + switch (type) { + case 'object': + case 'function': + { + if (value1 === null || value2 === null) { return false; } + const array = Array.isArray(value1); + if (array !== Array.isArray(value2)) { return false; } + if (visited1.has(value1)) { return false; } + visited1.add(value1); + return ( + array ? + areArraysEqual(/** @type {unknown[]} */ (value1), /** @type {unknown[]} */ (value2), visited1) : + areObjectsEqual(/** @type {import('core').UnknownObject} */ (value1), /** @type {import('core').UnknownObject} */ (value2), visited1) + ); + } + default: + return false; + } +} + +/** + * @param {import('core').UnknownObject} value1 + * @param {import('core').UnknownObject} value2 + * @param {Set} visited1 + * @returns {boolean} + */ +function areObjectsEqual(value1, value2, visited1) { + const keys1 = Object.keys(value1); + const keys2 = Object.keys(value2); + if (keys1.length !== keys2.length) { return false; } + + const keys1Set = new Set(keys1); + for (const key of keys2) { + if (!keys1Set.has(key) || !deepEqualInternal(value1[key], value2[key], visited1)) { return false; } + } + + return true; +} + +/** + * @param {unknown[]} value1 + * @param {unknown[]} value2 + * @param {Set} visited1 + * @returns {boolean} + */ +function areArraysEqual(value1, value2, visited1) { + const length = value1.length; + if (length !== value2.length) { return false; } + + for (let i = 0; i < length; ++i) { + if (!deepEqualInternal(value1[i], value2[i], visited1)) { return false; } + } + + return true; +} + +/** + * Creates a new base-16 (lower case) string of a sequence of random bytes of the given length. + * @param {number} length The number of bytes the string represents. The returned string's length will be twice as long. + * @returns {string} A string of random characters. + */ +export function generateId(length) { + const array = new Uint8Array(length); + crypto.getRandomValues(array); + let id = ''; + for (const value of array) { + id += value.toString(16).padStart(2, '0'); + } + return id; +} + +/** + * Creates an unresolved promise that can be resolved later, outside the promise's executor function. + * @template [T=unknown] + * @returns {import('core').DeferredPromiseDetails} An object `{promise, resolve, reject}`, containing the promise and the resolve/reject functions. + */ +export function deferPromise() { + /** @type {((value: T) => void)|undefined} */ + let resolve; + /** @type {((reason?: import('core').RejectionReason) => void)|undefined} */ + let reject; + const promise = new Promise((resolve2, reject2) => { + resolve = resolve2; + reject = reject2; + }); + return { + promise, + resolve: /** @type {(value: T) => void} */ (resolve), + reject: /** @type {(reason?: import('core').RejectionReason) => void} */ (reject) + }; +} + +/** + * Creates a promise that is resolved after a set delay. + * @param {number} delay How many milliseconds until the promise should be resolved. If 0, the promise is immediately resolved. + * @returns {Promise} A promise with two additional properties: `resolve` and `reject`, which can be used to complete the promise early. + */ +export function promiseTimeout(delay) { + return delay <= 0 ? Promise.resolve() : new Promise((resolve) => { setTimeout(resolve, delay); }); +} + +/** + * Creates a promise that will resolve after the next animation frame, using `requestAnimationFrame`. + * @param {number} [timeout] A maximum duration (in milliseconds) to wait until the promise resolves. If null or omitted, no timeout is used. + * @returns {Promise<{time: number, timeout: boolean}>} A promise that is resolved with `{time, timeout}`, where `time` is the timestamp from `requestAnimationFrame`, + * and `timeout` is a boolean indicating whether the cause was a timeout or not. + * @throws The promise throws an error if animation is not supported in this context, such as in a service worker. + */ +export function promiseAnimationFrame(timeout) { + return new Promise((resolve, reject) => { + if (typeof cancelAnimationFrame !== 'function' || typeof requestAnimationFrame !== 'function') { + reject(new Error('Animation not supported in this context')); + return; + } + + /** @type {?import('core').Timeout} */ + let timer = null; + /** @type {?number} */ + let frameRequest = null; + /** + * @param {number} time + */ + const onFrame = (time) => { + frameRequest = null; + if (timer !== null) { + clearTimeout(timer); + timer = null; + } + resolve({time, timeout: false}); + }; + const onTimeout = () => { + timer = null; + if (frameRequest !== null) { + // eslint-disable-next-line no-undef + cancelAnimationFrame(frameRequest); + frameRequest = null; + } + resolve({time: performance.now(), timeout: true}); + }; + + // eslint-disable-next-line no-undef + frameRequest = requestAnimationFrame(onFrame); + if (typeof timeout === 'number') { + timer = setTimeout(onTimeout, timeout); + } + }); +} diff --git a/ext/js/data/anki-note-builder.js b/ext/js/data/anki-note-builder.js index cdb0656468..48564d54d0 100644 --- a/ext/js/data/anki-note-builder.js +++ b/ext/js/data/anki-note-builder.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {deferPromise} from '../core.js'; +import {deferPromise} from '../core/utilities.js'; import {ExtensionError} from '../core/extension-error.js'; import {yomitan} from '../yomitan.js'; import {AnkiUtil} from './anki-util.js'; diff --git a/ext/js/data/anki-util.js b/ext/js/data/anki-util.js index 4717d472a2..57684887ff 100644 --- a/ext/js/data/anki-util.js +++ b/ext/js/data/anki-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {isObject} from '../core.js'; +import {isObject} from '../core/utilities.js'; /** * This class has some general utility functions for working with Anki data. diff --git a/ext/js/data/database.js b/ext/js/data/database.js index 43ac9e5b1f..e8575be2af 100644 --- a/ext/js/data/database.js +++ b/ext/js/data/database.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/data/json-schema.js b/ext/js/data/json-schema.js index ee58976ebf..9b7ea01117 100644 --- a/ext/js/data/json-schema.js +++ b/ext/js/data/json-schema.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {clone} from '../core.js'; +import {clone} from '../core/utilities.js'; import {CacheMap} from '../general/cache-map.js'; export class JsonSchemaError extends Error { diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index c6acf37309..cbaeb92b77 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {escapeRegExp, isObject} from '../core.js'; +import {escapeRegExp, isObject} from '../core/utilities.js'; import {parseJson, readResponseJson} from '../core/json.js'; import {TemplatePatcher} from '../templates/template-patcher.js'; import {JsonSchema} from './json-schema.js'; @@ -555,7 +555,9 @@ export class OptionsUtil { this._updateVersion19, this._updateVersion20, this._updateVersion21, - this._updateVersion22 + this._updateVersion22, + this._updateVersion23, + this._updateVersion24 ]; if (typeof targetVersion === 'number' && targetVersion < result.length) { result.splice(targetVersion); @@ -1140,8 +1142,32 @@ export class OptionsUtil { for (const {options: profileOptions} of options.profiles) { profileOptions.translation.searchResolution = 'letter'; } + } - return options; + /** + * - Added dictionaries[].partsOfSpeechFilter. + * @type {import('options-util').UpdateFunction} + */ + _updateVersion23(options) { + for (const {options: profileOptions} of options.profiles) { + for (const dictionary of profileOptions.dictionaries) { + dictionary.partsOfSpeechFilter = true; + } + } + } + + /** + * - Added dictionaries[].useDeinflections. + * @type {import('options-util').UpdateFunction} + */ + async _updateVersion24(options) { + await this._applyAnkiFieldTemplatesPatch(options, '/data/templates/anki-field-templates-upgrade-v24.handlebars'); + + for (const {options: profileOptions} of options.profiles) { + for (const dictionary of profileOptions.dictionaries) { + dictionary.useDeinflections = true; + } + } } /** diff --git a/ext/js/data/permissions-util.js b/ext/js/data/permissions-util.js index 76c5031b5b..43aaa87c86 100644 --- a/ext/js/data/permissions-util.js +++ b/ext/js/data/permissions-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/data/sandbox/anki-note-data-creator.js b/ext/js/data/sandbox/anki-note-data-creator.js index c0a1186980..5a608cd23d 100644 --- a/ext/js/data/sandbox/anki-note-data-creator.js +++ b/ext/js/data/sandbox/anki-note-data-creator.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -376,7 +376,7 @@ export class AnkiNoteDataCreator { case 'merge': type = 'termMerged'; break; } - const {inflections, score, dictionaryIndex, dictionaryPriority, sourceTermExactMatchCount, definitions} = dictionaryEntry; + const {inflectionRuleChainCandidates, score, dictionaryIndex, dictionaryPriority, sourceTermExactMatchCount, definitions} = dictionaryEntry; let {url} = context; if (typeof url !== 'string') { url = ''; } @@ -401,7 +401,7 @@ export class AnkiNoteDataCreator { source: (primarySource !== null ? primarySource.transformedText : null), rawSource: (primarySource !== null ? primarySource.originalText : null), sourceTerm: (type !== 'termMerged' ? (primarySource !== null ? primarySource.deinflectedText : null) : void 0), - reasons: inflections, + inflectionRuleChainCandidates, score, isPrimary: (type === 'term' ? dictionaryEntry.isPrimary : void 0), get sequence() { return self.getCachedValue(sequence); }, diff --git a/ext/js/data/sandbox/array-buffer-util.js b/ext/js/data/sandbox/array-buffer-util.js index 20715bd609..1857ec7425 100644 --- a/ext/js/data/sandbox/array-buffer-util.js +++ b/ext/js/data/sandbox/array-buffer-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/data/sandbox/string-util.js b/ext/js/data/sandbox/string-util.js index 3d810ffb45..3bc1c54940 100644 --- a/ext/js/data/sandbox/string-util.js +++ b/ext/js/data/sandbox/string-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/dictionary/dictionary-data-util.js b/ext/js/dictionary/dictionary-data-util.js index 50ae4b1194..c7c9ecfcc6 100644 --- a/ext/js/dictionary/dictionary-data-util.js +++ b/ext/js/dictionary/dictionary-data-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/dictionary/dictionary-database.js b/ext/js/dictionary/dictionary-database.js index 02db6322a0..1c52b7ab85 100644 --- a/ext/js/dictionary/dictionary-database.js +++ b/ext/js/dictionary/dictionary-database.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {log, stringReverse} from '../core.js'; +import {log} from '../core/logger.js'; +import {stringReverse} from '../core/utilities.js'; import {Database} from '../data/database.js'; export class DictionaryDatabase { diff --git a/ext/js/dictionary/dictionary-importer-media-loader.js b/ext/js/dictionary/dictionary-importer-media-loader.js index a5857dce43..86ed53f23e 100644 --- a/ext/js/dictionary/dictionary-importer-media-loader.js +++ b/ext/js/dictionary/dictionary-importer-media-loader.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../core.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; /** * Class used for loading and validating media during the dictionary import process. diff --git a/ext/js/dictionary/dictionary-importer.js b/ext/js/dictionary/dictionary-importer.js index 2c0c7e9c2e..c8f68d78f3 100644 --- a/ext/js/dictionary/dictionary-importer.js +++ b/ext/js/dictionary/dictionary-importer.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -24,9 +24,10 @@ import { ZipReader as ZipReader0, configure } from '../../lib/zip.js'; -import {stringReverse} from '../core.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'; const ajvSchemas = /** @type {import('dictionary-importer').CompiledSchemaValidators} */ (/** @type {unknown} */ (ajvSchemas0)); @@ -114,13 +115,13 @@ export class DictionaryImporter { // Files /** @type {import('dictionary-importer').QueryDetails} */ - const queryDetails = new Map([ + const queryDetails = [ ['termFiles', /^term_bank_(\d+)\.json$/], ['termMetaFiles', /^term_meta_bank_(\d+)\.json$/], ['kanjiFiles', /^kanji_bank_(\d+)\.json$/], ['kanjiMetaFiles', /^kanji_meta_bank_(\d+)\.json$/], ['tagFiles', /^tag_bank_(\d+)\.json$/] - ]); + ]; const {termFiles, termMetaFiles, kanjiFiles, kanjiMetaFiles, tagFiles} = Object.fromEntries(this._getArchiveFiles(fileMap, queryDetails)); // Load data @@ -159,7 +160,7 @@ export class DictionaryImporter { const glossaryList = entry.glossary; for (let j = 0, jj = glossaryList.length; j < jj; ++j) { const glossary = glossaryList[j]; - if (typeof glossary !== 'object' || glossary === null) { continue; } + if (typeof glossary !== 'object' || glossary === null || Array.isArray(glossary)) { continue; } glossaryList[j] = this._formatDictionaryTermGlossaryObject(glossary, entry, requirements); } if ((i % formatProgressInterval) === 0) { @@ -206,7 +207,7 @@ export class DictionaryImporter { try { await dictionaryDatabase.bulkAdd(objectStoreName, entries, i, count); } catch (e) { - errors.push(e instanceof Error ? e : new Error(`${e}`)); + errors.push(toError(e)); } this._progressData.index += count; @@ -691,16 +692,18 @@ export class DictionaryImporter { _getArchiveFiles(fileMap, queryDetails) { /** @type {import('dictionary-importer').QueryResult} */ const results = new Map(); - for (const [name, value] of fileMap.entries()) { - for (const [fileType, fileNameFormat] of queryDetails.entries()) { - let entries = results.get(fileType); - if (typeof entries === 'undefined') { - entries = []; - results.set(fileType, entries); - } - if (fileNameFormat.test(name)) { - entries.push(value); + for (const [fileType] of queryDetails) { + results.set(fileType, []); + } + + for (const [fileName, fileEntry] of fileMap.entries()) { + for (const [fileType, fileNameFormat] of queryDetails) { + if (!fileNameFormat.test(fileName)) { continue; } + const entries = results.get(fileType); + + if (typeof entries !== 'undefined') { + entries.push(fileEntry); break; } } diff --git a/ext/js/dictionary/dictionary-worker-handler.js b/ext/js/dictionary/dictionary-worker-handler.js index 9a72438687..1c11be9301 100644 --- a/ext/js/dictionary/dictionary-worker-handler.js +++ b/ext/js/dictionary/dictionary-worker-handler.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/dictionary/dictionary-worker-main.js b/ext/js/dictionary/dictionary-worker-main.js index 8ae283b895..d63a3a204b 100644 --- a/ext/js/dictionary/dictionary-worker-main.js +++ b/ext/js/dictionary/dictionary-worker-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {log} from '../core.js'; +import {log} from '../core/logger.js'; import {DictionaryWorkerHandler} from './dictionary-worker-handler.js'; /** Entry point. */ diff --git a/ext/js/dictionary/dictionary-worker-media-loader.js b/ext/js/dictionary/dictionary-worker-media-loader.js index e19a13d372..5c18e1846e 100644 --- a/ext/js/dictionary/dictionary-worker-media-loader.js +++ b/ext/js/dictionary/dictionary-worker-media-loader.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {generateId} from '../core.js'; +import {generateId} from '../core/utilities.js'; import {ExtensionError} from '../core/extension-error.js'; /** diff --git a/ext/js/dictionary/dictionary-worker.js b/ext/js/dictionary/dictionary-worker.js index 669c65accc..7a9e1114e0 100644 --- a/ext/js/dictionary/dictionary-worker.js +++ b/ext/js/dictionary/dictionary-worker.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/display/display-anki.js b/ext/js/display/display-anki.js index 759998c477..c51ddfa273 100644 --- a/ext/js/display/display-anki.js +++ b/ext/js/display/display-anki.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -import {EventListenerCollection, deferPromise} from '../core.js'; +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 {PopupMenu} from '../dom/popup-menu.js'; @@ -159,7 +161,7 @@ export class DisplayAnki { try { ({note: note, errors, requirements} = await this._createNote(dictionaryEntry, mode, [])); } catch (e) { - errors = [e instanceof Error ? e : new Error(`${e}`)]; + errors = [toError(e)]; } /** @type {import('display-anki').AnkiNoteLogData} */ const entry = {mode, note}; @@ -174,7 +176,7 @@ export class DisplayAnki { return { ankiNoteData, - ankiNoteDataException: ankiNoteDataException instanceof Error ? ankiNoteDataException : new Error(`${ankiNoteDataException}`), + ankiNoteDataException: toError(ankiNoteDataException), ankiNotes }; } @@ -490,7 +492,7 @@ export class DisplayAnki { addNoteOkay = true; } catch (e) { allErrors.length = 0; - allErrors.push(e instanceof Error ? e : new Error(`${e}`)); + allErrors.push(toError(e)); } if (addNoteOkay) { @@ -501,7 +503,7 @@ export class DisplayAnki { try { await yomitan.api.suspendAnkiCardsForNote(noteId); } catch (e) { - allErrors.push(e instanceof Error ? e : new Error(`${e}`)); + allErrors.push(toError(e)); } } button.disabled = true; @@ -509,7 +511,7 @@ export class DisplayAnki { } } } catch (e) { - allErrors.push(e instanceof Error ? e : new Error(`${e}`)); + allErrors.push(toError(e)); } finally { progressIndicatorVisible.clearOverride(overrideToken); } @@ -647,7 +649,7 @@ export class DisplayAnki { } } catch (e) { infos = this._getAnkiNoteInfoForceValue(notes, false); - ankiError = e instanceof Error ? e : new Error(`${e}`); + ankiError = toError(e); } /** @type {import('display-anki').DictionaryEntryDetails[]} */ @@ -855,11 +857,11 @@ export class DisplayAnki { await yomitan.api.noteView(noteIds[0], this._noteGuiMode, false); } catch (e) { const displayErrors = ( - e instanceof Error && e.message === 'Mode not supported' ? + toError(e).message === 'Mode not supported' ? [this._display.displayGenerator.instantiateTemplateFragment('footer-notification-anki-view-note-error')] : void 0 ); - this._showErrorNotification([e instanceof Error ? e : new Error(`${e}`)], displayErrors); + this._showErrorNotification([toError(e)], displayErrors); return; } } diff --git a/ext/js/display/display-audio.js b/ext/js/display/display-audio.js index 2df13306a1..8cbfc83fc8 100644 --- a/ext/js/display/display-audio.js +++ b/ext/js/display/display-audio.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../core.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; import {PopupMenu} from '../dom/popup-menu.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; import {AudioSystem} from '../media/audio-system.js'; diff --git a/ext/js/display/display-content-manager.js b/ext/js/display/display-content-manager.js index c78ef7e857..d13dffb303 100644 --- a/ext/js/display/display-content-manager.js +++ b/ext/js/display/display-content-manager.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../core.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js index 3a2a562115..7bf13b77e1 100644 --- a/ext/js/display/display-generator.js +++ b/ext/js/display/display-generator.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {isObject} from '../core.js'; +import {isObject} from '../core/utilities.js'; import {ExtensionError} from '../core/extension-error.js'; import {DictionaryDataUtil} from '../dictionary/dictionary-data-util.js'; import {HtmlTemplateCollection} from '../dom/html-template-collection.js'; @@ -67,13 +67,13 @@ export class DisplayGenerator { const node = this._instantiate('term-entry'); const headwordsContainer = this._querySelector(node, '.headword-list'); - const inflectionsContainer = this._querySelector(node, '.inflection-list'); + const inflectionRuleChainsContainer = this._querySelector(node, '.inflection-rule-chains'); const groupedPronunciationsContainer = this._querySelector(node, '.pronunciation-group-list'); const frequencyGroupListContainer = this._querySelector(node, '.frequency-group-list'); const definitionsContainer = this._querySelector(node, '.definition-list'); const headwordTagsContainer = this._querySelector(node, '.headword-list-tag-list'); - const {headwords, type, inflections, definitions, frequencies, pronunciations} = dictionaryEntry; + const {headwords, type, inflectionRuleChainCandidates, definitions, frequencies, pronunciations} = dictionaryEntry; const groupedPronunciations = DictionaryDataUtil.getGroupedPronunciations(dictionaryEntry); const pronunciationCount = groupedPronunciations.reduce((i, v) => i + v.pronunciations.length, 0); const groupedFrequencies = DictionaryDataUtil.groupTermFrequencies(dictionaryEntry); @@ -112,7 +112,7 @@ export class DisplayGenerator { } headwordsContainer.dataset.count = `${headwords.length}`; - this._appendMultiple(inflectionsContainer, this._createTermInflection.bind(this), inflections); + this._appendMultiple(inflectionRuleChainsContainer, this._createInflectionRuleChain.bind(this), inflectionRuleChainCandidates); this._appendMultiple(frequencyGroupListContainer, this._createFrequencyGroup.bind(this), groupedFrequencies, false); this._appendMultiple(groupedPronunciationsContainer, this._createGroupedPronunciation.bind(this), groupedPronunciations); this._appendMultiple(headwordTagsContainer, this._createTermTag.bind(this), termTags, headwords.length); @@ -356,6 +356,44 @@ export class DisplayGenerator { return node; } + /** + * @param {import('dictionary').InflectionRuleChainCandidate} inflectionRuleChain + * @returns {?HTMLElement} + */ + _createInflectionRuleChain(inflectionRuleChain) { + const {source, inflectionRules} = inflectionRuleChain; + if (!Array.isArray(inflectionRules) || inflectionRules.length === 0) { return null; } + const fragment = this._instantiate('inflection-rule-chain'); + + const sourceIcon = this._getInflectionSourceIcon(source); + + fragment.appendChild(sourceIcon); + + this._appendMultiple(fragment, this._createTermInflection.bind(this), inflectionRules); + return fragment; + } + + /** + * @param {import('dictionary').InflectionSource} source + * @returns {HTMLElement} + */ + _getInflectionSourceIcon(source) { + const icon = document.createElement('span'); + icon.classList.add('inflection-source-icon'); + icon.dataset.inflectionSource = source; + switch (source) { + case 'dictionary': + icon.title = 'Dictionary Deinflection'; + return icon; + case 'algorithm': + icon.title = 'Algorithm Deinflection'; + return icon; + case 'both': + icon.title = 'Dictionary and Algorithm Deinflection'; + return icon; + } + } + /** * @param {string} inflection * @returns {DocumentFragment} @@ -396,7 +434,7 @@ export class DisplayGenerator { } /** - * @param {import('dictionary-data').TermGlossary} entry + * @param {import('dictionary-data').TermGlossaryContent} entry * @param {string} dictionary * @returns {?HTMLElement} */ diff --git a/ext/js/display/display-history.js b/ext/js/display/display-history.js index 30bc3eecbd..255a8536d1 100644 --- a/ext/js/display/display-history.js +++ b/ext/js/display/display-history.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventDispatcher, generateId, isObject} from '../core.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; +import {generateId, isObject} from '../core/utilities.js'; /** * @augments EventDispatcher diff --git a/ext/js/display/display-notification.js b/ext/js/display/display-notification.js index df475adfb9..a955f3fa44 100644 --- a/ext/js/display/display-notification.js +++ b/ext/js/display/display-notification.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2017-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../core.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; export class DisplayNotification { diff --git a/ext/js/display/display-profile-selection.js b/ext/js/display/display-profile-selection.js index f2ebd3e40e..ff4fe6bfb4 100644 --- a/ext/js/display/display-profile-selection.js +++ b/ext/js/display/display-profile-selection.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventListenerCollection, generateId} from '../core.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; +import {generateId} from '../core/utilities.js'; import {PanelElement} from '../dom/panel-element.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/display/display-resizer.js b/ext/js/display/display-resizer.js index 8245e0bb22..7e346c7d5a 100644 --- a/ext/js/display/display-resizer.js +++ b/ext/js/display/display-resizer.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../core.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; export class DisplayResizer { /** diff --git a/ext/js/display/display.js b/ext/js/display/display.js index cae394f84f..689481f446 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2017-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -18,9 +18,14 @@ import {ThemeController} from '../app/theme-controller.js'; import {FrameEndpoint} from '../comm/frame-endpoint.js'; -import {DynamicProperty, EventDispatcher, EventListenerCollection, clone, deepEqual, log, promiseTimeout} from '../core.js'; import {extendApiMap, invokeApiMapHandler} from '../core/api-map.js'; +import {DynamicProperty} from '../core/dynamic-property.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; +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 {clone, deepEqual, promiseTimeout} from '../core/utilities.js'; import {PopupMenu} from '../dom/popup-menu.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; import {ScrollElement} from '../dom/scroll-element.js'; @@ -385,7 +390,7 @@ export class Display extends EventDispatcher { * @param {Error} error */ onError(error) { - if (yomitan.isExtensionUnloaded) { return; } + if (yomitan.webExtension.unloaded) { return; } log.error(error); } @@ -480,7 +485,7 @@ export class Display extends EventDispatcher { case 'overwrite': this._history.replaceState(state, content, url); break; - default: // 'new' + case 'new': this._updateHistoryState(); this._history.pushState(state, content, url); break; @@ -722,8 +727,7 @@ export class Display extends EventDispatcher { /** @type {import('display').WindowApiHandler<'displayExtensionUnloaded'>} */ _onMessageExtensionUnloaded() { - if (yomitan.isExtensionUnloaded) { return; } - yomitan.triggerExtensionUnloaded(); + yomitan.webExtension.triggerUnloaded(); } // Private @@ -787,7 +791,7 @@ export class Display extends EventDispatcher { break; } } catch (e) { - this.onError(e instanceof Error ? e : new Error(`${e}`)); + this.onError(toError(e)); } } @@ -922,7 +926,7 @@ export class Display extends EventDispatcher { }; this.setContent(details); } catch (error) { - this.onError(error instanceof Error ? error : new Error(`${error}`)); + this.onError(toError(error)); } } @@ -1889,7 +1893,7 @@ export class Display extends EventDispatcher { * @param {import('text-scanner').SearchedEventDetails} details */ _onContentTextScannerSearched({type, dictionaryEntries, sentence, textSource, optionsContext, error}) { - if (error !== null && !yomitan.isExtensionUnloaded) { + if (error !== null && !yomitan.webExtension.unloaded) { log.error(error); } diff --git a/ext/js/display/element-overflow-controller.js b/ext/js/display/element-overflow-controller.js index 35277fa5d2..e0b9035e82 100644 --- a/ext/js/display/element-overflow-controller.js +++ b/ext/js/display/element-overflow-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../core.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; export class ElementOverflowController { constructor() { diff --git a/ext/js/display/option-toggle-hotkey-handler.js b/ext/js/display/option-toggle-hotkey-handler.js index 73c92d596c..d9065e7f7b 100644 --- a/ext/js/display/option-toggle-hotkey-handler.js +++ b/ext/js/display/option-toggle-hotkey-handler.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,8 +16,9 @@ * along with this program. If not, see . */ -import {generateId} from '../core.js'; +import {generateId} from '../core/utilities.js'; import {ExtensionError} from '../core/extension-error.js'; +import {toError} from '../core/to-error.js'; import {yomitan} from '../yomitan.js'; export class OptionToggleHotkeyHandler { @@ -132,7 +133,7 @@ export class OptionToggleHotkeyHandler { * @returns {DocumentFragment} */ _createErrorMessage(path, error) { - const message = error instanceof Error ? error.message : `${error}`; + const message = toError(error).message; const fragment = document.createDocumentFragment(); const n1 = document.createElement('em'); n1.textContent = path; diff --git a/ext/js/display/popup-main.js b/ext/js/display/popup-main.js index fdb9550836..d4f622f2d5 100644 --- a/ext/js/display/popup-main.js +++ b/ext/js/display/popup-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {log} from '../core.js'; +import {log} from '../core/logger.js'; import {DocumentFocusController} from '../dom/document-focus-controller.js'; import {HotkeyHandler} from '../input/hotkey-handler.js'; import {JapaneseUtil} from '../language/sandbox/japanese-util.js'; diff --git a/ext/js/display/query-parser.js b/ext/js/display/query-parser.js index 11c5a4ec42..e129e1be13 100644 --- a/ext/js/display/query-parser.js +++ b/ext/js/display/query-parser.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventDispatcher, log} from '../core.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; +import {log} from '../core/logger.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; import {TextScanner} from '../language/text-scanner.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/display/sandbox/pronunciation-generator.js b/ext/js/display/sandbox/pronunciation-generator.js index d113485f9c..cfcf82a10d 100644 --- a/ext/js/display/sandbox/pronunciation-generator.js +++ b/ext/js/display/sandbox/pronunciation-generator.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/display/sandbox/structured-content-generator.js b/ext/js/display/sandbox/structured-content-generator.js index b74674fc1c..f73d790a5c 100644 --- a/ext/js/display/sandbox/structured-content-generator.js +++ b/ext/js/display/sandbox/structured-content-generator.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -354,6 +354,7 @@ export class StructuredContentGenerator { borderWidth, verticalAlign, textAlign, + textShadow, margin, marginTop, marginLeft, @@ -376,6 +377,7 @@ export class StructuredContentGenerator { if (typeof backgroundColor === 'string') { style.backgroundColor = backgroundColor; } if (typeof verticalAlign === 'string') { style.verticalAlign = verticalAlign; } if (typeof textAlign === 'string') { style.textAlign = textAlign; } + if (typeof textShadow === 'string') { style.textShadow = textShadow; } if (typeof textDecorationLine === 'string') { style.textDecoration = textDecorationLine; } else if (Array.isArray(textDecorationLine)) { diff --git a/ext/js/display/search-action-popup-controller.js b/ext/js/display/search-action-popup-controller.js index 3a2057a161..4c431abe05 100644 --- a/ext/js/display/search-action-popup-controller.js +++ b/ext/js/display/search-action-popup-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index 3df3a332b1..594a80aade 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -18,8 +18,8 @@ import * as wanakana from '../../lib/wanakana.js'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; -import {EventListenerCollection} from '../core.js'; import {createApiMap, invokeApiMapHandler} from '../core/api-map.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/display/search-main.js b/ext/js/display/search-main.js index fae7306ae8..3cdd1f2535 100644 --- a/ext/js/display/search-main.js +++ b/ext/js/display/search-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -17,7 +17,7 @@ */ import * as wanakana from '../../lib/wanakana.js'; -import {log} from '../core.js'; +import {log} from '../core/logger.js'; import {DocumentFocusController} from '../dom/document-focus-controller.js'; import {HotkeyHandler} from '../input/hotkey-handler.js'; import {JapaneseUtil} from '../language/sandbox/japanese-util.js'; diff --git a/ext/js/display/search-persistent-state-controller.js b/ext/js/display/search-persistent-state-controller.js index 1bd32f5bf2..d122018491 100644 --- a/ext/js/display/search-persistent-state-controller.js +++ b/ext/js/display/search-persistent-state-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventDispatcher} from '../core.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; /** * @augments EventDispatcher diff --git a/ext/js/dom/document-focus-controller.js b/ext/js/dom/document-focus-controller.js index fd88a35d9b..f23145b95f 100644 --- a/ext/js/dom/document-focus-controller.js +++ b/ext/js/dom/document-focus-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index df3bd35498..235a42d004 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -386,7 +386,7 @@ export class DocumentUtil { /** * Adds a fullscreen change event listener. This function handles all of the browser-specific variants. * @param {EventListener} onFullscreenChanged The event callback. - * @param {?import('../core.js').EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to. + * @param {?import('../core/event-listener-collection.js').EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to. */ static addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection = null) { const target = document; diff --git a/ext/js/dom/dom-data-binder.js b/ext/js/dom/dom-data-binder.js index e25583389d..7523e4340c 100644 --- a/ext/js/dom/dom-data-binder.js +++ b/ext/js/dom/dom-data-binder.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/dom/dom-text-scanner.js b/ext/js/dom/dom-text-scanner.js index 21770a7005..d605aecaed 100644 --- a/ext/js/dom/dom-text-scanner.js +++ b/ext/js/dom/dom-text-scanner.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -212,10 +212,8 @@ export class DOMTextScanner { /** * Gets information about how whitespace characters are treated. - * @param {Text} textNode The text node to check. - * @returns {{preserveNewlines: boolean, preserveWhitespace: boolean}} Information about the whitespace. - * The value of `preserveNewlines` indicates whether or not newline characters are treated as line breaks. - * The value of `preserveWhitespace` indicates whether or not sequences of whitespace characters are collapsed. + * @param {Text} textNode + * @returns {import('dom-text-scanner').WhitespaceSettings} */ _getWhitespaceSettings(textNode) { if (this._forcePreserveWhitespace) { @@ -412,13 +410,8 @@ export class DOMTextScanner { } /** - * Gets seek information about an element. - * @param {Element} element The element to check. - * @returns {{enterable: boolean, newlines: number}} The seek information. - * The `enterable` value indicates whether the content of this node should be entered. - * The `newlines` value corresponds to the number of newline characters that should be added. - * - 1 newline corresponds to a simple new line in the layout. - * - 2 newlines corresponds to a significant visual distinction since the previous content. + * @param {Element} element + * @returns {import('dom-text-scanner').ElementSeekInfo} */ static getElementSeekInfo(element) { let enterable = true; diff --git a/ext/js/dom/html-template-collection.js b/ext/js/dom/html-template-collection.js index 62d18224ab..981d5528b9 100644 --- a/ext/js/dom/html-template-collection.js +++ b/ext/js/dom/html-template-collection.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/dom/native-simple-dom-parser.js b/ext/js/dom/native-simple-dom-parser.js index 418a4e3cca..e40e7663c9 100644 --- a/ext/js/dom/native-simple-dom-parser.js +++ b/ext/js/dom/native-simple-dom-parser.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/dom/panel-element.js b/ext/js/dom/panel-element.js index 959ca42065..9c1289d717 100644 --- a/ext/js/dom/panel-element.js +++ b/ext/js/dom/panel-element.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventDispatcher} from '../core.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; /** * @augments EventDispatcher diff --git a/ext/js/dom/popup-menu.js b/ext/js/dom/popup-menu.js index 61f20ba89f..8a8a19baad 100644 --- a/ext/js/dom/popup-menu.js +++ b/ext/js/dom/popup-menu.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventDispatcher, EventListenerCollection} from '../core.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; import {querySelectorNotNull} from './query-selector.js'; /** diff --git a/ext/js/dom/query-selector.js b/ext/js/dom/query-selector.js index e881211daa..7d7a89ac7e 100644 --- a/ext/js/dom/query-selector.js +++ b/ext/js/dom/query-selector.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/ext/js/dom/sandbox/css-style-applier.js b/ext/js/dom/sandbox/css-style-applier.js index 6925f2635b..d250c0f5b4 100644 --- a/ext/js/dom/sandbox/css-style-applier.js +++ b/ext/js/dom/sandbox/css-style-applier.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/dom/scroll-element.js b/ext/js/dom/scroll-element.js index 95f5ce5b78..8005469b0d 100644 --- a/ext/js/dom/scroll-element.js +++ b/ext/js/dom/scroll-element.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/dom/selector-observer.js b/ext/js/dom/selector-observer.js index cff2985cda..791ce62738 100644 --- a/ext/js/dom/selector-observer.js +++ b/ext/js/dom/selector-observer.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/dom/simple-dom-parser.js b/ext/js/dom/simple-dom-parser.js index bca1cd8810..adc009bfb1 100644 --- a/ext/js/dom/simple-dom-parser.js +++ b/ext/js/dom/simple-dom-parser.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/dom/style-util.js b/ext/js/dom/style-util.js index 90dddd9ee6..ac20e6559f 100644 --- a/ext/js/dom/style-util.js +++ b/ext/js/dom/style-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js index 40ff5cc9d8..477295e67f 100644 --- a/ext/js/dom/text-source-element.js +++ b/ext/js/dom/text-source-element.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js index fd09fdda85..05e7b6fba9 100644 --- a/ext/js/dom/text-source-range.js +++ b/ext/js/dom/text-source-range.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import {toError} from '../core/to-error.js'; import {DocumentUtil} from './document-util.js'; import {DOMTextScanner} from './dom-text-scanner.js'; @@ -228,7 +229,7 @@ export class TextSourceRange { try { return this._range.compareBoundaryPoints(Range.START_TO_START, other.range) === 0; } catch (e) { - if (e instanceof Error && e.name === 'WrongDocumentError') { + if (toError(e).name === 'WrongDocumentError') { // This can happen with shadow DOMs if the ranges are in different documents. return false; } diff --git a/ext/js/extension/environment.js b/ext/js/extension/environment.js index 48a1a92ceb..30b8e9b4aa 100644 --- a/ext/js/extension/environment.js +++ b/ext/js/extension/environment.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/extension/web-extension.js b/ext/js/extension/web-extension.js new file mode 100644 index 0000000000..95a613390a --- /dev/null +++ b/ext/js/extension/web-extension.js @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2024 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import {EventDispatcher} from '../core/event-dispatcher.js'; +import {toError} from '../core/to-error.js'; + +/** + * @augments EventDispatcher + */ +export class WebExtension extends EventDispatcher { + constructor() { + super(); + /** @type {boolean} */ + this._unloaded = false; + } + + /** @type {boolean} */ + get unloaded() { + return this._unloaded; + } + + /** + * @param {string} path + * @returns {string} + */ + getUrl(path) { + return chrome.runtime.getURL(path); + } + + /** + * @param {unknown} message + * @param {(response: unknown) => void} responseCallback + * @throws {Error} + */ + sendMessage(message, responseCallback) { + try { + chrome.runtime.sendMessage(message, responseCallback); + } catch (error) { + this.triggerUnloaded(); + throw toError(error); + } + } + + /** + * @param {unknown} message + * @returns {Promise} + */ + sendMessagePromise(message) { + return new Promise((resolve, reject) => { + try { + this.sendMessage(message, (response) => { + const error = this.getLastError(); + if (error !== null) { + reject(error); + } else { + resolve(response); + } + }); + } catch (error) { + reject(error); + } + }); + } + + /** + * @param {unknown} message + */ + sendMessageIgnoreResponse(message) { + this.sendMessage(message, () => { + // Clear the last error + this.getLastError(); + }); + } + + /** + * @returns {?Error} + */ + getLastError() { + const {lastError} = chrome.runtime; + if (typeof lastError !== 'undefined') { + const {message} = lastError; + return new Error(typeof message === 'string' ? message : 'An unknown web extension error occured'); + } + return null; + } + + /** */ + triggerUnloaded() { + if (this._unloaded) { return; } + this._unloaded = true; + this.trigger('unloaded', {}); + } +} diff --git a/ext/js/general/cache-map.js b/ext/js/general/cache-map.js index 8650d8e694..4263210e23 100644 --- a/ext/js/general/cache-map.js +++ b/ext/js/general/cache-map.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/general/object-property-accessor.js b/ext/js/general/object-property-accessor.js index ce2417ce7e..d50948d9d3 100644 --- a/ext/js/general/object-property-accessor.js +++ b/ext/js/general/object-property-accessor.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/general/regex-util.js b/ext/js/general/regex-util.js index 6601e46341..301b1fcf28 100644 --- a/ext/js/general/regex-util.js +++ b/ext/js/general/regex-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/general/task-accumulator.js b/ext/js/general/task-accumulator.js index 86cb9e3eec..62f1286997 100644 --- a/ext/js/general/task-accumulator.js +++ b/ext/js/general/task-accumulator.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {log} from '../core.js'; +import {log} from '../core/logger.js'; /** * @template [K=unknown] diff --git a/ext/js/general/text-source-map.js b/ext/js/general/text-source-map.js index cfccf7c906..527c232b61 100644 --- a/ext/js/general/text-source-map.js +++ b/ext/js/general/text-source-map.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/input/hotkey-handler.js b/ext/js/input/hotkey-handler.js index 5969af057e..3b40a86d20 100644 --- a/ext/js/input/hotkey-handler.js +++ b/ext/js/input/hotkey-handler.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventDispatcher, EventListenerCollection} from '../core.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; import {DocumentUtil} from '../dom/document-util.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/input/hotkey-help-controller.js b/ext/js/input/hotkey-help-controller.js index 14063d9ad0..4c4f56d5dd 100644 --- a/ext/js/input/hotkey-help-controller.js +++ b/ext/js/input/hotkey-help-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {isObject} from '../core.js'; +import {isObject} from '../core/utilities.js'; import {parseJson} from '../core/json.js'; import {yomitan} from '../yomitan.js'; import {HotkeyUtil} from './hotkey-util.js'; diff --git a/ext/js/input/hotkey-util.js b/ext/js/input/hotkey-util.js index 601f5d55ab..bb3e82a8b0 100644 --- a/ext/js/input/hotkey-util.js +++ b/ext/js/input/hotkey-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/language/deinflector.js b/ext/js/language/deinflector.js index d2d92e53d6..7d75576d23 100644 --- a/ext/js/language/deinflector.js +++ b/ext/js/language/deinflector.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -59,7 +59,7 @@ export class Deinflector { for (const [reason, variants] of this.reasons) { for (const [kanaIn, kanaOut, rulesIn, rulesOut] of variants) { if ( - (rules !== 0 && (rules & rulesIn) === 0) || + !Deinflector.rulesMatch(rules, rulesIn) || !term.endsWith(kanaIn) || (term.length - kanaIn.length + kanaOut.length) <= 0 ) { @@ -80,7 +80,7 @@ export class Deinflector { /** * @param {string} term * @param {import('translation-internal').DeinflectionRuleFlags} rules - * @param {string[]} reasons + * @param {import('dictionary').InflectionRuleChain} reasons * @returns {import('translation-internal').Deinflection} */ _createDeinflection(term, rules, reasons) { @@ -124,4 +124,15 @@ export class Deinflector { } return value; } + + /** + * If `currentRules` is `0`, then `nextRules` is ignored and `true` is returned. + * Otherwise, there must be at least one shared rule between `currentRules` and `nextRules`. + * @param {number} currentRules + * @param {number} nextRules + * @returns {boolean} + */ + static rulesMatch(currentRules, nextRules) { + return currentRules === 0 || (currentRules & nextRules) !== 0; + } } diff --git a/ext/js/language/sandbox/japanese-util.js b/ext/js/language/sandbox/japanese-util.js index f5fc1c8669..89f4d5ed9c 100644 --- a/ext/js/language/sandbox/japanese-util.js +++ b/ext/js/language/sandbox/japanese-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index aed431d387..accb53fd58 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,10 @@ * along with this program. If not, see . */ -import {EventDispatcher, EventListenerCollection, clone, log} from '../core.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; +import {EventListenerCollection} from '../core/event-listener-collection.js'; +import {log} from '../core/logger.js'; +import {clone} from '../core/utilities.js'; import {DocumentUtil} from '../dom/document-util.js'; import {TextSourceElement} from '../dom/text-source-element.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 36ed8b4358..cedc7d3d18 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -70,7 +70,7 @@ export class Translator { async findTerms(mode, text, options) { const {enabledDictionaryMap, excludeDictionaryDefinitions, sortFrequencyDictionary, sortFrequencyDictionaryOrder} = options; const tagAggregator = new TranslatorTagAggregator(); - let {dictionaryEntries, originalTextLength} = await this._findTermsInternalWrapper(text, enabledDictionaryMap, options, tagAggregator); + let {dictionaryEntries, originalTextLength} = await this._findTermsInternal(text, enabledDictionaryMap, options, tagAggregator); switch (mode) { case 'group': @@ -208,7 +208,7 @@ export class Translator { * @param {TranslatorTagAggregator} tagAggregator * @returns {Promise} */ - async _findTermsInternalWrapper(text, enabledDictionaryMap, options, tagAggregator) { + async _findTermsInternal(text, enabledDictionaryMap, options, tagAggregator) { if (options.removeNonJapaneseCharacters) { text = this._getJapaneseOnlyText(text); } @@ -216,18 +216,30 @@ export class Translator { return {dictionaryEntries: [], originalTextLength: 0}; } - const deinflections = await this._findTermsInternal(text, enabledDictionaryMap, options); + const deinflections = await this._getDeinflections(text, enabledDictionaryMap, options); let originalTextLength = 0; + /** @type {import('dictionary').TermDictionaryEntry[]} */ const dictionaryEntries = []; const ids = new Set(); - for (const {databaseEntries, originalText, transformedText, deinflectedText, reasons} of deinflections) { + for (const {databaseEntries, originalText, transformedText, deinflectedText, inflectionRuleChainCandidates} of deinflections) { if (databaseEntries.length === 0) { continue; } originalTextLength = Math.max(originalTextLength, originalText.length); for (const databaseEntry of databaseEntries) { const {id} = databaseEntry; - if (ids.has(id)) { continue; } - const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, reasons, true, enabledDictionaryMap, tagAggregator); + if (ids.has(id)) { + const existingEntry = dictionaryEntries.find((entry) => { + return entry.definitions.some((definition) => definition.id === id); + }); + + if (existingEntry && transformedText.length >= existingEntry.headwords[0].sources[0].transformedText.length) { + this._mergeInflectionRuleChains(existingEntry, inflectionRuleChainCandidates); + } + + continue; + } + + const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, inflectionRuleChainCandidates, true, enabledDictionaryMap, tagAggregator); dictionaryEntries.push(dictionaryEntry); ids.add(id); } @@ -236,49 +248,173 @@ export class Translator { return {dictionaryEntries, originalTextLength}; } + /** + * @param {import('dictionary').TermDictionaryEntry} existingEntry + * @param {import('dictionary').InflectionRuleChainCandidate[]} inflectionRuleChainCandidates + */ + _mergeInflectionRuleChains(existingEntry, inflectionRuleChainCandidates) { + const existingChains = existingEntry.inflectionRuleChainCandidates; + + for (const {source, inflectionRules} of inflectionRuleChainCandidates) { + const duplicate = existingChains.find((existingChain) => this._areArraysEqualIgnoreOrder(existingChain.inflectionRules, inflectionRules)); + if (!duplicate) { + existingEntry.inflectionRuleChainCandidates.push({source, inflectionRules}); + } else if (duplicate.source !== source) { + duplicate.source = 'both'; + } + } + } + + /** + * @param {string[]} array1 + * @param {string[]} array2 + * @returns {boolean} + */ + _areArraysEqualIgnoreOrder(array1, array2) { + if (array1.length !== array2.length) { + return false; + } + + const frequencyCounter = new Map(); + + for (const element of array1) { + frequencyCounter.set(element, (frequencyCounter.get(element) || 0) + 1); + } + + for (const element of array2) { + const frequency = frequencyCounter.get(element); + if (!frequency) { + return false; + } + frequencyCounter.set(element, frequency - 1); + } + + return true; + } + + /** * @param {string} text * @param {Map} enabledDictionaryMap * @param {import('translation').FindTermsOptions} options * @returns {Promise} */ - async _findTermsInternal(text, enabledDictionaryMap, options) { - const deinflections = ( + async _getDeinflections(text, enabledDictionaryMap, options) { + let deinflections = ( options.deinflect ? - this._getAllDeinflections(text, options) : + this._getAlgorithmDeinflections(text, options) : [this._createDeinflection(text, text, text, 0, [])] ); if (deinflections.length === 0) { return []; } - const uniqueDeinflectionTerms = []; - const uniqueDeinflectionArrays = []; - const uniqueDeinflectionsMap = new Map(); + const {matchType} = options; + + await this._addEntriesToDeinflections(deinflections, enabledDictionaryMap, matchType); + + const dictionaryDeinflections = await this._getDictionaryDeinflections(deinflections, enabledDictionaryMap, matchType); + deinflections.push(...dictionaryDeinflections); + + for (const deinflection of deinflections) { + for (const entry of deinflection.databaseEntries) { + entry.definitions = entry.definitions.filter((definition) => !Array.isArray(definition)); + } + deinflection.databaseEntries = deinflection.databaseEntries.filter((entry) => entry.definitions.length); + } + deinflections = deinflections.filter((deinflection) => deinflection.databaseEntries.length); + + return deinflections; + } + + /** + * @param {import('translation-internal').DatabaseDeinflection[]} deinflections + * @param {Map} enabledDictionaryMap + * @param {import('dictionary').TermSourceMatchType} matchType + * @returns {Promise} + */ + async _getDictionaryDeinflections(deinflections, enabledDictionaryMap, matchType) { + /** @type {import('translation-internal').DatabaseDeinflection[]} */ + const dictionaryDeinflections = []; + for (const deinflection of deinflections) { + const {originalText, transformedText, inflectionRuleChainCandidates: algorithmChains, databaseEntries} = deinflection; + for (const entry of databaseEntries) { + const {dictionary, definitions} = entry; + const entryDictionary = enabledDictionaryMap.get(dictionary); + const useDeinflections = entryDictionary?.useDeinflections ?? true; + if (!useDeinflections) { continue; } + for (const definition of definitions) { + if (Array.isArray(definition)) { + const [formOf, inflectionRules] = definition; + if (!formOf) { continue; } + + const inflectionRuleChainCandidates = algorithmChains.map(({inflectionRules: algInflections}) => { + return { + source: /** @type {import('dictionary').InflectionSource} */ (algInflections.length === 0 ? 'dictionary' : 'both'), + inflectionRules: [...algInflections, ...inflectionRules] + }; + }); + + const dictionaryDeinflection = this._createDeinflection(originalText, transformedText, formOf, 0, inflectionRuleChainCandidates); + dictionaryDeinflections.push(dictionaryDeinflection); + } + } + } + } + + await this._addEntriesToDeinflections(dictionaryDeinflections, enabledDictionaryMap, matchType); + + return dictionaryDeinflections; + } + + /** + * @param {import('translation-internal').DatabaseDeinflection[]} deinflections + * @param {Map} enabledDictionaryMap + * @param {import('dictionary').TermSourceMatchType} matchType + */ + async _addEntriesToDeinflections(deinflections, enabledDictionaryMap, matchType) { + const uniqueDeinflectionsMap = this._groupDeinflectionsByTerm(deinflections); + const uniqueDeinflectionArrays = [...uniqueDeinflectionsMap.values()]; + const uniqueDeinflectionTerms = [...uniqueDeinflectionsMap.keys()]; + + const databaseEntries = await this._database.findTermsBulk(uniqueDeinflectionTerms, enabledDictionaryMap, matchType); + this._matchEntriesToDeinflections(databaseEntries, uniqueDeinflectionArrays, enabledDictionaryMap); + } + + /** + * @param {import('translation-internal').DatabaseDeinflection[]} deinflections + * @returns {Map} + */ + _groupDeinflectionsByTerm(deinflections) { + const result = new Map(); for (const deinflection of deinflections) { - const term = deinflection.deinflectedText; - let deinflectionArray = uniqueDeinflectionsMap.get(term); + const {deinflectedText} = deinflection; + let deinflectionArray = result.get(deinflectedText); if (typeof deinflectionArray === 'undefined') { deinflectionArray = []; - uniqueDeinflectionTerms.push(term); - uniqueDeinflectionArrays.push(deinflectionArray); - uniqueDeinflectionsMap.set(term, deinflectionArray); + result.set(deinflectedText, deinflectionArray); } deinflectionArray.push(deinflection); } + return result; + } - const {matchType} = options; - const databaseEntries = await this._database.findTermsBulk(uniqueDeinflectionTerms, enabledDictionaryMap, matchType); - + /** + * @param {import('dictionary-database').TermEntry[]} databaseEntries + * @param {import('translation-internal').DatabaseDeinflection[][]} uniqueDeinflectionArrays + * @param {Map} enabledDictionaryMap + */ + _matchEntriesToDeinflections(databaseEntries, uniqueDeinflectionArrays, enabledDictionaryMap) { for (const databaseEntry of databaseEntries) { + const entryDictionary = /** @type {import('translation').FindTermDictionary} */ (enabledDictionaryMap.get(databaseEntry.dictionary)); + const {partsOfSpeechFilter} = entryDictionary; + const definitionRules = Deinflector.rulesToRuleFlags(databaseEntry.rules); for (const deinflection of uniqueDeinflectionArrays[databaseEntry.index]) { const deinflectionRules = deinflection.rules; - if (deinflectionRules === 0 || (definitionRules & deinflectionRules) !== 0) { + if (!partsOfSpeechFilter || Deinflector.rulesMatch(deinflectionRules, definitionRules)) { deinflection.databaseEntries.push(databaseEntry); } } } - - return deinflections; } // Deinflections and text transformations @@ -288,7 +424,7 @@ export class Translator { * @param {import('translation').FindTermsOptions} options * @returns {import('translation-internal').DatabaseDeinflection[]} */ - _getAllDeinflections(text, options) { + _getAlgorithmDeinflections(text, options) { /** @type {import('translation-internal').TextDeinflectionOptionsArrays} */ const textOptionVariantArray = [ this._getTextReplacementsVariants(options), @@ -339,7 +475,12 @@ export class Translator { used.add(source); const rawSource = sourceMap.source.substring(0, sourceMap.getSourceLength(i)); for (const {term, rules, reasons} of /** @type {Deinflector} */ (this._deinflector).deinflect(source)) { - deinflections.push(this._createDeinflection(rawSource, source, term, rules, reasons)); + /** @type {import('dictionary').InflectionRuleChainCandidate} */ + const inflectionRuleChainCandidate = { + source: 'algorithm', + inflectionRules: reasons + }; + deinflections.push(this._createDeinflection(rawSource, source, term, rules, [inflectionRuleChainCandidate])); } } } @@ -432,11 +573,11 @@ export class Translator { * @param {string} transformedText * @param {string} deinflectedText * @param {import('translation-internal').DeinflectionRuleFlags} rules - * @param {string[]} reasons + * @param {import('dictionary').InflectionRuleChainCandidate[]} inflectionRuleChainCandidates * @returns {import('translation-internal').DatabaseDeinflection} */ - _createDeinflection(originalText, transformedText, deinflectedText, rules, reasons) { - return {originalText, transformedText, deinflectedText, rules, reasons, databaseEntries: []}; + _createDeinflection(originalText, transformedText, deinflectedText, rules, inflectionRuleChainCandidates) { + return {originalText, transformedText, deinflectedText, rules, inflectionRuleChainCandidates, databaseEntries: []}; } // Term dictionary entry grouping @@ -594,8 +735,8 @@ export class Translator { _groupDictionaryEntriesByHeadword(dictionaryEntries, tagAggregator) { const groups = new Map(); for (const dictionaryEntry of dictionaryEntries) { - const {inflections, headwords: [{term, reading}]} = dictionaryEntry; - const key = this._createMapKey([term, reading, ...inflections]); + const {inflectionRuleChainCandidates, headwords: [{term, reading}]} = dictionaryEntry; + const key = this._createMapKey([term, reading, ...inflectionRuleChainCandidates]); let groupDictionaryEntries = groups.get(key); if (typeof groupDictionaryEntries === 'undefined') { groupDictionaryEntries = []; @@ -1367,7 +1508,7 @@ export class Translator { * @param {number[]} sequences * @param {boolean} isPrimary * @param {import('dictionary').Tag[]} tags - * @param {import('dictionary-data').TermGlossary[]} entries + * @param {import('dictionary-data').TermGlossaryContent[]} entries * @returns {import('dictionary').TermDefinition} */ _createTermDefinition(index, headwordIndices, dictionary, dictionaryIndex, dictionaryPriority, id, score, sequences, isPrimary, tags, entries) { @@ -1418,7 +1559,7 @@ export class Translator { /** * @param {boolean} isPrimary - * @param {string[]} inflections + * @param {import('dictionary').InflectionRuleChainCandidate[]} inflectionRuleChainCandidates * @param {number} score * @param {number} dictionaryIndex * @param {number} dictionaryPriority @@ -1428,11 +1569,11 @@ export class Translator { * @param {import('dictionary').TermDefinition[]} definitions * @returns {import('dictionary').TermDictionaryEntry} */ - _createTermDictionaryEntry(isPrimary, inflections, score, dictionaryIndex, dictionaryPriority, sourceTermExactMatchCount, maxTransformedTextLength, headwords, definitions) { + _createTermDictionaryEntry(isPrimary, inflectionRuleChainCandidates, score, dictionaryIndex, dictionaryPriority, sourceTermExactMatchCount, maxTransformedTextLength, headwords, definitions) { return { type: 'term', isPrimary, - inflections, + inflectionRuleChainCandidates, score, frequencyOrder: 0, dictionaryIndex, @@ -1451,14 +1592,29 @@ export class Translator { * @param {string} originalText * @param {string} transformedText * @param {string} deinflectedText - * @param {string[]} reasons + * @param {import('dictionary').InflectionRuleChainCandidate[]} inflectionRuleChainCandidates * @param {boolean} isPrimary * @param {Map} enabledDictionaryMap * @param {TranslatorTagAggregator} tagAggregator * @returns {import('dictionary').TermDictionaryEntry} */ - _createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, reasons, isPrimary, enabledDictionaryMap, tagAggregator) { - const {matchType, matchSource, term, reading: rawReading, definitionTags, termTags, definitions, score, dictionary, id, sequence: rawSequence, rules} = databaseEntry; + _createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, inflectionRuleChainCandidates, isPrimary, enabledDictionaryMap, tagAggregator) { + const { + matchType, + matchSource, + term, + reading: rawReading, + definitionTags, + termTags, + definitions, + score, + dictionary, + id, + sequence: rawSequence, + rules + } = databaseEntry; + // cast is safe because getDeinflections filters out deinflection definitions + const contentDefinitions = /** @type {import('dictionary-data').TermGlossaryContent[]} */ (definitions); const reading = (rawReading.length > 0 ? rawReading : term); const {index: dictionaryIndex, priority: dictionaryPriority} = this._getDictionaryOrder(dictionary, enabledDictionaryMap); const sourceTermExactMatchCount = (isPrimary && deinflectedText === term ? 1 : 0); @@ -1476,14 +1632,14 @@ export class Translator { return this._createTermDictionaryEntry( isPrimary, - reasons, + inflectionRuleChainCandidates, score, dictionaryIndex, dictionaryPriority, sourceTermExactMatchCount, maxTransformedTextLength, [this._createTermHeadword(0, term, reading, [source], headwordTagGroups, rules)], - [this._createTermDefinition(0, [0], dictionary, dictionaryIndex, dictionaryPriority, id, score, [sequence], isPrimary, definitionTagGroups, definitions)] + [this._createTermDefinition(0, [0], dictionary, dictionaryIndex, dictionaryPriority, id, score, [sequence], isPrimary, definitionTagGroups, contentDefinitions)] ); } @@ -1527,7 +1683,7 @@ export class Translator { if (dictionaryEntry.isPrimary) { isPrimary = true; maxTransformedTextLength = Math.max(maxTransformedTextLength, dictionaryEntry.maxTransformedTextLength); - const dictionaryEntryInflections = dictionaryEntry.inflections; + const dictionaryEntryInflections = dictionaryEntry.inflectionRuleChainCandidates; if (inflections === null || dictionaryEntryInflections.length < inflections.length) { inflections = dictionaryEntryInflections; } @@ -1739,7 +1895,7 @@ export class Translator { if (i !== 0) { return i; } // Sort by the number of inflection reasons - i = v1.inflections.length - v2.inflections.length; + i = v1.inflectionRuleChainCandidates.length - v2.inflectionRuleChainCandidates.length; if (i !== 0) { return i; } // Sort by how many terms exactly match the source (e.g. for exact kana prioritization) diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js index 4e602f8c28..3a3b21d0b7 100644 --- a/ext/js/media/audio-downloader.js +++ b/ext/js/media/audio-downloader.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2017-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/media/audio-system.js b/ext/js/media/audio-system.js index c311b96cde..3d861d356a 100644 --- a/ext/js/media/audio-system.js +++ b/ext/js/media/audio-system.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventDispatcher} from '../core.js'; +import {EventDispatcher} from '../core/event-dispatcher.js'; import {TextToSpeechAudio} from './text-to-speech-audio.js'; /** diff --git a/ext/js/media/media-util.js b/ext/js/media/media-util.js index 1d70acd3d1..1f9f2d0bae 100644 --- a/ext/js/media/media-util.js +++ b/ext/js/media/media-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/media/text-to-speech-audio.js b/ext/js/media/text-to-speech-audio.js index cd1205e525..6a8a741f6e 100644 --- a/ext/js/media/text-to-speech-audio.js +++ b/ext/js/media/text-to-speech-audio.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/action-popup-main.js b/ext/js/pages/action-popup-main.js index 9f72cc82d1..b978d9899b 100644 --- a/ext/js/pages/action-popup-main.js +++ b/ext/js/pages/action-popup-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2017-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/common/extension-content-controller.js b/ext/js/pages/common/extension-content-controller.js index 1c3f9c7417..5b968d0c7e 100644 --- a/ext/js/pages/common/extension-content-controller.js +++ b/ext/js/pages/common/extension-content-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/generic-page-main.js b/ext/js/pages/generic-page-main.js index 4bb4c7aceb..3d5356ba72 100644 --- a/ext/js/pages/generic-page-main.js +++ b/ext/js/pages/generic-page-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/info-main.js b/ext/js/pages/info-main.js index 593b746072..dd55ab4b04 100644 --- a/ext/js/pages/info-main.js +++ b/ext/js/pages/info-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {log, promiseTimeout} from '../core.js'; +import {log} from '../core/logger.js'; +import {promiseTimeout} from '../core/utilities.js'; import {DocumentFocusController} from '../dom/document-focus-controller.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/pages/permissions-main.js b/ext/js/pages/permissions-main.js index e4ac3f3d3b..381356898a 100644 --- a/ext/js/pages/permissions-main.js +++ b/ext/js/pages/permissions-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {log, promiseTimeout} from '../core.js'; +import {log} from '../core/logger.js'; +import {promiseTimeout} from '../core/utilities.js'; import {DocumentFocusController} from '../dom/document-focus-controller.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/pages/settings/anki-controller.js b/ext/js/pages/settings/anki-controller.js index d64034a5ff..f6e19e149c 100644 --- a/ext/js/pages/settings/anki-controller.js +++ b/ext/js/pages/settings/anki-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -17,8 +17,10 @@ */ import {AnkiConnect} from '../../comm/anki-connect.js'; -import {EventListenerCollection, log} from '../../core.js'; +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 {querySelectorNotNull} from '../../dom/query-selector.js'; import {SelectorObserver} from '../../dom/selector-observer.js'; @@ -409,7 +411,7 @@ export class AnkiController { this._sortStringArray(result); return [result, null]; } catch (e) { - return [[], e instanceof Error ? e : new Error(`${e}`)]; + return [[], toError(e)]; } } @@ -422,7 +424,7 @@ export class AnkiController { this._sortStringArray(result); return [result, null]; } catch (e) { - return [[], e instanceof Error ? e : new Error(`${e}`)]; + return [[], toError(e)]; } } @@ -487,7 +489,7 @@ export class AnkiController { try { await this._testAnkiNoteViewer(mode); } catch (e) { - this._setAnkiNoteViewerStatus(true, e instanceof Error ? e : new Error(`${e}`)); + this._setAnkiNoteViewerStatus(true, toError(e)); return; } this._setAnkiNoteViewerStatus(true, null); diff --git a/ext/js/pages/settings/anki-templates-controller.js b/ext/js/pages/settings/anki-templates-controller.js index 56e992b05a..910e99acf0 100644 --- a/ext/js/pages/settings/anki-templates-controller.js +++ b/ext/js/pages/settings/anki-templates-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -17,6 +17,7 @@ */ import {ExtensionError} from '../../core/extension-error.js'; +import {toError} from '../../core/to-error.js'; import {AnkiNoteBuilder} from '../../data/anki-note-builder.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {JapaneseUtil} from '../../language/sandbox/japanese-util.js'; @@ -266,7 +267,7 @@ export class AnkiTemplatesController { allErrors.push(...errors); } } catch (e) { - allErrors.push(e instanceof Error ? e : new Error(`${e}`)); + allErrors.push(toError(e)); } /** diff --git a/ext/js/pages/settings/audio-controller.js b/ext/js/pages/settings/audio-controller.js index 2b46455f38..9633c4b3da 100644 --- a/ext/js/pages/settings/audio-controller.js +++ b/ext/js/pages/settings/audio-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventDispatcher, EventListenerCollection} from '../../core.js'; +import {EventDispatcher} from '../../core/event-dispatcher.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {AudioSystem} from '../../media/audio-system.js'; diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 2ae52925d0..f2eccd1e0e 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -17,8 +17,10 @@ */ import {Dexie} from '../../../lib/dexie.js'; -import {isObject, log} from '../../core.js'; import {parseJson} from '../../core/json.js'; +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 {querySelectorNotNull} from '../../dom/query-selector.js'; @@ -498,7 +500,7 @@ export class BackupController { try { await this._importSettingsFile(file); } catch (error) { - this._showSettingsImportError(error instanceof Error ? error : new Error(`${error}`)); + this._showSettingsImportError(toError(error)); } } diff --git a/ext/js/pages/settings/collapsible-dictionary-controller.js b/ext/js/pages/settings/collapsible-dictionary-controller.js index 341522cf59..e6930049c1 100644 --- a/ext/js/pages/settings/collapsible-dictionary-controller.js +++ b/ext/js/pages/settings/collapsible-dictionary-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../../core.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {yomitan} from '../../yomitan.js'; diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index 0132fe9e18..10dfdcdc5f 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventListenerCollection, log} from '../../core.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; +import {log} from '../../core/logger.js'; import {DictionaryWorker} from '../../dictionary/dictionary-worker.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {yomitan} from '../../yomitan.js'; @@ -164,7 +165,7 @@ class DictionaryEntry { /** */ _showDetails() { - const {title, revision, version, prefixWildcardsSupported} = this._dictionaryInfo; + const {title, revision, version, counts, prefixWildcardsSupported} = this._dictionaryInfo; const modal = this._dictionaryController.modalController.getModal('dictionary-details'); if (modal === null) { return; } @@ -181,12 +182,26 @@ class DictionaryEntry { const wildcardSupportedElement = querySelectorNotNull(modal.node, '.dictionary-prefix-wildcard-searches-supported'); /** @type {HTMLElement} */ const detailsTableElement = querySelectorNotNull(modal.node, '.dictionary-details-table'); + /** @type {HTMLElement} */ + const partsOfSpeechFilterSetting = querySelectorNotNull(modal.node, '.dictionary-parts-of-speech-filter-setting'); + /** @type {HTMLElement} */ + const partsOfSpeechFilterToggle = querySelectorNotNull(partsOfSpeechFilterSetting, '.dictionary-parts-of-speech-filter-toggle'); + /** @type {HTMLElement} */ + const useDeinflectionsSetting = querySelectorNotNull(modal.node, '.dictionary-use-deinflections-setting'); + /** @type {HTMLElement} */ + const useDeinflectionsToggle = querySelectorNotNull(useDeinflectionsSetting, '.dictionary-use-deinflections-toggle'); titleElement.textContent = title; versionElement.textContent = `rev.${revision}`; outdateElement.hidden = (version >= 3); countsElement.textContent = this._counts !== null ? JSON.stringify(this._counts, null, 4) : ''; wildcardSupportedElement.checked = prefixWildcardsSupported; + partsOfSpeechFilterSetting.hidden = !counts.terms.total; + partsOfSpeechFilterToggle.dataset.setting = `dictionaries[${this._index}].partsOfSpeechFilter`; + + useDeinflectionsSetting.hidden = !counts.terms.total; + useDeinflectionsToggle.dataset.setting = `dictionaries[${this._index}].useDeinflections`; + this._setupDetails(detailsTableElement); modal.setVisible(true); @@ -513,7 +528,9 @@ export class DictionaryController { priority: 0, enabled, allowSecondarySearches: false, - definitionsCollapsible: 'not-collapsible' + definitionsCollapsible: 'not-collapsible', + partsOfSpeechFilter: true, + useDeinflections: true }; } @@ -716,9 +733,9 @@ export class DictionaryController { /** */ _onDictionaryMoveButtonClick() { const modal = /** @type {import('./modal.js').Modal} */ (this._modalController.getModal('dictionary-move-location')); - const {index} = modal.node.dataset; - if (typeof index !== 'number') { return; } + const index = modal.node.dataset.index ?? ''; const indexNumber = Number.parseInt(index, 10); + if (Number.isNaN(indexNumber)) { return; } /** @type {HTMLInputElement} */ const targetStringInput = querySelectorNotNull(document, '#dictionary-move-location'); diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index eadfcb910d..183c0ccdd7 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,8 +16,9 @@ * along with this program. If not, see . */ -import {log} from '../../core.js'; import {ExtensionError} from '../../core/extension-error.js'; +import {log} from '../../core/logger.js'; +import {toError} from '../../core/to-error.js'; import {DictionaryWorker} from '../../dictionary/dictionary-worker.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {yomitan} from '../../yomitan.js'; @@ -126,7 +127,7 @@ export class DictionaryImportController { this._showErrors(errors); } } catch (error) { - this._showErrors([error instanceof Error ? error : new Error(`${error}`)]); + this._showErrors([toError(error)]); } finally { prevention.end(); this._setModifying(false); @@ -200,7 +201,7 @@ export class DictionaryImportController { await this._importDictionary(files[i], importDetails, onProgress); } } catch (err) { - this._showErrors([err instanceof Error ? err : new Error(`${err}`)]); + this._showErrors([toError(err)]); } finally { prevention.end(); for (const progress of progressContainers) { progress.hidden = true; } diff --git a/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js b/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js index e3d84ac228..61330bb843 100644 --- a/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js +++ b/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventListenerCollection, isObject} from '../../core.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; +import {isObject} from '../../core/utilities.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {HotkeyUtil} from '../../input/hotkey-util.js'; import {yomitan} from '../../yomitan.js'; diff --git a/ext/js/pages/settings/generic-setting-controller.js b/ext/js/pages/settings/generic-setting-controller.js index 8268f563c6..12db2ab7a3 100644 --- a/ext/js/pages/settings/generic-setting-controller.js +++ b/ext/js/pages/settings/generic-setting-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/settings/keyboard-mouse-input-field.js b/ext/js/pages/settings/keyboard-mouse-input-field.js index 0628d06562..310cbb1992 100644 --- a/ext/js/pages/settings/keyboard-mouse-input-field.js +++ b/ext/js/pages/settings/keyboard-mouse-input-field.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventDispatcher, EventListenerCollection} from '../../core.js'; +import {EventDispatcher} from '../../core/event-dispatcher.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {DocumentUtil} from '../../dom/document-util.js'; import {HotkeyUtil} from '../../input/hotkey-util.js'; diff --git a/ext/js/pages/settings/keyboard-shortcuts-controller.js b/ext/js/pages/settings/keyboard-shortcuts-controller.js index b45c656a86..396b0cc2a7 100644 --- a/ext/js/pages/settings/keyboard-shortcuts-controller.js +++ b/ext/js/pages/settings/keyboard-shortcuts-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../../core.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {DocumentUtil} from '../../dom/document-util.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {ObjectPropertyAccessor} from '../../general/object-property-accessor.js'; diff --git a/ext/js/pages/settings/mecab-controller.js b/ext/js/pages/settings/mecab-controller.js index 9c55c9a0cc..dec2be682a 100644 --- a/ext/js/pages/settings/mecab-controller.js +++ b/ext/js/pages/settings/mecab-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import {toError} from '../../core/to-error.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {yomitan} from '../../yomitan.js'; @@ -57,7 +58,7 @@ export class MecabController { await yomitan.api.testMecab(); this._setStatus('Connection was successful', false); } catch (e) { - this._setStatus(e instanceof Error ? e.message : `${e}`, true); + this._setStatus(toError(e).message, true); } finally { this._testActive = false; /** @type {HTMLButtonElement} */ (this._testButton).disabled = false; diff --git a/ext/js/pages/settings/modal-controller.js b/ext/js/pages/settings/modal-controller.js index 852bdcc50c..36e8505542 100644 --- a/ext/js/pages/settings/modal-controller.js +++ b/ext/js/pages/settings/modal-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/settings/modal.js b/ext/js/pages/settings/modal.js index 17a4605d67..7e20dcb471 100644 --- a/ext/js/pages/settings/modal.js +++ b/ext/js/pages/settings/modal.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/settings/nested-popups-controller.js b/ext/js/pages/settings/nested-popups-controller.js index 4f0aa7618a..ccdc3c9c3f 100644 --- a/ext/js/pages/settings/nested-popups-controller.js +++ b/ext/js/pages/settings/nested-popups-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/settings/permissions-origin-controller.js b/ext/js/pages/settings/permissions-origin-controller.js index 3a9db60243..a0f23af62c 100644 --- a/ext/js/pages/settings/permissions-origin-controller.js +++ b/ext/js/pages/settings/permissions-origin-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../../core.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; +import {toError} from '../../core/to-error.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; export class PermissionsOriginController { @@ -155,7 +156,7 @@ export class PermissionsOriginController { } catch (e) { const errorContainer = /** @type {HTMLElement} */ (this._errorContainer); errorContainer.hidden = false; - errorContainer.textContent = e instanceof Error ? e.message : `${e}`; + errorContainer.textContent = toError(e).message; } if (!added) { return false; } await this._updatePermissions(); diff --git a/ext/js/pages/settings/permissions-toggle-controller.js b/ext/js/pages/settings/permissions-toggle-controller.js index 055ce1f438..c775aa128e 100644 --- a/ext/js/pages/settings/permissions-toggle-controller.js +++ b/ext/js/pages/settings/permissions-toggle-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/settings/persistent-storage-controller.js b/ext/js/pages/settings/persistent-storage-controller.js index 70c9a177a3..baffa969f9 100644 --- a/ext/js/pages/settings/persistent-storage-controller.js +++ b/ext/js/pages/settings/persistent-storage-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {isObject} from '../../core.js'; +import {isObject} from '../../core/utilities.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {yomitan} from '../../yomitan.js'; diff --git a/ext/js/pages/settings/popup-preview-controller.js b/ext/js/pages/settings/popup-preview-controller.js index d8bc985071..5194410c80 100644 --- a/ext/js/pages/settings/popup-preview-controller.js +++ b/ext/js/pages/settings/popup-preview-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/settings/popup-preview-frame-main.js b/ext/js/pages/settings/popup-preview-frame-main.js index 7b42e11a96..e3d7d0ec30 100644 --- a/ext/js/pages/settings/popup-preview-frame-main.js +++ b/ext/js/pages/settings/popup-preview-frame-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -17,7 +17,7 @@ */ import {PopupFactory} from '../../app/popup-factory.js'; -import {log} from '../../core.js'; +import {log} from '../../core/logger.js'; import {HotkeyHandler} from '../../input/hotkey-handler.js'; import {yomitan} from '../../yomitan.js'; import {PopupPreviewFrame} from './popup-preview-frame.js'; diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js index 609710ba76..7a89964186 100644 --- a/ext/js/pages/settings/popup-preview-frame.js +++ b/ext/js/pages/settings/popup-preview-frame.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/settings/popup-window-controller.js b/ext/js/pages/settings/popup-window-controller.js index 0d56dc58b1..1b767f77e3 100644 --- a/ext/js/pages/settings/popup-window-controller.js +++ b/ext/js/pages/settings/popup-window-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/settings/profile-conditions-ui.js b/ext/js/pages/settings/profile-conditions-ui.js index 22e47a9be9..d07751fb4b 100644 --- a/ext/js/pages/settings/profile-conditions-ui.js +++ b/ext/js/pages/settings/profile-conditions-ui.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventDispatcher, EventListenerCollection} from '../../core.js'; +import {EventDispatcher} from '../../core/event-dispatcher.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {DocumentUtil} from '../../dom/document-util.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {KeyboardMouseInputField} from './keyboard-mouse-input-field.js'; diff --git a/ext/js/pages/settings/profile-controller.js b/ext/js/pages/settings/profile-controller.js index 54a410588e..73926a692b 100644 --- a/ext/js/pages/settings/profile-controller.js +++ b/ext/js/pages/settings/profile-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {clone, EventListenerCollection} from '../../core.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; +import {clone} from '../../core/utilities.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {yomitan} from '../../yomitan.js'; import {ProfileConditionsUI} from './profile-conditions-ui.js'; diff --git a/ext/js/pages/settings/recommended-permissions-controller.js b/ext/js/pages/settings/recommended-permissions-controller.js index a870de502f..84a4ef108f 100644 --- a/ext/js/pages/settings/recommended-permissions-controller.js +++ b/ext/js/pages/settings/recommended-permissions-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,8 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../../core.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; +import {toError} from '../../core/to-error.js'; export class RecommendedPermissionsController { /** @@ -92,7 +93,7 @@ export class RecommendedPermissionsController { } catch (e) { if (this._errorContainer !== null) { this._errorContainer.hidden = false; - this._errorContainer.textContent = e instanceof Error ? e.message : `${e}`; + this._errorContainer.textContent = toError(e).message; } } if (!added) { return false; } diff --git a/ext/js/pages/settings/scan-inputs-controller.js b/ext/js/pages/settings/scan-inputs-controller.js index 4854c28f50..2dfa3de33a 100644 --- a/ext/js/pages/settings/scan-inputs-controller.js +++ b/ext/js/pages/settings/scan-inputs-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../../core.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {DocumentUtil} from '../../dom/document-util.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {yomitan} from '../../yomitan.js'; diff --git a/ext/js/pages/settings/scan-inputs-simple-controller.js b/ext/js/pages/settings/scan-inputs-simple-controller.js index f025559591..e4f34c99d1 100644 --- a/ext/js/pages/settings/scan-inputs-simple-controller.js +++ b/ext/js/pages/settings/scan-inputs-simple-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/settings/secondary-search-dictionary-controller.js b/ext/js/pages/settings/secondary-search-dictionary-controller.js index 7f0882b87e..592f5eebf3 100644 --- a/ext/js/pages/settings/secondary-search-dictionary-controller.js +++ b/ext/js/pages/settings/secondary-search-dictionary-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../../core.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {yomitan} from '../../yomitan.js'; diff --git a/ext/js/pages/settings/sentence-termination-characters-controller.js b/ext/js/pages/settings/sentence-termination-characters-controller.js index f779394372..c393aaa138 100644 --- a/ext/js/pages/settings/sentence-termination-characters-controller.js +++ b/ext/js/pages/settings/sentence-termination-characters-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../../core.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; export class SentenceTerminationCharactersController { diff --git a/ext/js/pages/settings/settings-controller.js b/ext/js/pages/settings/settings-controller.js index 52b777a360..25f5e8adcf 100644 --- a/ext/js/pages/settings/settings-controller.js +++ b/ext/js/pages/settings/settings-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,9 @@ * along with this program. If not, see . */ -import {EventDispatcher, EventListenerCollection, generateId, isObject} from '../../core.js'; +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 {HtmlTemplateCollection} from '../../dom/html-template-collection.js'; diff --git a/ext/js/pages/settings/settings-display-controller.js b/ext/js/pages/settings/settings-display-controller.js index e575a1cb70..0a729d9621 100644 --- a/ext/js/pages/settings/settings-display-controller.js +++ b/ext/js/pages/settings/settings-display-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/settings/settings-main.js b/ext/js/pages/settings/settings-main.js index 5f70890b33..7e458043a6 100644 --- a/ext/js/pages/settings/settings-main.js +++ b/ext/js/pages/settings/settings-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {log} from '../../core.js'; +import {log} from '../../core/logger.js'; import {DocumentFocusController} from '../../dom/document-focus-controller.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {yomitan} from '../../yomitan.js'; diff --git a/ext/js/pages/settings/sort-frequency-dictionary-controller.js b/ext/js/pages/settings/sort-frequency-dictionary-controller.js index 3fdd66c778..f5b230f02c 100644 --- a/ext/js/pages/settings/sort-frequency-dictionary-controller.js +++ b/ext/js/pages/settings/sort-frequency-dictionary-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/settings/status-footer.js b/ext/js/pages/settings/status-footer.js index 4830dbd54f..786e1d5581 100644 --- a/ext/js/pages/settings/status-footer.js +++ b/ext/js/pages/settings/status-footer.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/pages/settings/storage-controller.js b/ext/js/pages/settings/storage-controller.js index 16e03786d3..6be1fe2498 100644 --- a/ext/js/pages/settings/storage-controller.js +++ b/ext/js/pages/settings/storage-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -39,6 +39,8 @@ export class StorageController { /** @type {?NodeListOf} */ this._storageUseFiniteNodes = null; /** @type {?NodeListOf} */ + this._storageUseExhaustWarnNodes = null; + /** @type {?NodeListOf} */ this._storageUseInfiniteNodes = null; /** @type {?NodeListOf} */ this._storageUseValidNodes = null; @@ -51,6 +53,7 @@ export class StorageController { this._storageUsageNodes = /** @type {NodeListOf} */ (document.querySelectorAll('.storage-usage')); this._storageQuotaNodes = /** @type {NodeListOf} */ (document.querySelectorAll('.storage-quota')); this._storageUseFiniteNodes = /** @type {NodeListOf} */ (document.querySelectorAll('.storage-use-finite')); + this._storageUseExhaustWarnNodes = /** @type {NodeListOf} */ (document.querySelectorAll('.storage-exhaustion-alert')); this._storageUseInfiniteNodes = /** @type {NodeListOf} */ (document.querySelectorAll('.storage-use-infinite')); this._storageUseValidNodes = /** @type {NodeListOf} */ (document.querySelectorAll('.storage-use-valid')); this._storageUseInvalidNodes = /** @type {NodeListOf} */ (document.querySelectorAll('.storage-use-invalid')); @@ -84,13 +87,19 @@ export class StorageController { const estimate = await this._storageEstimate(); const valid = (estimate !== null); + let storageIsLow = false; // Firefox reports usage as 0 when persistent storage is enabled. const finite = valid && ((typeof estimate.usage === 'number' && estimate.usage > 0) || !(await this._persistentStorageController.isStoragePeristent())); if (finite) { let {usage, quota} = estimate; + if (typeof usage !== 'number') { usage = 0; } - if (typeof quota !== 'number') { quota = 0; } + if (typeof quota !== 'number') { + quota = 0; + } else { + storageIsLow = quota <= (3 * 1000000000); + } const usageString = this._bytesToLabeledString(usage); const quotaString = this._bytesToLabeledString(quota); for (const node of /** @type {NodeListOf} */ (this._storageUsageNodes)) { @@ -105,6 +114,7 @@ export class StorageController { this._setElementsVisible(this._storageUseInfiniteNodes, valid && !finite); this._setElementsVisible(this._storageUseValidNodes, valid); this._setElementsVisible(this._storageUseInvalidNodes, !valid); + this._setElementsVisible(this._storageUseExhaustWarnNodes, storageIsLow); } finally { this._isUpdating = false; } diff --git a/ext/js/pages/settings/translation-text-replacements-controller.js b/ext/js/pages/settings/translation-text-replacements-controller.js index 9f3fda009e..98a1602687 100644 --- a/ext/js/pages/settings/translation-text-replacements-controller.js +++ b/ext/js/pages/settings/translation-text-replacements-controller.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../../core.js'; +import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; export class TranslationTextReplacementsController { diff --git a/ext/js/pages/welcome-main.js b/ext/js/pages/welcome-main.js index fbb60fe81a..35472ec234 100644 --- a/ext/js/pages/welcome-main.js +++ b/ext/js/pages/welcome-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2019-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {log} from '../core.js'; +import {log} from '../core/logger.js'; import {DocumentFocusController} from '../dom/document-focus-controller.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; import {yomitan} from '../yomitan.js'; 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 4989ced37f..932b6ab715 100644 --- a/ext/js/templates/sandbox/anki-template-renderer-content-manager.js +++ b/ext/js/templates/sandbox/anki-template-renderer-content-manager.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js index 158102393c..ef2c161062 100644 --- a/ext/js/templates/sandbox/anki-template-renderer.js +++ b/ext/js/templates/sandbox/anki-template-renderer.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -235,18 +235,20 @@ export class AnkiTemplateRenderer { return this._stringToMultiLineHtml(this._computeValueString(options, context)); } - /** @type {import('template-renderer').HelperFunction} */ + /** + * Usage: + * ```{{#regexReplace regex string [flags] [content]...}}content{{/regexReplace}}``` + * - regex: regular expression string + * - string: string to replace + * - flags: optional flags for regular expression. + * e.g. "i" for case-insensitive, "g" for replace all + * @type {import('template-renderer').HelperFunction} + */ _regexReplace(args, context, options) { - // Usage: - // {{#regexReplace regex string [flags] [content]...}}content{{/regexReplace}} - // regex: regular expression string - // string: string to replace - // flags: optional flags for regular expression - // e.g. "i" for case-insensitive, "g" for replace all const argCount = args.length; let value = this._computeValueString(options, context); if (argCount > 3) { - value = `${args.slice(3, -1).join('')}${value}`; + value = `${args.slice(3).join('')}${value}`; } if (argCount > 1) { try { @@ -262,17 +264,19 @@ export class AnkiTemplateRenderer { return value; } - /** @type {import('template-renderer').HelperFunction} */ + /** + * Usage: + * {{#regexMatch regex [flags] [content]...}}content{{/regexMatch}} + * - regex: regular expression string + * - flags: optional flags for regular expression + * e.g. "i" for case-insensitive, "g" for match all + * @type {import('template-renderer').HelperFunction} + */ _regexMatch(args, context, options) { - // Usage: - // {{#regexMatch regex [flags] [content]...}}content{{/regexMatch}} - // regex: regular expression string - // flags: optional flags for regular expression - // e.g. "i" for case-insensitive, "g" for match all const argCount = args.length; let value = this._computeValueString(options, context); if (argCount > 2) { - value = `${args.slice(2, -1).join('')}${value}`; + value = `${args.slice(2).join('')}${value}`; } if (argCount > 0) { try { @@ -671,7 +675,7 @@ export class AnkiTemplateRenderer { * @type {import('template-renderer').HelperFunction} */ _formatGlossary(args, _context, options) { - const [dictionary, content] = /** @type {[dictionary: string, content: import('dictionary-data').TermGlossary]} */ (args); + const [dictionary, content] = /** @type {[dictionary: string, content: import('dictionary-data').TermGlossaryContent]} */ (args); const data = options.data.root; if (typeof content === 'string') { return this._stringToMultiLineHtml(this._escape(content)); } if (!(typeof content === 'object' && content !== null)) { return ''; } diff --git a/ext/js/templates/sandbox/template-renderer-frame-api.js b/ext/js/templates/sandbox/template-renderer-frame-api.js index 28303e513c..a0017d7072 100644 --- a/ext/js/templates/sandbox/template-renderer-frame-api.js +++ b/ext/js/templates/sandbox/template-renderer-frame-api.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/templates/sandbox/template-renderer-frame-main.js b/ext/js/templates/sandbox/template-renderer-frame-main.js index f5868aff40..4ab7d2bcbd 100644 --- a/ext/js/templates/sandbox/template-renderer-frame-main.js +++ b/ext/js/templates/sandbox/template-renderer-frame-main.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/templates/sandbox/template-renderer-media-provider.js b/ext/js/templates/sandbox/template-renderer-media-provider.js index 0a7f5d9b15..29dd29aec5 100644 --- a/ext/js/templates/sandbox/template-renderer-media-provider.js +++ b/ext/js/templates/sandbox/template-renderer-media-provider.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/templates/sandbox/template-renderer.js b/ext/js/templates/sandbox/template-renderer.js index 239240b6bb..90fb63f306 100644 --- a/ext/js/templates/sandbox/template-renderer.js +++ b/ext/js/templates/sandbox/template-renderer.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2016-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/templates/template-patcher.js b/ext/js/templates/template-patcher.js index 33abb08e9e..77f55aea94 100644 --- a/ext/js/templates/template-patcher.js +++ b/ext/js/templates/template-patcher.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/ext/js/templates/template-renderer-proxy.js b/ext/js/templates/template-renderer-proxy.js index 25fe8fb1f5..e4814ec4a5 100644 --- a/ext/js/templates/template-renderer-proxy.js +++ b/ext/js/templates/template-renderer-proxy.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {generateId} from '../core.js'; +import {generateId} from '../core/utilities.js'; import {ExtensionError} from '../core/extension-error.js'; export class TemplateRendererProxy { diff --git a/ext/js/yomitan.js b/ext/js/yomitan.js index 8980c5898d..33afac2742 100644 --- a/ext/js/yomitan.js +++ b/ext/js/yomitan.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -18,9 +18,12 @@ import {API} from './comm/api.js'; import {CrossFrameAPI} from './comm/cross-frame-api.js'; -import {EventDispatcher, deferPromise, log} from './core.js'; import {createApiMap, invokeApiMapHandler} from './core/api-map.js'; +import {EventDispatcher} from './core/event-dispatcher.js'; import {ExtensionError} from './core/extension-error.js'; +import {log} from './core/logger.js'; +import {deferPromise} from './core/utilities.js'; +import {WebExtension} from './extension/web-extension.js'; /** * @returns {boolean} @@ -59,6 +62,9 @@ export class Yomitan extends EventDispatcher { constructor() { super(); + /** @type {WebExtension} */ + this._webExtension = new WebExtension(); + /** @type {string} */ this._extensionName = 'Yomitan'; try { @@ -71,7 +77,7 @@ export class Yomitan extends EventDispatcher { /** @type {?string} */ this._extensionUrlBase = null; try { - this._extensionUrlBase = chrome.runtime.getURL('/'); + this._extensionUrlBase = this._webExtension.getUrl('/'); } catch (e) { // NOP } @@ -83,10 +89,6 @@ export class Yomitan extends EventDispatcher { /** @type {?CrossFrameAPI} */ this._crossFrame = null; /** @type {boolean} */ - this._isExtensionUnloaded = false; - /** @type {boolean} */ - this._isTriggeringExtensionUnloaded = false; - /** @type {boolean} */ this._isReady = false; const {promise, resolve} = /** @type {import('core').DeferredPromiseDetails} */ (deferPromise()); @@ -108,6 +110,11 @@ export class Yomitan extends EventDispatcher { /* eslint-enable no-multi-spaces */ } + /** @type {WebExtension} */ + get webExtension() { + return this._webExtension; + } + /** * Whether the current frame is the background page/service worker or not. * @type {boolean} @@ -117,14 +124,6 @@ export class Yomitan extends EventDispatcher { return /** @type {boolean} */ (this._isBackground); } - /** - * Whether or not the extension is unloaded. - * @type {boolean} - */ - get isExtensionUnloaded() { - return this._isExtensionUnloaded; - } - /** * Gets the API instance for communicating with the backend. * This value will be null on the background page/service worker. @@ -154,9 +153,9 @@ export class Yomitan extends EventDispatcher { chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); if (!isBackground) { - this._api = new API(this); + this._api = new API(this._webExtension); - this.sendMessage({action: 'requestBackendReadySignal'}); + await this._webExtension.sendMessagePromise({action: 'requestBackendReadySignal'}); await this._isBackendReadyPromise; this._crossFrame = new CrossFrameAPI(); @@ -172,7 +171,7 @@ export class Yomitan extends EventDispatcher { */ ready() { this._isReady = true; - this.sendMessage({action: 'applicationReady'}); + this._webExtension.sendMessagePromise({action: 'applicationReady'}); } /** @@ -184,36 +183,6 @@ export class Yomitan extends EventDispatcher { return this._extensionUrlBase !== null && url.startsWith(this._extensionUrlBase); } - // TODO : this function needs type safety - /** - * Runs `chrome.runtime.sendMessage()` with additional exception handling events. - * @param {import('extension').ChromeRuntimeSendMessageArgs} args The arguments to be passed to `chrome.runtime.sendMessage()`. - * @throws {Error} Errors thrown by `chrome.runtime.sendMessage()` are re-thrown. - */ - sendMessage(...args) { - try { - // @ts-expect-error - issue with type conversion, somewhat difficult to resolve in pure JS - chrome.runtime.sendMessage(...args); - } catch (e) { - this.triggerExtensionUnloaded(); - throw e; - } - } - - /** - * Triggers the extensionUnloaded event. - */ - triggerExtensionUnloaded() { - this._isExtensionUnloaded = true; - if (this._isTriggeringExtensionUnloaded) { return; } - try { - this._isTriggeringExtensionUnloaded = true; - this.trigger('extensionUnloaded', {}); - } finally { - this._isTriggeringExtensionUnloaded = false; - } - } - /** */ triggerStorageChanged() { this.trigger('storageChanged', {}); diff --git a/ext/legal.html b/ext/legal.html index 94912c7ea8..bc58ed5a9a 100644 --- a/ext/legal.html +++ b/ext/legal.html @@ -35,7 +35,7 @@

    Yomitan License

    -Copyright (C) 2023  Yomitan Authors
    +Copyright (C) 2023-2024  Yomitan Authors
     Copyright (C) 2016-2022  Yomichan Authors
     
     This program is free software: you can redistribute it and/or modify
    diff --git a/ext/settings.html b/ext/settings.html
    index 8f432eb45f..38cbf6f658 100644
    --- a/ext/settings.html
    +++ b/ext/settings.html
    @@ -140,6 +140,12 @@ 

    Yomitan Settings

    + +
    + +
    diff --git a/ext/sw.js b/ext/sw.js index 981fb17fc3..31acd2175c 100644 --- a/ext/sw.js +++ b/ext/sw.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/ext/welcome.html b/ext/welcome.html index 406398819a..60aae31966 100644 --- a/ext/welcome.html +++ b/ext/welcome.html @@ -202,7 +202,7 @@

    Basic customization

    @@ -366,6 +366,24 @@

    Basic customization

    Hide…

    + +
    diff --git a/package-lock.json b/package-lock.json index ab97d37404..c181804797 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5868,9 +5868,9 @@ "dev": true }, "node_modules/vite": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", - "integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", + "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", "dev": true, "dependencies": { "esbuild": "^0.19.3", @@ -10364,9 +10364,9 @@ "dev": true }, "vite": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", - "integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.12.tgz", + "integrity": "sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==", "dev": true, "requires": { "esbuild": "^0.19.3", diff --git a/playwright.config.js b/playwright.config.js index 6bf645c4d1..4658ceb2df 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/anki-template-renderer.test.js b/test/anki-template-renderer.test.js new file mode 100644 index 0000000000..d8d2a3b5c0 --- /dev/null +++ b/test/anki-template-renderer.test.js @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2023-2024 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import {describe} from 'vitest'; +import {createAnkiTemplateRendererTest} from './fixtures/anki-template-renderer-test.js'; + +const test = await createAnkiTemplateRendererTest(); + +describe('AnkiTemplateRenderer', () => { + /** @type {import('template-renderer').CompositeRenderData} */ + const data = { + marker: 'test', + commonData: { + dictionaryEntry: { + type: 'kanji', + character: 'c', + dictionary: 'dictionary', + onyomi: [], + kunyomi: [], + tags: [], + stats: {}, + definitions: [], + frequencies: [] + }, + resultOutputMode: 'split', + mode: 'test', + glossaryLayoutMode: 'default', + compactTags: false, + context: { + url: 'http://localhost/', + documentTitle: 'documentTitle', + query: 'query', + fullQuery: 'query.full', + sentence: { + text: 'sentence.query.full', + offset: 9 + } + }, + media: void 0 + } + }; + const testCases = [ + { + name: 'regexMatch 1', + template: '{{#regexMatch "test" "gu"}}this is a test of regexMatch{{/regexMatch}}', + result: 'test' + }, + { + name: 'regexMatch 2', + template: '{{regexMatch "test" "gu" "this is a test of regexMatch"}}', + result: 'test' + }, + { + name: 'regexMatch 3', + template: '{{#if (regexMatch "test" "gu" "this is a test of regexMatch")}}true{{else}}false{{/if}}', + result: 'true' + }, + { + name: 'regexReplace 1', + template: '{{#regexReplace "test" "TEST" "gu"}}this is a test of regexReplace{{/regexReplace}}', + result: 'this is a TEST of regexReplace' + }, + { + name: 'regexReplace 2', + template: '{{regexReplace "test" "TEST" "gu" "this is a test of regexReplace"}}', + result: 'this is a TEST of regexReplace' + }, + { + name: 'regexReplace 3', + template: '{{#if (regexReplace "test" "" "gu" "test")}}true{{else}}false{{/if}}', + result: 'false' + } + ]; + describe.each(testCases)('$name', ({template, result: expectedResult}) => { + test('Test', ({expect, ankiTemplateRenderer}) => { + const {result} = ankiTemplateRenderer.templateRenderer.render(template, data, 'ankiNote'); + expect(result).toEqual(expectedResult); + }); + }); +}); diff --git a/test/cache-map.test.js b/test/cache-map.test.js index 5db35cf0c5..72579f2558 100644 --- a/test/cache-map.test.js +++ b/test/cache-map.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/test/core.test.js b/test/core.test.js index 127df78300..0ddcc2d1c9 100644 --- a/test/core.test.js +++ b/test/core.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -17,7 +17,8 @@ */ import {describe, expect, test} from 'vitest'; -import {DynamicProperty, deepEqual} from '../ext/js/core.js'; +import {DynamicProperty} from '../ext/js/core/dynamic-property.js'; +import {deepEqual} from '../ext/js/core/utilities.js'; /** */ function testDynamicProperty() { diff --git a/test/css-json.test.js b/test/css-json.test.js index 588651d280..b64419c31c 100644 --- a/test/css-json.test.js +++ b/test/css-json.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/test/data/anki-note-builder-test-results.json b/test/data/anki-note-builder-test-results.json index 86bffc6a0e..7129d74803 100644 --- a/test/data/anki-note-builder-test-results.json +++ b/test/data/anki-note-builder-test-results.json @@ -2867,5 +2867,41 @@ "url": "url:" } ] + }, + { + "name": "Test dictionary deinflection", + "results": [ + { + "audio": "", + "clipboard-image": "", + "clipboard-text": "", + "cloze-body": "のたもうた", + "cloze-prefix": "cloze-prefix", + "cloze-suffix": "cloze-suffix", + "conjugation": "past", + "dictionary": "Test Dictionary 2", + "document-title": "title", + "expression": "のたまう", + "frequencies": "", + "furigana": "のたまう", + "furigana-plain": "のたまう", + "glossary": "
    (v5, Test Dictionary 2) notamau definition
    ", + "glossary-brief": "
    notamau definition
    ", + "glossary-no-dictionary": "
    (v5) notamau definition
    ", + "part-of-speech": "Godan verb", + "pitch-accents": "No pitch accent data", + "pitch-accent-graphs": "No pitch accent data", + "pitch-accent-positions": "No pitch accent data", + "phonetic-transcriptions": "", + "reading": "のたまう", + "screenshot": "", + "search-query": "fullQuery", + "selection-text": "", + "sentence": "cloze-prefixのたもうたcloze-suffix", + "sentence-furigana": "cloze-prefixのたもうたcloze-suffix", + "tags": "v5", + "url": "url:" + } + ] } ] \ No newline at end of file diff --git a/test/data/database-test-cases.json b/test/data/database-test-cases.json index 02fddd4931..611903dd85 100644 --- a/test/data/database-test-cases.json +++ b/test/data/database-test-cases.json @@ -27,7 +27,7 @@ "ipa": 1 }, "terms": { - "total": 23 + "total": 25 } } }, @@ -36,7 +36,7 @@ { "kanji": 2, "kanjiMeta": 6, - "terms": 23, + "terms": 25, "termMeta": 39, "tagMeta": 15, "media": 6 @@ -45,7 +45,7 @@ "total": { "kanji": 2, "kanjiMeta": 6, - "terms": 23, + "terms": 25, "termMeta": 39, "tagMeta": 15, "media": 6 diff --git a/test/data/dictionaries/valid-dictionary1/term_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_bank_1.json index ce4290bdff..7f2af6ddc9 100644 --- a/test/data/dictionaries/valid-dictionary1/term_bank_1.json +++ b/test/data/dictionaries/valid-dictionary1/term_bank_1.json @@ -337,5 +337,7 @@ {"type": "structured-content", "content": "kouzou definition 3 (構造)"} ], 101, "P E1" - ] + ], + ["のたまう", "のたまう", "v5", "v5", 1, ["notamau definition"], 15, ""], + ["のたもうた", "のたもうた", "", "", 1, [["のたまう", ["past"]]], 16, ""] ] \ No newline at end of file diff --git a/test/data/dictionaries/valid-dictionary1/term_bank_2.json b/test/data/dictionaries/valid-dictionary1/term_bank_2.json index d46b4c144a..30e5418c09 100644 --- a/test/data/dictionaries/valid-dictionary1/term_bank_2.json +++ b/test/data/dictionaries/valid-dictionary1/term_bank_2.json @@ -103,6 +103,8 @@ { "tag": "span", "style": { + "color": "#dd2121", + "textShadow": "0.5px 0.5px 1px gray", "textDecorationLine": "underline", "textDecorationStyle": "wavy", "textDecorationColor": "red" diff --git a/test/data/html/dom-text-scanner.html b/test/data/html/dom-text-scanner.html index ff4d0493fa..1f537d4e3b 100644 --- a/test/data/html/dom-text-scanner.html +++ b/test/data/html/dom-text-scanner.html @@ -392,4 +392,4 @@

    DOMTextScanner Tests

    - \ No newline at end of file + diff --git a/test/data/html/js/html-test-utilities.js b/test/data/html/js/html-test-utilities.js index da3e753acd..b72b7fcaf9 100644 --- a/test/data/html/js/html-test-utilities.js +++ b/test/data/html/js/html-test-utilities.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/test/data/html/js/performance-frames.js b/test/data/html/js/performance-frames.js index 7484f97175..4432a2c01b 100644 --- a/test/data/html/js/performance-frames.js +++ b/test/data/html/js/performance-frames.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/data/html/js/popup-tests-frame1.js b/test/data/html/js/popup-tests-frame1.js index b1dc27563d..a54cf7174b 100644 --- a/test/data/html/js/popup-tests-frame1.js +++ b/test/data/html/js/popup-tests-frame1.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/data/html/js/popup-tests.js b/test/data/html/js/popup-tests.js index 3a46a04595..c838cebb66 100644 --- a/test/data/html/js/popup-tests.js +++ b/test/data/html/js/popup-tests.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/data/translator-test-inputs.json b/test/data/translator-test-inputs.json index ec7f1a1121..ce449c1e03 100644 --- a/test/data/translator-test-inputs.json +++ b/test/data/translator-test-inputs.json @@ -34,7 +34,9 @@ { "index": 0, "priority": 0, - "allowSecondarySearches": false + "allowSecondarySearches": false, + "partsOfSpeechFilter": true, + "useDeinflections": true } ] ] @@ -343,6 +345,13 @@ "mode": "split", "text": "構造", "options": "default" + }, + { + "name": "Test dictionary deinflection", + "func": "findTerms", + "mode": "split", + "text": "のたもうた", + "options": "default" } ] } \ No newline at end of file diff --git a/test/data/translator-test-results-note-data1.json b/test/data/translator-test-results-note-data1.json index 1342a63fed..c34842da82 100644 --- a/test/data/translator-test-results-note-data1.json +++ b/test/data/translator-test-results-note-data1.json @@ -341,7 +341,12 @@ "source": "打", "rawSource": "打", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 1, @@ -647,7 +652,12 @@ "source": "打", "rawSource": "打", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 2, @@ -967,7 +977,12 @@ "source": "打つ", "rawSource": "打つ", "sourceTerm": "打つ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "isPrimary": true, "sequence": 3, @@ -1273,7 +1288,12 @@ "source": "打つ", "rawSource": "打つ", "sourceTerm": "打つ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "isPrimary": true, "sequence": 3, @@ -1579,7 +1599,12 @@ "source": "打つ", "rawSource": "打つ", "sourceTerm": "打つ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 3, @@ -1885,7 +1910,12 @@ "source": "打つ", "rawSource": "打つ", "sourceTerm": "打つ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 3, @@ -2191,7 +2221,12 @@ "source": "打", "rawSource": "打", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 1, @@ -2497,7 +2532,12 @@ "source": "打", "rawSource": "打", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 2, @@ -2817,7 +2857,12 @@ "source": "打ち込む", "rawSource": "打ち込む", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "isPrimary": true, "sequence": 4, @@ -3231,7 +3276,12 @@ "source": "打ち込む", "rawSource": "打ち込む", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "isPrimary": true, "sequence": 4, @@ -3645,7 +3695,12 @@ "source": "打ち込む", "rawSource": "打ち込む", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 4, @@ -4059,7 +4114,12 @@ "source": "打ち込む", "rawSource": "打ち込む", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 4, @@ -4473,8 +4533,13 @@ "source": "打ち", "rawSource": "打ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "isPrimary": true, @@ -4781,8 +4846,13 @@ "source": "打ち", "rawSource": "打ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "isPrimary": true, @@ -5089,8 +5159,13 @@ "source": "打ち", "rawSource": "打ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "isPrimary": true, @@ -5397,8 +5472,13 @@ "source": "打ち", "rawSource": "打ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "isPrimary": true, @@ -5705,7 +5785,12 @@ "source": "打", "rawSource": "打", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 1, @@ -6011,7 +6096,12 @@ "source": "打", "rawSource": "打", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 2, @@ -6331,7 +6421,12 @@ "source": "画像", "rawSource": "画像", "sourceTerm": "画像", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 5, @@ -6485,7 +6580,12 @@ "source": "だ", "rawSource": "だ", "sourceTerm": "だ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 1, @@ -6796,7 +6896,12 @@ "source": "ダース", "rawSource": "ダース", "sourceTerm": "ダース", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 2, @@ -7116,7 +7221,12 @@ "source": "うつ", "rawSource": "うつ", "sourceTerm": "うつ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "isPrimary": true, "sequence": 3, @@ -7422,7 +7532,12 @@ "source": "うつ", "rawSource": "うつ", "sourceTerm": "うつ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 3, @@ -7733,7 +7848,12 @@ "source": "ぶつ", "rawSource": "ぶつ", "sourceTerm": "ぶつ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "isPrimary": true, "sequence": 3, @@ -8039,7 +8159,12 @@ "source": "ぶつ", "rawSource": "ぶつ", "sourceTerm": "ぶつ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 3, @@ -8350,7 +8475,12 @@ "source": "うちこむ", "rawSource": "うちこむ", "sourceTerm": "うちこむ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "isPrimary": true, "sequence": 4, @@ -8764,7 +8894,12 @@ "source": "うちこむ", "rawSource": "うちこむ", "sourceTerm": "うちこむ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 4, @@ -9178,8 +9313,13 @@ "source": "うち", "rawSource": "うち", "sourceTerm": "うつ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "isPrimary": true, @@ -9486,8 +9626,13 @@ "source": "うち", "rawSource": "うち", "sourceTerm": "うつ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "isPrimary": true, @@ -9799,7 +9944,12 @@ "source": "ぶちこむ", "rawSource": "ぶちこむ", "sourceTerm": "ぶちこむ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "isPrimary": true, "sequence": 4, @@ -10213,7 +10363,12 @@ "source": "ぶちこむ", "rawSource": "ぶちこむ", "sourceTerm": "ぶちこむ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 4, @@ -10627,8 +10782,13 @@ "source": "ぶち", "rawSource": "ぶち", "sourceTerm": "ぶつ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "isPrimary": true, @@ -10935,8 +11095,13 @@ "source": "ぶち", "rawSource": "ぶち", "sourceTerm": "ぶつ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "isPrimary": true, @@ -11248,7 +11413,12 @@ "source": "がぞう", "rawSource": "がぞう", "sourceTerm": "がぞう", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 5, @@ -11413,7 +11583,12 @@ "source": "打ち込む", "rawSource": "打ち込む", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "sequence": 4, "dictionary": "Test Dictionary 2", @@ -11850,7 +12025,12 @@ "source": "打ち込む", "rawSource": "打ち込む", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "sequence": 4, "dictionary": "Test Dictionary 2", @@ -12287,8 +12467,13 @@ "source": "打ち", "rawSource": "打ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "sequence": 3, @@ -12626,8 +12811,13 @@ "source": "打ち", "rawSource": "打ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "sequence": 3, @@ -12965,7 +13155,12 @@ "source": "打", "rawSource": "打", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "sequence": 1, "dictionary": "Test Dictionary 2", @@ -13269,7 +13464,12 @@ "source": "打", "rawSource": "打", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "sequence": 2, "dictionary": "Test Dictionary 2", @@ -13586,7 +13786,12 @@ "type": "termMerged", "source": "打ち込む", "rawSource": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "sequence": 4, "dictionary": "Test Dictionary 2", @@ -14351,8 +14556,13 @@ "type": "termMerged", "source": "打ち", "rawSource": "打ち", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "sequence": 3, @@ -14923,7 +15133,12 @@ "type": "termMerged", "source": "打", "rawSource": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "sequence": 1, "dictionary": "Test Dictionary 2", @@ -15220,7 +15435,12 @@ "type": "termMerged", "source": "打", "rawSource": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "sequence": 2, "dictionary": "Test Dictionary 2", @@ -15533,10 +15753,15 @@ "source": "打ち込んでいませんでした", "rawSource": "打ち込んでいませんでした", "sourceTerm": "打ち込む", - "reasons": [ - "-te", - "progressive or perfect", - "polite past negative" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "-te", + "progressive or perfect", + "polite past negative" + ] + } ], "score": 10, "isPrimary": true, @@ -15951,10 +16176,15 @@ "source": "打ち込んでいませんでした", "rawSource": "打ち込んでいませんでした", "sourceTerm": "打ち込む", - "reasons": [ - "-te", - "progressive or perfect", - "polite past negative" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "-te", + "progressive or perfect", + "polite past negative" + ] + } ], "score": 10, "isPrimary": true, @@ -16369,10 +16599,15 @@ "source": "打ち込んでいませんでした", "rawSource": "打ち込んでいませんでした", "sourceTerm": "打ち込む", - "reasons": [ - "-te", - "progressive or perfect", - "polite past negative" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "-te", + "progressive or perfect", + "polite past negative" + ] + } ], "score": 1, "isPrimary": true, @@ -16787,10 +17022,15 @@ "source": "打ち込んでいませんでした", "rawSource": "打ち込んでいませんでした", "sourceTerm": "打ち込む", - "reasons": [ - "-te", - "progressive or perfect", - "polite past negative" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "-te", + "progressive or perfect", + "polite past negative" + ] + } ], "score": 1, "isPrimary": true, @@ -17205,8 +17445,13 @@ "source": "打ち", "rawSource": "打ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "isPrimary": true, @@ -17513,8 +17758,13 @@ "source": "打ち", "rawSource": "打ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "isPrimary": true, @@ -17821,8 +18071,13 @@ "source": "打ち", "rawSource": "打ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "isPrimary": true, @@ -18129,8 +18384,13 @@ "source": "打ち", "rawSource": "打ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "isPrimary": true, @@ -18437,7 +18697,12 @@ "source": "打", "rawSource": "打", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 1, @@ -18743,7 +19008,12 @@ "source": "打", "rawSource": "打", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 2, @@ -19063,7 +19333,12 @@ "source": "打ち込む", "rawSource": "打(う)ち込(こ)む", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "isPrimary": true, "sequence": 4, @@ -19477,7 +19752,12 @@ "source": "打ち込む", "rawSource": "打(う)ち込(こ)む", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "isPrimary": true, "sequence": 4, @@ -19891,7 +20171,12 @@ "source": "打ち込む", "rawSource": "打(う)ち込(こ)む", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 4, @@ -20305,7 +20590,12 @@ "source": "打ち込む", "rawSource": "打(う)ち込(こ)む", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 4, @@ -20719,8 +21009,13 @@ "source": "打ち", "rawSource": "打(う)ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "isPrimary": true, @@ -21027,8 +21322,13 @@ "source": "打ち", "rawSource": "打(う)ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "isPrimary": true, @@ -21335,8 +21635,13 @@ "source": "打ち", "rawSource": "打(う)ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "isPrimary": true, @@ -21643,8 +21948,13 @@ "source": "打ち", "rawSource": "打(う)ち", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "isPrimary": true, @@ -21951,7 +22261,12 @@ "source": "打", "rawSource": "打", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 1, @@ -22257,7 +22572,12 @@ "source": "打", "rawSource": "打", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 2, @@ -22577,7 +22897,12 @@ "source": "打ち込む", "rawSource": "(打)(ち)(込)(む)", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "isPrimary": true, "sequence": 4, @@ -22991,7 +23316,12 @@ "source": "打ち込む", "rawSource": "(打)(ち)(込)(む)", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "isPrimary": true, "sequence": 4, @@ -23405,7 +23735,12 @@ "source": "打ち込む", "rawSource": "(打)(ち)(込)(む)", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 4, @@ -23819,7 +24154,12 @@ "source": "打ち込む", "rawSource": "(打)(ち)(込)(む)", "sourceTerm": "打ち込む", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 4, @@ -24233,8 +24573,13 @@ "source": "打ち", "rawSource": "(打)(ち)", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "isPrimary": true, @@ -24541,8 +24886,13 @@ "source": "打ち", "rawSource": "(打)(ち)", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "isPrimary": true, @@ -24849,8 +25199,13 @@ "source": "打ち", "rawSource": "(打)(ち)", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "isPrimary": true, @@ -25157,8 +25512,13 @@ "source": "打ち", "rawSource": "(打)(ち)", "sourceTerm": "打つ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "isPrimary": true, @@ -25465,7 +25825,12 @@ "source": "打", "rawSource": "(打)", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 1, @@ -25771,7 +26136,12 @@ "source": "打", "rawSource": "(打)", "sourceTerm": "打", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 2, @@ -26091,8 +26461,13 @@ "source": "よみ", "rawSource": "test", "sourceTerm": "よむ", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 100, "isPrimary": true, @@ -26245,7 +26620,12 @@ "source": "つよみ", "rawSource": "つtest", "sourceTerm": "つよみ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 90, "isPrimary": true, "sequence": 7, @@ -26397,8 +26777,13 @@ "source": "よみました", "rawSource": "testました", "sourceTerm": "よむ", - "reasons": [ - "polite past" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "polite past" + ] + } ], "score": 100, "isPrimary": true, @@ -26549,7 +26934,12 @@ "type": "termMerged", "source": "うちこむ", "rawSource": "うちこむ", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "sequence": 4, "dictionary": "Test Dictionary 2", @@ -27314,8 +27704,13 @@ "type": "termMerged", "source": "うち", "rawSource": "うち", - "reasons": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "sequence": 3, @@ -27893,7 +28288,12 @@ "source": "お手前", "rawSource": "お手前", "sourceTerm": "お手前", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 9, @@ -28221,7 +28621,12 @@ "source": "番号", "rawSource": "番号", "sourceTerm": "番号", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 10, @@ -28401,7 +28806,12 @@ "source": "中腰", "rawSource": "中腰", "sourceTerm": "中腰", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 11, @@ -28581,7 +28991,12 @@ "source": "所業", "rawSource": "所業", "sourceTerm": "所業", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 12, @@ -28761,7 +29176,12 @@ "source": "土木工事", "rawSource": "土木工事", "sourceTerm": "土木工事", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 13, @@ -28941,7 +29361,12 @@ "source": "好き", "rawSource": "好き", "sourceTerm": "好き", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "isPrimary": true, "sequence": 14, @@ -29149,7 +29574,12 @@ "source": "構造", "rawSource": "構造", "sourceTerm": "構造", - "reasons": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 35, "isPrimary": true, "sequence": 101, @@ -29289,5 +29719,118 @@ "media": {} } ] + }, + { + "name": "Test dictionary deinflection", + "noteDataList": [ + { + "marker": "{marker}", + "definition": { + "type": "term", + "id": 23, + "source": "のたもうた", + "rawSource": "のたもうた", + "sourceTerm": "のたまう", + "inflectionRuleChainCandidates": [ + { + "source": "both", + "inflectionRules": [ + "past" + ] + } + ], + "score": 1, + "isPrimary": true, + "sequence": 15, + "dictionary": "Test Dictionary 2", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "dictionaryNames": [ + "Test Dictionary 2" + ], + "expression": "のたまう", + "reading": "のたまう", + "expressions": [ + { + "sourceTerm": "のたまう", + "expression": "のたまう", + "reading": "のたまう", + "termTags": [], + "frequencies": [], + "pitches": [], + "furiganaSegments": [ + { + "text": "のたまう", + "furigana": "" + } + ], + "termFrequency": "normal", + "wordClasses": [ + "v5" + ] + } + ], + "glossary": [ + "notamau definition" + ], + "definitionTags": [ + { + "name": "v5", + "category": "default", + "notes": "", + "order": 0, + "score": 0, + "dictionary": "Test Dictionary 2", + "redundant": false + } + ], + "termTags": [], + "frequencies": [], + "pitches": [], + "phoneticTranscriptions": [], + "sourceTermExactMatchCount": 1, + "url": "url:", + "cloze": { + "sentence": "", + "prefix": "", + "body": "", + "suffix": "" + }, + "furiganaSegments": [ + { + "text": "のたまう", + "furigana": "" + } + ] + }, + "glossaryLayoutMode": "default", + "compactTags": false, + "group": false, + "merge": false, + "modeTermKanji": false, + "modeTermKana": false, + "modeKanji": false, + "compactGlossaries": false, + "uniqueExpressions": [ + "のたまう" + ], + "uniqueReadings": [ + "のたまう" + ], + "pitches": [], + "pitchCount": 0, + "phoneticTranscriptions": [], + "context": { + "query": "query", + "fullQuery": "fullQuery", + "document": { + "title": "title" + } + }, + "media": {} + } + ] } ] \ No newline at end of file diff --git a/test/data/translator-test-results.json b/test/data/translator-test-results.json index 50d97775c0..4a81c552bc 100644 --- a/test/data/translator-test-results.json +++ b/test/data/translator-test-results.json @@ -291,7 +291,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -454,7 +459,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -636,7 +646,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -801,7 +816,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -966,7 +986,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -1131,7 +1156,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -1296,7 +1326,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -1459,7 +1494,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -1641,7 +1681,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -1830,7 +1875,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -2019,7 +2069,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -2208,7 +2263,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -2397,8 +2457,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -2564,8 +2629,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -2731,8 +2801,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -2898,8 +2973,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -3065,7 +3145,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -3228,7 +3313,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -3410,7 +3500,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -3523,7 +3618,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -3692,7 +3792,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -3874,7 +3979,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -4039,7 +4149,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -4210,7 +4325,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -4375,7 +4495,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -4546,7 +4671,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -4735,7 +4865,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -4924,8 +5059,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -5091,8 +5231,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -5264,7 +5409,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -5453,7 +5603,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -5642,8 +5797,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -5809,8 +5969,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -5982,7 +6147,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -6105,7 +6275,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -6162,7 +6337,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -6219,7 +6399,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -6276,7 +6461,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -6333,8 +6523,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -6392,8 +6587,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -6451,8 +6651,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -6510,8 +6715,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -6569,7 +6779,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -6626,7 +6841,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -6689,7 +6909,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -6926,7 +7151,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -7163,8 +7393,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -7378,8 +7613,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -7593,7 +7833,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -7756,7 +8001,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -7938,7 +8188,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -8393,8 +8648,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -8803,7 +9063,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -8966,7 +9231,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -9148,10 +9418,15 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "-te", - "progressive or perfect", - "polite past negative" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "-te", + "progressive or perfect", + "polite past negative" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -9341,10 +9616,15 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "-te", - "progressive or perfect", - "polite past negative" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "-te", + "progressive or perfect", + "polite past negative" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -9534,10 +9814,15 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "-te", - "progressive or perfect", - "polite past negative" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "-te", + "progressive or perfect", + "polite past negative" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -9727,10 +10012,15 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "-te", - "progressive or perfect", - "polite past negative" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "-te", + "progressive or perfect", + "polite past negative" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -9920,8 +10210,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -10087,8 +10382,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -10254,8 +10554,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -10421,8 +10726,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -10588,7 +10898,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -10751,7 +11066,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -10933,7 +11253,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -11122,7 +11447,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -11311,7 +11641,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -11500,7 +11835,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -11689,8 +12029,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -11856,8 +12201,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -12023,8 +12373,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -12190,8 +12545,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -12357,7 +12717,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -12520,7 +12885,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -12702,7 +13072,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -12891,7 +13266,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -13080,7 +13460,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -13269,7 +13654,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -13458,8 +13848,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -13625,8 +14020,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -13792,8 +14192,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -13959,8 +14364,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 1, "frequencyOrder": 0, @@ -14126,7 +14536,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -14289,7 +14704,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -14471,8 +14891,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 100, "frequencyOrder": 0, @@ -14576,7 +15001,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 90, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -14679,8 +15109,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "polite past" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "polite past" + ] + } ], "score": 100, "frequencyOrder": 0, @@ -14784,7 +15219,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 10, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -15239,8 +15679,13 @@ { "type": "term", "isPrimary": true, - "inflections": [ - "masu stem" + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [ + "masu stem" + ] + } ], "score": 10, "frequencyOrder": 0, @@ -15655,7 +16100,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -15804,7 +16254,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -15899,7 +16354,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -15994,7 +16454,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -16089,7 +16554,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -16184,7 +16654,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 1, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -16296,7 +16771,12 @@ { "type": "term", "isPrimary": true, - "inflections": [], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], "score": 35, "frequencyOrder": 0, "dictionaryIndex": 0, @@ -16399,5 +16879,86 @@ "frequencies": [] } ] + }, + { + "name": "Test dictionary deinflection", + "originalTextLength": 5, + "dictionaryEntries": [ + { + "type": "term", + "isPrimary": true, + "inflectionRuleChainCandidates": [ + { + "source": "both", + "inflectionRules": [ + "past" + ] + } + ], + "score": 1, + "frequencyOrder": 0, + "dictionaryIndex": 0, + "dictionaryPriority": 0, + "sourceTermExactMatchCount": 1, + "maxTransformedTextLength": 5, + "headwords": [ + { + "index": 0, + "term": "のたまう", + "reading": "のたまう", + "sources": [ + { + "originalText": "のたもうた", + "transformedText": "のたもうた", + "deinflectedText": "のたまう", + "matchType": "exact", + "matchSource": "term", + "isPrimary": true + } + ], + "tags": [], + "wordClasses": [ + "v5" + ] + } + ], + "definitions": [ + { + "index": 0, + "headwordIndices": [ + 0 + ], + "dictionary": "Test Dictionary 2", + "dictionaryIndex": 0, + "dictionaryPriority": 0, + "id": 23, + "score": 1, + "frequencyOrder": 0, + "sequences": [ + 15 + ], + "isPrimary": true, + "tags": [ + { + "name": "v5", + "category": "default", + "order": 0, + "score": 0, + "content": [], + "dictionaries": [ + "Test Dictionary 2" + ], + "redundant": false + } + ], + "entries": [ + "notamau definition" + ] + } + ], + "pronunciations": [], + "frequencies": [] + } + ] } ] \ No newline at end of file diff --git a/test/database.test.js b/test/database.test.js index 0b9ce94ffe..5dbc104075 100644 --- a/test/database.test.js +++ b/test/database.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/test/deinflector.test.js b/test/deinflector.test.js index 9c8443d249..b00625cfa2 100644 --- a/test/deinflector.test.js +++ b/test/deinflector.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/test/dictionary-data-validate.test.js b/test/dictionary-data-validate.test.js index 5334bc2e49..4c96cdb0eb 100644 --- a/test/dictionary-data-validate.test.js +++ b/test/dictionary-data-validate.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/test/dictionary-data.test.js b/test/dictionary-data.test.js index b3a9c57da5..438e1e97ed 100644 --- a/test/dictionary-data.test.js +++ b/test/dictionary-data.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/dictionary-data.write.js b/test/dictionary-data.write.js index 0f6bbfcbcb..bdf635c876 100644 --- a/test/dictionary-data.write.js +++ b/test/dictionary-data.write.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/document-util.test.js b/test/document-util.test.js index cc8db7062d..8ced9d0748 100644 --- a/test/document-util.test.js +++ b/test/document-util.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -18,12 +18,12 @@ import {fileURLToPath} from 'node:url'; import path from 'path'; -import {describe, expect} from 'vitest'; +import {afterAll, describe, expect, test} from 'vitest'; import {DocumentUtil} from '../ext/js/dom/document-util.js'; import {DOMTextScanner} from '../ext/js/dom/dom-text-scanner.js'; import {TextSourceElement} from '../ext/js/dom/text-source-element.js'; import {TextSourceRange} from '../ext/js/dom/text-source-range.js'; -import {createDomTest} from './fixtures/dom-test.js'; +import {setupDomTest} from './fixtures/dom-test.js'; import {parseJson} from '../dev/json.js'; const dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -110,165 +110,176 @@ function findImposterElement(document) { return document.querySelector('div[style*="2147483646"]>*'); } -const test = createDomTest(path.join(dirname, 'data/html/document-util.html')); +const documentUtilTestEnv = await setupDomTest(path.join(dirname, 'data/html/document-util.html')); -describe('DocumentUtil', () => { - test('Text scanning functions', ({window}) => { - const {document} = window; - for (const testElement of /** @type {NodeListOf} */ (document.querySelectorAll('test-case[data-test-type=scan]'))) { - // Get test parameters - /** @type {import('test/document-util').DocumentUtilTestData} */ - const { - elementFromPointSelector, - caretRangeFromPointSelector, - startNodeSelector, - startOffset, - endNodeSelector, - endOffset, - resultType, - sentenceScanExtent, - sentence, - hasImposter, - terminateAtNewlines - } = parseJson(/** @type {string} */ (testElement.dataset.testData)); +describe('Document utility tests', () => { + const {window, teardown} = documentUtilTestEnv; + afterAll(() => teardown(global)); - const elementFromPointValue = querySelectorChildOrSelf(testElement, elementFromPointSelector); - const caretRangeFromPointValue = querySelectorChildOrSelf(testElement, caretRangeFromPointSelector); - const startNode = getChildTextNodeOrSelf(window, querySelectorChildOrSelf(testElement, startNodeSelector)); - const endNode = getChildTextNodeOrSelf(window, querySelectorChildOrSelf(testElement, endNodeSelector)); + describe('DocumentUtil', () => { + describe('Text scanning functions', () => { + let testIndex = 0; + const {document} = window; + for (const testElement of /** @type {NodeListOf} */ (document.querySelectorAll('test-case[data-test-type=scan]'))) { + test(`test-case-${testIndex++}`, () => { + // Get test parameters + /** @type {import('test/document-util').DocumentUtilTestData} */ + const { + elementFromPointSelector, + caretRangeFromPointSelector, + startNodeSelector, + startOffset, + endNodeSelector, + endOffset, + resultType, + sentenceScanExtent, + sentence, + hasImposter, + terminateAtNewlines + } = parseJson(/** @type {string} */ (testElement.dataset.testData)); - // Defaults to true - const terminateAtNewlines2 = typeof terminateAtNewlines === 'boolean' ? terminateAtNewlines : true; + const elementFromPointValue = querySelectorChildOrSelf(testElement, elementFromPointSelector); + const caretRangeFromPointValue = querySelectorChildOrSelf(testElement, caretRangeFromPointSelector); + const startNode = getChildTextNodeOrSelf(window, querySelectorChildOrSelf(testElement, startNodeSelector)); + const endNode = getChildTextNodeOrSelf(window, querySelectorChildOrSelf(testElement, endNodeSelector)); - expect(elementFromPointValue).not.toStrictEqual(null); - expect(caretRangeFromPointValue).not.toStrictEqual(null); - expect(startNode).not.toStrictEqual(null); - expect(endNode).not.toStrictEqual(null); + // Defaults to true + const terminateAtNewlines2 = typeof terminateAtNewlines === 'boolean' ? terminateAtNewlines : true; - // Setup functions - document.elementFromPoint = () => elementFromPointValue; + expect(elementFromPointValue).not.toStrictEqual(null); + expect(caretRangeFromPointValue).not.toStrictEqual(null); + expect(startNode).not.toStrictEqual(null); + expect(endNode).not.toStrictEqual(null); - document.caretRangeFromPoint = (x, y) => { - const imposter = getChildTextNodeOrSelf(window, findImposterElement(document)); - expect(!!imposter).toStrictEqual(!!hasImposter); + // Setup functions + document.elementFromPoint = () => elementFromPointValue; - const range = document.createRange(); - range.setStart(/** @type {Node} */ (imposter ? imposter : startNode), startOffset); - range.setEnd(/** @type {Node} */ (imposter ? imposter : startNode), endOffset); + document.caretRangeFromPoint = (x, y) => { + const imposter = getChildTextNodeOrSelf(window, findImposterElement(document)); + expect(!!imposter).toStrictEqual(!!hasImposter); - // Override getClientRects to return a rect guaranteed to contain (x, y) - range.getClientRects = () => { - /** @type {import('test/document-types').PseudoDOMRectList} */ - const domRectList = Object.assign( - [new DOMRect(x - 1, y - 1, 2, 2)], - { - /** - * @this {DOMRect[]} - * @param {number} index - * @returns {DOMRect} - */ - item: function item(index) { return this[index]; } - } - ); - return domRectList; - }; - return range; - }; + const range = document.createRange(); + range.setStart(/** @type {Node} */ (imposter ? imposter : startNode), startOffset); + range.setEnd(/** @type {Node} */ (imposter ? imposter : startNode), endOffset); - // Test docRangeFromPoint - const source = DocumentUtil.getRangeFromPoint(0, 0, { - deepContentScan: false, - normalizeCssZoom: true - }); - switch (resultType) { - case 'TextSourceRange': - expect(getPrototypeOfOrNull(source)).toStrictEqual(TextSourceRange.prototype); - break; - case 'TextSourceElement': - expect(getPrototypeOfOrNull(source)).toStrictEqual(TextSourceElement.prototype); - break; - case 'null': - expect(source).toStrictEqual(null); - break; - default: - expect.unreachable(); - break; - } - if (source === null) { continue; } + // Override getClientRects to return a rect guaranteed to contain (x, y) + range.getClientRects = () => { + /** @type {import('test/document-types').PseudoDOMRectList} */ + const domRectList = Object.assign( + [new DOMRect(x - 1, y - 1, 2, 2)], + { + /** + * @this {DOMRect[]} + * @param {number} index + * @returns {DOMRect} + */ + item: function item(index) { return this[index]; } + } + ); + return domRectList; + }; + return range; + }; - // Sentence info - const terminatorString = '…。..??!!'; - const terminatorMap = new Map(); - for (const char of terminatorString) { - terminatorMap.set(char, [false, true]); - } - const quoteArray = [['「', '」'], ['『', '』'], ['\'', '\''], ['"', '"']]; - const forwardQuoteMap = new Map(); - const backwardQuoteMap = new Map(); - for (const [char1, char2] of quoteArray) { - forwardQuoteMap.set(char1, [char2, false]); - backwardQuoteMap.set(char2, [char1, false]); - } + // Test docRangeFromPoint + const source = DocumentUtil.getRangeFromPoint(0, 0, { + deepContentScan: false, + normalizeCssZoom: true + }); + switch (resultType) { + case 'TextSourceRange': + expect(getPrototypeOfOrNull(source)).toStrictEqual(TextSourceRange.prototype); + break; + case 'TextSourceElement': + expect(getPrototypeOfOrNull(source)).toStrictEqual(TextSourceElement.prototype); + break; + case 'null': + expect(source).toStrictEqual(null); + break; + default: + expect.unreachable(); + break; + } + if (source === null) { return; } + + // Sentence info + const terminatorString = '…。..??!!'; + const terminatorMap = new Map(); + for (const char of terminatorString) { + terminatorMap.set(char, [false, true]); + } + const quoteArray = [['「', '」'], ['『', '』'], ['\'', '\''], ['"', '"']]; + const forwardQuoteMap = new Map(); + const backwardQuoteMap = new Map(); + for (const [char1, char2] of quoteArray) { + forwardQuoteMap.set(char1, [char2, false]); + backwardQuoteMap.set(char2, [char1, false]); + } - // Test docSentenceExtract - const sentenceActual = DocumentUtil.extractSentence( - source, - false, - sentenceScanExtent, - terminateAtNewlines2, - terminatorMap, - forwardQuoteMap, - backwardQuoteMap - ).text; - expect(sentenceActual).toStrictEqual(sentence); + // Test docSentenceExtract + const sentenceActual = DocumentUtil.extractSentence( + source, + false, + sentenceScanExtent, + terminateAtNewlines2, + terminatorMap, + forwardQuoteMap, + backwardQuoteMap + ).text; + expect(sentenceActual).toStrictEqual(sentence); - // Clean - source.cleanup(); - } + // Clean + source.cleanup(); + }); + } + }); }); -}); -describe('DOMTextScanner', () => { - test('Seek functions', async ({window}) => { - const {document} = window; - for (const testElement of /** @type {NodeListOf} */ (document.querySelectorAll('test-case[data-test-type=text-source-range-seek]'))) { - // Get test parameters - /** @type {import('test/document-util').DOMTextScannerTestData} */ - const { - seekNodeSelector, - seekNodeIsText, - seekOffset, - seekLength, - seekDirection, - expectedResultNodeSelector, - expectedResultNodeIsText, - expectedResultOffset, - expectedResultContent - } = parseJson(/** @type {string} */ (testElement.dataset.testData)); + describe('DOMTextScanner', () => { + describe('Seek functions', () => { + let testIndex = 0; + const {document} = window; + for (const testElement of /** @type {NodeListOf} */ (document.querySelectorAll('test-case[data-test-type=text-source-range-seek]'))) { + test(`test-case-${testIndex++}`, () => { + // Get test parameters + /** @type {import('test/document-util').DOMTextScannerTestData} */ + const { + seekNodeSelector, + seekNodeIsText, + seekOffset, + seekLength, + seekDirection, + expectedResultNodeSelector, + expectedResultNodeIsText, + expectedResultOffset, + expectedResultContent + } = parseJson(/** @type {string} */ (testElement.dataset.testData)); - /** @type {?Node} */ - let seekNode = testElement.querySelector(/** @type {string} */ (seekNodeSelector)); - if (seekNodeIsText && seekNode !== null) { - seekNode = seekNode.firstChild; - } + /** @type {?Node} */ + let seekNode = testElement.querySelector(/** @type {string} */ (seekNodeSelector)); + if (seekNodeIsText && seekNode !== null) { + seekNode = seekNode.firstChild; + } - const expectedResultContent2 = expectedResultContent.join('\n'); + const expectedResultContent2 = expectedResultContent.join('\n'); - /** @type {?Node} */ - let expectedResultNode = testElement.querySelector(/** @type {string} */ (expectedResultNodeSelector)); - if (expectedResultNodeIsText && expectedResultNode !== null) { - expectedResultNode = expectedResultNode.firstChild; - } + /** @type {?Node} */ + let expectedResultNode = testElement.querySelector(/** @type {string} */ (expectedResultNodeSelector)); + if (expectedResultNodeIsText && expectedResultNode !== null) { + expectedResultNode = expectedResultNode.firstChild; + } - const {node, offset, content} = ( + const {node, offset, content} = ( seekDirection === 'forward' ? new DOMTextScanner(/** @type {Node} */ (seekNode), seekOffset, true, false).seek(seekLength) : new DOMTextScanner(/** @type {Node} */ (seekNode), seekOffset, true, false).seek(-seekLength) - ); + ); - expect(node).toStrictEqual(expectedResultNode); - expect(offset).toStrictEqual(expectedResultOffset); - expect(content).toStrictEqual(expectedResultContent2); - } + expect(node).toStrictEqual(expectedResultNode); + expect(offset).toStrictEqual(expectedResultOffset); + expect(content).toStrictEqual(expectedResultContent2); + }); + } + }); }); }); diff --git a/test/dom-text-scanner.test.js b/test/dom-text-scanner.test.js index da38d24c96..e86d298027 100644 --- a/test/dom-text-scanner.test.js +++ b/test/dom-text-scanner.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -18,10 +18,10 @@ import {fileURLToPath} from 'node:url'; import path from 'path'; -import {describe, expect} from 'vitest'; +import {afterAll, describe, expect, test} from 'vitest'; import {parseJson} from '../dev/json.js'; import {DOMTextScanner} from '../ext/js/dom/dom-text-scanner.js'; -import {createDomTest} from './fixtures/dom-test.js'; +import {setupDomTest} from './fixtures/dom-test.js'; const dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -101,11 +101,13 @@ function createAbsoluteGetComputedStyle(window) { }; } - -const test = createDomTest(path.join(dirname, 'data/html/dom-text-scanner.html')); +const domTestEnv = await setupDomTest(path.join(dirname, 'data/html/dom-text-scanner.html')); describe('DOMTextScanner', () => { - test('Seek tests', ({window}) => { + const {window, teardown} = domTestEnv; + afterAll(() => teardown(global)); + + test('Seek tests', () => { const {document} = window; window.getComputedStyle = createAbsoluteGetComputedStyle(window); diff --git a/test/fixtures/anki-template-renderer-test.js b/test/fixtures/anki-template-renderer-test.js new file mode 100644 index 0000000000..90fa0440df --- /dev/null +++ b/test/fixtures/anki-template-renderer-test.js @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023-2024 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import {vi} from 'vitest'; +import {AnkiTemplateRenderer} from '../../ext/js/templates/sandbox/anki-template-renderer.js'; +import {fetch} from '../mocks/common.js'; +import {createDomTest} from './dom-test.js'; + +vi.stubGlobal('fetch', fetch); + +/** + * @returns {Promise>} + */ +export async function createAnkiTemplateRendererTest() { + const test = createDomTest(void 0); + const ankiTemplateRenderer = new AnkiTemplateRenderer(); + await ankiTemplateRenderer.prepare(); + /** @type {import('vitest').TestAPI<{window: import('jsdom').DOMWindow, ankiTemplateRenderer: AnkiTemplateRenderer}>} */ + const result = test.extend({ + window: async ({window}, use) => { await use(window); }, + // eslint-disable-next-line no-empty-pattern + ankiTemplateRenderer: 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(ankiTemplateRenderer); + } + }); + return result; +} diff --git a/test/fixtures/dom-test.js b/test/fixtures/dom-test.js index 8cfe80a923..a0a17127d6 100644 --- a/test/fixtures/dom-test.js +++ b/test/fixtures/dom-test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -35,6 +35,20 @@ function prepareWindow(window) { document.caretRangeFromPoint = () => null; } +/** + * + * @param {string} [htmlFilePath] + * @returns {Promise<{window: import('jsdom').DOMWindow; teardown: (global: unknown) => import('vitest').Awaitable}>} + */ +export async function setupDomTest(htmlFilePath) { + const html = typeof htmlFilePath === 'string' ? fs.readFileSync(htmlFilePath, {encoding: 'utf8'}) : ''; + const env = builtinEnvironments.jsdom; + const {teardown} = await env.setup(global, {jsdom: {html}}); + const window = /** @type {import('jsdom').DOMWindow} */ (/** @type {unknown} */ (global.window)); + prepareWindow(window); + return {window, teardown}; +} + /** * @param {string} [htmlFilePath] * @returns {import('vitest').TestAPI<{window: import('jsdom').DOMWindow}>} diff --git a/test/fixtures/translator-test.js b/test/fixtures/translator-test.js index 83644513aa..f162972dc1 100644 --- a/test/fixtures/translator-test.js +++ b/test/fixtures/translator-test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/test/hotkey-util.test.js b/test/hotkey-util.test.js index d89d571d84..f87d0ffda7 100644 --- a/test/hotkey-util.test.js +++ b/test/hotkey-util.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/test/japanese-util.test.js b/test/japanese-util.test.js index 177af4d5cf..ab14f209f3 100644 --- a/test/japanese-util.test.js +++ b/test/japanese-util.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/test/json-schema.test.js b/test/json-schema.test.js index 4773e8986c..fcb99ee4fe 100644 --- a/test/json-schema.test.js +++ b/test/json-schema.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/test/json.test.js b/test/json.test.js index 8cf014919b..54c33c347f 100644 --- a/test/json.test.js +++ b/test/json.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/mocks/common.js b/test/mocks/common.js index 7fe30a3e90..339e3ea0e9 100644 --- a/test/mocks/common.js +++ b/test/mocks/common.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/test/mocks/dictionary-importer-media-loader.js b/test/mocks/dictionary-importer-media-loader.js index ffda29b368..4878498009 100644 --- a/test/mocks/dictionary-importer-media-loader.js +++ b/test/mocks/dictionary-importer-media-loader.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/test/object-property-accessor.test.js b/test/object-property-accessor.test.js index 4d50b1e95b..79f78acef6 100644 --- a/test/object-property-accessor.test.js +++ b/test/object-property-accessor.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/test/options-util.test.js b/test/options-util.test.js index daffe886f5..ded16b4c13 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -21,7 +21,7 @@ import fs from 'fs'; import {fileURLToPath} from 'node:url'; import path from 'path'; -import {expect, test, vi} from 'vitest'; +import {expect, test, describe, vi} from 'vitest'; import {OptionsUtil} from '../ext/js/data/options-util.js'; import {TemplatePatcher} from '../ext/js/templates/template-patcher.js'; import {chrome, fetch} from './mocks/common.js'; @@ -425,7 +425,9 @@ function createProfileOptionsUpdatedTestData1() { priority: 0, enabled: true, allowSecondarySearches: false, - definitionsCollapsible: 'not-collapsible' + definitionsCollapsible: 'not-collapsible', + partsOfSpeechFilter: true, + useDeinflections: true } ], parsing: { @@ -602,7 +604,7 @@ function createOptionsUpdatedTestData1() { } ], profileCurrent: 0, - version: 22, + version: 24, global: { database: { prefixWildcardsSupported: false @@ -627,7 +629,7 @@ async function testUpdate() { /** */ async function testDefault() { - test('Default', async () => { + describe('Default', () => { /** @type {((options: import('options-util').IntermediateOptions) => void)[]} */ const data = [ (options) => options, @@ -639,27 +641,22 @@ async function testDefault() { } ]; - const optionsUtil = new OptionsUtil(); - await optionsUtil.prepare(); + test.each(data)('default-test-%#', async (modify) => { + const optionsUtil = new OptionsUtil(); + await optionsUtil.prepare(); - for (const modify of data) { const options = optionsUtil.getDefault(); - const optionsModified = structuredClone(options); modify(optionsModified); - const optionsUpdated = await optionsUtil.update(structuredClone(optionsModified)); expect(structuredClone(optionsUpdated)).toStrictEqual(structuredClone(options)); - } + }); }); } /** */ async function testFieldTemplatesUpdate() { - test('FieldTemplatesUpdate', async () => { - const optionsUtil = new OptionsUtil(); - await optionsUtil.prepare(); - + describe('FieldTemplatesUpdate', () => { const templatePatcher = new TemplatePatcher(); /** * @param {string} fileName @@ -1576,7 +1573,11 @@ async function testFieldTemplatesUpdate() { ]; const updatesPattern = /<<>>/g; - for (const {old, expected, oldVersion, newVersion} of data) { + + test.each(data)('field-templates-update-test-%#', async ({old, expected, oldVersion, newVersion}) => { + const optionsUtil = new OptionsUtil(); + await optionsUtil.prepare(); + const options = /** @type {import('core').SafeAny} */ (createOptionsTestData1()); options.profiles[0].options.anki.fieldTemplates = old; options.version = oldVersion; @@ -1586,7 +1587,7 @@ async function testFieldTemplatesUpdate() { const optionsUpdated = structuredClone(await optionsUtil.update(options, newVersion)); const fieldTemplatesActual = optionsUpdated.profiles[0].options.anki.fieldTemplates; expect(fieldTemplatesActual).toStrictEqual(expected2); - } + }); }); } diff --git a/test/playwright/global.setup.js b/test/playwright/global.setup.js index 1a16f1205b..8c2f8b50b4 100644 --- a/test/playwright/global.setup.js +++ b/test/playwright/global.setup.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/playwright/global.teardown.js b/test/playwright/global.teardown.js index 6787f25550..f3d8897f08 100644 --- a/test/playwright/global.teardown.js +++ b/test/playwright/global.teardown.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/playwright/integration.spec.js b/test/playwright/integration.spec.js index d5b8360c5f..bdcfddf0ab 100644 --- a/test/playwright/integration.spec.js +++ b/test/playwright/integration.spec.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/playwright/playwright-util.js b/test/playwright/playwright-util.js index ac68db4d9b..425d614068 100644 --- a/test/playwright/playwright-util.js +++ b/test/playwright/playwright-util.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/playwright/visual.spec.js b/test/playwright/visual.spec.js index 3b46a6d08c..3ecf4c6ef5 100644 --- a/test/playwright/visual.spec.js +++ b/test/playwright/visual.spec.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/profile-conditions-util.test.js b/test/profile-conditions-util.test.js index d5c8f8d2e0..417ca409a8 100644 --- a/test/profile-conditions-util.test.js +++ b/test/profile-conditions-util.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -18,12 +18,12 @@ /* eslint-disable no-multi-spaces */ -import {expect, test} from 'vitest'; +import {describe, expect, test} from 'vitest'; import {ProfileConditionsUtil} from '../ext/js/background/profile-conditions-util.js'; /** */ function testNormalizeContext() { - test('NormalizeContext', () => { + describe('NormalizeContext', () => { /** @type {{context: import('settings').OptionsContext, expected: import('profile-conditions-util').NormalizedOptionsContext}[]} */ const data = [ // Empty @@ -51,17 +51,17 @@ function testNormalizeContext() { } ]; - for (const {context, expected} of data) { + test.each(data)('normalize-context-test-%#', ({context, expected}) => { const profileConditionsUtil = new ProfileConditionsUtil(); const actual = profileConditionsUtil.normalizeContext(context); expect(actual).toStrictEqual(expected); - } + }); }); } /** */ function testSchemas() { - test('Schemas', () => { + describe('Schemas', () => { /** @type {{conditionGroups: import('settings').ProfileConditionGroup[], expectedSchema?: import('ext/json-schema').Schema, inputs?: {expected: boolean, context: import('settings').OptionsContext}[]}[]} */ const data = [ // Empty @@ -1100,7 +1100,7 @@ function testSchemas() { } ]; - for (const {conditionGroups, expectedSchema, inputs} of data) { + test.each(data)('schemas-test-%#', ({conditionGroups, expectedSchema, inputs}) => { const profileConditionsUtil = new ProfileConditionsUtil(); const schema = profileConditionsUtil.createSchema(conditionGroups); if (typeof expectedSchema !== 'undefined') { @@ -1113,7 +1113,7 @@ function testSchemas() { expect(actual).toStrictEqual(expected); } } - } + }); }); } diff --git a/test/text-source-map.test.js b/test/text-source-map.test.js index 54b39319a1..1825262714 100644 --- a/test/text-source-map.test.js +++ b/test/text-source-map.test.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -16,28 +16,28 @@ * along with this program. If not, see . */ -import {expect, test} from 'vitest'; +import {describe, expect, test} from 'vitest'; import {TextSourceMap} from '../ext/js/general/text-source-map.js'; /** */ function testSource() { - test('Source', () => { + describe('Source', () => { const data = [ ['source1'], ['source2'], ['source3'] ]; - for (const [source] of data) { + test.each(data)('source-test-%#', (source) => { const sourceMap = new TextSourceMap(source); expect(source).toStrictEqual(sourceMap.source); - } + }); }); } /** */ function testEquals() { - test('Equals', () => { + describe('Equals', () => { /** @type {[args1: [source1: string, mapping1: ?(number[])], args2: [source2: string, mapping2: ?(number[])], expectedEquals: boolean][]} */ const data = [ [['source1', null], ['source1', null], true], @@ -69,19 +69,19 @@ function testEquals() { [['source3', [1, 1, 1, 1, 1, 1, 1]], ['source6', [1, 1, 1, 1, 1, 1, 1]], false] ]; - for (const [[source1, mapping1], [source2, mapping2], expectedEquals] of data) { + test.each(data)('equals-test-%#', ([source1, mapping1], [source2, mapping2], expectedEquals) => { const sourceMap1 = new TextSourceMap(source1, mapping1); const sourceMap2 = new TextSourceMap(source2, mapping2); expect(sourceMap1.equals(sourceMap1)).toBe(true); expect(sourceMap2.equals(sourceMap2)).toBe(true); expect(sourceMap1.equals(sourceMap2)).toStrictEqual(expectedEquals); - } + }); }); } /** */ function testGetSourceLength() { - test('GetSourceLength', () => { + describe('GetSourceLength', () => { /** @type {[args: [source: string, mapping: number[]], finalLength: number, expectedValue: number][]} */ const data = [ [['source', [1, 1, 1, 1, 1, 1]], 1, 1], @@ -101,16 +101,16 @@ function testGetSourceLength() { [['source', [6, 6]], 1, 6] ]; - for (const [[source, mapping], finalLength, expectedValue] of data) { + test.each(data)('get-source-length-test-%#', ([source, mapping], finalLength, expectedValue) => { const sourceMap = new TextSourceMap(source, mapping); expect(sourceMap.getSourceLength(finalLength)).toStrictEqual(expectedValue); - } + }); }); } /** */ function testCombineInsert() { - test('CombineInsert', () => { + describe('CombineInsert', () => { /** @type {[args: [source: string, mapping: ?(number[])], expectedArgs: [expectedSource: string, expectedMapping: ?(number[])], operations: [operation: string, arg1: number, arg2: number][]][]} */ const data = [ // No operations @@ -214,7 +214,7 @@ function testCombineInsert() { ] ]; - for (const [[source, mapping], [expectedSource, expectedMapping], operations] of data) { + test.each(data)('combine-insert-test-%#', ([source, mapping], [expectedSource, expectedMapping], operations) => { const sourceMap = new TextSourceMap(source, mapping); const expectedSourceMap = new TextSourceMap(expectedSource, expectedMapping); for (const [operation, ...args] of operations) { @@ -228,7 +228,7 @@ function testCombineInsert() { } } expect(sourceMap.equals(expectedSourceMap)).toBe(true); - } + }); }); } diff --git a/test/utilities/anki.js b/test/utilities/anki.js index aa6c83d289..322acb0dd8 100644 --- a/test/utilities/anki.js +++ b/test/utilities/anki.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/test/utilities/translator.js b/test/utilities/translator.js index 81081af681..da4ae27da6 100644 --- a/test/utilities/translator.js +++ b/test/utilities/translator.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2020-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify diff --git a/types/dev/dictionary-validate.d.ts b/types/dev/dictionary-validate.d.ts index c39f4335c2..02d01b78c3 100644 --- a/types/dev/dictionary-validate.d.ts +++ b/types/dev/dictionary-validate.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,3 +29,8 @@ export type Schemas = { termBankV3: Schema; termMetaBankV3: Schema; }; + +/** + * An array of tuples of a regular expression for file types inside a dictionary and its corresponding schema. + */ +export type SchemasDetails = [fileNameFormat: RegExp, schema: unknown][]; diff --git a/types/dev/manifest.d.ts b/types/dev/manifest.d.ts index af475ee93b..ef2c831c3c 100644 --- a/types/dev/manifest.d.ts +++ b/types/dev/manifest.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/dev/schema-validate.d.ts b/types/dev/schema-validate.d.ts index b30c8a04d1..b6cbb8ddaa 100644 --- a/types/dev/schema-validate.d.ts +++ b/types/dev/schema-validate.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/anki-controller.d.ts b/types/ext/anki-controller.d.ts index f9a3d6460d..d2fa9162b1 100644 --- a/types/ext/anki-controller.d.ts +++ b/types/ext/anki-controller.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/anki-note-builder.d.ts b/types/ext/anki-note-builder.d.ts index fbcb932833..092978ed75 100644 --- a/types/ext/anki-note-builder.d.ts +++ b/types/ext/anki-note-builder.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/anki-template-renderer-content-manager.d.ts b/types/ext/anki-template-renderer-content-manager.d.ts index d7667ae2f9..ab300635b2 100644 --- a/types/ext/anki-template-renderer-content-manager.d.ts +++ b/types/ext/anki-template-renderer-content-manager.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/anki-templates-internal.d.ts b/types/ext/anki-templates-internal.d.ts index 2842765d33..ea0a407d9d 100644 --- a/types/ext/anki-templates-internal.d.ts +++ b/types/ext/anki-templates-internal.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/anki-templates.d.ts b/types/ext/anki-templates.d.ts index 098873e622..ad90e93e16 100644 --- a/types/ext/anki-templates.d.ts +++ b/types/ext/anki-templates.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -172,7 +172,7 @@ export type TermDictionaryEntry = { source: string | null; rawSource: string | null; sourceTerm?: string | null; - reasons: string[]; + inflectionRuleChainCandidates: Dictionary.InflectionRuleChainCandidate[]; score: number; isPrimary?: boolean; readonly sequence: number; diff --git a/types/ext/anki.d.ts b/types/ext/anki.d.ts index 051cd3e93f..4a903c52a4 100644 --- a/types/ext/anki.d.ts +++ b/types/ext/anki.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/api-map.d.ts b/types/ext/api-map.d.ts index 8c3215cfa2..8b3ba7e56d 100644 --- a/types/ext/api-map.d.ts +++ b/types/ext/api-map.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/api.d.ts b/types/ext/api.d.ts index 46dfbdc295..4f1b9026ff 100644 --- a/types/ext/api.d.ts +++ b/types/ext/api.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -112,6 +112,10 @@ export type GetTermFrequenciesDetailsTermReadingListItem = { }; type ApiSurface = { + applicationReady: { + params: void; + return: void; + }; optionsGet: { params: { optionsContext: Settings.OptionsContext; diff --git a/types/ext/application.d.ts b/types/ext/application.d.ts index 5c103fd73d..903c8e45f1 100644 --- a/types/ext/application.d.ts +++ b/types/ext/application.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -46,10 +46,6 @@ export type ApiSurface = { }; return: void; }; - applicationReady: { - params: void; - return: void; - }; applicationIsReady: { params: void; return: boolean; diff --git a/types/ext/audio-controller.d.ts b/types/ext/audio-controller.d.ts index cc1887fb19..194e399eb1 100644 --- a/types/ext/audio-controller.d.ts +++ b/types/ext/audio-controller.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/audio-downloader.d.ts b/types/ext/audio-downloader.d.ts index dfda8cb9f7..a785c1851c 100644 --- a/types/ext/audio-downloader.d.ts +++ b/types/ext/audio-downloader.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/audio-system.d.ts b/types/ext/audio-system.d.ts index 715f6afe60..0af94b5375 100644 --- a/types/ext/audio-system.d.ts +++ b/types/ext/audio-system.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/audio.d.ts b/types/ext/audio.d.ts index 4bf952c634..bf7489843b 100644 --- a/types/ext/audio.d.ts +++ b/types/ext/audio.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/backend.d.ts b/types/ext/backend.d.ts index 13f3bfe490..ce973630d6 100644 --- a/types/ext/backend.d.ts +++ b/types/ext/backend.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/backup-controller.d.ts b/types/ext/backup-controller.d.ts index d494ceb9c4..2ace4d5c77 100644 --- a/types/ext/backup-controller.d.ts +++ b/types/ext/backup-controller.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/cache-map.d.ts b/types/ext/cache-map.d.ts index fcf969e833..8ad4672c01 100644 --- a/types/ext/cache-map.d.ts +++ b/types/ext/cache-map.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/clipboard-monitor.d.ts b/types/ext/clipboard-monitor.d.ts index b0bb840bf4..5c9d9263c4 100644 --- a/types/ext/clipboard-monitor.d.ts +++ b/types/ext/clipboard-monitor.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/core.d.ts b/types/ext/core.d.ts index 48cb68d330..a18a7bf70a 100644 --- a/types/ext/core.d.ts +++ b/types/ext/core.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/cross-frame-api.d.ts b/types/ext/cross-frame-api.d.ts index 148bfe00d1..a7291ce4b5 100644 --- a/types/ext/cross-frame-api.d.ts +++ b/types/ext/cross-frame-api.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/css-style-applier.d.ts b/types/ext/css-style-applier.d.ts index d4db2da69d..48ce271404 100644 --- a/types/ext/css-style-applier.d.ts +++ b/types/ext/css-style-applier.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/database.d.ts b/types/ext/database.d.ts index e4a58e6011..641ced9425 100644 --- a/types/ext/database.d.ts +++ b/types/ext/database.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/deinflector.d.ts b/types/ext/deinflector.d.ts index e6fc203ea6..4e5f007b5e 100644 --- a/types/ext/deinflector.d.ts +++ b/types/ext/deinflector.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/dictionary-data-util.d.ts b/types/ext/dictionary-data-util.d.ts index 4ab06f11b8..ddf31e4e28 100644 --- a/types/ext/dictionary-data-util.d.ts +++ b/types/ext/dictionary-data-util.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/dictionary-data.d.ts b/types/ext/dictionary-data.d.ts index 0e0edd5c46..45db55bb4f 100644 --- a/types/ext/dictionary-data.d.ts +++ b/types/ext/dictionary-data.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -90,6 +90,11 @@ export type KanjiV3 = [ ]; export type TermGlossary = ( + TermGlossaryContent | + TermGlossaryDeinflection +); + +export type TermGlossaryContent = ( TermGlossaryString | TermGlossaryText | TermGlossaryImage | @@ -100,6 +105,10 @@ export type TermGlossaryString = string; export type TermGlossaryText = {type: 'text', text: string}; export type TermGlossaryImage = {type: 'image'} & TermImage; export type TermGlossaryStructuredContent = {type: 'structured-content', content: StructuredContent.Content}; +export type TermGlossaryDeinflection = [ + uninflected: string, + inflectionRuleChain: string[], +]; export type TermImage = StructuredContent.ImageElementBase & { // Compatibility properties diff --git a/types/ext/dictionary-database.d.ts b/types/ext/dictionary-database.d.ts index 3cf68543cf..2c416c6848 100644 --- a/types/ext/dictionary-database.d.ts +++ b/types/ext/dictionary-database.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/dictionary-importer-media-loader.d.ts b/types/ext/dictionary-importer-media-loader.d.ts index f56d7cf6bf..3cfc7ec49d 100644 --- a/types/ext/dictionary-importer-media-loader.d.ts +++ b/types/ext/dictionary-importer-media-loader.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/dictionary-importer.d.ts b/types/ext/dictionary-importer.d.ts index cda1bd19b4..17bd1bb49e 100644 --- a/types/ext/dictionary-importer.d.ts +++ b/types/ext/dictionary-importer.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,20 +25,13 @@ export type OnProgressCallback = (data: ProgressData) => void; /** * An enum representing the import step. - * - * `-2` `-1` Dictionary import is uninitialized. - * - * `0` Load dictionary archive and validate index step. - * - * `1` Load schemas and get archive files step. - * - * `2` Load and validate dictionary data step. - * - * `3` Format dictionary data and extended data support step. - * - * `4` Resolve async requirements and import media step. - * - * `5` Add dictionary descriptor and import data step. + * - `-2` `-1` - Dictionary import is uninitialized. + * - `0` - Load dictionary archive and validate index step. + * - `1` - Load schemas and get archive files step. + * - `2` - Load and validate dictionary data step. + * - `3` - Format dictionary data and extended data support step. + * - `4` - Resolve async requirements and import media step. + * - `5` - Add dictionary descriptor and import data step. */ export type ImportStep = -2 | -1 | 0 | 1 | 2 | 3 | 4 | 5; @@ -120,9 +113,9 @@ export type ImportRequirementContext = { export type ArchiveFileMap = Map; /** - * A map of file types inside a dictionary and its corresponding regular expressions. + * An array of tuples of a file type inside a dictionary and its corresponding regular expression. */ -export type QueryDetails = Map; +export type QueryDetails = [fileType: string, fileNameFormat: RegExp][]; /** * A map of file types inside a dictionary and its matching entries. diff --git a/types/ext/dictionary-worker-handler.d.ts b/types/ext/dictionary-worker-handler.d.ts index ddd6134c3a..dbdadfedf2 100644 --- a/types/ext/dictionary-worker-handler.d.ts +++ b/types/ext/dictionary-worker-handler.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/dictionary-worker-media-loader.d.ts b/types/ext/dictionary-worker-media-loader.d.ts index fab012834c..575df51d66 100644 --- a/types/ext/dictionary-worker-media-loader.d.ts +++ b/types/ext/dictionary-worker-media-loader.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/dictionary-worker.d.ts b/types/ext/dictionary-worker.d.ts index ac076ab8e6..f10ed4a55c 100644 --- a/types/ext/dictionary-worker.d.ts +++ b/types/ext/dictionary-worker.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/dictionary.d.ts b/types/ext/dictionary.d.ts index 7c348e7f5d..ca6a708a46 100644 --- a/types/ext/dictionary.d.ts +++ b/types/ext/dictionary.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2021-2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -208,9 +208,9 @@ export type TermDictionaryEntry = { */ isPrimary: boolean; /** - * A list of inflections that was applied to get the term. + * Ways that a looked-up word might be an inflected form of this term. */ - inflections: string[]; + inflectionRuleChainCandidates: InflectionRuleChainCandidate[]; /** * A score for the dictionary entry. */ @@ -253,6 +253,15 @@ export type TermDictionaryEntry = { frequencies: TermFrequency[]; }; +export type InflectionRuleChainCandidate = { + source: InflectionSource; + inflectionRules: InflectionRuleChain; +}; + +export type InflectionRuleChain = string[]; + +export type InflectionSource = 'algorithm' | 'dictionary' | 'both'; + /** * A term headword is a combination of a term, reading, and auxiliary information. */ @@ -337,7 +346,7 @@ export type TermDefinition = { /** * The definition entries. */ - entries: DictionaryData.TermGlossary[]; + entries: DictionaryData.TermGlossaryContent[]; }; /** diff --git a/types/ext/display-anki.d.ts b/types/ext/display-anki.d.ts index cc59a5c35b..1b0dd4ff3b 100644 --- a/types/ext/display-anki.d.ts +++ b/types/ext/display-anki.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/display-audio.d.ts b/types/ext/display-audio.d.ts index c9590cf2eb..8c4fcd3ba9 100644 --- a/types/ext/display-audio.d.ts +++ b/types/ext/display-audio.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/display-content-manager.d.ts b/types/ext/display-content-manager.d.ts index dc2269cf46..c0cb183ffe 100644 --- a/types/ext/display-content-manager.d.ts +++ b/types/ext/display-content-manager.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/display-history.d.ts b/types/ext/display-history.d.ts index 3a99d44371..2b507821cd 100644 --- a/types/ext/display-history.d.ts +++ b/types/ext/display-history.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/display.d.ts b/types/ext/display.d.ts index 127823d2b0..8666265974 100644 --- a/types/ext/display.d.ts +++ b/types/ext/display.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/document-util.d.ts b/types/ext/document-util.d.ts index aa655a67e3..3f042c9763 100644 --- a/types/ext/document-util.d.ts +++ b/types/ext/document-util.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/dom-data-binder.d.ts b/types/ext/dom-data-binder.d.ts index 5de941ff30..4835d68600 100644 --- a/types/ext/dom-data-binder.d.ts +++ b/types/ext/dom-data-binder.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/dom-text-scanner.d.ts b/types/ext/dom-text-scanner.d.ts index 33c01aa8a1..24a49974bf 100644 --- a/types/ext/dom-text-scanner.d.ts +++ b/types/ext/dom-text-scanner.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,3 +24,25 @@ * - `3` - Character should be added to the content and is a newline. */ export type CharacterAttributes = 0 | 1 | 2 | 3; + +/** + * Seek information about an element. + * The `enterable` value indicates whether the content of this node should be entered. + * The `newlines` value corresponds to the number of newline characters that should be added. + * - 1 newline corresponds to a simple new line in the layout. + * - 2 newlines corresponds to a significant visual distinction since the previous content. + */ +export type ElementSeekInfo = { + enterable: boolean; + newlines: number; +}; + +/** + * Information about the whitespace. + * `preserveNewlines` indicates whether or not newline characters are treated as line breaks. + * `preserveWhitespace` indicates whether or not sequences of whitespace characters are collapsed. + */ +export type WhitespaceSettings = { + preserveNewlines: boolean; + preserveWhitespace: boolean; +}; diff --git a/types/ext/dynamic-property.d.ts b/types/ext/dynamic-property.d.ts index 8dde372b38..d67008aa44 100644 --- a/types/ext/dynamic-property.d.ts +++ b/types/ext/dynamic-property.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/environment.d.ts b/types/ext/environment.d.ts index 9c0f5bf64f..394c783808 100644 --- a/types/ext/environment.d.ts +++ b/types/ext/environment.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/event-listener-collection.d.ts b/types/ext/event-listener-collection.d.ts index 415c48c435..9d1ae3c1d3 100644 --- a/types/ext/event-listener-collection.d.ts +++ b/types/ext/event-listener-collection.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/extension.d.ts b/types/ext/extension.d.ts index 5c4aa1757f..dc4657f041 100644 --- a/types/ext/extension.d.ts +++ b/types/ext/extension.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,38 +15,6 @@ * along with this program. If not, see . */ -import type * as Core from './core'; - -export type ChromeRuntimeSendMessageArgs1 = [ - message: Core.SafeAny, -]; - -export type ChromeRuntimeSendMessageArgs2 = [ - message: Core.SafeAny, - responseCallback: (response: Core.SafeAny) => void, -]; - -export type ChromeRuntimeSendMessageArgs3 = [ - message: Core.SafeAny, - options: chrome.runtime.MessageOptions, - responseCallback: (response: Core.SafeAny) => void, -]; - -export type ChromeRuntimeSendMessageArgs4 = [ - extensionId: string | undefined | null, - message: Core.SafeAny, - responseCallback: (response: Core.SafeAny) => void, -]; - -export type ChromeRuntimeSendMessageArgs5 = [ - extensionId: string | undefined | null, - message: Core.SafeAny, - options: chrome.runtime.MessageOptions, - responseCallback: (response: Core.SafeAny) => void, -]; - -export type ChromeRuntimeSendMessageArgs = ChromeRuntimeSendMessageArgs1 | ChromeRuntimeSendMessageArgs2 | ChromeRuntimeSendMessageArgs3 | ChromeRuntimeSendMessageArgs4 | ChromeRuntimeSendMessageArgs5; - export type HtmlElementWithContentWindow = HTMLIFrameElement | HTMLFrameElement | HTMLObjectElement; export type ContentOrigin = { diff --git a/types/ext/frame-ancestry-handler.d.ts b/types/ext/frame-ancestry-handler.d.ts index 4eb7f86143..3fc8fb1721 100644 --- a/types/ext/frame-ancestry-handler.d.ts +++ b/types/ext/frame-ancestry-handler.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/frame-client.d.ts b/types/ext/frame-client.d.ts index 54d0d88091..4a22a1868f 100644 --- a/types/ext/frame-client.d.ts +++ b/types/ext/frame-client.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/frame-offset-forwarder.d.ts b/types/ext/frame-offset-forwarder.d.ts index 6cc0aef28f..1224f657c5 100644 --- a/types/ext/frame-offset-forwarder.d.ts +++ b/types/ext/frame-offset-forwarder.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/frontend.d.ts b/types/ext/frontend.d.ts index b06e604021..53a849a247 100644 --- a/types/ext/frontend.d.ts +++ b/types/ext/frontend.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/generic-setting-controller.d.ts b/types/ext/generic-setting-controller.d.ts index f7a2be5f44..ec1dfff05d 100644 --- a/types/ext/generic-setting-controller.d.ts +++ b/types/ext/generic-setting-controller.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/hotkey-handler.d.ts b/types/ext/hotkey-handler.d.ts index c108d347f1..b5b9a86a2a 100644 --- a/types/ext/hotkey-handler.d.ts +++ b/types/ext/hotkey-handler.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/input.d.ts b/types/ext/input.d.ts index fbcb4df36f..ab05f01574 100644 --- a/types/ext/input.d.ts +++ b/types/ext/input.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/japanese-util.d.ts b/types/ext/japanese-util.d.ts index c2d2c41114..22b94e0736 100644 --- a/types/ext/japanese-util.d.ts +++ b/types/ext/japanese-util.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/json-schema.d.ts b/types/ext/json-schema.d.ts index c959628ba2..2d55eee54a 100644 --- a/types/ext/json-schema.d.ts +++ b/types/ext/json-schema.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/keyboard-mouse-input-field.d.ts b/types/ext/keyboard-mouse-input-field.d.ts index 572ffc5619..b53dbfb5ee 100644 --- a/types/ext/keyboard-mouse-input-field.d.ts +++ b/types/ext/keyboard-mouse-input-field.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/keyboard-shortcut-controller.d.ts b/types/ext/keyboard-shortcut-controller.d.ts index 0f276ce941..c0d1b4ec34 100644 --- a/types/ext/keyboard-shortcut-controller.d.ts +++ b/types/ext/keyboard-shortcut-controller.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/log.d.ts b/types/ext/log.d.ts index e8ca4c4390..5b33b2342b 100644 --- a/types/ext/log.d.ts +++ b/types/ext/log.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,12 +25,9 @@ export type LogContext = { /** * An enum representing the log error level. - * - * `0` _log_, _info_, _debug_ level. - * - * `1` _warn_ level. - * - * `2` _error_ level. + * - `0` - _log_, _info_, _debug_ level. + * - `1` - _warn_ level. + * - `2` - _error_ level. */ export type LogErrorLevelValue = 0 | 1 | 2; diff --git a/types/ext/mecab.d.ts b/types/ext/mecab.d.ts index bcbd476f41..fb2475784c 100644 --- a/types/ext/mecab.d.ts +++ b/types/ext/mecab.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/object-property-accessor.d.ts b/types/ext/object-property-accessor.d.ts index 635e94b306..360e6ea453 100644 --- a/types/ext/object-property-accessor.d.ts +++ b/types/ext/object-property-accessor.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/offscreen.d.ts b/types/ext/offscreen.d.ts index 9b1d844ab0..a721c1b952 100644 --- a/types/ext/offscreen.d.ts +++ b/types/ext/offscreen.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/options-util.d.ts b/types/ext/options-util.d.ts index 7242dc222f..62cf5ab6ce 100644 --- a/types/ext/options-util.d.ts +++ b/types/ext/options-util.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/panel-element.d.ts b/types/ext/panel-element.d.ts index 164acba5b2..c61b635814 100644 --- a/types/ext/panel-element.d.ts +++ b/types/ext/panel-element.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/popup-factory.d.ts b/types/ext/popup-factory.d.ts index a167a6f7d7..9c222d7322 100644 --- a/types/ext/popup-factory.d.ts +++ b/types/ext/popup-factory.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/popup-menu.d.ts b/types/ext/popup-menu.d.ts index 71b94468e1..c12a200e73 100644 --- a/types/ext/popup-menu.d.ts +++ b/types/ext/popup-menu.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/popup.d.ts b/types/ext/popup.d.ts index 6f39f1e4f6..4246e24ecc 100644 --- a/types/ext/popup.d.ts +++ b/types/ext/popup.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/profile-conditions-ui.d.ts b/types/ext/profile-conditions-ui.d.ts index 4e326781c0..0193d4d71b 100644 --- a/types/ext/profile-conditions-ui.d.ts +++ b/types/ext/profile-conditions-ui.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/profile-conditions-util.d.ts b/types/ext/profile-conditions-util.d.ts index 1590441042..441c9b978a 100644 --- a/types/ext/profile-conditions-util.d.ts +++ b/types/ext/profile-conditions-util.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/query-parser.d.ts b/types/ext/query-parser.d.ts index ea711d9e51..b106497309 100644 --- a/types/ext/query-parser.d.ts +++ b/types/ext/query-parser.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/request-builder.d.ts b/types/ext/request-builder.d.ts index 41ab522718..96a6392b8f 100644 --- a/types/ext/request-builder.d.ts +++ b/types/ext/request-builder.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/script-manager.d.ts b/types/ext/script-manager.d.ts index 6a1f9e2674..ac8fb5f7b0 100644 --- a/types/ext/script-manager.d.ts +++ b/types/ext/script-manager.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/search-persistent-state-controller.d.ts b/types/ext/search-persistent-state-controller.d.ts index 47b07119c5..7ee706ab89 100644 --- a/types/ext/search-persistent-state-controller.d.ts +++ b/types/ext/search-persistent-state-controller.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/selector-observer.d.ts b/types/ext/selector-observer.d.ts index 2b3e9381b3..8cdedca4a5 100644 --- a/types/ext/selector-observer.d.ts +++ b/types/ext/selector-observer.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/settings-controller.d.ts b/types/ext/settings-controller.d.ts index e0633c219f..aa6d29499d 100644 --- a/types/ext/settings-controller.d.ts +++ b/types/ext/settings-controller.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/settings-modifications.d.ts b/types/ext/settings-modifications.d.ts index 5541383afe..b052ba301c 100644 --- a/types/ext/settings-modifications.d.ts +++ b/types/ext/settings-modifications.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/settings.d.ts b/types/ext/settings.d.ts index 25ea46d9fc..96440823b9 100644 --- a/types/ext/settings.d.ts +++ b/types/ext/settings.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -258,6 +258,8 @@ export type DictionaryOptions = { enabled: boolean; allowSecondarySearches: boolean; definitionsCollapsible: DictionaryDefinitionsCollapsible; + partsOfSpeechFilter: boolean; + useDeinflections: boolean; }; export type ParsingOptions = { diff --git a/types/ext/simple-dom-parser.d.ts b/types/ext/simple-dom-parser.d.ts index 971871a2b1..0e58e26757 100644 --- a/types/ext/simple-dom-parser.d.ts +++ b/types/ext/simple-dom-parser.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/structured-content.d.ts b/types/ext/structured-content.d.ts index c9ad87f628..4f613969b9 100644 --- a/types/ext/structured-content.d.ts +++ b/types/ext/structured-content.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -63,6 +63,7 @@ export type StructuredContentStyle = { borderWidth?: string; verticalAlign?: VerticalAlign; textAlign?: TextAlign; + textShadow?: string; margin?: string; marginTop?: number | string; marginLeft?: number | string; diff --git a/types/ext/task-accumulator.d.ts b/types/ext/task-accumulator.d.ts index e96ba6a609..38c077483a 100644 --- a/types/ext/task-accumulator.d.ts +++ b/types/ext/task-accumulator.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/template-patcher.d.ts b/types/ext/template-patcher.d.ts index 852d3768ab..64a28e67b9 100644 --- a/types/ext/template-patcher.d.ts +++ b/types/ext/template-patcher.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/template-renderer-proxy.d.ts b/types/ext/template-renderer-proxy.d.ts index 74e95b54ef..6c2f761980 100644 --- a/types/ext/template-renderer-proxy.d.ts +++ b/types/ext/template-renderer-proxy.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/template-renderer.d.ts b/types/ext/template-renderer.d.ts index 52c6f745b0..3f63ea42ab 100644 --- a/types/ext/template-renderer.d.ts +++ b/types/ext/template-renderer.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/text-scanner.d.ts b/types/ext/text-scanner.d.ts index e2779e4d69..ff56b443fb 100644 --- a/types/ext/text-scanner.d.ts +++ b/types/ext/text-scanner.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -197,14 +197,10 @@ export type PointerEventType = ( /** * An enum representing the pen pointer state. - * - * `0` Not active. - * - * `1` Hovering. - * - * `2` Touching. - * - * `3` Hovering after touching. + * - `0` - Not active. + * - `1` - Hovering. + * - `2` - Touching. + * - `3` - Hovering after touching. */ export type PenPointerState = 0 | 1 | 2 | 3; diff --git a/types/ext/text-source.d.ts b/types/ext/text-source.d.ts index 7e085176c0..f2a4869b1a 100644 --- a/types/ext/text-source.d.ts +++ b/types/ext/text-source.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/translation-internal.d.ts b/types/ext/translation-internal.d.ts index 5845b45d44..ada611d9a4 100644 --- a/types/ext/translation-internal.d.ts +++ b/types/ext/translation-internal.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -16,6 +16,7 @@ */ import type * as DictionaryDatabase from './dictionary-database'; +import type * as Dictionary from './dictionary'; import type * as Translation from './translation'; export type TextDeinflectionOptions = [ @@ -52,7 +53,7 @@ export enum DeinflectionRuleFlags { export type Deinflection = { term: string; rules: DeinflectionRuleFlags; - reasons: string[]; + reasons: Dictionary.InflectionRuleChain; }; export type DatabaseDeinflection = { @@ -60,6 +61,6 @@ export type DatabaseDeinflection = { transformedText: string; deinflectedText: string; rules: DeinflectionRuleFlags; - reasons: string[]; + inflectionRuleChainCandidates: Dictionary.InflectionRuleChainCandidate[]; databaseEntries: DictionaryDatabase.TermEntry[]; }; diff --git a/types/ext/translation.d.ts b/types/ext/translation.d.ts index c8938e0001..604dbda6d4 100644 --- a/types/ext/translation.d.ts +++ b/types/ext/translation.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * Copyright (C) 2022 Yomichan Authors * * This program is free software: you can redistribute it and/or modify @@ -173,6 +173,14 @@ export type FindTermDictionary = { * Whether or not secondary term searches are allowed for this dictionary. */ allowSecondarySearches: boolean; + /** + * Whether this dictionary's part of speech rules should be used to filter results. + */ + partsOfSpeechFilter: boolean; + /** + * Whether to use the deinflections from this dictionary. + */ + useDeinflections: boolean; }; export type TermEnabledDictionaryMap = Map; diff --git a/types/ext/translator.d.ts b/types/ext/translator.d.ts index f1eb6bccf4..65a77e908e 100644 --- a/types/ext/translator.d.ts +++ b/types/ext/translator.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/ext/web-extension.d.ts b/types/ext/web-extension.d.ts new file mode 100644 index 0000000000..287e7a72ea --- /dev/null +++ b/types/ext/web-extension.d.ts @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2024 Yomitan Authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +export type Events = { + unloaded: Record; +}; diff --git a/types/other/globals.d.ts b/types/other/globals.d.ts index 4f6db6c990..9b75db0415 100644 --- a/types/other/globals.d.ts +++ b/types/other/globals.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/other/rollup-parse-ast.d.ts b/types/other/rollup-parse-ast.d.ts index 52a5ec98e8..d798f09ea1 100644 --- a/types/other/rollup-parse-ast.d.ts +++ b/types/other/rollup-parse-ast.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/test/core.d.ts b/types/test/core.d.ts index c01737e6bf..eec39990b6 100644 --- a/types/test/core.d.ts +++ b/types/test/core.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/test/database.d.ts b/types/test/database.d.ts index c24723e1bd..3c760ec34e 100644 --- a/types/test/database.d.ts +++ b/types/test/database.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/test/document-types.d.ts b/types/test/document-types.d.ts index 44831397f0..6b63f171b6 100644 --- a/types/test/document-types.d.ts +++ b/types/test/document-types.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/test/document-util.d.ts b/types/test/document-util.d.ts index 3c09f7f066..c59c2bf58f 100644 --- a/types/test/document-util.d.ts +++ b/types/test/document-util.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/test/dom-text-scanner.d.ts b/types/test/dom-text-scanner.d.ts index 988cb69090..ebbbab7082 100644 --- a/types/test/dom-text-scanner.d.ts +++ b/types/test/dom-text-scanner.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/test/json.d.ts b/types/test/json.d.ts index 9ae21b0f66..1b4fb91983 100644 --- a/types/test/json.d.ts +++ b/types/test/json.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/test/mocks.d.ts b/types/test/mocks.d.ts index 13b56ac64b..727f2c6301 100644 --- a/types/test/mocks.d.ts +++ b/types/test/mocks.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/types/test/translator.d.ts b/types/test/translator.d.ts index 7dff116efc..b213f9e0b5 100644 --- a/types/test/translator.d.ts +++ b/types/test/translator.d.ts @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/vitest.config.js b/vitest.config.js index fff50b6c58..e0e72e6316 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2023-2024 Yomitan Authors * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by