Instant type:answer feedback for Cloze Note Type

Is there a way to implement this code, that’s made for basic note type to be used in a cloze note type?

<script>
  (() => {
  /**
   * Type-in-the-answer live feedback for Anki
   * @author Matthias Metelka | @kleinerpirat
   */
    /* Helper functions to keep caret position while typing */
    const getCaretPosition = (contenteditable) => {
      contenteditable.focus();
      let _range = document.getSelection().getRangeAt(0);
      let range = _range.cloneRange();
      range.selectNodeContents(contenteditable);
      range.setEnd(_range.endContainer, _range.endOffset);
      return range.toString().length;
    };
    const setCaretPosition = (contenteditable, pos) => {
      contenteditable.focus();
      document.getSelection().collapse(contenteditable, pos);
    };

    /* Replace input with contenteditable div */
    const input = document.getElementById("typeans");
    const editable = document.createElement("div");
    editable.contentEditable = true;
    editable.id = "typeans";
    editable.innerHTML = input.value;
    input.replaceWith(editable);

    editable.addEventListener("input", () => {
      const pos = getCaretPosition(editable);
      /* Anki uses value attribute to compare answer */
      editable.value = editable.innerText;

      editable.innerHTML = (() => {
        const letters = editable.innerText.split("");
        const word = "{{Answer}}";
        let html = "";
        letters.forEach((letter, i) => {
          html += `<span class="${
            letter == word[i] ? "typeGood" : "typeBad"
          }">${letter}</span>`;
        });
        return html;
      })();
      setCaretPosition(editable, pos);
    });
  })();
</script>
[contenteditable]#typeans {
  border: thin solid var(--border);
  background: var(--frame-bg);
}

Maybe try this. It is a simplified version of the template I’m currently using:

Front Template:

{{cloze:Text}}

<!----------------------------------------------------->

<script>
var currentCloze = document.querySelectorAll(".cloze");

// Hide the original cloze elements and create input boxes
for (i = 0; i < currentCloze.length; i++) {
  currentCloze[i].style.display = "none";
  const input = document.createElement("input");
  input.type = "text";
  input.classList.add("custom_typebox");

  currentCloze[i].insertAdjacentElement("afterend", input);
}

var ctb = document.querySelectorAll('.custom_typebox');

// Iterate through custom_typebox elements to set placeholders and adjust widths
for (j = 0; j < ctb.length; j++) {
  var text = document.getElementsByClassName('cloze')[j].innerText; 
  var result = text.replace("[...]", "");
  var final_result = result.slice(1,-1);
  ctb[j].placeholder = final_result;
  
// Create a hidden element to calculate the actual width of the placeholder
  var hiddenElement = document.createElement('span');
  hiddenElement.innerText = final_result;
  hiddenElement.style.visibility = 'hidden';
  hiddenElement.style.whiteSpace = 'pre'; // Preserve white spaces

// Insert the hidden element into the page and use it to calculate the actual width of the placeholder
  document.body.appendChild(hiddenElement);
  var placeholderWidth = hiddenElement.getBoundingClientRect().width;

// Remove the hidden element from the page
  hiddenElement.parentNode.removeChild(hiddenElement);

// Set the width of the input field
  ctb[j].style.width = placeholderWidth + 9 + "px";
}
</script>

<!----------------------------------------------------->

<script>
   /**
   * Type-in-the-answer live feedback for Anki (vague variant)
   * @author Matthias Metelka | @kleinerpirat
   * Modified by @jcznk (for cloze compatibility, replacements, etc.)
   * Any errors or oversights are to be attributed to me
   * Only works with Anki ~ 2.1.56 and more recent versions.
   */

for (i = 0; i < 999; i++) {
  (() => {
    const input = document.getElementsByClassName('custom_typebox')[i];
    const cloze_content = document.getElementsByClassName('cloze')[i].getAttribute("data-cloze").replace(/<.*?>/g, "").replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&'); 
    const answer = cloze_content;

    if (input) {
      input.addEventListener("input", () => {
        input.classList.add("typed");
        input.classList.toggle(
          "goodsofar",
          input.value.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "") == answer.substring(0, input.value.length).toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "")
        );
        input.classList.toggle(
          "correct",
          input.value.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "") == answer.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "")
        );
      });
    }
  })();
}
</script>

Styling section:

/* ------------------------------------------------------ INPUT */

input[type=text]{
  color: black;
  background-color: unset;
  text-align: center;
  cursor: text;
  min-width: 150px;
  width: 150px;
  border-radius: 5px;
  border-style: solid; 
  border-width: 1px;
 }
input[type=text]::placeholder {
  opacity: 0.5;
}
.night_mode input[type=text]::placeholder {
  color: white;
}

/* -------------------------------------------------- TYPECHECK */

.custom_typebox.typed {
  background-color: #ff9999;
}
.custom_typebox.typed.goodsofar {
  background-color: #ffff7f;
}
.custom_typebox.typed.correct {
  background-color: #99ff99;
}
.custom_typebox.typed::placeholder {
  color: black !important;
}
1 Like

WoW! Thank you! Worked a lot!

*Getting used in the Forum

@jcznk, could you please help me? On the front card, I want to play audio only for c1, c2, and so on, but for some reason, the script returns blank for this TTS code:

{{tts de_DE voices=Apple_Anna_(Premium):cloze-only:Text}}

If I use it on the back card, it plays as expected.

Also, it plays c1 and c2 together. I haven’t figured out how to separate them, so that there’s one play for c1 and another for c2.

Unfortunately, I never used tts in conjunction with cloze deletions, so I have no familiarity with the code.

My first impression, though, is that both your requests are pretty difficult/impossible to accomplish, except maybe by using add-ons or complex workarounds.


On a side note, you might want to add this script to the Front template. I forgot it to include it last time. It allows the use of the Enter key to flip the card, even when the focus is inside a custom typebox.

<script>
for (let i = 0; i < ctb.length; i++) {
    ctb[i].addEventListener("keydown", function(event) {
        if (event.key === 'Enter') {
            if (typeof (pycmd) !== 'undefined') {
                ctb[i].blur(); 
                pycmd('ans'); 
            }
        }
    });
}
</script>

Amazing all your help!

I found something that worked as well.

 [anki:tts lang=de_DE voices=Apple_Anna_(Premium)] {{Artikel}}{{Wort_DE}}[/anki:tts]

I’ve been trying all day to convert the cloze script into a type-in-the-answer format, but unfortunately, I haven’t had any success.

By the way…do you have any type-in-the-answer card version of it?

I’m not sure if I’ve understood what you’re trying to achieve. Would you please elaborate your request?

I achieved with this:

<div class="class">
    <input type="text" class="ctb" placeholder="Type your answer here..." id="typeans">
    <!-- Assuming there's a replay button or other elements -->
</div>

<script>
  (() => {
    const input = document.getElementById("typeans");
    const answer = "{{Wort_DE}}"; // Replace "CorrectAnswer" with your actual answer string

    if (input) {
      input.addEventListener("input", () => {
        input.classList.add("typed");
        input.classList.toggle(
          "goodsofar",
          input.value == answer.substring(0, input.value.length)
        );
        input.classList.toggle(
          "correct",
          input.value == answer
        );
      });
    }
  })();
</script>

I wanted a input text box that I could use on phone and mac, with all the type-in-text feedback.

1 Like

The “Type-in-the-answer live feedback” script I posted does not work on 2.1.54. Some of the code it interacts with (mainly, the data-cloze attribute) was only introduced around version ~ 2.1.56.

EDIT: added a comment about this to the script.

1 Like

Yeh I’ve figured out so I’ve deleted my message, but you’ve been pretty fast on answer my stupid question. Thank you so much!

It’s work seamlessly, the only thing I could figure it out is the @a_ps last code, with the answer box, I mean the script work pretty well just like this so it’s not a real problem.