diff options
| author | Szymon Szukalski <szymon@szymonszukalski.com> | 2026-04-08 11:32:34 +1000 |
|---|---|---|
| committer | Szymon Szukalski <szymon@szymonszukalski.com> | 2026-04-08 11:32:34 +1000 |
| commit | 93d1f5ae8ab112e0ea14399894040378a812da0f (patch) | |
| tree | 921b71daf0308e402de38b0e22c26d90b4015344 /config.org | |
| parent | e4c9cb20a4419d19f8bbeca35a66fc8388cf430a (diff) | |
feat: switch capture to journal datetree
Diffstat (limited to 'config.org')
| -rw-r--r-- | config.org | 155 |
1 files changed, 113 insertions, 42 deletions
@@ -759,19 +759,19 @@ short and phrase-like so summaries and completion annotations stay readable. * Notes workflow -The note-taking system remains deliberately small. Daily notes stay as plain -Org files in =~/org/daily/=, while longer-lived notes use Denote inside the +The note-taking system remains deliberately small. Fast operational capture +goes into =~/org/journal.org=, while longer-lived notes use Denote inside the same root directory and rely on links for relationships. ** Org foundations The Org setup establishes the shared directories used by the workflow and provides helpers that open existing notes at point of use. Agenda views stay -focused on PARA notes, so project, area, and resource files can surface TODOs -without pulling in daily or archived notes. A curated =moc.org= in the Org root -acts as the startup landing page and quick navigation surface. The config -assumes that file already exists and opens it directly during startup rather -than creating it on demand. +focused on the journal plus PARA notes, so quick operational tasks and durable +project, area, and resource files can surface TODOs without pulling in archived +notes. A curated =moc.org= in the Org root acts as the startup landing page and +quick navigation surface. The config assumes that file already exists and opens +it directly during startup rather than creating it on demand. #+begin_src emacs-lisp (use-package org @@ -782,8 +782,8 @@ than creating it on demand. (defconst ss/org-directory (expand-file-name "~/org/") "Root directory for Org files.") - (defconst ss/org-daily-directory (expand-file-name "daily/" ss/org-directory) - "Directory for plain daily Org notes.") + (defconst ss/journal-file (expand-file-name "journal.org" ss/org-directory) + "Single-file work journal for operational capture.") (defconst ss/org-projects-directory (expand-file-name "projects/" ss/org-directory) "Directory for project notes.") @@ -800,6 +800,10 @@ than creating it on demand. (defconst ss/moc-file (expand-file-name "moc.org" ss/org-directory) "Central MOC note.") + (defconst ss/journal-section-headings + '("Tasks" "Notes" "Meetings") + "Per-day section headings maintained under each journal datetree entry.") + (defconst ss/org-agenda-directories (list ss/org-projects-directory ss/org-areas-directory @@ -838,33 +842,100 @@ than creating it on demand. (memq :template prompts)) (denote-org-capture)))) + (defun ss/journal-capture-time () + "Return the effective timestamp for the current journal capture." + (or org-overriding-default-time + (org-capture-get :default-time) + (current-time))) + + (defun ss/journal-calendar-date (&optional time) + "Return TIME as a Gregorian date list for datetree helpers." + (calendar-gregorian-from-absolute + (time-to-days (or time (current-time))))) + + (defun ss/journal-goto-date (&optional time create) + "Move to TIME's journal date heading. +When CREATE is non-nil, create the datetree entry when missing." + (goto-char (point-min)) + (if create + (progn + (require 'org-datetree) + (org-datetree-find-date-create (ss/journal-calendar-date time)) + (org-back-to-heading t) + t) + (when (re-search-forward + (format "^\\*+ %s\\b" + (format-time-string "%Y-%m-%d" (or time (current-time)))) + nil t) + (goto-char (match-beginning 0)) + t))) + + (defun ss/journal-ensure-day-sections () + "Ensure the standard section headings exist under the current journal day." + (org-back-to-heading t) + (let ((section-level (1+ (org-outline-level)))) + (save-restriction + (org-narrow-to-subtree) + (dolist (section ss/journal-section-headings) + (goto-char (point-min)) + (unless (org-find-exact-headline-in-buffer section) + (goto-char (point-max)) + (unless (bolp) + (insert "\n")) + (insert (make-string section-level ?*) " " section "\n")))))) + + (defun ss/journal-goto-section (section &optional time) + "Move to SECTION beneath TIME's journal date, creating structure as needed." + (unless (member section ss/journal-section-headings) + (user-error "Unknown journal section: %s" section)) + (ss/journal-goto-date time 'create) + (ss/journal-ensure-day-sections) + (let ((section-level (1+ (org-outline-level))) + position) + (save-restriction + (org-narrow-to-subtree) + (goto-char (point-min)) + (when (re-search-forward + (format "^%s %s$" + (make-string section-level ?*) + (regexp-quote section)) + nil t) + (setq position (match-beginning 0)))) + (unless position + (user-error "Journal section not found: %s" section)) + (goto-char position) + (org-back-to-heading t))) + + (defun ss/journal-capture-target (section) + "Select SECTION under today's journal datetree entry for capture." + (set-buffer (find-file-noselect (ss/require-existing-file ss/journal-file))) + (widen) + (ss/journal-goto-section section (ss/journal-capture-time))) + (defun ss/refresh-org-agenda-files (&rest _) - "Refresh `org-agenda-files' from the current PARA directories. + "Refresh `org-agenda-files' from the journal and PARA directories. Ignore any arguments passed by advice wrappers." (require 'org-agenda) (setq org-agenda-files (sort (delete-dups - (apply #'append - (mapcar (lambda (directory) - (directory-files-recursively - (ss/require-existing-directory directory) - "\\.org\\'")) - ss/org-agenda-directories))) + (append + (list (ss/require-existing-file ss/journal-file)) + (apply #'append + (mapcar (lambda (directory) + (directory-files-recursively + (ss/require-existing-directory directory) + "\\.org\\'")) + ss/org-agenda-directories)))) #'string<))) - (defun ss/ensure-daily-note (&optional time) - "Return the existing daily note path for TIME." - (let* ((date (or time (current-time))) - (file (expand-file-name - (format-time-string "%Y-%m-%d.org" date) - ss/org-daily-directory))) - (ss/require-existing-file file))) - - (defun ss/open-todays-note () - "Open today's daily Org note." + (defun ss/open-journal () + "Open `ss/journal-file', moving to today's entry when it exists." (interactive) - (find-file (ss/ensure-daily-note))) + (find-file (ss/require-existing-file ss/journal-file)) + (widen) + (unless (ss/journal-goto-date) + (goto-char (point-max)))) (defun ss/open-moc () "Open the central MOC note." @@ -887,7 +958,7 @@ than creating it on demand. ("C-c n i" . ss/people-insert-name) ("C-c n I" . ss/people-insert-summary) ("C-c n L" . ss/people-report-by-location) - ("C-c n d" . ss/open-todays-note) + ("C-c n d" . ss/open-journal) ("C-c n o" . ss/people-overview) ("C-c n O" . ss/people-report-by-role) ("C-c n p" . ss/people-open) @@ -906,13 +977,13 @@ than creating it on demand. ** Capture entry points -Daily capture goes to today's plain Org file. Tasks land under =Tasks= while -notes and meetings land under =Notes=. Denote capture uses Denote's own Org -integration so note identity, metadata, and directories stay under Denote's -control rather than custom code. The convenience templates keep the familiar -entry points, but only project capture injects a structural keyword by default. -The people rolodex lives outside =org-capture=: adding a person uses the -dedicated =ss/people-add= command so =~/org/areas/people/people.org= stays a compact, +Fast operational capture goes to =~/org/journal.org= using a datetree with +per-day =Tasks=, =Notes=, and =Meetings= headings. Denote capture uses Denote's +own Org integration so note identity, metadata, and directories stay under +Denote's control rather than custom code. The convenience templates keep the +familiar entry points, but only project capture injects a structural keyword by +default. The people rolodex lives outside =org-capture=: adding a person uses +the dedicated =ss/people-add= command so =~/org/areas/people/people.org= stays a compact, structured card file rather than turning into another capture target. #+begin_src emacs-lisp @@ -921,15 +992,15 @@ structured card file rather than turning into another capture target. :after (org denote) :config (setq org-capture-templates - `(("d" "Daily") - ("dt" "Task" entry - (file+headline ,#'ss/ensure-daily-note "Tasks") + `(("j" "Journal") + ("jt" "Task" entry + (function (lambda () (ss/journal-capture-target "Tasks"))) "* TODO %?") - ("dn" "Note" entry - (file+headline ,#'ss/ensure-daily-note "Notes") + ("jn" "Note" entry + (function (lambda () (ss/journal-capture-target "Notes"))) "* %?") - ("dm" "Meeting" entry - (file+headline ,#'ss/ensure-daily-note "Notes") + ("jm" "Meeting" entry + (function (lambda () (ss/journal-capture-target "Meetings"))) "* %<%H:%M> meeting %?") ("n" "Denote") ("nn" "Generic" plain |
