Closet For Anki [Official support]

Specifically this part:

Towel_Sniffer had this inside his setup, for example:

@vonnr You’ll probably have something similar somewhere in your setup. Simply move that CSS (without the appendStyleTag function) to the “Styling” section in the Cards editor :v:t2:

1 Like

Pardon but I’m a bit confused because I’m not really into this ‘CSS-injecting’ thing. Where exactly is this located? I know where the template’s ‘Styling’ section is along with the Front Template and Back Template. However, I don’t know what to do first. So would you mind showing the proper steps into doing this?

Initially, Closet used JavaScript to insert styling (CSS). This is not ideal, since it’s best practice to separate styling from functionality (scripts) as much as possible.

With v0.5.0, Closet no longer inserts CSS via JavaScript, but old Closet setups (like yours) need to be updated manually now.

Background info

Closet is essentially a TypeScript (more strict form of JavaScript) document in your Anki media folder, that is accessed via your note templates. Within the note template, Closet has a section called closetUserLogic, that contains custom user setups. These are settings to let Closet “know” how to behave for each individual user. And that part is what we are talking about in this discussion.

Here’s the release comment on v0.5.0:

  • Closet now is separated into both a JavaScript library, and a default CSS file. If you insert Closet with Asset Manager, the CSS import will be automatically added to the note type. You can also add it manually via putting @import url("__closet-0.5.0.css"); into your note type styling. This allows you to easily overwrite the default Closet styling with only CSS, which was a much asked feature.

Updating your setup for Closet v0.5.0+

With Asset Manager

If you’re using Asset Manager, you can find the Closet setup by going to “Manage Note Types” → select your note type → hit “Asset Manager”. It looks like this:
image
On double click, an editor window will open and you can update your setup to no longer contain functions that append CSS (like the one by Towel_Sniffer I showed above). Just move that styling to the “Styling” section of your Cards editor.

With the Cards editor (not recommended)

Within your card templates, you can find a script called Closet Setup.
There should be a function called closetUserLogic:

function closetUserLogic(closet, preset, chooseMemory) {
            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 */
         ...
         ##YOUR SETUP##
         ...
        return output;
    }

Within that function, you can cut out all functions that insert styling and move that styling to the “Styling” section of your Cards editor. Don’t forget to add this line: @import url("__closet-0.5.0.css"); to the top of your Styling section as well (this is done automatically with Asset Manager).

An example

Allow me to show the changes that would be needed for this setup from the website. Because the examples on the website haven’t been updated yet, I can demonstrate the migration with them (quite a tall image, click to expand):

Alternative

If you’re having trouble with the “coding” aspect of this and you didn’t customize your Closet setup to begin with, then it would be easiest to just hit “Reset” in the Closet Setup script within Asset Manager
(be sure you first know how to restore backups):

2 Likes

Yeah, I’m just upset right now because I’ve tried the alternative and it didn’t work. I’m stuck with the first option; I’ll try to figure this out. Anyway, thank you for lending a hand and explaining briefly but sensibly how to fix the issue.

@kleinerpirat So yeah, thankfully the ‘Write To Templates’ did the trick. Always the smallest detail make the most headache’s. Nonetheless, I’m still thankful for the simple endeavor. I might actually want to learn ‘coding’ seriously from now on. It seems to me that everyone here is a pedagogy when it comes to coding. What would be the best language to start with?

Glad you got it sorted out!

I think a lot of Anki users learn these things by tinkering around with their note templates.

If you want to add some interactivity and fancy designs to your notes, the classic combination of JavaScript/HTML/CSS would be the best option. The latter two aren’t programming languages, but a markup language to define website content and a stylesheet for fancy looks.
These three are fundamental for web content, so if you ever need to create a website, you can use that knowledge you gained from Anki.

JavaScript, the major programming language for frontend web development, is very easy to learn (almost as easy as Python) and it’s very forgiving.

There are tons of great beginner tutorials on YouTube. I recommend learning the syntax and basic programming concepts and then just diving into the creation of a nice Anki note type. Learning by doing is the fastest way forward imo.

If you run into a problem or need an idea, a Google search (which will probably lead to StackOverflow) is the easiest way to get input.

Good luck on that endeavour!

1 Like

I couldn’t agree more with you on this advice. I am very fortunate for this random encounter as well. Had it been for my the issue, it wouldn’t have led to this. Thanks again for the imparted knowledge.

1 Like

I can’t get selective activation via on / off to work for some time now.
Not sure if there was a breaking change I missed or if it’s a bug. Closet executes fine when I’m not using those tags.


Here is my setup:

filterManager.install(
    closet.recipes.activate({ tagname: 'on', storeId: 'flashcardActive' }),
    closet.recipes.deactivate({ tagname: 'off', storeId: 'flashcardActive' }),
)

Here is the error that’s that occurs when I use on or off
(don’t know if it’s of much use in this compressed form though):

An error occured while executing Closet: TypeError: o.values.forEach is not a function
    at __closet-0.5.1.js:1
    at e.execute (__closet-0.5.1.js:1)
    at Object.execute (__closet-0.5.1.js:1)
    at Object.execute (__closet-0.5.1.js:1)
    at e.evaluate (__closet-0.5.1.js:1)
    at __closet-0.5.1.js:1
    at Array.flatMap (<anonymous>)
    at u (__closet-0.5.1.js:1)
    at t.e.render (__closet-0.5.1.js:1)
    at t.renderToNodes (__closet-0.5.1.js:1)

EDIT: Solved the issue. It seems that the problem was with the Parallel Build. I installed the Universal Build and tested it and Closet is indeed working.

Hi,

I can’t get closet to work at all in AnkiDroid.

My setup: Closet 0.5.3 in Anki Windows 2.1.41, AnkiDroid 2.15alpha44 (ParallelB),

I set up closet on the PC, on a fresh install of Anki 2.1.41, and made a note with Cmds fields and set up the card template as shown in the tutorial video. I tried it with [[mix]] tags and clozes, and it works in the PC. But when I installed the ankidroid 2.15alpha44 on my phone, synced it with the PC profile, closet functionality does not appear. The card displays the clozes and mix tags exactly as entered in the field. ie. The clozes appear as “[[c1::blah blah blah]]” instead of “[…]”. And the mix tags, instead of shuffling the contents, just appear as [[mix1::Apple, Orange, Pear]].

I checked that all the media is synced, and the main __closet-0.5.3.js file and the closet CSS file is present in AnkiDroid’s media folder.

What could be the problem?

1 Like

Awesome! I’m glad to see that it still works. I’ve been waiting for 2.15 for quite some time now. Once it gets closer to the next release, I will make sure that Closet (i.e. ESM support) still works.

I have the same problem with the flashcard feature, it seems to not work at all

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

 closet.recipes.activate({ tagname: 'on', storeId: 'flashcardActive' }),
 closet.recipes.deactivate({ tagname: 'off', storeId: 'flashcardActive' }),

 closet.wrappers.product(closet.recipes.setNumber, closet.recipes.setNumber)({
 tagname: 'around',
 optionsFirst: { storeId: 'flashcardActiveTop' },
 optionsSecond: { storeId: 'flashcardActiveBottom' },
 }),

 closet.recipes.setNumber({ tagname: 'up', storeId: 'flashcardActiveTop' }),
 closet.recipes.setNumber({ tagname: 'down', storeId: 'flashcardActiveBottom' }),

 closet.recipes.activate({ tagname: 'show', storeId: 'flashcardShow' }),
 closet.recipes.activate({ tagname: 'hide', storeId: 'flashcardHide' }),

 closet.recipes.setNumber({ tagname: 'top', storeId: 'flashcardShowTop' }),
 closet.recipes.setNumber({ tagname: 'bottom', storeId: 'flashcardShowBottom' }),

 closet.wrappers.product(closet.recipes.setNumber, closet.recipes.setNumber)({
 tagname: 'ctxt',
 optionsFirst: { storeId: 'flashcardShowTop' },
 optionsSecond: { storeId: 'flashcardShowBottom' },
 }),
)

the flashcard part does not work at all. I am following both this forum and the Coset for Anki github, but could not find the flashcard part working. Any help would great.

edit: the extensions are updated and the Closet for Anki is 0.5.3

1 Like

Henrik created an issue on GitHub right after I reported that problem:

I’m sure he’ll fix it with the next update. We just have to be patient, since he’s quite busy in the official Anki repo too :slight_smile:

1 Like

MathJax clozes


Regarding [Feature Request: Closet inside Mathjax · Issue #39 · hgiesel/closet · GitHub]

My Setup for jaxc clozes
// MathJax
const ellipsis = `\\ldots`
const wrapWithBrackets = (v) => `\\left [ ${v} \\right ]`
const inactiveEllipser = () => wrapWithBrackets(ellipsis)

const backStylizer = closet.Stylizer.make({
    processor: v => `{\\color{dodgerblue}{ ${v} }}`,
})

const backEllipser = (tag) => ([tag.values[0]]) // <- this fixes the problem

const frontStylizer = backStylizer.toStylizer({
    mapper: wrapWithBrackets,
})

const frontEllipser = (tag) => ([
    tag.values[1]
    ? tag.values[1]
    : ellipsis
])

filterManager.install(
    closet.flashcard.recipes.cloze({
        tagname: 'jaxc',
        inactiveEllipser, // <- not sure about this one tbh
        frontStylizer,
        frontEllipser,
        backStylizer,
        backEllipser, // <- added backEllipser to fix issue
    })
)

That setup only works on the front side for me. I get a proper <mjx-container> on the front, whereas on the back Closet inserts <span class="closet-cloze__answer"> and therefore MathJax won’t interpret it:

 \[\ce{{\color{dodgerblue}{ <span class="closet-cloze__answer">4</span> }}
P + {\color{dodgerblue}{ <span class="closet-cloze__answer">5</span> }}
O_2 \to {\color{dodgerblue}{ <span class="closet-cloze__answer"></span> }}
P_4O_{10}}\]
Screenshots

Front

Anki

Back

Anki2

I mean I could just sanitize the containing div with .innerText, but maybe this can be fixed within the Closet setup too?

Edit: Solved it (partly)!
I solved the main issue by adding a back ellipser (added it with comments to my setup above). Now I’m just confused why that isn’t included in the GitHub example setups :smiley:

const backEllipser = (tag) => ([tag.values[0]])

However, multi-card clozes still don’t work with this approach, because inactive clozes still get wrapped with spans (<span class="closet-cloze is-inactive">), stopping MathJax from interpreting their content.

Temporary fix

One way to circumvent that issue is to remove jaxc tags with indices other than the current card index before MathJax executes (using %idx of Asset Manager):

el.innerHTML = el.innerHTML.replace(/\[\[jaxc[^{{%idx}}]::(.*?)]]/g, "$1")

Heya guy from the github issue here, I don’t frequent this forum much but thought I’d post my current setup which works for mathjax clozes. The old setup is outdated and was frankly a hack, at least in so far that I don’t really understand closet all that well which is why it’s wrong :grimacing:

I don’t have much time at hand right now so it’ll just be a copy and paste but hopefully that will get you going if you were still having problems. In the past I had closet mathjax clozes working with multiple clozes on one card but that seems to be broken, not that much of a problem for me given that I prefer making cards which are so small that they only need one cloze anyway but maybe you can get it working again.

Using:
Closet 0.5.3
Anki ⁨2.1.43 (0fbae6bc)⁩

Here’s the closet code:

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 ellipsis = `\\ldots`
const wrapWithBrackets = (v) => `\\left [ ${v} \\right ]`
const inactiveEllipser = () => wrapWithBrackets(ellipsis)

const backStylizer = closet.Stylizer.make({
    processor: v => `{\\color{cornflowerblue}{ ${v} }}`,
})

const frontStylizer = backStylizer.toStylizer({
    mapper: wrapWithBrackets,
})

const frontEllipser = (tag) => ([
    tag.values[1]
    ? tag.values[1]
    : ellipsis
])

const jaxcOptions = {
    frontStylizer: frontStylizer,
    frontEllipser: frontEllipser,
    backStylizer: backStylizer,
    backEllipser: (tag) => [tag.values[0]],
    inactiveEllipser: inactiveEllipser,
}


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

    closet.flashcard.recipes.cloze({
        tagname: "c",
        defaultBehavior: closet.flashcard.behaviors.Show,
    }),
    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,
    }),
    closet.flashcard.recipes.cloze.show({
        tagname: 'jaxc',
        ...jaxcOptions,
    }),
)

Here’s a link to download the Note Type (exported as a .apkg package) I use together with this closet code:

This produces the following result:

Card:

This should work
\([[jaxc1::\varphi]] \rightarrow [[jaxc1::\psi]]\)

Front:
image

Back:
image

2 Likes

That’s very nice of you @sandersantema to post your setup in order to help me :slight_smile:

I actually hacked my setup for multi-card use with this little unpolished script here:

MathJax Multi-Card Fix
function clearJax(element) {
   var string = element.innerHTML
   if(element.innerHTML.match(/\[\[jaxc\d::/)) element.innerHTML = clearJaxRec(string, [])
}

function clearJaxRec(string, stack) {
   let re = /(\[\[jaxc\d+::)|]]/g

   if ((match = re.exec(string)) !== null) {
      let start = match.index
      let end = re.lastIndex

      //opening bracket
      if (match[1] != null) {
         if (match[1] == "[[jaxc{{%idx}}::") {
            stack.push(true)
            return string.substring(0, start + match[1].length)
                   + clearJaxRec(string.substring(end), stack)
         }
         else stack.push(false)
      }
      // closing bracket
      else {
         if (stack.pop()) {
            return string.substring(0, start + match[0].length) + clearJaxRec(string.substring(end), stack)
         }
      }
      return string.substring(0, start) + clearJaxRec(string.substring(end), stack)
   }
   return string
}

If you pass a node containing your clozes as argument to this function, it will strip any jaxc tags with indices other than the current card. I just use {{%idx}} of Asset Manager for that.

The function needs to be executed before Closet, but I think a normal function call within your template will do that.

Caveat: My script doesn’t cope with this:
\[[[jaxc... → that would have to be changed to \[ [[jaxc....
But that’s the only downside I noticed and I couldn’t be bothered to fix the script :sweat_smile:

1 Like

My programming knowledge is zero, So please excuse me if the doubt is silly… Thank you @hengiesel for such a wonderful addon.

my problem.>>> I am not able to create a card with image occlusion.
What I did so far? >>> watched your youtube video and gone through the closetengine.com
After some trial and error, I was able to manage the cloze function but not image occlusion

Details of the problem>>>

  1. copied image to a multi-card as made in the youtube tutorial [ no problem detected]

  2. clicked the small square button on the toolbar and made small rectangles to occlude some of the details

  3. clicked right button and clicked “accept occlusion”

  4. clicked right button in the ‘Cmdsall’ space and pasted… >>[ problem>>> nothing gets pasted]

  5. [second try] i tried to make small squares by directly writing the code [[recthc1:: 100,100,110,110]]. but this is not getting reflected in the card when reviewing…

here i am posting my multicard java script

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 */
globalThis.target = closet.browser.recipes.occlusionEditor()(filterManager.registrar)

filterManager.install(
closet.recipes.shuffle({ tagname: “mix” }),
closet.recipes.order({ tagname: “ord” }),
/here is where I made the changes/

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

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

/My experiments ends here/
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,
}),

/** Make occlusions */

);;

Please tell me what changes I should make //
Also
Ever since i used asset manager and changed the Basic card as explained in the Youtube tutorial, in ankidoid all Basic cards are seen as blank… no texts are visible… but when we take edit window we can see the texts
please tell if there is any way we can make this addon work in ankidroid as welll

Thanks for your valuable time…

my setup

  1. ubuntu 18.04
    2)anki 2.1.43
  2. ankidoid- 2.14.6

The YouTube tutorial is a bit outdated, but nonetheless useful to get the basics.

This is where current Closet differs from the video. You can now designate a field for Closet to automatically insert the occlusion commands when you press “accept occlusions”. All you need to do is add a 0 at the end of that specific field’s name.

e.g.:

  • cmds 0 ← this will get occlusion commands
  • cmds 1
  • cmds 2

That is the default behaviour now, but you could theoretically change it back:

image

This didn’t work because [[recthc]] is not a tag specified in your setup.

[[rect1::100,100,100,100]] should work though.

The AnkiDroid visibility issue is due to something in the _closet.css file. You can find it in your media folder. I don’t remember where exactly, but at the start of the file, I changed a display: hidden; to display: block;

Tinker around a bit and you’ll find it, I’m sure.
Also, make sure you are using AnkiDroid 2.15 (still in alpha), as closet doesn’t support 2.14 and below.

Edit: AnkiDroid 2.15 is now in Beta, and I didn’t face any visibility issues there when I installed everything on a fresh profile, so maybe try that if possible.

Hi,
Could you help me out with installing new functions for closet?

Here’s what I tried:

  1. Created a functioning closet note type with fields, Front, Back, and Cmds0 through Cmds4.
  2. Copied the setup code from here.
  3. Added a blank script in the asset manager for my note type, pasted the code and wrapped it like said in the video
function incReveal(filterManager) {
[copied code] 
}
  1. At the bottom of the Script called Closet Setup, I added incReveal(filterManager)
  2. Write to Templates

But doing the above steps, instead of adding the desired functions, makes Closet completely unfunctional.

I can see that closet has changed a bit since the video came out, so what exactly should I be doing to add new functions?

Put this beneath the default closet setup:

Setup
/** Click to reveal cloze */

const removeObscure = function(event) {
  if (event.currentTarget.classList.contains('cl--obscure-clickable')) {
    event.currentTarget.classList.remove('cl--obscure')
    event.currentTarget.classList.remove('cl--obscure-hint')
    event.currentTarget.classList.remove('cl--obscure-fix')
  }
}

const wrappedClozeShow = closet.wrappers.aftermath(closet.flashcard.recipes.cloze.show, () => {
  document.querySelectorAll('.cl--obscure')
  .forEach(tag => {
    tag.addEventListener('click', removeObscure, {
      once: true,
    })
  })
})

const obscureAndClick = (t) => {
  return [`<span class="cl--obscure cl--obscure-hint cl--obscure-clickable">${t.values[0]}</span>`]
}

const obscureAndClickFix = (t) => {
  return [`<span class="cl--obscure cl--obscure-fix cl--obscure-clickable"><span>${t.values[0]}</span></span>`]
}

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

filterManager.install(
  wrappedClozeShow({
    tagname: 'c',
    frontEllipser: obscureAndClick,
    frontStylizer: frontStylizer,
  }),

  wrappedClozeShow({
    tagname: 'cx',
    frontEllipser: obscureAndClickFix,
    frontStylizer: frontStylizer,
  }),
)

and put the CSS into the Styling section of the card template:

CSS
.cl--obscure-hint {
  filter: blur(0.25em);
}
.cl--obscure-fix::after {
  content: 'XXXXXXXXXX';
  filter: blur(0.25em);
}
.cl--obscure-fix > span {
  display: none;
}
.closet-cloze__answer {
  color: cornflowerblue;
}

Explanation:

4 Likes