Issue with shortcut for a button


I have an issue where I’ve assigned a shortcut to activate a button while I’m reviewing Anki cards. The button works perfectly and there are no issues with respect to activating it. The button is a ‘show all cloze’ button that reveals all clozes in a note.

However… it only works every 2nd card. I have NO IDEA how this is even occurring, so I was wondering if someone could help me?

I’ll paste both the front and the back template code. My front template has code for cloze overlapping, which is why it is very long. This may be contributing to the issue. The thing is, I’m kind of forced to use coding for cloze overlapping since the add on for it is now behind a paywall and other addons aren’t too great. So while I’ve now got cloze overlapping as part of my notes, I’m encountering an issue with the button I’ve implemented and don’t know how to resolve it.

Front template (containing cloze overlapping code):

<div id="cloze-original" hidden="">{{Text}}</div>
<div id="cloze-anki-rendered" hidden="">{{cloze:Text}}</div>
<div id="cloze-overlapping-config" hidden="">{{Overlapping}}</div>
<div id="cloze-js-rendered" style="display:block"></div>

// This ceremony makes sure the render function is run exactly once:
(function() {
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");

var alreadyRendered = false;

function render() {
if (alreadyRendered) return;
alreadyRendered = true;

var config = document.getElementById("cloze-overlapping-config").innerText.split(/[,\s|.]+/);
var leadingClozes = config[0] === "0" ? 0 : (+config[0] || 99);
var followingClozes = config[1] === "0" ? 0 : (+config[1] || 99);
var showAllClozes = (config[2] || "true").toLowerCase() === "true"; // Set to false to omit, e.g. for long lyrics/poems
var revealAllClozes = (config[3] || "false").toLowerCase() === "true"; // On the back, reveal other clozes we didnʼt ask for?
var revealAllCustomPlaceholders = (config[4] || "false").toLowerCase() === "true";

var divOriginal = document.getElementById("cloze-original");
var divJsRendered = document.getElementById("cloze-js-rendered");

var currentCloze = +(
(document.body.className.match(/(^|\s)card(\d+)(\s|$)/) || [])[2] ||
((document.getElementById("qa_box") && document.getElementById("qa_box").className && document.getElementById("qa_box").className.match(/(^|\s)card(\d+)(\s|$)/)) || [])[2] ||

var allClozes = (function(){
var allMatches = divOriginal.innerHTML.match(/\{\{c\d+::[\s\S]*?\}\}/g);
var res = {};
for (var i = 0; i < allMatches.length; i++) {
var match = allMatches[i].match(/\{\{c(\d+)::([\s\S]*?)(::([\s\S]*?))?\}\}/);
res[+match[1]] = res[+match[1]] || {askAll: false, clozes: {}};
if (match[2] === "ask-all")
res[+match[1]].askAll = true;
res[+match[1]].clozes[allMatches[i]] = {content: match[2], placeholder: match[4] ? match[4] : "..."};
return res;

var isBackSide = document.getElementById("cloze-is-back") ? true : false;

var question = divOriginal.innerHTML;
for (var i in allClozes) {
for (var orig in allClozes[i].clozes) {
var replacement = "";
var markBlue = false;
var needle = new RegExp(escapeRegExp(orig), "g");
if (allClozes[i].askAll)
replacement = "";
else if (i == currentCloze || allClozes[currentCloze].askAll) {
markBlue = true;
replacement = isBackSide ? allClozes[i].clozes[orig].content : "[" + allClozes[i].clozes[orig].placeholder + "]";
} else if (currentCloze - leadingClozes <= i && i <= currentCloze + followingClozes)
replacement = allClozes[i].clozes[orig].content;
else if (showAllClozes && !allClozes[i].askAll)
replacement = (isBackSide && revealAllClozes) ? allClozes[i].clozes[orig].content : "[" + (revealAllCustomPlaceholders ? allClozes[i].clozes[orig].placeholder : "...") + "]";
else {
replacement = "";
// Also get rid of following new lines, commas, dots, etc.
needle = new RegExp(escapeRegExp(orig) + "(<br>|[\\s,.])*", "g");
question = question.replace(needle, function () {
return (markBlue ? "<span class=\"cloze\">" : "") + replacement + (markBlue ? "</span>" : "");

divJsRendered.innerHTML = question;

function delayedRender() {
if (window.requestAnimationFrame) window.requestAnimationFrame(render); // less flickering
else window.setTimeout(render, 0);

window.onload = delayedRender();
if (document.readyState === "complete") delayedRender();
else document.addEventListener("DOMContentLoaded", delayedRender);

Back template (containing button code and shortcut for the button):

<div id="cloze-is-back" hidden="">{{cloze:Text}}</div>


<div class="Notes">{{Personal Notes}}<div><p>

<div class="Extra">{{Extra}}<div><p>

<div class="Questions">{{Missed Questions}}<div>

<div id="cloze-is-back" hidden="">{{cloze:Text}}</div>

<button onclick="myFunction()">Show All Clozes</button><br>

// Custom shortcut for button
document.addEventListener('keyup', function (event) {
    if (event.key == "`"){ 

document.addEventListener('keydown', myKeyboardEventHandler);
function myFunction() {
var el2= document.getElementById("cloze-original");
const regex = /\{\{c\d+::[\s\S]*?/g;
var line = el2.innerHTML.replace(regex,"<strong style='color:lightblue'>");
var final = line.replace(/\}\}/g,"</strong>");
el2.innerHTML = final;
var el = document.getElementById("cloze-js-rendered");

if( ==="block") { = "block"; = "none";
} else { = "none"; = "block";

{{Reveal all}}

Thanks in advance.

Hello @DnkDT,

It seems like your button activation shortcut works only every other card, which is unexpected behavior. The issue might be related to how the script interacts with the Anki card templates, especially considering the complexity of your front template with cloze overlapping code.

While the script itself looks well-structured and functional, there could be conflicts or unexpected interactions between the script and Anki’s rendering process, leading to this intermittent behavior.

To troubleshoot this issue, you might consider simplifying the front template temporarily to see if the problem persists. This can help isolate whether the issue lies within the cloze overlapping code or elsewhere in the template.

Additionally, you could try debugging the script by adding console log statements to track its execution and see if it consistently triggers on every card. This can provide insights into where the script might be failing intermittently.

If the issue persists even after simplifying the template and debugging the script, it might be worth reaching out to Anki’s support or community forums for further assistance, as they may have insights specific to the Anki platform that could help resolve this issue.

Let me know if you need further assistance or if you have any other questions!

Best Regards,

Was this post written by a person or by a chatbot? Either way, it is vague to the point of being actively unhelpful.

Even if you mean well, you don’t need to be posting AI generated answers here. It is a waste of everyone’s time. If you see a situation where you think it’s absolutely necessary to, (1) the post should include something that will be useful to OP, and (2) you should disclose who the author of the post actually is.



I have now clarified what I meant by button in my post now. I’m not sure if you’re able to help, but I was just wondering if everything else in my post was clear? Is there something else I should add or any more context needed? Maybe I’ll try showing a vid demonstration of the issue?

And thanks for responding, because that reply wasn’t helpful at all haha.

I’m sorry about that other response. I think you explained your issue fine. JavaScript troubleshooting is outside my wheelhouse, but maybe there is someone else who might have advice.

I have to say though – I imagine that folks around here are generally in favor of users respecting the valuable efforts of developers. Pirating someone’s work to avoid paying a reasonable fee seems like not the best way to deal with this situation.

1 Like

When you say “pirating,” are you referring to the cloze overlapping code? And is that from the developers? I thought it was from someone on YouTube from what I heard?

I got that off reddit which is made freely available to anyone and is quite different to the add on. It functions differently and I also modified the code so that it works in my favour. It functions like normal cloze, unless I type something in a field which will tweak it and turn on overlapping mode, essentially.

It’s in no way similar in formatting to the add on. Cloze overlapping, while the term itself may be original, isn’t a completely unheard of concept. I think it’s quite reasonable for people to not want all clozes to be revealed, but some guy just happened to figure that out first and make a note type for that in an add on. This is my own unique system, but mostly from reddit. Anking also has it as part of his addon and I’m sure he got that idea from the first person who made a note type for it - but no one’s saying he’s pirating it? I think that’s a wrong word to use.

My apologies! I apparently misunderstood your message, and overstepped. I am sorry for doing that.


Haha all good, just wanted to clarify what I’m doing. That’s all. No biggie.

1 Like


I would really prefer specific feedback. As in, any attempt to help reconcile the issue on your part.

Because this is just a bit of a vague post that I could’ve obtained from google. That’s why I posted this issue on Anki forums.

If you have anything to be able to help me with this, that would be good. As in, trying to fix the bug or issue with the code.


I think I know what the bug is here.

On Anki desktop, due to how global variables are maintained from card front to back and between reviews, the keyup eventListener is actually stored between reviews. So, each time a card back is rendered, another eventListener is added, meaning that pressing the shortcut key actually presses the button multiple times depending on how many cards you’ve reviewed so far.

The code in myFunction where it does this

if( ==="block") { = "block"; = "none";
} else { = "none"; = "block";

is an effect that reverts to the previous state, if it were run again. So, clicking the button twice in a row would appear to do nothing. Thus, when the the number of times the shortcut clicks the button is odd, it works, when it’s even it doesn’t. This creates the alternating effect as the number of clicks goes up by 1 each time you render a card back.

This can be fixed by changing the code to be like this

  // Custom shortcut for button
  // Prevent adding the same shortcut multiple times on Anki Desktop
  // The listenerAlreadyAdded variable will be undefined on the first card review which will
  // cause the addKeyupListener function to be called
  // On subsequent card reviews, the listenerAlreadyAdded variable will already be true
  // and the addKeyupListener function will not be called again
  // On AnkiDroid, the listenerAlreadyAdded variable will always be undefined and eventListeners
  // will be added on every card review as they aren't persistent
  var listenerAlreadyAdded = !!listenerAlreadyAdded;
  function addKeyupListener() {
    listenerAlreadyAdded = true;
    document.addEventListener("keyup", function (event) {
      if (event.key == "`") {
  if (!listenerAlreadyAdded) addKeyupListener();

As a sidenote, instead of getting the button element and clicking, you can just call myFunction in the eventListener function. The current code would immediately break if you ever added a second button somewhere as it’s simply fetching any button it finds.

Thanks to your bug, I realized I was actually making the same error in one of my cards. I never realized because in my card running the function multiple times did nothing noticeable, except very occasionally I’d notice some lag when using the shortcut during long review sessions on desktop. Which was probably caused by the function being called a hundred times on the key press :laughing:


I honestly don’t know how to thank you.

The amount of agony I went through to just have something so simple cannot be fathomed. I spent hours being so tight on this small issue people around me started to notice. I eventually accepted defeat.

Thank you so much. And I’m glad it also helped you in some way.

1 Like