Allow specifying field values from org-mode properties (#22)

* Allow specifying field values from org-mode properties

* Update documentation and fine-tune the diff

* Refactor, fix indentation, and other minor fixes

Renamed all-fields to named-fields since the heading and content
before first subheading can also be a field.

---------

Co-authored-by: Renat Galimov <renat@Renats-MacBook-Pro.local>
Co-authored-by: orgtre <orgtre@posteo.net>
This commit is contained in:
Renat Galimov 2023-04-13 15:02:42 +03:00 committed by GitHub
parent 87394670a2
commit ab7b33b48c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 113 additions and 92 deletions

View file

@ -2,7 +2,7 @@
anki-editor is an [[https://www.gnu.org/software/emacs/emacs.html][Emacs]] minor mode for making [[https://apps.ankiweb.net][Anki]] cards with [[https://orgmode.org][Org Mode]]. anki-editor is an [[https://www.gnu.org/software/emacs/emacs.html][Emacs]] minor mode for making [[https://apps.ankiweb.net][Anki]] cards with [[https://orgmode.org][Org Mode]].
This repository is a fork of [[https://github.com/louietan/anki-editor][louietan/anki-editor]]. By now there are quite a few differences and extensions compared to the original: This repository is a fork of [[https://github.com/louietan/anki-editor][louietan/anki-editor]]. By now there are quite a few differences and extensions compared to the original:
- The develop branch of the original has been merged into the master branch; see the unreleased section of [[https://github.com/orgtre/anki-editor/blob/master/Changelog.org][Changelog.org]] for a list of the new features that come with this. - The develop branch of the original has been merged into the master branch; see the unreleased section of [[https://github.com/orgtre/anki-editor/blob/master/Changelog.org][Changelog.org]] for a list of the new features that come with this.
- Almost all outstanding pull requests to the original have been integrated; [[https://github.com/orgtre/anki-editor/issues/10][this issue]] tracks the two exceptions. - Almost all outstanding pull requests to the original have been integrated; [[https://github.com/orgtre/anki-editor/issues/10][this issue]] tracks the two exceptions.
- A more flexible note structure, outlined [[https://github.com/eyeinsky/org-anki/issues/48#issuecomment-1216625730][here]], has been introduced. - A more flexible note structure, outlined [[https://github.com/eyeinsky/org-anki/issues/48#issuecomment-1216625730][here]], has been introduced.
@ -20,7 +20,7 @@ Other than Emacs you need [[https://apps.ankiweb.net][Anki]], its [[https://gith
*** Using package.el *** Using package.el
If you're using the built-in =package.el= package manager, first manually download this repository and then type the following in Emacs: If you're using the built-in =package.el= package manager, first manually download this repository and then type the following in Emacs:
: M-x package-install-file [RET] "path/to/anki-editor.el" [RET] : M-x package-install-file [RET] "path/to/anki-editor.el" [RET]
@ -84,6 +84,19 @@ If you're using [[https://github.com/radian-software/straight.el][straight.el]]
This works for all note types, just make the first 2 fields absent and This works for all note types, just make the first 2 fields absent and
~anki-editor~ will use note heading as first field and the text below the heading as second field. ~anki-editor~ will use note heading as first field and the text below the heading as second field.
,* You can extract a field value from an org-property
:PROPERTIES:
:ANKI_NOTE_TYPE: Basic
:ANKI_FIELD_FRONT: Can one define an anki-field inside an org-mode property?
:ANKI_PREPEND_HEADING: nil
:END:
Yes. In this example, =anki-editor= will use the =ANKI_FIELD_FRONT= property value as
a front side of the Anki card and the body of the card as its back.
,** Front
Notice that property fields will override subheading fields.
This block will be skipped
#+END_SRC #+END_SRC
- Anki deck is provided by ~ANKI_DECK~ property. This property is - Anki deck is provided by ~ANKI_DECK~ property. This property is

View file

@ -442,6 +442,8 @@ The implementation is borrowed and simplified from ox-html."
(defconst anki-editor-prop-deck "ANKI_DECK") (defconst anki-editor-prop-deck "ANKI_DECK")
(defconst anki-editor-prop-format "ANKI_FORMAT") (defconst anki-editor-prop-format "ANKI_FORMAT")
(defconst anki-editor-prop-prepend-heading "ANKI_PREPEND_HEADING") (defconst anki-editor-prop-prepend-heading "ANKI_PREPEND_HEADING")
(defconst anki-editor-prop-field-prefix "ANKI_FIELD_"
"Anki fields with names got from an org-node property.")
(defconst anki-editor-prop-tags "ANKI_TAGS") (defconst anki-editor-prop-tags "ANKI_TAGS")
(defconst anki-editor-prop-tags-plus (concat anki-editor-prop-tags "+")) (defconst anki-editor-prop-tags-plus (concat anki-editor-prop-tags "+"))
(defconst anki-editor-prop-failure-reason "ANKI_FAILURE_REASON") (defconst anki-editor-prop-failure-reason "ANKI_FAILURE_REASON")
@ -806,6 +808,14 @@ Return a list of cons of (FIELD-NAME . FIELD-CONTENT)."
do (org-forward-heading-same-level nil t) do (org-forward-heading-same-level nil t)
until (= last-pt (point))))) until (= last-pt (point)))))
(defun anki-editor--property-fields (fields)
"Extract Anki FIELDS from entry properties."
(cl-loop for field in fields
for property = (concat anki-editor-prop-field-prefix (upcase field))
for property-value = (org-entry-get-with-inheritance property)
when property-value
collect (cons field property-value)))
(defun anki-editor--note-contents-before-subheading () (defun anki-editor--note-contents-before-subheading ()
"Get content between heading at point and next sub/heading. "Get content between heading at point and next sub/heading.
@ -857,97 +867,95 @@ When the `subheading-fields' don't match the `note-type's fields,
map missing fields to the `heading' and/or `content-before-subheading'. map missing fields to the `heading' and/or `content-before-subheading'.
Return a list of cons of (FIELD-NAME . FIELD-CONTENT)." Return a list of cons of (FIELD-NAME . FIELD-CONTENT)."
(anki-editor--with-collection-data-updated (anki-editor--with-collection-data-updated
(let* ((fields-matching (cl-intersection (let* ((model-fields (alist-get
(alist-get note-type note-type anki-editor--model-fields
anki-editor--model-fields nil nil #'string=))
nil nil #'string=) (property-fields (anki-editor--property-fields model-fields))
(mapcar #'car subheading-fields) (named-fields (seq-uniq (append subheading-fields property-fields)
:test #'string=)) (lambda (left right)
(fields-missing (cl-set-difference (string= (car left) (car right)))))
(alist-get note-type (fields-matching (cl-intersection
anki-editor--model-fields model-fields (mapcar #'car named-fields)
nil nil #'string=) :test #'string=))
(mapcar #'car subheading-fields) (fields-missing (cl-set-difference
:test #'string=)) model-fields (mapcar #'car named-fields)
(fields-extra (cl-set-difference :test #'string=))
(mapcar #'car subheading-fields) (fields-extra (cl-set-difference
(alist-get note-type (mapcar #'car named-fields) model-fields
anki-editor--model-fields :test #'string=))
nil nil #'string=) (fields (cl-loop for f in fields-matching
:test #'string=)) collect (cons f (alist-get
(fields (cl-loop for f in fields-matching f named-fields
collect (cons f (alist-get nil nil #'string=))))
f subheading-fields (heading-format anki-editor-prepend-heading-format))
nil nil #'string=)))) (cond ((equal 0 (length fields-missing))
(heading-format anki-editor-prepend-heading-format)) (when (< 0 (length fields-extra))
(cond ((equal 0 (length fields-missing)) (user-error "Failed to map all named fields")))
(when (< 0 (length fields-extra)) ((equal 1 (length fields-missing))
(user-error "Failed to map all subheadings to a field"))) (if (equal 0 (length fields-extra))
((equal 1 (length fields-missing)) (if (equal "" (string-trim content-before-subheading))
(if (equal 0 (length fields-extra)) (push (cons (car fields-missing) heading)
(if (equal "" (string-trim content-before-subheading)) fields)
(push (cons (car fields-missing) heading) (if prepend-heading
fields) (push (cons (car fields-missing)
(if prepend-heading (concat
(push (cons (car fields-missing) (format heading-format heading)
(concat content-before-subheading))
(format heading-format heading) fields)
content-before-subheading)) (push (cons (car fields-missing)
fields) content-before-subheading)
(push (cons (car fields-missing) fields)))
content-before-subheading) (if (equal "" (string-trim content-before-subheading))
fields))) (push (cons (car fields-missing)
(if (equal "" (string-trim content-before-subheading)) (anki-editor--concat-fields
(push (cons (car fields-missing) fields-extra subheading-fields level))
(anki-editor--concat-fields fields)
fields-extra subheading-fields level)) (if prepend-heading
fields) (push (cons (car fields-missing)
(if prepend-heading (concat
(push (cons (car fields-missing) (format heading-format heading)
(concat content-before-subheading
(format heading-format heading)
content-before-subheading
(anki-editor--concat-fields
fields-extra subheading-fields
level)))
fields)
(push (cons (car fields-missing)
(concat content-before-subheading
(anki-editor--concat-fields
fields-extra subheading-fields
level)))
fields)))))
((equal 2 (length fields-missing))
(if (equal 0 (length fields-extra))
(progn
(push (cons (nth 1 fields-missing)
content-before-subheading)
fields)
(push (cons (car fields-missing)
heading)
fields))
(if (equal "" (string-trim content-before-subheading))
(progn
(push (cons (nth 1 fields-missing)
(anki-editor--concat-fields (anki-editor--concat-fields
fields-extra subheading-fields level)) fields-extra subheading-fields
fields) level)))
(push (cons (car fields-missing) fields)
heading) (push (cons (car fields-missing)
fields)) (concat content-before-subheading
(progn (anki-editor--concat-fields
(push (cons (nth 1 fields-missing) fields-extra subheading-fields
(concat content-before-subheading level)))
(anki-editor--concat-fields fields)))))
fields-extra subheading-fields level))) ((equal 2 (length fields-missing))
fields) (if (equal 0 (length fields-extra))
(push (cons (car fields-missing) (progn
heading) (push (cons (nth 1 fields-missing)
fields))))) content-before-subheading)
((< 2 (length fields-missing)) fields)
(user-error (concat "Cannot map note fields: " (push (cons (car fields-missing)
"more than two fields missing")))) heading)
fields))) fields))
(if (equal "" (string-trim content-before-subheading))
(progn
(push (cons (nth 1 fields-missing)
(anki-editor--concat-fields
fields-extra subheading-fields level))
fields)
(push (cons (car fields-missing)
heading)
fields))
(progn
(push (cons (nth 1 fields-missing)
(concat content-before-subheading
(anki-editor--concat-fields
fields-extra subheading-fields level)))
fields)
(push (cons (car fields-missing)
heading)
fields)))))
((< 2 (length fields-missing))
(user-error (concat "Cannot map note fields: "
"more than two fields missing"))))
fields)))
(defun anki-editor--concat-fields (field-names field-alist level) (defun anki-editor--concat-fields (field-names field-alist level)
"Concat field names and content of fields in list `field-names'." "Concat field names and content of fields in list `field-names'."