Merge branch 'main' into issue-4276

This commit is contained in:
Max Romanowski 2025-09-03 16:49:35 +02:00
commit c408f2bb62
11 changed files with 46 additions and 27 deletions

View file

@ -241,6 +241,7 @@ Siyuan Mattuwu Yan <syan4@ualberta.ca>
Lee Doughty <32392044+leedoughty@users.noreply.github.com> Lee Doughty <32392044+leedoughty@users.noreply.github.com>
memchr <memchr@proton.me> memchr <memchr@proton.me>
Max Romanowski <maxr777@proton.me> Max Romanowski <maxr777@proton.me>
Aldlss <ayaldlss@gmail.com>
******************** ********************

View file

@ -1,5 +1,10 @@
adding-add-shortcut-ctrlandenter = Add (shortcut: ctrl+enter) adding-add-shortcut-ctrlandenter = Add (shortcut: ctrl+enter)
adding-added = Added adding-added = Added
adding-added-cards =
Added { $count ->
[one] { $count } card
*[other] { $count } cards
}
adding-discard-current-input = Discard current input? adding-discard-current-input = Discard current input?
adding-keep-editing = Keep Editing adding-keep-editing = Keep Editing
adding-edit = Edit "{ $val }" adding-edit = Edit "{ $val }"

View file

@ -59,7 +59,7 @@ message AddNoteRequest {
} }
message AddNoteResponse { message AddNoteResponse {
collection.OpChanges changes = 1; collection.OpChangesWithCount changes = 1;
int64 note_id = 2; int64 note_id = 2;
} }

View file

@ -528,7 +528,7 @@ class Collection(DeprecatedNamesMixin):
def new_note(self, notetype: NotetypeDict) -> Note: def new_note(self, notetype: NotetypeDict) -> Note:
return Note(self, notetype) return Note(self, notetype)
def add_note(self, note: Note, deck_id: DeckId) -> OpChanges: def add_note(self, note: Note, deck_id: DeckId) -> OpChangesWithCount:
hooks.note_will_be_added(self, note, deck_id) hooks.note_will_be_added(self, note, deck_id)
out = self._backend.add_note(note=note._to_backend_note(), deck_id=deck_id) out = self._backend.add_note(note=note._to_backend_note(), deck_id=deck_id)
note.id = NoteId(out.note_id) note.id = NoteId(out.note_id)

View file

@ -8,7 +8,7 @@ from collections.abc import Callable
import aqt.editor import aqt.editor
import aqt.forms import aqt.forms
from anki._legacy import deprecated from anki._legacy import deprecated
from anki.collection import OpChanges, SearchNode from anki.collection import OpChanges, OpChangesWithCount, SearchNode
from anki.decks import DeckId from anki.decks import DeckId
from anki.models import NotetypeId from anki.models import NotetypeId
from anki.notes import Note, NoteFieldsCheckResult, NoteId from anki.notes import Note, NoteFieldsCheckResult, NoteId
@ -294,13 +294,13 @@ class AddCards(QMainWindow):
target_deck_id = self.deck_chooser.selected_deck_id target_deck_id = self.deck_chooser.selected_deck_id
def on_success(changes: OpChanges) -> None: def on_success(changes: OpChangesWithCount) -> None:
# only used for detecting changed sticky fields on close # only used for detecting changed sticky fields on close
self._last_added_note = note self._last_added_note = note
self.addHistory(note) self.addHistory(note)
tooltip(tr.adding_added(), period=500) tooltip(tr.adding_added_cards(count=changes.count), period=500)
av_player.stop_and_clear_queue() av_player.stop_and_clear_queue()
self._load_new_note(sticky_fields_from=note) self._load_new_note(sticky_fields_from=note)
gui_hooks.add_cards_did_add_note(note) gui_hooks.add_cards_did_add_note(note)

View file

@ -18,7 +18,7 @@ def add_note(
parent: QWidget, parent: QWidget,
note: Note, note: Note,
target_deck_id: DeckId, target_deck_id: DeckId,
) -> CollectionOp[OpChanges]: ) -> CollectionOp[OpChangesWithCount]:
return CollectionOp(parent, lambda col: col.add_note(note, target_deck_id)) return CollectionOp(parent, lambda col: col.add_note(note, target_deck_id))

View file

@ -115,7 +115,7 @@ class ThemeManager:
# Workaround for Qt bug. First attempt was percent-escaping the chars, # Workaround for Qt bug. First attempt was percent-escaping the chars,
# but Qt can't handle that. # but Qt can't handle that.
# https://forum.qt.io/topic/55274/solved-qss-with-special-characters/11 # https://forum.qt.io/topic/55274/solved-qss-with-special-characters/11
path = re.sub(r"([\u00A1-\u00FF])", r"\\\1", path) path = re.sub(r"(['\u00A1-\u00FF])", r"\\\1", path)
return path return path
def icon_from_resources(self, path: str | ColoredIcon) -> QIcon: def icon_from_resources(self, path: str | ColoredIcon) -> QIcon:

View file

@ -309,6 +309,13 @@ fn handle_version_install_or_update(state: &State, choice: MainMenuChoice) -> Re
command.env("UV_NO_CACHE", "1"); command.env("UV_NO_CACHE", "1");
} }
// Add mirror environment variable if enabled
if let Some((python_mirror, pypi_mirror)) = get_mirror_urls(state)? {
command
.env("UV_PYTHON_INSTALL_MIRROR", &python_mirror)
.env("UV_DEFAULT_INDEX", &pypi_mirror);
}
match command.ensure_success() { match command.ensure_success() {
Ok(_) => { Ok(_) => {
// Sync succeeded // Sync succeeded
@ -673,6 +680,12 @@ fn fetch_versions(state: &State) -> Result<Vec<String>> {
cmd.arg(&versions_script); cmd.arg(&versions_script);
// Add mirror environment variable if enabled
if let Some((python_mirror, pypi_mirror)) = get_mirror_urls(state)? {
cmd.env("UV_PYTHON_INSTALL_MIRROR", &python_mirror)
.env("UV_DEFAULT_INDEX", &pypi_mirror);
}
let output = match cmd.utf8_output() { let output = match cmd.utf8_output() {
Ok(output) => output, Ok(output) => output,
Err(e) => { Err(e) => {
@ -725,15 +738,7 @@ fn apply_version_kind(version_kind: &VersionKind, state: &State) -> Result<()> {
&format!("anki-release=={version}\",\n \"anki=={version}\",\n \"aqt=={version}"), &format!("anki-release=={version}\",\n \"anki=={version}\",\n \"aqt=={version}"),
), ),
}; };
write_file(&state.user_pyproject_path, &updated_content)?;
// Add mirror configuration if enabled
let final_content = if let Some((python_mirror, pypi_mirror)) = get_mirror_urls(state)? {
format!("{updated_content}\n\n[[tool.uv.index]]\nname = \"mirror\"\nurl = \"{pypi_mirror}\"\ndefault = true\n\n[tool.uv]\npython-install-mirror = \"{python_mirror}\"\n")
} else {
updated_content
};
write_file(&state.user_pyproject_path, &final_content)?;
// Update .python-version based on version kind // Update .python-version based on version kind
match version_kind { match version_kind {

View file

@ -135,6 +135,8 @@ pub struct NormalDeckSchema11 {
review_limit_today: Option<DayLimit>, review_limit_today: Option<DayLimit>,
#[serde(default, deserialize_with = "default_on_invalid")] #[serde(default, deserialize_with = "default_on_invalid")]
new_limit_today: Option<DayLimit>, new_limit_today: Option<DayLimit>,
#[serde(default, deserialize_with = "default_on_invalid")]
desired_retention: Option<u32>,
} }
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] #[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
@ -249,6 +251,7 @@ impl Default for NormalDeckSchema11 {
new_limit: None, new_limit: None,
review_limit_today: None, review_limit_today: None,
new_limit_today: None, new_limit_today: None,
desired_retention: None,
} }
} }
} }
@ -325,7 +328,7 @@ impl From<NormalDeckSchema11> for NormalDeck {
new_limit: deck.new_limit, new_limit: deck.new_limit,
review_limit_today: deck.review_limit_today, review_limit_today: deck.review_limit_today,
new_limit_today: deck.new_limit_today, new_limit_today: deck.new_limit_today,
desired_retention: None, desired_retention: deck.desired_retention.map(|v| v as f32 / 100.0),
} }
} }
} }
@ -367,6 +370,7 @@ impl From<Deck> for DeckSchema11 {
new_limit: norm.new_limit, new_limit: norm.new_limit,
review_limit_today: norm.review_limit_today, review_limit_today: norm.review_limit_today,
new_limit_today: norm.new_limit_today, new_limit_today: norm.new_limit_today,
desired_retention: norm.desired_retention.map(|v| (v * 100.0) as u32),
common: deck.into(), common: deck.into(),
}), }),
DeckKind::Filtered(ref filt) => DeckSchema11::Filtered(FilteredDeckSchema11 { DeckKind::Filtered(ref filt) => DeckSchema11::Filtered(FilteredDeckSchema11 {
@ -431,7 +435,8 @@ static RESERVED_DECK_KEYS: Set<&'static str> = phf_set! {
"browserCollapsed", "browserCollapsed",
"extendRev", "extendRev",
"id", "id",
"collapsed" "collapsed",
"desiredRetention",
}; };
impl From<&Deck> for DeckTodaySchema11 { impl From<&Deck> for DeckTodaySchema11 {

View file

@ -87,7 +87,7 @@ impl TryFrom<anki_proto::notes::AddNoteRequest> for AddNoteRequest {
} }
impl Collection { impl Collection {
pub fn add_note(&mut self, note: &mut Note, did: DeckId) -> Result<OpOutput<()>> { pub fn add_note(&mut self, note: &mut Note, did: DeckId) -> Result<OpOutput<usize>> {
self.transact(Op::AddNote, |col| col.add_note_inner(note, did)) self.transact(Op::AddNote, |col| col.add_note_inner(note, did))
} }
@ -372,7 +372,7 @@ impl Collection {
Ok(()) Ok(())
} }
pub(crate) fn add_note_inner(&mut self, note: &mut Note, did: DeckId) -> Result<()> { pub(crate) fn add_note_inner(&mut self, note: &mut Note, did: DeckId) -> Result<usize> {
let nt = self let nt = self
.get_notetype(note.notetype_id)? .get_notetype(note.notetype_id)?
.or_invalid("missing note type")?; .or_invalid("missing note type")?;
@ -383,10 +383,11 @@ impl Collection {
note.prepare_for_update(ctx.notetype, normalize_text)?; note.prepare_for_update(ctx.notetype, normalize_text)?;
note.set_modified(ctx.usn); note.set_modified(ctx.usn);
self.add_note_only_undoable(note)?; self.add_note_only_undoable(note)?;
self.generate_cards_for_new_note(&ctx, note, did)?; let count = self.generate_cards_for_new_note(&ctx, note, did)?;
self.set_last_deck_for_notetype(note.notetype_id, did)?; self.set_last_deck_for_notetype(note.notetype_id, did)?;
self.set_last_notetype_for_deck(did, note.notetype_id)?; self.set_last_notetype_for_deck(did, note.notetype_id)?;
self.set_current_notetype_id(note.notetype_id) self.set_current_notetype_id(note.notetype_id)?;
Ok(count)
} }
pub fn update_note(&mut self, note: &mut Note) -> Result<OpOutput<()>> { pub fn update_note(&mut self, note: &mut Note) -> Result<OpOutput<()>> {

View file

@ -215,7 +215,7 @@ impl Collection {
ctx: &CardGenContext<impl Deref<Target = Notetype>>, ctx: &CardGenContext<impl Deref<Target = Notetype>>,
note: &Note, note: &Note,
target_deck_id: DeckId, target_deck_id: DeckId,
) -> Result<()> { ) -> Result<usize> {
self.generate_cards_for_note( self.generate_cards_for_note(
ctx, ctx,
note, note,
@ -231,7 +231,8 @@ impl Collection {
note: &Note, note: &Note,
) -> Result<()> { ) -> Result<()> {
let existing = self.storage.existing_cards_for_note(note.id)?; let existing = self.storage.existing_cards_for_note(note.id)?;
self.generate_cards_for_note(ctx, note, &existing, ctx.last_deck, &mut Default::default()) self.generate_cards_for_note(ctx, note, &existing, ctx.last_deck, &mut Default::default())?;
Ok(())
} }
fn generate_cards_for_note( fn generate_cards_for_note(
@ -241,12 +242,13 @@ impl Collection {
existing: &[AlreadyGeneratedCardInfo], existing: &[AlreadyGeneratedCardInfo],
target_deck_id: Option<DeckId>, target_deck_id: Option<DeckId>,
cache: &mut CardGenCache, cache: &mut CardGenCache,
) -> Result<()> { ) -> Result<usize> {
let cards = ctx.new_cards_required(note, existing, true); let cards = ctx.new_cards_required(note, existing, true);
if cards.is_empty() { if cards.is_empty() {
return Ok(()); return Ok(0);
} }
self.add_generated_cards(note.id, &cards, target_deck_id, cache) self.add_generated_cards(note.id, &cards, target_deck_id, cache)?;
Ok(cards.len())
} }
pub(crate) fn generate_cards_for_notetype( pub(crate) fn generate_cards_for_notetype(