Closet For Anki [Official support]

  1. It would be possible to click on each cloze that you want, included the hided ones, and be able to reveal it?

  2. It would be possible to edit the ellipsers in a more customizable and easy way (and the same for the text of the cloze).?

Thanks in advance

@hengiesel @kleinerpirat I’ve realized that when I download the closet add-on from ankiweb, I get the 0.2.4 version, how can I install the newer one?

I’m using Anki on iMac → Version 2.1.35 (84dcaa86)
Python 3.8.0 Qt 5.14.2 PyQt 5.14.2

To get the newest version of Closet you need to run Anki 2.1.41 or above.

List of supported versions on the add-on page:

Supported Anki versions:
2.1.31-2.1.33 (updated 2020-10-06)
2.1.34-2.1.35 (updated 2020-11-16)
2.1.36-2.1.40 (updated 2020-12-22)
2.1.41-2.1.42+ (updated 2021-04-13)
1 Like

I’m afraid that’s intended for now. Originally the YouTube served as an installation help, even though that’s also outdated by now (even though it can still be helpful for the steps below). Right now my priority is to assist to get Anki 2.15 on the way. Closet will come after. A short, updated way on installation is:

  1. Install Closet and Asset Manager add-on. (Anki Persistence add-on no longer needed)
  2. Setup Closet to your liking within Asset Manager.
  3. Insert Closet into the Template by using Asset Manager.

Short answer: yes. There’s an example on the Closet homepage, which goes into that direction, even though it does not for hidden clozes, even though you could also set that up. I’ll try to remember this one.

1 Like

I’ve just updated to Anki 2.1.42, so I’m now using the last version of closet (0.5.3) and asset manager. I see that is easier to modify the styles now by changing things on the style section of the templates, and that the functions that I had on the previous closet 0.2.4 are differently written.

I have no idea about coding, but I was able to figure out how to add the commands to work with “ch” and “cr” clozes, however, one of the most important functions to me was to use things like [[show::ch2]] in certain cases, but this code doesn’t seem to work @kleinerpirat @hengiesel

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

If I put it on my closet setup I see the flashcard with all the “closet code” like in the editor window (Example. → The car is [[ch1::red]] and [[ch2::big]]).

What I have to do in order to have this working like in the previous version of closet?

Another thing, do you know what are the “tags” or the “formulas” to be able to modify the style of the various closet elements using css?

And last, it’s possible to enable a function to auto-scroll to the concrete cloze you are asked for? It would be very helpful for big texts and small screens (like mobiles).

Thanks in advance

First of all, respect for getting things working with the new version! Without coding experience, that is not an easy task :mechanical_arm:

This is a known issue, but it hasn’t been fixed yet:

The easiest way to get the classes of Closet elements is to use the trusty AnkiWebView Inspector. With that add-on you can analyze (almost) every web view of Anki with the Chrome web tools:

That symbol image in the top left corner activates the element inspector. With that you can select HTML elements, that will then be shown in the “Elements” tab.

Yes. As you can see in the image above, the active cloze will have a different class than the inactive ones. You could simply select the first active cloze and scroll to that:

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

That timeout should in theory make this execute after Closet… But I have my doubts because Closet executes asynchronously. Otherwise, set that 0 at the end to 100.
I’m on mobile right now so I can’t test this.

For iOS I’m using this smooth scroll polyfill, which you can insert as a JavaScript snippet with Asset Manager (if you want a smooth animation on AnkiMobile).

I didn’t have much time to test this for you, but it should work :crossed_fingers:

1 Like

Thank you so much for your answer, however, I don’t know where to put that code in order to have the automatic scroll function. Did I have to put in on the template directly (on both sides of the flashcard) or I have to wrote it in my closet setup (in that case, where I have to put it?).

On the other hand, I think I’ll return to an older setup of closet to be able to use that function until the problem gets solved.

I also have to say that I was testing, independently of this function, how works the cloze system created by closet on my Ankidroid (I always use the last version of Ankidroid), and when I preview a card with, for example, 3 hidden clozes, sometimes, I have to click on “reveal answer” several times to see the answer, I’m not sure if it happens too reviewing “officially” -doing real reviews of cards while studying- (all this using the new Closet 0.5.3).

EDIT → Yes, with Ankidroid doing the traditional reviews, the cloze system works only in some occasions.

I’ve been using (and abusing) closet for a while now. I’ve made it do everything I can possibly make it do, but I am curious about the block tags.

In the github issue regarding this, hengiesel showed a syntax like [[#mixl::attr1::attr2]]<attr3 stripped by one layer of <br> (if exists)>[[/mixl]]. And I’m wondering if this syntax will actually be implemented. As I am using it currently, I have to do something like this instead:

[[#code]]javascript::
for (let i = 0; i < j; i++) {
  // ...
}
[[/code]]

And I think I would like to type

[[#code::javascript]]
for (let i = 0; i < j; i++) {
  // ...
}
[[/code]]

instead. However, it does work for me, so I have no serious problem with the addon. Thank you for this! =)

2 Likes

This should actually work. Block tags are not an alternative to inline tags, but rather I extended tags to support both inline and block. You only have to separate the optics used for them accordingly. So sth. like:

registrar.register(tagname, filter, {
    inlineOptics: [separated({ sep: "::", max: 1 })],
})

And then inside your filter, you can explicitly use blockValues / inlineValues / blockText / inlineText. Tell me if that works for you :slight_smile:

1 Like

EDIT:
After messing around more for a bit, I discovered that I can use the apply recipe instead of the style recipe as it gives the separated values. So I guess that answers my question. Thank you!

EDIT 2: Now my second question is, how to separate the optics? I currently have the solution as something like this

filterManager.install(
  closet.recipes.apply({
    tagname: "code",
    optics: [closet.template.optics.separated({ sep: "::", max: 2, trim: true })],
    apply: (e) => {
      const { hasBlock, hasInline, inlineValues, blockValues } = e;

      if (hasBlock) {
        const [lang, startLine = 1] = inlineValues;
        const [code] = blockValues;

        return (
          `<pre lazyload style="opacity: 0" class="prettyprint linenums:${startLine}">` +
          `<code class="${lang} language-${lang}">${code}</code>` +
          `</pre>`
        );
      } else if (hasInline) {
        return null;
      } else {
        return null;
      }
    },
  }),
);

After messing with things around, I must admit that I have the slightest clue on how to actually apply this. Following my [[#code]][[/code]] example, can you use this code and apply it? Sorry for the bother :sweat_smile:
<!-- code – >
Also, from this example, what would be a better way to deal with the arguments instead of using this hack of "0-123456789"?

I am using the below-mentioned code. My objective is to make the cloze hide the default option.
But it is not functioning… Can somebody tell me where the problem is ?

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


           closet.flashcard.recipes.cloze({
                tagname: 'c',
                defaultBehavior: closet.flashcard.behaviors.Hide,
            }),

Thank you :smiley: :smiley:

The code for autoscroll needs to be copied at the end of the code using asset manager.

tools>>managenotetypes>>commands>>assets>>closetsetup

Hey, I didn’t see that you edited our post:

This is what the apply recipe does: registrar.register(tagname, apply, { optics });
From this, you can infer this:

filterManager.registrar.register("code", (e) => {
  const { hasBlock, hasInline, inlineValues, blockValues } = e;

  if (hasBlock) {
    const [lang, startLine = 1] = inlineValues;
    const [code] = blockValues;

    return (
      `<pre lazyload style="opacity: 0" class="prettyprint linenums:${startLine}">` +
      `<code class="${lang} language-${lang}">${code}</code>` +
      `</pre>`
    );
  } else if (hasInline) {
    return null;
  } else {
    return null;
  }
}, { inlineOptics: [closet.template.optics.separated({ sep: "::", max: 2, trim: true })] });
1 Like

Try removing the first three lines. Only this part should be necessary:

closet.flashcard.recipes.cloze({
    tagname: 'c',
    defaultBehavior: closet.flashcard.behaviors.Hide,
})

Hi, Thanks for the reply

used the code as you said… Not working

the cloze is highlighted and shown… i am giving the whole code below…

Thanks for your great effort…This addon has increased the power of anki multifold…

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.show({tagname: "c"}),
closet.flashcard.recipes.cloze.reveal({tagname: "cr"}),    
closet.flashcard.recipes.cloze.hide({tagname: "ch"}),

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

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

closet.flashcard.recipes.cloze({
tagname: ‘c’,
defaultBehavior: closet.flashcard.behaviors.Hide,
}),

closet.browser.recipes.rect.show({tagname: 'rect'}),

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

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

/* i added here */
closet.browser.recipes.rect({
tagname: ‘rect’,
defaultBehavior: closet.flashcard.behaviors.Hide,
}),

/* i added here ends here*/

);;

/** 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,
}),
)

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

When tried to execute this code the below mentioned error is shown

SyntaxError: 1:0: Unexpected token: operator (<)

Thank you and have a great day

Did you place the script inside a script tag? Try removing the script tags, as it seems to be parsing the included script tags as part of javascript.

<script> //This is getting parsed as well. Try removing them in the script.
...
</script>
2 Likes

@hengiesel , hi

Can you tell what the problem is…

Thanks for your time .

Hi Hengiesel,

I configured a cloze overlapping card type using closet. For testing purposes, it contains 10 lines, therefore i created 10 cards in the card template.
I noticed, that always 10 cards are created, no matter how many clozes are actually on the card. I understand, that it’s because there are no conditions for the card generation.

Is there a way to have a card type with x amount of cards (representing the number of maximum clozes), where only the ones actually necessary are created?
E.g. my card type has 10 card templates, but on the card only [[ch1]] - [[ch5]] are used, it will only create 5 cards instead of 10 (5 of them intended, 5 of them only showing …

That would be such a huge improvement for my plan of integration of closet into my card templates. :slight_smile:

Thank you in advance and thanks for the great contribution to the anki community!

If the front side of a card is empty, it won’t be created. You can use this behaviour for conditional card creation.

Suppose your command fields are named cmds1, cmds2, etc. - wrap your front template with these tags in Asset Manager:

{{#cmds{{%cardidx}}}}
<!--Your front template-->
{{/cmds{{%cardidx}}}}

Only if the corresponding cmds field is filled, a card will be created.

For more detailed information, see the (partly outdated) YouTube tutorial: