Closet For Anki [Official support]

It looks like you are adding a card under the Basic note type (see top left of your screenshot). Change this to whatever your ‘Closet’ template card is named.
Once / if that is already done, make sure that your ‘cmds1’ field has some text in it (active seems to be the standard text)

Anyone had luck making the ‘overlapping clozes’ code work? I’m struggling to get it through.

Edit:

@shallash and @joaoapenas Did you get this feature to work? I’ve been trying for a few days but cannot figure it out.

Img 1 - Desired output from website

Img 2 - og Code from website -> probably dated since it doesn't have closet.flashcards.recipes

Img 3 - Sample note text I've been working off of

Sample template
{{#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" })







closet.flashcard.recipes.setNumber({ tagname: "up", storeId: "flashcardActiveTop" }),
closet.flashcard.recipes.setNumber({ tagname: "down", storeId: "flashcardActiveBottom" }),
closet.flashcard.recipes.setNumber({ tagname: "top", storeId: "flashcardShowTop" }),
closet.flashcard.recipes.setNumber({ tagname: "bottom", storeId: "flashcardShowBottom" }),

closet.wrappers.product(closet.flashcard.recipes.setNumber, closet.flashcard.recipes.setNumber)({
tagname: "around",
optionsFirst: { storeId: "flashcardActiveTop" },
optionsSecond: { storeId: "flashcardActiveBottom" },
}),
closet.wrappers.product(closet.flashcard.recipes.setNumber, closet.flashcard.recipes.setNumber)({
tagname: "ctxt",
optionsFirst: { storeId: "flashcardShowTop" },
optionsSecond: { storeId: "flashcardShowBottom" },
}),









);
}
</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, })
);
closetPromise.then(() => {
if (!window.ONCLICK_SET) {
// only set keydown event listener once
window.addEventListener("click", (event) => {
if (event.target.nodeName === "rect") {
event.target.parentElement.classList.remove("is-front");
event.target.parentElement.classList.add("is-back");
}
})
window.addEventListener("keydown", (event) => {
if (event.code === "KeyG") {
const next = document.querySelector(".closet-rect.is-front");
if (next) {
next.classList.remove("is-front");
next.classList.add("is-back");
}
}
});
window.ONCLICK_SET = true;
}
});
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>```


This template is mine. Did you or @ankinium figure out what the issue was?
If not, let me know. It’ll help me if I know which template of mine you’re working with:

Template 1 - aka incremental image occlusion

Incremental Image Occlusion - AnkiWeb

or

Template 2 - Closet Note Template

https://ankiweb.net/shared/info/1671894111

May seem self-evident, but most people use template 2 so I like to be sure what you are using.

hey there,
unfortunately I couldn’t figure out what the issue was.
But as you already assumed correctly: I were using Template 2 :slight_smile:

regards

Hi,

It’s a cool and useful add-on for creating quizzes, please add a “switch box” or simply a small rectangle next to options to the checkmark or select the correct option. Like the below image of a quiz, it makes this add-on more professional and better performance.

Hi there,

don’t know if this has been asked before, but is there a way of some kind to convert the original Image occlusion enhanced format made by Glutanimate (addon code: 1374772155) into the incremental occlusion format?
Hypothetically, it would be possible by converting the svg-format of the occlusions by Glutanimate into code with coordinates just like the one in the IO field of the one by one format.

Does anyone have a solution for this?

1 Like

Hello, Is the a hotkey or shortcut on the keyboard for “reveal next” and/or “reveal all”? It is tedious to click through each closet image occlusion box one by one via the mouse. I have tried “G” “N” and “H” without success. Thanks

1 Like

hi,
is closet ONLY be need to be turned on when making new cards?
(i almost ONLY use closet for IO with reveal one by one function).

because it’s known to be conflicting with some other addons.

thanks

update 231201T005421

hi, i recently wanna make video’s screenshots/ppts into image occlusions,

for video, i’ll watch them and take snapshot /w vlc, which could result in filename-timestamp(timestamp inside the file).jpg etc

for ppts, obviously one could simply output them as sequence of .jpgs.

then, with the “media import” addon,
one could simply import the whole series into closet file type.

now, you can easily add mask by clicking the closet button.

and one can easily use “rect1” for all the masks on the same screenshot/ppt.

the resulting closet is 1 single file,
which will be more easy to handle than image occlusion’s several files (no matter which xx hide, xx show you chose).

the problem is that, when closet addon is turn-ed on,
adding notes ususally result in the error msg, which can be suppressed.

thanks

ps i am using windows’s 2.1.54 qt6 version.

I am getting the following error whenever I create a random new cloze deletion anywhere on anki.
But I get it only once after starting anki and then it doesn’t show up on the next clozes I make.
There is no functional issue using anki but just that it is a little annoying so I just ignore it hoping the developers will fix it in the next update.
Really appreciate your work developers, thank you!

Error
An error occurred. Please start Anki while holding down the shift key, which will temporarily disable the add-ons you have installed.
If the issue only occurs when add-ons are enabled, please use the Tools > Add-ons menu item to disable some add-ons and restart Anki, repeating until you discover the add-on that is causing the problem.
When you’ve discovered the add-on that is causing the problem, please report the issue to the add-on author.
Debug info:
Anki 23.10.1 (fac9e0ee) Python 3.9.15 Qt 6.6.0 PyQt 6.6.0
Platform: Windows-10-10.0.19045
Flags: frz=True ao=True sv=3
Add-ons, last update check: 2023-12-22 07:56:39
Add-ons possibly involved: ⁨Closet For Anki⁩

Caught exception:
Traceback (most recent call last):
File “aqt.webview”, line 50, in cmd
File “aqt.webview”, line 157, in onCmd
File “aqt.webview”, line 680, in onBridgeCmd
File “aqt.hooks”, line 5415, in call
File "C:\Users\shrut\AppData\Roaming\Anki2\addons21\272311064\src\webview
init
.py", line 90, in add_occlusion_messages
on_cloze(editor)
File “C:\Users\shrut\AppData\Roaming\Anki2\addons21\272311064\src\editor_init
.py”, line 182, in on_cloze
return _old(editor)
NameError: name ‘_old’ is not defined

Hi, thank you for your awesome add on. I have the following problem:

When I make a IO one by one card, when I hit “accept occlusions”, nothing happens, i.e. nothing is pasted in the “I0” field. I am wondering if this is an issue with the Closet add-on. If I restart Anki and make an IO one by one card it works fine, but as soon as I create another card with a different note type, the IO one by one feature seems to stop working again.

Your help is highly appreciated. Thank you

Hi, I use version 23.12.1-windowa-q6 and I would like to know how to use a keyboard shortcut for [[cl1::]] and [[cx1::]] instead of always having to type. There are shortcuts crl + 1, crl + 2. They don’t make the omission in my version of Anki or I don’t know how to use the shortcut. The crl+0 shortcut used in the image works perfectly, but the default shortcuts either don’t work in this version or I don’t know how to use them. This add-on is brilliant and very useful, thank you very much in advance.

@hengiesel @kleinerpirat (:sweat_smile:)

I am having the same issue.
Click to reveal works just fine but since my other note types are working with pressing a button to reveal (using a remote controller) it’s tedious to always switch between them…

<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" })







closet.flashcard.recipes.setNumber({ tagname: "up", storeId: "flashcardActiveTop" }),
closet.flashcard.recipes.setNumber({ tagname: "down", storeId: "flashcardActiveBottom" }),
closet.flashcard.recipes.setNumber({ tagname: "top", storeId: "flashcardShowTop" }),
closet.flashcard.recipes.setNumber({ tagname: "bottom", storeId: "flashcardShowBottom" }),

closet.wrappers.product(closet.flashcard.recipes.setNumber, closet.flashcard.recipes.setNumber)({
tagname: "around",
optionsFirst: { storeId: "flashcardActiveTop" },
optionsSecond: { storeId: "flashcardActiveBottom" },
}),
closet.wrappers.product(closet.flashcard.recipes.setNumber, closet.flashcard.recipes.setNumber)({
tagname: "ctxt",
optionsFirst: { storeId: "flashcardShowTop" },
optionsSecond: { storeId: "flashcardShowBottom" },
}),









);
}
</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, })
);
closetPromise.then(() => {
if (!window.ONCLICK_SET) {
// only set keydown event listener once
window.addEventListener("click", (event) => {
if (event.target.nodeName === "rect") {
event.target.parentElement.classList.remove("is-front");
event.target.parentElement.classList.add("is-back");
}
})
window.addEventListener("keydown", (event) => {
if (event.code === "KeyG") {
const next = document.querySelector(".closet-rect.is-front");
if (next) {
next.classList.remove("is-front");
next.classList.add("is-back");
}
}
});
window.ONCLICK_SET = true;
}
});
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>

Hey,
I tried using closet to create IO one-by-one cards with the Anking cards.
But it keeps saying:
"This note type does not seem to support Closet. Closet needs to be inserted into the card templates using Asset Manager, or you can download a note type which already supports Closet. "
Downloading the asset manager add did not work, because Anki could not install this add on

Can you maybe tell me how to fix this. Because according to Anking it should be working with this add-on.
Thanks

Anki Version: ⁨24.04.1 (ccd9ca1a)⁩

Thing is crashing as I go to make a new cloze deletion using a mouse hotkey:


Anki 24.06.3 (d678e393)  (ao)
Python 3.9.18 Qt 6.6.2 PyQt 6.6.1
Platform: Windows-10-10.0.22631

Traceback (most recent call last):
  File "aqt.webview", line 50, in cmd
  File "aqt.webview", line 160, in _onCmd
  File "aqt.webview", line 689, in _onBridgeCmd
  File "_aqt.hooks", line 5416, in __call__
  File "C:\Users\User\AppData\Roaming\Anki2\addons21\272311064\src\webview\__init__.py", line 90, in add_occlusion_messages
    on_cloze(editor)
  File "C:\Users\User\AppData\Roaming\Anki2\addons21\272311064\src\editor\__init__.py", line 182, in on_cloze
    return _old(editor)
NameError: name '_old' is not defined

===Add-ons (active)===
(add-on provided name [Add-on folder, installed at, version, is config changed])
AMBOSS add-on ['0amboss_addon', 2024-08-05T10:24, 'None', '']
Closet For Anki ['272311064', 2022-08-12T04:27, 'None', '']
Hint Hotkeys ['1844908621', 2023-04-14T10:13, 'None', '']

===IDs of active AnkiWeb add-ons===
1844908621 272311064

===Add-ons (inactive)===
(add-on provided name [Add-on folder, installed at, version, is config changed])
Anki magnifying glass mouse cursor ['842653376', 2022-12-31T07:44, 'None', mod]

Can someone translate?

Just a heads-up that the video link in add-on page (https://ankiweb.net/shared/info/272311064) is broken and the link to the website wants to install some dodgy browser add-on.

2 Likes

The add-on’s author is no longer active. I’ll try to reach to him to remove the link.

2 Likes