undo support for note adding
This commit is contained in:
parent
105ce94dd4
commit
40aff4447a
@ -8,3 +8,4 @@ undo-redo-action = Redo { $action }
|
||||
undo-action-redone = { $action } Redone
|
||||
undo-answer-card = Answer Card
|
||||
undo-unbury-unsuspend = Unbury/Unsuspend
|
||||
undo-add-note = Add Note
|
||||
|
@ -38,7 +38,7 @@ def test_op():
|
||||
note["Front"] = "one"
|
||||
col.addNote(note)
|
||||
col.reset()
|
||||
assert col.undoName() == "add"
|
||||
assert "add" in col.undoName().lower()
|
||||
c = col.sched.getCard()
|
||||
col.sched.answerCard(c, 2)
|
||||
assert col.undoName() == "Review"
|
||||
@ -54,7 +54,6 @@ def test_review():
|
||||
note["Front"] = "two"
|
||||
col.addNote(note)
|
||||
col.reset()
|
||||
assert not col.undoName()
|
||||
# answer
|
||||
assert col.sched.counts() == (2, 0, 0)
|
||||
c = col.sched.getCard()
|
||||
|
@ -178,7 +178,6 @@ class AddCards(QDialog):
|
||||
if not askUser(tr(TR.ADDING_YOU_HAVE_A_CLOZE_DELETION_NOTE)):
|
||||
return None
|
||||
self.mw.col.add_note(note, self.deckChooser.selectedId())
|
||||
self.mw.col.clear_python_undo()
|
||||
self.addHistory(note)
|
||||
self.previousNote = note
|
||||
self.mw.requireReset(reason=ResetReason.AddCardsAddNote, context=self)
|
||||
|
@ -123,6 +123,25 @@ impl Card {
|
||||
matches!(self.queue, CardQueue::Learn | CardQueue::PreviewRepeat)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CardAdded(Card);
|
||||
|
||||
impl Undo for CardAdded {
|
||||
fn undo(self: Box<Self>, col: &mut crate::collection::Collection) -> Result<()> {
|
||||
col.remove_card_for_undo(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CardRemoved(Card);
|
||||
|
||||
impl Undo for CardRemoved {
|
||||
fn undo(self: Box<Self>, col: &mut crate::collection::Collection) -> Result<()> {
|
||||
col.add_card_for_undo(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CardUpdated(Card);
|
||||
|
||||
@ -184,7 +203,16 @@ impl Collection {
|
||||
}
|
||||
card.mtime = TimestampSecs::now();
|
||||
card.usn = self.usn()?;
|
||||
self.storage.add_card(card)
|
||||
self.storage.add_card(card)?;
|
||||
self.save_undo(Box::new(CardAdded(card.clone())));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Used for undoing
|
||||
fn add_card_for_undo(&mut self, card: Card) -> Result<()> {
|
||||
self.storage.add_or_update_card(&card)?;
|
||||
self.save_undo(Box::new(CardAdded(card)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove cards and any resulting orphaned notes.
|
||||
@ -210,13 +238,20 @@ impl Collection {
|
||||
}
|
||||
|
||||
pub(crate) fn remove_card_only(&mut self, card: Card, usn: Usn) -> Result<()> {
|
||||
// fixme: undo
|
||||
self.storage.remove_card(card.id)?;
|
||||
self.storage.add_card_grave(card.id, usn)?;
|
||||
self.save_undo(Box::new(CardRemoved(card)));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Only used when undoing; does not add a grave.
|
||||
fn remove_card_for_undo(&mut self, card: Card) -> Result<()> {
|
||||
self.storage.remove_card(card.id)?;
|
||||
self.save_undo(Box::new(CardRemoved(card)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_deck(&mut self, cards: &[CardID], deck_id: DeckID) -> Result<()> {
|
||||
let deck = self.get_deck(deck_id)?.ok_or(AnkiError::NotFound)?;
|
||||
if deck.is_filtered() {
|
||||
|
@ -10,6 +10,7 @@ pub enum CollectionOp {
|
||||
Bury,
|
||||
Suspend,
|
||||
UnburyUnsuspend,
|
||||
AddNote,
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
@ -20,6 +21,7 @@ impl Collection {
|
||||
CollectionOp::Bury => TR::StudyingBury,
|
||||
CollectionOp::Suspend => TR::StudyingSuspend,
|
||||
CollectionOp::UnburyUnsuspend => TR::UndoUnburyUnsuspend,
|
||||
CollectionOp::AddNote => TR::UndoAddNote,
|
||||
};
|
||||
|
||||
self.i18n.tr(key).to_string()
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
use crate::{
|
||||
backend_proto as pb,
|
||||
collection::Collection,
|
||||
collection::{Collection, CollectionOp},
|
||||
decks::DeckID,
|
||||
define_newtype,
|
||||
err::{AnkiError, Result},
|
||||
@ -298,7 +298,7 @@ impl Collection {
|
||||
}
|
||||
|
||||
pub fn add_note(&mut self, note: &mut Note, did: DeckID) -> Result<()> {
|
||||
self.transact(None, |col| {
|
||||
self.transact(Some(CollectionOp::AddNote), |col| {
|
||||
let nt = col
|
||||
.get_notetype(note.notetype_id)?
|
||||
.ok_or_else(|| AnkiError::invalid_input("missing note type"))?;
|
||||
@ -319,9 +319,16 @@ impl Collection {
|
||||
note.prepare_for_update(&ctx.notetype, normalize_text)?;
|
||||
note.set_modified(ctx.usn);
|
||||
self.storage.add_note(note)?;
|
||||
self.save_undo(Box::new(NoteAdded(note.clone())));
|
||||
self.generate_cards_for_new_note(ctx, note, did)
|
||||
}
|
||||
|
||||
fn add_note_for_undo(&mut self, note: Note) -> Result<()> {
|
||||
self.storage.add_or_update_note(¬e)?;
|
||||
self.save_undo(Box::new(NoteAdded(note)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_note(&mut self, note: &mut Note) -> Result<()> {
|
||||
let mut existing_note = self.storage.get_note(note.id)?.ok_or(AnkiError::NotFound)?;
|
||||
if !note_modified(&mut existing_note, note) {
|
||||
@ -398,6 +405,12 @@ impl Collection {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_note_for_undo(&mut self, note: Note) -> Result<()> {
|
||||
self.storage.remove_note(note.id)?;
|
||||
self.save_undo(Box::new(NoteRemoved(note)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove provided notes, and any cards that use them.
|
||||
pub(crate) fn remove_notes(&mut self, nids: &[NoteID]) -> Result<()> {
|
||||
let usn = self.usn()?;
|
||||
@ -560,6 +573,24 @@ fn note_modified(existing_note: &mut Note, note: &Note) -> bool {
|
||||
notes_differ
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NoteAdded(Note);
|
||||
|
||||
impl Undo for NoteAdded {
|
||||
fn undo(self: Box<Self>, col: &mut crate::collection::Collection) -> Result<()> {
|
||||
col.remove_note_for_undo(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NoteRemoved(Note);
|
||||
|
||||
impl Undo for NoteRemoved {
|
||||
fn undo(self: Box<Self>, col: &mut crate::collection::Collection) -> Result<()> {
|
||||
col.add_note_for_undo(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NoteUpdated(Note);
|
||||
|
||||
@ -578,7 +609,7 @@ mod test {
|
||||
use super::{anki_base91, field_checksum};
|
||||
use crate::{
|
||||
collection::open_test_collection, config::ConfigKey, decks::DeckID, err::Result,
|
||||
search::SortMode,
|
||||
prelude::*, search::SortMode,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@ -676,4 +707,42 @@ mod test {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn undo() -> Result<()> {
|
||||
let mut col = open_test_collection();
|
||||
let nt = col
|
||||
.get_notetype_by_name("basic (and reversed card)")?
|
||||
.unwrap();
|
||||
|
||||
let assert_initial = |col: &mut Collection| -> Result<()> {
|
||||
assert_eq!(col.search_notes("")?.len(), 0);
|
||||
assert_eq!(col.search_cards("", SortMode::NoOrder)?.len(), 0);
|
||||
Ok(())
|
||||
};
|
||||
|
||||
let assert_after_add = |col: &mut Collection| -> Result<()> {
|
||||
assert_eq!(col.search_notes("")?.len(), 1);
|
||||
assert_eq!(col.search_cards("", SortMode::NoOrder)?.len(), 2);
|
||||
Ok(())
|
||||
};
|
||||
|
||||
assert_initial(&mut col)?;
|
||||
|
||||
let mut note = nt.new_note();
|
||||
note.set_field(0, "a")?;
|
||||
note.set_field(1, "b")?;
|
||||
|
||||
col.add_note(&mut note, DeckID(1)).unwrap();
|
||||
|
||||
assert_after_add(&mut col)?;
|
||||
col.undo()?;
|
||||
assert_initial(&mut col)?;
|
||||
col.redo()?;
|
||||
assert_after_add(&mut col)?;
|
||||
col.undo()?;
|
||||
assert_initial(&mut col)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ impl super::SqliteStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add or update card, using the provided ID. Used when syncing.
|
||||
/// Add or update card, using the provided ID. Used for syncing & undoing.
|
||||
pub(crate) fn add_or_update_card(&self, card: &Card) -> Result<()> {
|
||||
let mut stmt = self.db.prepare_cached(include_str!("add_or_update.sql"))?;
|
||||
stmt.execute(params![
|
||||
|
Loading…
Reference in New Issue
Block a user