yanki 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/dist/abap-DXFkqnOI.js +816 -0
  2. package/dist/actionscript-3-D6NljDpC.js +1 -0
  3. package/dist/ada-CowR2XfX.js +1 -0
  4. package/dist/andromeeda-YxQm0tCS.js +1 -0
  5. package/dist/angular-html-CZfDmWtJ.js +16 -0
  6. package/dist/angular-ts-CIhYrOL5.js +666 -0
  7. package/dist/apache-rS0jd3Ly.js +1 -0
  8. package/dist/apex-Sfo2eW0G.js +274 -0
  9. package/dist/apl-DPLNWaKG.js +87 -0
  10. package/dist/applescript-BXKAgOFh.js +38 -0
  11. package/dist/ara-CG4fK2Nq.js +21 -0
  12. package/dist/asciidoc-C539GviS.js +1 -0
  13. package/dist/asm-PWN5J14X.js +1 -0
  14. package/dist/astro-71xeBtnw.js +32 -0
  15. package/dist/aurora-x-BaWyeHV_.js +1 -0
  16. package/dist/awk-i0IPvypD.js +1 -0
  17. package/dist/ayu-dark-Bn5gmY5k.js +1 -0
  18. package/dist/ballerina-T9ysyp6P.js +62 -0
  19. package/dist/bat-BPiaQZfK.js +1 -0
  20. package/dist/beancount-Urb1RsFe.js +1 -0
  21. package/dist/berry-CxrokwfH.js +1 -0
  22. package/dist/bibtex-BaedD2tq.js +1 -0
  23. package/dist/bicep-3ghuYFLd.js +1 -0
  24. package/dist/bin/cli.js +10 -0
  25. package/dist/blade-D3A5GssZ.js +831 -0
  26. package/dist/c-RCJZWN-0.js +81 -0
  27. package/dist/cadence-Bgpqy2XC.js +1 -0
  28. package/dist/catppuccin-frappe-BsOrZxH8.js +1 -0
  29. package/dist/catppuccin-latte-BYdKNJ10.js +1 -0
  30. package/dist/catppuccin-macchiato-DVLwECkk.js +1 -0
  31. package/dist/catppuccin-mocha-CEfge3mM.js +1 -0
  32. package/dist/clarity-CIekO_uJ.js +1 -0
  33. package/dist/clojure-BF6G6X0H.js +1 -0
  34. package/dist/cmake-DXZpi2gR.js +1 -0
  35. package/dist/cobol-Bi0pgIzz.js +1 -0
  36. package/dist/codeowners-CGmujMTu.js +1 -0
  37. package/dist/codeql-BOpLLL-w.js +1 -0
  38. package/dist/coffee-Cp6Hkwsd.js +100 -0
  39. package/dist/common-lisp-C3qUB5O8.js +301 -0
  40. package/dist/cpp-CeZn6MzL.js +186 -0
  41. package/dist/crystal-B5pCH4eR.js +25 -0
  42. package/dist/csharp-DISxKEhY.js +756 -0
  43. package/dist/css-CbYhyuC0.js +505 -0
  44. package/dist/csv-B2DkETJQ.js +1 -0
  45. package/dist/cue-C6Aznpr-.js +1 -0
  46. package/dist/cypher-ByMv4Xf1.js +1 -0
  47. package/dist/d-PifQWv0n.js +1 -0
  48. package/dist/dark-plus-KEYLhlmT.js +1 -0
  49. package/dist/dart-DZLoTQm4.js +1 -0
  50. package/dist/dax-DsfXcHUZ.js +29 -0
  51. package/dist/desktop-D71BffLY.js +1 -0
  52. package/dist/diff-DvyTQcux.js +1 -0
  53. package/dist/docker-DNR26wTC.js +1 -0
  54. package/dist/dracula-DGO8GyiP.js +1 -0
  55. package/dist/dracula-soft-9B1nZgL-.js +1 -0
  56. package/dist/dream-maker-Eh5U-gDp.js +52 -0
  57. package/dist/elixir-Exce1dLy.js +17 -0
  58. package/dist/elm-D7CfdsEG.js +1 -0
  59. package/dist/emacs-lisp-51pKhIDe.js +5754 -0
  60. package/dist/erb-S7d1X-Y-.js +1 -0
  61. package/dist/erlang-DS9ZWoKD.js +1 -0
  62. package/dist/fennel-DuK7IM-L.js +1 -0
  63. package/dist/fish-DfeQjIbs.js +5 -0
  64. package/dist/fluent-KPqz0Sb3.js +1 -0
  65. package/dist/fortran-fixed-form-6v7-xzYY.js +1 -0
  66. package/dist/fortran-free-form-D6pmzCqS.js +1 -0
  67. package/dist/fsharp-JZjmD0Li.js +1 -0
  68. package/dist/gdresource-CFKM8Ske.js +1 -0
  69. package/dist/gdscript-CoEqzGFw.js +44 -0
  70. package/dist/gdshader-B_SUYfiV.js +1 -0
  71. package/dist/genie-DAfrLhwG.js +1 -0
  72. package/dist/gherkin-B0PjAhci.js +1 -0
  73. package/dist/git-commit-XY8bXq83.js +1 -0
  74. package/dist/git-rebase-BE3WlPTi.js +1 -0
  75. package/dist/github-dark-CzPA46E-.js +1 -0
  76. package/dist/github-dark-default-BXF7Vm5l.js +1 -0
  77. package/dist/github-dark-dimmed-CRDKj6ck.js +1 -0
  78. package/dist/github-light-CRlnGVMD.js +1 -0
  79. package/dist/github-light-default-UREJT2Bw.js +1 -0
  80. package/dist/gleam-Dd6f7Z5P.js +1 -0
  81. package/dist/glimmer-js-I0TZ3_O1.js +13 -0
  82. package/dist/glimmer-ts-CY38hlIb.js +13 -0
  83. package/dist/glsl-tOUOXML3.js +1 -0
  84. package/dist/gnuplot-nclm9rTJ.js +266 -0
  85. package/dist/go-BJn7Ek5W.js +1 -0
  86. package/dist/graphql-DwwLrXH1.js +4 -0
  87. package/dist/groovy-wChcbJ1V.js +37 -0
  88. package/dist/hack-DmjQSZc3.js +86 -0
  89. package/dist/haml-map1ik7u.js +1 -0
  90. package/dist/handlebars-CCc9qS91.js +1 -0
  91. package/dist/haskell-DOVD4hs_.js +559 -0
  92. package/dist/haxe-Dit6kIrv.js +11 -0
  93. package/dist/hcl-CpAANOdC.js +1 -0
  94. package/dist/hjson-DZqG9GXz.js +50 -0
  95. package/dist/hlsl-DmDrTTlz.js +1 -0
  96. package/dist/houston-CZZ6oYdA.js +1 -0
  97. package/dist/html-NmvpQUfQ.js +74 -0
  98. package/dist/html-derivative-QphQoco-.js +1 -0
  99. package/dist/http-B58UUte0.js +1 -0
  100. package/dist/hxml-4EC8aR1F.js +1 -0
  101. package/dist/hy-y-6HuL9l.js +3 -0
  102. package/dist/imba-CyaA9484.js +223 -0
  103. package/dist/index-UHZxbkBe.js +2 -0
  104. package/dist/ini-DeVv6D4_.js +1 -0
  105. package/dist/java-ClXEvkw9.js +68 -0
  106. package/dist/javascript-Dch3xQiY.js +699 -0
  107. package/dist/jinja-BrXMjE5a.js +1 -0
  108. package/dist/jison-DlEKeWyG.js +1 -0
  109. package/dist/json-CupVZNk8.js +25 -0
  110. package/dist/json5-Bh8mriwU.js +17 -0
  111. package/dist/jsonc-DYI1rfmx.js +25 -0
  112. package/dist/jsonl-BUpeXbsf.js +25 -0
  113. package/dist/jsonnet-C9d3aiqh.js +1 -0
  114. package/dist/jssm-Dble9ECP.js +1 -0
  115. package/dist/jsx-CsyrCbsw.js +699 -0
  116. package/dist/julia-jDzGJc6I.js +9 -0
  117. package/dist/kotlin-BIxS-Weu.js +1 -0
  118. package/dist/kusto-DGEpfOTx.js +1 -0
  119. package/dist/latex-bjPeSN-x.js +3 -0
  120. package/dist/less-Du6_OKDb.js +159 -0
  121. package/dist/lib/index.d.ts +1022 -0
  122. package/dist/lib/index.js +1 -0
  123. package/dist/light-plus-BsvsQ1iS.js +1 -0
  124. package/dist/liquid-DHpvaAyy.js +14 -0
  125. package/dist/log-Ksn5IXup.js +1 -0
  126. package/dist/logo-DdacRhvC.js +1 -0
  127. package/dist/lua-Cy9U1SwF.js +1 -0
  128. package/dist/make-B9S9BZZh.js +1 -0
  129. package/dist/markdown-BdfWgkoX.js +129 -0
  130. package/dist/marko-neMFgnTa.js +8 -0
  131. package/dist/material-theme-B2BuIiKK.js +1 -0
  132. package/dist/material-theme-darker-BrGg7AAd.js +1 -0
  133. package/dist/material-theme-lighter-DDRuGeQH.js +1 -0
  134. package/dist/material-theme-ocean-CBL0qBdF.js +1 -0
  135. package/dist/material-theme-palenight-D7gg1Usp.js +1 -0
  136. package/dist/matlab-Btshr8M_.js +70 -0
  137. package/dist/mdc-_-l14nii.js +76 -0
  138. package/dist/mdx-CBPJd_fO.js +1 -0
  139. package/dist/mermaid-Dkb1Nx48.js +1 -0
  140. package/dist/min-dark-iSbrOpM4.js +1 -0
  141. package/dist/min-light-BITGhEdf.js +1 -0
  142. package/dist/mojo--7WWnkdy.js +517 -0
  143. package/dist/monokai-sMI-pExk.js +1 -0
  144. package/dist/move-MN12aA4C.js +1 -0
  145. package/dist/narrat-B9CT-1u6.js +7 -0
  146. package/dist/nextflow-DBxHOdLe.js +1 -0
  147. package/dist/nginx-C3DmzmOp.js +1 -0
  148. package/dist/night-owl-BeocmOPF.js +1 -0
  149. package/dist/nim-BjhyiCLB.js +1 -0
  150. package/dist/nix-B7rNE5kf.js +1 -0
  151. package/dist/nord-CsyjKwr8.js +1 -0
  152. package/dist/nushell-CrgTADc5.js +1 -0
  153. package/dist/objective-c-DHmGyzbM.js +109 -0
  154. package/dist/objective-cpp-BWKJ1FCf.js +239 -0
  155. package/dist/ocaml-RqY_Nz63.js +1 -0
  156. package/dist/one-dark-pro-BTtaZsq5.js +1 -0
  157. package/dist/one-light-alpzPJ78.js +1 -0
  158. package/dist/pascal-B6ZnTe72.js +1 -0
  159. package/dist/perl-PZnu_VA7.js +1 -0
  160. package/dist/php-m9Z3qs_k.js +771 -0
  161. package/dist/plsql-BbJj1K1w.js +1 -0
  162. package/dist/po-HrnDn_2Q.js +1 -0
  163. package/dist/poimandres-Cda-MJFk.js +1 -0
  164. package/dist/postcss-DXT9h7v2.js +1 -0
  165. package/dist/powerquery-CApMHEaB.js +1 -0
  166. package/dist/powershell-9ZOzOPqN.js +1 -0
  167. package/dist/prisma-BBJYjQ0k.js +1 -0
  168. package/dist/prolog-BH_RS3WO.js +1 -0
  169. package/dist/proto-DOtRmeXT.js +1 -0
  170. package/dist/pug-jyQP1XtO.js +1 -0
  171. package/dist/puppet-37ic6j3l.js +1 -0
  172. package/dist/purescript-Bm5O5oLm.js +7 -0
  173. package/dist/python-DwuVtWc2.js +518 -0
  174. package/dist/qml-DM3j-f55.js +1 -0
  175. package/dist/qmldir-MS3qTAOR.js +1 -0
  176. package/dist/qss-FJDVp-XM.js +1 -0
  177. package/dist/r-BXfENWL6.js +1 -0
  178. package/dist/racket-BbLA0SU8.js +353 -0
  179. package/dist/raku-D384ylkT.js +1 -0
  180. package/dist/razor-DokJXJPk.js +40 -0
  181. package/dist/red-jaXbsbtS.js +1 -0
  182. package/dist/reg-CrhH3_Og.js +1 -0
  183. package/dist/regexp-C_ZPRiAj.js +26 -0
  184. package/dist/rel-BuawaRXJ.js +1 -0
  185. package/dist/riscv-D-aEEwJo.js +13 -0
  186. package/dist/rose-pine-BIAFg_EN.js +1 -0
  187. package/dist/rose-pine-dawn-C5m_N-6l.js +1 -0
  188. package/dist/rose-pine-moon-B9S0JTD1.js +1 -0
  189. package/dist/rst-DlAMacAQ.js +1 -0
  190. package/dist/ruby-eQ1iIi6H.js +80 -0
  191. package/dist/rust-DGxQkqYo.js +1 -0
  192. package/dist/sas-BhBoFsCt.js +1 -0
  193. package/dist/sass-CMDmr8et.js +2 -0
  194. package/dist/scala-Cd0cRMx9.js +5 -0
  195. package/dist/scheme-BTeww-4z.js +136 -0
  196. package/dist/scss-DoQ2ojfq.js +90 -0
  197. package/dist/shaderlab-Bb-6Dmi7.js +1 -0
  198. package/dist/shellscript-BZfs-ost.js +1 -0
  199. package/dist/shellsession-B6XH_PlG.js +1 -0
  200. package/dist/slack-dark-C7oZ9nno.js +1 -0
  201. package/dist/slack-ochin-hXH8Gyq8.js +1 -0
  202. package/dist/smalltalk-DSsji4Hu.js +1 -0
  203. package/dist/snazzy-light-CA9nliXM.js +1 -0
  204. package/dist/solarized-dark-C86elO-m.js +1 -0
  205. package/dist/solarized-light-xPNGhBYe.js +1 -0
  206. package/dist/solidity-CThH5sBG.js +1 -0
  207. package/dist/soy-rVg8SA9F.js +1 -0
  208. package/dist/sparql-Cmp61EUJ.js +1 -0
  209. package/dist/splunk-6XBPEST2.js +1 -0
  210. package/dist/sql-DxR2xW-a.js +21 -0
  211. package/dist/ssh-config-BH1M7C1g.js +1 -0
  212. package/dist/stata-DGeqZgIM.js +3 -0
  213. package/dist/stylus-DSrLtGYv.js +15 -0
  214. package/dist/svelte-BMtjZn0z.js +1 -0
  215. package/dist/swift-C8WrqfNz.js +544 -0
  216. package/dist/sync-DItkgf9b.js +303 -0
  217. package/dist/synthwave-84-BBDuFDsq.js +1 -0
  218. package/dist/system-verilog-BscxmKrE.js +8 -0
  219. package/dist/systemd-IQPSIA3x.js +1 -0
  220. package/dist/tasl-BxwAa5i0.js +1 -0
  221. package/dist/tcl-C_8Fx7bH.js +1 -0
  222. package/dist/terraform-DBeuZS66.js +1 -0
  223. package/dist/tex-BqVuN0dX.js +1 -0
  224. package/dist/tokyo-night-eJfcURhx.js +1 -0
  225. package/dist/toml-BT9ZzGyQ.js +69 -0
  226. package/dist/tsv-DnLUQrgA.js +1 -0
  227. package/dist/tsx-BlxWTfDV.js +699 -0
  228. package/dist/turtle-C15OxdQ5.js +1 -0
  229. package/dist/twig-DDlPLETn.js +40 -0
  230. package/dist/typescript-DC8MraHL.js +666 -0
  231. package/dist/typespec-Bx89rGXK.js +1 -0
  232. package/dist/typst-I4qd5QHW.js +10 -0
  233. package/dist/v-C2TBxDwV.js +1 -0
  234. package/dist/vala-CO5hpdkB.js +1 -0
  235. package/dist/vb-beD-FUib.js +1 -0
  236. package/dist/verilog-DVfdqzEq.js +1 -0
  237. package/dist/vesper-BSB_bK09.js +1 -0
  238. package/dist/vhdl-QZ3jNtnE.js +515 -0
  239. package/dist/viml-BLluXI4E.js +1 -0
  240. package/dist/vitesse-black-B3g-KkBK.js +1 -0
  241. package/dist/vitesse-dark-Bxkoe-BC.js +1 -0
  242. package/dist/vitesse-light-Br6ll-O0.js +1 -0
  243. package/dist/vue-B6Rs-4k4.js +6 -0
  244. package/dist/vue-html-Blw9Jzz9.js +1 -0
  245. package/dist/vyper-o-cPXEvd.js +575 -0
  246. package/dist/wasm-2oxoyaYy.js +1 -0
  247. package/dist/wasm-Cicx_DS6.js +1 -0
  248. package/dist/wenyan-Cavfe_d0.js +1 -0
  249. package/dist/wgsl-BZz1Hhek.js +1 -0
  250. package/dist/wikitext-zfUCmn4z.js +40 -0
  251. package/dist/wolfram-BICIrM8O.js +1 -0
  252. package/dist/xml-sp2Egr0Z.js +1 -0
  253. package/dist/xsl-BQYvujlj.js +1 -0
  254. package/dist/yaml-C5gCGmDW.js +200 -0
  255. package/dist/zenscript-B1nm99XP.js +2 -0
  256. package/dist/zig-xtV5iK4E.js +1 -0
  257. package/license.txt +21 -0
  258. package/package.json +109 -0
  259. package/readme.md +535 -0
package/readme.md ADDED
@@ -0,0 +1,535 @@
1
+ <!--+ Warning: Content inside HTML comment blocks was generated by mdat and may be overwritten. +-->
2
+
3
+ <!-- title -->
4
+
5
+ # yanki
6
+
7
+ <!-- /title -->
8
+
9
+ <!-- badges -->
10
+
11
+ [![NPM Package yanki](https://img.shields.io/npm/v/yanki.svg)](https://npmjs.com/package/yanki)
12
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
13
+
14
+ <!-- /badges -->
15
+
16
+ <!-- short-description -->
17
+
18
+ **An ultra-simple CLI tool and TypeScript library for syncing Markdown to Anki flashcards. No customization. No configuration. No fuss.**
19
+
20
+ <!-- /short-description -->
21
+
22
+ > \[!IMPORTANT]
23
+ > Yanki is feature-complete but will remain zero-versioned until it's been thoroughly tested. Please exercise caution and make backups of your Anki notes until the 1.0.0 release.
24
+
25
+ ## Overview
26
+
27
+ Yanki simply syncs a folder of Markdown notes to Anki. The primary novelty of its approach is in how Markdown is translated into Anki notes. The **structure** of a Markdown note determines the **type** of Anki note it becomes, so no extra syntax or Anki-specific markup is required — just pure Markdown.
28
+
29
+ This library leverages the [`yanki-connect`](https://github.com/kitschpatrol/yanki-connect) library, and powers the soon-to-be-released `yanki-obsidian` plugin.
30
+
31
+ The "Y" prefix in "Yanki" is in the "Yet another" naming tradition; a nod to Anki's robust and occasionally duplicative ecosystem of third-party tools. (Also, appropriately, Yankī are a variety of [truant youth](https://en.wikipedia.org/wiki/Yankee#/media/File:ヤンキー.jpg).)
32
+
33
+ ## Quick start
34
+
35
+ Assuming you have a folder of Markdown note files, the [Anki app](https://apps.ankiweb.net) is open and has the the [Anki-Connect](https://foosoft.net/projects/anki-connect/) add-on installed:
36
+
37
+ ```sh
38
+ npx yanki ./folder-of-markdown
39
+ ```
40
+
41
+ This will turn the folder's Markdown files into Anki notes and send them up to the Anki database.
42
+
43
+ ## Features
44
+
45
+ ### One Markdown file = one Anki note
46
+
47
+ Avoid the complexity of mixing and matching multi-note and single-note syntaxes. One local Markdown file always yields one Anki note.
48
+
49
+ ### Local folder hierarchy = Anki deck hierarchy
50
+
51
+ Yanki uses the source Markdown file's parent directory name as the deck name. Complex folder hierarchies are also supported — Anki decks will be created and nested as needed to match the structure of the local file system.
52
+
53
+ ### Embrace of Anki's default note types
54
+
55
+ More note types, more problems.
56
+
57
+ Yanki _only_ supports turning Markdown into the _Basic_, _Basic (and reversed card)_, _Basic (type in the answer)_, and _Cloze_ note types that ship as defaults in the Anki App.
58
+
59
+ ### Infer Anki note type from Markdown structure
60
+
61
+ Since the number of supported note types is small, the type of Anki note to create from a given document can be inferred from a few simple rules about the structure of the Markdown.
62
+
63
+ For example, a Basic note is any Markdown file with a `---` horizontal rule splitting the front and back of the card:
64
+
65
+ ```md
66
+ I'm the front.
67
+
68
+ ---
69
+
70
+ I'm the back.
71
+ ```
72
+
73
+ That's it, no extra metadata or Anki-specific markup is required. You can add whatever additional Markdown syntax you'd like to style the note.
74
+
75
+ The structural cues for all four supported note types are described [later in this document](#markdown-note-types).
76
+
77
+ ### Tags in frontmatter
78
+
79
+ Optionally, you can add a `tags` array to your Markdown file's frontmatter and have it automatically synchronized to the Anki database. Frontmatter is also used to store the Anki note's ID after an initial synchronization.
80
+
81
+ ### Intelligent synchronization
82
+
83
+ Your local Markdown files are the single point of truth for what will and up in Anki, but Yanki knows to leave your other Anki notes alone.
84
+
85
+ When you edit a local markdown note, Yanki makes every effort to update rather than delete it in the Anki database so that progress is preserved.
86
+
87
+ But when you do want to delete something, it's as simple as deleting the local Markdown note from the file system and running `yanki sync` to remove it from the Anki database. Protections are in place to prevent deleting Anki notes that weren't initially created by Yanki.
88
+
89
+ If you use [AnkiWeb](https://ankiweb.net/) to sync your notes to the cloud, Yanki will also trigger this next step in the sync, automating the flow from Markdown → Anki → AnkiWeb in one shot.
90
+
91
+ ### Existing notes are untouched
92
+
93
+ Yanki tags the notes it's in charge of with a hidden field, so it will never touch your existing Anki notes. (_But please exercise caution until the 1.0 release..._)
94
+
95
+ ### Fancy markdown
96
+
97
+ An extended palette of markdown syntax is available out of the box:
98
+
99
+ - [GitHub Flavored Markdown](https://github.github.com/gfm/), including `| tables |`, `~~strike-throughs~~`, `- [x] task lists`, and autolinks.
100
+ - Syntax highlighting via [Shiki](https://shiki.style).
101
+ - GitHub-style [Alerts](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#alerts).
102
+ - [WikiLinks](https://github.com/Python-Markdown/markdown/blob/master/docs/extensions/wikilinks.md)
103
+ - [LaTeX formatted mathematical expressions](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/writing-mathematical-expressions) via [MathJax](https://www.mathjax.org)
104
+ - Support for the [`==highlights==`](https://github.com/ipikuka/remark-flexible-markers) syntax.
105
+
106
+ ### Custom styles
107
+
108
+ Yanki uses Anki's built-in CSS stylesheet to style cards by default, but it makes it simple to set a custom stylesheet for all of your Yanki-managed notes without having to go on a click-quest in the Anki user interface.
109
+
110
+ ## Markdown note types
111
+
112
+ Yanki automatically infers the _type_ of Note you'd like to create in Anki based on the presence or absence of certain element in your Markdown files.
113
+
114
+ The rules were designed with the semantic and visual nature of Markdown in mind.
115
+
116
+ The most minimal examples to "trigger" different note types are shown below, but the implementation can handle additional weirdness and will generally do the right thing if it encounters elements that might indicate conflicting note types.
117
+
118
+ You're free to use additional Markdown in your note files to style and structure the front and back of your flashcards. Image markup will work, but currently assets must be hosted externally and are not copied into Anki's media storage system.
119
+
120
+ ### Basic
121
+
122
+ A **basic** card is created from any file with a `---`:
123
+
124
+ ```md
125
+ This is the front of the card
126
+
127
+ ---
128
+
129
+ This is the back of the card
130
+ ```
131
+
132
+ ### Basic (and reversed card)
133
+
134
+ Doubling up the `---` identifies the note as being **reversible** (and will result in the generation of two cards in Anki).
135
+
136
+ _Mnemonic: Twice the `---` for twice the cards._
137
+
138
+ ```md
139
+ Sometimes the answer is the question
140
+
141
+ ---
142
+
143
+ ---
144
+
145
+ Sometimes the question is the answer
146
+ ```
147
+
148
+ <em>Mnemonic: The syntax resembles a `_blank to be filled in_`.</em>
149
+
150
+ ### Basic (type in the answer)
151
+
152
+ If the last statement in the Markdown file is `_emphasized like this_`, it becomes the type-in-the-answer text in Anki.
153
+
154
+ ```md
155
+ Jazz isn't dead
156
+
157
+ _It just smells funny_
158
+ ```
159
+
160
+ ---
161
+
162
+ ### Cloze
163
+
164
+ Text that is `~~struck through~~` with the [somewhat esoteric double-tilde syntax](https://github.github.com/gfm/#strikethrough-extension-) will be hidden in the resulting _cloze_ card:
165
+
166
+ _Mnemonic: The `~~strike through~~` implies redaction._
167
+
168
+ ```md
169
+ All will be ~~revealed~~.
170
+ ```
171
+
172
+ Multiple clozes are supported, which will create additional cards. You can add a `---` to include back-of-card information as well. Hints are also supported, and are indicated by giving the hint text `_emphasis_` at the end of the cloze strike-through:
173
+
174
+ ```md
175
+ ~~All~~ will be ~~revealed _here's a hint_~~.
176
+
177
+ ---
178
+
179
+ Additional revelations on the back of the card.
180
+ ```
181
+
182
+ Clozing a block element is not currently supported.
183
+
184
+ ## Getting started
185
+
186
+ ### Dependencies
187
+
188
+ The `yanki` CLI tool requires Node 18+. The exported TypeScript / JavaScript APIs are isomorphic, and can run in both browser-based and Node runtime environments. The Yanki library is ESM-only, is implemented in TypeScript, and bundles a complete set of type definitions.
189
+
190
+ The [Anki](https://apps.ankiweb.net) desktop app with the [Anki-Connect](https://foosoft.net/projects/anki-connect/) add-on installed and configured is also required to do anything useful with the library.
191
+
192
+ ### Installation
193
+
194
+ Invoke directly:
195
+
196
+ ```sh
197
+ npx yanki ./folder-to-sync/**/*.md
198
+ ```
199
+
200
+ ...or install globally:
201
+
202
+ ```sh
203
+ npm install --global yanki
204
+ ```
205
+
206
+ ...or install locally in your JavaScript or TypeScript project to use the exported APIs:
207
+
208
+ ```sh
209
+ npm install --save-dev yanki
210
+ ```
211
+
212
+ ## Usage
213
+
214
+ ### Basics
215
+
216
+ #### Setup
217
+
218
+ Create a folder of Markdown files that you'd like to use as Anki notes. (See the [section on Markdown notes](#markdown-note-types) for details on how to structure your document to create different card types in Anki.)
219
+
220
+ Launch the Anki desktop app. Ensure that the [Anki-Connect](https://foosoft.net/projects/anki-connect/) add-on is installed and set up.
221
+
222
+ #### Create
223
+
224
+ Pass the path to a folder of Markdown notes to turn them into Anki notes and send them to the Anki database:
225
+
226
+ ```sh
227
+ yanki ./your-deck-folder
228
+ ```
229
+
230
+ You'll now see your Markdown files as HTML-rendered notes in the Anki desktop app.
231
+
232
+ #### Update
233
+
234
+ Edit the Markdown file locally (not in Anki!) and run `yanki ./your-deck-folder` again.
235
+
236
+ #### Delete
237
+
238
+ Delete the Markdown file locally (not in Anki!) and run `yanki ./your-deck-folder` again.
239
+
240
+ ### CLI
241
+
242
+ All available commands and options for advanced use cases are described below, but shouldn't typically be necessary.
243
+
244
+ <!-- cli-help -->
245
+
246
+ #### Command: `yanki`
247
+
248
+ Run a Yanki command. Defaults to `sync` if a command is not provided.
249
+
250
+ This section lists top-level commands for `yanki`.
251
+
252
+ If no command is provided, `yanki sync` is run by default.
253
+
254
+ Usage:
255
+
256
+ ```txt
257
+ yanki [command]
258
+ ```
259
+
260
+ | Command | Argument | Description |
261
+ | -------- | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
262
+ | `sync` | `<directory>` `[options]` | Perform a one-way synchronization from a local directory of Markdown files to the Anki database. Any Markdown files in subdirectories are included as well. _(Default command.)_ |
263
+ | `list` | `[options]` | Utility command to list Yanki-created notes in the Anki database. |
264
+ | `delete` | `[options]` | Utility command to manually delete Yanki-created notes in the Anki database. This is for advanced use cases, usually the `sync` command takes care of deleting files from Anki Database once they're removed from the local file system. |
265
+ | `style` | `[options]` | Utility command to set the CSS stylesheet for all present and future Yanki-created notes. |
266
+
267
+ _See the sections below for more information on each subcommand._
268
+
269
+ #### Subcommand: `yanki sync`
270
+
271
+ Perform a one-way synchronization from a local directory of Markdown files to the Anki database. Any Markdown files in subdirectories are included as well.
272
+
273
+ Usage:
274
+
275
+ ```txt
276
+ yanki sync <directory> [options]
277
+ ```
278
+
279
+ | Positional Argument | Description | Type |
280
+ | ------------------- | ------------------------------------------------------------------------ | -------- |
281
+ | `directory` | The path to the local directory of Markdown files to sync. _(Required.)_ | `string` |
282
+
283
+ | Option | Alias | Description | Type | Default |
284
+ | -------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------- |
285
+ | `--dry-run` | `-d` | Run without making any changes to the Anki database. See a report of what would have been done. | `boolean` | `false` |
286
+ | `--namespace` | `-n` | Advanced option for managing multiple Yanki synchronization groups. Case insensitive. See the readme for more information. | `string` | `"Yanki"` |
287
+ | `--anki-connect` | | Host and port of the Anki-Connect server. The default is usually fine. See the Anki-Connect documentation for more information. | `string` | `"http://127.0.0.1:8765"` |
288
+ | `--anki-auto-launch` | `-l` | Attempt to open the Anki desktop app if it's not already running. (Experimental, macOS only.) | `boolean` | `false` |
289
+ | `--anki-web` | `-w` | Automatically sync any changes to AnkiWeb after Yanki has finished syncing locally. If false, only local Anki data is updated and you must manually invoke a sync to AnkiWeb. This is the equivalent of pushing the "sync" button in the Anki app. | `boolean` | `true` |
290
+ | `--json` | | Output the sync report as JSON. | `boolean` | `false` |
291
+ | `--verbose` | | Enable verbose logging. | `boolean` | `false` |
292
+ | `--help` | `-h` | Show help | `boolean` | |
293
+ | `--version` | `-v` | Show version number | `boolean` | |
294
+
295
+ #### Subcommand: `yanki list`
296
+
297
+ Utility command to list Yanki-created notes in the Anki database.
298
+
299
+ Usage:
300
+
301
+ ```txt
302
+ yanki list [options]
303
+ ```
304
+
305
+ | Option | Alias | Description | Type | Default |
306
+ | -------------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------- |
307
+ | `--namespace` | `-n` | Advanced option to list notes in a specific namespace. Case insensitive. Notes from the default internal namespace are listed by default. Pass `'*'` to list all Yanki-created notes in the Anki database. | `string` | `"Yanki"` |
308
+ | `--anki-connect` | | Host and port of the Anki-Connect server. The default is usually fine. See the Anki-Connect documentation for more information. | `string` | `"http://127.0.0.1:8765"` |
309
+ | `--anki-auto-launch` | `-l` | Attempt to open the Anki desktop app if it's not already running. (Experimental, macOS only.) | `boolean` | `false` |
310
+ | `--json` | | Output the list of notes as JSON to stdout. | `boolean` | `false` |
311
+ | `--help` | `-h` | Show help | `boolean` | |
312
+ | `--version` | `-v` | Show version number | `boolean` | |
313
+
314
+ #### Subcommand: `yanki delete`
315
+
316
+ Utility command to manually delete Yanki-created notes in the Anki database. This is for advanced use cases, usually the `sync` command takes care of deleting files from Anki Database once they're removed from the local file system.
317
+
318
+ Usage:
319
+
320
+ ```txt
321
+ yanki delete [options]
322
+ ```
323
+
324
+ | Option | Alias | Description | Type | Default |
325
+ | -------------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------- |
326
+ | `--dry-run` | `-d` | Run without making any changes to the Anki database. See a report of what would have been done. | `boolean` | `false` |
327
+ | `--namespace` | `-n` | Advanced option to list notes in a specific namespace. Case insensitive. Notes from the default internal namespace are listed by default. If you've synced notes to multiple namespaces, Pass `'*'` to delete all Yanki-created notes in the Anki database. | `string` | `"Yanki"` |
328
+ | `--anki-connect` | | Host and port of the Anki-Connect server. The default is usually fine. See the Anki-Connect documentation for more information. | `string` | `"http://127.0.0.1:8765"` |
329
+ | `--anki-auto-launch` | `-l` | Attempt to open the Anki desktop app if it's not already running. (Experimental, macOS only.) | `boolean` | `false` |
330
+ | `--anki-web` | `-w` | Automatically sync any changes to AnkiWeb after Yanki has finished syncing locally. If false, only local Anki data is updated and you must manually invoke a sync to AnkiWeb. This is the equivalent of pushing the "sync" button in the Anki app. | `boolean` | `true` |
331
+ | `--json` | | Output the list of deleted notes as JSON to stdout. | `boolean` | `false` |
332
+ | `--verbose` | | Enable verbose logging. | `boolean` | `false` |
333
+ | `--help` | `-h` | Show help | `boolean` | |
334
+ | `--version` | `-v` | Show version number | `boolean` | |
335
+
336
+ #### Subcommand: `yanki style`
337
+
338
+ Utility command to set the CSS stylesheet for all present and future Yanki-created notes.
339
+
340
+ Usage:
341
+
342
+ ```txt
343
+ yanki style [options]
344
+ ```
345
+
346
+ | Option | Alias | Description | Type | Default |
347
+ | -------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------------------------- |
348
+ | `--dry-run` | `-d` | Run without making any changes to the Anki database. See a report of what would have been done. | `boolean` | `false` |
349
+ | `--css` | `-c` | Path to the CSS stylesheet to set for all Yanki-created notes. If not provided, the default Anki stylesheet is used. | `string` | |
350
+ | `--anki-connect` | | Host and port of the Anki-Connect server. The default is usually fine. See the Anki-Connect documentation for more information. | `string` | `"http://127.0.0.1:8765"` |
351
+ | `--anki-auto-launch` | `-l` | Attempt to open the Anki desktop app if it's not already running. (Experimental, macOS only.) | `boolean` | `false` |
352
+ | `--anki-web` | `-w` | Automatically sync any changes to AnkiWeb after Yanki has finished syncing locally. If false, only local Anki data is updated and you must manually invoke a sync to AnkiWeb. This is the equivalent of pushing the "sync" button in the Anki app. | `boolean` | `true` |
353
+ | `--json` | | Output the list of updated note types / models as JSON to stdout. | `boolean` | `false` |
354
+ | `--verbose` | | Enable verbose logging. | `boolean` | `false` |
355
+ | `--help` | `-h` | Show help | `boolean` | |
356
+ | `--version` | `-v` | Show version number | `boolean` | |
357
+
358
+ <!-- /cli-help -->
359
+
360
+ ### Library
361
+
362
+ #### API
363
+
364
+ This package also exposes an API for integrating syncing capability programmatically in other contexts. (For example, in the soon-to-be-released `yanki-obsidian` plugin.
365
+
366
+ The primary functions of interest are:
367
+
368
+ ```ts
369
+ function getNoteFromMarkdown(markdown: string, options: NoteFromMarkdownOptions): Promise<YankiNote>
370
+
371
+ function syncFiles(
372
+ allLocalFilePaths: string[],
373
+ options?: PartialDeep<SyncOptions>,
374
+ readFile?: (filePath: string) => Promise<string>,
375
+ writeFile?: (filePath: string, data: string) => Promise<void>,
376
+ ): Promise<SyncReport>
377
+
378
+ function syncNotes(
379
+ allLocalNotes: YankiNote[],
380
+ options?: PartialDeep<SyncOptions>,
381
+ ): Promise<SyncReport>
382
+
383
+ function listNotes(options?: PartialDeep<ListOptions>): Promise<ListReport>
384
+
385
+ function cleanNotes(options?: PartialDeep<CleanOptions>): Promise<CleanReport>
386
+
387
+ function setStyle(options: PartialDeep<StyleOptions>): Promise<StyleReport>
388
+ ```
389
+
390
+ See the [source code](https://github.com/kitschpatrol/yanki/blob/main/src/lib/index.ts) for additional exports and inline documentation.
391
+
392
+ ## Advanced Features
393
+
394
+ ### Namespaces
395
+
396
+ For simplicity's sake, Yanki anticipates syncing one folder of notes on one machine as its primary use case. Every note uploaded to Anki has a "namespace" string in a hidden field. Yanki uses this field to identify the notes it's in charge of, and to identify notes for deletion if they are present in the Anki database but missing in the collection of local markdown notes.
397
+
398
+ For example, if you run:
399
+
400
+ ```sh
401
+ yanki sync ./important-cards
402
+ ```
403
+
404
+ Followed by:
405
+
406
+ ```sh
407
+ yanki sync ./more-important-cards
408
+ ```
409
+
410
+ Any notes synced from the `important-cards` folder _will be deleted_ from Anki by the second command to sync `more-important-cards`, because they share the same default namespace, and because you local notes are a single source of truth for the sync system.
411
+
412
+ If you don't want this behavior, then you can pass the `--namespace` flag to explicitly state that you want the commands to exist in separate namespaces:
413
+
414
+ ```sh
415
+ yanki sync ./important-cards --namespace "Foo"
416
+ yanki sync ./more-important-cards --namespace "Bar"
417
+ ```
418
+
419
+ This will let both directories sync and co-exist.
420
+
421
+ But in general, a better solution would be to give them a common parent directory and, just sync that:
422
+
423
+ ```sh
424
+ # Move to a common parent
425
+ mv ./important-cards ~/cards/imporant-cards
426
+ mv ./more-important-cards ~/cards/more-important-cards
427
+
428
+ # Sync the parent
429
+ yanki sync ~/cards
430
+ ```
431
+
432
+ This makes `~/cards` the single source of truth, and spares the cognitive overhead of namespaces.
433
+
434
+ Yanki will put the cards in eponymous decks, so you'll still have a clean separation of the two groups of cards in Anki.
435
+
436
+ ### Styles
437
+
438
+ The [`yanki style`](#subcommand-yanki-style) command lets you assign a CSS stylesheet to all Yanki-managed notes.
439
+
440
+ Because of how styles work in Anki and how Yanki manages card models, assigned style is a _global_ operation to _all_ Yanki-managed cards. (Though your other Anki cards, as always, will be untouched.)
441
+
442
+ Your custom stylesheets can take advantage of several CSS classes added to the top-level `<div>` of each card by Yanki.
443
+
444
+ The classes include:
445
+
446
+ - `yanki`: Shared by all cards generated by Yanki
447
+ - `namespace-$name`: The namespace associated with the card
448
+ - `front` | `back`: The "side" of the card"
449
+ - `model-$name`: The name of the card type / model, one of the following. They're wordy, but consistent with Anki's default model naming scheme:
450
+ - `model-yanki-basic`
451
+ - `model-yanki-basic-and-reversed-card`
452
+ - `model-yanki-basic-type-in-the-answer`
453
+ - `model-yanki-cloze`
454
+
455
+ For example, the front of a basic card would look like:
456
+
457
+ ```html
458
+ <div class="yanki namespace-yanki front model-yanki-basic">
459
+ <!-- The rest of the card's markup is here -->
460
+ </div>
461
+ ```
462
+
463
+ ### Browser environments
464
+
465
+ The Yanki TypeScript / JavaScript library is idempotent, so you can run it in a browser you'd like.
466
+
467
+ There's one exception, the `syncFiles(...)` function, which by default relies on file system access to work.
468
+
469
+ To retain `syncFiles(...)`'s utility in a browser environment, has the optional arguments `readFile` and `writeFile`, which are implemented by `node:fs/promises` by default in Node environments.
470
+
471
+ Running Yanki in a browser environment required implementing and passing `readFile` and `writeFile` implementations to `syncFiles(...)` that are suited to your particular use case. (A warning will be provided if you neglect to do so.)
472
+
473
+ The rest of the library should work fine in both contexts without special measures.
474
+
475
+ ## Background
476
+
477
+ ### Implementation notes
478
+
479
+ The excellent [unified](https://unifiedjs.com) / [remark](https://remark.js.org) / [rehype](https://github.com/rehypejs/rehype) libraries are used extensively to traverse and render the underlying Markdown ASTs.
480
+
481
+ [Anki-Connect](https://foosoft.net/projects/anki-connect/) provides access to Anki's database.
482
+
483
+ For type safety, access to Anki-Connect is managed through my wrapper library, [yanki-connect](https://github.com/kitschpatrol/yanki-connect).
484
+
485
+ Behind the scenes, Yanki creates new note type models to match the four default Anki types. It keeps track of the notes it has ownership of via a hidden `YankiNamespace` field in each note.
486
+
487
+ ### Other projects
488
+
489
+ - Luke Murray's [markdown-anki-decks](https://github.com/lukesmurray/markdown-anki-decks)
490
+ - Oleksandr Shlinchak's [mdanki](https://github.com/ashlinchak/mdanki).
491
+ - Ben Weinstein-Raun's [ankidown](https://github.com/ankicommunity/ankidown/)
492
+ - Alex Biosa's [Markdown2Anki](https://github.com/Mochitto/Markdown2Anki)
493
+ - Pradhyo Bijja's [anki-markdown-notes](https://github.com/Pradhyo/anki-markdown-notes)
494
+
495
+ ## The future
496
+
497
+ Areas of improvement before a 1.0.0 release:
498
+
499
+ - [ ] Refine handling of duplicate note ID edge cases.
500
+
501
+ Possible features on the horizon:
502
+
503
+ - [ ] Including some built-in CSS stylesheet options might be nice, since Anki's defaults can't always anticipate the kinds of HTML you're likely to generate from Markdown.
504
+
505
+ - [ ] Support for Mermaid diagrams.
506
+
507
+ - [ ] Either embedding media assets or implementing integration with Anki's media library could be helpful for offline review. (Though externally hosted image links seem generally fine for now.)
508
+
509
+ - [ ] It would be nice to find a way to talk to the Anki database that doesn't require the Anki app to be running, but my research hasn't yet turned up anything as robust and reliable as Anki-Connect for this purpose.
510
+
511
+ ## Maintainers
512
+
513
+ [@kitschpatrol](https://github.com/kitschpatrol)
514
+
515
+ ## Acknowledgements
516
+
517
+ Thanks to Alex Yatskov for creating [Anki-Connect](https://foosoft.net/projects/anki-connect/).
518
+
519
+ Thanks to the [unified team](https://github.com/orgs/unifiedjs/people) for their superb ecosystem of AST tools.
520
+
521
+ <!-- contributing -->
522
+
523
+ ## Contributing
524
+
525
+ [Issues](https://github.com/kitschpatrol/yanki/issues) and pull requests are welcome.
526
+
527
+ <!-- /contributing -->
528
+
529
+ <!-- license -->
530
+
531
+ ## License
532
+
533
+ [MIT](license.txt) © Eric Mika
534
+
535
+ <!-- /license -->