Anki Forums

How to include external files in your template (JS, CSS, etc.) | [Guide]

In another thread I announced that I’ll create a guide on how to include external JavaScript libraries in a note template. I will use Tippy.js as an example, since it covers both the JavaScript and the CSS part.

Update 01.08.21: The sample script has been updated to the format used by the Tippy Tooltips add-on.

Disclaimer: JavaScript is not officially supported by Anki. Therefore, if you run into trouble, there are no official documents to help you out. Things might break in the future and if you don’t understand the code you’re using, you’re at the mercy of other people to fix things. I’ll try to maintain this post for some time, but I can’t promise anything. If you encounter any issues, please let me know in the comments.

Tippy Tooltips sample

You can download a working sample note type here: Tippy Tooltips - Sample Note Type - AnkiWeb


How to include external libraries

Anki cards are like websites in many ways. A common way to use external libraries on the web is to link to their their content delivery network (e.g. https://unpkg.com):

But our cards should be usable without an internet connection too. That’s why we want to store our external libraries inside the collection.media folder.

🛈 collection.media is synced between all clients (Anki Desktop, AnkiMobile etc.), so we only need to do this once.

We should also strive for cross-platform compatibility, so we can share our deck knowing that other users can enjoy it as well. Keeping these considerations in mind, I will show you an approach that works on all Anki clients - offline.


Download the necessary files

Every good JavaScript library has setup instructions somewhere on its landing page. As we can see on the “Getting Started” page of Tippy, the following files are required:

Optional:

🛈 Always download the minified version (.min) - if available - to save space.

image

When presented with a raw text page like the ones from UNPKG, hit Ctrl+S to save the file to your computer.


Rename and move to media folder:

After we finished downloading the code, it’s time to move our files to collection.media.

Attention: Because the files aren’t referenced directly in our fields, Anki will count them as unused when we click on Check Media:

To prevent this, we must prepend the filenames with an underscore:

image


Import JavaScript files

I once explained on the AnkiDroid repo how to import external files into an Anki template.

Now we got a special challenge: Popper needs to be loaded before Tippy, because Tippy depends on Popper. The following script is an improved version of the one on GitHub. It allows us to import a hierarchical structure of dependent libraries.

Cross-platform script loader
var files = [{
    name: "Popper",
    url: "_popper.min.js",
    dependent: {
        name: "Tippy",
        url: "_tippy.umd.min.js",
        init: initTooltips
    }
}]

function getAnkiPrefix() {
    return globalThis.ankiPlatform === "desktop" ?
        "" :
        globalThis.AnkiDroidJS ?
        "https://appassets.androidplatform.net" :
        "."
}

if (!globalThis.ExternalScriptsLoaded) {
    var promises = []
    for (file of files) {
        promises.push(loadScript(file))
    }
    Promise.all(promises).then(() => {
        globalThis.ExternalScriptsLoaded = true
    })
}

async function loadScript(file) {
    var scriptPromise = import (`${getAnkiPrefix()}/${file.url}`)
    scriptPromise.then(() => {
            console.log(`Loaded ${file.name}.`)
            if (file.init) file.init()
            if (file.dependent) loadScript(file.dependent)
        },
        (error) => console.log(`An error occured while loading ${file.name}:`, error)
    )
    return scriptPromise
}

🛈 You can use this script as a template for your own JavaScript libraries. Simply exchange/extend the contents of files.

As you can see, the files can have a property called init. This can be any function we want to execute right after the library has been loaded:

...
    {
        name: "Tippy",
        url: "_tippy.umd.min.js",
        init: helloTippy // <- executed after Tippy has loaded
    }
...
function helloTippy() {console.log("Hello, Tippy!")}

image

🛈 The Tippy file is nested into the Popper file as dependent. Analogous to init for functions, the dependent file (Tippy) will start loading once the dependency (Popper) has finished loading.

var files = [{
    name: "Popper",                  // <- dependency for Tippy
    url: "_popper.min.js",
    dependent: { 
        name: "Tippy",               // <- this file will load
        url: "_tippy.umd.min.js",    // once the dependency (Popper) 
        init: initTooltips           // has finished loading
    }
}]

Use imported library

Now that we have told Anki how to import our library, we can use it in our own template code.

Updated (01.08.21): This works in conjunction with the Tippy Tooltips add-on.

Here is a simple script that converts elements with a data-tippy-content attribute to working tooltips (CSS provided in sample note type):

Tooltip script - Updated for Tippy Tooltips add-on
function initTooltips() {
    globalThis.tippyAvailable = true
    createTooltips()
}

if (globalThis.tippyAvailable) setTimeout(() => {createTooltips()}, 0)

function createTooltips() {
    tippy("[data-tippy-content]", {
        placement: "top",
        allowHTML: true,
        theme: "sample",
        interactive: true,
        trigger: "mouseenter click",
        animation: "scale-extreme",
        appendTo: document.body,
    })
}

🛈 Notice the function initTooltips(), which was called by our import script above? It sets the global variable tippyAvailable = true, allowing the execution of createTooltips() on further cards.

Input

Output

Kooha-2021-08-01-10_02_49


Import CSS files

Because of the non-dynamic nature of CSS, the process of importing external stylesheets is much simpler.

For example, we want to include the following two CSS files for our tooltips:

All we need to do is to put the following lines on top of the Styling section of our template:

@import url("_tippy.css");
@import url("_scale-extreme.css");
Screenshot

image


This was a pretty long post. Thanks for bearing with me - I hope you could get something out of it! I’d like to give special thanks to @hengiesel for his help in getting me where I am in terms of Anki-specific JavaScript. He’s been a major source of inspiration for a lot of my work.

11 Likes

First of all, thanks for sharing this awesome feature.

But when a strike a quite sizable text, the tooltip stays above the word, getting out of my card.

maIy4p85ra

Another thing, for example, let’s say i have a quite big text section striked and because of that editing the card gets messy, , is there a way a i can hide the striked text in the editor and unhide when needed?

This might be a limitation caused by the Anki webview itself. I can replicate it by using Anki in window-mode (not maximized):

Kooha-2021-07-28-16_25_21

The viewport information seems to be off when the window is not maximized. Tippy (actually Popper) thinks it has enough space for the tooltips.

@Casartelli a quick fix for this would be to change the default top positioning to bottom:

tippy(tip, {
            content: strike.innerHTML,
            placement: "bottom",    // <- changed to bottom (from top)
            allowHTML: true,
            theme: "sample",
            interactive: true,
            trigger: "mouseenter click",
            animation: "scale-extreme",
            appendTo: document.body,
        })

The following idea is certainly worth looking into:


An advantage of the <strike> approach is that we can easily batch-edit them via RegEx into another format. Should the abovementioned idea get implemented one day, I can help you with that switch.

1 Like

I was about to say that my window was already maximized and the tooltip still gets off. But change “top” to “bottom” solved my issue.

I took a look, but I don’t understand a lot of programming.

I searched on Google and find this HTML details Tag, but didn’t work the way wanted. :disappointed_relieved:
Anyway, thanks a lot for helping me out, i’m gonna keep searching on Google and see if i can find a solution.

@Casartelli @gbrl.sc I’m in the process of creating an add-on for this:

Kooha-2021-07-29-12_23_37

It’s basically a stripped down version of Add Hyperlink with CSS injection for the editor fields.
@ijgnd I hope that’s alright. I will create a fork of your repo before I publish anything on AnkiWeb.
(& sorry for the misspelling in the GIF :sweat_smile:)

That would be awesome!

By the way, i’ve been using your tooltip template, but in cloze seems not to work properly when i don’t have < u > before the strike.

MTnKXVR3LV

1 Like

Thanks for testing!

To be honest, I didn’t really think this through properly.
The approach I’m taking with the add-on will be more stable and user friendly, but it will take some time.
Perhaps it’s best to hold off on that functionality for a while, until I have refined the process.


I think I’ll write that add-on from scratch, rather than modifying Add Hyperlink, because I want it to be usable in-editor without a dialog:

Thanks for your hard work on @gbrl.sc idea. I’m sure there are many people who will make great use of the add on.

Great guide and addon.

For people using the code from this site (and not the one included in the addon sample note type) - does your tooltip arrow also sometimes look strange?