diff options
| author | Jakob L. Kreuze <zerodaysfordays@sdf.org> | 2025-10-26 16:57:18 -0400 |
|---|---|---|
| committer | Jakob L. Kreuze <zerodaysfordays@sdf.org> | 2025-10-26 16:57:18 -0400 |
| commit | b15a4163d8354feaafe6529c6a344e2b5f614f82 (patch) | |
| tree | 09e4b536bcb5a39a8825cad03d18462dad4d3229 | |
| parent | 939594d1cfd2d11f7acc300f117204c050af2d13 (diff) | |
Add custom card types
| -rw-r--r-- | README.md | 39 | ||||
| -rw-r--r-- | example-cards.json | 12 | ||||
| -rw-r--r-- | srs-anywhere.html | 1 | ||||
| -rw-r--r-- | srs-anywhere.js | 57 |
4 files changed, 102 insertions, 7 deletions
@@ -39,3 +39,42 @@ Keys generated and modified by SRS Anywhere are prefixed by the `_` character. } ] ``` + +### Custom Cards + +Custom card types can be defined in a `srs-anywhere-custom-card-types.js` file. +To register a new card type, add it as a new entry to the `CARD_TYPES` object, where the key is the the name of the card type. +A card type should be an object containing at least the following keys: `validate`, `front`, and `back`. +`validate` takes the card and throws an `Error` if it is malformed. +`front` returns the desired HTML code for the front of the card. +`back` returns the desired HTML code for the back of the card. +Optionally, `showFront` and `showBack` are functions that are evaluted when the front or back of the card are shown, respectively. + +For example, to define a card type for a [cloze deletion test](https://en.wikipedia.org/wiki/Cloze_test): + +```javascript +const CLOZE_REGEX = /\[.*?\]/ig; +CARD_TYPES["clozeAll"] = { + "validate": function (card) { + if (typeof card.back !== "string") { + throw new Error(`malformed card ${card.id}: invalid back (should be string)`) + } + if (!CLOZE_REGEX.test(card.back)) { + throw new Error(`malformed card ${card.id}: contains no cloze; +please enclose deletion in brackets (e.g., [...])`) + } + }, + "front": (card) => card.back.replaceAll(CLOZE_REGEX, '[...]'), + "back": (card) => card.back +}; +``` + +The `type` key in the cards JSON file associates an entry with the custom card type. + +```javascript +{ + "id": "cca013c4-450a-41a1-abed-28abdc73413a", + "type": "clozeAll", + "back": "This is a card containing a [cloze deletion]!" +} +``` diff --git a/example-cards.json b/example-cards.json new file mode 100644 index 0000000..9b856f0 --- /dev/null +++ b/example-cards.json @@ -0,0 +1,12 @@ +[ + { + "id": "d8968711-cd50-42e3-9360-f17a35cc944a", + "front": "This is the front of a card.", + "back": "And this is the back of a card" + }, + { + "id": "a8942790-aea7-41d1-afd1-3cc3c3ecb664", + "front": "Cards can contain <strong>arbitrary</strong> <em>HTML</em>.", + "back": "Back." + } +] diff --git a/srs-anywhere.html b/srs-anywhere.html index 1e4aa4e..06101ce 100644 --- a/srs-anywhere.html +++ b/srs-anywhere.html @@ -74,5 +74,6 @@ </div> <script src="srs-anywhere.js"></script> + <script src="srs-anywhere-custom-card-types.js"></script> </body> </html> diff --git a/srs-anywhere.js b/srs-anywhere.js index 6dc82bf..0931b07 100644 --- a/srs-anywhere.js +++ b/srs-anywhere.js @@ -22,6 +22,25 @@ function reportException(e) { } /*********************************************************************** + * Card types and dispatch * + ***********************************************************************/ + +const CARD_TYPES = { + "default": { + "validate": function (card) { + if (!card.front) { + throw new Error(`malformed card ${card.id}: missing front`) + } + if (!card.back) { + throw new Error(`malformed card ${card.id}: missing back`) + } + }, + "front": (card) => card.front, + "back": (card) => card.back + } +}; + +/*********************************************************************** * Actual FSRS5 algorithm * ***********************************************************************/ @@ -148,11 +167,26 @@ Current session: ${pad(minutes)}:${pad(seconds)}`; function loadCard(card) { window.card.revealed = false; - window.card.front.innerHTML = card.front; window.card.front.style.display = "block"; - window.card.back.innerHTML = card.back; window.card.back.style.display = "none"; window.buttons.style.display = "none"; + + const cardType = CARD_TYPES[card.type]; + + window.card.front.innerHTML = cardType.front(card); + window.card.back.innerHTML = cardType.back(card); + if (typeof cardType.showFront === "function") { + window.card.showFront = () => cardType.showFront(card); + } else { + window.card.showFront = () => null; + } + if (typeof cardType.showBack === "function") { + window.card.showBack = () => cardType.showBack(card); + } else { + window.card.showBack = () => null; + } + + window.card.showFront(); } function flipCard() { @@ -162,13 +196,14 @@ function flipCard() { window.card.revealed = true; - // Reveal the "back". if (window.card.front.style.display !== "block") { window.card.front.style.display = "block"; window.card.back.style.display = "none"; + window.card.showFront(); } else { window.card.front.style.display = "none"; window.card.back.style.display = "block"; + window.card.showBack(); } // Reveal the buttons. @@ -280,11 +315,19 @@ function checkCards(cards) { const card = cards[i]; if (!card.id) { throw new Error("malformed card: missing id") - } else if (!card.front) { - throw new Error(`malformed card ${card.id}: missing front`) - } else if (!card.back) { - throw new Error(`malformed card ${card.id}: missing bacak`) } + + if (!card.type) { + card.type = "default"; + } + if (typeof CARD_TYPES[card.type].front !== "function") { + throw new Error(`malformed card type ${card.type}: front not callable`); + } + if (typeof CARD_TYPES[card.type].back !== "function") { + throw new Error(`malformed card type ${card.type}: back not callable`); + } + CARD_TYPES[card.type].validate(card); + if (!card._scheduled) { card._scheduled = today().toJSON(); } |