mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
Optionally restore original position and reset counts when forgetting (#1714)
* Add forget prompt with options - Restore original position - Reset reps and lapses * Restore position when resetting for export * Add config context to avoid passing keys * Add routine to fetch defaults; use method-specific enum (dae) * Keep original position by default (dae) * Fix code completion for forget dialog (dae) Needs to be a symbolic link to the generated file
This commit is contained in:
parent
c21e6e2b97
commit
b9c3b12f71
17 changed files with 278 additions and 20 deletions
|
@ -135,6 +135,8 @@ scheduling-new-options-group-name = New options group name:
|
||||||
scheduling-options-group = Options group:
|
scheduling-options-group = Options group:
|
||||||
scheduling-order = Order
|
scheduling-order = Order
|
||||||
scheduling-parent-limit = (parent limit: { $val })
|
scheduling-parent-limit = (parent limit: { $val })
|
||||||
|
scheduling-reset-counts = Reset repetition and lapse counts
|
||||||
|
scheduling-restore-position = Restore original position where possible
|
||||||
scheduling-review = Review
|
scheduling-review = Review
|
||||||
scheduling-reviews = Reviews
|
scheduling-reviews = Reviews
|
||||||
scheduling-seconds = seconds
|
scheduling-seconds = seconds
|
||||||
|
|
|
@ -41,6 +41,10 @@ message ConfigKey {
|
||||||
PASTE_STRIPS_FORMATTING = 16;
|
PASTE_STRIPS_FORMATTING = 16;
|
||||||
NORMALIZE_NOTE_TEXT = 17;
|
NORMALIZE_NOTE_TEXT = 17;
|
||||||
IGNORE_ACCENTS_IN_SEARCH = 18;
|
IGNORE_ACCENTS_IN_SEARCH = 18;
|
||||||
|
RESTORE_POSITION_BROWSER = 19;
|
||||||
|
RESTORE_POSITION_REVIEWER = 20;
|
||||||
|
RESET_COUNTS_BROWSER = 21;
|
||||||
|
RESET_COUNTS_REVIEWER = 22;
|
||||||
}
|
}
|
||||||
enum String {
|
enum String {
|
||||||
SET_DUE_BROWSER = 0;
|
SET_DUE_BROWSER = 0;
|
||||||
|
|
|
@ -30,6 +30,8 @@ service SchedulerService {
|
||||||
rpc RebuildFilteredDeck(decks.DeckId) returns (collection.OpChangesWithCount);
|
rpc RebuildFilteredDeck(decks.DeckId) returns (collection.OpChangesWithCount);
|
||||||
rpc ScheduleCardsAsNew(ScheduleCardsAsNewRequest)
|
rpc ScheduleCardsAsNew(ScheduleCardsAsNewRequest)
|
||||||
returns (collection.OpChanges);
|
returns (collection.OpChanges);
|
||||||
|
rpc ScheduleCardsAsNewDefaults(ScheduleCardsAsNewDefaultsRequest)
|
||||||
|
returns (ScheduleCardsAsNewDefaultsResponse);
|
||||||
rpc SetDueDate(SetDueDateRequest) returns (collection.OpChanges);
|
rpc SetDueDate(SetDueDateRequest) returns (collection.OpChanges);
|
||||||
rpc SortCards(SortCardsRequest) returns (collection.OpChangesWithCount);
|
rpc SortCards(SortCardsRequest) returns (collection.OpChangesWithCount);
|
||||||
rpc SortDeck(SortDeckRequest) returns (collection.OpChangesWithCount);
|
rpc SortDeck(SortDeckRequest) returns (collection.OpChangesWithCount);
|
||||||
|
@ -172,8 +174,24 @@ message BuryOrSuspendCardsRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
message ScheduleCardsAsNewRequest {
|
message ScheduleCardsAsNewRequest {
|
||||||
|
enum Context {
|
||||||
|
BROWSER = 0;
|
||||||
|
REVIEWER = 1;
|
||||||
|
}
|
||||||
repeated int64 card_ids = 1;
|
repeated int64 card_ids = 1;
|
||||||
bool log = 2;
|
bool log = 2;
|
||||||
|
bool restore_position = 3;
|
||||||
|
bool reset_counts = 4;
|
||||||
|
optional Context context = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ScheduleCardsAsNewDefaultsRequest {
|
||||||
|
ScheduleCardsAsNewRequest.Context context = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ScheduleCardsAsNewDefaultsResponse {
|
||||||
|
bool restore_position = 1;
|
||||||
|
bool reset_counts = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SetDueDateRequest {
|
message SetDueDateRequest {
|
||||||
|
|
|
@ -21,6 +21,7 @@ ignored-classes=
|
||||||
StripHtmlRequest,
|
StripHtmlRequest,
|
||||||
CustomStudyRequest,
|
CustomStudyRequest,
|
||||||
Cram,
|
Cram,
|
||||||
|
ScheduleCardsAsNewRequest,
|
||||||
|
|
||||||
[REPORTS]
|
[REPORTS]
|
||||||
output-format=colorized
|
output-format=colorized
|
||||||
|
|
|
@ -15,6 +15,8 @@ CongratsInfo = scheduler_pb2.CongratsInfoResponse
|
||||||
UnburyDeck = scheduler_pb2.UnburyDeckRequest
|
UnburyDeck = scheduler_pb2.UnburyDeckRequest
|
||||||
BuryOrSuspend = scheduler_pb2.BuryOrSuspendCardsRequest
|
BuryOrSuspend = scheduler_pb2.BuryOrSuspendCardsRequest
|
||||||
CustomStudyRequest = scheduler_pb2.CustomStudyRequest
|
CustomStudyRequest = scheduler_pb2.CustomStudyRequest
|
||||||
|
ScheduleCardsAsNew = scheduler_pb2.ScheduleCardsAsNewRequest
|
||||||
|
ScheduleCardsAsNewDefaults = scheduler_pb2.ScheduleCardsAsNewDefaultsResponse
|
||||||
FilteredDeckForUpdate = decks_pb2.FilteredDeckForUpdate
|
FilteredDeckForUpdate = decks_pb2.FilteredDeckForUpdate
|
||||||
|
|
||||||
|
|
||||||
|
@ -163,9 +165,28 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
|
||||||
# Resetting/rescheduling
|
# Resetting/rescheduling
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def schedule_cards_as_new(self, card_ids: Sequence[CardId]) -> OpChanges:
|
def schedule_cards_as_new(
|
||||||
"Put cards at the end of the new queue."
|
self,
|
||||||
return self.col._backend.schedule_cards_as_new(card_ids=card_ids, log=True)
|
card_ids: Sequence[CardId],
|
||||||
|
*,
|
||||||
|
restore_position: bool = False,
|
||||||
|
reset_counts: bool = False,
|
||||||
|
context: ScheduleCardsAsNew.Context.V | None = None,
|
||||||
|
) -> OpChanges:
|
||||||
|
"Place cards back into the new queue."
|
||||||
|
request = ScheduleCardsAsNew(
|
||||||
|
card_ids=card_ids,
|
||||||
|
log=True,
|
||||||
|
restore_position=restore_position,
|
||||||
|
reset_counts=reset_counts,
|
||||||
|
context=context,
|
||||||
|
)
|
||||||
|
return self.col._backend.schedule_cards_as_new(request)
|
||||||
|
|
||||||
|
def schedule_cards_as_new_defaults(
|
||||||
|
self, context: ScheduleCardsAsNew.Context.V
|
||||||
|
) -> ScheduleCardsAsNewDefaults:
|
||||||
|
return self.col._backend.schedule_cards_as_new_defaults(context)
|
||||||
|
|
||||||
def set_due_date(
|
def set_due_date(
|
||||||
self,
|
self,
|
||||||
|
@ -203,7 +224,8 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
|
||||||
" where id in %s" % sids
|
" where id in %s" % sids
|
||||||
)
|
)
|
||||||
# and forget any non-new cards, changing their due numbers
|
# and forget any non-new cards, changing their due numbers
|
||||||
self.col._backend.schedule_cards_as_new(card_ids=non_new, log=False)
|
request = ScheduleCardsAsNew(card_ids=non_new, log=False, restore_position=True)
|
||||||
|
self.col._backend.schedule_cards_as_new(request)
|
||||||
|
|
||||||
# Repositioning new cards
|
# Repositioning new cards
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
|
@ -17,6 +17,7 @@ ignored-classes=
|
||||||
ChangeNotetypeRequest,
|
ChangeNotetypeRequest,
|
||||||
CustomStudyRequest,
|
CustomStudyRequest,
|
||||||
Cram,
|
Cram,
|
||||||
|
ScheduleCardsAsNewRequest,
|
||||||
|
|
||||||
[REPORTS]
|
[REPORTS]
|
||||||
output-format=colorized
|
output-format=colorized
|
||||||
|
|
|
@ -18,6 +18,7 @@ from anki.consts import *
|
||||||
from anki.errors import NotFoundError
|
from anki.errors import NotFoundError
|
||||||
from anki.lang import without_unicode_isolation
|
from anki.lang import without_unicode_isolation
|
||||||
from anki.notes import NoteId
|
from anki.notes import NoteId
|
||||||
|
from anki.scheduler.base import ScheduleCardsAsNew
|
||||||
from anki.tags import MARKED_TAG
|
from anki.tags import MARKED_TAG
|
||||||
from anki.utils import is_mac
|
from anki.utils import is_mac
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
|
@ -862,10 +863,12 @@ class Browser(QMainWindow):
|
||||||
@skip_if_selection_is_empty
|
@skip_if_selection_is_empty
|
||||||
@ensure_editor_saved
|
@ensure_editor_saved
|
||||||
def forget_cards(self) -> None:
|
def forget_cards(self) -> None:
|
||||||
forget_cards(
|
if op := forget_cards(
|
||||||
parent=self,
|
parent=self,
|
||||||
card_ids=self.selected_cards(),
|
card_ids=self.selected_cards(),
|
||||||
).run_in_background()
|
context=ScheduleCardsAsNew.Context.BROWSER,
|
||||||
|
):
|
||||||
|
op.run_in_background()
|
||||||
|
|
||||||
# Edit: selection
|
# Edit: selection
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
|
@ -14,6 +14,7 @@ from . import customstudy
|
||||||
from . import dconf
|
from . import dconf
|
||||||
from . import debug
|
from . import debug
|
||||||
from . import filtered_deck
|
from . import filtered_deck
|
||||||
|
from . import forget
|
||||||
from . import editaddon
|
from . import editaddon
|
||||||
from . import editcurrent
|
from . import editcurrent
|
||||||
from . import edithtml
|
from . import edithtml
|
||||||
|
|
6
qt/aqt/forms/forget.py
Normal file
6
qt/aqt/forms/forget.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
from aqt.qt import qtmajor
|
||||||
|
|
||||||
|
if qtmajor > 5:
|
||||||
|
from .forget_qt6 import *
|
||||||
|
else:
|
||||||
|
from .forget_qt5 import * # type: ignore
|
94
qt/aqt/forms/forget.ui
Normal file
94
qt/aqt/forms/forget.ui
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>Dialog</class>
|
||||||
|
<widget class="QDialog" name="Dialog">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>235</width>
|
||||||
|
<height>118</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>actions_forget_card</string>
|
||||||
|
</property>
|
||||||
|
<property name="modal">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="restore_position">
|
||||||
|
<property name="text">
|
||||||
|
<string>scheduling_restore_position</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="reset_counts">
|
||||||
|
<property name="text">
|
||||||
|
<string>scheduling_reset_counts</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>20</width>
|
||||||
|
<height>40</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QDialogButtonBox" name="buttonBox">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="standardButtons">
|
||||||
|
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>accepted()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>accept()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>248</x>
|
||||||
|
<y>254</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>157</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
<connection>
|
||||||
|
<sender>buttonBox</sender>
|
||||||
|
<signal>rejected()</signal>
|
||||||
|
<receiver>Dialog</receiver>
|
||||||
|
<slot>reject()</slot>
|
||||||
|
<hints>
|
||||||
|
<hint type="sourcelabel">
|
||||||
|
<x>316</x>
|
||||||
|
<y>260</y>
|
||||||
|
</hint>
|
||||||
|
<hint type="destinationlabel">
|
||||||
|
<x>286</x>
|
||||||
|
<y>274</y>
|
||||||
|
</hint>
|
||||||
|
</hints>
|
||||||
|
</connection>
|
||||||
|
</connections>
|
||||||
|
</ui>
|
1
qt/aqt/forms/forget_qt6.py
Symbolic link
1
qt/aqt/forms/forget_qt6.py
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../../.bazel/bin/qt/aqt/forms/forget_qt6.py
|
|
@ -19,6 +19,7 @@ from anki.collection import (
|
||||||
from anki.decks import DeckId
|
from anki.decks import DeckId
|
||||||
from anki.notes import NoteId
|
from anki.notes import NoteId
|
||||||
from anki.scheduler import CustomStudyRequest, FilteredDeckForUpdate, UnburyDeck
|
from anki.scheduler import CustomStudyRequest, FilteredDeckForUpdate, UnburyDeck
|
||||||
|
from anki.scheduler.base import ScheduleCardsAsNew
|
||||||
from anki.scheduler.v3 import CardAnswer
|
from anki.scheduler.v3 import CardAnswer
|
||||||
from anki.scheduler.v3 import Scheduler as V3Scheduler
|
from anki.scheduler.v3 import Scheduler as V3Scheduler
|
||||||
from aqt.operations import CollectionOp
|
from aqt.operations import CollectionOp
|
||||||
|
@ -65,10 +66,37 @@ def set_due_date_dialog(
|
||||||
|
|
||||||
|
|
||||||
def forget_cards(
|
def forget_cards(
|
||||||
*, parent: QWidget, card_ids: Sequence[CardId]
|
*,
|
||||||
) -> CollectionOp[OpChanges]:
|
parent: QWidget,
|
||||||
|
card_ids: Sequence[CardId],
|
||||||
|
context: ScheduleCardsAsNew.Context.V | None = None,
|
||||||
|
) -> CollectionOp[OpChanges] | None:
|
||||||
|
assert aqt.mw
|
||||||
|
|
||||||
|
dialog = QDialog(parent)
|
||||||
|
disable_help_button(dialog)
|
||||||
|
form = aqt.forms.forget.Ui_Dialog()
|
||||||
|
form.setupUi(dialog)
|
||||||
|
|
||||||
|
if context is not None:
|
||||||
|
defaults = aqt.mw.col.sched.schedule_cards_as_new_defaults(context)
|
||||||
|
form.restore_position.setChecked(defaults.restore_position)
|
||||||
|
form.reset_counts.setChecked(defaults.reset_counts)
|
||||||
|
|
||||||
|
if not dialog.exec():
|
||||||
|
return None
|
||||||
|
|
||||||
|
restore_position = form.restore_position.isChecked()
|
||||||
|
reset_counts = form.reset_counts.isChecked()
|
||||||
|
|
||||||
return CollectionOp(
|
return CollectionOp(
|
||||||
parent, lambda col: col.sched.schedule_cards_as_new(card_ids)
|
parent,
|
||||||
|
lambda col: col.sched.schedule_cards_as_new(
|
||||||
|
card_ids,
|
||||||
|
restore_position=restore_position,
|
||||||
|
reset_counts=reset_counts,
|
||||||
|
context=context,
|
||||||
|
),
|
||||||
).success(
|
).success(
|
||||||
lambda _: tooltip(
|
lambda _: tooltip(
|
||||||
tr.scheduling_forgot_cards(cards=len(card_ids)), parent=parent
|
tr.scheduling_forgot_cards(cards=len(card_ids)), parent=parent
|
||||||
|
|
|
@ -19,6 +19,7 @@ import aqt.operations
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki.cards import Card, CardId
|
from anki.cards import Card, CardId
|
||||||
from anki.collection import Config, OpChanges, OpChangesWithCount
|
from anki.collection import Config, OpChanges, OpChangesWithCount
|
||||||
|
from anki.scheduler.base import ScheduleCardsAsNew
|
||||||
from anki.scheduler.v3 import CardAnswer, NextStates, QueuedCards
|
from anki.scheduler.v3 import CardAnswer, NextStates, QueuedCards
|
||||||
from anki.scheduler.v3 import Scheduler as V3Scheduler
|
from anki.scheduler.v3 import Scheduler as V3Scheduler
|
||||||
from anki.tags import MARKED_TAG
|
from anki.tags import MARKED_TAG
|
||||||
|
@ -1067,10 +1068,12 @@ time = %(time)d;
|
||||||
).run_in_background()
|
).run_in_background()
|
||||||
|
|
||||||
def forget_current_card(self) -> None:
|
def forget_current_card(self) -> None:
|
||||||
forget_cards(
|
if op := forget_cards(
|
||||||
parent=self.mw,
|
parent=self.mw,
|
||||||
card_ids=[self.card.id],
|
card_ids=[self.card.id],
|
||||||
).run_in_background()
|
context=ScheduleCardsAsNew.Context.REVIEWER,
|
||||||
|
):
|
||||||
|
op.run_in_background()
|
||||||
|
|
||||||
def on_create_copy(self) -> None:
|
def on_create_copy(self) -> None:
|
||||||
if self.card:
|
if self.card:
|
||||||
|
|
|
@ -32,6 +32,10 @@ impl From<BoolKeyProto> for BoolKey {
|
||||||
BoolKeyProto::PasteStripsFormatting => BoolKey::PasteStripsFormatting,
|
BoolKeyProto::PasteStripsFormatting => BoolKey::PasteStripsFormatting,
|
||||||
BoolKeyProto::NormalizeNoteText => BoolKey::NormalizeNoteText,
|
BoolKeyProto::NormalizeNoteText => BoolKey::NormalizeNoteText,
|
||||||
BoolKeyProto::IgnoreAccentsInSearch => BoolKey::IgnoreAccentsInSearch,
|
BoolKeyProto::IgnoreAccentsInSearch => BoolKey::IgnoreAccentsInSearch,
|
||||||
|
BoolKeyProto::RestorePositionBrowser => BoolKey::RestorePositionBrowser,
|
||||||
|
BoolKeyProto::RestorePositionReviewer => BoolKey::RestorePositionReviewer,
|
||||||
|
BoolKeyProto::ResetCountsBrowser => BoolKey::ResetCountsBrowser,
|
||||||
|
BoolKeyProto::ResetCountsReviewer => BoolKey::ResetCountsReviewer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ mod states;
|
||||||
use super::Backend;
|
use super::Backend;
|
||||||
pub(super) use crate::backend_proto::scheduler_service::Service as SchedulerService;
|
pub(super) use crate::backend_proto::scheduler_service::Service as SchedulerService;
|
||||||
use crate::{
|
use crate::{
|
||||||
backend_proto::{self as pb},
|
backend_proto as pb,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
scheduler::{
|
scheduler::{
|
||||||
new::NewCardDueOrder,
|
new::NewCardDueOrder,
|
||||||
|
@ -111,11 +111,26 @@ impl SchedulerService for Backend {
|
||||||
fn schedule_cards_as_new(&self, input: pb::ScheduleCardsAsNewRequest) -> Result<pb::OpChanges> {
|
fn schedule_cards_as_new(&self, input: pb::ScheduleCardsAsNewRequest) -> Result<pb::OpChanges> {
|
||||||
self.with_col(|col| {
|
self.with_col(|col| {
|
||||||
let cids = input.card_ids.into_newtype(CardId);
|
let cids = input.card_ids.into_newtype(CardId);
|
||||||
let log = input.log;
|
col.reschedule_cards_as_new(
|
||||||
col.reschedule_cards_as_new(&cids, log).map(Into::into)
|
&cids,
|
||||||
|
input.log,
|
||||||
|
input.restore_position,
|
||||||
|
input.reset_counts,
|
||||||
|
input
|
||||||
|
.context
|
||||||
|
.and_then(pb::schedule_cards_as_new_request::Context::from_i32),
|
||||||
|
)
|
||||||
|
.map(Into::into)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn schedule_cards_as_new_defaults(
|
||||||
|
&self,
|
||||||
|
input: pb::ScheduleCardsAsNewDefaultsRequest,
|
||||||
|
) -> Result<pb::ScheduleCardsAsNewDefaultsResponse> {
|
||||||
|
self.with_col(|col| Ok(col.reschedule_cards_as_new_defaults(input.context())))
|
||||||
|
}
|
||||||
|
|
||||||
fn set_due_date(&self, input: pb::SetDueDateRequest) -> Result<pb::OpChanges> {
|
fn set_due_date(&self, input: pb::SetDueDateRequest) -> Result<pb::OpChanges> {
|
||||||
let config = input.config_key.map(|v| v.key().into());
|
let config = input.config_key.map(|v| v.key().into());
|
||||||
let days = input.days;
|
let days = input.days;
|
||||||
|
|
|
@ -27,6 +27,10 @@ pub enum BoolKey {
|
||||||
PreviewBothSides,
|
PreviewBothSides,
|
||||||
Sched2021,
|
Sched2021,
|
||||||
IgnoreAccentsInSearch,
|
IgnoreAccentsInSearch,
|
||||||
|
RestorePositionBrowser,
|
||||||
|
RestorePositionReviewer,
|
||||||
|
ResetCountsBrowser,
|
||||||
|
ResetCountsReviewer,
|
||||||
|
|
||||||
#[strum(to_string = "normalize_note_text")]
|
#[strum(to_string = "normalize_note_text")]
|
||||||
NormalizeNoteText,
|
NormalizeNoteText,
|
||||||
|
@ -55,6 +59,8 @@ impl Collection {
|
||||||
| BoolKey::FutureDueShowBacklog
|
| BoolKey::FutureDueShowBacklog
|
||||||
| BoolKey::ShowRemainingDueCountsInStudy
|
| BoolKey::ShowRemainingDueCountsInStudy
|
||||||
| BoolKey::CardCountsSeparateInactive
|
| BoolKey::CardCountsSeparateInactive
|
||||||
|
| BoolKey::RestorePositionBrowser
|
||||||
|
| BoolKey::RestorePositionReviewer
|
||||||
| BoolKey::NormalizeNoteText => self.get_config_optional(key).unwrap_or(true),
|
| BoolKey::NormalizeNoteText => self.get_config_optional(key).unwrap_or(true),
|
||||||
|
|
||||||
// other options default to false
|
// other options default to false
|
||||||
|
|
|
@ -5,16 +5,20 @@ use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
|
|
||||||
|
pub use crate::backend_proto::scheduler::{
|
||||||
|
schedule_cards_as_new_request::Context as ScheduleAsNewContext,
|
||||||
|
ScheduleCardsAsNewDefaultsResponse,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
card::{CardQueue, CardType},
|
card::{CardQueue, CardType},
|
||||||
config::SchedulerVersion,
|
config::{BoolKey, SchedulerVersion},
|
||||||
deckconfig::NewCardInsertOrder,
|
deckconfig::NewCardInsertOrder,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
search::{SearchNode, SortMode, StateKind},
|
search::{SearchNode, SortMode, StateKind},
|
||||||
};
|
};
|
||||||
|
|
||||||
impl Card {
|
impl Card {
|
||||||
fn schedule_as_new(&mut self, position: u32) {
|
fn schedule_as_new(&mut self, position: u32, reset_counts: bool) {
|
||||||
self.remove_from_filtered_deck_before_reschedule();
|
self.remove_from_filtered_deck_before_reschedule();
|
||||||
self.due = position as i32;
|
self.due = position as i32;
|
||||||
self.ctype = CardType::New;
|
self.ctype = CardType::New;
|
||||||
|
@ -22,6 +26,10 @@ impl Card {
|
||||||
self.interval = 0;
|
self.interval = 0;
|
||||||
self.ease_factor = 0;
|
self.ease_factor = 0;
|
||||||
self.original_position = None;
|
self.original_position = None;
|
||||||
|
if reset_counts {
|
||||||
|
self.reps = 0;
|
||||||
|
self.lapses = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the card is new, change its position, and return true.
|
/// If the card is new, change its position, and return true.
|
||||||
|
@ -112,7 +120,14 @@ fn nids_in_preserved_order(cards: &[Card]) -> Vec<NoteId> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collection {
|
impl Collection {
|
||||||
pub fn reschedule_cards_as_new(&mut self, cids: &[CardId], log: bool) -> Result<OpOutput<()>> {
|
pub fn reschedule_cards_as_new(
|
||||||
|
&mut self,
|
||||||
|
cids: &[CardId],
|
||||||
|
log: bool,
|
||||||
|
restore_position: bool,
|
||||||
|
reset_counts: bool,
|
||||||
|
context: Option<ScheduleAsNewContext>,
|
||||||
|
) -> Result<OpOutput<()>> {
|
||||||
let usn = self.usn()?;
|
let usn = self.usn()?;
|
||||||
let mut position = self.get_next_card_position();
|
let mut position = self.get_next_card_position();
|
||||||
self.transact(Op::ScheduleAsNew, |col| {
|
self.transact(Op::ScheduleAsNew, |col| {
|
||||||
|
@ -120,18 +135,52 @@ impl Collection {
|
||||||
let cards = col.storage.all_searched_cards_in_search_order()?;
|
let cards = col.storage.all_searched_cards_in_search_order()?;
|
||||||
for mut card in cards {
|
for mut card in cards {
|
||||||
let original = card.clone();
|
let original = card.clone();
|
||||||
card.schedule_as_new(position);
|
if restore_position && card.original_position.is_some() {
|
||||||
|
card.schedule_as_new(card.original_position.unwrap(), reset_counts);
|
||||||
|
} else {
|
||||||
|
card.schedule_as_new(position, reset_counts);
|
||||||
|
position += 1;
|
||||||
|
}
|
||||||
if log {
|
if log {
|
||||||
col.log_manually_scheduled_review(&card, &original, usn)?;
|
col.log_manually_scheduled_review(&card, &original, usn)?;
|
||||||
}
|
}
|
||||||
col.update_card_inner(&mut card, original, usn)?;
|
col.update_card_inner(&mut card, original, usn)?;
|
||||||
position += 1;
|
|
||||||
}
|
}
|
||||||
col.set_next_card_position(position)?;
|
col.set_next_card_position(position)?;
|
||||||
col.storage.clear_searched_cards_table()
|
col.storage.clear_searched_cards_table()?;
|
||||||
|
|
||||||
|
match context {
|
||||||
|
Some(ScheduleAsNewContext::Browser) => {
|
||||||
|
col.set_config_bool_inner(BoolKey::RestorePositionBrowser, restore_position)?;
|
||||||
|
col.set_config_bool_inner(BoolKey::ResetCountsBrowser, reset_counts)?;
|
||||||
|
}
|
||||||
|
Some(ScheduleAsNewContext::Reviewer) => {
|
||||||
|
col.set_config_bool_inner(BoolKey::RestorePositionReviewer, restore_position)?;
|
||||||
|
col.set_config_bool_inner(BoolKey::ResetCountsReviewer, reset_counts)?;
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reschedule_cards_as_new_defaults(
|
||||||
|
&self,
|
||||||
|
context: ScheduleAsNewContext,
|
||||||
|
) -> ScheduleCardsAsNewDefaultsResponse {
|
||||||
|
match context {
|
||||||
|
ScheduleAsNewContext::Browser => ScheduleCardsAsNewDefaultsResponse {
|
||||||
|
restore_position: self.get_config_bool(BoolKey::RestorePositionBrowser),
|
||||||
|
reset_counts: self.get_config_bool(BoolKey::ResetCountsBrowser),
|
||||||
|
},
|
||||||
|
ScheduleAsNewContext::Reviewer => ScheduleCardsAsNewDefaultsResponse {
|
||||||
|
restore_position: self.get_config_bool(BoolKey::RestorePositionReviewer),
|
||||||
|
reset_counts: self.get_config_bool(BoolKey::ResetCountsReviewer),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sort_cards(
|
pub fn sort_cards(
|
||||||
&mut self,
|
&mut self,
|
||||||
cids: &[CardId],
|
cids: &[CardId],
|
||||||
|
|
Loading…
Reference in a new issue