branch: externals/denote-journal
commit a95b24136c4a366a6760db060894830b76a4d3dc
Author: Protesilaos Stavrou <i...@protesilaos.com>
Commit: Protesilaos Stavrou <i...@protesilaos.com>

    Define integration with the M-x calendar
---
 README.org        | 46 +++++++++++++++++++++++----
 denote-journal.el | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 133 insertions(+), 6 deletions(-)

diff --git a/README.org b/README.org
index 5b79a663d5..95ddb9157d 100644
--- a/README.org
+++ b/README.org
@@ -123,11 +123,12 @@ entry, it creates a new one by calling 
~denote-journal-new-entry~
 be done via ~org-capture~  
([[#h:create-a-journal-entry-using-org-capture][Create a journal entry using 
Org capture]]).
 
 When called with a prefix argument (=C-u= with default key bindings),
-the command ~denote-journal-new-or-existing-entry~ prompts for a date.
-It then determines whether to visit an existing file or create a new
-one, as described above. The date selection interface is controlled by
-the user option ~denote-date-prompt-use-org-read-date~, which is part
-of the main ~denote~ package. By default, this is a simple minibuffer
+the command ~denote-journal-new-or-existing-entry~ prompts for a date
+([[#h:integrate-with-the-calendar-to-view-or-create-journal-entries][Integrate 
with the ~calendar~ to view or create journal entries]]). It
+then determines whether to visit an existing file or create a new one,
+as described above. The date selection interface is controlled by the
+user option ~denote-date-prompt-use-org-read-date~, which is part of
+the main ~denote~ package. By default, this is a simple minibuffer
 prompt, though users can opt in to the more advanced
 minibuffer+calendar date picker that Org uses for its own date
 selection operations.
@@ -148,6 +149,37 @@ date and then performs the aforementioned. With a double 
prefix
 argument (=C-u C-u=), it also produces a link whose description
 includes just the file's identifier.
 
+** Integrate with the ~calendar~ to view or create journal entries
+:PROPERTIES:
+:CUSTOM_ID: h:integrate-with-the-calendar-to-view-or-create-journal-entries
+:END:
+
+[ Part of {{{development-version}}}. ]
+
+#+findex: denote-journal-calendar-new-or-existing
+The command ~denote-journal-calendar-new-or-existing~ creates a new
+journal entry for the date at point in the =M-x calendar= buffer. If
+an entry exists, it visits it. This is the same as the command
+~denote-journal-new-or-existing-entry~ 
([[#h:create-a-new-journal-entry-or-use-an-existing-one][Create a new journal 
entry or use an existing one]]).
+
+#+findex: denote-journal-calendar-find-file
+The command ~denote-journal-calendar-find-file~ visits the Denote
+journal entry corresponding to the date at point in the =M-x
+calendar=. If there are multiple entries, it prompts with minibuffer
+completion to select one among them.
+
+#+findex: denote-journal-calendar-mode
+#+vindex: denote-journal-calendar
+The minor mode ~denote-journal-calendar-mode~ marks the dates in the
+=M-x calendar= that have a Denote journal entry. The face applied to
+them is the ~denote-journal-calendar~.
+
+#+vindex: denote-journal-calendar-mode-map
+The ~denote-journal-calendar-mode~ also activates the key map called
+~denote-journal-calendar-mode-map~. It defines bindings for the
+aforementioned commands: =F= for ~denote-journal-calendar-find-file~
+and =N= for ~denote-journal-calendar-new-or-existing~.
+
 ** The ~denote-journal-directory~
 :PROPERTIES:
 :CUSTOM_ID: h:the-denote-journal-directory
@@ -371,7 +403,9 @@ Everything is in place to set up the package.
   ;; strings.
   (setq denote-journal-keyword "journal")
   ;; Read the doc string of `denote-journal-title-format'.
-  (setq denote-journal-title-format 'day-date-month-year))
+  (setq denote-journal-title-format 'day-date-month-year)
+
+  (denote-journal-calendar-mode 1))
 #+end_src
 
 * Acknowledgements
diff --git a/denote-journal.el b/denote-journal.el
index ee1469ca8c..281159b2bf 100644
--- a/denote-journal.el
+++ b/denote-journal.el
@@ -32,6 +32,7 @@
 ;;; Code:
 
 (require 'denote)
+(require 'calendar)
 
 (defgroup denote-journal nil
   "Convenience functions for daily journaling with Denote."
@@ -283,5 +284,97 @@ file's title.  This has the same meaning as in 
`denote-link'."
                  (denote-get-link-description path)
                  id-only)))
 
+;;;; Integration with the `calendar'
+
+(defface denote-journal-calendar
+  '((t :inherit highlight :box (:line-width (-1 . -1))))
+  "Face to mark a Denote journal entry in the `calendar'.")
+
+(defun denote-journal-calendar--file-to-date (file)
+  "Convert FILE to date.
+Return (MONTH DAY YEAR) or nil if not an Org time-string."
+  (when-let* ((identifier (denote-retrieve-filename-identifier file))
+              (date (denote--id-to-date identifier))
+              (numbers (mapcar #'string-to-number (split-string date "-"))))
+    (pcase-let ((`(,year ,month ,day) numbers))
+      (list month day year))))
+
+(defun denote-journal-calendar--get-files ()
+  "Return list of files matching variable `denote-journal-keyword'."
+  (let ((denote-directory (file-name-as-directory (denote-journal-directory))))
+    (denote-directory-files (denote-journal--keyword-regex))))
+
+(defun denote-journal-calendar--get-files-as-dates ()
+  "Return list of files as dates in the form of (MONTH DAY YEAR)."
+  (delq nil (mapcar #'denote-journal-calendar--file-to-date 
(denote-journal-calendar--get-files))))
+
+(defun denote-journal-calendar-mark-dates ()
+  "Mark all days in the `calendar' for which there is a Denote journal entry."
+  (interactive)
+  (when-let* ((dates (denote-journal-calendar--get-files-as-dates)))
+    (dolist (date dates)
+      (when (calendar-date-is-visible-p date)
+        (calendar-mark-visible-date date 'denote-journal-calendar)))))
+
+(defun denote-journal-calendar--date-at-point-to-internal-date (&optional 
calendar-date)
+  "Return `encode-time' value of `calendar' date at point or CALENDAR-DATE.
+CALENDAR-DATE is commensurate with `calendar-cursor-to-date'."
+  (when-let* ((current-date (or calendar-date (calendar-cursor-to-date t))))
+    (pcase-let ((`(,month ,day ,year) current-date))
+      (encode-time (decode-time (date-to-time (format "%s-%02d-%02d" year 
month day)))))))
+
+(defun denote-journal-calendar--date-at-point-to-identifier ()
+  "Return path to Denote journal entry for the `calendar' date at point."
+  (when-let* ((date (denote-journal-calendar--date-at-point-to-internal-date)))
+    (denote-journal--entry-today date)))
+
+(defun denote-journal-calendar-find-file ()
+  "Show the Denote journal entry for the `calendar' date at point.
+If there are more than one files, prompt with completion to select one
+among them."
+  (declare (interactive-only t))
+  (interactive nil calendar-mode)
+  (unless (derived-mode-p 'calendar-mode)
+    (user-error "Only use this command inside the `calendar'"))
+  (if-let* ((files (denote-journal-calendar--date-at-point-to-identifier))
+            (file (if (> (length files) 1)
+                      (completing-read "Select journal entry: " file nil t)
+                    (car files))))
+      (find-file-other-window file)
+    (user-error "No Denote journal entry for this date")))
+
+(defun denote-journal-calendar-new-or-existing ()
+  "Like `denote-journal-new-or-existing-entry' for the `calendar' date at 
point."
+  (declare (interactive-only t))
+  (interactive nil calendar-mode)
+  (unless (derived-mode-p 'calendar-mode)
+    (user-error "Only use this command inside the `calendar'"))
+  (if-let* ((date (calendar-cursor-to-date t))
+            (internal (denote-journal-calendar--date-at-point-to-internal-date 
date)))
+      (progn
+        (calendar-mark-visible-date date 'denote-journal-calendar)
+        ;; Do not use the same `calendar' window...
+        (cl-letf (((symbol-function #'find-file) #'find-file-other-window))
+          (denote-journal-new-or-existing-entry internal)))
+    (user-error "No Denote journal entry for this date")))
+
+(defvar denote-journal-calendar-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "N" #'denote-journal-calendar-new-or-existing)
+    (define-key map "F" #'denote-journal-calendar-find-file)
+    map)
+  "Key map for `denote-journal-calendar-mode'.")
+
+;;;###autoload
+(define-minor-mode denote-journal-calendar-mode
+  "Mark Denote journal entries in the `calendar' using 
`denote-journal-calendar' face."
+  :global t
+  :init-value nil
+  (if denote-journal-calendar-mode
+      (dolist (hook '(calendar-today-visible-hook 
calendar-today-invisible-hook))
+        (add-hook hook #'denote-journal-calendar-mark-dates))
+    (dolist (hook '(calendar-today-visible-hook calendar-today-invisible-hook))
+      (remove-hook hook #'denote-journal-calendar-mark-dates))))
+
 (provide 'denote-journal)
 ;;; denote-journal.el ends here

Reply via email to