denote-journal-extras.el (10303B)
1 ;;; denote-journal-extras.el --- Convenience functions for daily journaling -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2023-2024 Free Software Foundation, Inc. 4 5 ;; Author: Protesilaos Stavrou <info@protesilaos.com> 6 ;; Maintainer: Protesilaos Stavrou <info@protesilaos.com> 7 ;; URL: https://github.com/protesilaos/denote 8 9 ;; This file is NOT part of GNU Emacs. 10 11 ;; This program is free software; you can redistribute it and/or modify 12 ;; it under the terms of the GNU General Public License as published by 13 ;; the Free Software Foundation, either version 3 of the License, or 14 ;; (at your option) any later version. 15 16 ;; This program is distributed in the hope that it will be useful, 17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 ;; GNU General Public License for more details. 20 21 ;; You should have received a copy of the GNU General Public License 22 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 23 24 ;;; Commentary: 25 26 ;; This is a set of optional convenience functions that used to be 27 ;; provided in the Denote manual. They facilitate the use of Denote 28 ;; for daily journaling. 29 30 ;;; Code: 31 32 (require 'denote) 33 34 (defgroup denote-journal-extras nil 35 "Denote for daily journaling." 36 :group 'denote 37 :link '(info-link "(denote) Top") 38 :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote")) 39 40 (defcustom denote-journal-extras-directory 41 (expand-file-name "journal" denote-directory) 42 "Directory for storing daily journal entries. 43 This can either be the same as the variable `denote-directory' or 44 a subdirectory of it. 45 46 A value of nil means to use the variable `denote-directory'. 47 Journal entries will thus be in a flat listing together with all 48 other notes. They can still be retrieved easily by searching for 49 the `denote-journal-extras-keyword'." 50 :group 'denote-journal-extras 51 :type '(choice (directory :tag "Provide directory path (is created if missing)") 52 (const :tag "Use the `denote-directory'" nil))) 53 54 (defcustom denote-journal-extras-keyword "journal" 55 "Single word keyword to tag journal entries. 56 It is used by `denote-journal-extras-new-entry' to add a keyword 57 to the newly created file." 58 :group 'denote-journal-extras 59 :type 'string) 60 61 (defcustom denote-journal-extras-title-format 'day-date-month-year-24h 62 "Date format to construct the title with `denote-journal-extras-new-entry'. 63 The value is either a symbol or an arbitrary string that is 64 passed to `format-time-string' (consult its documentation for the 65 technicalities). 66 67 Acceptable symbols and their corresponding styles are: 68 69 | Symbol | Style | 70 |-------------------------+-----------------------------------| 71 | day | Monday | 72 | day-date-month-year | Monday 19 September 2023 | 73 | day-date-month-year-24h | Monday 19 September 2023 20:49 | 74 | day-date-month-year-12h | Monday 19 September 2023 08:49 PM | 75 76 With a nil value, make `denote-journal-extras-new-entry' prompt 77 for a title." 78 :group 'denote-journal-extras 79 :type '(choice 80 (const :tag "Prompt for title with `denote-journal-extras-new-entry'" nil) 81 (const :tag "Monday" 82 :doc "The `format-time-string' is: %A" 83 day) 84 (const :tag "Monday 19 September 2023" 85 :doc "The `format-time-string' is: %A %e %B %Y" 86 day-date-month-year) 87 (const :tag "Monday 19 September 2023 20:49" 88 :doc "The `format-time-string' is: %A %e %B %Y %H:%M" 89 day-date-month-year-24h) 90 (const :tag "Monday 19 September 2023 08:49 PM" 91 :doc "The `format-time-string' is: %A %e %B %Y %I:%M %^p" 92 day-date-month-year-12h) 93 (string :tag "Custom string with `format-time-string' specifiers"))) 94 95 (defcustom denote-journal-extras-hook nil 96 "Normal hook called after `denote-journal-extras-new-entry'. 97 Use this to, for example, set a timer after starting a new 98 journal entry (refer to the `tmr' package on GNU ELPA)." 99 :group 'denote-journal-extras 100 :type 'hook) 101 102 (defun denote-journal-extras-directory () 103 "Make the variable `denote-journal-extras-directory' and its parents." 104 (if-let (((stringp denote-journal-extras-directory)) 105 (directory (file-name-as-directory (expand-file-name denote-journal-extras-directory)))) 106 (progn 107 (when (not (file-directory-p denote-journal-extras-directory)) 108 (make-directory directory :parents)) 109 directory) 110 (denote-directory))) 111 112 (defun denote-journal-extras-daily--title-format (&optional date) 113 "Return present date in `denote-journal-extras-title-format' or prompt for title. 114 With optional DATE, use it instead of the present date. DATE has 115 the same format as that returned by `current-time'." 116 (format-time-string 117 (if (and denote-journal-extras-title-format 118 (stringp denote-journal-extras-title-format)) 119 denote-journal-extras-title-format 120 (pcase denote-journal-extras-title-format 121 ('day "%A") 122 ('day-date-month-year "%A %e %B %Y") 123 ('day-date-month-year-24h "%A %e %B %Y %H:%M") 124 ('day-date-month-year-12h "%A %e %B %Y %I:%M %^p") 125 (_ (denote-title-prompt (format-time-string "%F" date))))) 126 date)) 127 128 (defun denote-journal-extras--get-template () 129 "Return template that has `journal' key in `denote-templates'. 130 If no template with `journal' key exists but `denote-templates' 131 is non-nil, prompt the user for a template among 132 `denote-templates'. Else return nil. 133 134 Also see `denote-journal-extras-new-entry'." 135 (if-let ((template (alist-get 'journal denote-templates))) 136 template 137 (when denote-templates 138 (denote-template-prompt)))) 139 140 ;;;###autoload 141 (defun denote-journal-extras-new-entry (&optional date) 142 "Create a new journal entry in variable `denote-journal-extras-directory'. 143 Use `denote-journal-extras-keyword' as a keyword for the newly 144 created file. Set the title of the new entry according to the 145 value of the user option `denote-journal-extras-title-format'. 146 147 With optional DATE as a prefix argument, prompt for a date. If 148 `denote-date-prompt-use-org-read-date' is non-nil, use the Org 149 date selection module. 150 151 When called from Lisp DATE is a string and has the same format as 152 that covered in the documentation of the `denote' function. It 153 is internally processed by `denote-parse-date'." 154 (interactive (list (when current-prefix-arg (denote-date-prompt)))) 155 (let ((internal-date (denote-parse-date date)) 156 (denote-directory (denote-journal-extras-directory))) 157 (denote 158 (denote-journal-extras-daily--title-format internal-date) 159 `(,denote-journal-extras-keyword) 160 nil nil date 161 (denote-journal-extras--get-template)) 162 (run-hooks 'denote-journal-extras-hook))) 163 164 (defun denote-journal-extras--entry-today (&optional date) 165 "Return list of files matching a journal for today or optional DATE. 166 DATE has the same format as that returned by `denote-parse-date'." 167 (denote-directory-files 168 (format "%sT[0-9]\\{6\\}.*_%s" 169 (format-time-string "%Y%m%d" date) 170 denote-journal-extras-keyword))) 171 172 ;;;###autoload 173 (defun denote-journal-extras-new-or-existing-entry (&optional date) 174 "Locate an existing journal entry or create a new one. 175 A journal entry is one that has `denote-journal-extras-keyword' as 176 part of its file name. 177 178 If there are multiple journal entries for the current date, 179 prompt for one using minibuffer completion. If there is only 180 one, visit it outright. If there is no journal entry, create one 181 by calling `denote-journal-extra-new-entry'. 182 183 With optional DATE as a prefix argument, prompt for a date. If 184 `denote-date-prompt-use-org-read-date' is non-nil, use the Org 185 date selection module. 186 187 When called from Lisp, DATE is a string and has the same format 188 as that covered in the documentation of the `denote' function. 189 It is internally processed by `denote-parse-date'." 190 (interactive 191 (list 192 (when current-prefix-arg 193 (denote-date-prompt)))) 194 (let* ((internal-date (denote-parse-date date)) 195 (files (denote-journal-extras--entry-today internal-date))) 196 (cond 197 ((length> files 1) 198 (find-file (completing-read "Select journal entry: " files nil :require-match))) 199 (files 200 (find-file (car files))) 201 (t 202 (denote-journal-extras-new-entry date))))) 203 204 ;;;###autoload 205 (defun denote-journal-extras-link-or-create-entry (&optional date id-only) 206 "Use `denote-link' on journal entry, creating it if necessary. 207 A journal entry is one that has `denote-journal-extras-keyword' as 208 part of its file name. 209 210 If there are multiple journal entries for the current date, 211 prompt for one using minibuffer completion. If there is only 212 one, link to it outright. If there is no journal entry, create one 213 by calling `denote-journal-extra-new-entry' and link to it. 214 215 With optional DATE as a prefix argument, prompt for a date. If 216 `denote-date-prompt-use-org-read-date' is non-nil, use the Org 217 date selection module. 218 219 When called from Lisp, DATE is a string and has the same format 220 as that covered in the documentation of the `denote' function. 221 It is internally processed by `denote-parse-date'. 222 223 With optional ID-ONLY as a prefix argument create a link that 224 consists of just the identifier. Else try to also include the 225 file's title. This has the same meaning as in `denote-link'." 226 (interactive 227 (pcase current-prefix-arg 228 ('(16) (list (denote-date-prompt) :id-only)) 229 ('(4) (list (denote-date-prompt))))) 230 (let* ((internal-date (denote-parse-date date)) 231 (files (denote-journal-extras--entry-today internal-date)) 232 (path)) 233 (cond 234 ((length> files 1) 235 (setq path (completing-read "Select journal entry: " files nil :require-match))) 236 (files 237 (setq path (car files))) 238 (t 239 (save-window-excursion 240 (denote-journal-extras-new-entry date) 241 (save-buffer) 242 (setq path (buffer-file-name))))) 243 (denote-link path 244 (denote-filetype-heuristics (buffer-file-name)) 245 (denote--link-get-description path) 246 id-only))) 247 248 (provide 'denote-journal-extras) 249 ;;; denote-journal-extras.el ends here