From 52d3d42fddfc799153a5d094c1422f1fe59424b4 Mon Sep 17 00:00:00 2001 From: Stefan Vukovic Date: Tue, 16 Apr 2024 21:23:02 +0200 Subject: [PATCH 1/2] add profile switching --- ext/data/schemas/options-schema.json | 8 ++++--- ext/js/data/options-util.js | 17 +++++++++++++- ext/js/display/display.js | 22 +++++++++++++++++++ .../settings/keyboard-shortcuts-controller.js | 2 ++ ext/templates-settings.html | 2 ++ test/options-util.test.js | 6 +++-- 6 files changed, 51 insertions(+), 6 deletions(-) diff --git a/ext/data/schemas/options-schema.json b/ext/data/schemas/options-schema.json index 660961a811..13cdced7a6 100644 --- a/ext/data/schemas/options-schema.json +++ b/ext/data/schemas/options-schema.json @@ -1149,8 +1149,8 @@ } }, "default": [ - {"action": "close", "argument": "", "key": "Escape", "modifiers": [], "scopes": ["popup"], "enabled": true}, - {"action": "focusSearchBox", "argument": "", "key": "Escape", "modifiers": [], "scopes": ["search"], "enabled": true}, + {"action": "close", "argument": "", "key": "Escape", "modifiers": [], "scopes": ["popup"], "enabled": true}, + {"action": "focusSearchBox", "argument": "", "key": "Escape", "modifiers": [], "scopes": ["search"], "enabled": true}, {"action": "previousEntry", "argument": "3", "key": "PageUp", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, {"action": "nextEntry", "argument": "3", "key": "PageDown", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, {"action": "lastEntry", "argument": "", "key": "End", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, @@ -1159,12 +1159,14 @@ {"action": "nextEntry", "argument": "1", "key": "ArrowDown", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, {"action": "historyBackward", "argument": "", "key": "KeyB", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, {"action": "historyForward", "argument": "", "key": "KeyF", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, + {"action": "profilePrevious", "argument": "", "key": "Minus", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, + {"action": "profileNext", "argument": "", "key": "Equal", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, {"action": "addNoteKanji", "argument": "", "key": "KeyK", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, {"action": "addNoteTermKanji", "argument": "", "key": "KeyE", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, {"action": "addNoteTermKana", "argument": "", "key": "KeyR", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, {"action": "playAudio", "argument": "", "key": "KeyP", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, {"action": "viewNotes", "argument": "", "key": "KeyV", "modifiers": ["alt"], "scopes": ["popup", "search"], "enabled": true}, - {"action": "copyHostSelection", "argument": "", "key": "KeyC", "modifiers": ["ctrl"], "scopes": ["popup"], "enabled": true} + {"action": "copyHostSelection", "argument": "", "key": "KeyC", "modifiers": ["ctrl"], "scopes": ["popup"], "enabled": true} ] } } diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index 4e468aeadf..a959362de5 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -536,7 +536,8 @@ export class OptionsUtil { this._updateVersion27, this._updateVersion28, this._updateVersion29, - this._updateVersion30 + this._updateVersion30, + this._updateVersion31 ]; /* eslint-enable @typescript-eslint/unbound-method */ if (typeof targetVersion === 'number' && targetVersion < result.length) { @@ -1233,6 +1234,20 @@ export class OptionsUtil { } } + /** + * - Added profilePrevious and profileNext to hotkeys. + * @type {import('options-util').UpdateFunction} + */ + async _updateVersion31(options) { + for (const profile of options.profiles) { + profile.options.inputs.hotkeys.push( + {action: 'profilePrevious', key: 'Minus', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true}, + {action: 'profileNext', key: 'Equal', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true} + ); + } + } + + /** * @param {string} url * @returns {Promise} diff --git a/ext/js/display/display.js b/ext/js/display/display.js index 10c1545c80..6b73f19b20 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -196,6 +196,8 @@ export class Display extends EventDispatcher { ['firstEntry', () => { this._focusEntry(0, 0, true); }], ['historyBackward', () => { this._sourceTermView(); }], ['historyForward', () => { this._nextTermView(); }], + ['profilePrevious', async () => { await this._setProfile(-1); }], + ['profileNext', async () => { await this._setProfile(1); }], ['copyHostSelection', () => this._copyHostSelection()], ['nextEntryDifferentDictionary', () => { this._focusEntryWithDifferentDictionary(1, true); }], ['previousEntryDifferentDictionary', () => { this._focusEntryWithDifferentDictionary(-1, true); }] @@ -1996,6 +1998,26 @@ export class Display extends EventDispatcher { this._focusEntry(this._index + count * sign, 0, true); } + /** + * @param {number} direction + */ + async _setProfile(direction) { + const optionsFull = await this.application.api.optionsGetFull(); + + const profileCount = optionsFull.profiles.length; + const newProfile = (optionsFull.profileCurrent + direction + profileCount) % profileCount; + + /** @type {import('settings-modifications').ScopedModificationSet} */ + const modification = { + action: 'set', + path: 'profileCurrent', + value: newProfile, + scope: 'global', + optionsContext: null + }; + await this.application.api.modifySettings([modification], 'search'); + } + /** */ _closeAllPopupMenus() { for (const popupMenu of PopupMenu.openMenus) { diff --git a/ext/js/pages/settings/keyboard-shortcuts-controller.js b/ext/js/pages/settings/keyboard-shortcuts-controller.js index b1ea6bb005..1e00d9074b 100644 --- a/ext/js/pages/settings/keyboard-shortcuts-controller.js +++ b/ext/js/pages/settings/keyboard-shortcuts-controller.js @@ -59,6 +59,8 @@ export class KeyboardShortcutController { ['previousEntryDifferentDictionary', {scopes: new Set(['popup', 'search'])}], ['historyBackward', {scopes: new Set(['popup', 'search'])}], ['historyForward', {scopes: new Set(['popup', 'search'])}], + ['profilePrevious', {scopes: new Set(['popup', 'search'])}], + ['profileNext', {scopes: new Set(['popup', 'search'])}], ['addNoteKanji', {scopes: new Set(['popup', 'search'])}], ['addNoteTermKanji', {scopes: new Set(['popup', 'search'])}], ['addNoteTermKana', {scopes: new Set(['popup', 'search'])}], diff --git a/ext/templates-settings.html b/ext/templates-settings.html index 0fb29c2235..ce703d89f1 100644 --- a/ext/templates-settings.html +++ b/ext/templates-settings.html @@ -363,6 +363,8 @@ + + diff --git a/test/options-util.test.js b/test/options-util.test.js index d1e9389c6a..578082f4dc 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -490,7 +490,9 @@ function createProfileOptionsUpdatedTestData1() { {action: 'addNoteTermKana', argument: '', key: 'KeyR', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true}, {action: 'playAudio', argument: '', key: 'KeyP', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true}, {action: 'viewNotes', argument: '', key: 'KeyV', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true}, - {action: 'copyHostSelection', argument: '', key: 'KeyC', modifiers: ['ctrl'], scopes: ['popup'], enabled: true} + {action: 'copyHostSelection', argument: '', key: 'KeyC', modifiers: ['ctrl'], scopes: ['popup'], enabled: true}, + {action: 'profilePrevious', argument: '', key: 'Minus', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true}, + {action: 'profileNext', argument: '', key: 'Equal', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true} ] /* eslint-enable @stylistic/no-multi-spaces */ }, @@ -602,7 +604,7 @@ function createOptionsUpdatedTestData1() { } ], profileCurrent: 0, - version: 30, + version: 31, global: { database: { prefixWildcardsSupported: false From 62ea7a2a075cfcbb230a235c490f4b3bb51dbf39 Mon Sep 17 00:00:00 2001 From: StefanVukovic99 Date: Mon, 22 Apr 2024 21:58:32 +0200 Subject: [PATCH 2/2] add duplicate behavior dropdown (#853) * duplicate behavior dropdown * allow duplicate for existing users * Update docs/anki-integration.md Co-authored-by: James Maa Signed-off-by: StefanVukovic99 * Update docs/anki-integration.md Co-authored-by: James Maa Signed-off-by: StefanVukovic99 * Update docs/anki-integration.md Co-authored-by: James Maa Signed-off-by: StefanVukovic99 * remove suggestion comment --------- Signed-off-by: StefanVukovic99 Co-authored-by: James Maa --- docs/anki-integration.md | 11 ++++++----- ext/data/schemas/options-schema.json | 5 +++++ ext/js/data/options-util.js | 15 +++++++++++++-- ext/js/display/display-anki.js | 19 +++++++++++++++++-- ext/settings.html | 16 +++++++++++++++- test/options-util.test.js | 3 ++- types/ext/settings.d.ts | 3 +++ 7 files changed, 61 insertions(+), 11 deletions(-) diff --git a/docs/anki-integration.md b/docs/anki-integration.md index 3e75301519..cae3d96e0f 100644 --- a/docs/anki-integration.md +++ b/docs/anki-integration.md @@ -101,13 +101,14 @@ be able to create a flashcard for はし because they s Once Yomitan is configured, it becomes trivial to create new flashcards with a single click. You will see the following icons next to term definitions: -- Clicking ![](../img/btn-add-expression.png) adds the current expression as kanji (e.g. 食べる). -- Clicking ![](../img/btn-add-reading.png) adds the current expression as hiragana or katakana (e.g. たべる). +- Clicking ![](../img/btn-add-expression.png) adds the current expression (e.g. 食べる). +- Clicking ![](../img/btn-add-reading.png) adds the current expression's reading (e.g. たべる). -If "Check for card duplicates" is on, and a card for the current definition already exists in the deck, you will see the following icons instead: +If _Check for card duplicates_ is on, and a card for the current definition already exists in the deck, you will see the book icon. +If _When a duplicate is detected_ is set to `Prevent adding`, the icons will appear grayed out. If set to `Allow adding`, the icons will change to: -- Adding the current expression: ![](../img/btn-add-duplicate-expression.png) -- Adding as hiragana or katakana: ![](../img/btn-add-duplicate-reading.png) +- ![](../img/btn-add-duplicate-expression.png): to add the expression +- ![](../img/btn-add-duplicate-reading.png): to add the reading Below are some troubleshooting tips you can try if you are unable to create new flashcards: diff --git a/ext/data/schemas/options-schema.json b/ext/data/schemas/options-schema.json index 13cdced7a6..b3f688817d 100644 --- a/ext/data/schemas/options-schema.json +++ b/ext/data/schemas/options-schema.json @@ -988,6 +988,11 @@ "type": "boolean", "default": true }, + "duplicateBehavior": { + "type": "string", + "enum": ["prevent", "new"], + "default": "prevent" + }, "fieldTemplates": { "type": ["string", "null"], "default": null diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index a959362de5..5319cfe454 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -537,7 +537,8 @@ export class OptionsUtil { this._updateVersion28, this._updateVersion29, this._updateVersion30, - this._updateVersion31 + this._updateVersion31, + this._updateVersion32 ]; /* eslint-enable @typescript-eslint/unbound-method */ if (typeof targetVersion === 'number' && targetVersion < result.length) { @@ -1234,11 +1235,21 @@ export class OptionsUtil { } } + /** + * - Added anki.duplicateBehavior + * @type {import('options-util').UpdateFunction} + */ + _updateVersion31(options) { + for (const {options: profileOptions} of options.profiles) { + profileOptions.anki.duplicateBehavior = 'new'; + } + } + /** * - Added profilePrevious and profileNext to hotkeys. * @type {import('options-util').UpdateFunction} */ - async _updateVersion31(options) { + async _updateVersion32(options) { for (const profile of options.profiles) { profile.options.inputs.hotkeys.push( {action: 'profilePrevious', key: 'Minus', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true}, diff --git a/ext/js/display/display-anki.js b/ext/js/display/display-anki.js index 446b8b4838..23f7157f91 100644 --- a/ext/js/display/display-anki.js +++ b/ext/js/display/display-anki.js @@ -74,6 +74,8 @@ export class DisplayAnki { this._duplicateScope = 'collection'; /** @type {boolean} */ this._duplicateScopeCheckAllModels = false; + /** @type {import('settings').AnkiDuplicateBehavior} */ + this._duplicateBehavior = 'prevent'; /** @type {import('settings').AnkiScreenshotFormat} */ this._screenshotFormat = 'png'; /** @type {number} */ @@ -192,6 +194,7 @@ export class DisplayAnki { tags, duplicateScope, duplicateScopeCheckAllModels, + duplicateBehavior, suspendNewCards, checkForDuplicates, displayTags, @@ -212,6 +215,7 @@ export class DisplayAnki { this._displayTags = displayTags; this._duplicateScope = duplicateScope; this._duplicateScopeCheckAllModels = duplicateScopeCheckAllModels; + this._duplicateBehavior = duplicateBehavior; this._screenshotFormat = format; this._screenshotQuality = quality; this._scanLength = scanLength; @@ -419,7 +423,7 @@ export class DisplayAnki { // If entry has noteIds, show the "add duplicate" button. if (Array.isArray(noteIds) && noteIds.length > 0) { - this._showDuplicateAddButton(button); + this._updateButtonForDuplicate(button); } } @@ -441,6 +445,17 @@ export class DisplayAnki { } } + /** + * @param {HTMLButtonElement} button + */ + _updateButtonForDuplicate(button) { + if (this._duplicateBehavior === 'prevent') { + button.disabled = true; + } else { + this._showDuplicateAddButton(button); + } + } + /** * @param {number} i * @param {(?import('anki').NoteInfo)[]} noteInfos @@ -550,7 +565,7 @@ export class DisplayAnki { } } // Now that this dictionary entry has a duplicate in Anki, show the "add duplicate" buttons. - this._showDuplicateAddButton(button); + this._updateButtonForDuplicate(button); this._updateViewNoteButton(dictionaryEntryIndex, [noteId], true); } diff --git a/ext/settings.html b/ext/settings.html index ae559f4adc..3c94b95ea2 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -1619,7 +1619,6 @@

Yomitan Settings

Check for card duplicates
-
When a card is detected as a duplicate, the add buttons will turn red and change appearance.
+
+
+
+
+ When a duplicate is detected +
+
+
+ +
+
+
diff --git a/test/options-util.test.js b/test/options-util.test.js index 578082f4dc..0966d0d3b0 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -440,6 +440,7 @@ function createProfileOptionsUpdatedTestData1() { screenshot: {format: 'png', quality: 92}, terms: {deck: '', model: '', fields: {}}, kanji: {deck: '', model: '', fields: {}}, + duplicateBehavior: 'new', duplicateScope: 'collection', duplicateScopeCheckAllModels: false, displayTags: 'never', @@ -604,7 +605,7 @@ function createOptionsUpdatedTestData1() { } ], profileCurrent: 0, - version: 31, + version: 32, global: { database: { prefixWildcardsSupported: false diff --git a/types/ext/settings.d.ts b/types/ext/settings.d.ts index 8075476ba8..4e3b185ae7 100644 --- a/types/ext/settings.d.ts +++ b/types/ext/settings.d.ts @@ -281,6 +281,7 @@ export type AnkiOptions = { kanji: AnkiNoteOptions; duplicateScope: AnkiDuplicateScope; duplicateScopeCheckAllModels: boolean; + duplicateBehavior: AnkiDuplicateBehavior; checkForDuplicates: boolean; fieldTemplates: string | null; suspendNewCards: boolean; @@ -394,6 +395,8 @@ export type AnkiScreenshotFormat = 'png' | 'jpeg'; export type AnkiDuplicateScope = 'collection' | 'deck' | 'deck-root'; +export type AnkiDuplicateBehavior = 'prevent' | 'new'; + export type AnkiDisplayTags = 'never' | 'always' | 'non-standard'; export type AnkiNoteGuiMode = 'browse' | 'edit';