Site of James

Placing my knowledge on a web page

Using Svelte to Build Reactive Anki Addons

2021-04-28 Web Programming James

Anki is already using Svelte in its interface, and you should too!

See the GitHub stats. In 2.1.28, the stats screen was re-worked to use Svelte components, over vanilla JavaScript.

(Actually, Anki is written in a bunch of different languages which make it an interesting read. Python, TypeScript, Svelte, Rust, and SQL are all at play.)

Taking advantage of the fact that Anki uses QtWebEngine to display most of its content in HMTL and CSS, it’s possible to make your addon’s UI in JavaScript as well. QtWebEngine is based off of Chromium (https://wiki.qt.io/QtWebEngine) so it should have no problem running modern JavaScript.

I wanted to turn a tool I had made into an Anki addon. With the web views in mind, and how not-fun it is to make a complex data-fetching GUI in old-school Python and Qt, plus taking advantage of the framework-less nature of Svelte to learn it at the same time, this blog post was born.

Interate the Python Side

Anki Desktop is still written in Python, so there is some Python required to add your JS-based addon to Anki.

There’s more code than this for a working addon than what’s on this page, but this is the basic flow is this:

  • Make a Python class that inherits from AnkiWebView
  • Load your JS and CSS bundles from disk, bundled with your addon
  • Make a small index.html and use the stdHtml function to open an AnkiWebView with the Svelte app
class KyroWebView(AnkiWebView):
    def __init__(self):
        AnkiWebView.__init__(self, title="Kyoro")

        with open(os.path.join(addon_path, "bundle.js"), "r") as f:
            appContents = f.read()
        with open(os.path.join(addon_path, "bundle.css"), "r") as f:
            appCss = f.read()

        html = """
            <script defer>{0}</script>
            <style>{1}</style>
            <style>{2}</style>
            <!DOCTYPE html><body></body></html>
        """.format(appContents, appCss)

        self.stdHtml(html)

When to show the view is up to the app, but I inserted a item in the Anki menu bar that called a function to display it:

def showApp():
    mw.kyoroApp = KyroWebView()
    mw.kyoroApp.show()
    mw.kyoroApp.setFocus()
    mw.kyoroApp.activateWindow()

The idea is to somehow open your webview which mounts your Svelte application. You webapp will take it from there. This model is similar to the index.html “target page” in single page apps.

Write the Svelte Side

Interaction with Anki Itself

Normal Python-based Anki addons have access to Anki’s internals directly. That means it’s a huge surface area of things possibly changing, but allows for great flexibility if you’re brave, and it’s there by default.

For JS-only there are two options:

Use the native pycmd bridge provided by Anki

Anki exports a function into its webviews called pycmd, which you can call from JavaScript, and which it uses internally for its own web views, such as the reviews and others.

Then, you do what by listening to a hook in Python called webview_did_receive_js_message, and act upon what it was called with. You can also respond back to JS with information this way.

Info here

This approach allows you provide more powerful features at the cost of writing them yourself, or touching on Anki internals.

Use AnkiConnect as a dependency

AnkiConnect is a popular REST API for Anki addons. It provides the basics such as CRUD for cards, notes, decks, and related, but your user must first configure it to accept local web requests, which is an extra step. However, doing a field survey showed that many JS-based addons have this in their install steps, so it’s not unheard of.

You’ll have to have your users install this first and configure its CORS list to accept the calls you’re going to make (your addons is running in a webpage after all):

null must be added to the webCorsOriginList setting for AnkiConnect (this is the origin sent by the WebView)

{
    "webCorsOriginList": [
        null,
        "http://localhost",
        "http://localhost:8080"
    ]
}

AnkiConnect may be the way to go if all your addon needs is the functionality provided by it.

Friendly with Anki’s Webviews and Native Look

Your Svelte app’s styles may look weird or wrong at first.

To support common styles, pycmd, and other functions of the Anki webviews, Anki includes some default JS and CSS. Even though you can add your own JS and CSS, some are hardcoded. The best way would be to remove them at runtime through DOM manipulation.

alt text

There’s also things you may want to listen to in your app, to set themes, such as isLin (Linux) and the nightMode attributes on body so you can listen to the user.

Why not just use Python (native) addons?

Advantages of Svelte

  • More resilient to changes in the Anki codebase. The addons system in Anki relies on digging into Anki internals to place hooks and set items on existing Python objects, which is fragile for large addons
  • Quicker to build and change a powerful UI, once set up, compared to native Qt
  • Use existing web technologies, tools, and frameworks to build and test the app
  • Utilize the Chrome developer tools to debug and change your addon in real-time, rather than having to continually restart Anki with updates
  • (subjective) better-looking UIs with web technologies

Disadvantages

  • Not as flexible or powerful as Python-based addons
  • If using AnkiConnect, you’ll require your users to install and configure that first. They likely won’t know what “CORS” is.

So, should you do it?

Do it, it’s fun!