Merge pull request #1102 from RumovZ/more-browser
More browser fixes and features
This commit is contained in:
commit
325920aa23
@ -12,6 +12,8 @@ browsing-browser-appearance = Browser Appearance
|
||||
browsing-browser-options = Browser Options
|
||||
browsing-buried = Buried
|
||||
browsing-card = Card
|
||||
# Exactly one character representing 'Cards'; should differ from browsing-note-initial.
|
||||
browsing-card-initial = C
|
||||
browsing-card-list = Card List
|
||||
browsing-card-state = Card State
|
||||
browsing-cards-cant-be-manually-moved-into = Cards can't be manually moved into a filtered deck.
|
||||
@ -63,6 +65,8 @@ browsing-new = (new)
|
||||
browsing-new-note-type = New note type:
|
||||
browsing-no-flag = No Flag
|
||||
browsing-note = Note
|
||||
# Exactly one character representing 'Notes'; should differ from browsing-card-initial.
|
||||
browsing-note-initial = N
|
||||
browsing-notes-tagged = Notes tagged.
|
||||
browsing-nothing = Nothing
|
||||
browsing-only-new-cards-can-be-repositioned = Only new cards can be repositioned.
|
||||
|
@ -497,8 +497,22 @@ class Collection:
|
||||
reverse: bool = False,
|
||||
) -> Sequence[CardId]:
|
||||
"""Return card ids matching the provided search.
|
||||
|
||||
To programmatically construct a search string, see .build_search_string().
|
||||
To define a sort order, see _build_sort_mode().
|
||||
|
||||
If order=True, use the sort order stored in the collection config
|
||||
If order=False, do no ordering
|
||||
|
||||
If order is a string, that text is added after 'order by' in the sql statement.
|
||||
You must add ' asc' or ' desc' to the order, as Anki will replace asc with
|
||||
desc and vice versa when reverse is set in the collection config, eg
|
||||
order="c.ivl asc, c.due desc".
|
||||
|
||||
If order is a BuiltinSort.Kind value, sort using that builtin sort, eg
|
||||
col.find_cards("", order=BuiltinSort.Kind.CARD_DUE)
|
||||
|
||||
The reverse argument only applies when a BuiltinSort.Kind is provided;
|
||||
otherwise the collection config defines whether reverse is set or not.
|
||||
"""
|
||||
mode = _build_sort_mode(order, reverse)
|
||||
return cast(
|
||||
@ -512,8 +526,9 @@ class Collection:
|
||||
reverse: bool = False,
|
||||
) -> Sequence[NoteId]:
|
||||
"""Return note ids matching the provided search.
|
||||
|
||||
To programmatically construct a search string, see .build_search_string().
|
||||
To define a sort order, see _build_sort_mode().
|
||||
The order parameter is documented in .find_cards().
|
||||
"""
|
||||
mode = _build_sort_mode(order, reverse)
|
||||
return cast(
|
||||
@ -1072,22 +1087,6 @@ def _build_sort_mode(
|
||||
order: Union[bool, str, BuiltinSort.Kind.V],
|
||||
reverse: bool,
|
||||
) -> _pb.SortOrder:
|
||||
"""Return a SortOrder object for use in find_cards() or find_notes().
|
||||
|
||||
If order=True, use the sort order stored in the collection config
|
||||
If order=False, do no ordering
|
||||
|
||||
If order is a string, that text is added after 'order by' in the sql statement.
|
||||
You must add ' asc' or ' desc' to the order, as Anki will replace asc with
|
||||
desc and vice versa when reverse is set in the collection config, eg
|
||||
order="c.ivl asc, c.due desc".
|
||||
|
||||
If order is a BuiltinSort.Kind value, sort using that builtin sort, eg
|
||||
col.find_cards("", order=BuiltinSort.Kind.CARD_DUE)
|
||||
|
||||
The reverse argument only applies when a BuiltinSort.Kind is provided;
|
||||
otherwise the collection config defines whether reverse is set or not.
|
||||
"""
|
||||
if isinstance(order, str):
|
||||
return _pb.SortOrder(custom=order)
|
||||
elif isinstance(order, bool):
|
||||
|
@ -37,6 +37,7 @@ from aqt.scheduling_ops import (
|
||||
unsuspend_cards,
|
||||
)
|
||||
from aqt.sidebar import SidebarTreeView
|
||||
from aqt.switch import Switch
|
||||
from aqt.table import Table
|
||||
from aqt.tag_ops import add_tags, clear_unused_tags, remove_tags_for_notes
|
||||
from aqt.utils import (
|
||||
@ -329,9 +330,9 @@ class Browser(QMainWindow):
|
||||
selected = self.table.len_selection()
|
||||
cur = self.table.len()
|
||||
tr_title = (
|
||||
tr.browsing_window_title
|
||||
if self.table.is_card_state()
|
||||
else tr.browsing_window_title_notes
|
||||
tr.browsing_window_title_notes
|
||||
if self.table.is_notes_mode()
|
||||
else tr.browsing_window_title
|
||||
)
|
||||
self.setWindowTitle(
|
||||
without_unicode_isolation(tr_title(total=cur, selected=selected))
|
||||
@ -374,10 +375,11 @@ class Browser(QMainWindow):
|
||||
|
||||
def setup_table(self) -> None:
|
||||
self.table = Table(self)
|
||||
self.form.radio_cards.setChecked(self.table.is_card_state())
|
||||
self.form.radio_notes.setChecked(not self.table.is_card_state())
|
||||
self.table.set_view(self.form.tableView)
|
||||
qconnect(self.form.radio_cards.toggled, self.on_table_state_changed)
|
||||
switch = Switch(11, tr.browsing_card_initial(), tr.browsing_note_initial())
|
||||
switch.setChecked(self.table.is_notes_mode())
|
||||
qconnect(switch.toggled, self.on_table_state_changed)
|
||||
self.form.gridLayout.addWidget(switch, 0, 0)
|
||||
|
||||
def setupEditor(self) -> None:
|
||||
def add_preview_button(leftbuttons: List[str], editor: Editor) -> None:
|
||||
@ -430,10 +432,10 @@ class Browser(QMainWindow):
|
||||
self._update_flags_menu()
|
||||
gui_hooks.browser_did_change_row(self)
|
||||
|
||||
@ensure_editor_saved_on_trigger
|
||||
def on_table_state_changed(self) -> None:
|
||||
@ensure_editor_saved
|
||||
def on_table_state_changed(self, checked: bool) -> None:
|
||||
self.mw.progress.start()
|
||||
self.table.toggle_state(self.form.radio_cards.isChecked(), self._lastSearchTxt)
|
||||
self.table.toggle_state(checked, self._lastSearchTxt)
|
||||
self.mw.progress.finish()
|
||||
|
||||
# Sidebar
|
||||
|
@ -91,7 +91,7 @@
|
||||
<property name="verticalSpacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="searchEdit">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
@ -109,30 +109,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="view_state" stretch="0,1">
|
||||
<property name="bottomMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radio_cards">
|
||||
<property name="text">
|
||||
<string>qt_accel_cards</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="radio_notes">
|
||||
<property name="text">
|
||||
<string>qt_accel_notes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableView" name="tableView">
|
||||
<property name="sizePolicy">
|
||||
|
115
qt/aqt/switch.py
Normal file
115
qt/aqt/switch.py
Normal file
@ -0,0 +1,115 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
|
||||
from aqt import colors
|
||||
from aqt.qt import *
|
||||
from aqt.theme import theme_manager
|
||||
|
||||
|
||||
class Switch(QAbstractButton):
|
||||
"""A horizontal slider to toggle between two states which can be denoted by short strings.
|
||||
The left state is the default and corresponds to isChecked=False.
|
||||
"""
|
||||
|
||||
_margin: int = 2
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
radius: int = 10,
|
||||
left_label: str = "",
|
||||
right_label: str = "",
|
||||
parent: QWidget = None,
|
||||
) -> None:
|
||||
super().__init__(parent=parent)
|
||||
self.setCheckable(True)
|
||||
super().setChecked(False)
|
||||
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
self._left_label = left_label
|
||||
self._right_label = right_label
|
||||
self._path_radius = radius
|
||||
self._knob_radius = radius - self._margin
|
||||
self._left_position = self._position = self._path_radius + self._margin
|
||||
self._right_position = 3 * self._path_radius + self._margin
|
||||
|
||||
@pyqtProperty(int) # type: ignore
|
||||
def position(self) -> int:
|
||||
return self._position
|
||||
|
||||
@position.setter # type: ignore
|
||||
def position(self, position: int) -> None:
|
||||
self._position = position
|
||||
self.update()
|
||||
|
||||
@property
|
||||
def start_position(self) -> int:
|
||||
return self._left_position if self.isChecked() else self._right_position
|
||||
|
||||
@property
|
||||
def end_position(self) -> int:
|
||||
return self._right_position if self.isChecked() else self._left_position
|
||||
|
||||
@property
|
||||
def label(self) -> str:
|
||||
return self._right_label if self.isChecked() else self._left_label
|
||||
|
||||
def sizeHint(self) -> QSize:
|
||||
return QSize(
|
||||
4 * self._path_radius + 2 * self._margin,
|
||||
2 * self._path_radius + 2 * self._margin,
|
||||
)
|
||||
|
||||
def setChecked(self, checked: bool) -> None:
|
||||
super().setChecked(checked)
|
||||
self._position = self.end_position
|
||||
self.update()
|
||||
|
||||
def paintEvent(self, _event: QPaintEvent) -> None:
|
||||
painter = QPainter(self)
|
||||
painter.setRenderHint(QPainter.Antialiasing, True)
|
||||
painter.setPen(Qt.NoPen)
|
||||
self._paint_path(painter)
|
||||
self._paint_knob(painter)
|
||||
self._paint_label(painter)
|
||||
|
||||
def _paint_path(self, painter: QPainter) -> None:
|
||||
painter.setBrush(QBrush(theme_manager.qcolor(colors.FRAME_BG)))
|
||||
rectangle = QRectF(
|
||||
self._margin,
|
||||
self._margin,
|
||||
self.width() - 2 * self._margin,
|
||||
self.height() - 2 * self._margin,
|
||||
)
|
||||
painter.drawRoundedRect(rectangle, self._path_radius, self._path_radius)
|
||||
|
||||
def _current_knob_rectangle(self) -> QRectF:
|
||||
return QRectF(
|
||||
self.position - self._knob_radius, # type: ignore
|
||||
2 * self._margin,
|
||||
2 * self._knob_radius,
|
||||
2 * self._knob_radius,
|
||||
)
|
||||
|
||||
def _paint_knob(self, painter: QPainter) -> None:
|
||||
painter.setBrush(QBrush(theme_manager.qcolor(colors.HIGHLIGHT_BG)))
|
||||
painter.drawEllipse(self._current_knob_rectangle())
|
||||
|
||||
def _paint_label(self, painter: QPainter) -> None:
|
||||
painter.setPen(QColor("white"))
|
||||
font = painter.font()
|
||||
font.setPixelSize(int(1.5 * self._knob_radius))
|
||||
painter.setFont(font)
|
||||
painter.drawText(self._current_knob_rectangle(), Qt.AlignCenter, self.label)
|
||||
|
||||
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
|
||||
super().mouseReleaseEvent(event)
|
||||
if event.button() == Qt.LeftButton:
|
||||
animation = QPropertyAnimation(self, b"position", self)
|
||||
animation.setDuration(100)
|
||||
animation.setStartValue(self.start_position)
|
||||
animation.setEndValue(self.end_position)
|
||||
animation.start()
|
||||
|
||||
def enterEvent(self, event: QEvent) -> None:
|
||||
self.setCursor(Qt.PointingHandCursor)
|
||||
super().enterEvent(event)
|
@ -48,8 +48,7 @@ class SearchContext:
|
||||
search: str
|
||||
browser: aqt.browser.Browser
|
||||
order: Union[bool, str] = True
|
||||
# if set, provided card ids will be used instead of the regular search
|
||||
# fixme: legacy support for card_ids?
|
||||
# if set, provided ids will be used instead of the regular search
|
||||
ids: Optional[Sequence[ItemId]] = None
|
||||
|
||||
|
||||
@ -94,8 +93,8 @@ class Table:
|
||||
def has_next(self) -> bool:
|
||||
return self.has_current() and self._current().row() < self.len() - 1
|
||||
|
||||
def is_card_state(self) -> bool:
|
||||
return self._state.is_card_state()
|
||||
def is_notes_mode(self) -> bool:
|
||||
return self._state.is_notes_mode()
|
||||
|
||||
# Get objects
|
||||
|
||||
@ -194,15 +193,15 @@ class Table:
|
||||
self._model.search(SearchContext(search=txt, browser=self.browser))
|
||||
self._restore_selection(self._intersected_selection)
|
||||
|
||||
def toggle_state(self, is_card_state: bool, last_search: str) -> None:
|
||||
if is_card_state == self.is_card_state():
|
||||
def toggle_state(self, is_notes_mode: bool, last_search: str) -> None:
|
||||
if is_notes_mode == self.is_notes_mode():
|
||||
return
|
||||
self._save_selection()
|
||||
self._state = self._model.toggle_state(
|
||||
SearchContext(search=last_search, browser=self.browser)
|
||||
)
|
||||
self.col.set_config_bool(
|
||||
Config.Bool.BROWSER_TABLE_SHOW_NOTES_MODE, not self.is_card_state()
|
||||
Config.Bool.BROWSER_TABLE_SHOW_NOTES_MODE, self.is_notes_mode()
|
||||
)
|
||||
self._set_sort_indicator()
|
||||
self._set_column_sizes()
|
||||
@ -328,14 +327,14 @@ class Table:
|
||||
|
||||
def _on_context_menu(self, _point: QPoint) -> None:
|
||||
menu = QMenu()
|
||||
if self.is_card_state():
|
||||
main = self.browser.form.menu_Cards
|
||||
other = self.browser.form.menu_Notes
|
||||
other_name = tr.qt_accel_notes()
|
||||
else:
|
||||
if self.is_notes_mode():
|
||||
main = self.browser.form.menu_Notes
|
||||
other = self.browser.form.menu_Cards
|
||||
other_name = tr.qt_accel_cards()
|
||||
else:
|
||||
main = self.browser.form.menu_Cards
|
||||
other = self.browser.form.menu_Notes
|
||||
other_name = tr.qt_accel_notes()
|
||||
for action in main.actions():
|
||||
menu.addAction(action)
|
||||
menu.addSeparator()
|
||||
@ -417,7 +416,8 @@ class Table:
|
||||
current = current or rows[0]
|
||||
self._select_rows(rows)
|
||||
self._set_current(current)
|
||||
self._scroll_to_row(current)
|
||||
# editor may pop up and hide the row later on
|
||||
QTimer.singleShot(100, lambda: self._scroll_to_row(current))
|
||||
if self.len_selection() == 0:
|
||||
# no row change will fire
|
||||
self.browser.onRowChanged(QItemSelection(), QItemSelection())
|
||||
@ -431,7 +431,7 @@ class Table:
|
||||
if rows:
|
||||
if len(rows) < self.SELECTION_LIMIT:
|
||||
return rows
|
||||
if current in rows:
|
||||
if current and current in rows:
|
||||
return [current]
|
||||
return rows[0:1]
|
||||
return [current if current else 0]
|
||||
@ -453,17 +453,19 @@ class Table:
|
||||
selected_rows = self._model.get_item_rows(
|
||||
self._state.get_new_items(self._selected_items)
|
||||
)
|
||||
current_row = self._current_item and self._model.get_item_row(
|
||||
self._state.get_new_item(self._current_item)
|
||||
)
|
||||
current_row = None
|
||||
if self._current_item:
|
||||
if new_current := self._state.get_new_items([self._current_item]):
|
||||
current_row = self._model.get_item_row(new_current[0])
|
||||
return selected_rows, current_row
|
||||
|
||||
# Move
|
||||
|
||||
def _scroll_to_row(self, row: int) -> None:
|
||||
"""Scroll vertically to row."""
|
||||
position = self._view.rowViewportPosition(row)
|
||||
visible = 0 <= position < self._view.viewport().height()
|
||||
top_border = self._view.rowViewportPosition(row)
|
||||
bottom_border = top_border + self._view.rowHeight(0)
|
||||
visible = top_border >= 0 and bottom_border < self._view.viewport().height()
|
||||
if not visible:
|
||||
horizontal = self._view.horizontalScrollBar().value()
|
||||
self._view.scrollTo(self._model.index(row, 0), self._view.PositionAtCenter)
|
||||
@ -527,9 +529,9 @@ class ItemState(ABC):
|
||||
def __init__(self, col: Collection) -> None:
|
||||
self.col = col
|
||||
|
||||
def is_card_state(self) -> bool:
|
||||
"""Return True if the state is a CardState."""
|
||||
return isinstance(self, CardState)
|
||||
def is_notes_mode(self) -> bool:
|
||||
"""Return True if the state is a NoteState."""
|
||||
return isinstance(self, NoteState)
|
||||
|
||||
# Stateless Helpers
|
||||
|
||||
@ -543,6 +545,8 @@ class ItemState(ABC):
|
||||
|
||||
# Columns and sorting
|
||||
|
||||
# abstractproperty is deprecated but used due to mypy limitations
|
||||
# (https://github.com/python/mypy/issues/1362)
|
||||
@abstractproperty
|
||||
def columns(self) -> List[Tuple[str, str]]:
|
||||
"""Return all for the state available columns."""
|
||||
@ -605,15 +609,9 @@ class ItemState(ABC):
|
||||
def toggle_state(self) -> ItemState:
|
||||
"""Return an instance of the other state."""
|
||||
|
||||
@abstractmethod
|
||||
def get_new_item(self, old_item: ItemId) -> ItemId:
|
||||
"""Given an id from the other state, return the corresponding id for
|
||||
this state."""
|
||||
|
||||
@abstractmethod
|
||||
def get_new_items(self, old_items: Sequence[ItemId]) -> ItemList:
|
||||
"""Given a list of ids from the other state, return the corresponding
|
||||
ids for this state."""
|
||||
"""Given a list of ids from the other state, return the corresponding ids for this state."""
|
||||
|
||||
|
||||
class CardState(ItemState):
|
||||
@ -705,9 +703,6 @@ class CardState(ItemState):
|
||||
def toggle_state(self) -> NoteState:
|
||||
return NoteState(self.col)
|
||||
|
||||
def get_new_item(self, old_item: ItemId) -> CardId:
|
||||
return super().card_ids_from_note_ids([old_item])[0]
|
||||
|
||||
def get_new_items(self, old_items: Sequence[ItemId]) -> Sequence[CardId]:
|
||||
return super().card_ids_from_note_ids(old_items)
|
||||
|
||||
@ -725,11 +720,13 @@ class NoteState(ItemState):
|
||||
def _load_columns(self) -> None:
|
||||
self._columns = [
|
||||
("note", tr.browsing_note()),
|
||||
("noteCards", tr.qt_accel_cards().replace("&", "")),
|
||||
("noteCards", tr.editing_cards()),
|
||||
("noteCrt", tr.browsing_created()),
|
||||
("noteEase", tr.browsing_average_ease()),
|
||||
("noteFld", tr.browsing_sort_field()),
|
||||
("noteLapses", tr.scheduling_lapses()),
|
||||
("noteMod", tr.search_note_modified()),
|
||||
("noteReps", tr.scheduling_reviews()),
|
||||
("noteTags", tr.editing_tags()),
|
||||
]
|
||||
self._columns.sort(key=itemgetter(1))
|
||||
@ -793,9 +790,6 @@ class NoteState(ItemState):
|
||||
def toggle_state(self) -> CardState:
|
||||
return CardState(self.col)
|
||||
|
||||
def get_new_item(self, old_item: ItemId) -> NoteId:
|
||||
return super().note_ids_from_card_ids([old_item])[0]
|
||||
|
||||
def get_new_items(self, old_items: Sequence[ItemId]) -> Sequence[NoteId]:
|
||||
return super().note_ids_from_card_ids(old_items)
|
||||
|
||||
@ -811,6 +805,8 @@ class Cell:
|
||||
|
||||
|
||||
class CellRow:
|
||||
is_deleted: bool = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
cells: Generator[Tuple[str, bool], None, None],
|
||||
@ -842,7 +838,9 @@ class CellRow:
|
||||
|
||||
@staticmethod
|
||||
def deleted(length: int) -> CellRow:
|
||||
return CellRow.generic(length, tr.browsing_row_deleted())
|
||||
row = CellRow.generic(length, tr.browsing_row_deleted())
|
||||
row.is_deleted = True
|
||||
return row
|
||||
|
||||
|
||||
def backend_color_to_aqt_color(color: BrowserRow.Color.V) -> Optional[Tuple[str, str]]:
|
||||
@ -896,7 +894,7 @@ class DataModel(QAbstractTableModel):
|
||||
self._rows[item] = self._fetch_row_from_backend(item)
|
||||
return self._rows[item]
|
||||
|
||||
def _fetch_row_from_backend(self, item: int) -> CellRow:
|
||||
def _fetch_row_from_backend(self, item: ItemId) -> CellRow:
|
||||
try:
|
||||
row = CellRow(*self.col.browser_row_for_id(item))
|
||||
except NotFoundError:
|
||||
@ -904,8 +902,9 @@ class DataModel(QAbstractTableModel):
|
||||
except Exception as e:
|
||||
return CellRow.generic(self.len_columns(), str(e))
|
||||
|
||||
# fixme: hook needs state
|
||||
gui_hooks.browser_did_fetch_row(item, row, self._state.active_columns)
|
||||
gui_hooks.browser_did_fetch_row(
|
||||
item, self._state.is_notes_mode(), row, self._state.active_columns
|
||||
)
|
||||
return row
|
||||
|
||||
# Reset
|
||||
@ -1113,6 +1112,8 @@ class DataModel(QAbstractTableModel):
|
||||
return None
|
||||
|
||||
def flags(self, index: QModelIndex) -> Qt.ItemFlags:
|
||||
if self.get_row(index).is_deleted:
|
||||
return Qt.ItemFlags(Qt.NoItemFlags)
|
||||
return cast(Qt.ItemFlags, Qt.ItemIsEnabled | Qt.ItemIsSelectable)
|
||||
|
||||
|
||||
|
@ -406,7 +406,12 @@ hooks = [
|
||||
),
|
||||
Hook(
|
||||
name="browser_did_fetch_row",
|
||||
args=["card_id: int", "row: aqt.table.CellRow", "columns: Sequence[str]"],
|
||||
args=[
|
||||
"card_or_note_id: aqt.table.ItemId",
|
||||
"is_note: bool",
|
||||
"row: aqt.table.CellRow",
|
||||
"columns: Sequence[str]",
|
||||
],
|
||||
doc="""Allows you to add or modify content to a row in the browser.
|
||||
|
||||
You can mutate the row object to change what is displayed. Any columns the
|
||||
|
@ -810,18 +810,20 @@ message SortOrder {
|
||||
NOTE_CARDS = 0;
|
||||
NOTE_CREATION = 1;
|
||||
NOTE_EASE = 2;
|
||||
NOTE_MOD = 3;
|
||||
NOTE_FIELD = 4;
|
||||
NOTE_TAGS = 5;
|
||||
NOTETYPE = 6;
|
||||
CARD_MOD = 7;
|
||||
CARD_REPS = 8;
|
||||
CARD_DUE = 9;
|
||||
CARD_EASE = 10;
|
||||
CARD_LAPSES = 11;
|
||||
CARD_INTERVAL = 12;
|
||||
CARD_DECK = 13;
|
||||
CARD_TEMPLATE = 14;
|
||||
NOTE_FIELD = 3;
|
||||
NOTE_LAPSES = 4;
|
||||
NOTE_MOD = 5;
|
||||
NOTE_REPS = 6;
|
||||
NOTE_TAGS = 7;
|
||||
NOTETYPE = 8;
|
||||
CARD_MOD = 9;
|
||||
CARD_REPS = 10;
|
||||
CARD_DUE = 11;
|
||||
CARD_EASE = 12;
|
||||
CARD_LAPSES = 13;
|
||||
CARD_INTERVAL = 14;
|
||||
CARD_DECK = 15;
|
||||
CARD_TEMPLATE = 16;
|
||||
}
|
||||
Kind kind = 1;
|
||||
bool reverse = 2;
|
||||
|
@ -100,8 +100,10 @@ impl From<SortKindProto> for SortKind {
|
||||
SortKindProto::NoteCards => SortKind::NoteCards,
|
||||
SortKindProto::NoteCreation => SortKind::NoteCreation,
|
||||
SortKindProto::NoteEase => SortKind::NoteEase,
|
||||
SortKindProto::NoteLapses => SortKind::NoteLapses,
|
||||
SortKindProto::NoteMod => SortKind::NoteMod,
|
||||
SortKindProto::NoteField => SortKind::NoteField,
|
||||
SortKindProto::NoteReps => SortKind::NoteReps,
|
||||
SortKindProto::NoteTags => SortKind::NoteTags,
|
||||
SortKindProto::Notetype => SortKind::Notetype,
|
||||
SortKindProto::CardMod => SortKind::CardMod,
|
||||
|
@ -428,7 +428,9 @@ impl RowContext for NoteRowContext<'_> {
|
||||
"noteCrt" => self.note_creation_str(),
|
||||
"noteEase" => self.note_ease_str(),
|
||||
"noteFld" => self.note_field_str(),
|
||||
"noteLapses" => self.cards.iter().map(|c| c.lapses).sum::<u32>().to_string(),
|
||||
"noteMod" => self.note.mtime.date_string(),
|
||||
"noteReps" => self.cards.iter().map(|c| c.reps).sum::<u32>().to_string(),
|
||||
"noteTags" => self.note.tags.join(" "),
|
||||
_ => "".to_string(),
|
||||
})
|
||||
|
@ -275,9 +275,11 @@ pub enum SortKind {
|
||||
#[serde(rename = "noteCrt")]
|
||||
NoteCreation,
|
||||
NoteEase,
|
||||
NoteLapses,
|
||||
NoteMod,
|
||||
#[serde(rename = "noteFld")]
|
||||
NoteField,
|
||||
NoteReps,
|
||||
#[serde(rename = "note")]
|
||||
Notetype,
|
||||
NoteTags,
|
||||
|
@ -91,10 +91,12 @@ impl SortKind {
|
||||
SortKind::NoteCards
|
||||
| SortKind::NoteCreation
|
||||
| SortKind::NoteEase
|
||||
| SortKind::NoteMod
|
||||
| SortKind::NoteField
|
||||
| SortKind::Notetype
|
||||
| SortKind::NoteTags => RequiredTable::Notes,
|
||||
| SortKind::NoteLapses
|
||||
| SortKind::NoteMod
|
||||
| SortKind::NoteReps
|
||||
| SortKind::NoteTags
|
||||
| SortKind::Notetype => RequiredTable::Notes,
|
||||
SortKind::CardTemplate => RequiredTable::CardsAndNotes,
|
||||
SortKind::CardMod
|
||||
| SortKind::CardReps
|
||||
@ -250,11 +252,12 @@ fn card_order_from_sortkind(kind: SortKind) -> Cow<'static, str> {
|
||||
|
||||
fn note_order_from_sortkind(kind: SortKind) -> Cow<'static, str> {
|
||||
match kind {
|
||||
SortKind::NoteCards => "(select pos from sort_order where nid = n.id) asc".into(),
|
||||
SortKind::NoteCards | SortKind::NoteEase | SortKind::NoteLapses | SortKind::NoteReps => {
|
||||
"(select pos from sort_order where nid = n.id) asc".into()
|
||||
}
|
||||
SortKind::NoteCreation => "n.id asc".into(),
|
||||
SortKind::NoteEase => "(select pos from sort_order where nid = n.id) asc".into(),
|
||||
SortKind::NoteMod => "n.mod asc".into(),
|
||||
SortKind::NoteField => "n.sfld collate nocase asc".into(),
|
||||
SortKind::NoteMod => "n.mod asc".into(),
|
||||
SortKind::NoteTags => "n.tags asc".into(),
|
||||
SortKind::Notetype => "(select pos from sort_order where ntid = n.mid) asc".into(),
|
||||
_ => "".into(),
|
||||
@ -265,10 +268,12 @@ fn prepare_sort(col: &mut Collection, kind: SortKind) -> Result<()> {
|
||||
use SortKind::*;
|
||||
let sql = match kind {
|
||||
CardDeck => include_str!("deck_order.sql"),
|
||||
Notetype => include_str!("notetype_order.sql"),
|
||||
CardTemplate => include_str!("template_order.sql"),
|
||||
NoteCards => include_str!("note_cards_order.sql"),
|
||||
NoteEase => include_str!("note_ease_order.sql"),
|
||||
NoteLapses => include_str!("note_lapses_order.sql"),
|
||||
NoteReps => include_str!("note_reps_order.sql"),
|
||||
Notetype => include_str!("notetype_order.sql"),
|
||||
_ => return Ok(()),
|
||||
};
|
||||
|
||||
|
10
rslib/src/search/note_lapses_order.sql
Normal file
10
rslib/src/search/note_lapses_order.sql
Normal file
@ -0,0 +1,10 @@
|
||||
DROP TABLE IF EXISTS sort_order;
|
||||
CREATE TEMPORARY TABLE sort_order (
|
||||
pos integer PRIMARY KEY,
|
||||
nid integer NOT NULL UNIQUE
|
||||
);
|
||||
INSERT INTO sort_order (nid)
|
||||
SELECT nid
|
||||
FROM cards
|
||||
GROUP BY nid
|
||||
ORDER BY SUM(lapses);
|
10
rslib/src/search/note_reps_order.sql
Normal file
10
rslib/src/search/note_reps_order.sql
Normal file
@ -0,0 +1,10 @@
|
||||
DROP TABLE IF EXISTS sort_order;
|
||||
CREATE TEMPORARY TABLE sort_order (
|
||||
pos integer PRIMARY KEY,
|
||||
nid integer NOT NULL UNIQUE
|
||||
);
|
||||
INSERT INTO sort_order (nid)
|
||||
SELECT nid
|
||||
FROM cards
|
||||
GROUP BY nid
|
||||
ORDER BY SUM(reps);
|
Loading…
Reference in New Issue
Block a user