summaryrefslogtreecommitdiff
path: root/gnosis.el
diff options
context:
space:
mode:
Diffstat (limited to 'gnosis.el')
-rw-r--r--gnosis.el457
1 files changed, 367 insertions, 90 deletions
diff --git a/gnosis.el b/gnosis.el
index eceb00e..1a7edf9 100644
--- a/gnosis.el
+++ b/gnosis.el
@@ -5,7 +5,7 @@
;; Author: Thanos Apollo <[email protected]>
;; Keywords: extensions
;; URL: https://thanosapollo.org/projects/gnosis
-;; Version: 0.2.4
+;; Version: 0.3.0
;; Package-Requires: ((emacs "27.2") (emacsql "20240124") (compat "29.1.4.2"))
@@ -36,13 +36,15 @@
;;; Code:
-(require 'emacsql-sqlite)
(require 'cl-lib)
+(require 'vc)
+(require 'emacsql-sqlite)
+
(require 'gnosis-algorithm)
(require 'gnosis-string-edit)
-(require 'vc)
+(require 'animate)
(defgroup gnosis nil
"Spaced Repetition System For Note Taking & Self Testing."
@@ -125,7 +127,7 @@ When nil, the image will be displayed at its original size."
(defconst gnosis-db-version 2
"Gnosis database version.")
-(defvar gnosis-note-types '("MCQ" "Cloze" "Basic" "Double" "y-or-n")
+(defvar gnosis-note-types '("MCQ" "MC-Cloze" "Cloze" "Basic" "Double" "y-or-n")
"Gnosis available note types.")
(defvar gnosis-previous-note-tags '()
@@ -150,6 +152,14 @@ car value is the prompt, cdr is the prewritten string.")
car value is the prompt, cdr is the prewritten string.")
+(defvar gnosis-mc-cloze-guidance
+ '("MC-Cloze Example: This is an example answer&&option2&&option3" . ""))
+
+(defcustom gnosis-mc-cloze-separator "&&"
+ "Sseparator for choices on multiple choice clozes."
+ :type 'string
+ :group 'gnosis)
+
(defcustom gnosis-mcq-separator "\n--\n"
"Separator for stem field and options in mcq note type.
@@ -162,6 +172,11 @@ Seperate the question/stem from options."
:type 'string
:group 'gnosis)
+(defcustom gnosis-center-content t
+ "When t, centers text."
+ :type 'boolean
+ :group 'gosis)
+
(defvar gnosis-due-notes-total nil
"Total due notes.")
@@ -174,8 +189,7 @@ Seperate the question/stem from options."
:prefix 'gnosis-face)
(defface gnosis-face-extra
- '((t :inherit italic
- :foreground "#9C91E4"))
+ '((t :inherit font-lock-doc-face))
"Face for extra-notes."
:group 'gnosis-faces)
@@ -185,7 +199,9 @@ Seperate the question/stem from options."
:group 'gnosis-face-faces)
(defface gnosis-face-separator
- '((t :inherit warning))
+ '((default :inherit org-hide)
+ (((background light)) :strike-through "gray70")
+ (t :strike-through "gray30"))
"Face for section separator."
:group 'gnosis-face)
@@ -296,15 +312,118 @@ History is disabled."
if (= i index) collect new-item
else collect item))
+(defun gnosis-insert-separator ()
+ "Insert a dashed line spanning the entire width of the buffer."
+ (interactive)
+ (let* ((width (window-width))
+ (dash-line (concat (make-string width ?-))))
+ (insert "\n" dash-line "\n")
+ ;; Apply an overlay to hide only the dashes
+ (let ((start (save-excursion (forward-line -1) (point)))
+ (end (point)))
+ (let ((overlay (make-overlay start end)))
+ (overlay-put overlay 'face 'gnosis-face-separator)
+ (overlay-put overlay 'display (make-string width ?\s))))))
+
+(defun gnosis-center-current-line (&optional center?)
+ "Centers text in the current line ignoring leading spaces.
+
+Acts only when CENTER? is t."
+ (interactive)
+ (let* ((start (line-beginning-position))
+ (end (line-end-position))
+ (text (string-trim (buffer-substring start end)))
+ (padding (max (/ (- (window-width) (length text)) 2) 0))
+ (center? (or center? gnosis-center-content)))
+ (when center?
+ (delete-region start end)
+ (insert (make-string padding ? ) text))))
+
+(defun gnosis-center-string (input-string &optional center?)
+ "Center each line of the given INPUT-STRING in the current window width.
+
+Acts only when CENTER? is t."
+ (let ((window-width (window-width))
+ (center? (or center? gnosis-center-content)))
+ (when center?
+ (mapconcat
+ (lambda (line)
+ (let* ((text (string-trim line))
+ (wrapped (with-temp-buffer
+ (insert text)
+ (fill-region (point-min) (point-max))
+ (buffer-string)))
+ (lines (split-string wrapped "\n")))
+ (mapconcat
+ (lambda (line)
+ (let ((padding (max (/ (- window-width (length line)) 2) 0)))
+ (concat (make-string padding ? ) line)))
+ lines
+ "\n")))
+ (split-string input-string "\n")
+ "\n"))))
+
+(defun gnosis-apply-center-buffer-overlay (&optional point)
+ "Center text in buffer starting at POINT using `gnosis-center-current-line'.
+This will not be applied to sentences that start with double space."
+ (save-excursion
+ (goto-char (or point (point-min)))
+ (while (not (or (= (point-max) (point)) (looking-at "^ ")))
+ (gnosis-center-current-line)
+ (forward-line 1))))
+
+(defun gnosis-apply-syntax-overlay ()
+ "Apply custom font overlays for syntax highlighting, and remove delimiters."
+ (let ((syntax-highlights '(("\\*\\([^*[:space:]][^*]*[^*[:space:]]\\)\\*" . bold)
+ ("/\\([^/[:space:]][^/]*[^/[:space:]]\\)/" . italic)
+ ("=\\([^=[:space:]][^=]*[^=[:space:]]\\)=" . font-lock-constant-face)
+ ("~\\([^~[:space:]][^~]*[^~[:space:]]\\)~" . font-lock-keyword-face))))
+ (save-excursion
+ (cl-loop for (regex . face) in syntax-highlights
+ do (progn
+ (goto-char (point-min))
+ (while (re-search-forward regex nil t)
+ (let ((start (match-beginning 1))
+ (end (match-end 1)))
+ (overlay-put (make-overlay start end) 'face face)
+ (delete-region end (match-end 0))
+ (delete-region (match-beginning 0) start))))))))
+
(defun gnosis-display-question (id &optional fill-paragraph-p)
"Display main row for note ID.
If FILL-PARAGRAPH-P, insert question using `fill-paragraph'."
- (let ((question (gnosis-get 'main 'notes `(= id ,id))))
+ (let ((question (gnosis-get 'main 'notes `(= id ,id)))
+ (fill-paragraph-p (or fill-paragraph-p t)))
(erase-buffer)
(if fill-paragraph-p
- (fill-paragraph (insert "\n" (propertize question 'face 'gnosis-face-main))))
- (insert "\n" (propertize question 'face 'gnosis-face-main))))
+ (fill-paragraph (insert "\n" (propertize question 'face 'gnosis-face-main)))
+ (insert "\n" (propertize question 'face 'gnosis-face-main)))
+ (newline)
+ (gnosis-apply-center-buffer-overlay)
+ (gnosis-apply-syntax-overlay)))
+
+(cl-defun gnosis-display-image (id &optional (image 'images))
+ "Display image for note ID.
+
+IMAGE is the image type to display, usually should be either =images'
+or =extra-image'. Instead of using =extra-image' post review, prefer
+=gnosis-display-extra' which displays the =extra-image' as well.
+
+Refer to =gnosis-db-schema-extras' for informations on images stored."
+ (let* ((img (gnosis-get image 'extras `(= id ,id)))
+ (path-to-image (expand-file-name (or img "") (file-name-as-directory gnosis-images-dir)))
+ (image (create-image path-to-image 'png nil :width gnosis-image-width :height gnosis-image-height))
+ (image-width (car (image-size image t)))
+ (frame-width (window-text-width))) ;; Width of the current window in columns
+ (cond ((or (not img) (string-empty-p img))
+ (insert "\n\n"))
+ ((and img (file-exists-p path-to-image))
+ (let* ((padding-cols (/ (- frame-width (floor (/ image-width (frame-char-width)))) 2))
+ (padding (make-string (max 0 padding-cols) ?\s)))
+ (insert "\n\n" padding) ;; Insert padding before the image
+ (insert-image image)
+ (insert "\n\n"))))))
(defun gnosis-display-mcq-options (id)
"Display answer options for mcq note ID."
@@ -315,7 +434,7 @@ If FILL-PARAGRAPH-P, insert question using `fill-paragraph'."
do (insert (format "\n%s. %s" option-num option))
(setf option-num (1+ option-num)))))
-(defun gnosis-display-cloze-sentence (sentence clozes &optional fill-paragraph-p)
+(cl-defun gnosis-display-cloze-sentence (sentence clozes &optional (fill-paragraph-p nil))
"Display cloze sentence for SENTENCE with CLOZES.
If FILL-PARAGRAPH-P, insert using `fill-paragraph'"
@@ -325,7 +444,8 @@ If FILL-PARAGRAPH-P, insert using `fill-paragraph'"
(if fill-paragraph-p
(fill-paragraph
(insert "\n" cloze-sentence))
- (insert "\n" cloze-sentence))))
+ (insert "\n" (gnosis-center-string cloze-sentence)))
+ (gnosis-apply-syntax-overlay)))
(defun gnosis-display-basic-answer (answer success user-input)
"Display ANSWER.
@@ -335,12 +455,14 @@ When SUCCESS nil, display USER-INPUT as well"
(propertize "Answer:" 'face 'gnosis-face-directions)
" "
(propertize answer 'face 'gnosis-face-correct))
+ (gnosis-center-current-line)
;; Insert user wrong answer
(when (not success)
(insert "\n"
- (propertize "Your answer:" 'face 'gnosis-face-directions)
+ (propertize "Your answer:" 'face 'gnosis-face-directions)
" "
- (propertize user-input 'face 'gnosis-face-false))))
+ (propertize user-input 'face 'gnosis-face-false))
+ (gnosis-center-current-line)))
(cl-defun gnosis-display-y-or-n-answer (&key answer success)
"Display y-or-n answer for note ID.
@@ -353,16 +475,17 @@ SUCCESS is t when user-input is correct, else nil"
"\n\n"
(propertize "Answer:" 'face 'gnosis-face-directions)
" "
- (propertize answer 'face (if success 'gnosis-face-correct 'gnosis-face-false)))))
+ (propertize answer 'face (if success 'gnosis-face-correct 'gnosis-face-false)))
+ (gnosis-center-current-line)))
(defun gnosis-display-hint (hint)
"Display HINT."
(let ((hint (or hint "")))
(goto-char (point-max))
- (insert
- (propertize "\n\n-----\n" 'face 'gnosis-face-separator)
- (propertize hint 'face 'gnosis-face-hint))))
+ (gnosis-insert-separator)
+ (and (not (string-empty-p hint))
+ (insert (gnosis-center-string (propertize hint 'face 'gnosis-face-hint))))))
(cl-defun gnosis-display-cloze-reveal (&key (cloze-char gnosis-cloze-string) replace (success t) (face nil))
"Replace CLOZE-CHAR with REPLACE.
@@ -373,7 +496,8 @@ If FACE nil, propertize replace using `gnosis-face-correct', or
(search-forward cloze-char nil t)
(replace-match (propertize replace 'face (if (not face)
(if success 'gnosis-face-correct 'gnosis-face-false)
- face))))
+ face)))
+ (gnosis-center-current-line))
(cl-defun gnosis-display-cloze-user-answer (user-input &optional (false t))
"Display USER-INPUT answer for cloze note upon failed review.
@@ -383,45 +507,31 @@ If FALSE t, use gnosis-face-false face"
(insert "\n\n"
(propertize "Your answer:" 'face 'gnosis-face-directions)
" "
- (propertize user-input 'face (if false 'gnosis-face-false 'gnosis-face-correct))))
+ (propertize user-input 'face (if false 'gnosis-face-false 'gnosis-face-correct)))
+ (gnosis-center-current-line)
+ (newline))
(defun gnosis-display-correct-answer-mcq (answer user-choice)
"Display correct ANSWER & USER-CHOICE for MCQ note."
- (insert "\n\n"
- (propertize "Correct Answer:" 'face 'gnosis-face-directions)
- " "
- (propertize answer 'face 'gnosis-face-correct)
- "\n"
- (propertize "Your answer:" 'face 'gnosis-face-directions)
- " "
- (propertize user-choice 'face (if (string= answer user-choice)
- 'gnosis-face-correct
- 'gnosis-face-false))))
-
-(cl-defun gnosis-display-image (id &optional (image 'images))
- "Display image for note ID.
-
-IMAGE is the image type to display, usually should be either `images'
-or `extra-image'. Instead of using `extra-image' post review, prefer
-`gnosis-display-extra' which displays the `extra-image' as well.
-
-Refer to `gnosis-db-schema-extras' for informations on images stored."
- (let* ((img (gnosis-get image 'extras `(= id ,id)))
- (path-to-image (expand-file-name (or img "") (file-name-as-directory gnosis-images-dir)))
- (image (create-image path-to-image 'png nil :width gnosis-image-width :height gnosis-image-height)))
- (cond ((or (not img) (string-empty-p img))
- (insert "\n\n"))
- ((and img (file-exists-p path-to-image))
- (insert "\n\n")
- (insert-image image)))))
+ (insert (gnosis-center-string
+ (format "\n\n%s %s\n%s %s"
+ (propertize "Correct Answer:" 'face 'gnosis-face-directions)
+ (propertize answer 'face 'gnosis-face-correct)
+ (propertize "Your answer:" 'face 'gnosis-face-directions)
+ (propertize user-choice 'face (if (string= answer user-choice)
+ 'gnosis-face-correct
+ 'gnosis-face-false))))))
(defun gnosis-display-extra (id)
"Display extra information & extra-image for note ID."
(let ((extras (or (gnosis-get 'extra-notes 'extras `(= id ,id)) "")))
(goto-char (point-max))
- (insert (propertize "\n\n-----\n" 'face 'gnosis-face-separator))
+ (gnosis-insert-separator)
(gnosis-display-image id 'extra-image)
- (fill-paragraph (insert "\n" (propertize extras 'face 'gnosis-face-extra)))))
+ (insert "\n" (gnosis-center-string
+ (propertize extras 'face 'gnosis-face-extra))
+ "\n")
+ (gnosis-apply-syntax-overlay)))
;;;###autoload
(defun gnosis-read-string-from-buffer (prompt string)
@@ -433,9 +543,7 @@ included in the resulting string. If nil, no prompt will be
inserted in the buffer.
Also see `gnosis-string-edit'."
- (gnosis-string-edit
- prompt
- string
+ (gnosis-string-edit prompt string
(lambda (edited)
(setq string (substring-no-properties edited))
(exit-recursive-edit))
@@ -459,7 +567,7 @@ Also see `gnosis-string-edit'."
'face (if success 'gnosis-face-correct 'gnosis-face-false))))
;; Default behaviour
(goto-char (point-max))
- (insert next-review-msg))))
+ (insert (gnosis-center-string next-review-msg)))))
(cl-defun gnosis--prompt (prompt &optional (downcase nil) (split nil))
"PROMPT user for input until `q' is given.
@@ -496,7 +604,7 @@ Set SPLIT to t to split all input given."
(error "No decks found. Please create a deck first with `gnosis-add-deck'"))
(if id
(gnosis-get 'name 'decks `(= id ,id))
- (gnosis-completing-read "Deck: " (gnosis-select 'name 'decks))))
+ (funcall gnosis-completing-read-function "Deck: " (gnosis-select 'name 'decks))))
(cl-defun gnosis--get-deck-id (&optional (deck (gnosis--get-deck-name)))
"Return id for DECK name."
@@ -842,6 +950,62 @@ See `gnosis-add-note--cloze' for more reference."
:images (gnosis-select-images)
:tags (gnosis-prompt-tags--split gnosis-previous-note-tags)))
+(cl-defun gnosis-mc-cloze-extract-options (str &optional (char gnosis-mc-cloze-separator))
+ "Extract options for MC-CLOZE note type from STR.
+
+CHAR: separator for mc-cloze, default to `gnosis-mc-cloze-separator'"
+ (cl-remove-if
+ #'null
+ (mapcar (lambda (s)
+ (when (string-match-p (regexp-quote char) s)
+ (split-string s (regexp-quote char))))
+ (split-string str " "))))
+
+(cl-defun gnosis-add-note--mc-cloze (&key deck question options answer
+ extra (images nil) tags (suspend 0))
+ "Add MC-CLOZE note type to DECK.
+
+Refer to `gnosis-add-note-mc-cloze' for how this procedure should be used
+
+DECK: Deck to add note to
+QUESTION: Question, a string
+OPTIONS: Answer options, a list of strings
+ANSWER: the correct string, from OPTIONS.
+EXTRA: Extra notes
+IMAGES: Images to display during & after review
+TAGS: Tags for note
+SUSPEND: whether to suspend not"
+ (cl-assert (stringp deck) nil "Deck name must be a string")
+ (cl-assert (stringp question) nil "Question must be a string")
+ (cl-assert (listp options) nil "Options must be a list")
+ (cl-assert (stringp extra) nil "Extra value must be a string")
+ (cl-assert (listp images) nil "Images must be a list of string paths")
+ (cl-assert (listp tags) nil "Tags value must be a list of tags as strings")
+ (cl-assert (or (= suspend 1) (= suspend 0)) nil "Suspend value must be either 0 or 1")
+ (gnosis-add-note-fields deck "mc-cloze" question options answer extra tags (or suspend 0)
+ (car images) (cdr images)))
+
+(defun gnosis-add-note-mc-cloze (deck)
+ "Add MC-CLOZE note type to DECK.
+
+MC-CLOZE (Multiple Choice Cloze) note type consists of a sentence with a
+single cloze, for which the user will be prompted to select the correct
+answer."
+ (interactive)
+ (let* ((input (gnosis-read-string-from-buffer (or (car gnosis-mc-cloze-guidance) "")
+ (or (cdr gnosis-mc-cloze-guidance) "")))
+ (question (gnosis-mc-cloze-remove-separator input))
+ (options (gnosis-mc-cloze-extract-options input)))
+ ;; Create a note for each option extracted
+ (cl-loop for option in options
+ do (gnosis-add-note--mc-cloze :deck deck
+ :question question
+ :options option
+ :answer (car option)
+ :extra (gnosis-read-string-from-buffer "Extra" "")
+ :images (gnosis-select-images)
+ :tags (gnosis-prompt-tags--split gnosis-previous-note-tags)))))
+
;;;###autoload
(defun gnosis-add-note (&optional deck type)
"Create note(s) as TYPE interactively.
@@ -853,7 +1017,7 @@ TYPE: Type of gnosis note, must be one of `gnosis-note-types'"
(unless (y-or-n-p "You are using a testing environment! Continue?")
(error "Aborted")))
(let* ((deck (or deck (gnosis--get-deck-name)))
- (type (or type (funcall gnosis-completing-read-function "Type: " gnosis-note-types nil t)))
+ (type (or type (completing-read "Type: " gnosis-note-types nil t)))
(func-name (intern (format "gnosis-add-note-%s" (downcase type)))))
(if (fboundp func-name)
(progn (funcall func-name deck)
@@ -915,6 +1079,12 @@ Valid cloze formats include:
(mapcar (lambda (tag-group) (nreverse (cdr tag-group)))
(nreverse result-alist))))
+(defun gnosis-mc-cloze-remove-separator (string &optional separator)
+ "Remove SEPARATOR and all followed words from STRING."
+ (let* ((separator (or separator gnosis-mc-cloze-separator))
+ (result (replace-regexp-in-string (format "%s[^ ]*" separator) "" string)))
+ result))
+
(defun gnosis-compare-strings (str1 str2)
"Compare STR1 and STR2.
@@ -1269,6 +1439,26 @@ Used to reveal all clozes left with `gnosis-face-cloze-unanswered' face."
(gnosis-display-next-review id success)
success))
+(defun gnosis-review-mc-cloze (id)
+ "Review MC-CLOZE note of ID."
+ (let ((main (gnosis-get 'main 'notes `(= id ,id)))
+ ;; Cloze needs to be a list, we take car as the answer
+ (cloze (list (gnosis-get 'answer 'notes `(= id ,id))))
+ (user-choice nil)
+ (success nil))
+ (gnosis-display-cloze-sentence main cloze)
+ (gnosis-display-image id)
+ (setf user-choice (gnosis-mcq-answer id)
+ success (string= user-choice (car cloze)))
+ (gnosis-display-cloze-reveal :replace (car cloze)
+ :success success)
+ ;; Display user answer only upon failure
+ (unless success
+ (gnosis-display-cloze-user-answer user-choice))
+ (gnosis-display-extra id)
+ (gnosis-display-next-review id success)
+ success))
+
(defun gnosis-review-note (id)
"Start review for note with value of id ID, if note is unsuspended."
(when (gnosis-suspended-p id)
@@ -1297,6 +1487,8 @@ Used to reveal all clozes left with `gnosis-face-cloze-unanswered' face."
(interactive)
(let ((default-directory dir))
(vc-pull)
+ ;; Fix sync by adding a small delay
+ (sit-for 0.3)
;; Reopen gnosis-db after pull
(setf gnosis-db (emacsql-sqlite-open (expand-file-name "gnosis.db" dir)))))
@@ -1319,8 +1511,7 @@ NOTE-NUM: The number of notes reviewed in the session."
(shell-command (format "%s %s %s" git "add" (shell-quote-argument "gnosis.db")))
(shell-command (format "%s %s %s" git "commit -m"
(shell-quote-argument (format "Total notes for session: %d" note-num)))))
- (when (and gnosis-vc-auto-push
- (not gnosis-testing))
+ (when (and gnosis-vc-auto-push (not gnosis-testing))
(gnosis-vc-push))
(message "Review session finished. %d notes reviewed." note-num)))
@@ -1583,17 +1774,16 @@ SUSPEND: Suspend note, 0 for unsuspend, 1 for suspend"
(cl-assert (or (stringp options) (listp options)) nil "Options must be a string, or a list for MCQ")
(cl-assert (or (= suspend 0) (= suspend 1)) nil "Suspend must be either 0 or 1")
;; Construct the update clause for the emacsql update statement.
- (cl-loop for (field . value) in
- `((main . ,main)
- (options . ,options)
- (answer . ,answer)
- (tags . ,tags)
- (extra-notes . ,extra-notes)
- (images . ,image)
- (extra-image . ,second-image)
- (ef . ',ef)
- (ff . ,ff)
- (suspend . ,suspend))
+ (cl-loop for (field . value) in `((main . ,main)
+ (options . ,options)
+ (answer . ,answer)
+ (tags . ,tags)
+ (extra-notes . ,extra-notes)
+ (images . ,image)
+ (extra-image . ,second-image)
+ (ef . ',ef)
+ (ff . ,ff)
+ (suspend . ,suspend))
when value
do (cond ((memq field '(extra-notes images extra-image))
(gnosis-update 'extras `(= ,field ,value) `(= id ,id)))
@@ -1678,10 +1868,10 @@ to improve readability."
(interactive)
;; Refresh modeline
(setq gnosis-due-notes-total (length (gnosis-review-get-due-notes)))
- (let ((review-type (funcall gnosis-completing-read-function "Review: " '("Due notes"
- "Due notes of deck"
- "Due notes of specified tag(s)"
- "All notes of tag(s)"))))
+ (let ((review-type (gnosis-completing-read "Review: " '("Due notes"
+ "Due notes of deck"
+ "Due notes of specified tag(s)"
+ "All notes of tag(s)"))))
(pcase review-type
("Due notes" (gnosis-review-session (gnosis-collect-note-ids :due t)))
("Due notes of deck" (gnosis-review-session (gnosis-collect-note-ids :due t :deck (gnosis--get-deck-id))))
@@ -1767,6 +1957,8 @@ Return note ids for notes that match QUERY."
else
collect (replace-regexp-in-string "\n" " " (format "%s" item))))
+;; TODO: Rewrite. Tags should be an input of strings, interactive
+;; handling should be done by "helper" funcs
(cl-defun gnosis-collect-note-ids (&key (tags nil) (due nil) (deck nil) (query nil))
"Return list of note ids based on TAGS, DUE, DECKS, QUERY.
@@ -1919,12 +2111,13 @@ for dashboard type.
DASHBOARD-TYPE: either 'Notes' or 'Decks' to display the respective dashboard."
(interactive)
- (let ((dashboard-type (or dashboard-type (cadr (read-multiple-choice
- "Display dashboard for:"
- '((?n "notes")
- (?d "decks")
- (?t "tags")
- (?s "search")))))))
+ (let ((dashboard-type (or dashboard-type
+ (cadr (read-multiple-choice
+ "Display dashboard for:"
+ '((?n "notes")
+ (?d "decks")
+ (?t "tags")
+ (?s "search")))))))
(if note-ids (gnosis-dashboard-output-notes note-ids)
(pcase dashboard-type
("notes" (gnosis-dashboard-output-notes (gnosis-collect-note-ids)))
@@ -1963,6 +2156,90 @@ DASHBOARD-TYPE: either 'Notes' or 'Decks' to display the respective dashboard."
(gnosis-db-init)
+;;;; Gnosis Demo ;;;;
+;;;;;;;;;;;;;;;;;;;;;
+
+(defun gnosis-animate-string (string vpos &optional hpos string-section face)
+ "Display STRING animations starting at position VPOS, HPOS in BUFFER-NAME.
+
+If STRING-SECTION and FACE are provided, highlight the occurrences of
+STRING-SECTION in the STRING with FACE.
+
+If STRING-SECTION is nil, apply FACE to the entire STRING."
+ (let ((animate-n-steps 60))
+ (goto-char (point-min))
+ (animate-string string vpos hpos)
+ (and face
+ (if string-section
+ (progn
+ (goto-char (point-min))
+ (while (search-forward string-section nil t)
+ (add-text-properties (match-beginning 0) (match-end 0) `(face ,face))))
+ (add-text-properties (line-beginning-position) (line-end-position) `(face ,face))))))
+
+;;;###autoload
+(defun gnosis-demo ()
+ "Start gnosis demo."
+ (interactive)
+ (pop-to-buffer-same-window "*Gnosis Demo*")
+ (fundamental-mode)
+ (setq-local display-line-numbers nil)
+ (erase-buffer)
+ (gnosis-animate-string "Welcome to the Gnosis demo!" 2 nil "Gnosis demo" 'underline)
+ (sit-for 1)
+ (gnosis-animate-string "Gnosis is a tool designed to create a gnostikon" 3 nil "gnostikon" 'bold-italic)
+ (sit-for 1.5)
+ (gnosis-animate-string "--A place to store & test your knowledge--" 4 nil nil 'italic)
+ (sit-for 1)
+ (gnosis-animate-string "The objective of gnosis is to maximize memory retention." 6 nil
+ "maximize memory retention" 'underline)
+ (sit-for 1)
+ (gnosis-animate-string "Consistency is key; be sure to do your daily reviews!" 8 nil "Consistency is key" 'bold)
+ (sit-for 1)
+ (gnosis-animate-string "Create meaningful notes; Gnosis offers a plethora of note types --try them!"
+ 9 nil "Create Meaningful Notes" 'bold)
+ (sit-for 1)
+ (when (y-or-n-p "Try out demo gnosis note review session?")
+ (gnosis-demo-create-deck)
+ (gnosis-review-session (gnosis-select-by-tag '("demo")))))
+
+(defun gnosis-demo-create-deck ()
+ "Create demo deck."
+ (let ((deck-name "demo")
+ (note-tags '("demo")))
+ (if (not (cl-some #'(lambda (x) (member "demo" x)) (gnosis-select 'name 'decks)))
+ (progn (gnosis-add-deck deck-name)
+ (gnosis-add-note--basic :deck deck-name
+ :question "What's the =key= that was mentioned in the /demo/ of gnosis?"
+ :hint "__ is key!"
+ :answer "Consistency"
+ :extra "/Regular review/ at increasing intervals *reinforces memory retention*. Strengthening neural connections & making it easier to recall information long-term"
+ :tags note-tags)
+ (gnosis-add-note--mc-cloze :deck deck-name
+ :question "Gnosis has a plethora of note types!"
+ :options '("plethora" "limited selection")
+ :answer "plethora"
+ :extra "*Note types include*:\n\n=MCQ=: Multiple Choice Questions, to easily copy textbook review questions :)\n=Cloze=: Fill in the blank sentences\n=Basic=: Question-Answer-Explanation note\n=Double=: Creates 2 basic notes, second one is generated with the question & answer switched\n=MC-Cloze=: Multiple question cloze question, just like this current note\n=y-or-n=: Y or N questions"
+ :tags note-tags)
+ (gnosis-add-note--mcq :deck deck-name
+ :question "Which one is the capital of Greece?"
+ :choices '("Athens" "Sparta" "Rome" "Berlin")
+ :correct-answer 1
+ :extra "Athens (Αθήνα) is the largest city of Greece & one of the world's oldest cities, with it's recorded history spanning over 3,500 years."
+ :tags note-tags)
+ (gnosis-add-note--cloze :deck deck-name
+ :note "GNU Emacs is an extensible editor created by {c1:Richard} {c1:Stallman} in {c2:1984}"
+ :hint ""
+ :tags note-tags
+ :extra "Emacs was originally implemented in 1976 on the MIT AI Lab's Incompatible Timesharing System (ITS), as a collection of TECO macros. The name “Emacs” was originally chosen as an abbreviation of “Editor MACroS”. =This version of Emacs=, GNU Emacs, was originally *written in 1984*")
+ (gnosis-add-note--y-or-n :deck deck-name
+ :question "Is GNU Emacs the greatest program ever written?"
+ :hint "Duh"
+ :answer 121
+ :extra ""
+ :tags note-tags))
+ (error "Demo deck already exists"))))
+
;; Gnosis mode ;;
;;;;;;;;;;;;;;;;;
@@ -1972,18 +2249,18 @@ DASHBOARD-TYPE: either 'Notes' or 'Decks' to display the respective dashboard."
:global t
:group 'gnosis
:lighter nil
- (setq gnosis-due-notes-total (length (gnosis-review-get-due-notes)))
- (if gnosis-modeline-mode
- (progn
- (add-to-list 'global-mode-string '(:eval
- (format " G:%d" gnosis-due-notes-total)))
- (force-mode-line-update))
- (setq global-mode-string
- (seq-remove (lambda (item)
- (and (listp item) (eq (car item) :eval)
- (string-prefix-p " G:" (format "%s" (eval (cadr item))))))
- global-mode-string))
- (force-mode-line-update)))
+ (setq gnosis-due-notes-total (length (gnosis-review-get-due-notes)))
+ (if gnosis-modeline-mode
+ (progn
+ (add-to-list 'global-mode-string '(:eval
+ (format " G:%d" gnosis-due-notes-total)))
+ (force-mode-line-update))
+ (setq global-mode-string
+ (seq-remove (lambda (item)
+ (and (listp item) (eq (car item) :eval)
+ (string-prefix-p " G:" (format "%s" (eval (cadr item))))))
+ global-mode-string))
+ (force-mode-line-update)))
(define-derived-mode gnosis-mode special-mode "Gnosis"
"Gnosis Mode."