The original post is on GitHub with Chinese version.
GitHub Repository: pilgrimlyieu/Anki: My Anki Templates & Assets
Introduction
Warning: This script has not undergone extensive testing and may still have issues in edge cases. You are welcome to try it, provide feedback, or contribute! Additionally, the script is customized based on my own needs, and there might be rare use cases that affect its usability. You can choose to remove parts yourself, but no related support is provided.
An enhanced Anki Markdown script that supports:
- Markdown syntax
- Basic syntax
- KaTeX math formulas (including mhchem support)
- highlight.js syntax highlighting
==xxx==
highlight support- Footnote support
- Mermaid diagram support
- Customizable extensions
- Cloze support
- Supports configuring styles for Clozes (
.cloze
,.cloze-inactive
) - Supports nested Clozes
- Supports using Clozes in KaTeX formulas, code blocks, etc.
- Does NOT support using Clozes in Mermaid diagrams
- Supports configuring styles for Clozes (
- โฆ
Demonstration
Taking Cloze as an example, the field content is as follows (there is a typo in the formula, but it doesnโt affect the content):
### This is a test
- **Bold** test
- *Italic* test
- ==highlight== test
- <u>HTML underline</u> test
1. This's
2. ordered
3. test[^test]
4. I'm {{c1::Cloze 1}}
- I'm {{c1::nested in Cloze 1 named {{c2::Cloze 2}} lol}} (nested test)
- I'm {{c2::nested in Cloze 2 named {{c1::Cloze 1}} lol}} (nested test)
- I'm normal {{c2::Cloze 2}}
[^test]: footnote test
This is {{c1::$\LaTeX$}}. This is $\lim_{n \to \infty} {{c1::\sum_{i=0}^n \frac{1}{{{c2::i^2}}} }} = \frac{\pi^2}{6}$, and
$$
\begin{cases}
{{c1::\int_0^\infty t^x \e^{-t} \d x}}, & \text{Display test}\\
\int_0^\infty {{c1::{{c2::t^x}} \e^{-t} }} \d x, & \text{Copied}\\
\pu{1 mol}, & \text{mhchem test}
\end{cases}
$$
```python
# Code block
print("highlight test")
# Codeblock Cloze
{{c1::def func:
print("This is a func")
return {{c2::lambda x: x ** 2}}}}
```
```mermaid
flowchart LR
A[This is mermaid] --> B[test]
%% C("{{c1::mermaid}}") -.->|or| D{"{{c1::My {{c2::PlantUML}}}}"}
E{"Cloze is not supported in mermaid"} --> F("yet")
```
Note that some characters will be converted to HTML, such as
<
becoming<
, so it is slightly different from actual Markdown syntax.
The color of the Cloze is #ec6c4f
, and the color of the inactive Cloze is #a8c5f2
, with an added underline. You can modify the configuration yourself; they are distinguished here for demonstration purposes.
The content on the front of Cloze 1 is as follows:
The content on the back of Cloze 1 is as follows:
The content on the front of Cloze 2 is as follows:
The content on the back of Cloze 2 is as follows:
Instructions for Use
First, relevant dependencies should be placed in the collection.media
directory, including _Anki-Markdown.js
and dependencies appearing within the script (the following dependencies are optional and will be fetched from a CDN if the local file does not exist), which might include:
_katex.css
_highlight.min.css
_katex.js
_auto-render.js
_markdown-it.min.js
_highlight.min.js
_mhchem.js
_markdown-it-mark.min.js
_markdown-it-footnote.min.js
_mermaid.min.js
- โฆ
Taking a Cloze type note template as an example, the Basic type is similar.
The note template used for the demonstration above is as follows (partial):
<div class="border1">
<div class="h1 xcolor xleft">
<span class="ximg"><img src="_space.png" height="24" width="36" /></span>
้ฎ้ข
</div>
<div class="h2 xleft" id="front">{{cloze:้ฎ้ข}}</div>
</div>
<script src="_Anki-Markdown.js"></script>
Here, ้ฎ้ข
is the first field of the note template.
The usage method is as follows:
- Add an id to the field(s) where you want to add Markdown support, such as
front
above. - Add
<script src="_Anki-Markdown.js"></script>
to the template.
Then open _Anki-Markdown.js
, and in the config
constant configuration under the --- Configuration ---
section, add the corresponding field id(s) to fieldIds
. If there are Clozes, they should also be added to clozeFieldIds
. The default values are as follows:
/** @type {string[]} Field IDs to render */
fieldIds: ["front", "back", "extra", "extra1", "extra2"],
/** @type {string[]} Field IDs that might contain Cloze deletions and need placeholder processing. */
clozeFieldIds: ["front", "back"],
Similar operations apply to both the front and back.
Configuration Instructions
You can modify the configuration in _Anki-Markdown.js
, such as the field id(s) to be rendered as mentioned above.
In addition, you can modify the resources
variable and add the required resource files there. The internal order will affect the loading order. By default, they will be loaded from the local directory (i.e., collection.media
). If not found, they will be loaded from the configured CDN. You can choose the type, dependencies, etc.:
{
type: "script",
name: "mhchem",
local: "_mhchem.js",
cdn: "https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/contrib/mhchem.min.js",
dependsOn: "katex",
},
This is an example of the mhchem dependency for KaTeX.
katexOptions
is the KaTeX configuration, which contains a lot of personal configurations (including old configurations for compatibility), such as the macros
configuration, etc. You can modify it yourself.
markdownOptions
is the markdown-it configuration, and mermaidOptions
is the Mermaid configuration, similar to the above.
In addition, some customizable parts are reserved within the script, along with clear comments and debugging statements. You can modify and extend them and debug yourself.
Known Limitations
- Clozes cannot be used in Mermaid diagrams.
- The processing of Clozes in KaTeX formulas and code blocks may be unstable (no issues found yet).
Development Guide
This section is for developers only and can be ignored by regular users.
The functionality of other parts is generally stable, except for potential issues with HTML escaping that may not be handled. The most likely part to encounter problems is the Cloze section, especially Clozes appearing within KaTeX formulas or code blocks.
The following example briefly illustrates the logic of how the script handles Clozes:
This is {{c1::a test}}.
This is a {{c1::nested {{c2::cloze}} test}}.
This will be processed by Anki as:
<!-- Cloze 1 -->
This is <span class="cloze" data-cloze="a test" data-ordinal="1">[...]</span>.
This is a <span class="cloze" data-cloze="nested <span class="cloze-inactive" data-ordinal="2">cloze</span> test" data-ordinal="1">[...]</span>.
or
<!-- Cloze 1 -->
This is <span class="cloze-inactive" data-ordinal="1">a test</span>.
This is a <span class="cloze-inactive" data-ordinal="1">nested <span class="cloze" data-cloze="cloze" data-ordinal="2">[...]</span> test</span>.
As can be seen, there are two cases:
- Active Cloze: class is
cloze
, and it also has adata-cloze
attribute storing the Cloze content. - Inactive Cloze: class is
cloze-inactive
.
Both types of Cloze have a data-ordinal
attribute indicating the Cloze number.
In standard Markdown parsing, this is not a problem and generally does not interfere with normal rendering.
However, in KaTeX formula blocks or highlight.js syntax highlighted code blocks, parsing anomalies may occur.
At the same time, the content within the Cloze span tag needs to participate in the subsequent Markdown rendering (including KaTeX, Mermaid, etc.), and finally, it needs to support custom styling for Clozes. Therefore, simply removing the Cloze-related tags is not an option.
The script thus adopts an approach โ replacing the Cloze span tags with rarely used Unicode characters to minimize interference with subsequent rendering as much as possible. It uses the format โถi๐xxxโฟi๐
, where i
is the unique identifier of the Cloze, and xxx
is the content within the Cloze span tag.
i
serves as an index, identifying the specific information saved by the script in clozePlaceholdersData
, which will be used to restore the content after rendering is complete.
For example, the content of Cloze 1 above will be replaced with:
This is โถ1๐[...]โฟ1๐.
This is a โถ2๐[...]โฟ2๐.
The content of Cloze 2 will be replaced with:
This is โถ1๐a testโฟ1๐.
This is a โถ3๐nested โถ2๐[...]โฟ2๐ testโฟ3๐.
Unfortunately, even with this replacement, it still affects the rendering of KaTeX formulas or code blocks.
The KaTeX part is demonstrated with the following example:
$a{{c1::b}}c$
After replacing the Cloze and performing KaTeX parsing, this becomes:
<span
><span class="katex"
><span class="katex-mathml"
><math xmlns="http://www.w3.org/1998/Math/MathML"
><semantics
><mrow
><mi>a</mi><mtext>โถ</mtext><mn>1</mn><mtext>๐</mtext
><mo stretchy="false">[</mo><mi mathvariant="normal">.</mi
><mi mathvariant="normal">.</mi><mi mathvariant="normal">.</mi
><mo stretchy="false">]</mo><mtext>โฟ</mtext><mn>1</mn
><mtext>๐</mtext><mi>c</mi></mrow
><annotation encoding="application/x-tex"
>aโถ1๐[...]โฟ1๐c</annotation
></semantics
></math
></span
><span class="katex-html" aria-hidden="true"
><span class="base"
><span class="strut" style="height: 1em; vertical-align: -0.25em"></span
><span class="mord mathnormal">a</span><span class="mord">โถ1๐</span
><span class="mopen">[</span><span class="mord">...</span
><span class="mclose">]</span><span class="mord">โฟ1๐</span
><span class="mord mathnormal">c</span></span
></span
></span
></span
>
As can be seen in the span with class katex-html
, the delimiters โถ1๐
and โฟ1๐
are wrapped in <span class="mord">...</span>
. Therefore, they cannot be simply replaced back; this outer tag must be stripped away.
Besides the mord
class, depending on the Clozeโs position, classes like mtight
might also appear. Hence, I perform pre-processing in the script like this:
// --- Pre-processing step to remove KaTeX spans around markers ---
// KaTeX will wrap the markers in spans with classes like "mord", "mtight", etc. like <span class="mord mtight">โถ1๐</span>
const katexStartMarkerWrapperRegex =
/<span\s+class="mord(?: m[a-z]+)*"[^>]*>((?:โถ(?:(?:<span[^>]*>)?\d+(?:<\/span>)?|\d+)๐)+)<\/span>/g;
const katexEndMarkerWrapperRegex =
/<span\s+class="mord(?: m[a-z]+)*"[^>]*>((?:โฟ(?:(?:<span[^>]*>)?\d+(?:<\/span>)?|\d+)๐)+)<\/span>/g;
// Remove the KaTeX wrappers, leaving only the marker content
let currentHtml = htmlContent
.replace(katexStartMarkerWrapperRegex, "$1")
.replace(katexEndMarkerWrapperRegex, "$1");
This is currently a fragile patch and may require improvements as new issues are discovered.
For code blocks, the following example is used:
```python
{{c1::print(1)}}
```
This will be rendered as:
<pre
class="hljs"
><code>โถ<span class="hljs-number">1</span>๐[...]โฟ<span class="hljs-number">1</span>๐
</code></pre>
As seen, the delimiters are even split, and the number used for identification is wrapped in <span class="hljs-number">...</span>
.
Furthermore, this does not necessarily happen, and when it does, it may not occur in pairs. Therefore, the script performs relatively complex processing, which is not detailed here.
Perhaps changing the sequence number to different Unicode characters might avoid this problem.