mirror of
https://github.com/org-roam/org-roam
synced 2025-09-30 17:00:56 -05:00
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
This commit is contained in:
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
(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)))
|
||||
(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))
|
||||
|
||||
|
@@ -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
|
||||
(describe "org-roam-capture :entry-node option"
|
||||
:var ((temp-dir) (org-roam-directory) (org-roam-db-location))
|
||||
|
||||
(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))
|
||||
|
||||
(after-each
|
||||
(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: Test File\n\n* Parent Heading\n:PROPERTIES:\n:ID: parent-id\n:END:\n"))
|
||||
(insert "#+TITLE: Parent File\n\n* Existing Heading\n"))
|
||||
|
||||
;; 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))))
|
||||
;; 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"))))
|
||||
|
||||
;; 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)))
|
||||
(org-roam-capture)
|
||||
|
||||
;; 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))))
|
||||
;; 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))
|
||||
|
Reference in New Issue
Block a user