Action
review flashcards
A simple system for reviewing notes in the form of spaced repetition flashcards.
Drafts forum post.
Tags
- This action uses three types of tag:
flashcard
: used to identify notes that are to be reviewed as flashcardsreviewYYYYMMDD
: used to keep track of the date when flashcards are duemasteryX
: used to calculate when the next review date will be
Making Flashcards
- turn any note into a flashcard by adding the three tags listed above, e.g.
flashcard
,mastery1
andreview20200716
- the action convert to flashcard will add these tags automatically
- use the line break (
—-
) to split the flashcards into different sides- sides will be revealed one at a time
- there is no limit to the number of sides a flashcard can have
Reviewing Flashcards
- when this action is run, it will find all notes with the
flashcard
tag and a review date tag that is on or before the current date - if you select “remembered”, the mastery count will be doubled
- if you select “forgot”, the mastery count will be halved (down to a minimum of 1)
- the new mastery count indicates the number of days before the next review
- e.g. after remembering a card with the
mastery2
tag on 20200716, the tag will be updated tomastery4
, and the review date tag will be updated toreview20200720
- e.g. after forgetting a card with the
mastery2
tag on 20200716, the tag will be updated themastery1
, and the review date tag will be updated toreview20200717
- e.g. after remembering a card with the
Changes
1.01
- added autoscroll on pressing the “flip” button
- added dark mode
- tweaked heading font sizes
- divided CSS and functions into separate steps for clarity
Steps
-
script
// CSS let stylesheet = ` <style> @charset "utf-8"; html { font-size:150%; font-family: Avenir, Helvetica, Arial, Freesans, clean, sans-serif; } body { margin:0; padding:1em; } @media (max-device-width: 480px) { } @media (min-device-width: 481px) { body { margin:auto; max-width:600px; } } h1, h2, h3, h4, h5, h6 { font-weight: bold; } h1 { font-size: 1rem; border-bottom: 1px solid #CCCCCC; } h2 { border-bottom: 1px solid #CCCCCC; color: #000000; font-size: 1.25rem; } h3 { font-size: 1.1rem; } h4 { font-size: 1rem; } h5 { font-size: 1rem; } h6 { color: #777777; background-color: inherit; font-size: 14px; } hr { height: 0.2em; border: 0; color: #CCCCCC; background-color: #CCCCCC; } p, blockquote, ul, ol, dl, li, table, pre { margin: 1em 0; } blockquote { background: #f9f9f9; border-left: 10px solid #ccc; margin: 1.5em 10px; padding: .5em 10px; } code, pre { border-radius: 3px; background-color: #F8F8F8; color: inherit; } code { border: 1px solid #EAEAEA; margin: 0 2px; padding: 0 5px; } pre { border: 1px solid #CCCCCC; line-height: 1.25em; overflow: auto; padding: 6px 10px; } pre > code { border: 0; margin: 0; padding: 0; } a, a:visited { color: #4183C4; background-color: inherit; text-decoration: none; } table { margin: 1em 0; border: 1px solid #aaa; border-collapse: collapse; } th { padding:.25em .5em; background: #efefef; border: 1px solid #ccc; } td { padding:.25em .5em; border: 1px solid #ccc; } img { max-width: 90%; } .flipbutton { background: #e7e7e7; border: none; padding: 1rem; text-align: center; text-decoration: none; margin: auto; width: 50%; font-family:'Avenir',sans-serif; font-size: 1rem; border-radius: 6px; display: block; box-shadow: none; } .yesbutton { background: #d2f8d2; border: none; padding: 1rem; text-align: center; text-decoration: none; margin: auto; width: 50%; font-family:'Avenir',sans-serif; font-size: 1rem; border-radius: 6px; display: block; box-shadow: none; } .nobutton { background: #f8d2d2; border: none; padding: 1rem; text-align: center; text-decoration: none; margin: auto; width: 50%; font-family:'Avenir',sans-serif; font-size: 1rem; border-radius: 6px; display: block; box-shadow: none; } .content { display: none; } #section1 { display: block; } @media (prefers-color-scheme: dark) { body { color: #eee; background: #121212; } body a { color: #809fff; } blockquote { background: #404040; border-left: 10px solid #686868; } th { background: #404040; } .flipbutton { background: #606060; color: #D3D3D3; } .yesbutton { background: #093909; color: #D3D3D3; } .nobutton { background: #6C1111; color: #D3D3D3; } } </style> <script> function yesButton() { Drafts.send("cardCheckButton", "remembered"); Drafts.continue(); } function noButton() { Drafts.send("cardCheckButton", "forgot"); Drafts.continue() } function flipButton(divCount) { let nextSection = "section"+(divCount+1); let thisButton = "flipbutton"+divCount; document.getElementById(thisButton).style.display = "none"; document.getElementById(nextSection).style.display = "block"; document.getElementById(nextSection).scrollIntoView(); } </script> `
-
script
let divCount = 1; // format and number the divisions of each card function divFormat(divContent) { return `<div class = 'content' id = 'section${divCount}'>`+divContent+`<br> <button id = 'flipbutton${divCount}' class = 'flipbutton' onclick = 'flipButton(${divCount})'>flip</button></div>` // <button onclick='document.getElementById("section${divCount+1}").style.display = "block"' class = 'flipbutton'>flip</button></div>` } // format the final division of the card with remembered/forgot buttons function finalDivFormat(finalDivContent) { return `<div class = 'content' id = 'section${divCount}'>`+finalDivContent+`<br> <button name = "yesbutton1" class="yesbutton" onclick="yesButton()">remembered</button><button name = "nobutton1" class="nobutton" onclick="noButton()">forgot</button></div> ` } // display card as HTML function displayCard(cardInput) { let cardTitle = cardInput.title; // process markdown to HTML let cardContent = cardInput.processTemplate("%%[[body]]%%"); // divide card into sections by line break let dividedCard = cardContent.split("<hr />"); // assign div class and ID to each section divCount = 1; let dividedCardDivs = [] for (let z = 0; z < (dividedCard.length-1); z++) { dividedCardDivs.push(divFormat(dividedCard[z])); divCount++; } dividedCardDivs.push(finalDivFormat(dividedCard[dividedCard.length - 1])); // display as HTML let cardHTML = "<html><head><title>" + cardTitle + "</title>" + stylesheet + "<body>" + dividedCardDivs.join("")+"</body>"; return cardHTML; } // search all drafts for flashcard tag let allCards = Draft.query("", "all", ["flashcard"], [], "modified", false, false); let dueCards = []; // get today's date var now = new Date(); // format as YYYYMMDD function yyyymmdd(dateInput) { var y = dateInput.getFullYear(); var m = dateInput.getMonth() + 1; var d = dateInput.getDate(); var mm = m < 10 ? '0' + m : m; var dd = d < 10 ? '0' + d : d; return '' + y + mm + dd; } currentDate = yyyymmdd(now); // calculate the date for next review by adding mastery level to current date function getReviewDate(date, days) { const copy = new Date(Number(date)) copy.setDate(date.getDate() + days) return copy } for (let i = 0; i < allCards.length; i++) { let cardDueDate; let cardMastery; allCardTags = allCards[i].tags; // go through each tag of each card for (let j = 0; j < allCardTags.length; j++) { // find tag containing string "review" if (allCardTags[j].match(/review/) != null) { //identify card due date let dueDateTag = allCardTags[j]; let dateMatch = dueDateTag.match(/(\d+)/); cardDueDate = dateMatch[0]; if (currentDate >= cardDueDate) { // if due, remove old due date tag allCards[i].removeTag(allCardTags[j]); allCards[i].update(); // if due, display card content let html = displayCard(allCards[i]) let preview = HTMLPreview.create(); preview.show(html); for (let k = 0; k < allCardTags.length; k++) { // find tag containing string "mastery" if (allCardTags[k].match(/mastery/) != null) { // identify mastery counter tag let cardMasteryTag = allCardTags[k]; let cardMasteryMatch = cardMasteryTag.match(/(\d+)/); cardMastery = parseInt(cardMasteryMatch[0]); // remove old mastery counter tag allCards[i].removeTag(allCardTags[k]); allCards[i].update(); } } // double card mastery counter if remembered, halve if forgotten let cardCheck = context.previewValues["cardCheckButton"]; if (cardCheck === "remembered") { cardMastery = cardMastery * 2; } if ((cardCheck === "forgot") && (cardMastery > 1)) { cardMastery = cardMastery / 2; } // update review date with new mastery counter let nextReviewDate = getReviewDate(now, cardMastery); let nextReviewDateFormatted = yyyymmdd(nextReviewDate); // add new review date tag allCards[i].addTag("review"+nextReviewDateFormatted); // add new mastery counter tag allCards[i].addTag("mastery"+cardMastery); allCards[i].update(); } } } }
Options
-
After Success Default Notification Info Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.