mirror of
https://github.com/ankitects/anki.git
synced 2025-12-14 07:10:59 -05:00
enable redo support
Also: - fix issues where the Undo action in the Browse screen was not consistent with the main window. The existing hook signature has been changed; from a snapshot of the add-on code from a few months ago, it was not a hook that was being used by anyone. - change the undo shortcut in the Browse window to match the main window. It was different because undoing a change in the editing area could accidentally trigger an undo of an operation, but the damage is limited now that (most) operations can be redone. If it still proves to be a problem, perhaps we should just always swallow ctrl+z when an editing field is focused.
This commit is contained in:
parent
1f77be01e7
commit
9f3f6bab7d
10 changed files with 126 additions and 56 deletions
|
|
@ -30,5 +30,6 @@ qt-accel-support-anki = &Support Anki...
|
||||||
qt-accel-switch-profile = &Switch Profile
|
qt-accel-switch-profile = &Switch Profile
|
||||||
qt-accel-tools = &Tools
|
qt-accel-tools = &Tools
|
||||||
qt-accel-undo = &Undo
|
qt-accel-undo = &Undo
|
||||||
|
qt-accel-redo = &Redo
|
||||||
qt-accel-set-due-date = Set &Due Date...
|
qt-accel-set-due-date = Set &Due Date...
|
||||||
qt-accel-forget = &Forget
|
qt-accel-forget = &Forget
|
||||||
|
|
|
||||||
|
|
@ -885,7 +885,7 @@ table.review-log {{ {revlog_style} }}
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def undo_status(self) -> UndoStatus:
|
def undo_status(self) -> UndoStatus:
|
||||||
"Return the undo status. At the moment, redo is not supported."
|
"Return the undo status."
|
||||||
# check backend first
|
# check backend first
|
||||||
if status := self._check_backend_undo_status():
|
if status := self._check_backend_undo_status():
|
||||||
return status
|
return status
|
||||||
|
|
@ -939,6 +939,14 @@ table.review-log {{ {revlog_style} }}
|
||||||
self.models._clear_cache()
|
self.models._clear_cache()
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
def redo(self) -> OpChangesAfterUndo:
|
||||||
|
"""Returns result of backend redo operation, or throws UndoEmpty."""
|
||||||
|
out = self._backend.redo()
|
||||||
|
self.clear_python_undo()
|
||||||
|
if out.changes.notetype:
|
||||||
|
self.models._clear_cache()
|
||||||
|
return out
|
||||||
|
|
||||||
def undo_legacy(self) -> LegacyUndoResult:
|
def undo_legacy(self) -> LegacyUndoResult:
|
||||||
"Returns None if the legacy undo queue is empty."
|
"Returns None if the legacy undo queue is empty."
|
||||||
if isinstance(self._undo, _ReviewsUndo):
|
if isinstance(self._undo, _ReviewsUndo):
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.editor import Editor
|
from aqt.editor import Editor
|
||||||
from aqt.exporting import ExportDialog
|
from aqt.exporting import ExportDialog
|
||||||
from aqt.operations.card import set_card_deck, set_card_flag
|
from aqt.operations.card import set_card_deck, set_card_flag
|
||||||
from aqt.operations.collection import undo
|
from aqt.operations.collection import redo, undo
|
||||||
from aqt.operations.note import remove_notes
|
from aqt.operations.note import remove_notes
|
||||||
from aqt.operations.scheduling import (
|
from aqt.operations.scheduling import (
|
||||||
forget_cards,
|
forget_cards,
|
||||||
|
|
@ -35,6 +35,7 @@ from aqt.operations.tag import (
|
||||||
)
|
)
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.switch import Switch
|
from aqt.switch import Switch
|
||||||
|
from aqt.undo import UndoActionsInfo
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
HelpPage,
|
HelpPage,
|
||||||
KeyboardModifiersPressed,
|
KeyboardModifiersPressed,
|
||||||
|
|
@ -101,7 +102,8 @@ class Browser(QMainWindow):
|
||||||
self.setupMenus()
|
self.setupMenus()
|
||||||
self.setupHooks()
|
self.setupHooks()
|
||||||
self.setupEditor()
|
self.setupEditor()
|
||||||
self.onUndoState(self.mw.form.actionUndo.isEnabled())
|
# disable undo/redo
|
||||||
|
self.on_undo_state_change(mw.undo_actions_info())
|
||||||
self.setupSearch(card, search)
|
self.setupSearch(card, search)
|
||||||
gui_hooks.browser_will_show(self)
|
gui_hooks.browser_will_show(self)
|
||||||
self.show()
|
self.show()
|
||||||
|
|
@ -139,6 +141,7 @@ class Browser(QMainWindow):
|
||||||
f = self.form
|
f = self.form
|
||||||
# edit
|
# edit
|
||||||
qconnect(f.actionUndo.triggered, self.undo)
|
qconnect(f.actionUndo.triggered, self.undo)
|
||||||
|
qconnect(f.actionRedo.triggered, self.redo)
|
||||||
qconnect(f.actionInvertSelection.triggered, self.table.invert_selection)
|
qconnect(f.actionInvertSelection.triggered, self.table.invert_selection)
|
||||||
qconnect(f.actionSelectNotes.triggered, self.selectNotes)
|
qconnect(f.actionSelectNotes.triggered, self.selectNotes)
|
||||||
if not isMac:
|
if not isMac:
|
||||||
|
|
@ -786,14 +789,14 @@ where id in %s"""
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
||||||
def setupHooks(self) -> None:
|
def setupHooks(self) -> None:
|
||||||
gui_hooks.undo_state_did_change.append(self.onUndoState)
|
gui_hooks.undo_state_did_change.append(self.on_undo_state_change)
|
||||||
gui_hooks.backend_will_block.append(self.table.on_backend_will_block)
|
gui_hooks.backend_will_block.append(self.table.on_backend_will_block)
|
||||||
gui_hooks.backend_did_block.append(self.table.on_backend_did_block)
|
gui_hooks.backend_did_block.append(self.table.on_backend_did_block)
|
||||||
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
|
gui_hooks.operation_did_execute.append(self.on_operation_did_execute)
|
||||||
gui_hooks.focus_did_change.append(self.on_focus_change)
|
gui_hooks.focus_did_change.append(self.on_focus_change)
|
||||||
|
|
||||||
def teardownHooks(self) -> None:
|
def teardownHooks(self) -> None:
|
||||||
gui_hooks.undo_state_did_change.remove(self.onUndoState)
|
gui_hooks.undo_state_did_change.remove(self.on_undo_state_change)
|
||||||
gui_hooks.backend_will_block.remove(self.table.on_backend_will_block)
|
gui_hooks.backend_will_block.remove(self.table.on_backend_will_block)
|
||||||
gui_hooks.backend_did_block.remove(self.table.on_backend_will_block)
|
gui_hooks.backend_did_block.remove(self.table.on_backend_will_block)
|
||||||
gui_hooks.operation_did_execute.remove(self.on_operation_did_execute)
|
gui_hooks.operation_did_execute.remove(self.on_operation_did_execute)
|
||||||
|
|
@ -805,10 +808,15 @@ where id in %s"""
|
||||||
def undo(self) -> None:
|
def undo(self) -> None:
|
||||||
undo(parent=self)
|
undo(parent=self)
|
||||||
|
|
||||||
def onUndoState(self, on: bool) -> None:
|
def redo(self) -> None:
|
||||||
self.form.actionUndo.setEnabled(on)
|
redo(parent=self)
|
||||||
if on:
|
|
||||||
self.form.actionUndo.setText(self.mw.form.actionUndo.text())
|
def on_undo_state_change(self, info: UndoActionsInfo) -> None:
|
||||||
|
self.form.actionUndo.setText(info.undo_text)
|
||||||
|
self.form.actionUndo.setEnabled(info.can_undo)
|
||||||
|
self.form.actionRedo.setText(info.redo_text)
|
||||||
|
self.form.actionRedo.setEnabled(info.can_redo)
|
||||||
|
self.form.actionRedo.setVisible(info.show_redo)
|
||||||
|
|
||||||
# Edit: replacing
|
# Edit: replacing
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
|
||||||
|
|
@ -144,12 +144,12 @@
|
||||||
<attribute name="horizontalHeaderCascadingSectionResizes">
|
<attribute name="horizontalHeaderCascadingSectionResizes">
|
||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</attribute>
|
</attribute>
|
||||||
<attribute name="horizontalHeaderHighlightSections">
|
|
||||||
<bool>false</bool>
|
|
||||||
</attribute>
|
|
||||||
<attribute name="horizontalHeaderMinimumSectionSize">
|
<attribute name="horizontalHeaderMinimumSectionSize">
|
||||||
<number>20</number>
|
<number>20</number>
|
||||||
</attribute>
|
</attribute>
|
||||||
|
<attribute name="horizontalHeaderHighlightSections">
|
||||||
|
<bool>false</bool>
|
||||||
|
</attribute>
|
||||||
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
|
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
</attribute>
|
</attribute>
|
||||||
|
|
@ -209,7 +209,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>750</width>
|
<width>750</width>
|
||||||
<height>21</height>
|
<height>24</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuEdit">
|
<widget class="QMenu" name="menuEdit">
|
||||||
|
|
@ -217,6 +217,7 @@
|
||||||
<string>qt_accel_edit</string>
|
<string>qt_accel_edit</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionUndo"/>
|
<addaction name="actionUndo"/>
|
||||||
|
<addaction name="actionRedo"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="action_toggle_mode"/>
|
<addaction name="action_toggle_mode"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
|
|
@ -319,7 +320,7 @@
|
||||||
<string>qt_accel_undo</string>
|
<string>qt_accel_undo</string>
|
||||||
</property>
|
</property>
|
||||||
<property name="shortcut">
|
<property name="shortcut">
|
||||||
<string notr="true">Ctrl+Alt+Z</string>
|
<string notr="true">Ctrl+Z</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionInvertSelection">
|
<action name="actionInvertSelection">
|
||||||
|
|
@ -613,6 +614,14 @@
|
||||||
<string notr="true">Alt+T</string>
|
<string notr="true">Alt+T</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionRedo">
|
||||||
|
<property name="text">
|
||||||
|
<string>qt_accel_redo</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string notr="true">Ctrl+Shift+Z</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="icons.qrc"/>
|
<include location="icons.qrc"/>
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>667</width>
|
<width>667</width>
|
||||||
<height>22</height>
|
<height>24</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuHelp">
|
<widget class="QMenu" name="menuHelp">
|
||||||
|
|
@ -63,6 +63,7 @@
|
||||||
<string>qt_accel_edit</string>
|
<string>qt_accel_edit</string>
|
||||||
</property>
|
</property>
|
||||||
<addaction name="actionUndo"/>
|
<addaction name="actionUndo"/>
|
||||||
|
<addaction name="actionRedo"/>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuCol">
|
<widget class="QMenu" name="menuCol">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
|
|
@ -237,6 +238,17 @@
|
||||||
<string notr="true">Ctrl+Shift+A</string>
|
<string notr="true">Ctrl+Shift+A</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionRedo">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>qt_accel_redo</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string notr="true">Ctrl+Shift+Z</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<resources>
|
<resources>
|
||||||
<include location="icons.qrc"/>
|
<include location="icons.qrc"/>
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ from aqt.emptycards import show_empty_cards
|
||||||
from aqt.legacy import install_pylib_legacy
|
from aqt.legacy import install_pylib_legacy
|
||||||
from aqt.mediacheck import check_media_db
|
from aqt.mediacheck import check_media_db
|
||||||
from aqt.mediasync import MediaSyncer
|
from aqt.mediasync import MediaSyncer
|
||||||
from aqt.operations.collection import undo
|
from aqt.operations.collection import redo, undo
|
||||||
from aqt.operations.deck import set_current_deck
|
from aqt.operations.deck import set_current_deck
|
||||||
from aqt.profiles import ProfileManager as ProfileManagerType
|
from aqt.profiles import ProfileManager as ProfileManagerType
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
|
|
@ -61,6 +61,7 @@ from aqt.qt import sip
|
||||||
from aqt.sync import sync_collection, sync_login
|
from aqt.sync import sync_collection, sync_login
|
||||||
from aqt.taskman import TaskManager
|
from aqt.taskman import TaskManager
|
||||||
from aqt.theme import theme_manager
|
from aqt.theme import theme_manager
|
||||||
|
from aqt.undo import UndoActionsInfo
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
HelpPage,
|
HelpPage,
|
||||||
KeyboardModifiersPressed,
|
KeyboardModifiersPressed,
|
||||||
|
|
@ -1070,44 +1071,31 @@ title="%s" %s>%s</button>""" % (
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def undo(self) -> None:
|
def undo(self) -> None:
|
||||||
"Call collection_ops.py:undo() directly instead."
|
"Call operations/collection.py:undo() directly instead."
|
||||||
undo(parent=self)
|
undo(parent=self)
|
||||||
|
|
||||||
def update_undo_actions(self, status: Optional[UndoStatus] = None) -> None:
|
def redo(self) -> None:
|
||||||
"""Update menu text and enable/disable menu item as appropriate.
|
"Call operations/collection.py:redo() directly instead."
|
||||||
Plural as this may handle redo in the future too."""
|
redo(parent=self)
|
||||||
if self.col:
|
|
||||||
status = status or self.col.undo_status()
|
|
||||||
undo_action = status.undo or None
|
|
||||||
else:
|
|
||||||
undo_action = None
|
|
||||||
|
|
||||||
if undo_action:
|
def undo_actions_info(self) -> UndoActionsInfo:
|
||||||
undo_action = tr.undo_undo_action(val=undo_action)
|
"Info about the current undo/redo state for updating menus."
|
||||||
self.form.actionUndo.setText(undo_action)
|
status = self.col.undo_status() if self.col else UndoStatus()
|
||||||
self.form.actionUndo.setEnabled(True)
|
return UndoActionsInfo.from_undo_status(status)
|
||||||
gui_hooks.undo_state_did_change(True)
|
|
||||||
else:
|
|
||||||
self.form.actionUndo.setText(tr.undo_undo())
|
|
||||||
self.form.actionUndo.setEnabled(False)
|
|
||||||
gui_hooks.undo_state_did_change(False)
|
|
||||||
|
|
||||||
def _update_undo_actions_for_status_and_save(self, status: UndoStatus) -> None:
|
def update_undo_actions(self) -> None:
|
||||||
"""Update menu text and enable/disable menu item as appropriate.
|
"""Tell the UI to redraw the undo/redo menu actions based on the current state.
|
||||||
Plural as this may handle redo in the future too."""
|
|
||||||
undo_action = status.undo
|
|
||||||
|
|
||||||
if undo_action:
|
Usually you do not need to call this directly; it is called when a
|
||||||
undo_action = tr.undo_undo_action(val=undo_action)
|
CollectionOp is run, and will be called when the legacy .reset() or
|
||||||
self.form.actionUndo.setText(undo_action)
|
.checkpoint() methods are used."""
|
||||||
self.form.actionUndo.setEnabled(True)
|
info = self.undo_actions_info()
|
||||||
gui_hooks.undo_state_did_change(True)
|
self.form.actionUndo.setText(info.undo_text)
|
||||||
else:
|
self.form.actionUndo.setEnabled(info.can_undo)
|
||||||
self.form.actionUndo.setText(tr.undo_undo())
|
self.form.actionRedo.setText(info.redo_text)
|
||||||
self.form.actionUndo.setEnabled(False)
|
self.form.actionRedo.setEnabled(info.can_redo)
|
||||||
gui_hooks.undo_state_did_change(False)
|
self.form.actionRedo.setVisible(info.show_redo)
|
||||||
|
gui_hooks.undo_state_did_change(info)
|
||||||
self.col.autosave()
|
|
||||||
|
|
||||||
def checkpoint(self, name: str) -> None:
|
def checkpoint(self, name: str) -> None:
|
||||||
self.col.save(name)
|
self.col.save(name)
|
||||||
|
|
@ -1233,7 +1221,8 @@ title="%s" %s>%s</button>""" % (
|
||||||
qconnect(m.actionExit.triggered, self.close)
|
qconnect(m.actionExit.triggered, self.close)
|
||||||
qconnect(m.actionPreferences.triggered, self.onPrefs)
|
qconnect(m.actionPreferences.triggered, self.onPrefs)
|
||||||
qconnect(m.actionAbout.triggered, self.onAbout)
|
qconnect(m.actionAbout.triggered, self.onAbout)
|
||||||
qconnect(m.actionUndo.triggered, self.onUndo)
|
qconnect(m.actionUndo.triggered, self.undo)
|
||||||
|
qconnect(m.actionRedo.triggered, self.redo)
|
||||||
if qtminor < 11:
|
if qtminor < 11:
|
||||||
m.actionUndo.setShortcut(QKeySequence("Ctrl+Alt+Z"))
|
m.actionUndo.setShortcut(QKeySequence("Ctrl+Alt+Z"))
|
||||||
qconnect(m.actionFullDatabaseCheck.triggered, self.onCheckDB)
|
qconnect(m.actionFullDatabaseCheck.triggered, self.onCheckDB)
|
||||||
|
|
|
||||||
|
|
@ -111,9 +111,8 @@ class CollectionOp(Generic[ResultWithChanges]):
|
||||||
if self._success:
|
if self._success:
|
||||||
self._success(result)
|
self._success(result)
|
||||||
finally:
|
finally:
|
||||||
# update undo status
|
mw.update_undo_actions()
|
||||||
status = mw.col.undo_status()
|
mw.autosave()
|
||||||
mw._update_undo_actions_for_status_and_save(status)
|
|
||||||
# fire change hooks
|
# fire change hooks
|
||||||
self._fire_change_hooks_after_op_performed(result, initiator)
|
self._fire_change_hooks_after_op_performed(result, initiator)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,15 @@ def undo(*, parent: QWidget) -> None:
|
||||||
).run_in_background()
|
).run_in_background()
|
||||||
|
|
||||||
|
|
||||||
|
def redo(*, parent: QWidget) -> None:
|
||||||
|
"Redo the last operation, and refresh the UI."
|
||||||
|
|
||||||
|
def on_success(out: OpChangesAfterUndo) -> None:
|
||||||
|
tooltip(tr.undo_action_redone(action=out.operation), parent=parent)
|
||||||
|
|
||||||
|
CollectionOp(parent, lambda col: col.redo()).success(on_success).run_in_background()
|
||||||
|
|
||||||
|
|
||||||
def _legacy_undo(*, parent: QWidget) -> None:
|
def _legacy_undo(*, parent: QWidget) -> None:
|
||||||
from aqt import mw
|
from aqt import mw
|
||||||
|
|
||||||
|
|
|
||||||
36
qt/aqt/undo.py
Normal file
36
qt/aqt/undo.py
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from anki.collection import UndoStatus
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class UndoActionsInfo:
|
||||||
|
can_undo: bool
|
||||||
|
can_redo: bool
|
||||||
|
|
||||||
|
undo_text: str
|
||||||
|
redo_text: str
|
||||||
|
|
||||||
|
# menu item is hidden when legacy undo is active, since it can't be undone
|
||||||
|
show_redo: bool
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_undo_status(status: UndoStatus) -> UndoActionsInfo:
|
||||||
|
from aqt import tr
|
||||||
|
|
||||||
|
return UndoActionsInfo(
|
||||||
|
can_undo=bool(status.undo),
|
||||||
|
can_redo=bool(status.redo),
|
||||||
|
undo_text=tr.undo_undo_action(val=status.undo)
|
||||||
|
if status.undo
|
||||||
|
else tr.undo_undo(),
|
||||||
|
redo_text=tr.undo_redo_action(action=status.undo)
|
||||||
|
if status.redo
|
||||||
|
else tr.undo_redo(),
|
||||||
|
show_redo=status.last_step > 0,
|
||||||
|
)
|
||||||
|
|
@ -30,6 +30,7 @@ from anki.models import NotetypeDict
|
||||||
from anki.collection import OpChangesAfterUndo
|
from anki.collection import OpChangesAfterUndo
|
||||||
from aqt.qt import QDialog, QEvent, QMenu, QWidget
|
from aqt.qt import QDialog, QEvent, QMenu, QWidget
|
||||||
from aqt.tagedit import TagEdit
|
from aqt.tagedit import TagEdit
|
||||||
|
from aqt.undo import UndoActionsInfo
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Hook list
|
# Hook list
|
||||||
|
|
@ -675,9 +676,7 @@ gui_hooks.webview_did_inject_style_into_page.append(mytest)
|
||||||
args=["col: anki.collection.Collection"],
|
args=["col: anki.collection.Collection"],
|
||||||
legacy_hook="colLoading",
|
legacy_hook="colLoading",
|
||||||
),
|
),
|
||||||
Hook(
|
Hook(name="undo_state_did_change", args=["info: UndoActionsInfo"]),
|
||||||
name="undo_state_did_change", args=["can_undo: bool"], legacy_hook="undoState"
|
|
||||||
),
|
|
||||||
Hook(name="review_did_undo", args=["card_id: int"], legacy_hook="revertedCard"),
|
Hook(name="review_did_undo", args=["card_id: int"], legacy_hook="revertedCard"),
|
||||||
Hook(
|
Hook(
|
||||||
name="style_did_init",
|
name="style_did_init",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue