Fixing CodeRunner jQuery injection

CodeRunner is one of my favourite development environments on macOS. I use it for small one-off projects or for testing concepts for integration into larger projects. But in version 4.0.3, jQuery injection in a custom HTML page is broken, giving the error:

It’s probably due to some unescaped bit of code in their minified jQuery, but I didn’t have time to work that hard. Instead I reported the error to the developer an fixed it myself. The original (default) run script for jQuery is:

echo "<script>$(cat "$CR_SCRIPTS_DIR/jquery.min.js")</script><script src=\"file://$PWD/$filename\"></script>"

Instead, I just pointed the jQuery source path to a local file on my drive. It also has the advantage of allowing me to use whatever version of jQuery that I want. So the new run script is:

echo "<script src=\"file:///Users/alan/Documents/dev/jquery.min.js\"></script><script src=\"file://$PWD/$filename\"></script>"

Problem solved and back to work.

Extending the Anki Cloze Anything script for language learners

It’s possible to use cloze deletion cards within standard Anki note types using the Anki Cloze Anything setup. But additional scripts are required to allow it to function seamlessly in a typical language-learning environment. I’ll show you how to flexibly display a sentence with or without Anki Cloze Anything markup and also not break AwesomeTTS.

Anki’s built-in cloze deletion system

The built-in cloze deletion feature in Anki is an excellent way for language learners to actively test their recall. For example, a cloze deletion note type with the following content requires the learner to supply the missing word:

This will render the front side as:

and the reverse side will be rendered as:

But the greatest weakness of the built-in cloze deletion feature is that the only way to generate cloze deletion cards is to use the cloze deletion note type; and it is not possible to generate cloze deletion cards from other note types. This introduces an inefficiency for language learners. Often we use example sentences in straight vocabulary notes as an aid to using the word in context. I use example sentences that appear on the back side of the card. It would be ideal if I could create a card type within my standard vocabulary notes that generates a cloze card with the example sentence. Otherwise, I am forced to create a separate cloze deletion note for my example sentence if I want to also test on that sentence. This is the problem that Anki Cloze Anything solves.

Anki Cloze Anything

In its simplest form, this is a script that allows you to use cloze deletion card types in a regular note. Instead of Anki’s typical {{c1:somecloze}} markup, it uses ((c1::somecloze)) style markup, but in most respects works the same as the built-in system. Anki Cloze Anything also has an add-on that addresses some workflow efficiency needs; but it will work just fine with only the Javascript on the card template.

However, because Anki Cloze Anything requires that I use a particular markup for the sentences, I can no longer display the same sentence on another card type. Using the example above, if I have a vocabulary card for перевязать then I’d like to have an illustrative sentence on the the back of my card, Она перевяза́ла упако́вку шпагатом. . But Anki Cloze Anything requires me to commit to something like: Она ((c1::перевяза́ла)) упако́вку шпагатом. if I also want to use the same sentence in a cloze-type note. Unless I modify my standard card template, then my sentence will display with the Anki Cloze Anything markup.

Essentially, what Anki Cloze Anything does for me is that it allows me to use a single illustrative sentence as both an example sentence on the back of a standard vocabulary card and to be a cloze sentence at the same time. It saves time by not requiring me to add sentences twice (once to my standard vocabulary card and again to a cloze deletion card.)

But this idea only works if I can display (and pronounce) the sentence in the correct way on the correct card. This is where the two-fold solution described below comes into play.

Enhancing the Anki Cloze Anything template

If I want to display a sentence containing Anki Cloze Anything markup on a standard (non-cloze) card type, then I have to strip out the markup. Fortunately, jQuery makes this easy. Assume I have the following on my standard card template:

<!-- if an example sentence is available, show it -->
{{#sentence_ru}}
   <hr/>
   <div style="padding-left:10px;, padding-right:10px;">
      <span class="rusentence">
         {{sentence_ru}}
      </span> - <span class="ensentence smaller">{{sentence_en}}</span>
   </div>
{{/sentence_ru}}

If the span class rusentence contains a sentence with Anki Cloze Anything markup, then I need to strip away the markup when the standard card displays; so I just need a script to address that:

$('span.rusentence').each(function() {
   var text = $(this).text();
   text = text.replace(/\({2}c\d::([^\0]+)\){2}/g, "$1");
   text = text.replace(new RegExp('`', 'g'),"")
   $(this).text(text);
});

If you enclose this between <script>...</script> tags in the standard note template, then the sentence will display normally, while still allowing you to use the same sentence in a different Anki Cloze Anything card type.

But what about AwesomeTTS

Many language learners use AwesomeTTS, an add-on that provides text-to-speech capabilities to Anki cards. However, using AwesomeTTS on an Anki Cloze Anything card doesn’t work as intendeded, because it just reads the text with the markup. How to fix this?

Fortunately, AwesomeTTS has a text preprocessor that allows you to apply regular expressions (regex) to the text before it feeds the text to the TTS service. The functionality is at Toos → AwesomeTTS → Configuration → Text → Advanced. From there, add a text replacement rule:

The rule \({2}c\d::(.+)\){2}\1 is very similar to the Javascript we used above in the template. With this text-replacement rule in place, AwesomeTTS now reads the sentence correctly even if it contains Anki Cloze Anything markup.

Caveats

The regex described here doesn’t account for the hint markup feature of Anki Cloze Anything. I’ll have to work on that at some point.

References

Complete fix for broken Knowclip .apkg files

I think this is the last word on fixing Knowclip .apkg files. I’ve developed this in bits and pieces; but hopefully this is the last word on the subject. See my previous articles, here and here, for the details. This issue, again, is that Knowclip gives these notes and cards sequential id values starting at 1. But Anki uses the note.id and the card.id as the creation date. I logged it as an issue on Github, but as of 2021-04-15 no action has been taken.

Fixing Knowclip .apkg files: one more thing

(N.B. A much-improved version of this script is published in a later post) Fixing the Knowclip note files as I described previously, it turns out, is only half of the fix with the broken .apkg files. You also need to fix the cards table. Why? Same reason. The rows are number sequentially from 1. But since Anki uses the card id field as the date added, the added field is always wrong.

Fixing Knowclip Anki apkg creation dates

(N.B. A much-improved version of this script is published in a later post) Language learners who want to develop their listening comprehension skills often turn to YouTube for videos that feature native language content. Often these videos have subtitles in the original language. A handful of applications allow users to take these videos along with their subtitles and chop them up into sentence-length bites that are suitable for Anki cards. Once such application is Knowclip.

Generating HTML from Markdown in Anki fields

I write in Markdown because it’s much easier to keep the flow of writing going without taking my hands off the keyboard. I also like to write content in Anki cards in Markdown. Over the years there have been various ways in of supporting this through add-ons: The venerable Power Format Pack was great but no longer supports Anki 2.1, so it became useless. Auto Markdown worked for a while but as of Anki version 2.

Pre-processing Russian text for the AwesomeTTS add-on in Anki

The Anki add-on AwesomeTTS has been a vital tool for language learners using the Anki application on the desktop. It allows you to have elements of the card read aloud using text-to-speech capabilities. The new developer of the add-on has added a number of voice options, including the Microsoft Azure voices. The neural voices for Russian are quite good. But they have one major issue, syllabic stress marks that are sometimes seen in text intended for language learners cause the Microsoft Azure voices to grossly mispronounce the word.

Factor analysis of failed language cards in Anki

After developing a rudimentary approach to detecting resistant language learning cards in Anki, I began teasing out individual factors. Once I was able to adjust the number of lapses for the age of the card, I could examine the effect of different factors on the difficulty score that I described previously. Findings Some of the interesting findings from this analysis: Prompt-answer direction - 62% of lapses were in the Russian → English (recognition) direction.

Refactoring Anki language cards

Regardless of how closely you adhere to the 20 rules for formating knowledge, there are cards that seem destined to leechdom. For me part of the problem is that with languages, straight-up vocabulary cards take words out of the rich context in which they exist in the wild. With my maturing collection of Russian decks, I recently started to go through these resistant cards and figure out why they are so difficult.

Parsing Russian Wiktionary content using XPath

As readers of this blog know, I’m an avid user of Anki to learn Russian. I have a number of sources for reference content that go onto my Anki cards. Notably, I use Wiktionary to get word definitions and the word with the proper syllabic stress marked. (This is an aid to pronunciation for Russian language learners.) Since I’m lazy to the core, I came up with a system way of grabbing the stress-marked word from the Wiktionary page using lxml and XPath.