Compare commits

...

5 Commits

Author SHA1 Message Date
b6a898d6d6 (feat!)capture: unify template variables
Instead of using various capture template variables, like
org-roam-dailies-capture-templates or org-roam-capture-ref-templates,
just use org-roam-capture-templates for everything.

From now on, each individual template will able to optionally specify a
more detailed context for which they're dedicated for, using :kind and
:action properties. Both of them accept either, a symbol or a list of
symbols.

If it's a list, then it can optionally start with :not symbol to
indicate inverse logic, otherwise it will act akin to memq. If it's a
symbol, then it will work akin to eq, but with support for special 'any
value, which indicates that it will work in any contexts; 'any can be
also used inside of the list based values.

Org-roam and the built-in extension will provide the following contexts:
- :action {capture,goto,any}
- :kind {normal,daily,protocol,any}
but third party extensions / users can easily extend them for their own
needs.

If :action isn't specified it will implicitly default to 'capture, while
:kind will default to 'normal contexts.

BREAKING CHANGE: org-roam-dailies-capture-templates and
org-roam-capture-ref-templates are now removed in the favor of unified
approach.
2021-08-25 00:36:17 +03:00
e9ae19c01c (chore): adding changelog entry (#1800)
Related to #1693 and #1681
2021-08-23 14:34:50 +08:00
d25d477b4f (refactor): ensure dependencies are loaded for org-roam-utils.el (#1799)
If org-roam-utils.el is byte-compiled when org-macs is not loaded (`org-with-point-at` macro missing), error can appear about missing `org-with-point-at`.
2021-08-23 01:41:58 +03:00
04b7780ff9 fix: Do not skip invisible headings when getting node at point. (#1798) 2021-08-22 00:01:52 +08:00
858f531d96 (feat)buffer: optimize reflinks fetch (#1795)
Use SQL join instead of iterating over each ref and running another sql query.
2021-08-21 16:18:41 +08:00
9 changed files with 186 additions and 185 deletions

View File

@ -1,7 +1,17 @@
# Changelog
## TBD
### Added
### Removed
### Changed
- [#1795](https://github.com/org-roam/org-roam/pull/1795) buffer: optimized reflinks fetch
### Fixed
- [#1798](https://github.com/org-roam/org-roam/pull/1798) org-roam-node-at-point: do not skip invisible headings
## 2.1.0
### Added
- [#1693](https://github.com/org-roam/org-roam/pull/1693) added `filter-fn` and `templates` parameter to `org-roam-capture`, `org-roam-node-find`, and `org-roam-node-insert`
- [#1709](https://github.com/org-roam/org-roam/pull/1709) added ability to specify default value in org-roam capture templates
- [#1710](https://github.com/org-roam/org-roam/pull/1710) added `org-roam-extract-subtree`
- [#1720](https://github.com/org-roam/org-roam/pull/1720) added `org-roam-db-update-on-save`
@ -18,7 +28,7 @@
- [#1788](https://github.com/org-roam/org-roam/pull/1788) the point is not moved if the node is already visited
### Fixed
- [#1608](https://github.com/org-roam/org-roam/pull/1608) migration: empty ROAM_REFS are now removed
- [#1608](https://github.com/org-roam/org-roam/pull/1608) migration: empty ROAM_REFS are now removed
- [#1609](https://github.com/org-roam/org-roam/pull/1609) migration: fixed file-link replacement
- [#1653](https://github.com/org-roam/org-roam/pull/1653) migration: fixed tags migration
- [#1694](https://github.com/org-roam/org-roam/pull/1694) core: nodes with no title are now skipped
@ -38,7 +48,7 @@
### Changed
- [#1352](https://github.com/org-roam/org-roam/pull/1352) prefer lower-case for roam_tag and roam_alias in interactive commands
- [#1513](https://github.com/org-roam/org-roam/pull/1513) replaced hardcoded "svg" with defcustom org-roam-graph-filetype
- [#1513](https://github.com/org-roam/org-roam/pull/1513) replaced hardcoded "svg" with defcustom org-roam-graph-filetype
- [#1540](https://github.com/org-roam/org-roam/pull/1540) allow `roam_tag` and `roam_alias` to be specified on multiple lines
### Fixed

View File

@ -36,6 +36,18 @@
;; One can use dailies for various purposes, e.g. journaling, fleeting notes,
;; scratch notes and whatever else you can came up with.
;;
;; To add new capture templates dedicated for dailies, specify ":kind daily" for
;; each of such template in `org-roam-capture-templates', e.g.
;;
;; (setq org-roam-capture-templates
;; '(("d" "daily" entry "* %?" :kind daily
;; :if-new (file+head "%<%Y-%m-%d>.org"
;; "#+title: %<%Y-%m-%d>\n"))))
;;
;; Note that in order for your daily files to properly integrate with the
;; calendar, each daily file should be named with a format understood by
;; `org-parse-time-string'.
;;
;;; Code:
(require 'f)
(require 'dash)
@ -49,8 +61,9 @@
;;; Options
(defcustom org-roam-dailies-directory "daily/"
"Path to daily-notes.
This path is relative to `org-roam-directory'."
"Path to daily-notes. This path is relative to `org-roam-directory'.
Daily based capture templates will automatically start from this
path."
:group 'org-roam
:type 'string)
@ -59,74 +72,6 @@ This path is relative to `org-roam-directory'."
:group 'org-roam
:type 'hook)
(defcustom org-roam-dailies-capture-templates
`(("d" "default" entry
"* %?"
:if-new (file+head "%<%Y-%m-%d>.org"
"#+title: %<%Y-%m-%d>\n")))
"Capture templates for daily-notes in Org-roam.
Note that for daily files to show up in the calendar, they have to be of format
\"org-time-string.org\".
See `org-roam-capture-templates' for the template documentation."
:group 'org-roam
:type '(repeat
(choice (list :tag "Multikey description"
(string :tag "Keys ")
(string :tag "Description"))
(list :tag "Template entry"
(string :tag "Keys ")
(string :tag "Description ")
(choice :tag "Capture Type " :value entry
(const :tag "Org entry" entry)
(const :tag "Plain list item" item)
(const :tag "Checkbox item" checkitem)
(const :tag "Plain text" plain)
(const :tag "Table line" table-line))
(choice :tag "Template "
(string)
(list :tag "File"
(const :format "" file)
(file :tag "Template file"))
(list :tag "Function"
(const :format "" function)
(function :tag "Template function")))
(plist :inline t
;; Give the most common options as checkboxes
:options (((const :format "%v " :if-new)
(choice :tag "Node location"
(list :tag "File"
(const :format "" file)
(string :tag " File"))
(list :tag "File & Head Content"
(const :format "" file+head)
(string :tag " File")
(string :tag " Head Content"))
(list :tag "File & Outline path"
(const :format "" file+olp)
(string :tag " File")
(list :tag "Outline path"
(repeat (string :tag "Headline"))))
(list :tag "File & Head Content & Outline path"
(const :format "" file+head+olp)
(string :tag " File")
(string :tag " Head Content")
(list :tag "Outline path"
(repeat (string :tag "Headline"))))))
((const :format "%v " :prepend) (const t))
((const :format "%v " :immediate-finish) (const t))
((const :format "%v " :jump-to-captured) (const t))
((const :format "%v " :empty-lines) (const 1))
((const :format "%v " :empty-lines-before) (const 1))
((const :format "%v " :empty-lines-after) (const 1))
((const :format "%v " :clock-in) (const t))
((const :format "%v " :clock-keep) (const t))
((const :format "%v " :clock-resume) (const t))
((const :format "%v " :time-prompt) (const t))
((const :format "%v " :tree-type) (const week))
((const :format "%v " :unnarrowed) (const t))
((const :format "%v " :table-line-pos) (string))
((const :format "%v " :kill-buffer) (const t))))))))
;;; Commands
;;;; Today
;;;###autoload
@ -306,8 +251,8 @@ When GOTO is non-nil, go the note without creating an entry."
(let ((org-roam-directory (expand-file-name org-roam-dailies-directory org-roam-directory)))
(org-roam-capture- :goto (when goto '(4))
:node (org-roam-node-create)
:templates org-roam-dailies-capture-templates
:props (list :override-default-time time)))
:props (list :kind 'daily
:override-default-time time)))
(when goto (run-hooks 'org-roam-dailies-find-file-hook)))
(add-hook 'org-roam-capture-preface-hook #'org-roam-dailies--override-capture-time-h)
@ -317,6 +262,12 @@ When GOTO is non-nil, go the note without creating an entry."
(when (org-roam-capture--get :override-default-time)
(org-capture-put :default-time (org-roam-capture--get :override-default-time)))))
(when (org-roam-capture--load-templates-p 'org-roam-dailies)
(push '("d" "daily" entry "* %?" :kind daily
:if-new (file+head "%<%Y-%m-%d>.org"
"#+title: %<%Y-%m-%d>\n"))
org-roam-capture-templates))
;;; Bindings
(defvar org-roam-dailies-map (make-sparse-keymap)
"Keymap for `org-roam-dailies'.")

View File

@ -32,12 +32,21 @@
;; 1. "roam-node": This protocol simply opens the node given by the node ID
;; 2. "roam-ref": This protocol creates or opens the node with the given REF
;;
;; You can find detailed instructions on how to setup the protocol in the
;; documentation for Org-roam.
;; To add new capture templates dedicated for the protocol, specify ":kind
;; protocol" for each of such template in `org-roam-capture-templates', e.g.
;;
;; (setq org-roam-capture-templates
;; '(("r" "ref" plain "%?" :kind protocol
;; :if-new (file+head "${slug}.org"
;; "#+title: ${title}")
;; :unnarrowed t)))
;;
;; You can find a detailed instruction on how to setup the protocol in the
;; manual for Org-roam.
;;
;;; Code:
(require 'org-protocol)
(require 'ol) ;; for org-link-decode
(require 'ol) ; to use `org-link-decode'
(require 'org-roam)
;;; Options
@ -46,73 +55,12 @@
:type 'boolean
:group 'org-roam)
(defcustom org-roam-capture-ref-templates
'(("r" "ref" plain "%?"
:if-new (file+head "${slug}.org"
"#+title: ${title}")
:unnarrowed t))
"The Org-roam templates used during a capture from the roam-ref protocol.
See `org-roam-capture-templates' for the template documentation."
:group 'org-roam
:type '(repeat
(choice (list :tag "Multikey description"
(string :tag "Keys ")
(string :tag "Description"))
(list :tag "Template entry"
(string :tag "Keys ")
(string :tag "Description ")
(choice :tag "Capture Type " :value entry
(const :tag "Org entry" entry)
(const :tag "Plain list item" item)
(const :tag "Checkbox item" checkitem)
(const :tag "Plain text" plain)
(const :tag "Table line" table-line))
(choice :tag "Template "
(string)
(list :tag "File"
(const :format "" file)
(file :tag "Template file"))
(list :tag "Function"
(const :format "" function)
(function :tag "Template function")))
(plist :inline t
;; Give the most common options as checkboxes
:options (((const :format "%v " :if-new)
(choice :tag "Node location"
(list :tag "File"
(const :format "" file)
(string :tag " File"))
(list :tag "File & Head Content"
(const :format "" file+head)
(string :tag " File")
(string :tag " Head Content"))
(list :tag "File & Outline path"
(const :format "" file+olp)
(string :tag " File")
(list :tag "Outline path"
(repeat (string :tag "Headline"))))
(list :tag "File & Head Content & Outline path"
(const :format "" file+head+olp)
(string :tag " File")
(string :tag " Head Content")
(list :tag "Outline path"
(repeat (string :tag "Headline"))))))
((const :format "%v " :prepend) (const t))
((const :format "%v " :immediate-finish) (const t))
((const :format "%v " :jump-to-captured) (const t))
((const :format "%v " :empty-lines) (const 1))
((const :format "%v " :empty-lines-before) (const 1))
((const :format "%v " :empty-lines-after) (const 1))
((const :format "%v " :clock-in) (const t))
((const :format "%v " :clock-keep) (const t))
((const :format "%v " :clock-resume) (const t))
((const :format "%v " :time-prompt) (const t))
((const :format "%v " :tree-type) (const week))
((const :format "%v " :unnarrowed) (const t))
((const :format "%v " :table-line-pos) (string))
((const :format "%v " :kill-buffer) (const t))))))))
;;; Protocols
(mapc (lambda (spec) (cl-pushnew spec org-protocol-protocol-alist :test #'equal))
'(("org-roam-ref" :protocol "roam-ref" :function org-roam-protocol-open-ref)
("org-roam-node" :protocol "roam-node" :function org-roam-protocol-open-node)))
;;; Handlers
;;;; roam-ref
(defun org-roam-protocol-open-ref (info)
"Process an org-protocol://roam-ref?ref= style url with INFO.
@ -146,29 +94,9 @@ It opens or creates a note with the given ref.
:node (org-roam-node-create :title (plist-get info :title))
:info (list :ref (plist-get info :ref)
:body (plist-get info :body))
:templates org-roam-capture-ref-templates)
:props '(:kind protocol))
nil)
(defun org-roam-protocol-open-node (info)
"This handler simply opens the file with emacsclient.
INFO is a plist containing additional information passed by the protocol URL.
It should contain the FILE key, pointing to the path of the file to open.
Example protocol string:
org-protocol://roam-node?node=uuid"
(when-let ((node (plist-get info :node)))
(raise-frame)
(org-roam-node-visit (org-roam-populate (org-roam-node-create :id node)) nil 'force))
nil)
(push '("org-roam-ref" :protocol "roam-ref" :function org-roam-protocol-open-ref)
org-protocol-protocol-alist)
(push '("org-roam-node" :protocol "roam-node" :function org-roam-protocol-open-node)
org-protocol-protocol-alist)
;;; Capture implementation
(add-hook 'org-roam-capture-preface-hook #'org-roam-protocol--try-capture-to-ref-h)
(defun org-roam-protocol--try-capture-to-ref-h ()
"Try to capture to an existing node that match the ref."
@ -186,6 +114,27 @@ org-protocol://roam-node?node=uuid"
(when-let ((ref (plist-get org-roam-capture--info :ref)))
(org-roam-ref-add ref)))
(when (org-roam-capture--load-templates-p 'org-roam-protocol)
(push '("r" "ref" plain "%?" :kind protocol
:if-new (file+head "${slug}.org"
"#+title: ${title}")
:unnarrowed t)
org-roam-capture-templates))
;;;; roam-node
(defun org-roam-protocol-open-node (info)
"This handler simply opens the file with emacsclient.
INFO is a plist containing additional information passed by the protocol URL.
It should contain the FILE key, pointing to the path of the file to open.
Example protocol string:
org-protocol://roam-node?node=uuid"
(when-let ((node (plist-get info :node)))
(raise-frame)
(org-roam-node-visit (org-roam-populate (org-roam-node-create :id node)) nil 'force))
nil)
(provide 'org-roam-protocol)

View File

@ -38,6 +38,33 @@
(defvar org-end-time-was-given)
;;; Options
(defcustom org-roam-capture-load-default-templates t
"Whether to include default Org-roam capture templates during the loading.
The value will also affect default templates provided by
Org-roam's extensions. It can be:
t
Include all the default capture templates provided by
Org-roam and its extensions.
nil
Don't include the default capture templates provided by
Org-roam and its extensions.
a list of symbols
Each symbol in the list corresponds to a `provide'd FEATURE,
for which the default capture templates will be automatically
included (if any). The list can start with a special value
`:not', in which case the logic will be inverted to
exclusion, e.g. (:not org-roam-dailies) won't include the
default templates provided by `org-roam-dailies', but will
include for other features."
:type '(choice
(const :tag "Yes" t)
(const :tag "No" nil)
(repeat :tag "List features" symbol))
:group 'org-roam)
(defcustom org-roam-capture-templates
'(("d" "default" plain "%?"
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
@ -378,9 +405,18 @@ This variable is populated dynamically, and is only non-nil
during the Org-roam capture process.")
(defconst org-roam-capture--template-keywords (list :if-new :id :link-description :call-location
:region)
:region :kind :action)
"Keywords used in `org-roam-capture-templates' specific to Org-roam.")
(defun org-roam-capture--load-templates-p (feature)
"Return t if capture templates for FEATURE are allowed to be loaded.
See `org-roam-capture-load-default-templates' for more details."
(let ((user-value org-roam-capture-load-default-templates))
(pcase user-value
((pred consp) (org-roam--valid-option-p feature user-value))
((pred null) nil)
('t t))))
;;; Main entry point
;;;###autoload
(cl-defun org-roam-capture- (&key goto keys node info props templates)
@ -390,11 +426,18 @@ INFO is a plist for filling up Org-roam's capture templates.
NODE is an `org-roam-node' construct containing information about the node.
PROPS is a plist containing additional Org-roam properties for each template.
TEMPLATES is a list of org-roam templates."
(let* ((props (plist-put props :call-location (point-marker)))
(let* ((props (thread-first props
(plist-put :call-location (point-marker))
(plist-put :action (or (plist-get props :action) (if goto 'goto 'capture)))
(plist-put :kind (or (plist-get props :kind) 'normal))))
(templates (org-roam-capture-get-templates
:action (plist-get props :action)
:kind (plist-get props :kind)
:templates (or templates org-roam-capture-templates)))
(org-capture-templates
(mapcar (lambda (template)
(org-roam-capture--convert-template template props))
(or templates org-roam-capture-templates)))
templates))
(org-roam-capture--node node)
(org-roam-capture--info info))
(when (and (not keys)
@ -429,6 +472,27 @@ valid for the capture (i.e. initialization, and finalization of
the capture)."
(plist-get org-capture-plist :org-roam))
(cl-defun org-roam-capture-get-templates (&key (kind 'normal)
(action 'capture)
(templates org-roam-capture-templates))
"Return list of narrowed down capture TEMPLATES to a suitable KIND and ACTION."
(cl-loop for templ in templates
for templ-kind = (or (plist-get templ :kind) 'normal)
for templ-action = (or (plist-get templ :action) 'capture)
when (and (org-roam--valid-option-p kind templ-kind)
(org-roam--valid-option-p action templ-action))
if (org-roam-capture--dull-template-p templ) collect it
else collect templ))
(defun org-roam-capture--dull-template-p (template)
"Return prefix key declaration of TEMPLATE if it's a prefix key based one.
Unlike `org-capture', in Org-roam such templates can also
optionally specify dedicated `:kind' and `:action' values."
(cl-loop for property in (cddr template) by #'cddr
unless (memq property '(:kind :action))
return nil
finally return (cl-subseq template 0 2)))
(defun org-roam-capture--get (keyword)
"Get the value for KEYWORD from the `org-roam-capture-template'."
(plist-get (plist-get org-capture-plist :org-roam) keyword))

View File

@ -202,6 +202,10 @@ nodes." org-id-locations-file)
;;; Obsolete functions
(make-obsolete 'org-roam-get-keyword 'org-collect-keywords "org-roam 2.0")
;;; Obsolete variables
(make-obsolete-variable 'org-roam-dailies-capture-templates 'org-roam-capture-templates "org-roam 2.1")
(make-obsolete-variable 'org-roam-capture-ref-templates 'org-roam-capture-templates "org-roam 2.1")
(provide 'org-roam-compat)
;;; org-roam-compat.el ends here

View File

@ -542,22 +542,20 @@ Sorts by title."
(defun org-roam-reflinks-get (node)
"Return the reflinks for NODE."
(let ((refs (org-roam-db-query [:select [ref] :from refs
:where (= node-id $s1)]
(let ((refs (org-roam-db-query [:select :distinct [refs:ref links:source links:pos links:properties]
:from refs
:left-join links
:where (= refs:node-id $s1)
:and (= links:dest refs:ref)]
(org-roam-node-id node)))
links)
(pcase-dolist (`(,ref) refs)
(pcase-dolist (`(,source-id ,pos ,properties) (org-roam-db-query
[:select [source pos properties]
:from links
:where (= dest $s1)]
ref))
(push (org-roam-populate
(org-roam-reflink-create
:source-node (org-roam-node-create :id source-id)
:ref ref
:point pos
:properties properties)) links)))
(pcase-dolist (`(,ref ,source-id ,pos ,properties) refs)
(push (org-roam-populate
(org-roam-reflink-create
:source-node (org-roam-node-create :id source-id)
:ref ref
:point pos
:properties properties)) links))
links))
(defun org-roam-reflinks-sort (a b)

View File

@ -180,7 +180,7 @@ populated."
(magit-section-up)
(org-roam-node-at-point)))
(t (org-with-wide-buffer
(org-back-to-heading-or-point-min)
(org-back-to-heading-or-point-min t)
(while (and (not (org-roam-db-node-p))
(not (bobp)))
(org-roam-up-heading-or-point-min))
@ -827,7 +827,7 @@ If region is active, then use it instead of the node at point."
"Convert current subtree at point to a node, and extract it into a new file."
(interactive)
(save-excursion
(org-back-to-heading-or-point-min)
(org-back-to-heading-or-point-min t)
(when (bobp) (user-error "Already a top-level node"))
(org-id-get-create)
(save-buffer)
@ -865,7 +865,7 @@ If region is active, then use it instead of the node at point."
Recursively traverses up the headline tree to find the
first encapsulating ID."
(org-with-wide-buffer
(org-back-to-heading-or-point-min)
(org-back-to-heading-or-point-min t)
(while (and (not (org-roam-db-node-p))
(not (bobp)))
(org-roam-up-heading-or-point-min))

View File

@ -32,6 +32,8 @@
;;
;;; Code:
(require 'org-roam)
;;; String utilities
;; TODO Refactor this.
(defun org-roam-replace-string (old new s)
@ -103,6 +105,29 @@ If FILE, set `default-directory' to FILE's directory and insert its contents."
(setq-local default-directory (file-name-directory ,file)))
,@body)))))
;;; Processing options
(defun org-roam--valid-option-p (option choice)
"Return t if OPTION satisfies CHOICE, else nil.
OPTION is a symbol, while CHOICE is either, a symbol or a list of
symbols that can optionally start with `:not' keyword.
If CHOICE is a list that indicates negation, then the function
will return t if OPTION isn't in the list. Otherwise it will
return t is OPTION is present in the CHOICE.
When CHOICE is a symbol, it will behave like `eq', except of
special 'any value, in which case it will always return t,
independently OPTION's value.
CHOICE as a list can too contain 'any, in which case any OPTION
value will be considered as part of the CHOICE, with respect to
negation."
(let* ((choices (-list choice))
(intersection (-intersection (list option 'any) choices))
(negation (eq :not (car choices))))
(or (and intersection (not negation))
(and (not intersection) negation))))
;;; Formatting
(defun org-roam-format-template (template replacer)
"Format TEMPLATE with the function REPLACER.

View File

@ -94,7 +94,6 @@
(eval-when-compile
(require 'subr-x))
(require 'org-roam-utils)
(require 'org-roam-compat)
;;; Options
@ -310,6 +309,7 @@ E.g. (\".org\") => (\"*.org\" \"*.org.gpg\")"
(provide 'org-roam)
(cl-eval-when (load eval)
(require 'org-roam-utils)
(require 'org-roam-db)
(require 'org-roam-node)
(require 'org-roam-capture)