From 139ad7df43dd64acb6dd23f088f978cc5b0988a4 Mon Sep 17 00:00:00 2001 From: James Maa Date: Mon, 28 Oct 2024 11:53:52 -0700 Subject: [PATCH 01/17] Upgrade publish-edge (#1521) * Upgrade publish-edge * Hardcode value * Bump version * Undo hardcode --- .github/workflows/publish-edge.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-edge.yml b/.github/workflows/publish-edge.yml index 88a4118cdf..f3fc289981 100644 --- a/.github/workflows/publish-edge.yml +++ b/.github/workflows/publish-edge.yml @@ -15,11 +15,10 @@ jobs: fileName: "*" - name: Publish on Microsoft Edge Addons - uses: wdzeng/edge-addon@v1 + uses: wdzeng/edge-addon@v2 id: webStorePublish with: product-id: 18e6c4cd-6383-4f38-95e9-92a629f60817 zip-path: yomitan-edge.zip client-id: ${{ secrets.EDGE_CLIENT_ID }} - client-secret: ${{ secrets.EDGE_CLIENT_SECRET }} - access-token-url: ${{ secrets.EDGE_ACCESS_TOKEN_URL }} \ No newline at end of file + api-key: ${{ secrets.EDGE_API_KEY }} \ No newline at end of file From 7ed53c16d69d720b0e1139e14ef36dbdc6a93d15 Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Mon, 28 Oct 2024 17:58:39 -0400 Subject: [PATCH 02/17] Allow scanning without moving mouse from clicking (#1538) * Allow scanning without moving mouse from clicking * Fix clicking or tapping to hide popup --- ext/js/display/display.js | 2 - ext/js/display/query-parser.js | 1 - ext/js/language/text-scanner.js | 68 ++------------------------------- types/ext/text-scanner.d.ts | 2 - 4 files changed, 4 insertions(+), 69 deletions(-) diff --git a/ext/js/display/display.js b/ext/js/display/display.js index 3f6d893a8d..af81bed84d 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -2011,8 +2011,6 @@ export class Display extends EventDispatcher { getSearchContext: this._getSearchContext.bind(this), searchTerms: true, searchKanji: false, - searchOnClick: true, - searchOnClickOnly: true, textSourceGenerator: this._textSourceGenerator, }); this._contentTextScanner.includeSelector = '.click-scannable,.click-scannable *'; diff --git a/ext/js/display/query-parser.js b/ext/js/display/query-parser.js index cf487a3d35..1221cca319 100644 --- a/ext/js/display/query-parser.js +++ b/ext/js/display/query-parser.js @@ -66,7 +66,6 @@ export class QueryParser extends EventDispatcher { getSearchContext, searchTerms: true, searchKanji: false, - searchOnClick: true, textSourceGenerator, }); /** @type {?(import('../language/ja/japanese-wanakana.js'))} */ diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index 58c6b1f294..ea059be856 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -39,8 +39,6 @@ export class TextScanner extends EventDispatcher { ignorePoint = null, searchTerms = false, searchKanji = false, - searchOnClick = false, - searchOnClickOnly = false, textSourceGenerator, }) { super(); @@ -58,10 +56,6 @@ export class TextScanner extends EventDispatcher { this._searchTerms = searchTerms; /** @type {boolean} */ this._searchKanji = searchKanji; - /** @type {boolean} */ - this._searchOnClick = searchOnClick; - /** @type {boolean} */ - this._searchOnClickOnly = searchOnClickOnly; /** @type {import('../dom/text-source-generator').TextSourceGenerator} */ this._textSourceGenerator = textSourceGenerator; @@ -662,7 +656,6 @@ export class TextScanner extends EventDispatcher { switch (e.button) { case 0: // Primary - if (this._searchOnClick) { this._resetPreventNextClickScan(); } this._scanTimerClear(); this._triggerClear('mousedown'); break; @@ -674,6 +667,8 @@ export class TextScanner extends EventDispatcher { } break; } + + this._onMouseMove(e); } /** */ @@ -694,28 +689,7 @@ export class TextScanner extends EventDispatcher { return false; } - if (this._searchOnClick) { - this._onSearchClick(e); - } - } - - /** - * @param {MouseEvent} e - */ - _onSearchClick(e) { - const preventNextClickScan = this._preventNextClickScan; - this._preventNextClickScan = false; - if (this._preventNextClickScanTimer !== null) { - clearTimeout(this._preventNextClickScanTimer); - this._preventNextClickScanTimer = null; - } - - if (preventNextClickScan) { return; } - - const modifiers = getActiveModifiersAndButtons(e); - const modifierKeys = getActiveModifiers(e); - const inputInfo = this._createInputInfo(null, 'mouse', 'click', false, modifiers, modifierKeys); - void this._searchAt(e.clientX, e.clientY, inputInfo); + this._onMouseMove(e); } /** */ @@ -1141,9 +1115,7 @@ export class TextScanner extends EventDispatcher { const capture = true; /** @type {import('event-listener-collection').AddEventListenerArgs[]} */ let eventListenerInfos; - if (this._searchOnClickOnly) { - eventListenerInfos = this._getMouseClickOnlyEventListeners(capture); - } else if (this._arePointerEventsSupported()) { + if (this._arePointerEventsSupported()) { eventListenerInfos = this._getPointerEventListeners(capture); } else { eventListenerInfos = [...this._getMouseEventListeners(capture)]; @@ -1154,9 +1126,6 @@ export class TextScanner extends EventDispatcher { eventListenerInfos.push(...this._getTouchEventListeners(capture)); } } - if (this._searchOnClick) { - eventListenerInfos.push(...this._getMouseClickOnlyEventListeners2(capture)); - } eventListenerInfos.push(this._getSelectionChangeCheckUserSelectionListener()); @@ -1223,35 +1192,6 @@ export class TextScanner extends EventDispatcher { ]; } - /** - * @param {boolean} capture - * @returns {import('event-listener-collection').AddEventListenerArgs[]} - */ - _getMouseClickOnlyEventListeners(capture) { - return [ - [this._node, 'click', this._onClick.bind(this), capture], - ]; - } - - /** - * @param {boolean} capture - * @returns {import('event-listener-collection').AddEventListenerArgs[]} - */ - _getMouseClickOnlyEventListeners2(capture) { - const {documentElement} = document; - /** @type {import('event-listener-collection').AddEventListenerArgs[]} */ - const entries = [ - [document, 'selectionchange', this._onSelectionChange.bind(this)], - ]; - if (documentElement !== null) { - entries.push([documentElement, 'mousedown', this._onSearchClickMouseDown.bind(this), capture]); - if (this._touchInputEnabled) { - entries.push([documentElement, 'touchstart', this._onSearchClickTouchStart.bind(this), {passive: true, capture}]); - } - } - return entries; - } - /** * @returns {import('event-listener-collection').AddEventListenerArgs} */ diff --git a/types/ext/text-scanner.d.ts b/types/ext/text-scanner.d.ts index 36cff9a4ae..859bfca270 100644 --- a/types/ext/text-scanner.d.ts +++ b/types/ext/text-scanner.d.ts @@ -154,8 +154,6 @@ export type ConstructorDetails = { ignorePoint?: ((x: number, y: number) => Promise) | null; searchTerms?: boolean; searchKanji?: boolean; - searchOnClick?: boolean; - searchOnClickOnly?: boolean; textSourceGenerator: TextSourceGenerator; }; From e3f279c345503f2f23c6bd51d0315d3bc681ced0 Mon Sep 17 00:00:00 2001 From: marv Date: Tue, 29 Oct 2024 00:11:45 -0700 Subject: [PATCH 03/17] Use Playwright Report Summary Action (#1543) * Use Playwright Report Summary Action * Move comment logic * Use output summary * Remove if statement * Remove comments --- .github/workflows/playwright.yml | 6 ++++++ .github/workflows/playwright_comment.yml | 23 +++++++++++++++-------- playwright.config.js | 5 ++++- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index a792076a98..924d2481ee 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -106,3 +106,9 @@ jobs: name: playwright-output path: playwright-output if: github.event_name == 'pull_request' + + - uses: actions/upload-artifact@v4 + with: + name: playwright-results-json + path: playwright-results.json + if: github.event_name == 'pull_request' diff --git a/.github/workflows/playwright_comment.yml b/.github/workflows/playwright_comment.yml index 8b3d5bb69b..5ae1adf853 100644 --- a/.github/workflows/playwright_comment.yml +++ b/.github/workflows/playwright_comment.yml @@ -35,6 +35,19 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} run_id: ${{ github.event.workflow_run.id }} name: master-screenshots-outcome + - name: Grab playwright-results-json from PR run + uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # pin@v2 + continue-on-error: true + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + run_id: ${{ github.event.workflow_run.id }} + name: playwright-results-json + + - name: Generate summary from playwright-results.json (expected to fail to comment) + id: playwright-summary + uses: daun/playwright-report-summary@v3 + with: + report-file: playwright-results.json - name: Load artifacts into environment variables id: playwright @@ -70,18 +83,12 @@ jobs: if: steps.playwright.outputs.MASTER_SCREENSHOTS_OUTCOME != 'failure' && (steps.playwright.outputs.FLAKY != 0 || steps.playwright.outputs.FAILED != 0) with: issue: ${{ steps.source-run-info.outputs.pullRequestNumber }} - message: | - :warning: Flaky tests or visual differences caused by this PR; please check the playwright report. - - View Playwright Report (note: open the "playwright-report" artifact) + message: ${{ steps.playwright-summary.outputs.summary }} - name: "[Comment] Success: No visual differences introduced by this PR" uses: mshick/add-pr-comment@dd126dd8c253650d181ad9538d8b4fa218fc31e8 # pin@v2 if: steps.playwright.outputs.MASTER_SCREENSHOTS_OUTCOME != 'failure' && steps.playwright.outputs.FLAKY == 0 && steps.playwright.outputs.FAILED == 0 with: issue: ${{ steps.source-run-info.outputs.pullRequestNumber }} - message: | - :heavy_check_mark: No visual differences introduced by this PR. - - View Playwright Report (note: open the "playwright-report" artifact) + message: ${{ steps.playwright-summary.outputs.summary }} update-only: true \ No newline at end of file diff --git a/playwright.config.js b/playwright.config.js index acfceb1d19..dfbec44b67 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -47,7 +47,10 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : void 0, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + reporter: [ + ['html'], + ['json', {outputFile: 'playwright-results.json'}], + ], /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ From 0fd70090fb755ba067465c69d8c3bedeb587a69d Mon Sep 17 00:00:00 2001 From: Khai Truong <56820749+khaitruong922@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:23:16 +0700 Subject: [PATCH 04/17] Sort entries by primary reading (#1497) * Search term by exact reading when clicking on gloss link * lint * update typing * type * sort by reading match * fix lint and test * add test * fix typing * rename * refactor * refactor * rename to primary reading * remove extract reading implementation --- ext/js/background/backend.js | 8 +- ext/js/display/display.js | 13 +- ext/js/language/translator.js | 68 ++- test/data/anki-note-builder-test-results.json | 178 +++++++ test/data/database-test-cases.json | 6 +- .../valid-dictionary1/term_bank_1.json | 4 +- test/data/translator-test-inputs.json | 26 + .../translator-test-results-note-data1.json | 458 ++++++++++++++++++ test/data/translator-test-results.json | 450 +++++++++++++++++ test/utilities/translator.js | 2 + types/ext/api.d.ts | 1 + types/ext/dictionary.d.ts | 4 + types/ext/translation.d.ts | 4 + types/test/translator.d.ts | 1 + 14 files changed, 1188 insertions(+), 35 deletions(-) diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 8fe408ee9a..ceb736c40b 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -1463,7 +1463,9 @@ export class Backend { /** @type {import('translator').FindTermsMode} */ const mode = 'simple'; const options = this._getProfileOptions(optionsContext, false); - const details = {matchType: /** @type {import('translation').FindTermsMatchType} */ ('exact'), deinflect: true}; + + /** @type {import('api').FindTermsDetails} */ + const details = {matchType: 'exact', deinflect: true}; const findTermsOptions = this._getTranslatorFindTermsOptions(mode, details, options); /** @type {import('api').ParseTextLine[]} */ const results = []; @@ -2455,9 +2457,10 @@ export class Backend { * @returns {import('translation').FindTermsOptions} An options object. */ _getTranslatorFindTermsOptions(mode, details, options) { - let {matchType, deinflect} = details; + let {matchType, deinflect, primaryReading} = details; if (typeof matchType !== 'string') { matchType = /** @type {import('translation').FindTermsMatchType} */ ('exact'); } if (typeof deinflect !== 'boolean') { deinflect = true; } + if (typeof primaryReading !== 'string') { primaryReading = ''; } const enabledDictionaryMap = this._getTranslatorEnabledDictionaryMap(options); const { general: {mainDictionary, sortFrequencyDictionary, sortFrequencyDictionaryOrder, language}, @@ -2484,6 +2487,7 @@ export class Backend { return { matchType, deinflect, + primaryReading, mainDictionary, sortFrequencyDictionary, sortFrequencyDictionaryOrder, diff --git a/ext/js/display/display.js b/ext/js/display/display.js index af81bed84d..14b943e22e 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -1265,14 +1265,15 @@ export class Display extends EventDispatcher { /** * @param {boolean} isKanji * @param {string} source + * @param {string} primaryReading * @param {boolean} wildcardsEnabled * @param {import('settings').OptionsContext} optionsContext * @returns {Promise} */ - async _findDictionaryEntries(isKanji, source, wildcardsEnabled, optionsContext) { + async _findDictionaryEntries(isKanji, source, primaryReading, wildcardsEnabled, optionsContext) { /** @type {import('dictionary').DictionaryEntry[]} */ let dictionaryEntries = []; - const {findDetails, source: source2} = this._getFindDetails(source, wildcardsEnabled); + const {findDetails, source: source2} = this._getFindDetails(source, primaryReading, wildcardsEnabled); if (isKanji) { dictionaryEntries = await this._application.api.kanjiFind(source, optionsContext); if (dictionaryEntries.length > 0) { return dictionaryEntries; } @@ -1289,12 +1290,13 @@ export class Display extends EventDispatcher { /** * @param {string} source + * @param {string} primaryReading * @param {boolean} wildcardsEnabled * @returns {{findDetails: import('api').FindTermsDetails, source: string}} */ - _getFindDetails(source, wildcardsEnabled) { + _getFindDetails(source, primaryReading, wildcardsEnabled) { /** @type {import('api').FindTermsDetails} */ - const findDetails = {}; + const findDetails = {primaryReading}; if (wildcardsEnabled) { const match = /^([*\uff0a]*)([\w\W]*?)([*\uff0a]*)$/.exec(source); if (match !== null) { @@ -1327,6 +1329,7 @@ export class Display extends EventDispatcher { if (query === null) { query = ''; } let queryFull = urlSearchParams.get('full'); queryFull = (queryFull !== null ? queryFull : query); + const primaryReading = urlSearchParams.get('primary_reading') ?? ''; const queryOffsetString = urlSearchParams.get('offset'); let queryOffset = 0; if (queryOffsetString !== null) { @@ -1358,7 +1361,7 @@ export class Display extends EventDispatcher { let {dictionaryEntries} = content; if (!Array.isArray(dictionaryEntries)) { - dictionaryEntries = hasEnabledDictionaries && lookup && query.length > 0 ? await this._findDictionaryEntries(type === 'kanji', query, wildcardsEnabled, optionsContext) : []; + dictionaryEntries = hasEnabledDictionaries && lookup && query.length > 0 ? await this._findDictionaryEntries(type === 'kanji', query, primaryReading, wildcardsEnabled, optionsContext) : []; if (this._setContentToken !== token) { return; } content.dictionaryEntries = dictionaryEntries; changeHistory = true; diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 21719da3d1..c5ce979074 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -76,13 +76,13 @@ export class Translator { * @returns {Promise<{dictionaryEntries: import('dictionary').TermDictionaryEntry[], originalTextLength: number}>} An object containing dictionary entries and the length of the original source text. */ async findTerms(mode, text, options) { - const {enabledDictionaryMap, excludeDictionaryDefinitions, sortFrequencyDictionary, sortFrequencyDictionaryOrder, language} = options; + const {enabledDictionaryMap, excludeDictionaryDefinitions, sortFrequencyDictionary, sortFrequencyDictionaryOrder, language, primaryReading} = options; const tagAggregator = new TranslatorTagAggregator(); - let {dictionaryEntries, originalTextLength} = await this._findTermsInternal(text, options, tagAggregator); + let {dictionaryEntries, originalTextLength} = await this._findTermsInternal(text, options, tagAggregator, primaryReading); switch (mode) { case 'group': - dictionaryEntries = this._groupDictionaryEntriesByHeadword(language, dictionaryEntries, tagAggregator); + dictionaryEntries = this._groupDictionaryEntriesByHeadword(language, dictionaryEntries, tagAggregator, primaryReading); break; case 'merge': dictionaryEntries = await this._getRelatedDictionaryEntries(dictionaryEntries, options, tagAggregator); @@ -217,9 +217,10 @@ export class Translator { * @param {string} text * @param {import('translation').FindTermsOptions} options * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading * @returns {Promise<{dictionaryEntries: import('translation-internal').TermDictionaryEntry[], originalTextLength: number}>} */ - async _findTermsInternal(text, options, tagAggregator) { + async _findTermsInternal(text, options, tagAggregator, primaryReading) { const {removeNonJapaneseCharacters, enabledDictionaryMap} = options; if (removeNonJapaneseCharacters && (['ja', 'zh', 'yue'].includes(options.language))) { text = this._getJapaneseChineseOnlyText(text); @@ -230,16 +231,17 @@ export class Translator { const deinflections = await this._getDeinflections(text, options); - return this._getDictionaryEntries(deinflections, enabledDictionaryMap, tagAggregator); + return this._getDictionaryEntries(deinflections, enabledDictionaryMap, tagAggregator, primaryReading); } /** * @param {import('translation-internal').DatabaseDeinflection[]} deinflections * @param {import('translation').TermEnabledDictionaryMap} enabledDictionaryMap * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading * @returns {{dictionaryEntries: import('translation-internal').TermDictionaryEntry[], originalTextLength: number}} */ - _getDictionaryEntries(deinflections, enabledDictionaryMap, tagAggregator) { + _getDictionaryEntries(deinflections, enabledDictionaryMap, tagAggregator, primaryReading) { let originalTextLength = 0; /** @type {import('translation-internal').TermDictionaryEntry[]} */ const dictionaryEntries = []; @@ -261,13 +263,13 @@ export class Translator { continue; } if (transformedText.length > existingTransformedLength) { - dictionaryEntries.splice(existingIndex, 1, this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, true, enabledDictionaryMap, tagAggregator)); + dictionaryEntries.splice(existingIndex, 1, this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, true, enabledDictionaryMap, tagAggregator, primaryReading)); } else { this._mergeInflectionRuleChains(existingEntry, inflectionRuleChainCandidates); this._mergeTextProcessorRuleChains(existingEntry, textProcessorRuleChainCandidates); } } else { - const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, true, enabledDictionaryMap, tagAggregator); + const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, true, enabledDictionaryMap, tagAggregator, primaryReading); dictionaryEntries.push(dictionaryEntry); ids.add(id); } @@ -680,7 +682,7 @@ export class Translator { * @returns {Promise} */ async _getRelatedDictionaryEntries(dictionaryEntries, options, tagAggregator) { - const {mainDictionary, enabledDictionaryMap, language} = options; + const {mainDictionary, enabledDictionaryMap, language, primaryReading} = options; /** @type {import('translator').SequenceQuery[]} */ const sequenceList = []; /** @type {import('translation-internal').DictionaryEntryGroup[]} */ @@ -711,20 +713,20 @@ export class Translator { if (sequenceList.length > 0) { const secondarySearchDictionaryMap = this._getSecondarySearchDictionaryMap(enabledDictionaryMap); - await this._addRelatedDictionaryEntries(groupedDictionaryEntries, ungroupedDictionaryEntriesMap, sequenceList, enabledDictionaryMap, tagAggregator); + await this._addRelatedDictionaryEntries(groupedDictionaryEntries, ungroupedDictionaryEntriesMap, sequenceList, enabledDictionaryMap, tagAggregator, primaryReading); for (const group of groupedDictionaryEntries) { this._sortTermDictionaryEntriesById(group.dictionaryEntries); } if (ungroupedDictionaryEntriesMap.size > 0 || secondarySearchDictionaryMap.size > 0) { - await this._addSecondaryRelatedDictionaryEntries(language, groupedDictionaryEntries, ungroupedDictionaryEntriesMap, enabledDictionaryMap, secondarySearchDictionaryMap, tagAggregator); + await this._addSecondaryRelatedDictionaryEntries(language, groupedDictionaryEntries, ungroupedDictionaryEntriesMap, enabledDictionaryMap, secondarySearchDictionaryMap, tagAggregator, primaryReading); } } const newDictionaryEntries = []; for (const group of groupedDictionaryEntries) { - newDictionaryEntries.push(this._createGroupedDictionaryEntry(language, group.dictionaryEntries, true, tagAggregator)); + newDictionaryEntries.push(this._createGroupedDictionaryEntry(language, group.dictionaryEntries, true, tagAggregator, primaryReading)); } - newDictionaryEntries.push(...this._groupDictionaryEntriesByHeadword(language, ungroupedDictionaryEntriesMap.values(), tagAggregator)); + newDictionaryEntries.push(...this._groupDictionaryEntriesByHeadword(language, ungroupedDictionaryEntriesMap.values(), tagAggregator, primaryReading)); return newDictionaryEntries; } @@ -734,8 +736,9 @@ export class Translator { * @param {import('translator').SequenceQuery[]} sequenceList * @param {import('translation').TermEnabledDictionaryMap} enabledDictionaryMap * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading */ - async _addRelatedDictionaryEntries(groupedDictionaryEntries, ungroupedDictionaryEntriesMap, sequenceList, enabledDictionaryMap, tagAggregator) { + async _addRelatedDictionaryEntries(groupedDictionaryEntries, ungroupedDictionaryEntriesMap, sequenceList, enabledDictionaryMap, tagAggregator, primaryReading) { const databaseEntries = await this._database.findTermsBySequenceBulk(sequenceList); for (const databaseEntry of databaseEntries) { const {dictionaryEntries, ids} = groupedDictionaryEntries[databaseEntry.index]; @@ -743,7 +746,7 @@ export class Translator { if (ids.has(id)) { continue; } const {term} = databaseEntry; - const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, term, term, term, [], [], false, enabledDictionaryMap, tagAggregator); + const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, term, term, term, [], [], false, enabledDictionaryMap, tagAggregator, primaryReading); dictionaryEntries.push(dictionaryEntry); ids.add(id); ungroupedDictionaryEntriesMap.delete(id); @@ -757,8 +760,9 @@ export class Translator { * @param {import('translation').TermEnabledDictionaryMap} enabledDictionaryMap * @param {import('translation').TermEnabledDictionaryMap} secondarySearchDictionaryMap * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading */ - async _addSecondaryRelatedDictionaryEntries(language, groupedDictionaryEntries, ungroupedDictionaryEntriesMap, enabledDictionaryMap, secondarySearchDictionaryMap, tagAggregator) { + async _addSecondaryRelatedDictionaryEntries(language, groupedDictionaryEntries, ungroupedDictionaryEntriesMap, enabledDictionaryMap, secondarySearchDictionaryMap, tagAggregator, primaryReading) { // Prepare grouping info /** @type {import('dictionary-database').TermExactRequest[]} */ const termList = []; @@ -816,7 +820,7 @@ export class Translator { for (const {ids, dictionaryEntries} of target.groups) { if (ids.has(id)) { continue; } - const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, sourceText, sourceText, sourceText, [], [], false, enabledDictionaryMap, tagAggregator); + const dictionaryEntry = this._createTermDictionaryEntryFromDatabaseEntry(databaseEntry, sourceText, sourceText, sourceText, [], [], false, enabledDictionaryMap, tagAggregator, primaryReading); dictionaryEntries.push(dictionaryEntry); ids.add(id); ungroupedDictionaryEntriesMap.delete(id); @@ -828,9 +832,10 @@ export class Translator { * @param {string} language * @param {Iterable} dictionaryEntries * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading * @returns {import('translation-internal').TermDictionaryEntry[]} */ - _groupDictionaryEntriesByHeadword(language, dictionaryEntries, tagAggregator) { + _groupDictionaryEntriesByHeadword(language, dictionaryEntries, tagAggregator, primaryReading) { /** @type {Map} */ const groups = new Map(); const readingNormalizer = this._readingNormalizers.get(language); @@ -848,7 +853,7 @@ export class Translator { const newDictionaryEntries = []; for (const groupDictionaryEntries of groups.values()) { - newDictionaryEntries.push(this._createGroupedDictionaryEntry(language, groupDictionaryEntries, false, tagAggregator)); + newDictionaryEntries.push(this._createGroupedDictionaryEntry(language, groupDictionaryEntries, false, tagAggregator, primaryReading)); } return newDictionaryEntries; } @@ -1668,12 +1673,13 @@ export class Translator { * @param {string} dictionaryAlias * @param {number} dictionaryPriority * @param {number} sourceTermExactMatchCount + * @param {boolean} matchPrimaryReading * @param {number} maxOriginalTextLength * @param {import('dictionary').TermHeadword[]} headwords * @param {import('dictionary').TermDefinition[]} definitions * @returns {import('translation-internal').TermDictionaryEntry} */ - _createTermDictionaryEntry(isPrimary, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, score, dictionaryIndex, dictionaryAlias, dictionaryPriority, sourceTermExactMatchCount, maxOriginalTextLength, headwords, definitions) { + _createTermDictionaryEntry(isPrimary, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, score, dictionaryIndex, dictionaryAlias, dictionaryPriority, sourceTermExactMatchCount, matchPrimaryReading, maxOriginalTextLength, headwords, definitions) { return { type: 'term', isPrimary, @@ -1685,6 +1691,7 @@ export class Translator { dictionaryAlias, dictionaryPriority, sourceTermExactMatchCount, + matchPrimaryReading, maxOriginalTextLength, headwords, definitions, @@ -1703,9 +1710,10 @@ export class Translator { * @param {boolean} isPrimary * @param {Map} enabledDictionaryMap * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading * @returns {import('translation-internal').TermDictionaryEntry} */ - _createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, isPrimary, enabledDictionaryMap, tagAggregator) { + _createTermDictionaryEntryFromDatabaseEntry(databaseEntry, originalText, transformedText, deinflectedText, textProcessorRuleChainCandidates, inflectionRuleChainCandidates, isPrimary, enabledDictionaryMap, tagAggregator, primaryReading) { const { matchType, matchSource, @@ -1723,6 +1731,7 @@ export class Translator { // 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 matchPrimaryReading = primaryReading.length > 0 && reading === primaryReading; const {index: dictionaryIndex, priority: dictionaryPriority} = this._getDictionaryOrder(dictionary, enabledDictionaryMap); const dictionaryAlias = this._getDictionaryAlias(dictionary, enabledDictionaryMap); const sourceTermExactMatchCount = (isPrimary && deinflectedText === term ? 1 : 0); @@ -1747,6 +1756,7 @@ export class Translator { dictionaryAlias, dictionaryPriority, sourceTermExactMatchCount, + matchPrimaryReading, maxOriginalTextLength, [this._createTermHeadword(0, term, reading, [source], headwordTagGroups, rules)], [this._createTermDefinition(0, [0], dictionary, dictionaryIndex, dictionaryAlias, dictionaryPriority, id, score, [sequence], isPrimary, definitionTagGroups, contentDefinitions)], @@ -1758,9 +1768,10 @@ export class Translator { * @param {import('translation-internal').TermDictionaryEntry[]} dictionaryEntries * @param {boolean} checkDuplicateDefinitions * @param {TranslatorTagAggregator} tagAggregator + * @param {string} primaryReading * @returns {import('translation-internal').TermDictionaryEntry} */ - _createGroupedDictionaryEntry(language, dictionaryEntries, checkDuplicateDefinitions, tagAggregator) { + _createGroupedDictionaryEntry(language, dictionaryEntries, checkDuplicateDefinitions, tagAggregator, primaryReading) { // Headwords are generated before sorting, so that the order of dictionaryEntries can be maintained const definitionEntries = []; /** @type {Map} */ @@ -1820,7 +1831,11 @@ export class Translator { const headwordsArray = [...headwords.values()]; let sourceTermExactMatchCount = 0; - for (const {sources} of headwordsArray) { + let matchPrimaryReading = false; + for (const {sources, reading} of headwordsArray) { + if (primaryReading.length > 0 && reading === primaryReading) { + matchPrimaryReading = true; + } for (const source of sources) { if (source.isPrimary && source.matchSource === 'term') { ++sourceTermExactMatchCount; @@ -1838,6 +1853,7 @@ export class Translator { dictionaryAlias, dictionaryPriority, sourceTermExactMatchCount, + matchPrimaryReading, maxOriginalTextLength, headwordsArray, definitions, @@ -2017,8 +2033,12 @@ export class Translator { * @returns {number} */ const compareFunction = (v1, v2) => { + // Sort by reading match + let i = (v2.matchPrimaryReading ? 1 : 0) - (v1.matchPrimaryReading ? 1 : 0); + if (i !== 0) { return i; } + // Sort by length of source term - let i = v2.maxOriginalTextLength - v1.maxOriginalTextLength; + i = v2.maxOriginalTextLength - v1.maxOriginalTextLength; if (i !== 0) { return i; } // Sort by length of the shortest text processing chain diff --git a/test/data/anki-note-builder-test-results.json b/test/data/anki-note-builder-test-results.json index a7e954152d..b496d0b880 100644 --- a/test/data/anki-note-builder-test-results.json +++ b/test/data/anki-note-builder-test-results.json @@ -4606,5 +4606,183 @@ "url": "url:" } ] + }, + { + "name": "Find terms using primary reading 1", + "results": [ + { + "audio": "", + "clipboard-image": "", + "clipboard-text": "", + "cloze-body": "自重", + "cloze-body-kana": "じちょう", + "cloze-prefix": "cloze-prefix", + "cloze-suffix": "cloze-suffix", + "conjugation": "", + "dictionary": "Test Dictionary 2", + "dictionary-alias": "termsDictAlias", + "document-title": "title", + "expression": "自重", + "frequencies": "", + "frequency-harmonic-rank": "9999999", + "frequency-harmonic-occurrence": "0", + "frequency-average-rank": "9999999", + "frequency-average-occurrence": "0", + "furigana": "自重じちょう", + "furigana-plain": "自重[じちょう]", + "glossary": "
(n, termsDictAlias) jichou definition
", + "glossary-brief": "
jichou definition
", + "glossary-no-dictionary": "
(n) jichou definition
", + "glossary-first": "
(n, termsDictAlias) jichou definition
", + "glossary-first-brief": "
jichou definition
", + "glossary-first-no-dictionary": "
(n) jichou definition
", + "part-of-speech": "Noun", + "pitch-accents": "", + "pitch-accent-graphs": "", + "pitch-accent-graphs-jj": "", + "pitch-accent-positions": "", + "pitch-accent-categories": "", + "phonetic-transcriptions": "", + "reading": "じちょう", + "screenshot": "", + "search-query": "fullQuery", + "popup-selection-text": "", + "sentence": "cloze-prefix自重cloze-suffix", + "sentence-furigana": "cloze-prefix自重cloze-suffix", + "tags": "n", + "url": "url:" + }, + { + "audio": "", + "clipboard-image": "", + "clipboard-text": "", + "cloze-body": "自重", + "cloze-body-kana": "じじゅう", + "cloze-prefix": "cloze-prefix", + "cloze-suffix": "cloze-suffix", + "conjugation": "", + "dictionary": "Test Dictionary 2", + "dictionary-alias": "termsDictAlias", + "document-title": "title", + "expression": "自重", + "frequencies": "", + "frequency-harmonic-rank": "9999999", + "frequency-harmonic-occurrence": "0", + "frequency-average-rank": "9999999", + "frequency-average-occurrence": "0", + "furigana": "自重じじゅう", + "furigana-plain": "自重[じじゅう]", + "glossary": "
(n, termsDictAlias) jijuu definition
", + "glossary-brief": "
jijuu definition
", + "glossary-no-dictionary": "
(n) jijuu definition
", + "glossary-first": "
(n, termsDictAlias) jijuu definition
", + "glossary-first-brief": "
jijuu definition
", + "glossary-first-no-dictionary": "
(n) jijuu definition
", + "part-of-speech": "Noun", + "pitch-accents": "", + "pitch-accent-graphs": "", + "pitch-accent-graphs-jj": "", + "pitch-accent-positions": "", + "pitch-accent-categories": "", + "phonetic-transcriptions": "", + "reading": "じじゅう", + "screenshot": "", + "search-query": "fullQuery", + "popup-selection-text": "", + "sentence": "cloze-prefix自重cloze-suffix", + "sentence-furigana": "cloze-prefix自重cloze-suffix", + "tags": "n", + "url": "url:" + } + ] + }, + { + "name": "Find terms using primary reading 2", + "results": [ + { + "audio": "", + "clipboard-image": "", + "clipboard-text": "", + "cloze-body": "自重", + "cloze-body-kana": "じじゅう", + "cloze-prefix": "cloze-prefix", + "cloze-suffix": "cloze-suffix", + "conjugation": "", + "dictionary": "Test Dictionary 2", + "dictionary-alias": "termsDictAlias", + "document-title": "title", + "expression": "自重", + "frequencies": "", + "frequency-harmonic-rank": "9999999", + "frequency-harmonic-occurrence": "0", + "frequency-average-rank": "9999999", + "frequency-average-occurrence": "0", + "furigana": "自重じじゅう", + "furigana-plain": "自重[じじゅう]", + "glossary": "
(n, termsDictAlias) jijuu definition
", + "glossary-brief": "
jijuu definition
", + "glossary-no-dictionary": "
(n) jijuu definition
", + "glossary-first": "
(n, termsDictAlias) jijuu definition
", + "glossary-first-brief": "
jijuu definition
", + "glossary-first-no-dictionary": "
(n) jijuu definition
", + "part-of-speech": "Noun", + "pitch-accents": "", + "pitch-accent-graphs": "", + "pitch-accent-graphs-jj": "", + "pitch-accent-positions": "", + "pitch-accent-categories": "", + "phonetic-transcriptions": "", + "reading": "じじゅう", + "screenshot": "", + "search-query": "fullQuery", + "popup-selection-text": "", + "sentence": "cloze-prefix自重cloze-suffix", + "sentence-furigana": "cloze-prefix自重cloze-suffix", + "tags": "n", + "url": "url:" + }, + { + "audio": "", + "clipboard-image": "", + "clipboard-text": "", + "cloze-body": "自重", + "cloze-body-kana": "じちょう", + "cloze-prefix": "cloze-prefix", + "cloze-suffix": "cloze-suffix", + "conjugation": "", + "dictionary": "Test Dictionary 2", + "dictionary-alias": "termsDictAlias", + "document-title": "title", + "expression": "自重", + "frequencies": "", + "frequency-harmonic-rank": "9999999", + "frequency-harmonic-occurrence": "0", + "frequency-average-rank": "9999999", + "frequency-average-occurrence": "0", + "furigana": "自重じちょう", + "furigana-plain": "自重[じちょう]", + "glossary": "
(n, termsDictAlias) jichou definition
", + "glossary-brief": "
jichou definition
", + "glossary-no-dictionary": "
(n) jichou definition
", + "glossary-first": "
(n, termsDictAlias) jichou definition
", + "glossary-first-brief": "
jichou definition
", + "glossary-first-no-dictionary": "
(n) jichou definition
", + "part-of-speech": "Noun", + "pitch-accents": "", + "pitch-accent-graphs": "", + "pitch-accent-graphs-jj": "", + "pitch-accent-positions": "", + "pitch-accent-categories": "", + "phonetic-transcriptions": "", + "reading": "じちょう", + "screenshot": "", + "search-query": "fullQuery", + "popup-selection-text": "", + "sentence": "cloze-prefix自重cloze-suffix", + "sentence-furigana": "cloze-prefix自重cloze-suffix", + "tags": "n", + "url": "url:" + } + ] } ] diff --git a/test/data/database-test-cases.json b/test/data/database-test-cases.json index 6d0cbe2f59..1595ffc4ae 100644 --- a/test/data/database-test-cases.json +++ b/test/data/database-test-cases.json @@ -28,7 +28,7 @@ "ipa": 1 }, "terms": { - "total": 31 + "total": 33 } } }, @@ -37,7 +37,7 @@ { "kanji": 2, "kanjiMeta": 6, - "terms": 31, + "terms": 33, "termMeta": 39, "tagMeta": 15, "media": 6 @@ -46,7 +46,7 @@ "total": { "kanji": 2, "kanjiMeta": 6, - "terms": 31, + "terms": 33, "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 0a635b84d6..7c3e005a40 100644 --- a/test/data/dictionaries/valid-dictionary1/term_bank_1.json +++ b/test/data/dictionaries/valid-dictionary1/term_bank_1.json @@ -345,5 +345,7 @@ ["English", "", "n", "n", 1, ["English definition"], 19, ""], ["language", "", "n", "n", 1, ["language definition"], 20, ""], ["USB", "ユーエスビー", "n", "n", 1, ["USB definition"], 21, ""], - ["마시다", "", "v", "v", 1, ["masida definition"], 22, ""] + ["마시다", "", "v", "v", 1, ["masida definition"], 22, ""], + ["自重", "じちょう", "n", "n", 1, ["jichou definition"], 23, ""], + ["自重", "じじゅう", "n", "n", 2, ["jijuu definition"], 24, ""] ] diff --git a/test/data/translator-test-inputs.json b/test/data/translator-test-inputs.json index 25115e9c23..bceb9d68eb 100644 --- a/test/data/translator-test-inputs.json +++ b/test/data/translator-test-inputs.json @@ -513,6 +513,32 @@ "searchResolution": "word" } ] + }, + { + "name": "Find terms using primary reading 1", + "func": "findTerms", + "mode": "split", + "text": "自重", + "options": [ + "default", + { + "type": "terms", + "primaryReading": "じちょう" + } + ] + }, + { + "name": "Find terms using primary reading 2", + "func": "findTerms", + "mode": "split", + "text": "自重", + "options": [ + "default", + { + "type": "terms", + "primaryReading": "じじゅう" + } + ] } ] } diff --git a/test/data/translator-test-results-note-data1.json b/test/data/translator-test-results-note-data1.json index e43042a0a8..dedba5601e 100644 --- a/test/data/translator-test-results-note-data1.json +++ b/test/data/translator-test-results-note-data1.json @@ -35095,5 +35095,463 @@ "media": {} } ] + }, + { + "name": "Find terms using primary reading 1", + "noteDataList": [ + { + "marker": "{marker}", + "definition": { + "type": "term", + "id": 31, + "source": "自重", + "rawSource": "自重", + "sourceTerm": "自重", + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 1, + "isPrimary": true, + "sequence": 23, + "dictionary": "Test Dictionary 2", + "dictionaryAlias": "termsDictAlias", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "dictionaryNames": [ + "Test Dictionary 2" + ], + "expression": "自重", + "reading": "じちょう", + "expressions": [ + { + "sourceTerm": "自重", + "expression": "自重", + "reading": "じちょう", + "termTags": [], + "frequencies": [], + "pitches": [], + "furiganaSegments": [ + { + "text": "自重", + "furigana": "じちょう" + } + ], + "termFrequency": "normal", + "wordClasses": [ + "n" + ] + } + ], + "glossary": [ + "jichou definition" + ], + "glossaryScopedStyles": ".yomitan-glossary ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "dictScopedStyles": ".yomitan-glossary [data-dictionary=\"Test Dictionary 2\"] ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "definitionTags": [ + { + "name": "n", + "category": "partOfSpeech", + "notes": "noun", + "order": 0, + "score": 0, + "dictionary": "Test Dictionary 2", + "redundant": false + } + ], + "termTags": [], + "frequencies": [], + "frequencyHarmonic": -1, + "frequencyAverage": -1, + "pitches": [], + "phoneticTranscriptions": [], + "sourceTermExactMatchCount": 1, + "url": "url:", + "cloze": { + "sentence": "", + "prefix": "", + "body": "", + "bodyKana": "", + "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": {} + }, + { + "marker": "{marker}", + "definition": { + "type": "term", + "id": 32, + "source": "自重", + "rawSource": "自重", + "sourceTerm": "自重", + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 2, + "isPrimary": true, + "sequence": 24, + "dictionary": "Test Dictionary 2", + "dictionaryAlias": "termsDictAlias", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "dictionaryNames": [ + "Test Dictionary 2" + ], + "expression": "自重", + "reading": "じじゅう", + "expressions": [ + { + "sourceTerm": "自重", + "expression": "自重", + "reading": "じじゅう", + "termTags": [], + "frequencies": [], + "pitches": [], + "furiganaSegments": [ + { + "text": "自重", + "furigana": "じじゅう" + } + ], + "termFrequency": "normal", + "wordClasses": [ + "n" + ] + } + ], + "glossary": [ + "jijuu definition" + ], + "glossaryScopedStyles": ".yomitan-glossary ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "dictScopedStyles": ".yomitan-glossary [data-dictionary=\"Test Dictionary 2\"] ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "definitionTags": [ + { + "name": "n", + "category": "partOfSpeech", + "notes": "noun", + "order": 0, + "score": 0, + "dictionary": "Test Dictionary 2", + "redundant": false + } + ], + "termTags": [], + "frequencies": [], + "frequencyHarmonic": -1, + "frequencyAverage": -1, + "pitches": [], + "phoneticTranscriptions": [], + "sourceTermExactMatchCount": 1, + "url": "url:", + "cloze": { + "sentence": "", + "prefix": "", + "body": "", + "bodyKana": "", + "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": {} + } + ] + }, + { + "name": "Find terms using primary reading 2", + "noteDataList": [ + { + "marker": "{marker}", + "definition": { + "type": "term", + "id": 32, + "source": "自重", + "rawSource": "自重", + "sourceTerm": "自重", + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 2, + "isPrimary": true, + "sequence": 24, + "dictionary": "Test Dictionary 2", + "dictionaryAlias": "termsDictAlias", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "dictionaryNames": [ + "Test Dictionary 2" + ], + "expression": "自重", + "reading": "じじゅう", + "expressions": [ + { + "sourceTerm": "自重", + "expression": "自重", + "reading": "じじゅう", + "termTags": [], + "frequencies": [], + "pitches": [], + "furiganaSegments": [ + { + "text": "自重", + "furigana": "じじゅう" + } + ], + "termFrequency": "normal", + "wordClasses": [ + "n" + ] + } + ], + "glossary": [ + "jijuu definition" + ], + "glossaryScopedStyles": ".yomitan-glossary ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "dictScopedStyles": ".yomitan-glossary [data-dictionary=\"Test Dictionary 2\"] ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "definitionTags": [ + { + "name": "n", + "category": "partOfSpeech", + "notes": "noun", + "order": 0, + "score": 0, + "dictionary": "Test Dictionary 2", + "redundant": false + } + ], + "termTags": [], + "frequencies": [], + "frequencyHarmonic": -1, + "frequencyAverage": -1, + "pitches": [], + "phoneticTranscriptions": [], + "sourceTermExactMatchCount": 1, + "url": "url:", + "cloze": { + "sentence": "", + "prefix": "", + "body": "", + "bodyKana": "", + "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": {} + }, + { + "marker": "{marker}", + "definition": { + "type": "term", + "id": 31, + "source": "自重", + "rawSource": "自重", + "sourceTerm": "自重", + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 1, + "isPrimary": true, + "sequence": 23, + "dictionary": "Test Dictionary 2", + "dictionaryAlias": "termsDictAlias", + "dictionaryOrder": { + "index": 0, + "priority": 0 + }, + "dictionaryNames": [ + "Test Dictionary 2" + ], + "expression": "自重", + "reading": "じちょう", + "expressions": [ + { + "sourceTerm": "自重", + "expression": "自重", + "reading": "じちょう", + "termTags": [], + "frequencies": [], + "pitches": [], + "furiganaSegments": [ + { + "text": "自重", + "furigana": "じちょう" + } + ], + "termFrequency": "normal", + "wordClasses": [ + "n" + ] + } + ], + "glossary": [ + "jichou definition" + ], + "glossaryScopedStyles": ".yomitan-glossary ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "dictScopedStyles": ".yomitan-glossary [data-dictionary=\"Test Dictionary 2\"] ul[data-sc-content='glossary'] {\n color: #ffff00;\n}", + "definitionTags": [ + { + "name": "n", + "category": "partOfSpeech", + "notes": "noun", + "order": 0, + "score": 0, + "dictionary": "Test Dictionary 2", + "redundant": false + } + ], + "termTags": [], + "frequencies": [], + "frequencyHarmonic": -1, + "frequencyAverage": -1, + "pitches": [], + "phoneticTranscriptions": [], + "sourceTermExactMatchCount": 1, + "url": "url:", + "cloze": { + "sentence": "", + "prefix": "", + "body": "", + "bodyKana": "", + "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": {} + } + ] } ] diff --git a/test/data/translator-test-results.json b/test/data/translator-test-results.json index 1cd5b4ba9d..bd09e49945 100644 --- a/test/data/translator-test-results.json +++ b/test/data/translator-test-results.json @@ -314,6 +314,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -494,6 +495,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -693,6 +695,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -874,6 +877,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -1055,6 +1059,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -1236,6 +1241,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -1417,6 +1423,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -1597,6 +1604,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -1796,6 +1804,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -2002,6 +2011,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -2208,6 +2218,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -2414,6 +2425,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -2625,6 +2637,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -2811,6 +2824,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -2997,6 +3011,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -3183,6 +3198,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -3364,6 +3380,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -3544,6 +3561,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -3743,6 +3761,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -3866,6 +3885,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -4052,6 +4072,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 3, "headwords": [ { @@ -4247,6 +4268,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -4433,6 +4455,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -4614,6 +4637,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -4801,6 +4825,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -4982,6 +5007,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -5169,6 +5195,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -5375,6 +5402,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -5586,6 +5614,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -5772,6 +5801,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -5959,6 +5989,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -6165,6 +6196,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -6376,6 +6408,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -6562,6 +6595,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -6749,6 +6783,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 3, "headwords": [ { @@ -6882,6 +6917,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -6949,6 +6985,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -7016,6 +7053,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -7083,6 +7121,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -7155,6 +7194,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -7227,6 +7267,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -7299,6 +7340,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -7371,6 +7413,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -7438,6 +7481,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -7505,6 +7549,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -7578,6 +7623,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -7833,6 +7879,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -8093,6 +8140,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -8328,6 +8376,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -8558,6 +8607,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -8738,6 +8788,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -8937,6 +8988,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 2, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -9424,6 +9476,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 2, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -9857,6 +9910,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -10037,6 +10091,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -10257,6 +10312,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -10484,6 +10540,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -10711,6 +10768,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -10938,6 +10996,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -11149,6 +11208,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -11335,6 +11395,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -11521,6 +11582,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -11707,6 +11769,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -11888,6 +11951,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -12068,6 +12132,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 1, "headwords": [ { @@ -12269,6 +12334,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 10, "headwords": [ { @@ -12477,6 +12543,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 10, "headwords": [ { @@ -12685,6 +12752,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 10, "headwords": [ { @@ -12893,6 +12961,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 10, "headwords": [ { @@ -13106,6 +13175,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 5, "headwords": [ { @@ -13294,6 +13364,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 5, "headwords": [ { @@ -13482,6 +13553,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 5, "headwords": [ { @@ -13670,6 +13742,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 5, "headwords": [ { @@ -13853,6 +13926,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -14035,6 +14109,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -14236,6 +14311,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -14444,6 +14520,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -14652,6 +14729,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -14860,6 +14938,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 12, "headwords": [ { @@ -15073,6 +15152,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 6, "headwords": [ { @@ -15261,6 +15341,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 6, "headwords": [ { @@ -15449,6 +15530,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 6, "headwords": [ { @@ -15637,6 +15719,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 6, "headwords": [ { @@ -15820,6 +15903,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 3, "headwords": [ { @@ -16002,6 +16086,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 3, "headwords": [ { @@ -16208,6 +16293,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -16323,6 +16409,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 5, "headwords": [ { @@ -16447,6 +16534,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 7, "headwords": [ { @@ -16560,6 +16648,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -17047,6 +17136,7 @@ "dictionaryAlias": "", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -17486,6 +17576,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 3, "headwords": [ { @@ -17646,6 +17737,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -17752,6 +17844,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -17858,6 +17951,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -17964,6 +18058,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -18070,6 +18165,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -18193,6 +18289,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -18340,6 +18437,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 5, "headwords": [ { @@ -18426,6 +18524,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -18498,6 +18597,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 7, "headwords": [ { @@ -18586,6 +18686,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 3, "headwords": [ { @@ -18674,6 +18775,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -18857,6 +18959,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -19046,6 +19149,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -19229,6 +19333,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -19418,6 +19523,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -19535,6 +19641,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -19719,6 +19826,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 2, "headwords": [ { @@ -19925,6 +20033,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 0, + "matchPrimaryReading": false, "maxOriginalTextLength": 7, "headwords": [ { @@ -20011,6 +20120,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 7, "headwords": [ { @@ -20099,6 +20209,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 8, "headwords": [ { @@ -20192,6 +20303,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 4, "headwords": [ { @@ -20278,6 +20390,7 @@ "dictionaryAlias": "termsDictAlias", "dictionaryPriority": 0, "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, "maxOriginalTextLength": 7, "headwords": [ { @@ -20341,5 +20454,342 @@ "frequencies": [] } ] + }, + { + "name": "Find term using primary reading 1", + "originalTextLength": 2, + "dictionaryEntries": [ + { + "type": "term", + "isPrimary": true, + "textProcessorRuleChainCandidates": [ + [] + ], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 1, + "frequencyOrder": 0, + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "sourceTermExactMatchCount": 1, + "matchPrimaryReading": true, + "maxOriginalTextLength": 2, + "headwords": [ + { + "index": 0, + "term": "自重", + "reading": "じちょう", + "sources": [ + { + "originalText": "自重", + "transformedText": "自重", + "deinflectedText": "自重", + "matchType": "exact", + "matchSource": "term", + "isPrimary": true + } + ], + "tags": [], + "wordClasses": [ + "n" + ] + } + ], + "definitions": [ + { + "index": 0, + "headwordIndices": [ + 0 + ], + "dictionary": "Test Dictionary 2", + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "id": 31, + "score": 1, + "frequencyOrder": 0, + "sequences": [ + 23 + ], + "isPrimary": true, + "tags": [ + { + "name": "n", + "category": "partOfSpeech", + "order": 0, + "score": 0, + "content": [ + "noun" + ], + "dictionaries": [ + "Test Dictionary 2" + ], + "redundant": false + } + ], + "entries": [ + "jichou definition" + ] + } + ], + "pronunciations": [], + "frequencies": [] + }, + { + "type": "term", + "isPrimary": true, + "textProcessorRuleChainCandidates": [ + [] + ], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 2, + "frequencyOrder": 0, + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, + "maxOriginalTextLength": 2, + "headwords": [ + { + "index": 0, + "term": "自重", + "reading": "じじゅう", + "sources": [ + { + "originalText": "自重", + "transformedText": "自重", + "deinflectedText": "自重", + "matchType": "exact", + "matchSource": "term", + "isPrimary": true + } + ], + "tags": [], + "wordClasses": [ + "n" + ] + } + ], + "definitions": [ + { + "index": 0, + "headwordIndices": [ + 0 + ], + "dictionary": "Test Dictionary 2", + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "id": 32, + "score": 2, + "frequencyOrder": 0, + "sequences": [ + 24 + ], + "isPrimary": true, + "tags": [ + { + "name": "n", + "category": "partOfSpeech", + "order": 0, + "score": 0, + "content": [ + "noun" + ], + "dictionaries": [ + "Test Dictionary 2" + ], + "redundant": false + } + ], + "entries": [ + "jijuu definition" + ] + } + ], + "pronunciations": [], + "frequencies": [] + } + ] + }, + { + "name": "Find term using primary reading 2", + "originalTextLength": 2, + "dictionaryEntries": [ + + { + "type": "term", + "isPrimary": true, + "textProcessorRuleChainCandidates": [ + [] + ], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 2, + "frequencyOrder": 0, + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "sourceTermExactMatchCount": 1, + "matchPrimaryReading": true, + "maxOriginalTextLength": 2, + "headwords": [ + { + "index": 0, + "term": "自重", + "reading": "じじゅう", + "sources": [ + { + "originalText": "自重", + "transformedText": "自重", + "deinflectedText": "自重", + "matchType": "exact", + "matchSource": "term", + "isPrimary": true + } + ], + "tags": [], + "wordClasses": [ + "n" + ] + } + ], + "definitions": [ + { + "index": 0, + "headwordIndices": [ + 0 + ], + "dictionary": "Test Dictionary 2", + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "id": 32, + "score": 2, + "frequencyOrder": 0, + "sequences": [ + 24 + ], + "isPrimary": true, + "tags": [ + { + "name": "n", + "category": "partOfSpeech", + "order": 0, + "score": 0, + "content": [ + "noun" + ], + "dictionaries": [ + "Test Dictionary 2" + ], + "redundant": false + } + ], + "entries": [ + "jijuu definition" + ] + } + ], + "pronunciations": [], + "frequencies": [] + }, + { + "type": "term", + "isPrimary": true, + "textProcessorRuleChainCandidates": [ + [] + ], + "inflectionRuleChainCandidates": [ + { + "source": "algorithm", + "inflectionRules": [] + } + ], + "score": 1, + "frequencyOrder": 0, + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "sourceTermExactMatchCount": 1, + "matchPrimaryReading": false, + "maxOriginalTextLength": 2, + "headwords": [ + { + "index": 0, + "term": "自重", + "reading": "じちょう", + "sources": [ + { + "originalText": "自重", + "transformedText": "自重", + "deinflectedText": "自重", + "matchType": "exact", + "matchSource": "term", + "isPrimary": true + } + ], + "tags": [], + "wordClasses": [ + "n" + ] + } + ], + "definitions": [ + { + "index": 0, + "headwordIndices": [ + 0 + ], + "dictionary": "Test Dictionary 2", + "dictionaryIndex": 0, + "dictionaryAlias": "termsDictAlias", + "dictionaryPriority": 0, + "id": 31, + "score": 1, + "frequencyOrder": 0, + "sequences": [ + 23 + ], + "isPrimary": true, + "tags": [ + { + "name": "n", + "category": "partOfSpeech", + "order": 0, + "score": 0, + "content": [ + "noun" + ], + "dictionaries": [ + "Test Dictionary 2" + ], + "redundant": false + } + ], + "entries": [ + "jichou definition" + ] + } + ], + "pronunciations": [], + "frequencies": [] + } + ] } ] diff --git a/test/utilities/translator.js b/test/utilities/translator.js index e288c8df2e..314b1a680b 100644 --- a/test/utilities/translator.js +++ b/test/utilities/translator.js @@ -124,6 +124,7 @@ export function createFindTermsOptions(dictionaryName, optionsPresets, optionsAr sortFrequencyDictionary, sortFrequencyDictionaryOrder, removeNonJapaneseCharacters, + primaryReading, excludeDictionaryDefinitions, searchResolution, language, @@ -136,6 +137,7 @@ export function createFindTermsOptions(dictionaryName, optionsPresets, optionsAr sortFrequencyDictionary: typeof sortFrequencyDictionary !== 'undefined' ? sortFrequencyDictionary : null, sortFrequencyDictionaryOrder: typeof sortFrequencyDictionaryOrder !== 'undefined' ? sortFrequencyDictionaryOrder : 'ascending', removeNonJapaneseCharacters: typeof removeNonJapaneseCharacters !== 'undefined' ? removeNonJapaneseCharacters : false, + primaryReading: typeof primaryReading !== 'undefined' ? primaryReading : '', textReplacements, enabledDictionaryMap, excludeDictionaryDefinitions: Array.isArray(excludeDictionaryDefinitions) ? new Set(excludeDictionaryDefinitions) : null, diff --git a/types/ext/api.d.ts b/types/ext/api.d.ts index 46be7938eb..45996c48c2 100644 --- a/types/ext/api.d.ts +++ b/types/ext/api.d.ts @@ -48,6 +48,7 @@ import type { export type FindTermsDetails = { matchType?: Translation.FindTermsMatchType; deinflect?: boolean; + primaryReading?: string; }; export type ParseTextResultItem = { diff --git a/types/ext/dictionary.d.ts b/types/ext/dictionary.d.ts index ac9b7940ca..5a6fef4ab7 100644 --- a/types/ext/dictionary.d.ts +++ b/types/ext/dictionary.d.ts @@ -247,6 +247,10 @@ export type TermDictionaryEntry = { * The number of primary sources that had an exact text match for the term. */ sourceTermExactMatchCount: number; + /** + * Whether the term reading matched the primary reading. + */ + matchPrimaryReading: boolean; /** * The maximum length of the original text for all primary sources. */ diff --git a/types/ext/translation.d.ts b/types/ext/translation.d.ts index 4d60507ce3..a24fb744bd 100644 --- a/types/ext/translation.d.ts +++ b/types/ext/translation.d.ts @@ -68,6 +68,10 @@ export type FindTermsOptions = { * Whether or not deinflection should be performed. */ deinflect: boolean; + /** + * The reading which will be sorted to the top of the results. + */ + primaryReading: string; /** * The name of the primary dictionary to search. */ diff --git a/types/test/translator.d.ts b/types/test/translator.d.ts index efd5cc3fcf..2d18b75d13 100644 --- a/types/test/translator.d.ts +++ b/types/test/translator.d.ts @@ -44,6 +44,7 @@ export type FindTermsOptionsPreset = { sortFrequencyDictionary?: string | null; sortFrequencyDictionaryOrder?: FindTermsSortOrder; removeNonJapaneseCharacters?: boolean; + primaryReading?: string; textReplacements?: (FindTermsTextReplacement[] | null)[]; enabledDictionaryMap?: [key: string, value: FindTermDictionary][]; excludeDictionaryDefinitions?: string[] | null; From 87ef6fdfad2cd8ccc30242a1562a190e3021302b Mon Sep 17 00:00:00 2001 From: James Maa Date: Tue, 29 Oct 2024 12:53:36 -0700 Subject: [PATCH 05/17] Change playwright dictionary loading timeout (#1548) Change timeout --- test/playwright/integration.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/playwright/integration.spec.js b/test/playwright/integration.spec.js index b9f5170002..ca4ef694df 100644 --- a/test/playwright/integration.spec.js +++ b/test/playwright/integration.spec.js @@ -73,7 +73,7 @@ test('anki add', async ({context, page, extensionId}) => { mimeType: 'application/x-zip', buffer: Buffer.from(dictionary), }); - await expect(page.locator('id=dictionaries')).toHaveText('Dictionaries (1 installed, 1 enabled)', {timeout: 5 * 60 * 1000}); + await expect(page.locator('id=dictionaries')).toHaveText('Dictionaries (1 installed, 1 enabled)', {timeout: 1 * 60 * 1000}); // Connect to anki await page.locator('.toggle', {has: page.locator('[data-setting="anki.enable"]')}).click(); From c4b2d3d187c8dcd21f8adea160bc39ebb663e604 Mon Sep 17 00:00:00 2001 From: Olga <137053456+ImenaOphelia@users.noreply.github.com> Date: Tue, 29 Oct 2024 21:35:04 +0100 Subject: [PATCH 06/17] Change Polish Example to an Infinitive (#1549) --- ext/js/language/language-descriptors.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/js/language/language-descriptors.js b/ext/js/language/language-descriptors.js index bdaebd61be..ef71d5f912 100644 --- a/ext/js/language/language-descriptors.js +++ b/ext/js/language/language-descriptors.js @@ -261,7 +261,7 @@ const languageDescriptors = [ iso: 'pl', iso639_3: 'pol', name: 'Polish', - exampleText: 'czytacie', + exampleText: 'czytać', textPreprocessors: capitalizationPreprocessors, }, { From 66309bc9d986446f51036e5576d154b10f80c9f4 Mon Sep 17 00:00:00 2001 From: Olga <137053456+ImenaOphelia@users.noreply.github.com> Date: Wed, 30 Oct 2024 20:12:23 +0100 Subject: [PATCH 07/17] Change Examples to Infinitives (#1550) --- ext/js/language/language-descriptors.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ext/js/language/language-descriptors.js b/ext/js/language/language-descriptors.js index ef71d5f912..a07437b711 100644 --- a/ext/js/language/language-descriptors.js +++ b/ext/js/language/language-descriptors.js @@ -78,7 +78,7 @@ const languageDescriptors = [ iso: 'de', iso639_3: 'deu', name: 'German', - exampleText: 'gelesen', + exampleText: 'lesen', textPreprocessors: { ...capitalizationPreprocessors, eszettPreprocessor, @@ -142,7 +142,7 @@ const languageDescriptors = [ iso: 'grc', iso639_3: 'grc', name: 'Ancient Greek', - exampleText: 'γράφω', + exampleText: 'γράφω', /* 'to write' */ textPreprocessors: { ...capitalizationPreprocessors, removeAlphabeticDiacritics, @@ -165,7 +165,7 @@ const languageDescriptors = [ iso: 'id', iso639_3: 'ind', name: 'Indonesian', - exampleText: 'membaca', + exampleText: 'baca', textPreprocessors: capitalizationPreprocessors, }, { @@ -182,7 +182,7 @@ const languageDescriptors = [ iso: 'la', iso639_3: 'lat', name: 'Latin', - exampleText: 'legere', + exampleText: 'legō', textPreprocessors: { ...capitalizationPreprocessors, removeAlphabeticDiacritics, @@ -275,7 +275,7 @@ const languageDescriptors = [ iso: 'ro', iso639_3: 'ron', name: 'Romanian', - exampleText: 'citit', + exampleText: 'citi', textPreprocessors: { ...capitalizationPreprocessors, removeAlphabeticDiacritics, @@ -307,7 +307,7 @@ const languageDescriptors = [ iso: 'sh', iso639_3: 'hbs', name: 'Serbo-Croatian', - exampleText: 'čitaše', + exampleText: 'čìtati', textPreprocessors: { ...capitalizationPreprocessors, removeSerboCroatianAccentMarks, @@ -317,7 +317,7 @@ const languageDescriptors = [ iso: 'sq', iso639_3: 'sqi', name: 'Albanian', - exampleText: 'ndihmojme', + exampleText: 'ndihmoj', /* 'to help' */ textPreprocessors: capitalizationPreprocessors, languageTransforms: albanianTransforms, }, @@ -349,14 +349,14 @@ const languageDescriptors = [ iso: 'tr', iso639_3: 'tur', name: 'Turkish', - exampleText: 'okuyor', + exampleText: 'okumak', textPreprocessors: capitalizationPreprocessors, }, { iso: 'uk', iso639_3: 'ukr', name: 'Ukrainian', - exampleText: 'читаєте', + exampleText: 'читати', textPreprocessors: capitalizationPreprocessors, }, { From 4b1dfbe2d451adda21de571b8f940f4bb26433f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Nov 2024 11:28:18 -0700 Subject: [PATCH 08/17] Bump the minor group with 4 updates (#1554) Bumps the minor group with 4 updates: [lycheeverse/lychee-action](https://github.com/lycheeverse/lychee-action), [github/codeql-action](https://github.com/github/codeql-action), [softprops/action-gh-release](https://github.com/softprops/action-gh-release) and [actions/dependency-review-action](https://github.com/actions/dependency-review-action). Updates `lycheeverse/lychee-action` from 2.0.0 to 2.0.2 - [Release notes](https://github.com/lycheeverse/lychee-action/releases) - [Commits](https://github.com/lycheeverse/lychee-action/compare/v2.0.0...v2.0.2) Updates `github/codeql-action` from 3.26.12 to 3.27.0 - [Release notes](https://github.com/github/codeql-action/releases) - [Commits](https://github.com/github/codeql-action/compare/v3.26.12...v3.27.0) Updates `softprops/action-gh-release` from 2.0.8 to 2.0.9 - [Release notes](https://github.com/softprops/action-gh-release/releases) - [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md) - [Commits](https://github.com/softprops/action-gh-release/compare/c062e08bd532815e2082a85e87e3ef29c3e6d191...e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8) Updates `actions/dependency-review-action` from 4.3.4 to 4.4.0 - [Release notes](https://github.com/actions/dependency-review-action/releases) - [Commits](https://github.com/actions/dependency-review-action/compare/5a2ce3f5b92ee19cbb1541a4984c76d921601d7c...4081bf99e2866ebe428fc0477b69eb4fcda7220a) --- updated-dependencies: - dependency-name: lycheeverse/lychee-action dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor - dependency-name: softprops/action-gh-release dependency-type: direct:production update-type: version-update:semver-patch dependency-group: minor - dependency-name: actions/dependency-review-action dependency-type: direct:production update-type: version-update:semver-minor dependency-group: minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/broken-links.yml | 2 +- .github/workflows/codeql.yml | 6 +++--- .github/workflows/create-prerelease-on-tag.yml | 2 +- .github/workflows/dependency-review.yml | 2 +- .github/workflows/scorecard.yml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/broken-links.yml b/.github/workflows/broken-links.yml index 4c3eeeb2da..8b8f1cfbd6 100644 --- a/.github/workflows/broken-links.yml +++ b/.github/workflows/broken-links.yml @@ -21,7 +21,7 @@ jobs: run: npm ci - name: Build Legal run: npm run license-report:html - - uses: lycheeverse/lychee-action@v2.0.0 + - uses: lycheeverse/lychee-action@v2.0.2 with: fail: true jobSummary: false diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index aab0563064..a8a2334d1c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -51,7 +51,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3.26.12 + uses: github/codeql-action/init@v3.27.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -65,7 +65,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3.26.12 + uses: github/codeql-action/autobuild@v3.27.0 # ℹ️ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -78,6 +78,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3.26.12 + uses: github/codeql-action/analyze@v3.27.0 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/create-prerelease-on-tag.yml b/.github/workflows/create-prerelease-on-tag.yml index 753b3082d8..bedfa6dd56 100644 --- a/.github/workflows/create-prerelease-on-tag.yml +++ b/.github/workflows/create-prerelease-on-tag.yml @@ -40,7 +40,7 @@ jobs: - name: Release id: release - uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # pin@v2 + uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b8 # pin@v2 with: generate_release_notes: true prerelease: true diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index bf2dcfbae9..5edc997f09 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -24,4 +24,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: 'Dependency Review' - uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4 + uses: actions/dependency-review-action@4081bf99e2866ebe428fc0477b69eb4fcda7220a # v4.4.0 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 22cb6238b8..4439f5de9e 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -64,6 +64,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@572cc5268d94f11b89e12e7a166cf93275856072 # v2.22.12 + uses: github/codeql-action/upload-sarif@48c3e2675613624ea7978e5d132169f97bc3b578 # v2.22.12 with: sarif_file: results.sarif From 7c198d4a67dfec71e9edd1c11227c994a2a9b6c7 Mon Sep 17 00:00:00 2001 From: James Maa Date: Fri, 1 Nov 2024 11:35:56 -0700 Subject: [PATCH 09/17] =?UTF-8?q?Reapply=20"Support=20default=20setting=20?= =?UTF-8?q?overrides=20on=20different=20languages=20(#1=E2=80=A6=20(#1535)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reapply "Support default setting overrides on different languages (#1368)" (#1530) This reverts commit 019c45838a2669ac63d9f3cf1c9bf981b144e2e1. * Make recommended settings lazy-load --- .eslintrc.json | 19 + ext/data/recommended-settings.json | 406 ++++++++++++++++++ .../schemas/recommended-settings-schema.json | 180 ++++++++ .../recommended-settings-controller.js | 194 +++++++++ ext/js/pages/settings/settings-main.js | 3 + ext/js/pages/welcome-main.js | 4 + ext/templates-modals.html | 24 ++ ext/templates-settings.html | 17 + test/data/json.json | 11 + types/ext/settings-controller.d.ts | 10 + 10 files changed, 868 insertions(+) create mode 100644 ext/data/recommended-settings.json create mode 100644 ext/data/schemas/recommended-settings-schema.json create mode 100644 ext/js/pages/settings/recommended-settings-controller.js diff --git a/.eslintrc.json b/.eslintrc.json index faee1c51d0..64e8525893 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -694,6 +694,25 @@ } }] } + }, + { + "files": [ + "ext/data/recommended-settings.json" + ], + "rules": { + "jsonc/sort-keys": ["error", { + "pathPattern": ".*", + "order": [ + "modification", + "description" + ] + }, { + "pathPattern": ".*", + "order": { + "type": "asc" + } + }] + } } ] } diff --git a/ext/data/recommended-settings.json b/ext/data/recommended-settings.json new file mode 100644 index 0000000000..c492a2540a --- /dev/null +++ b/ext/data/recommended-settings.json @@ -0,0 +1,406 @@ +{ + "da": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "de": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "el": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "en": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "es": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "fi": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "fr": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + }, + { + "modification": { + "action": "set", + "path": "translation.textReplacements.groups", + "value": [ + [ + { + "pattern": "l'", + "ignoreCase": true, + "replacement": "" + }, + { + "pattern": "j'", + "ignoreCase": true, + "replacement": "" + }, + { + "pattern": "d'", + "ignoreCase": true, + "replacement": "" + } + ] + ] + }, + "description": "Separating the l', j', d' from the word." + } + ], + "hu": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "id": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "it": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "mn": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "nl": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "pl": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "pt": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "ro": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "ru": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "sh": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "sq": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "sv": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "tr": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ], + "vi": [ + { + "modification": { + "action": "set", + "path": "scanning.scanResolution", + "value": "word" + }, + "description": "Scan text one word at a time (as opposed to one character)." + }, + { + "modification": { + "action": "set", + "path": "translation.searchResolution", + "value": "word" + }, + "description": "Lookup whole words in the dictionary." + } + ] +} diff --git a/ext/data/schemas/recommended-settings-schema.json b/ext/data/schemas/recommended-settings-schema.json new file mode 100644 index 0000000000..2c8054244b --- /dev/null +++ b/ext/data/schemas/recommended-settings-schema.json @@ -0,0 +1,180 @@ +{ + "$id": "recommendedSetttings", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Contains data for recommended default options overrides by language.", + "type": "object", + "$defs": { + "path": { + "type": "string", + "minLength": 2 + }, + "value": { + "anyOf": [ + { + "type": "string", + "minLength": 1 + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array" + }, + { + "type": "object" + } + ] + }, + "description": { + "type": "string", + "minLength": 2 + } + }, + "patternProperties": { + "^.{2,}$": { + "title": "Language", + "type": "array", + "items": { + "title": "Modification", + "type": "object", + "oneOf": [ + { + "type": "object", + "title": "ModificationSet", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "set" + }, + "path": { + "$ref": "#/$defs/path" + }, + "value": { + "$ref": "#/$defs/value" + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + }, + { + "type": "object", + "title": "ModificationDelete", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "delete" + }, + "path": { + "$ref": "#/$defs/path" + }, + "value": { + "$ref": "#/$defs/value" + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + }, + { + "type": "object", + "title": "ModificationSwap", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "swap" + }, + "path1": { + "$ref": "#/$defs/path" + }, + "path2": { + "$ref": "#/$defs/path" + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + }, + { + "type": "object", + "title": "ModificationSplice", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "splice" + }, + "path": { + "$ref": "#/$defs/path" + }, + "start": { + "type": "number" + }, + "deleteCount": { + "type": "number" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/$defs/value" + } + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + }, + { + "type": "object", + "title": "ModificationPush", + "properties": { + "modification": { + "type": "object", + "properties": { + "action": { + "type": "string", + "const": "push" + }, + "path": { + "$ref": "#/$defs/path" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/$defs/value" + } + } + } + }, + "description": { + "$ref": "#/$defs/description" + } + } + } + ] + } + } + } +} diff --git a/ext/js/pages/settings/recommended-settings-controller.js b/ext/js/pages/settings/recommended-settings-controller.js new file mode 100644 index 0000000000..6cc21e14ce --- /dev/null +++ b/ext/js/pages/settings/recommended-settings-controller.js @@ -0,0 +1,194 @@ +/* + * 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 {fetchJson} from '../../core/fetch-utilities.js'; +import {log} from '../../core/log.js'; +import {querySelectorNotNull} from '../../dom/query-selector.js'; + +export class RecommendedSettingsController { + /** + * @param {import('./settings-controller.js').SettingsController} settingsController + */ + constructor(settingsController) { + /** @type {import('./settings-controller.js').SettingsController} */ + this._settingsController = settingsController; + /** @type {HTMLElement} */ + this._recommendedSettingsModal = querySelectorNotNull(document, '#recommended-settings-modal'); + /** @type {HTMLInputElement} */ + this._languageSelect = querySelectorNotNull(document, '#language-select'); + /** @type {HTMLInputElement} */ + this._applyButton = querySelectorNotNull(document, '#recommended-settings-apply-button'); + /** @type {Map} */ + this._recommendedSettings = new Map(); + } + + /** */ + async prepare() { + this._languageSelect.addEventListener('change', this._onLanguageSelectChanged.bind(this), false); + this._applyButton.addEventListener('click', this._onApplyButtonClicked.bind(this), false); + } + + /** + * @param {Event} _e + */ + async _onLanguageSelectChanged(_e) { + const setLanguage = this._languageSelect.value; + if (typeof setLanguage !== 'string') { return; } + + const recommendedSettings = await this._getRecommendedSettings(setLanguage); + if (typeof recommendedSettings !== 'undefined') { + const settingsList = querySelectorNotNull(document, '#recommended-settings-list'); + settingsList.innerHTML = ''; + this._recommendedSettings = new Map(); + + for (const [index, setting] of recommendedSettings.entries()) { + this._recommendedSettings.set(index.toString(), setting); + + const {description} = setting; + const template = this._settingsController.instantiateTemplate('recommended-settings-list-item'); + + // Render label + this._renderLabel(template, setting); + + // Render description + const descriptionElement = querySelectorNotNull(template, '.settings-item-description'); + if (description !== 'undefined') { + descriptionElement.textContent = description; + } + + // Render checkbox + const checkbox = /** @type {HTMLInputElement} */ (querySelectorNotNull(template, 'input[type="checkbox"]')); + checkbox.value = index.toString(); + + settingsList.append(template); + } + this._recommendedSettingsModal.hidden = false; + } + } + + /** + * + * @param {string} language + * @returns {Promise} + */ + async _getRecommendedSettings(language) { + if (typeof this._recommendedSettingsByLanguage === 'undefined') { + /** @type {import('settings-controller').RecommendedSettingsByLanguage} */ + this._recommendedSettingsByLanguage = await fetchJson('/data/recommended-settings.json'); + } + + return this._recommendedSettingsByLanguage[language]; + } + + /** + * @param {MouseEvent} e + */ + _onApplyButtonClicked(e) { + e.preventDefault(); + /** @type {NodeListOf} */ + const enabledCheckboxes = querySelectorNotNull(document, '#recommended-settings-list').querySelectorAll('input[type="checkbox"]:checked'); + if (enabledCheckboxes.length > 0) { + const modifications = []; + for (const checkbox of enabledCheckboxes) { + const index = checkbox.value; + const setting = this._recommendedSettings.get(index); + if (typeof setting === 'undefined') { continue; } + modifications.push(setting.modification); + } + void this._settingsController.modifyProfileSettings(modifications).then( + (results) => { + results.map((result) => { + if (Object.hasOwn(result, 'error')) { + log.error(new Error(`Failed to apply recommended setting: ${JSON.stringify(result)}`)); + } + }); + }, + ); + void this._settingsController.refresh(); + } + this._recommendedSettingsModal.hidden = true; + } + + /** + * @param {Element} template + * @param {import('settings-controller').RecommendedSetting} setting + */ + _renderLabel(template, setting) { + const label = querySelectorNotNull(template, '.settings-item-label'); + + const {modification} = setting; + switch (modification.action) { + case 'set': { + const {path, value} = modification; + const pathCodeElement = document.createElement('code'); + pathCodeElement.textContent = path; + const valueCodeElement = document.createElement('code'); + valueCodeElement.textContent = JSON.stringify(value, null, 2); + + label.appendChild(document.createTextNode('Setting ')); + label.appendChild(pathCodeElement); + label.appendChild(document.createTextNode(' = ')); + label.appendChild(valueCodeElement); + break; + } + case 'delete': { + const {path} = modification; + const pathCodeElement = document.createElement('code'); + pathCodeElement.textContent = path; + + label.appendChild(document.createTextNode('Deleting ')); + label.appendChild(pathCodeElement); + break; + } + case 'swap': { + const {path1, path2} = modification; + const path1CodeElement = document.createElement('code'); + path1CodeElement.textContent = path1; + const path2CodeElement = document.createElement('code'); + path2CodeElement.textContent = path2; + + label.appendChild(document.createTextNode('Swapping ')); + label.appendChild(path1CodeElement); + label.appendChild(document.createTextNode(' and ')); + label.appendChild(path2CodeElement); + break; + } + case 'splice': { + const {path, start, deleteCount, items} = modification; + const pathCodeElement = document.createElement('code'); + pathCodeElement.textContent = path; + + label.appendChild(document.createTextNode('Splicing ')); + label.appendChild(pathCodeElement); + label.appendChild(document.createTextNode(` at ${start} deleting ${deleteCount} items and inserting ${items.length} items`)); + break; + } + case 'push': { + const {path, items} = modification; + const pathCodeElement = document.createElement('code'); + pathCodeElement.textContent = path; + + label.appendChild(document.createTextNode(`Pushing ${items.length} items to `)); + label.appendChild(pathCodeElement); + break; + } + default: { + log.error(new Error(`Unknown modification: ${modification}`)); + } + } + } +} diff --git a/ext/js/pages/settings/settings-main.js b/ext/js/pages/settings/settings-main.js index f070c03876..4a7925bf68 100644 --- a/ext/js/pages/settings/settings-main.js +++ b/ext/js/pages/settings/settings-main.js @@ -40,6 +40,7 @@ import {PersistentStorageController} from './persistent-storage-controller.js'; import {PopupPreviewController} from './popup-preview-controller.js'; import {PopupWindowController} from './popup-window-controller.js'; import {ProfileController} from './profile-controller.js'; +import {RecommendedSettingsController} from './recommended-settings-controller.js'; import {ScanInputsController} from './scan-inputs-controller.js'; import {ScanInputsSimpleController} from './scan-inputs-simple-controller.js'; import {SecondarySearchDictionaryController} from './secondary-search-dictionary-controller.js'; @@ -174,6 +175,8 @@ await Application.main(true, async (application) => { const sortFrequencyDictionaryController = new SortFrequencyDictionaryController(settingsController); preparePromises.push(sortFrequencyDictionaryController.prepare()); + const recommendedSettingsController = new RecommendedSettingsController(settingsController); + preparePromises.push(recommendedSettingsController.prepare()); await Promise.all(preparePromises); diff --git a/ext/js/pages/welcome-main.js b/ext/js/pages/welcome-main.js index ae00c0c637..c4d75ee8c3 100644 --- a/ext/js/pages/welcome-main.js +++ b/ext/js/pages/welcome-main.js @@ -26,6 +26,7 @@ import {GenericSettingController} from './settings/generic-setting-controller.js import {LanguagesController} from './settings/languages-controller.js'; import {ModalController} from './settings/modal-controller.js'; import {RecommendedPermissionsController} from './settings/recommended-permissions-controller.js'; +import {RecommendedSettingsController} from './settings/recommended-settings-controller.js'; import {ScanInputsSimpleController} from './settings/scan-inputs-simple-controller.js'; import {SettingsController} from './settings/settings-controller.js'; import {SettingsDisplayController} from './settings/settings-display-controller.js'; @@ -105,6 +106,9 @@ await Application.main(true, async (application) => { const languagesController = new LanguagesController(settingsController); preparePromises.push(languagesController.prepare()); + const recommendedSettingsController = new RecommendedSettingsController(settingsController); + preparePromises.push(recommendedSettingsController.prepare()); + await Promise.all(preparePromises); document.documentElement.dataset.loaded = 'true'; diff --git a/ext/templates-modals.html b/ext/templates-modals.html index 6f1a3a02b2..4b0a088ddb 100644 --- a/ext/templates-modals.html +++ b/ext/templates-modals.html @@ -335,6 +335,30 @@

Pronunciation Dictionaries

+ + + + + + diff --git a/test/data/json.json b/test/data/json.json index 9fdda0b86b..321bff6483 100644 --- a/test/data/json.json +++ b/test/data/json.json @@ -98,6 +98,11 @@ "typeFile": "types/test/json.d.ts", "type": "AjvSchema" }, + { + "path": "ext/data/schemas/recommended-settings-schema.json", + "typeFile": "types/test/json.d.ts", + "type": "AjvSchema" + }, { "path": "test/data/translator-test-inputs.json", "typeFile": "types/test/translator.d.ts", @@ -192,6 +197,12 @@ "typeFile": "types/ext/dictionary-recommended.d.ts", "type": "RecommendedDictionaries", "schema": "ext/data/schemas/recommended-dictionaries-schema.json" + }, + { + "path": "ext/data/recommended-settings.json", + "typeFile": "types/ext/settings-controller.d.ts", + "type": "RecommendedSettingsByLanguage", + "schema": "ext/data/schemas/recommended-settings-schema.json" } ] } diff --git a/types/ext/settings-controller.d.ts b/types/ext/settings-controller.d.ts index e12651ebea..bbb393fbb7 100644 --- a/types/ext/settings-controller.d.ts +++ b/types/ext/settings-controller.d.ts @@ -22,6 +22,7 @@ import type * as Core from './core'; import type * as Settings from './settings'; import type * as SettingsModifications from './settings-modifications'; import type {EventNames, EventArgument as BaseEventArgument} from './core'; +import type {Modification} from './settings-modifications'; export type PageExitPrevention = { end: () => void; @@ -57,3 +58,12 @@ export type SettingsModification = THasScope extends export type SettingsExtraFields = THasScope extends true ? null : SettingsModifications.OptionsScope; export type ModifyResult = Core.Response; + +export type RecommendedSetting = { + modification: Modification; + description: string; +}; + +export type RecommendedSettingsByLanguage = { + [key: string]: RecommendedSetting[]; +}; From 92bac4e73082fa5b524c5ad49e7b541529f8422d Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Sun, 3 Nov 2024 16:02:29 -0500 Subject: [PATCH 10/17] Add radical normalization preprocessor to Japanese, Chinese, and Cantonese (#1559) * Add radical normalization to japanese * Add radical normalization to chinese * Add CJK Strokes Range * Add to yue * Move normalizeRadicalCharacters to CJK-util * Fix tests --- .eslintrc.json | 1 + ext/js/language/CJK-util.js | 39 +++++++++++++++++++++++++ ext/js/language/language-descriptors.js | 8 +++++ types/ext/language-descriptors.d.ts | 13 +++++++-- 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 64e8525893..1eb1278c19 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -560,6 +560,7 @@ "ext/js/dom/css-style-applier.js", "ext/js/language/CJK-util.js", "ext/js/language/ja/japanese.js", + "ext/js/language/text-processors.js", "ext/js/language/text-utilities.js", "ext/js/templates/anki-template-renderer-content-manager.js", "ext/js/templates/anki-template-renderer.js", diff --git a/ext/js/language/CJK-util.js b/ext/js/language/CJK-util.js index 5c59afb58b..5c56468261 100644 --- a/ext/js/language/CJK-util.js +++ b/ext/js/language/CJK-util.js @@ -15,6 +15,8 @@ * along with this program. If not, see . */ +import {basicTextProcessorOptions} from './text-processors.js'; + /** @type {import('CJK-util').CodepointRange} */ const CJK_UNIFIED_IDEOGRAPHS_RANGE = [0x4e00, 0x9fff]; /** @type {import('CJK-util').CodepointRange} */ @@ -94,3 +96,40 @@ export function isCodePointInRanges(codePoint, ranges) { } return false; } + +/** @type {import('CJK-util').CodepointRange} */ +export const KANGXI_RADICALS_RANGE = [0x2f00, 0x2fdf]; + +/** @type {import('CJK-util').CodepointRange} */ +export const CJK_RADICALS_SUPPLEMENT_RANGE = [0x2e80, 0x2eff]; + +/** @type {import('CJK-util').CodepointRange} */ +export const CJK_STROKES_RANGE = [0x31c0, 0x31ef]; + +/** @type {import('CJK-util').CodepointRange[]} */ +export const CJK_RADICALS_RANGES = [ + KANGXI_RADICALS_RANGE, + CJK_RADICALS_SUPPLEMENT_RANGE, + CJK_STROKES_RANGE, +]; + +/** + * @param {string} text + * @returns {string} + */ +export function normalizeRadicals(text) { + let result = ''; + for (let i = 0; i < text.length; i++) { + const codePoint = text[i].codePointAt(0); + result += codePoint && (isCodePointInRanges(codePoint, CJK_RADICALS_RANGES)) ? text[i].normalize('NFKD') : text[i]; + } + return result; +} + +/** @type {import('language').TextProcessor} */ +export const normalizeRadicalCharacters = { + name: 'Normalize radical characters', + description: '⼀ → 一 (U+2F00 → U+4E00)', + options: basicTextProcessorOptions, + process: (str, setting) => (setting ? normalizeRadicals(str) : str), +}; diff --git a/ext/js/language/language-descriptors.js b/ext/js/language/language-descriptors.js index a07437b711..02e2f35e1a 100644 --- a/ext/js/language/language-descriptors.js +++ b/ext/js/language/language-descriptors.js @@ -16,6 +16,7 @@ */ import {removeArabicScriptDiacritics} from './ar/arabic-text-preprocessors.js'; +import {normalizeRadicalCharacters} from './CJK-util.js'; import {eszettPreprocessor} from './de/german-text-preprocessors.js'; import {germanTransforms} from './de/german-transforms.js'; import {englishTransforms} from './en/english-transforms.js'; @@ -212,6 +213,7 @@ const languageDescriptors = [ convertHalfWidthCharacters, alphabeticToHiragana, normalizeCombiningCharacters, + normalizeRadicalCharacters, alphanumericWidthVariants, convertHiraganaToKatakana, collapseEmphaticSequences, @@ -374,6 +376,9 @@ const languageDescriptors = [ iso639_3: 'yue', name: 'Cantonese', exampleText: '讀', + textPreprocessors: { + normalizeRadicalCharacters, + }, }, { iso: 'zh', @@ -382,6 +387,9 @@ const languageDescriptors = [ exampleText: '读', isTextLookupWorthy: isStringPartiallyChinese, readingNormalizer: normalizePinyin, + textPreprocessors: { + normalizeRadicalCharacters, + }, }, ]; diff --git a/types/ext/language-descriptors.d.ts b/types/ext/language-descriptors.d.ts index 62643e0c75..199ec4b587 100644 --- a/types/ext/language-descriptors.d.ts +++ b/types/ext/language-descriptors.d.ts @@ -135,6 +135,7 @@ type AllTextProcessors = { convertHalfWidthCharacters: TextProcessor; alphabeticToHiragana: TextProcessor; normalizeCombiningCharacters: TextProcessor; + normalizeRadicalCharacters: TextProcessor; alphanumericWidthVariants: BidirectionalConversionPreprocessor; convertHiraganaToKatakana: BidirectionalConversionPreprocessor; collapseEmphaticSequences: TextProcessor<[collapseEmphatic: boolean, collapseEmphaticFull: boolean]>; @@ -200,6 +201,14 @@ type AllTextProcessors = { normalizeDiacritics: TextProcessor<'old' | 'new' | 'off'>; }; }; - yue: Record; - zh: Record; + yue: { + pre: { + normalizeRadicalCharacters: TextProcessor; + }; + }; + zh: { + pre: { + normalizeRadicalCharacters: TextProcessor; + }; + }; }; From d09cd38d8fc57a2a747106209e1b50c7e5cee8b5 Mon Sep 17 00:00:00 2001 From: Khai Truong <56820749+khaitruong922@users.noreply.github.com> Date: Mon, 4 Nov 2024 13:12:14 +0700 Subject: [PATCH 11/17] Update dictionary will preserve its settings (#1553) * Fix updating dictionaries resets their positioning * preserve settings when update dictionary * cleanup * cleanup pr * lint * Add profile id * Revert "Add profile id" This reverts commit b33bc945b483dcc219e3d1f2c9f96d4f408ca34f. * Reapply "Add profile id" This reverts commit 4d984d0a7a3ea30e055ed55616c15e01fe8390c8. * lint * don't need to handle id collision * update for loop syntax --- ext/js/data/options-util.js | 11 +++++ .../pages/settings/dictionary-controller.js | 19 +++++++- .../settings/dictionary-import-controller.js | 46 ++++++++++++++----- ext/js/pages/settings/profile-controller.js | 3 +- test/options-util.test.js | 3 +- types/ext/settings-controller.d.ts | 5 ++ types/ext/settings.d.ts | 1 + 7 files changed, 73 insertions(+), 15 deletions(-) diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index ad49ab7886..28aca32475 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -564,6 +564,7 @@ export class OptionsUtil { this._updateVersion50, this._updateVersion51, this._updateVersion52, + this._updateVersion53, ]; /* eslint-enable @typescript-eslint/unbound-method */ if (typeof targetVersion === 'number' && targetVersion < result.length) { @@ -1498,6 +1499,16 @@ export class OptionsUtil { } } + /** + * - Added profile id + * @type {import('options-util').UpdateFunction} + */ + async _updateVersion53(options) { + for (let i = 0; i < options.profiles.length; i++) { + options.profiles[i].id = `profile-${i}`; + } + } + /** * @param {string} url * @returns {Promise} diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index c45c01f148..a56720937a 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -1139,9 +1139,24 @@ export class DictionaryController { downloadUrl = downloadUrl ?? dictionaryInfo.downloadUrl; if (typeof downloadUrl !== 'string') { throw new Error('Attempted to update dictionary without download URL'); } - await this._deleteDictionary(dictionaryTitle); + const options = await this._settingsController.getOptionsFull(); + const {profiles} = options; + + /** @type {import('settings-controller.js').ProfilesDictionarySettings} */ + const profilesDictionarySettings = {}; + + for (const profile of profiles) { + const dictionaries = profile.options.dictionaries; + for (let i = 0; i < dictionaries.length; ++i) { + if (dictionaries[i].name === dictionaryTitle) { + profilesDictionarySettings[profile.id] = {...dictionaries[i], index: i}; + break; + } + } + } - this._settingsController.trigger('importDictionaryFromUrl', {url: downloadUrl}); + await this._deleteDictionary(dictionaryTitle); + this._settingsController.trigger('importDictionaryFromUrl', {url: downloadUrl, profilesDictionarySettings}); } /** diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index f2e0623550..ed358ff9b3 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -127,6 +127,7 @@ export class DictionaryImportController { const onProgress = importProgressTracker.onProgress.bind(importProgressTracker); await this._importDictionaries( this._generateFilesFromUrls([url], onProgress), + null, importProgressTracker, ); void this._recommendedDictionaryQueue.shift(); @@ -251,8 +252,8 @@ export class DictionaryImportController { /** * @param {import('settings-controller').EventArgument<'importDictionaryFromUrl'>} details */ - _onEventImportDictionaryFromUrl({url}) { - void this.importFilesFromURLs(url); + _onEventImportDictionaryFromUrl({url, profilesDictionarySettings}) { + void this.importFilesFromURLs(url, profilesDictionarySettings); } /** */ @@ -311,6 +312,7 @@ export class DictionaryImportController { const importProgressTracker = new ImportProgressTracker(this._getFileImportSteps(), fileArray.length); void this._importDictionaries( this._arrayToAsyncGenerator(fileArray), + null, importProgressTracker, ); } @@ -416,6 +418,7 @@ export class DictionaryImportController { node.value = ''; void this._importDictionaries( this._arrayToAsyncGenerator(files2), + null, new ImportProgressTracker(this._getFileImportSteps(), files2.length), ); } @@ -424,19 +427,21 @@ export class DictionaryImportController { async _onImportFromURL() { const text = this._importURLText.value.trim(); if (!text) { return; } - await this.importFilesFromURLs(text); + await this.importFilesFromURLs(text, null); } /** * @param {string} text + * @param {import('settings-controller').ProfilesDictionarySettings} profilesDictionarySettings */ - async importFilesFromURLs(text) { + async importFilesFromURLs(text, profilesDictionarySettings) { const urls = text.split('\n'); const importProgressTracker = new ImportProgressTracker(this._getUrlImportSteps(), urls.length); const onProgress = importProgressTracker.onProgress.bind(importProgressTracker); void this._importDictionaries( this._generateFilesFromUrls(urls, onProgress), + profilesDictionarySettings, importProgressTracker, ); } @@ -518,9 +523,10 @@ export class DictionaryImportController { /** * @param {AsyncGenerator} dictionaries + * @param {import('settings-controller').ProfilesDictionarySettings} profilesDictionarySettings * @param {ImportProgressTracker} importProgressTracker */ - async _importDictionaries(dictionaries, importProgressTracker) { + async _importDictionaries(dictionaries, profilesDictionarySettings, importProgressTracker) { if (this._modifying) { return; } const statusFooter = this._statusFooter; @@ -557,6 +563,7 @@ export class DictionaryImportController { ...errors, ...(await this._importDictionaryFromZip( file, + profilesDictionarySettings, importDetails, onProgress, ) ?? []), @@ -613,18 +620,19 @@ export class DictionaryImportController { /** * @param {File} file + * @param {import('settings-controller').ProfilesDictionarySettings} profilesDictionarySettings * @param {import('dictionary-importer').ImportDetails} importDetails * @param {import('dictionary-worker').ImportProgressCallback} onProgress * @returns {Promise} */ - async _importDictionaryFromZip(file, importDetails, onProgress) { + async _importDictionaryFromZip(file, profilesDictionarySettings, importDetails, onProgress) { const archiveContent = await this._readFile(file); const {result, errors} = await new DictionaryWorker().importDictionary(archiveContent, importDetails, onProgress); if (!result) { return errors; } - const errors2 = await this._addDictionarySettings(result); + const errors2 = await this._addDictionarySettings(result, profilesDictionarySettings); await this._settingsController.application.api.triggerDatabaseUpdated('dictionary', 'import'); @@ -638,9 +646,10 @@ export class DictionaryImportController { /** * @param {import('dictionary-importer').Summary} summary + * @param {import('settings-controller').ProfilesDictionarySettings} profilesDictionarySettings * @returns {Promise} */ - async _addDictionarySettings(summary) { + async _addDictionarySettings(summary, profilesDictionarySettings) { const {title, sequenced, styles} = summary; let optionsFull; // Workaround Firefox bug sometimes causing getOptionsFull to fail @@ -659,11 +668,26 @@ export class DictionaryImportController { const targets = []; const profileCount = optionsFull.profiles.length; for (let i = 0; i < profileCount; ++i) { - const {options} = optionsFull.profiles[i]; + const {options, id: profileId} = optionsFull.profiles[i]; const enabled = profileIndex === i; - const value = DictionaryController.createDefaultDictionarySettings(title, enabled, styles); + const defaultSettings = DictionaryController.createDefaultDictionarySettings(title, enabled, styles); const path1 = `profiles[${i}].options.dictionaries`; - targets.push({action: 'push', path: path1, items: [value]}); + + if (profilesDictionarySettings === null || typeof profilesDictionarySettings[profileId] === 'undefined') { + targets.push({action: 'push', path: path1, items: [defaultSettings]}); + } else { + const {index, ...currentSettings} = profilesDictionarySettings[profileId]; + targets.push({ + action: 'splice', + path: path1, + start: index, + items: [{ + ...currentSettings, + name: title, + }], + deleteCount: 0, + }); + } if (sequenced && options.general.mainDictionary === '') { const path2 = `profiles[${i}].options.general.mainDictionary`; diff --git a/ext/js/pages/settings/profile-controller.js b/ext/js/pages/settings/profile-controller.js index 558b45bedc..11f5a9ea00 100644 --- a/ext/js/pages/settings/profile-controller.js +++ b/ext/js/pages/settings/profile-controller.js @@ -17,7 +17,7 @@ */ import {EventListenerCollection} from '../../core/event-listener-collection.js'; -import {clone} from '../../core/utilities.js'; +import {clone, generateId} from '../../core/utilities.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {ProfileConditionsUI} from './profile-conditions-ui.js'; @@ -184,6 +184,7 @@ export class ProfileController { // Create new profile const newProfile = clone(profile); newProfile.name = this._createCopyName(profile.name, this._profiles, 100); + newProfile.id = generateId(16); // Update state const index = this._profiles.length; diff --git a/test/options-util.test.js b/test/options-util.test.js index d4ac27f663..3f37f8ee80 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -565,6 +565,7 @@ function createOptionsUpdatedTestData1() { return { profiles: [ { + id: 'profile-0', name: 'Default', options: createProfileOptionsUpdatedTestData1(), conditionGroups: [ @@ -644,7 +645,7 @@ function createOptionsUpdatedTestData1() { }, ], profileCurrent: 0, - version: 52, + version: 53, global: { database: { prefixWildcardsSupported: false, diff --git a/types/ext/settings-controller.d.ts b/types/ext/settings-controller.d.ts index bbb393fbb7..d87ca5d4a0 100644 --- a/types/ext/settings-controller.d.ts +++ b/types/ext/settings-controller.d.ts @@ -28,6 +28,10 @@ export type PageExitPrevention = { end: () => void; }; +type ProfileDictionarySettings = Settings.DictionaryOptions & {index: number}; + +export type ProfilesDictionarySettings = {[profileId: string]: ProfileDictionarySettings} | null; + export type Events = { optionsChanged: { options: Settings.ProfileOptions; @@ -42,6 +46,7 @@ export type Events = { }; importDictionaryFromUrl: { url: string; + profilesDictionarySettings: ProfilesDictionarySettings; }; dictionaryEnabled: Record; scanInputsChanged: { diff --git a/types/ext/settings.d.ts b/types/ext/settings.d.ts index ceffe1e00f..2d03f0f1c3 100644 --- a/types/ext/settings.d.ts +++ b/types/ext/settings.d.ts @@ -70,6 +70,7 @@ export type GlobalDatabaseOptions = { }; export type Profile = { + id: string; name: string; conditionGroups: ProfileConditionGroup[]; options: ProfileOptions; From 37ba526de4d696eddb7113aec8fc39b1163a3984 Mon Sep 17 00:00:00 2001 From: James Maa Date: Mon, 4 Nov 2024 09:35:08 -0800 Subject: [PATCH 12/17] Fix example text popup not updating correctly when scan resolution is set to `word` (#1527) * Add option to disallowExpandStartOffset * Types --- ext/js/app/frontend.js | 2 +- ext/js/dom/dom-text-scanner.js | 8 ++++---- ext/js/dom/text-source-range.js | 3 ++- ext/js/language/text-scanner.js | 16 +++++++++------- types/ext/text-scanner.d.ts | 2 +- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index 830f50fd14..e58828df13 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -224,7 +224,7 @@ export class Frontend { */ async setTextSource(textSource) { this._textScanner.setCurrentTextSource(null); - await this._textScanner.search(textSource); + await this._textScanner.search(textSource, null, false, true); } /** diff --git a/ext/js/dom/dom-text-scanner.js b/ext/js/dom/dom-text-scanner.js index 8171a37140..8413c79b0d 100644 --- a/ext/js/dom/dom-text-scanner.js +++ b/ext/js/dom/dom-text-scanner.js @@ -137,11 +137,11 @@ export class DOMTextScanner { if (nodeType === TEXT_NODE) { lastNode = node; - if (!( - forward ? + const shouldContinueScanning = forward ? this._seekTextNodeForward(/** @type {Text} */ (node), resetOffset) : - this._seekTextNodeBackward(/** @type {Text} */ (node), resetOffset) - )) { + this._seekTextNodeBackward(/** @type {Text} */ (node), resetOffset); + + if (!shouldContinueScanning) { // Length reached break; } diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js index 4450d32845..18f49cd733 100644 --- a/ext/js/dom/text-source-range.js +++ b/ext/js/dom/text-source-range.js @@ -166,7 +166,8 @@ export class TextSourceRange { */ setStartOffset(length, layoutAwareScan, stopAtWordBoundary = false) { if (this._disallowExpandSelection) { return 0; } - const state = new DOMTextScanner(this._range.startContainer, this._range.startOffset, !layoutAwareScan, layoutAwareScan, stopAtWordBoundary).seek(-length); + let state = new DOMTextScanner(this._range.startContainer, this._range.startOffset, !layoutAwareScan, layoutAwareScan, stopAtWordBoundary); + state = state.seek(-length); this._range.setStart(state.node, state.offset); this._rangeStartOffset = this._range.startOffset; this._content = state.content + this._content; diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index ea059be856..15e82162a4 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -425,12 +425,13 @@ export class TextScanner extends EventDispatcher { /** * @param {import('text-source').TextSource} textSource - * @param {import('text-scanner').InputInfoDetail} [inputDetail] - * @param {boolean} showEmpty + * @param {import('text-scanner').InputInfoDetail?} [inputDetail] + * @param {boolean} showEmpty shows a "No results found" popup if no results are found + * @param {boolean} disallowExpandStartOffset disallows expanding the start offset of the range */ - async search(textSource, inputDetail, showEmpty = false) { + async search(textSource, inputDetail, showEmpty = false, disallowExpandStartOffset = false) { const inputInfo = this._createInputInfo(null, 'script', 'script', true, [], [], inputDetail); - await this._search(textSource, this._searchTerms, this._searchKanji, inputInfo, showEmpty); + await this._search(textSource, this._searchTerms, this._searchKanji, inputInfo, showEmpty, disallowExpandStartOffset); } // Private @@ -455,8 +456,9 @@ export class TextScanner extends EventDispatcher { * @param {boolean} searchKanji * @param {import('text-scanner').InputInfo} inputInfo * @param {boolean} showEmpty shows a "No results found" popup if no results are found + * @param {boolean} disallowExpandStartOffset disallows expanding the start offset of the range */ - async _search(textSource, searchTerms, searchKanji, inputInfo, showEmpty = false) { + async _search(textSource, searchTerms, searchKanji, inputInfo, showEmpty = false, disallowExpandStartOffset = false) { try { const isAltText = textSource instanceof TextSourceElement; if (inputInfo.pointerType === 'touch') { @@ -479,7 +481,7 @@ export class TextScanner extends EventDispatcher { null ); - if (this._scanResolution === 'word') { + if (this._scanResolution === 'word' && !disallowExpandStartOffset) { // Move the start offset to the beginning of the word textSource.setStartOffset(this._scanLength, this._layoutAwareScan, true); } @@ -1497,7 +1499,7 @@ export class TextScanner extends EventDispatcher { * @param {boolean} passive * @param {import('input').Modifier[]} modifiers * @param {import('input').ModifierKey[]} modifierKeys - * @param {import('text-scanner').InputInfoDetail} [detail] + * @param {import('text-scanner').InputInfoDetail?} [detail] * @returns {import('text-scanner').InputInfo} */ _createInputInfo(input, pointerType, eventType, passive, modifiers, modifierKeys, detail) { diff --git a/types/ext/text-scanner.d.ts b/types/ext/text-scanner.d.ts index 859bfca270..54ab47838e 100644 --- a/types/ext/text-scanner.d.ts +++ b/types/ext/text-scanner.d.ts @@ -104,7 +104,7 @@ export type InputInfo = { passive: boolean; modifiers: Input.Modifier[]; modifierKeys: Input.ModifierKey[]; - detail: InputInfoDetail | undefined; + detail: InputInfoDetail | undefined | null; }; export type InputInfoDetail = { From afd0b677de777c7e060787c4e9f5e56ada59a0cd Mon Sep 17 00:00:00 2001 From: Khai Truong <56820749+khaitruong922@users.noreply.github.com> Date: Tue, 5 Nov 2024 01:33:01 +0700 Subject: [PATCH 13/17] Set editing profile index correctly when deleting profile (#1560) * handle editing profile index correctly when delete profile * lint * simplify index calculation * rename variables * Revert "rename variables" This reverts commit 4d81d24415703a732dee19a1e2dd525c288d190b. --- ext/js/pages/settings/profile-controller.js | 6 ++++-- ext/js/pages/settings/settings-controller.js | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ext/js/pages/settings/profile-controller.js b/ext/js/pages/settings/profile-controller.js index 11f5a9ea00..84dcb19ef5 100644 --- a/ext/js/pages/settings/profile-controller.js +++ b/ext/js/pages/settings/profile-controller.js @@ -259,8 +259,10 @@ export class ProfileController { this._updateProfileSelectOptions(); // Update profile index - if (settingsProfileIndex === profileIndex) { - this._settingsController.profileIndex = profileCurrentNew; + if (settingsProfileIndex >= profileIndex) { + this._settingsController.profileIndex = settingsProfileIndex - 1; + } else { + this._settingsController.refreshProfileIndex(); } // Modify settings diff --git a/ext/js/pages/settings/settings-controller.js b/ext/js/pages/settings/settings-controller.js index ee44f875f3..09057818f5 100644 --- a/ext/js/pages/settings/settings-controller.js +++ b/ext/js/pages/settings/settings-controller.js @@ -67,6 +67,11 @@ export class SettingsController extends EventDispatcher { this._setProfileIndex(value, true); } + /** */ + refreshProfileIndex() { + this._setProfileIndex(this._profileIndex, true); + } + /** @type {HtmlTemplateCollection} */ get templates() { return this._templates; From 093a2f370558ea576ddef9b99d5afa9e0386ee06 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 12:09:47 -0800 Subject: [PATCH 14/17] Bump CodSpeedHQ/action from 2 to 3 (#1297) Bumps [CodSpeedHQ/action](https://github.com/codspeedhq/action) from 2 to 3. - [Release notes](https://github.com/codspeedhq/action/releases) - [Changelog](https://github.com/CodSpeedHQ/action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codspeedhq/action/compare/v2...v3) --- updated-dependencies: - dependency-name: CodSpeedHQ/action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: marv --- .github/workflows/bench.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index b365d4567d..e8805d5f85 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -26,7 +26,7 @@ jobs: run: npm run build:libs - name: Run Benchmarks - uses: CodSpeedHQ/action@v2 + uses: CodSpeedHQ/action@v3 with: token: ${{ secrets.CODSPEED_TOKEN }} run: npm run bench From 37513b12c690735b0655f4ab01cbf1a1d450209e Mon Sep 17 00:00:00 2001 From: Kuuuube <61125188+Kuuuube@users.noreply.github.com> Date: Mon, 4 Nov 2024 20:36:19 -0500 Subject: [PATCH 15/17] Restore searchOnClick for search page (#1565) --- ext/js/display/display.js | 2 + ext/js/display/query-parser.js | 1 + ext/js/language/text-scanner.js | 67 ++++++++++++++++++++++++++++++++- types/ext/text-scanner.d.ts | 2 + 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/ext/js/display/display.js b/ext/js/display/display.js index 14b943e22e..610f659396 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -2014,6 +2014,8 @@ export class Display extends EventDispatcher { getSearchContext: this._getSearchContext.bind(this), searchTerms: true, searchKanji: false, + searchOnClick: true, + searchOnClickOnly: true, textSourceGenerator: this._textSourceGenerator, }); this._contentTextScanner.includeSelector = '.click-scannable,.click-scannable *'; diff --git a/ext/js/display/query-parser.js b/ext/js/display/query-parser.js index 1221cca319..cf487a3d35 100644 --- a/ext/js/display/query-parser.js +++ b/ext/js/display/query-parser.js @@ -66,6 +66,7 @@ export class QueryParser extends EventDispatcher { getSearchContext, searchTerms: true, searchKanji: false, + searchOnClick: true, textSourceGenerator, }); /** @type {?(import('../language/ja/japanese-wanakana.js'))} */ diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index 15e82162a4..f76bdf5251 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -39,6 +39,8 @@ export class TextScanner extends EventDispatcher { ignorePoint = null, searchTerms = false, searchKanji = false, + searchOnClick = false, + searchOnClickOnly = false, textSourceGenerator, }) { super(); @@ -56,6 +58,10 @@ export class TextScanner extends EventDispatcher { this._searchTerms = searchTerms; /** @type {boolean} */ this._searchKanji = searchKanji; + /** @type {boolean} */ + this._searchOnClick = searchOnClick; + /** @type {boolean} */ + this._searchOnClickOnly = searchOnClickOnly; /** @type {import('../dom/text-source-generator').TextSourceGenerator} */ this._textSourceGenerator = textSourceGenerator; @@ -658,6 +664,7 @@ export class TextScanner extends EventDispatcher { switch (e.button) { case 0: // Primary + if (this._searchOnClick) { this._resetPreventNextClickScan(); } this._scanTimerClear(); this._triggerClear('mousedown'); break; @@ -691,9 +698,33 @@ export class TextScanner extends EventDispatcher { return false; } + if (this._searchOnClick) { + this._onSearchClick(e); + return; + } + this._onMouseMove(e); } + /** + * @param {MouseEvent} e + */ + _onSearchClick(e) { + const preventNextClickScan = this._preventNextClickScan; + this._preventNextClickScan = false; + if (this._preventNextClickScanTimer !== null) { + clearTimeout(this._preventNextClickScanTimer); + this._preventNextClickScanTimer = null; + } + + if (preventNextClickScan) { return; } + + const modifiers = getActiveModifiersAndButtons(e); + const modifierKeys = getActiveModifiers(e); + const inputInfo = this._createInputInfo(null, 'mouse', 'click', false, modifiers, modifierKeys); + void this._searchAt(e.clientX, e.clientY, inputInfo); + } + /** */ _onAuxClick() { this._preventNextContextMenu = false; @@ -1117,7 +1148,9 @@ export class TextScanner extends EventDispatcher { const capture = true; /** @type {import('event-listener-collection').AddEventListenerArgs[]} */ let eventListenerInfos; - if (this._arePointerEventsSupported()) { + if (this._searchOnClickOnly) { + eventListenerInfos = this._getMouseClickOnlyEventListeners(capture); + } else if (this._arePointerEventsSupported()) { eventListenerInfos = this._getPointerEventListeners(capture); } else { eventListenerInfos = [...this._getMouseEventListeners(capture)]; @@ -1128,6 +1161,9 @@ export class TextScanner extends EventDispatcher { eventListenerInfos.push(...this._getTouchEventListeners(capture)); } } + if (this._searchOnClick) { + eventListenerInfos.push(...this._getMouseClickOnlyEventListeners2(capture)); + } eventListenerInfos.push(this._getSelectionChangeCheckUserSelectionListener()); @@ -1194,6 +1230,35 @@ export class TextScanner extends EventDispatcher { ]; } + /** + * @param {boolean} capture + * @returns {import('event-listener-collection').AddEventListenerArgs[]} + */ + _getMouseClickOnlyEventListeners(capture) { + return [ + [this._node, 'click', this._onClick.bind(this), capture], + ]; + } + + /** + * @param {boolean} capture + * @returns {import('event-listener-collection').AddEventListenerArgs[]} + */ + _getMouseClickOnlyEventListeners2(capture) { + const {documentElement} = document; + /** @type {import('event-listener-collection').AddEventListenerArgs[]} */ + const entries = [ + [document, 'selectionchange', this._onSelectionChange.bind(this)], + ]; + if (documentElement !== null) { + entries.push([documentElement, 'mousedown', this._onSearchClickMouseDown.bind(this), capture]); + if (this._touchInputEnabled) { + entries.push([documentElement, 'touchstart', this._onSearchClickTouchStart.bind(this), {passive: true, capture}]); + } + } + return entries; + } + /** * @returns {import('event-listener-collection').AddEventListenerArgs} */ diff --git a/types/ext/text-scanner.d.ts b/types/ext/text-scanner.d.ts index 54ab47838e..dd733e371d 100644 --- a/types/ext/text-scanner.d.ts +++ b/types/ext/text-scanner.d.ts @@ -154,6 +154,8 @@ export type ConstructorDetails = { ignorePoint?: ((x: number, y: number) => Promise) | null; searchTerms?: boolean; searchKanji?: boolean; + searchOnClick?: boolean; + searchOnClickOnly?: boolean; textSourceGenerator: TextSourceGenerator; }; From dc174a9a7e69271f93f531b75cf240f6b24fd9e4 Mon Sep 17 00:00:00 2001 From: Khai Truong <56820749+khaitruong922@users.noreply.github.com> Date: Wed, 6 Nov 2024 01:38:56 +0700 Subject: [PATCH 16/17] Update dictionary will update alias if alias is the same as previous title (#1568) Update dictionary alias if it is the same as title --- ext/js/pages/settings/dictionary-import-controller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index ed358ff9b3..1b1b335136 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -676,7 +676,8 @@ export class DictionaryImportController { if (profilesDictionarySettings === null || typeof profilesDictionarySettings[profileId] === 'undefined') { targets.push({action: 'push', path: path1, items: [defaultSettings]}); } else { - const {index, ...currentSettings} = profilesDictionarySettings[profileId]; + const {index, alias, name, ...currentSettings} = profilesDictionarySettings[profileId]; + const newAlias = alias === name ? title : alias; targets.push({ action: 'splice', path: path1, @@ -684,6 +685,7 @@ export class DictionaryImportController { items: [{ ...currentSettings, name: title, + alias: newAlias, }], deleteCount: 0, }); From e7cfac47aa0bdcf617b8688de0d571d75ef2f547 Mon Sep 17 00:00:00 2001 From: James Maa Date: Tue, 5 Nov 2024 10:49:42 -0800 Subject: [PATCH 17/17] Add support page (#1562) * Add support page * Comments --- ext/info.html | 10 ++++---- ext/js/pages/support-main.js | 41 +++++++++++++++++++++++++++++++++ ext/support.html | 44 ++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 ext/js/pages/support-main.js create mode 100644 ext/support.html diff --git a/ext/info.html b/ext/info.html index d663a93bef..7c85b80be3 100644 --- a/ext/info.html +++ b/ext/info.html @@ -66,11 +66,13 @@
diff --git a/ext/js/pages/support-main.js b/ext/js/pages/support-main.js new file mode 100644 index 0000000000..5f35b7960f --- /dev/null +++ b/ext/js/pages/support-main.js @@ -0,0 +1,41 @@ +/* + * 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 {ThemeController} from '../app/theme-controller.js'; +import {Application} from '../application.js'; +import {SettingsController} from './settings/settings-controller.js'; + +await Application.main(true, async (application) => { + const settingsController = new SettingsController(application); + await settingsController.prepare(); + + /** @type {ThemeController} */ + const themeController = new ThemeController(document.documentElement); + themeController.prepare(); + const optionsFull = await application.api.optionsGetFull(); + const {profiles, profileCurrent} = optionsFull; + const defaultProfile = (profileCurrent >= 0 && profileCurrent < profiles.length) ? profiles[profileCurrent] : null; + if (defaultProfile !== null) { + themeController.theme = defaultProfile.options.general.popupTheme; + themeController.siteOverride = true; + themeController.updateTheme(); + } + + document.body.hidden = false; + + document.documentElement.dataset.loaded = 'true'; +}); diff --git a/ext/support.html b/ext/support.html new file mode 100644 index 0000000000..00bcf3e819 --- /dev/null +++ b/ext/support.html @@ -0,0 +1,44 @@ + + + + + + Support Yomitan + + + + + + + + + + + + + + +
+
+
+ + + +

Support Yomitan ❤️

+ +

Here are some ways to support Yomitan:

+
+
+ +
+
+
+
+
+ + +