(feat): Overhaul org-roam-dailies (#978)

Implement ideas from `org-journal` to `org-roam-dailies`.

Update the doc.
This commit is contained in:
Leo Vivier
2020-11-10 14:31:38 +01:00
committed by GitHub
parent 8c81104816
commit 4f6eb285bf
6 changed files with 741 additions and 153 deletions

View File

@ -123,7 +123,7 @@ A slip-box requires a method of quickly capturing ideas. These are called
*fleeting notes*: they are simple reminders of information or ideas that will *fleeting notes*: they are simple reminders of information or ideas that will
need to be processed later on, or trashed. This is typically accomplished using need to be processed later on, or trashed. This is typically accomplished using
~org-capture~ (see info:org#capture), or using Org-roam's daily notes ~org-capture~ (see info:org#capture), or using Org-roam's daily notes
functionality (see [[*Daily Notes][Daily Notes]]). This provides a central inbox for collecting functionality (see [[*Daily-notes][Daily-notes]]). This provides a central inbox for collecting
thoughts, to be processed later into permanent notes. thoughts, to be processed later into permanent notes.
Permanent notes are further split into two categories: *literature notes* and Permanent notes are further split into two categories: *literature notes* and
@ -993,11 +993,13 @@ This protocol finds or creates a new note with a given ~roam_key~ (see [[*Anatom
To use this, create the following [[https://en.wikipedia.org/wiki/Bookmarklet][bookmarklet]] in your browser: To use this, create the following [[https://en.wikipedia.org/wiki/Bookmarklet][bookmarklet]] in your browser:
#+BEGIN_SRC javascript #+BEGIN_SRC javascript
javascript:location.href = javascript:location.href =
'org-protocol://roam-ref?template=r&ref=' 'org-protocol://roam-ref?template=r&ref='
+ encodeURIComponent(location.href) + encodeURIComponent(location.href)
+ '&title=' + '&title='
+ encodeURIComponent(document.title) + encodeURIComponent(document.title)
+ '&body='
+ encodeURIComponent(window.getSelection())
#+END_SRC #+END_SRC
or as a keybinding in ~qutebrowser~ in , using the ~config.py~ file (see or as a keybinding in ~qutebrowser~ in , using the ~config.py~ file (see
@ -1011,7 +1013,122 @@ where ~template~ is the template key for a template in
~org-roam-capture-ref-templates~ (see [[*The Templating System][The Templating System]]). These templates ~org-roam-capture-ref-templates~ (see [[*The Templating System][The Templating System]]). These templates
should contain a ~#+roam_key: ${ref}~ in it. should contain a ~#+roam_key: ${ref}~ in it.
* TODO Daily Notes * Daily-notes
Org-roam provides journaling capabilities akin to
[[#org-journal][Org-journal]] with ~org-roam-dailies~.
** Configuration
For ~org-roam-dailies~ to work, you need to define two variables:
- Variable: ~org-roam-dailies-directory~
Path to daily-notes.
- Variable: ~org-roam-dailies-capture-templates~
Capture templates for daily-notes in Org-roam.
Here is a sane default configuration:
#+begin_src emacs-lisp
(setq org-roam-dailies-directory "daily/")
(setq org-roam-dailies-capture-templates
'(("d" "default" entry
#'org-roam-capture--get-point
"* %?"
:file-name "daily/%<%Y-%m-%d>"
:head "#+title: %<%Y-%m-%d>\n\n")))
#+end_src
Make sure that ~org-roam-dailies-directory~ appears in ~:file-name~ for your
notes to be recognized as daily-notes. You can have different templates
placing their notes in different directories, but the one in
~org-roam-dailies-directory~ will be considered as the main one in commands.
See [[*The Templating System][The Templating System]] for creating new
templates. ~org-roam-dailies~ provides an extra ~:olp~ option which allows
specifying the outline-path to a heading:
#+begin_src emacs-lisp
(setq org-roam-dailies-capture-templates
'(("l" "lab" entry
#'org-roam-capture--get-point
"* %?"
:file-name "daily/%<%Y-%m-%d>"
:head "#+title: %<%Y-%m-%d>\n\n* Lab notes\n* Journal"
:olp ("Journal"))
("j" "journal" entry
#'org-roam-capture--get-point
"* %?"
:file-name "daily/%<%Y-%m-%d>"
:head "#+title: %<%Y-%m-%d>\n\n* Lab notes\n* Journal"
:olp ("Lab notes"))))
#+end_src
The template ~l~ will put its notes under the heading Lab notes, and the
template ~j~ will put its notes under the heading Journal. When you use
~:olp~, make sure that the headings are present in ~:head~.
** Capturing and finding daily-notes
- Function: ~org-roam-dailies-capture-today~ &optional goto
Create an entry in the daily note for today.
When ~goto~ is non-nil, go the note without creating an entry.
- Function: ~org-roam-dailies-find-today~
Find the daily note for today, creating it if necessary.
There are variants of those commands for ~-yesterday~ and ~-tomorrow~:
- Function: ~org-roam-dailies-capture-yesterday~ n &optional goto
Create an entry in the daily note for yesteday.
With numeric argument ~n~, use the daily note ~n~ days in the past.
- Function: ~org-roam-dailies-find-yesterday~
With numeric argument N, use the daily-note N days in the future.
There are also commands which allow you to use Emacss ~calendar~ to find the date
- Function: ~org-roam-dailies-capture-date~
Create an entry in the daily note for a date using the calendar.
Prefer past dates, unless ~prefer-future~ is non-nil.
With a 'C-u' prefix or when ~goto~ is non-nil, go the note without
creating an entry.
- Function: ~org-roam-dailies-find-date~
Find the daily note for a date using the calendar, creating it if necessary.
Prefer past dates, unless ~prefer-future~ is non-nil.
** Navigation
You can navigate between daily-notes:
- Function: ~org-roam-dailies-find-directory~
Find and open ~org-roam-dailies-directory~.
- Function: ~org-roam-dailies-find-previous-note~
When in an daily-note, find the previous one.
- Function: ~org-roam-dailies-find-next-note~
When in an daily-note, find the next one.
* Diagnosing and Repairing Files * Diagnosing and Repairing Files
@ -1198,10 +1315,11 @@ that uses an external search engine and indexer.
:CUSTOM_ID: org-journal :CUSTOM_ID: org-journal
:END: :END:
[[https://github.com/bastibe/org-journal][Org-journal]] is a more [[https://github.com/bastibe/org-journal][Org-journal]] provides journaling
powerful alternative to the simple function ~org-roam-dailies-today~. It capabilities to Org-mode. A lot of its functionalities have been incorporated
provides better journaling capabilities, and a nice calendar interface into Org-roam under the name ~org-roam-dailies~. It remains a good tool if
to see all dated entries. you want to isolate your verbose journal entries from the ideas you would
write on a scratchpad.
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(use-package org-journal (use-package org-journal
@ -1210,7 +1328,7 @@ to see all dated entries.
:custom :custom
(org-journal-date-prefix "#+title: ") (org-journal-date-prefix "#+title: ")
(org-journal-file-format "%Y-%m-%d.org") (org-journal-file-format "%Y-%m-%d.org")
(org-journal-dir "/path/to/org-roam-files/") (org-journal-dir "/path/to/journal/files/")
(org-journal-date-format "%A, %d %B %Y")) (org-journal-date-format "%A, %d %B %Y"))
#+END_SRC #+END_SRC

View File

@ -78,7 +78,7 @@ General Public License for more details.
* Graphing:: * Graphing::
* Org-roam Completion System:: * Org-roam Completion System::
* Roam Protocol:: * Roam Protocol::
* Daily Notes:: * Daily-notes::
* Diagnosing and Repairing Files:: * Diagnosing and Repairing Files::
* Finding Unlinked References:: * Finding Unlinked References::
* Performance Optimization:: * Performance Optimization::
@ -129,6 +129,12 @@ Roam Protocol
* The roam-file protocol:: * The roam-file protocol::
* The roam-ref protocol:: * The roam-ref protocol::
Daily-notes
* Configuration::
* Capturing and finding daily-notes::
* Navigation::
Performance Optimization Performance Optimization
* Profiling Key Operations:: * Profiling Key Operations::
@ -260,7 +266,7 @@ A slip-box requires a method of quickly capturing ideas. These are called
@strong{fleeting notes}: they are simple reminders of information or ideas that will @strong{fleeting notes}: they are simple reminders of information or ideas that will
need to be processed later on, or trashed. This is typically accomplished using need to be processed later on, or trashed. This is typically accomplished using
@code{org-capture} (see @ref{capture,,,org,}), or using Org-roam's daily notes @code{org-capture} (see @ref{capture,,,org,}), or using Org-roam's daily notes
functionality (see @ref{Daily Notes}). This provides a central inbox for collecting functionality (see @ref{Daily-notes}). This provides a central inbox for collecting
thoughts, to be processed later into permanent notes. thoughts, to be processed later into permanent notes.
Permanent notes are further split into two categories: @strong{literature notes} and Permanent notes are further split into two categories: @strong{literature notes} and
@ -402,7 +408,7 @@ Where @code{/path/to/my/info/files} is the location where you keep info files. T
@lisp @lisp
(require 'info) (require 'info)
(add-to-list 'Info-default-directory-list (add-to-list 'Info-default-directory-list
"/path/to/my/info/files") "/path/to/my/info/files")
@end lisp @end lisp
You can also use one of the default locations, such as: You can also use one of the default locations, such as:
@ -1329,10 +1335,12 @@ To use this, create the following @uref{https://en.wikipedia.org/wiki/Bookmarkle
@example @example
javascript:location.href = javascript:location.href =
'org-protocol://roam-ref?template=r&ref=' 'org-protocol://roam-ref?template=r&ref='
+ encodeURIComponent(location.href) + encodeURIComponent(location.href)
+ '&title=' + '&title='
+ encodeURIComponent(document.title) + encodeURIComponent(document.title)
+ '&body='
+ encodeURIComponent(window.getSelection())
@end example @end example
or as a keybinding in @code{qutebrowser} in , using the @code{config.py} file (see or as a keybinding in @code{qutebrowser} in , using the @code{config.py} file (see
@ -1346,8 +1354,153 @@ where @code{template} is the template key for a template in
@code{org-roam-capture-ref-templates} (see @ref{The Templating System}). These templates @code{org-roam-capture-ref-templates} (see @ref{The Templating System}). These templates
should contain a @code{#+roam_key: $@{ref@}} in it. should contain a @code{#+roam_key: $@{ref@}} in it.
@node Daily Notes @node Daily-notes
@chapter @strong{TODO} Daily Notes @chapter Daily-notes
Org-roam provides journaling capabilities akin to
@ref{Org-journal} with @code{org-roam-dailies}.
@menu
* Configuration::
* Capturing and finding daily-notes::
* Navigation::
@end menu
@node Configuration
@section Configuration
For @code{org-roam-dailies} to work, you need to define two variables:
@itemize
@item
Variable: @code{org-roam-dailies-directory}
Path to daily-notes.
@item
Variable: @code{org-roam-dailies-capture-templates}
Capture templates for daily-notes in Org-roam.
@end itemize
Here is a sane default configuration:
@lisp
(setq org-roam-dailies-directory "daily/")
(setq org-roam-dailies-capture-templates
'(("d" "default" entry
#'org-roam-capture--get-point
"* %?"
:file-name "daily/%<%Y-%m-%d>"
:head "#+title: %<%Y-%m-%d>\n\n")))
@end lisp
Make sure that @code{org-roam-dailies-directory} appears in @code{:file-name} for your
notes to be recognized as daily-notes. You can have different templates
placing their notes in different directories, but the one in
@code{org-roam-dailies-directory} will be considered as the main one in commands.
See @ref{The Templating System} for creating new
templates. @code{org-roam-dailies} provides an extra @code{:olp} option which allows
specifying the outline-path to a heading:
@lisp
(setq org-roam-dailies-capture-templates
'(("l" "lab" entry
#'org-roam-capture--get-point
"* %?"
:file-name "daily/%<%Y-%m-%d>"
:head "#+title: %<%Y-%m-%d>\n\n* Lab notes\n* Journal"
:olp ("Journal"))
("j" "journal" entry
#'org-roam-capture--get-point
"* %?"
:file-name "daily/%<%Y-%m-%d>"
:head "#+title: %<%Y-%m-%d>\n\n* Lab notes\n* Journal"
:olp ("Lab notes"))))
@end lisp
The template @code{l} will put its notes under the heading Lab notes, and the
template @code{j} will put its notes under the heading Journal. When you use
@code{:olp}, make sure that the headings are present in @code{:head}.
@node Capturing and finding daily-notes
@section Capturing and finding daily-notes
@itemize
@item
Function: @code{org-roam-dailies-capture-today} &optional goto
Create an entry in the daily note for today.
When @code{goto} is non-nil, go the note without creating an entry.
@item
Function: @code{org-roam-dailies-find-today}
Find the daily note for today, creating it if necessary.
@end itemize
There are variants of those commands for @code{-yesterday} and @code{-tomorrow}:
@itemize
@item
Function: @code{org-roam-dailies-capture-yesterday} n &optional goto
Create an entry in the daily note for yesteday.
With numeric argument @code{n}, use the daily note @code{n} days in the past.
@item
Function: @code{org-roam-dailies-find-yesterday}
With numeric argument N, use the daily-note N days in the future.
@end itemize
There are also commands which allow you to use Emacss @code{calendar} to find the date
@itemize
@item
Function: @code{org-roam-dailies-capture-date}
Create an entry in the daily note for a date using the calendar.
Prefer past dates, unless @code{prefer-future} is non-nil.
With a 'C-u' prefix or when @code{goto} is non-nil, go the note without
creating an entry.
@item
Function: @code{org-roam-dailies-find-date}
Find the daily note for a date using the calendar, creating it if necessary.
Prefer past dates, unless @code{prefer-future} is non-nil.
@end itemize
@node Navigation
@section Navigation
You can navigate between daily-notes:
@itemize
@item
Function: @code{org-roam-dailies-find-directory}
Find and open @code{org-roam-dailies-directory}.
@item
Function: @code{org-roam-dailies-find-previous-note}
When in an daily-note, find the previous one.
@item
Function: @code{org-roam-dailies-find-next-note}
When in an daily-note, find the next one.
@end itemize
@node Diagnosing and Repairing Files @node Diagnosing and Repairing Files
@chapter Diagnosing and Repairing Files @chapter Diagnosing and Repairing Files
@ -1567,10 +1720,11 @@ that uses an external search engine and indexer.
@node Org-journal @node Org-journal
@subsection Org-journal @subsection Org-journal
@uref{https://github.com/bastibe/org-journal, Org-journal} is a more @uref{https://github.com/bastibe/org-journal, Org-journal} provides journaling
powerful alternative to the simple function @code{org-roam-dailies-today}. It capabilities to Org-mode. A lot of its functionalities have been incorporated
provides better journaling capabilities, and a nice calendar interface into Org-roam under the name @code{org-roam-dailies}. It remains a good tool if
to see all dated entries. you want to isolate your verbose journal entries from the ideas you would
write on a scratchpad.
@lisp @lisp
(use-package org-journal (use-package org-journal
@ -1579,7 +1733,7 @@ to see all dated entries.
:custom :custom
(org-journal-date-prefix "#+title: ") (org-journal-date-prefix "#+title: ")
(org-journal-file-format "%Y-%m-%d.org") (org-journal-file-format "%Y-%m-%d.org")
(org-journal-dir "/path/to/org-roam-files/") (org-journal-dir "/path/to/journal/files/")
(org-journal-date-format "%A, %d %B %Y")) (org-journal-date-format "%A, %d %B %Y"))
@end lisp @end lisp
@ -1708,5 +1862,5 @@ call @code{ivy-immediate-done}, typically bound to @code{C-M-j}. Alternatively,
Org-roam should provide a selectable ``[?] bar'' candidate at the top of the candidate list. Org-roam should provide a selectable ``[?] bar'' candidate at the top of the candidate list.
@end table @end table
Emacs 28.0.50 (Org mode 9.4) Emacs 27.1.50 (Org mode 9.4)
@bye @bye

View File

@ -41,6 +41,9 @@
(defvar org-roam-directory) (defvar org-roam-directory)
(defvar org-roam-mode) (defvar org-roam-mode)
(defvar org-roam-title-to-slug-function) (defvar org-roam-title-to-slug-function)
(defvar org-roam-dailies-directory)
(defvar org-roam-dailies-capture--file-name-default)
(defvar org-roam-dailies-capture--header-default)
(declare-function org-roam--get-title-path-completions "org-roam") (declare-function org-roam--get-title-path-completions "org-roam")
(declare-function org-roam--get-ref-path-completions "org-roam") (declare-function org-roam--get-ref-path-completions "org-roam")
(declare-function org-roam--file-path-from-id "org-roam") (declare-function org-roam--file-path-from-id "org-roam")
@ -49,6 +52,12 @@
(declare-function org-roam-mode "org-roam") (declare-function org-roam-mode "org-roam")
(declare-function org-roam-completion--completing-read "org-roam-completion") (declare-function org-roam-completion--completing-read "org-roam-completion")
(defvar org-roam-capture--file-name-default "%<%Y%m%d%H%M%S>-${slug}"
"The default file-name format for Org-roam templates.")
(defvar org-roam-capture--header-default "#+title: ${title}\n"
"The default header for Org-roam templates.")
(defvar org-roam-capture--file-path nil (defvar org-roam-capture--file-path nil
"The file path for the Org-roam capture. "The file path for the Org-roam capture.
This variable is set during the Org-roam capture process.") This variable is set during the Org-roam capture process.")
@ -74,14 +83,14 @@ note with the given `ref'.")
(defvar org-roam-capture-additional-template-props nil (defvar org-roam-capture-additional-template-props nil
"Additional props to be added to the Org-roam template.") "Additional props to be added to the Org-roam template.")
(defconst org-roam-capture--template-keywords '(:file-name :head) (defconst org-roam-capture--template-keywords '(:file-name :head :olp)
"Keywords used in `org-roam-capture-templates' specific to Org-roam.") "Keywords used in `org-roam-capture-templates' specific to Org-roam.")
(defcustom org-roam-capture-templates (defcustom org-roam-capture-templates
'(("d" "default" plain (function org-roam-capture--get-point) `(("d" "default" plain (function org-roam-capture--get-point)
"%?" "%?"
:file-name "%<%Y%m%d%H%M%S>-${slug}" :file-name ,org-roam-capture--file-name-default
:head "#+title: ${title}\n" :head ,org-roam-capture--header-default
:unnarrowed t)) :unnarrowed t))
"Capture templates for Org-roam. "Capture templates for Org-roam.
The Org-roam capture-templates builds on the default behaviours of The Org-roam capture-templates builds on the default behaviours of
@ -217,12 +226,18 @@ Template string :\n%v")
((const :format "%v " :table-line-pos) (string)) ((const :format "%v " :table-line-pos) (string))
((const :format "%v " :kill-buffer) (const t)))))) ((const :format "%v " :kill-buffer) (const t))))))
(defvar org-roam-capture-ref--file-name-default "${slug}"
"The default file-name for `org-roam-capture-ref-templates'.")
(defvar org-roam-capture-ref--header-default "#+title: ${title}\n#+roam_key: ${ref}"
"The default header for `org-roam-capture-ref-templates'.")
(defcustom org-roam-capture-ref-templates (defcustom org-roam-capture-ref-templates
'(("r" "ref" plain (function org-roam-capture--get-point) `(("r" "ref" plain (function org-roam-capture--get-point)
"%?" "%?"
:file-name "${slug}" :file-name ,org-roam-capture-ref--file-name-default
:head "#+title: ${title}\n#+roam_key: ${ref}\n" :head ,org-roam-capture--header-default
:unnarrowed t)) :unnarrowed t))
"The Org-roam templates used during a capture from the roam-ref protocol. "The Org-roam templates used during a capture from the roam-ref protocol.
Details on how to specify for the template is given in `org-roam-capture-templates'." Details on how to specify for the template is given in `org-roam-capture-templates'."
:group 'org-roam :group 'org-roam
@ -387,6 +402,18 @@ The file is saved if the original value of :no-save is not t and
(with-current-buffer (org-capture-get :buffer) (with-current-buffer (org-capture-get :buffer)
(save-buffer))))) (save-buffer)))))
(defun org-roam-capture--expand-file-name (file-name)
"Expand FILE-NAME for `org-roam-capture'.
Prepend `org-roam-dailies-directory' to the value of `:file' when
capturing a daily-note."
(when file-name
(pcase org-roam-capture--context
('dailies
(concat org-roam-dailies-directory file-name))
(_
file-name))))
(defun org-roam-capture--new-file () (defun org-roam-capture--new-file ()
"Return the path to the new file during an Org-roam capture. "Return the path to the new file during an Org-roam capture.
@ -408,26 +435,52 @@ aborted, we do the following:
3. Add a function on `org-capture-before-finalize-hook' that saves 3. Add a function on `org-capture-before-finalize-hook' that saves
the file if the original value of :no-save is not t and the file if the original value of :no-save is not t and
`org-note-abort' is not t." `org-note-abort' is not t."
(let* ((name-templ (org-roam-capture--get :file-name)) (let* ((name-templ (or (org-roam-capture--get :file-name)
(pcase org-roam-capture--context
('dailies
(or (-some-> org-roam-dailies-directory
(file-name-as-directory)
(concat org-roam-dailies-capture--file-name-default))
(user-error "`org-roam-dailies-directory' cannot be nil")))
('ref
org-roam-capture-ref--file-name-default)
(_
org-roam-capture--file-name-default))))
(new-id (s-trim (org-roam-capture--fill-template (new-id (s-trim (org-roam-capture--fill-template
name-templ))) name-templ)))
(file-path (org-roam--file-path-from-id new-id)) (file-path (org-roam--file-path-from-id new-id))
(roam-head (org-roam-capture--get :head)) (roam-head (or (org-roam-capture--get :head)
(pcase org-roam-capture--context
('dailies
org-roam-dailies-capture--header-default)
('ref
org-roam-capture-ref--header-default)
(_
org-roam-capture--header-default))))
(org-template (org-capture-get :template)) (org-template (org-capture-get :template))
(roam-template (concat roam-head org-template))) (roam-template (concat roam-head org-template)))
(unless (file-exists-p file-path) (unless (or (file-exists-p file-path)
(cl-some (lambda (buffer)
(string= (buffer-file-name buffer)
file-path))
(buffer-list)))
(make-directory (file-name-directory file-path) t) (make-directory (file-name-directory file-path) t)
(org-roam-capture--put :orig-no-save (org-capture-get :no-save) (org-roam-capture--put :orig-no-save (org-capture-get :no-save)
:new-file t) :new-file t)
(org-capture-put :template (pcase org-roam-capture--context
;; Fixes org-capture-place-plain-text throwing 'invalid search bound' ('dailies
;; when both :unnarowed t and "%?" is missing from the template string; ;; Populate the header of the daily file before capture to prevent it
;; may become unnecessary when the upstream bug is fixed ;; from appearing in the buffer-restriction
(if (s-contains-p "%?" roam-template) (save-window-excursion
roam-template (find-file file-path)
(concat roam-template "%?")) (insert (substring (org-capture-fill-template (concat roam-head "*"))
:type 'plain 0 -2))
:no-save t)) (set-buffer-modified-p nil))
(org-capture-put :template org-template))
(_
(org-capture-put :template roam-template
:type 'plain)))
(org-capture-put :no-save t))
file-path)) file-path))
(defun org-roam-capture--get-point () (defun org-roam-capture--get-point ()
@ -446,32 +499,53 @@ If there is no file with that ref, a file with that ref is created.
This function is used solely in Org-roam's capture templates: see This function is used solely in Org-roam's capture templates: see
`org-roam-capture-templates'." `org-roam-capture-templates'."
(let ((file-path (pcase org-roam-capture--context (let* ((file-path (pcase org-roam-capture--context
('capture ('capture
(or (cdr (assoc 'file org-roam-capture--info)) (or (cdr (assoc 'file org-roam-capture--info))
(org-roam-capture--new-file))) (org-roam-capture--new-file)))
('title ('title
(org-roam-capture--new-file)) (org-roam-capture--new-file))
('dailies ('dailies
(org-capture-put :default-time (cdr (assoc 'time org-roam-capture--info))) (org-capture-put :default-time (cdr (assoc 'time org-roam-capture--info)))
(org-roam-capture--new-file)) (org-roam-capture--new-file))
('ref ('ref
(let ((completions (org-roam--get-ref-path-completions)) (let ((completions (org-roam--get-ref-path-completions))
(ref (cdr (assoc 'ref org-roam-capture--info)))) (ref (cdr (assoc 'ref org-roam-capture--info))))
(if-let ((pl (cdr (assoc ref completions)))) (if-let ((pl (cdr (assoc ref completions))))
(plist-get pl :path) (plist-get pl :path)
(org-roam-capture--new-file)))) (org-roam-capture--new-file))))
(_ (error "Invalid org-roam-capture-context"))))) (_ (error "Invalid org-roam-capture-context")))))
(org-capture-put :template (org-capture-put :template
(org-roam-capture--fill-template (org-capture-get :template))) (org-roam-capture--fill-template (org-capture-get :template)))
(org-roam-capture--put :file-path file-path) (org-roam-capture--put :file-path file-path
:finalize (or (org-capture-get :finalize)
(org-roam-capture--get :finalize)))
(while org-roam-capture-additional-template-props (while org-roam-capture-additional-template-props
(let ((prop (pop org-roam-capture-additional-template-props)) (let ((prop (pop org-roam-capture-additional-template-props))
(val (pop org-roam-capture-additional-template-props))) (val (pop org-roam-capture-additional-template-props)))
(org-roam-capture--put prop val))) (org-roam-capture--put prop val)))
(set-buffer (org-capture-target-buffer file-path)) (set-buffer (org-capture-target-buffer file-path))
(widen) (widen)
(goto-char (point-max)))) (if-let* ((olp (when (eq org-roam-capture--context 'dailies)
(--> (org-roam-capture--get :olp)
(pcase it
((pred stringp)
(list it))
((pred listp)
it)
(wrong-type
(signal 'wrong-type-argument
`((stringp listp)
,wrong-type))))))))
(condition-case err
(when-let ((marker (org-find-olp `(,file-path ,@olp))))
(goto-char marker)
(set-marker marker nil))
(error
(when (org-roam-capture--get :new-file)
(kill-buffer))
(signal (car err) (cdr err))))
(goto-char (point-min)))))
(defun org-roam-capture--convert-template (template) (defun org-roam-capture--convert-template (template)
"Convert TEMPLATE from Org-roam syntax to `org-capture-templates' syntax." "Convert TEMPLATE from Org-roam syntax to `org-capture-templates' syntax."

View File

@ -1,8 +1,10 @@
;;; org-roam-dailies.el --- Daily notes for Org-roam -*- coding: utf-8; lexical-binding: t; -*- ;;; org-roam-dailies.el --- Daily-notes for Org-roam -*- coding: utf-8; lexical-binding: t; -*-
;;; ;;;
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com> ;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020 Leo Vivier <leo.vivier+dev@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; Leo Vivier <leo.vivier+dev@gmail.com>
;; URL: https://github.com/org-roam/org-roam ;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience ;; Keywords: org-mode, roam, convenience
;; Version: 1.2.2 ;; Version: 1.2.2
@ -27,7 +29,7 @@
;;; Commentary: ;;; Commentary:
;; ;;
;; This library provides functionality for creating daily notes. This is a ;; This library provides functionality for creating daily-notes. This is a
;; concept borrowed from Roam Research. ;; concept borrowed from Roam Research.
;; ;;
;;; Code: ;;; Code:
@ -35,102 +37,336 @@
(require 'org-capture) (require 'org-capture)
(require 'org-roam-capture) (require 'org-roam-capture)
(require 'org-roam-macs) (require 'org-roam-macs)
(require 'f)
;;;; Declarations
(defvar org-roam-mode)
(defvar org-roam-directory)
(declare-function org-roam--org-file-p "org-roam")
(declare-function org-roam--file-path-from-id "org-roam")
(declare-function org-roam--find-file "org-roam")
(declare-function org-roam-mode "org-roam")
(defvar org-roam-dailies-capture--file-name-default "%<%Y-%m-%d>"
"The default file-name for `org-roam-dailies-capture-templates'.")
(defvar org-roam-dailies-capture--header-default "#+title: %<%Y-%m-%d>\n"
"The default header for `org-roam-dailies-capture-templates'.")
;;;; Customizable variables
(defcustom org-roam-dailies-directory "daily/"
"Path to daily-notes."
:group 'org-roam
:type 'string)
(defcustom org-roam-dailies-find-file-hook nil
"Hook that is run right after navigating to a daily-note."
:group 'org-roam
:type 'hook)
(defcustom org-roam-dailies-capture-templates (defcustom org-roam-dailies-capture-templates
'(("d" "daily" plain (function org-roam-capture--get-point) '(("d" "default" entry (function org-roam-capture--get-point)
"" "* %?"
:immediate-finish t :file-name "daily/%<%Y-%m-%d>"
:file-name "%<%Y-%m-%d>" :head "#+title: %<%Y-%m-%d>\n"))
:head "#+title: %<%Y-%m-%d>")) "Capture templates for daily-notes in Org-roam."
"Capture templates for daily notes in Org-roam."
:group 'org-roam :group 'org-roam
;; Adapted from `org-capture-templates' ;; Adapted from `org-capture-templates'
:type :type
'(repeat '(repeat
(choice :value ("d" "daily" plain (function org-roam-capture--get-point) (choice :value ("d" "default" plain (function org-roam-capture--get-point)
"" "%?"
:immediate-finish t :file-name "daily/%<%Y-%m-%d>"
:file-name "%<%Y-%m-%d>" :head "#+title: %<%Y-%m-%d>\n"
:head "#+title: %<%Y-%m-%d>") :unnarrowed t)
(list :tag "Multikey description" (list :tag "Multikey description"
(string :tag "Keys ") (string :tag "Keys ")
(string :tag "Description")) (string :tag "Description"))
(list :tag "Template entry" (list :tag "Template entry"
(string :tag "Keys ") (string :tag "Keys ")
(string :tag "Description ") (string :tag "Description ")
(const :format "" plain) (choice :tag "Type "
(const :format "" (function org-roam-capture--get-point)) (const :tag "Plain" plain)
(choice :tag "Template " (const :tag "Entry (for creating headlines)" entry))
(string :tag "String" (const :format "" #'org-roam-capture--get-point)
:format "String:\n \ (choice :tag "Template "
(string :tag "String"
:format "String:\n \
Template string :\n%v") Template string :\n%v")
(list :tag "File" (list :tag "File"
(const :format "" file) (const :format "" file)
(file :tag "Template file ")) (file :tag "Template file "))
(list :tag "Function" (list :tag "Function"
(const :format "" function) (const :format "" function)
(function :tag "Template function "))) (function :tag "Template function ")))
(const :format "" :immediate-finish) (const :format "" t) (const :format "File name format :" :file-name)
(const :format "File name format :" :file-name) (string :format " %v" :value "daily/%<%Y-%m-%d>")
(string :format " %v" :value "#+title: ${title}\n") (const :format "Header format :" :head)
(const :format "Header format :" :head) (string :format " %v" :value "#+title: ${title}\n")
(string :format "\n%v" :value "%<%Y%m%d%H%M%S>-${slug}") (plist :inline t
(plist :inline t :tag "Options"
:tag "Options" ;; Give the most common options as checkboxes
;; Give the most common options as checkboxes :options
:options (((const :tag "Outline path" :olp)
(((const :format "%v " :prepend) (const t)) (repeat :tag "Headings"
((const :format "%v " :jump-to-captured) (const t)) (string :tag "Heading")))
((const :format "%v " :empty-lines) (const 1)) ((const :format "%v " :unnarrowed) (const t))
((const :format "%v " :empty-lines-before) (const 1)) ((const :format "%v " :prepend) (const t))
((const :format "%v " :empty-lines-after) (const 1)) ((const :format "%v " :immediate-finish) (const t))
((const :format "%v " :clock-in) (const t)) ((const :format "%v " :jump-to-captured) (const t))
((const :format "%v " :clock-keep) (const t)) ((const :format "%v " :empty-lines) (const 1))
((const :format "%v " :clock-resume) (const t)) ((const :format "%v " :empty-lines-before) (const 1))
((const :format "%v " :time-prompt) (const t)) ((const :format "%v " :empty-lines-after) (const 1))
((const :format "%v " :tree-type) (const week)) ((const :format "%v " :clock-in) (const t))
((const :format "%v " :table-line-pos) (string)) ((const :format "%v " :clock-keep) (const t))
((const :format "%v " :kill-buffer) (const t)) ((const :format "%v " :clock-resume) (const t))
((const :format "%v " :unnarrowed) (const t)))))))) ((const :format "%v " :time-prompt) (const t))
((const :format "%v " :tree-type) (const week))
((const :format "%v " :table-line-pos) (string))
((const :format "%v " :kill-buffer) (const t))))))))
;; Declarations ;;;; Utilities
(defvar org-roam-mode) (defun org-roam-dailies-directory--get-absolute-path ()
(declare-function org-roam--file-path-from-id "org-roam") "Get absolute path to `org-roam-dailies-directory'."
(declare-function org-roam-mode "org-roam") (-> (concat
(file-name-as-directory org-roam-directory)
org-roam-dailies-directory)
(file-truename)))
(defun org-roam-dailies--file-for-time (time) (defun org-roam-dailies-find-directory ()
"Create and find file for TIME." "Find and open `org-roam-dailies-directory'."
(let ((org-roam-capture-templates org-roam-dailies-capture-templates) (interactive)
(org-roam--find-file (org-roam-dailies-directory--get-absolute-path)))
(defun org-roam-dailies--daily-note-p (&optional file)
"Return t if FILE is an Org-roam daily-note, nil otherwise.
If FILE is not specified, use the current buffer's file-path."
(when-let ((path (or file
(-> (buffer-base-buffer)
(buffer-file-name))))
(directory (org-roam-dailies-directory--get-absolute-path)))
(setq path (file-truename path))
(save-match-data
(and
(org-roam--org-file-p path)
(f-descendant-of-p path directory)))))
(defun org-roam-dailies--capture (time &optional goto)
"Capture an entry in a daily-note for TIME, creating it if necessary.
When GOTO is non-nil, go the note without creating an entry."
(unless org-roam-mode (org-roam-mode))
(let ((org-roam-capture-templates (--> org-roam-dailies-capture-templates
(if goto (list (car it)) it)))
(org-roam-capture--info (list (cons 'time time))) (org-roam-capture--info (list (cons 'time time)))
(org-roam-capture--context 'dailies)) (org-roam-capture--context 'dailies))
(setq org-roam-capture-additional-template-props (list :finalize 'find-file)) (setq org-roam-capture-additional-template-props (list :finalize 'find-file))
(org-roam-capture--capture))) (org-roam-capture--capture (when goto '(4)))))
(defun org-roam-dailies-today () ;;;; Commands
"Create and find the daily note for today." ;;; Today
(defun org-roam-dailies-capture-today (&optional goto)
"Create an entry in the daily-note for today.
When GOTO is non-nil, go the note without creating an entry."
(interactive "P")
(org-roam-dailies--capture (current-time) goto)
(when goto
(run-hooks 'org-roam-dailies-find-file-hook)
(message "Showing daily-note for today")))
(defun org-roam-dailies-find-today ()
"Find the daily-note for today, creating it if necessary."
(interactive) (interactive)
(unless org-roam-mode (org-roam-mode)) (org-roam-dailies-capture-today t))
(org-roam-dailies--file-for-time (current-time)))
(defun org-roam-dailies-tomorrow (n) ;;; Tomorrow
"Create and find the daily note for tomorrow. (defun org-roam-dailies-capture-tomorrow (n &optional goto)
With numeric argument N, use N days in the future." "Create an entry in the daily-note for tomorrow.
With numeric argument N, use the daily-note N days in the future.
With a `C-u' prefix or when GOTO is non-nil, go the note without
creating an entry."
(interactive "p") (interactive "p")
(unless org-roam-mode (org-roam-mode)) (org-roam-dailies--capture (time-add (* n 86400) (current-time)) goto))
(org-roam-dailies--file-for-time (time-add (* n 86400) (current-time))))
(defun org-roam-dailies-yesterday (n) (defun org-roam-dailies-find-tomorrow (n)
"Create and find the file for yesterday. "Find the daily-note for tomorrow, creating it if necessary.
With numeric argument N, use N days in the past."
With numeric argument N, use the daily-note N days in the
future."
(interactive "p") (interactive "p")
(unless org-roam-mode (org-roam-mode)) (org-roam-dailies-capture-tomorrow n t))
(org-roam-dailies-tomorrow (- n)))
(defun org-roam-dailies-date () ;;; Yesterday
"Create the file for any date using the calendar interface." (defun org-roam-dailies-capture-yesterday (n &optional goto)
"Create an entry in the daily-note for yesteday.
With numeric argument N, use the daily-note N days in the past.
When GOTO is non-nil, go the note without creating an entry."
(interactive "p")
(org-roam-dailies-capture-tomorrow (- n) goto))
(defun org-roam-dailies-find-yesterday (n)
"Find the daily-note for yesterday, creating it if necessary.
With numeric argument N, use the daily-note N days in the
future."
(interactive "p")
(org-roam-dailies-capture-tomorrow (- n) t))
;;; Calendar
(defvar org-roam-dailies-calendar-hook (list 'org-roam-dailies-calendar-mark-entries)
"Hooks to run when showing the `org-roam-dailies-calendar'.")
(defun org-roam-dailies-calendar--install-hook ()
"Install Org-roam-dailies hooks to calendar."
(add-hook 'calendar-today-visible-hook #'org-roam-dailies-calendar--run-hook)
(add-hook 'calendar-today-invisible-hook #'org-roam-dailies-calendar--run-hook))
(defun org-roam-dailies-calendar--run-hook ()
"Run Org-roam-dailies hooks to calendar."
(run-hooks 'org-roam-dailies-calendar-hook)
(remove-hook 'calendar-today-visible-hook #'org-roam-dailies-calendar--run-hook)
(remove-hook 'calendar-today-invisible-hook #'org-roam-dailies-calendar--run-hook))
(defun org-roam-dailies-calendar--file-to-date (&optional file)
"Convert FILE to date.
Return (MONTH DAY YEAR)."
(let ((file (or file
(-> (buffer-base-buffer)
(buffer-file-name)))))
(cl-destructuring-bind (_ _ _ d m y _ _ _)
(-> file
(file-name-nondirectory)
(file-name-sans-extension)
(org-parse-time-string))
(list m d y))))
(defun org-roam-dailies-calendar--date-to-time (date)
"Convert DATE as returned from the calendar (MONTH DAY YEAR) to a time."
(encode-time 0 0 0 (nth 1 date) (nth 0 date) (nth 2 date)))
(defun org-roam-dailies-calendar-mark-entries ()
"Mark days in the calendar for which a daily-note is present."
(when (file-exists-p (org-roam-dailies-directory--get-absolute-path))
(dolist (date (mapcar #'org-roam-dailies-calendar--file-to-date
(org-roam-dailies--list-files)))
(when (calendar-date-is-visible-p date)
(calendar-mark-visible-date date 'org-roam-dailies-calendar-note)))))
;;; Date
(defun org-roam-dailies-capture-date (&optional goto prefer-future)
"Create an entry in the daily-note for a date using the calendar.
Prefer past dates, unless PREFER-FUTURE is non-nil.
With a `C-u' prefix or when GOTO is non-nil, go the note without
creating an entry."
(interactive "P")
(org-roam-dailies-calendar--install-hook)
(let* ((time-str (let ((org-read-date-prefer-future prefer-future))
(org-read-date nil nil nil (if goto
"Find daily-note: "
"Capture to daily-note: "))))
(time (org-read-date nil t time-str)))
(org-roam-dailies--capture time goto)
(when goto
(run-hooks 'org-roam-dailies-find-file-hook)
(message "Showing note for %s" time-str))))
(defun org-roam-dailies-find-date (prefer-future)
"Find the daily-note for a date using the calendar, creating it if necessary.
Prefer past dates, unless PREFER-FUTURE is non-nil."
(interactive) (interactive)
(let ((time (org-read-date nil 'to-time nil "Date: "))) (org-roam-dailies-capture-date t prefer-future))
(org-roam-dailies--file-for-time time)))
;;; Navigation
(defun org-roam-dailies--list-files (&rest extra-files)
"List all files in `org-roam-dailies-directory'.
EXTRA-FILES can be used to append extra files to the list."
(let ((dir (org-roam-dailies-directory--get-absolute-path)))
(append (--remove (let ((file (file-name-nondirectory it)))
(when (or (auto-save-file-name-p file)
(backup-file-name-p file)
(string-match "^\\." file))
it))
(directory-files-recursively dir ""))
extra-files)))
(defun org-roam-dailies--find-next-note-path (&optional n file)
"Find next daily-note from FILE.
With numeric argument N, find note N days in the future. If N is
negative, find note N days in the past.
If FILE is not provided, use the file visited by the current
buffer."
(unless (org-roam-dailies--daily-note-p file)
(user-error "Not in a daily-note"))
(let ((n (or n 1))
(file (or file
(-> (buffer-base-buffer)
(buffer-file-name)))))
;; Ensure that the buffer is saved before moving
(save-buffer file)
(let* ((list (org-roam-dailies--list-files))
(position
(cl-position-if (lambda (candidate)
(string= file candidate))
list)))
(pcase n
((pred (natnump))
(if (eq position (- (length list) 1))
(user-error "Already at newest note")
(message "Showing next daily-note")))
((pred (integerp))
(if (eq position 0)
(user-error "Already at oldest note")
(message "Showing previous daily-note"))))
(nth (+ position n) list))))
(defun org-roam-dailies-find-next-note (&optional n)
"Find next daily-note.
With numeric argument N, find note N days in the future. If N is
negative, find note N days in the past."
(interactive "p")
(let* ((n (or n 1))
(next (org-roam-dailies--find-next-note-path n)))
(find-file next)
(run-hooks 'org-roam-dailies-find-file-hook)))
(defun org-roam-dailies-find-previous-note (&optional n)
"Find previous daily-note.
With numeric argument N, find note N days in the past. If N is
negative, find note N days in the future."
(interactive "p")
(let ((n (if n (- n) -1)))
(org-roam-dailies-find-next-note n)))
;;;; Bindings
(defvar org-roam-dailies-map (make-sparse-keymap)
"Keymap for `org-roam-dailies'.")
(define-prefix-command 'org-roam-dailies-map)
(define-key org-roam-dailies-map (kbd "d") #'org-roam-dailies-find-today)
(define-key org-roam-dailies-map (kbd "y") #'org-roam-dailies-find-yesterday)
(define-key org-roam-dailies-map (kbd "t") #'org-roam-dailies-find-tomorrow)
(define-key org-roam-dailies-map (kbd "n") #'org-roam-dailies-capture-today)
(define-key org-roam-dailies-map (kbd "f") #'org-roam-dailies-find-next-note)
(define-key org-roam-dailies-map (kbd "b") #'org-roam-dailies-find-previous-note)
(define-key org-roam-dailies-map (kbd "c") #'org-roam-dailies-find-date)
(define-key org-roam-dailies-map (kbd "v") #'org-roam-dailies-capture-date)
(define-key org-roam-dailies-map (kbd ".") #'org-roam-dailies-find-directory)
(provide 'org-roam-dailies) (provide 'org-roam-dailies)

View File

@ -65,6 +65,11 @@ This face is used on the region target by `org-roam-insertion'
during an `org-roam-capture'." during an `org-roam-capture'."
:group 'org-roam-faces) :group 'org-roam-faces)
(defface org-roam-dailies-calendar-note
'((t :inherit (org-roam-link) :underline nil))
"Face for dates with a daily-note in the calendar"
:group 'org-roam-faces)
;;; _ ;;; _
(provide 'org-roam-faces) (provide 'org-roam-faces)

View File

@ -374,13 +374,14 @@ Like `file-name-extension', but does not strip version number."
If FILE is not specified, use the current buffer's file-path." If FILE is not specified, use the current buffer's file-path."
(when-let ((path (or file (when-let ((path (or file
org-roam-file-name org-roam-file-name
(buffer-file-name)))) (-> (buffer-base-buffer)
(save-match-data (buffer-file-name)))))
(and (save-match-data
(org-roam--org-file-p path) (and
(not (and org-roam-file-exclude-regexp (org-roam--org-file-p path)
(string-match-p org-roam-file-exclude-regexp path))) (not (and org-roam-file-exclude-regexp
(f-descendant-of-p path (expand-file-name org-roam-directory)))))) (string-match-p org-roam-file-exclude-regexp path)))
(f-descendant-of-p path (expand-file-name org-roam-directory))))))
(defun org-roam--shell-command-files (cmd) (defun org-roam--shell-command-files (cmd)
"Run CMD in the shell and return a list of files. If no files are found, an empty list is returned." "Run CMD in the shell and return a list of files. If no files are found, an empty list is returned."