mirror of
https://github.com/ankitects/anki.git
synced 2025-09-23 00:12:25 -04:00
Merge pull request #543 from Arthur-Milchior/sort_according_to_path
Sort according to path
This commit is contained in:
commit
6ecf2ffa2c
7 changed files with 59 additions and 46 deletions
|
@ -5,7 +5,6 @@ from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
import operator
|
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||||
|
|
||||||
|
@ -127,7 +126,7 @@ class DeckManager:
|
||||||
# child of an existing deck then it needs to be renamed
|
# child of an existing deck then it needs to be renamed
|
||||||
deck = self.get(did)
|
deck = self.get(did)
|
||||||
if "::" in deck["name"]:
|
if "::" in deck["name"]:
|
||||||
base = deck["name"].split("::")[-1]
|
base = self.basename(deck["name"])
|
||||||
suffix = ""
|
suffix = ""
|
||||||
while True:
|
while True:
|
||||||
# find an unused name
|
# find an unused name
|
||||||
|
@ -261,15 +260,15 @@ class DeckManager:
|
||||||
ontoDeckName = self.get(ontoDeckDid)["name"]
|
ontoDeckName = self.get(ontoDeckDid)["name"]
|
||||||
|
|
||||||
if ontoDeckDid is None or ontoDeckDid == "":
|
if ontoDeckDid is None or ontoDeckDid == "":
|
||||||
if len(self._path(draggedDeckName)) > 1:
|
if len(self.path(draggedDeckName)) > 1:
|
||||||
self.rename(draggedDeck, self._basename(draggedDeckName))
|
self.rename(draggedDeck, self.basename(draggedDeckName))
|
||||||
elif self._canDragAndDrop(draggedDeckName, ontoDeckName):
|
elif self._canDragAndDrop(draggedDeckName, ontoDeckName):
|
||||||
draggedDeck = self.get(draggedDeckDid)
|
draggedDeck = self.get(draggedDeckDid)
|
||||||
draggedDeckName = draggedDeck["name"]
|
draggedDeckName = draggedDeck["name"]
|
||||||
ontoDeckName = self.get(ontoDeckDid)["name"]
|
ontoDeckName = self.get(ontoDeckDid)["name"]
|
||||||
assert ontoDeckName.strip()
|
assert ontoDeckName.strip()
|
||||||
self.rename(
|
self.rename(
|
||||||
draggedDeck, ontoDeckName + "::" + self._basename(draggedDeckName)
|
draggedDeck, ontoDeckName + "::" + self.basename(draggedDeckName)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _canDragAndDrop(self, draggedDeckName: str, ontoDeckName: str) -> bool:
|
def _canDragAndDrop(self, draggedDeckName: str, ontoDeckName: str) -> bool:
|
||||||
|
@ -283,24 +282,44 @@ class DeckManager:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _isParent(self, parentDeckName: str, childDeckName: str) -> Any:
|
def _isParent(self, parentDeckName: str, childDeckName: str) -> Any:
|
||||||
return self._path(childDeckName) == self._path(parentDeckName) + [
|
return self.path(childDeckName) == self.path(parentDeckName) + [
|
||||||
self._basename(childDeckName)
|
self.basename(childDeckName)
|
||||||
]
|
]
|
||||||
|
|
||||||
def _isAncestor(self, ancestorDeckName: str, descendantDeckName: str) -> Any:
|
def _isAncestor(self, ancestorDeckName: str, descendantDeckName: str) -> Any:
|
||||||
ancestorPath = self._path(ancestorDeckName)
|
ancestorPath = self.path(ancestorDeckName)
|
||||||
return ancestorPath == self._path(descendantDeckName)[0 : len(ancestorPath)]
|
return ancestorPath == self.path(descendantDeckName)[0 : len(ancestorPath)]
|
||||||
|
|
||||||
def _path(self, name: str) -> Any:
|
@staticmethod
|
||||||
|
def path(name: str) -> Any:
|
||||||
return name.split("::")
|
return name.split("::")
|
||||||
|
|
||||||
def _basename(self, name: str) -> Any:
|
_path = path
|
||||||
return self._path(name)[-1]
|
|
||||||
|
@classmethod
|
||||||
|
def basename(cls, name: str) -> Any:
|
||||||
|
return cls.path(name)[-1]
|
||||||
|
|
||||||
|
_basename = basename
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def immediate_parent_path(cls, name: str) -> Any:
|
||||||
|
return cls._path(name)[:-1]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def immediate_parent(cls, name: str) -> Any:
|
||||||
|
pp = cls.immediate_parent_path(name)
|
||||||
|
if pp:
|
||||||
|
return "::".join(pp)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def key(cls, deck: Dict[str, Any]) -> List[str]:
|
||||||
|
return cls.path(deck["name"])
|
||||||
|
|
||||||
def _ensureParents(self, name: str) -> Any:
|
def _ensureParents(self, name: str) -> Any:
|
||||||
"Ensure parents exist, and return name with case matching parents."
|
"Ensure parents exist, and return name with case matching parents."
|
||||||
s = ""
|
s = ""
|
||||||
path = self._path(name)
|
path = self.path(name)
|
||||||
if len(path) < 2:
|
if len(path) < 2:
|
||||||
return name
|
return name
|
||||||
for p in path[:-1]:
|
for p in path[:-1]:
|
||||||
|
@ -454,7 +473,7 @@ class DeckManager:
|
||||||
|
|
||||||
def _checkDeckTree(self) -> None:
|
def _checkDeckTree(self) -> None:
|
||||||
decks = self.col.decks.all()
|
decks = self.col.decks.all()
|
||||||
decks.sort(key=operator.itemgetter("name"))
|
decks.sort(key=self.key)
|
||||||
names: Set[str] = set()
|
names: Set[str] = set()
|
||||||
|
|
||||||
for deck in decks:
|
for deck in decks:
|
||||||
|
@ -465,14 +484,14 @@ class DeckManager:
|
||||||
self.save(deck)
|
self.save(deck)
|
||||||
|
|
||||||
# ensure no sections are blank
|
# ensure no sections are blank
|
||||||
if not all(deck["name"].split("::")):
|
if not all(self.path(deck["name"])):
|
||||||
self.col.log("fix deck with missing sections", deck["name"])
|
self.col.log("fix deck with missing sections", deck["name"])
|
||||||
deck["name"] = "recovered%d" % intTime(1000)
|
deck["name"] = "recovered%d" % intTime(1000)
|
||||||
self.save(deck)
|
self.save(deck)
|
||||||
|
|
||||||
# immediate parent must exist
|
# immediate parent must exist
|
||||||
if "::" in deck["name"]:
|
if "::" in deck["name"]:
|
||||||
immediateParent = "::".join(deck["name"].split("::")[:-1])
|
immediateParent = self.immediate_parent(deck["name"])
|
||||||
if immediateParent not in names:
|
if immediateParent not in names:
|
||||||
self.col.log("fix deck with missing parent", deck["name"])
|
self.col.log("fix deck with missing parent", deck["name"])
|
||||||
self._ensureParents(deck["name"])
|
self._ensureParents(deck["name"])
|
||||||
|
@ -570,14 +589,13 @@ class DeckManager:
|
||||||
childMap = {}
|
childMap = {}
|
||||||
|
|
||||||
# go through all decks, sorted by name
|
# go through all decks, sorted by name
|
||||||
for deck in sorted(self.all(), key=operator.itemgetter("name")):
|
for deck in sorted(self.all(), key=self.key):
|
||||||
node: Dict[int, Any] = {}
|
node: Dict[int, Any] = {}
|
||||||
childMap[deck["id"]] = node
|
childMap[deck["id"]] = node
|
||||||
|
|
||||||
# add note to immediate parent
|
# add note to immediate parent
|
||||||
parts = deck["name"].split("::")
|
immediateParent = self.immediate_parent(deck["name"])
|
||||||
if len(parts) > 1:
|
if immediateParent is not None:
|
||||||
immediateParent = "::".join(parts[:-1])
|
|
||||||
pid = nameMap[immediateParent]["id"]
|
pid = nameMap[immediateParent]["id"]
|
||||||
childMap[pid][deck["id"]] = node
|
childMap[pid][deck["id"]] = node
|
||||||
|
|
||||||
|
@ -587,7 +605,7 @@ class DeckManager:
|
||||||
"All parents of did."
|
"All parents of did."
|
||||||
# get parent and grandparent names
|
# get parent and grandparent names
|
||||||
parents: List[str] = []
|
parents: List[str] = []
|
||||||
for part in self.get(did)["name"].split("::")[:-1]:
|
for part in self.immediate_parent_path(self.get(did)["name"]):
|
||||||
if not parents:
|
if not parents:
|
||||||
parents.append(part)
|
parents.append(part)
|
||||||
else:
|
else:
|
||||||
|
@ -605,7 +623,7 @@ class DeckManager:
|
||||||
"All existing parents of name"
|
"All existing parents of name"
|
||||||
if "::" not in name:
|
if "::" not in name:
|
||||||
return []
|
return []
|
||||||
names = name.split("::")[:-1]
|
names = self.immediate_parent_path(name)
|
||||||
head = []
|
head = []
|
||||||
parents = []
|
parents = []
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
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.importing.base import Importer
|
from anki.importing.base import Importer
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.storage import Collection
|
from anki.storage import Collection
|
||||||
|
@ -257,13 +258,13 @@ class Anki2Importer(Importer):
|
||||||
name = g["name"]
|
name = g["name"]
|
||||||
# if there's a prefix, replace the top level deck
|
# if there's a prefix, replace the top level deck
|
||||||
if self.deckPrefix:
|
if self.deckPrefix:
|
||||||
tmpname = "::".join(name.split("::")[1:])
|
tmpname = "::".join(DeckManager.path(name)[1:])
|
||||||
name = self.deckPrefix
|
name = self.deckPrefix
|
||||||
if tmpname:
|
if tmpname:
|
||||||
name += "::" + tmpname
|
name += "::" + tmpname
|
||||||
# manually create any parents so we can pull in descriptions
|
# manually create any parents so we can pull in descriptions
|
||||||
head = ""
|
head = ""
|
||||||
for parent in name.split("::")[:-1]:
|
for parent in DeckManager.immediate_parent_path(name):
|
||||||
if head:
|
if head:
|
||||||
head += "::"
|
head += "::"
|
||||||
head += parent
|
head += parent
|
||||||
|
|
|
@ -14,6 +14,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 DeckManager
|
||||||
from anki.schedv2 import Scheduler as V2
|
from anki.schedv2 import Scheduler as V2
|
||||||
from anki.utils import ids2str, intTime
|
from anki.utils import ids2str, intTime
|
||||||
|
|
||||||
|
@ -152,18 +153,11 @@ class Scheduler(V2):
|
||||||
lims: Dict[str, List[int]] = {}
|
lims: Dict[str, List[int]] = {}
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
def parent(name):
|
|
||||||
parts = name.split("::")
|
|
||||||
if len(parts) < 2:
|
|
||||||
return None
|
|
||||||
parts = parts[:-1]
|
|
||||||
return "::".join(parts)
|
|
||||||
|
|
||||||
for deck in decks:
|
for deck in decks:
|
||||||
p = parent(deck["name"])
|
p = DeckManager.immediate_parent(deck["name"])
|
||||||
# new
|
# new
|
||||||
nlim = self._deckNewLimitSingle(deck)
|
nlim = self._deckNewLimitSingle(deck)
|
||||||
if p:
|
if p is not None:
|
||||||
nlim = min(nlim, lims[p][0])
|
nlim = min(nlim, lims[p][0])
|
||||||
new = self._newForDeck(deck["id"], nlim)
|
new = self._newForDeck(deck["id"], nlim)
|
||||||
# learning
|
# learning
|
||||||
|
|
|
@ -16,6 +16,7 @@ import anki # pylint: disable=unused-import
|
||||||
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 DeckManager
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.rsbackend import FormatTimeSpanContext, SchedTimingToday
|
from anki.rsbackend import FormatTimeSpanContext, SchedTimingToday
|
||||||
from anki.utils import ids2str, intTime
|
from anki.utils import ids2str, intTime
|
||||||
|
@ -239,19 +240,12 @@ order by due"""
|
||||||
lims: Dict[str, List[int]] = {}
|
lims: Dict[str, List[int]] = {}
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
def parent(name):
|
|
||||||
parts = name.split("::")
|
|
||||||
if len(parts) < 2:
|
|
||||||
return None
|
|
||||||
parts = parts[:-1]
|
|
||||||
return "::".join(parts)
|
|
||||||
|
|
||||||
childMap = self.col.decks.childMap()
|
childMap = self.col.decks.childMap()
|
||||||
for deck in decks:
|
for deck in decks:
|
||||||
p = parent(deck["name"])
|
p = DeckManager.immediate_parent(deck["name"])
|
||||||
# new
|
# new
|
||||||
nlim = self._deckNewLimitSingle(deck)
|
nlim = self._deckNewLimitSingle(deck)
|
||||||
if p:
|
if p is not None:
|
||||||
nlim = min(nlim, lims[p][0])
|
nlim = min(nlim, lims[p][0])
|
||||||
new = self._newForDeck(deck["id"], nlim)
|
new = self._newForDeck(deck["id"], nlim)
|
||||||
# learning
|
# learning
|
||||||
|
@ -279,7 +273,7 @@ order by due"""
|
||||||
def _groupChildren(self, grps: List[List[Any]]) -> Any:
|
def _groupChildren(self, grps: List[List[Any]]) -> Any:
|
||||||
# first, split the group names into components
|
# first, split the group names into components
|
||||||
for g in grps:
|
for g in grps:
|
||||||
g[0] = g[0].split("::")
|
g[0] = DeckManager.path(g[0])
|
||||||
# and sort based on those components
|
# and sort based on those components
|
||||||
grps.sort(key=itemgetter(0))
|
grps.sort(key=itemgetter(0))
|
||||||
# then run main function
|
# then run main function
|
||||||
|
|
|
@ -34,6 +34,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||||
import anki
|
import anki
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
|
from anki.decks import DeckManager
|
||||||
from anki.models import NoteType
|
from anki.models import NoteType
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
from anki.rsbackend import TemplateReplacementList
|
from anki.rsbackend import TemplateReplacementList
|
||||||
|
@ -153,7 +154,7 @@ def fields_for_rendering(
|
||||||
fields["Tags"] = note.stringTags().strip()
|
fields["Tags"] = note.stringTags().strip()
|
||||||
fields["Type"] = card.note_type()["name"]
|
fields["Type"] = card.note_type()["name"]
|
||||||
fields["Deck"] = col.decks.name(card.odid or card.did)
|
fields["Deck"] = col.decks.name(card.odid or card.did)
|
||||||
fields["Subdeck"] = fields["Deck"].split("::")[-1]
|
fields["Subdeck"] = DeckManager.basename(fields["Deck"])
|
||||||
fields["Card"] = card.template()["name"]
|
fields["Card"] = card.template()["name"]
|
||||||
flag = card.userFlag()
|
flag = card.userFlag()
|
||||||
fields["CardFlag"] = flag and f"flag{flag}" or ""
|
fields["CardFlag"] = flag and f"flag{flag}" or ""
|
||||||
|
|
|
@ -20,6 +20,7 @@ from anki import hooks
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
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.lang import _, ngettext
|
from anki.lang import _, ngettext
|
||||||
from anki.models import NoteType
|
from anki.models import NoteType
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
|
@ -1302,7 +1303,7 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
def addDecks(parent, decks):
|
def addDecks(parent, decks):
|
||||||
for head, did, rev, lrn, new, children in decks:
|
for head, did, rev, lrn, new, children in decks:
|
||||||
name = self.mw.col.decks.get(did)["name"]
|
name = self.mw.col.decks.get(did)["name"]
|
||||||
shortname = name.split("::")[-1]
|
shortname = DeckManager.basename(name)
|
||||||
if children:
|
if children:
|
||||||
subm = parent.addMenu(shortname)
|
subm = parent.addMenu(shortname)
|
||||||
subm.addItem(_("Filter"), self._filterFunc("deck", name))
|
subm.addItem(_("Filter"), self._filterFunc("deck", name))
|
||||||
|
|
|
@ -3,6 +3,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
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
|
from anki.decks import DeckManager
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
|
@ -51,7 +52,10 @@ class StudyDeck(QDialog):
|
||||||
if title:
|
if title:
|
||||||
self.setWindowTitle(title)
|
self.setWindowTitle(title)
|
||||||
if not names:
|
if not names:
|
||||||
names = sorted(self.mw.col.decks.allNames(dyn=dyn, force_default=False))
|
names = sorted(
|
||||||
|
self.mw.col.decks.allNames(dyn=dyn, force_default=False),
|
||||||
|
key=DeckManager._path,
|
||||||
|
)
|
||||||
self.nameFunc = None
|
self.nameFunc = None
|
||||||
self.origNames = names
|
self.origNames = names
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in a new issue