mirror of
https://github.com/ankitects/anki.git
synced 2025-09-19 06:22:22 -04:00
parent
349bd9d681
commit
c299e271e8
16 changed files with 201 additions and 254 deletions
|
@ -31,19 +31,19 @@ from anki.rsbackend import ( # pylint: disable=unused-import
|
||||||
ConcatSeparator,
|
ConcatSeparator,
|
||||||
DBError,
|
DBError,
|
||||||
DupeIn,
|
DupeIn,
|
||||||
FilterToSearchIn,
|
Flag,
|
||||||
FormatTimeSpanContext,
|
FormatTimeSpanContext,
|
||||||
InvalidInput,
|
InvalidInput,
|
||||||
NamedFilter,
|
|
||||||
NoteIDs,
|
NoteIDs,
|
||||||
Progress,
|
Progress,
|
||||||
RustBackend,
|
RustBackend,
|
||||||
|
SearchTerm,
|
||||||
pb,
|
pb,
|
||||||
)
|
)
|
||||||
from anki.sched import Scheduler as V1Scheduler
|
from anki.sched import Scheduler as V1Scheduler
|
||||||
from anki.schedv2 import Scheduler as V2Scheduler
|
from anki.schedv2 import Scheduler as V2Scheduler
|
||||||
from anki.tags import TagManager
|
from anki.tags import TagManager
|
||||||
from anki.utils import devMode, ids2str, intTime
|
from anki.utils import devMode, ids2str, intTime, splitFields, stripHTMLMedia
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from anki.rsbackend import FormatTimeSpanContextValue, TRValue
|
from anki.rsbackend import FormatTimeSpanContextValue, TRValue
|
||||||
|
@ -460,8 +460,8 @@ class Collection:
|
||||||
)
|
)
|
||||||
return self.backend.search_cards(search=query, order=mode)
|
return self.backend.search_cards(search=query, order=mode)
|
||||||
|
|
||||||
def find_notes(self, query: str) -> Sequence[int]:
|
def find_notes(self, *terms: Union[str, SearchTerm]) -> Sequence[int]:
|
||||||
return self.backend.search_notes(query)
|
return self.backend.search_notes(self.build_search_string(*terms))
|
||||||
|
|
||||||
def find_and_replace(
|
def find_and_replace(
|
||||||
self,
|
self,
|
||||||
|
@ -474,8 +474,39 @@ class Collection:
|
||||||
) -> int:
|
) -> int:
|
||||||
return anki.find.findReplace(self, nids, src, dst, regex, field, fold)
|
return anki.find.findReplace(self, nids, src, dst, regex, field, fold)
|
||||||
|
|
||||||
|
# returns array of ("dupestr", [nids])
|
||||||
def findDupes(self, fieldName: str, search: str = "") -> List[Tuple[Any, list]]:
|
def findDupes(self, fieldName: str, search: str = "") -> List[Tuple[Any, list]]:
|
||||||
return anki.find.findDupes(self, fieldName, search)
|
nids = self.findNotes(search, SearchTerm(field_name=fieldName))
|
||||||
|
# go through notes
|
||||||
|
vals: Dict[str, List[int]] = {}
|
||||||
|
dupes = []
|
||||||
|
fields: Dict[int, int] = {}
|
||||||
|
|
||||||
|
def ordForMid(mid):
|
||||||
|
if mid not in fields:
|
||||||
|
model = self.models.get(mid)
|
||||||
|
for c, f in enumerate(model["flds"]):
|
||||||
|
if f["name"].lower() == fieldName.lower():
|
||||||
|
fields[mid] = c
|
||||||
|
break
|
||||||
|
return fields[mid]
|
||||||
|
|
||||||
|
for nid, mid, flds in self.db.all(
|
||||||
|
"select id, mid, flds from notes where id in " + ids2str(nids)
|
||||||
|
):
|
||||||
|
flds = splitFields(flds)
|
||||||
|
ord = ordForMid(mid)
|
||||||
|
if ord is None:
|
||||||
|
continue
|
||||||
|
val = flds[ord]
|
||||||
|
val = stripHTMLMedia(val)
|
||||||
|
# empty does not count as duplicate
|
||||||
|
if not val:
|
||||||
|
continue
|
||||||
|
vals.setdefault(val, []).append(nid)
|
||||||
|
if len(vals[val]) == 2:
|
||||||
|
dupes.append((val, vals[val]))
|
||||||
|
return dupes
|
||||||
|
|
||||||
findCards = find_cards
|
findCards = find_cards
|
||||||
findNotes = find_notes
|
findNotes = find_notes
|
||||||
|
@ -484,68 +515,35 @@ class Collection:
|
||||||
# Search Strings
|
# Search Strings
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def search_string(
|
def build_search_string(
|
||||||
self,
|
self, *terms: Union[str, SearchTerm], negate=False, match_any=False
|
||||||
*,
|
|
||||||
negate: bool = False,
|
|
||||||
concat_by_or: bool = False,
|
|
||||||
searches: Optional[List[str]] = None,
|
|
||||||
name: Optional["FilterToSearchIn.NamedFilterValue"] = None,
|
|
||||||
tag: Optional[str] = None,
|
|
||||||
deck: Optional[str] = None,
|
|
||||||
note: Optional[str] = None,
|
|
||||||
template: Optional[int] = None,
|
|
||||||
dupe: Optional[Tuple[int, str]] = None,
|
|
||||||
forgot_in_days: Optional[int] = None,
|
|
||||||
added_in_days: Optional[int] = None,
|
|
||||||
due_in_days: Optional[int] = None,
|
|
||||||
nids: Optional[List[int]] = None,
|
|
||||||
field_name: Optional[str] = None,
|
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Helper function for the backend's search string operations.
|
"""Helper function for the backend's search string operations.
|
||||||
|
|
||||||
Pass search strings as 'search_strings' to normalize.
|
Pass terms as strings to normalize.
|
||||||
Pass multiple to concatenate (defaults to 'and').
|
Pass fields of backend.proto/FilterToSearchIn as valid SearchTerms.
|
||||||
|
Pass multiple terms to concatenate (defaults to 'and', 'or' when 'match_any=True').
|
||||||
Pass 'negate=True' to negate the end result.
|
Pass 'negate=True' to negate the end result.
|
||||||
May raise InvalidInput.
|
May raise InvalidInput.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def append_filter(filter_in):
|
searches = []
|
||||||
filters.append(self.backend.filter_to_search(filter_in))
|
for term in terms:
|
||||||
|
if isinstance(term, SearchTerm):
|
||||||
if name:
|
term = self.backend.filter_to_search(term)
|
||||||
append_filter(FilterToSearchIn(name=name))
|
searches.append(term)
|
||||||
if tag:
|
if match_any:
|
||||||
append_filter(FilterToSearchIn(tag=tag))
|
|
||||||
if deck:
|
|
||||||
append_filter(FilterToSearchIn(deck=deck))
|
|
||||||
if note:
|
|
||||||
append_filter(FilterToSearchIn(note=note))
|
|
||||||
if template:
|
|
||||||
append_filter(FilterToSearchIn(template=template))
|
|
||||||
if dupe:
|
|
||||||
dupe_in = DupeIn(mid=BackendNoteTypeID(ntid=dupe[0]), text=dupe[1])
|
|
||||||
append_filter(FilterToSearchIn(dupe=dupe_in))
|
|
||||||
if forgot_in_days:
|
|
||||||
append_filter(FilterToSearchIn(forgot_in_days=forgot_in_days))
|
|
||||||
if added_in_days:
|
|
||||||
append_filter(FilterToSearchIn(added_in_days=added_in_days))
|
|
||||||
if due_in_days:
|
|
||||||
append_filter(FilterToSearchIn(due_in_days=due_in_days))
|
|
||||||
if nids:
|
|
||||||
append_filter(FilterToSearchIn(nids=NoteIDs(nids=nids)))
|
|
||||||
if field_name:
|
|
||||||
append_filter(FilterToSearchIn(field_name=field_name))
|
|
||||||
if concat_by_or:
|
|
||||||
sep = ConcatSeparator.OR
|
sep = ConcatSeparator.OR
|
||||||
else:
|
else:
|
||||||
sep = ConcatSeparator.AND
|
sep = ConcatSeparator.AND
|
||||||
search_string = self.backend.concatenate_searches(sep=sep, searches=filters)
|
search_string = self.backend.concatenate_searches(sep=sep, searches=searches)
|
||||||
if negate:
|
if negate:
|
||||||
search_string = self.backend.negate_search(search_string)
|
search_string = self.backend.negate_search(search_string)
|
||||||
return search_string
|
return search_string
|
||||||
|
|
||||||
def replace_search_term(self, search: str, replacement: str) -> str:
|
def replace_search_term(self, search: str, replacement: str) -> str:
|
||||||
|
"""Wrapper for the according backend function."""
|
||||||
|
|
||||||
return self.backend.replace_search_term(search=search, replacement=replacement)
|
return self.backend.replace_search_term(search=search, replacement=replacement)
|
||||||
|
|
||||||
# Config
|
# Config
|
||||||
|
@ -788,5 +786,18 @@ table.review-log {{ {revlog_style} }}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def dupe_search_term(mid: int, text: str) -> SearchTerm:
|
||||||
|
"""Helper function for building a DupeIn message."""
|
||||||
|
|
||||||
|
dupe_in = DupeIn(mid=BackendNoteTypeID(ntid=mid), text=text)
|
||||||
|
return SearchTerm(dupe=dupe_in)
|
||||||
|
|
||||||
|
|
||||||
|
def nid_search_term(nids: List[int]) -> SearchTerm:
|
||||||
|
"""Helper function for building a NoteIDs message."""
|
||||||
|
|
||||||
|
return SearchTerm(nids=NoteIDs(nids=nids))
|
||||||
|
|
||||||
|
|
||||||
# legacy name
|
# legacy name
|
||||||
_Collection = Collection
|
_Collection = Collection
|
||||||
|
|
|
@ -3,10 +3,9 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Optional, Set, Tuple
|
from typing import TYPE_CHECKING, Optional, Set
|
||||||
|
|
||||||
from anki.hooks import *
|
from anki.hooks import *
|
||||||
from anki.utils import ids2str, splitFields, stripHTMLMedia
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from anki.collection import Collection
|
from anki.collection import Collection
|
||||||
|
@ -64,41 +63,3 @@ def fieldNames(col, downcase=True) -> List:
|
||||||
if name not in fields: # slower w/o
|
if name not in fields: # slower w/o
|
||||||
fields.add(name)
|
fields.add(name)
|
||||||
return list(fields)
|
return list(fields)
|
||||||
|
|
||||||
|
|
||||||
# returns array of ("dupestr", [nids])
|
|
||||||
def findDupes(
|
|
||||||
col: Collection, fieldName: str, search: str = ""
|
|
||||||
) -> List[Tuple[Any, List]]:
|
|
||||||
# limit search to notes with applicable field name
|
|
||||||
search = col.search_string(searches=[search], field_name=fieldName)
|
|
||||||
# go through notes
|
|
||||||
vals: Dict[str, List[int]] = {}
|
|
||||||
dupes = []
|
|
||||||
fields: Dict[int, int] = {}
|
|
||||||
|
|
||||||
def ordForMid(mid):
|
|
||||||
if mid not in fields:
|
|
||||||
model = col.models.get(mid)
|
|
||||||
for c, f in enumerate(model["flds"]):
|
|
||||||
if f["name"].lower() == fieldName.lower():
|
|
||||||
fields[mid] = c
|
|
||||||
break
|
|
||||||
return fields[mid]
|
|
||||||
|
|
||||||
for nid, mid, flds in col.db.all(
|
|
||||||
"select id, mid, flds from notes where id in " + ids2str(col.findNotes(search))
|
|
||||||
):
|
|
||||||
flds = splitFields(flds)
|
|
||||||
ord = ordForMid(mid)
|
|
||||||
if ord is None:
|
|
||||||
continue
|
|
||||||
val = flds[ord]
|
|
||||||
val = stripHTMLMedia(val)
|
|
||||||
# empty does not count as duplicate
|
|
||||||
if not val:
|
|
||||||
continue
|
|
||||||
vals.setdefault(val, []).append(nid)
|
|
||||||
if len(vals[val]) == 2:
|
|
||||||
dupes.append((val, vals[val]))
|
|
||||||
return dupes
|
|
||||||
|
|
|
@ -47,8 +47,8 @@ TagTreeNode = pb.TagTreeNode
|
||||||
NoteType = pb.NoteType
|
NoteType = pb.NoteType
|
||||||
DeckTreeNode = pb.DeckTreeNode
|
DeckTreeNode = pb.DeckTreeNode
|
||||||
StockNoteType = pb.StockNoteType
|
StockNoteType = pb.StockNoteType
|
||||||
FilterToSearchIn = pb.FilterToSearchIn
|
SearchTerm = pb.FilterToSearchIn
|
||||||
NamedFilter = pb.FilterToSearchIn.NamedFilter
|
Flag = pb.FilterToSearchIn.Flag
|
||||||
DupeIn = pb.FilterToSearchIn.DupeIn
|
DupeIn = pb.FilterToSearchIn.DupeIn
|
||||||
NoteIDs = pb.NoteIDs
|
NoteIDs = pb.NoteIDs
|
||||||
BackendNoteTypeID = pb.NoteTypeID
|
BackendNoteTypeID = pb.NoteTypeID
|
||||||
|
|
|
@ -16,7 +16,7 @@ import re
|
||||||
from typing import Collection, List, Optional, Sequence, Tuple
|
from typing import Collection, List, Optional, Sequence, Tuple
|
||||||
|
|
||||||
import anki # pylint: disable=unused-import
|
import anki # pylint: disable=unused-import
|
||||||
from anki.rsbackend import FilterToSearchIn
|
from anki.collection import SearchTerm
|
||||||
from anki.utils import ids2str
|
from anki.utils import ids2str
|
||||||
|
|
||||||
|
|
||||||
|
@ -87,8 +87,7 @@ class TagManager:
|
||||||
|
|
||||||
def rename_tag(self, old: str, new: str) -> int:
|
def rename_tag(self, old: str, new: str) -> int:
|
||||||
"Rename provided tag, returning number of changed notes."
|
"Rename provided tag, returning number of changed notes."
|
||||||
search = self.col.backend.filter_to_search(FilterToSearchIn(tag=old))
|
nids = self.col.find_notes(SearchTerm(tag=old))
|
||||||
nids = self.col.find_notes(search)
|
|
||||||
if not nids:
|
if not nids:
|
||||||
return 0
|
return 0
|
||||||
escaped_name = re.sub(r"[*_\\]", r"\\\g<0>", old)
|
escaped_name = re.sub(r"[*_\\]", r"\\\g<0>", old)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import aqt.deckchooser
|
||||||
import aqt.editor
|
import aqt.editor
|
||||||
import aqt.forms
|
import aqt.forms
|
||||||
import aqt.modelchooser
|
import aqt.modelchooser
|
||||||
|
from anki.collection import nid_search_term
|
||||||
from anki.consts import MODEL_CLOZE
|
from anki.consts import MODEL_CLOZE
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
from anki.utils import htmlToTextLine, isMac
|
from anki.utils import htmlToTextLine, isMac
|
||||||
|
@ -144,7 +145,7 @@ class AddCards(QDialog):
|
||||||
def onHistory(self) -> None:
|
def onHistory(self) -> None:
|
||||||
m = QMenu(self)
|
m = QMenu(self)
|
||||||
for nid in self.history:
|
for nid in self.history:
|
||||||
if self.mw.col.findNotes(self.mw.col.search_string(nids=[nid])):
|
if self.mw.col.findNotes(nid_search_term([nid])):
|
||||||
note = self.mw.col.getNote(nid)
|
note = self.mw.col.getNote(nid)
|
||||||
fields = note.fields
|
fields = note.fields
|
||||||
txt = htmlToTextLine(", ".join(fields))
|
txt = htmlToTextLine(", ".join(fields))
|
||||||
|
@ -161,7 +162,7 @@ class AddCards(QDialog):
|
||||||
m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
|
m.exec_(self.historyButton.mapToGlobal(QPoint(0, 0)))
|
||||||
|
|
||||||
def editHistory(self, nid):
|
def editHistory(self, nid):
|
||||||
self.mw.browser_search(nids=[nid])
|
self.mw.browser_search(nid_search_term([nid]))
|
||||||
|
|
||||||
def addNote(self, note) -> Optional[Note]:
|
def addNote(self, note) -> Optional[Note]:
|
||||||
note.model()["did"] = self.deckChooser.selectedId()
|
note.model()["did"] = self.deckChooser.selectedId()
|
||||||
|
|
|
@ -13,7 +13,7 @@ from typing import List, Optional, Sequence, Tuple, cast
|
||||||
import aqt
|
import aqt
|
||||||
import aqt.forms
|
import aqt.forms
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
from anki.collection import Collection, InvalidInput, NamedFilter
|
from anki.collection import Collection, Flag, InvalidInput, SearchTerm, nid_search_term
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.lang import without_unicode_isolation
|
from anki.lang import without_unicode_isolation
|
||||||
from anki.models import NoteType
|
from anki.models import NoteType
|
||||||
|
@ -612,7 +612,9 @@ class Browser(QMainWindow):
|
||||||
qconnect(self.form.searchEdit.lineEdit().returnPressed, self.onSearchActivated)
|
qconnect(self.form.searchEdit.lineEdit().returnPressed, self.onSearchActivated)
|
||||||
self.form.searchEdit.setCompleter(None)
|
self.form.searchEdit.setCompleter(None)
|
||||||
self._searchPrompt = tr(TR.BROWSING_TYPE_HERE_TO_SEARCH)
|
self._searchPrompt = tr(TR.BROWSING_TYPE_HERE_TO_SEARCH)
|
||||||
self._searchPromptFilter = self.col.search_string(name=NamedFilter.CURRENT_DECK)
|
self._searchPromptFilter = self.col.build_search_string(
|
||||||
|
SearchTerm(current_deck=True)
|
||||||
|
)
|
||||||
self.form.searchEdit.addItems(
|
self.form.searchEdit.addItems(
|
||||||
[self._searchPrompt] + self.mw.pm.profile["searchHistory"]
|
[self._searchPrompt] + self.mw.pm.profile["searchHistory"]
|
||||||
)
|
)
|
||||||
|
@ -659,7 +661,7 @@ class Browser(QMainWindow):
|
||||||
c = self.card = self.mw.reviewer.card
|
c = self.card = self.mw.reviewer.card
|
||||||
nid = c and c.nid or 0
|
nid = c and c.nid or 0
|
||||||
if nid:
|
if nid:
|
||||||
search = self.col.search_string(nids=[nid])
|
search = self.col.build_search_string(nid_search_term([nid]))
|
||||||
search = gui_hooks.default_search(search, c)
|
search = gui_hooks.default_search(search, c)
|
||||||
self.model.search(search)
|
self.model.search(search)
|
||||||
self.focusCid(c.id)
|
self.focusCid(c.id)
|
||||||
|
@ -671,7 +673,7 @@ class Browser(QMainWindow):
|
||||||
self._onRowChanged(None, None)
|
self._onRowChanged(None, None)
|
||||||
|
|
||||||
def normalize_search(self, search: str) -> str:
|
def normalize_search(self, search: str) -> str:
|
||||||
normed = self.col.search_string(searches=[search])
|
normed = self.col.build_search_string(search)
|
||||||
self._lastSearchTxt = normed
|
self._lastSearchTxt = normed
|
||||||
self.form.searchEdit.lineEdit().setText(normed)
|
self.form.searchEdit.lineEdit().setText(normed)
|
||||||
return normed
|
return normed
|
||||||
|
@ -951,23 +953,21 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
|
|
||||||
ml.popupOver(self.form.filter)
|
ml.popupOver(self.form.filter)
|
||||||
|
|
||||||
def update_search(self, *terms: str):
|
def update_search(self, *terms: Union[str, SearchTerm]):
|
||||||
"Modify the current search string based on modified keys, then refresh."
|
"Modify the current search string based on modified keys, then refresh."
|
||||||
try:
|
try:
|
||||||
search = self.col.search_string(searches=list(terms))
|
search = self.col.build_search_string(*terms)
|
||||||
mods = self.mw.app.keyboardModifiers()
|
mods = self.mw.app.keyboardModifiers()
|
||||||
if mods & Qt.AltModifier:
|
if mods & Qt.AltModifier:
|
||||||
search = self.col.search_string(negate=True, searches=[search])
|
search = self.col.build_search_string(search, negate=True)
|
||||||
cur = str(self.form.searchEdit.lineEdit().text())
|
cur = str(self.form.searchEdit.lineEdit().text())
|
||||||
if cur != self._searchPrompt:
|
if cur != self._searchPrompt:
|
||||||
if mods & Qt.ControlModifier and mods & Qt.ShiftModifier:
|
if mods & Qt.ControlModifier and mods & Qt.ShiftModifier:
|
||||||
search = self.col.replace_search_term(cur, search)
|
search = self.col.replace_search_term(cur, search)
|
||||||
elif mods & Qt.ControlModifier:
|
elif mods & Qt.ControlModifier:
|
||||||
search = self.col.search_string(searches=[cur, search])
|
search = self.col.build_search_string(cur, search)
|
||||||
elif mods & Qt.ShiftModifier:
|
elif mods & Qt.ShiftModifier:
|
||||||
search = self.col.search_string(
|
search = self.col.build_search_string(cur, search, match_any=True)
|
||||||
concat_by_or=True, searches=[cur, search]
|
|
||||||
)
|
|
||||||
except InvalidInput as e:
|
except InvalidInput as e:
|
||||||
show_invalid_search_error(e)
|
show_invalid_search_error(e)
|
||||||
else:
|
else:
|
||||||
|
@ -993,9 +993,9 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
subm.addChild(
|
subm.addChild(
|
||||||
self._simpleFilters(
|
self._simpleFilters(
|
||||||
(
|
(
|
||||||
(tr(TR.BROWSING_ADDED_TODAY), NamedFilter.ADDED_TODAY),
|
(tr(TR.BROWSING_ADDED_TODAY), SearchTerm(added_in_days=1)),
|
||||||
(tr(TR.BROWSING_STUDIED_TODAY), NamedFilter.STUDIED_TODAY),
|
(tr(TR.BROWSING_STUDIED_TODAY), SearchTerm(studied_today=True)),
|
||||||
(tr(TR.BROWSING_AGAIN_TODAY), NamedFilter.AGAIN_TODAY),
|
(tr(TR.BROWSING_AGAIN_TODAY), SearchTerm(forgot_in_days=1)),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -1006,20 +1006,20 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
subm.addChild(
|
subm.addChild(
|
||||||
self._simpleFilters(
|
self._simpleFilters(
|
||||||
(
|
(
|
||||||
(tr(TR.ACTIONS_NEW), NamedFilter.NEW),
|
(tr(TR.ACTIONS_NEW), SearchTerm(new=True)),
|
||||||
(tr(TR.SCHEDULING_LEARNING), NamedFilter.LEARN),
|
(tr(TR.SCHEDULING_LEARNING), SearchTerm(learn=True)),
|
||||||
(tr(TR.SCHEDULING_REVIEW), NamedFilter.REVIEW),
|
(tr(TR.SCHEDULING_REVIEW), SearchTerm(review=True)),
|
||||||
(tr(TR.FILTERING_IS_DUE), NamedFilter.DUE),
|
(tr(TR.FILTERING_IS_DUE), SearchTerm(due=True)),
|
||||||
None,
|
None,
|
||||||
(tr(TR.BROWSING_SUSPENDED), NamedFilter.SUSPENDED),
|
(tr(TR.BROWSING_SUSPENDED), SearchTerm(suspended=True)),
|
||||||
(tr(TR.BROWSING_BURIED), NamedFilter.BURIED),
|
(tr(TR.BROWSING_BURIED), SearchTerm(buried=True)),
|
||||||
None,
|
None,
|
||||||
(tr(TR.ACTIONS_RED_FLAG), NamedFilter.RED_FLAG),
|
(tr(TR.ACTIONS_RED_FLAG), SearchTerm(flag=Flag.RED)),
|
||||||
(tr(TR.ACTIONS_ORANGE_FLAG), NamedFilter.ORANGE_FLAG),
|
(tr(TR.ACTIONS_ORANGE_FLAG), SearchTerm(flag=Flag.ORANGE)),
|
||||||
(tr(TR.ACTIONS_GREEN_FLAG), NamedFilter.GREEN_FLAG),
|
(tr(TR.ACTIONS_GREEN_FLAG), SearchTerm(flag=Flag.GREEN)),
|
||||||
(tr(TR.ACTIONS_BLUE_FLAG), NamedFilter.BLUE_FLAG),
|
(tr(TR.ACTIONS_BLUE_FLAG), SearchTerm(flag=Flag.BLUE)),
|
||||||
(tr(TR.BROWSING_NO_FLAG), NamedFilter.NO_FLAG),
|
(tr(TR.BROWSING_NO_FLAG), SearchTerm(flag=Flag.WITHOUT)),
|
||||||
(tr(TR.BROWSING_ANY_FLAG), NamedFilter.ANY_FLAG),
|
(tr(TR.BROWSING_ANY_FLAG), SearchTerm(flag=Flag.ANY)),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -1045,9 +1045,7 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
|
|
||||||
def _onSaveFilter(self) -> None:
|
def _onSaveFilter(self) -> None:
|
||||||
try:
|
try:
|
||||||
filt = self.col.search_string(
|
filt = self.col.build_search_string(self.form.searchEdit.lineEdit().text())
|
||||||
searches=[self.form.searchEdit.lineEdit().text()]
|
|
||||||
)
|
|
||||||
except InvalidInput as e:
|
except InvalidInput as e:
|
||||||
show_invalid_search_error(e)
|
show_invalid_search_error(e)
|
||||||
else:
|
else:
|
||||||
|
@ -1088,12 +1086,12 @@ QTableView {{ gridline-color: {grid} }}
|
||||||
def _currentFilterIsSaved(self) -> Optional[str]:
|
def _currentFilterIsSaved(self) -> Optional[str]:
|
||||||
filt = self.form.searchEdit.lineEdit().text()
|
filt = self.form.searchEdit.lineEdit().text()
|
||||||
try:
|
try:
|
||||||
filt = self.col.search_string(searches=[filt])
|
filt = self.col.build_search_string(filt)
|
||||||
except InvalidInput:
|
except InvalidInput:
|
||||||
pass
|
pass
|
||||||
for k, v in self.col.get_config("savedFilters").items():
|
for k, v in self.col.get_config("savedFilters").items():
|
||||||
try:
|
try:
|
||||||
v = self.col.search_string(searches=[v])
|
v = self.col.build_search_string(v)
|
||||||
except InvalidInput:
|
except InvalidInput:
|
||||||
pass
|
pass
|
||||||
if filt == v:
|
if filt == v:
|
||||||
|
@ -1494,7 +1492,7 @@ where id in %s"""
|
||||||
tv = self.form.tableView
|
tv = self.form.tableView
|
||||||
tv.selectionModel().clear()
|
tv.selectionModel().clear()
|
||||||
|
|
||||||
search = self.col.search_string(nids=nids)
|
search = self.col.build_search_string(nid_search_term(nids))
|
||||||
self.search_for(search)
|
self.search_for(search)
|
||||||
|
|
||||||
tv.selectAll()
|
tv.selectAll()
|
||||||
|
@ -1703,7 +1701,7 @@ where id in %s"""
|
||||||
t += (
|
t += (
|
||||||
"""<li><a href=# onclick="pycmd('%s');return false;">%s</a>: %s</a>"""
|
"""<li><a href=# onclick="pycmd('%s');return false;">%s</a>: %s</a>"""
|
||||||
% (
|
% (
|
||||||
self.col.search_string(nids=nids).replace('"', """),
|
html.escape(self.col.build_search_string(nid_search_term(nids))),
|
||||||
tr(TR.BROWSING_NOTE_COUNT, count=len(nids)),
|
tr(TR.BROWSING_NOTE_COUNT, count=len(nids)),
|
||||||
html.escape(val),
|
html.escape(val),
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# 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.collection import NamedFilter
|
from anki.collection import SearchTerm
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import TR, disable_help_button, showInfo, showWarning, tr
|
from aqt.utils import TR, disable_help_button, showInfo, showWarning, tr
|
||||||
|
@ -160,29 +160,33 @@ class CustomStudy(QDialog):
|
||||||
dyn = self.mw.col.decks.get(did)
|
dyn = self.mw.col.decks.get(did)
|
||||||
# and then set various options
|
# and then set various options
|
||||||
if i == RADIO_FORGOT:
|
if i == RADIO_FORGOT:
|
||||||
search = self.mw.col.search_string(forgot_in_days=spin)
|
search = self.mw.col.build_search_string(SearchTerm(forgot_in_days=spin))
|
||||||
dyn["terms"][0] = [search, DYN_MAX_SIZE, DYN_RANDOM]
|
dyn["terms"][0] = [search, DYN_MAX_SIZE, DYN_RANDOM]
|
||||||
dyn["resched"] = False
|
dyn["resched"] = False
|
||||||
elif i == RADIO_AHEAD:
|
elif i == RADIO_AHEAD:
|
||||||
search = self.mw.col.search_string(due_in_days=spin)
|
search = self.mw.col.build_search_string(SearchTerm(due_in_days=spin))
|
||||||
dyn["terms"][0] = [search, DYN_MAX_SIZE, DYN_DUE]
|
dyn["terms"][0] = [search, DYN_MAX_SIZE, DYN_DUE]
|
||||||
dyn["resched"] = True
|
dyn["resched"] = True
|
||||||
elif i == RADIO_PREVIEW:
|
elif i == RADIO_PREVIEW:
|
||||||
search = self.mw.col.search_string(name=NamedFilter.NEW, added_in_days=spin)
|
search = self.mw.col.build_search_string(
|
||||||
|
SearchTerm(new=True), SearchTerm(added_in_days=spin)
|
||||||
|
)
|
||||||
dyn["terms"][0] = [search, DYN_MAX_SIZE, DYN_OLDEST]
|
dyn["terms"][0] = [search, DYN_MAX_SIZE, DYN_OLDEST]
|
||||||
dyn["resched"] = False
|
dyn["resched"] = False
|
||||||
elif i == RADIO_CRAM:
|
elif i == RADIO_CRAM:
|
||||||
type = f.cardType.currentRow()
|
type = f.cardType.currentRow()
|
||||||
if type == TYPE_NEW:
|
if type == TYPE_NEW:
|
||||||
terms = self.mw.col.search_string(name=NamedFilter.NEW)
|
terms = self.mw.col.build_search_string(SearchTerm(new=True))
|
||||||
ord = DYN_ADDED
|
ord = DYN_ADDED
|
||||||
dyn["resched"] = True
|
dyn["resched"] = True
|
||||||
elif type == TYPE_DUE:
|
elif type == TYPE_DUE:
|
||||||
terms = self.mw.col.search_string(name=NamedFilter.DUE)
|
terms = self.mw.col.build_search_string(SearchTerm(due=True))
|
||||||
ord = DYN_DUE
|
ord = DYN_DUE
|
||||||
dyn["resched"] = True
|
dyn["resched"] = True
|
||||||
elif type == TYPE_REVIEW:
|
elif type == TYPE_REVIEW:
|
||||||
terms = self.mw.col.search_string(negate=True, name=NamedFilter.NEW)
|
terms = self.mw.col.build_search_string(
|
||||||
|
SearchTerm(new=True), negate=True
|
||||||
|
)
|
||||||
ord = DYN_RANDOM
|
ord = DYN_RANDOM
|
||||||
dyn["resched"] = True
|
dyn["resched"] = True
|
||||||
else:
|
else:
|
||||||
|
@ -191,8 +195,8 @@ class CustomStudy(QDialog):
|
||||||
dyn["resched"] = False
|
dyn["resched"] = False
|
||||||
dyn["terms"][0] = [(terms + tags).strip(), spin, ord]
|
dyn["terms"][0] = [(terms + tags).strip(), spin, ord]
|
||||||
# add deck limit
|
# add deck limit
|
||||||
dyn["terms"][0][0] = self.mw.col.search_string(
|
dyn["terms"][0][0] = self.mw.col.build_search_string(
|
||||||
deck=self.deck["name"], searches=[dyn["terms"][0][0]]
|
dyn["terms"][0][0], SearchTerm(deck=self.deck["name"])
|
||||||
)
|
)
|
||||||
self.mw.col.decks.save(dyn)
|
self.mw.col.decks.save(dyn)
|
||||||
# generate cards
|
# generate cards
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki.collection import InvalidInput, NamedFilter
|
from anki.collection import InvalidInput, SearchTerm
|
||||||
from anki.lang import without_unicode_isolation
|
from anki.lang import without_unicode_isolation
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
|
@ -47,11 +47,9 @@ class DeckConf(QDialog):
|
||||||
self.initialSetup()
|
self.initialSetup()
|
||||||
self.loadConf()
|
self.loadConf()
|
||||||
if search:
|
if search:
|
||||||
search = self.mw.col.search_string(searches=[search], name=NamedFilter.DUE)
|
search = self.mw.col.build_search_string(search, SearchTerm(due=True))
|
||||||
self.form.search.setText(search)
|
self.form.search.setText(search)
|
||||||
search_2 = self.mw.col.search_string(
|
search_2 = self.mw.col.build_search_string(search, SearchTerm(new=True))
|
||||||
searches=[search], name=NamedFilter.NEW
|
|
||||||
)
|
|
||||||
self.form.search_2.setText(search_2)
|
self.form.search_2.setText(search_2)
|
||||||
self.form.search.selectAll()
|
self.form.search.selectAll()
|
||||||
|
|
||||||
|
@ -123,11 +121,11 @@ class DeckConf(QDialog):
|
||||||
else:
|
else:
|
||||||
d["delays"] = None
|
d["delays"] = None
|
||||||
|
|
||||||
search = self.mw.col.search_string(searches=[f.search.text()])
|
search = self.mw.col.build_search_string(f.search.text())
|
||||||
terms = [[search, f.limit.value(), f.order.currentIndex()]]
|
terms = [[search, f.limit.value(), f.order.currentIndex()]]
|
||||||
|
|
||||||
if f.secondFilter.isChecked():
|
if f.secondFilter.isChecked():
|
||||||
search_2 = self.mw.col.search_string(searches=[f.search_2.text()])
|
search_2 = self.mw.col.build_search_string(f.search_2.text())
|
||||||
terms.append([search_2, f.limit_2.value(), f.order_2.currentIndex()])
|
terms.append([search_2, f.limit_2.value(), f.order_2.currentIndex()])
|
||||||
|
|
||||||
d["terms"] = terms
|
d["terms"] = terms
|
||||||
|
|
|
@ -21,6 +21,7 @@ from bs4 import BeautifulSoup
|
||||||
import aqt
|
import aqt
|
||||||
import aqt.sound
|
import aqt.sound
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
|
from anki.collection import dupe_search_term
|
||||||
from anki.hooks import runFilter
|
from anki.hooks import runFilter
|
||||||
from anki.httpclient import HttpClient
|
from anki.httpclient import HttpClient
|
||||||
from anki.notes import Note
|
from anki.notes import Note
|
||||||
|
@ -539,7 +540,9 @@ class Editor:
|
||||||
self.web.eval("setBackgrounds(%s);" % json.dumps(cols))
|
self.web.eval("setBackgrounds(%s);" % json.dumps(cols))
|
||||||
|
|
||||||
def showDupes(self):
|
def showDupes(self):
|
||||||
self.mw.browser_search(dupe=(self.note.model()["id"], self.note.fields[0]))
|
self.mw.browser_search(
|
||||||
|
dupe_search_term(self.note.model()["id"], self.note.fields[0])
|
||||||
|
)
|
||||||
|
|
||||||
def fieldsAreBlank(self, previousNote=None):
|
def fieldsAreBlank(self, previousNote=None):
|
||||||
if not self.note:
|
if not self.note:
|
||||||
|
|
|
@ -66,7 +66,7 @@ class EmptyCardsDialog(QDialog):
|
||||||
self._delete_button.clicked.connect(self._on_delete)
|
self._delete_button.clicked.connect(self._on_delete)
|
||||||
|
|
||||||
def _on_note_link_clicked(self, link):
|
def _on_note_link_clicked(self, link):
|
||||||
self.mw.browser_search(searches=[link])
|
self.mw.browser_search(link)
|
||||||
|
|
||||||
def _on_delete(self):
|
def _on_delete(self):
|
||||||
self.mw.progress.start()
|
self.mw.progress.start()
|
||||||
|
|
|
@ -26,7 +26,7 @@ import aqt.stats
|
||||||
import aqt.toolbar
|
import aqt.toolbar
|
||||||
import aqt.webview
|
import aqt.webview
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki.collection import Collection
|
from anki.collection import Collection, SearchTerm
|
||||||
from anki.decks import Deck
|
from anki.decks import Deck
|
||||||
from anki.hooks import runHook
|
from anki.hooks import runHook
|
||||||
from anki.lang import without_unicode_isolation
|
from anki.lang import without_unicode_isolation
|
||||||
|
@ -1141,7 +1141,7 @@ title="%s" %s>%s</button>""" % (
|
||||||
deck = self.col.decks.current()
|
deck = self.col.decks.current()
|
||||||
if not search:
|
if not search:
|
||||||
if not deck["dyn"]:
|
if not deck["dyn"]:
|
||||||
search = self.col.search_string(deck=deck["name"])
|
search = self.col.build_search_string(SearchTerm(deck=deck["name"]))
|
||||||
while self.col.decks.id_for_name(
|
while self.col.decks.id_for_name(
|
||||||
without_unicode_isolation(tr(TR.QT_MISC_FILTERED_DECK, val=n))
|
without_unicode_isolation(tr(TR.QT_MISC_FILTERED_DECK, val=n))
|
||||||
):
|
):
|
||||||
|
@ -1621,10 +1621,10 @@ title="%s" %s>%s</button>""" % (
|
||||||
# Helpers for all windows
|
# Helpers for all windows
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def browser_search(self, **kwargs) -> None:
|
def browser_search(self, *terms: Union[str, SearchTerm]) -> None:
|
||||||
"""Wrapper for col.search_string() to look up the result in the browser."""
|
"""Wrapper for col.build_search_string() to look up the result in the browser."""
|
||||||
|
|
||||||
search = self.col.search_string(**kwargs)
|
search = self.col.build_search_string(*terms)
|
||||||
browser = aqt.dialogs.open("Browser", self)
|
browser = aqt.dialogs.open("Browser", self)
|
||||||
browser.form.searchEdit.lineEdit().setText(search)
|
browser.form.searchEdit.lineEdit().setText(search)
|
||||||
browser.onSearchActivated()
|
browser.onSearchActivated()
|
||||||
|
|
|
@ -9,6 +9,7 @@ from concurrent.futures import Future
|
||||||
from typing import Iterable, List, Optional, Sequence, TypeVar
|
from typing import Iterable, List, Optional, Sequence, TypeVar
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
|
from anki.collection import nid_search_term
|
||||||
from anki.rsbackend import TR, Interrupted, ProgressKind, pb
|
from anki.rsbackend import TR, Interrupted, ProgressKind, pb
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
|
@ -145,7 +146,7 @@ class MediaChecker:
|
||||||
|
|
||||||
if out is not None:
|
if out is not None:
|
||||||
nid, err = out
|
nid, err = out
|
||||||
self.mw.browser_search(nids=[nid])
|
self.mw.browser_search(nid_search_term([nid]))
|
||||||
showText(err, type="html")
|
showText(err, type="html")
|
||||||
else:
|
else:
|
||||||
tooltip(tr(TR.MEDIA_CHECK_ALL_LATEX_RENDERED))
|
tooltip(tr(TR.MEDIA_CHECK_ALL_LATEX_RENDERED))
|
||||||
|
|
|
@ -7,6 +7,7 @@ from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
|
from anki.collection import SearchTerm
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
from aqt.sound import av_player
|
from aqt.sound import av_player
|
||||||
from aqt.toolbar import BottomBar
|
from aqt.toolbar import BottomBar
|
||||||
|
@ -72,7 +73,7 @@ class Overview:
|
||||||
self.mw.onDeckConf()
|
self.mw.onDeckConf()
|
||||||
elif url == "cram":
|
elif url == "cram":
|
||||||
deck = self.mw.col.decks.current()["name"]
|
deck = self.mw.col.decks.current()["name"]
|
||||||
self.mw.onCram(self.mw.col.search_string(deck=deck))
|
self.mw.onCram(self.mw.col.build_search_string(SearchTerm(deck=deck)))
|
||||||
elif url == "refresh":
|
elif url == "refresh":
|
||||||
self.mw.col.sched.rebuild_filtered_deck(self.mw.col.decks.selected())
|
self.mw.col.sched.rebuild_filtered_deck(self.mw.col.decks.selected())
|
||||||
self.mw.reset()
|
self.mw.reset()
|
||||||
|
|
|
@ -9,10 +9,7 @@ from enum import Enum
|
||||||
from typing import Iterable, List, Optional
|
from typing import Iterable, List, Optional
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki.collection import ( # pylint: disable=unused-import
|
from anki.collection import SearchTerm
|
||||||
FilterToSearchIn,
|
|
||||||
NamedFilter,
|
|
||||||
)
|
|
||||||
from anki.errors import DeckRenameError
|
from anki.errors import DeckRenameError
|
||||||
from anki.rsbackend import DeckTreeNode, TagTreeNode
|
from anki.rsbackend import DeckTreeNode, TagTreeNode
|
||||||
from aqt import gui_hooks
|
from aqt import gui_hooks
|
||||||
|
@ -294,14 +291,14 @@ class SidebarTreeView(QTreeView):
|
||||||
item = SidebarItem(
|
item = SidebarItem(
|
||||||
tr(TR.BROWSING_WHOLE_COLLECTION),
|
tr(TR.BROWSING_WHOLE_COLLECTION),
|
||||||
":/icons/collection.svg",
|
":/icons/collection.svg",
|
||||||
self._named_filter(NamedFilter.WHOLE_COLLECTION),
|
self._filter_func(SearchTerm(whole_collection=True)),
|
||||||
item_type=SidebarItemType.COLLECTION,
|
item_type=SidebarItemType.COLLECTION,
|
||||||
)
|
)
|
||||||
root.addChild(item)
|
root.addChild(item)
|
||||||
item = SidebarItem(
|
item = SidebarItem(
|
||||||
tr(TR.BROWSING_CURRENT_DECK),
|
tr(TR.BROWSING_CURRENT_DECK),
|
||||||
":/icons/deck.svg",
|
":/icons/deck.svg",
|
||||||
self._named_filter(NamedFilter.CURRENT_DECK),
|
self._filter_func(SearchTerm(current_deck=True)),
|
||||||
item_type=SidebarItemType.CURRENT_DECK,
|
item_type=SidebarItemType.CURRENT_DECK,
|
||||||
)
|
)
|
||||||
root.addChild(item)
|
root.addChild(item)
|
||||||
|
@ -313,7 +310,7 @@ class SidebarTreeView(QTreeView):
|
||||||
item = SidebarItem(
|
item = SidebarItem(
|
||||||
name,
|
name,
|
||||||
":/icons/heart.svg",
|
":/icons/heart.svg",
|
||||||
self._saved_filter(filt),
|
self._filter_func(filt),
|
||||||
item_type=SidebarItemType.FILTER,
|
item_type=SidebarItemType.FILTER,
|
||||||
)
|
)
|
||||||
root.addChild(item)
|
root.addChild(item)
|
||||||
|
@ -333,7 +330,7 @@ class SidebarTreeView(QTreeView):
|
||||||
item = SidebarItem(
|
item = SidebarItem(
|
||||||
node.name,
|
node.name,
|
||||||
":/icons/tag.svg",
|
":/icons/tag.svg",
|
||||||
self._tag_filter(head + node.name),
|
self._filter_func(SearchTerm(tag=head + node.name)),
|
||||||
toggle_expand(),
|
toggle_expand(),
|
||||||
not node.collapsed,
|
not node.collapsed,
|
||||||
item_type=SidebarItemType.TAG,
|
item_type=SidebarItemType.TAG,
|
||||||
|
@ -358,7 +355,7 @@ class SidebarTreeView(QTreeView):
|
||||||
item = SidebarItem(
|
item = SidebarItem(
|
||||||
node.name,
|
node.name,
|
||||||
":/icons/deck.svg",
|
":/icons/deck.svg",
|
||||||
self._deck_filter(head + node.name),
|
self._filter_func(SearchTerm(deck=head + node.name)),
|
||||||
toggle_expand(),
|
toggle_expand(),
|
||||||
not node.collapsed,
|
not node.collapsed,
|
||||||
item_type=SidebarItemType.DECK,
|
item_type=SidebarItemType.DECK,
|
||||||
|
@ -377,7 +374,7 @@ class SidebarTreeView(QTreeView):
|
||||||
item = SidebarItem(
|
item = SidebarItem(
|
||||||
nt["name"],
|
nt["name"],
|
||||||
":/icons/notetype.svg",
|
":/icons/notetype.svg",
|
||||||
self._note_filter(nt["name"]),
|
self._filter_func(SearchTerm(note=nt["name"])),
|
||||||
item_type=SidebarItemType.NOTETYPE,
|
item_type=SidebarItemType.NOTETYPE,
|
||||||
id=nt["id"],
|
id=nt["id"],
|
||||||
)
|
)
|
||||||
|
@ -386,32 +383,17 @@ class SidebarTreeView(QTreeView):
|
||||||
child = SidebarItem(
|
child = SidebarItem(
|
||||||
tmpl["name"],
|
tmpl["name"],
|
||||||
":/icons/notetype.svg",
|
":/icons/notetype.svg",
|
||||||
self._template_filter(nt["name"], c),
|
self._filter_func(
|
||||||
|
SearchTerm(note=nt["name"]), SearchTerm(template=c)
|
||||||
|
),
|
||||||
item_type=SidebarItemType.TEMPLATE,
|
item_type=SidebarItemType.TEMPLATE,
|
||||||
)
|
)
|
||||||
item.addChild(child)
|
item.addChild(child)
|
||||||
|
|
||||||
root.addChild(item)
|
root.addChild(item)
|
||||||
|
|
||||||
def _named_filter(self, name: "FilterToSearchIn.NamedFilterValue") -> Callable:
|
def _filter_func(self, *terms: Union[str, SearchTerm]) -> Callable:
|
||||||
return lambda: self.browser.update_search(self.col.search_string(name=name))
|
return lambda: self.browser.update_search(self.col.build_search_string(*terms))
|
||||||
|
|
||||||
def _tag_filter(self, tag: str) -> Callable:
|
|
||||||
return lambda: self.browser.update_search(self.col.search_string(tag=tag))
|
|
||||||
|
|
||||||
def _deck_filter(self, deck: str) -> Callable:
|
|
||||||
return lambda: self.browser.update_search(self.col.search_string(deck=deck))
|
|
||||||
|
|
||||||
def _note_filter(self, note: str) -> Callable:
|
|
||||||
return lambda: self.browser.update_search(self.col.search_string(note=note))
|
|
||||||
|
|
||||||
def _template_filter(self, note: str, template: int) -> Callable:
|
|
||||||
return lambda: self.browser.update_search(
|
|
||||||
self.col.search_string(note=note), self.col.search_string(template=template)
|
|
||||||
)
|
|
||||||
|
|
||||||
def _saved_filter(self, saved: str) -> Callable:
|
|
||||||
return lambda: self.browser.update_search(saved)
|
|
||||||
|
|
||||||
# Context menu actions
|
# Context menu actions
|
||||||
###########################
|
###########################
|
||||||
|
|
|
@ -765,41 +765,39 @@ message BuiltinSearchOrder {
|
||||||
}
|
}
|
||||||
|
|
||||||
message FilterToSearchIn {
|
message FilterToSearchIn {
|
||||||
enum NamedFilter {
|
|
||||||
WHOLE_COLLECTION = 0;
|
|
||||||
CURRENT_DECK = 1;
|
|
||||||
ADDED_TODAY = 2;
|
|
||||||
STUDIED_TODAY = 3;
|
|
||||||
AGAIN_TODAY = 4;
|
|
||||||
NEW = 5;
|
|
||||||
LEARN = 6;
|
|
||||||
REVIEW = 7;
|
|
||||||
DUE = 8;
|
|
||||||
SUSPENDED = 9;
|
|
||||||
BURIED = 10;
|
|
||||||
RED_FLAG = 11;
|
|
||||||
ORANGE_FLAG = 12;
|
|
||||||
GREEN_FLAG = 13;
|
|
||||||
BLUE_FLAG = 14;
|
|
||||||
NO_FLAG = 15;
|
|
||||||
ANY_FLAG = 16;
|
|
||||||
}
|
|
||||||
message DupeIn {
|
message DupeIn {
|
||||||
NoteTypeID mid = 1;
|
NoteTypeID mid = 1;
|
||||||
string text = 2;
|
string text = 2;
|
||||||
}
|
}
|
||||||
|
enum Flag {
|
||||||
|
WITHOUT = 0;
|
||||||
|
ANY = 1;
|
||||||
|
RED = 2;
|
||||||
|
ORANGE = 3;
|
||||||
|
GREEN = 4;
|
||||||
|
BLUE = 5;
|
||||||
|
}
|
||||||
oneof filter {
|
oneof filter {
|
||||||
NamedFilter name = 1;
|
string tag = 1;
|
||||||
string tag = 2;
|
string deck = 2;
|
||||||
string deck = 3;
|
string note = 3;
|
||||||
string note = 4;
|
uint32 template = 4;
|
||||||
uint32 template = 5;
|
NoteIDs nids = 5;
|
||||||
DupeIn dupe = 6;
|
DupeIn dupe = 6;
|
||||||
uint32 forgot_in_days = 7;
|
string field_name = 7;
|
||||||
uint32 added_in_days = 8;
|
uint32 forgot_in_days = 8;
|
||||||
int32 due_in_days = 9;
|
uint32 added_in_days = 9;
|
||||||
NoteIDs nids = 10;
|
int32 due_in_days = 10;
|
||||||
string field_name = 11;
|
bool whole_collection = 11;
|
||||||
|
bool current_deck = 12;
|
||||||
|
bool studied_today = 13;
|
||||||
|
bool new = 14;
|
||||||
|
bool learn = 15;
|
||||||
|
bool review = 16;
|
||||||
|
bool due = 17;
|
||||||
|
bool suspended = 18;
|
||||||
|
bool buried = 19;
|
||||||
|
Flag flag = 20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -293,38 +293,8 @@ impl From<pb::DeckConfigId> for DeckConfID {
|
||||||
impl From<pb::FilterToSearchIn> for Node<'_> {
|
impl From<pb::FilterToSearchIn> for Node<'_> {
|
||||||
fn from(msg: pb::FilterToSearchIn) -> Self {
|
fn from(msg: pb::FilterToSearchIn) -> Self {
|
||||||
use pb::filter_to_search_in::Filter;
|
use pb::filter_to_search_in::Filter;
|
||||||
use pb::filter_to_search_in::NamedFilter;
|
use pb::filter_to_search_in::Flag;
|
||||||
match msg
|
match msg.filter.unwrap_or(Filter::WholeCollection(true)) {
|
||||||
.filter
|
|
||||||
.unwrap_or(Filter::Name(NamedFilter::WholeCollection as i32))
|
|
||||||
{
|
|
||||||
Filter::Name(name) => {
|
|
||||||
match NamedFilter::from_i32(name).unwrap_or(NamedFilter::WholeCollection) {
|
|
||||||
NamedFilter::WholeCollection => Node::Search(SearchNode::WholeCollection),
|
|
||||||
NamedFilter::CurrentDeck => Node::Search(SearchNode::Deck("current".into())),
|
|
||||||
NamedFilter::AddedToday => Node::Search(SearchNode::AddedInDays(1)),
|
|
||||||
NamedFilter::StudiedToday => Node::Search(SearchNode::Rated {
|
|
||||||
days: 1,
|
|
||||||
ease: EaseKind::AnyAnswerButton,
|
|
||||||
}),
|
|
||||||
NamedFilter::AgainToday => Node::Search(SearchNode::Rated {
|
|
||||||
days: 1,
|
|
||||||
ease: EaseKind::AnswerButton(1),
|
|
||||||
}),
|
|
||||||
NamedFilter::New => Node::Search(SearchNode::State(StateKind::New)),
|
|
||||||
NamedFilter::Learn => Node::Search(SearchNode::State(StateKind::Learning)),
|
|
||||||
NamedFilter::Review => Node::Search(SearchNode::State(StateKind::Review)),
|
|
||||||
NamedFilter::Due => Node::Search(SearchNode::State(StateKind::Due)),
|
|
||||||
NamedFilter::Suspended => Node::Search(SearchNode::State(StateKind::Suspended)),
|
|
||||||
NamedFilter::Buried => Node::Search(SearchNode::State(StateKind::Buried)),
|
|
||||||
NamedFilter::RedFlag => Node::Search(SearchNode::Flag(1)),
|
|
||||||
NamedFilter::OrangeFlag => Node::Search(SearchNode::Flag(2)),
|
|
||||||
NamedFilter::GreenFlag => Node::Search(SearchNode::Flag(3)),
|
|
||||||
NamedFilter::BlueFlag => Node::Search(SearchNode::Flag(4)),
|
|
||||||
NamedFilter::NoFlag => Node::Search(SearchNode::Flag(0)),
|
|
||||||
NamedFilter::AnyFlag => Node::Not(Box::new(Node::Search(SearchNode::Flag(0)))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Filter::Tag(s) => Node::Search(SearchNode::Tag(
|
Filter::Tag(s) => Node::Search(SearchNode::Tag(
|
||||||
escape_anki_wildcards(&s).into_owned().into(),
|
escape_anki_wildcards(&s).into_owned().into(),
|
||||||
)),
|
)),
|
||||||
|
@ -337,10 +307,16 @@ impl From<pb::FilterToSearchIn> for Node<'_> {
|
||||||
Filter::Template(u) => {
|
Filter::Template(u) => {
|
||||||
Node::Search(SearchNode::CardTemplate(TemplateKind::Ordinal(u as u16)))
|
Node::Search(SearchNode::CardTemplate(TemplateKind::Ordinal(u as u16)))
|
||||||
}
|
}
|
||||||
|
Filter::Nids(nids) => Node::Search(SearchNode::NoteIDs(nids.into_id_string().into())),
|
||||||
Filter::Dupe(dupe) => Node::Search(SearchNode::Duplicates {
|
Filter::Dupe(dupe) => Node::Search(SearchNode::Duplicates {
|
||||||
note_type_id: dupe.mid.unwrap_or(pb::NoteTypeId { ntid: 0 }).into(),
|
note_type_id: dupe.mid.unwrap_or(pb::NoteTypeId { ntid: 0 }).into(),
|
||||||
text: dupe.text.into(),
|
text: dupe.text.into(),
|
||||||
}),
|
}),
|
||||||
|
Filter::FieldName(s) => Node::Search(SearchNode::SingleField {
|
||||||
|
field: escape_anki_wildcards(&s).into_owned().into(),
|
||||||
|
text: "*".to_string().into(),
|
||||||
|
is_re: false,
|
||||||
|
}),
|
||||||
Filter::ForgotInDays(u) => Node::Search(SearchNode::Rated {
|
Filter::ForgotInDays(u) => Node::Search(SearchNode::Rated {
|
||||||
days: u,
|
days: u,
|
||||||
ease: EaseKind::AnswerButton(1),
|
ease: EaseKind::AnswerButton(1),
|
||||||
|
@ -350,12 +326,26 @@ impl From<pb::FilterToSearchIn> for Node<'_> {
|
||||||
operator: "<=".to_string(),
|
operator: "<=".to_string(),
|
||||||
kind: PropertyKind::Due(i),
|
kind: PropertyKind::Due(i),
|
||||||
}),
|
}),
|
||||||
Filter::Nids(nids) => Node::Search(SearchNode::NoteIDs(nids.into_id_string().into())),
|
Filter::WholeCollection(_) => Node::Search(SearchNode::WholeCollection),
|
||||||
Filter::FieldName(s) => Node::Search(SearchNode::SingleField {
|
Filter::CurrentDeck(_) => Node::Search(SearchNode::Deck("current".into())),
|
||||||
field: escape_anki_wildcards(&s).into_owned().into(),
|
Filter::StudiedToday(_) => Node::Search(SearchNode::Rated {
|
||||||
text: "*".to_string().into(),
|
days: 1,
|
||||||
is_re: false,
|
ease: EaseKind::AnyAnswerButton,
|
||||||
}),
|
}),
|
||||||
|
Filter::New(_) => Node::Search(SearchNode::State(StateKind::New)),
|
||||||
|
Filter::Learn(_) => Node::Search(SearchNode::State(StateKind::Learning)),
|
||||||
|
Filter::Review(_) => Node::Search(SearchNode::State(StateKind::Review)),
|
||||||
|
Filter::Due(_) => Node::Search(SearchNode::State(StateKind::Due)),
|
||||||
|
Filter::Suspended(_) => Node::Search(SearchNode::State(StateKind::Suspended)),
|
||||||
|
Filter::Buried(_) => Node::Search(SearchNode::State(StateKind::Buried)),
|
||||||
|
Filter::Flag(flag) => match Flag::from_i32(flag).unwrap_or(Flag::Any) {
|
||||||
|
Flag::Without => Node::Search(SearchNode::Flag(0)),
|
||||||
|
Flag::Any => Node::Not(Box::new(Node::Search(SearchNode::Flag(0)))),
|
||||||
|
Flag::Red => Node::Search(SearchNode::Flag(1)),
|
||||||
|
Flag::Orange => Node::Search(SearchNode::Flag(2)),
|
||||||
|
Flag::Green => Node::Search(SearchNode::Flag(3)),
|
||||||
|
Flag::Blue => Node::Search(SearchNode::Flag(4)),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue