Skip to content

Commit

Permalink
Add Reveal All button (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
nihil-admirari committed Mar 15, 2023
1 parent 548d424 commit c3490f7
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 90 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Hey 👋
I got a bit confused with the original [Cloze Overlapper](https://github.com/glutanimate/cloze-overlapper), and it felt a bit wrong to keep redundant information in our collections. The author also [keeps updates for Anki 2.1 behind a paywall](https://github.com/glutanimate/cloze-overlapper/issues/42#issuecomment-675031109).

Here is a pure JavaScript version that you can paste into your card templates:
* the JavaScript module responsible for rendering is in
[_cloze-overlapper.mjs](_cloze-overlapper.mjs), and it **must be put into** Anki's
[collection.media folder](https://docs.ankiweb.net/media.html#manually-adding-media),
* the front side is in [front.html](front.html),
* the back side is in [back.html](back.html).

Expand All @@ -18,7 +21,7 @@ Reddit thread: https://old.reddit.com/r/Anki/comments/116nky2/simple_cloze_overl

## Options (per note)

A good idea is to create a new note type (based on Cloze), e.g. “Cloze (overlapping)”, copy the front and back templates from here, and add a new field to it to control the behavior per each note. The templates below assume that the field will be called “Overlapping”.
A good idea is to create a new note type (based on Cloze), e.g. “Cloze (overlapping)”, copy the front and back templates from here, and add a new field to it to control the behavior per each note. The templates below assume that the field will be called `Before|After|NonContext|RevelAll|InactiveHints`.

The options (separated by space, comma, pipe, etc.) are:

Expand Down
77 changes: 77 additions & 0 deletions _cloze-overlapper.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const ASK_ALL_CLOZE = 'ask-all';
const CONFIG_SPLIT_RE = /[,\s|.]+/;
const CARD_NUM_RE = /(?<=\bcard)[1-9]\d*\b/;
const CLOZE_RE = /\{\{c([1-9]\d*)::(.*?)(?:::(.*?))?\}\}/g;

function revealAllCallback(e) {
const target = e.currentTarget;
window.requestAnimationFrame(() => {
renderClozes(true);
target.hidden = true;
});
}

export function renderClozes(revealAllClozes) {
const config = document.getElementById('cloze-config').content
.textContent.split(CONFIG_SPLIT_RE);
const revealAllButton = document.getElementById('reveal-all-button');
const clozeSrc = document.getElementById('cloze-source').innerHTML;

const contextBefore = config[0] === '0' ? 0 : (+config[0] || 1);
const contextAfter = +config[1] || 0;
const showNonContext = (config[2] || 'true').toLowerCase() === 'true';
revealAllClozes ??= (config[3] || 'false').toLowerCase() === 'true';
const showInactiveHints = (config[4] || 'false').toLowerCase() === 'true';

const curCard = +(
document.body.className.match(CARD_NUM_RE)?.[0]
?? document.getElementById('qa_box')?.className.match(CARD_NUM_RE)?.[0]
?? document.getElementById('anki-cloze').content
.querySelector('.cloze[data-ordinal]')?.dataset['ordinal']
?? 1
);
const isBack = !!revealAllButton;
const askAll = new RegExp(
String.raw`\{\{c${curCard}::${ASK_ALL_CLOZE}(?:::.*?)?\}\}`).test(clozeSrc);

let unrevealedClozesPresent = false;
document.getElementById('rendered-cloze').innerHTML = clozeSrc.replaceAll(
CLOZE_RE, (_, cardNum, cloze, hint) => {
cardNum = +cardNum;
hint ??= '...';
// ASK_ALL_CLOZE itself is never shown.
if (cloze === ASK_ALL_CLOZE) {
return '';
}
if (cardNum === curCard || askAll) {
const span = document.createElement('span');
span.className = 'cloze';
span.dataset['ordinal'] = cardNum;
if (isBack) {
span.innerHTML = cloze;
} else {
span.dataset['cloze'] = cloze;
span.innerHTML = `[${hint}]`;
}
return span.outerHTML;
}
if (curCard - contextBefore <= cardNum && cardNum <= curCard + contextAfter) {
return cloze;
}
if (showNonContext) {
if (isBack && revealAllClozes) {
return cloze;
}
unrevealedClozesPresent = true;
return `[${showInactiveHints ? hint : '...'}]`;
}
return '';
}
);

if (isBack && unrevealedClozesPresent) {
revealAllButton.addEventListener(
'click', revealAllCallback, { once: true, passive: true });
revealAllButton.hidden = false;
}
}
15 changes: 13 additions & 2 deletions back.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
<div id="cloze-is-back" hidden="">{{cloze:Text}}</div>
{{FrontSide}}
<template id="anki-cloze">{{cloze:Text}}</template>
<template id="cloze-config">{{Before|After|NonContext|RevelAll|InactiveHints}}</template>
<template id="cloze-source">{{Text}}</template>
<div id="rendered-cloze"></div>
{{Back Extra}}

<div>
<button id="reveal-all-button" hidden>Reveal All</button>
</div>

<script type="module">
import { renderClozes } from '/_cloze-overlapper.mjs';
window.requestAnimationFrame(_ => renderClozes());
</script>
95 changes: 8 additions & 87 deletions front.html
Original file line number Diff line number Diff line change
@@ -1,88 +1,9 @@
<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"></div>

<script>
// 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] || 1);
var followingClozes = +config[1] || 0;
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] ||
0
);

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);
})();
<template id="anki-cloze">{{cloze:Text}}</template>
<template id="cloze-config">{{Before|After|NonContext|RevelAll|InactiveHints}}</template>
<template id="cloze-source">{{Text}}</template>
<div id="rendered-cloze"></div>

<script type="module">
import { renderClozes } from '/_cloze-overlapper.mjs';
window.requestAnimationFrame(_ => renderClozes());
</script>

0 comments on commit c3490f7

Please sign in to comment.