mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
add a webview_did_receive_js_message hook
This commit is contained in:
parent
a5db36e208
commit
d54f719558
11 changed files with 131 additions and 38 deletions
|
@ -2133,7 +2133,7 @@ update cards set usn=?, mod=?, did=? where id in """
|
||||||
frm.fields.addItems(fields)
|
frm.fields.addItems(fields)
|
||||||
self._dupesButton = None
|
self._dupesButton = None
|
||||||
# links
|
# links
|
||||||
frm.webView.onBridgeCmd = self.dupeLinkClicked
|
frm.webView.set_bridge_command(self.dupeLinkClicked, "find_dupes")
|
||||||
|
|
||||||
def onFin(code):
|
def onFin(code):
|
||||||
saveGeom(d, "findDupes")
|
saveGeom(d, "findDupes")
|
||||||
|
|
|
@ -216,6 +216,9 @@ class CardLayout(QDialog):
|
||||||
pform.backWeb.stdHtml(
|
pform.backWeb.stdHtml(
|
||||||
self.mw.reviewer.revHtml(), css=["reviewer.css"], js=jsinc
|
self.mw.reviewer.revHtml(), css=["reviewer.css"], js=jsinc
|
||||||
)
|
)
|
||||||
|
# specify a context for add-ons
|
||||||
|
pform.frontWeb.set_bridge_command(lambda msg: None, "card_layout")
|
||||||
|
pform.backWeb.set_bridge_command(lambda msg: None, "card_layout")
|
||||||
|
|
||||||
def updateMainArea(self):
|
def updateMainArea(self):
|
||||||
if self._isCloze():
|
if self._isCloze():
|
||||||
|
|
|
@ -8,25 +8,25 @@ import aqt
|
||||||
from anki.errors import DeckRenameError
|
from anki.errors import DeckRenameError
|
||||||
from anki.lang import _, ngettext
|
from anki.lang import _, ngettext
|
||||||
from anki.utils import fmtTimeSpan, ids2str
|
from anki.utils import fmtTimeSpan, ids2str
|
||||||
from aqt import gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.sound import av_player
|
from aqt.sound import av_player
|
||||||
|
from aqt.toolbar import BottomBar
|
||||||
from aqt.utils import askUser, getOnlyText, openHelp, openLink, shortcut, showWarning
|
from aqt.utils import askUser, getOnlyText, openHelp, openLink, shortcut, showWarning
|
||||||
|
|
||||||
|
|
||||||
class DeckBrowser:
|
class DeckBrowser:
|
||||||
_dueTree: Any
|
_dueTree: Any
|
||||||
|
|
||||||
def __init__(self, mw):
|
def __init__(self, mw: AnkiQt) -> None:
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
self.web = mw.web
|
self.web = mw.web
|
||||||
self.bottom = aqt.toolbar.BottomBar(mw, mw.bottomWeb)
|
self.bottom = BottomBar(mw, mw.bottomWeb)
|
||||||
self.scrollPos = QPoint(0, 0)
|
self.scrollPos = QPoint(0, 0)
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
av_player.stop_and_clear_queue()
|
av_player.stop_and_clear_queue()
|
||||||
self.web.resetHandlers()
|
self.web.set_bridge_command(self._linkHandler, "deck_browser")
|
||||||
self.web.onBridgeCmd = self._linkHandler
|
|
||||||
self._renderPage()
|
self._renderPage()
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
|
@ -330,7 +330,7 @@ where id > ?""",
|
||||||
b
|
b
|
||||||
)
|
)
|
||||||
self.bottom.draw(buf)
|
self.bottom.draw(buf)
|
||||||
self.bottom.web.onBridgeCmd = self._linkHandler
|
self.bottom.web.set_bridge_command(self._linkHandler, "deck_browser_bottom_bar")
|
||||||
|
|
||||||
def _onShared(self):
|
def _onShared(self):
|
||||||
openLink(aqt.appShared + "decks/")
|
openLink(aqt.appShared + "decks/")
|
||||||
|
|
|
@ -96,7 +96,7 @@ class Editor:
|
||||||
self.web = EditorWebView(self.widget, self)
|
self.web = EditorWebView(self.widget, self)
|
||||||
self.web.title = "editor"
|
self.web.title = "editor"
|
||||||
self.web.allowDrops = True
|
self.web.allowDrops = True
|
||||||
self.web.onBridgeCmd = self.onBridgeCmd
|
self.web.set_bridge_command(self.onBridgeCmd, "editor")
|
||||||
self.outerLayout.addWidget(self.web, 1)
|
self.outerLayout.addWidget(self.web, 1)
|
||||||
|
|
||||||
righttopbtns: List[str] = [
|
righttopbtns: List[str] = [
|
||||||
|
|
|
@ -913,6 +913,50 @@ class _UndoStateDidChangeHook:
|
||||||
undo_state_did_change = _UndoStateDidChangeHook()
|
undo_state_did_change = _UndoStateDidChangeHook()
|
||||||
|
|
||||||
|
|
||||||
|
class _WebviewDidReceiveJsMessageFilter:
|
||||||
|
"""Used to handle pycmd() messages sent from Javascript.
|
||||||
|
|
||||||
|
Message is the string passed to pycmd(). Context is what was
|
||||||
|
passed to set_bridge_command(), such as 'editor' or 'reviewer'.
|
||||||
|
|
||||||
|
For messages you don't want to handle, return handled unchanged.
|
||||||
|
|
||||||
|
If you handle a message and don't want it passed to the original
|
||||||
|
bridge command handler, return (True, None).
|
||||||
|
|
||||||
|
If you want to pass a value to pycmd's result callback, you can
|
||||||
|
return it with (True, some_value)."""
|
||||||
|
|
||||||
|
_hooks: List[Callable[[Tuple[bool, Any], str, str], Tuple[bool, Any]]] = []
|
||||||
|
|
||||||
|
def append(
|
||||||
|
self, cb: Callable[[Tuple[bool, Any], str, str], Tuple[bool, Any]]
|
||||||
|
) -> None:
|
||||||
|
"""(handled: Tuple[bool, Any], message: str, context: str)"""
|
||||||
|
self._hooks.append(cb)
|
||||||
|
|
||||||
|
def remove(
|
||||||
|
self, cb: Callable[[Tuple[bool, Any], str, str], Tuple[bool, Any]]
|
||||||
|
) -> None:
|
||||||
|
if cb in self._hooks:
|
||||||
|
self._hooks.remove(cb)
|
||||||
|
|
||||||
|
def __call__(
|
||||||
|
self, handled: Tuple[bool, Any], message: str, context: str
|
||||||
|
) -> Tuple[bool, Any]:
|
||||||
|
for filter in self._hooks:
|
||||||
|
try:
|
||||||
|
handled = filter(handled, message, context)
|
||||||
|
except:
|
||||||
|
# if the hook fails, remove it
|
||||||
|
self._hooks.remove(filter)
|
||||||
|
raise
|
||||||
|
return handled
|
||||||
|
|
||||||
|
|
||||||
|
webview_did_receive_js_message = _WebviewDidReceiveJsMessageFilter()
|
||||||
|
|
||||||
|
|
||||||
class _WebviewWillShowContextMenuHook:
|
class _WebviewWillShowContextMenuHook:
|
||||||
_hooks: List[Callable[["aqt.webview.AnkiWebView", QMenu], None]] = []
|
_hooks: List[Callable[["aqt.webview.AnkiWebView", QMenu], None]] = []
|
||||||
|
|
||||||
|
|
|
@ -643,8 +643,9 @@ from the profile screen."
|
||||||
if self.resetModal:
|
if self.resetModal:
|
||||||
# we don't have to change the webview, as we have a covering window
|
# we don't have to change the webview, as we have a covering window
|
||||||
return
|
return
|
||||||
self.web.resetHandlers()
|
self.web.set_bridge_command(
|
||||||
self.web.onBridgeCmd = lambda url: self.delayedMaybeReset()
|
lambda url: self.delayedMaybeReset(), "reset_required"
|
||||||
|
)
|
||||||
i = _("Waiting for editing to finish.")
|
i = _("Waiting for editing to finish.")
|
||||||
b = self.button("refresh", _("Resume Now"), id="resume")
|
b = self.button("refresh", _("Resume Now"), id="resume")
|
||||||
self.web.stdHtml(
|
self.web.stdHtml(
|
||||||
|
|
|
@ -2,24 +2,26 @@
|
||||||
# Copyright: Ankitects Pty Ltd and contributors
|
# Copyright: Ankitects Pty Ltd and contributors
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import aqt
|
import aqt
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from aqt.sound import av_player
|
from aqt.sound import av_player
|
||||||
|
from aqt.toolbar import BottomBar
|
||||||
from aqt.utils import askUserDialog, openLink, shortcut, tooltip
|
from aqt.utils import askUserDialog, openLink, shortcut, tooltip
|
||||||
|
|
||||||
|
|
||||||
class Overview:
|
class Overview:
|
||||||
"Deck overview."
|
"Deck overview."
|
||||||
|
|
||||||
def __init__(self, mw):
|
def __init__(self, mw: aqt.AnkiQt) -> None:
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
self.web = mw.web
|
self.web = mw.web
|
||||||
self.bottom = aqt.toolbar.BottomBar(mw, mw.bottomWeb)
|
self.bottom = BottomBar(mw, mw.bottomWeb)
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
av_player.stop_and_clear_queue()
|
av_player.stop_and_clear_queue()
|
||||||
self.web.resetHandlers()
|
self.web.set_bridge_command(self._linkHandler, "overview")
|
||||||
self.web.onBridgeCmd = self._linkHandler
|
|
||||||
self.mw.setStateShortcuts(self._shortcutKeys())
|
self.mw.setStateShortcuts(self._shortcutKeys())
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
|
@ -235,7 +237,7 @@ to their original deck."""
|
||||||
b
|
b
|
||||||
)
|
)
|
||||||
self.bottom.draw(buf)
|
self.bottom.draw(buf)
|
||||||
self.bottom.web.onBridgeCmd = self._linkHandler
|
self.bottom.web.set_bridge_command(self._linkHandler, "overview_bottom")
|
||||||
|
|
||||||
# Studying more
|
# Studying more
|
||||||
######################################################################
|
######################################################################
|
||||||
|
|
|
@ -10,7 +10,6 @@ import re
|
||||||
import unicodedata as ucd
|
import unicodedata as ucd
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
import aqt
|
|
||||||
from anki import hooks
|
from anki import hooks
|
||||||
from anki.cards import Card
|
from anki.cards import Card
|
||||||
from anki.lang import _, ngettext
|
from anki.lang import _, ngettext
|
||||||
|
@ -19,6 +18,7 @@ from anki.utils import bodyClass, stripHTML
|
||||||
from aqt import AnkiQt, gui_hooks
|
from aqt import AnkiQt, gui_hooks
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
from aqt.sound import av_player, getAudio
|
from aqt.sound import av_player, getAudio
|
||||||
|
from aqt.toolbar import BottomBar
|
||||||
from aqt.utils import (
|
from aqt.utils import (
|
||||||
askUserDialog,
|
askUserDialog,
|
||||||
downArrow,
|
downArrow,
|
||||||
|
@ -42,15 +42,14 @@ class Reviewer:
|
||||||
self._current_side_audio: Optional[List[AVTag]] = None
|
self._current_side_audio: Optional[List[AVTag]] = None
|
||||||
self.typeCorrect = None # web init happens before this is set
|
self.typeCorrect = None # web init happens before this is set
|
||||||
self.state: Optional[str] = None
|
self.state: Optional[str] = None
|
||||||
self.bottom = aqt.toolbar.BottomBar(mw, mw.bottomWeb)
|
self.bottom = BottomBar(mw, mw.bottomWeb)
|
||||||
hooks.card_did_leech.append(self.onLeech)
|
hooks.card_did_leech.append(self.onLeech)
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
self.mw.col.reset()
|
self.mw.col.reset()
|
||||||
self.web.resetHandlers()
|
|
||||||
self.mw.setStateShortcuts(self._shortcutKeys())
|
self.mw.setStateShortcuts(self._shortcutKeys())
|
||||||
self.web.onBridgeCmd = self._linkHandler
|
self.web.set_bridge_command(self._linkHandler, "reviewer")
|
||||||
self.bottom.web.onBridgeCmd = self._linkHandler
|
self.bottom.web.set_bridge_command(self._linkHandler, "reviewer_bottom")
|
||||||
self._reps = None
|
self._reps = None
|
||||||
self.nextCard()
|
self.nextCard()
|
||||||
|
|
||||||
|
|
|
@ -2,12 +2,16 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import aqt
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from aqt.qt import *
|
from aqt.qt import *
|
||||||
|
from aqt.webview import AnkiWebView
|
||||||
|
|
||||||
|
|
||||||
class Toolbar:
|
class Toolbar:
|
||||||
def __init__(self, mw, web):
|
def __init__(self, mw: aqt.AnkiQt, web: AnkiWebView) -> None:
|
||||||
self.mw = mw
|
self.mw = mw
|
||||||
self.web = web
|
self.web = web
|
||||||
self.link_handlers = {
|
self.link_handlers = {
|
||||||
|
@ -22,7 +26,7 @@ class Toolbar:
|
||||||
self.web.requiresCol = False
|
self.web.requiresCol = False
|
||||||
|
|
||||||
def draw(self):
|
def draw(self):
|
||||||
self.web.onBridgeCmd = self._linkHandler
|
self.web.set_bridge_command(self._linkHandler, "top_toolbar")
|
||||||
self.web.stdHtml(self._body % self._centerLinks(), css=["toolbar.css"])
|
self.web.stdHtml(self._body % self._centerLinks(), css=["toolbar.css"])
|
||||||
self.web.adjustHeightToFit()
|
self.web.adjustHeightToFit()
|
||||||
|
|
||||||
|
@ -107,7 +111,8 @@ class BottomBar(Toolbar):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def draw(self, buf):
|
def draw(self, buf):
|
||||||
self.web.onBridgeCmd = self._linkHandler
|
# note: some screens may override this
|
||||||
|
self.web.set_bridge_command(self._linkHandler, "bottom_toolbar")
|
||||||
self.web.stdHtml(
|
self.web.stdHtml(
|
||||||
self._centerBody % buf, css=["toolbar.css", "toolbar-bottom.css"]
|
self._centerBody % buf, css=["toolbar.css", "toolbar-bottom.css"]
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
import sys
|
import sys
|
||||||
|
from typing import Any, List, Optional, Tuple
|
||||||
|
|
||||||
from anki.lang import _
|
from anki.lang import _
|
||||||
from anki.utils import isLin, isMac, isWin
|
from anki.utils import isLin, isMac, isWin
|
||||||
|
@ -99,18 +100,21 @@ class AnkiWebPage(QWebEnginePage): # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class AnkiWebView(QWebEngineView): # type: ignore
|
class AnkiWebView(QWebEngineView): # type: ignore
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent: Optional[QWidget] = None) -> None:
|
||||||
QWebEngineView.__init__(self, parent=parent)
|
QWebEngineView.__init__(self, parent=parent) # type: ignore
|
||||||
self.title = "default"
|
self.title = "default"
|
||||||
self._page = AnkiWebPage(self._onBridgeCmd)
|
self._page = AnkiWebPage(self._onBridgeCmd)
|
||||||
self._page.setBackgroundColor(self._getWindowColor()) # reduce flicker
|
self._page.setBackgroundColor(self._getWindowColor()) # reduce flicker
|
||||||
|
|
||||||
|
# in new code, use .set_bridge_command() instead of setting this directly
|
||||||
|
self.onBridgeCmd: Callable[[str], Any] = self.defaultOnBridgeCmd
|
||||||
|
|
||||||
self._domDone = True
|
self._domDone = True
|
||||||
self._pendingActions = []
|
self._pendingActions: List[Tuple[str, List[Any]]] = []
|
||||||
self.requiresCol = True
|
self.requiresCol = True
|
||||||
self.setPage(self._page)
|
self.setPage(self._page)
|
||||||
|
|
||||||
self._page.profile().setHttpCacheType(QWebEngineProfile.NoCache)
|
self._page.profile().setHttpCacheType(QWebEngineProfile.NoCache) # type: ignore
|
||||||
self.resetHandlers()
|
self.resetHandlers()
|
||||||
self.allowDrops = False
|
self.allowDrops = False
|
||||||
self._filterSet = False
|
self._filterSet = False
|
||||||
|
@ -119,7 +123,7 @@ class AnkiWebView(QWebEngineView): # type: ignore
|
||||||
self,
|
self,
|
||||||
context=Qt.WidgetWithChildrenShortcut,
|
context=Qt.WidgetWithChildrenShortcut,
|
||||||
activated=self.onEsc,
|
activated=self.onEsc,
|
||||||
)
|
) # type: ignore
|
||||||
if isMac:
|
if isMac:
|
||||||
for key, fn in [
|
for key, fn in [
|
||||||
(QKeySequence.Copy, self.onCopy),
|
(QKeySequence.Copy, self.onCopy),
|
||||||
|
@ -129,13 +133,13 @@ class AnkiWebView(QWebEngineView): # type: ignore
|
||||||
]:
|
]:
|
||||||
QShortcut(
|
QShortcut(
|
||||||
key, self, context=Qt.WidgetWithChildrenShortcut, activated=fn
|
key, self, context=Qt.WidgetWithChildrenShortcut, activated=fn
|
||||||
)
|
) # type: ignore
|
||||||
QShortcut(
|
QShortcut(
|
||||||
QKeySequence("ctrl+shift+v"),
|
QKeySequence("ctrl+shift+v"),
|
||||||
self,
|
self,
|
||||||
context=Qt.WidgetWithChildrenShortcut,
|
context=Qt.WidgetWithChildrenShortcut,
|
||||||
activated=self.onPaste,
|
activated=self.onPaste,
|
||||||
)
|
) # type: ignore
|
||||||
|
|
||||||
def eventFilter(self, obj, evt):
|
def eventFilter(self, obj, evt):
|
||||||
# disable pinch to zoom gesture
|
# disable pinch to zoom gesture
|
||||||
|
@ -391,7 +395,7 @@ body {{ zoom: {}; background: {}; {} }}
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _onBridgeCmd(self, cmd):
|
def _onBridgeCmd(self, cmd: str) -> Any:
|
||||||
if self._shouldIgnoreWebEvent():
|
if self._shouldIgnoreWebEvent():
|
||||||
print("ignored late bridge cmd", cmd)
|
print("ignored late bridge cmd", cmd)
|
||||||
return
|
return
|
||||||
|
@ -403,14 +407,22 @@ body {{ zoom: {}; background: {}; {} }}
|
||||||
if cmd == "domDone":
|
if cmd == "domDone":
|
||||||
self._domDone = True
|
self._domDone = True
|
||||||
self._maybeRunActions()
|
self._maybeRunActions()
|
||||||
|
else:
|
||||||
|
handled, result = gui_hooks.webview_did_receive_js_message(
|
||||||
|
(False, None), cmd, self._bridge_command_name
|
||||||
|
)
|
||||||
|
if handled:
|
||||||
|
return result
|
||||||
else:
|
else:
|
||||||
return self.onBridgeCmd(cmd)
|
return self.onBridgeCmd(cmd)
|
||||||
|
|
||||||
def defaultOnBridgeCmd(self, cmd):
|
def defaultOnBridgeCmd(self, cmd: str) -> Any:
|
||||||
print("unhandled bridge cmd:", cmd)
|
print("unhandled bridge cmd:", cmd)
|
||||||
|
|
||||||
|
# legacy
|
||||||
def resetHandlers(self):
|
def resetHandlers(self):
|
||||||
self.onBridgeCmd = self.defaultOnBridgeCmd
|
self.onBridgeCmd = self.defaultOnBridgeCmd
|
||||||
|
self._bridge_command_name = "unknown"
|
||||||
|
|
||||||
def adjustHeightToFit(self):
|
def adjustHeightToFit(self):
|
||||||
self.evalWithCallback("$(document.body).height()", self._onHeight)
|
self.evalWithCallback("$(document.body).height()", self._onHeight)
|
||||||
|
@ -429,3 +441,11 @@ body {{ zoom: {}; background: {}; {} }}
|
||||||
|
|
||||||
height = math.ceil(qvar * scaleFactor)
|
height = math.ceil(qvar * scaleFactor)
|
||||||
self.setFixedHeight(height)
|
self.setFixedHeight(height)
|
||||||
|
|
||||||
|
def set_bridge_command(self, func: Callable[[str], Any], context: str) -> None:
|
||||||
|
"""Set a handler for pycmd() messages received from Javascript.
|
||||||
|
|
||||||
|
Context is a human readable name that is provided to the
|
||||||
|
webview_did_receive_js_message hook."""
|
||||||
|
self.onBridgeCmd = func
|
||||||
|
self._bridge_command_name = context
|
||||||
|
|
|
@ -65,11 +65,6 @@ hooks = [
|
||||||
args=["browser: aqt.browser.Browser"],
|
args=["browser: aqt.browser.Browser"],
|
||||||
legacy_hook="browser.rowChanged",
|
legacy_hook="browser.rowChanged",
|
||||||
),
|
),
|
||||||
Hook(
|
|
||||||
name="webview_will_show_context_menu",
|
|
||||||
args=["webview: aqt.webview.AnkiWebView", "menu: QMenu"],
|
|
||||||
legacy_hook="AnkiWebView.contextMenuEvent",
|
|
||||||
),
|
|
||||||
# States
|
# States
|
||||||
###################
|
###################
|
||||||
Hook(
|
Hook(
|
||||||
|
@ -98,6 +93,30 @@ hooks = [
|
||||||
legacy_hook="reset",
|
legacy_hook="reset",
|
||||||
doc="Called when the interface needs to be redisplayed after non-trivial changes have been made.",
|
doc="Called when the interface needs to be redisplayed after non-trivial changes have been made.",
|
||||||
),
|
),
|
||||||
|
# Webview
|
||||||
|
###################
|
||||||
|
Hook(
|
||||||
|
name="webview_did_receive_js_message",
|
||||||
|
args=["handled: Tuple[bool, Any]", "message: str", "context: str"],
|
||||||
|
return_type="Tuple[bool, Any]",
|
||||||
|
doc="""Used to handle pycmd() messages sent from Javascript.
|
||||||
|
|
||||||
|
Message is the string passed to pycmd(). Context is what was
|
||||||
|
passed to set_bridge_command(), such as 'editor' or 'reviewer'.
|
||||||
|
|
||||||
|
For messages you don't want to handle, return handled unchanged.
|
||||||
|
|
||||||
|
If you handle a message and don't want it passed to the original
|
||||||
|
bridge command handler, return (True, None).
|
||||||
|
|
||||||
|
If you want to pass a value to pycmd's result callback, you can
|
||||||
|
return it with (True, some_value).""",
|
||||||
|
),
|
||||||
|
Hook(
|
||||||
|
name="webview_will_show_context_menu",
|
||||||
|
args=["webview: aqt.webview.AnkiWebView", "menu: QMenu"],
|
||||||
|
legacy_hook="AnkiWebView.contextMenuEvent",
|
||||||
|
),
|
||||||
# Main
|
# Main
|
||||||
###################
|
###################
|
||||||
Hook(name="profile_did_open", legacy_hook="profileLoaded"),
|
Hook(name="profile_did_open", legacy_hook="profileLoaded"),
|
||||||
|
|
Loading…
Reference in a new issue