diff --git a/ext/bg/data/options-schema.json b/ext/bg/data/options-schema.json index 44bff10a..8c00d455 100644 --- a/ext/bg/data/options-schema.json +++ b/ext/bg/data/options-schema.json @@ -739,7 +739,8 @@ "kanji", "duplicateScope", "checkForDuplicates", - "fieldTemplates" + "fieldTemplates", + "suspendNewCards" ], "properties": { "enable": { @@ -841,6 +842,10 @@ "fieldTemplates": { "type": ["string", "null"], "default": null + }, + "suspendNewCards": { + "type": "boolean", + "default": false } } }, diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index 05c07ce2..68d9fc43 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -122,6 +122,22 @@ class AnkiConnect { return await this._invoke('multi', {actions}); } + async suspendCards(cardIds) { + if (!this._enabled) { return false; } + await this._checkVersion(); + return await this._invoke('suspend', {cards: cardIds}); + } + + async findCards(query) { + if (!this._enabled) { return []; } + await this._checkVersion(); + return await this._invoke('findCards', {query}); + } + + async findCardsForNote(noteId) { + return await this.findCards(`nid:${noteId}`); + } + getRootDeckName(deckName) { const index = deckName.indexOf('::'); return index >= 0 ? deckName.substring(0, index) : deckName; diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 6410b0fc..11a17381 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -97,6 +97,7 @@ class Backend { ['getAnkiNoteInfo', {async: true, contentScript: true, handler: this._onApiGetAnkiNoteInfo.bind(this)}], ['injectAnkiNoteMedia', {async: true, contentScript: true, handler: this._onApiInjectAnkiNoteMedia.bind(this)}], ['noteView', {async: true, contentScript: true, handler: this._onApiNoteView.bind(this)}], + ['suspendAnkiCardsForNote', {async: true, contentScript: true, handler: this._onApiSuspendAnkiCardsForNote.bind(this)}], ['commandExec', {async: false, contentScript: true, handler: this._onApiCommandExec.bind(this)}], ['getDefinitionAudioInfo', {async: true, contentScript: true, handler: this._onApiGetDefinitionAudioInfo.bind(this)}], ['downloadDefinitionAudio', {async: true, contentScript: true, handler: this._onApiDownloadDefinitionAudio.bind(this)}], @@ -495,6 +496,16 @@ class Backend { return await this._anki.guiBrowseNote(noteId); } + async _onApiSuspendAnkiCardsForNote({noteId}) { + const cardIds = await this._anki.findCardsForNote(noteId); + const count = cardIds.length; + if (count > 0) { + const okay = await this._anki.suspendCards(cardIds); + if (!okay) { return 0; } + } + return count; + } + _onApiCommandExec({command, params}) { return this._runCommand(command, params); } diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 0d3e42a1..cbc390da 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -687,6 +687,8 @@ class OptionsUtil { // Added sentenceParsing.enableTerminationCharacters. // Added sentenceParsing.terminationCharacters. // Changed general.popupActionBarLocation. + // Added inputs.hotkeys. + // Added anki.suspendNewCards. for (const profile of options.profiles) { profile.options.translation.textReplacements = { searchOriginal: true, @@ -731,6 +733,7 @@ class OptionsUtil { {action: 'copyHostSelection', key: 'KeyC', modifiers: ['ctrl'], scopes: ['popup', 'search'], enabled: true} ] }; + profile.options.anki.suspendNewCards = false; } return options; } diff --git a/ext/bg/settings2.html b/ext/bg/settings2.html index 5b9d702d..f44b7eab 100644 --- a/ext/bg/settings2.html +++ b/ext/bg/settings2.html @@ -1357,7 +1357,7 @@
-
+
Check for card duplicates
When a card is detected as a duplicate, the add buttons will be disabled.
@@ -1435,6 +1435,16 @@
+ +
+
+
Suspend new cards
+
New cards will be suspended when a note is added.
+
+
+ +
+
Configure Anki card format…
diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 4fde30b0..433a52e2 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -93,6 +93,10 @@ const api = (() => { return this._invoke('noteView', {noteId}); } + suspendAnkiCardsForNote(noteId) { + return this._invoke('suspendAnkiCardsForNote', {noteId}); + } + getDefinitionAudioInfo(source, expression, reading, details) { return this._invoke('getDefinitionAudioInfo', {source, expression, reading, details}); } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 45019039..67498d01 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -1181,10 +1181,18 @@ class Display extends EventDispatcher { const overrideToken = this._progressIndicatorVisible.setOverride(true); try { + const {anki: {suspendNewCards}} = this._options; const noteContext = this._getNoteContext(); const note = await this._createNote(definition, mode, noteContext, true); const noteId = await api.addAnkiNote(note); if (noteId) { + if (suspendNewCards) { + try { + await api.suspendAnkiCardsForNote(noteId); + } catch (e) { + // NOP + } + } button.disabled = true; this._viewerButtonShow(definitionIndex, noteId); } else { diff --git a/test/test-options-util.js b/test/test-options-util.js index beef6f80..e245a285 100644 --- a/test/test-options-util.js +++ b/test/test-options-util.js @@ -417,7 +417,8 @@ function createProfileOptionsUpdatedTestData1() { kanji: {deck: '', model: '', fields: {}}, duplicateScope: 'collection', checkForDuplicates: true, - fieldTemplates: null + fieldTemplates: null, + suspendNewCards: false }, sentenceParsing: { scanExtent: 200,