diff --git a/README.org b/README.org index 8699f43..78f7209 100644 --- a/README.org +++ b/README.org @@ -51,14 +51,14 @@ anki-editor -- an Emacs package that helps you create Anki cards in Org-mode ** Command Cheatsheet - | Command | Brief Description | - |-----------------------------------------------+---------------------------------------------------------------| - | =anki-editor-submit= | Send notes in current buffer to Anki. | - | =anki-editor-insert-deck= | Insert a deck heading with the same level as current heading. | - | =anki-editor-insert-note= | Insert the skeleton of a note. | - | =anki-editor-insert-tags= | Insert a tag at point with autocompletion. | - | =anki-editor-export-heading-contents-to-html= | Export the contents of the heading at point to HTML. | - | =anki-editor-convert-region-to-html= | Convert and replace region to HTML. | + | Command | Brief Description | + |-----------------------------------------------+------------------------------------------------------| + | =anki-editor-submit= | Send notes in current buffer to Anki. | + | =anki-editor-insert-deck= | Insert a deck heading. | + | =anki-editor-insert-note= | Insert the skeleton of a note. | + | =anki-editor-insert-tags= | Insert a tag at point with autocompletion. | + | =anki-editor-export-heading-contents-to-html= | Export the contents of the heading at point to HTML. | + | =anki-editor-convert-region-to-html= | Convert and replace region to HTML. | *Since I'm not a native English speaker, let me know if there's any ambiguity or grammatical mistakes.* diff --git a/anki-editor.el b/anki-editor.el index 379e6ed..f29de06 100644 --- a/anki-editor.el +++ b/anki-editor.el @@ -170,23 +170,46 @@ of that heading." ;;;###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." + "Insert a deck heading. +With PREFIX, only insert the deck name at point." (interactive "P") (message "Fetching decks...") (let ((decks (sort (anki-editor--anki-connect-invoke-result "deckNames" 5) #'string-lessp)) deckname) (setq deckname (completing-read "Choose a deck: " decks)) - (unless prefix (org-insert-heading-respect-content)) - (insert deckname) - (unless prefix (anki-editor--set-tags-fix anki-editor-deck-tag)))) + (if prefix + (insert deckname) + (let (inserted) + (anki-editor--visit-superior-headings + (lambda () + (when (member anki-editor-deck-tag (org-get-tags)) + (anki-editor--insert-deck-heading deckname) + (setq inserted t)))) + + (unless inserted + (anki-editor--insert-deck-heading deckname)))))) ;;;###autoload (defun anki-editor-insert-note () "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." +along with subheadings that correspond to fields. + +Where the note is inserted depends on where the point is. + +When the point is somewhere inside a note heading, the new note +is inserted below this note with same heading level. + +Or when the point is outside any note heading but inside a +heading that isn't tagged with 'deck' but under a deck heading, +the new note is one level lower to and is inserted at the bottom +of this heading. + +Or when the point is inside a deck heading, the behavior is the +same as above. + +Otherwise, it's inserted at point." (interactive) (message "Fetching note types...") (let ((note-types (sort (anki-editor--anki-connect-invoke-result "modelNames" 5) #'string-lessp)) @@ -195,19 +218,39 @@ that correspond to fields." (message "Fetching note fields...") (setq fields (anki-editor--anki-connect-invoke-result "modelFieldNames" 5 `((modelName . ,note-type))) 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-prop-note-type) 1) note-type) - (dolist (field fields) - (save-excursion - (org-insert-heading-respect-content) - (org-do-demote) - (insert field))) - (org-next-visible-heading 1) - (end-of-line) - (newline-and-indent))) + + ;; find and go to the best position, then insert the note + (let ((cur-point (point)) + pt-of-grp + inserted) + (anki-editor--visit-superior-headings + (lambda () + (let ((tags (org-get-tags))) + (cond + ;; if runs into a note heading, inserts the note heading with + ;; the same level + ((member anki-editor-note-tag tags) + (progn + (anki-editor--insert-note-skeleton note-heading note-type fields) + (setq inserted t) + t)) + ;; if runs into a deck heading, inserts the note heading one + ;; level lower to current deck heading or to the group + ;; heading that was visited before + ((member anki-editor-deck-tag tags) + (progn + (when pt-of-grp (goto-char pt-of-grp)) + (anki-editor--insert-note-skeleton note-heading note-type fields t) + (setq inserted t) + t)) + ;; otherwise, consider it as a group heading and save its + ;; point for further consideration, then continue + (t (progn + (unless pt-of-grp (setq pt-of-grp (point))) + nil)))))) + (unless inserted + (goto-char cur-point) + (anki-editor--insert-note-skeleton note-heading note-type fields))))) ;;;###autoload (defun anki-editor-insert-tags () @@ -246,7 +289,7 @@ that correspond to fields." "Upgrade anki-connect to the latest version. This will display a confirmation dialog box in Anki asking if you -want to continue. The upgrading is done by downloading the latest +want to continue. The upgrading is done by downloading the latest code in the master branch of its Github repo. This is useful when new version of this package depends on the @@ -259,6 +302,12 @@ bugfixes or new features of anki-connect." ;;; Core Functions +(defun anki-editor--insert-deck-heading (deckname) + "Insert a deck heading with DECKNAME." + (org-insert-heading-respect-content) + (insert deckname) + (anki-editor--set-tags-fix anki-editor-deck-tag)) + (defun anki-editor--process-note-heading (deck) "Process note heading at point. DECK is used when the action is note creation." @@ -276,6 +325,28 @@ DECK is used when the action is note creation." (push `(deck . ,deck) note) (anki-editor--save-note note))) +(defun anki-editor--insert-note-skeleton (heading note-type fields &optional demote) + "Insert a note skeleton with HEADING, NOTE-TYPE and FIELDS. +If DEMOTE is t, demote the inserted note heading." + (org-insert-heading-respect-content) + (when demote (org-do-demote)) + (insert heading) + (anki-editor--set-tags-fix anki-editor-note-tag) + (org-set-property (substring (symbol-name anki-editor-prop-note-type) 1) note-type) + (dolist (field fields) + (save-excursion + (org-insert-heading-respect-content) + (org-do-demote) + (insert field))) + + ;; TODO: Is it a good idea to automatically move to the first field + ;; heading and open a new line ? + + ;; (org-next-visible-heading 1) + ;; (end-of-line) + ;; (newline-and-indent) + ) + (defun anki-editor--save-note (note) "Request anki-connect for updating or creating NOTE." (if (= (alist-get 'note-id note) -1) @@ -446,6 +517,21 @@ DECK is used when the action is note creation." (insert replacement) (cons original replacement))) +(defun anki-editor--visit-superior-headings (visitor &optional level) + "Move point to and call VISITOR at each superior heading from point. +Don't pass LEVEL, it's only used in recursion. +Stops when VISITOR returns t or point reaches the beginning of buffer." + (let (stop) + (when (org-at-heading-p) + (let ((cur-level (car (org-heading-components)))) + (when (or (null level) (< cur-level level)) + (setq level cur-level + stop (funcall visitor))))) + (when (and (not stop) (/= (point) (point-min))) + (org-previous-visible-heading 1) + (anki-editor--visit-superior-headings visitor level)))) + + (provide 'anki-editor) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;