Currently, the AnkiQt
class contains hardcoded references to all pages displayed in the main window. I’d like to introduce dynamic routing to make it easier for addons to define their own pages or replace existing ones.
Here’s a first draft of what an addon using this system could look like:
from aqt import mw
from aqt.router import *
test = Widget()
test.html = """
<div style="text-align:center;">
<div>First Page</div>
<button onclick='return pycmd("open:second")'>GO next</button>
</div>
"""
page = Page("", test, "Decks")
mw.router.add_page(page)
test2 = Widget()
test2.html = """
<div style="text-align:center;">
<div>Second Page</div>
<button onclick='return pycmd("open:")'>GO back</button>
</div>
"""
page = Page("second", test2, "Second")
mw.router.add_page(page)
Think this is worth finishing and PR’ing into Anki?
Later, I’d like to make the UI even more addon-friendly e.g. by introducing a registry of actions associated with each entity type (such as decks). Addons could contribute actions to this registry, and any UI component that works with a given entity could then dynamically populate context menus or buttons based on the registered actions.
2 Likes
abdo
May 31, 2025, 8:40am
2
Anki’s UI is being slowly migrated to Svelte. Introducing such API at this stage is probably not worth it since it’ll almost certainly need to be reworked in the near future.
opened 09:18AM - 20 Mar 25 UTC
Once we're done with #3830, I'd like for us to start on a new reviewing screen t… hat can be shared across the clients. Posting this here to provide a bit of a roadmap, and encourage discussion. Feedback welcome!
The current review screen uses two webviews - the 'main' and 'bottom' webview, and we update the two in lockstep (e.g. displaying buttons when the answer is revealed). These changes are coordinated by the logic in reviewer.py. The code is awkward to maintain, as it relies on legacy practices (creating HTML/JS from Python strings, and sending messages back and forth across the Python/TS boundary). The mobile clients have had to implement their own reviewer logic, meaning changes to things like the answer buttons have to be done multiple times. This has also lead to inconsistencies, most notable of which is AnkiDroid reloads the webview on each card transition.
## Sandboxing
Another issue with the current approach is that it loads the user-provided content on the same origin as the program interface. This allows shared deck JS to manipulate Anki's interface, and make requests to our internal webserver. We've had to block certain methods from being accessed by the review screen due to security issues, and that's only partly solved the problem. It's become a FAQ: https://github.com/ankitects/anki/security/policy#faq
A potentially better solution for isolating user-provided content would be to load it inside an iframe with a different origin. (e.g. something like `<iframe sandbox="allow-scripts ...">`). This would prevent card content from accidentally or deliberately styling/modifying the interface outside of the card, or making any requests via our internal API. I've briefly experimented with this many years ago, but gave up at the time, as there were some extra issues to deal with (e.g. where keystroke events arrive when the outer vs inner frame has focus, sizing the iframe to fit properly, how to use a different origin, etc.) Nothing that looked insurmountable, but it got put in the too-hard basket at the time.
As far as I'm aware, there are a few ways we could give the iframe a different origin, limiting its ability to access our internal API:
- Hosting another webserver on another port. Cons: requires running multiple webservers, and possibly not as secure (some browsers have special rules for different ports on localhost I think?)
- Using 'localhost' and '127.0.0.1' for the different origins. Cons: I think we currently avoid 'localhost', as there are rare misconfigured Windows machines that lack the hostname
- Initializing the iframe with a blob: URL instead of using our server. Cons: the page needs to be bundled into a single file and base64 encoded, which would be awkward, and slow if it pulls in megabytes of dependencies.
The hosting frame (our interface) would not be able to directly update the iframe DOM, as we currently do. So when the user reveals the answer, or answers a card, the outer frame would make the relevant API requests, and then need to use postMessage to send a message to the iframe. The iframe would have a message handler we wrote, that receives the message and updates the DOM appropriately.
## DOM updates
Based on previous experience with Svelte on AnkiWeb, I'd recommend we keep using manual DOM methods to inject the card content onto the page, rather than using reactive statements/simply inlining the HTML in a Svelte page with {... | html}, as it was a cause of flicker in the past.
## Bottom Bar
Moving the bottom UI elements into the same page would save us the costly extra webview creation, and make things simpler. It was originally done with multiple webviews out of a desire to ensure the buttons were always visible, regardless of the styling of the card content. But if card content is isolated in an iframe, we should be able to ensure the buttons are always visible, using z-index, etc.
Moving the buttons into this page would mean they'd be shared across the clients, and we could make changes like a 2 button mode in a single place.
## Scratchpad
Currently the desktop has no scratchpad, and the mobile clients have implemented their own versions. We'll need to decide whether we wish to keep the bespoke implementations (that have the advantage of access to native APIs like PencilKit), or provide a shared implement that all clients can use.
## Compatibility
This is likely to break a lot of add-ons that alter the review screen, and may break some more advanced card templates too. I think we should start this off as a prototype/MVP with a toggle switch allowing the user to use the old or new review paths (defaulting to the old one), as we'll likely need some time to transition over if the MVP proves practical.
opened 11:40AM - 18 Feb 25 UTC
editor
Currently, the editor is driven by editor.py. When the user selects a note,
or c… hanges a notetype/deck in the add screen, our Python code sends a bunch of
JavaScript to the webview in loadNote(). This presents a number of problems:
- It's messy - we have web implementation details leaking into the Python code
- It's prone to race conditions due to the asynchronous message passing, and
so our code has hacks like sending the current note ID back to Python to ensure
they match.
- It's a necessary step to being able to integrate the editor into a future
implemented-in-Svelte browse screen.
- And most importantly, it makes integrating the editor into the mobile clients
impractical, since they would need to duplicate all the logic that we currently
have in the Python code.
I propose we shift the majority of the logic into the web end. For the editcurrent
and browser cases, the Python-visible API would consist of something very simple
like loadNoteId(bigint | null). That would cause the JS side to request all required
information via mediasrv, instead of editor.py being responsible for pushing note
data & metadata with eval().
For the addcards case, the editor would need to be wrapped in another Svelte component
that provides selectors for deck and notetype, as we currently implement them in Qt.
Some challenges we'll face:
- Add-ons currently rely on Python hooks and monkey patching to alter editor behaviour,
and it may not be possible to preserve existing functionality in a backwards-compatible
way.
- Copy/paste and drag & drop might be tricky. Are the JS APIs sufficient for us to handle
this (mostly) on the JS end? Or will we need to continue handling this separately for each
client, and sending messages to the webview?
Back when Henrik was actively involved, he made decent progress on some of these changes.
Unfortunately, I felt the changes were too disruptive at the time, and the PRs ended
up withering.
- Loading via mediasrv: https://github.com/ankitects/anki/pull/1691
- Notetype/deck selector: https://github.com/ankitects/anki/pull/1881
They've likely developed a bunch of conflicts since then, but they may still be useful
as a reference.
Since this is a fairly large change, I think we'll want to start with a basic proof of concept
to feel out the add-on and pasting issues (e.g. ignoring the addcards case for now) first.
2 Likes