Add ability to restore a notetype to its original configuration (#2472)
* Store the original stock notetype kind in the notetype Will allow us to provide a command to restore a notetype to its default settings/templates. * Add a new action to restore a notetype to its original state
This commit is contained in:
parent
e361bb9514
commit
dd13e78eca
@ -45,4 +45,4 @@ good-names =
|
|||||||
ip,
|
ip,
|
||||||
|
|
||||||
[IMPORTS]
|
[IMPORTS]
|
||||||
ignored-modules = anki.*_pb2, anki.sync_pb2, win32file,pywintypes,socket,win32pipe,pyaudio,anki.scheduler_pb2
|
ignored-modules = anki.*_pb2, anki.sync_pb2, win32file,pywintypes,socket,win32pipe,pyaudio,anki.scheduler_pb2,anki.notetypes_pb2
|
||||||
|
@ -57,3 +57,8 @@ card-templates-this-will-create-card-proceed =
|
|||||||
*[other] This will create { $count } cards. Proceed?
|
*[other] This will create { $count } cards. Proceed?
|
||||||
}
|
}
|
||||||
card-templates-type-boxes-warning = Only one typing box per card template is supported.
|
card-templates-type-boxes-warning = Only one typing box per card template is supported.
|
||||||
|
card-templates-restore-to-default = Restore to Default
|
||||||
|
card-templates-restore-to-default-confirmation = This will reset all fields and templates in this notetype to their default
|
||||||
|
values, and remove any custom styling. Do you wish to proceed?
|
||||||
|
card-templates-restored-to-default = Notetype has been restored to its original state.
|
||||||
|
|
||||||
|
@ -30,6 +30,8 @@ service NotetypesService {
|
|||||||
returns (ChangeNotetypeInfo);
|
returns (ChangeNotetypeInfo);
|
||||||
rpc ChangeNotetype(ChangeNotetypeRequest) returns (collection.OpChanges);
|
rpc ChangeNotetype(ChangeNotetypeRequest) returns (collection.OpChanges);
|
||||||
rpc GetFieldNames(NotetypeId) returns (generic.StringList);
|
rpc GetFieldNames(NotetypeId) returns (generic.StringList);
|
||||||
|
rpc RestoreNotetypeToStock(RestoreNotetypeToStockRequest)
|
||||||
|
returns (collection.OpChanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
message NotetypeId {
|
message NotetypeId {
|
||||||
@ -56,12 +58,14 @@ message Notetype {
|
|||||||
Kind kind = 1;
|
Kind kind = 1;
|
||||||
uint32 sort_field_idx = 2;
|
uint32 sort_field_idx = 2;
|
||||||
string css = 3;
|
string css = 3;
|
||||||
/// This is now stored separately; retrieve with DefaultsForAdding()
|
// This is now stored separately; retrieve with DefaultsForAdding()
|
||||||
int64 target_deck_id_unused = 4;
|
int64 target_deck_id_unused = 4;
|
||||||
string latex_pre = 5;
|
string latex_pre = 5;
|
||||||
string latex_post = 6;
|
string latex_post = 6;
|
||||||
bool latex_svg = 7;
|
bool latex_svg = 7;
|
||||||
repeated CardRequirement reqs = 8;
|
repeated CardRequirement reqs = 8;
|
||||||
|
// Only set on notetypes created with Anki 2.1.62+.
|
||||||
|
StockNotetype.OriginalStockKind original_stock_kind = 9;
|
||||||
|
|
||||||
bytes other = 255;
|
bytes other = 255;
|
||||||
}
|
}
|
||||||
@ -119,12 +123,24 @@ message AddOrUpdateNotetypeRequest {
|
|||||||
|
|
||||||
message StockNotetype {
|
message StockNotetype {
|
||||||
enum Kind {
|
enum Kind {
|
||||||
BASIC = 0;
|
KIND_BASIC = 0;
|
||||||
BASIC_AND_REVERSED = 1;
|
KIND_BASIC_AND_REVERSED = 1;
|
||||||
BASIC_OPTIONAL_REVERSED = 2;
|
KIND_BASIC_OPTIONAL_REVERSED = 2;
|
||||||
BASIC_TYPING = 3;
|
KIND_BASIC_TYPING = 3;
|
||||||
CLOZE = 4;
|
KIND_CLOZE = 4;
|
||||||
IMAGE_OCCLUSION = 5;
|
}
|
||||||
|
// This is decoupled from Kind to allow us to evolve notetypes over time
|
||||||
|
// (eg an older notetype might require different JS), and allow us to store
|
||||||
|
// a type even for notetypes that we don't add by default. Code should not
|
||||||
|
// assume that the entries here are always +1 from Kind.
|
||||||
|
enum OriginalStockKind {
|
||||||
|
ORIGINAL_STOCK_KIND_UNKNOWN = 0;
|
||||||
|
ORIGINAL_STOCK_KIND_BASIC = 1;
|
||||||
|
ORIGINAL_STOCK_KIND_BASIC_AND_REVERSED = 2;
|
||||||
|
ORIGINAL_STOCK_KIND_BASIC_OPTIONAL_REVERSED = 3;
|
||||||
|
ORIGINAL_STOCK_KIND_BASIC_TYPING = 4;
|
||||||
|
ORIGINAL_STOCK_KIND_CLOZE = 5;
|
||||||
|
ORIGINAL_STOCK_KIND_IMAGE_OCCLUSION = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
Kind kind = 1;
|
Kind kind = 1;
|
||||||
@ -185,3 +201,10 @@ message ChangeNotetypeInfo {
|
|||||||
ChangeNotetypeRequest input = 5;
|
ChangeNotetypeRequest input = 5;
|
||||||
string old_notetype_name = 6;
|
string old_notetype_name = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message RestoreNotetypeToStockRequest {
|
||||||
|
NotetypeId notetype_id = 1;
|
||||||
|
// Older notetypes did not store their original stock kind, so we allow the UI
|
||||||
|
// to pass in an override to use when missing, or for tests.
|
||||||
|
optional StockNotetype.Kind force_kind = 2;
|
||||||
|
}
|
@ -181,7 +181,7 @@ class ModelManager(DeprecatedNamesMixin):
|
|||||||
"Create a new model, and return it."
|
"Create a new model, and return it."
|
||||||
# caller should call save() after modifying
|
# caller should call save() after modifying
|
||||||
notetype = from_json_bytes(
|
notetype = from_json_bytes(
|
||||||
self.col._backend.get_stock_notetype_legacy(StockNotetypeKind.BASIC)
|
self.col._backend.get_stock_notetype_legacy(StockNotetypeKind.KIND_BASIC)
|
||||||
)
|
)
|
||||||
notetype["flds"] = []
|
notetype["flds"] = []
|
||||||
notetype["tmpls"] = []
|
notetype["tmpls"] = []
|
||||||
@ -277,7 +277,7 @@ class ModelManager(DeprecatedNamesMixin):
|
|||||||
def new_field(self, name: str) -> FieldDict:
|
def new_field(self, name: str) -> FieldDict:
|
||||||
assert isinstance(name, str)
|
assert isinstance(name, str)
|
||||||
notetype = from_json_bytes(
|
notetype = from_json_bytes(
|
||||||
self.col._backend.get_stock_notetype_legacy(StockNotetypeKind.BASIC)
|
self.col._backend.get_stock_notetype_legacy(StockNotetypeKind.KIND_BASIC)
|
||||||
)
|
)
|
||||||
field = notetype["flds"][0]
|
field = notetype["flds"][0]
|
||||||
field["name"] = name
|
field["name"] = name
|
||||||
@ -321,7 +321,7 @@ class ModelManager(DeprecatedNamesMixin):
|
|||||||
|
|
||||||
def new_template(self, name: str) -> TemplateDict:
|
def new_template(self, name: str) -> TemplateDict:
|
||||||
notetype = from_json_bytes(
|
notetype = from_json_bytes(
|
||||||
self.col._backend.get_stock_notetype_legacy(StockNotetypeKind.BASIC)
|
self.col._backend.get_stock_notetype_legacy(StockNotetypeKind.KIND_BASIC)
|
||||||
)
|
)
|
||||||
template = notetype["tmpls"][0]
|
template = notetype["tmpls"][0]
|
||||||
template["name"] = name
|
template["name"] = name
|
||||||
@ -393,6 +393,16 @@ and notes.mid = ? and cards.ord = ?""",
|
|||||||
op_bytes = self.col._backend.change_notetype_raw(input.SerializeToString())
|
op_bytes = self.col._backend.change_notetype_raw(input.SerializeToString())
|
||||||
return OpChanges.FromString(op_bytes)
|
return OpChanges.FromString(op_bytes)
|
||||||
|
|
||||||
|
def restore_notetype_to_stock(
|
||||||
|
self, notetype_id: NotetypeId, force_kind: StockNotetypeKind.V | None
|
||||||
|
) -> OpChanges:
|
||||||
|
msg = notetypes_pb2.RestoreNotetypeToStockRequest(
|
||||||
|
notetype_id=notetypes_pb2.NotetypeId(ntid=notetype_id),
|
||||||
|
)
|
||||||
|
if force_kind is not None:
|
||||||
|
msg.force_kind = force_kind
|
||||||
|
return self.col._backend.restore_notetype_to_stock(msg)
|
||||||
|
|
||||||
# legacy API - used by unit tests and add-ons
|
# legacy API - used by unit tests and add-ons
|
||||||
|
|
||||||
def change( # pylint: disable=invalid-name
|
def change( # pylint: disable=invalid-name
|
||||||
|
@ -31,13 +31,13 @@ def get_stock_notetypes(
|
|||||||
out: list[
|
out: list[
|
||||||
tuple[str, Callable[[anki.collection.Collection], anki.models.NotetypeDict]]
|
tuple[str, Callable[[anki.collection.Collection], anki.models.NotetypeDict]]
|
||||||
] = []
|
] = []
|
||||||
# add standard
|
# add standard - this order should match the one in notetypes.proto
|
||||||
for kind in [
|
for kind in [
|
||||||
StockNotetypeKind.BASIC,
|
StockNotetypeKind.KIND_BASIC,
|
||||||
StockNotetypeKind.BASIC_TYPING,
|
StockNotetypeKind.KIND_BASIC_AND_REVERSED,
|
||||||
StockNotetypeKind.BASIC_AND_REVERSED,
|
StockNotetypeKind.KIND_BASIC_OPTIONAL_REVERSED,
|
||||||
StockNotetypeKind.BASIC_OPTIONAL_REVERSED,
|
StockNotetypeKind.KIND_BASIC_TYPING,
|
||||||
StockNotetypeKind.CLOZE,
|
StockNotetypeKind.KIND_CLOZE,
|
||||||
]:
|
]:
|
||||||
note_type = from_json_bytes(col._backend.get_stock_notetype_legacy(kind))
|
note_type = from_json_bytes(col._backend.get_stock_notetype_legacy(kind))
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ def get_stock_notetypes(
|
|||||||
def _legacy_add_basic_model(
|
def _legacy_add_basic_model(
|
||||||
col: anki.collection.Collection,
|
col: anki.collection.Collection,
|
||||||
) -> anki.models.NotetypeDict:
|
) -> anki.models.NotetypeDict:
|
||||||
note_type = _get_stock_notetype(col, StockNotetypeKind.BASIC)
|
note_type = _get_stock_notetype(col, StockNotetypeKind.KIND_BASIC)
|
||||||
col.models.add(note_type)
|
col.models.add(note_type)
|
||||||
return note_type
|
return note_type
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ def _legacy_add_basic_model(
|
|||||||
def _legacy_add_basic_typing_model(
|
def _legacy_add_basic_typing_model(
|
||||||
col: anki.collection.Collection,
|
col: anki.collection.Collection,
|
||||||
) -> anki.models.NotetypeDict:
|
) -> anki.models.NotetypeDict:
|
||||||
note_type = _get_stock_notetype(col, StockNotetypeKind.BASIC_TYPING)
|
note_type = _get_stock_notetype(col, StockNotetypeKind.KIND_BASIC_TYPING)
|
||||||
col.models.add(note_type)
|
col.models.add(note_type)
|
||||||
return note_type
|
return note_type
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ def _legacy_add_basic_typing_model(
|
|||||||
def _legacy_add_forward_reverse(
|
def _legacy_add_forward_reverse(
|
||||||
col: anki.collection.Collection,
|
col: anki.collection.Collection,
|
||||||
) -> anki.models.NotetypeDict:
|
) -> anki.models.NotetypeDict:
|
||||||
note_type = _get_stock_notetype(col, StockNotetypeKind.BASIC_AND_REVERSED)
|
note_type = _get_stock_notetype(col, StockNotetypeKind.KIND_BASIC_AND_REVERSED)
|
||||||
col.models.add(note_type)
|
col.models.add(note_type)
|
||||||
return note_type
|
return note_type
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ def _legacy_add_forward_reverse(
|
|||||||
def _legacy_add_forward_optional_reverse(
|
def _legacy_add_forward_optional_reverse(
|
||||||
col: anki.collection.Collection,
|
col: anki.collection.Collection,
|
||||||
) -> anki.models.NotetypeDict:
|
) -> anki.models.NotetypeDict:
|
||||||
note_type = _get_stock_notetype(col, StockNotetypeKind.BASIC_OPTIONAL_REVERSED)
|
note_type = _get_stock_notetype(col, StockNotetypeKind.KIND_BASIC_OPTIONAL_REVERSED)
|
||||||
col.models.add(note_type)
|
col.models.add(note_type)
|
||||||
return note_type
|
return note_type
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ def _legacy_add_forward_optional_reverse(
|
|||||||
def _legacy_add_cloze_model(
|
def _legacy_add_cloze_model(
|
||||||
col: anki.collection.Collection,
|
col: anki.collection.Collection,
|
||||||
) -> anki.models.NotetypeDict:
|
) -> anki.models.NotetypeDict:
|
||||||
note_type = _get_stock_notetype(col, StockNotetypeKind.CLOZE)
|
note_type = _get_stock_notetype(col, StockNotetypeKind.KIND_CLOZE)
|
||||||
col.models.add(note_type)
|
col.models.add(note_type)
|
||||||
return note_type
|
return note_type
|
||||||
|
|
||||||
|
@ -1,20 +1,25 @@
|
|||||||
# 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
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
from typing import Any, Match, Optional
|
from typing import Any, Match, Optional, cast
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
import aqt.forms
|
import aqt.forms
|
||||||
import aqt.operations
|
import aqt.operations
|
||||||
|
from anki import stdmodels
|
||||||
from anki.collection import OpChanges
|
from anki.collection import OpChanges
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.lang import without_unicode_isolation
|
from anki.lang import without_unicode_isolation
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
|
from anki.notetypes_pb2 import StockNotetype
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.forms import browserdisp
|
from aqt.forms import browserdisp
|
||||||
from aqt.operations.notetype import update_notetype_legacy
|
from aqt.operations.notetype import restore_notetype_to_stock, update_notetype_legacy
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.schema_change_tracker import ChangeTracker
|
from aqt.schema_change_tracker import ChangeTracker
|
||||||
from aqt.sound import av_player, play_clicked_audio
|
from aqt.sound import av_player, play_clicked_audio
|
||||||
@ -696,6 +701,31 @@ class CardLayout(QDialog):
|
|||||||
self.ord = len(self.templates) - 1
|
self.ord = len(self.templates) - 1
|
||||||
self.redraw_everything()
|
self.redraw_everything()
|
||||||
|
|
||||||
|
def on_restore_to_default(
|
||||||
|
self, force_kind: StockNotetype.Kind.V | None = None
|
||||||
|
) -> None:
|
||||||
|
if force_kind is None and not self.model.get("originalStockKind", 0):
|
||||||
|
SelectStockNotetype(
|
||||||
|
mw=self.mw,
|
||||||
|
on_success=lambda kind: self.on_restore_to_default(force_kind=kind),
|
||||||
|
parent=self,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not askUser(
|
||||||
|
tr.card_templates_restore_to_default_confirmation(), defaultno=True
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
def on_success(changes: OpChanges) -> None:
|
||||||
|
self.change_tracker.set_unchanged()
|
||||||
|
self.close()
|
||||||
|
showInfo(tr.card_templates_restored_to_default(), parent=self.mw)
|
||||||
|
|
||||||
|
restore_notetype_to_stock(
|
||||||
|
parent=self, notetype_id=self.model["id"], force_kind=force_kind
|
||||||
|
).success(on_success).run_in_background()
|
||||||
|
|
||||||
def onFlip(self) -> None:
|
def onFlip(self) -> None:
|
||||||
old = self.current_template()
|
old = self.current_template()
|
||||||
self._flipQA(old, old)
|
self._flipQA(old, old)
|
||||||
@ -717,6 +747,14 @@ class CardLayout(QDialog):
|
|||||||
a = m.addAction(tr.card_templates_add_card_type())
|
a = m.addAction(tr.card_templates_add_card_type())
|
||||||
qconnect(a.triggered, self.onAddCard)
|
qconnect(a.triggered, self.onAddCard)
|
||||||
|
|
||||||
|
a = m.addAction(
|
||||||
|
tr.actions_with_ellipsis(action=tr.card_templates_restore_to_default())
|
||||||
|
)
|
||||||
|
qconnect(
|
||||||
|
a.triggered,
|
||||||
|
lambda: self.on_restore_to_default(), # pylint: disable=unnecessary-lambda
|
||||||
|
)
|
||||||
|
|
||||||
a = m.addAction(tr.card_templates_remove_card_type())
|
a = m.addAction(tr.card_templates_remove_card_type())
|
||||||
qconnect(a.triggered, self.onRemove)
|
qconnect(a.triggered, self.onRemove)
|
||||||
|
|
||||||
@ -869,3 +907,42 @@ class CardLayout(QDialog):
|
|||||||
|
|
||||||
def onHelp(self) -> None:
|
def onHelp(self) -> None:
|
||||||
openHelp(HelpPage.TEMPLATES)
|
openHelp(HelpPage.TEMPLATES)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectStockNotetype(QDialog):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
mw: AnkiQt,
|
||||||
|
on_success: Callable[[StockNotetype.Kind.V], None],
|
||||||
|
parent: QWidget,
|
||||||
|
) -> None:
|
||||||
|
self.mw = mw
|
||||||
|
QDialog.__init__(self, parent, Qt.WindowType.Window)
|
||||||
|
self.dialog = aqt.forms.addmodel.Ui_Dialog()
|
||||||
|
self.dialog.setupUi(self)
|
||||||
|
self.setWindowTitle("Anki")
|
||||||
|
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||||
|
disable_help_button(self)
|
||||||
|
stock_types = stdmodels.get_stock_notetypes(mw.col)
|
||||||
|
|
||||||
|
for name, func in stock_types:
|
||||||
|
item = QListWidgetItem(name)
|
||||||
|
self.dialog.models.addItem(item)
|
||||||
|
self.dialog.models.setCurrentRow(0)
|
||||||
|
# the list widget will swallow the enter key
|
||||||
|
s = QShortcut(QKeySequence("Return"), self)
|
||||||
|
qconnect(s.activated, self.accept)
|
||||||
|
# help
|
||||||
|
# self.dialog.buttonBox.standardButton(QDialogButtonBox.StandardButton.Help).
|
||||||
|
self.on_success = on_success
|
||||||
|
self.show()
|
||||||
|
|
||||||
|
def reject(self) -> None:
|
||||||
|
QDialog.reject(self)
|
||||||
|
|
||||||
|
def accept(self) -> None:
|
||||||
|
kind = cast(StockNotetype.Kind.V, self.dialog.models.currentRow())
|
||||||
|
QDialog.accept(self)
|
||||||
|
# On Mac, we need to allow time for the existing modal to close or
|
||||||
|
# Qt gets confused.
|
||||||
|
self.mw.progress.single_shot(100, lambda: self.on_success(kind), True)
|
||||||
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from anki.collection import OpChanges, OpChangesWithId
|
from anki.collection import OpChanges, OpChangesWithId
|
||||||
from anki.models import ChangeNotetypeRequest, NotetypeDict, NotetypeId
|
from anki.models import ChangeNotetypeRequest, NotetypeDict, NotetypeId
|
||||||
|
from anki.stdmodels import StockNotetypeKind
|
||||||
from aqt.operations import CollectionOp
|
from aqt.operations import CollectionOp
|
||||||
from aqt.qt import QWidget
|
from aqt.qt import QWidget
|
||||||
|
|
||||||
@ -37,3 +38,12 @@ def change_notetype_of_notes(
|
|||||||
*, parent: QWidget, input: ChangeNotetypeRequest
|
*, parent: QWidget, input: ChangeNotetypeRequest
|
||||||
) -> CollectionOp[OpChanges]:
|
) -> CollectionOp[OpChanges]:
|
||||||
return CollectionOp(parent, lambda col: col.models.change_notetype_of_notes(input))
|
return CollectionOp(parent, lambda col: col.models.change_notetype_of_notes(input))
|
||||||
|
|
||||||
|
|
||||||
|
def restore_notetype_to_stock(
|
||||||
|
*, parent: QWidget, notetype_id: NotetypeId, force_kind: StockNotetypeKind.V | None
|
||||||
|
) -> CollectionOp[OpChanges]:
|
||||||
|
return CollectionOp(
|
||||||
|
parent,
|
||||||
|
lambda col: col.models.restore_notetype_to_stock(notetype_id, force_kind),
|
||||||
|
)
|
||||||
|
@ -34,3 +34,6 @@ class ChangeTracker:
|
|||||||
|
|
||||||
def changed(self) -> bool:
|
def changed(self) -> bool:
|
||||||
return self._changed != Change.NO_CHANGE
|
return self._changed != Change.NO_CHANGE
|
||||||
|
|
||||||
|
def set_unchanged(self) -> None:
|
||||||
|
self._changed = Change.NO_CHANGE
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
use super::Backend;
|
use super::Backend;
|
||||||
use crate::config::get_aux_notetype_config_key;
|
use crate::config::get_aux_notetype_config_key;
|
||||||
use crate::notetype::all_stock_notetypes;
|
use crate::notetype::stock::get_stock_notetype;
|
||||||
|
use crate::notetype::stock::StockKind;
|
||||||
use crate::notetype::ChangeNotetypeInput;
|
use crate::notetype::ChangeNotetypeInput;
|
||||||
use crate::notetype::Notetype;
|
use crate::notetype::Notetype;
|
||||||
use crate::notetype::NotetypeChangeInfo;
|
use crate::notetype::NotetypeChangeInfo;
|
||||||
@ -81,10 +82,7 @@ impl NotetypesService for Backend {
|
|||||||
&self,
|
&self,
|
||||||
input: pb::notetypes::StockNotetype,
|
input: pb::notetypes::StockNotetype,
|
||||||
) -> Result<pb::generic::Json> {
|
) -> Result<pb::generic::Json> {
|
||||||
// fixme: use individual functions instead of full vec
|
let nt = get_stock_notetype(input.kind(), &self.tr);
|
||||||
let mut all = all_stock_notetypes(&self.tr);
|
|
||||||
let idx = (input.kind as usize).min(all.len() - 1);
|
|
||||||
let nt = all.swap_remove(idx);
|
|
||||||
let schema11: NotetypeSchema11 = nt.into();
|
let schema11: NotetypeSchema11 = nt.into();
|
||||||
serde_json::to_vec(&schema11)
|
serde_json::to_vec(&schema11)
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
@ -208,6 +206,20 @@ impl NotetypesService for Backend {
|
|||||||
self.with_col(|col| col.storage.get_field_names(input.into()))
|
self.with_col(|col| col.storage.get_field_names(input.into()))
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn restore_notetype_to_stock(
|
||||||
|
&self,
|
||||||
|
input: pb::notetypes::RestoreNotetypeToStockRequest,
|
||||||
|
) -> Result<pb::collection::OpChanges> {
|
||||||
|
let force_kind = input.force_kind.and_then(StockKind::from_i32);
|
||||||
|
self.with_col(|col| {
|
||||||
|
col.restore_notetype_to_stock(
|
||||||
|
input.notetype_id.or_invalid("missing notetype id")?.into(),
|
||||||
|
force_kind,
|
||||||
|
)
|
||||||
|
.map(Into::into)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<pb::notetypes::Notetype> for Notetype {
|
impl From<pb::notetypes::Notetype> for Notetype {
|
||||||
|
@ -10,13 +10,15 @@ use regex::Regex;
|
|||||||
use crate::io::metadata;
|
use crate::io::metadata;
|
||||||
use crate::io::read_file;
|
use crate::io::read_file;
|
||||||
use crate::media::MediaManager;
|
use crate::media::MediaManager;
|
||||||
|
use crate::notetype::stock::empty_stock;
|
||||||
use crate::notetype::CardGenContext;
|
use crate::notetype::CardGenContext;
|
||||||
use crate::notetype::Notetype;
|
use crate::notetype::Notetype;
|
||||||
use crate::notetype::NotetypeConfig;
|
use crate::notetype::NotetypeKind;
|
||||||
use crate::pb::image_occlusion::image_cloze_note_response::Value;
|
use crate::pb::image_occlusion::image_cloze_note_response::Value;
|
||||||
use crate::pb::image_occlusion::ImageClozeNote;
|
use crate::pb::image_occlusion::ImageClozeNote;
|
||||||
use crate::pb::image_occlusion::ImageClozeNoteResponse;
|
use crate::pb::image_occlusion::ImageClozeNoteResponse;
|
||||||
pub use crate::pb::image_occlusion::ImageData;
|
pub use crate::pb::image_occlusion::ImageData;
|
||||||
|
use crate::pb::notetypes::stock_notetype::OriginalStockKind;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
@ -101,7 +103,7 @@ impl Collection {
|
|||||||
|
|
||||||
fn add_io_notetype(&mut self) -> Result<()> {
|
fn add_io_notetype(&mut self) -> Result<()> {
|
||||||
let usn = self.usn()?;
|
let usn = self.usn()?;
|
||||||
let mut nt = self.image_occlusion_notetype();
|
let mut nt = image_occlusion_notetype(&self.tr);
|
||||||
self.add_notetype_inner(&mut nt, usn, false)?;
|
self.add_notetype_inner(&mut nt, usn, false)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -187,28 +189,28 @@ impl Collection {
|
|||||||
|
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn image_occlusion_notetype(&mut self) -> Notetype {
|
pub(crate) fn image_occlusion_notetype(tr: &I18n) -> Notetype {
|
||||||
let tr = &self.tr;
|
const IMAGE_CLOZE_CSS: &str = include_str!("image_occlusion_styling.css");
|
||||||
const IMAGE_CLOZE_CSS: &str = include_str!("image_occlusion_styling.css");
|
let mut nt = empty_stock(
|
||||||
let mut nt = Notetype {
|
NotetypeKind::Cloze,
|
||||||
name: tr.notetypes_image_occlusion_name().into(),
|
OriginalStockKind::ImageOcclusion,
|
||||||
config: NotetypeConfig::new_cloze(),
|
tr.notetypes_image_occlusion_name(),
|
||||||
..Default::default()
|
);
|
||||||
};
|
nt.config.css = IMAGE_CLOZE_CSS.to_string();
|
||||||
nt.config.css = IMAGE_CLOZE_CSS.to_string();
|
let occlusion = tr.notetypes_occlusion();
|
||||||
let occlusion = tr.notetypes_occlusion();
|
nt.add_field(occlusion.as_ref());
|
||||||
nt.add_field(occlusion.as_ref());
|
let image = tr.notetypes_image();
|
||||||
let image = tr.notetypes_image();
|
nt.add_field(image.as_ref());
|
||||||
nt.add_field(image.as_ref());
|
let header = tr.notetypes_header();
|
||||||
let header = tr.notetypes_header();
|
nt.add_field(header.as_ref());
|
||||||
nt.add_field(header.as_ref());
|
let back_extra = tr.notetypes_back_extra_field();
|
||||||
let back_extra = tr.notetypes_back_extra_field();
|
nt.add_field(back_extra.as_ref());
|
||||||
nt.add_field(back_extra.as_ref());
|
let comments = tr.notetypes_comments_field();
|
||||||
let comments = tr.notetypes_comments_field();
|
nt.add_field(comments.as_ref());
|
||||||
nt.add_field(comments.as_ref());
|
let qfmt = format!(
|
||||||
let qfmt = format!(
|
"<div style=\"display: none\">{{{{cloze:{}}}}}</div>
|
||||||
"<div style=\"display: none\">{{{{cloze:{}}}}}</div>
|
|
||||||
<div id=container>
|
<div id=container>
|
||||||
{{{{{}}}}}
|
{{{{{}}}}}
|
||||||
<canvas id=\"canvas\" class=\"image-occlusion-canvas\"></canvas>
|
<canvas id=\"canvas\" class=\"image-occlusion-canvas\"></canvas>
|
||||||
@ -222,25 +224,24 @@ try {{
|
|||||||
}}
|
}}
|
||||||
</script>
|
</script>
|
||||||
",
|
",
|
||||||
occlusion,
|
occlusion,
|
||||||
image,
|
image,
|
||||||
tr.notetypes_error_loading_image_occlusion(),
|
tr.notetypes_error_loading_image_occlusion(),
|
||||||
);
|
);
|
||||||
let afmt = format!(
|
let afmt = format!(
|
||||||
"{{{{{}}}}}
|
"{{{{{}}}}}
|
||||||
{}
|
{}
|
||||||
<button id=\"toggle\">{}</button>
|
<button id=\"toggle\">{}</button>
|
||||||
<br>
|
<br>
|
||||||
{{{{{}}}}}
|
{{{{{}}}}}
|
||||||
<br>
|
<br>
|
||||||
{{{{{}}}}}",
|
{{{{{}}}}}",
|
||||||
header,
|
header,
|
||||||
qfmt,
|
qfmt,
|
||||||
tr.notetypes_toggle_masks(),
|
tr.notetypes_toggle_masks(),
|
||||||
back_extra,
|
back_extra,
|
||||||
comments,
|
comments,
|
||||||
);
|
);
|
||||||
nt.add_template(nt.name.clone(), qfmt, afmt);
|
nt.add_template(nt.name.clone(), qfmt, afmt);
|
||||||
nt
|
nt
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ mod emptycards;
|
|||||||
mod fields;
|
mod fields;
|
||||||
mod notetypechange;
|
mod notetypechange;
|
||||||
mod render;
|
mod render;
|
||||||
|
mod restore;
|
||||||
mod schema11;
|
mod schema11;
|
||||||
mod schemachange;
|
mod schemachange;
|
||||||
pub(crate) mod stock;
|
pub(crate) mod stock;
|
||||||
|
93
rslib/src/notetype/restore.rs
Normal file
93
rslib/src/notetype/restore.rs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
use crate::notetype::stock::get_original_stock_notetype;
|
||||||
|
use crate::notetype::stock::StockKind;
|
||||||
|
use crate::pb::notetypes::stock_notetype::Kind;
|
||||||
|
use crate::pb::notetypes::stock_notetype::OriginalStockKind;
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
impl Collection {
|
||||||
|
/// If force_kind is not Unknown, it will be used in preference to the kind
|
||||||
|
/// stored in the notetype. If Unknown, and the kind stored in the
|
||||||
|
/// notetype is also Unknown, an error will be returned.
|
||||||
|
pub(crate) fn restore_notetype_to_stock(
|
||||||
|
&mut self,
|
||||||
|
notetype_id: NotetypeId,
|
||||||
|
force_kind: Option<StockKind>,
|
||||||
|
) -> Result<OpOutput<()>> {
|
||||||
|
let mut nt = self
|
||||||
|
.storage
|
||||||
|
.get_notetype(notetype_id)?
|
||||||
|
.or_not_found(notetype_id)?;
|
||||||
|
let stock_kind = match (nt.config.original_stock_kind(), force_kind) {
|
||||||
|
(_, Some(force_kind)) => match force_kind {
|
||||||
|
Kind::Basic => OriginalStockKind::Basic,
|
||||||
|
Kind::BasicAndReversed => OriginalStockKind::BasicAndReversed,
|
||||||
|
Kind::BasicOptionalReversed => OriginalStockKind::BasicOptionalReversed,
|
||||||
|
Kind::BasicTyping => OriginalStockKind::BasicTyping,
|
||||||
|
Kind::Cloze => OriginalStockKind::Cloze,
|
||||||
|
},
|
||||||
|
(stock, _) => stock,
|
||||||
|
};
|
||||||
|
if stock_kind == OriginalStockKind::Unknown {
|
||||||
|
invalid_input!("unknown original notetype kind");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stock_nt = get_original_stock_notetype(stock_kind, &self.tr)?;
|
||||||
|
for (idx, item) in stock_nt.templates.iter_mut().enumerate() {
|
||||||
|
item.ord = Some(idx as u32);
|
||||||
|
}
|
||||||
|
nt.templates = stock_nt.templates;
|
||||||
|
for (idx, item) in stock_nt.fields.iter_mut().enumerate() {
|
||||||
|
item.ord = Some(idx as u32);
|
||||||
|
}
|
||||||
|
nt.fields = stock_nt.fields;
|
||||||
|
nt.config.css = stock_nt.config.css;
|
||||||
|
if force_kind.is_some() {
|
||||||
|
nt.config.original_stock_kind = stock_kind as i32;
|
||||||
|
nt.config.kind = stock_nt.config.kind;
|
||||||
|
}
|
||||||
|
self.update_notetype(&mut nt, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn adding_and_removing_fields_and_templates() -> Result<()> {
|
||||||
|
let mut col = Collection::new();
|
||||||
|
let nt = col.get_notetype_by_name("Basic")?.unwrap();
|
||||||
|
let note = NoteAdder::basic(&mut col)
|
||||||
|
.fields(&["front", "back"])
|
||||||
|
.add(&mut col);
|
||||||
|
|
||||||
|
col.restore_notetype_to_stock(nt.id, Some(StockKind::BasicOptionalReversed))?;
|
||||||
|
let note = col.storage.get_note(note.id)?.unwrap();
|
||||||
|
assert_eq!(note.fields(), &["front", "back", ""]);
|
||||||
|
assert_eq!(
|
||||||
|
col.storage.db_scalar::<u32>("select count(*) from cards")?,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
col.restore_notetype_to_stock(nt.id, Some(StockKind::BasicAndReversed))?;
|
||||||
|
let note = col.storage.get_note(note.id)?.unwrap();
|
||||||
|
assert_eq!(note.fields(), &["front", "back"]);
|
||||||
|
assert_eq!(
|
||||||
|
col.storage.db_scalar::<u32>("select count(*) from cards")?,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
|
||||||
|
col.restore_notetype_to_stock(nt.id, Some(StockKind::Cloze))?;
|
||||||
|
let note = col.storage.get_note(note.id)?.unwrap();
|
||||||
|
assert_eq!(note.fields(), &["front", "back"]);
|
||||||
|
assert_eq!(
|
||||||
|
col.storage.db_scalar::<u32>("select count(*) from cards")?,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ use crate::notetype::NotetypeConfig;
|
|||||||
use crate::serde::default_on_invalid;
|
use crate::serde::default_on_invalid;
|
||||||
use crate::serde::deserialize_bool_from_anything;
|
use crate::serde::deserialize_bool_from_anything;
|
||||||
use crate::serde::deserialize_number_from_string;
|
use crate::serde::deserialize_number_from_string;
|
||||||
|
use crate::serde::is_default;
|
||||||
use crate::timestamp::TimestampSecs;
|
use crate::timestamp::TimestampSecs;
|
||||||
use crate::types::Usn;
|
use crate::types::Usn;
|
||||||
|
|
||||||
@ -59,6 +60,8 @@ pub struct NotetypeSchema11 {
|
|||||||
pub latexsvg: bool,
|
pub latexsvg: bool,
|
||||||
#[serde(default, deserialize_with = "default_on_invalid")]
|
#[serde(default, deserialize_with = "default_on_invalid")]
|
||||||
pub(crate) req: CardRequirementsSchema11,
|
pub(crate) req: CardRequirementsSchema11,
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
pub(crate) original_stock_kind: i32,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub(crate) other: HashMap<String, Value>,
|
pub(crate) other: HashMap<String, Value>,
|
||||||
}
|
}
|
||||||
@ -103,6 +106,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(),
|
||||||
|
original_stock_kind: nt.original_stock_kind,
|
||||||
other: other_to_bytes(&nt.other),
|
other: other_to_bytes(&nt.other),
|
||||||
},
|
},
|
||||||
fields: nt.flds.into_iter().map(Into::into).collect(),
|
fields: nt.flds.into_iter().map(Into::into).collect(),
|
||||||
@ -160,6 +164,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()),
|
||||||
|
original_stock_kind: c.original_stock_kind,
|
||||||
other: bytes_to_other(&c.other),
|
other: bytes_to_other(&c.other),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,7 +172,13 @@ impl From<Notetype> for NotetypeSchema11 {
|
|||||||
|
|
||||||
/// See [crate::deckconfig::schema11::clear_other_duplicates()].
|
/// See [crate::deckconfig::schema11::clear_other_duplicates()].
|
||||||
fn clear_other_field_duplicates(other: &mut HashMap<String, Value>) {
|
fn clear_other_field_duplicates(other: &mut HashMap<String, Value>) {
|
||||||
for key in &["description", "plainText", "collapsed", "excludeFromSearch"] {
|
for key in &[
|
||||||
|
"description",
|
||||||
|
"plainText",
|
||||||
|
"collapsed",
|
||||||
|
"excludeFromSearch",
|
||||||
|
"originalStockKind",
|
||||||
|
] {
|
||||||
other.remove(*key);
|
other.remove(*key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,13 @@ use crate::config::ConfigEntry;
|
|||||||
use crate::config::ConfigKey;
|
use crate::config::ConfigKey;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::i18n::I18n;
|
use crate::i18n::I18n;
|
||||||
|
use crate::image_occlusion::imagedata::image_occlusion_notetype;
|
||||||
|
use crate::invalid_input;
|
||||||
use crate::notetype::Notetype;
|
use crate::notetype::Notetype;
|
||||||
|
use crate::pb::notetypes::notetype::config::Kind as NotetypeKind;
|
||||||
use crate::pb::notetypes::stock_notetype::Kind;
|
use crate::pb::notetypes::stock_notetype::Kind;
|
||||||
|
pub(crate) use crate::pb::notetypes::stock_notetype::Kind as StockKind;
|
||||||
|
use crate::pb::notetypes::stock_notetype::OriginalStockKind;
|
||||||
use crate::storage::SqliteStorage;
|
use crate::storage::SqliteStorage;
|
||||||
use crate::timestamp::TimestampSecs;
|
use crate::timestamp::TimestampSecs;
|
||||||
|
|
||||||
@ -16,7 +21,7 @@ impl SqliteStorage {
|
|||||||
for (idx, mut nt) in all_stock_notetypes(tr).into_iter().enumerate() {
|
for (idx, mut nt) in all_stock_notetypes(tr).into_iter().enumerate() {
|
||||||
nt.prepare_for_update(None, true)?;
|
nt.prepare_for_update(None, true)?;
|
||||||
self.add_notetype(&mut nt)?;
|
self.add_notetype(&mut nt)?;
|
||||||
if idx == Kind::Basic as usize {
|
if idx == 0 {
|
||||||
self.set_config_entry(&ConfigEntry::boxed(
|
self.set_config_entry(&ConfigEntry::boxed(
|
||||||
ConfigKey::CurrentNotetypeId.into(),
|
ConfigKey::CurrentNotetypeId.into(),
|
||||||
serde_json::to_vec(&nt.id)?,
|
serde_json::to_vec(&nt.id)?,
|
||||||
@ -29,7 +34,8 @@ impl SqliteStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if changing this, make sure to update StockNotetype enum
|
// If changing this, make sure to update StockNotetype enum. Other parts of the
|
||||||
|
// code expect the order here to be the same as the enum.
|
||||||
pub fn all_stock_notetypes(tr: &I18n) -> Vec<Notetype> {
|
pub fn all_stock_notetypes(tr: &I18n) -> Vec<Notetype> {
|
||||||
vec![
|
vec![
|
||||||
basic(tr),
|
basic(tr),
|
||||||
@ -45,11 +51,55 @@ fn fieldref<S: AsRef<str>>(name: S) -> String {
|
|||||||
format!("{{{{{}}}}}", name.as_ref())
|
format!("{{{{{}}}}}", name.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn basic(tr: &I18n) -> Notetype {
|
/// Create an empty notetype with a given name and stock kind.
|
||||||
let mut nt = Notetype {
|
pub(crate) fn empty_stock(
|
||||||
name: tr.notetypes_basic_name().into(),
|
nt_kind: NotetypeKind,
|
||||||
|
original_stock_kind: OriginalStockKind,
|
||||||
|
name: impl Into<String>,
|
||||||
|
) -> Notetype {
|
||||||
|
Notetype {
|
||||||
|
name: name.into(),
|
||||||
|
config: NotetypeConfig {
|
||||||
|
kind: nt_kind as i32,
|
||||||
|
original_stock_kind: original_stock_kind as i32,
|
||||||
|
..if nt_kind == NotetypeKind::Cloze {
|
||||||
|
NotetypeConfig::new_cloze()
|
||||||
|
} else {
|
||||||
|
NotetypeConfig::new()
|
||||||
|
}
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_stock_notetype(kind: StockKind, tr: &I18n) -> Notetype {
|
||||||
|
match kind {
|
||||||
|
Kind::Basic => basic(tr),
|
||||||
|
Kind::BasicAndReversed => basic_forward_reverse(tr),
|
||||||
|
Kind::BasicOptionalReversed => basic_optional_reverse(tr),
|
||||||
|
Kind::BasicTyping => basic_typing(tr),
|
||||||
|
Kind::Cloze => cloze(tr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_original_stock_notetype(kind: OriginalStockKind, tr: &I18n) -> Result<Notetype> {
|
||||||
|
Ok(match kind {
|
||||||
|
OriginalStockKind::Unknown => invalid_input!("original stock kind not provided"),
|
||||||
|
OriginalStockKind::Basic => basic(tr),
|
||||||
|
OriginalStockKind::BasicAndReversed => basic_forward_reverse(tr),
|
||||||
|
OriginalStockKind::BasicOptionalReversed => basic_optional_reverse(tr),
|
||||||
|
OriginalStockKind::BasicTyping => basic_typing(tr),
|
||||||
|
OriginalStockKind::Cloze => cloze(tr),
|
||||||
|
OriginalStockKind::ImageOcclusion => image_occlusion_notetype(tr),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn basic(tr: &I18n) -> Notetype {
|
||||||
|
let mut nt = empty_stock(
|
||||||
|
NotetypeKind::Normal,
|
||||||
|
OriginalStockKind::Basic,
|
||||||
|
tr.notetypes_basic_name(),
|
||||||
|
);
|
||||||
let front = tr.notetypes_front_field();
|
let front = tr.notetypes_front_field();
|
||||||
let back = tr.notetypes_back_field();
|
let back = tr.notetypes_back_field();
|
||||||
nt.add_field(front.as_ref());
|
nt.add_field(front.as_ref());
|
||||||
@ -68,6 +118,7 @@ pub(crate) fn basic(tr: &I18n) -> Notetype {
|
|||||||
|
|
||||||
pub(crate) fn basic_typing(tr: &I18n) -> Notetype {
|
pub(crate) fn basic_typing(tr: &I18n) -> Notetype {
|
||||||
let mut nt = basic(tr);
|
let mut nt = basic(tr);
|
||||||
|
nt.config.original_stock_kind = StockKind::BasicTyping as i32;
|
||||||
nt.name = tr.notetypes_basic_type_answer_name().into();
|
nt.name = tr.notetypes_basic_type_answer_name().into();
|
||||||
let front = tr.notetypes_front_field();
|
let front = tr.notetypes_front_field();
|
||||||
let back = tr.notetypes_back_field();
|
let back = tr.notetypes_back_field();
|
||||||
@ -83,6 +134,7 @@ pub(crate) fn basic_typing(tr: &I18n) -> Notetype {
|
|||||||
|
|
||||||
pub(crate) fn basic_forward_reverse(tr: &I18n) -> Notetype {
|
pub(crate) fn basic_forward_reverse(tr: &I18n) -> Notetype {
|
||||||
let mut nt = basic(tr);
|
let mut nt = basic(tr);
|
||||||
|
nt.config.original_stock_kind = StockKind::BasicAndReversed as i32;
|
||||||
nt.name = tr.notetypes_basic_reversed_name().into();
|
nt.name = tr.notetypes_basic_reversed_name().into();
|
||||||
let front = tr.notetypes_front_field();
|
let front = tr.notetypes_front_field();
|
||||||
let back = tr.notetypes_back_field();
|
let back = tr.notetypes_back_field();
|
||||||
@ -100,6 +152,7 @@ pub(crate) fn basic_forward_reverse(tr: &I18n) -> Notetype {
|
|||||||
|
|
||||||
pub(crate) fn basic_optional_reverse(tr: &I18n) -> Notetype {
|
pub(crate) fn basic_optional_reverse(tr: &I18n) -> Notetype {
|
||||||
let mut nt = basic_forward_reverse(tr);
|
let mut nt = basic_forward_reverse(tr);
|
||||||
|
nt.config.original_stock_kind = StockKind::BasicOptionalReversed as i32;
|
||||||
nt.name = tr.notetypes_basic_optional_reversed_name().into();
|
nt.name = tr.notetypes_basic_optional_reversed_name().into();
|
||||||
let addrev = tr.notetypes_add_reverse_field();
|
let addrev = tr.notetypes_add_reverse_field();
|
||||||
nt.add_field(addrev.as_ref());
|
nt.add_field(addrev.as_ref());
|
||||||
@ -109,11 +162,11 @@ pub(crate) fn basic_optional_reverse(tr: &I18n) -> Notetype {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn cloze(tr: &I18n) -> Notetype {
|
pub(crate) fn cloze(tr: &I18n) -> Notetype {
|
||||||
let mut nt = Notetype {
|
let mut nt = empty_stock(
|
||||||
name: tr.notetypes_cloze_name().into(),
|
NotetypeKind::Cloze,
|
||||||
config: NotetypeConfig::new_cloze(),
|
OriginalStockKind::Cloze,
|
||||||
..Default::default()
|
tr.notetypes_cloze_name(),
|
||||||
};
|
);
|
||||||
let text = tr.notetypes_text_field();
|
let text = tr.notetypes_text_field();
|
||||||
nt.add_field(text.as_ref());
|
nt.add_field(text.as_ref());
|
||||||
let back_extra = tr.notetypes_back_extra_field();
|
let back_extra = tr.notetypes_back_extra_field();
|
||||||
|
@ -20,6 +20,10 @@ where
|
|||||||
Ok(T::deserialize(v).unwrap_or_default())
|
Ok(T::deserialize(v).unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_default<T: Default + PartialEq>(t: &T) -> bool {
|
||||||
|
*t == Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn deserialize_int_from_number<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
pub(crate) fn deserialize_int_from_number<'de, T, D>(deserializer: D) -> Result<T, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
|
Loading…
Reference in New Issue
Block a user