mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
add context to card rendering, and rework related hooks
- the context exists for the lifecycle of one card's render, and caches calls to things like .card() to avoid add-ons needing to do their own cache management. - add-ons can optionally add extra data to the context if they need it across multiple filters - removed card_will_render. the legacy hook is still available for now - card_did_render is now called only once, with both front and back text
This commit is contained in:
parent
d2865235df
commit
2a0b480103
6 changed files with 159 additions and 179 deletions
|
@ -31,7 +31,7 @@ from anki.rsbackend import RustBackend
|
||||||
from anki.sched import Scheduler as V1Scheduler
|
from anki.sched import Scheduler as V1Scheduler
|
||||||
from anki.schedv2 import Scheduler as V2Scheduler
|
from anki.schedv2 import Scheduler as V2Scheduler
|
||||||
from anki.tags import TagManager
|
from anki.tags import TagManager
|
||||||
from anki.template import render_card
|
from anki.template import TemplateRenderContext, render_card
|
||||||
from anki.types import NoteType, QAData, Template
|
from anki.types import NoteType, QAData, Template
|
||||||
from anki.utils import (
|
from anki.utils import (
|
||||||
devMode,
|
devMode,
|
||||||
|
@ -635,7 +635,6 @@ where c.nid = n.id and c.id in %s group by nid"""
|
||||||
def _renderQA(
|
def _renderQA(
|
||||||
self, data: QAData, qfmt: Optional[str] = None, afmt: Optional[str] = None
|
self, data: QAData, qfmt: Optional[str] = None, afmt: Optional[str] = None
|
||||||
) -> Dict[str, Union[str, int]]:
|
) -> Dict[str, Union[str, int]]:
|
||||||
"Returns hash of id, question, answer."
|
|
||||||
# extract info from data
|
# extract info from data
|
||||||
split_fields = splitFields(data[6])
|
split_fields = splitFields(data[6])
|
||||||
card_ord = data[4]
|
card_ord = data[4]
|
||||||
|
@ -665,35 +664,39 @@ where c.nid = n.id and c.id in %s group by nid"""
|
||||||
fields["CardFlag"] = self._flagNameFromCardFlags(flag)
|
fields["CardFlag"] = self._flagNameFromCardFlags(flag)
|
||||||
fields["c%d" % (card_ord + 1)] = "1"
|
fields["c%d" % (card_ord + 1)] = "1"
|
||||||
|
|
||||||
# legacy
|
# legacy hook
|
||||||
fields = runFilter("mungeFields", fields, model, data, self)
|
fields = runFilter("mungeFields", fields, model, data, self)
|
||||||
|
|
||||||
# allow add-ons to modify the available fields & templates
|
ctx = TemplateRenderContext(self, data, fields)
|
||||||
(qfmt, afmt) = hooks.card_will_render((qfmt, afmt), fields, model, data)
|
|
||||||
|
|
||||||
# render fields
|
# render fields. if any custom filters are encountered,
|
||||||
|
# the field_filter hook will be called.
|
||||||
try:
|
try:
|
||||||
qatext = render_card(self, qfmt, afmt, fields, card_ord)
|
qtext, atext = render_card(self, qfmt, afmt, ctx)
|
||||||
except anki.rsbackend.BackendException as e:
|
except anki.rsbackend.BackendException as e:
|
||||||
errmsg = _("Card template has a problem:") + f"<br>{e}"
|
errmsg = _("Card template has a problem:") + f"<br>{e}"
|
||||||
qatext = (errmsg, errmsg)
|
qtext = errmsg
|
||||||
|
atext = errmsg
|
||||||
|
|
||||||
ret: Dict[str, Any] = dict(q=qatext[0], a=qatext[1], id=card_id)
|
# avoid showing the user a confusing blank card if they've
|
||||||
|
# forgotten to add a cloze deletion
|
||||||
|
if model["type"] == MODEL_CLOZE:
|
||||||
|
if not self.models._availClozeOrds(model, data[6], False):
|
||||||
|
qtext = (
|
||||||
|
qtext
|
||||||
|
+ "<p>"
|
||||||
|
+ _("Please edit this note and add some cloze deletions. (%s)")
|
||||||
|
% ("<a href=%s#cloze>%s</a>" % (HELP_SITE, _("help")))
|
||||||
|
)
|
||||||
|
|
||||||
# allow add-ons to modify the generated result
|
# allow add-ons to modify the generated result
|
||||||
for type in "q", "a":
|
(qtext, atext) = hooks.card_did_render((qtext, atext), ctx)
|
||||||
ret[type] = hooks.card_did_render(
|
|
||||||
ret[type], type, fields, model, data, self
|
|
||||||
)
|
|
||||||
|
|
||||||
# empty cloze?
|
# legacy hook
|
||||||
if type == "q" and model["type"] == MODEL_CLOZE:
|
qtext = runFilter("mungeQA", qtext, "q", fields, model, data, self)
|
||||||
if not self.models._availClozeOrds(model, data[6], False):
|
atext = runFilter("mungeQA", atext, "a", fields, model, data, self)
|
||||||
ret["q"] += "<p>" + _(
|
|
||||||
"Please edit this note and add some cloze deletions. (%s)"
|
|
||||||
) % ("<a href=%s#cloze>%s</a>" % (HELP_SITE, _("help")))
|
|
||||||
|
|
||||||
return ret
|
return dict(q=qtext, a=atext, id=card_id)
|
||||||
|
|
||||||
def _qaData(self, where="") -> Any:
|
def _qaData(self, where="") -> Any:
|
||||||
"Return [cid, nid, mid, did, ord, tags, flds, cardFlags] db query"
|
"Return [cid, nid, mid, did, ord, tags, flds, cardFlags] db query"
|
||||||
|
|
|
@ -18,7 +18,7 @@ import decorator
|
||||||
|
|
||||||
import anki
|
import anki
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
from anki.types import QAData
|
from anki.template import TemplateRenderContext
|
||||||
|
|
||||||
# New hook/filter handling
|
# New hook/filter handling
|
||||||
##############################################################################
|
##############################################################################
|
||||||
|
@ -58,71 +58,31 @@ class _CardDidRenderFilter:
|
||||||
"""Can modify the resulting text after rendering completes."""
|
"""Can modify the resulting text after rendering completes."""
|
||||||
|
|
||||||
_hooks: List[
|
_hooks: List[
|
||||||
Callable[
|
Callable[[Tuple[str, str], TemplateRenderContext], Tuple[str, str]]
|
||||||
[
|
|
||||||
str,
|
|
||||||
str,
|
|
||||||
Dict[str, str],
|
|
||||||
Dict[str, Any],
|
|
||||||
QAData,
|
|
||||||
"anki.storage._Collection",
|
|
||||||
],
|
|
||||||
str,
|
|
||||||
]
|
|
||||||
] = []
|
] = []
|
||||||
|
|
||||||
def append(
|
def append(
|
||||||
self,
|
self, cb: Callable[[Tuple[str, str], TemplateRenderContext], Tuple[str, str]]
|
||||||
cb: Callable[
|
|
||||||
[
|
|
||||||
str,
|
|
||||||
str,
|
|
||||||
Dict[str, str],
|
|
||||||
Dict[str, Any],
|
|
||||||
QAData,
|
|
||||||
"anki.storage._Collection",
|
|
||||||
],
|
|
||||||
str,
|
|
||||||
],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""(text: str, side: str, fields: Dict[str, str], notetype: Dict[str, Any], data: QAData, col: anki.storage._Collection)"""
|
"""(text: Tuple[str, str], ctx: TemplateRenderContext)"""
|
||||||
self._hooks.append(cb)
|
self._hooks.append(cb)
|
||||||
|
|
||||||
def remove(
|
def remove(
|
||||||
self,
|
self, cb: Callable[[Tuple[str, str], TemplateRenderContext], Tuple[str, str]]
|
||||||
cb: Callable[
|
|
||||||
[
|
|
||||||
str,
|
|
||||||
str,
|
|
||||||
Dict[str, str],
|
|
||||||
Dict[str, Any],
|
|
||||||
QAData,
|
|
||||||
"anki.storage._Collection",
|
|
||||||
],
|
|
||||||
str,
|
|
||||||
],
|
|
||||||
) -> None:
|
) -> None:
|
||||||
if cb in self._hooks:
|
if cb in self._hooks:
|
||||||
self._hooks.remove(cb)
|
self._hooks.remove(cb)
|
||||||
|
|
||||||
def __call__(
|
def __call__(
|
||||||
self,
|
self, text: Tuple[str, str], ctx: TemplateRenderContext
|
||||||
text: str,
|
) -> Tuple[str, str]:
|
||||||
side: str,
|
|
||||||
fields: Dict[str, str],
|
|
||||||
notetype: Dict[str, Any],
|
|
||||||
data: QAData,
|
|
||||||
col: anki.storage._Collection,
|
|
||||||
) -> str:
|
|
||||||
for filter in self._hooks:
|
for filter in self._hooks:
|
||||||
try:
|
try:
|
||||||
text = filter(text, side, fields, notetype, data, col)
|
text = filter(text, ctx)
|
||||||
except:
|
except:
|
||||||
# if the hook fails, remove it
|
# if the hook fails, remove it
|
||||||
self._hooks.remove(filter)
|
self._hooks.remove(filter)
|
||||||
raise
|
raise
|
||||||
# legacy support
|
|
||||||
runFilter("mungeQA", text, side, fields, notetype, data, col)
|
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
@ -153,53 +113,6 @@ class _CardOdueWasInvalidHook:
|
||||||
card_odue_was_invalid = _CardOdueWasInvalidHook()
|
card_odue_was_invalid = _CardOdueWasInvalidHook()
|
||||||
|
|
||||||
|
|
||||||
class _CardWillRenderFilter:
|
|
||||||
"""Can modify the available fields and question/answer templates prior to rendering."""
|
|
||||||
|
|
||||||
_hooks: List[
|
|
||||||
Callable[
|
|
||||||
[Tuple[str, str], Dict[str, str], Dict[str, Any], QAData], Tuple[str, str]
|
|
||||||
]
|
|
||||||
] = []
|
|
||||||
|
|
||||||
def append(
|
|
||||||
self,
|
|
||||||
cb: Callable[
|
|
||||||
[Tuple[str, str], Dict[str, str], Dict[str, Any], QAData], Tuple[str, str]
|
|
||||||
],
|
|
||||||
) -> None:
|
|
||||||
"""(templates: Tuple[str, str], fields: Dict[str, str], notetype: Dict[str, Any], data: QAData)"""
|
|
||||||
self._hooks.append(cb)
|
|
||||||
|
|
||||||
def remove(
|
|
||||||
self,
|
|
||||||
cb: Callable[
|
|
||||||
[Tuple[str, str], Dict[str, str], Dict[str, Any], QAData], Tuple[str, str]
|
|
||||||
],
|
|
||||||
) -> None:
|
|
||||||
if cb in self._hooks:
|
|
||||||
self._hooks.remove(cb)
|
|
||||||
|
|
||||||
def __call__(
|
|
||||||
self,
|
|
||||||
templates: Tuple[str, str],
|
|
||||||
fields: Dict[str, str],
|
|
||||||
notetype: Dict[str, Any],
|
|
||||||
data: QAData,
|
|
||||||
) -> Tuple[str, str]:
|
|
||||||
for filter in self._hooks:
|
|
||||||
try:
|
|
||||||
templates = filter(templates, fields, notetype, data)
|
|
||||||
except:
|
|
||||||
# if the hook fails, remove it
|
|
||||||
self._hooks.remove(filter)
|
|
||||||
raise
|
|
||||||
return templates
|
|
||||||
|
|
||||||
|
|
||||||
card_will_render = _CardWillRenderFilter()
|
|
||||||
|
|
||||||
|
|
||||||
class _DeckAddedHook:
|
class _DeckAddedHook:
|
||||||
_hooks: List[Callable[[Dict[str, Any]], None]] = []
|
_hooks: List[Callable[[Dict[str, Any]], None]] = []
|
||||||
|
|
||||||
|
@ -253,22 +166,31 @@ exporters_list_created = _ExportersListCreatedHook()
|
||||||
|
|
||||||
|
|
||||||
class _FieldFilterFilter:
|
class _FieldFilterFilter:
|
||||||
_hooks: List[Callable[[str, str, str, Dict[str, str]], str]] = []
|
"""Allows you to define custom {{filters:..}}
|
||||||
|
|
||||||
def append(self, cb: Callable[[str, str, str, Dict[str, str]], str]) -> None:
|
Your add-on can check filter_name to decide whether it should modify
|
||||||
"""(field_text: str, field_name: str, filter_name: str, fields: Dict[str, str])"""
|
field_text or not before returning it."""
|
||||||
|
|
||||||
|
_hooks: List[Callable[[str, str, str, TemplateRenderContext], str]] = []
|
||||||
|
|
||||||
|
def append(self, cb: Callable[[str, str, str, TemplateRenderContext], str]) -> None:
|
||||||
|
"""(field_text: str, field_name: str, filter_name: str, ctx: TemplateRenderContext)"""
|
||||||
self._hooks.append(cb)
|
self._hooks.append(cb)
|
||||||
|
|
||||||
def remove(self, cb: Callable[[str, str, str, Dict[str, str]], str]) -> None:
|
def remove(self, cb: Callable[[str, str, str, TemplateRenderContext], str]) -> None:
|
||||||
if cb in self._hooks:
|
if cb in self._hooks:
|
||||||
self._hooks.remove(cb)
|
self._hooks.remove(cb)
|
||||||
|
|
||||||
def __call__(
|
def __call__(
|
||||||
self, field_text: str, field_name: str, filter_name: str, fields: Dict[str, str]
|
self,
|
||||||
|
field_text: str,
|
||||||
|
field_name: str,
|
||||||
|
filter_name: str,
|
||||||
|
ctx: TemplateRenderContext,
|
||||||
) -> str:
|
) -> str:
|
||||||
for filter in self._hooks:
|
for filter in self._hooks:
|
||||||
try:
|
try:
|
||||||
field_text = filter(field_text, field_name, filter_name, fields)
|
field_text = filter(field_text, field_name, filter_name, ctx)
|
||||||
except:
|
except:
|
||||||
# if the hook fails, remove it
|
# if the hook fails, remove it
|
||||||
self._hooks.remove(filter)
|
self._hooks.remove(filter)
|
||||||
|
|
|
@ -7,12 +7,13 @@ import html
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Optional, Tuple
|
||||||
|
|
||||||
import anki
|
import anki
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.types import NoteType, QAData
|
from anki.template import TemplateRenderContext
|
||||||
|
from anki.types import NoteType
|
||||||
from anki.utils import call, checksum, isMac, namedtmp, stripHTML, tmpdir
|
from anki.utils import call, checksum, isMac, namedtmp, stripHTML, tmpdir
|
||||||
|
|
||||||
pngCommands = [
|
pngCommands = [
|
||||||
|
@ -47,15 +48,18 @@ def stripLatex(text) -> Any:
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
|
||||||
# media code and some add-ons depend on the current name
|
def on_card_did_render(
|
||||||
def mungeQA(
|
text: Tuple[str, str], ctx: TemplateRenderContext
|
||||||
html: str,
|
) -> Tuple[str, str]:
|
||||||
type: str,
|
qtext, atext = text
|
||||||
fields: Dict[str, str],
|
|
||||||
model: NoteType,
|
qtext = render_latex(qtext, ctx.note_type(), ctx.col())
|
||||||
data: QAData,
|
atext = render_latex(atext, ctx.note_type(), ctx.col())
|
||||||
col: anki.storage._Collection,
|
|
||||||
) -> str:
|
return (qtext, atext)
|
||||||
|
|
||||||
|
|
||||||
|
def render_latex(html: str, model: NoteType, col: anki.storage._Collection,) -> str:
|
||||||
"Convert TEXT with embedded latex tags to image links."
|
"Convert TEXT with embedded latex tags to image links."
|
||||||
for match in regexps["standard"].finditer(html):
|
for match in regexps["standard"].finditer(html):
|
||||||
html = html.replace(match.group(), _imgLink(col, match.group(1), model))
|
html = html.replace(match.group(), _imgLink(col, match.group(1), model))
|
||||||
|
@ -184,4 +188,4 @@ def _errMsg(type: str, texpath: str) -> Any:
|
||||||
|
|
||||||
|
|
||||||
# setup q/a filter - type ignored due to import cycle
|
# setup q/a filter - type ignored due to import cycle
|
||||||
hooks.card_did_render.append(mungeQA) # type: ignore
|
hooks.card_did_render.append(on_card_did_render) # type: ignore
|
||||||
|
|
|
@ -17,7 +17,7 @@ from typing import Any, Callable, List, Optional, Tuple, Union
|
||||||
from anki.consts import *
|
from anki.consts import *
|
||||||
from anki.db import DB, DBError
|
from anki.db import DB, DBError
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.latex import mungeQA
|
from anki.latex import render_latex
|
||||||
from anki.template import expand_clozes
|
from anki.template import expand_clozes
|
||||||
from anki.utils import checksum, isMac, isWin
|
from anki.utils import checksum, isMac, isWin
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@ create table meta (dirMod int, lastUsn int); insert into meta values (0, 0);
|
||||||
strings = [string]
|
strings = [string]
|
||||||
for string in strings:
|
for string in strings:
|
||||||
# handle latex
|
# handle latex
|
||||||
string = mungeQA(string, None, None, model, None, self.col)
|
string = render_latex(string, model, self.col)
|
||||||
# extract filenames
|
# extract filenames
|
||||||
for reg in self.regexps:
|
for reg in self.regexps:
|
||||||
for match in re.finditer(reg, string):
|
for match in re.finditer(reg, string):
|
||||||
|
|
|
@ -15,7 +15,7 @@ the filter is skipped.
|
||||||
Add-ons can register a filter with the following code:
|
Add-ons can register a filter with the following code:
|
||||||
|
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
hooks.field_replacement.append(myfunc)
|
hooks.field_filter.append(myfunc)
|
||||||
|
|
||||||
This will call myfunc, passing the field text in as the first argument.
|
This will call myfunc, passing the field text in as the first argument.
|
||||||
Your function should decide if it wants to modify the text by checking
|
Your function should decide if it wants to modify the text by checking
|
||||||
|
@ -29,35 +29,98 @@ template_legacy.py file, using the legacy addHook() system.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import anki
|
import anki
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki.hooks import runFilter
|
|
||||||
from anki.rsbackend import TemplateReplacementList
|
from anki.rsbackend import TemplateReplacementList
|
||||||
from anki.sound import stripSounds
|
from anki.sound import stripSounds
|
||||||
|
from anki.types import NoteType, QAData
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateRenderContext:
|
||||||
|
"""Holds information for the duration of one card render.
|
||||||
|
|
||||||
|
This may fetch information lazily in the future, so please avoid
|
||||||
|
using the _private fields directly."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, col: anki.storage._Collection, qadata: QAData, fields: Dict[str, str]
|
||||||
|
) -> None:
|
||||||
|
self._col = col
|
||||||
|
self._qadata = qadata
|
||||||
|
self._fields = fields
|
||||||
|
|
||||||
|
self._note_type: Optional[NoteType] = None
|
||||||
|
self._card: Optional[anki.cards.Card] = None
|
||||||
|
self._note: Optional[anki.notes.Note] = None
|
||||||
|
|
||||||
|
# if you need to store extra state to share amongst rendering
|
||||||
|
# hooks, you can insert it into this dictionary
|
||||||
|
self.extra_state: Dict[str, Any] = {}
|
||||||
|
|
||||||
|
def col(self) -> anki.storage._Collection:
|
||||||
|
return self._col
|
||||||
|
|
||||||
|
def fields(self) -> Dict[str, str]:
|
||||||
|
return self._fields
|
||||||
|
|
||||||
|
def card_id(self) -> int:
|
||||||
|
return self._qadata[0]
|
||||||
|
|
||||||
|
def note_id(self) -> int:
|
||||||
|
return self._qadata[1]
|
||||||
|
|
||||||
|
def deck_id(self) -> int:
|
||||||
|
return self._qadata[3]
|
||||||
|
|
||||||
|
def card_ord(self) -> int:
|
||||||
|
return self._qadata[4]
|
||||||
|
|
||||||
|
def card(self) -> Optional[anki.cards.Card]:
|
||||||
|
"""Returns the card being rendered. Will return None in the add screen.
|
||||||
|
|
||||||
|
Be careful not to call .q() or .a() on the card, or you'll create an
|
||||||
|
infinite loop."""
|
||||||
|
if not self._card:
|
||||||
|
try:
|
||||||
|
self._card = self.col().getCard(self.card_id())
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self._card
|
||||||
|
|
||||||
|
def note(self) -> anki.notes.Note:
|
||||||
|
if not self._note:
|
||||||
|
self._note = self.col().getNote(self.note_id())
|
||||||
|
|
||||||
|
return self._note
|
||||||
|
|
||||||
|
def note_type(self) -> NoteType:
|
||||||
|
if not self._note_type:
|
||||||
|
self._note_type = self.col().models.get(self._qadata[2])
|
||||||
|
|
||||||
|
return self._note_type
|
||||||
|
|
||||||
|
|
||||||
def render_card(
|
def render_card(
|
||||||
col: anki.storage._Collection,
|
col: anki.storage._Collection, qfmt: str, afmt: str, ctx: TemplateRenderContext
|
||||||
qfmt: str,
|
|
||||||
afmt: str,
|
|
||||||
fields: Dict[str, str],
|
|
||||||
card_ord: int,
|
|
||||||
) -> Tuple[str, str]:
|
) -> Tuple[str, str]:
|
||||||
"""Renders the provided templates, returning rendered q & a text.
|
"""Renders the provided templates, returning rendered q & a text.
|
||||||
|
|
||||||
Will raise if the template is invalid."""
|
Will raise if the template is invalid."""
|
||||||
(qnodes, anodes) = col.backend.render_card(qfmt, afmt, fields, card_ord)
|
(qnodes, anodes) = col.backend.render_card(qfmt, afmt, ctx.fields(), ctx.card_ord())
|
||||||
|
|
||||||
qtext = apply_custom_filters(qnodes, fields, front_side=None)
|
qtext = apply_custom_filters(qnodes, ctx, front_side=None)
|
||||||
atext = apply_custom_filters(anodes, fields, front_side=qtext)
|
atext = apply_custom_filters(anodes, ctx, front_side=qtext)
|
||||||
|
|
||||||
return qtext, atext
|
return qtext, atext
|
||||||
|
|
||||||
|
|
||||||
def apply_custom_filters(
|
def apply_custom_filters(
|
||||||
rendered: TemplateReplacementList, fields: Dict[str, str], front_side: Optional[str]
|
rendered: TemplateReplacementList,
|
||||||
|
ctx: TemplateRenderContext,
|
||||||
|
front_side: Optional[str],
|
||||||
) -> str:
|
) -> str:
|
||||||
"Complete rendering by applying any pending custom filters."
|
"Complete rendering by applying any pending custom filters."
|
||||||
# template already fully rendered?
|
# template already fully rendered?
|
||||||
|
@ -76,11 +139,16 @@ def apply_custom_filters(
|
||||||
field_text = node.current_text
|
field_text = node.current_text
|
||||||
for filter_name in node.filters:
|
for filter_name in node.filters:
|
||||||
field_text = hooks.field_filter(
|
field_text = hooks.field_filter(
|
||||||
field_text, node.field_name, filter_name, fields
|
field_text, node.field_name, filter_name, ctx
|
||||||
)
|
)
|
||||||
# legacy hook - the second and fifth argument are no longer used
|
# legacy hook - the second and fifth argument are no longer used.
|
||||||
field_text = runFilter(
|
field_text = anki.hooks.runFilter(
|
||||||
"fmod_" + filter_name, field_text, "", fields, node.field_name, ""
|
"fmod_" + filter_name,
|
||||||
|
field_text,
|
||||||
|
"",
|
||||||
|
ctx.fields(),
|
||||||
|
node.field_name,
|
||||||
|
"",
|
||||||
)
|
)
|
||||||
|
|
||||||
res += field_text
|
res += field_text
|
||||||
|
|
|
@ -55,42 +55,25 @@ hooks = [
|
||||||
Hook(
|
Hook(
|
||||||
name="tag_added", args=["tag: str"], legacy_hook="newTag", legacy_no_args=True,
|
name="tag_added", args=["tag: str"], legacy_hook="newTag", legacy_no_args=True,
|
||||||
),
|
),
|
||||||
Hook(
|
|
||||||
name="card_will_render",
|
|
||||||
args=[
|
|
||||||
"templates: Tuple[str, str]",
|
|
||||||
"fields: Dict[str, str]",
|
|
||||||
"notetype: Dict[str, Any]",
|
|
||||||
"data: QAData",
|
|
||||||
],
|
|
||||||
return_type="Tuple[str, str]",
|
|
||||||
doc="Can modify the available fields and question/answer templates prior to rendering.",
|
|
||||||
),
|
|
||||||
Hook(
|
|
||||||
name="card_did_render",
|
|
||||||
args=[
|
|
||||||
"text: str",
|
|
||||||
"side: str",
|
|
||||||
"fields: Dict[str, str]",
|
|
||||||
"notetype: Dict[str, Any]",
|
|
||||||
"data: QAData",
|
|
||||||
# the hook in latex.py needs access to the collection and
|
|
||||||
# can't rely on the GUI's mw.col
|
|
||||||
"col: anki.storage._Collection",
|
|
||||||
],
|
|
||||||
return_type="str",
|
|
||||||
legacy_hook="mungeQA",
|
|
||||||
doc="Can modify the resulting text after rendering completes.",
|
|
||||||
),
|
|
||||||
Hook(
|
Hook(
|
||||||
name="field_filter",
|
name="field_filter",
|
||||||
args=[
|
args=[
|
||||||
"field_text: str",
|
"field_text: str",
|
||||||
"field_name: str",
|
"field_name: str",
|
||||||
"filter_name: str",
|
"filter_name: str",
|
||||||
"fields: Dict[str, str]",
|
"ctx: TemplateRenderContext",
|
||||||
],
|
],
|
||||||
return_type="str",
|
return_type="str",
|
||||||
|
doc="""Allows you to define custom {{filters:..}}
|
||||||
|
|
||||||
|
Your add-on can check filter_name to decide whether it should modify
|
||||||
|
field_text or not before returning it.""",
|
||||||
|
),
|
||||||
|
Hook(
|
||||||
|
name="card_did_render",
|
||||||
|
args=["text: Tuple[str, str]", "ctx: TemplateRenderContext",],
|
||||||
|
return_type="Tuple[str, str]",
|
||||||
|
doc="Can modify the resulting text after rendering completes.",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue