Closet For Anki [Official support]

This is the official support thread for the add-on Closet For Anki. If you have any questions, or suggestions, you can leave them here.

If you’re familiar with GitHub, you can also leave an issue in the official repository instead.

9 Likes

Hi. Thank you for your work. It is brilliant.
I would like to ask if I can change the color of the cloze? Thank you

Hi @n0ob,

in case you have the default settings, and you have Asset Manager installed, if you go to “Manage Note Types > Assets > Closet Setup”, you will find those three lines:

    closet.recipes.cloze.show({ tagname: 'c' }),
    closet.recipes.cloze.reveal({ tagname: 'cr' }),
    closet.recipes.cloze.hide({ tagname: 'ch' }),

This is where the clozing functionality is defined.
After the line above with the comment (/* here goes you setup ... */)

You paste in this:

const wrapWithBrackets = (v) => `[${v}]`

const clozeBack = closet.Stylizer.make({
    processor: v => `<span style="color: red;">${v}</span>`,
})

const clozeFront = clozeBack.toStylizer({
    mapper: wrapWithBrackets,
})

const clozeOptions = {
    frontStylizer: clozeFront,
    backStylizer: clozeBack,
}

Notice the “red” in this block, this is where you can define your color. You can use HTML color names, or you can use a hex code, like #ff0000.

Now change where the clozes are defined to this:

    closet.recipes.cloze.show({ tagname: 'c', ...clozeOptions }),
    closet.recipes.cloze.reveal({ tagname: 'cr', ...clozeOptions }),
    closet.recipes.cloze.hide({ tagname: 'ch', ...clozeOptions }),

If you mess up, and Closet stops working, you can always click “Reset” in the same window. This will bring you back to where you started (or you could just copy the code somewhere safe, before you start).

2 Likes

Awesome. Thank you so much.

Hey bro, nice add-on.
I’m using Anki 2.1.35 on the latest build of Windows 10.
I know the video you uploaded on Youtube is outdated but it was rather simple so I followed it but when I get to the templating part [[]]] syntax won’t work.
The installation part of the docs at https://closetengine.com is also empty.
No error, no nothing. Just those simple steps of installing Asset manager, Anki persistance and closet.

At the moment I would recommend waiting a few days till Anki 2.1.38 releases, install that, and then try to set up Closet. I’ve pushed a big update for Anki versions 2.1.36+ which make using the add-on a better experience.

If you still want to use it, I assume you did not correctly install Closet into the template. If you want to, you can export a deck with the note type where you want to install Closet, and I could help you out there.

Hey, thanks for your response.
Unfortunately I can’t update due to other crucial add-ons like the migaku series which follows a 6 month development cycle.
Injecting/Writing scripts into the templates works. When I click ‘write to template’ a div containing a script tag appears in the template which contains:

<div id="anki-am" data-name="Assets by ASSET MANAGER" data-version="2.0">
    <script data-name="Prevent reinclusion" data-version="v0.1">
        var ankiAms = document.querySelectorAll('#anki-am')
          if (ankiAms.length > 1) {
            for (const am of Array.from(ankiAms).slice(0, -1)) {
              am.outerHTML = ''
          }
        }
    </script>
    <script data-name="Anki Persistence" data-version="v0.5.3">
        if (typeof(window.Persistence) === 'undefined') {
          var _persistenceKey = 'github.com/SimonLammer/anki-persistence/';
          var _defaultKey = '_default';
          window.Persistence_sessionStorage = function() { // used in android, iOS, web
            var isAvailable = false;
            try {
              if (typeof(window.sessionStorage) === 'object') {
                isAvailable = true;
                this.clear = function() {
                  for (var i = 0; i < sessionStorage.length; i++) {
                    var k = sessionStorage.key(i);
                    if (k.indexOf(_persistenceKey) == 0) {
                      sessionStorage.removeItem(k);
                      i--;
                    }
                  };
                };
                this.setItem = function(key, value) {
                  if (value == undefined) {
                    value = key;
                    key = _defaultKey;
                  }
                  sessionStorage.setItem(_persistenceKey + key, JSON.stringify(value));
                };
                this.getItem = function(key) {
                  if (key == undefined) {
                    key = _defaultKey;
                  }
                  return JSON.parse(sessionStorage.getItem(_persistenceKey + key));
                };
                this.removeItem = function(key) {
                  if (key == undefined) {
                    key = _defaultKey;
                  }
                  sessionStorage.removeItem(_persistenceKey + key);
                };
              }
            } catch(err) {}
            this.isAvailable = function() {
              return isAvailable;
            };
          };
          window.Persistence_windowKey = function(persistentKey) { // used in windows, linux, mac
            var obj = window[persistentKey];
            var isAvailable = false;
            if (typeof(obj) === 'object') {
              isAvailable = true;
              this.clear = function() {
                obj[_persistenceKey] = {};
              };
              this.setItem = function(key, value) {
                if (value == undefined) {
                  value = key;
                  key = _defaultKey;
                }
                obj[_persistenceKey][key] = value;
              };
              this.getItem = function(key) {
                if (key == undefined) {
                  key = _defaultKey;
                }
                return obj[_persistenceKey][key] == undefined ? null : obj[_persistenceKey][key];
              };
              this.removeItem = function(key) {
                if (key == undefined) {
                  key = _defaultKey;
                }
                delete obj[_persistenceKey][key];
              };

              if (obj[_persistenceKey] == undefined) {
                this.clear();
              }
            }
            this.isAvailable = function() {
              return isAvailable;
            };
          };
          /*
           *   client  | sessionStorage | persistentKey | useful location |
           * ----------|----------------|---------------|-----------------|
           * web       |       YES      |       -       |       NO        |
           * windows   |       NO       |       py      |       NO        |
           * android   |       YES      |       -       |       NO        |
           * linux 2.0 |       NO       |       qt      |       YES       |
           * linux 2.1 |       NO       |       qt      |       YES       |
           * mac 2.0   |       NO       |       py      |       NO        |
           * mac 2.1   |       NO       |       qt      |       YES       |
           * iOS       |       YES      |       -       |       NO        |
           */
          window.Persistence = new Persistence_sessionStorage(); // android, iOS, web
          if (!Persistence.isAvailable()) {
            window.Persistence = new Persistence_windowKey("py"); // windows, mac (2.0)
          }
          if (!Persistence.isAvailable()) {
            var titleStartIndex = window.location.toString().indexOf('title'); // if titleStartIndex > 0, window.location is useful
            var titleContentIndex = window.location.toString().indexOf('main', titleStartIndex);
            if (titleStartIndex > 0 && titleContentIndex > 0 && (titleContentIndex - titleStartIndex) < 10) {
              window.Persistence = new Persistence_windowKey("qt"); // linux, mac (2.1)
            }
          }
        }
    </script>
    <script data-name="Closet Setup" data-version="v0.1">
        function closetUserLogic(
            closet,
            preset,
            chooseMemory,
        ) {
                const elements = closet.anki.getQaChildNodes()
            const memoryMap = chooseMemory('closet__1')
            const memory = memoryMap.map

            const filterManager = closet.FilterManager.make(preset, memory)

            /* here goes the setup - change it to fit your own needs */

            filterManager.install(
                closet.recipes.shuffle({ tagname: 'mix' }),
                closet.recipes.order({ tagname: 'ord' }),

                closet.recipes.cloze.show({ tagname: 'c' }),
                closet.recipes.cloze.reveal({ tagname: 'cr' }),
                closet.recipes.cloze.hide({ tagname: 'ch' }),

                closet.recipes.multipleChoice.show({ tagname: 'mc' }),
                closet.recipes.multipleChoice.reveal({ tagname: 'mcr' }),
                closet.recipes.multipleChoice.hide({ tagname: 'mch' }),

                closet.browser.recipes.rect.show({ tagname: 'rect' }),
                closet.browser.recipes.rect.reveal({ tagname: 'rectr' }),
                closet.browser.recipes.rect.hide({ tagname: 'recth' }),
            )

            /* end of setup */

            return [[
                elements,
                memoryMap,
                filterManager,
            ]]
        }

        var closetPromise = import(globalThis.ankiPlatform === 'desktop' ? '/__closet.js' : './__closet.js')
            .then(
                closet => closet.anki.initialize(closet, closetUserLogic, '{{Card}}', '{{Tags}}', 'front'),
                error => console.log('An error occured while loading Closet:', error),
            ).catch(error => console.log('An error occured while executing Closet:', error))
    </script>
</div>

I tried getting rid of some add-ons but nothing seems to work…

@Obscurus Closet should already work at this point. Try inserting a sequence like: [[mix1::Word 1||Word 2]] into a field, and open it in the reviewer. In case it doesn’t work, try open the web console, by using this add-on.

@hengiesel I did all the tests I could still the same result: I see the exact sequence instead of the effect. I don’t know what I should do in the console but I can confirm that the js code is indeed injected .

@Obscurus When opening the console, you should see a message like Closet executed in 2.915ms.. That tells you that Closet executed, and it works. If there’s another message (error message), please post it here.

If not, you haven’t set up Closet correctly. In that case, it’s hard to tell you what exactly happened. I can help you best, if you create a new card of that note type, put it into a separate deck, and export that deck as an .apkg file, and upload it somewhere, so I can download it in return and inspect it.

@hengiesel Thanks mate, I feel kinda embarrassed… of course you must’ve left a debug feature like that :D, but I digress.
This is the console output:

spinner.css:1 Failed to load resource: the server responded with a status of 404 (NOT FOUND)
chase_mode.js:8 Waiting for pycmd
__closet.js:1 Failed to load module script: The server responded with a non-JavaScript MIME type of "text/plain". Strict MIME type checking is enforced for module scripts per HTML spec.
VM138:44 An error occured while loading Closet: TypeError: Failed to fetch dynamically imported module: http://127.0.0.1:65083/__closet.js
chase_mode.js:23
spinner.css:1 Failed to load resource: the server responded with a status of 404 (NOT FOUND)
9chase_mode.js:23

OK, it seems you’re hit by the Windows bug. That was a bug that sometimes occurs in pre-v2.1.36 Anki on Windows.

On the add-on page, I also mention:

iOS, macOS, and Linux are supported currently, Windows and Android will follow with Anki v2.1.36 and AnkiDriod 2.15 respectively.

I’m sorry you were hit by this. I think I will highlight this statement a little bit more, so nobody else falls into this trap. So at this point, you can only either update, or remove Closet again. To remove Closet from the note template, go into Asset Manager, and uncheck “Enabled” on the scripts tab, and then click “Write to templates”. This will cleanly remove all scripts.

1 Like

Isn’t there a way to fix it with some tweaks?
At this point switching to linux might be a better option :slight_smile:
Say I know this is out of the blue but how does one start developing add-ons? I mean if I’m serious about doing Anki I should be able to debug add-ons…

The tweaks I had to make were on Anki’s side, and were included in 2.1.36 :slight_smile:

In fact Anki has its own development docs, here.

1 Like

It would be interesting to have something like this addon:

Because I see that in the future your addon will work on ankidroid, and the one I posted earlier only works on the desktop.

1 Like

@huandney That’s a great idea, and certainly doable. I’ve already done something very similar, however without the interactivity.
I will tell you once I sat down to implement this.

2 Likes

Hi! I’m not sure if this is a glitch, or if I’m just being dumb, but i can’t seem to get image occlusions working properly… There are a few parts of this

  1. When i enter image occlusion mode it doesnt count up, but is instead always stuck at 0.
  2. when i exit image occlusion mode (by clicking accept occlusions) it dosnt copy them to my clipboard.

I’m using windows 10, with version 2.1.39.

Thank you so much!!

Closet has a settings section, where you can set up, how the process of creating image occlusions should work. You probably watched the YT video, and seen that they are copied to clipboard.

Ever since, I added an alternative to create them, where they are copied to a cmds field which ends in 0, e.g. Code 0, this means that you don’t have to paste them per hand each time.

3 Likes

Oh my god im such an idiot… thank you so much!!!

1 Like

Can you point me in the right direction for where to override the default CSS styling for cloze elements? Currently, the rendered HTML is: <span style="color: cornflowerblue;">[...]</span> but that default colour isn’t ideal for my template.