CSS Injector [Official Support]

Doesn’t hang anymore, but CSS isn’t applied either. :upside_down_face:

1 Like

Thanks for testing! I’ll get back to it tomorrow morning.

1 Like

@basiskarten I couldn’t reproduce the issue on Windows or Linux.

Have you tried restarting Anki after editing your stylesheets? Anki caches the CSS files, I think. Perhaps I could enable a “hot reload” by programmatically deleting the files and restoring them.

1 Like

Hi Matthias, sorry, it actually does work! The issue was caused by interference from an older instance of css injector that I have in another folder. I thought this instance wouldn’t run because of the following setting in its meta.json file:

"max_point_version": 49

However, it needed to be:

"max_point_version": -49

…to prevent it from running. Sorry for the confusion!

1 Like

CSS files should only be cached for 10 seconds in 2.1.50

1 Like

Thanks for publishing and maintaining this useful add-on!

I noticed the following issue while testing the new feature:

  • When the note type is changed in the Add or Browser window, if the number of fields increases, field.css is not inserted into the newly added fields.
1 Like

Hello! First, thank you for the great addon, it is really helpful.

Using 2.1.53 qt6 on Windows 10, I noticed this:

  • in the Edit card dialogue, the addon works fine.
  • in the Add card dialogue and in the Browser, the addon behaves weirdly. I think there might be a bug in the way it handles fields
    (or in the way Anki itself handles them; I remember a somewhat similar bug when using this addon on Anki 2.1.49)
Add card window

It works fine for the ‘default’ note type; by default, I mean the NT chosen by Anki upon opening the Add card dialogue, e.g. because the last note you added was of that type.
Then, if I switch to another NT that has a greater number of fields, problems arise.
E.g. if the ‘default’ NT had 12 fields, after switching to another NT the addon will not work from field 13 onwards.

I used AnkiWebView inspector and noticed something peculiar.
In this example, ‘Cloze - Prove’ is the default note type. It has 12 fields. ‘Basic’ is the note type I switch into.
I disabled all addons expect CSS injector and the WebView inspector.

A screenshot that shows the notes’ fields and their position:

Cloze - Prove

Basic

Upon inspecting field 12 of Cloze - Prove (with CSS injector disabled)

Upon inspecting field 12 of Cloze - Prove (from now on, CSS injector is always enabled)

Upon inspecting field 12 of Basic

You can see that the field is incorrectly categorized as “Hint10”, the field that occupied the same position in the ‘default’ NT.
This happens for all fields from 1 to 12.

Then, if I inspect field 13 of Basic:

As you can see, there is no sign of the addon in this field’s code (no notetype attribute, no mid, ord etc.)
This happens for all fields starting from 13.

No matter what NT I switch to or how many times I change NT, the field attribute will either be absent (in this example, because its position is 13 or greater), or its value will be equal to the corresponding field in the ‘default’ NT.

But:
Problems also arise if I switch to a note type with fewer fields (e.g. 2) and then back

Default NT, 1st inspection of field 12 (everything normal):

Switching to a NT with just 2 fields:

Switching back to default NT, then inspecting field 12 again:

Inspecting other two fields of default NT just to be sure:

Field 3:

Field 2:

After switching to a NT with a fewer number of fields than the previous one (Y<X), when I switch back the addon will not work on any field with a position greater than Y.
In this example, after switching to a NT with just 2 fields and then back to ‘default’ NT, starting from field 3 there is no trace of the addon’s code.
(No matter what, where still present, the value of ‘field’ attribute always corresponds to that of the ‘default’ NT.)

Browser

In the Browser the behaviour is similar, but even weirder. I am not sure if I understood it completely.
For simplicity, I’ll just describe what I see upon opening the Browser the first time, not including searches etc.
Apparently:

  • the note type of the note in the upmost position determines up onto which field the addon will be able to work. E.g. if the upmost note has a NT with 25 fields, if I then switch the focus to another note (in the same window, without doing any search), the addon will only work up until field 25.
  • if I reverse the sorting, close the browser and then open it again, the new upmost NT will determine which field the addon will work up to.
  • if I switch the focus to another note (in the same window, without doing any search) with a fewer number of fields (Y<X), from now on the addon will work only up to field n° Y.
    No matter how many times I do this. If I switch again the focus to another note type with even fewer fields (Z<Y), when I move to a NT with a number of fields > Z the addon will only work up until field n° Z.
  • the ‘field’ attribute value seems to depend on the first NT that is loaded when opening the Browser, not the upmost one.
    E.g. I open the browser, and before it’s fully loaded I click on a note in the middle of the screen; if I then inspect the ‘field’ attributes (where present) of any note in the same window, their values all correspond to the fields of the ‘first loaded’ note type.

Very long report, sorry.
The problem seems to appear on Anki 2.1.52 qt6 too. I have not tested on any other version.

1 Like

Thanks for the detailed report! Because I use a single notetype for all my notes, I forgot to properly test the notetype switching behavior. It should be easy to fix, I will try to get an update out tonight.

2 Likes

The fix is out (2.1.50+ only atm), thanks @hkr @jcznk for the bug reports!

2 Likes

Thanks for the great addon! One minor bug: one of your querySelector calls is made on undefined sometimes (always for me). Checking for undefined inside the forEach’s that have the querySelector(‘anki-editable’) calls seems to solve the problem:
s.roots.forEach((i, o) => {
if (i === undefined) {return;} // ⇐ here
var n, l;
let e = i.querySelector(“anki-editable”);

and
refresh: (t, i) => r(void 0, null, function*() {
s.initialized ? yield s.injectCSS() : yield s.init(""), document.documentElement.setAttribute(“notetype”, t), document.documentElement.setAttribute(“mid”, i), s.roots.forEach(o => {
if (o === undefined) {return;} // ⇐ here
let e = o.querySelector(“anki-editable”);

Thank you for investigating this! I’ll look into it. And sorry about the minified JS that ships with the Ankiweb package - it makes the add-on a bit difficult to debug.

Here’s the source code for future reference: anki-css-injector/injector.ts at master · kleinerpirat/anki-css-injector · GitHub

In the next update I will disable that minification in esbuild, because the primary purpose of this add-on has been to serve as an updated reference for other add-on authors :v:t2:

First of all, thanks for the excellent addon!

This has been probably reported already, but just on the off chance it wasn’t; the last update broke the addon for me. When opening a dialog, such as Add dialog, the html tag looks like this:

<html class="" dir="ltr">

And some styles are not applied. When I switch to another notetype and back, it reads

<html class="" dir="ltr" notetype="tango" mid="1349860982381">

And everything works fine.
Anki version ⁨2.1.52 (ab1c2395)⁩, Python 3.9.7, Qt 6.3.0, PyQt 6.3.0.

1 Like

Out of curiosity I had a glance at the non-minified source, you have probably already figured it out but otherwise one guess would be the use of setTimeout to wait for loading of the shadowRoots to complete. That may not be 100%, you may want to look at using a MutationObserver watching for anki-editable elements being added instead. I mentioned it because I have run into that problem myself in another context (see GitHub - TRIAEIOU/Chromium-canvas-video-tab: Chrome extension that injects "Open in new tab" links in Instructure's Canvas LMS. for an example of use of mutationObserver).

1 Like

I’m not a big fan of setTimeout because it’s sort of a Hail Mary move - you can only pray it works (sadly, I do use it in this add-on too). I’m aware that a MutationObserver is the most reliable method, but it depends on hard-coded HTML structure of the editor. I’d rather have something close to an API, which is why I try so hard with all the promises etc.

Thanks for the tip, though! I’ll keep it in mind and might use it as a last resort, since everything else has proven to be unreliable.

I did a little digging as well. It seems that <anki-editable> elements can be reliably obtained through RichTextInputAPI without using setTimeout or MutationObserver . The element property of the API is a Promise that will be resolved with <anki-editable> element.

I too have encountered this issue. About 2 or 3 times out of 100 times it fails to set the note-related attributes (mid, notetype) for the HTML root element.

2 Likes

Thanks Hikaru for the invaluable input, as always! I took a look at RichTextInputAPI, but it seems to be private. A search for registerPackage("anki/ within Anki’s source code revealed the following packages that can be required:

  • anki/location
  • anki/NoteEditor
  • anki/surround
  • anki/TemplateButtons
  • anki/PlainTextInput
  • anki/bridgecommand
  • anki/packages
  • anki/shortcuts
  • anki/ui
  • anki/theme

After an initial test, I believe PlainTextInput.lifecycle.onMount might also do the trick. It fires later than NoteEditor.lifecycle.onMount and makes the Svelte ticks unnecessary.

Or do you have a working example for how to use RichTextInputAPI to obtain any <anki-editable>?

Say, meanwhile, can I perhaps have the previous version of the addon since it worked without issues for me please puppy eyes

Here is an example of an asynchronous generator that iterates over <anki-editable> elements obtained through RichTextInputAPI. This can be used within ts/src in the repo. Note that the target property in tsconfig.json needs to be set to es2018 or higher, since AsyncGenerator is set as the return type.

TS code
import * as NoteEditor from "anki/NoteEditor";
import { get } from "svelte/store";
import type { PlainTextInputAPI } from "@anki/editor/plain-text-input";
import type { RichTextInputAPI } from "@anki/editor/rich-text-input";

function isRichTextInputApi(
    input: RichTextInputAPI | PlainTextInputAPI,
): input is RichTextInputAPI {
    return input.name === "rich-text";
}

async function* iterateAnkiEditables(): AsyncGenerator<HTMLElement> {
    while (!NoteEditor.instances[0]?.fields?.length) {
        await new Promise(requestAnimationFrame);
    }
    for (const fieldApi of NoteEditor.instances[0].fields) {
        const inputs = get(fieldApi.editingArea.editingInputs) as (
            | PlainTextInputAPI
            | RichTextInputAPI
        )[];
        const richTextInputApi = inputs.find(isRichTextInputApi)!;
        const ankiEditable = await richTextInputApi.element;
        yield ankiEditable;
    }
}

And here is the js code that is converted from the above code into a format that can be used in the console tab of the inspector.

JS code
async function* iterateAnkiEditables() {
    const noteEditor = require("anki/NoteEditor").instances[0];
    while (!noteEditor.fields?.length) {
        await new Promise(requestAnimationFrame);
    }
    for (const fieldApi of noteEditor.fields) {
        const inputs = require("svelte/store").get(fieldApi.editingArea.editingInputs);
        const richTextInputApi = inputs.find((input) => input.name === "rich-text");
        const ankiEditable = await richTextInputApi.element;
        yield ankiEditable;
    }
}

for await (const editable of iterateAnkiEditables()) {
    console.log(editable);
}
Screenshot

5 Likes

@oakkitten thanks to Hikaru, the issue you reported above should be fixed with the update I just pushed.

@hkr Is it alright that I used your async generator in my add-on? I have credited you accordingly. I can’t thank you enough, it simplifies my editor work so much!

2 Likes

Yes it is fixed! You are amazing! :tada:

1 Like