Closet For Anki [Official support]

There seems to be something wrong with your template HTML.
See this sample note for reference: https://ankiweb.net/shared/info/818839225

Since v0.2.0, Closet doesn’t need Anki Persistence installed anymore.
You can uninstall the add-on if you want.

1 Like

It worked, tanks a lot :grin:

@kleinerpirat @hengiesel
Can you make this possible ? Please help

If you mean the cloze font-weight, use this in your note template’s styling section:

.closet-cloze.is-active {
  font-weight: bold;
}
How to get the class of any Closet element

To find the names of other Closet elements, use the AnkiWebView Inspector:

2 Likes

Below is the code I use in the closet setup… Unfortunately, the auto-scroll is not functioning properly
Can somebody spot the issue , please @kleinerpirat
thanks in advance

const elements = closet.template.anki.getQaChildNodes();
const memory = chooseMemory("closet__1");
const filterManager = closet.FilterManager.make(preset, memory.map);

const output = [[elements, memory, filterManager]];

/* here goes the setup - change it to fit your own needs */
/* style change*/
const wrapWithBrackets = (v) => `[${v}]`

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

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

const clozeOptions = {
    frontStylizer: clozeFront,
    backStylizer: clozeBack,
}
/* style over*/


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


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








    closet.flashcard.recipes.cloze({
        tagname: "ch",
        defaultBehavior: closet.flashcard.behaviors.Hide,
    }),


    closet.flashcard.recipes.multipleChoice({
        tagname: "mc",
        defaultBehavior: closet.flashcard.behaviors.Show,
    }),
    closet.flashcard.recipes.sort({
        tagname: "sort",
        defaultBehavior: closet.flashcard.behaviors.Show,
    }),

    closet.browser.recipes.rect({
        tagname: "rect",
        defaultBehavior: closet.flashcard.behaviors.Show,
    }),






);;
// Auto scroll to first active cloze
setTimeout(() => {
   document.querySelector(".closet-cloze.is-active").scrollIntoView({
      behavior: "smooth",
      block: "center"
   })
}, 0)

Styling

With that I didn’t mean a stylizer in the Closet setup. Just a regular CSS definition in the “Styling” section of Anki’s note template editor (not Asset Manager). You can scrap all that stylizer code - which is the reason why my function didn’t work - and paste the following definition into “Styling”:

.closet-cloze.is-active {
   color: yellow;
   font-weight: bold;
   font-style: italic;
}

Closet setup

From what I can tell, you want your clozes to hide by default (with tagname “c”). Here is a rework of your current setup + the auto-scroll function:

const elements = closet.template.anki.getQaChildNodes();
const memory = chooseMemory("closet__1");
const filterManager = closet.FilterManager.make(preset, memory.map);

const output = [[elements, memory, filterManager]];

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

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

    closet.flashcard.recipes.cloze({
        tagname: "c",
        defaultBehavior: closet.flashcard.behaviors.Hide,
    }),
    closet.flashcard.recipes.multipleChoice({
        tagname: "mc",
        defaultBehavior: closet.flashcard.behaviors.Show,
    }),
    closet.flashcard.recipes.sort({
        tagname: "sort",
        defaultBehavior: closet.flashcard.behaviors.Show,
    }),

    closet.browser.recipes.rect({
        tagname: "rect",
        defaultBehavior: closet.flashcard.behaviors.Show,
    }),
);

/* auto-scroll to first active cloze */
closetPromise.then(() => {
    if ((cloze = document.querySelector(".closet-cloze.is-active")) != null) {
        cloze.scrollIntoView({
            behavior: "smooth",
            block: "center"
        })
    }
})

Make sure to change <hr id="answer"> to just <hr>, if you got that in your back template HTML. We don’t want Anki’s auto-scroll to interfere in this case.

I’ve been playing with this addon again for a while, and now I am wondering, how do I make closet evaluate the inlineValues when there are blockValues? Or is it not possible yet? I have a setup like this,

function parted(args) {
  const {
    tagname,
    blockOptics = [],
    blockApply,
    inlineOptics = [],
    inlineApply,
    blankApply
  } = args;

  const filter = (args) => {
    const { hasBlock, hasInline, inlineValues, blockValues, num } = args;

    if (hasBlock && blockApply) {
      return blockApply({ inlineValues, blockValues, num });
    } else if (hasInline && inlineApply) {
      return inlineApply({ inlineValues, num });
    } else if (blankApply) {
      return blankApply({ num });
    } else {
      return false;
    }
  };

  return (registrar) => {
    registrar.register(tagname, filter, { inlineOptics, blockOptics });
  }
};

filterManager.install(
  parted({
    tagname: "gt",
    inlineOptics: [closet.template.optics.separated({ sep: "::", max: 2, trim: true })],
    inlineApply({ inlineValues }) {
      const [left, right] = inlineValues.map((x) => parseFloat(x));

      return left > right ? "1" : "0";
    }
  }),
  parted({
    tagname: "if",
    inlineOptics: [closet.template.optics.separated({ sep: "::", max: 1, trim: true })],
    blockOptics: [closet.template.optics.separated({ sep: "::", max: 1, trim: true })],
    blockApply({ inlineValues, blockValues }) {
      const [condition] = inlineValues;
      const [block] = blockValues;

      return condition.trim() == "1" ? block : "";
    }
  })
)

In the HTML, it would look like this then.

<!-- -->
[[#if::[[gt::[[genr::0, 10]]::5]]]]
<p>The number is greater than 5</p>
[[/if]]
<!-- -->

However, I would find that the condition parts are not being evaluated by closet. Is it possible?

Yep, it seems you’ve come across a bug / missing feature. I’ve opened an issue here. Only tags inside the block part are parsed, if you use block tags currently.

I noticed another bug I think, but this is related to Asset Manager. Whenever I clone a note type which already has a custom closet setup, when Anki restarts, Closet adds another closet setup. So essentially I get stuck with two Closet Setup scripts which are both protected by the addon. To fix this, I have to

  1. Disable closet addon
  2. Remove both setups from the duplicated note type
  3. Re-enable closet addon
  4. Place the previous custom setup.

This gets a bit tedious when I try to make a new card type out of my previous one, but I think this is also a minor issue.

3 Likes

Thanks for pointing this out. I checked, and noticed that the same thing occured when I cloned my Note Type containing the default, unmodified Closet Setup script.

Upon restarting, and cloning this Note Type, another Closet Setup script was added to the second generation clone.

Hello there,
I’m trying to create cards to study articles of laws and, by the moment, I’m using the add-on Enhanced Cloze (unmaintained Fork for 2.1) [Enhanced Cloze (unmaintained Fork for 2.1) - AnkiWeb], but I want to use Closet because it would allow me to be more flexible doing my flashcards (my goal is to make several hide clozes being able to click on each one to reveal it, although they’re not the ones I’m being asked for).

However, I have 2 problems that I don’t know how to solve and that are the reason to keep using the other addon:
1] The clozes that I create doesn’t work well on my Ankidroid version 2.15.6 (they don’t show the majority of times when I press “Show Answer”).

2] I know how to turn, for example, the default clozes into hide clozes (as someone posted before), but I have no idea about being able to make them revealed by a click and vice versa (or a touch, in my case, because I study the most of my time with my phone).

I show an example of a flashcard that I did with the other add-on to clarify what I want to achieve (the red capsules are the ones I have to answer, the other ones are other cloze numbers, but can click the cloze that I want and see it’s hidden content, and vice versa).

If I could have a solution to these two problems, I would use, without any doubt, the Closet addon. Especially considering that I could make more than 20 clozes per card (one of the various limitations of enhanced cloze 2.1 fork).

That’s all, thanks for your work

1] The clozes that I create doesn’t work well on my Ankidroid version 2.15.6 (they don’t show the majority of times when I press “Show Answer”).

Which version does your Android phone run on? It seems like you don’t just need Ankidroid 2.15+, but also Android version 10.

Other than that, it’s hard to say where your problem lies. Are you sure it only occurs on Ankidroid?

In my MacOS Catalina 10.15.7 the cloze system works well. I’m using Ankidroid 2.15.6 on a OnePlus 5t with Oxygen OS 10.0.1 and Android 10.

Where am I going wrong here?

Script inserted into the back template
var clozes = document.getElementsByClassName("closet-cloze__answer");
var clr = window.getComputedStyle(clozes[0]).color;
var bg = window.getComputedStyle(clozes[0]).background;
 for (i=1; i<clozes.length; ++i) {
  clozes[i].style.background = clr;
  clozes[i].onclick = function() {this.style.background=bg ;}
 }

It’s throwing the error Uncaught TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'. at the second line of the script. It handles normal clozes fine.

This is working perfectly… Thanks a lot. :smiley: :smiley: :smiley: :smiley:

@shallash Sorry for the late answer, I’ve been a bit busy the last few days.

The reason it doesn’t handle Closet clozes the same way as default clozes is that Closet first needs to execute (convert the syntax provided in the fields into web components).

If you check the previewer with AnkiWebView Inspector, you’ll find a line like this on each card flip: image

If you don’t make your script asynchronous in some form, it will always execute before Closet.

You can either do it with setTimeout():

setTimeout(() => { /* your script */ }, 100) // 100 = arbitrary delay in ms

Or, more elegant & reliable: wrap your script in a .then() for closetPromise inside your Closet setup:

closetPromise.then(() => { /* your script */ })

I found this video helpful in understanding what’s going on with “asynchronous” JavaScript: https://www.youtube.com/watch?v=8aGhZQkoFbQ

closetPromise is defined as

closetPromise = import(`${getAnkiPrefix()}/__closet-0.5.3.js`);

which means it will resolve once the loading of the closet library finishes, not once Closet finished executing. The way to do that would be to use aftermath actions, like I mentioned once above. This also allows for things like setting the priority of these actions, so they won’t just be executed in insertion order. I’ll try to set up some documentation for this part of Closet, as it seems to be more in demand :slight_smile:

3 Likes

I want to show the answers blanked out like this, but I can’t get it to work.
example
Instead of beeing blanked out the answers are visible.

This is my code inside of "Closet Setup”:

const elements = closet.template.anki.getQaChildNodes();
const memory = chooseMemory("closet__1");
const filterManager = closet.FilterManager.make(preset, memory.map);

const output = [[elements, memory, filterManager]];

/* here goes the setup - change it to fit your own needs */
const blank = function(tag) {
  return [tag.values[0].replace(closet.unicodeAlphanumericPattern, '_')]
}

const blankOptions = {
  frontEllipser: blank,
  frontStylizer: closet.Stylizer.make({
    process: v => `<span style="color: cornflowerblue">${v}</span>`,
    separator: '',
  }),
}

filterManager.install(
    closet.recipes.shuffle({ tagname: "mix" }),
    closet.recipes.order({ tagname: "ord" }),
  closet.flashcard.recipes.cloze.show({
    tagname: 'c',
    ...blankOptions,
  }),
  closet.flashcard.recipes.cloze.hide({
    tagname: 'ch',
    ...blankOptions,
  }),
  closet.flashcard.recipes.cloze.reveal({
    tagname: 'cr',
    ...blankOptions,
  }),
    closet.flashcard.recipes.multipleChoice({
        tagname: "mc",
        defaultBehavior: closet.flashcard.behaviors.Show,
    }),
    closet.flashcard.recipes.sort({
        tagname: "sort",
        defaultBehavior: closet.flashcard.behaviors.Show,
    }),

    closet.browser.recipes.rect({
        tagname: "rect",
        defaultBehavior: closet.flashcard.behaviors.Show,
    }),
);;

Did I miss something out?

Hi,
When you use the Cloze function use ‘ch’ instead of c
eg What is the capital of Peru? [[ch1:: Lima]]
This should work, pls try this.

1 Like

Thank you for your suggestion but it’s still not working, whether I use ch1 or c. After experimenting a bit I figured there must be something wrong with one oft those functions:

const blank = function(tag) {
  return [tag.values[0].replace(closet.unicodeAlphanumericPattern, '_')]
}

const blankOptions = {
  frontEllipser: blank,
  frontStylizer: closet.Stylizer.make({
    process: v => `<span style="color: cornflowerblue">${v}</span>`,
    separator: '',
  }),
}

When I use those functions the tags of the answers on the card dissapear, so the code must be in working in some way at least. But neither are the answers blanked out nor are they conflowerblue. So maybe the code is outdated and I don’t know how to fix it or there is something wrong with my setup.