Merge branch 'develop'
This commit is contained in:
commit
b8c48138b9
2 changed files with 277 additions and 151 deletions
86
README.org
86
README.org
|
@ -1,33 +1,69 @@
|
|||
anki-editor -- an Emacs package that helps you create Anki cards in Org-mode
|
||||
|
||||
* Requirements
|
||||
- [[https://github.com/FooSoft/anki-connect#installation][anki-connect]], an Anki add-on which is required for this package to
|
||||
interact with Anki.
|
||||
|
||||
- [[https://github.com/FooSoft/anki-connect#installation][anki-connect]],
|
||||
an Anki add-on required by this package to interact with Anki.
|
||||
- curl
|
||||
|
||||
* Usage
|
||||
1. Download it and put it into your Emacs' =load-path=
|
||||
2. =(require 'anki-editor)=
|
||||
3. Write notes in org syntax, e.g. [[./examples.org][examples.org]]
|
||||
- Headings of deck are tagged with =deck=
|
||||
- Headings of note are tagged with =note=
|
||||
- Custom properties of a note heading can be used to specify note
|
||||
type and tags
|
||||
- Subheadings of a note heading are fields of its note type
|
||||
- The contents of field headings will be converted to html by
|
||||
org-mode's html backend, with the latex syntax translated to
|
||||
the Anki style
|
||||
4. Command Cheat Sheet
|
||||
| Command | Default Keybinding | Description |
|
||||
|-----------------------------------------------+--------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| =anki-editor-setup-default-keybindings= | N/A | Set up default keybindings. |
|
||||
| =anki-editor-submit= | C-c a s | Send notes in current buffer to Anki. |
|
||||
| =anki-editor-insert-deck= | C-c a i d | Insert a deck heading with the same level as current heading. With prefix, only insert the deck name. |
|
||||
| =anki-editor-insert-note= | C-c a i n | Insert a note heading that's one level lower to current heading. The inserted heading will be structured with the property drawer and subheadings that correspond to the fields of the selected note type. |
|
||||
| =anki-editor-export-heading-contents-to-html= | C-c a e | Export the contents of the heading at point to HTML. |
|
||||
| =anki-editor-convert-region-to-html= | N/A | Convert and replace region to HTML. |
|
||||
|
||||
*Not a native speaker, hope this README is clear enough. Happy hacking :)*
|
||||
** Installation
|
||||
|
||||
** Demo
|
||||
[[./demo.gif]]
|
||||
- Clone this repo and add the directory to your Emacs' =load-path=,
|
||||
then =(require 'anki-editor)=.
|
||||
- Or if you use =package.el=, just download
|
||||
[[./anki-editor.el][anki-editor.el]] and visit it in Emacs, then
|
||||
=M-x package-install-file=.
|
||||
|
||||
** The Syntax
|
||||
|
||||
Now you can compose Anki notes in Org syntax, e.g. lists, code
|
||||
blocks, tables, latex fragments / environments, when being
|
||||
submitted to Anki, they will be converted to HTML by Org-mode's
|
||||
HTML backend with specific markers (e.g. latex) translated to the
|
||||
Anki style.
|
||||
|
||||
The structure of contents is as follow, which is inspired by
|
||||
=org-drill=. See [[./examples.org][examples.org]] for reference.
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
* English :deck:
|
||||
** Vocabulary
|
||||
*** Item :note:
|
||||
:PROPERTIES:
|
||||
:ANKI_NOTE_TYPE: Basic (and reversed card)
|
||||
:ANKI_TAGS: vocab idioms
|
||||
:END:
|
||||
**** Front
|
||||
(it's) raining cats and dogs
|
||||
**** Back
|
||||
it's raining very hard
|
||||
#+END_EXAMPLE
|
||||
|
||||
- Headings of deck are tagged with =deck=
|
||||
- Headings of note are tagged with =note=
|
||||
- Levels are not significant, but note headings must be descendents
|
||||
of their deck headings
|
||||
- Custom properties of a note heading can be used to specify note
|
||||
type and tags
|
||||
- Subheadings of a note heading are fields of its note type
|
||||
|
||||
** Command Cheatsheet
|
||||
|
||||
| Command | Keybinding | Brief Description |
|
||||
|-----------------------------------------------+------------+---------------------------------------------------------------|
|
||||
| =anki-editor-setup-default-keybindings= | N/A | Set up default keybindings. |
|
||||
| =anki-editor-submit= | C-c a s | Send notes in current buffer to Anki. |
|
||||
| =anki-editor-insert-deck= | C-c a i d | Insert a deck heading with the same level as current heading. |
|
||||
| =anki-editor-insert-note= | C-c a i n | Insert the skeleton of a note. |
|
||||
| =anki-editor-export-heading-contents-to-html= | C-c a e | Export the contents of the heading at point to HTML. |
|
||||
| =anki-editor-convert-region-to-html= | N/A | Convert and replace region to HTML. |
|
||||
| =anki-editor-anki-connect-upgrade= | N/A | Upgrade anki-connect to the latest version. |
|
||||
|
||||
|
||||
*Since I'm not a native English speaker, let me know if there's any ambiguity or grammatical mistakes.*
|
||||
|
||||
* Demo
|
||||
|
||||
[[./demo.gif]]
|
||||
|
|
342
anki-editor.el
342
anki-editor.el
|
@ -1,108 +1,143 @@
|
|||
;;; anki-editor.el --- Create Anki cards in Org-mode -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2018 Louie Tan
|
||||
|
||||
;; Author: Louie Tan <louietanlei@gmail.com>
|
||||
|
||||
;; This file is not part of GNU Emacs.
|
||||
|
||||
;; This program is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
;;; anki-editor.el --- Create Anki Cards in Org-mode
|
||||
;;
|
||||
;; This program is distaributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
;; Copyright (C) 2018 Louie Tan <louietanlei@gmail.com>
|
||||
;;
|
||||
;; Filename: anki-editor.el
|
||||
;; Description: Create Anki Cards in Org-mode
|
||||
;; Author: Louie Tan
|
||||
;; Version: 0.1.0
|
||||
;; URL: https://github.com/louietan/anki-editor
|
||||
;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; This package is for people who use Anki as SRS but would like to
|
||||
;; create cards in Org-mode. It does so by using Org-mode's built-in
|
||||
;; HTML backend to generate HTML with specific syntax (e.g. latex)
|
||||
;; translated to the Anki style, then sends requests to anki-connect
|
||||
;; (an Anki addon that runs an HTTP server to expose Anki functions
|
||||
;; as APIs).
|
||||
;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; This program is free software: you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or (at
|
||||
;; your option) any later version.
|
||||
;;
|
||||
;; This program is distributed in the hope that it will be useful, but
|
||||
;; WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
;; General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
|
||||
;;
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;;; Code:
|
||||
|
||||
(require 'json)
|
||||
(require 'org-element)
|
||||
|
||||
|
||||
(defconst anki-editor-note-tag "note")
|
||||
(defconst anki-editor-deck-tag "deck")
|
||||
(defconst anki-editor-note-type-prop :ANKI_NOTE_TYPE)
|
||||
(defconst anki-editor-note-tags-prop :ANKI_TAGS)
|
||||
(defconst anki-editor-html-output-buffer-name "*anki-editor html output*")
|
||||
(defconst anki-editor-anki-connect-listening-address "127.0.0.1")
|
||||
(defconst anki-editor-anki-connect-listening-port "8765")
|
||||
(defvar anki-editor-note-tag "note")
|
||||
(defvar anki-editor-deck-tag "deck")
|
||||
(defvar anki-editor-note-type-prop :ANKI_NOTE_TYPE)
|
||||
(defvar anki-editor-note-tags-prop :ANKI_TAGS)
|
||||
(defvar anki-editor-note-id-prop :ANKI_NOTE_ID)
|
||||
(defvar anki-editor-note-failure-reason-prop :ANKI_FAILURE_REASON)
|
||||
(defvar anki-editor-html-output-buffer-name "*anki-editor html output*")
|
||||
(defvar anki-editor-anki-connect-listening-address "127.0.0.1")
|
||||
(defvar anki-editor-anki-connect-listening-port "8765")
|
||||
|
||||
;; Commands
|
||||
|
||||
;;;###autoload
|
||||
(defun anki-editor-submit ()
|
||||
"Send notes in current buffer to Anki."
|
||||
"Send notes in current buffer to Anki.
|
||||
|
||||
For each note heading, if there's no note id in property drawer,
|
||||
create a note, otherwise, update fields and tags of the existing
|
||||
note.
|
||||
|
||||
If one fails, the failure reason will be set in property drawer
|
||||
of that heading."
|
||||
(interactive)
|
||||
(let* ((tree (org-element-parse-buffer))
|
||||
(note-headings (anki-editor--get-note-headings tree))
|
||||
(total (length note-headings)))
|
||||
|
||||
(if (null note-headings)
|
||||
(message "No notes found in current buffer")
|
||||
|
||||
(message "Submitting %d notes to Anki..." total)
|
||||
(anki-editor--anki-connect-invoke
|
||||
"addNotes" 5
|
||||
`(("notes" . ,(mapcar #'anki-editor--anki-connect-heading-to-note
|
||||
note-headings)))
|
||||
(lambda (result)
|
||||
(let ((failed (seq-count #'null result)))
|
||||
(message (format "Submitted %d notes, %d successful, %d failed." total (- total failed) failed))))))))
|
||||
(let ((total 0)
|
||||
(failed 0))
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(let (current-tags current-deck)
|
||||
(while (not (= (point) (point-max)))
|
||||
(when (org-at-heading-p)
|
||||
(setq current-tags (org-get-tags))
|
||||
(cond
|
||||
((member anki-editor-deck-tag current-tags) (setq current-deck (nth 4 (org-heading-components))))
|
||||
((member anki-editor-note-tag current-tags) (progn
|
||||
(setq total (1+ total))
|
||||
(anki-editor--clear-failure-reason)
|
||||
(condition-case err
|
||||
(anki-editor--process-note-heading current-deck)
|
||||
(error (progn
|
||||
(setq failed (1+ failed))
|
||||
(anki-editor--set-failure-reason (error-message-string err)))))))))
|
||||
(org-next-visible-heading 1))))
|
||||
(message (with-output-to-string
|
||||
(princ (format "Submitted %d notes, with %d failed." total failed))
|
||||
(when (> failed 0)
|
||||
(princ " Check property drawers for failure reasons."))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun anki-editor-insert-deck (&optional prefix)
|
||||
"Insert a deck heading with the same level as current heading.
|
||||
With prefix, only insert the deck name."
|
||||
With PREFIX, only insert the deck name."
|
||||
(interactive "P")
|
||||
(message "Fetching decks...")
|
||||
(anki-editor--anki-connect-invoke
|
||||
"deckNames" 5 nil
|
||||
(lambda (result)
|
||||
(let (deckname)
|
||||
(setq result (append (sort result #'string-lessp) nil)
|
||||
deckname (completing-read "Choose a deck: " result))
|
||||
(unless prefix (org-insert-heading-respect-content))
|
||||
(insert deckname)
|
||||
(unless prefix (anki-editor--set-tags-fix anki-editor-deck-tag))))))
|
||||
(let* ((response (anki-editor--anki-connect-invoke "deckNames" 5))
|
||||
(err (alist-get 'error response))
|
||||
result deckname)
|
||||
(when err (error "Error fetching deck names: %s" err))
|
||||
(setq result (sort (alist-get 'result response) #'string-lessp)
|
||||
deckname (completing-read "Choose a deck: " result))
|
||||
(unless prefix (org-insert-heading-respect-content))
|
||||
(insert deckname)
|
||||
(unless prefix (anki-editor--set-tags-fix anki-editor-deck-tag))))
|
||||
|
||||
;;;###autoload
|
||||
(defun anki-editor-insert-note ()
|
||||
"Insert a note heading that's one level lower to current heading.
|
||||
The inserted heading will be structured with the property drawer
|
||||
and subheadings that correspond to the fields of the selected
|
||||
note type."
|
||||
"Insert the skeleton of a note.
|
||||
The contents to be insrted are structured with a note heading
|
||||
that's one level lower to the current one as well as subheadings
|
||||
that correspond to fields."
|
||||
(interactive)
|
||||
(message "Fetching note types...")
|
||||
(anki-editor--anki-connect-invoke
|
||||
"modelNames" 5 nil
|
||||
(lambda (note-types)
|
||||
(let (note-type note-heading)
|
||||
(setq note-types (append (sort note-types #'string-lessp) nil)
|
||||
note-type (completing-read "Choose a note type: " note-types))
|
||||
(message "Fetching note fields...")
|
||||
(anki-editor--anki-connect-invoke
|
||||
"modelFieldNames" 5 `((modelName . ,note-type))
|
||||
(lambda (fields)
|
||||
(setq note-heading (read-from-minibuffer "Enter the heading: " "Item"))
|
||||
(org-insert-heading-respect-content)
|
||||
(org-do-demote)
|
||||
(insert note-heading)
|
||||
(anki-editor--set-tags-fix anki-editor-note-tag)
|
||||
(org-set-property (substring (symbol-name anki-editor-note-type-prop) 1) note-type)
|
||||
(seq-each (lambda (field)
|
||||
(save-excursion
|
||||
(org-insert-heading-respect-content)
|
||||
(org-do-demote)
|
||||
(insert field)))
|
||||
fields)
|
||||
(org-next-visible-heading 1)
|
||||
(end-of-line)
|
||||
(newline-and-indent)))))))
|
||||
(let* ((response (anki-editor--anki-connect-invoke "modelNames" 5))
|
||||
(err (alist-get 'error response))
|
||||
(note-types (alist-get 'result response))
|
||||
note-type note-heading fields)
|
||||
|
||||
(when err (error "Error fetching note types: %s" err))
|
||||
(setq note-types (sort note-types #'string-lessp)
|
||||
note-type (completing-read "Choose a note type: " note-types))
|
||||
(message "Fetching note fields...")
|
||||
(setq response (anki-editor--anki-connect-invoke "modelFieldNames" 5 `((modelName . ,note-type)))
|
||||
fields (alist-get 'result response)
|
||||
note-heading (read-from-minibuffer "Enter the heading: " "Item"))
|
||||
(org-insert-heading-respect-content)
|
||||
(org-do-demote)
|
||||
(insert note-heading)
|
||||
(anki-editor--set-tags-fix anki-editor-note-tag)
|
||||
(org-set-property (substring (symbol-name anki-editor-note-type-prop) 1) note-type)
|
||||
(seq-each (lambda (field)
|
||||
(save-excursion
|
||||
(org-insert-heading-respect-content)
|
||||
(org-do-demote)
|
||||
(insert field)))
|
||||
fields)
|
||||
(org-next-visible-heading 1)
|
||||
(end-of-line)
|
||||
(newline-and-indent)))
|
||||
|
||||
;;;###autoload
|
||||
(defun anki-editor-export-heading-contents-to-html ()
|
||||
|
@ -142,41 +177,98 @@ note type."
|
|||
(local-set-key (car map) (cdr map)))
|
||||
(message "anki-editor default keybindings have been set"))
|
||||
|
||||
;;;###autoload
|
||||
(defun anki-editor-anki-connect-upgrade ()
|
||||
"Upgrade anki-connect to the latest version.
|
||||
|
||||
;; Core Functions
|
||||
This will display a confirmation dialog box in Anki asking if you
|
||||
want to continue. The upgrading is done by downloading the latest
|
||||
code in the master branch of its Github repo.
|
||||
|
||||
(defun anki-editor--get-note-headings (data &optional test)
|
||||
(unless test (setq test 'identity))
|
||||
(org-element-map data 'headline
|
||||
(lambda (element)
|
||||
(let ((tags (org-element-property :tags element)))
|
||||
(when (and (member anki-editor-note-tag tags) (funcall test element))
|
||||
element)))))
|
||||
This is useful when new version of this package depends on the
|
||||
bugfixes or new features of anki-connect."
|
||||
(interactive)
|
||||
(let* ((response (anki-editor--anki-connect-invoke "upgrade" 5))
|
||||
(result (alist-get 'result response))
|
||||
(err (alist-get 'error response)))
|
||||
(when err (error err))
|
||||
(when (and (booleanp result) result)
|
||||
(message "anki-connect has upgraded, you may have to restart Anki to make it in effect."))))
|
||||
|
||||
;;; Core Functions
|
||||
|
||||
(defun anki-editor--process-note-heading (deck)
|
||||
(unless deck (error "No deck specified"))
|
||||
|
||||
(let (note-elem note)
|
||||
(setq note-elem (org-element-at-point)
|
||||
note-elem (let ((content (buffer-substring
|
||||
(org-element-property :begin note-elem)
|
||||
(org-element-property :end note-elem))))
|
||||
(with-temp-buffer
|
||||
(insert content)
|
||||
(car (org-element-contents (org-element-parse-buffer)))))
|
||||
note (anki-editor--heading-to-note note-elem))
|
||||
(add-to-list 'note `(deck . ,deck))
|
||||
(anki-editor--save-note note)))
|
||||
|
||||
(defun anki-editor--save-note (note)
|
||||
(if (= (alist-get 'note-id note) -1)
|
||||
(anki-editor--create-note note)
|
||||
(anki-editor--update-note note)))
|
||||
|
||||
(defun anki-editor--create-note (note)
|
||||
(let* ((response (anki-editor--anki-connect-invoke
|
||||
"addNote" 5 `((note . ,(anki-editor--anki-connect-map-note note)))))
|
||||
(result (alist-get 'result response))
|
||||
(err (alist-get 'error response)))
|
||||
(if result
|
||||
(org-set-property (substring (symbol-name anki-editor-note-id-prop) 1)
|
||||
(format "%d" (alist-get 'result response)))
|
||||
(error (or err "Sorry, the operation was unsuccessful and detailed information is unavailable.")))))
|
||||
|
||||
(defun anki-editor--update-note (note)
|
||||
"Update fields and tags of NOTE."
|
||||
(let* ((response (anki-editor--anki-connect-invoke
|
||||
"updateNoteFields" 5 `((note . ,(anki-editor--anki-connect-map-note note)))))
|
||||
(err (alist-get 'error response)))
|
||||
(when err (error err))
|
||||
;; update tags
|
||||
(let (existing-note added-tags removed-tags)
|
||||
(setq response (anki-editor--anki-connect-invoke "notesInfo" 5 `(("notes" . (,(alist-get 'note-id note)))))
|
||||
err (alist-get 'error response))
|
||||
(when err (error err))
|
||||
(setq existing-note (car (alist-get 'result response))
|
||||
added-tags (cl-set-difference (alist-get 'tags note) (alist-get 'tags existing-note) :test #'string-equal)
|
||||
removed-tags (cl-set-difference (alist-get 'tags existing-note) (alist-get 'tags note) :test #'string-equal))
|
||||
(when added-tags
|
||||
(anki-editor--anki-connect-invoke "addTags" 5 `(("notes" . (,(alist-get 'note-id note)))
|
||||
("tags" . ,(mapconcat #'identity added-tags " ")))))
|
||||
(when removed-tags
|
||||
(anki-editor--anki-connect-invoke "removeTags" 5 `(("notes" . (,(alist-get 'note-id note)))
|
||||
("tags" . ,(mapconcat #'identity removed-tags " "))))))))
|
||||
|
||||
(defun anki-editor--set-failure-reason (reason)
|
||||
(org-set-property (substring (symbol-name anki-editor-note-failure-reason-prop) 1) reason))
|
||||
|
||||
(defun anki-editor--clear-failure-reason ()
|
||||
(org-delete-property (substring (symbol-name anki-editor-note-failure-reason-prop) 1)))
|
||||
|
||||
(defun anki-editor--heading-to-note (heading)
|
||||
(let (deck note-type tags fields)
|
||||
(setq deck (anki-editor--get-deck-name heading)
|
||||
(let (note-id note-type tags fields)
|
||||
(setq note-id (org-element-property anki-editor-note-id-prop heading)
|
||||
note-type (org-element-property anki-editor-note-type-prop heading)
|
||||
tags (org-element-property anki-editor-note-tags-prop heading)
|
||||
fields (mapcar #'anki-editor--heading-to-note-field (anki-editor--get-subheadings heading)))
|
||||
|
||||
(unless deck (error "Please specify a deck !"))
|
||||
(unless note-type (error "Please specify a note type !"))
|
||||
(unless fields (error "Please specify fields !"))
|
||||
(unless note-type (error "Missing note type"))
|
||||
(unless fields (error "Missing fields"))
|
||||
|
||||
`((deck . ,deck)
|
||||
`((note-id . ,(string-to-number (or note-id "-1")))
|
||||
(note-type . ,note-type)
|
||||
(tags . ,(and tags (split-string tags " ")))
|
||||
(fields . ,fields))))
|
||||
|
||||
(defun anki-editor--get-deck-name (element)
|
||||
(let ((ancestor (anki-editor--find-ancestor
|
||||
element
|
||||
(lambda (it)
|
||||
(member anki-editor-deck-tag (org-element-property :tags it))))))
|
||||
(and ancestor
|
||||
(substring-no-properties (org-element-property :raw-value ancestor)))))
|
||||
|
||||
(defun anki-editor--get-subheadings (heading)
|
||||
(org-element-map (org-element-contents heading)
|
||||
'headline 'identity nil nil 'headline))
|
||||
|
@ -203,9 +295,9 @@ note type."
|
|||
|
||||
(defun anki-editor--buffer-to-html ()
|
||||
(when (> (buffer-size) 0)
|
||||
(save-mark-and-excursion
|
||||
(mark-whole-buffer)
|
||||
(org-html-convert-region-to-html))))
|
||||
(insert
|
||||
(org-export-string-as
|
||||
(delete-and-extract-region (point-min) (point-max)) 'html t))))
|
||||
|
||||
(defun anki-editor--replace-latex ()
|
||||
(let (object type memo)
|
||||
|
@ -255,18 +347,11 @@ note type."
|
|||
(add-to-list 'translated record)))
|
||||
(setq anki-editor--replacement-records (cl-set-difference anki-editor--replacement-records translated))))
|
||||
|
||||
;; Utilities
|
||||
;;; Utilities
|
||||
|
||||
(defun anki-editor--hash (type text)
|
||||
(sha1 (format "%s %s" (symbol-name type) text)))
|
||||
|
||||
(defun anki-editor--find-ancestor (element test)
|
||||
(let ((parent (org-element-property :parent element)))
|
||||
(and parent
|
||||
(if (funcall test parent)
|
||||
parent
|
||||
(anki-editor--find-ancestor parent test)))))
|
||||
|
||||
(defun anki-editor--set-tags-fix (tags)
|
||||
(org-set-tags-to tags)
|
||||
(org-fix-tags-on-the-fly))
|
||||
|
@ -280,9 +365,9 @@ note type."
|
|||
(insert replacement)
|
||||
(cons original replacement)))
|
||||
|
||||
;; anki-connect
|
||||
;;; anki-connect
|
||||
|
||||
(defun anki-editor--anki-connect-invoke (action version &optional params success)
|
||||
(defun anki-editor--anki-connect-invoke (action version &optional params)
|
||||
(let* ((data `(("action" . ,action)
|
||||
("version" . ,version)))
|
||||
(request-body (json-encode
|
||||
|
@ -296,23 +381,28 @@ note type."
|
|||
(set-buffer-multibyte t)
|
||||
(insert request-body))
|
||||
|
||||
(let* ((response (shell-command-to-string
|
||||
(let* ((raw-resp (shell-command-to-string
|
||||
(format "curl %s:%s --silent -X POST --data-binary @%s"
|
||||
anki-editor-anki-connect-listening-address
|
||||
anki-editor-anki-connect-listening-port
|
||||
request-tempfile)))
|
||||
anki-error)
|
||||
resp error)
|
||||
|
||||
(when (file-exists-p request-tempfile) (delete-file request-tempfile))
|
||||
(condition-case err
|
||||
(progn
|
||||
(setq response (json-read-from-string response)
|
||||
anki-error (alist-get 'error response))
|
||||
(when anki-error (error "anki-connect responded with error: %s" anki-error))
|
||||
(when success (funcall success (alist-get 'result response))))
|
||||
(error (message "%s" (error-message-string err)))))))
|
||||
(let ((json-array-type 'list))
|
||||
(setq resp (json-read-from-string raw-resp)
|
||||
error (alist-get 'error resp)))
|
||||
(error (setq error
|
||||
(format "Unexpected error communicating with anki-connect: %s, the response was %s"
|
||||
(error-message-string err)
|
||||
(prin1-to-string raw-resp)))))
|
||||
`((result . ,(alist-get 'result resp))
|
||||
(error . ,error)))))
|
||||
|
||||
(defun anki-editor--anki-connect-map-note (note)
|
||||
`(("deckName" . ,(alist-get 'deck note))
|
||||
`(("id" . ,(alist-get 'note-id note))
|
||||
("deckName" . ,(alist-get 'deck note))
|
||||
("modelName" . ,(alist-get 'note-type note))
|
||||
("fields" . ,(alist-get 'fields note))
|
||||
;; Convert tags to a vector since empty list is identical to nil
|
||||
|
@ -324,7 +414,7 @@ note type."
|
|||
(anki-editor--anki-connect-map-note
|
||||
(anki-editor--heading-to-note heading)))
|
||||
|
||||
|
||||
(provide 'anki-editor)
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;; anki-editor.el ends here
|
||||
|
|
Loading…
Reference in a new issue