org-roam-dailies.el (16485B)
1 ;;; org-roam-dailies.el --- Daily-notes for Org-roam -*- coding: utf-8; lexical-binding: t; -*- 2 ;;; 3 ;; Copyright © 2020-2022 Jethro Kuan <jethrokuan95@gmail.com> 4 ;; Copyright © 2020 Leo Vivier <leo.vivier+dev@gmail.com> 5 6 ;; Author: Jethro Kuan <jethrokuan95@gmail.com> 7 ;; Leo Vivier <leo.vivier+dev@gmail.com> 8 ;; URL: https://github.com/org-roam/org-roam 9 ;; Keywords: org-mode, roam, convenience 10 ;; Version: 2.2.2 11 ;; Package-Requires: ((emacs "26.1") (dash "2.13") (org-roam "2.1")) 12 13 ;; This file is NOT part of GNU Emacs. 14 15 ;; This program is free software; you can redistribute it and/or modify 16 ;; it under the terms of the GNU General Public License as published by 17 ;; the Free Software Foundation; either version 3, or (at your option) 18 ;; any later version. 19 ;; 20 ;; This program is distributed in the hope that it will be useful, 21 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 22 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 ;; GNU General Public License for more details. 24 ;; 25 ;; You should have received a copy of the GNU General Public License 26 ;; along with GNU Emacs; see the file COPYING. If not, write to the 27 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 28 ;; Boston, MA 02110-1301, USA. 29 30 ;;; Commentary: 31 ;; 32 ;; This extension provides functionality for creating daily-notes, or shortly 33 ;; "dailies". Dailies implemented here as a unique node per unique file, where 34 ;; each file named after certain date and stored in `org-roam-dailies-directory'. 35 ;; 36 ;; One can use dailies for various purposes, e.g. journaling, fleeting notes, 37 ;; scratch notes or whatever else you can think of. 38 ;; 39 ;;; Code: 40 (require 'dash) 41 (require 'org-roam) 42 43 ;;; Faces 44 (defface org-roam-dailies-calendar-note 45 '((t :inherit (org-link) :underline nil)) 46 "Face for dates with a daily-note in the calendar." 47 :group 'org-roam-faces) 48 49 ;;; Options 50 (defcustom org-roam-dailies-directory "daily/" 51 "Path to daily-notes. 52 This path is relative to `org-roam-directory'." 53 :group 'org-roam 54 :type 'string) 55 56 (defcustom org-roam-dailies-find-file-hook nil 57 "Hook that is run right after navigating to a daily-note." 58 :group 'org-roam 59 :type 'hook) 60 61 (defcustom org-roam-dailies-capture-templates 62 `(("d" "default" entry 63 "* %?" 64 :target (file+head "%<%Y-%m-%d>.org" 65 "#+title: %<%Y-%m-%d>\n"))) 66 "Capture templates for daily-notes in Org-roam. 67 Note that for daily files to show up in the calendar, they have to be of format 68 \"org-time-string.org\". 69 See `org-roam-capture-templates' for the template documentation." 70 :group 'org-roam 71 :type '(repeat 72 (choice (list :tag "Multikey description" 73 (string :tag "Keys ") 74 (string :tag "Description")) 75 (list :tag "Template entry" 76 (string :tag "Keys ") 77 (string :tag "Description ") 78 (choice :tag "Capture Type " :value entry 79 (const :tag "Org entry" entry) 80 (const :tag "Plain list item" item) 81 (const :tag "Checkbox item" checkitem) 82 (const :tag "Plain text" plain) 83 (const :tag "Table line" table-line)) 84 (choice :tag "Template " 85 (string) 86 (list :tag "File" 87 (const :format "" file) 88 (file :tag "Template file")) 89 (list :tag "Function" 90 (const :format "" function) 91 (function :tag "Template function"))) 92 (plist :inline t 93 ;; Give the most common options as checkboxes 94 :options (((const :format "%v " :target) 95 (choice :tag "Node location" 96 (list :tag "File" 97 (const :format "" file) 98 (string :tag " File")) 99 (list :tag "File & Head Content" 100 (const :format "" file+head) 101 (string :tag " File") 102 (string :tag " Head Content")) 103 (list :tag "File & Outline path" 104 (const :format "" file+olp) 105 (string :tag " File") 106 (list :tag "Outline path" 107 (repeat (string :tag "Headline")))) 108 (list :tag "File & Head Content & Outline path" 109 (const :format "" file+head+olp) 110 (string :tag " File") 111 (string :tag " Head Content") 112 (list :tag "Outline path" 113 (repeat (string :tag "Headline")))))) 114 ((const :format "%v " :prepend) (const t)) 115 ((const :format "%v " :immediate-finish) (const t)) 116 ((const :format "%v " :jump-to-captured) (const t)) 117 ((const :format "%v " :empty-lines) (const 1)) 118 ((const :format "%v " :empty-lines-before) (const 1)) 119 ((const :format "%v " :empty-lines-after) (const 1)) 120 ((const :format "%v " :clock-in) (const t)) 121 ((const :format "%v " :clock-keep) (const t)) 122 ((const :format "%v " :clock-resume) (const t)) 123 ((const :format "%v " :time-prompt) (const t)) 124 ((const :format "%v " :tree-type) (const week)) 125 ((const :format "%v " :unnarrowed) (const t)) 126 ((const :format "%v " :table-line-pos) (string)) 127 ((const :format "%v " :kill-buffer) (const t)))))))) 128 129 ;;; Commands 130 ;;;; Today 131 ;;;###autoload 132 (defun org-roam-dailies-capture-today (&optional goto keys) 133 "Create an entry in the daily-note for today. 134 When GOTO is non-nil, go the note without creating an entry. 135 136 ELisp programs can set KEYS to a string associated with a template. 137 In this case, interactive selection will be bypassed." 138 (interactive "P") 139 (org-roam-dailies--capture (current-time) goto keys)) 140 141 ;;;###autoload 142 (defun org-roam-dailies-goto-today (&optional keys) 143 "Find the daily-note for today, creating it if necessary. 144 145 ELisp programs can set KEYS to a string associated with a template. 146 In this case, interactive selection will be bypassed." 147 (interactive) 148 (org-roam-dailies-capture-today t keys)) 149 150 ;;;; Tomorrow 151 ;;;###autoload 152 (defun org-roam-dailies-capture-tomorrow (n &optional goto keys) 153 "Create an entry in the daily-note for tomorrow. 154 155 With numeric argument N, use the daily-note N days in the future. 156 157 With a `C-u' prefix or when GOTO is non-nil, go the note without 158 creating an entry. 159 160 ELisp programs can set KEYS to a string associated with a template. 161 In this case, interactive selection will be bypassed." 162 (interactive "p") 163 (org-roam-dailies--capture (time-add (* n 86400) (current-time)) goto keys)) 164 165 ;;;###autoload 166 (defun org-roam-dailies-goto-tomorrow (n &optional keys) 167 "Find the daily-note for tomorrow, creating it if necessary. 168 169 With numeric argument N, use the daily-note N days in the 170 future. 171 172 ELisp programs can set KEYS to a string associated with a template. 173 In this case, interactive selection will be bypassed." 174 (interactive "p") 175 (org-roam-dailies-capture-tomorrow n t keys)) 176 177 ;;;; Yesterday 178 ;;;###autoload 179 (defun org-roam-dailies-capture-yesterday (n &optional goto keys) 180 "Create an entry in the daily-note for yesteday. 181 182 With numeric argument N, use the daily-note N days in the past. 183 184 When GOTO is non-nil, go the note without creating an entry. 185 186 ELisp programs can set KEYS to a string associated with a template. 187 In this case, interactive selection will be bypassed." 188 (interactive "p") 189 (org-roam-dailies-capture-tomorrow (- n) goto keys)) 190 191 ;;;###autoload 192 (defun org-roam-dailies-goto-yesterday (n &optional keys) 193 "Find the daily-note for yesterday, creating it if necessary. 194 195 With numeric argument N, use the daily-note N days in the 196 future. 197 198 ELisp programs can set KEYS to a string associated with a template. 199 In this case, interactive selection will be bypassed." 200 (interactive "p") 201 (org-roam-dailies-capture-tomorrow (- n) t keys)) 202 203 ;;;; Date 204 ;;;###autoload 205 (defun org-roam-dailies-capture-date (&optional goto prefer-future keys) 206 "Create an entry in the daily-note for a date using the calendar. 207 Prefer past dates, unless PREFER-FUTURE is non-nil. 208 With a `C-u' prefix or when GOTO is non-nil, go the note without 209 creating an entry. 210 211 ELisp programs can set KEYS to a string associated with a template. 212 In this case, interactive selection will be bypassed." 213 (interactive "P") 214 (let ((time (let ((org-read-date-prefer-future prefer-future)) 215 (org-read-date nil t nil (if goto 216 "Find daily-note: " 217 "Capture to daily-note: "))))) 218 (org-roam-dailies--capture time goto keys))) 219 220 ;;;###autoload 221 (defun org-roam-dailies-goto-date (&optional prefer-future keys) 222 "Find the daily-note for a date using the calendar, creating it if necessary. 223 Prefer past dates, unless PREFER-FUTURE is non-nil. 224 225 ELisp programs can set KEYS to a string associated with a template. 226 In this case, interactive selection will be bypassed." 227 (interactive) 228 (org-roam-dailies-capture-date t prefer-future keys)) 229 230 ;;;; Navigation 231 (defun org-roam-dailies-goto-next-note (&optional n) 232 "Find next daily-note. 233 234 With numeric argument N, find note N days in the future. If N is 235 negative, find note N days in the past." 236 (interactive "p") 237 (unless (org-roam-dailies--daily-note-p) 238 (user-error "Not in a daily-note")) 239 (setq n (or n 1)) 240 (let* ((dailies (org-roam-dailies--list-files)) 241 (position 242 (cl-position-if (lambda (candidate) 243 (string= (buffer-file-name (buffer-base-buffer)) candidate)) 244 dailies)) 245 note) 246 (unless position 247 (user-error "Can't find current note file - have you saved it yet?")) 248 (pcase n 249 ((pred (natnump)) 250 (when (eq position (- (length dailies) 1)) 251 (user-error "Already at newest note"))) 252 ((pred (integerp)) 253 (when (eq position 0) 254 (user-error "Already at oldest note")))) 255 (setq note (nth (+ position n) dailies)) 256 (find-file note) 257 (run-hooks 'org-roam-dailies-find-file-hook))) 258 259 (defun org-roam-dailies-goto-previous-note (&optional n) 260 "Find previous daily-note. 261 262 With numeric argument N, find note N days in the past. If N is 263 negative, find note N days in the future." 264 (interactive "p") 265 (let ((n (if n (- n) -1))) 266 (org-roam-dailies-goto-next-note n))) 267 268 (defun org-roam-dailies--list-files (&rest extra-files) 269 "List all files in `org-roam-dailies-directory'. 270 EXTRA-FILES can be used to append extra files to the list." 271 (let ((dir (expand-file-name org-roam-dailies-directory org-roam-directory)) 272 (regexp (rx-to-string `(and "." (or ,@org-roam-file-extensions))))) 273 (append (--remove (let ((file (file-name-nondirectory it))) 274 (when (or (auto-save-file-name-p file) 275 (backup-file-name-p file) 276 (string-match "^\\." file)) 277 it)) 278 (directory-files-recursively dir regexp)) 279 extra-files))) 280 281 (defun org-roam-dailies--daily-note-p (&optional file) 282 "Return t if FILE is an Org-roam daily-note, nil otherwise. 283 If FILE is not specified, use the current buffer's file-path." 284 (when-let ((path (expand-file-name 285 (or file 286 (buffer-file-name (buffer-base-buffer))))) 287 (directory (expand-file-name org-roam-dailies-directory org-roam-directory))) 288 (setq path (expand-file-name path)) 289 (save-match-data 290 (and 291 (org-roam-file-p path) 292 (org-roam-descendant-of-p path directory))))) 293 294 ;;;###autoload 295 (defun org-roam-dailies-find-directory () 296 "Find and open `org-roam-dailies-directory'." 297 (interactive) 298 (find-file (expand-file-name org-roam-dailies-directory org-roam-directory))) 299 300 ;;; Calendar integration 301 (defun org-roam-dailies-calendar--file-to-date (file) 302 "Convert FILE to date. 303 Return (MONTH DAY YEAR) or nil if not an Org time-string." 304 (ignore-errors 305 (cl-destructuring-bind (_ _ _ d m y _ _ _) 306 (org-parse-time-string 307 (file-name-sans-extension 308 (file-name-nondirectory file))) 309 (list m d y)))) 310 311 (defun org-roam-dailies-calendar-mark-entries () 312 "Mark days in the calendar for which a daily-note is present." 313 (when (file-exists-p (expand-file-name org-roam-dailies-directory org-roam-directory)) 314 (dolist (date (remove nil 315 (mapcar #'org-roam-dailies-calendar--file-to-date 316 (org-roam-dailies--list-files)))) 317 (when (calendar-date-is-visible-p date) 318 (calendar-mark-visible-date date 'org-roam-dailies-calendar-note))))) 319 320 (add-hook 'calendar-today-visible-hook #'org-roam-dailies-calendar-mark-entries) 321 (add-hook 'calendar-today-invisible-hook #'org-roam-dailies-calendar-mark-entries) 322 323 ;;; Capture implementation 324 (add-to-list 'org-roam-capture--template-keywords :override-default-time) 325 326 (defun org-roam-dailies--capture (time &optional goto keys) 327 "Capture an entry in a daily-note for TIME, creating it if necessary. 328 When GOTO is non-nil, go the note without creating an entry. 329 330 ELisp programs can set KEYS to a string associated with a template. 331 In this case, interactive selection will be bypassed." 332 (let ((org-roam-directory (expand-file-name org-roam-dailies-directory org-roam-directory)) 333 (org-roam-dailies-directory "./")) 334 (org-roam-capture- :goto (when goto '(4)) 335 :keys keys 336 :node (org-roam-node-create) 337 :templates org-roam-dailies-capture-templates 338 :props (list :override-default-time time))) 339 (when goto (run-hooks 'org-roam-dailies-find-file-hook))) 340 341 (add-hook 'org-roam-capture-preface-hook #'org-roam-dailies--override-capture-time-h) 342 (defun org-roam-dailies--override-capture-time-h () 343 "Override the `:default-time' with the time from `:override-default-time'." 344 (prog1 nil 345 (when (org-roam-capture--get :override-default-time) 346 (org-capture-put :default-time (org-roam-capture--get :override-default-time))))) 347 348 ;;; Bindings 349 (defvar org-roam-dailies-map (make-sparse-keymap) 350 "Keymap for `org-roam-dailies'.") 351 352 (define-prefix-command 'org-roam-dailies-map) 353 354 (define-key org-roam-dailies-map (kbd "d") #'org-roam-dailies-goto-today) 355 (define-key org-roam-dailies-map (kbd "y") #'org-roam-dailies-goto-yesterday) 356 (define-key org-roam-dailies-map (kbd "t") #'org-roam-dailies-goto-tomorrow) 357 (define-key org-roam-dailies-map (kbd "n") #'org-roam-dailies-capture-today) 358 (define-key org-roam-dailies-map (kbd "f") #'org-roam-dailies-goto-next-note) 359 (define-key org-roam-dailies-map (kbd "b") #'org-roam-dailies-goto-previous-note) 360 (define-key org-roam-dailies-map (kbd "c") #'org-roam-dailies-goto-date) 361 (define-key org-roam-dailies-map (kbd "v") #'org-roam-dailies-capture-date) 362 (define-key org-roam-dailies-map (kbd ".") #'org-roam-dailies-find-directory) 363 364 (provide 'org-roam-dailies) 365 366 ;;; org-roam-dailies.el ends here