-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathundo.rs
More file actions
127 lines (111 loc) · 4.21 KB
/
Copy pathundo.rs
File metadata and controls
127 lines (111 loc) · 4.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024-2026 TripleACS Pty Ltd t/a 2pi Software
//! Undo/redo types for the Krillnotes workspace.
//!
//! `RetractInverse` captures the "before-state" needed to reverse any
//! workspace mutation. It is carried by `Operation::RetractOperation` so
//! that retract entries can be synced to peers via `.swarm` diffs.
use crate::{AttachmentMeta, FieldValue, Note};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
fn default_script_category() -> String {
"library".to_string()
}
/// The inverse data needed to reverse one or more workspace mutations.
///
/// Applied by [`crate::Workspace::undo`] to restore previous state.
/// Serialised into `Operation::RetractOperation.inverse` for the log.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RetractInverse {
/// Inverse of `CreateNote` — delete the created note.
DeleteNote { note_id: String },
/// Inverse of `DeleteNote` (recursive) — restore the full subtree.
///
/// `notes` are ordered parent-first (root of deleted subtree is index 0).
/// Attachment `.enc` files remain on disk after a note-level delete, so
/// only the `attachments` DB rows need to be re-inserted.
SubtreeRestore {
notes: Vec<Note>,
attachments: Vec<AttachmentMeta>,
},
/// Inverse of `UpdateNote` — restore full note state atomically.
NoteRestore {
note_id: String,
old_title: String,
old_fields: BTreeMap<String, FieldValue>,
old_tags: Vec<String>,
#[serde(default)]
old_is_checked: bool,
},
/// Inverse of `MoveNote` — return note to its previous position.
PositionRestore {
note_id: String,
old_parent_id: Option<String>,
old_position: f64,
},
/// Inverse of `CreateUserScript` — delete the created script.
DeleteScript { script_id: String },
/// Inverse of `UpdateUserScript` or `DeleteUserScript` — restore script.
ScriptRestore {
script_id: String,
name: String,
description: String,
source_code: String,
load_order: i32,
enabled: bool,
#[serde(default = "default_script_category")]
category: String,
},
/// Inverse of `DeleteAttachment` — restores a soft-deleted attachment.
///
/// The `.enc.trash` file is renamed back to `.enc` and the DB row is re-inserted.
AttachmentRestore { meta: AttachmentMeta },
/// Inverse of `AttachmentRestore` (i.e. redo of an undone DeleteAttachment).
///
/// Re-soft-deletes the attachment: renames `.enc` → `.enc.trash` and removes the DB row.
AttachmentSoftDelete { attachment_id: String },
/// Inverse of a compound action (tree hook, batch import).
/// Items are applied in **reverse** order (LIFO — children before parent).
Batch(Vec<RetractInverse>),
}
/// Returned by [`crate::Workspace::undo`] and [`crate::Workspace::redo`].
///
/// The frontend uses `affected_note_id` to re-select the relevant note
/// after applying the inverse.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UndoResult {
/// Note to select/highlight after undo/redo. `None` for script operations.
pub affected_note_id: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_retract_inverse_batch_serializes() {
let batch = RetractInverse::Batch(vec![
RetractInverse::DeleteNote {
note_id: "n1".into(),
},
RetractInverse::DeleteNote {
note_id: "n2".into(),
},
]);
let json = serde_json::to_string(&batch).unwrap();
let back: RetractInverse = serde_json::from_str(&json).unwrap();
match back {
RetractInverse::Batch(items) => assert_eq!(items.len(), 2),
_ => panic!("wrong variant"),
}
}
#[test]
fn test_undo_result_no_note() {
let r = UndoResult {
affected_note_id: None,
};
assert!(r.affected_note_id.is_none());
}
}