Compare commits

...

1 Commits

Author SHA1 Message Date
Dustin Farris
0799985296 feat!(capture): add option to create entry ids
This un-breaks a breaking change where we addressed an issue that
prevented ids from being generated for 'entry' capture types.  While
this was a much-asked feature, the change also disrupted user workflows
that depend on the old behavior of entries creating org headings and
nothing more.

Users can now opt in to generating a heading id by passing
`:entry-node t` in their org-roam capture templates definition.

Amend: ed94524964
2025-09-29 16:20:57 -07:00
4 changed files with 99 additions and 41 deletions

View File

@@ -847,7 +847,9 @@ of the template are similar to ~org-capture~ templates.
automatically chooses this template for you.
2. The template is given a description of ~"default"~.
3. ~plain~ text is inserted. Other options include Org headings via
~entry~.
~entry~. Note that ~entry~ type captures create regular Org headings
without IDs by default. To create an Org-roam node with an ID, use
the ~:entry-node t~ option.
4. Notice that the ~target~ that's usually in Org-capture templates is missing
here.
5. ~"%?"~ is the template inserted on each call to ~org-roam-capture-~.
@@ -1221,6 +1223,10 @@ Here is a sane default configuration:
"#+title: %<%Y-%m-%d>\n"))))
#+end_src
Note: The ~entry~ type capture above creates a regular Org heading without an
ID. If you want each daily entry to be an Org-roam node with its own ID, add
~:entry-node t~ to the template.
See [[*The Templating System][The Templating System]] for creating new templates.
*** Usage

View File

@@ -1219,7 +1219,9 @@ automatically chooses this template for you.
The template is given a description of @code{"default"}.
@item
@code{plain} text is inserted. Other options include Org headings via
@code{entry}.
@code{entry}. Note that @code{entry} type captures create regular Org headings
without IDs by default. To create an Org-roam node with an ID, use
the @code{:entry-node t} option.
@item
Notice that the @code{target} that's usually in Org-capture templates is missing
here.
@@ -1703,6 +1705,10 @@ Here is a sane default configuration:
"#+title: %<%Y-%m-%d>\n"))))
@end lisp
Note: The @code{entry} type capture above creates a regular Org heading without an
ID@. If you want each daily entry to be an Org-roam node with its own ID, add
@code{:entry-node t} to the template.
See @ref{The Templating System} for creating new templates.
@node Usage
@@ -2421,5 +2427,5 @@ When GOTO is non-nil, go the note without creating an entry."
@printindex vr
Emacs 30.1 (Org mode 9.7.29)
Emacs 30.2 (Org mode 9.7.34)
@bye

View File

@@ -63,9 +63,11 @@ description A short string describing the template, which will be shown
during selection.
type The type of entry. Valid types are:
entry an Org node, with a headline. Will be filed
as the child of the target entry or as a
top level entry. Its default template is:
entry an Org heading. Will be filed as the child
of the target entry or as a top level entry.
Use :entry-node t to create an ID for this
heading, making it an org-roam node.
Its default template is:
\"* %?\n %a\"
item a plain list item, will be placed in the
first plain list at the target location.
@@ -135,6 +137,11 @@ The following options are supported for the :target property:
The rest of the entry is a property list of additional options. Recognized
properties are:
:entry-node When set to t for entry-type captures, creates an ID for
the captured entry heading. When nil or not specified,
no ID is created for the entry. Only applies to templates
with type 'entry'.
:prepend Normally newly captured information will be appended at
the target location (last child, last table line,
last list item...). Setting this property will
@@ -390,7 +397,7 @@ This variable is populated dynamically, and is only non-nil
during the Org-roam capture process.")
(defconst org-roam-capture--template-keywords (list :target :id :link-description :call-location
:region)
:region :entry-node)
"Keywords used in `org-roam-capture-templates' specific to Org-roam.")
;;; Main entry point
@@ -578,12 +585,13 @@ capture target."
target-entry-p (and (derived-mode-p 'org-mode) (org-at-heading-p))))))
;; Setup `org-id' for the current capture target and return it back to the
;; caller.
;; Unless it's an entry type, then we want to create an ID for the entry instead
;; For entry type: only create ID if :entry-node is t
(pcase (org-capture-get :type)
('entry
(advice-add #'org-capture-place-entry :after #'org-roam-capture--create-id-for-entry)
(org-roam-capture--put :new-node-p t)
(setq id (org-roam-node-id org-roam-capture--node)))
(when (org-roam-capture--get :entry-node)
(advice-add #'org-capture-place-entry :after #'org-roam-capture--create-id-for-entry)
(org-roam-capture--put :new-node-p t)
(setq id (org-roam-node-id org-roam-capture--node))))
(_
(save-excursion
(goto-char p)
@@ -610,7 +618,7 @@ capture target."
(advice-remove #'org-capture-place-template #'org-roam-capture-run-new-node-hook-a))
(defun org-roam-capture--create-id-for-entry ()
"Create the ID for the new entry."
"Create the ID for the new entry heading."
(org-entry-put (point) "ID" (org-roam-capture--get :id))
(advice-remove #'org-capture-place-entry #'org-roam-capture--create-id-for-entry))

View File

@@ -58,38 +58,76 @@
(org-roam-capture--fill-template (lambda () "foo"))
:to-equal "foo")))
(describe "org-roam-capture entry-type ID creation"
(it "creates ID for entry-type captures"
(let* ((temp-dir (make-temp-file "org-roam-test" t))
(test-file (expand-file-name "test.org" temp-dir))
(org-roam-directory temp-dir)
(org-roam-capture--node (org-roam-node-create :id (org-id-new)))
(org-roam-capture--info (make-hash-table :test 'equal))
capture-id)
(unwind-protect
(progn
;; Create initial file content
(with-temp-file test-file
(insert "#+TITLE: Test File\n\n* Parent Heading\n:PROPERTIES:\n:ID: parent-id\n:END:\n"))
(describe "org-roam-capture :entry-node option"
:var ((temp-dir) (org-roam-directory) (org-roam-db-location))
;; Mock org-capture context and get-target
(cl-letf* (((symbol-function 'org-capture-get)
(lambda (prop)
(pcase prop
(:type 'entry)
(_ nil))))
((symbol-function 'org-roam-capture--get-target)
(lambda () `(file ,test-file))))
(before-each
(setq temp-dir (make-temp-file "org-roam-test" t))
(setq org-roam-directory temp-dir)
(setq org-roam-db-location (expand-file-name "org-roam.db" temp-dir))
(org-roam-db-sync))
;; Call the setup function
(with-current-buffer (find-file-noselect test-file)
(org-roam-capture--setup-target-location)
(setq capture-id (org-roam-capture--get :id)))
(after-each
(delete-directory temp-dir t))
;; Verify ID was created and stored for entry type
(expect capture-id :not :to-be nil)
(expect (org-roam-capture--get :new-node-p) :to-be t)))
(delete-directory temp-dir t))))
(it "does not create ID for entry-type capture without :entry-node option"
(let* ((test-file (expand-file-name "test-parent.org" temp-dir))
(org-roam-capture-templates
'(("t" "test" entry "* ${title}"
:target (file "test-parent.org")
:unnarrowed t))))
;; Create parent file with an existing heading
(with-temp-file test-file
(insert "#+TITLE: Parent File\n\n* Existing Heading\n"))
;; Mock the node selection to return a new node
(cl-letf (((symbol-function 'org-roam-node-read)
(lambda (&rest _)
(org-roam-node-create :title "New Entry Without ID"))))
(org-roam-capture)
;; Finalize the capture
(org-capture-finalize))
;; Verify the captured entry exists but has no ID
(with-temp-buffer
(insert-file-contents test-file)
(goto-char (point-min))
(re-search-forward "^\\* New Entry Without ID$")
(let ((has-id (org-entry-get (point) "ID")))
(expect has-id :to-be nil)))))
(it "creates ID for entry-type capture with :entry-node t option"
(let* ((test-file (expand-file-name "test-parent-with-id.org" temp-dir))
(org-roam-capture-templates
'(("t" "test" entry "* ${title}"
:target (file "test-parent-with-id.org")
:entry-node t
:unnarrowed t))))
;; Create parent file with an existing heading
(with-temp-file test-file
(insert "#+TITLE: Parent File\n\n* Existing Heading\n"))
;; Mock the node selection to return a new node
(cl-letf (((symbol-function 'org-roam-node-read)
(lambda (&rest _)
(org-roam-node-create :title "New Entry With ID"))))
(org-roam-capture)
;; Finalize the capture
(org-capture-finalize))
;; Verify the captured entry has an ID
(with-temp-buffer
(insert-file-contents test-file)
(goto-char (point-min))
(re-search-forward "^\\* New Entry With ID$")
(let ((has-id (org-entry-get (point) "ID")))
(expect has-id :not :to-be nil)))))
(it "creates ID at target for non-entry-type captures"
(let* ((temp-dir (make-temp-file "org-roam-test" t))