在 Emacs 中管理您的 Anki 卡片。
目前辅助 Anki 制卡的插件主要是 eyeinsky/org-anki 和 louietan/anki-editor 。
anki-editor 我只是很浅的用了一下,马上就被它的格式要求给劝退了: anki-editor 需要使用专门的标题行来标记卡片的正反面。这首先导致原有的笔记不能快速转化为卡片,需要自己转换;其次制作好的卡片几乎只能由 anki-editor 使用,无法融入到现有的工作流中,需要专门的文件来存放。
org-anki 比较好地解决了上述缺点,它使用当前的标题栏作为正面,笔记内容作为反面。这正好和我记笔记的方式吻合,原有的笔记马上就能拿来制卡,也不需要专门的文件。所以 org-anki 我用了很长一段时间,通过pdf-tools + org-note + org-anki 实践渐进式阅读,已经读了很多大部头的书。
org-anki 的缺点如下:
- 同步速度慢
org-anki 同步笔记实在是太慢了。我经常需要修改卡片,对于卡片比较多的文件,同步简直是个噩梦。
以 examples/headings.org 为例, org-anki 和 anki-helper 同步和删除所有卡片( 422张)的时间对比如下:
(benchmark 1 ‘(xxx-sync-all)) (benchmark 1 ‘(xxx-delete-all)) org-anki 123.855104s (35.423518s in 5 GCs) 150.862886s (21.709634s in 8 GCs) anki-helper 0.410945s 0.098308s - 仅支持 entry ,模板字段填充方式僵硬
org-anki 的另外一个问题是它只适用于 entry ,没有提供 API 来添加自定义的卡片制做函数。一个 entry 一张卡片的好处一是可以保存卡片的信息,如卡片 ID,是否已经修改等,这样方便后续修改;二是可以对不同的 entry 进行不同操作。但是有些类型的卡片是不需要这类额外的信息的,一个常见的例子就是单词卡片。我需要只是收集生词,然后同步到 Anki ,后续几乎不会修改。显然一个单词一个 entry 是比较浪费的。
org-anki 仅支持两种字段填充方式:一是标题行为卡片正面,内容为反面;二是使用子标题来区分字段。后一种做法又回到了 anki-editor。这里的问题是每个人的习惯是不一样的,自定义的模板也千奇百怪,所以必须要提供一套足够灵活的 API 来方便用户创建自己的字段填充函数。
- 缺乏多媒体内容处理
org-anki 针对图片处理的 PR 还没有合并,其他多媒体内容也不支持。
anki-helper 支持在卡片中包含图片和音频文件的链接,在制卡的过程中会将其复制到Anki 的媒体目录下。
我认为 anki-helper 比较好的解决了上述了个问题,同时增加了其他特点:
- 尽可能地保持原始文本内容不变
对于 Cloze 类型的卡片来说,常规的做法是使用
{{c1:xxx}}
这种形式,但是这会破坏原有内容,不利于其他操作。这其实是一种变相的专有化。Anki-helper 使用 org-mode 内置的富文本标记来作为完形填空的标志,假设有以下文本:
*Canberra* was founded in *1913*.
在制卡的过程中,它会变成如下格式:
{{c1::Canberra}} was founded in {{c2::1913}}.
我认为这样是符合直觉的,因为你强调的部分往往是你想要记住的部分。
注意: 此功能由变量
anki-helper-cloze-use-emphasis
控制,默认情况下关闭。同时不支持下述类型的转化:{{c1::Canberra}} was founded in {{c1::1913}}.
- Anki
- FooSoft/anki-connect
- curl
M-x package-vc-install RET https://github.com/Elilif/emacs-anki-helper RET
(add-to-list 'load-path "path-to-anki-helper")
(require 'anki-helper)
使用方法和 org-anki 类似。
anki-helper-cloze-use-emphasis
是否将
org-emphasis-alist
中的标记视为 Cloze 的标记。该值如果是一个符号,如 ~bold~,则文本*Canberra* was founded in *1913*.
在制卡的过程中会变成如下格式:
{{c1::Canberra}} was founded in {{c2::1913}}.
注意: 不支持下述类型的转化:
{{c1::Canberra}} was founded in {{c1::1913}}.
anki-helper-default-note-type
默认的卡片模板类型
anki-helper-default-deck
默认的卡组名称
anki-helper-default-tags
默认的卡片标签
anki-helper-default-match
用于筛选满足条件的 entry ,详见变量文档。
anki-helper-skip-function
用于判断是否跳过某个 entry ,详见
org-map-entries
anki-helper-inherit-tags
是否继承父标题的标签
anki-helper-media-directory
Anki 保存多媒体文件的目录
anki-helper-note-types
模板名称及其对应的字段
#+ANKI_DECK:
#+ANKI_MATCH:
#+ANKI_NOTE_TYPE:
#+ANKI_TAGS:
上述关键字分别对应相应的全局变量。
比如说您有如下 org 文件:
#+ANKI_DECK: Default #+ANKI_MATCH: TODO="TODO"|+DATE="today" #+ANKI_NOTE_TYPE: Basic #+ANKI_TAGS: test * test note 1 back side * TODO test note 2 back side * test note 3 :PROPERTIES: :DATE: today :END: back side * test note 4 back side
那么使用 anki-helper-entry-sync-all
只会创建两张卡片: test note 2
和 test note
3
。
ANKI_NOTE_TYPE
ANKI_DECK
每个 entry 可以有各自的属性。上述变量的优先级为 Properties > file-local
variables > global variables
。
anki-helper 默认提供了一系列函数来对 entry 类型的卡片进行操作(后续会增加更多操作):
anki-helper-entry-sync
将光标位置下的 entry 制成卡片,如果已经是卡片则忽略。
anki-helper-entry-sync-all
将当前 buffer 中所有满足条件的 entries 制成卡片,如果已经是卡片则忽略。
anki-helper-entry-delete
如果光标下的 entry 是卡片且满足条件,则删除。
anki-helper-entry-delete-all
删除当前 buffer 中所有满足条件的卡片。
anki-helper-entry-update
如果光标下的 entry 是卡片且有过修改,则更新。
anki-helper-entry-update-all
更新当前 buffer 中所有修改过的卡片。
anki-helper-entry-browse
在 Anki 中浏览当前 entry
anki-helper 提供了几个 API :
anki-helper-request
anki-helper-create-note
anki-helper-create-notes
具体用法详见函数文档。
作为参考,您可以查阅 anki-helper-set-front-region
和 anki-helper-make-two-sided-card
两个函数,它们提供了一种交互式制卡的方案。
anki-helper-find-notes
在 Anki 中搜索指定 QUERY
变量 anki-helper-fields-get-alist
设置了两个基本的字段获取函数:~anki-helper-fields-get-default~ 用于 Anki 默认的 Basic 模板,使用标题行为卡片正面,内容为卡片反面、~anki-helper-fields-get-cloze~ 用于 Anki 默认的 Cloze 模板,使用标题下的内容来填充 Text
字段,~Back Extra~ 字段为标题行。您可以定义自己的字段获取函数,详见 anki-helper-fields-get-alist
的文档。
详见 anki-helper-callback-alist