mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
Fix unable to save field dialog if certain fields are deleted (#2663)
* Fix unable to save field dialog if certain fields are deleted Implemented solution suggested in issue #2556 * Fix unable to save field dialog if certain fields are deleted fixed code formating * Fix unable to save field dialog if certain fields are deleted Made new functions to check referencelessness. Added unit test.
This commit is contained in:
parent
a7b4c90146
commit
e7bf248a62
3 changed files with 103 additions and 0 deletions
|
@ -139,6 +139,7 @@ Nil Admirari <https://github.com/nihil-admirari>
|
||||||
Michael Winkworth <github.com/SteelColossus>
|
Michael Winkworth <github.com/SteelColossus>
|
||||||
Mateusz Wojewoda <kawa1.11@o2.pl>
|
Mateusz Wojewoda <kawa1.11@o2.pl>
|
||||||
Jarrett Ye <jarrett.ye@outlook.com>
|
Jarrett Ye <jarrett.ye@outlook.com>
|
||||||
|
Sam Waechter <github.com/swektr>
|
||||||
|
|
||||||
********************
|
********************
|
||||||
|
|
||||||
|
|
|
@ -551,13 +551,21 @@ impl Notetype {
|
||||||
fields: HashMap<String, Option<String>>,
|
fields: HashMap<String, Option<String>>,
|
||||||
parsed: &mut [(Option<ParsedTemplate>, Option<ParsedTemplate>)],
|
parsed: &mut [(Option<ParsedTemplate>, Option<ParsedTemplate>)],
|
||||||
) {
|
) {
|
||||||
|
let first_remaining_field_name = &self.fields.get(0).unwrap().name;
|
||||||
|
let is_cloze = self.is_cloze();
|
||||||
for (idx, (q_opt, a_opt)) in parsed.iter_mut().enumerate() {
|
for (idx, (q_opt, a_opt)) in parsed.iter_mut().enumerate() {
|
||||||
if let Some(q) = q_opt {
|
if let Some(q) = q_opt {
|
||||||
q.rename_and_remove_fields(&fields);
|
q.rename_and_remove_fields(&fields);
|
||||||
|
if !q.contains_field_replacement() || is_cloze && !q.contains_cloze_replacement() {
|
||||||
|
q.add_missing_field_replacement(first_remaining_field_name, is_cloze);
|
||||||
|
}
|
||||||
self.templates[idx].config.q_format = q.template_to_string();
|
self.templates[idx].config.q_format = q.template_to_string();
|
||||||
}
|
}
|
||||||
if let Some(a) = a_opt {
|
if let Some(a) = a_opt {
|
||||||
a.rename_and_remove_fields(&fields);
|
a.rename_and_remove_fields(&fields);
|
||||||
|
if is_cloze && !a.contains_cloze_replacement() {
|
||||||
|
a.add_missing_field_replacement(first_remaining_field_name, is_cloze);
|
||||||
|
}
|
||||||
self.templates[idx].config.a_format = a.template_to_string();
|
self.templates[idx].config.a_format = a.template_to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -745,3 +753,73 @@ impl Collection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests
|
||||||
|
//---------------------------------------
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_templates_after_removing_crucial_fields() {
|
||||||
|
// Normal Test (all front fields removed)
|
||||||
|
let mut nt_norm = Notetype::default();
|
||||||
|
nt_norm.add_field("baz"); // Fields "foo" and "bar" were removed
|
||||||
|
nt_norm.fields[0].ord = Some(2);
|
||||||
|
|
||||||
|
nt_norm.add_template("Card 1", "front {{foo}}", "back {{bar}}");
|
||||||
|
nt_norm.templates[0].ord = Some(0);
|
||||||
|
let mut parsed = nt_norm.parsed_templates();
|
||||||
|
|
||||||
|
let mut field_map: HashMap<String, Option<String>> = HashMap::new();
|
||||||
|
field_map.insert("foo".to_owned(), None);
|
||||||
|
field_map.insert("bar".to_owned(), None);
|
||||||
|
|
||||||
|
nt_norm.update_templates_for_renamed_and_removed_fields(field_map, &mut parsed);
|
||||||
|
assert_eq!(nt_norm.templates[0].config.q_format, "front {{baz}}");
|
||||||
|
assert_eq!(nt_norm.templates[0].config.a_format, "back ");
|
||||||
|
|
||||||
|
// Cloze Test 1/2 (front and back cloze fields removed)
|
||||||
|
let mut nt_cloze = Notetype {
|
||||||
|
config: Notetype::new_cloze_config(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
nt_cloze.add_field("baz"); // Fields "foo" and "bar" were removed
|
||||||
|
nt_cloze.fields[0].ord = Some(2);
|
||||||
|
|
||||||
|
nt_cloze.add_template("Card 1", "front {{cloze:foo}}", "back {{cloze:bar}}");
|
||||||
|
nt_cloze.templates[0].ord = Some(0);
|
||||||
|
let mut parsed = nt_cloze.parsed_templates();
|
||||||
|
|
||||||
|
let mut field_map: HashMap<String, Option<String>> = HashMap::new();
|
||||||
|
field_map.insert("foo".to_owned(), None);
|
||||||
|
field_map.insert("bar".to_owned(), None);
|
||||||
|
|
||||||
|
nt_cloze.update_templates_for_renamed_and_removed_fields(field_map, &mut parsed);
|
||||||
|
assert_eq!(nt_cloze.templates[0].config.q_format, "front {{cloze:baz}}");
|
||||||
|
assert_eq!(nt_cloze.templates[0].config.a_format, "back {{cloze:baz}}");
|
||||||
|
|
||||||
|
// Cloze Test 2/2 (only back cloze field is removed)
|
||||||
|
let mut nt_cloze = Notetype {
|
||||||
|
config: Notetype::new_cloze_config(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
nt_cloze.add_field("foo");
|
||||||
|
nt_cloze.fields[0].ord = Some(0);
|
||||||
|
nt_cloze.add_field("baz");
|
||||||
|
nt_cloze.fields[1].ord = Some(2);
|
||||||
|
// ^ only field "bar" was removed
|
||||||
|
|
||||||
|
nt_cloze.add_template("Card 1", "front {{cloze:foo}}", "back {{cloze:bar}}");
|
||||||
|
nt_cloze.templates[0].ord = Some(0);
|
||||||
|
let mut parsed = nt_cloze.parsed_templates();
|
||||||
|
|
||||||
|
let mut field_map: HashMap<String, Option<String>> = HashMap::new();
|
||||||
|
field_map.insert("bar".to_owned(), None);
|
||||||
|
|
||||||
|
nt_cloze.update_templates_for_renamed_and_removed_fields(field_map, &mut parsed);
|
||||||
|
assert_eq!(nt_cloze.templates[0].config.q_format, "front {{cloze:foo}}");
|
||||||
|
assert_eq!(nt_cloze.templates[0].config.a_format, "back {{cloze:foo}}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -730,6 +730,30 @@ impl ParsedTemplate {
|
||||||
let old_nodes = std::mem::take(&mut self.0);
|
let old_nodes = std::mem::take(&mut self.0);
|
||||||
self.0 = rename_and_remove_fields(old_nodes, fields);
|
self.0 = rename_and_remove_fields(old_nodes, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn contains_cloze_replacement(&self) -> bool {
|
||||||
|
self.0.iter().any(|node| {
|
||||||
|
matches!(
|
||||||
|
node,
|
||||||
|
ParsedNode::Replacement {key:_, filters} if filters.iter().any(|f| f=="cloze")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn contains_field_replacement(&self) -> bool {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.any(|node| matches!(node, ParsedNode::Replacement { key: _, filters: _ }))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_missing_field_replacement(&mut self, field_name: &str, is_cloze: bool) {
|
||||||
|
let key = String::from(field_name);
|
||||||
|
let filters = match is_cloze {
|
||||||
|
true => vec![String::from("cloze")],
|
||||||
|
false => Vec::new(),
|
||||||
|
};
|
||||||
|
self.0.push(ParsedNode::Replacement { key, filters });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rename_and_remove_fields(
|
fn rename_and_remove_fields(
|
||||||
|
|
Loading…
Reference in a new issue