2020-01-13 05:38:05 +01:00
|
|
|
# Copyright: Ankitects Pty Ltd and contributors
|
|
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
|
|
|
|
"""
|
|
|
|
See pylib/tools/genhooks.py for more info.
|
|
|
|
"""
|
|
|
|
|
2020-01-15 00:11:20 +01:00
|
|
|
import sys
|
2020-01-13 05:38:05 +01:00
|
|
|
|
Move away from Bazel (#2202)
(for upgrading users, please see the notes at the bottom)
Bazel brought a lot of nice things to the table, such as rebuilds based on
content changes instead of modification times, caching of build products,
detection of incorrect build rules via a sandbox, and so on. Rewriting the build
in Bazel was also an opportunity to improve on the Makefile-based build we had
prior, which was pretty poor: most dependencies were external or not pinned, and
the build graph was poorly defined and mostly serialized. It was not uncommon
for fresh checkouts to fail due to floating dependencies, or for things to break
when trying to switch to an older commit.
For day-to-day development, I think Bazel served us reasonably well - we could
generally switch between branches while being confident that builds would be
correct and reasonably fast, and not require full rebuilds (except on Windows,
where the lack of a sandbox and the TS rules would cause build breakages when TS
files were renamed/removed).
Bazel achieves that reliability by defining rules for each programming language
that define how source files should be turned into outputs. For the rules to
work with Bazel's sandboxing approach, they often have to reimplement or
partially bypass the standard tools that each programming language provides. The
Rust rules call Rust's compiler directly for example, instead of using Cargo,
and the Python rules extract each PyPi package into a separate folder that gets
added to sys.path.
These separate language rules allow proper declaration of inputs and outputs,
and offer some advantages such as caching of build products and fine-grained
dependency installation. But they also bring some downsides:
- The rules don't always support use-cases/platforms that the standard language
tools do, meaning they need to be patched to be used. I've had to contribute a
number of patches to the Rust, Python and JS rules to unblock various issues.
- The dependencies we use with each language sometimes make assumptions that do
not hold in Bazel, meaning they either need to be pinned or patched, or the
language rules need to be adjusted to accommodate them.
I was hopeful that after the initial setup work, things would be relatively
smooth-sailing. Unfortunately, that has not proved to be the case. Things
frequently broke when dependencies or the language rules were updated, and I
began to get frustrated at the amount of Anki development time I was instead
spending on build system upkeep. It's now about 2 years since switching to
Bazel, and I think it's time to cut losses, and switch to something else that's
a better fit.
The new build system is based on a small build tool called Ninja, and some
custom Rust code in build/. This means that to build Anki, Bazel is no longer
required, but Ninja and Rust need to be installed on your system. Python and
Node toolchains are automatically downloaded like in Bazel.
This new build system should result in faster builds in some cases:
- Because we're using cargo to build now, Rust builds are able to take advantage
of pipelining and incremental debug builds, which we didn't have with Bazel.
It's also easier to override the default linker on Linux/macOS, which can
further improve speeds.
- External Rust crates are now built with opt=1, which improves performance
of debug builds.
- Esbuild is now used to transpile TypeScript, instead of invoking the TypeScript
compiler. This results in faster builds, by deferring typechecking to test/check
time, and by allowing more work to happen in parallel.
As an example of the differences, when testing with the mold linker on Linux,
adding a new message to tags.proto (which triggers a recompile of the bulk of
the Rust and TypeScript code) results in a compile that goes from about 22s on
Bazel to about 7s in the new system. With the standard linker, it's about 9s.
Some other changes of note:
- Our Rust workspace now uses cargo-hakari to ensure all packages agree on
available features, preventing unnecessary rebuilds.
- pylib/anki is now a PEP420 implicit namespace, avoiding the need to merge
source files and generated files into a single folder for running. By telling
VSCode about the extra search path, code completion now works with generated
files without needing to symlink them into the source folder.
- qt/aqt can't use PEP420 as it's difficult to get rid of aqt/__init__.py.
Instead, the generated files are now placed in a separate _aqt package that's
added to the path.
- ts/lib is now exposed as @tslib, so the source code and generated code can be
provided under the same namespace without a merging step.
- MyPy and PyLint are now invoked once for the entire codebase.
- dprint will be used to format TypeScript/json files in the future instead of
the slower prettier (currently turned off to avoid causing conflicts). It can
automatically defer to prettier when formatting Svelte files.
- svelte-check is now used for typechecking our Svelte code, which revealed a
few typing issues that went undetected with the old system.
- The Jest unit tests now work on Windows as well.
If you're upgrading from Bazel, updated usage instructions are in docs/development.md and docs/build.md. A summary of the changes:
- please remove node_modules and .bazel
- install rustup (https://rustup.rs/)
- install rsync if not already installed (on windows, use pacman - see docs/windows.md)
- install Ninja (unzip from https://github.com/ninja-build/ninja/releases/tag/v1.11.1 and
place on your path, or from your distro/homebrew if it's 1.10+)
- update .vscode/settings.json from .vscode.dist
2022-11-27 06:24:20 +01:00
|
|
|
sys.path.append("pylib/tools")
|
|
|
|
|
2020-11-01 05:26:58 +01:00
|
|
|
from hookslib import Hook, write_file
|
2020-01-15 00:11:20 +01:00
|
|
|
|
2020-11-01 05:26:58 +01:00
|
|
|
prefix = """\
|
2021-04-13 10:45:05 +02:00
|
|
|
# Copyright: Ankitects Pty Ltd and contributors
|
|
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
|
2020-11-01 05:26:58 +01:00
|
|
|
# This file is automatically generated; edit tools/genhooks_gui.py instead.
|
|
|
|
# Please import from anki.hooks instead of this file.
|
2020-03-04 18:11:13 +01:00
|
|
|
|
2020-11-01 05:26:58 +01:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2022-07-22 11:33:07 +02:00
|
|
|
from typing import Any, Callable, Sequence, Literal, Type
|
2020-11-01 05:26:58 +01:00
|
|
|
|
|
|
|
import anki
|
|
|
|
import aqt
|
|
|
|
from anki.cards import Card
|
2021-03-24 07:28:38 +01:00
|
|
|
from anki.decks import DeckDict, DeckConfigDict
|
2020-11-01 05:26:58 +01:00
|
|
|
from anki.hooks import runFilter, runHook
|
2021-03-27 12:46:49 +01:00
|
|
|
from anki.models import NotetypeDict
|
2021-05-08 09:51:36 +02:00
|
|
|
from anki.collection import OpChangesAfterUndo
|
2021-09-05 13:54:04 +02:00
|
|
|
from aqt.qt import QDialog, QEvent, QMenu, QModelIndex, QWidget, QMimeData
|
2020-11-01 05:26:58 +01:00
|
|
|
from aqt.tagedit import TagEdit
|
2021-05-19 07:18:39 +02:00
|
|
|
from aqt.undo import UndoActionsInfo
|
2020-11-01 05:26:58 +01:00
|
|
|
"""
|
2020-01-13 05:38:05 +01:00
|
|
|
|
|
|
|
# Hook list
|
|
|
|
######################################################################
|
|
|
|
|
|
|
|
hooks = [
|
2020-01-15 04:03:11 +01:00
|
|
|
# Reviewing
|
|
|
|
###################
|
2020-02-08 06:31:41 +01:00
|
|
|
Hook(
|
|
|
|
name="overview_did_refresh",
|
|
|
|
args=["overview: aqt.overview.Overview"],
|
|
|
|
doc="""Allow to update the overview window. E.g. add the deck name in the
|
|
|
|
title.""",
|
|
|
|
),
|
2020-02-17 16:49:21 +01:00
|
|
|
Hook(
|
|
|
|
name="overview_will_render_content",
|
|
|
|
args=[
|
|
|
|
"overview: aqt.overview.Overview",
|
|
|
|
"content: aqt.overview.OverviewContent",
|
|
|
|
],
|
|
|
|
doc="""Used to modify HTML content sections in the overview body
|
|
|
|
|
|
|
|
'content' contains the sections of HTML content the overview body
|
|
|
|
will be updated with.
|
|
|
|
|
|
|
|
When modifying the content of a particular section, please make sure your
|
|
|
|
changes only perform the minimum required edits to make your add-on work.
|
|
|
|
You should avoid overwriting or interfering with existing data as much
|
|
|
|
as possible, instead opting to append your own changes, e.g.:
|
|
|
|
|
|
|
|
def on_overview_will_render_content(overview, content):
|
|
|
|
content.table += "\n<div>my html</div>"
|
|
|
|
""",
|
|
|
|
),
|
2022-07-05 00:28:47 +02:00
|
|
|
Hook(
|
|
|
|
name="overview_will_render_bottom",
|
|
|
|
args=[
|
|
|
|
"link_handler: Callable[[str], bool]",
|
|
|
|
"links: list[list[str]]",
|
|
|
|
],
|
|
|
|
return_type="Callable[[str], bool]",
|
|
|
|
doc="""Allows adding buttons to the Overview bottom bar.
|
|
|
|
|
|
|
|
Append a list of strings to 'links' argument to add new buttons.
|
|
|
|
- The first value is the shortcut to appear in the tooltip.
|
|
|
|
- The second value is the url to be triggered.
|
|
|
|
- The third value is the text of the new button.
|
|
|
|
|
|
|
|
Extend the callable 'link_handler' to handle new urls. This callable
|
|
|
|
accepts one argument: the triggered url.
|
|
|
|
Make a check of the triggered url, call any functions related to
|
|
|
|
that trigger, and return the new link_handler.
|
|
|
|
|
|
|
|
Example:
|
|
|
|
links.append(['H', 'hello', 'Click me!'])
|
|
|
|
def custom_link_handler(url):
|
|
|
|
if url == 'hello':
|
|
|
|
print('Hello World!')
|
|
|
|
return link_handler(url=url)
|
|
|
|
return custom_link_handler
|
|
|
|
""",
|
|
|
|
),
|
2020-01-13 09:37:08 +01:00
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="reviewer_did_show_question",
|
2020-01-13 09:37:08 +01:00
|
|
|
args=["card: Card"],
|
|
|
|
legacy_hook="showQuestion",
|
|
|
|
legacy_no_args=True,
|
|
|
|
),
|
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="reviewer_did_show_answer",
|
2020-01-13 09:37:08 +01:00
|
|
|
args=["card: Card"],
|
|
|
|
legacy_hook="showAnswer",
|
|
|
|
legacy_no_args=True,
|
|
|
|
),
|
2020-08-14 19:52:20 +02:00
|
|
|
Hook(
|
|
|
|
name="reviewer_will_init_answer_buttons",
|
2020-08-18 16:37:45 +02:00
|
|
|
args=[
|
2021-12-09 00:11:22 +01:00
|
|
|
"buttons_tuple: tuple[tuple[int, str], ...]",
|
2020-08-18 16:37:45 +02:00
|
|
|
"reviewer: aqt.reviewer.Reviewer",
|
|
|
|
"card: Card",
|
|
|
|
],
|
2021-12-09 00:11:22 +01:00
|
|
|
return_type="tuple[tuple[int, str], ...]",
|
2020-08-14 19:52:20 +02:00
|
|
|
doc="""Used to modify list of answer buttons
|
|
|
|
|
2020-08-20 16:33:46 +02:00
|
|
|
buttons_tuple is a tuple of buttons, with each button represented by a
|
|
|
|
tuple containing an int for the button's ease and a string for the
|
|
|
|
button's label.
|
2020-08-18 16:37:45 +02:00
|
|
|
|
2020-08-20 16:33:46 +02:00
|
|
|
Return a tuple of the form ((int, str), ...), e.g.:
|
|
|
|
((1, "Label1"), (2, "Label2"), ...)
|
2020-08-14 19:52:20 +02:00
|
|
|
|
2020-08-20 16:33:46 +02:00
|
|
|
Note: import _ from anki.lang to support translation, using, e.g.,
|
2020-08-18 16:37:45 +02:00
|
|
|
((1, _("Label1")), ...)
|
2020-08-14 19:52:20 +02:00
|
|
|
""",
|
|
|
|
),
|
2020-01-24 15:36:05 +01:00
|
|
|
Hook(
|
|
|
|
name="reviewer_will_answer_card",
|
|
|
|
args=[
|
2021-12-09 00:11:22 +01:00
|
|
|
"ease_tuple: tuple[bool, Literal[1, 2, 3, 4]]",
|
2020-01-24 15:48:05 +01:00
|
|
|
"reviewer: aqt.reviewer.Reviewer",
|
|
|
|
"card: Card",
|
2020-01-24 15:36:05 +01:00
|
|
|
],
|
2021-12-09 00:11:22 +01:00
|
|
|
return_type="tuple[bool, Literal[1, 2, 3, 4]]",
|
2020-01-24 15:36:05 +01:00
|
|
|
doc="""Used to modify the ease at which a card is rated or to bypass
|
|
|
|
rating the card completely.
|
|
|
|
|
|
|
|
ease_tuple is a tuple consisting of a boolean expressing whether the reviewer
|
|
|
|
should continue with rating the card, and an integer expressing the ease at
|
|
|
|
which the card should be rated.
|
|
|
|
|
|
|
|
If your code just needs to be notified of the card rating event, you should use
|
|
|
|
the reviewer_did_answer_card hook instead.""",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="reviewer_did_answer_card",
|
2021-05-19 08:05:12 +02:00
|
|
|
args=[
|
|
|
|
"reviewer: aqt.reviewer.Reviewer",
|
|
|
|
"card: Card",
|
|
|
|
"ease: Literal[1, 2, 3, 4]",
|
|
|
|
],
|
2020-01-24 15:36:05 +01:00
|
|
|
),
|
2020-01-14 23:53:57 +01:00
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="reviewer_will_show_context_menu",
|
2020-01-15 04:03:11 +01:00
|
|
|
args=["reviewer: aqt.reviewer.Reviewer", "menu: QMenu"],
|
|
|
|
legacy_hook="Reviewer.contextMenuEvent",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="reviewer_will_end",
|
|
|
|
legacy_hook="reviewCleanup",
|
|
|
|
doc="Called before Anki transitions from the review screen to another screen.",
|
|
|
|
),
|
2020-07-30 20:06:16 +02:00
|
|
|
Hook(
|
|
|
|
name="reviewer_will_play_question_sounds",
|
2021-12-09 00:11:22 +01:00
|
|
|
args=["card: Card", "tags: list[anki.sound.AVTag]"],
|
2020-07-31 02:06:13 +02:00
|
|
|
doc="""Called before showing the question/front side.
|
|
|
|
|
|
|
|
`tags` can be used to inspect and manipulate the sounds
|
|
|
|
that will be played (if any).
|
|
|
|
|
|
|
|
This won't be called when the user manually plays sounds
|
|
|
|
using `Replay Audio`.
|
|
|
|
|
2020-07-31 03:41:49 +02:00
|
|
|
Note that this hook is called even when the `Automatically play audio`
|
2020-07-31 02:06:13 +02:00
|
|
|
option is unchecked; This is so as to allow playing custom
|
|
|
|
sounds regardless of that option.""",
|
2020-07-30 20:06:16 +02:00
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="reviewer_will_play_answer_sounds",
|
2021-12-09 00:11:22 +01:00
|
|
|
args=["card: Card", "tags: list[anki.sound.AVTag]"],
|
2020-07-31 02:06:13 +02:00
|
|
|
doc="""Called before showing the answer/back side.
|
|
|
|
|
|
|
|
`tags` can be used to inspect and manipulate the sounds
|
|
|
|
that will be played (if any).
|
|
|
|
|
|
|
|
This won't be called when the user manually plays sounds
|
|
|
|
using `Replay Audio`.
|
|
|
|
|
2020-07-31 03:41:49 +02:00
|
|
|
Note that this hook is called even when the `Automatically play audio`
|
2020-07-31 02:06:13 +02:00
|
|
|
option is unchecked; This is so as to allow playing custom
|
|
|
|
sounds regardless of that option.""",
|
2020-07-30 20:06:16 +02:00
|
|
|
),
|
2022-05-18 05:44:56 +02:00
|
|
|
Hook(
|
|
|
|
name="reviewer_will_replay_recording",
|
|
|
|
args=["path: str"],
|
|
|
|
return_type="str",
|
|
|
|
doc="""Used to inspect and modify a recording recorded by "Record Own Voice" before replaying.""",
|
|
|
|
),
|
2022-07-18 04:59:56 +02:00
|
|
|
Hook(
|
|
|
|
name="reviewer_will_suspend_note",
|
|
|
|
args=["nid: int"],
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="reviewer_will_suspend_card",
|
|
|
|
args=["id: int"],
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="reviewer_will_bury_note",
|
|
|
|
args=["nid: int"],
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="reviewer_will_bury_card",
|
|
|
|
args=["id: int"],
|
|
|
|
),
|
2020-03-04 18:11:13 +01:00
|
|
|
# Debug
|
|
|
|
###################
|
|
|
|
Hook(
|
|
|
|
name="debug_console_will_show",
|
|
|
|
args=["debug_window: QDialog"],
|
|
|
|
doc="""Allows editing the debug window. E.g. setting a default code, or
|
|
|
|
previous code.""",
|
|
|
|
),
|
2020-03-04 18:20:02 +01:00
|
|
|
Hook(
|
|
|
|
name="debug_console_did_evaluate_python",
|
2021-02-02 14:30:53 +01:00
|
|
|
args=["output: str", "query: str", "debug_window: aqt.forms.debug.Ui_Dialog"],
|
2020-03-04 18:20:02 +01:00
|
|
|
return_type="str",
|
|
|
|
doc="""Allows processing the debug result. E.g. logging queries and
|
|
|
|
result, saving last query to display it later...""",
|
|
|
|
),
|
2020-02-28 13:34:54 +01:00
|
|
|
# Card layout
|
|
|
|
###################
|
|
|
|
Hook(
|
|
|
|
name="card_layout_will_show",
|
|
|
|
args=["clayout: aqt.clayout.CardLayout"],
|
|
|
|
doc="""Allow to change the display of the card layout. After most values are
|
|
|
|
set and before the window is actually shown.""",
|
|
|
|
),
|
2020-02-28 13:22:25 +01:00
|
|
|
# Multiple windows
|
|
|
|
###################
|
|
|
|
# reviewer, clayout and browser
|
2020-01-15 04:03:11 +01:00
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="card_will_show",
|
2020-01-15 04:03:11 +01:00
|
|
|
args=["text: str", "card: Card", "kind: str"],
|
|
|
|
return_type="str",
|
|
|
|
legacy_hook="prepareQA",
|
|
|
|
doc="Can modify card text before review/preview.",
|
2020-01-14 23:53:57 +01:00
|
|
|
),
|
2020-02-29 17:01:38 +01:00
|
|
|
# Deck browser
|
|
|
|
###################
|
|
|
|
Hook(
|
|
|
|
name="deck_browser_did_render",
|
|
|
|
args=["deck_browser: aqt.deckbrowser.DeckBrowser"],
|
|
|
|
doc="""Allow to update the deck browser window. E.g. change its title.""",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="deck_browser_will_render_content",
|
|
|
|
args=[
|
|
|
|
"deck_browser: aqt.deckbrowser.DeckBrowser",
|
|
|
|
"content: aqt.deckbrowser.DeckBrowserContent",
|
|
|
|
],
|
|
|
|
doc="""Used to modify HTML content sections in the deck browser body
|
|
|
|
|
|
|
|
'content' contains the sections of HTML content the deck browser body
|
|
|
|
will be updated with.
|
|
|
|
|
|
|
|
When modifying the content of a particular section, please make sure your
|
|
|
|
changes only perform the minimum required edits to make your add-on work.
|
|
|
|
You should avoid overwriting or interfering with existing data as much
|
|
|
|
as possible, instead opting to append your own changes, e.g.:
|
|
|
|
|
|
|
|
def on_deck_browser_will_render_content(deck_browser, content):
|
2022-11-23 09:00:28 +01:00
|
|
|
content.stats += "\\n<div>my html</div>"
|
2020-02-29 17:01:38 +01:00
|
|
|
""",
|
|
|
|
),
|
2021-04-24 02:14:54 +02:00
|
|
|
# Deck options (legacy screen)
|
|
|
|
###############################
|
2020-02-24 15:29:23 +01:00
|
|
|
Hook(
|
|
|
|
name="deck_conf_did_setup_ui_form",
|
|
|
|
args=["deck_conf: aqt.deckconf.DeckConf"],
|
|
|
|
doc="Allows modifying or adding widgets in the deck options UI form",
|
|
|
|
),
|
2020-02-24 13:42:30 +01:00
|
|
|
Hook(
|
|
|
|
name="deck_conf_will_show",
|
|
|
|
args=["deck_conf: aqt.deckconf.DeckConf"],
|
|
|
|
doc="Allows modifying the deck options dialog before it is shown",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="deck_conf_did_load_config",
|
2021-03-24 07:28:38 +01:00
|
|
|
args=[
|
|
|
|
"deck_conf: aqt.deckconf.DeckConf",
|
|
|
|
"deck: DeckDict",
|
|
|
|
"config: DeckConfigDict",
|
|
|
|
],
|
2020-02-24 13:42:30 +01:00
|
|
|
doc="Called once widget state has been set from deck config",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="deck_conf_will_save_config",
|
2021-03-24 07:28:38 +01:00
|
|
|
args=[
|
|
|
|
"deck_conf: aqt.deckconf.DeckConf",
|
|
|
|
"deck: DeckDict",
|
|
|
|
"config: DeckConfigDict",
|
|
|
|
],
|
2020-02-24 13:42:30 +01:00
|
|
|
doc="Called before widget state is saved to config",
|
|
|
|
),
|
2020-05-22 21:24:05 +02:00
|
|
|
Hook(
|
|
|
|
name="deck_conf_did_add_config",
|
2020-05-23 11:14:52 +02:00
|
|
|
args=[
|
|
|
|
"deck_conf: aqt.deckconf.DeckConf",
|
2021-03-24 07:28:38 +01:00
|
|
|
"deck: DeckDict",
|
|
|
|
"config: DeckConfigDict",
|
2020-05-23 11:14:52 +02:00
|
|
|
"new_name: str",
|
|
|
|
"new_conf_id: int",
|
|
|
|
],
|
|
|
|
doc="""Allows modification of a newly created config group
|
|
|
|
|
|
|
|
This hook is called after the config group was created, but
|
|
|
|
before initializing the widget state.
|
|
|
|
|
|
|
|
`deck_conf` will point to the old config group, `new_conf_id` will
|
|
|
|
point to the newly created config group.
|
|
|
|
|
|
|
|
Config groups are created as clones of the current one.
|
|
|
|
""",
|
2020-05-22 21:24:05 +02:00
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="deck_conf_will_remove_config",
|
2021-03-24 07:28:38 +01:00
|
|
|
args=[
|
|
|
|
"deck_conf: aqt.deckconf.DeckConf",
|
|
|
|
"deck: DeckDict",
|
|
|
|
"config: DeckConfigDict",
|
|
|
|
],
|
2020-05-22 21:24:05 +02:00
|
|
|
doc="Called before current config group is removed",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="deck_conf_will_rename_config",
|
2020-05-23 11:14:52 +02:00
|
|
|
args=[
|
|
|
|
"deck_conf: aqt.deckconf.DeckConf",
|
2021-03-24 07:28:38 +01:00
|
|
|
"deck: DeckDict",
|
|
|
|
"config: DeckConfigDict",
|
2020-05-23 11:14:52 +02:00
|
|
|
"new_name: str",
|
|
|
|
],
|
2020-05-22 21:24:05 +02:00
|
|
|
doc="Called before config group is renamed",
|
|
|
|
),
|
2021-04-24 02:14:54 +02:00
|
|
|
# Deck options (new screen)
|
|
|
|
############################
|
|
|
|
Hook(
|
|
|
|
name="deck_options_did_load",
|
|
|
|
args=[
|
|
|
|
"deck_options: aqt.deckoptions.DeckOptionsDialog",
|
|
|
|
],
|
2021-04-25 11:24:19 +02:00
|
|
|
doc="""Can be used to inject extra options into the config screen.
|
|
|
|
|
|
|
|
See the example add-ons at:
|
|
|
|
https://github.com/ankitects/anki-addons/tree/main/demos/deckoptions_svelte
|
|
|
|
https://github.com/ankitects/anki-addons/tree/main/demos/deckoptions_raw_html
|
|
|
|
""",
|
2021-04-24 02:14:54 +02:00
|
|
|
),
|
2021-03-25 02:28:01 +01:00
|
|
|
# Filtered deck options
|
|
|
|
###################
|
|
|
|
Hook(
|
|
|
|
name="filtered_deck_dialog_did_load_deck",
|
|
|
|
args=[
|
|
|
|
"filtered_deck_dialog: aqt.filtered_deck.FilteredDeckConfigDialog",
|
|
|
|
"filtered_deck: anki.scheduler.FilteredDeckForUpdate",
|
|
|
|
],
|
|
|
|
doc="Allows updating widget state once the filtered deck config is loaded",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="filtered_deck_dialog_will_add_or_update_deck",
|
|
|
|
args=[
|
|
|
|
"filtered_deck_dialog: aqt.filtered_deck.FilteredDeckConfigDialog",
|
|
|
|
"filtered_deck: anki.scheduler.FilteredDeckForUpdate",
|
|
|
|
],
|
|
|
|
doc="Allows modifying the filtered deck config object before it is written",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="filtered_deck_dialog_did_add_or_update_deck",
|
|
|
|
args=[
|
|
|
|
"filtered_deck_dialog: aqt.filtered_deck.FilteredDeckConfigDialog",
|
|
|
|
"filtered_deck: anki.scheduler.FilteredDeckForUpdate",
|
2021-03-25 02:33:15 +01:00
|
|
|
"deck_id: int",
|
2021-03-25 02:28:01 +01:00
|
|
|
],
|
|
|
|
doc="Allows performing changes after a filtered deck has been added or updated",
|
|
|
|
),
|
2020-01-15 04:03:11 +01:00
|
|
|
# Browser
|
|
|
|
###################
|
2021-01-02 19:38:57 +01:00
|
|
|
Hook(
|
|
|
|
name="default_search",
|
|
|
|
args=["current_search: str", "c: Card"],
|
|
|
|
return_type="str",
|
|
|
|
doc="Change the default search when the card browser is opened with card `c`.",
|
|
|
|
),
|
2020-02-29 17:02:51 +01:00
|
|
|
Hook(name="browser_will_show", args=["browser: aqt.browser.Browser"]),
|
2020-01-14 23:53:57 +01:00
|
|
|
Hook(
|
2020-01-15 08:18:11 +01:00
|
|
|
name="browser_menus_did_init",
|
2020-01-14 23:53:57 +01:00
|
|
|
args=["browser: aqt.browser.Browser"],
|
|
|
|
legacy_hook="browser.setupMenus",
|
|
|
|
),
|
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="browser_will_show_context_menu",
|
2020-01-14 23:53:57 +01:00
|
|
|
args=["browser: aqt.browser.Browser", "menu: QMenu"],
|
|
|
|
legacy_hook="browser.onContextMenu",
|
|
|
|
),
|
2021-06-25 16:24:24 +02:00
|
|
|
Hook(
|
|
|
|
name="browser_sidebar_will_show_context_menu",
|
|
|
|
args=[
|
|
|
|
"sidebar: aqt.browser.SidebarTreeView",
|
|
|
|
"menu: QMenu",
|
|
|
|
"item: aqt.browser.SidebarItem",
|
|
|
|
"index: QModelIndex",
|
|
|
|
],
|
|
|
|
),
|
2020-03-27 23:06:22 +01:00
|
|
|
Hook(
|
|
|
|
name="browser_header_will_show_context_menu",
|
|
|
|
args=["browser: aqt.browser.Browser", "menu: QMenu"],
|
|
|
|
),
|
2020-01-14 23:53:57 +01:00
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="browser_did_change_row",
|
2020-01-14 23:53:57 +01:00
|
|
|
args=["browser: aqt.browser.Browser"],
|
|
|
|
legacy_hook="browser.rowChanged",
|
|
|
|
),
|
2020-02-15 21:03:15 +01:00
|
|
|
Hook(
|
|
|
|
name="browser_will_build_tree",
|
|
|
|
args=[
|
|
|
|
"handled: bool",
|
2021-04-13 11:05:49 +02:00
|
|
|
"tree: aqt.browser.SidebarItem",
|
|
|
|
"stage: aqt.browser.SidebarStage",
|
2020-02-15 21:03:15 +01:00
|
|
|
"browser: aqt.browser.Browser",
|
|
|
|
],
|
|
|
|
return_type="bool",
|
|
|
|
doc="""Used to add or replace items in the browser sidebar tree
|
|
|
|
|
|
|
|
'tree' is the root SidebarItem that all other items are added to.
|
|
|
|
|
|
|
|
'stage' is an enum describing the different construction stages of
|
|
|
|
the sidebar tree at which you can interject your changes.
|
|
|
|
The different values can be inspected by looking at
|
2021-04-13 11:05:49 +02:00
|
|
|
aqt.browser.SidebarStage.
|
2020-02-15 21:03:15 +01:00
|
|
|
|
|
|
|
If you want Anki to proceed with the construction of the tree stage
|
|
|
|
in question after your have performed your changes or additions,
|
|
|
|
return the 'handled' boolean unchanged.
|
|
|
|
|
|
|
|
On the other hand, if you want to prevent Anki from adding its own
|
|
|
|
items at a particular construction stage (e.g. in case your add-on
|
|
|
|
implements its own version of that particular stage), return 'True'.
|
|
|
|
|
|
|
|
If you return 'True' at SidebarStage.ROOT, the sidebar will not be
|
|
|
|
populated by any of the other construction stages. For any other stage
|
|
|
|
the tree construction will just continue as usual.
|
|
|
|
|
|
|
|
For example, if your code wishes to replace the tag tree, you could do:
|
|
|
|
|
|
|
|
def on_browser_will_build_tree(handled, root, stage, browser):
|
|
|
|
if stage != SidebarStage.TAGS:
|
|
|
|
# not at tag tree building stage, pass on
|
|
|
|
return handled
|
|
|
|
|
|
|
|
# your tag tree construction code
|
|
|
|
# root.addChild(...)
|
|
|
|
|
|
|
|
# your code handled tag tree construction, no need for Anki
|
|
|
|
# or other add-ons to build the tag tree
|
|
|
|
return True
|
|
|
|
""",
|
|
|
|
),
|
2020-03-21 07:38:46 +01:00
|
|
|
Hook(
|
|
|
|
name="browser_will_search",
|
2021-04-12 09:50:44 +02:00
|
|
|
args=["context: aqt.browser.SearchContext"],
|
2020-03-21 07:38:46 +01:00
|
|
|
doc="""Allows you to modify the search text, or perform your own search.
|
|
|
|
|
|
|
|
You can modify context.search to change the text that is sent to the
|
|
|
|
searching backend.
|
|
|
|
|
2022-07-01 02:12:27 +02:00
|
|
|
If you set context.ids to a list of ids, the regular search will
|
2020-03-21 07:38:46 +01:00
|
|
|
not be performed, and the provided ids will be used instead.
|
|
|
|
|
2022-07-01 02:12:27 +02:00
|
|
|
Your add-on should check if context.ids is not None, and return
|
2020-03-21 07:38:46 +01:00
|
|
|
without making changes if it has been set.
|
2022-07-01 02:12:27 +02:00
|
|
|
|
|
|
|
In versions of Anki lower than 2.1.45 the field to check is
|
|
|
|
context.card_ids rather than context.ids
|
2020-03-21 07:38:46 +01:00
|
|
|
""",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="browser_did_search",
|
2021-04-12 09:50:44 +02:00
|
|
|
args=["context: aqt.browser.SearchContext"],
|
2020-03-21 07:38:46 +01:00
|
|
|
doc="""Allows you to modify the list of returned card ids from a search.""",
|
|
|
|
),
|
2021-03-23 10:13:52 +01:00
|
|
|
Hook(
|
|
|
|
name="browser_did_fetch_row",
|
2021-03-29 12:16:50 +02:00
|
|
|
args=[
|
2021-04-12 09:50:44 +02:00
|
|
|
"card_or_note_id: aqt.browser.ItemId",
|
2021-03-29 12:16:50 +02:00
|
|
|
"is_note: bool",
|
2021-04-12 09:50:44 +02:00
|
|
|
"row: aqt.browser.CellRow",
|
2021-03-29 12:16:50 +02:00
|
|
|
"columns: Sequence[str]",
|
|
|
|
],
|
2021-03-23 10:13:52 +01:00
|
|
|
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
|
|
|
|
backend did not recognize will be returned as an empty string, and can be
|
|
|
|
replaced with custom content.
|
|
|
|
|
|
|
|
Columns is a list of string values identifying what each column in the row
|
|
|
|
represents.
|
|
|
|
""",
|
|
|
|
),
|
2021-04-11 10:27:43 +02:00
|
|
|
Hook(
|
|
|
|
name="browser_did_fetch_columns",
|
2021-12-09 00:11:22 +01:00
|
|
|
args=["columns: dict[str, aqt.browser.Column]"],
|
2021-04-11 10:27:43 +02:00
|
|
|
doc="""Allows you to add custom columns to the browser.
|
|
|
|
|
2022-11-24 11:18:57 +01:00
|
|
|
columns is a dictionary of data objects. You can add an entry with a custom
|
2021-04-11 10:27:43 +02:00
|
|
|
column to describe how it should be displayed in the browser or modify
|
|
|
|
existing entries.
|
|
|
|
|
|
|
|
Every column in the dictionary will be toggleable by the user.
|
|
|
|
""",
|
|
|
|
),
|
2022-05-19 03:10:12 +02:00
|
|
|
# Previewer
|
|
|
|
###################
|
|
|
|
Hook(
|
|
|
|
name="previewer_did_init",
|
|
|
|
args=["previewer: aqt.browser.previewer.Previewer"],
|
|
|
|
doc="""Called after the previewer is initialized.""",
|
|
|
|
),
|
2021-03-13 14:59:32 +01:00
|
|
|
# Main window states
|
2020-01-15 04:03:11 +01:00
|
|
|
###################
|
2021-03-13 14:59:32 +01:00
|
|
|
# these refer to things like deckbrowser, overview and reviewer state,
|
2020-01-15 03:16:54 +01:00
|
|
|
Hook(
|
|
|
|
name="state_will_change",
|
2022-09-12 11:31:22 +02:00
|
|
|
args=[
|
|
|
|
"new_state: aqt.main.MainWindowState",
|
|
|
|
"old_state: aqt.main.MainWindowState",
|
|
|
|
],
|
2020-01-15 03:16:54 +01:00
|
|
|
legacy_hook="beforeStateChange",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="state_did_change",
|
2022-09-12 11:31:22 +02:00
|
|
|
args=[
|
|
|
|
"new_state: aqt.main.MainWindowState",
|
|
|
|
"old_state: aqt.main.MainWindowState",
|
|
|
|
],
|
2020-01-15 03:16:54 +01:00
|
|
|
legacy_hook="afterStateChange",
|
|
|
|
),
|
|
|
|
# different sig to original
|
|
|
|
Hook(
|
|
|
|
name="state_shortcuts_will_change",
|
2022-09-12 11:31:22 +02:00
|
|
|
args=[
|
|
|
|
"state: aqt.main.MainWindowState",
|
|
|
|
"shortcuts: list[tuple[str, Callable]]",
|
|
|
|
],
|
2020-01-15 03:16:54 +01:00
|
|
|
),
|
2021-03-13 14:59:32 +01:00
|
|
|
# UI state/refreshing
|
|
|
|
###################
|
2020-01-15 03:46:53 +01:00
|
|
|
Hook(
|
|
|
|
name="state_did_revert",
|
|
|
|
args=["action: str"],
|
|
|
|
legacy_hook="revertedState",
|
2021-05-08 09:51:36 +02:00
|
|
|
doc="Legacy hook, called after undoing.",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="state_did_undo",
|
|
|
|
args=["changes: OpChangesAfterUndo"],
|
|
|
|
doc="Called after backend undoes a change.",
|
2020-01-15 03:46:53 +01:00
|
|
|
),
|
2020-01-15 03:16:54 +01:00
|
|
|
Hook(
|
|
|
|
name="state_did_reset",
|
|
|
|
legacy_hook="reset",
|
2021-04-06 09:07:38 +02:00
|
|
|
doc="""Legacy 'reset' hook. Called by mw.reset() and CollectionOp() to redraw the UI.
|
more reset refactoring
'card modified' covers the common case where we need to rebuild the
study queue, but is also set when changing the card flags. We want to
avoid a queue rebuild in that case, as it causes UI flicker, and may
result in a different card being shown. Note marking doesn't trigger
a queue build, but still causes flicker, and may return the user back
to the front side when they were looking at the answer.
I still think entity-based change tracking is the simplest in the
common case, but to solve the above, I've introduced an enum describing
the last operation that was taken. This currently is not trying to list
out all possible operations, and just describes the ones we want to
special-case.
Other changes:
- Fire the old 'state_did_reset' hook after an operation is performed,
so legacy code can refresh itself after an operation is performed.
- Fire the new `operation_did_execute` hook when mw.reset() is called,
so that as the UI is updated to the use the new hook, it will still
be able to refresh after legacy code calls mw.reset()
- Update the deck browser, overview and review screens to listen to
the new hook, instead of relying on the main window to call moveToState()
- Add a 'set flag' backend action, so we can distinguish it from a
normal card update.
- Drop the separate added/modified entries in the change list in
favour of a single entry per entity.
- Add typing to mw.state
- Tweak perform_op()
- Convert a few more actions to use perform_op()
2021-03-14 10:54:15 +01:00
|
|
|
|
|
|
|
New code should use `operation_did_execute` instead.
|
|
|
|
""",
|
2020-01-15 03:16:54 +01:00
|
|
|
),
|
2021-03-13 14:59:32 +01:00
|
|
|
Hook(
|
|
|
|
name="operation_did_execute",
|
2021-12-09 00:11:22 +01:00
|
|
|
args=["changes: anki.collection.OpChanges", "handler: object | None"],
|
2021-03-13 14:59:32 +01:00
|
|
|
doc="""Called after an operation completes.
|
more reset refactoring
'card modified' covers the common case where we need to rebuild the
study queue, but is also set when changing the card flags. We want to
avoid a queue rebuild in that case, as it causes UI flicker, and may
result in a different card being shown. Note marking doesn't trigger
a queue build, but still causes flicker, and may return the user back
to the front side when they were looking at the answer.
I still think entity-based change tracking is the simplest in the
common case, but to solve the above, I've introduced an enum describing
the last operation that was taken. This currently is not trying to list
out all possible operations, and just describes the ones we want to
special-case.
Other changes:
- Fire the old 'state_did_reset' hook after an operation is performed,
so legacy code can refresh itself after an operation is performed.
- Fire the new `operation_did_execute` hook when mw.reset() is called,
so that as the UI is updated to the use the new hook, it will still
be able to refresh after legacy code calls mw.reset()
- Update the deck browser, overview and review screens to listen to
the new hook, instead of relying on the main window to call moveToState()
- Add a 'set flag' backend action, so we can distinguish it from a
normal card update.
- Drop the separate added/modified entries in the change list in
favour of a single entry per entity.
- Add typing to mw.state
- Tweak perform_op()
- Convert a few more actions to use perform_op()
2021-03-14 10:54:15 +01:00
|
|
|
Changes can be inspected to determine whether the UI needs updating.
|
|
|
|
|
2021-03-18 01:54:02 +01:00
|
|
|
This will also be called when the legacy mw.reset() is used.
|
more reset refactoring
'card modified' covers the common case where we need to rebuild the
study queue, but is also set when changing the card flags. We want to
avoid a queue rebuild in that case, as it causes UI flicker, and may
result in a different card being shown. Note marking doesn't trigger
a queue build, but still causes flicker, and may return the user back
to the front side when they were looking at the answer.
I still think entity-based change tracking is the simplest in the
common case, but to solve the above, I've introduced an enum describing
the last operation that was taken. This currently is not trying to list
out all possible operations, and just describes the ones we want to
special-case.
Other changes:
- Fire the old 'state_did_reset' hook after an operation is performed,
so legacy code can refresh itself after an operation is performed.
- Fire the new `operation_did_execute` hook when mw.reset() is called,
so that as the UI is updated to the use the new hook, it will still
be able to refresh after legacy code calls mw.reset()
- Update the deck browser, overview and review screens to listen to
the new hook, instead of relying on the main window to call moveToState()
- Add a 'set flag' backend action, so we can distinguish it from a
normal card update.
- Drop the separate added/modified entries in the change list in
favour of a single entry per entity.
- Add typing to mw.state
- Tweak perform_op()
- Convert a few more actions to use perform_op()
2021-03-14 10:54:15 +01:00
|
|
|
""",
|
2021-03-13 14:59:32 +01:00
|
|
|
),
|
2021-03-14 13:08:37 +01:00
|
|
|
Hook(
|
|
|
|
name="focus_did_change",
|
|
|
|
args=[
|
2021-12-09 00:11:22 +01:00
|
|
|
"new: QWidget | None",
|
|
|
|
"old: QWidget | None",
|
2021-03-14 13:08:37 +01:00
|
|
|
],
|
|
|
|
doc="""Called each time the focus changes. Can be used to defer updates from
|
|
|
|
`operation_did_execute` until a window is brought to the front.""",
|
|
|
|
),
|
2021-03-18 01:54:02 +01:00
|
|
|
Hook(
|
|
|
|
name="backend_will_block",
|
2021-04-06 09:07:38 +02:00
|
|
|
doc="""Called before one or more DB tasks are run in the background.
|
2021-03-18 01:54:02 +01:00
|
|
|
|
|
|
|
Subscribers can use this to set a flag to avoid DB queries until the operation
|
|
|
|
completes, as doing so will freeze the UI until the long-running operation
|
|
|
|
completes.
|
|
|
|
""",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="backend_did_block",
|
2021-04-06 09:07:38 +02:00
|
|
|
doc="""Called after one or more DB tasks finish running in the background.
|
2021-03-18 01:54:02 +01:00
|
|
|
Called regardless of the success of individual operations, and only called when
|
|
|
|
there are no outstanding ops.
|
|
|
|
""",
|
|
|
|
),
|
2021-11-24 22:17:41 +01:00
|
|
|
Hook(
|
|
|
|
name="theme_did_change",
|
|
|
|
doc="Called after night mode is toggled.",
|
|
|
|
),
|
2020-01-22 01:46:35 +01:00
|
|
|
# Webview
|
|
|
|
###################
|
|
|
|
Hook(
|
|
|
|
name="webview_did_receive_js_message",
|
2021-12-09 00:11:22 +01:00
|
|
|
args=["handled: tuple[bool, Any]", "message: str", "context: Any"],
|
|
|
|
return_type="tuple[bool, Any]",
|
2020-01-22 01:46:35 +01:00
|
|
|
doc="""Used to handle pycmd() messages sent from Javascript.
|
|
|
|
|
2020-02-08 23:59:29 +01:00
|
|
|
Message is the string passed to pycmd().
|
|
|
|
|
|
|
|
For messages you don't want to handle, return 'handled' unchanged.
|
2020-01-22 01:46:35 +01:00
|
|
|
|
|
|
|
If you handle a message and don't want it passed to the original
|
|
|
|
bridge command handler, return (True, None).
|
2020-02-08 23:59:29 +01:00
|
|
|
|
2020-01-22 01:46:35 +01:00
|
|
|
If you want to pass a value to pycmd's result callback, you can
|
2020-02-08 23:59:29 +01:00
|
|
|
return it with (True, some_value).
|
|
|
|
|
|
|
|
Context is the instance that was passed to set_bridge_command().
|
|
|
|
It can be inspected to check which screen this hook is firing
|
|
|
|
in, and to get a reference to the screen. For example, if your
|
|
|
|
code wishes to function only in the review screen, you could do:
|
|
|
|
|
|
|
|
if not isinstance(context, aqt.reviewer.Reviewer):
|
|
|
|
# not reviewer, pass on message
|
|
|
|
return handled
|
|
|
|
|
|
|
|
if message == "my-mark-action":
|
|
|
|
# our message, call onMark() on the reviewer instance
|
|
|
|
context.onMark()
|
|
|
|
# and don't pass message to other handlers
|
|
|
|
return (True, None)
|
|
|
|
else:
|
|
|
|
# some other command, pass it on
|
|
|
|
return handled
|
|
|
|
""",
|
2020-01-22 01:46:35 +01:00
|
|
|
),
|
2020-02-12 22:00:13 +01:00
|
|
|
Hook(
|
|
|
|
name="webview_will_set_content",
|
2020-08-31 05:29:28 +02:00
|
|
|
args=[
|
|
|
|
"web_content: aqt.webview.WebContent",
|
2021-12-09 00:11:22 +01:00
|
|
|
"context: object | None",
|
2020-08-31 05:29:28 +02:00
|
|
|
],
|
2020-02-12 22:00:13 +01:00
|
|
|
doc="""Used to modify web content before it is rendered.
|
|
|
|
|
|
|
|
Web_content contains the HTML, JS, and CSS the web view will be
|
|
|
|
populated with.
|
|
|
|
|
|
|
|
Context is the instance that was passed to stdHtml().
|
|
|
|
It can be inspected to check which screen this hook is firing
|
|
|
|
in, and to get a reference to the screen. For example, if your
|
|
|
|
code wishes to function only in the review screen, you could do:
|
|
|
|
|
2020-02-15 15:03:58 +01:00
|
|
|
def on_webview_will_set_content(web_content: WebContent, context):
|
|
|
|
|
|
|
|
if not isinstance(context, aqt.reviewer.Reviewer):
|
|
|
|
# not reviewer, do not modify content
|
|
|
|
return
|
|
|
|
|
|
|
|
# reviewer, perform changes to content
|
|
|
|
|
|
|
|
context: aqt.reviewer.Reviewer
|
|
|
|
|
|
|
|
addon_package = mw.addonManager.addonFromModule(__name__)
|
|
|
|
|
|
|
|
web_content.css.append(
|
|
|
|
f"/_addons/{addon_package}/web/my-addon.css")
|
|
|
|
web_content.js.append(
|
|
|
|
f"/_addons/{addon_package}/web/my-addon.js")
|
2020-02-12 22:00:13 +01:00
|
|
|
|
2020-02-15 15:03:58 +01:00
|
|
|
web_content.head += "<script>console.log('my-addon')</script>"
|
|
|
|
web_content.body += "<div id='my-addon'></div>"
|
2020-02-12 22:00:13 +01:00
|
|
|
""",
|
|
|
|
),
|
2020-01-22 01:46:35 +01:00
|
|
|
Hook(
|
|
|
|
name="webview_will_show_context_menu",
|
|
|
|
args=["webview: aqt.webview.AnkiWebView", "menu: QMenu"],
|
|
|
|
legacy_hook="AnkiWebView.contextMenuEvent",
|
|
|
|
),
|
2020-08-28 09:10:35 +02:00
|
|
|
Hook(
|
|
|
|
name="webview_did_inject_style_into_page",
|
2020-08-29 14:00:28 +02:00
|
|
|
args=["webview: aqt.webview.AnkiWebView"],
|
2020-08-28 09:10:35 +02:00
|
|
|
doc='''Called after standard styling is injected into an external
|
|
|
|
html file, such as when loading the new graphs. You can use this hook to
|
|
|
|
mutate the DOM before the page is revealed.
|
|
|
|
|
|
|
|
For example:
|
|
|
|
|
|
|
|
def mytest(web: AnkiWebView):
|
|
|
|
page = os.path.basename(web.page().url().path())
|
|
|
|
if page != "graphs.html":
|
|
|
|
return
|
|
|
|
web.eval(
|
|
|
|
"""
|
|
|
|
div = document.createElement("div");
|
|
|
|
div.innerHTML = 'hello';
|
|
|
|
document.body.appendChild(div);
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
|
|
|
|
gui_hooks.webview_did_inject_style_into_page.append(mytest)
|
2020-08-29 14:00:28 +02:00
|
|
|
''',
|
|
|
|
),
|
2020-01-15 04:03:11 +01:00
|
|
|
# Main
|
|
|
|
###################
|
2020-05-28 13:30:22 +02:00
|
|
|
Hook(
|
|
|
|
name="main_window_did_init",
|
|
|
|
doc="""Executed after the main window is fully initialized
|
|
|
|
|
|
|
|
A sample use case for this hook would be to delay actions until Anki objects
|
|
|
|
like the profile or collection are fully initialized. In contrast to
|
|
|
|
`profile_did_open`, this hook will only fire once per Anki session and
|
|
|
|
is thus suitable for single-shot subscribers.
|
|
|
|
""",
|
|
|
|
),
|
2020-08-08 14:59:13 +02:00
|
|
|
Hook(
|
2020-08-16 18:33:33 +02:00
|
|
|
name="main_window_should_require_reset",
|
|
|
|
args=[
|
|
|
|
"will_reset: bool",
|
2021-12-09 00:11:22 +01:00
|
|
|
"reason: aqt.main.ResetReason | str",
|
|
|
|
"context: object | None",
|
2020-08-16 18:33:33 +02:00
|
|
|
],
|
2020-08-08 14:59:13 +02:00
|
|
|
return_type="bool",
|
|
|
|
doc="""Executed before the main window will require a reset
|
2020-08-16 18:33:33 +02:00
|
|
|
|
2020-08-08 14:59:13 +02:00
|
|
|
This hook can be used to change the behavior of the main window,
|
|
|
|
when other dialogs, like the AddCards or Browser, require a reset
|
|
|
|
from the main window.
|
2020-08-16 18:33:33 +02:00
|
|
|
If you decide to use this hook, make you sure you check the reason for the reset.
|
|
|
|
Some reasons require more attention than others, and skipping important ones might
|
|
|
|
put the main window into an invalid state (e.g. display a deleted note).
|
2020-08-08 14:59:13 +02:00
|
|
|
""",
|
|
|
|
),
|
2020-03-20 11:59:59 +01:00
|
|
|
Hook(name="backup_did_complete"),
|
2020-05-28 13:30:22 +02:00
|
|
|
Hook(
|
|
|
|
name="profile_did_open",
|
|
|
|
legacy_hook="profileLoaded",
|
|
|
|
doc="""Executed whenever a user profile has been opened
|
|
|
|
|
|
|
|
Please note that this hook will also be called on profile switches, so if you
|
|
|
|
are looking to simply delay an add-on action in a single-shot manner,
|
|
|
|
`main_window_did_init` is likely the more suitable choice.
|
|
|
|
""",
|
|
|
|
),
|
2020-01-15 04:03:11 +01:00
|
|
|
Hook(name="profile_will_close", legacy_hook="unloadProfile"),
|
2022-04-19 09:10:34 +02:00
|
|
|
Hook(
|
|
|
|
name="collection_will_temporarily_close",
|
|
|
|
args=["col: anki.collection.Collection"],
|
|
|
|
doc="""Called before one-way syncs and colpkg imports/exports.""",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="collection_did_temporarily_close",
|
|
|
|
args=["col: anki.collection.Collection"],
|
|
|
|
doc="""Called after one-way syncs and colpkg imports/exports.""",
|
|
|
|
),
|
2020-01-15 04:03:11 +01:00
|
|
|
Hook(
|
|
|
|
name="collection_did_load",
|
2020-05-20 11:45:46 +02:00
|
|
|
args=["col: anki.collection.Collection"],
|
2020-01-15 04:03:11 +01:00
|
|
|
legacy_hook="colLoading",
|
|
|
|
),
|
2021-05-19 07:18:39 +02:00
|
|
|
Hook(name="undo_state_did_change", args=["info: UndoActionsInfo"]),
|
2020-01-15 03:16:54 +01:00
|
|
|
Hook(name="review_did_undo", args=["card_id: int"], legacy_hook="revertedCard"),
|
|
|
|
Hook(
|
2020-01-15 08:18:11 +01:00
|
|
|
name="style_did_init",
|
2020-01-15 03:16:54 +01:00
|
|
|
args=["style: str"],
|
|
|
|
return_type="str",
|
|
|
|
legacy_hook="setupStyle",
|
|
|
|
),
|
2020-02-15 23:21:23 +01:00
|
|
|
Hook(
|
|
|
|
name="top_toolbar_did_init_links",
|
2021-12-09 00:11:22 +01:00
|
|
|
args=["links: list[str]", "top_toolbar: aqt.toolbar.Toolbar"],
|
2020-02-20 16:23:33 +01:00
|
|
|
doc="""Used to modify or add links in the top toolbar of Anki's main window
|
|
|
|
|
|
|
|
'links' is a list of HTML link elements. Add-ons can generate their own links
|
|
|
|
by using aqt.toolbar.Toolbar.create_link. Links created in that way can then be
|
|
|
|
appended to the link list, e.g.:
|
|
|
|
|
|
|
|
def on_top_toolbar_did_init_links(links, toolbar):
|
|
|
|
my_link = toolbar.create_link(...)
|
|
|
|
links.append(my_link)
|
|
|
|
""",
|
2020-02-20 15:17:48 +01:00
|
|
|
),
|
2020-07-10 17:38:40 +02:00
|
|
|
Hook(
|
|
|
|
name="top_toolbar_did_redraw",
|
|
|
|
args=["top_toolbar: aqt.toolbar.Toolbar"],
|
|
|
|
doc="""Executed when the top toolbar is redrawn""",
|
|
|
|
),
|
2020-02-04 00:07:15 +01:00
|
|
|
Hook(
|
2020-08-31 05:29:28 +02:00
|
|
|
name="media_sync_did_progress",
|
|
|
|
args=["entry: aqt.mediasync.LogEntryWithTime"],
|
2020-02-15 23:21:23 +01:00
|
|
|
),
|
2020-02-05 02:55:14 +01:00
|
|
|
Hook(name="media_sync_did_start_or_stop", args=["running: bool"]),
|
2020-02-24 10:46:36 +01:00
|
|
|
Hook(
|
2020-05-20 05:39:21 +02:00
|
|
|
name="empty_cards_will_show",
|
|
|
|
args=["diag: aqt.emptycards.EmptyCardsDialog"],
|
|
|
|
doc="""Allows changing the list of cards to delete.""",
|
2020-02-24 10:46:36 +01:00
|
|
|
),
|
2020-09-14 12:22:01 +02:00
|
|
|
Hook(name="sync_will_start", args=[]),
|
2020-09-14 13:18:16 +02:00
|
|
|
Hook(
|
|
|
|
name="sync_did_finish",
|
|
|
|
args=[],
|
|
|
|
doc="""Executes after the sync of the collection concluded.
|
|
|
|
|
|
|
|
Note that the media sync did not necessarily finish at this point.""",
|
|
|
|
),
|
2020-11-09 10:45:14 +01:00
|
|
|
Hook(name="media_check_will_start", args=[]),
|
2022-05-27 08:25:34 +02:00
|
|
|
Hook(
|
|
|
|
name="media_check_did_finish",
|
|
|
|
args=["output: anki.media.CheckMediaResponse"],
|
|
|
|
doc="""Called after Media Check finishes.
|
|
|
|
|
|
|
|
`output` provides access to the unused/missing file lists and the text output that will be shown in the Check Media screen.""",
|
|
|
|
),
|
2022-07-22 04:45:47 +02:00
|
|
|
# Importing/exporting data
|
|
|
|
###################
|
|
|
|
Hook(
|
|
|
|
name="exporter_will_export",
|
|
|
|
args=[
|
|
|
|
"export_options: aqt.import_export.exporting.ExportOptions",
|
|
|
|
"exporter: aqt.import_export.exporting.Exporter",
|
|
|
|
],
|
|
|
|
return_type="aqt.import_export.exporting.ExportOptions",
|
|
|
|
doc="""Called before collection and deck exports.
|
|
|
|
|
|
|
|
Allows add-ons to be notified of impending deck exports and potentially
|
|
|
|
modify the export options. To perform the export unaltered, please return
|
|
|
|
`export_options` as is, e.g.:
|
|
|
|
|
|
|
|
def on_exporter_will_export(export_options: ExportOptions, exporter: Exporter):
|
|
|
|
if not isinstance(exporter, ApkgExporter):
|
|
|
|
return export_options
|
|
|
|
export_options.limit = ...
|
|
|
|
return export_options
|
|
|
|
""",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="exporter_did_export",
|
|
|
|
args=[
|
|
|
|
"export_options: aqt.import_export.exporting.ExportOptions",
|
|
|
|
"exporter: aqt.import_export.exporting.Exporter",
|
|
|
|
],
|
|
|
|
doc="""Called after collection and deck exports.""",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="legacy_exporter_will_export",
|
|
|
|
args=["legacy_exporter: anki.exporting.Exporter"],
|
|
|
|
doc="""Called before collection and deck exports performed by legacy exporters.""",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="legacy_exporter_did_export",
|
|
|
|
args=["legacy_exporter: anki.exporting.Exporter"],
|
|
|
|
doc="""Called after collection and deck exports performed by legacy exporters.""",
|
|
|
|
),
|
2022-07-22 11:33:07 +02:00
|
|
|
Hook(
|
|
|
|
name="exporters_list_did_initialize",
|
|
|
|
args=["exporters: list[Type[aqt.import_export.exporting.Exporter]]"],
|
|
|
|
doc="""Called after the list of exporter classes is created.
|
|
|
|
|
|
|
|
Allows you to register custom exporters and/or replace existing ones by
|
|
|
|
modifying the exporter list.
|
|
|
|
""",
|
|
|
|
),
|
2021-08-28 20:37:31 +02:00
|
|
|
# Dialog Manager
|
|
|
|
###################
|
|
|
|
Hook(
|
|
|
|
name="dialog_manager_did_open_dialog",
|
|
|
|
args=[
|
|
|
|
"dialog_manager: aqt.DialogManager",
|
|
|
|
"dialog_name: str",
|
|
|
|
"dialog_instance: QWidget",
|
|
|
|
],
|
|
|
|
doc="""Executed after aqt.dialogs creates a dialog window""",
|
|
|
|
),
|
2020-01-15 04:03:11 +01:00
|
|
|
# Adding cards
|
|
|
|
###################
|
2020-01-15 03:16:54 +01:00
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="add_cards_will_show_history_menu",
|
2020-01-15 03:16:54 +01:00
|
|
|
args=["addcards: aqt.addcards.AddCards", "menu: QMenu"],
|
|
|
|
legacy_hook="AddCards.onHistory",
|
|
|
|
),
|
2020-08-31 05:29:28 +02:00
|
|
|
Hook(
|
|
|
|
name="add_cards_did_init",
|
|
|
|
args=["addcards: aqt.addcards.AddCards"],
|
|
|
|
),
|
2020-01-15 03:16:54 +01:00
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="add_cards_did_add_note",
|
2020-01-15 03:16:54 +01:00
|
|
|
args=["note: anki.notes.Note"],
|
|
|
|
legacy_hook="AddCards.noteAdded",
|
|
|
|
),
|
2020-03-06 16:25:02 +01:00
|
|
|
Hook(
|
2020-03-20 11:59:59 +01:00
|
|
|
name="add_cards_will_add_note",
|
2021-12-09 00:11:22 +01:00
|
|
|
args=["problem: str | None", "note: anki.notes.Note"],
|
|
|
|
return_type="str | None",
|
2020-03-06 16:25:02 +01:00
|
|
|
doc="""Decides whether the note should be added to the collection or
|
|
|
|
not. It is assumed to come from the addCards window.
|
|
|
|
|
|
|
|
reason_to_already_reject is the first reason to reject that
|
|
|
|
was found, or None. If your filter wants to reject, it should
|
|
|
|
replace return the reason to reject. Otherwise return the
|
|
|
|
input.""",
|
|
|
|
),
|
2022-07-30 12:41:42 +02:00
|
|
|
Hook(
|
|
|
|
name="add_cards_might_add_note",
|
|
|
|
args=["optional_problems: list[str]", "note: anki.notes.Note"],
|
|
|
|
doc="""
|
|
|
|
Allows you to provide an optional reason to reject a note. A
|
|
|
|
yes / no dialog will open displaying the problem, to which the
|
|
|
|
user can decide if they would like to add the note anyway.
|
|
|
|
|
|
|
|
optional_problems is a list containing the optional reasons for which
|
|
|
|
you might reject a note. If your add-on wants to add a reason,
|
|
|
|
it should append the reason to the list.
|
|
|
|
|
|
|
|
An example add-on that asks the user for confirmation before adding a
|
|
|
|
card without tags:
|
|
|
|
|
|
|
|
def might_reject_empty_tag(optional_problems, note):
|
|
|
|
if not any(note.tags):
|
|
|
|
optional_problems.append("Add cards without tags?")
|
|
|
|
""",
|
|
|
|
),
|
2020-06-03 23:29:09 +02:00
|
|
|
Hook(
|
|
|
|
name="addcards_will_add_history_entry",
|
|
|
|
args=["line: str", "note: anki.notes.Note"],
|
|
|
|
return_type="str",
|
|
|
|
doc="""Allows changing the history line in the add-card window.""",
|
|
|
|
),
|
2021-09-25 04:22:42 +02:00
|
|
|
Hook(
|
|
|
|
name="add_cards_did_change_note_type",
|
|
|
|
args=["old: anki.models.NoteType", "new: anki.models.NoteType"],
|
|
|
|
doc="""Executed after the user selects a new note type when adding
|
|
|
|
cards.""",
|
|
|
|
),
|
2021-10-12 09:55:21 +02:00
|
|
|
Hook(
|
|
|
|
name="add_cards_did_change_deck",
|
|
|
|
args=["new_deck_id: int"],
|
|
|
|
doc="""Executed after the user selects a new different deck when
|
|
|
|
adding cards.""",
|
|
|
|
),
|
2020-01-15 04:03:11 +01:00
|
|
|
# Editing
|
|
|
|
###################
|
2020-10-04 22:50:02 +02:00
|
|
|
Hook(
|
|
|
|
name="editor_did_init_left_buttons",
|
2021-12-09 00:11:22 +01:00
|
|
|
args=["buttons: list[str]", "editor: aqt.editor.Editor"],
|
2020-10-04 22:50:02 +02:00
|
|
|
),
|
2020-01-15 03:16:54 +01:00
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="editor_did_init_buttons",
|
2021-12-09 00:11:22 +01:00
|
|
|
args=["buttons: list[str]", "editor: aqt.editor.Editor"],
|
2020-01-15 03:16:54 +01:00
|
|
|
),
|
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="editor_did_init_shortcuts",
|
2021-12-09 00:11:22 +01:00
|
|
|
args=["shortcuts: list[tuple]", "editor: aqt.editor.Editor"],
|
2020-01-15 03:16:54 +01:00
|
|
|
legacy_hook="setupEditorShortcuts",
|
|
|
|
),
|
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="editor_will_show_context_menu",
|
2020-01-15 03:16:54 +01:00
|
|
|
args=["editor_webview: aqt.editor.EditorWebView", "menu: QMenu"],
|
|
|
|
legacy_hook="EditorWebView.contextMenuEvent",
|
|
|
|
),
|
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="editor_did_fire_typing_timer",
|
2020-01-15 03:16:54 +01:00
|
|
|
args=["note: anki.notes.Note"],
|
|
|
|
legacy_hook="editTimer",
|
|
|
|
),
|
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="editor_did_focus_field",
|
2020-01-15 03:16:54 +01:00
|
|
|
args=["note: anki.notes.Note", "current_field_idx: int"],
|
|
|
|
legacy_hook="editFocusGained",
|
|
|
|
),
|
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="editor_did_unfocus_field",
|
2020-01-15 03:16:54 +01:00
|
|
|
args=["changed: bool", "note: anki.notes.Note", "current_field_idx: int"],
|
|
|
|
return_type="bool",
|
|
|
|
legacy_hook="editFocusLost",
|
|
|
|
),
|
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="editor_did_load_note",
|
2020-01-15 03:16:54 +01:00
|
|
|
args=["editor: aqt.editor.Editor"],
|
|
|
|
legacy_hook="loadNote",
|
|
|
|
),
|
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="editor_did_update_tags",
|
2020-01-15 03:16:54 +01:00
|
|
|
args=["note: anki.notes.Note"],
|
|
|
|
legacy_hook="tagsUpdated",
|
|
|
|
),
|
2020-08-08 23:14:55 +02:00
|
|
|
Hook(
|
|
|
|
name="editor_will_munge_html",
|
|
|
|
args=["txt: str", "editor: aqt.editor.Editor"],
|
|
|
|
return_type="str",
|
|
|
|
doc="""Allows manipulating the text that will be saved by the editor""",
|
|
|
|
),
|
2020-01-15 03:16:54 +01:00
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="editor_will_use_font_for_field",
|
2020-01-15 03:16:54 +01:00
|
|
|
args=["font: str"],
|
|
|
|
return_type="str",
|
|
|
|
legacy_hook="mungeEditingFontName",
|
|
|
|
),
|
2020-03-16 04:34:42 +01:00
|
|
|
Hook(
|
|
|
|
name="editor_web_view_did_init",
|
|
|
|
args=["editor_web_view: aqt.editor.EditorWebView"],
|
|
|
|
),
|
2020-08-31 05:29:28 +02:00
|
|
|
Hook(
|
|
|
|
name="editor_did_init",
|
|
|
|
args=["editor: aqt.editor.Editor"],
|
|
|
|
),
|
2020-03-24 10:17:01 +01:00
|
|
|
Hook(
|
|
|
|
name="editor_will_load_note",
|
|
|
|
args=["js: str", "note: anki.notes.Note", "editor: aqt.editor.Editor"],
|
|
|
|
return_type="str",
|
|
|
|
doc="""Allows changing the javascript commands to load note before
|
|
|
|
executing it and do change in the QT editor.""",
|
|
|
|
),
|
2021-07-19 07:21:13 +02:00
|
|
|
Hook(
|
|
|
|
name="editor_did_paste",
|
|
|
|
args=[
|
|
|
|
"editor: aqt.editor.Editor",
|
|
|
|
"html: str",
|
|
|
|
"internal: bool",
|
|
|
|
"extended: bool",
|
|
|
|
],
|
|
|
|
doc="""Called after some data is pasted by python into an editor field.""",
|
|
|
|
),
|
2021-09-05 12:20:27 +02:00
|
|
|
Hook(
|
|
|
|
name="editor_will_process_mime",
|
|
|
|
args=[
|
|
|
|
"mime: QMimeData",
|
2021-09-05 13:54:04 +02:00
|
|
|
"editor_web_view: aqt.editor.EditorWebView",
|
2021-09-05 12:20:27 +02:00
|
|
|
"internal: bool",
|
|
|
|
"extended: bool",
|
|
|
|
"drop_event: bool",
|
|
|
|
],
|
|
|
|
return_type="QMimeData",
|
|
|
|
doc="""
|
|
|
|
Used to modify MIME data stored in the clipboard after a drop or a paste.
|
|
|
|
Called after the user pastes or drag-and-drops something to Anki
|
|
|
|
before Anki processes the data.
|
|
|
|
|
|
|
|
The function should return a new or existing QMimeData object.
|
|
|
|
|
|
|
|
"mime" contains the corresponding QMimeData object.
|
|
|
|
"internal" indicates whether the drop or paste is performed between Anki fields.
|
|
|
|
Most likely you want to skip processing if "internal" was set to True.
|
|
|
|
"extended" indicates whether the user requested an extended paste.
|
|
|
|
"drop_event" indicates whether the event was triggered by a drag-and-drop
|
|
|
|
or by a right-click paste.
|
|
|
|
""",
|
|
|
|
),
|
2020-03-31 15:00:26 +02:00
|
|
|
# Tag
|
|
|
|
###################
|
2020-04-01 09:13:08 +02:00
|
|
|
Hook(name="tag_editor_did_process_key", args=["tag_edit: TagEdit", "evt: QEvent"]),
|
2020-01-20 13:01:38 +01:00
|
|
|
# Sound/video
|
|
|
|
###################
|
|
|
|
Hook(name="av_player_will_play", args=["tag: anki.sound.AVTag"]),
|
2020-01-22 05:39:18 +01:00
|
|
|
Hook(
|
|
|
|
name="av_player_did_begin_playing",
|
|
|
|
args=["player: aqt.sound.Player", "tag: anki.sound.AVTag"],
|
|
|
|
),
|
|
|
|
Hook(name="av_player_did_end_playing", args=["player: aqt.sound.Player"]),
|
2022-05-09 03:08:34 +02:00
|
|
|
Hook(
|
|
|
|
name="av_player_will_play_tags",
|
|
|
|
args=[
|
|
|
|
"tags: list[anki.sound.AVTag]",
|
|
|
|
"side: str",
|
|
|
|
"context: Any",
|
|
|
|
],
|
|
|
|
doc="""Called before playing a card side's sounds.
|
|
|
|
|
|
|
|
`tags` can be used to inspect and manipulate the sounds
|
|
|
|
that will be played (if any).
|
|
|
|
|
|
|
|
`side` can either be "question" or "answer".
|
|
|
|
|
|
|
|
`context` is the screen where the sounds will be played (e.g., Reviewer, Previewer, and CardLayout).
|
|
|
|
|
|
|
|
This won't be called when the user manually plays sounds
|
|
|
|
using `Replay Audio`.
|
|
|
|
|
|
|
|
Note that this hook is called even when the `Automatically play audio`
|
|
|
|
option is unchecked; This is so as to allow playing custom
|
|
|
|
sounds regardless of that option.""",
|
|
|
|
),
|
2020-03-02 00:54:58 +01:00
|
|
|
# Addon
|
|
|
|
###################
|
|
|
|
Hook(
|
|
|
|
name="addon_config_editor_will_display_json",
|
|
|
|
args=["text: str"],
|
|
|
|
return_type="str",
|
|
|
|
doc="""Allows changing the text of the json configuration before actually
|
|
|
|
displaying it to the user. For example, you can replace "\\\\n" by
|
|
|
|
some actual new line. Then you can replace the new lines by "\\\\n"
|
|
|
|
while reading the file and let the user uses real new line in
|
|
|
|
string instead of its encoding.""",
|
|
|
|
),
|
2020-03-02 01:09:52 +01:00
|
|
|
Hook(
|
|
|
|
name="addon_config_editor_will_save_json",
|
|
|
|
args=["text: str"],
|
|
|
|
return_type="str",
|
2022-11-23 09:00:28 +01:00
|
|
|
doc="""Deprecated. Use addon_config_editor_will_update_json instead.
|
|
|
|
Allows changing the text of the json configuration that was
|
|
|
|
received from the user before actually reading it. For
|
|
|
|
example, you can replace new line in strings by some "\\\\n".""",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="addon_config_editor_will_update_json",
|
|
|
|
args=["text: str", "addon: str"],
|
|
|
|
return_type="str",
|
2022-12-07 06:39:57 +01:00
|
|
|
replaces="addon_config_editor_will_save_json",
|
|
|
|
replaced_hook_args=["text: str"],
|
2020-03-02 01:09:52 +01:00
|
|
|
doc="""Allows changing the text of the json configuration that was
|
|
|
|
received from the user before actually reading it. For
|
|
|
|
example, you can replace new line in strings by some "\\\\n".""",
|
|
|
|
),
|
2020-03-06 21:04:51 +01:00
|
|
|
Hook(
|
|
|
|
name="addons_dialog_will_show",
|
|
|
|
args=["dialog: aqt.addons.AddonsDialog"],
|
|
|
|
doc="""Allows changing the add-on dialog before it is shown. E.g. add
|
|
|
|
buttons.""",
|
|
|
|
),
|
2020-03-06 22:21:42 +01:00
|
|
|
Hook(
|
|
|
|
name="addons_dialog_did_change_selected_addon",
|
|
|
|
args=["dialog: aqt.addons.AddonsDialog", "add_on: aqt.addons.AddonMeta"],
|
|
|
|
doc="""Allows doing an action when a single add-on is selected.""",
|
|
|
|
),
|
2021-06-15 02:01:29 +02:00
|
|
|
Hook(
|
|
|
|
name="addons_dialog_will_delete_addons",
|
2021-12-09 00:11:22 +01:00
|
|
|
args=["dialog: aqt.addons.AddonsDialog", "ids: list[str]"],
|
2021-06-15 02:01:29 +02:00
|
|
|
doc="""Allows doing an action before an add-on is deleted.""",
|
|
|
|
),
|
2020-03-19 12:03:09 +01:00
|
|
|
# Model
|
|
|
|
###################
|
2020-08-31 13:50:38 +02:00
|
|
|
Hook(
|
|
|
|
name="models_advanced_will_show",
|
|
|
|
args=["advanced: QDialog"],
|
|
|
|
),
|
2020-08-31 05:29:28 +02:00
|
|
|
Hook(
|
2020-08-31 13:35:01 +02:00
|
|
|
name="models_did_init_buttons",
|
2020-08-23 18:18:11 +02:00
|
|
|
args=[
|
2021-12-09 00:11:22 +01:00
|
|
|
"buttons: list[tuple[str, Callable[[], None]]]",
|
2020-08-23 18:18:11 +02:00
|
|
|
"models: aqt.models.Models",
|
|
|
|
],
|
2021-12-09 00:11:22 +01:00
|
|
|
return_type="list[tuple[str, Callable[[], None]]]",
|
2020-08-31 13:35:01 +02:00
|
|
|
doc="""Allows adding buttons to the Model dialog""",
|
2020-08-31 05:29:28 +02:00
|
|
|
),
|
2020-10-11 19:16:30 +02:00
|
|
|
# Fields
|
|
|
|
###################
|
|
|
|
Hook(
|
|
|
|
name="fields_did_rename_field",
|
2020-10-12 04:37:51 +02:00
|
|
|
args=[
|
|
|
|
"dialog: aqt.fields.FieldDialog",
|
2021-03-27 12:46:49 +01:00
|
|
|
"field: anki.models.FieldDict",
|
2020-10-12 04:37:51 +02:00
|
|
|
"old_name: str",
|
|
|
|
],
|
2020-10-11 19:16:30 +02:00
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="fields_did_delete_field",
|
2021-03-27 12:46:49 +01:00
|
|
|
args=["dialog: aqt.fields.FieldDialog", "field: anki.models.FieldDict"],
|
2020-10-11 19:16:30 +02:00
|
|
|
),
|
2020-07-16 21:48:46 +02:00
|
|
|
# Stats
|
|
|
|
###################
|
|
|
|
Hook(
|
|
|
|
name="stats_dialog_will_show",
|
|
|
|
args=["dialog: aqt.stats.NewDeckStats"],
|
|
|
|
doc="""Allows changing the stats dialog before it is shown.""",
|
|
|
|
),
|
|
|
|
Hook(
|
|
|
|
name="stats_dialog_old_will_show",
|
2020-07-17 03:08:09 +02:00
|
|
|
args=["dialog: aqt.stats.DeckStats"],
|
2020-07-16 21:48:46 +02:00
|
|
|
doc="""Allows changing the old stats dialog before it is shown.""",
|
|
|
|
),
|
2020-01-15 04:03:11 +01:00
|
|
|
# Other
|
|
|
|
###################
|
|
|
|
Hook(
|
|
|
|
name="current_note_type_did_change",
|
2021-03-27 12:46:49 +01:00
|
|
|
args=["notetype: NotetypeDict"],
|
2020-01-15 04:03:11 +01:00
|
|
|
legacy_hook="currentModelChanged",
|
|
|
|
legacy_no_args=True,
|
|
|
|
),
|
rework filtered deck screen & search errors
- Filtered deck creation now happens as an atomic operation, and is
undoable.
- The logic for initial search text, normalizing searches and so on
has been pushed into the backend.
- Use protobuf to pass the filtered deck to the updated dialog, so
we don't need to deal with untyped JSON.
- Change the "revise your search?" prompt to be a simple info box -
user has access to cancel and build buttons, and doesn't need a separate
prompt. Tweak the wording so the 'show excluded' button should be more
obvious.
- Filtered decks have a time appended to them instead of a number,
primarily because it's easier to implement. No objections going back to
the old behaviour if someone wants to contribute a clean patch.
The standard de-duplication will happen if two decks are created in the
same minute with the same name.
- Tweak the default sort order, and start with two searches. The UI
will still hide the second search by default, but by starting with two,
the frontend doesn't need logic for creating the starting text.
- Search errors now have their own error type, instead of using
InvalidInput, as that was intended mainly for bad API calls. The markdown
conversion is done when the error is converted from the backend, allowing
errors to printed as a string without any special handling by the calling
code.
TODO: when building a new filtered deck, update_active() is clobbering
the undo log when the overview is refreshed
2021-03-24 12:52:48 +01:00
|
|
|
Hook(name="sidebar_should_refresh_decks", doc="Legacy, do not use."),
|
2021-04-30 09:30:48 +02:00
|
|
|
Hook(name="sidebar_should_refresh_notetypes", doc="Legacy, do not use."),
|
2020-01-15 04:03:11 +01:00
|
|
|
Hook(
|
2020-01-15 08:45:35 +01:00
|
|
|
name="deck_browser_will_show_options_menu",
|
2020-01-15 04:03:11 +01:00
|
|
|
args=["menu: QMenu", "deck_id: int"],
|
|
|
|
legacy_hook="showDeckOptions",
|
|
|
|
),
|
2021-07-02 11:16:10 +02:00
|
|
|
Hook(
|
|
|
|
name="flag_label_did_change",
|
|
|
|
args=[],
|
|
|
|
doc="Used to update the GUI when a new flag label is assigned.",
|
|
|
|
),
|
2020-01-13 05:38:05 +01:00
|
|
|
]
|
|
|
|
|
2020-11-01 05:26:58 +01:00
|
|
|
suffix = ""
|
|
|
|
|
2020-01-13 05:38:05 +01:00
|
|
|
if __name__ == "__main__":
|
2020-11-01 05:26:58 +01:00
|
|
|
path = sys.argv[1]
|
|
|
|
write_file(path, hooks, prefix, suffix)
|