[Tip] - Dynamic HTML/JS inside <anki-editable>

If you ever tried to create a dynamic UI inside of <anki-editable> with Anki’s new editor (2.1.50+), you probably realised that the editor will make quick work of your fancy interface by turning it into static HTML as soon as it detects HTML changes. Reason: The editable (RichTextInput) bidirectionally syncs with the HTML editor (PlainTextInput), refreshing the whole innerHTML of the element and therefore removing any attached EventListeners and other goodies in the process.


I’ll take my add-on Tippy Tooltips as an example.


Altough it seems like the perfect match for a custom Svelte component, abovementioned restriction forced me to use a rather hacky method to live-update <anki-editable> on input:

// The current tooltip anchor inside <anki-editable>
// is marked with the attribute [active],
// so I know which anchor to sync the changes to.
instance.input.addEventListener("input", (e) => {
    ).dataset.tippyContent = e.target.innerHTML;

Official API Solution

Today after a deliberate search, I have found @hengiesel has (like so many times) thought about this problem a long time ago and made it possible to decouple PlainTextInput from RichTextInput with the function preventResubscription that’s exported with RichTextInputAPI.

(Here @hkr described how to access that API: CSS Injector [Official Support] - #54 by hkr)

// output
// () => {
//        allowResubscription.set(true);
//      }

As you can see, the function returns another function that can be used to recouple PlainTextInput with RichTextInput again.

What are the implications?

This means you can build a dynamic UI (e.g. with a framework like Svelte) inside Anki’s fields and it will actually work.

I hope some of you will find this information useful and create wonderful things with it!


Anki uses it for the Mathjax editor, btw.

It’s a similar situation as with your tooltip editor: You edit the Mathjax in a small window, which updates the preview in the main window. To correctly update the preview, it needs event listeners hooked up, and for those event listeners to not be automatically removed upon focus change, we need this preventResubscription.

You should call the callback, once your custom editor looses focus and moves back to the main editor, or goes to a different field altogether.