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