diff --git a/rslib/src/backend/card.rs b/rslib/src/backend/card.rs index 8172f3f56..83c2d6eec 100644 --- a/rslib/src/backend/card.rs +++ b/rslib/src/backend/card.rs @@ -26,7 +26,7 @@ impl CardsService for Backend { let op = if input.skip_undo_entry { None } else { - Some(UndoableOpKind::UpdateCard) + Some(Op::UpdateCard) }; let mut card: Card = input.card.ok_or(AnkiError::NotFound)?.try_into()?; col.update_card_with_op(&mut card, op) diff --git a/rslib/src/backend/notes.rs b/rslib/src/backend/notes.rs index 5c82ad5a4..bb5331a69 100644 --- a/rslib/src/backend/notes.rs +++ b/rslib/src/backend/notes.rs @@ -51,7 +51,7 @@ impl NotesService for Backend { let op = if input.skip_undo_entry { None } else { - Some(UndoableOpKind::UpdateNote) + Some(Op::UpdateNote) }; let mut note: Note = input.note.ok_or(AnkiError::NotFound)?.into(); col.update_note_with_op(&mut note, op) diff --git a/rslib/src/card/mod.rs b/rslib/src/card/mod.rs index dec89213a..85b8ffc63 100644 --- a/rslib/src/card/mod.rs +++ b/rslib/src/card/mod.rs @@ -3,12 +3,13 @@ pub(crate) mod undo; +use crate::define_newtype; use crate::err::{AnkiError, Result}; use crate::notes::NoteID; use crate::{ - collection::Collection, config::SchedulerVersion, timestamp::TimestampSecs, types::Usn, + collection::Collection, config::SchedulerVersion, prelude::*, timestamp::TimestampSecs, + types::Usn, }; -use crate::{define_newtype, undo::UndoableOpKind}; use crate::{deckconf::DeckConf, decks::DeckID}; use num_enum::TryFromPrimitive; @@ -139,11 +140,7 @@ impl Card { } impl Collection { - pub(crate) fn update_card_with_op( - &mut self, - card: &mut Card, - op: Option, - ) -> Result<()> { + pub(crate) fn update_card_with_op(&mut self, card: &mut Card, op: Option) -> Result<()> { let existing = self.storage.get_card(card.id)?.ok_or(AnkiError::NotFound)?; self.transact(op, |col| col.update_card_inner(card, existing, col.usn()?)) } @@ -211,7 +208,7 @@ impl Collection { self.storage.set_search_table_to_card_ids(cards, false)?; let sched = self.scheduler_version(); let usn = self.usn()?; - self.transact(Some(UndoableOpKind::SetDeck), |col| { + self.transact(Some(Op::SetDeck), |col| { for mut card in col.storage.all_searched_cards()? { if card.deck_id == deck_id { continue; diff --git a/rslib/src/collection.rs b/rslib/src/collection.rs index 2e664b087..4f1c15692 100644 --- a/rslib/src/collection.rs +++ b/rslib/src/collection.rs @@ -85,7 +85,7 @@ pub struct Collection { impl Collection { /// Execute the provided closure in a transaction, rolling back if /// an error is returned. - pub(crate) fn transact(&mut self, op: Option, func: F) -> Result + pub(crate) fn transact(&mut self, op: Option, func: F) -> Result where F: FnOnce(&mut Collection) -> Result, { diff --git a/rslib/src/config/undo.rs b/rslib/src/config/undo.rs index 1aec6d075..3c11c300a 100644 --- a/rslib/src/config/undo.rs +++ b/rslib/src/config/undo.rs @@ -71,7 +71,7 @@ mod test { fn undo() -> Result<()> { let mut col = open_test_collection(); // the op kind doesn't matter, we just need undo enabled - let op = Some(UndoableOpKind::Bury); + let op = Some(Op::Bury); // test key let key = BoolKey::NormalizeNoteText; diff --git a/rslib/src/decks/mod.rs b/rslib/src/decks/mod.rs index f0e80a4d7..2a9581fda 100644 --- a/rslib/src/decks/mod.rs +++ b/rslib/src/decks/mod.rs @@ -10,16 +10,14 @@ pub use crate::backend_proto::{ deck_kind::Kind as DeckKind, filtered_search_term::FilteredSearchOrder, Deck as DeckProto, DeckCommon, DeckKind as DeckKindProto, FilteredDeck, FilteredSearchTerm, NormalDeck, }; -use crate::{ - backend_proto as pb, markdown::render_markdown, text::sanitize_html_no_images, - undo::UndoableOpKind, -}; +use crate::{backend_proto as pb, markdown::render_markdown, text::sanitize_html_no_images}; use crate::{ collection::Collection, deckconf::DeckConfID, define_newtype, err::{AnkiError, Result}, i18n::TR, + prelude::*, text::normalize_to_nfc, timestamp::TimestampSecs, types::Usn, @@ -283,7 +281,7 @@ impl Collection { return Err(AnkiError::invalid_input("deck to add must have id 0")); } - self.transact(Some(UndoableOpKind::AddDeck), |col| { + self.transact(Some(Op::AddDeck), |col| { let usn = col.usn()?; col.prepare_deck_for_update(deck, usn)?; deck.set_modified(usn); @@ -293,14 +291,14 @@ impl Collection { } pub fn update_deck(&mut self, deck: &mut Deck) -> Result<()> { - self.transact(Some(UndoableOpKind::UpdateDeck), |col| { + self.transact(Some(Op::UpdateDeck), |col| { let existing_deck = col.storage.get_deck(deck.id)?.ok_or(AnkiError::NotFound)?; col.update_deck_inner(deck, existing_deck, col.usn()?) }) } pub fn rename_deck(&mut self, did: DeckID, new_human_name: &str) -> Result<()> { - self.transact(Some(UndoableOpKind::RenameDeck), |col| { + self.transact(Some(Op::RenameDeck), |col| { let existing_deck = col.storage.get_deck(did)?.ok_or(AnkiError::NotFound)?; let mut deck = existing_deck.clone(); deck.name = human_deck_name_to_native(new_human_name); @@ -468,7 +466,7 @@ impl Collection { pub fn remove_decks_and_child_decks(&mut self, dids: &[DeckID]) -> Result { let mut card_count = 0; - self.transact(Some(UndoableOpKind::RemoveDeck), |col| { + self.transact(Some(Op::RemoveDeck), |col| { let usn = col.usn()?; for did in dids { if let Some(deck) = col.storage.get_deck(*did)? { @@ -627,7 +625,7 @@ impl Collection { target: Option, ) -> Result<()> { let usn = self.usn()?; - self.transact(Some(UndoableOpKind::RenameDeck), |col| { + self.transact(Some(Op::RenameDeck), |col| { let target_deck; let mut target_name = None; if let Some(target) = target { diff --git a/rslib/src/lib.rs b/rslib/src/lib.rs index c3a138ad0..bcbf847f2 100644 --- a/rslib/src/lib.rs +++ b/rslib/src/lib.rs @@ -24,6 +24,7 @@ mod markdown; pub mod media; pub mod notes; pub mod notetype; +pub mod ops; mod preferences; pub mod prelude; pub mod revlog; diff --git a/rslib/src/notes/mod.rs b/rslib/src/notes/mod.rs index 18dee8f3f..900ccca52 100644 --- a/rslib/src/notes/mod.rs +++ b/rslib/src/notes/mod.rs @@ -306,7 +306,7 @@ impl Collection { } pub fn add_note(&mut self, note: &mut Note, did: DeckID) -> Result<()> { - self.transact(Some(UndoableOpKind::AddNote), |col| { + self.transact(Some(Op::AddNote), |col| { let nt = col .get_notetype(note.notetype_id)? .ok_or_else(|| AnkiError::invalid_input("missing note type"))?; @@ -335,14 +335,10 @@ impl Collection { #[cfg(test)] pub(crate) fn update_note(&mut self, note: &mut Note) -> Result<()> { - self.update_note_with_op(note, Some(UndoableOpKind::UpdateNote)) + self.update_note_with_op(note, Some(Op::UpdateNote)) } - pub(crate) fn update_note_with_op( - &mut self, - note: &mut Note, - op: Option, - ) -> Result<()> { + pub(crate) fn update_note_with_op(&mut self, note: &mut Note, op: Option) -> Result<()> { let mut existing_note = self.storage.get_note(note.id)?.ok_or(AnkiError::NotFound)?; if !note_differs_from_db(&mut existing_note, note) { // nothing to do @@ -398,7 +394,7 @@ impl Collection { /// Remove provided notes, and any cards that use them. pub(crate) fn remove_notes(&mut self, nids: &[NoteID]) -> Result<()> { let usn = self.usn()?; - self.transact(Some(UndoableOpKind::RemoveNote), |col| { + self.transact(Some(Op::RemoveNote), |col| { for nid in nids { let nid = *nid; if let Some(_existing_note) = col.storage.get_note(nid)? { diff --git a/rslib/src/notes/undo.rs b/rslib/src/notes/undo.rs index 02bec7443..c7e42340f 100644 --- a/rslib/src/notes/undo.rs +++ b/rslib/src/notes/undo.rs @@ -96,7 +96,7 @@ impl Collection { op.changes.last() { note.id == before_change.id - && op.kind == UndoableOpKind::UpdateNote + && op.kind == Op::UpdateNote && op.timestamp.elapsed_secs() < 60 } else { false diff --git a/rslib/src/ops.rs b/rslib/src/ops.rs new file mode 100644 index 000000000..47330406c --- /dev/null +++ b/rslib/src/ops.rs @@ -0,0 +1,57 @@ +// Copyright: Ankitects Pty Ltd and contributors +// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html + +use crate::prelude::*; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum Op { + AddDeck, + AddNote, + AnswerCard, + Bury, + RemoveDeck, + RemoveNote, + RenameDeck, + ScheduleAsNew, + SetDueDate, + Suspend, + UnburyUnsuspend, + UpdateCard, + UpdateDeck, + UpdateNote, + UpdatePreferences, + UpdateTag, + SetDeck, +} + +impl Op { + pub(crate) fn needs_study_queue_reset(self) -> bool { + self != Op::AnswerCard + } +} + +impl Collection { + pub fn describe_op_kind(&self, op: Op) -> String { + let key = match op { + Op::AddDeck => TR::UndoAddDeck, + Op::AddNote => TR::UndoAddNote, + Op::AnswerCard => TR::UndoAnswerCard, + Op::Bury => TR::StudyingBury, + Op::RemoveDeck => TR::DecksDeleteDeck, + Op::RemoveNote => TR::StudyingDeleteNote, + Op::RenameDeck => TR::ActionsRenameDeck, + Op::ScheduleAsNew => TR::UndoForgetCard, + Op::SetDueDate => TR::ActionsSetDueDate, + Op::Suspend => TR::StudyingSuspend, + Op::UnburyUnsuspend => TR::UndoUnburyUnsuspend, + Op::UpdateCard => TR::UndoUpdateCard, + Op::UpdateDeck => TR::UndoUpdateDeck, + Op::UpdateNote => TR::UndoUpdateNote, + Op::UpdatePreferences => TR::PreferencesPreferences, + Op::UpdateTag => TR::UndoUpdateTag, + Op::SetDeck => TR::BrowsingChangeDeck, + }; + + self.i18n.tr(key).to_string() + } +} diff --git a/rslib/src/preferences.rs b/rslib/src/preferences.rs index e1b4eb80f..ada05b45b 100644 --- a/rslib/src/preferences.rs +++ b/rslib/src/preferences.rs @@ -10,6 +10,7 @@ use crate::{ collection::Collection, config::BoolKey, err::Result, + prelude::*, scheduler::timing::local_minutes_west_for_stamp, }; @@ -23,10 +24,9 @@ impl Collection { } pub fn set_preferences(&mut self, prefs: Preferences) -> Result<()> { - self.transact( - Some(crate::undo::UndoableOpKind::UpdatePreferences), - |col| col.set_preferences_inner(prefs), - ) + self.transact(Some(Op::UpdatePreferences), |col| { + col.set_preferences_inner(prefs) + }) } fn set_preferences_inner( diff --git a/rslib/src/prelude.rs b/rslib/src/prelude.rs index 2e0d589ac..8b201112a 100644 --- a/rslib/src/prelude.rs +++ b/rslib/src/prelude.rs @@ -11,9 +11,9 @@ pub use crate::{ i18n::{tr_args, tr_strs, I18n, TR}, notes::{Note, NoteID}, notetype::{NoteType, NoteTypeID}, + ops::Op, revlog::RevlogID, timestamp::{TimestampMillis, TimestampSecs}, types::Usn, - undo::UndoableOpKind, }; pub use slog::{debug, Logger}; diff --git a/rslib/src/scheduler/answering/mod.rs b/rslib/src/scheduler/answering/mod.rs index 87b649250..ea9b4f3cd 100644 --- a/rslib/src/scheduler/answering/mod.rs +++ b/rslib/src/scheduler/answering/mod.rs @@ -241,9 +241,7 @@ impl Collection { /// Answer card, writing its new state to the database. pub fn answer_card(&mut self, answer: &CardAnswer) -> Result<()> { - self.transact(Some(UndoableOpKind::AnswerCard), |col| { - col.answer_card_inner(answer) - }) + self.transact(Some(Op::AnswerCard), |col| col.answer_card_inner(answer)) } fn answer_card_inner(&mut self, answer: &CardAnswer) -> Result<()> { diff --git a/rslib/src/scheduler/bury_and_suspend.rs b/rslib/src/scheduler/bury_and_suspend.rs index 43f766bf3..69784344c 100644 --- a/rslib/src/scheduler/bury_and_suspend.rs +++ b/rslib/src/scheduler/bury_and_suspend.rs @@ -69,7 +69,7 @@ impl Collection { } pub fn unbury_or_unsuspend_cards(&mut self, cids: &[CardID]) -> Result<()> { - self.transact(Some(UndoableOpKind::UnburyUnsuspend), |col| { + self.transact(Some(Op::UnburyUnsuspend), |col| { col.storage.set_search_table_to_card_ids(cids, false)?; col.unsuspend_or_unbury_searched_cards() }) @@ -126,8 +126,8 @@ impl Collection { mode: BuryOrSuspendMode, ) -> Result<()> { let op = match mode { - BuryOrSuspendMode::Suspend => UndoableOpKind::Suspend, - BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => UndoableOpKind::Bury, + BuryOrSuspendMode::Suspend => Op::Suspend, + BuryOrSuspendMode::BurySched | BuryOrSuspendMode::BuryUser => Op::Bury, }; self.transact(Some(op), |col| { col.storage.set_search_table_to_card_ids(cids, false)?; diff --git a/rslib/src/scheduler/new.rs b/rslib/src/scheduler/new.rs index 06691a421..c7cd1583d 100644 --- a/rslib/src/scheduler/new.rs +++ b/rslib/src/scheduler/new.rs @@ -7,9 +7,9 @@ use crate::{ decks::DeckID, err::Result, notes::NoteID, + prelude::*, search::SortMode, types::Usn, - undo::UndoableOpKind, }; use rand::seq::SliceRandom; use std::collections::{HashMap, HashSet}; @@ -106,7 +106,7 @@ impl Collection { pub fn reschedule_cards_as_new(&mut self, cids: &[CardID], log: bool) -> Result<()> { let usn = self.usn()?; let mut position = self.get_next_card_position(); - self.transact(Some(UndoableOpKind::ScheduleAsNew), |col| { + self.transact(Some(Op::ScheduleAsNew), |col| { col.storage.set_search_table_to_card_ids(cids, true)?; let cards = col.storage.all_searched_cards_in_search_order()?; for mut card in cards { diff --git a/rslib/src/scheduler/reviews.rs b/rslib/src/scheduler/reviews.rs index cec735a4e..ecfa8fe1b 100644 --- a/rslib/src/scheduler/reviews.rs +++ b/rslib/src/scheduler/reviews.rs @@ -7,8 +7,7 @@ use crate::{ config::StringKey, deckconf::INITIAL_EASE_FACTOR_THOUSANDS, err::Result, - prelude::AnkiError, - undo::UndoableOpKind, + prelude::*, }; use lazy_static::lazy_static; use rand::distributions::{Distribution, Uniform}; @@ -100,7 +99,7 @@ impl Collection { let today = self.timing_today()?.days_elapsed; let mut rng = rand::thread_rng(); let distribution = Uniform::from(spec.min..=spec.max); - self.transact(Some(UndoableOpKind::SetDueDate), |col| { + self.transact(Some(Op::SetDueDate), |col| { col.storage.set_search_table_to_card_ids(cids, false)?; for mut card in col.storage.all_searched_cards()? { let original = card.clone(); diff --git a/rslib/src/tags/mod.rs b/rslib/src/tags/mod.rs index 39985dd72..de437b5a9 100644 --- a/rslib/src/tags/mod.rs +++ b/rslib/src/tags/mod.rs @@ -341,7 +341,7 @@ impl Collection { tags: &[Regex], mut repl: R, ) -> Result { - self.transact(Some(UndoableOpKind::UpdateTag), |col| { + self.transact(Some(Op::UpdateTag), |col| { col.transform_notes(nids, |note, _nt| { let mut changed = false; for re in tags { @@ -392,7 +392,7 @@ impl Collection { ) .map_err(|_| AnkiError::invalid_input("invalid regex"))?; - self.transact(Some(UndoableOpKind::UpdateTag), |col| { + self.transact(Some(Op::UpdateTag), |col| { col.transform_notes(nids, |note, _nt| { let mut need_to_add = true; let mut match_count = 0; diff --git a/rslib/src/undo/mod.rs b/rslib/src/undo/mod.rs index 55a8d2449..917b10cad 100644 --- a/rslib/src/undo/mod.rs +++ b/rslib/src/undo/mod.rs @@ -2,10 +2,9 @@ // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html mod changes; -mod ops; +pub use crate::ops::Op; pub(crate) use changes::UndoableChange; -pub use ops::UndoableOpKind; use crate::backend_proto as pb; use crate::prelude::*; @@ -15,7 +14,7 @@ const UNDO_LIMIT: usize = 30; #[derive(Debug)] pub(crate) struct UndoableOp { - pub kind: UndoableOpKind, + pub kind: Op, pub timestamp: TimestampSecs, pub changes: Vec, } @@ -51,7 +50,7 @@ impl UndoManager { } } - fn begin_step(&mut self, op: Option) { + fn begin_step(&mut self, op: Option) { println!("begin: {:?}", op); if op.is_none() { self.undo_steps.clear(); @@ -88,11 +87,11 @@ impl UndoManager { .unwrap_or(true) } - fn can_undo(&self) -> Option { + fn can_undo(&self) -> Option { self.undo_steps.front().map(|s| s.kind) } - fn can_redo(&self) -> Option { + fn can_redo(&self) -> Option { self.redo_steps.last().map(|s| s.kind) } @@ -102,11 +101,11 @@ impl UndoManager { } impl Collection { - pub fn can_undo(&self) -> Option { + pub fn can_undo(&self) -> Option { self.state.undo.can_undo() } - pub fn can_redo(&self) -> Option { + pub fn can_redo(&self) -> Option { self.state.undo.can_redo() } @@ -156,7 +155,7 @@ impl Collection { } /// If op is None, clears the undo/redo queues. - pub(crate) fn begin_undoable_operation(&mut self, op: Option) { + pub(crate) fn begin_undoable_operation(&mut self, op: Option) { self.state.undo.begin_step(op); } @@ -219,7 +218,7 @@ mod test { // record a few undo steps for i in 3..=4 { - col.transact(Some(UndoableOpKind::UpdateCard), |col| { + col.transact(Some(Op::UpdateCard), |col| { col.get_and_update_card(cid, |card| { card.interval = i; Ok(()) @@ -231,41 +230,41 @@ mod test { } assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 4); - assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard)); + assert_eq!(col.can_undo(), Some(Op::UpdateCard)); assert_eq!(col.can_redo(), None); // undo a step col.undo().unwrap(); assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 3); - assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard)); - assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard)); + assert_eq!(col.can_undo(), Some(Op::UpdateCard)); + assert_eq!(col.can_redo(), Some(Op::UpdateCard)); // and again col.undo().unwrap(); assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 2); assert_eq!(col.can_undo(), None); - assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard)); + assert_eq!(col.can_redo(), Some(Op::UpdateCard)); // redo a step col.redo().unwrap(); assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 3); - assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard)); - assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard)); + assert_eq!(col.can_undo(), Some(Op::UpdateCard)); + assert_eq!(col.can_redo(), Some(Op::UpdateCard)); // and another col.redo().unwrap(); assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 4); - assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard)); + assert_eq!(col.can_undo(), Some(Op::UpdateCard)); assert_eq!(col.can_redo(), None); // and undo the redo col.undo().unwrap(); assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 3); - assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard)); - assert_eq!(col.can_redo(), Some(UndoableOpKind::UpdateCard)); + assert_eq!(col.can_undo(), Some(Op::UpdateCard)); + assert_eq!(col.can_redo(), Some(Op::UpdateCard)); // if any action is performed, it should clear the redo queue - col.transact(Some(UndoableOpKind::UpdateCard), |col| { + col.transact(Some(Op::UpdateCard), |col| { col.get_and_update_card(cid, |card| { card.interval = 5; Ok(()) @@ -275,7 +274,7 @@ mod test { }) .unwrap(); assert_eq!(col.storage.get_card(cid).unwrap().unwrap().interval, 5); - assert_eq!(col.can_undo(), Some(UndoableOpKind::UpdateCard)); + assert_eq!(col.can_undo(), Some(Op::UpdateCard)); assert_eq!(col.can_redo(), None); // and any action that doesn't support undoing will clear both queues diff --git a/rslib/src/undo/ops.rs b/rslib/src/undo/ops.rs deleted file mode 100644 index e505b2fa5..000000000 --- a/rslib/src/undo/ops.rs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright: Ankitects Pty Ltd and contributors -// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html - -use crate::prelude::*; - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum UndoableOpKind { - AddDeck, - AddNote, - AnswerCard, - Bury, - RemoveDeck, - RemoveNote, - RenameDeck, - ScheduleAsNew, - SetDueDate, - Suspend, - UnburyUnsuspend, - UpdateCard, - UpdateDeck, - UpdateNote, - UpdatePreferences, - UpdateTag, - SetDeck, -} - -impl UndoableOpKind { - pub(crate) fn needs_study_queue_reset(self) -> bool { - self != UndoableOpKind::AnswerCard - } -} - -impl Collection { - pub fn describe_op_kind(&self, op: UndoableOpKind) -> String { - let key = match op { - UndoableOpKind::AddDeck => TR::UndoAddDeck, - UndoableOpKind::AddNote => TR::UndoAddNote, - UndoableOpKind::AnswerCard => TR::UndoAnswerCard, - UndoableOpKind::Bury => TR::StudyingBury, - UndoableOpKind::RemoveDeck => TR::DecksDeleteDeck, - UndoableOpKind::RemoveNote => TR::StudyingDeleteNote, - UndoableOpKind::RenameDeck => TR::ActionsRenameDeck, - UndoableOpKind::ScheduleAsNew => TR::UndoForgetCard, - UndoableOpKind::SetDueDate => TR::ActionsSetDueDate, - UndoableOpKind::Suspend => TR::StudyingSuspend, - UndoableOpKind::UnburyUnsuspend => TR::UndoUnburyUnsuspend, - UndoableOpKind::UpdateCard => TR::UndoUpdateCard, - UndoableOpKind::UpdateDeck => TR::UndoUpdateDeck, - UndoableOpKind::UpdateNote => TR::UndoUpdateNote, - UndoableOpKind::UpdatePreferences => TR::PreferencesPreferences, - UndoableOpKind::UpdateTag => TR::UndoUpdateTag, - UndoableOpKind::SetDeck => TR::BrowsingChangeDeck, - }; - - self.i18n.tr(key).to_string() - } -}