Merge pull request #445 from glutanimate/new-html-view-hooks-2
Allow add-on authors to easily inject their own content into Anki's web views – take 3
This commit is contained in:
commit
990a6c394b
@ -219,5 +219,5 @@ suggestions, bug reports and donations."
|
||||
abt.label.setMinimumWidth(800)
|
||||
abt.label.setMinimumHeight(600)
|
||||
dialog.show()
|
||||
abt.label.stdHtml(abouttext, js=" ")
|
||||
abt.label.stdHtml(abouttext, js=[])
|
||||
return dialog
|
||||
|
@ -1384,27 +1384,20 @@ by clicking on one on the left."""
|
||||
info, cs = self._cardInfoData()
|
||||
reps = self._revlogData(cs)
|
||||
|
||||
class CardInfoDialog(QDialog):
|
||||
silentlyClose = True
|
||||
|
||||
def reject(self):
|
||||
saveGeom(self, "revlog")
|
||||
return QDialog.reject(self)
|
||||
|
||||
d = CardInfoDialog(self)
|
||||
card_info_dialog = CardInfoDialog(self)
|
||||
l = QVBoxLayout()
|
||||
l.setContentsMargins(0, 0, 0, 0)
|
||||
w = AnkiWebView()
|
||||
w = AnkiWebView(title="browser card info")
|
||||
l.addWidget(w)
|
||||
w.stdHtml(info + "<p>" + reps)
|
||||
w.stdHtml(info + "<p>" + reps, context=card_info_dialog)
|
||||
bb = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
l.addWidget(bb)
|
||||
bb.rejected.connect(d.reject)
|
||||
d.setLayout(l)
|
||||
d.setWindowModality(Qt.WindowModal)
|
||||
d.resize(500, 400)
|
||||
restoreGeom(d, "revlog")
|
||||
d.show()
|
||||
bb.rejected.connect(card_info_dialog.reject)
|
||||
card_info_dialog.setLayout(l)
|
||||
card_info_dialog.setWindowModality(Qt.WindowModal)
|
||||
card_info_dialog.resize(500, 400)
|
||||
restoreGeom(card_info_dialog, "revlog", CardInfoDialog)
|
||||
card_info_dialog.show()
|
||||
|
||||
def _cardInfoData(self):
|
||||
from anki.stats import CardStats
|
||||
@ -1563,7 +1556,7 @@ where id in %s"""
|
||||
self._previewWindow.silentlyClose = True
|
||||
vbox = QVBoxLayout()
|
||||
vbox.setContentsMargins(0, 0, 0, 0)
|
||||
self._previewWeb = AnkiWebView()
|
||||
self._previewWeb = AnkiWebView(title="previewer")
|
||||
vbox.addWidget(self._previewWeb)
|
||||
bbox = QDialogButtonBox()
|
||||
|
||||
@ -1658,12 +1651,15 @@ where id in %s"""
|
||||
"mathjax/MathJax.js",
|
||||
"reviewer.js",
|
||||
]
|
||||
web_context = PreviewDialog(dialog=self._previewWindow, browser=self)
|
||||
self._previewWeb.stdHtml(
|
||||
self.mw.reviewer.revHtml(), css=["reviewer.css"], js=jsinc
|
||||
self.mw.reviewer.revHtml(),
|
||||
css=["reviewer.css"],
|
||||
js=jsinc,
|
||||
context=web_context,
|
||||
)
|
||||
self._previewWeb.set_bridge_command(
|
||||
self._on_preview_bridge_cmd,
|
||||
PreviewDialog(dialog=self._previewWindow, browser=self),
|
||||
self._on_preview_bridge_cmd, web_context,
|
||||
)
|
||||
|
||||
def _on_preview_bridge_cmd(self, cmd: str) -> Any:
|
||||
@ -2161,10 +2157,10 @@ update cards set usn=?, mod=?, did=? where id in """
|
||||
frm.fields.addItems(fields)
|
||||
self._dupesButton = None
|
||||
# links
|
||||
frm.webView.set_bridge_command(
|
||||
self.dupeLinkClicked, FindDupesDialog(dialog=d, browser=self)
|
||||
)
|
||||
frm.webView.stdHtml("")
|
||||
frm.webView.title = "find duplicates"
|
||||
web_context = FindDupesDialog(dialog=d, browser=self)
|
||||
frm.webView.set_bridge_command(self.dupeLinkClicked, web_context)
|
||||
frm.webView.stdHtml("", context=web_context)
|
||||
|
||||
def onFin(code):
|
||||
saveGeom(d, "findDupes")
|
||||
@ -2173,13 +2169,15 @@ update cards set usn=?, mod=?, did=? where id in """
|
||||
|
||||
def onClick():
|
||||
field = fields[frm.fields.currentIndex()]
|
||||
self.duplicatesReport(frm.webView, field, frm.search.text(), frm)
|
||||
self.duplicatesReport(
|
||||
frm.webView, field, frm.search.text(), frm, web_context
|
||||
)
|
||||
|
||||
search = frm.buttonBox.addButton(_("Search"), QDialogButtonBox.ActionRole)
|
||||
search.clicked.connect(onClick)
|
||||
d.show()
|
||||
|
||||
def duplicatesReport(self, web, fname, search, frm):
|
||||
def duplicatesReport(self, web, fname, search, frm, web_context):
|
||||
self.mw.progress.start()
|
||||
res = self.mw.col.findDupes(fname, search)
|
||||
if not self._dupesButton:
|
||||
@ -2204,7 +2202,7 @@ update cards set usn=?, mod=?, did=? where id in """
|
||||
)
|
||||
)
|
||||
t += "</ol>"
|
||||
web.stdHtml(t)
|
||||
web.stdHtml(t, context=web_context)
|
||||
self.mw.progress.finish()
|
||||
|
||||
def _onTagDupes(self, res):
|
||||
@ -2478,3 +2476,19 @@ Are you sure you want to continue?"""
|
||||
|
||||
def onHelp(self):
|
||||
openHelp("browsermisc")
|
||||
|
||||
|
||||
# Card Info Dialog
|
||||
######################################################################
|
||||
|
||||
|
||||
class CardInfoDialog(QDialog):
|
||||
silentlyClose = True
|
||||
|
||||
def __init__(self, browser: Browser, *args, **kwargs):
|
||||
super().__init__(browser, *args, **kwargs)
|
||||
self.browser = browser
|
||||
|
||||
def reject(self):
|
||||
saveGeom(self, "revlog")
|
||||
return QDialog.reject(self)
|
||||
|
@ -203,9 +203,9 @@ class CardLayout(QDialog):
|
||||
|
||||
def setupWebviews(self):
|
||||
pform = self.pform
|
||||
pform.frontWeb = AnkiWebView()
|
||||
pform.frontWeb = AnkiWebView(title="card layout front")
|
||||
pform.frontPrevBox.addWidget(pform.frontWeb)
|
||||
pform.backWeb = AnkiWebView()
|
||||
pform.backWeb = AnkiWebView(title="card layout back")
|
||||
pform.backPrevBox.addWidget(pform.backWeb)
|
||||
jsinc = [
|
||||
"jquery.js",
|
||||
@ -215,10 +215,10 @@ class CardLayout(QDialog):
|
||||
"reviewer.js",
|
||||
]
|
||||
pform.frontWeb.stdHtml(
|
||||
self.mw.reviewer.revHtml(), css=["reviewer.css"], js=jsinc
|
||||
self.mw.reviewer.revHtml(), css=["reviewer.css"], js=jsinc, context=self,
|
||||
)
|
||||
pform.backWeb.stdHtml(
|
||||
self.mw.reviewer.revHtml(), css=["reviewer.css"], js=jsinc
|
||||
self.mw.reviewer.revHtml(), css=["reviewer.css"], js=jsinc, context=self,
|
||||
)
|
||||
pform.frontWeb.set_bridge_command(self._on_bridge_cmd, self)
|
||||
pform.backWeb.set_bridge_command(self._on_bridge_cmd, self)
|
||||
|
@ -109,6 +109,7 @@ class DeckBrowser:
|
||||
self._body % dict(tree=tree, stats=stats, countwarn=self._countWarn()),
|
||||
css=["deckbrowser.css"],
|
||||
js=["jquery.js", "jquery-ui.js", "deckbrowser.js"],
|
||||
context=self,
|
||||
)
|
||||
self.web.key = "deckBrowser"
|
||||
self._drawButtons()
|
||||
@ -340,9 +341,10 @@ where id > ?""",
|
||||
<button title='%s' onclick='pycmd(\"%s\");'>%s</button>""" % tuple(
|
||||
b
|
||||
)
|
||||
self.bottom.draw(buf)
|
||||
self.bottom.web.set_bridge_command(
|
||||
self._linkHandler, DeckBrowserBottomBar(self)
|
||||
self.bottom.draw(
|
||||
buf=buf,
|
||||
link_handler=self._linkHandler,
|
||||
web_context=DeckBrowserBottomBar(self),
|
||||
)
|
||||
|
||||
def _onShared(self):
|
||||
|
@ -95,7 +95,6 @@ class Editor:
|
||||
|
||||
def setupWeb(self) -> None:
|
||||
self.web = EditorWebView(self.widget, self)
|
||||
self.web.title = "editor"
|
||||
self.web.allowDrops = True
|
||||
self.web.set_bridge_command(self.onBridgeCmd, self)
|
||||
self.outerLayout.addWidget(self.web, 1)
|
||||
@ -167,6 +166,7 @@ class Editor:
|
||||
_html % (bgcol, bgcol, topbuts, _("Show Duplicates")),
|
||||
css=["editor.css"],
|
||||
js=["jquery.js", "editor.js"],
|
||||
context=self,
|
||||
)
|
||||
|
||||
# Top buttons
|
||||
@ -937,7 +937,7 @@ to a cloze type first, via Edit>Change Note Type."""
|
||||
|
||||
class EditorWebView(AnkiWebView):
|
||||
def __init__(self, parent, editor):
|
||||
AnkiWebView.__init__(self)
|
||||
AnkiWebView.__init__(self, title="editor")
|
||||
self.editor = editor
|
||||
self.strip = self.editor.mw.pm.profile["stripHTML"]
|
||||
self.setAcceptDrops(True)
|
||||
|
@ -7,7 +7,7 @@ See pylib/anki/hooks.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, Dict, List, Tuple
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
import anki
|
||||
import aqt
|
||||
@ -1129,6 +1129,67 @@ class _WebviewDidReceiveJsMessageFilter:
|
||||
webview_did_receive_js_message = _WebviewDidReceiveJsMessageFilter()
|
||||
|
||||
|
||||
class _WebviewWillSetContentHook:
|
||||
"""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:
|
||||
|
||||
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")
|
||||
|
||||
web_content.head += "<script>console.log('my-addon')</script>"
|
||||
web_content.body += "<div id='my-addon'></div>"
|
||||
"""
|
||||
|
||||
_hooks: List[Callable[["aqt.webview.WebContent", Optional[Any]], None]] = []
|
||||
|
||||
def append(
|
||||
self, cb: Callable[["aqt.webview.WebContent", Optional[Any]], None]
|
||||
) -> None:
|
||||
"""(web_content: aqt.webview.WebContent, context: Optional[Any])"""
|
||||
self._hooks.append(cb)
|
||||
|
||||
def remove(
|
||||
self, cb: Callable[["aqt.webview.WebContent", Optional[Any]], None]
|
||||
) -> None:
|
||||
if cb in self._hooks:
|
||||
self._hooks.remove(cb)
|
||||
|
||||
def __call__(
|
||||
self, web_content: aqt.webview.WebContent, context: Optional[Any]
|
||||
) -> None:
|
||||
for hook in self._hooks:
|
||||
try:
|
||||
hook(web_content, context)
|
||||
except:
|
||||
# if the hook fails, remove it
|
||||
self._hooks.remove(hook)
|
||||
raise
|
||||
|
||||
|
||||
webview_will_set_content = _WebviewWillSetContentHook()
|
||||
|
||||
|
||||
class _WebviewWillShowContextMenuHook:
|
||||
_hooks: List[Callable[["aqt.webview.AnkiWebView", QMenu], None]] = []
|
||||
|
||||
|
@ -663,9 +663,8 @@ from the profile screen."
|
||||
if self.resetModal:
|
||||
# we don't have to change the webview, as we have a covering window
|
||||
return
|
||||
self.web.set_bridge_command(
|
||||
lambda url: self.delayedMaybeReset(), ResetRequired(self)
|
||||
)
|
||||
web_context = ResetRequired(self)
|
||||
self.web.set_bridge_command(lambda url: self.delayedMaybeReset(), web_context)
|
||||
i = _("Waiting for editing to finish.")
|
||||
b = self.button("refresh", _("Resume Now"), id="resume")
|
||||
self.web.stdHtml(
|
||||
@ -676,7 +675,8 @@ from the profile screen."
|
||||
%s</div></div></center>
|
||||
<script>$('#resume').focus()</script>
|
||||
"""
|
||||
% (i, b)
|
||||
% (i, b),
|
||||
context=web_context,
|
||||
)
|
||||
self.bottomWeb.hide()
|
||||
self.web.setFocus()
|
||||
@ -717,19 +717,16 @@ title="%s" %s>%s</button>""" % (
|
||||
self.form = aqt.forms.main.Ui_MainWindow()
|
||||
self.form.setupUi(self)
|
||||
# toolbar
|
||||
tweb = self.toolbarWeb = aqt.webview.AnkiWebView()
|
||||
tweb.title = "top toolbar"
|
||||
tweb = self.toolbarWeb = aqt.webview.AnkiWebView(title="top toolbar")
|
||||
tweb.setFocusPolicy(Qt.WheelFocus)
|
||||
self.toolbar = aqt.toolbar.Toolbar(self, tweb)
|
||||
self.toolbar.draw()
|
||||
# main area
|
||||
self.web = aqt.webview.AnkiWebView()
|
||||
self.web.title = "main webview"
|
||||
self.web = aqt.webview.AnkiWebView(title="main webview")
|
||||
self.web.setFocusPolicy(Qt.WheelFocus)
|
||||
self.web.setMinimumWidth(400)
|
||||
# bottom area
|
||||
sweb = self.bottomWeb = aqt.webview.AnkiWebView()
|
||||
sweb.title = "bottom toolbar"
|
||||
sweb = self.bottomWeb = aqt.webview.AnkiWebView(title="bottom toolbar")
|
||||
sweb.setFocusPolicy(Qt.WheelFocus)
|
||||
# add in a layout
|
||||
self.mainLayout = QVBoxLayout()
|
||||
|
@ -151,6 +151,7 @@ class Overview:
|
||||
),
|
||||
css=["overview.css"],
|
||||
js=["jquery.js", "overview.js"],
|
||||
context=self,
|
||||
)
|
||||
|
||||
def _desc(self, deck):
|
||||
@ -243,8 +244,9 @@ to their original deck."""
|
||||
<button title="%s" onclick='pycmd("%s")'>%s</button>""" % tuple(
|
||||
b
|
||||
)
|
||||
self.bottom.draw(buf)
|
||||
self.bottom.web.set_bridge_command(self._linkHandler, OverviewBottomBar(self))
|
||||
self.bottom.draw(
|
||||
buf=buf, link_handler=self._linkHandler, web_context=OverviewBottomBar(self)
|
||||
)
|
||||
|
||||
# Studying more
|
||||
######################################################################
|
||||
|
@ -153,6 +153,7 @@ class Reviewer:
|
||||
"mathjax/MathJax.js",
|
||||
"reviewer.js",
|
||||
],
|
||||
context=self,
|
||||
)
|
||||
# show answer / ease buttons
|
||||
self.bottom.web.show()
|
||||
@ -160,6 +161,7 @@ class Reviewer:
|
||||
self._bottomHTML(),
|
||||
css=["toolbar-bottom.css", "reviewer-bottom.css"],
|
||||
js=["jquery.js", "reviewer-bottom.js"],
|
||||
context=ReviewerBottomBar(self),
|
||||
)
|
||||
|
||||
# Showing the question
|
||||
|
@ -95,7 +95,10 @@ class DeckStats(QDialog):
|
||||
stats = self.mw.col.stats()
|
||||
stats.wholeCollection = self.wholeCollection
|
||||
self.report = stats.report(type=self.period)
|
||||
self.form.web.title = "deck stats"
|
||||
self.form.web.stdHtml(
|
||||
"<html><body>" + self.report + "</body></html>", js=["jquery.js", "plot.js"]
|
||||
"<html><body>" + self.report + "</body></html>",
|
||||
js=["jquery.js", "plot.js"],
|
||||
context=self,
|
||||
)
|
||||
self.mw.progress.finish()
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Optional
|
||||
|
||||
import aqt
|
||||
from anki.lang import _
|
||||
from aqt.qt import *
|
||||
@ -37,9 +39,18 @@ class Toolbar:
|
||||
self.web.setFixedHeight(30)
|
||||
self.web.requiresCol = False
|
||||
|
||||
def draw(self):
|
||||
self.web.set_bridge_command(self._linkHandler, TopToolbar(self))
|
||||
self.web.stdHtml(self._body % self._centerLinks(), css=["toolbar.css"])
|
||||
def draw(
|
||||
self,
|
||||
buf: str = "",
|
||||
web_context: Optional[Any] = None,
|
||||
link_handler: Optional[Callable[[str], Any]] = None,
|
||||
):
|
||||
web_context = web_context or TopToolbar(self)
|
||||
link_handler = link_handler or self._linkHandler
|
||||
self.web.set_bridge_command(link_handler, web_context)
|
||||
self.web.stdHtml(
|
||||
self._body % self._centerLinks(), css=["toolbar.css"], context=web_context,
|
||||
)
|
||||
self.web.adjustHeightToFit()
|
||||
|
||||
# Available links
|
||||
@ -122,10 +133,19 @@ class BottomBar(Toolbar):
|
||||
%s</td></tr></table></center>
|
||||
"""
|
||||
|
||||
def draw(self, buf):
|
||||
def draw(
|
||||
self,
|
||||
buf: str = "",
|
||||
web_context: Optional[Any] = None,
|
||||
link_handler: Optional[Callable[[str], Any]] = None,
|
||||
):
|
||||
# note: some screens may override this
|
||||
self.web.set_bridge_command(self._linkHandler, BottomToolbar(self))
|
||||
web_context = web_context or BottomToolbar(self)
|
||||
link_handler = link_handler or self._linkHandler
|
||||
self.web.set_bridge_command(link_handler, web_context)
|
||||
self.web.stdHtml(
|
||||
self._centerBody % buf, css=["toolbar.css", "toolbar-bottom.css"]
|
||||
self._centerBody % buf,
|
||||
css=["toolbar.css", "toolbar-bottom.css"],
|
||||
context=web_context,
|
||||
)
|
||||
self.web.adjustHeightToFit()
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright: Ankitects Pty Ltd and contributors
|
||||
# -*- coding: utf-8 -*-
|
||||
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||
import dataclasses
|
||||
import json
|
||||
import math
|
||||
import sys
|
||||
@ -96,14 +97,76 @@ class AnkiWebPage(QWebEnginePage): # type: ignore
|
||||
return self._onBridgeCmd(str)
|
||||
|
||||
|
||||
# Add-ons
|
||||
##########################################################################
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class WebContent:
|
||||
"""Stores all dynamically modified content that a particular web view
|
||||
will be populated with.
|
||||
|
||||
Attributes:
|
||||
body {str} -- HTML body
|
||||
head {str} -- HTML head
|
||||
css {List[str]} -- List of media server subpaths,
|
||||
each pointing to a CSS file
|
||||
js {List[str]} -- List of media server subpaths,
|
||||
each pointing to a JS file
|
||||
|
||||
Important Notes:
|
||||
- When modifying the attributes specified above, please make sure your
|
||||
changes only perform the minimum requried 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_webview_will_set_content(web_content: WebContent, context):
|
||||
web_content.body += "<my_html>"
|
||||
web_content.head += "<my_head>"
|
||||
|
||||
- The paths specified in `css` and `js` need to be accessible by Anki's
|
||||
media server. All list members without a specified subpath are assumed
|
||||
to be located under `/_anki`, which is the media server subpath used
|
||||
for all web assets shipped with Anki.
|
||||
|
||||
Add-ons may expose their own web assets by utilizing
|
||||
aqt.addons.AddonManager.setWebExports(). Web exports registered
|
||||
in this manner may then be accessed under the `/_addons` subpath.
|
||||
|
||||
E.g., to allow access to a `my-addon.js` and `my-addon.css` residing
|
||||
in a "web" subfolder in your add-on package, first register the
|
||||
corresponding web export:
|
||||
|
||||
> from aqt import mw
|
||||
> mw.addonManager.setWebExports(__name__, r"web/.*(css|js)")
|
||||
|
||||
Then append the subpaths to the corresponding web_content fields
|
||||
within a function subscribing to gui_hooks.webview_will_set_content:
|
||||
|
||||
def on_webview_will_set_content(web_content: WebContent, context):
|
||||
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")
|
||||
"""
|
||||
|
||||
body: str = ""
|
||||
head: str = ""
|
||||
css: List[str] = dataclasses.field(default_factory=lambda: [])
|
||||
js: List[str] = dataclasses.field(default_factory=lambda: [])
|
||||
|
||||
|
||||
# Main web view
|
||||
##########################################################################
|
||||
|
||||
|
||||
class AnkiWebView(QWebEngineView): # type: ignore
|
||||
def __init__(self, parent: Optional[QWidget] = None) -> None:
|
||||
def __init__(
|
||||
self, parent: Optional[QWidget] = None, title: str = "default"
|
||||
) -> None:
|
||||
QWebEngineView.__init__(self, parent=parent) # type: ignore
|
||||
self.title = "default"
|
||||
self.title = title
|
||||
self._page = AnkiWebPage(self._onBridgeCmd)
|
||||
self._page.setBackgroundColor(self._getWindowColor()) # reduce flicker
|
||||
|
||||
@ -254,11 +317,23 @@ class AnkiWebView(QWebEngineView): # type: ignore
|
||||
return QColor("#ececec")
|
||||
return self.style().standardPalette().color(QPalette.Window)
|
||||
|
||||
def stdHtml(self, body, css=None, js=None, head=""):
|
||||
if css is None:
|
||||
css = []
|
||||
if js is None:
|
||||
js = ["jquery.js"]
|
||||
def stdHtml(
|
||||
self,
|
||||
body: str,
|
||||
css: Optional[List[str]] = None,
|
||||
js: Optional[List[str]] = None,
|
||||
head: str = "",
|
||||
context: Optional[Any] = None,
|
||||
):
|
||||
|
||||
web_content = WebContent(
|
||||
body=body,
|
||||
head=head,
|
||||
js=["webview.js"] + (["jquery.js"] if js is None else js),
|
||||
css=["webview.css"] + ([] if css is None else css),
|
||||
)
|
||||
|
||||
gui_hooks.webview_will_set_content(web_content, context)
|
||||
|
||||
palette = self.style().standardPalette()
|
||||
color_hl = palette.color(QPalette.Highlight).name()
|
||||
@ -299,16 +374,12 @@ div[contenteditable="true"]:focus {
|
||||
"color_hl_txt": color_hl_txt,
|
||||
}
|
||||
|
||||
csstxt = "\n".join(
|
||||
[self.bundledCSS("webview.css")] + [self.bundledCSS(fname) for fname in css]
|
||||
)
|
||||
jstxt = "\n".join(
|
||||
[self.bundledScript("webview.js")]
|
||||
+ [self.bundledScript(fname) for fname in js]
|
||||
)
|
||||
csstxt = "\n".join(self.bundledCSS(fname) for fname in web_content.css)
|
||||
jstxt = "\n".join(self.bundledScript(fname) for fname in web_content.js)
|
||||
|
||||
from aqt import mw
|
||||
|
||||
head = mw.baseHTML() + head + csstxt + jstxt
|
||||
head = mw.baseHTML() + web_content.head + csstxt + jstxt
|
||||
|
||||
body_class = theme_manager.body_class()
|
||||
|
||||
@ -334,20 +405,25 @@ body {{ zoom: {}; background: {}; {} }}
|
||||
widgetspec,
|
||||
head,
|
||||
body_class,
|
||||
body,
|
||||
web_content.body,
|
||||
)
|
||||
# print(html)
|
||||
self.setHtml(html)
|
||||
|
||||
def webBundlePath(self, path):
|
||||
def webBundlePath(self, path: str) -> str:
|
||||
from aqt import mw
|
||||
|
||||
return "http://127.0.0.1:%d/_anki/%s" % (mw.mediaServer.getPort(), path)
|
||||
if path.startswith("/"):
|
||||
subpath = ""
|
||||
else:
|
||||
subpath = "/_anki/"
|
||||
|
||||
def bundledScript(self, fname):
|
||||
return f"http://127.0.0.1:{mw.mediaServer.getPort()}{subpath}{path}"
|
||||
|
||||
def bundledScript(self, fname: str) -> str:
|
||||
return '<script src="%s"></script>' % self.webBundlePath(fname)
|
||||
|
||||
def bundledCSS(self, fname):
|
||||
def bundledCSS(self, fname: str) -> str:
|
||||
return '<link rel="stylesheet" type="text/css" href="%s">' % self.webBundlePath(
|
||||
fname
|
||||
)
|
||||
|
@ -163,6 +163,40 @@ hooks = [
|
||||
return handled
|
||||
""",
|
||||
),
|
||||
Hook(
|
||||
name="webview_will_set_content",
|
||||
args=["web_content: aqt.webview.WebContent", "context: Optional[Any]",],
|
||||
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:
|
||||
|
||||
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")
|
||||
|
||||
web_content.head += "<script>console.log('my-addon')</script>"
|
||||
web_content.body += "<div id='my-addon'></div>"
|
||||
""",
|
||||
),
|
||||
Hook(
|
||||
name="webview_will_show_context_menu",
|
||||
args=["webview: aqt.webview.AnkiWebView", "menu: QMenu"],
|
||||
|
Loading…
Reference in New Issue
Block a user