Clozes: A way to alter context of each card independently

Is your feature request related to a problem? Please describe.

Currently each cloze-card changes only part of the text - answer-text: it hides it on the front of the card and hightlights on the back.

I.e. we are lacking an ability to alter the context of the answer-text, depending on the part we are testing.

Describe the solution you’d like

As the most general and basic solution I suggest to make a way to mark text to be shown on cloze-card-X only, i.e.:

1. to be hidden from back side of all other cards.

2. to look like a normal text on front and back side of the cloze-card-X

The syntax can look like this:

{{#c3::text-which-is-hidden-on-both-sides-unless-it-is-card-3::placeholder-to-be-shown-on-other-cards}}

placeholder can be skipped, then empty string is used instead.

nesting logic works like this: if one {{#cX::}} tag is inside of another {{#cY::}} it is ignored for the higher level cloze (cY).

Usecase1: make some items optional when recalling high-level of a nested cloze.

ClozeText:

List of groceries:

{{c4::

a) {{c1::Apple}} {{#c4::(it’s fine to confuse this with Oranges)}}

b) {{c2::Milk}} {{#c4::(it’s fine to forget completely)}}

c) {{c3::Toilet paper}}

}}

or more complex example with colorcoding:

List of groceries:

{{c4::

a) {{c1::10 {{#c4::<span style="color: lightgray;">}}small{{#c4::</span>}}  Apples}}

b) {{c2::Milk, {{#c4::<span style="color: gray;">}}2%{{#c4::</span>}}, 1 litter}}

c) {{c3::Toilet paper}}

}}

Usecase2: it can replace hide-all addon:

ClozeText:

List of groceries:

a) {{#c1::{{c1::Apple}}::(food)}}

b) {{#c2::{{c2::Milk}}::(drinks)}}

c) {{#c3::{{c3::Toilet paper}}::…}}

Usecase3: it can limit amount of hints you have for the answer-text:

ClozeText:

Alphabet:

{{#c1::{{#c2::{{c1::A}}}}}}

{{#c1::{{#c2::{{#c3::{{c2::B}}}}}}}}

{{#c2::{{#c3::{{#c4::{{c3::C}}}}}}}}

{{#c3::{{#c4::{{#c5::{{c4::D}}}}}}}}

(and so on)

Describe alternatives you’ve considered

  1. There are conditional syntax in note templates, so we can add specific notes for each cloze, but:

а) this requires part of the text to be moved out of place, which kinda sticks out from the very idea of clozes.

b) it is not scalable. One would need to create 10+ hint fiels to cover all possible closes the hint can be required for.

2. One can simulate this with css/js and special tags/markings withing ClozeText. But this will make text field messy and may conflict with various addons/future features.

  1. One can use Basic note type for this, but this leads to a lot text duplicates and is hard to maintain/change/read

One can simulate this with css/js and special tags/markings withing ClozeText. But this will make text field messy and may conflict with various addons/future features.

Indeed, and that seems fine to me though? I have a similar use case in one of my cards and use CSS + javascript. The technique can in fact be used to make whatever conditional logic based on the cloze number, not just this. So it think it’s quite flexible and no addons are needed, thus, conflicts shouldn’t be a problem?

For your use case

For doing this specifically:

As the most general and basic solution I suggest to make a way to mark text to be shown on cloze-card-X only

Wrap any cloze-conditional text in a <span class="onlyX"> (X = the cloze number it should appear on). THe CSS hides all such elements by default. A JS snippet reads which cloze card is currently showing from the <body> class and reveals the matching ones.

Requirements for your template


Styling

/* Hide all onlyX elements initially, they will be displayed using javascript logic */
[class*="only"] {
    display: none;
}

Front Template

<div>{{cloze:ClozeText}}</div>

<script>
var observer = new MutationObserver(function () {
    var n = document.body.className.match(/card(\d+)/)?.[1];
    if (!n) return;
    document.querySelectorAll('[class*="only"]').forEach(function (el) {
        if (el.classList.contains("only" + n)) {
            el.style.display = "inline"; // could also change this to "block"
        }
    });
});
observer.observe(document.body, { attributes: true, attributeFilter: ["class"] });
</script>

The MutationObserver is needed on the front because Anki sets the cardX body class asynchronously. On Android this happens on the back as well.


Back Template

<div>{{cloze:ClozeText}}</div>

<script>
(function () {
    var isAndroid = /Android/i.test(navigator.userAgent);

    function applyOnlyClasses() {
        var n = document.body.className.match(/card(\d+)/)?.[1];
        if (!n) return;
        document.querySelectorAll('[class*="only"]').forEach(function (el) {
            if (el.classList.contains("only" + n)) {
                el.style.display = "inline";  // could also change this to "block"
            }
        });
    }

    if (isAndroid) {
        var observer = new MutationObserver(function () {
            applyOnlyClasses();
        });
        observer.observe(document.body, { attributes: true, attributeFilter: ["class"] });
    } else {
        // On desktop, the body class is already set by the time the back is rendered
        // so the observer isn't needed
        applyOnlyClasses();
    }
})();
</script>

Usage in your note field

Taking your grocery list example usecase1

ClozeText

List of groceries:
{{c4::
a) {{c1::Apple}} <span class="only4">(easy to confuse with Oranges)</span>
b) {{c2::Milk}} <span class="only4">(fine to forget completely)</span>
c) {{c3::Toilet paper}}
}}

Results should be like this

Card 1 front

List of groceries:
a) [...]
b) Milk
c) Toilet paper

Card 2 front

List of groceries:
a) Apples
b) [...]
c) Toilet paper

Card 3 front

List of groceries:
a) Apples
b) Milk
c) [...]

Card 1, 2, 3 back

List of groceries:
a) Apples
b) Milk
c) Toilet paper

Card 4
Front:

List of groceries:
[...]

Back:

List of groceries:
a) Apples (easy to confuse with Oranges)
b) Milk (fine to forget completely)
c) Toiler paper

Notes

  • Use <div class="onlyX"> with display: block in the CSS override instead of inline if you want block-level conditional content
  • Stack classes to show content on multiple cards: class="only1 only3" — just add an extra check or use a broader approach in the JS if you need this
  • Works with any number of clozes; no changes needed to the JS as you add more {{cN::...}} to the field

Thats very usefull, thank you!

A few points:

  1. spans dont work with complex html, which have divs, for instance. and divs will create unnesesary line breaks in a simle text. How do you solve it?
  2. Your point can be applied to the existence of the cloze-note-type itself. With js-css one can avoid using even {{c1::}} syntax.
  3. Basically, thanks to you, the code is ready and should be more or less easy to imbed it into the app. Why dont do it and have more clean and easy to handle Text field?

I don’t think I understand. Spans can contain whatever html you want so wrapping complex html with a span works fine. If you need bits and pieces displayed, then you will indeed need many spans which can be tedious to add to the text, is that what you meant?

The line-break is decided by whether the code sets display: block or display: inline on to the onlyX-classed element. That logic could be made more complex too. For example, if the onlyX element is a span → use inline, if a div → use block.

That’s not clear to me, but I think I have a better idea for your suggestion: inspired by the multi cloze syntax, adding a # before the c would make the text show only on the cards with the numbers after c.

For example, {{#c2,4,7::Hello World!}} would show Hello World! on cards 2, 4, and 7 only. It’d also create those cards, so there’s no need to use normal cloze markers.

yeah, i dont understand why but inline block handles divs no problem. What a code you’ e wrote!

i will try to add preprocessing there to reduce clutter in ClozeText and will start using it right away.

the point there is that one cloze shouldn’t kill text of the other if that text is clearly been used by other cloze.

your syntax is clearly more optimal for the mentioned use cases.

i will try to add preprocessing there to reduce clutter in ClozeText and will start using it right away.

Ah. I actually can’t. In fact I dont use phrases like (this word is optional to remember), I use color coding.

So my use case rather looks looks this:

List of groceries:

{{c4::

a) {{c1::10 {{#c4::<span style="color: lightgray;">}}small{{#c4::</span>}}  Apples}}

b) {{c2::Milk, {{#c4::<span style="color: gray;">}}2%{{#c4::</span>}}, 1 litter}}

c) {{c3::Toilet paper}}

}}

It really seems like you want the Free form note type.

Edit: you can modify that note type to not have back fields. Instead, the front field will be displayed on both sides of the card. Clozes can be achieved via CSS (the note type seems to already do something like that with <span class=cloze>…</span>).

In fact I do use a similar note type with ~20 fields atm. It is a basic note type with which I (optionally) simulate cloze functionality. I copy paste my text with little variations, some times I have to have 12 variations of the same text. It works great untill I need to alter it. And once I forget how much time it took me to create the note…

do you mean one can have a fully functioning cloze syntaxis in basic note type with this? I need to try this.

Here’s an example using red to denote clozes and lime to denote optional info:

  • Field 1:
    The sky is <span style="color: #ff0000">blue</span>.
    
  • Field 2:
    Apple are <span style="color: #ff0000">red</span>.
    
  • Field 3:
    <span style="color: #ff0000">
    <ul>
    <li>The sky is blue.</li>
    <li>Apples are <span style="color: #00ff00">usually</span> red.</li>
    </ul>
    </span>
    

Then the front/back templates will all look like this except for the numbers:

<div id=front>{{Text1}}</div>
<div id=back>{{Text1}}</div>

Styling:

#front span[style*="color: #ff0000"] {
    font-size: 0px;
}
#front span[style*="color: #ff0000"]:after {
    content: "[...]";
    color: lightblue;
    font-size: 20px;
}
#back span[style*="color: #ff0000"] {
    color: lightblue !important;
}

The resulted cards:


Further more, if you regularly want to have a list like the above, you can get rid of the third field above and automatically stitch the fields together in the third card type’s templates.

You can then add the optional lime text to the second field, and using CSS, make it show only on the third card.

Cool idea, thank you! I propably use it.
with custom html-tags it can be pretty compact.

but our conversation here moved to an issue, which is rather related to another topic:
https://forums.ankiweb.net/t/merge-basic-and-cloze-notes/21409?u=nap