Closet For Anki [Official support]

Does anyone else use the Edit Field During Review Cloze addon? It just updated and is no longer working with my closet template (changes won’t save and I can’t resize images anymore). I’m still on Anki 2.1.47 though, so if yours is working, can you say what Anki version you’re using?

2.1.48 working.

Hey there, I don’t know if this has been asked already (sorry if that’s the case)
I’m using Closet for image occlusions. It works so far but when a card comes up all occlusions are often selected (like in the image below). However if I answer the card or go into edit mode and just go back again (without changing anything), it works like intended with incremental occlusions. Any idea why?

at first encounter:

after entering edit-mode or switching back to the card after answearing it

I don’t think I ever had any built-in support for incremental occlusions. I’m not really sure who wrote this, maybe @kleinerpirat knows more :thinking:

3 Likes

@Ankili You can dm me your current template and I’ll investigate once I find the time.

2 Likes

Is it possible to copy/paste some of Closet features to other note types? For instance just the mixing feature ("[mix1::]]") so already existing cards can remain in their intervals and I don’t have to create them as a new Closet card? :slight_smile:
Thanks in advance!

Is there a way to preserve the order of multiple choices? I have a paragraph which contains several multiple choices in order, so I wonder if it’s possible to only shuffle the order of items within the same [[mc::]], while preserving the sequence of original [[mc::]]s.

Nice to meet you.
I’m suffering from the same phenomenon as @Ankili.
If you don’t mind, please let me know the solution.

The template I used is a deck template shared by Anki.

The version I’m using is 2.1.49.
I reverted back to 2.1.47 to try it out.
I have disabled all add-ons except Closet.
But it doesn’t work.

Can you please help me?
Thanks for reading this far.

Doesn’t this support for an image with long height? If I tried to use such images for occlusion, they are resized on editing fields so that it makes me harder to make the occlusions on those images.
(On reviewing, they appear just as it is, not resized)
This doens’t happens only when I deactivated this add-on.



Animation

I downloaded the add-on, but when I try running it I am getting an error message saying the note type doesn’t support Closet. What should I do?

Hm, it might be, that there is indeed a max-height applied to the images. If possibly you should crop the image for now.

You need to insert the Closet JS first into the notetype. This is not easily done by somebody without some web-dev background. You might have more luck with Ankings pre-made closet notetypes available here

hmm unfortunately I could’t get the incremental reveal by pressing a key to work.

Front:

{{#cmds1}}
<div id="extras" class="extras">{{cmds0}} {{cmds1}}</div>

<div id="content" class="content {{Tags}}">
        <div id="bottom" class="top container">
                <div class="main">{{edit:block}}</div>
        </div>
</div>

<div id="tags">{{clickable:Tags}}</div>
{{/cmds1}}

<div id="anki-am" data-name="Assets by ASSET MANAGER" data-version="2.1">
  <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="Ruby Support" data-version="v0.1">
    function rubySupport(filterManager) {
      const rubyStylizer = closet.Stylizer.make({
        separator: "",
        mapper: (v, i) => (i === 0 ? `<rb>${v}</rb>` : `<rt>${v}</rt>`),
        processor: (v) => `<ruby>${v}</ruby>`,
      });
      filterManager.install(
        closet.recipes.stylize({
          tagname: "rb",
          separator: {
            sep: "::",
            max: 2,
          },
          stylizer: rubyStylizer,
        })
      );
    }
  </script>
  <script data-name="Flashcard Features" data-version="v0.1">
    function flashcardFeatures(filterManager) {
      filterManager.install(
        closet.recipes.activate({ tagname: "on", storeId: "flashcardActive" }),
        closet.recipes.deactivate({ tagname: "off", storeId: "flashcardActive" }),
        closet.recipes.activate({ tagname: "show", storeId: "flashcardShow" }),
        closet.recipes.activate({ tagname: "hide", storeId: "flashcardHide" })
      );
    }
  </script>
  <script data-name="Closet Setup" data-version="v0.1">
    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],
      ]; /** 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>`,
      });
      /*here goes the setup - change it to fit your own needs*/
      filterManager.install(
        closet.recipes.shuffle({ tagname: "mix" }),
        closet.recipes.order({ tagname: "ord", }),
        wrappedClozeShow({ tagname: "cl", frontEllipser: obscureAndClick, frontStylizer: frontStylizer, }),
        wrappedClozeShow({ tagname: "cx", frontEllipser: obscureAndClickFix, frontStylizer: frontStylizer, }),
        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, })
      );
      if (!window.ONCLICK_SET) {
        window.addEventListener("keydown", (event) => {
          if (event.code === "KeyG") {
            const rect = document.querySelector(".closet-rect.is-front");
            if (rect) {
              reveal(rect);
            }
          }
        });
        window.ONCLICK_SET = true;
      }
            closetPromise.then(() => {   
                if ((rect = document.querySelector(".closet-rect.is-active")) != null) incrementalIO(rect)
                function incrementalIO(first) {
                    for (rect of document.querySelectorAll(".closet-rect.is-active")) {
                        rect.classList.remove("is-active")
                    }
                    activate(first)
                }
                function activate(rect) {
                    rect.classList.add("is-active")
                    rect.addEventListener("click", reveal)
                }
                function reveal() {
                    this.classList.remove("is-front")
                    this.classList.add("is-back")
                    if ((next = this.nextElementSibling) != null) {
                        activate(next)
                    }
                }
            });
      return output;
    }
    var getAnkiPrefix = () =>
      globalThis.ankiPlatform === "desktop"
        ? ""
        : globalThis.AnkiDroidJS
        ? "https://appassets.androidplatform.net"
        : ".";
    var closetPromise = import(`${getAnkiPrefix()}/__closet-0.5.3.js`);
    closetPromise
      .then(
        ({ closet }) =>
          closet.template.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)
      );
    if (globalThis.onUpdateHook) {
      onUpdateHook.push(() => closetPromise);
    }
  </script>
</div>

Back:

{{#cmds1}}
<div id="extras" class="extras">{{cmds0}} {{cmds1}}</div>

<div id="content" class="content {{Tags}}">
        <div id="bottom" class="top container">
                <div class="main">{{edit:block}}</div>
        </div>
</div>

<div id="content" class="content {{Tags}}">
        <div id="bottom" class="bottom container">
                <div class="main">{{edit:notes}}</div>
        </div>
</div>

<div id="tags">{{clickable:Tags}}</div>
{{/cmds1}}

<div id="anki-am" data-name="Assets by ASSET MANAGER" data-version="2.1">
    <script data-name="Button Toggle" data-version="v0.1">
        var toggle = document.getElementById('toggle')
        var toggleShow = document.getElementById('toggle_show')

        var content = document.getElementById('content')
        var contentShow = document.getElementById('content_show')

        toggle.addEventListener('click', () => {
            content.style.display = 'none'
            contentShow.style.display = 'grid'
        })

        toggleShow.addEventListener('click', () => {
            content.style.display = 'grid'
            contentShow.style.display = 'none'
        })

        document.addEventListener('keyup', (event) => {
            if (event.keyCode === 73 /* 'KeyI' */) {
                const saveDisplay = window.getComputedStyle(content).getPropertyValue('display')
                content.style.display = window.getComputedStyle(contentShow).getPropertyValue('display')
                contentShow.style.display = saveDisplay
            }
        })

    </script>
    <script data-name="Ruby Support" data-version="v0.1">
        function rubySupport(filterManager) {

        const rubyStylizer = closet.Stylizer.make({
          separator: '',
          mapper: (v, i) => i === 0
            ? `<rb>${v}</rb>`
            : `<rt>${v}</rt>`,
          processor: (v) => `<ruby>${v}</ruby>`,
        })

        filterManager.install(closet.recipes.stylize({ 
            tagname: 'rb',
            separator: { sep: '::', max: 2 },
            stylizer: rubyStylizer,
        }))

        }
    </script>
    <script data-name="Flashcard Features" data-version="v0.1">
        function flashcardFeatures(filterManager) {

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

        }
    </script>
    <script data-name="Closet Setup" data-version="v0.1">
        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]];

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

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

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

  wrappedClozeShow({
    tagname: 'cl',
    frontEllipser: obscureAndClick,
    frontStylizer: frontStylizer,
  }),

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

                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,
                }),
            );;;
            return output;
        }

        var getAnkiPrefix = () =>
            globalThis.ankiPlatform === "desktop"
                ? ""
                : globalThis.AnkiDroidJS
                ? "https://appassets.androidplatform.net"
                : ".";

        var closetPromise = import(`${getAnkiPrefix()}/__closet-0.5.3.js`);
        closetPromise
            .then(
                ({ closet }) =>
                    closet.template.anki.initialize(
                        closet,
                        closetUserLogic,
                        "{{Card}}",
                        "{{Tags}}",
                        "back",
                    ),
                (error) => console.log("An error occured while loading Closet:", error),
            )
            .catch((error) =>
                console.log("An error occured while executing Closet:", error),
            );

        if (globalThis.onUpdateHook) {
            onUpdateHook.push(() => closetPromise);
        }
    </script>
</div>

Anyone any suggestions? :slight_smile:

I got the same error with Anking. Here is the whole error message: This note type does not seem to support Closet. Closet needs to be inserted into the card template using Asset Manager, or you can download a note type which already supports Closet.
This is happening when I click the image occlusion button. I feel like I am missing a basic step here.

Did you install the notetype by downloading the add-on?

Hi, I have a bunch of cards(waay more than a bunch ) with note type image occlusion enhanced, that i would like to change to closet, I’m not tech savy, how can I do this?
note I don’t have asset manager, and I have some of the Anking settings installed and im using a mac, Kindly advise

@kleinerpirat @hengiesel Sorry for the @, but just wanted to get attention with something potentially quick. Would be happy to remunerate if that’s what it takes. Just let me know by PM.

Is there a way to change the color of “[…]” in Styling? I managed to change the color of the revealed answer, but not the […].


Also, is there a shortcut for generating Closet clozes with the same number (no increment)? Ctrl+Shift+C just makes sequential clozes. FWIW, I have the Shortcut addon mapping no-increment cloze to Ctrl+Shift+D (which is easier than Ctrl+Shift+Alt+C to me).

Running Anki 2.1.44, Closet 0.5.3

Thanks!

.closet-cloze {
  color: #DE3163;
}

Regarding the shortcut, I’m not sure. It doesn’t work at all on my 2.1.49 install. But as far as I remember Ctrl+Shift+Alt+C to keep the index has worked in the past.

That CSS didn’t change the color of the […] for me. Instead, it changed the color of inactive clozes (e.g., Cloze 2 is now the specified color on Card 1 with […] for Cloze 1.


Yeah, the no-increment thing doesn’t work for me either.

Also ran into a new issue. Cards that aren’t Card 1 will display “Card” followed by the # at the end (e.g., that “Card 3” in the images above). Looking to hide or get rid of that if possible. —
Update: had to delete

<div id="cardtype">{{Card}}</div>
<div id="tags">{{Tags}}</div>

on the front and back. Hope there’s a way to stop it from automatically showing up.

I feel bad using up your time. Can I actually send you the card and commission you to alter it? I’m just hoping to marry the Anking card functionality/interface with the full power of the Closet engine.

I started with the closet-r template from the Closet deck and used trial-error to bring in some Anking features (since I have zero coding knowledge).

Hi, for some reason the red border won’t show up when trying to create a IO one by one card with AnKing. Here’s a picture of the add ons I use if there’s any weird conflict going on.

Hey there, I was looking to learn more about this addon but I think that the website may be down.