mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
Merge branch 'int_type' into main
This commit is contained in:
commit
07c6c4044c
35 changed files with 389 additions and 290 deletions
|
@ -5,7 +5,7 @@ from __future__ import annotations
|
||||||
|
|
||||||
import pprint
|
import pprint
|
||||||
import time
|
import time
|
||||||
from typing import List, Optional
|
from typing import List, NewType, Optional
|
||||||
|
|
||||||
import anki # pylint: disable=unused-import
|
import anki # pylint: disable=unused-import
|
||||||
import anki._backend.backend_pb2 as _pb
|
import anki._backend.backend_pb2 as _pb
|
||||||
|
@ -26,15 +26,24 @@ from anki.sound import AVTag
|
||||||
# - rev queue: integer day
|
# - rev queue: integer day
|
||||||
# - lrn queue: integer timestamp
|
# - lrn queue: integer timestamp
|
||||||
|
|
||||||
|
# types
|
||||||
|
CardID = NewType("CardID", int)
|
||||||
|
|
||||||
|
|
||||||
class Card:
|
class Card:
|
||||||
_note: Optional[Note]
|
_note: Optional[Note]
|
||||||
timerStarted: Optional[float]
|
timerStarted: Optional[float]
|
||||||
lastIvl: int
|
lastIvl: int
|
||||||
ord: int
|
ord: int
|
||||||
|
nid: anki.notes.NoteID
|
||||||
|
id: CardID
|
||||||
|
did: anki.decks.DeckID
|
||||||
|
odid: anki.decks.DeckID
|
||||||
|
queue: CardQueue
|
||||||
|
type: CardType
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, col: anki.collection.Collection, id: Optional[int] = None
|
self, col: anki.collection.Collection, id: Optional[CardID] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
self.col = col.weakref()
|
self.col = col.weakref()
|
||||||
self.timerStarted = None
|
self.timerStarted = None
|
||||||
|
@ -55,14 +64,14 @@ class Card:
|
||||||
def _load_from_backend_card(self, c: _pb.Card) -> None:
|
def _load_from_backend_card(self, c: _pb.Card) -> None:
|
||||||
self._render_output = None
|
self._render_output = None
|
||||||
self._note = None
|
self._note = None
|
||||||
self.id = c.id
|
self.id = CardID(c.id)
|
||||||
self.nid = c.note_id
|
self.nid = anki.notes.NoteID(c.note_id)
|
||||||
self.did = c.deck_id
|
self.did = anki.decks.DeckID(c.deck_id)
|
||||||
self.ord = c.template_idx
|
self.ord = c.template_idx
|
||||||
self.mod = c.mtime_secs
|
self.mod = c.mtime_secs
|
||||||
self.usn = c.usn
|
self.usn = c.usn
|
||||||
self.type = c.ctype
|
self.type = CardType(c.ctype)
|
||||||
self.queue = c.queue
|
self.queue = CardQueue(c.queue)
|
||||||
self.due = c.due
|
self.due = c.due
|
||||||
self.ivl = c.interval
|
self.ivl = c.interval
|
||||||
self.factor = c.ease_factor
|
self.factor = c.ease_factor
|
||||||
|
@ -70,7 +79,7 @@ class Card:
|
||||||
self.lapses = c.lapses
|
self.lapses = c.lapses
|
||||||
self.left = c.remaining_steps
|
self.left = c.remaining_steps
|
||||||
self.odue = c.original_due
|
self.odue = c.original_due
|
||||||
self.odid = c.original_deck_id
|
self.odid = anki.decks.DeckID(c.original_deck_id)
|
||||||
self.flags = c.flags
|
self.flags = c.flags
|
||||||
self.data = c.data
|
self.data = c.data
|
||||||
|
|
||||||
|
@ -158,21 +167,24 @@ class Card:
|
||||||
def startTimer(self) -> None:
|
def startTimer(self) -> None:
|
||||||
self.timerStarted = time.time()
|
self.timerStarted = time.time()
|
||||||
|
|
||||||
|
def current_deck_id(self) -> anki.decks.DeckID:
|
||||||
|
return anki.decks.DeckID(self.odid or self.did)
|
||||||
|
|
||||||
def timeLimit(self) -> int:
|
def timeLimit(self) -> int:
|
||||||
"Time limit for answering in milliseconds."
|
"Time limit for answering in milliseconds."
|
||||||
conf = self.col.decks.confForDid(self.odid or self.did)
|
conf = self.col.decks.confForDid(self.current_deck_id())
|
||||||
return conf["maxTaken"] * 1000
|
return conf["maxTaken"] * 1000
|
||||||
|
|
||||||
def shouldShowTimer(self) -> bool:
|
def shouldShowTimer(self) -> bool:
|
||||||
conf = self.col.decks.confForDid(self.odid or self.did)
|
conf = self.col.decks.confForDid(self.current_deck_id())
|
||||||
return conf["timer"]
|
return conf["timer"]
|
||||||
|
|
||||||
def replay_question_audio_on_answer_side(self) -> bool:
|
def replay_question_audio_on_answer_side(self) -> bool:
|
||||||
conf = self.col.decks.confForDid(self.odid or self.did)
|
conf = self.col.decks.confForDid(self.current_deck_id())
|
||||||
return conf.get("replayq", True)
|
return conf.get("replayq", True)
|
||||||
|
|
||||||
def autoplay(self) -> bool:
|
def autoplay(self) -> bool:
|
||||||
return self.col.decks.confForDid(self.odid or self.did)["autoplay"]
|
return self.col.decks.confForDid(self.current_deck_id())["autoplay"]
|
||||||
|
|
||||||
def timeTaken(self) -> int:
|
def timeTaken(self) -> int:
|
||||||
"Time taken to answer card, in integer MS."
|
"Time taken to answer card, in integer MS."
|
||||||
|
|
|
@ -34,16 +34,16 @@ from dataclasses import dataclass, field
|
||||||
import anki.latex
|
import anki.latex
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki._backend import RustBackend, Translations
|
from anki._backend import RustBackend, Translations
|
||||||
from anki.cards import Card
|
from anki.cards import Card, CardID
|
||||||
from anki.config import Config, ConfigManager
|
from anki.config import Config, ConfigManager
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.dbproxy import DBProxy
|
from anki.dbproxy import DBProxy
|
||||||
from anki.decks import DeckManager
|
from anki.decks import DeckID, DeckManager
|
||||||
from anki.errors import AnkiError, DBError
|
from anki.errors import AnkiError, DBError
|
||||||
from anki.lang import TR, FormatTimeSpan
|
from anki.lang import TR, FormatTimeSpan
|
||||||
from anki.media import MediaManager, media_paths_from_col_path
|
from anki.media import MediaManager, media_paths_from_col_path
|
||||||
from anki.models import ModelManager, NoteType
|
from anki.models import ModelManager, NoteType, NoteTypeID
|
||||||
from anki.notes import Note
|
from anki.notes import Note, NoteID
|
||||||
from anki.scheduler.v1 import Scheduler as V1Scheduler
|
from anki.scheduler.v1 import Scheduler as V1Scheduler
|
||||||
from anki.scheduler.v2 import Scheduler as V2Scheduler
|
from anki.scheduler.v2 import Scheduler as V2Scheduler
|
||||||
from anki.scheduler.v3 import Scheduler as V3TestScheduler
|
from anki.scheduler.v3 import Scheduler as V3TestScheduler
|
||||||
|
@ -317,7 +317,7 @@ class Collection:
|
||||||
# Object creation helpers
|
# Object creation helpers
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def get_card(self, id: int) -> Card:
|
def get_card(self, id: CardID) -> Card:
|
||||||
return Card(self, id)
|
return Card(self, id)
|
||||||
|
|
||||||
def update_card(self, card: Card) -> None:
|
def update_card(self, card: Card) -> None:
|
||||||
|
@ -325,7 +325,7 @@ class Collection:
|
||||||
Unlike card.flush(), this will invalidate any current checkpoint."""
|
Unlike card.flush(), this will invalidate any current checkpoint."""
|
||||||
self._backend.update_card(card=card._to_backend_card(), skip_undo_entry=False)
|
self._backend.update_card(card=card._to_backend_card(), skip_undo_entry=False)
|
||||||
|
|
||||||
def get_note(self, id: int) -> Note:
|
def get_note(self, id: NoteID) -> Note:
|
||||||
return Note(self, id=id)
|
return Note(self, id=id)
|
||||||
|
|
||||||
def update_note(self, note: Note) -> OpChanges:
|
def update_note(self, note: Note) -> OpChanges:
|
||||||
|
@ -356,7 +356,7 @@ class Collection:
|
||||||
# Deletion logging
|
# Deletion logging
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def _logRem(self, ids: List[int], type: int) -> None:
|
def _logRem(self, ids: List[Union[int, NoteID]], type: int) -> None:
|
||||||
self.db.executemany(
|
self.db.executemany(
|
||||||
"insert into graves values (%d, ?, %d)" % (self.usn(), type),
|
"insert into graves values (%d, ?, %d)" % (self.usn(), type),
|
||||||
([x] for x in ids),
|
([x] for x in ids),
|
||||||
|
@ -368,16 +368,16 @@ class Collection:
|
||||||
def new_note(self, notetype: NoteType) -> Note:
|
def new_note(self, notetype: NoteType) -> Note:
|
||||||
return Note(self, notetype)
|
return Note(self, notetype)
|
||||||
|
|
||||||
def add_note(self, note: Note, deck_id: int) -> OpChanges:
|
def add_note(self, note: Note, deck_id: DeckID) -> OpChanges:
|
||||||
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 = out.note_id
|
note.id = NoteID(out.note_id)
|
||||||
return out.changes
|
return out.changes
|
||||||
|
|
||||||
def remove_notes(self, note_ids: Sequence[int]) -> OpChanges:
|
def remove_notes(self, note_ids: Sequence[NoteID]) -> OpChanges:
|
||||||
hooks.notes_will_be_deleted(self, note_ids)
|
hooks.notes_will_be_deleted(self, note_ids)
|
||||||
return self._backend.remove_notes(note_ids=note_ids, card_ids=[])
|
return self._backend.remove_notes(note_ids=note_ids, card_ids=[])
|
||||||
|
|
||||||
def remove_notes_by_card(self, card_ids: List[int]) -> None:
|
def remove_notes_by_card(self, card_ids: List[CardID]) -> None:
|
||||||
if hooks.notes_will_be_deleted.count():
|
if hooks.notes_will_be_deleted.count():
|
||||||
nids = self.db.list(
|
nids = self.db.list(
|
||||||
f"select nid from cards where id in {ids2str(card_ids)}"
|
f"select nid from cards where id in {ids2str(card_ids)}"
|
||||||
|
@ -385,8 +385,8 @@ class Collection:
|
||||||
hooks.notes_will_be_deleted(self, nids)
|
hooks.notes_will_be_deleted(self, nids)
|
||||||
self._backend.remove_notes(note_ids=[], card_ids=card_ids)
|
self._backend.remove_notes(note_ids=[], card_ids=card_ids)
|
||||||
|
|
||||||
def card_ids_of_note(self, note_id: int) -> Sequence[int]:
|
def card_ids_of_note(self, note_id: NoteID) -> Sequence[CardID]:
|
||||||
return self._backend.cards_of_note(note_id)
|
return [CardID(id) for id in self._backend.cards_of_note(note_id)]
|
||||||
|
|
||||||
def defaults_for_adding(
|
def defaults_for_adding(
|
||||||
self, *, current_review_card: Optional[Card]
|
self, *, current_review_card: Optional[Card]
|
||||||
|
@ -396,24 +396,26 @@ class Collection:
|
||||||
or current notetype.
|
or current notetype.
|
||||||
"""
|
"""
|
||||||
if card := current_review_card:
|
if card := current_review_card:
|
||||||
home_deck = card.odid or card.did
|
home_deck = card.current_deck_id()
|
||||||
else:
|
else:
|
||||||
home_deck = 0
|
home_deck = DeckID(0)
|
||||||
|
|
||||||
return self._backend.defaults_for_adding(
|
return self._backend.defaults_for_adding(
|
||||||
home_deck_of_current_review_card=home_deck,
|
home_deck_of_current_review_card=home_deck,
|
||||||
)
|
)
|
||||||
|
|
||||||
def default_deck_for_notetype(self, notetype_id: int) -> Optional[int]:
|
def default_deck_for_notetype(self, notetype_id: NoteTypeID) -> Optional[DeckID]:
|
||||||
"""If 'change deck depending on notetype' is enabled in the preferences,
|
"""If 'change deck depending on notetype' is enabled in the preferences,
|
||||||
return the last deck used with the provided notetype, if any.."""
|
return the last deck used with the provided notetype, if any.."""
|
||||||
if self.get_config_bool(Config.Bool.ADDING_DEFAULTS_TO_CURRENT_DECK):
|
if self.get_config_bool(Config.Bool.ADDING_DEFAULTS_TO_CURRENT_DECK):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
DeckID(
|
||||||
self._backend.default_deck_for_notetype(
|
self._backend.default_deck_for_notetype(
|
||||||
ntid=notetype_id,
|
ntid=notetype_id,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
or None
|
or None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -430,10 +432,10 @@ class Collection:
|
||||||
self.add_note(note, note.model()["did"])
|
self.add_note(note, note.model()["did"])
|
||||||
return len(note.cards())
|
return len(note.cards())
|
||||||
|
|
||||||
def remNotes(self, ids: Sequence[int]) -> None:
|
def remNotes(self, ids: Sequence[NoteID]) -> None:
|
||||||
self.remove_notes(ids)
|
self.remove_notes(ids)
|
||||||
|
|
||||||
def _remNotes(self, ids: List[int]) -> None:
|
def _remNotes(self, ids: List[NoteID]) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Cards
|
# Cards
|
||||||
|
@ -445,11 +447,11 @@ class Collection:
|
||||||
def cardCount(self) -> Any:
|
def cardCount(self) -> Any:
|
||||||
return self.db.scalar("select count() from cards")
|
return self.db.scalar("select count() from cards")
|
||||||
|
|
||||||
def remove_cards_and_orphaned_notes(self, card_ids: Sequence[int]) -> None:
|
def remove_cards_and_orphaned_notes(self, card_ids: Sequence[CardID]) -> None:
|
||||||
"You probably want .remove_notes_by_card() instead."
|
"You probably want .remove_notes_by_card() instead."
|
||||||
self._backend.remove_cards(card_ids=card_ids)
|
self._backend.remove_cards(card_ids=card_ids)
|
||||||
|
|
||||||
def set_deck(self, card_ids: Sequence[int], deck_id: int) -> OpChanges:
|
def set_deck(self, card_ids: Sequence[CardID], deck_id: int) -> OpChanges:
|
||||||
return self._backend.set_deck(card_ids=card_ids, deck_id=deck_id)
|
return self._backend.set_deck(card_ids=card_ids, deck_id=deck_id)
|
||||||
|
|
||||||
def get_empty_cards(self) -> EmptyCardsReport:
|
def get_empty_cards(self) -> EmptyCardsReport:
|
||||||
|
@ -457,10 +459,10 @@ class Collection:
|
||||||
|
|
||||||
# legacy
|
# legacy
|
||||||
|
|
||||||
def remCards(self, ids: List[int], notes: bool = True) -> None:
|
def remCards(self, ids: List[CardID], notes: bool = True) -> None:
|
||||||
self.remove_cards_and_orphaned_notes(ids)
|
self.remove_cards_and_orphaned_notes(ids)
|
||||||
|
|
||||||
def emptyCids(self) -> List[int]:
|
def emptyCids(self) -> List[CardID]:
|
||||||
print("emptyCids() will go away")
|
print("emptyCids() will go away")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -468,7 +470,7 @@ class Collection:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def after_note_updates(
|
def after_note_updates(
|
||||||
self, nids: List[int], mark_modified: bool, generate_cards: bool = True
|
self, nids: List[NoteID], mark_modified: bool, generate_cards: bool = True
|
||||||
) -> None:
|
) -> None:
|
||||||
self._backend.after_note_updates(
|
self._backend.after_note_updates(
|
||||||
nids=nids, generate_cards=generate_cards, mark_notes_modified=mark_modified
|
nids=nids, generate_cards=generate_cards, mark_notes_modified=mark_modified
|
||||||
|
@ -476,11 +478,11 @@ class Collection:
|
||||||
|
|
||||||
# legacy
|
# legacy
|
||||||
|
|
||||||
def updateFieldCache(self, nids: List[int]) -> None:
|
def updateFieldCache(self, nids: List[NoteID]) -> None:
|
||||||
self.after_note_updates(nids, mark_modified=False, generate_cards=False)
|
self.after_note_updates(nids, mark_modified=False, generate_cards=False)
|
||||||
|
|
||||||
# this also updates field cache
|
# this also updates field cache
|
||||||
def genCards(self, nids: List[int]) -> List[int]:
|
def genCards(self, nids: List[NoteID]) -> List[int]:
|
||||||
self.after_note_updates(nids, mark_modified=False, generate_cards=True)
|
self.after_note_updates(nids, mark_modified=False, generate_cards=True)
|
||||||
# previously returned empty cards, no longer does
|
# previously returned empty cards, no longer does
|
||||||
return []
|
return []
|
||||||
|
@ -493,7 +495,7 @@ class Collection:
|
||||||
query: str,
|
query: str,
|
||||||
order: Union[bool, str, BuiltinSort.Kind.V] = False,
|
order: Union[bool, str, BuiltinSort.Kind.V] = False,
|
||||||
reverse: bool = False,
|
reverse: bool = False,
|
||||||
) -> Sequence[int]:
|
) -> Sequence[CardID]:
|
||||||
"""Return card ids matching the provided search.
|
"""Return card ids matching the provided search.
|
||||||
|
|
||||||
To programmatically construct a search string, see .build_search_string().
|
To programmatically construct a search string, see .build_search_string().
|
||||||
|
@ -523,9 +525,11 @@ class Collection:
|
||||||
mode = _pb.SortOrder(
|
mode = _pb.SortOrder(
|
||||||
builtin=_pb.SortOrder.Builtin(kind=order, reverse=reverse)
|
builtin=_pb.SortOrder.Builtin(kind=order, reverse=reverse)
|
||||||
)
|
)
|
||||||
return self._backend.search_cards(search=query, order=mode)
|
return [
|
||||||
|
CardID(id) for id in self._backend.search_cards(search=query, order=mode)
|
||||||
|
]
|
||||||
|
|
||||||
def find_notes(self, *terms: Union[str, SearchNode]) -> Sequence[int]:
|
def find_notes(self, *terms: Union[str, SearchNode]) -> Sequence[NoteID]:
|
||||||
"""Return note ids matching the provided search or searches.
|
"""Return note ids matching the provided search or searches.
|
||||||
|
|
||||||
If more than one search is provided, they will be ANDed together.
|
If more than one search is provided, they will be ANDed together.
|
||||||
|
@ -536,12 +540,15 @@ class Collection:
|
||||||
Eg: col.find_notes(SearchNode(deck="test"), "foo") will return notes
|
Eg: col.find_notes(SearchNode(deck="test"), "foo") will return notes
|
||||||
that have a card in deck called "test", and have the text "foo".
|
that have a card in deck called "test", and have the text "foo".
|
||||||
"""
|
"""
|
||||||
return self._backend.search_notes(self.build_search_string(*terms))
|
return [
|
||||||
|
NoteID(did)
|
||||||
|
for did in self._backend.search_notes(self.build_search_string(*terms))
|
||||||
|
]
|
||||||
|
|
||||||
def find_and_replace(
|
def find_and_replace(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
note_ids: Sequence[int],
|
note_ids: Sequence[NoteID],
|
||||||
search: str,
|
search: str,
|
||||||
replacement: str,
|
replacement: str,
|
||||||
regex: bool = False,
|
regex: bool = False,
|
||||||
|
@ -569,7 +576,7 @@ class Collection:
|
||||||
dupes = []
|
dupes = []
|
||||||
fields: Dict[int, int] = {}
|
fields: Dict[int, int] = {}
|
||||||
|
|
||||||
def ordForMid(mid: int) -> int:
|
def ordForMid(mid: NoteTypeID) -> int:
|
||||||
if mid not in fields:
|
if mid not in fields:
|
||||||
model = self.models.get(mid)
|
model = self.models.get(mid)
|
||||||
for c, f in enumerate(model["flds"]):
|
for c, f in enumerate(model["flds"]):
|
||||||
|
@ -735,7 +742,7 @@ class Collection:
|
||||||
|
|
||||||
return CollectionStats(self)
|
return CollectionStats(self)
|
||||||
|
|
||||||
def card_stats(self, card_id: int, include_revlog: bool) -> str:
|
def card_stats(self, card_id: CardID, include_revlog: bool) -> str:
|
||||||
import anki.stats as st
|
import anki.stats as st
|
||||||
|
|
||||||
if include_revlog:
|
if include_revlog:
|
||||||
|
@ -1028,7 +1035,7 @@ table.review-log {{ {revlog_style} }}
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def set_user_flag_for_cards(self, flag: int, cids: Sequence[int]) -> OpChanges:
|
def set_user_flag_for_cards(self, flag: int, cids: Sequence[CardID]) -> OpChanges:
|
||||||
return self._backend.set_flag(card_ids=cids, flag=flag)
|
return self._backend.set_flag(card_ids=cids, flag=flag)
|
||||||
|
|
||||||
def set_wants_abort(self) -> None:
|
def set_wants_abort(self) -> None:
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, NewType, Optional
|
||||||
|
|
||||||
import anki
|
import anki
|
||||||
from anki.lang import TR
|
from anki.lang import TR
|
||||||
|
@ -18,20 +18,22 @@ NEW_CARDS_RANDOM = 0
|
||||||
NEW_CARDS_DUE = 1
|
NEW_CARDS_DUE = 1
|
||||||
|
|
||||||
# Queue types
|
# Queue types
|
||||||
QUEUE_TYPE_MANUALLY_BURIED = -3
|
CardQueue = NewType("CardQueue", int)
|
||||||
QUEUE_TYPE_SIBLING_BURIED = -2
|
QUEUE_TYPE_MANUALLY_BURIED = CardQueue(-3)
|
||||||
QUEUE_TYPE_SUSPENDED = -1
|
QUEUE_TYPE_SIBLING_BURIED = CardQueue(-2)
|
||||||
QUEUE_TYPE_NEW = 0
|
QUEUE_TYPE_SUSPENDED = CardQueue(-1)
|
||||||
QUEUE_TYPE_LRN = 1
|
QUEUE_TYPE_NEW = CardQueue(0)
|
||||||
QUEUE_TYPE_REV = 2
|
QUEUE_TYPE_LRN = CardQueue(1)
|
||||||
QUEUE_TYPE_DAY_LEARN_RELEARN = 3
|
QUEUE_TYPE_REV = CardQueue(2)
|
||||||
QUEUE_TYPE_PREVIEW = 4
|
QUEUE_TYPE_DAY_LEARN_RELEARN = CardQueue(3)
|
||||||
|
QUEUE_TYPE_PREVIEW = CardQueue(4)
|
||||||
|
|
||||||
# Card types
|
# Card types
|
||||||
CARD_TYPE_NEW = 0
|
CardType = NewType("CardType", int)
|
||||||
CARD_TYPE_LRN = 1
|
CARD_TYPE_NEW = CardType(0)
|
||||||
CARD_TYPE_REV = 2
|
CARD_TYPE_LRN = CardType(1)
|
||||||
CARD_TYPE_RELEARNING = 3
|
CARD_TYPE_REV = CardType(2)
|
||||||
|
CARD_TYPE_RELEARNING = CardType(3)
|
||||||
|
|
||||||
# removal types
|
# removal types
|
||||||
REM_CARD = 0
|
REM_CARD = 0
|
||||||
|
|
|
@ -11,6 +11,7 @@ from typing import Any, Dict, Iterable, List, NewType, Optional, Sequence, Tuple
|
||||||
|
|
||||||
import anki # pylint: disable=unused-import
|
import anki # pylint: disable=unused-import
|
||||||
import anki._backend.backend_pb2 as _pb
|
import anki._backend.backend_pb2 as _pb
|
||||||
|
from anki.cards import CardID
|
||||||
from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithID
|
from anki.collection import OpChanges, OpChangesWithCount, OpChangesWithID
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.errors import NotFoundError
|
from anki.errors import NotFoundError
|
||||||
|
@ -30,6 +31,10 @@ DeckDict = Dict[str, Any]
|
||||||
DeckConfigDict = Dict[str, Any]
|
DeckConfigDict = Dict[str, Any]
|
||||||
|
|
||||||
DeckID = NewType("DeckID", int)
|
DeckID = NewType("DeckID", int)
|
||||||
|
DeckConfID = NewType("DeckConfID", int)
|
||||||
|
|
||||||
|
DEFAULT_DECK_ID = DeckID(1)
|
||||||
|
DEFAULT_DECK_CONF_ID = DeckConfID(1)
|
||||||
|
|
||||||
|
|
||||||
class DecksDictProxy:
|
class DecksDictProxy:
|
||||||
|
@ -42,7 +47,7 @@ class DecksDictProxy:
|
||||||
|
|
||||||
def __getitem__(self, item: Any) -> Any:
|
def __getitem__(self, item: Any) -> Any:
|
||||||
self._warn()
|
self._warn()
|
||||||
return self._col.decks.get(int(item))
|
return self._col.decks.get(DeckID(int(item)))
|
||||||
|
|
||||||
def __setitem__(self, key: Any, val: Any) -> None:
|
def __setitem__(self, key: Any, val: Any) -> None:
|
||||||
self._warn()
|
self._warn()
|
||||||
|
@ -120,8 +125,8 @@ class DeckManager:
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
create: bool = True,
|
create: bool = True,
|
||||||
type: int = 0,
|
type: DeckConfID = DeckConfID(0),
|
||||||
) -> Optional[int]:
|
) -> Optional[DeckID]:
|
||||||
"Add a deck with NAME. Reuse deck if already exists. Return id as int."
|
"Add a deck with NAME. Reuse deck if already exists. Return id as int."
|
||||||
id = self.id_for_name(name)
|
id = self.id_for_name(name)
|
||||||
if id:
|
if id:
|
||||||
|
@ -132,17 +137,17 @@ class DeckManager:
|
||||||
deck = self.new_deck_legacy(bool(type))
|
deck = self.new_deck_legacy(bool(type))
|
||||||
deck["name"] = name
|
deck["name"] = name
|
||||||
out = self.add_deck_legacy(deck)
|
out = self.add_deck_legacy(deck)
|
||||||
return out.id
|
return DeckID(out.id)
|
||||||
|
|
||||||
@legacy_func(sub="remove")
|
@legacy_func(sub="remove")
|
||||||
def rem(self, did: int, cardsToo: bool = True, childrenToo: bool = True) -> None:
|
def rem(self, did: DeckID, cardsToo: bool = True, childrenToo: bool = True) -> None:
|
||||||
"Remove the deck. If cardsToo, delete any cards inside."
|
"Remove the deck. If cardsToo, delete any cards inside."
|
||||||
if isinstance(did, str):
|
if isinstance(did, str):
|
||||||
did = int(did)
|
did = int(did)
|
||||||
assert cardsToo and childrenToo
|
assert cardsToo and childrenToo
|
||||||
self.remove([did])
|
self.remove([did])
|
||||||
|
|
||||||
def remove(self, dids: Sequence[int]) -> OpChangesWithCount:
|
def remove(self, dids: Sequence[DeckID]) -> OpChangesWithCount:
|
||||||
return self.col._backend.remove_decks(dids)
|
return self.col._backend.remove_decks(dids)
|
||||||
|
|
||||||
def all_names_and_ids(
|
def all_names_and_ids(
|
||||||
|
@ -153,20 +158,20 @@ class DeckManager:
|
||||||
skip_empty_default=skip_empty_default, include_filtered=include_filtered
|
skip_empty_default=skip_empty_default, include_filtered=include_filtered
|
||||||
)
|
)
|
||||||
|
|
||||||
def id_for_name(self, name: str) -> Optional[int]:
|
def id_for_name(self, name: str) -> Optional[DeckID]:
|
||||||
try:
|
try:
|
||||||
return self.col._backend.get_deck_id_by_name(name)
|
return DeckID(self.col._backend.get_deck_id_by_name(name))
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_legacy(self, did: int) -> Optional[DeckDict]:
|
def get_legacy(self, did: DeckID) -> Optional[DeckDict]:
|
||||||
try:
|
try:
|
||||||
return from_json_bytes(self.col._backend.get_deck_legacy(did))
|
return from_json_bytes(self.col._backend.get_deck_legacy(did))
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def have(self, id: int) -> bool:
|
def have(self, id: DeckID) -> bool:
|
||||||
return not self.get_legacy(int(id))
|
return not self.get_legacy(id)
|
||||||
|
|
||||||
def get_all_legacy(self) -> List[DeckDict]:
|
def get_all_legacy(self) -> List[DeckDict]:
|
||||||
return list(from_json_bytes(self.col._backend.get_all_decks_legacy()).values())
|
return list(from_json_bytes(self.col._backend.get_all_decks_legacy()).values())
|
||||||
|
@ -188,7 +193,7 @@ class DeckManager:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def find_deck_in_tree(
|
def find_deck_in_tree(
|
||||||
cls, node: DeckTreeNode, deck_id: int
|
cls, node: DeckTreeNode, deck_id: DeckID
|
||||||
) -> Optional[DeckTreeNode]:
|
) -> Optional[DeckTreeNode]:
|
||||||
if node.deck_id == deck_id:
|
if node.deck_id == deck_id:
|
||||||
return node
|
return node
|
||||||
|
@ -215,12 +220,12 @@ class DeckManager:
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
def collapse(self, did: int) -> None:
|
def collapse(self, did: DeckID) -> None:
|
||||||
deck = self.get(did)
|
deck = self.get(did)
|
||||||
deck["collapsed"] = not deck["collapsed"]
|
deck["collapsed"] = not deck["collapsed"]
|
||||||
self.save(deck)
|
self.save(deck)
|
||||||
|
|
||||||
def collapseBrowser(self, did: int) -> None:
|
def collapseBrowser(self, did: DeckID) -> None:
|
||||||
deck = self.get(did)
|
deck = self.get(did)
|
||||||
collapsed = deck.get("browserCollapsed", False)
|
collapsed = deck.get("browserCollapsed", False)
|
||||||
deck["browserCollapsed"] = not collapsed
|
deck["browserCollapsed"] = not collapsed
|
||||||
|
@ -230,7 +235,7 @@ class DeckManager:
|
||||||
return len(self.all_names_and_ids())
|
return len(self.all_names_and_ids())
|
||||||
|
|
||||||
def card_count(
|
def card_count(
|
||||||
self, dids: Union[int, Iterable[int]], include_subdecks: bool
|
self, dids: Union[DeckID, Iterable[DeckID]], include_subdecks: bool
|
||||||
) -> Any:
|
) -> Any:
|
||||||
if isinstance(dids, int):
|
if isinstance(dids, int):
|
||||||
dids = {dids}
|
dids = {dids}
|
||||||
|
@ -244,18 +249,18 @@ class DeckManager:
|
||||||
)
|
)
|
||||||
return count
|
return count
|
||||||
|
|
||||||
def get(self, did: Union[int, str], default: bool = True) -> Optional[DeckDict]:
|
def get(self, did: Union[DeckID, str], default: bool = True) -> Optional[DeckDict]:
|
||||||
if not did:
|
if not did:
|
||||||
if default:
|
if default:
|
||||||
return self.get_legacy(1)
|
return self.get_legacy(DEFAULT_DECK_ID)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
id = int(did)
|
id = DeckID(int(did))
|
||||||
deck = self.get_legacy(id)
|
deck = self.get_legacy(id)
|
||||||
if deck:
|
if deck:
|
||||||
return deck
|
return deck
|
||||||
elif default:
|
elif default:
|
||||||
return self.get_legacy(1)
|
return self.get_legacy(DEFAULT_DECK_ID)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -294,7 +299,9 @@ class DeckManager:
|
||||||
|
|
||||||
# legacy
|
# legacy
|
||||||
def renameForDragAndDrop(
|
def renameForDragAndDrop(
|
||||||
self, draggedDeckDid: Union[int, str], ontoDeckDid: Optional[Union[int, str]]
|
self,
|
||||||
|
draggedDeckDid: Union[DeckID, str],
|
||||||
|
ontoDeckDid: Optional[Union[DeckID, str]],
|
||||||
) -> None:
|
) -> None:
|
||||||
if not ontoDeckDid:
|
if not ontoDeckDid:
|
||||||
onto = 0
|
onto = 0
|
||||||
|
@ -309,21 +316,21 @@ class DeckManager:
|
||||||
"A list of all deck config."
|
"A list of all deck config."
|
||||||
return list(from_json_bytes(self.col._backend.all_deck_config_legacy()))
|
return list(from_json_bytes(self.col._backend.all_deck_config_legacy()))
|
||||||
|
|
||||||
def confForDid(self, did: int) -> DeckConfigDict:
|
def confForDid(self, did: DeckID) -> DeckConfigDict:
|
||||||
deck = self.get(did, default=False)
|
deck = self.get(did, default=False)
|
||||||
assert deck
|
assert deck
|
||||||
if "conf" in deck:
|
if "conf" in deck:
|
||||||
dcid = int(deck["conf"]) # may be a string
|
dcid = DeckConfID(int(deck["conf"])) # may be a string
|
||||||
conf = self.get_config(dcid)
|
conf = self.get_config(dcid)
|
||||||
if not conf:
|
if not conf:
|
||||||
# fall back on default
|
# fall back on default
|
||||||
conf = self.get_config(1)
|
conf = self.get_config(DEFAULT_DECK_CONF_ID)
|
||||||
conf["dyn"] = False
|
conf["dyn"] = False
|
||||||
return conf
|
return conf
|
||||||
# dynamic decks have embedded conf
|
# dynamic decks have embedded conf
|
||||||
return deck
|
return deck
|
||||||
|
|
||||||
def get_config(self, conf_id: int) -> Optional[DeckConfigDict]:
|
def get_config(self, conf_id: DeckConfID) -> Optional[DeckConfigDict]:
|
||||||
try:
|
try:
|
||||||
return from_json_bytes(self.col._backend.get_deck_config_legacy(conf_id))
|
return from_json_bytes(self.col._backend.get_deck_config_legacy(conf_id))
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
|
@ -348,10 +355,10 @@ class DeckManager:
|
||||||
|
|
||||||
def add_config_returning_id(
|
def add_config_returning_id(
|
||||||
self, name: str, clone_from: Optional[DeckConfigDict] = None
|
self, name: str, clone_from: Optional[DeckConfigDict] = None
|
||||||
) -> int:
|
) -> DeckConfID:
|
||||||
return self.add_config(name, clone_from)["id"]
|
return self.add_config(name, clone_from)["id"]
|
||||||
|
|
||||||
def remove_config(self, id: int) -> None:
|
def remove_config(self, id: DeckConfID) -> None:
|
||||||
"Remove a configuration and update all decks using it."
|
"Remove a configuration and update all decks using it."
|
||||||
self.col.modSchema(check=True)
|
self.col.modSchema(check=True)
|
||||||
for g in self.all():
|
for g in self.all():
|
||||||
|
@ -363,11 +370,11 @@ class DeckManager:
|
||||||
self.save(g)
|
self.save(g)
|
||||||
self.col._backend.remove_deck_config(id)
|
self.col._backend.remove_deck_config(id)
|
||||||
|
|
||||||
def setConf(self, grp: DeckConfigDict, id: int) -> None:
|
def setConf(self, grp: DeckConfigDict, id: DeckConfID) -> None:
|
||||||
grp["conf"] = id
|
grp["conf"] = id
|
||||||
self.save(grp)
|
self.save(grp)
|
||||||
|
|
||||||
def didsForConf(self, conf: DeckConfigDict) -> List[int]:
|
def didsForConf(self, conf: DeckConfigDict) -> List[DeckID]:
|
||||||
dids = []
|
dids = []
|
||||||
for deck in self.all():
|
for deck in self.all():
|
||||||
if "conf" in deck and deck["conf"] == conf["id"]:
|
if "conf" in deck and deck["conf"] == conf["id"]:
|
||||||
|
@ -394,19 +401,19 @@ class DeckManager:
|
||||||
# Deck utils
|
# Deck utils
|
||||||
#############################################################
|
#############################################################
|
||||||
|
|
||||||
def name(self, did: int, default: bool = False) -> str:
|
def name(self, did: DeckID, default: bool = False) -> str:
|
||||||
deck = self.get(did, default=default)
|
deck = self.get(did, default=default)
|
||||||
if deck:
|
if deck:
|
||||||
return deck["name"]
|
return deck["name"]
|
||||||
return self.col.tr(TR.DECKS_NO_DECK)
|
return self.col.tr(TR.DECKS_NO_DECK)
|
||||||
|
|
||||||
def name_if_exists(self, did: int) -> Optional[str]:
|
def name_if_exists(self, did: DeckID) -> Optional[str]:
|
||||||
deck = self.get(did, default=False)
|
deck = self.get(did, default=False)
|
||||||
if deck:
|
if deck:
|
||||||
return deck["name"]
|
return deck["name"]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def setDeck(self, cids: List[int], did: int) -> None:
|
def setDeck(self, cids: List[CardID], did: DeckID) -> None:
|
||||||
self.col.db.execute(
|
self.col.db.execute(
|
||||||
f"update cards set did=?,usn=?,mod=? where id in {ids2str(cids)}",
|
f"update cards set did=?,usn=?,mod=? where id in {ids2str(cids)}",
|
||||||
did,
|
did,
|
||||||
|
@ -414,7 +421,7 @@ class DeckManager:
|
||||||
intTime(),
|
intTime(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def cids(self, did: int, children: bool = False) -> List[int]:
|
def cids(self, did: DeckID, children: bool = False) -> List[CardID]:
|
||||||
if not children:
|
if not children:
|
||||||
return self.col.db.list("select id from cards where did=?", did)
|
return self.col.db.list("select id from cards where did=?", did)
|
||||||
dids = [did]
|
dids = [did]
|
||||||
|
@ -422,13 +429,13 @@ class DeckManager:
|
||||||
dids.append(id)
|
dids.append(id)
|
||||||
return self.col.db.list(f"select id from cards where did in {ids2str(dids)}")
|
return self.col.db.list(f"select id from cards where did in {ids2str(dids)}")
|
||||||
|
|
||||||
def for_card_ids(self, cids: List[int]) -> List[int]:
|
def for_card_ids(self, cids: List[CardID]) -> List[DeckID]:
|
||||||
return self.col.db.list(f"select did from cards where id in {ids2str(cids)}")
|
return self.col.db.list(f"select did from cards where id in {ids2str(cids)}")
|
||||||
|
|
||||||
# Deck selection
|
# Deck selection
|
||||||
#############################################################
|
#############################################################
|
||||||
|
|
||||||
def active(self) -> List[int]:
|
def active(self) -> List[DeckID]:
|
||||||
"The currrently active dids."
|
"The currrently active dids."
|
||||||
return self.col.get_config("activeDecks", [1])
|
return self.col.get_config("activeDecks", [1])
|
||||||
|
|
||||||
|
@ -439,10 +446,10 @@ class DeckManager:
|
||||||
def current(self) -> DeckDict:
|
def current(self) -> DeckDict:
|
||||||
return self.get(self.selected())
|
return self.get(self.selected())
|
||||||
|
|
||||||
def select(self, did: int) -> None:
|
def select(self, did: DeckID) -> None:
|
||||||
"Select a new branch."
|
"Select a new branch."
|
||||||
# make sure arg is an int
|
# make sure arg is an int; legacy callers may be passing in a string
|
||||||
did = int(did)
|
did = DeckID(did)
|
||||||
current = self.selected()
|
current = self.selected()
|
||||||
active = self.deck_and_child_ids(did)
|
active = self.deck_and_child_ids(did)
|
||||||
if current != did or active != self.active():
|
if current != did or active != self.active():
|
||||||
|
@ -483,29 +490,31 @@ class DeckManager:
|
||||||
def key(cls, deck: DeckDict) -> List[str]:
|
def key(cls, deck: DeckDict) -> List[str]:
|
||||||
return cls.path(deck["name"])
|
return cls.path(deck["name"])
|
||||||
|
|
||||||
def children(self, did: int) -> List[Tuple[str, int]]:
|
def children(self, did: DeckID) -> List[Tuple[str, DeckID]]:
|
||||||
"All children of did, as (name, id)."
|
"All children of did, as (name, id)."
|
||||||
name = self.get(did)["name"]
|
name = self.get(did)["name"]
|
||||||
actv = []
|
actv = []
|
||||||
for g in self.all_names_and_ids():
|
for g in self.all_names_and_ids():
|
||||||
if g.name.startswith(f"{name}::"):
|
if g.name.startswith(f"{name}::"):
|
||||||
actv.append((g.name, g.id))
|
actv.append((g.name, DeckID(g.id)))
|
||||||
return actv
|
return actv
|
||||||
|
|
||||||
def child_ids(self, parent_name: str) -> Iterable[int]:
|
def child_ids(self, parent_name: str) -> Iterable[DeckID]:
|
||||||
prefix = f"{parent_name}::"
|
prefix = f"{parent_name}::"
|
||||||
return (d.id for d in self.all_names_and_ids() if d.name.startswith(prefix))
|
return (
|
||||||
|
DeckID(d.id) for d in self.all_names_and_ids() if d.name.startswith(prefix)
|
||||||
|
)
|
||||||
|
|
||||||
def deck_and_child_ids(self, deck_id: int) -> List[int]:
|
def deck_and_child_ids(self, deck_id: DeckID) -> List[DeckID]:
|
||||||
parent_name = self.get_legacy(deck_id)["name"]
|
parent_name = self.get_legacy(deck_id)["name"]
|
||||||
out = [deck_id]
|
out = [deck_id]
|
||||||
out.extend(self.child_ids(parent_name))
|
out.extend(self.child_ids(parent_name))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
childMapNode = Dict[int, Any]
|
childMapNode = Dict[DeckID, Any]
|
||||||
# Change to Dict[int, "DeckManager.childMapNode"] when MyPy allow recursive type
|
# Change to Dict[int, "DeckManager.childMapNode"] when MyPy allow recursive type
|
||||||
|
|
||||||
def childDids(self, did: int, childMap: DeckManager.childMapNode) -> List:
|
def childDids(self, did: DeckID, childMap: DeckManager.childMapNode) -> List:
|
||||||
def gather(node: DeckManager.childMapNode, arr: List) -> None:
|
def gather(node: DeckManager.childMapNode, arr: List) -> None:
|
||||||
for did, child in node.items():
|
for did, child in node.items():
|
||||||
arr.append(did)
|
arr.append(did)
|
||||||
|
@ -533,7 +542,7 @@ class DeckManager:
|
||||||
return childMap
|
return childMap
|
||||||
|
|
||||||
def parents(
|
def parents(
|
||||||
self, did: int, nameMap: Optional[Dict[str, DeckDict]] = None
|
self, did: DeckID, nameMap: Optional[Dict[str, DeckDict]] = None
|
||||||
) -> List[DeckDict]:
|
) -> List[DeckDict]:
|
||||||
"All parents of did."
|
"All parents of did."
|
||||||
# get parent and grandparent names
|
# get parent and grandparent names
|
||||||
|
@ -575,14 +584,14 @@ class DeckManager:
|
||||||
# Dynamic decks
|
# Dynamic decks
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def new_filtered(self, name: str) -> int:
|
def new_filtered(self, name: str) -> DeckID:
|
||||||
"Return a new dynamic deck and set it as the current deck."
|
"Return a new dynamic deck and set it as the current deck."
|
||||||
did = self.id(name, type=1)
|
did = self.id(name, type=DEFAULT_DECK_CONF_ID)
|
||||||
self.select(did)
|
self.select(did)
|
||||||
return did
|
return did
|
||||||
|
|
||||||
# 1 for dyn, 0 for standard
|
# 1 for dyn, 0 for standard
|
||||||
def isDyn(self, did: Union[int, str]) -> int:
|
def isDyn(self, did: Union[DeckID, str]) -> int:
|
||||||
return self.get(did)["dyn"]
|
return self.get(did)["dyn"]
|
||||||
|
|
||||||
# legacy
|
# legacy
|
||||||
|
|
|
@ -12,7 +12,9 @@ from typing import Any, Dict, List, Optional, Tuple, Union
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
|
from anki.cards import CardID
|
||||||
from anki.collection import Collection
|
from anki.collection import Collection
|
||||||
|
from anki.decks import DeckID
|
||||||
from anki.lang import TR
|
from anki.lang import TR
|
||||||
from anki.utils import ids2str, namedtmp, splitFields, stripHTML
|
from anki.utils import ids2str, namedtmp, splitFields, stripHTML
|
||||||
|
|
||||||
|
@ -27,8 +29,8 @@ class Exporter:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
col: Collection,
|
col: Collection,
|
||||||
did: Optional[int] = None,
|
did: Optional[DeckID] = None,
|
||||||
cids: Optional[List[int]] = None,
|
cids: Optional[List[CardID]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.col = col.weakref()
|
self.col = col.weakref()
|
||||||
self.did = did
|
self.did = did
|
||||||
|
@ -184,7 +186,7 @@ class AnkiExporter(Exporter):
|
||||||
def key(col: Collection) -> str:
|
def key(col: Collection) -> str:
|
||||||
return col.tr(TR.EXPORTING_ANKI_20_DECK)
|
return col.tr(TR.EXPORTING_ANKI_20_DECK)
|
||||||
|
|
||||||
def deckIds(self) -> List[int]:
|
def deckIds(self) -> List[DeckID]:
|
||||||
if self.cids:
|
if self.cids:
|
||||||
return self.col.decks.for_card_ids(self.cids)
|
return self.col.decks.for_card_ids(self.cids)
|
||||||
elif self.did:
|
elif self.did:
|
||||||
|
|
|
@ -6,6 +6,7 @@ from __future__ import annotations
|
||||||
from typing import TYPE_CHECKING, Optional, Set
|
from typing import TYPE_CHECKING, Optional, Set
|
||||||
|
|
||||||
from anki.hooks import *
|
from anki.hooks import *
|
||||||
|
from anki.notes import NoteID
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from anki.collection import Collection
|
from anki.collection import Collection
|
||||||
|
@ -29,7 +30,7 @@ class Finder:
|
||||||
|
|
||||||
def findReplace(
|
def findReplace(
|
||||||
col: Collection,
|
col: Collection,
|
||||||
nids: List[int],
|
nids: List[NoteID],
|
||||||
src: str,
|
src: str,
|
||||||
dst: str,
|
dst: str,
|
||||||
regex: bool = False,
|
regex: bool = False,
|
||||||
|
@ -48,7 +49,7 @@ def findReplace(
|
||||||
).count
|
).count
|
||||||
|
|
||||||
|
|
||||||
def fieldNamesForNotes(col: Collection, nids: List[int]) -> List[str]:
|
def fieldNamesForNotes(col: Collection, nids: List[NoteID]) -> List[str]:
|
||||||
return list(col.field_names_for_note_ids(nids))
|
return list(col.field_names_for_note_ids(nids))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,14 @@ import os
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
|
from anki.cards import CardID
|
||||||
from anki.collection import Collection
|
from anki.collection import Collection
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.decks import DeckManager
|
from anki.decks import DeckID, DeckManager
|
||||||
from anki.importing.base import Importer
|
from anki.importing.base import Importer
|
||||||
from anki.lang import TR
|
from anki.lang import TR
|
||||||
|
from anki.models import NoteTypeID
|
||||||
|
from anki.notes import NoteID
|
||||||
from anki.utils import intTime, joinFields, splitFields, stripHTMLMedia
|
from anki.utils import intTime, joinFields, splitFields, stripHTMLMedia
|
||||||
|
|
||||||
GUID = 1
|
GUID = 1
|
||||||
|
@ -29,7 +32,7 @@ class Anki2Importer(Importer):
|
||||||
super().__init__(col, file)
|
super().__init__(col, file)
|
||||||
|
|
||||||
# set later, defined here for typechecking
|
# set later, defined here for typechecking
|
||||||
self._decks: Dict[int, int] = {}
|
self._decks: Dict[DeckID, DeckID] = {}
|
||||||
self.source_needs_upgrade = False
|
self.source_needs_upgrade = False
|
||||||
|
|
||||||
def run(self, media: None = None) -> None:
|
def run(self, media: None = None) -> None:
|
||||||
|
@ -79,7 +82,7 @@ class Anki2Importer(Importer):
|
||||||
|
|
||||||
def _importNotes(self) -> None:
|
def _importNotes(self) -> None:
|
||||||
# build guid -> (id,mod,mid) hash & map of existing note ids
|
# build guid -> (id,mod,mid) hash & map of existing note ids
|
||||||
self._notes: Dict[str, Tuple[int, int, int]] = {}
|
self._notes: Dict[str, Tuple[NoteID, int, NoteTypeID]] = {}
|
||||||
existing = {}
|
existing = {}
|
||||||
for id, guid, mod, mid in self.dst.db.execute(
|
for id, guid, mod, mid in self.dst.db.execute(
|
||||||
"select id, guid, mod, mid from notes"
|
"select id, guid, mod, mid from notes"
|
||||||
|
@ -215,9 +218,9 @@ class Anki2Importer(Importer):
|
||||||
|
|
||||||
def _prepareModels(self) -> None:
|
def _prepareModels(self) -> None:
|
||||||
"Prepare index of schema hashes."
|
"Prepare index of schema hashes."
|
||||||
self._modelMap: Dict[int, int] = {}
|
self._modelMap: Dict[NoteTypeID, NoteTypeID] = {}
|
||||||
|
|
||||||
def _mid(self, srcMid: int) -> Any:
|
def _mid(self, srcMid: NoteTypeID) -> Any:
|
||||||
"Return local id for remote MID."
|
"Return local id for remote MID."
|
||||||
# already processed this mid?
|
# already processed this mid?
|
||||||
if srcMid in self._modelMap:
|
if srcMid in self._modelMap:
|
||||||
|
@ -246,7 +249,7 @@ class Anki2Importer(Importer):
|
||||||
self.dst.models.update(model)
|
self.dst.models.update(model)
|
||||||
break
|
break
|
||||||
# as they don't match, try next id
|
# as they don't match, try next id
|
||||||
mid += 1
|
mid = NoteTypeID(mid + 1)
|
||||||
# save map and return new mid
|
# save map and return new mid
|
||||||
self._modelMap[srcMid] = mid
|
self._modelMap[srcMid] = mid
|
||||||
return mid
|
return mid
|
||||||
|
@ -254,7 +257,7 @@ class Anki2Importer(Importer):
|
||||||
# Decks
|
# Decks
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def _did(self, did: int) -> Any:
|
def _did(self, did: DeckID) -> Any:
|
||||||
"Given did in src col, return local id."
|
"Given did in src col, return local id."
|
||||||
# already converted?
|
# already converted?
|
||||||
if did in self._decks:
|
if did in self._decks:
|
||||||
|
@ -305,7 +308,7 @@ class Anki2Importer(Importer):
|
||||||
if self.source_needs_upgrade:
|
if self.source_needs_upgrade:
|
||||||
self.src.upgrade_to_v2_scheduler()
|
self.src.upgrade_to_v2_scheduler()
|
||||||
# build map of (guid, ord) -> cid and used id cache
|
# build map of (guid, ord) -> cid and used id cache
|
||||||
self._cards: Dict[Tuple[str, int], int] = {}
|
self._cards: Dict[Tuple[str, int], CardID] = {}
|
||||||
existing = {}
|
existing = {}
|
||||||
for guid, ord, cid in self.dst.db.execute(
|
for guid, ord, cid in self.dst.db.execute(
|
||||||
"select f.guid, c.ord, c.id from cards c, notes f " "where c.nid = f.id"
|
"select f.guid, c.ord, c.id from cards c, notes f " "where c.nid = f.id"
|
||||||
|
@ -430,7 +433,7 @@ insert or ignore into revlog values (?,?,?,?,?,?,?,?,?)""",
|
||||||
# the user likely used subdirectories
|
# the user likely used subdirectories
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _mungeMedia(self, mid: int, fieldsStr: str) -> str:
|
def _mungeMedia(self, mid: NoteTypeID, fieldsStr: str) -> str:
|
||||||
fields = splitFields(fieldsStr)
|
fields = splitFields(fieldsStr)
|
||||||
|
|
||||||
def repl(match):
|
def repl(match):
|
||||||
|
|
|
@ -10,6 +10,8 @@ from anki.config import Config
|
||||||
from anki.consts import NEW_CARDS_RANDOM, STARTING_FACTOR
|
from anki.consts import NEW_CARDS_RANDOM, STARTING_FACTOR
|
||||||
from anki.importing.base import Importer
|
from anki.importing.base import Importer
|
||||||
from anki.lang import TR
|
from anki.lang import TR
|
||||||
|
from anki.models import NoteTypeID
|
||||||
|
from anki.notes import NoteID
|
||||||
from anki.utils import (
|
from anki.utils import (
|
||||||
fieldChecksum,
|
fieldChecksum,
|
||||||
guid64,
|
guid64,
|
||||||
|
@ -19,6 +21,11 @@ from anki.utils import (
|
||||||
timestampID,
|
timestampID,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TagMappedUpdate = Tuple[int, int, str, str, NoteID, str, str]
|
||||||
|
TagModifiedUpdate = Tuple[int, int, str, str, NoteID, str]
|
||||||
|
NoTagUpdate = Tuple[int, int, str, NoteID, str]
|
||||||
|
Updates = Union[TagMappedUpdate, TagModifiedUpdate, NoTagUpdate]
|
||||||
|
|
||||||
# Stores a list of fields, tags and deck
|
# Stores a list of fields, tags and deck
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
|
@ -122,7 +129,7 @@ class NoteImporter(Importer):
|
||||||
if f == "_tags":
|
if f == "_tags":
|
||||||
self._tagsMapped = True
|
self._tagsMapped = True
|
||||||
# gather checks for duplicate comparison
|
# gather checks for duplicate comparison
|
||||||
csums: Dict[str, List[int]] = {}
|
csums: Dict[str, List[NoteID]] = {}
|
||||||
for csum, id in self.col.db.execute(
|
for csum, id in self.col.db.execute(
|
||||||
"select csum, id from notes where mid = ?", self.model["id"]
|
"select csum, id from notes where mid = ?", self.model["id"]
|
||||||
):
|
):
|
||||||
|
@ -133,12 +140,12 @@ class NoteImporter(Importer):
|
||||||
firsts: Dict[str, bool] = {}
|
firsts: Dict[str, bool] = {}
|
||||||
fld0idx = self.mapping.index(self.model["flds"][0]["name"])
|
fld0idx = self.mapping.index(self.model["flds"][0]["name"])
|
||||||
self._fmap = self.col.models.fieldMap(self.model)
|
self._fmap = self.col.models.fieldMap(self.model)
|
||||||
self._nextID = timestampID(self.col.db, "notes")
|
self._nextID = NoteID(timestampID(self.col.db, "notes"))
|
||||||
# loop through the notes
|
# loop through the notes
|
||||||
updates = []
|
updates: List[Updates] = []
|
||||||
updateLog = []
|
updateLog = []
|
||||||
new = []
|
new = []
|
||||||
self._ids: List[int] = []
|
self._ids: List[NoteID] = []
|
||||||
self._cards: List[Tuple] = []
|
self._cards: List[Tuple] = []
|
||||||
dupeCount = 0
|
dupeCount = 0
|
||||||
dupes: List[str] = []
|
dupes: List[str] = []
|
||||||
|
@ -203,9 +210,9 @@ class NoteImporter(Importer):
|
||||||
found = False
|
found = False
|
||||||
# newly add
|
# newly add
|
||||||
if not found:
|
if not found:
|
||||||
data = self.newData(n)
|
new_data = self.newData(n)
|
||||||
if data:
|
if new_data:
|
||||||
new.append(data)
|
new.append(new_data)
|
||||||
# note that we've seen this note once already
|
# note that we've seen this note once already
|
||||||
firsts[fld0] = True
|
firsts[fld0] = True
|
||||||
self.addNew(new)
|
self.addNew(new)
|
||||||
|
@ -235,15 +242,17 @@ class NoteImporter(Importer):
|
||||||
self.log.extend(updateLog)
|
self.log.extend(updateLog)
|
||||||
self.total = len(self._ids)
|
self.total = len(self._ids)
|
||||||
|
|
||||||
def newData(self, n: ForeignNote) -> Optional[list]:
|
def newData(
|
||||||
|
self, n: ForeignNote
|
||||||
|
) -> Tuple[NoteID, str, NoteTypeID, int, int, str, str, str, int, int, str]:
|
||||||
id = self._nextID
|
id = self._nextID
|
||||||
self._nextID += 1
|
self._nextID = NoteID(self._nextID + 1)
|
||||||
self._ids.append(id)
|
self._ids.append(id)
|
||||||
self.processFields(n)
|
self.processFields(n)
|
||||||
# note id for card updates later
|
# note id for card updates later
|
||||||
for ord, c in list(n.cards.items()):
|
for ord, c in list(n.cards.items()):
|
||||||
self._cards.append((id, ord, c))
|
self._cards.append((id, ord, c))
|
||||||
return [
|
return (
|
||||||
id,
|
id,
|
||||||
guid64(),
|
guid64(),
|
||||||
self.model["id"],
|
self.model["id"],
|
||||||
|
@ -255,28 +264,35 @@ class NoteImporter(Importer):
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
"",
|
"",
|
||||||
]
|
)
|
||||||
|
|
||||||
def addNew(self, rows: List[List[Union[int, str]]]) -> None:
|
def addNew(
|
||||||
|
self,
|
||||||
|
rows: List[
|
||||||
|
Tuple[NoteID, str, NoteTypeID, int, int, str, str, str, int, int, str]
|
||||||
|
],
|
||||||
|
) -> None:
|
||||||
self.col.db.executemany(
|
self.col.db.executemany(
|
||||||
"insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", rows
|
"insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)", rows
|
||||||
)
|
)
|
||||||
|
|
||||||
def updateData(self, n: ForeignNote, id: int, sflds: List[str]) -> Optional[list]:
|
def updateData(
|
||||||
|
self, n: ForeignNote, id: NoteID, sflds: List[str]
|
||||||
|
) -> Optional[Updates]:
|
||||||
self._ids.append(id)
|
self._ids.append(id)
|
||||||
self.processFields(n, sflds)
|
self.processFields(n, sflds)
|
||||||
if self._tagsMapped:
|
if self._tagsMapped:
|
||||||
tags = self.col.tags.join(n.tags)
|
tags = self.col.tags.join(n.tags)
|
||||||
return [intTime(), self.col.usn(), n.fieldsStr, tags, id, n.fieldsStr, tags]
|
return (intTime(), self.col.usn(), n.fieldsStr, tags, id, n.fieldsStr, tags)
|
||||||
elif self.tagModified:
|
elif self.tagModified:
|
||||||
tags = self.col.db.scalar("select tags from notes where id = ?", id)
|
tags = self.col.db.scalar("select tags from notes where id = ?", id)
|
||||||
tagList = self.col.tags.split(tags) + self.tagModified.split()
|
tagList = self.col.tags.split(tags) + self.tagModified.split()
|
||||||
tags = self.col.tags.join(tagList)
|
tags = self.col.tags.join(tagList)
|
||||||
return [intTime(), self.col.usn(), n.fieldsStr, tags, id, n.fieldsStr]
|
return (intTime(), self.col.usn(), n.fieldsStr, tags, id, n.fieldsStr)
|
||||||
else:
|
else:
|
||||||
return [intTime(), self.col.usn(), n.fieldsStr, id, n.fieldsStr]
|
return (intTime(), self.col.usn(), n.fieldsStr, id, n.fieldsStr)
|
||||||
|
|
||||||
def addUpdates(self, rows: List[List[Union[int, str]]]) -> None:
|
def addUpdates(self, rows: List[Updates]) -> None:
|
||||||
changes = self.col.db.scalar("select total_changes()")
|
changes = self.col.db.scalar("select total_changes()")
|
||||||
if self._tagsMapped:
|
if self._tagsMapped:
|
||||||
self.col.db.executemany(
|
self.col.db.executemany(
|
||||||
|
|
|
@ -17,6 +17,7 @@ import anki
|
||||||
import anki._backend.backend_pb2 as _pb
|
import anki._backend.backend_pb2 as _pb
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.latex import render_latex, render_latex_returning_errors
|
from anki.latex import render_latex, render_latex_returning_errors
|
||||||
|
from anki.models import NoteTypeID
|
||||||
from anki.sound import SoundOrVideoTag
|
from anki.sound import SoundOrVideoTag
|
||||||
from anki.template import av_tags_to_native
|
from anki.template import av_tags_to_native
|
||||||
from anki.utils import intTime
|
from anki.utils import intTime
|
||||||
|
@ -159,7 +160,7 @@ class MediaManager:
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def filesInStr(
|
def filesInStr(
|
||||||
self, mid: int, string: str, includeRemote: bool = False
|
self, mid: NoteTypeID, string: str, includeRemote: bool = False
|
||||||
) -> List[str]:
|
) -> List[str]:
|
||||||
l = []
|
l = []
|
||||||
model = self.col.models.get(mid)
|
model = self.col.models.get(mid)
|
||||||
|
|
|
@ -8,7 +8,7 @@ import pprint
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from typing import Any, Dict, List, Optional, Sequence, Tuple, Union
|
from typing import Any, Dict, List, NewType, Optional, Sequence, Tuple, Union
|
||||||
|
|
||||||
import anki # pylint: disable=unused-import
|
import anki # pylint: disable=unused-import
|
||||||
import anki._backend.backend_pb2 as _pb
|
import anki._backend.backend_pb2 as _pb
|
||||||
|
@ -35,6 +35,7 @@ NoteTypeNameIDUseCount = _pb.NoteTypeNameIDUseCount
|
||||||
NoteType = Dict[str, Any]
|
NoteType = Dict[str, Any]
|
||||||
Field = Dict[str, Any]
|
Field = Dict[str, Any]
|
||||||
Template = Dict[str, Union[str, int, None]]
|
Template = Dict[str, Union[str, int, None]]
|
||||||
|
NoteTypeID = NewType("NoteTypeID", int)
|
||||||
|
|
||||||
|
|
||||||
class ModelsDictProxy:
|
class ModelsDictProxy:
|
||||||
|
@ -47,7 +48,7 @@ class ModelsDictProxy:
|
||||||
|
|
||||||
def __getitem__(self, item: Any) -> Any:
|
def __getitem__(self, item: Any) -> Any:
|
||||||
self._warn()
|
self._warn()
|
||||||
return self._col.models.get(int(item))
|
return self._col.models.get(NoteTypeID(int(item)))
|
||||||
|
|
||||||
def __setitem__(self, key: str, val: Any) -> None:
|
def __setitem__(self, key: str, val: Any) -> None:
|
||||||
self._warn()
|
self._warn()
|
||||||
|
@ -114,16 +115,16 @@ class ModelManager:
|
||||||
# need to cache responses from the backend. Please do not
|
# need to cache responses from the backend. Please do not
|
||||||
# access the cache directly!
|
# access the cache directly!
|
||||||
|
|
||||||
_cache: Dict[int, NoteType] = {}
|
_cache: Dict[NoteTypeID, NoteType] = {}
|
||||||
|
|
||||||
def _update_cache(self, nt: NoteType) -> None:
|
def _update_cache(self, nt: NoteType) -> None:
|
||||||
self._cache[nt["id"]] = nt
|
self._cache[nt["id"]] = nt
|
||||||
|
|
||||||
def _remove_from_cache(self, ntid: int) -> None:
|
def _remove_from_cache(self, ntid: NoteTypeID) -> None:
|
||||||
if ntid in self._cache:
|
if ntid in self._cache:
|
||||||
del self._cache[ntid]
|
del self._cache[ntid]
|
||||||
|
|
||||||
def _get_cached(self, ntid: int) -> Optional[NoteType]:
|
def _get_cached(self, ntid: NoteTypeID) -> Optional[NoteType]:
|
||||||
return self._cache.get(ntid)
|
return self._cache.get(ntid)
|
||||||
|
|
||||||
def _clear_cache(self) -> None:
|
def _clear_cache(self) -> None:
|
||||||
|
@ -143,11 +144,11 @@ class ModelManager:
|
||||||
def allNames(self) -> List[str]:
|
def allNames(self) -> List[str]:
|
||||||
return [n.name for n in self.all_names_and_ids()]
|
return [n.name for n in self.all_names_and_ids()]
|
||||||
|
|
||||||
def ids(self) -> List[int]:
|
def ids(self) -> List[NoteTypeID]:
|
||||||
return [n.id for n in self.all_names_and_ids()]
|
return [NoteTypeID(n.id) for n in self.all_names_and_ids()]
|
||||||
|
|
||||||
# only used by importing code
|
# only used by importing code
|
||||||
def have(self, id: int) -> bool:
|
def have(self, id: NoteTypeID) -> bool:
|
||||||
if isinstance(id, str):
|
if isinstance(id, str):
|
||||||
id = int(id)
|
id = int(id)
|
||||||
return any(True for e in self.all_names_and_ids() if e.id == id)
|
return any(True for e in self.all_names_and_ids() if e.id == id)
|
||||||
|
@ -162,7 +163,7 @@ class ModelManager:
|
||||||
m = self.get(self.col.conf["curModel"])
|
m = self.get(self.col.conf["curModel"])
|
||||||
if m:
|
if m:
|
||||||
return m
|
return m
|
||||||
return self.get(self.all_names_and_ids()[0].id)
|
return self.get(NoteTypeID(self.all_names_and_ids()[0].id))
|
||||||
|
|
||||||
def setCurrent(self, m: NoteType) -> None:
|
def setCurrent(self, m: NoteType) -> None:
|
||||||
self.col.conf["curModel"] = m["id"]
|
self.col.conf["curModel"] = m["id"]
|
||||||
|
@ -170,13 +171,13 @@ class ModelManager:
|
||||||
# Retrieving and creating models
|
# Retrieving and creating models
|
||||||
#############################################################
|
#############################################################
|
||||||
|
|
||||||
def id_for_name(self, name: str) -> Optional[int]:
|
def id_for_name(self, name: str) -> Optional[NoteTypeID]:
|
||||||
try:
|
try:
|
||||||
return self.col._backend.get_notetype_id_by_name(name)
|
return NoteTypeID(self.col._backend.get_notetype_id_by_name(name))
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get(self, id: int) -> Optional[NoteType]:
|
def get(self, id: NoteTypeID) -> Optional[NoteType]:
|
||||||
"Get model with ID, or None."
|
"Get model with ID, or None."
|
||||||
# deal with various legacy input types
|
# deal with various legacy input types
|
||||||
if id is None:
|
if id is None:
|
||||||
|
@ -195,7 +196,7 @@ class ModelManager:
|
||||||
|
|
||||||
def all(self) -> List[NoteType]:
|
def all(self) -> List[NoteType]:
|
||||||
"Get all models."
|
"Get all models."
|
||||||
return [self.get(nt.id) for nt in self.all_names_and_ids()]
|
return [self.get(NoteTypeID(nt.id)) for nt in self.all_names_and_ids()]
|
||||||
|
|
||||||
def byName(self, name: str) -> Optional[NoteType]:
|
def byName(self, name: str) -> Optional[NoteType]:
|
||||||
"Get model with NAME."
|
"Get model with NAME."
|
||||||
|
@ -222,10 +223,10 @@ class ModelManager:
|
||||||
|
|
||||||
def remove_all_notetypes(self) -> None:
|
def remove_all_notetypes(self) -> None:
|
||||||
for nt in self.all_names_and_ids():
|
for nt in self.all_names_and_ids():
|
||||||
self._remove_from_cache(nt.id)
|
self._remove_from_cache(NoteTypeID(nt.id))
|
||||||
self.col._backend.remove_notetype(nt.id)
|
self.col._backend.remove_notetype(nt.id)
|
||||||
|
|
||||||
def remove(self, id: int) -> None:
|
def remove(self, id: NoteTypeID) -> None:
|
||||||
"Modifies schema."
|
"Modifies schema."
|
||||||
self._remove_from_cache(id)
|
self._remove_from_cache(id)
|
||||||
self.col._backend.remove_notetype(id)
|
self.col._backend.remove_notetype(id)
|
||||||
|
@ -257,7 +258,7 @@ class ModelManager:
|
||||||
# Tools
|
# Tools
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
def nids(self, ntid: int) -> List[int]:
|
def nids(self, ntid: NoteTypeID) -> List[anki.notes.NoteID]:
|
||||||
"Note ids for M."
|
"Note ids for M."
|
||||||
if isinstance(ntid, dict):
|
if isinstance(ntid, dict):
|
||||||
# legacy callers passed in note type
|
# legacy callers passed in note type
|
||||||
|
@ -403,7 +404,7 @@ class ModelManager:
|
||||||
self.reposition_template(m, template, idx)
|
self.reposition_template(m, template, idx)
|
||||||
self.save(m)
|
self.save(m)
|
||||||
|
|
||||||
def template_use_count(self, ntid: int, ord: int) -> int:
|
def template_use_count(self, ntid: NoteTypeID, ord: int) -> int:
|
||||||
return self.col.db.scalar(
|
return self.col.db.scalar(
|
||||||
"""
|
"""
|
||||||
select count() from cards, notes where cards.nid = notes.id
|
select count() from cards, notes where cards.nid = notes.id
|
||||||
|
@ -420,7 +421,7 @@ and notes.mid = ? and cards.ord = ?""",
|
||||||
def change(
|
def change(
|
||||||
self,
|
self,
|
||||||
m: NoteType,
|
m: NoteType,
|
||||||
nids: List[int],
|
nids: List[anki.notes.NoteID],
|
||||||
newModel: NoteType,
|
newModel: NoteType,
|
||||||
fmap: Optional[Dict[int, Union[None, int]]],
|
fmap: Optional[Dict[int, Union[None, int]]],
|
||||||
cmap: Optional[Dict[int, Union[None, int]]],
|
cmap: Optional[Dict[int, Union[None, int]]],
|
||||||
|
@ -434,7 +435,10 @@ and notes.mid = ? and cards.ord = ?""",
|
||||||
self.col.after_note_updates(nids, mark_modified=True)
|
self.col.after_note_updates(nids, mark_modified=True)
|
||||||
|
|
||||||
def _changeNotes(
|
def _changeNotes(
|
||||||
self, nids: List[int], newModel: NoteType, map: Dict[int, Union[None, int]]
|
self,
|
||||||
|
nids: List[anki.notes.NoteID],
|
||||||
|
newModel: NoteType,
|
||||||
|
map: Dict[int, Union[None, int]],
|
||||||
) -> None:
|
) -> None:
|
||||||
d = []
|
d = []
|
||||||
nfields = len(newModel["flds"])
|
nfields = len(newModel["flds"])
|
||||||
|
@ -464,7 +468,7 @@ and notes.mid = ? and cards.ord = ?""",
|
||||||
|
|
||||||
def _changeCards(
|
def _changeCards(
|
||||||
self,
|
self,
|
||||||
nids: List[int],
|
nids: List[anki.notes.NoteID],
|
||||||
oldModel: NoteType,
|
oldModel: NoteType,
|
||||||
newModel: NoteType,
|
newModel: NoteType,
|
||||||
map: Dict[int, Union[None, int]],
|
map: Dict[int, Union[None, int]],
|
||||||
|
|
|
@ -5,28 +5,33 @@ from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import pprint
|
import pprint
|
||||||
from typing import Any, List, Optional, Sequence, Tuple
|
from typing import Any, List, NewType, Optional, Sequence, Tuple
|
||||||
|
|
||||||
import anki # pylint: disable=unused-import
|
import anki # pylint: disable=unused-import
|
||||||
import anki._backend.backend_pb2 as _pb
|
import anki._backend.backend_pb2 as _pb
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki.consts import MODEL_STD
|
from anki.consts import MODEL_STD
|
||||||
from anki.models import NoteType, Template
|
from anki.models import NoteType, NoteTypeID, Template
|
||||||
from anki.utils import joinFields
|
from anki.utils import joinFields
|
||||||
|
|
||||||
DuplicateOrEmptyResult = _pb.NoteIsDuplicateOrEmptyOut.State
|
DuplicateOrEmptyResult = _pb.NoteIsDuplicateOrEmptyOut.State
|
||||||
|
|
||||||
|
# types
|
||||||
|
NoteID = NewType("NoteID", int)
|
||||||
|
|
||||||
|
|
||||||
class Note:
|
class Note:
|
||||||
# not currently exposed
|
# not currently exposed
|
||||||
flags = 0
|
flags = 0
|
||||||
data = ""
|
data = ""
|
||||||
|
id: NoteID
|
||||||
|
mid: NoteTypeID
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
col: anki.collection.Collection,
|
col: anki.collection.Collection,
|
||||||
model: Optional[NoteType] = None,
|
model: Optional[NoteType] = None,
|
||||||
id: Optional[int] = None,
|
id: Optional[NoteID] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
assert not (model and id)
|
assert not (model and id)
|
||||||
self.col = col.weakref()
|
self.col = col.weakref()
|
||||||
|
@ -46,9 +51,9 @@ class Note:
|
||||||
self._load_from_backend_note(n)
|
self._load_from_backend_note(n)
|
||||||
|
|
||||||
def _load_from_backend_note(self, n: _pb.Note) -> None:
|
def _load_from_backend_note(self, n: _pb.Note) -> None:
|
||||||
self.id = n.id
|
self.id = NoteID(n.id)
|
||||||
self.guid = n.guid
|
self.guid = n.guid
|
||||||
self.mid = n.notetype_id
|
self.mid = NoteTypeID(n.notetype_id)
|
||||||
self.mod = n.mtime_secs
|
self.mod = n.mtime_secs
|
||||||
self.usn = n.usn
|
self.usn = n.usn
|
||||||
self.tags = list(n.tags)
|
self.tags = list(n.tags)
|
||||||
|
@ -93,7 +98,7 @@ class Note:
|
||||||
) -> anki.cards.Card:
|
) -> anki.cards.Card:
|
||||||
card = anki.cards.Card(self.col)
|
card = anki.cards.Card(self.col)
|
||||||
card.ord = ord
|
card.ord = ord
|
||||||
card.did = 1
|
card.did = anki.decks.DEFAULT_DECK_ID
|
||||||
|
|
||||||
model = custom_note_type or self.model()
|
model = custom_note_type or self.model()
|
||||||
template = copy.copy(
|
template = copy.copy(
|
||||||
|
@ -119,7 +124,7 @@ class Note:
|
||||||
def cards(self) -> List[anki.cards.Card]:
|
def cards(self) -> List[anki.cards.Card]:
|
||||||
return [self.col.getCard(id) for id in self.card_ids()]
|
return [self.col.getCard(id) for id in self.card_ids()]
|
||||||
|
|
||||||
def card_ids(self) -> Sequence[int]:
|
def card_ids(self) -> Sequence[anki.cards.CardID]:
|
||||||
return self.col.card_ids_of_note(self.id)
|
return self.col.card_ids_of_note(self.id)
|
||||||
|
|
||||||
def model(self) -> Optional[NoteType]:
|
def model(self) -> Optional[NoteType]:
|
||||||
|
|
|
@ -13,6 +13,7 @@ SchedTimingToday = _pb.SchedTimingTodayOut
|
||||||
|
|
||||||
from typing import List, Optional, Sequence
|
from typing import List, Optional, Sequence
|
||||||
|
|
||||||
|
from anki.cards import CardID
|
||||||
from anki.consts import CARD_TYPE_NEW, NEW_CARDS_RANDOM, QUEUE_TYPE_NEW, QUEUE_TYPE_REV
|
from anki.consts import CARD_TYPE_NEW, NEW_CARDS_RANDOM, QUEUE_TYPE_NEW, QUEUE_TYPE_REV
|
||||||
from anki.decks import DeckConfigDict, DeckID, DeckTreeNode
|
from anki.decks import DeckConfigDict, DeckID, DeckTreeNode
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
|
@ -90,10 +91,10 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
|
||||||
# Filtered deck handling
|
# Filtered deck handling
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def rebuild_filtered_deck(self, deck_id: int) -> OpChangesWithCount:
|
def rebuild_filtered_deck(self, deck_id: DeckID) -> OpChangesWithCount:
|
||||||
return self.col._backend.rebuild_filtered_deck(deck_id)
|
return self.col._backend.rebuild_filtered_deck(deck_id)
|
||||||
|
|
||||||
def empty_filtered_deck(self, deck_id: int) -> OpChanges:
|
def empty_filtered_deck(self, deck_id: DeckID) -> OpChanges:
|
||||||
return self.col._backend.empty_filtered_deck(deck_id)
|
return self.col._backend.empty_filtered_deck(deck_id)
|
||||||
|
|
||||||
def get_or_create_filtered_deck(self, deck_id: DeckID) -> FilteredDeckForUpdate:
|
def get_or_create_filtered_deck(self, deck_id: DeckID) -> FilteredDeckForUpdate:
|
||||||
|
@ -107,10 +108,10 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
|
||||||
# Suspending & burying
|
# Suspending & burying
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def unsuspend_cards(self, ids: Sequence[int]) -> OpChanges:
|
def unsuspend_cards(self, ids: Sequence[CardID]) -> OpChanges:
|
||||||
return self.col._backend.restore_buried_and_suspended_cards(ids)
|
return self.col._backend.restore_buried_and_suspended_cards(ids)
|
||||||
|
|
||||||
def unbury_cards(self, ids: List[int]) -> OpChanges:
|
def unbury_cards(self, ids: List[CardID]) -> OpChanges:
|
||||||
return self.col._backend.restore_buried_and_suspended_cards(ids)
|
return self.col._backend.restore_buried_and_suspended_cards(ids)
|
||||||
|
|
||||||
def unbury_cards_in_current_deck(
|
def unbury_cards_in_current_deck(
|
||||||
|
@ -119,12 +120,12 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
|
||||||
) -> None:
|
) -> None:
|
||||||
self.col._backend.unbury_cards_in_current_deck(mode)
|
self.col._backend.unbury_cards_in_current_deck(mode)
|
||||||
|
|
||||||
def suspend_cards(self, ids: Sequence[int]) -> OpChanges:
|
def suspend_cards(self, ids: Sequence[CardID]) -> OpChanges:
|
||||||
return self.col._backend.bury_or_suspend_cards(
|
return self.col._backend.bury_or_suspend_cards(
|
||||||
card_ids=ids, mode=BuryOrSuspend.SUSPEND
|
card_ids=ids, mode=BuryOrSuspend.SUSPEND
|
||||||
)
|
)
|
||||||
|
|
||||||
def bury_cards(self, ids: Sequence[int], manual: bool = True) -> OpChanges:
|
def bury_cards(self, ids: Sequence[CardID], manual: bool = True) -> OpChanges:
|
||||||
if manual:
|
if manual:
|
||||||
mode = BuryOrSuspend.BURY_USER
|
mode = BuryOrSuspend.BURY_USER
|
||||||
else:
|
else:
|
||||||
|
@ -137,13 +138,13 @@ 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: List[int]) -> OpChanges:
|
def schedule_cards_as_new(self, card_ids: List[CardID]) -> OpChanges:
|
||||||
"Put cards at the end of the new queue."
|
"Put cards at the end of the new queue."
|
||||||
return self.col._backend.schedule_cards_as_new(card_ids=card_ids, log=True)
|
return self.col._backend.schedule_cards_as_new(card_ids=card_ids, log=True)
|
||||||
|
|
||||||
def set_due_date(
|
def set_due_date(
|
||||||
self,
|
self,
|
||||||
card_ids: List[int],
|
card_ids: List[CardID],
|
||||||
days: str,
|
days: str,
|
||||||
config_key: Optional[Config.String.Key.V] = None,
|
config_key: Optional[Config.String.Key.V] = None,
|
||||||
) -> OpChanges:
|
) -> OpChanges:
|
||||||
|
@ -162,7 +163,7 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
|
||||||
config_key=key, # type: ignore
|
config_key=key, # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
def resetCards(self, ids: List[int]) -> None:
|
def resetCards(self, ids: List[CardID]) -> None:
|
||||||
"Completely reset cards for export."
|
"Completely reset cards for export."
|
||||||
sids = ids2str(ids)
|
sids = ids2str(ids)
|
||||||
assert self.col.db
|
assert self.col.db
|
||||||
|
@ -184,7 +185,7 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
|
||||||
|
|
||||||
def reposition_new_cards(
|
def reposition_new_cards(
|
||||||
self,
|
self,
|
||||||
card_ids: Sequence[int],
|
card_ids: Sequence[CardID],
|
||||||
starting_from: int,
|
starting_from: int,
|
||||||
step_size: int,
|
step_size: int,
|
||||||
randomize: bool,
|
randomize: bool,
|
||||||
|
@ -198,10 +199,10 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
|
||||||
shift_existing=shift_existing,
|
shift_existing=shift_existing,
|
||||||
)
|
)
|
||||||
|
|
||||||
def randomizeCards(self, did: int) -> None:
|
def randomizeCards(self, did: DeckID) -> None:
|
||||||
self.col._backend.sort_deck(deck_id=did, randomize=True)
|
self.col._backend.sort_deck(deck_id=did, randomize=True)
|
||||||
|
|
||||||
def orderCards(self, did: int) -> None:
|
def orderCards(self, did: DeckID) -> None:
|
||||||
self.col._backend.sort_deck(deck_id=did, randomize=False)
|
self.col._backend.sort_deck(deck_id=did, randomize=False)
|
||||||
|
|
||||||
def resortConf(self, conf: DeckConfigDict) -> None:
|
def resortConf(self, conf: DeckConfigDict) -> None:
|
||||||
|
@ -212,7 +213,7 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
|
||||||
self.orderCards(did)
|
self.orderCards(did)
|
||||||
|
|
||||||
# for post-import
|
# for post-import
|
||||||
def maybeRandomizeDeck(self, did: Optional[int] = None) -> None:
|
def maybeRandomizeDeck(self, did: Optional[DeckID] = None) -> None:
|
||||||
if not did:
|
if not did:
|
||||||
did = self.col.decks.selected()
|
did = self.col.decks.selected()
|
||||||
conf = self.col.decks.confForDid(did)
|
conf = self.col.decks.confForDid(did)
|
||||||
|
@ -223,7 +224,7 @@ select id from cards where did in %s and queue = {QUEUE_TYPE_REV} and due <= ? l
|
||||||
# legacy
|
# legacy
|
||||||
def sortCards(
|
def sortCards(
|
||||||
self,
|
self,
|
||||||
cids: List[int],
|
cids: List[CardID],
|
||||||
start: int = 1,
|
start: int = 1,
|
||||||
step: int = 1,
|
step: int = 1,
|
||||||
shuffle: bool = False,
|
shuffle: bool = False,
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
|
|
||||||
from typing import List, Optional, Tuple
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
from anki.cards import Card
|
from anki.cards import Card, CardID
|
||||||
from anki.consts import CARD_TYPE_RELEARNING, QUEUE_TYPE_DAY_LEARN_RELEARN
|
from anki.consts import CARD_TYPE_RELEARNING, QUEUE_TYPE_DAY_LEARN_RELEARN
|
||||||
from anki.decks import DeckConfigDict
|
from anki.decks import DeckConfigDict, DeckID
|
||||||
|
from anki.notes import NoteID
|
||||||
from anki.scheduler.base import SchedulerBase, UnburyCurrentDeck
|
from anki.scheduler.base import SchedulerBase, UnburyCurrentDeck
|
||||||
from anki.utils import from_json_bytes, ids2str
|
from anki.utils import from_json_bytes, ids2str
|
||||||
|
|
||||||
|
@ -14,11 +15,11 @@ class SchedulerBaseWithLegacy(SchedulerBase):
|
||||||
"Legacy aliases and helpers. These will go away in the future."
|
"Legacy aliases and helpers. These will go away in the future."
|
||||||
|
|
||||||
def reschedCards(
|
def reschedCards(
|
||||||
self, card_ids: List[int], min_interval: int, max_interval: int
|
self, card_ids: List[CardID], min_interval: int, max_interval: int
|
||||||
) -> None:
|
) -> None:
|
||||||
self.set_due_date(card_ids, f"{min_interval}-{max_interval}!")
|
self.set_due_date(card_ids, f"{min_interval}-{max_interval}!")
|
||||||
|
|
||||||
def buryNote(self, nid: int) -> None:
|
def buryNote(self, nid: NoteID) -> None:
|
||||||
note = self.col.get_note(nid)
|
note = self.col.get_note(nid)
|
||||||
self.bury_cards(note.card_ids())
|
self.bury_cards(note.card_ids())
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ class SchedulerBaseWithLegacy(SchedulerBase):
|
||||||
print("_nextDueMsg() is obsolete")
|
print("_nextDueMsg() is obsolete")
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def rebuildDyn(self, did: Optional[int] = None) -> Optional[int]:
|
def rebuildDyn(self, did: Optional[DeckID] = None) -> Optional[int]:
|
||||||
did = did or self.col.decks.selected()
|
did = did or self.col.decks.selected()
|
||||||
count = self.rebuild_filtered_deck(did).count or None
|
count = self.rebuild_filtered_deck(did).count or None
|
||||||
if not count:
|
if not count:
|
||||||
|
@ -57,7 +58,7 @@ class SchedulerBaseWithLegacy(SchedulerBase):
|
||||||
self.col.decks.select(did)
|
self.col.decks.select(did)
|
||||||
return count
|
return count
|
||||||
|
|
||||||
def emptyDyn(self, did: Optional[int], lim: Optional[str] = None) -> None:
|
def emptyDyn(self, did: Optional[DeckID], lim: Optional[str] = None) -> None:
|
||||||
if lim is None:
|
if lim is None:
|
||||||
self.empty_filtered_deck(did)
|
self.empty_filtered_deck(did)
|
||||||
return
|
return
|
||||||
|
@ -79,13 +80,13 @@ due = (case when odue>0 then odue else due end), odue = 0, odid = 0, usn = ? whe
|
||||||
self.col.usn(),
|
self.col.usn(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def remFromDyn(self, cids: List[int]) -> None:
|
def remFromDyn(self, cids: List[CardID]) -> None:
|
||||||
self.emptyDyn(None, f"id in {ids2str(cids)} and odid")
|
self.emptyDyn(None, f"id in {ids2str(cids)} and odid")
|
||||||
|
|
||||||
# used by v2 scheduler and some add-ons
|
# used by v2 scheduler and some add-ons
|
||||||
def update_stats(
|
def update_stats(
|
||||||
self,
|
self,
|
||||||
deck_id: int,
|
deck_id: DeckID,
|
||||||
new_delta: int = 0,
|
new_delta: int = 0,
|
||||||
review_delta: int = 0,
|
review_delta: int = 0,
|
||||||
milliseconds_delta: int = 0,
|
milliseconds_delta: int = 0,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import anki
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
|
from anki.decks import DeckID
|
||||||
from anki.utils import ids2str, intTime
|
from anki.utils import ids2str, intTime
|
||||||
|
|
||||||
from .v2 import QueueConfig
|
from .v2 import QueueConfig
|
||||||
|
@ -97,7 +98,7 @@ class Scheduler(V2):
|
||||||
if card:
|
if card:
|
||||||
idx = self.countIdx(card)
|
idx = self.countIdx(card)
|
||||||
if idx == QUEUE_TYPE_LRN:
|
if idx == QUEUE_TYPE_LRN:
|
||||||
counts[QUEUE_TYPE_LRN] += card.left // 1000
|
counts[int(QUEUE_TYPE_LRN)] += card.left // 1000
|
||||||
else:
|
else:
|
||||||
counts[idx] += 1
|
counts[idx] += 1
|
||||||
|
|
||||||
|
@ -299,10 +300,11 @@ limit %d"""
|
||||||
if card.odid:
|
if card.odid:
|
||||||
card.did = card.odid
|
card.did = card.odid
|
||||||
card.odue = 0
|
card.odue = 0
|
||||||
card.odid = 0
|
card.odid = DeckID(0)
|
||||||
# if rescheduling is off, it needs to be set back to a new card
|
# if rescheduling is off, it needs to be set back to a new card
|
||||||
if not resched and not lapse:
|
if not resched and not lapse:
|
||||||
card.queue = card.type = CARD_TYPE_NEW
|
card.queue = QUEUE_TYPE_NEW
|
||||||
|
card.type = CARD_TYPE_NEW
|
||||||
card.due = self.col.nextID("pos")
|
card.due = self.col.nextID("pos")
|
||||||
|
|
||||||
def _startingLeft(self, card: Card) -> int:
|
def _startingLeft(self, card: Card) -> int:
|
||||||
|
@ -401,7 +403,7 @@ where queue in ({QUEUE_TYPE_LRN},{QUEUE_TYPE_DAY_LEARN_RELEARN}) and type = {CAR
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _lrnForDeck(self, did: int) -> int:
|
def _lrnForDeck(self, did: DeckID) -> int:
|
||||||
cnt = (
|
cnt = (
|
||||||
self.col.db.scalar(
|
self.col.db.scalar(
|
||||||
f"""
|
f"""
|
||||||
|
@ -426,7 +428,7 @@ and due <= ? limit ?)""",
|
||||||
# Reviews
|
# Reviews
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def _deckRevLimit(self, did: int) -> int:
|
def _deckRevLimit(self, did: DeckID) -> int:
|
||||||
return self._deckNewLimit(did, self._deckRevLimitSingle)
|
return self._deckNewLimit(did, self._deckRevLimitSingle)
|
||||||
|
|
||||||
def _resetRev(self) -> None:
|
def _resetRev(self) -> None:
|
||||||
|
@ -541,7 +543,7 @@ did = ? and queue = {QUEUE_TYPE_REV} and due <= ? limit ?""",
|
||||||
card.due = card.odue
|
card.due = card.odue
|
||||||
if card.odid:
|
if card.odid:
|
||||||
card.did = card.odid
|
card.did = card.odid
|
||||||
card.odid = 0
|
card.odid = DeckID(0)
|
||||||
card.odue = 0
|
card.odue = 0
|
||||||
|
|
||||||
# Interval management
|
# Interval management
|
||||||
|
@ -617,7 +619,8 @@ did = ? and queue = {QUEUE_TYPE_REV} and due <= ? limit ?""",
|
||||||
card.due = card.odue
|
card.due = card.odue
|
||||||
if card.odid:
|
if card.odid:
|
||||||
card.did = card.odid
|
card.did = card.odid
|
||||||
card.odue = card.odid = 0
|
card.odue = 0
|
||||||
|
card.odid = DeckID(0)
|
||||||
card.queue = QUEUE_TYPE_SUSPENDED
|
card.queue = QUEUE_TYPE_SUSPENDED
|
||||||
# notify UI
|
# notify UI
|
||||||
hooks.card_did_leech(card)
|
hooks.card_did_leech(card)
|
||||||
|
|
|
@ -11,9 +11,9 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||||
import anki # pylint: disable=unused-import
|
import anki # pylint: disable=unused-import
|
||||||
import anki._backend.backend_pb2 as _pb
|
import anki._backend.backend_pb2 as _pb
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki.cards import Card
|
from anki.cards import Card, CardID
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.decks import DeckConfigDict, DeckDict
|
from anki.decks import DeckConfigDict, DeckDict, DeckID
|
||||||
from anki.lang import FormatTimeSpan
|
from anki.lang import FormatTimeSpan
|
||||||
from anki.scheduler.legacy import SchedulerBaseWithLegacy
|
from anki.scheduler.legacy import SchedulerBaseWithLegacy
|
||||||
from anki.utils import ids2str, intTime
|
from anki.utils import ids2str, intTime
|
||||||
|
@ -75,7 +75,9 @@ class Scheduler(SchedulerBaseWithLegacy):
|
||||||
|
|
||||||
def _reset_counts(self) -> None:
|
def _reset_counts(self) -> None:
|
||||||
tree = self.deck_due_tree(self.col.decks.selected())
|
tree = self.deck_due_tree(self.col.decks.selected())
|
||||||
node = self.col.decks.find_deck_in_tree(tree, int(self.col.conf["curDeck"]))
|
node = self.col.decks.find_deck_in_tree(
|
||||||
|
tree, DeckID(int(self.col.conf["curDeck"]))
|
||||||
|
)
|
||||||
if not node:
|
if not node:
|
||||||
# current deck points to a missing deck
|
# current deck points to a missing deck
|
||||||
self.newCount = 0
|
self.newCount = 0
|
||||||
|
@ -144,7 +146,7 @@ class Scheduler(SchedulerBaseWithLegacy):
|
||||||
|
|
||||||
def _resetNew(self) -> None:
|
def _resetNew(self) -> None:
|
||||||
self._newDids = self.col.decks.active()[:]
|
self._newDids = self.col.decks.active()[:]
|
||||||
self._newQueue: List[int] = []
|
self._newQueue: List[CardID] = []
|
||||||
self._updateNewCardRatio()
|
self._updateNewCardRatio()
|
||||||
|
|
||||||
def _fillNew(self, recursing: bool = False) -> bool:
|
def _fillNew(self, recursing: bool = False) -> bool:
|
||||||
|
@ -210,7 +212,7 @@ class Scheduler(SchedulerBaseWithLegacy):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _deckNewLimit(
|
def _deckNewLimit(
|
||||||
self, did: int, fn: Optional[Callable[[DeckDict], int]] = None
|
self, did: DeckID, fn: Optional[Callable[[DeckDict], int]] = None
|
||||||
) -> int:
|
) -> int:
|
||||||
if not fn:
|
if not fn:
|
||||||
fn = self._deckNewLimitSingle
|
fn = self._deckNewLimitSingle
|
||||||
|
@ -225,7 +227,7 @@ class Scheduler(SchedulerBaseWithLegacy):
|
||||||
lim = min(rem, lim)
|
lim = min(rem, lim)
|
||||||
return lim
|
return lim
|
||||||
|
|
||||||
def _newForDeck(self, did: int, lim: int) -> int:
|
def _newForDeck(self, did: DeckID, lim: int) -> int:
|
||||||
"New count for a single deck."
|
"New count for a single deck."
|
||||||
if not lim:
|
if not lim:
|
||||||
return 0
|
return 0
|
||||||
|
@ -301,8 +303,8 @@ select count() from cards where did in %s and queue = {QUEUE_TYPE_PREVIEW}
|
||||||
def _resetLrn(self) -> None:
|
def _resetLrn(self) -> None:
|
||||||
self._updateLrnCutoff(force=True)
|
self._updateLrnCutoff(force=True)
|
||||||
self._resetLrnCount()
|
self._resetLrnCount()
|
||||||
self._lrnQueue: List[Tuple[int, int]] = []
|
self._lrnQueue: List[Tuple[int, CardID]] = []
|
||||||
self._lrnDayQueue: List[int] = []
|
self._lrnDayQueue: List[CardID] = []
|
||||||
self._lrnDids = self.col.decks.active()[:]
|
self._lrnDids = self.col.decks.active()[:]
|
||||||
|
|
||||||
# sub-day learning
|
# sub-day learning
|
||||||
|
@ -397,7 +399,7 @@ did = ? and queue = {QUEUE_TYPE_DAY_LEARN_RELEARN} and due <= ? limit ?""",
|
||||||
return hooks.scheduler_review_limit_for_single_deck(lim, d)
|
return hooks.scheduler_review_limit_for_single_deck(lim, d)
|
||||||
|
|
||||||
def _resetRev(self) -> None:
|
def _resetRev(self) -> None:
|
||||||
self._revQueue: List[int] = []
|
self._revQueue: List[CardID] = []
|
||||||
|
|
||||||
def _fillRev(self, recursing: bool = False) -> bool:
|
def _fillRev(self, recursing: bool = False) -> bool:
|
||||||
"True if a review card can be fetched."
|
"True if a review card can be fetched."
|
||||||
|
@ -720,7 +722,8 @@ limit ?"""
|
||||||
card.ivl = self._graduatingIvl(card, conf, early)
|
card.ivl = self._graduatingIvl(card, conf, early)
|
||||||
card.due = self.today + card.ivl
|
card.due = self.today + card.ivl
|
||||||
card.factor = conf["initialFactor"]
|
card.factor = conf["initialFactor"]
|
||||||
card.type = card.queue = QUEUE_TYPE_REV
|
card.type = CARD_TYPE_REV
|
||||||
|
card.queue = QUEUE_TYPE_REV
|
||||||
|
|
||||||
def _logLrn(
|
def _logLrn(
|
||||||
self,
|
self,
|
||||||
|
@ -788,7 +791,7 @@ limit ?"""
|
||||||
if card.odid:
|
if card.odid:
|
||||||
card.did = card.odid
|
card.did = card.odid
|
||||||
card.odue = 0
|
card.odue = 0
|
||||||
card.odid = 0
|
card.odid = DeckID(0)
|
||||||
|
|
||||||
def _restorePreviewCard(self, card: Card) -> None:
|
def _restorePreviewCard(self, card: Card) -> None:
|
||||||
assert card.odid
|
assert card.odid
|
||||||
|
@ -803,7 +806,7 @@ limit ?"""
|
||||||
else:
|
else:
|
||||||
card.queue = QUEUE_TYPE_DAY_LEARN_RELEARN
|
card.queue = QUEUE_TYPE_DAY_LEARN_RELEARN
|
||||||
else:
|
else:
|
||||||
card.queue = card.type
|
card.queue = CardQueue(card.type)
|
||||||
|
|
||||||
# Answering a review card
|
# Answering a review card
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
@ -1072,7 +1075,7 @@ limit ?"""
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def _burySiblings(self, card: Card) -> None:
|
def _burySiblings(self, card: Card) -> None:
|
||||||
toBury: List[int] = []
|
toBury: List[CardID] = []
|
||||||
nconf = self._newConf(card)
|
nconf = self._newConf(card)
|
||||||
buryNew = nconf.get("bury", True)
|
buryNew = nconf.get("bury", True)
|
||||||
rconf = self._revConf(card)
|
rconf = self._revConf(card)
|
||||||
|
|
|
@ -19,6 +19,8 @@ import anki # pylint: disable=unused-import
|
||||||
import anki._backend.backend_pb2 as _pb
|
import anki._backend.backend_pb2 as _pb
|
||||||
import anki.collection
|
import anki.collection
|
||||||
from anki.collection import OpChangesWithCount
|
from anki.collection import OpChangesWithCount
|
||||||
|
from anki.decks import DeckID
|
||||||
|
from anki.notes import NoteID
|
||||||
from anki.utils import ids2str
|
from anki.utils import ids2str
|
||||||
|
|
||||||
# public exports
|
# public exports
|
||||||
|
@ -48,7 +50,7 @@ class TagManager:
|
||||||
def clear_unused_tags(self) -> OpChangesWithCount:
|
def clear_unused_tags(self) -> OpChangesWithCount:
|
||||||
return self.col._backend.clear_unused_tags()
|
return self.col._backend.clear_unused_tags()
|
||||||
|
|
||||||
def byDeck(self, did: int, children: bool = False) -> List[str]:
|
def byDeck(self, did: DeckID, children: bool = False) -> List[str]:
|
||||||
basequery = "select n.tags from cards c, notes n WHERE c.nid = n.id"
|
basequery = "select n.tags from cards c, notes n WHERE c.nid = n.id"
|
||||||
if not children:
|
if not children:
|
||||||
query = f"{basequery} AND c.did=?"
|
query = f"{basequery} AND c.did=?"
|
||||||
|
@ -68,11 +70,11 @@ class TagManager:
|
||||||
# Bulk addition/removal from specific notes
|
# Bulk addition/removal from specific notes
|
||||||
#############################################################
|
#############################################################
|
||||||
|
|
||||||
def bulk_add(self, note_ids: Sequence[int], tags: str) -> OpChangesWithCount:
|
def bulk_add(self, note_ids: Sequence[NoteID], tags: str) -> OpChangesWithCount:
|
||||||
"""Add space-separate tags to provided notes, returning changed count."""
|
"""Add space-separate tags to provided notes, returning changed count."""
|
||||||
return self.col._backend.add_note_tags(note_ids=note_ids, tags=tags)
|
return self.col._backend.add_note_tags(note_ids=note_ids, tags=tags)
|
||||||
|
|
||||||
def bulk_remove(self, note_ids: Sequence[int], tags: str) -> OpChangesWithCount:
|
def bulk_remove(self, note_ids: Sequence[NoteID], tags: str) -> OpChangesWithCount:
|
||||||
return self.col._backend.remove_note_tags(note_ids=note_ids, tags=tags)
|
return self.col._backend.remove_note_tags(note_ids=note_ids, tags=tags)
|
||||||
|
|
||||||
# Find&replace
|
# Find&replace
|
||||||
|
@ -175,12 +177,12 @@ class TagManager:
|
||||||
) -> None:
|
) -> None:
|
||||||
print("tags.register() is deprecated and no longer works")
|
print("tags.register() is deprecated and no longer works")
|
||||||
|
|
||||||
def bulkAdd(self, ids: List[int], tags: str, add: bool = True) -> None:
|
def bulkAdd(self, ids: List[NoteID], tags: str, add: bool = True) -> None:
|
||||||
"Add tags in bulk. TAGS is space-separated."
|
"Add tags in bulk. TAGS is space-separated."
|
||||||
if add:
|
if add:
|
||||||
self.bulk_add(ids, tags)
|
self.bulk_add(ids, tags)
|
||||||
else:
|
else:
|
||||||
self.bulk_remove(ids, tags)
|
self.bulk_remove(ids, tags)
|
||||||
|
|
||||||
def bulkRem(self, ids: List[int], tags: str) -> None:
|
def bulkRem(self, ids: List[NoteID], tags: str) -> None:
|
||||||
self.bulkAdd(ids, tags, False)
|
self.bulkAdd(ids, tags, False)
|
||||||
|
|
|
@ -172,7 +172,7 @@ class TemplateRenderContext:
|
||||||
# add (most) special fields
|
# add (most) special fields
|
||||||
fields["Tags"] = self._note.stringTags().strip()
|
fields["Tags"] = self._note.stringTags().strip()
|
||||||
fields["Type"] = self._note_type["name"]
|
fields["Type"] = self._note_type["name"]
|
||||||
fields["Deck"] = self._col.decks.name(self._card.odid or self._card.did)
|
fields["Deck"] = self._col.decks.name(self._card.current_deck_id())
|
||||||
fields["Subdeck"] = DeckManager.basename(fields["Deck"])
|
fields["Subdeck"] = DeckManager.basename(fields["Deck"])
|
||||||
if self._template:
|
if self._template:
|
||||||
fields["Card"] = self._template["name"]
|
fields["Card"] = self._template["name"]
|
||||||
|
|
|
@ -23,7 +23,7 @@ hooks = [
|
||||||
Hook(name="schema_will_change", args=["proceed: bool"], return_type="bool"),
|
Hook(name="schema_will_change", args=["proceed: bool"], return_type="bool"),
|
||||||
Hook(
|
Hook(
|
||||||
name="notes_will_be_deleted",
|
name="notes_will_be_deleted",
|
||||||
args=["col: anki.collection.Collection", "ids: Sequence[int]"],
|
args=["col: anki.collection.Collection", "ids: Sequence[anki.notes.NoteID]"],
|
||||||
legacy_hook="remNotes",
|
legacy_hook="remNotes",
|
||||||
),
|
),
|
||||||
Hook(name="media_files_did_export", args=["count: int"]),
|
Hook(name="media_files_did_export", args=["count: int"]),
|
||||||
|
|
|
@ -8,7 +8,9 @@ import aqt.editor
|
||||||
import aqt.forms
|
import aqt.forms
|
||||||
from anki.collection import OpChanges, SearchNode
|
from anki.collection import OpChanges, SearchNode
|
||||||
from anki.consts import MODEL_CLOZE
|
from anki.consts import MODEL_CLOZE
|
||||||
from anki.notes import DuplicateOrEmptyResult, Note
|
from anki.decks import DeckID
|
||||||
|
from anki.models import NoteTypeID
|
||||||
|
from anki.notes import DuplicateOrEmptyResult, Note, NoteID
|
||||||
from anki.utils import htmlToTextLine, isMac
|
from anki.utils import htmlToTextLine, isMac
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.note_ops import add_note
|
from aqt.note_ops import add_note
|
||||||
|
@ -47,7 +49,7 @@ class AddCards(QDialog):
|
||||||
self.setupEditor()
|
self.setupEditor()
|
||||||
self.setupButtons()
|
self.setupButtons()
|
||||||
self._load_new_note()
|
self._load_new_note()
|
||||||
self.history: List[int] = []
|
self.history: List[NoteID] = []
|
||||||
self._last_added_note: Optional[Note] = None
|
self._last_added_note: Optional[Note] = None
|
||||||
restoreGeom(self, "add")
|
restoreGeom(self, "add")
|
||||||
addCloseShortcut(self)
|
addCloseShortcut(self)
|
||||||
|
@ -64,12 +66,12 @@ class AddCards(QDialog):
|
||||||
self.notetype_chooser = NoteTypeChooser(
|
self.notetype_chooser = NoteTypeChooser(
|
||||||
mw=self.mw,
|
mw=self.mw,
|
||||||
widget=self.form.modelArea,
|
widget=self.form.modelArea,
|
||||||
starting_notetype_id=defaults.notetype_id,
|
starting_notetype_id=NoteTypeID(defaults.notetype_id),
|
||||||
on_button_activated=self.show_notetype_selector,
|
on_button_activated=self.show_notetype_selector,
|
||||||
on_notetype_changed=self.on_notetype_change,
|
on_notetype_changed=self.on_notetype_change,
|
||||||
)
|
)
|
||||||
self.deck_chooser = aqt.deckchooser.DeckChooser(
|
self.deck_chooser = aqt.deckchooser.DeckChooser(
|
||||||
self.mw, self.form.deckArea, starting_deck_id=defaults.deck_id
|
self.mw, self.form.deckArea, starting_deck_id=DeckID(defaults.deck_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
def helpRequested(self) -> None:
|
def helpRequested(self) -> None:
|
||||||
|
@ -109,7 +111,7 @@ class AddCards(QDialog):
|
||||||
def show_notetype_selector(self) -> None:
|
def show_notetype_selector(self) -> None:
|
||||||
self.editor.call_after_note_saved(self.notetype_chooser.choose_notetype)
|
self.editor.call_after_note_saved(self.notetype_chooser.choose_notetype)
|
||||||
|
|
||||||
def on_notetype_change(self, notetype_id: int) -> None:
|
def on_notetype_change(self, notetype_id: NoteTypeID) -> None:
|
||||||
# need to adjust current deck?
|
# need to adjust current deck?
|
||||||
if deck_id := self.mw.col.default_deck_for_notetype(notetype_id):
|
if deck_id := self.mw.col.default_deck_for_notetype(notetype_id):
|
||||||
self.deck_chooser.selected_deck_id = deck_id
|
self.deck_chooser.selected_deck_id = deck_id
|
||||||
|
@ -178,7 +180,7 @@ class AddCards(QDialog):
|
||||||
gui_hooks.add_cards_will_show_history_menu(self, m)
|
gui_hooks.add_cards_will_show_history_menu(self, m)
|
||||||
m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
|
m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
|
||||||
|
|
||||||
def editHistory(self, nid: int) -> None:
|
def editHistory(self, nid: NoteID) -> None:
|
||||||
aqt.dialogs.open("Browser", self.mw, search=(SearchNode(nid=nid),))
|
aqt.dialogs.open("Browser", self.mw, search=(SearchNode(nid=nid),))
|
||||||
|
|
||||||
def add_current_note(self) -> None:
|
def add_current_note(self) -> None:
|
||||||
|
|
|
@ -22,12 +22,13 @@ from typing import (
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
import aqt.forms
|
import aqt.forms
|
||||||
from anki.cards import Card
|
from anki.cards import Card, CardID
|
||||||
from anki.collection import BrowserRow, Collection, Config, OpChanges, SearchNode
|
from anki.collection import BrowserRow, Collection, Config, OpChanges, SearchNode
|
||||||
from anki.consts import *
|
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.models import NoteType
|
from anki.models import NoteType
|
||||||
|
from anki.notes import NoteID
|
||||||
from anki.stats import CardStats
|
from anki.stats import CardStats
|
||||||
from anki.tags import MARKED_TAG
|
from anki.tags import MARKED_TAG
|
||||||
from anki.utils import ids2str, isMac, isWin
|
from anki.utils import ids2str, isMac, isWin
|
||||||
|
@ -96,7 +97,7 @@ class SearchContext:
|
||||||
browser: Browser
|
browser: Browser
|
||||||
order: Union[bool, str] = True
|
order: Union[bool, str] = True
|
||||||
# if set, provided card ids will be used instead of the regular search
|
# if set, provided card ids will be used instead of the regular search
|
||||||
card_ids: Optional[Sequence[int]] = None
|
card_ids: Optional[Sequence[CardID]] = None
|
||||||
|
|
||||||
|
|
||||||
# Data model
|
# Data model
|
||||||
|
@ -169,13 +170,13 @@ class DataModel(QAbstractTableModel):
|
||||||
self.activeCols: List[str] = self.col.get_config(
|
self.activeCols: List[str] = self.col.get_config(
|
||||||
"activeCols", ["noteFld", "template", "cardDue", "deck"]
|
"activeCols", ["noteFld", "template", "cardDue", "deck"]
|
||||||
)
|
)
|
||||||
self.cards: Sequence[int] = []
|
self.cards: Sequence[CardID] = []
|
||||||
self._rows: Dict[int, CellRow] = {}
|
self._rows: Dict[int, CellRow] = {}
|
||||||
self._last_refresh = 0.0
|
self._last_refresh = 0.0
|
||||||
# serve stale content to avoid hitting the DB?
|
# serve stale content to avoid hitting the DB?
|
||||||
self.block_updates = False
|
self.block_updates = False
|
||||||
|
|
||||||
def get_id(self, index: QModelIndex) -> int:
|
def get_id(self, index: QModelIndex) -> CardID:
|
||||||
return self.cards[index.row()]
|
return self.cards[index.row()]
|
||||||
|
|
||||||
def get_cell(self, index: QModelIndex) -> Cell:
|
def get_cell(self, index: QModelIndex) -> Cell:
|
||||||
|
@ -197,7 +198,7 @@ class DataModel(QAbstractTableModel):
|
||||||
self._rows[cid] = self._fetch_row_from_backend(cid)
|
self._rows[cid] = self._fetch_row_from_backend(cid)
|
||||||
return self._rows[cid]
|
return self._rows[cid]
|
||||||
|
|
||||||
def _fetch_row_from_backend(self, cid: int) -> CellRow:
|
def _fetch_row_from_backend(self, cid: CardID) -> CellRow:
|
||||||
try:
|
try:
|
||||||
row = CellRow(*self.col.browser_row_for_card(cid))
|
row = CellRow(*self.col.browser_row_for_card(cid))
|
||||||
except NotFoundError:
|
except NotFoundError:
|
||||||
|
@ -1048,13 +1049,13 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
# Menu helpers
|
# Menu helpers
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def selected_cards(self) -> List[int]:
|
def selected_cards(self) -> List[CardID]:
|
||||||
return [
|
return [
|
||||||
self.model.cards[idx.row()]
|
self.model.cards[idx.row()]
|
||||||
for idx in self.form.tableView.selectionModel().selectedRows()
|
for idx in self.form.tableView.selectionModel().selectedRows()
|
||||||
]
|
]
|
||||||
|
|
||||||
def selected_notes(self) -> List[int]:
|
def selected_notes(self) -> List[NoteID]:
|
||||||
return self.col.db.list(
|
return self.col.db.list(
|
||||||
"""
|
"""
|
||||||
select distinct nid from cards
|
select distinct nid from cards
|
||||||
|
@ -1067,13 +1068,13 @@ where id in %s"""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def selectedNotesAsCards(self) -> List[int]:
|
def selectedNotesAsCards(self) -> List[CardID]:
|
||||||
return self.col.db.list(
|
return self.col.db.list(
|
||||||
"select id from cards where nid in (%s)"
|
"select id from cards where nid in (%s)"
|
||||||
% ",".join([str(s) for s in self.selected_notes()])
|
% ",".join([str(s) for s in self.selected_notes()])
|
||||||
)
|
)
|
||||||
|
|
||||||
def oneModelNotes(self) -> List[int]:
|
def oneModelNotes(self) -> List[NoteID]:
|
||||||
sf = self.selected_notes()
|
sf = self.selected_notes()
|
||||||
if not sf:
|
if not sf:
|
||||||
return []
|
return []
|
||||||
|
@ -1589,7 +1590,7 @@ where id in %s"""
|
||||||
def onCardList(self) -> None:
|
def onCardList(self) -> None:
|
||||||
self.form.tableView.setFocus()
|
self.form.tableView.setFocus()
|
||||||
|
|
||||||
def focusCid(self, cid: int) -> None:
|
def focusCid(self, cid: CardID) -> None:
|
||||||
try:
|
try:
|
||||||
row = list(self.model.cards).index(cid)
|
row = list(self.model.cards).index(cid)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@ -1603,7 +1604,7 @@ where id in %s"""
|
||||||
|
|
||||||
|
|
||||||
class ChangeModel(QDialog):
|
class ChangeModel(QDialog):
|
||||||
def __init__(self, browser: Browser, nids: List[int]) -> None:
|
def __init__(self, browser: Browser, nids: List[NoteID]) -> None:
|
||||||
QDialog.__init__(self, browser)
|
QDialog.__init__(self, browser)
|
||||||
self.browser = browser
|
self.browser = browser
|
||||||
self.nids = nids
|
self.nids = nids
|
||||||
|
|
|
@ -5,12 +5,14 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
|
|
||||||
|
from anki.cards import CardID
|
||||||
|
from anki.decks import DeckID
|
||||||
from aqt import AnkiQt
|
from aqt import AnkiQt
|
||||||
|
|
||||||
|
|
||||||
def set_card_deck(*, mw: AnkiQt, card_ids: Sequence[int], deck_id: int) -> None:
|
def set_card_deck(*, mw: AnkiQt, card_ids: Sequence[CardID], deck_id: DeckID) -> None:
|
||||||
mw.perform_op(lambda: mw.col.set_deck(card_ids, deck_id))
|
mw.perform_op(lambda: mw.col.set_deck(card_ids, deck_id))
|
||||||
|
|
||||||
|
|
||||||
def set_card_flag(*, mw: AnkiQt, card_ids: Sequence[int], flag: int) -> None:
|
def set_card_flag(*, mw: AnkiQt, card_ids: Sequence[CardID], flag: int) -> None:
|
||||||
mw.perform_op(lambda: mw.col.set_user_flag_for_cards(flag, card_ids))
|
mw.perform_op(lambda: mw.col.set_user_flag_for_cards(flag, card_ids))
|
||||||
|
|
|
@ -40,7 +40,7 @@ class DeckBrowserContent:
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RenderDeckNodeContext:
|
class RenderDeckNodeContext:
|
||||||
current_deck_id: int
|
current_deck_id: DeckID
|
||||||
|
|
||||||
|
|
||||||
class DeckBrowser:
|
class DeckBrowser:
|
||||||
|
@ -101,7 +101,7 @@ class DeckBrowser:
|
||||||
source, target = arg.split(",")
|
source, target = arg.split(",")
|
||||||
self._handle_drag_and_drop(DeckID(int(source)), DeckID(int(target or 0)))
|
self._handle_drag_and_drop(DeckID(int(source)), DeckID(int(target or 0)))
|
||||||
elif cmd == "collapse":
|
elif cmd == "collapse":
|
||||||
self._collapse(int(arg))
|
self._collapse(DeckID(int(arg)))
|
||||||
elif cmd == "v2upgrade":
|
elif cmd == "v2upgrade":
|
||||||
self._confirm_upgrade()
|
self._confirm_upgrade()
|
||||||
elif cmd == "v2upgradeinfo":
|
elif cmd == "v2upgradeinfo":
|
||||||
|
@ -112,7 +112,7 @@ class DeckBrowser:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _selDeck(self, did: str) -> None:
|
def _selDeck(self, did: str) -> None:
|
||||||
self.mw.col.decks.select(int(did))
|
self.mw.col.decks.select(DeckID(int(did)))
|
||||||
self.mw.onOverview()
|
self.mw.onOverview()
|
||||||
|
|
||||||
# HTML generation
|
# HTML generation
|
||||||
|
@ -255,13 +255,13 @@ class DeckBrowser:
|
||||||
a = m.addAction(tr(TR.ACTIONS_OPTIONS))
|
a = m.addAction(tr(TR.ACTIONS_OPTIONS))
|
||||||
qconnect(a.triggered, lambda b, did=did: self._options(DeckID(int(did))))
|
qconnect(a.triggered, lambda b, did=did: self._options(DeckID(int(did))))
|
||||||
a = m.addAction(tr(TR.ACTIONS_EXPORT))
|
a = m.addAction(tr(TR.ACTIONS_EXPORT))
|
||||||
qconnect(a.triggered, lambda b, did=did: self._export(int(did)))
|
qconnect(a.triggered, lambda b, did=did: self._export(DeckID(int(did))))
|
||||||
a = m.addAction(tr(TR.ACTIONS_DELETE))
|
a = m.addAction(tr(TR.ACTIONS_DELETE))
|
||||||
qconnect(a.triggered, lambda b, did=did: self._delete(DeckID(int(did))))
|
qconnect(a.triggered, lambda b, did=did: self._delete(DeckID(int(did))))
|
||||||
gui_hooks.deck_browser_will_show_options_menu(m, int(did))
|
gui_hooks.deck_browser_will_show_options_menu(m, int(did))
|
||||||
m.exec_(QCursor.pos())
|
m.exec_(QCursor.pos())
|
||||||
|
|
||||||
def _export(self, did: int) -> None:
|
def _export(self, did: DeckID) -> None:
|
||||||
self.mw.onExport(did=did)
|
self.mw.onExport(did=did)
|
||||||
|
|
||||||
def _rename(self, did: DeckID) -> None:
|
def _rename(self, did: DeckID) -> None:
|
||||||
|
@ -279,7 +279,7 @@ class DeckBrowser:
|
||||||
self.mw.col.decks.select(did)
|
self.mw.col.decks.select(did)
|
||||||
self.mw.onDeckConf()
|
self.mw.onDeckConf()
|
||||||
|
|
||||||
def _collapse(self, did: int) -> None:
|
def _collapse(self, did: DeckID) -> None:
|
||||||
self.mw.col.decks.collapse(did)
|
self.mw.col.decks.collapse(did)
|
||||||
node = self.mw.col.decks.find_deck_in_tree(self._dueTree, did)
|
node = self.mw.col.decks.find_deck_in_tree(self._dueTree, did)
|
||||||
if node:
|
if node:
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from anki.decks import DEFAULT_DECK_ID, DeckID
|
||||||
from aqt import AnkiQt
|
from aqt import AnkiQt
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import TR, HelpPage, shortcut, tr
|
from aqt.utils import TR, HelpPage, shortcut, tr
|
||||||
|
@ -14,17 +15,17 @@ class DeckChooser(QHBoxLayout):
|
||||||
mw: AnkiQt,
|
mw: AnkiQt,
|
||||||
widget: QWidget,
|
widget: QWidget,
|
||||||
label: bool = True,
|
label: bool = True,
|
||||||
starting_deck_id: Optional[int] = None,
|
starting_deck_id: Optional[DeckID] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
QHBoxLayout.__init__(self)
|
QHBoxLayout.__init__(self)
|
||||||
self._widget = widget # type: ignore
|
self._widget = widget # type: ignore
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
self._setup_ui(show_label=label)
|
self._setup_ui(show_label=label)
|
||||||
|
|
||||||
self._selected_deck_id = 0
|
self._selected_deck_id = DeckID(0)
|
||||||
# default to current deck if starting id not provided
|
# default to current deck if starting id not provided
|
||||||
if starting_deck_id is None:
|
if starting_deck_id is None:
|
||||||
starting_deck_id = self.mw.col.get_config("curDeck", default=1) or 1
|
starting_deck_id = DeckID(self.mw.col.get_config("curDeck", default=1) or 1)
|
||||||
self.selected_deck_id = starting_deck_id
|
self.selected_deck_id = starting_deck_id
|
||||||
|
|
||||||
def _setup_ui(self, show_label: bool) -> None:
|
def _setup_ui(self, show_label: bool) -> None:
|
||||||
|
@ -56,13 +57,13 @@ class DeckChooser(QHBoxLayout):
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def selected_deck_id(self) -> int:
|
def selected_deck_id(self) -> DeckID:
|
||||||
self._ensure_selected_deck_valid()
|
self._ensure_selected_deck_valid()
|
||||||
|
|
||||||
return self._selected_deck_id
|
return self._selected_deck_id
|
||||||
|
|
||||||
@selected_deck_id.setter
|
@selected_deck_id.setter
|
||||||
def selected_deck_id(self, id: int) -> None:
|
def selected_deck_id(self, id: DeckID) -> None:
|
||||||
if id != self._selected_deck_id:
|
if id != self._selected_deck_id:
|
||||||
self._selected_deck_id = id
|
self._selected_deck_id = id
|
||||||
self._ensure_selected_deck_valid()
|
self._ensure_selected_deck_valid()
|
||||||
|
@ -70,7 +71,7 @@ class DeckChooser(QHBoxLayout):
|
||||||
|
|
||||||
def _ensure_selected_deck_valid(self) -> None:
|
def _ensure_selected_deck_valid(self) -> None:
|
||||||
if not self.mw.col.decks.get(self._selected_deck_id, default=False):
|
if not self.mw.col.decks.get(self._selected_deck_id, default=False):
|
||||||
self.selected_deck_id = 1
|
self.selected_deck_id = DEFAULT_DECK_ID
|
||||||
|
|
||||||
def _update_button_label(self) -> None:
|
def _update_button_label(self) -> None:
|
||||||
self.deck.setText(self.selected_deck_name().replace("&", "&&"))
|
self.deck.setText(self.selected_deck_name().replace("&", "&&"))
|
||||||
|
@ -103,7 +104,7 @@ class DeckChooser(QHBoxLayout):
|
||||||
onDeckChange = choose_deck
|
onDeckChange = choose_deck
|
||||||
deckName = selected_deck_name
|
deckName = selected_deck_name
|
||||||
|
|
||||||
def selectedId(self) -> int:
|
def selectedId(self) -> DeckID:
|
||||||
return self.selected_deck_id
|
return self.selected_deck_id
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
|
|
|
@ -5,9 +5,10 @@ from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
from typing import Any
|
from typing import Any, List
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
|
from anki.cards import CardID
|
||||||
from anki.collection import EmptyCardsReport
|
from anki.collection import EmptyCardsReport
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.qt import QDialog, QDialogButtonBox, qconnect
|
from aqt.qt import QDialog, QDialogButtonBox, qconnect
|
||||||
|
@ -88,14 +89,14 @@ class EmptyCardsDialog(QDialog):
|
||||||
self.mw.taskman.run_in_background(delete, on_done)
|
self.mw.taskman.run_in_background(delete, on_done)
|
||||||
|
|
||||||
def _delete_cards(self, keep_notes: bool) -> int:
|
def _delete_cards(self, keep_notes: bool) -> int:
|
||||||
to_delete = []
|
to_delete: List[CardID] = []
|
||||||
note: EmptyCardsReport.NoteWithEmptyCards
|
note: EmptyCardsReport.NoteWithEmptyCards
|
||||||
for note in self.report.notes:
|
for note in self.report.notes:
|
||||||
if keep_notes and note.will_delete_note:
|
if keep_notes and note.will_delete_note:
|
||||||
# leave first card
|
# leave first card
|
||||||
to_delete.extend(note.card_ids[1:])
|
to_delete.extend([CardID(id) for id in note.card_ids[1:]])
|
||||||
else:
|
else:
|
||||||
to_delete.extend(note.card_ids)
|
to_delete.extend([CardID(id) for id in note.card_ids])
|
||||||
|
|
||||||
self.mw.col.remove_cards_and_orphaned_notes(to_delete)
|
self.mw.col.remove_cards_and_orphaned_notes(to_delete)
|
||||||
return len(to_delete)
|
return len(to_delete)
|
||||||
|
|
|
@ -11,6 +11,8 @@ from typing import List, Optional
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
|
from anki.cards import CardID
|
||||||
|
from anki.decks import DeckID
|
||||||
from anki.exporting import Exporter, exporters
|
from anki.exporting import Exporter, exporters
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
|
@ -28,8 +30,8 @@ class ExportDialog(QDialog):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
mw: aqt.main.AnkiQt,
|
mw: aqt.main.AnkiQt,
|
||||||
did: Optional[int] = None,
|
did: Optional[DeckID] = None,
|
||||||
cids: Optional[List[int]] = None,
|
cids: Optional[List[CardID]] = None,
|
||||||
):
|
):
|
||||||
QDialog.__init__(self, mw, Qt.Window)
|
QDialog.__init__(self, mw, Qt.Window)
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
|
@ -42,7 +44,7 @@ class ExportDialog(QDialog):
|
||||||
self.setup(did)
|
self.setup(did)
|
||||||
self.exec_()
|
self.exec_()
|
||||||
|
|
||||||
def setup(self, did: Optional[int]) -> None:
|
def setup(self, did: Optional[DeckID]) -> None:
|
||||||
self.exporters = exporters(self.col)
|
self.exporters = exporters(self.col)
|
||||||
# if a deck specified, start with .apkg type selected
|
# if a deck specified, start with .apkg type selected
|
||||||
idx = 0
|
idx = 0
|
||||||
|
|
|
@ -7,6 +7,7 @@ from typing import List, Optional, Sequence
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki.lang import TR
|
from anki.lang import TR
|
||||||
|
from anki.notes import NoteID
|
||||||
from aqt import AnkiQt, QWidget
|
from aqt import AnkiQt, QWidget
|
||||||
from aqt.qt import QDialog, Qt
|
from aqt.qt import QDialog, Qt
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
|
@ -31,7 +32,7 @@ def find_and_replace(
|
||||||
*,
|
*,
|
||||||
mw: AnkiQt,
|
mw: AnkiQt,
|
||||||
parent: QWidget,
|
parent: QWidget,
|
||||||
note_ids: Sequence[int],
|
note_ids: Sequence[NoteID],
|
||||||
search: str,
|
search: str,
|
||||||
replacement: str,
|
replacement: str,
|
||||||
regex: bool,
|
regex: bool,
|
||||||
|
@ -82,7 +83,9 @@ def find_and_replace_tag(
|
||||||
class FindAndReplaceDialog(QDialog):
|
class FindAndReplaceDialog(QDialog):
|
||||||
COMBO_NAME = "BrowserFindAndReplace"
|
COMBO_NAME = "BrowserFindAndReplace"
|
||||||
|
|
||||||
def __init__(self, parent: QWidget, *, mw: AnkiQt, note_ids: Sequence[int]) -> None:
|
def __init__(
|
||||||
|
self, parent: QWidget, *, mw: AnkiQt, note_ids: Sequence[NoteID]
|
||||||
|
) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
self.note_ids = note_ids
|
self.note_ids = note_ids
|
||||||
|
|
|
@ -52,8 +52,9 @@ from anki.collection import (
|
||||||
UndoResult,
|
UndoResult,
|
||||||
UndoStatus,
|
UndoStatus,
|
||||||
)
|
)
|
||||||
from anki.decks import DeckDict
|
from anki.decks import DeckDict, DeckID
|
||||||
from anki.hooks import runHook
|
from anki.hooks import runHook
|
||||||
|
from anki.notes import NoteID
|
||||||
from anki.sound import AVTag, SoundOrVideoTag
|
from anki.sound import AVTag, SoundOrVideoTag
|
||||||
from anki.types import assert_exhaustive
|
from anki.types import assert_exhaustive
|
||||||
from anki.utils import devMode, ids2str, intTime, isMac, isWin, splitFields
|
from anki.utils import devMode, ids2str, intTime, isMac, isWin, splitFields
|
||||||
|
@ -1381,7 +1382,7 @@ title="%s" %s>%s</button>""" % (
|
||||||
|
|
||||||
aqt.importing.onImport(self)
|
aqt.importing.onImport(self)
|
||||||
|
|
||||||
def onExport(self, did: Optional[int] = None) -> None:
|
def onExport(self, did: Optional[DeckID] = None) -> None:
|
||||||
import aqt.exporting
|
import aqt.exporting
|
||||||
|
|
||||||
aqt.exporting.ExportDialog(self, did=did)
|
aqt.exporting.ExportDialog(self, did=did)
|
||||||
|
@ -1534,7 +1535,7 @@ title="%s" %s>%s</button>""" % (
|
||||||
# Log note deletion
|
# Log note deletion
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def onRemNotes(self, col: Collection, nids: Sequence[int]) -> None:
|
def onRemNotes(self, col: Collection, nids: Sequence[NoteID]) -> None:
|
||||||
path = os.path.join(self.pm.profileFolder(), "deleted.txt")
|
path = os.path.join(self.pm.profileFolder(), "deleted.txt")
|
||||||
existed = os.path.exists(path)
|
existed = os.path.exists(path)
|
||||||
with open(path, "ab") as f:
|
with open(path, "ab") as f:
|
||||||
|
|
|
@ -8,7 +8,7 @@ from typing import Any, List, Optional, Sequence
|
||||||
import aqt.clayout
|
import aqt.clayout
|
||||||
from anki import stdmodels
|
from anki import stdmodels
|
||||||
from anki.lang import without_unicode_isolation
|
from anki.lang import without_unicode_isolation
|
||||||
from anki.models import NoteType, NoteTypeNameIDUseCount
|
from anki.models import NoteType, NoteTypeID, NoteTypeNameIDUseCount
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
|
@ -33,7 +33,7 @@ class Models(QDialog):
|
||||||
mw: AnkiQt,
|
mw: AnkiQt,
|
||||||
parent: Optional[QWidget] = None,
|
parent: Optional[QWidget] = None,
|
||||||
fromMain: bool = False,
|
fromMain: bool = False,
|
||||||
selected_notetype_id: Optional[int] = None,
|
selected_notetype_id: Optional[NoteTypeID] = None,
|
||||||
):
|
):
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
parent = parent or mw
|
parent = parent or mw
|
||||||
|
|
|
@ -5,7 +5,8 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Callable, Sequence
|
from typing import Callable, Sequence
|
||||||
|
|
||||||
from anki.notes import Note
|
from anki.decks import DeckID
|
||||||
|
from anki.notes import Note, NoteID
|
||||||
from aqt import AnkiQt
|
from aqt import AnkiQt
|
||||||
from aqt.main import PerformOpOptionalSuccessCallback
|
from aqt.main import PerformOpOptionalSuccessCallback
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ def add_note(
|
||||||
*,
|
*,
|
||||||
mw: AnkiQt,
|
mw: AnkiQt,
|
||||||
note: Note,
|
note: Note,
|
||||||
target_deck_id: int,
|
target_deck_id: DeckID,
|
||||||
success: PerformOpOptionalSuccessCallback = None,
|
success: PerformOpOptionalSuccessCallback = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
mw.perform_op(lambda: mw.col.add_note(note, target_deck_id), success=success)
|
mw.perform_op(lambda: mw.col.add_note(note, target_deck_id), success=success)
|
||||||
|
@ -30,7 +31,7 @@ def update_note(*, mw: AnkiQt, note: Note, after_hooks: Callable[[], None]) -> N
|
||||||
def remove_notes(
|
def remove_notes(
|
||||||
*,
|
*,
|
||||||
mw: AnkiQt,
|
mw: AnkiQt,
|
||||||
note_ids: Sequence[int],
|
note_ids: Sequence[NoteID],
|
||||||
success: PerformOpOptionalSuccessCallback = None,
|
success: PerformOpOptionalSuccessCallback = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
mw.perform_op(lambda: mw.col.remove_notes(note_ids), success=success)
|
mw.perform_op(lambda: mw.col.remove_notes(note_ids), success=success)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
# 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 typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from anki.models import NoteTypeID
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import TR, HelpPage, shortcut, tr
|
from aqt.utils import TR, HelpPage, shortcut, tr
|
||||||
|
@ -22,14 +23,16 @@ class NoteTypeChooser(QHBoxLayout):
|
||||||
deleted.
|
deleted.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
_selected_notetype_id: NoteTypeID
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
mw: AnkiQt,
|
mw: AnkiQt,
|
||||||
widget: QWidget,
|
widget: QWidget,
|
||||||
starting_notetype_id: int,
|
starting_notetype_id: NoteTypeID,
|
||||||
on_button_activated: Optional[Callable[[], None]] = None,
|
on_button_activated: Optional[Callable[[], None]] = None,
|
||||||
on_notetype_changed: Optional[Callable[[int], None]] = None,
|
on_notetype_changed: Optional[Callable[[NoteTypeID], None]] = None,
|
||||||
show_prefix_label: bool = True,
|
show_prefix_label: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
QHBoxLayout.__init__(self)
|
QHBoxLayout.__init__(self)
|
||||||
|
@ -41,7 +44,7 @@ class NoteTypeChooser(QHBoxLayout):
|
||||||
self.on_button_activated = self.choose_notetype
|
self.on_button_activated = self.choose_notetype
|
||||||
self._setup_ui(show_label=show_prefix_label)
|
self._setup_ui(show_label=show_prefix_label)
|
||||||
gui_hooks.state_did_reset.append(self.reset_state)
|
gui_hooks.state_did_reset.append(self.reset_state)
|
||||||
self._selected_notetype_id = 0
|
self._selected_notetype_id = NoteTypeID(0)
|
||||||
# triggers UI update; avoid firing changed hook on startup
|
# triggers UI update; avoid firing changed hook on startup
|
||||||
self.on_notetype_changed = None
|
self.on_notetype_changed = None
|
||||||
self.selected_notetype_id = starting_notetype_id
|
self.selected_notetype_id = starting_notetype_id
|
||||||
|
@ -118,7 +121,7 @@ class NoteTypeChooser(QHBoxLayout):
|
||||||
self.selected_notetype_id = id
|
self.selected_notetype_id = id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def selected_notetype_id(self) -> int:
|
def selected_notetype_id(self) -> NoteTypeID:
|
||||||
# theoretically this should not be necessary, as we're listening to
|
# theoretically this should not be necessary, as we're listening to
|
||||||
# resets
|
# resets
|
||||||
self._ensure_selected_notetype_valid()
|
self._ensure_selected_notetype_valid()
|
||||||
|
@ -126,7 +129,7 @@ class NoteTypeChooser(QHBoxLayout):
|
||||||
return self._selected_notetype_id
|
return self._selected_notetype_id
|
||||||
|
|
||||||
@selected_notetype_id.setter
|
@selected_notetype_id.setter
|
||||||
def selected_notetype_id(self, id: int) -> None:
|
def selected_notetype_id(self, id: NoteTypeID) -> None:
|
||||||
if id != self._selected_notetype_id:
|
if id != self._selected_notetype_id:
|
||||||
self._selected_notetype_id = id
|
self._selected_notetype_id = id
|
||||||
self._ensure_selected_notetype_valid()
|
self._ensure_selected_notetype_valid()
|
||||||
|
@ -139,7 +142,9 @@ class NoteTypeChooser(QHBoxLayout):
|
||||||
|
|
||||||
def _ensure_selected_notetype_valid(self) -> None:
|
def _ensure_selected_notetype_valid(self) -> None:
|
||||||
if not self.mw.col.models.get(self._selected_notetype_id):
|
if not self.mw.col.models.get(self._selected_notetype_id):
|
||||||
self.selected_notetype_id = self.mw.col.models.all_names_and_ids()[0].id
|
self.selected_notetype_id = NoteTypeID(
|
||||||
|
self.mw.col.models.all_names_and_ids()[0].id
|
||||||
|
)
|
||||||
|
|
||||||
def _update_button_label(self) -> None:
|
def _update_button_label(self) -> None:
|
||||||
self.button.setText(self.selected_notetype_name().replace("&", "&&"))
|
self.button.setText(self.selected_notetype_name().replace("&", "&&"))
|
||||||
|
|
|
@ -13,7 +13,7 @@ from typing import Any, Callable, List, Match, Optional, Sequence, Tuple, Union
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki.cards import Card
|
from anki.cards import Card, CardID
|
||||||
from anki.collection import Config, OpChanges
|
from anki.collection import Config, OpChanges
|
||||||
from anki.tags import MARKED_TAG
|
from anki.tags import MARKED_TAG
|
||||||
from anki.utils import stripHTML
|
from anki.utils import stripHTML
|
||||||
|
@ -74,7 +74,7 @@ class Reviewer:
|
||||||
self.card: Optional[Card] = None
|
self.card: Optional[Card] = None
|
||||||
self.cardQueue: List[Card] = []
|
self.cardQueue: List[Card] = []
|
||||||
self.hadCardQueue = False
|
self.hadCardQueue = False
|
||||||
self._answeredIds: List[int] = []
|
self._answeredIds: List[CardID] = []
|
||||||
self._recordedAudio: Optional[str] = None
|
self._recordedAudio: Optional[str] = None
|
||||||
self.typeCorrect: str = None # web init happens before this is set
|
self.typeCorrect: str = None # web init happens before this is set
|
||||||
self.state: Optional[str] = None
|
self.state: Optional[str] = None
|
||||||
|
@ -834,7 +834,7 @@ time = %(time)d;
|
||||||
qconnect(a.triggered, func)
|
qconnect(a.triggered, func)
|
||||||
|
|
||||||
def onOptions(self) -> None:
|
def onOptions(self) -> None:
|
||||||
self.mw.onDeckConf(self.mw.col.decks.get(self.card.odid or self.card.did))
|
self.mw.onDeckConf(self.mw.col.decks.get(self.card.current_deck_id()))
|
||||||
|
|
||||||
def set_flag_on_current_card(self, desired_flag: int) -> None:
|
def set_flag_on_current_card(self, desired_flag: int) -> None:
|
||||||
# need to toggle off?
|
# need to toggle off?
|
||||||
|
|
|
@ -6,9 +6,11 @@ from __future__ import annotations
|
||||||
from typing import List, Optional, Sequence
|
from typing import List, Optional, Sequence
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
|
from anki.cards import CardID
|
||||||
from anki.collection import CARD_TYPE_NEW, Config
|
from anki.collection import CARD_TYPE_NEW, Config
|
||||||
from anki.decks import DeckID
|
from anki.decks import DeckID
|
||||||
from anki.lang import TR
|
from anki.lang import TR
|
||||||
|
from anki.notes import NoteID
|
||||||
from anki.scheduler import FilteredDeckForUpdate
|
from anki.scheduler import FilteredDeckForUpdate
|
||||||
from aqt import AnkiQt
|
from aqt import AnkiQt
|
||||||
from aqt.main import PerformOpOptionalSuccessCallback
|
from aqt.main import PerformOpOptionalSuccessCallback
|
||||||
|
@ -20,7 +22,7 @@ def set_due_date_dialog(
|
||||||
*,
|
*,
|
||||||
mw: aqt.AnkiQt,
|
mw: aqt.AnkiQt,
|
||||||
parent: QWidget,
|
parent: QWidget,
|
||||||
card_ids: List[int],
|
card_ids: List[CardID],
|
||||||
config_key: Optional[Config.String.Key.V],
|
config_key: Optional[Config.String.Key.V],
|
||||||
) -> None:
|
) -> None:
|
||||||
if not card_ids:
|
if not card_ids:
|
||||||
|
@ -53,7 +55,7 @@ def set_due_date_dialog(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def forget_cards(*, mw: aqt.AnkiQt, parent: QWidget, card_ids: List[int]) -> None:
|
def forget_cards(*, mw: aqt.AnkiQt, parent: QWidget, card_ids: List[CardID]) -> None:
|
||||||
if not card_ids:
|
if not card_ids:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -66,7 +68,7 @@ def forget_cards(*, mw: aqt.AnkiQt, parent: QWidget, card_ids: List[int]) -> Non
|
||||||
|
|
||||||
|
|
||||||
def reposition_new_cards_dialog(
|
def reposition_new_cards_dialog(
|
||||||
*, mw: AnkiQt, parent: QWidget, card_ids: Sequence[int]
|
*, mw: AnkiQt, parent: QWidget, card_ids: Sequence[CardID]
|
||||||
) -> None:
|
) -> None:
|
||||||
assert mw.col.db
|
assert mw.col.db
|
||||||
row = mw.col.db.first(
|
row = mw.col.db.first(
|
||||||
|
@ -111,7 +113,7 @@ def reposition_new_cards(
|
||||||
*,
|
*,
|
||||||
mw: AnkiQt,
|
mw: AnkiQt,
|
||||||
parent: QWidget,
|
parent: QWidget,
|
||||||
card_ids: Sequence[int],
|
card_ids: Sequence[CardID],
|
||||||
starting_from: int,
|
starting_from: int,
|
||||||
step_size: int,
|
step_size: int,
|
||||||
randomize: bool,
|
randomize: bool,
|
||||||
|
@ -134,7 +136,7 @@ def reposition_new_cards(
|
||||||
def suspend_cards(
|
def suspend_cards(
|
||||||
*,
|
*,
|
||||||
mw: AnkiQt,
|
mw: AnkiQt,
|
||||||
card_ids: Sequence[int],
|
card_ids: Sequence[CardID],
|
||||||
success: PerformOpOptionalSuccessCallback = None,
|
success: PerformOpOptionalSuccessCallback = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
mw.perform_op(lambda: mw.col.sched.suspend_cards(card_ids), success=success)
|
mw.perform_op(lambda: mw.col.sched.suspend_cards(card_ids), success=success)
|
||||||
|
@ -143,7 +145,7 @@ def suspend_cards(
|
||||||
def suspend_note(
|
def suspend_note(
|
||||||
*,
|
*,
|
||||||
mw: AnkiQt,
|
mw: AnkiQt,
|
||||||
note_id: int,
|
note_id: NoteID,
|
||||||
success: PerformOpOptionalSuccessCallback = None,
|
success: PerformOpOptionalSuccessCallback = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
mw.taskman.run_in_background(
|
mw.taskman.run_in_background(
|
||||||
|
@ -152,14 +154,14 @@ def suspend_note(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def unsuspend_cards(*, mw: AnkiQt, card_ids: Sequence[int]) -> None:
|
def unsuspend_cards(*, mw: AnkiQt, card_ids: Sequence[CardID]) -> None:
|
||||||
mw.perform_op(lambda: mw.col.sched.unsuspend_cards(card_ids))
|
mw.perform_op(lambda: mw.col.sched.unsuspend_cards(card_ids))
|
||||||
|
|
||||||
|
|
||||||
def bury_cards(
|
def bury_cards(
|
||||||
*,
|
*,
|
||||||
mw: AnkiQt,
|
mw: AnkiQt,
|
||||||
card_ids: Sequence[int],
|
card_ids: Sequence[CardID],
|
||||||
success: PerformOpOptionalSuccessCallback = None,
|
success: PerformOpOptionalSuccessCallback = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
mw.perform_op(lambda: mw.col.sched.bury_cards(card_ids), success=success)
|
mw.perform_op(lambda: mw.col.sched.bury_cards(card_ids), success=success)
|
||||||
|
@ -168,7 +170,7 @@ def bury_cards(
|
||||||
def bury_note(
|
def bury_note(
|
||||||
*,
|
*,
|
||||||
mw: AnkiQt,
|
mw: AnkiQt,
|
||||||
note_id: int,
|
note_id: NoteID,
|
||||||
success: PerformOpOptionalSuccessCallback = None,
|
success: PerformOpOptionalSuccessCallback = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
mw.taskman.run_in_background(
|
mw.taskman.run_in_background(
|
||||||
|
|
|
@ -9,6 +9,7 @@ from typing import Dict, Iterable, List, Optional, Tuple, cast
|
||||||
import aqt
|
import aqt
|
||||||
from anki.collection import Config, OpChanges, SearchJoiner, SearchNode
|
from anki.collection import Config, OpChanges, SearchJoiner, SearchNode
|
||||||
from anki.decks import DeckID, DeckTreeNode
|
from anki.decks import DeckID, DeckTreeNode
|
||||||
|
from anki.models import NoteTypeID
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
from anki.tags import TagTreeNode
|
from anki.tags import TagTreeNode
|
||||||
from anki.types import assert_exhaustive
|
from anki.types import assert_exhaustive
|
||||||
|
@ -976,7 +977,7 @@ class SidebarTreeView(QTreeView):
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
|
|
||||||
def toggle_expand() -> Callable[[bool], None]:
|
def toggle_expand() -> Callable[[bool], None]:
|
||||||
did = node.deck_id # pylint: disable=cell-var-from-loop
|
did = DeckID(node.deck_id) # pylint: disable=cell-var-from-loop
|
||||||
return lambda _: self.mw.col.decks.collapseBrowser(did)
|
return lambda _: self.mw.col.decks.collapseBrowser(did)
|
||||||
|
|
||||||
item = SidebarItem(
|
item = SidebarItem(
|
||||||
|
@ -1158,7 +1159,7 @@ class SidebarTreeView(QTreeView):
|
||||||
###########################
|
###########################
|
||||||
|
|
||||||
def rename_deck(self, item: SidebarItem, new_name: str) -> None:
|
def rename_deck(self, item: SidebarItem, new_name: str) -> None:
|
||||||
deck = self.mw.col.decks.get(item.id)
|
deck = self.mw.col.decks.get(DeckID(item.id))
|
||||||
if not new_name:
|
if not new_name:
|
||||||
return
|
return
|
||||||
new_name = item.name_prefix + new_name
|
new_name = item.name_prefix + new_name
|
||||||
|
@ -1291,11 +1292,14 @@ class SidebarTreeView(QTreeView):
|
||||||
|
|
||||||
def manage_notetype(self, item: SidebarItem) -> None:
|
def manage_notetype(self, item: SidebarItem) -> None:
|
||||||
Models(
|
Models(
|
||||||
self.mw, parent=self.browser, fromMain=True, selected_notetype_id=item.id
|
self.mw,
|
||||||
|
parent=self.browser,
|
||||||
|
fromMain=True,
|
||||||
|
selected_notetype_id=NoteTypeID(item.id),
|
||||||
)
|
)
|
||||||
|
|
||||||
def manage_template(self, item: SidebarItem) -> None:
|
def manage_template(self, item: SidebarItem) -> None:
|
||||||
note = Note(self.col, self.col.models.get(item._parent_item.id))
|
note = Note(self.col, self.col.models.get(NoteTypeID(item._parent_item.id)))
|
||||||
CardLayout(self.mw, note, ord=item.id, parent=self, fill_empty=True)
|
CardLayout(self.mw, note, ord=item.id, parent=self, fill_empty=True)
|
||||||
|
|
||||||
# Helpers
|
# Helpers
|
||||||
|
|
|
@ -5,6 +5,7 @@ from typing import List, Optional
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki.collection import OpChangesWithID
|
from anki.collection import OpChangesWithID
|
||||||
|
from anki.decks import DeckID
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.deck_ops import add_deck_dialog
|
from aqt.deck_ops import add_deck_dialog
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
|
@ -167,7 +168,7 @@ class StudyDeck(QDialog):
|
||||||
default = self.names[self.form.list.currentRow()]
|
default = self.names[self.form.list.currentRow()]
|
||||||
|
|
||||||
def success(out: OpChangesWithID) -> None:
|
def success(out: OpChangesWithID) -> None:
|
||||||
deck = self.mw.col.decks.get(out.id)
|
deck = self.mw.col.decks.get(DeckID(out.id))
|
||||||
self.name = deck["name"]
|
self.name = deck["name"]
|
||||||
|
|
||||||
# make sure we clean up reset hook when manually exiting
|
# make sure we clean up reset hook when manually exiting
|
||||||
|
|
|
@ -7,6 +7,7 @@ from typing import Callable, Sequence
|
||||||
|
|
||||||
from anki.collection import OpChangesWithCount
|
from anki.collection import OpChangesWithCount
|
||||||
from anki.lang import TR
|
from anki.lang import TR
|
||||||
|
from anki.notes import NoteID
|
||||||
from aqt import AnkiQt, QWidget
|
from aqt import AnkiQt, QWidget
|
||||||
from aqt.main import PerformOpOptionalSuccessCallback
|
from aqt.main import PerformOpOptionalSuccessCallback
|
||||||
from aqt.utils import showInfo, tooltip, tr
|
from aqt.utils import showInfo, tooltip, tr
|
||||||
|
@ -15,7 +16,7 @@ from aqt.utils import showInfo, tooltip, tr
|
||||||
def add_tags(
|
def add_tags(
|
||||||
*,
|
*,
|
||||||
mw: AnkiQt,
|
mw: AnkiQt,
|
||||||
note_ids: Sequence[int],
|
note_ids: Sequence[NoteID],
|
||||||
space_separated_tags: str,
|
space_separated_tags: str,
|
||||||
success: PerformOpOptionalSuccessCallback = None,
|
success: PerformOpOptionalSuccessCallback = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -27,7 +28,7 @@ def add_tags(
|
||||||
def remove_tags_for_notes(
|
def remove_tags_for_notes(
|
||||||
*,
|
*,
|
||||||
mw: AnkiQt,
|
mw: AnkiQt,
|
||||||
note_ids: Sequence[int],
|
note_ids: Sequence[NoteID],
|
||||||
space_separated_tags: str,
|
space_separated_tags: str,
|
||||||
success: PerformOpOptionalSuccessCallback = None,
|
success: PerformOpOptionalSuccessCallback = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
Loading…
Reference in a new issue