Skip to content

Instantly share code, notes, and snippets.

@hgiesel
Last active July 17, 2019 18:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hgiesel/2e8361afccca5713414a6a4ee66b7ece to your computer and use it in GitHub Desktop.
Save hgiesel/2e8361afccca5713414a6a4ee66b7ece to your computer and use it in GitHub Desktop.
A Multiple Choice Template for Anki Cards
<script>
// MULTIPLE CHOICE BACK TEMPLATE v1.3 {{{
// https://gist.github.com/hgiesel/2e8361afccca5713414a6a4ee66b7ece
if (window.Persistence && Persistence.isAvailable() && Persistence.getItem("multipleChoiceSettings")) {
var settings = Persistence.getItem("multipleChoiceSettings")
var query = settings.query
var colors = settings.colors
var fieldPadding = settings.fieldPadding
var syntax = {
openDelim: settings.syntax.openDelim,
closeDelim: settings.syntax.closeDelim,
fieldSeparator: settings.syntax.fieldSeparator,
}
var output = {
openDelim: settings.output.openDelim,
closeDelim: settings.output.closeDelim,
fieldSeparator: settings.output.fieldSeparator,
}
////
function escapeString(str) {
return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
var exprRegex = RegExp(`(?:${escapeString(syntax.openDelim)})(.*?)(?:${escapeString(syntax.closeDelim)})`, 'gm')
var refRegex = /^\^\d+$/
var drawRegex = /^\^\d+\+\d+/
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex)
currentIndex -= 1
// And swap it with the current element.
temporaryValue = array[currentIndex]
array[currentIndex] = array[randomIndex]
array[randomIndex] = temporaryValue
}
return array;
}
var results = []
var theBody = document.querySelector(query).innerHTML
var m = exprRegex.exec(theBody)
while (m) {
results.push(m[1])
m = exprRegex.exec(theBody)
}
if (results.length > 0) {
var alteredResults = []
//// PROCESSING OF BACK CARD
if (window.Persistence && Persistence.isAvailable() && Persistence.getItem("multipleChoiceData")) {
alteredResults = Persistence.getItem("multipleChoiceData")
var splitResults = []
for ([i, group] of results.entries()) {
splitResults.push(group.split(syntax.fieldSeparator).map((v, j) => [i, j, v, true]))
}
for ([i, group] of alteredResults.entries()) {
for ([j, field] of group.entries()) {
if (field[2].includes('class="cloze"')) {
var visibilitySave = alteredResults[i][j][3]
alteredResults[i][j] = [...splitResults[field[0]][field[1]]]
alteredResults[i][j][3] = visibilitySave
}
}
}
var stylizedAlteredResults = []
for (group of alteredResults) {
var actualvaluesalter = []
for ([i, field] of group.entries()) {
if (field[3]) {
var theIndex = i % colors.length
actualvaluesalter.push(`<span style="color: ${colors[theIndex]}; padding: 0px ${fieldPadding};">${field[2]}</span>`)
}
}
stylizedAlteredResults.push(actualvaluesalter.join(output.fieldSeparator))
}
for ([i, v] of results.entries()) {
var replacement = document.querySelector(query).innerHTML
.replace(`${syntax.openDelim}${v}${syntax.closeDelim}`, `${output.openDelim}${stylizedAlteredResults[i]}${output.closeDelim}`)
document.querySelector(query).innerHTML = replacement
}
// for ClozeOverlapper
if (query === "div#clozed") {
var results = []
var theBody = document.getElementById('original').innerHTML
var m = exprRegex.exec(theBody)
while (m) {
results.push(m[1])
m = exprRegex.exec(theBody)
}
var innerSplitResults = []
for ([i, group] of results.entries()) {
innerSplitResults.push(group.split(syntax.fieldSeparator).map((v, j) => [i, j, v, true]))
}
var origResults = []
for ([i, group] of innerSplitResults.entries()) {
var newGroup = []
for ([j, field] of group.entries()) {
if (refRegex.test(field[2])) {
var refGroup = field[2].match(/\d+/)[0]
for (elem of origResults[refGroup]) {
newGroup.push([...elem])
}
}
else if (drawRegex.test(field[2])) {
var match = field[2].match(/^\^(\d+)\+(\d+)$/)
var drawGroup = match[1]
var drawAmount = match[2]
for (i = 0; i < drawAmount; i++) {
var lastElemIndex = [...origResults[drawGroup]].reverse().findIndex(v => v[3])
if (lastElemIndex != -1) {
var actualIndex = origResults[drawGroup].length - lastElemIndex - 1
newGroup.push([...origResults[drawGroup][actualIndex]])
// set visibility to false
origResults[drawGroup][actualIndex][3] = false
}
}
}
else {
var iIndex = alteredResults[i][j][0]
var jIndex = alteredResults[i][j][1]
var newContent = innerSplitResults[iIndex][jIndex][2]
newGroup.push([iIndex, jIndex, newContent, true])
}
}
origResults.push(newGroup)
}
var stylizedOrigResults = []
for (group of origResults) {
var actualvaluesorig = []
for ([i, field] of group.entries()) {
if (field[3]) {
var theIndex = i % colors.length
actualvaluesorig.push(`<span style="color: ${colors[theIndex]}; padding: 0px ${fieldPadding};">${field[2]}</span>`)
}
}
stylizedOrigResults.push(actualvaluesorig.join(output.fieldSeparator))
}
for ([i, v] of results.entries()) {
var replacementorig = document.getElementById('original').innerHTML
.replace(`${syntax.openDelim}${v}${syntax.closeDelim}`, `${output.openDelim}${stylizedOrigResults[i]}${output.closeDelim}`)
document.getElementById('original').innerHTML = replacementorig
}
}
}
if (window.Persistence && Persistence.isAvailable()) {
Persistence.removeItem("multipleChoiceData")
Persistence.removeItem("multipleChoiceSettings")
}
}
}
// MULTIPLE CHOICE BACK TEMPLATE v1.3 }}}
</script>
<script>
// MULTIPLE CHOICE BACK TEMPLATE v1.3 {{{
// https://gist.github.com/hgiesel/2e8361afccca5713414a6a4ee66b7ece
if(window.Persistence&&Persistence.isAvailable()&&Persistence.getItem("multipleChoiceSettings")){var settings=Persistence.getItem("multipleChoiceSettings"),query=settings.query,colors=settings.colors,fieldPadding=settings.fieldPadding,syntax={openDelim:settings.syntax.openDelim,closeDelim:settings.syntax.closeDelim,fieldSeparator:settings.syntax.fieldSeparator},output={openDelim:settings.output.openDelim,closeDelim:settings.output.closeDelim,fieldSeparator:settings.output.fieldSeparator};function escapeString(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}var exprRegex=RegExp(`(?:${escapeString(syntax.openDelim)})(.*?)(?:${escapeString(syntax.closeDelim)})`,"gm"),refRegex=/^\^\d+$/,drawRegex=/^\^\d+\+\d+/;function shuffle(e){for(var t,s,r=e.length;0!==r;)s=Math.floor(Math.random()*r),t=e[r-=1],e[r]=e[s],e[s]=t;return e}for(var results=[],theBody=document.querySelector(query).innerHTML,m=exprRegex.exec(theBody);m;)results.push(m[1]),m=exprRegex.exec(theBody);if(results.length>0){var alteredResults=[];if(window.Persistence&&Persistence.isAvailable()&&Persistence.getItem("multipleChoiceData")){alteredResults=Persistence.getItem("multipleChoiceData");var splitResults=[];for([i,group]of results.entries())splitResults.push(group.split(syntax.fieldSeparator).map((e,t)=>[i,t,e,!0]));for([i,group]of alteredResults.entries())for([j,field]of group.entries())if(field[2].includes('class="cloze"')){var visibilitySave=alteredResults[i][j][3];alteredResults[i][j]=[...splitResults[field[0]][field[1]]],alteredResults[i][j][3]=visibilitySave}var stylizedAlteredResults=[];for(group of alteredResults){var actualvaluesalter=[];for([i,field]of group.entries())if(field[3]){var theIndex=i%colors.length;actualvaluesalter.push(`<span style="color: ${colors[theIndex]}; padding: 0px ${fieldPadding};">${field[2]}</span>`)}stylizedAlteredResults.push(actualvaluesalter.join(output.fieldSeparator))}for([i,v]of results.entries()){var replacement=document.querySelector(query).innerHTML.replace(`${syntax.openDelim}${v}${syntax.closeDelim}`,`${output.openDelim}${stylizedAlteredResults[i]}${output.closeDelim}`);document.querySelector(query).innerHTML=replacement}if("div#clozed"===query){for(results=[],theBody=document.getElementById("original").innerHTML,m=exprRegex.exec(theBody);m;)results.push(m[1]),m=exprRegex.exec(theBody);var innerSplitResults=[];for([i,group]of results.entries())innerSplitResults.push(group.split(syntax.fieldSeparator).map((e,t)=>[i,t,e,!0]));var origResults=[];for([i,group]of innerSplitResults.entries()){var newGroup=[];for([j,field]of group.entries())if(refRegex.test(field[2])){var refGroup=field[2].match(/\d+/)[0];for(elem of origResults[refGroup])newGroup.push([...elem])}else if(drawRegex.test(field[2])){var match=field[2].match(/^\^(\d+)\+(\d+)$/),drawGroup=match[1],drawAmount=match[2];for(i=0;i<drawAmount;i++){var lastElemIndex=[...origResults[drawGroup]].reverse().findIndex(e=>e[3]);if(-1!=lastElemIndex){var actualIndex=origResults[drawGroup].length-lastElemIndex-1;newGroup.push([...origResults[drawGroup][actualIndex]]),origResults[drawGroup][actualIndex][3]=!1}}}else{var iIndex=alteredResults[i][j][0],jIndex=alteredResults[i][j][1],newContent=innerSplitResults[iIndex][jIndex][2];newGroup.push([iIndex,jIndex,newContent,!0])}origResults.push(newGroup)}var stylizedOrigResults=[];for(group of origResults){var actualvaluesorig=[];for([i,field]of group.entries())if(field[3]){theIndex=i%colors.length;actualvaluesorig.push(`<span style="color: ${colors[theIndex]}; padding: 0px ${fieldPadding};">${field[2]}</span>`)}stylizedOrigResults.push(actualvaluesorig.join(output.fieldSeparator))}for([i,v]of results.entries()){var replacementorig=document.getElementById("original").innerHTML.replace(`${syntax.openDelim}${v}${syntax.closeDelim}`,`${output.openDelim}${stylizedOrigResults[i]}${output.closeDelim}`);document.getElementById("original").innerHTML=replacementorig}}}window.Persistence&&Persistence.isAvailable()&&(Persistence.removeItem("multipleChoiceData"),Persistence.removeItem("multipleChoiceSettings"))}}
// MULTIPLE CHOICE BACK TEMPLATE v1.3 }}}
</script>
<script>
// MULTIPLE CHOICE FRONT TEMPLATE v1.3 {{{
// https://gist.github.com/hgiesel/2e8361afccca5713414a6a4ee66b7ece
var query = 'div#clozed'
var colors = ['orange', 'olive', 'maroon', 'aqua', 'fuchsia', 'navy', 'lime']
var fieldPadding = '4px'
var syntax = {
openDelim: '(^',
closeDelim: '^)',
fieldSeparator: '::',
}
var output = {
openDelim: '〔',
closeDelim: '〕',
fieldSeparator: '',
}
////
var multipleChoiceSettings = {
query: query,
colors: colors,
fieldPadding: fieldPadding,
syntax: syntax,
output: output,
}
function escapeString(str) {
return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
var exprRegex = RegExp(`(?:${escapeString(syntax.openDelim)})(.*?)(?:${escapeString(syntax.closeDelim)})`, 'gm')
var refRegex = /^\^\d+$/
var drawRegex = /^\^\d+\+\d+/
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex
// While there remain elements to shuffle...
while (0 !== currentIndex) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex)
currentIndex -= 1
// And swap it with the current element.
temporaryValue = array[currentIndex]
array[currentIndex] = array[randomIndex]
array[randomIndex] = temporaryValue
}
return array;
}
var theBody = document.querySelector(query).innerHTML
var results = []
var m = exprRegex.exec(theBody)
while (m) {
results.push(m[1])
m = exprRegex.exec(theBody)
}
if (results.length > 0) {
var alteredResults = []
//// PROCESSING ON FRONT CARD
var splitResults = []
for ([i, group] of results.entries()) {
splitResults.push(group.split(syntax.fieldSeparator).map((v, j) => [i, j, v, true]))
}
var clozeHints = document.querySelectorAll(`${query} .cloze`)
var clozeHintGroups = []
for (ch of clozeHints) {
var clozeHintMultChoices = []
var m = exprRegex.exec(ch.innerHTML)
while (m) {
clozeHintMultChoices.push(m[1])
m = exprRegex.exec(ch.innerHTML)
}
if (clozeHintMultChoices.length > 0) {
var clozeHintSplitResults = []
for ([i, group] of clozeHintMultChoices.entries()) {
clozeHintSplitResults = group.split(syntax.fieldSeparator)
}
for ([i, group] of splitResults.entries()) {
if (JSON.stringify(group.map(v => v[2])) === JSON.stringify(clozeHintSplitResults)) {
clozeHintGroups.push(i)
}
}
}
}
var shuffledResults = []
for (group of splitResults) {
var doShuffle = true
for (field of group) {
if (drawRegex.test(field[2])) {
doShuffle = false
}
}
if (doShuffle) {
shuffle(group)
}
shuffledResults.push(group)
}
for ([i, group] of shuffledResults.entries()) {
for ([j, field] of group.entries()) {
if (refRegex.test(field[2])) {
var referredGroup = [...shuffledResults[field[2].match(/\d+/)[0]]]
var newGroup = []
// make a deepcopy
for (oldGroupField of referredGroup) {
newGroup.push([...oldGroupField])
}
group.splice(j, 1, ...newGroup)
}
if (drawRegex.test(field[2])) {
var drawnFields = []
var match = field[2].match(/^\^(\d+)\+(\d+)$/)
var drawGroup = match[1]
var drawAmount = match[2]
for (i = 0; i < drawAmount; i++) {
var lastElemIndex = [...shuffledResults[drawGroup]].reverse().findIndex(v => v[3])
if (lastElemIndex != -1) {
var actualIndex = shuffledResults[drawGroup].length - lastElemIndex - 1
drawnFields.push([...shuffledResults[drawGroup][actualIndex]])
// set visibility to false
shuffledResults[drawGroup][actualIndex][3] = false
}
}
var replacedFields = shuffle(group.splice(0, j).concat(drawnFields))
group.splice(0, 1, ...replacedFields)
}
}
alteredResults.push(group)
}
var stylizedResults = []
for (group of alteredResults) {
var actualvalues = []
for ([i, field] of group.entries()) {
if (field[3]) {
var theIndex = i % colors.length
actualvalues.push(`<span style="color: ${colors[theIndex]}; padding: 0px ${fieldPadding};">${field[2]}</span>`)
}
}
stylizedResults.push(actualvalues.join(output.fieldSeparator))
}
for ([i, v] of results.entries()) {
var replacement = document.querySelector(query).innerHTML
.replace(`${syntax.openDelim}${v}${syntax.closeDelim}`, `${output.openDelim}${stylizedResults[i]}${output.closeDelim}`)
document.querySelector(query).innerHTML = replacement
}
var saveResults = []
for ([i, group] of alteredResults.entries()) {
if (!clozeHintGroups.includes(i)) {
var newGroup = []
for ([j, field] of group.entries()) {
var groupIndex = field[0]
var content = field[2]
var currentRefGroup = null
if (refRegex.test(content)) {
var currentRefGroup = content.match(/\d+/)[0]
}
var currentDrawGroup = null
var currentDrawAmount = null
if (drawRegex.test(content)) {
var theMatch = content.match(/^\^(\d+)\+(\d+)$/)
currentDrawGroup = theMatch[1]
currentDrawAmount = theMatch[2]
}
for (elem of clozeHintGroups) {
if (groupIndex > elem) {
groupIndex -= 1
}
if (currentRefGroup && currentRefGroup > elem) {
currentRefGroup -= 1
}
if (currentDrawGroup && currentDrawGroup > elem) {
currentDrawGroup -= 1
}
}
if (refRegex.test(content)) {
content = `^${currentRefGroup}`
}
if (drawRegex.test(content)) {
content = `^${currentDrawGroup}+${currentDrawAmount}`
}
newGroup.push([groupIndex, field[1], content, field[3]])
}
saveResults.push(newGroup)
}
}
// Saving in Persistence
if (window.Persistence && Persistence.isAvailable()) {
Persistence.setItem("multipleChoiceData", saveResults)
Persistence.setItem("multipleChoiceSettings", multipleChoiceSettings)
}
}
// MULTIPLE CHOICE FRONT TEMPLATE v1.3 }}}
</script>
<script>
// MULTIPLE CHOICE FRONT TEMPLATE v1.3 {{{
// https://gist.github.com/hgiesel/2e8361afccca5713414a6a4ee66b7ece
var query = 'div#thecard'
var colors = ['orange', 'olive', 'maroon', 'aqua', 'fuchsia', 'navy', 'lime']
var fieldPadding = '4px'
var syntax = {
openDelim: '(^',
closeDelim: '^)',
fieldSeparator: '::',
}
var output = {
openDelim: '〔',
closeDelim: '〕',
fieldSeparator: '',
}
////
var multipleChoiceSettings={query:query,colors:colors,fieldPadding:fieldPadding,syntax:syntax,output:output};function escapeString(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}var exprRegex=RegExp(`(?:${escapeString(syntax.openDelim)})(.*?)(?:${escapeString(syntax.closeDelim)})`,"gm"),refRegex=/^\^\d+$/,drawRegex=/^\^\d+\+\d+/;function shuffle(e){for(var r,t,l=e.length;0!==l;)t=Math.floor(Math.random()*l),r=e[l-=1],e[l]=e[t],e[t]=r;return e}for(var theBody=document.querySelector(query).innerHTML,results=[],m=exprRegex.exec(theBody);m;)results.push(m[1]),m=exprRegex.exec(theBody);if(results.length>0){var alteredResults=[],splitResults=[];for([i,group]of results.entries())splitResults.push(group.split(syntax.fieldSeparator).map((e,r)=>[i,r,e,!0]));var clozeHints=document.querySelectorAll(`${query} .cloze`),clozeHintGroups=[];for(ch of clozeHints){var clozeHintMultChoices=[];for(m=exprRegex.exec(ch.innerHTML);m;)clozeHintMultChoices.push(m[1]),m=exprRegex.exec(ch.innerHTML);if(clozeHintMultChoices.length>0){var clozeHintSplitResults=[];for([i,group]of clozeHintMultChoices.entries())clozeHintSplitResults=group.split(syntax.fieldSeparator);for([i,group]of splitResults.entries())JSON.stringify(group.map(e=>e[2]))===JSON.stringify(clozeHintSplitResults)&&clozeHintGroups.push(i)}}var shuffledResults=[];for(group of splitResults){var doShuffle=!0;for(field of group)drawRegex.test(field[2])&&(doShuffle=!1);doShuffle&&shuffle(group),shuffledResults.push(group)}for([i,group]of shuffledResults.entries()){for([j,field]of group.entries()){if(refRegex.test(field[2])){var referredGroup=[...shuffledResults[field[2].match(/\d+/)[0]]],newGroup=[];for(oldGroupField of referredGroup)newGroup.push([...oldGroupField]);group.splice(j,1,...newGroup)}if(drawRegex.test(field[2])){var drawnFields=[],match=field[2].match(/^\^(\d+)\+(\d+)$/),drawGroup=match[1],drawAmount=match[2];for(i=0;i<drawAmount;i++){var lastElemIndex=[...shuffledResults[drawGroup]].reverse().findIndex(e=>e[3]);if(-1!=lastElemIndex){var actualIndex=shuffledResults[drawGroup].length-lastElemIndex-1;drawnFields.push([...shuffledResults[drawGroup][actualIndex]]),shuffledResults[drawGroup][actualIndex][3]=!1}}var replacedFields=shuffle(group.splice(0,j).concat(drawnFields));group.splice(0,1,...replacedFields)}}alteredResults.push(group)}var stylizedResults=[];for(group of alteredResults){var actualvalues=[];for([i,field]of group.entries())if(field[3]){var theIndex=i%colors.length;actualvalues.push(`<span style="color: ${colors[theIndex]}; padding: 0px ${fieldPadding};">${field[2]}</span>`)}stylizedResults.push(actualvalues.join(output.fieldSeparator))}for([i,v]of results.entries()){var replacement=document.querySelector(query).innerHTML.replace(`${syntax.openDelim}${v}${syntax.closeDelim}`,`${output.openDelim}${stylizedResults[i]}${output.closeDelim}`);document.querySelector(query).innerHTML=replacement}var saveResults=[];for([i,group]of alteredResults.entries())if(!clozeHintGroups.includes(i)){newGroup=[];for([j,field]of group.entries()){var groupIndex=field[0],content=field[2],currentRefGroup=null;if(refRegex.test(content))currentRefGroup=content.match(/\d+/)[0];var currentDrawGroup=null,currentDrawAmount=null;if(drawRegex.test(content)){var theMatch=content.match(/^\^(\d+)\+(\d+)$/);currentDrawGroup=theMatch[1],currentDrawAmount=theMatch[2]}for(elem of clozeHintGroups)groupIndex>elem&&(groupIndex-=1),currentRefGroup&&currentRefGroup>elem&&(currentRefGroup-=1),currentDrawGroup&&currentDrawGroup>elem&&(currentDrawGroup-=1);refRegex.test(content)&&(content=`^${currentRefGroup}`),drawRegex.test(content)&&(content=`^${currentDrawGroup}+${currentDrawAmount}`),newGroup.push([groupIndex,field[1],content,field[3]])}saveResults.push(newGroup)}window.Persistence&&Persistence.isAvailable()&&(Persistence.setItem("multipleChoiceData",saveResults),Persistence.setItem("multipleChoiceSettings",multipleChoiceSettings))}
// MULTIPLE CHOICE FRONT TEMPLATE v1.3 }}}
</script>
@hgiesel
Copy link
Author

hgiesel commented Jul 12, 2019

READ BEFORE INSTALLING:
The part which contains the fields with possible multiple choices must be surrounded with <div id="thecard"></div>.
This part must not contain the script itself, otherwise it will change itself. So basically your card should look somewhat like this:

<div id="thecard">
    your stuff
    [...]
</div>
[...]
<script>
    this script
</script>

The id="thecard" part can be changed, by changing the query variable in the script.

In order for this to work, you need to install anki-persistence!
Just put the <script> tag with anki-persistence before this one. It is no longer optional.
For more info, also see reddit thread for v1.2 and reddit thread for v1.3.

  • TODO:
    • MultipleChoice in ClozeHints combined with Cloze MultipleChoice. ClozeHint MultipleChoice should work in a step independent of the other MultipleChoice.
    • Support for Back Only Multiple Choice
    • Option to generally randomize color order

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment