mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 01:06:35 -04:00
add stock note types in backend
This commit is contained in:
parent
7811a04df8
commit
540892639f
10 changed files with 342 additions and 42 deletions
|
@ -533,8 +533,13 @@ message NoteTypeConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
message CardRequirement {
|
message CardRequirement {
|
||||||
|
enum CardRequirementKind {
|
||||||
|
None = 0;
|
||||||
|
Any = 1;
|
||||||
|
All = 2;
|
||||||
|
}
|
||||||
uint32 card_ord = 1;
|
uint32 card_ord = 1;
|
||||||
uint32 kind = 2;
|
CardRequirementKind kind = 2;
|
||||||
repeated uint32 field_ords = 3;
|
repeated uint32 field_ords = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -547,3 +552,11 @@ message NoteType {
|
||||||
repeated NoteField fields = 8;
|
repeated NoteField fields = 8;
|
||||||
repeated CardTemplate templates = 9;
|
repeated CardTemplate templates = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum StockNoteType {
|
||||||
|
StockNoteTypeBasic = 0;
|
||||||
|
StockNoteTypeBasicAndReversed = 1;
|
||||||
|
StockNoteTypeBasicOptionalReversed = 2;
|
||||||
|
StockNoteTypeBasicTyping = 3;
|
||||||
|
StockNoteTypeCloze = 4;
|
||||||
|
}
|
||||||
|
|
|
@ -13,13 +13,6 @@ from anki.dbproxy import DBProxy
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.media import media_paths_from_col_path
|
from anki.media import media_paths_from_col_path
|
||||||
from anki.rsbackend import RustBackend
|
from anki.rsbackend import RustBackend
|
||||||
from anki.stdmodels import (
|
|
||||||
addBasicModel,
|
|
||||||
addBasicTypingModel,
|
|
||||||
addClozeModel,
|
|
||||||
addForwardOptionalReverse,
|
|
||||||
addForwardReverse,
|
|
||||||
)
|
|
||||||
from anki.utils import intTime
|
from anki.utils import intTime
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,19 +44,13 @@ def Collection(
|
||||||
db = DBProxy(weakref.proxy(backend), path)
|
db = DBProxy(weakref.proxy(backend), path)
|
||||||
|
|
||||||
# initial setup required?
|
# initial setup required?
|
||||||
create = db.scalar("select models = '{}' from col")
|
create = db.scalar("select decks = '{}' from col")
|
||||||
if create:
|
if create:
|
||||||
initial_db_setup(db)
|
initial_db_setup(db)
|
||||||
|
|
||||||
# add db to col and do any remaining upgrades
|
# add db to col and do any remaining upgrades
|
||||||
col = _Collection(db, backend=backend, server=server, log=should_log)
|
col = _Collection(db, backend=backend, server=server, log=should_log)
|
||||||
if create:
|
if create:
|
||||||
# add in reverse order so basic is default
|
|
||||||
addClozeModel(col)
|
|
||||||
addBasicTypingModel(col)
|
|
||||||
addForwardOptionalReverse(col)
|
|
||||||
addForwardReverse(col)
|
|
||||||
addBasicModel(col)
|
|
||||||
col.save()
|
col.save()
|
||||||
else:
|
else:
|
||||||
db.begin()
|
db.begin()
|
||||||
|
|
|
@ -827,7 +827,7 @@ impl Backend {
|
||||||
pb::set_config_json::Op::Val(val) => {
|
pb::set_config_json::Op::Val(val) => {
|
||||||
// ensure it's a well-formed object
|
// ensure it's a well-formed object
|
||||||
let val: JsonValue = serde_json::from_slice(&val)?;
|
let val: JsonValue = serde_json::from_slice(&val)?;
|
||||||
col.set_config(&input.key, &val)
|
col.set_config(input.key.as_str(), &val)
|
||||||
}
|
}
|
||||||
pb::set_config_json::Op::Remove(_) => col.remove_config(&input.key),
|
pb::set_config_json::Op::Remove(_) => col.remove_config(&input.key),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
use crate::collection::Collection;
|
use crate::{
|
||||||
use crate::decks::DeckID;
|
collection::Collection, decks::DeckID, err::Result, notetype::NoteTypeID,
|
||||||
use crate::err::Result;
|
timestamp::TimestampSecs,
|
||||||
use crate::timestamp::TimestampSecs;
|
};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use serde_aux::field_attributes::deserialize_bool_from_anything;
|
use serde_aux::field_attributes::deserialize_bool_from_anything;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
|
@ -38,6 +38,7 @@ pub(crate) enum ConfigKey {
|
||||||
CreationOffset,
|
CreationOffset,
|
||||||
Rollover,
|
Rollover,
|
||||||
LocalOffset,
|
LocalOffset,
|
||||||
|
CurrentNoteTypeID,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ConfigKey> for &'static str {
|
impl From<ConfigKey> for &'static str {
|
||||||
|
@ -49,6 +50,7 @@ impl From<ConfigKey> for &'static str {
|
||||||
ConfigKey::CreationOffset => "creationOffset",
|
ConfigKey::CreationOffset => "creationOffset",
|
||||||
ConfigKey::Rollover => "rollover",
|
ConfigKey::Rollover => "rollover",
|
||||||
ConfigKey::LocalOffset => "localOffset",
|
ConfigKey::LocalOffset => "localOffset",
|
||||||
|
ConfigKey::CurrentNoteTypeID => "curModel",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -83,9 +85,12 @@ impl Collection {
|
||||||
self.get_config_optional(key).unwrap_or_default()
|
self.get_config_optional(key).unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_config<T: Serialize>(&self, key: &str, val: &T) -> Result<()> {
|
pub(crate) fn set_config<'a, T: Serialize, K>(&self, key: K, val: &T) -> Result<()>
|
||||||
|
where
|
||||||
|
K: Into<&'a str>,
|
||||||
|
{
|
||||||
self.storage
|
self.storage
|
||||||
.set_config_value(key, val, self.usn()?, TimestampSecs::now())
|
.set_config_value(key.into(), val, self.usn()?, TimestampSecs::now())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remove_config(&self, key: &str) -> Result<()> {
|
pub(crate) fn remove_config(&self, key: &str) -> Result<()> {
|
||||||
|
@ -117,6 +122,16 @@ impl Collection {
|
||||||
pub(crate) fn get_rollover(&self) -> Option<i8> {
|
pub(crate) fn get_rollover(&self) -> Option<i8> {
|
||||||
self.get_config_optional(ConfigKey::Rollover)
|
self.get_config_optional(ConfigKey::Rollover)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn get_current_notetype_id(&self) -> Option<NoteTypeID> {
|
||||||
|
self.get_config_optional(ConfigKey::CurrentNoteTypeID)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn set_current_notetype_id(&self, id: NoteTypeID) -> Result<()> {
|
||||||
|
self.set_config(ConfigKey::CurrentNoteTypeID, &id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, PartialEq, Debug)]
|
#[derive(Deserialize, PartialEq, Debug)]
|
||||||
|
|
|
@ -2,15 +2,20 @@
|
||||||
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
mod schema11;
|
mod schema11;
|
||||||
|
mod stock;
|
||||||
|
|
||||||
pub use crate::backend_proto::{
|
pub use crate::backend_proto::{
|
||||||
CardRequirement, CardTemplate, CardTemplateConfig, NoteField, NoteFieldConfig, NoteType,
|
card_requirement::CardRequirementKind, CardRequirement, CardTemplate, CardTemplateConfig,
|
||||||
NoteTypeConfig, NoteTypeKind,
|
NoteField, NoteFieldConfig, NoteType, NoteTypeConfig, NoteTypeKind,
|
||||||
};
|
};
|
||||||
pub use schema11::{CardTemplateSchema11, NoteFieldSchema11, NoteTypeSchema11};
|
pub use schema11::{CardTemplateSchema11, NoteFieldSchema11, NoteTypeSchema11};
|
||||||
|
|
||||||
use crate::{define_newtype, text::ensure_string_in_nfc};
|
use crate::{
|
||||||
use std::collections::HashSet;
|
define_newtype,
|
||||||
|
template::{without_legacy_template_directives, FieldRequirements, ParsedTemplate},
|
||||||
|
text::ensure_string_in_nfc,
|
||||||
|
};
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
|
|
||||||
define_newtype!(NoteTypeID, i64);
|
define_newtype!(NoteTypeID, i64);
|
||||||
|
@ -36,9 +41,6 @@ pub(crate) const DEFAULT_LATEX_HEADER: &str = r#"\documentclass[12pt]{article}
|
||||||
|
|
||||||
pub(crate) const DEFAULT_LATEX_FOOTER: &str = r#"\end{document}"#;
|
pub(crate) const DEFAULT_LATEX_FOOTER: &str = r#"\end{document}"#;
|
||||||
|
|
||||||
// other: vec![], // fixme: ensure empty map converted to empty bytes
|
|
||||||
// fixme: rollback savepoint when tags not changed
|
|
||||||
|
|
||||||
impl NoteType {
|
impl NoteType {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut nt = Self::default();
|
let mut nt = Self::default();
|
||||||
|
@ -87,6 +89,53 @@ impl NoteType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update_requirements(&mut self) {
|
||||||
|
let field_map: HashMap<&str, u16> = self
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, field)| (field.name.as_str(), idx as u16))
|
||||||
|
.collect();
|
||||||
|
let reqs: Vec<_> = self
|
||||||
|
.templates
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(ord, tmpl)| {
|
||||||
|
let conf = tmpl.config.as_ref().unwrap();
|
||||||
|
let normalized = without_legacy_template_directives(&conf.q_format);
|
||||||
|
if let Ok(tmpl) = ParsedTemplate::from_text(normalized.as_ref()) {
|
||||||
|
let mut req = match tmpl.requirements(&field_map) {
|
||||||
|
FieldRequirements::Any(ords) => CardRequirement {
|
||||||
|
card_ord: ord as u32,
|
||||||
|
kind: CardRequirementKind::Any as i32,
|
||||||
|
field_ords: ords.into_iter().map(|n| n as u32).collect(),
|
||||||
|
},
|
||||||
|
FieldRequirements::All(ords) => CardRequirement {
|
||||||
|
card_ord: ord as u32,
|
||||||
|
kind: CardRequirementKind::All as i32,
|
||||||
|
field_ords: ords.into_iter().map(|n| n as u32).collect(),
|
||||||
|
},
|
||||||
|
FieldRequirements::None => CardRequirement {
|
||||||
|
card_ord: ord as u32,
|
||||||
|
kind: CardRequirementKind::None as i32,
|
||||||
|
field_ords: vec![],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
req.field_ords.sort_unstable();
|
||||||
|
req
|
||||||
|
} else {
|
||||||
|
// template parsing failures make card unsatisfiable
|
||||||
|
CardRequirement {
|
||||||
|
card_ord: ord as u32,
|
||||||
|
kind: CardRequirementKind::None as i32,
|
||||||
|
field_ords: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
self.config.as_mut().unwrap().reqs = reqs;
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn normalize_names(&mut self) {
|
pub(crate) fn normalize_names(&mut self) {
|
||||||
ensure_string_in_nfc(&mut self.name);
|
ensure_string_in_nfc(&mut self.name);
|
||||||
for f in &mut self.fields {
|
for f in &mut self.fields {
|
||||||
|
@ -96,4 +145,37 @@ impl NoteType {
|
||||||
ensure_string_in_nfc(&mut t.name);
|
ensure_string_in_nfc(&mut t.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_field<S: Into<String>>(&mut self, name: S) {
|
||||||
|
let mut config = NoteFieldConfig::default();
|
||||||
|
config.font_name = "Arial".to_string();
|
||||||
|
config.font_size = 20;
|
||||||
|
let mut field = NoteField::default();
|
||||||
|
field.name = name.into();
|
||||||
|
field.config = Some(config);
|
||||||
|
self.fields.push(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_template<S1, S2, S3>(&mut self, name: S1, qfmt: S2, afmt: S3)
|
||||||
|
where
|
||||||
|
S1: Into<String>,
|
||||||
|
S2: Into<String>,
|
||||||
|
S3: Into<String>,
|
||||||
|
{
|
||||||
|
let mut config = CardTemplateConfig::default();
|
||||||
|
config.q_format = qfmt.into();
|
||||||
|
config.a_format = afmt.into();
|
||||||
|
|
||||||
|
let mut tmpl = CardTemplate::default();
|
||||||
|
tmpl.name = name.into();
|
||||||
|
tmpl.config = Some(config);
|
||||||
|
|
||||||
|
self.templates.push(tmpl);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn prepare_for_adding(&mut self) {
|
||||||
|
self.normalize_names();
|
||||||
|
self.ensure_names_unique();
|
||||||
|
self.update_requirements();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||||
use serde_tuple::Serialize_tuple;
|
use serde_tuple::Serialize_tuple;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::NoteTypeID;
|
use super::{CardRequirementKind, NoteTypeID};
|
||||||
|
|
||||||
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Clone)]
|
#[derive(Serialize_repr, Deserialize_repr, PartialEq, Debug, Clone)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
@ -101,7 +101,7 @@ impl From<NoteTypeSchema11> for NoteType {
|
||||||
latex_post: nt.latex_post,
|
latex_post: nt.latex_post,
|
||||||
latex_svg: nt.latexsvg,
|
latex_svg: nt.latexsvg,
|
||||||
reqs: nt.req.0.into_iter().map(Into::into).collect(),
|
reqs: nt.req.0.into_iter().map(Into::into).collect(),
|
||||||
other: serde_json::to_vec(&nt.other).unwrap(),
|
other: other_to_bytes(&nt.other),
|
||||||
}),
|
}),
|
||||||
fields: nt.flds.into_iter().map(Into::into).collect(),
|
fields: nt.flds.into_iter().map(Into::into).collect(),
|
||||||
templates: nt.tmpls.into_iter().map(Into::into).collect(),
|
templates: nt.tmpls.into_iter().map(Into::into).collect(),
|
||||||
|
@ -109,6 +109,29 @@ impl From<NoteTypeSchema11> for NoteType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn other_to_bytes(other: &HashMap<String, Value>) -> Vec<u8> {
|
||||||
|
if other.is_empty() {
|
||||||
|
vec![]
|
||||||
|
} else {
|
||||||
|
serde_json::to_vec(other).unwrap_or_else(|e| {
|
||||||
|
// theoretically should never happen
|
||||||
|
println!("serialization failed for {:?}: {}", other, e);
|
||||||
|
vec![]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bytes_to_other(bytes: &[u8]) -> HashMap<String, Value> {
|
||||||
|
if bytes.is_empty() {
|
||||||
|
Default::default()
|
||||||
|
} else {
|
||||||
|
serde_json::from_slice(bytes).unwrap_or_else(|e| {
|
||||||
|
println!("deserialization failed for other: {}", e);
|
||||||
|
Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<NoteType> for NoteTypeSchema11 {
|
impl From<NoteType> for NoteTypeSchema11 {
|
||||||
fn from(p: NoteType) -> Self {
|
fn from(p: NoteType) -> Self {
|
||||||
let c = p.config.unwrap();
|
let c = p.config.unwrap();
|
||||||
|
@ -135,7 +158,7 @@ impl From<NoteType> for NoteTypeSchema11 {
|
||||||
latex_post: c.latex_post,
|
latex_post: c.latex_post,
|
||||||
latexsvg: c.latex_svg,
|
latexsvg: c.latex_svg,
|
||||||
req: CardRequirementsSchema11(c.reqs.into_iter().map(Into::into).collect()),
|
req: CardRequirementsSchema11(c.reqs.into_iter().map(Into::into).collect()),
|
||||||
other: serde_json::from_slice(&c.other).unwrap_or_default(),
|
other: bytes_to_other(&c.other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,7 +167,11 @@ impl From<CardRequirementSchema11> for CardRequirement {
|
||||||
fn from(r: CardRequirementSchema11) -> Self {
|
fn from(r: CardRequirementSchema11) -> Self {
|
||||||
CardRequirement {
|
CardRequirement {
|
||||||
card_ord: r.card_ord as u32,
|
card_ord: r.card_ord as u32,
|
||||||
kind: r.kind as u32,
|
kind: match r.kind {
|
||||||
|
FieldRequirementKindSchema11::Any => CardRequirementKind::Any,
|
||||||
|
FieldRequirementKindSchema11::All => CardRequirementKind::All,
|
||||||
|
FieldRequirementKindSchema11::None => CardRequirementKind::None,
|
||||||
|
} as i32,
|
||||||
field_ords: r.field_ords.into_iter().map(|n| n as u32).collect(),
|
field_ords: r.field_ords.into_iter().map(|n| n as u32).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,10 +181,10 @@ impl From<CardRequirement> for CardRequirementSchema11 {
|
||||||
fn from(p: CardRequirement) -> Self {
|
fn from(p: CardRequirement) -> Self {
|
||||||
CardRequirementSchema11 {
|
CardRequirementSchema11 {
|
||||||
card_ord: p.card_ord as u16,
|
card_ord: p.card_ord as u16,
|
||||||
kind: match p.kind {
|
kind: match p.kind() {
|
||||||
0 => FieldRequirementKindSchema11::Any,
|
CardRequirementKind::Any => FieldRequirementKindSchema11::Any,
|
||||||
1 => FieldRequirementKindSchema11::All,
|
CardRequirementKind::All => FieldRequirementKindSchema11::All,
|
||||||
_ => FieldRequirementKindSchema11::None,
|
CardRequirementKind::None => FieldRequirementKindSchema11::None,
|
||||||
},
|
},
|
||||||
field_ords: p.field_ords.into_iter().map(|n| n as u16).collect(),
|
field_ords: p.field_ords.into_iter().map(|n| n as u16).collect(),
|
||||||
}
|
}
|
||||||
|
@ -202,7 +229,7 @@ impl From<NoteFieldSchema11> for NoteField {
|
||||||
rtl: f.rtl,
|
rtl: f.rtl,
|
||||||
font_name: f.font,
|
font_name: f.font,
|
||||||
font_size: f.size as u32,
|
font_size: f.size as u32,
|
||||||
other: serde_json::to_vec(&f.other).unwrap(),
|
other: other_to_bytes(&f.other),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,7 +245,7 @@ impl From<NoteField> for NoteFieldSchema11 {
|
||||||
rtl: conf.rtl,
|
rtl: conf.rtl,
|
||||||
font: conf.font_name,
|
font: conf.font_name,
|
||||||
size: conf.font_size as u16,
|
size: conf.font_size as u16,
|
||||||
other: serde_json::from_slice(&conf.other).unwrap(),
|
other: bytes_to_other(&conf.other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,7 +286,7 @@ impl From<CardTemplateSchema11> for CardTemplate {
|
||||||
target_deck_id: t.did.unwrap_or(DeckID(0)).0,
|
target_deck_id: t.did.unwrap_or(DeckID(0)).0,
|
||||||
browser_font_name: t.bfont,
|
browser_font_name: t.bfont,
|
||||||
browser_font_size: t.bsize as u32,
|
browser_font_size: t.bsize as u32,
|
||||||
other: serde_json::to_vec(&t.other).unwrap(),
|
other: other_to_bytes(&t.other),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,7 +309,7 @@ impl From<CardTemplate> for CardTemplateSchema11 {
|
||||||
},
|
},
|
||||||
bfont: conf.browser_font_name,
|
bfont: conf.browser_font_name,
|
||||||
bsize: conf.browser_font_size as u8,
|
bsize: conf.browser_font_size as u8,
|
||||||
other: serde_json::from_slice(&conf.other).unwrap(),
|
other: bytes_to_other(&conf.other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
130
rslib/src/notetype/stock.rs
Normal file
130
rslib/src/notetype/stock.rs
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
use super::NoteTypeKind;
|
||||||
|
use crate::{
|
||||||
|
config::ConfigKey, err::Result, i18n::I18n, i18n::TR, notetype::NoteType,
|
||||||
|
storage::SqliteStorage, timestamp::TimestampSecs,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub use crate::backend_proto::StockNoteType;
|
||||||
|
|
||||||
|
impl SqliteStorage {
|
||||||
|
pub(crate) fn add_stock_notetypes(&self, i18n: &I18n) -> Result<()> {
|
||||||
|
for (idx, mut nt) in all_stock_notetypes(i18n).into_iter().enumerate() {
|
||||||
|
self.add_new_notetype(&mut nt)?;
|
||||||
|
if idx == StockNoteType::Basic as usize {
|
||||||
|
self.set_config_value(
|
||||||
|
ConfigKey::CurrentNoteTypeID.into(),
|
||||||
|
&nt.id(),
|
||||||
|
self.usn(false)?,
|
||||||
|
TimestampSecs::now(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if changing this, make sure to update StockNoteType enum
|
||||||
|
pub fn all_stock_notetypes(i18n: &I18n) -> Vec<NoteType> {
|
||||||
|
vec![
|
||||||
|
basic(i18n),
|
||||||
|
basic_forward_reverse(i18n),
|
||||||
|
basic_optional_reverse(i18n),
|
||||||
|
basic_typing(i18n),
|
||||||
|
cloze(i18n),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns {{name}}
|
||||||
|
fn fieldref<S: AsRef<str>>(name: S) -> String {
|
||||||
|
format!("{{{{{}}}}}", name.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn basic(i18n: &I18n) -> NoteType {
|
||||||
|
let mut nt = NoteType::new();
|
||||||
|
nt.name = i18n.tr(TR::NotetypesBasicName).into();
|
||||||
|
let front = i18n.tr(TR::NotetypesFrontField);
|
||||||
|
let back = i18n.tr(TR::NotetypesBackField);
|
||||||
|
nt.add_field(front.as_ref());
|
||||||
|
nt.add_field(back.as_ref());
|
||||||
|
nt.add_template(
|
||||||
|
i18n.tr(TR::NotetypesCard1Name),
|
||||||
|
fieldref(front),
|
||||||
|
format!(
|
||||||
|
"{}\n\n<hr id=answer>\n\n{}",
|
||||||
|
fieldref("FrontSide"),
|
||||||
|
fieldref(back),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
nt.prepare_for_adding();
|
||||||
|
nt
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn basic_typing(i18n: &I18n) -> NoteType {
|
||||||
|
let mut nt = basic(i18n);
|
||||||
|
nt.name = i18n.tr(TR::NotetypesBasicTypeAnswerName).into();
|
||||||
|
let front = i18n.tr(TR::NotetypesFrontField);
|
||||||
|
let back = i18n.tr(TR::NotetypesBackField);
|
||||||
|
let tmpl = nt.templates[0].config.as_mut().unwrap();
|
||||||
|
tmpl.q_format = format!("{}\n\n{{{{type:{}}}}}", fieldref(front.as_ref()), back);
|
||||||
|
tmpl.a_format = format!(
|
||||||
|
"{}\n\n<hr id=answer>\n\n{{{{type:{}}}}}",
|
||||||
|
fieldref(front),
|
||||||
|
back
|
||||||
|
);
|
||||||
|
nt.prepare_for_adding();
|
||||||
|
nt
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn basic_forward_reverse(i18n: &I18n) -> NoteType {
|
||||||
|
let mut nt = basic(i18n);
|
||||||
|
nt.name = i18n.tr(TR::NotetypesBasicReversedName).into();
|
||||||
|
let front = i18n.tr(TR::NotetypesFrontField);
|
||||||
|
let back = i18n.tr(TR::NotetypesBackField);
|
||||||
|
nt.add_template(
|
||||||
|
i18n.tr(TR::NotetypesCard2Name),
|
||||||
|
fieldref(back),
|
||||||
|
format!(
|
||||||
|
"{}\n\n<hr id=answer>\n\n{}",
|
||||||
|
fieldref("FrontSide"),
|
||||||
|
fieldref(front),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
nt.prepare_for_adding();
|
||||||
|
nt
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn basic_optional_reverse(i18n: &I18n) -> NoteType {
|
||||||
|
let mut nt = basic_forward_reverse(i18n);
|
||||||
|
nt.name = i18n.tr(TR::NotetypesBasicOptionalReversedName).into();
|
||||||
|
let addrev = i18n.tr(TR::NotetypesAddReverseField);
|
||||||
|
nt.add_field(addrev.as_ref());
|
||||||
|
let tmpl = nt.templates[1].config.as_mut().unwrap();
|
||||||
|
tmpl.q_format = format!("{{{{#{}}}}}{}{{{{/{}}}}}", addrev, tmpl.q_format, addrev);
|
||||||
|
nt.prepare_for_adding();
|
||||||
|
nt
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cloze(i18n: &I18n) -> NoteType {
|
||||||
|
let mut nt = NoteType::new();
|
||||||
|
nt.name = i18n.tr(TR::NotetypesClozeName).into();
|
||||||
|
let text = i18n.tr(TR::NotetypesTextField);
|
||||||
|
nt.add_field(text.as_ref());
|
||||||
|
let fmt = format!("{{{{cloze:{}}}}}", text);
|
||||||
|
nt.add_template(nt.name.clone(), fmt.clone(), fmt);
|
||||||
|
let mut config = nt.config.as_mut().unwrap();
|
||||||
|
config.kind = NoteTypeKind::Cloze as i32;
|
||||||
|
config.css += "
|
||||||
|
.cloze {
|
||||||
|
font-weight: bold;
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
.nightMode .cloze {
|
||||||
|
color: lightblue;
|
||||||
|
}
|
||||||
|
";
|
||||||
|
nt.prepare_for_adding();
|
||||||
|
nt
|
||||||
|
}
|
|
@ -1,3 +1,22 @@
|
||||||
insert into notetypes (id, name, mtime_secs, usn, config)
|
insert into notetypes (id, name, mtime_secs, usn, config)
|
||||||
values
|
values
|
||||||
(?, ?, ?, ?, ?)
|
(
|
||||||
|
(
|
||||||
|
case
|
||||||
|
when ?1 in (
|
||||||
|
select
|
||||||
|
id
|
||||||
|
from notetypes
|
||||||
|
) then (
|
||||||
|
select
|
||||||
|
max(id) + 1
|
||||||
|
from notetypes
|
||||||
|
)
|
||||||
|
else ?1
|
||||||
|
end
|
||||||
|
),
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?,
|
||||||
|
?
|
||||||
|
);
|
|
@ -8,6 +8,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
err::{AnkiError, DBErrorKind, Result},
|
err::{AnkiError, DBErrorKind, Result},
|
||||||
notetype::{NoteTypeID, NoteTypeSchema11},
|
notetype::{NoteTypeID, NoteTypeSchema11},
|
||||||
|
timestamp::TimestampMillis,
|
||||||
};
|
};
|
||||||
use prost::Message;
|
use prost::Message;
|
||||||
use rusqlite::{params, Row, NO_PARAMS};
|
use rusqlite::{params, Row, NO_PARAMS};
|
||||||
|
@ -160,6 +161,27 @@ impl SqliteStorage {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_new_notetype(&self, nt: &mut NoteType) -> Result<()> {
|
||||||
|
assert!(nt.id == 0);
|
||||||
|
|
||||||
|
let mut stmt = self.db.prepare_cached(include_str!("add_notetype.sql"))?;
|
||||||
|
let mut config_bytes = vec![];
|
||||||
|
nt.config.as_ref().unwrap().encode(&mut config_bytes)?;
|
||||||
|
stmt.execute(params![
|
||||||
|
TimestampMillis::now(),
|
||||||
|
nt.name,
|
||||||
|
nt.mtime_secs,
|
||||||
|
nt.usn,
|
||||||
|
config_bytes
|
||||||
|
])?;
|
||||||
|
nt.id = self.db.last_insert_rowid();
|
||||||
|
|
||||||
|
self.update_notetype_fields(nt.id(), &nt.fields)?;
|
||||||
|
self.update_notetype_templates(nt.id(), &nt.templates)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// Upgrading/downgrading
|
// Upgrading/downgrading
|
||||||
|
|
||||||
pub(crate) fn upgrade_notetypes_to_schema15(&self) -> Result<()> {
|
pub(crate) fn upgrade_notetypes_to_schema15(&self) -> Result<()> {
|
||||||
|
@ -181,6 +203,7 @@ impl SqliteStorage {
|
||||||
self.update_notetype_fields(ntid, &nt.fields)?;
|
self.update_notetype_fields(ntid, &nt.fields)?;
|
||||||
self.update_notetype_templates(ntid, &nt.templates)?;
|
self.update_notetype_templates(ntid, &nt.templates)?;
|
||||||
}
|
}
|
||||||
|
self.db.execute("update col set models = ''", NO_PARAMS)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,9 @@ fn unicase_compare(s1: &str, s2: &str) -> Ordering {
|
||||||
UniCase::new(s1).cmp(&UniCase::new(s2))
|
UniCase::new(s1).cmp(&UniCase::new(s2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fixme: rollback savepoint when tags not changed
|
||||||
|
// fixme: switch away from proto for top level struct
|
||||||
|
|
||||||
// currently public for dbproxy
|
// currently public for dbproxy
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SqliteStorage {
|
pub struct SqliteStorage {
|
||||||
|
@ -195,6 +198,7 @@ impl SqliteStorage {
|
||||||
|
|
||||||
if create {
|
if create {
|
||||||
storage.add_default_deck_config(i18n)?;
|
storage.add_default_deck_config(i18n)?;
|
||||||
|
storage.add_stock_notetypes(i18n)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if create || upgrade {
|
if create || upgrade {
|
||||||
|
|
Loading…
Reference in a new issue