mirror of
https://github.com/org-roam/org-roam
synced 2025-09-30 17:00:56 -05:00
(feature): use org-capture templates (#216)
Instead of implementing our own templating system, we abuse org-capture's templating system. We add 2 additional properties: - :head: a starting template that goes at the beginning of the file. - :file-name: a string that expands to the file name The templates are customizable at `org-roam-capture-templates` and `org-roam-ref-capture-templates`.
This commit is contained in:
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,19 +1,20 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 0.1.3 (TBD)
|
## 1.0.0
|
||||||
|
|
||||||
The biggest change, by far, the shift of database storage into SQLite.
|
|
||||||
This means that the org-roam cache needs to be built manually at least
|
|
||||||
once via `M-x org-roam-build-cache`.
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
* [#200][gh-200] Move Org-roam cache into a SQLite database.
|
* [#200][gh-200] Move Org-roam cache into a SQLite database.
|
||||||
|
* [#203][gh-203] Roam protocol is deprecated, in favour of extending org-roam-protocol.
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
* [#182][gh-182] Support file name aliases via `#+ROAM_ALIAS`.
|
* [#182][gh-182] Support file name aliases via `#+ROAM_ALIAS`.
|
||||||
* [#188][gh-188] Add `org-roam-protocol`, shifting `roam://` link handling into Emacs-lisp.
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
* [#165][gh-165] Add templating functionality via `org-roam-templates`.
|
* [#216][gh-216] Adds templating functionality by extending org-capture.
|
||||||
|
|
||||||
|
|
||||||
|
### Bugfixes
|
||||||
|
* [#207][gh-207], [#221][gh-221] small bugfixes to Org-roam graph generation
|
||||||
|
|
||||||
## 0.1.2 (2020-02-21)
|
## 0.1.2 (2020-02-21)
|
||||||
|
|
||||||
@@ -102,10 +103,12 @@ Mostly a documentation/cleanup release.
|
|||||||
[gh-141]: https://github.com/jethrokuan/org-roam/pull/141
|
[gh-141]: https://github.com/jethrokuan/org-roam/pull/141
|
||||||
[gh-142]: https://github.com/jethrokuan/org-roam/pull/142
|
[gh-142]: https://github.com/jethrokuan/org-roam/pull/142
|
||||||
[gh-143]: https://github.com/jethrokuan/org-roam/pull/143
|
[gh-143]: https://github.com/jethrokuan/org-roam/pull/143
|
||||||
[gh-165]: https://github.com/jethrokuan/org-roam/pull/165
|
|
||||||
[gh-182]: https://github.com/jethrokuan/org-roam/pull/182
|
[gh-182]: https://github.com/jethrokuan/org-roam/pull/182
|
||||||
[gh-188]: https://github.com/jethrokuan/org-roam/pull/188
|
[gh-188]: https://github.com/jethrokuan/org-roam/pull/188
|
||||||
[gh-200]: https://github.com/jethrokuan/org-roam/pull/200
|
[gh-200]: https://github.com/jethrokuan/org-roam/pull/200
|
||||||
|
[gh-207]: https://github.com/jethrokuan/org-roam/pull/207
|
||||||
|
[gh-216]: https://github.com/jethrokuan/org-roam/pull/216
|
||||||
|
[gh-221]: https://github.com/jethrokuan/org-roam/pull/221
|
||||||
|
|
||||||
# Local Variables:
|
# Local Variables:
|
||||||
# eval: (auto-fill-mode -1)
|
# eval: (auto-fill-mode -1)
|
||||||
|
BIN
doc/images/roam-ref.gif
Normal file
BIN
doc/images/roam-ref.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.5 MiB |
@@ -1,29 +1,36 @@
|
|||||||
## What is Roam protocol?
|
## What is Roam protocol?
|
||||||
|
|
||||||
Org-roam defines two protocols that help boost productivity, by
|
Org-roam defines two protocols that help boost productivity, by
|
||||||
extending `org-protocol`.
|
extending `org-protocol`: the `roam-file` and `roam-ref` protocol.
|
||||||
|
|
||||||
The first protocol is the `roam-file` protocol. This is a simple
|
## The `roam-file` protocol
|
||||||
protocol that opens the path specified by the `file` key (e.g.
|
|
||||||
`org-protocol:/roam-file?file=/tmp/file.org`). This is used in the
|
|
||||||
generated graph.
|
|
||||||
|
|
||||||
The second protocol is the `roam-ref` protocol. This protocol finds or
|
This is a simple protocol that opens the path specified by the `file`
|
||||||
creates a new note with a given `ROAM_KEY` (see
|
key (e.g. `org-protocol://roam-file?file=/tmp/file.org`). This is used
|
||||||
[Anatomy](anatomy.md)).
|
in the generated graph.
|
||||||
|
|
||||||
|
## The `roam-ref` Protocol
|
||||||
|
|
||||||
|
This protocol finds or creates a new note with a given `ROAM_KEY` (see
|
||||||
|
[Anatomy](anatomy.md)):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
To use this, create a Firefox bookmarklet as follows:
|
To use this, create a Firefox bookmarklet as follows:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
javascript:location.href =
|
javascript:location.href =
|
||||||
'org-protocol:/roam-ref?template=ref&ref='
|
'org-protocol:/roam-ref?template=r&ref='
|
||||||
+ encodeURIComponent(location.href)
|
+ encodeURIComponent(location.href)
|
||||||
+ '&title='
|
+ '&title='
|
||||||
+ encodeURIComponent(document.title)
|
+ encodeURIComponent(document.title)
|
||||||
```
|
```
|
||||||
|
|
||||||
where `template` is the template you have defined for your web
|
where `template` is the template key for a template in
|
||||||
snippets. This template should contain a `#+ROAM_KEY: {ref}` in it.
|
`org-roam-ref-capture-templates`. More documentation on the templating
|
||||||
|
system can be found [here](templating.md).
|
||||||
|
|
||||||
|
These templates should contain a `#+ROAM_KEY: {ref}` in it.
|
||||||
|
|
||||||
## Org-protocol Setup
|
## Org-protocol Setup
|
||||||
|
|
||||||
|
83
doc/templating.md
Normal file
83
doc/templating.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
Rather than creating blank files on `org-roam-insert` and
|
||||||
|
`org-roam-find-file`, it is may be desirable to prefill the file with
|
||||||
|
content. This may include:
|
||||||
|
|
||||||
|
- Time of creation
|
||||||
|
- File it was created from
|
||||||
|
- Clipboard content
|
||||||
|
- Any other data you may want to input manually
|
||||||
|
|
||||||
|
This requires a complex template insertion system, but fortunately,
|
||||||
|
Org ships with a powerful one: `org-capture`. However, org-capture was
|
||||||
|
not designed for such use. Org-roam abuses `org-capture` to some
|
||||||
|
extent, extending its syntax. To first understand how org-roam's
|
||||||
|
templating system works, it may be useful to look into org-capture.
|
||||||
|
|
||||||
|
## Org-roam Templates
|
||||||
|
|
||||||
|
The org-roam capture template extends org-capture's template with 2
|
||||||
|
additional properties:
|
||||||
|
|
||||||
|
1. `:file-name`: This is the file name template used when a new note
|
||||||
|
is created.
|
||||||
|
2. `:head`: This is the template that is inserted on initial note
|
||||||
|
creation.
|
||||||
|
|
||||||
|
### Org-roam Template Expansion
|
||||||
|
|
||||||
|
Org-roam's template definitions also extend org-capture's template
|
||||||
|
syntax, to allow prefilling of strings. In many scenarios,
|
||||||
|
`org-roam-capture` is passed a mapping between variables and strings.
|
||||||
|
For example, during `org-roam-insert`, a title is prompted for. If the
|
||||||
|
title doesn't already exist, we would like to create a new file,
|
||||||
|
without prompting for the title again.
|
||||||
|
|
||||||
|
Variables passed are expanded with the `${var}` syntax. For example,
|
||||||
|
eduring `org-roam-insert`, `${title}` is prefilled for expansion. Any
|
||||||
|
variables that do not contain strings, are prompted for values using
|
||||||
|
`completing-read`.
|
||||||
|
|
||||||
|
After doing this expansion, the org-capture's template expansion
|
||||||
|
system is used to fill up the rest of the template. You may read up
|
||||||
|
more on this on [org-capture's documentation
|
||||||
|
page](https://orgmode.org/manual/Template-expansion.html#Template-expansion).
|
||||||
|
|
||||||
|
For example, take the template: `"%<%Y%m%d%H%M%S>-${title}"`, with the title
|
||||||
|
`"Foo"`. The template is first expanded into `%<%Y%m%d%H%M%S>-Foo`. Then
|
||||||
|
org-capture expands `%<%Y%m%d%H%M%S>` with timestamp: e.g.
|
||||||
|
`20200213032037-Foo`.
|
||||||
|
|
||||||
|
This templating system is used throughout org-roam templates.
|
||||||
|
|
||||||
|
### Template examples
|
||||||
|
|
||||||
|
Here I walkthrough the default template, reproduced below.
|
||||||
|
|
||||||
|
```
|
||||||
|
("d" "default" plain (function org-roam--capture-get-point)
|
||||||
|
"%?"
|
||||||
|
:file-name "%<%Y%m%d%H%M%S>-${slug}"
|
||||||
|
:head "#+TITLE: ${title}\n"
|
||||||
|
:unnarrowed t)
|
||||||
|
```
|
||||||
|
|
||||||
|
1. The template has short key `"d"`. If you have only one template,
|
||||||
|
org-roam 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`.
|
||||||
|
4. `(function org-roam--capture-get-point)` should not be changed.
|
||||||
|
5. `"%?"` is the template inserted on each call to `org-roam-capture`.
|
||||||
|
This template means don't insert any content, but place the cursor
|
||||||
|
here.
|
||||||
|
6. `:file-name` is the file-name template for a new note, if it
|
||||||
|
doesn't yet exist. This creates a file at path that looks like
|
||||||
|
`/path/to/org-roam-directory/20200213032037-foo.org`.
|
||||||
|
7. `:head` contains the initial template to be inserted (once only),
|
||||||
|
at the beginning of the file. Here, the title global attribute is
|
||||||
|
inserted.
|
||||||
|
8. `:unnarrowed t` tells org-capture to show the contents for the
|
||||||
|
whole file, rather than narrowing to just the entry.
|
||||||
|
|
||||||
|
Other options you may want to learn about include `:immediate-finish`.
|
||||||
|
|
@@ -9,6 +9,7 @@ nav:
|
|||||||
- Installation: installation.md
|
- Installation: installation.md
|
||||||
- Configuration: configuration.md
|
- Configuration: configuration.md
|
||||||
- Anatomy of a Roam file: anatomy.md
|
- Anatomy of a Roam file: anatomy.md
|
||||||
|
- The Templating System: templating.md
|
||||||
- Ecosystem: ecosystem.md
|
- Ecosystem: ecosystem.md
|
||||||
- Similar Packages: comparison.md
|
- Similar Packages: comparison.md
|
||||||
- "Appendix: Note-taking Workflow": notetaking_workflow.md
|
- "Appendix: Note-taking Workflow": notetaking_workflow.md
|
||||||
|
@@ -31,6 +31,15 @@
|
|||||||
(require 'org-roam)
|
(require 'org-roam)
|
||||||
|
|
||||||
(declare-function org-roam-find-ref "org-roam" (&optional info))
|
(declare-function org-roam-find-ref "org-roam" (&optional info))
|
||||||
|
(declare-function org-roam--capture-get-point "org-roam" ())
|
||||||
|
|
||||||
|
(defvar org-roam-ref-capture-templates
|
||||||
|
'(("r" "ref" plain (function org-roam--capture-get-point)
|
||||||
|
""
|
||||||
|
:file-name "${slug}"
|
||||||
|
:head "#+TITLE: ${title}
|
||||||
|
#+ROAM_KEY: ${ref}\n"
|
||||||
|
:unnarrowed t)))
|
||||||
|
|
||||||
(defun org-roam-protocol-open-ref (info)
|
(defun org-roam-protocol-open-ref (info)
|
||||||
"Process an org-protocol://roam-ref?ref= style url with INFO.
|
"Process an org-protocol://roam-ref?ref= style url with INFO.
|
||||||
@@ -38,10 +47,9 @@
|
|||||||
The sub-protocol used to reach this function is set in
|
The sub-protocol used to reach this function is set in
|
||||||
`org-protocol-protocol-alist'.
|
`org-protocol-protocol-alist'.
|
||||||
|
|
||||||
This function decodes a ref, and places it into
|
This function decodes a ref.
|
||||||
This function detects an file, and opens it.
|
|
||||||
|
|
||||||
javascript:location.href = \\='org-protocol://roam-ref?ref=\\='+ \\
|
javascript:location.href = \\='org-protocol://roam-ref?template=r&ref=\\='+ \\
|
||||||
encodeURIComponent(location.href) + \\='&title=\\=' \\
|
encodeURIComponent(location.href) + \\='&title=\\=' \\
|
||||||
encodeURIComponent(document.title) + \\='&body=\\=' + \\
|
encodeURIComponent(document.title) + \\='&body=\\=' + \\
|
||||||
encodeURIComponent(window.getSelection())"
|
encodeURIComponent(window.getSelection())"
|
||||||
@@ -50,13 +58,21 @@ This function detects an file, and opens it.
|
|||||||
(let ((key (car k.v))
|
(let ((key (car k.v))
|
||||||
(val (cdr k.v)))
|
(val (cdr k.v)))
|
||||||
(cons key (org-link-decode val)))) alist)))
|
(cons key (org-link-decode val)))) alist)))
|
||||||
(when (assoc 'ref decoded-alist)
|
(unless (assoc 'ref decoded-alist)
|
||||||
|
(error "No ref key provided."))
|
||||||
|
(when-let ((title (cdr (assoc 'title decoded-alist))))
|
||||||
|
(push (cons 'slug (org-roam--title-to-slug title)) decoded-alist))
|
||||||
|
(let* ((org-roam-capture-templates org-roam-ref-capture-templates)
|
||||||
|
(org-roam--capture-context 'ref)
|
||||||
|
(org-roam--capture-info decoded-alist)
|
||||||
|
(template (cdr (assoc 'template decoded-alist))))
|
||||||
(raise-frame)
|
(raise-frame)
|
||||||
(org-roam-find-ref decoded-alist)))
|
(org-roam-capture nil template)
|
||||||
|
(message "Item captured.")))
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
(defun org-roam-protocol-open-file (info)
|
(defun org-roam-protocol-open-file (info)
|
||||||
"Process an org-protocol://roam-ref?ref= style url with INFO.
|
"This handler simply opens the file with emacsclient.
|
||||||
|
|
||||||
Example protocol string:
|
Example protocol string:
|
||||||
|
|
||||||
|
276
org-roam.el
276
org-roam.el
@@ -36,6 +36,7 @@
|
|||||||
;;;; Library Requires
|
;;;; Library Requires
|
||||||
(require 'org)
|
(require 'org)
|
||||||
(require 'org-element)
|
(require 'org-element)
|
||||||
|
(require 'org-capture)
|
||||||
(require 'ob-core) ;for org-babel-parse-header-arguments
|
(require 'ob-core) ;for org-babel-parse-header-arguments
|
||||||
(require 'subr-x)
|
(require 'subr-x)
|
||||||
(require 'dash)
|
(require 'dash)
|
||||||
@@ -69,12 +70,6 @@ All Org files, at any level of nesting, is considered part of the Org-roam."
|
|||||||
:type 'directory
|
:type 'directory
|
||||||
:group 'org-roam)
|
:group 'org-roam)
|
||||||
|
|
||||||
(defcustom org-roam-new-file-directory nil
|
|
||||||
"Path to where new Org-roam files are created.
|
|
||||||
If nil, default to the org-roam-directory (preferred)."
|
|
||||||
:type 'directory
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-buffer-position 'right
|
(defcustom org-roam-buffer-position 'right
|
||||||
"Position of `org-roam' buffer.
|
"Position of `org-roam' buffer.
|
||||||
Valid values are
|
Valid values are
|
||||||
@@ -109,26 +104,6 @@ If nil, always ask for filename."
|
|||||||
:type 'boolean
|
:type 'boolean
|
||||||
:group 'org-roam)
|
:group 'org-roam)
|
||||||
|
|
||||||
(defcustom org-roam-graph-viewer (executable-find "firefox")
|
|
||||||
"Path to executable for viewing SVG."
|
|
||||||
:type 'string
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-graphviz-executable (executable-find "dot")
|
|
||||||
"Path to graphviz executable."
|
|
||||||
:type 'string
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-graph-max-title-length 100
|
|
||||||
"Maximum length of titles in Graphviz graph nodes."
|
|
||||||
:type 'number
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-graph-node-shape "ellipse"
|
|
||||||
"Shape of Graphviz nodes."
|
|
||||||
:type 'string
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
;;;; Dynamic variables
|
;;;; Dynamic variables
|
||||||
(defvar org-roam--current-buffer nil
|
(defvar org-roam--current-buffer nil
|
||||||
"Currently displayed file in `org-roam' buffer.")
|
"Currently displayed file in `org-roam' buffer.")
|
||||||
@@ -596,21 +571,8 @@ specified via the #+ROAM_ALIAS property."
|
|||||||
(slug (-reduce-from #'replace title pairs)))
|
(slug (-reduce-from #'replace title pairs)))
|
||||||
(s-downcase slug))))
|
(s-downcase slug))))
|
||||||
|
|
||||||
;;;; New file creation
|
;;;; Org-roam capture
|
||||||
(defvar org-roam-templates
|
(defun org-roam--new-file-path (id)
|
||||||
(list (list "default" (list :file #'org-roam--file-name-timestamp-title
|
|
||||||
:content "#+TITLE: ${title}")))
|
|
||||||
"Templates to insert for new files in org-roam.")
|
|
||||||
|
|
||||||
(defun org-roam--file-name-timestamp-title (title)
|
|
||||||
"Return a file name (without extension) for new files.
|
|
||||||
|
|
||||||
It uses TITLE and the current timestamp to form a unique title."
|
|
||||||
(let ((timestamp (format-time-string "%Y%m%d%H%M%S" (current-time)))
|
|
||||||
(slug (org-roam--title-to-slug title)))
|
|
||||||
(format "%s_%s" timestamp slug)))
|
|
||||||
|
|
||||||
(defun org-roam--new-file-path (id &optional absolute)
|
|
||||||
"The file path for a new Org-roam file, with identifier ID.
|
"The file path for a new Org-roam file, with identifier ID.
|
||||||
If ABSOLUTE, return an absolute file-path. Else, return a relative file-path."
|
If ABSOLUTE, return an absolute file-path. Else, return a relative file-path."
|
||||||
(let ((absolute-file-path (file-truename
|
(let ((absolute-file-path (file-truename
|
||||||
@@ -618,44 +580,111 @@ If ABSOLUTE, return an absolute file-path. Else, return a relative file-path."
|
|||||||
(if org-roam-encrypt-files
|
(if org-roam-encrypt-files
|
||||||
(concat id ".org.gpg")
|
(concat id ".org.gpg")
|
||||||
(concat id ".org"))
|
(concat id ".org"))
|
||||||
(or org-roam-new-file-directory
|
org-roam-directory))))
|
||||||
org-roam-directory)))))
|
absolute-file-path))
|
||||||
(if absolute
|
|
||||||
absolute-file-path
|
|
||||||
(file-relative-name absolute-file-path
|
|
||||||
(file-truename org-roam-directory)))))
|
|
||||||
|
|
||||||
(defun org-roam--get-template (&optional template-key)
|
(defvar org-roam--capture-file-name-default "%<%Y%m%d%H%M%S>"
|
||||||
"Return an Org-roam template. TEMPLATE-KEY is used to get a template."
|
"The default file name format for org-roam templates.")
|
||||||
(unless org-roam-templates
|
|
||||||
(user-error "No templates defined"))
|
|
||||||
(if template-key
|
|
||||||
(cadr (assoc template-key org-roam-templates))
|
|
||||||
(if (= (length org-roam-templates) 1)
|
|
||||||
(cadar org-roam-templates)
|
|
||||||
(cadr (assoc (completing-read "Template: " org-roam-templates)
|
|
||||||
org-roam-templates)))))
|
|
||||||
|
|
||||||
(defun org-roam--make-new-file (&optional info)
|
(defvar org-roam--capture-header-default "#+TITLE: ${title}\n"
|
||||||
(let ((template (org-roam--get-template (cdr (assoc 'template info))))
|
"The default file name format for org-roam templates.")
|
||||||
(title (or (cdr (assoc 'title info))
|
|
||||||
(completing-read "Title: " nil)))
|
(defvar org-roam--capture-file-path nil
|
||||||
file-name-fn file-path)
|
"The file path for the Org-roam capture. This variable is set
|
||||||
(fset 'file-name-fn (plist-get template :file))
|
during the Org-roam capture process.")
|
||||||
(setq file-path (org-roam--new-file-path (file-name-fn title) t))
|
|
||||||
(push (cons 'slug (org-roam--title-to-slug title)) info)
|
(defvar org-roam--capture-info nil
|
||||||
(unless (file-exists-p file-path)
|
"An alist of additional information passed to the org-roam
|
||||||
(org-roam--touch-file file-path)
|
template. This variable is populated dynamically, and is only
|
||||||
(write-region
|
non-nil during the org-roam capture process.")
|
||||||
(s-format (plist-get template :content)
|
|
||||||
'aget
|
(defvar org-roam--capture-context nil
|
||||||
info)
|
"A cons cell containing the context (search term) to get the
|
||||||
nil file-path nil))
|
exact point in a file. This variable is populated dynamically,
|
||||||
(org-roam--db-update-file file-path)
|
and is only active during an org-roam capture process.
|
||||||
|
|
||||||
|
E.g. ('title . \"New Title\")")
|
||||||
|
|
||||||
|
(defvar org-roam-capture-templates
|
||||||
|
'(("d" "default" plain (function org-roam--capture-get-point)
|
||||||
|
"%?"
|
||||||
|
:file-name "%<%Y%m%d%H%M%S>-${slug}"
|
||||||
|
:head "#+TITLE: ${title}\n"
|
||||||
|
:unnarrowed t))
|
||||||
|
"Capture templates for org-roam.")
|
||||||
|
|
||||||
|
(defun org-roam--fill-template (str &optional info)
|
||||||
|
"Return a file name from template STR."
|
||||||
|
(-> str
|
||||||
|
(s-format (lambda (key)
|
||||||
|
(or (s--aget info key)
|
||||||
|
(completing-read (format "%s: " key ) nil))) nil)
|
||||||
|
(org-capture-fill-template)))
|
||||||
|
|
||||||
|
(defun org-roam--capture-new-file ()
|
||||||
|
"Creates a new file, by reading the file-name attribute of the
|
||||||
|
currently active org-roam template. Returns the path to the new file."
|
||||||
|
(let* ((name-templ (or (org-capture-get :file-name)
|
||||||
|
org-roam--capture-file-name-default))
|
||||||
|
(new-id (s-trim (org-roam--fill-template
|
||||||
|
name-templ
|
||||||
|
org-roam--capture-info)))
|
||||||
|
(file-path (org-roam--new-file-path new-id)))
|
||||||
|
(org-roam--touch-file file-path)
|
||||||
|
(write-region
|
||||||
|
(org-roam--fill-template (or (org-capture-get :head)
|
||||||
|
org-roam--capture-header-default)
|
||||||
|
org-roam--capture-info)
|
||||||
|
nil file-path nil)
|
||||||
|
(sleep-for 0.2) ;; Hack: expand-file-name stringp nil error sporadically otherwise
|
||||||
|
file-path))
|
||||||
|
|
||||||
|
(defun org-roam--capture-get-point ()
|
||||||
|
"Returns exact point to file for org-capture-template.
|
||||||
|
The file to use is dependent on the context:
|
||||||
|
|
||||||
|
If the search is via title, it is assumed that the file does not
|
||||||
|
yet exist, and org-roam will attempt to create new file.
|
||||||
|
|
||||||
|
If the search is via ref, it is matched against the Org-roam database.
|
||||||
|
If there is no file with that ref, a file with that ref is created."
|
||||||
|
(pcase org-roam--capture-context
|
||||||
|
('title
|
||||||
|
(let ((file-path (org-roam--capture-new-file)))
|
||||||
|
(setq org-roam--capture-file-path file-path)
|
||||||
|
(set-buffer (org-capture-target-buffer file-path))
|
||||||
|
(widen)
|
||||||
|
(goto-char (point-max))))
|
||||||
|
('ref
|
||||||
|
(let* ((completions (org-roam--get-ref-path-completions))
|
||||||
|
(ref (cdr (assoc 'ref org-roam--capture-info)))
|
||||||
|
(file-path (or (cdr (assoc ref completions))
|
||||||
|
(org-roam--capture-new-file))))
|
||||||
|
(setq org-roam--capture-file-path file-path)
|
||||||
|
(set-buffer (org-capture-target-buffer file-path))
|
||||||
|
(widen)
|
||||||
|
(goto-char (point-max))))
|
||||||
|
(_ (error "Invalid org-roam-capture-context."))))
|
||||||
|
|
||||||
|
(defun org-roam-capture (&optional goto keys)
|
||||||
|
"Create a new file using an Org-roam template, and returns the
|
||||||
|
path to the edited file. The templates are defined at
|
||||||
|
`org-roam-capture-templates'."
|
||||||
|
(interactive "P")
|
||||||
|
(let ((org-capture-templates org-roam-capture-templates)
|
||||||
|
file-path)
|
||||||
|
(when (= (length org-capture-templates) 1)
|
||||||
|
(setq keys (caar org-capture-templates)))
|
||||||
|
(org-capture goto keys)
|
||||||
|
(setq file-path org-roam--capture-file-path)
|
||||||
|
(setq org-roam--capture-file-path nil)
|
||||||
file-path))
|
file-path))
|
||||||
|
|
||||||
;;; Interactive Commands
|
;;; Interactive Commands
|
||||||
;;;; org-roam-insert
|
;;;; org-roam-insert
|
||||||
|
(defvar org-roam--capture-insert-point nil
|
||||||
|
"The point to jump to after the call to `org-roam-insert'.")
|
||||||
|
|
||||||
(defun org-roam-insert (prefix)
|
(defun org-roam-insert (prefix)
|
||||||
"Find an org-roam file, and insert a relative org link to it at point.
|
"Find an org-roam file, and insert a relative org link to it at point.
|
||||||
If PREFIX, downcase the title before insertion."
|
If PREFIX, downcase the title before insertion."
|
||||||
@@ -669,22 +698,44 @@ If PREFIX, downcase the title before insertion."
|
|||||||
(completions (org-roam--get-title-path-completions))
|
(completions (org-roam--get-title-path-completions))
|
||||||
(title (completing-read "File: " completions nil nil region-text))
|
(title (completing-read "File: " completions nil nil region-text))
|
||||||
(region-or-title (or region-text title))
|
(region-or-title (or region-text title))
|
||||||
(absolute-file-path (or (cdr (assoc title completions))
|
(target-file-path (cdr (assoc title completions)))
|
||||||
(org-roam--make-new-file (list (cons 'title title)))))
|
|
||||||
(current-file-path (-> (or (buffer-base-buffer)
|
(current-file-path (-> (or (buffer-base-buffer)
|
||||||
(current-buffer))
|
(current-buffer))
|
||||||
(buffer-file-name)
|
(buffer-file-name)
|
||||||
(file-truename)
|
(file-truename)
|
||||||
(file-name-directory))))
|
(file-name-directory)))
|
||||||
(when region ;; Remove previously selected text.
|
(buf (current-buffer))
|
||||||
(goto-char (car region))
|
(p (point-marker)))
|
||||||
(delete-char (- (cdr region) (car region))))
|
(unless (and target-file-path
|
||||||
(insert (format "[[%s][%s]]"
|
(file-exists-p target-file-path))
|
||||||
(concat "file:" (file-relative-name absolute-file-path
|
(let* ((org-roam--capture-info (list (cons 'title title)
|
||||||
current-file-path))
|
(cons 'slug (org-roam--title-to-slug title))))
|
||||||
(format org-roam-link-title-format (if prefix
|
(org-roam--capture-context 'title))
|
||||||
(downcase region-or-title)
|
(setq target-file-path (org-roam-capture))))
|
||||||
region-or-title))))))
|
(with-current-buffer buf
|
||||||
|
(when region ;; Remove previously selected text.
|
||||||
|
(delete-region (car region) (cdr region)))
|
||||||
|
(let ((link-location (concat "file:" (file-relative-name target-file-path current-file-path)))
|
||||||
|
(description (format org-roam-link-title-format (if prefix
|
||||||
|
(downcase region-or-title)
|
||||||
|
region-or-title))))
|
||||||
|
(goto-char p)
|
||||||
|
(insert (format "[[%s][%s]]"
|
||||||
|
link-location
|
||||||
|
description))
|
||||||
|
(setq org-roam--capture-insert-point (point))))))
|
||||||
|
|
||||||
|
(defun org-roam--capture-advance-point ()
|
||||||
|
"Advances the point if it is updated.
|
||||||
|
We need this function because typically org-capture prevents the
|
||||||
|
point from being advanced, whereas when a link is inserted, the
|
||||||
|
point moves some characters forward. This is added as a hook to
|
||||||
|
`org-capture-after-finalize-hook'."
|
||||||
|
(when org-roam--capture-insert-point
|
||||||
|
(goto-char org-roam--capture-insert-point)
|
||||||
|
(setq org-roam--capture-insert-point nil)))
|
||||||
|
|
||||||
|
(add-hook 'org-capture-after-finalize-hook #'org-roam--capture-advance-point)
|
||||||
|
|
||||||
;;;; org-roam-find-file
|
;;;; org-roam-find-file
|
||||||
(defun org-roam--get-title-path-completions ()
|
(defun org-roam--get-title-path-completions ()
|
||||||
@@ -705,10 +756,14 @@ If PREFIX, downcase the title before insertion."
|
|||||||
"Find and open an org-roam file."
|
"Find and open an org-roam file."
|
||||||
(interactive)
|
(interactive)
|
||||||
(let* ((completions (org-roam--get-title-path-completions))
|
(let* ((completions (org-roam--get-title-path-completions))
|
||||||
(title-or-slug (completing-read "File: " completions))
|
(title (completing-read "File: " completions))
|
||||||
(absolute-file-path (or (cdr (assoc title-or-slug completions))
|
(file-path (cdr (assoc title completions))))
|
||||||
(org-roam--make-new-file (list (cons 'title title-or-slug))))))
|
(if file-path
|
||||||
(find-file absolute-file-path)))
|
(find-file file-path)
|
||||||
|
(let* ((org-roam--capture-info (list (cons 'title title)
|
||||||
|
(cons 'slug (org-roam--title-to-slug title))))
|
||||||
|
(org-roam--capture-context 'title))
|
||||||
|
(org-roam-capture '(4))))))
|
||||||
|
|
||||||
;;;; org-roam-find-ref
|
;;;; org-roam-find-ref
|
||||||
(defun org-roam--get-ref-path-completions ()
|
(defun org-roam--get-ref-path-completions ()
|
||||||
@@ -724,11 +779,8 @@ INFO is an alist containing additional information."
|
|||||||
(interactive)
|
(interactive)
|
||||||
(let* ((completions (org-roam--get-ref-path-completions))
|
(let* ((completions (org-roam--get-ref-path-completions))
|
||||||
(ref (or (cdr (assoc 'ref info))
|
(ref (or (cdr (assoc 'ref info))
|
||||||
(completing-read "Ref: " (org-roam--get-ref-path-completions))))
|
(completing-read "Ref: " (org-roam--get-ref-path-completions) nil t))))
|
||||||
(file-path (cdr (assoc ref completions))))
|
(find-file (cdr (assoc ref completions)))))
|
||||||
(if file-path
|
|
||||||
(find-file file-path)
|
|
||||||
(find-file (org-roam--make-new-file info)))))
|
|
||||||
|
|
||||||
;;;; org-roam-switch-to-buffer
|
;;;; org-roam-switch-to-buffer
|
||||||
(defun org-roam--get-roam-buffers ()
|
(defun org-roam--get-roam-buffers ()
|
||||||
@@ -756,10 +808,15 @@ INFO is an alist containing additional information."
|
|||||||
;;;; Daily notes
|
;;;; Daily notes
|
||||||
(defun org-roam--file-for-time (time)
|
(defun org-roam--file-for-time (time)
|
||||||
"Create and find file for TIME."
|
"Create and find file for TIME."
|
||||||
(let* ((org-roam-templates (list (list "daily" (list :file (lambda (title) title)
|
(let* ((title (format-time-string "%Y-%m-%d" time))
|
||||||
:content "#+TITLE: ${title}")))))
|
(org-roam-capture-templates (list (list "d" "daily" 'plain (list 'function #'org-roam--capture-get-point)
|
||||||
(org-roam--make-new-file (list (cons 'title (format-time-string "%Y-%m-%d" time))
|
""
|
||||||
(cons 'template "daily")))))
|
:immediate-finish t
|
||||||
|
:file-name "${title}"
|
||||||
|
:head "#+TITLE: ${title}")))
|
||||||
|
(org-roam--capture-context 'title)
|
||||||
|
(org-roam--capture-info (list (cons 'title title))))
|
||||||
|
(org-roam-capture)))
|
||||||
|
|
||||||
(defun org-roam-today ()
|
(defun org-roam-today ()
|
||||||
"Create and find file for today."
|
"Create and find file for today."
|
||||||
@@ -786,7 +843,6 @@ INFO is an alist containing additional information."
|
|||||||
(let ((path (org-roam--file-for-time time)))
|
(let ((path (org-roam--file-for-time time)))
|
||||||
(org-roam--find-file path))))
|
(org-roam--find-file path))))
|
||||||
|
|
||||||
|
|
||||||
;;; The org-roam buffer
|
;;; The org-roam buffer
|
||||||
;;;; org-roam-link-face
|
;;;; org-roam-link-face
|
||||||
(defface org-roam-link
|
(defface org-roam-link
|
||||||
@@ -968,6 +1024,28 @@ Valid states are 'visible, 'exists and 'none."
|
|||||||
('none (org-roam--setup-buffer))))
|
('none (org-roam--setup-buffer))))
|
||||||
|
|
||||||
;;; The graphviz links graph
|
;;; The graphviz links graph
|
||||||
|
;;;; Options
|
||||||
|
(defcustom org-roam-graph-viewer (executable-find "firefox")
|
||||||
|
"Path to executable for viewing SVG."
|
||||||
|
:type 'string
|
||||||
|
:group 'org-roam)
|
||||||
|
|
||||||
|
(defcustom org-roam-graphviz-executable (executable-find "dot")
|
||||||
|
"Path to graphviz executable."
|
||||||
|
:type 'string
|
||||||
|
:group 'org-roam)
|
||||||
|
|
||||||
|
(defcustom org-roam-graph-max-title-length 100
|
||||||
|
"Maximum length of titles in Graphviz graph nodes."
|
||||||
|
:type 'number
|
||||||
|
:group 'org-roam)
|
||||||
|
|
||||||
|
(defcustom org-roam-graph-node-shape "ellipse"
|
||||||
|
"Shape of Graphviz nodes."
|
||||||
|
:type 'string
|
||||||
|
:group 'org-roam)
|
||||||
|
|
||||||
|
;;;; Functions
|
||||||
(defun org-roam--build-graph ()
|
(defun org-roam--build-graph ()
|
||||||
"Build the Graphviz string.
|
"Build the Graphviz string.
|
||||||
The Org-roam database titles table is read, to obtain the list of titles.
|
The Org-roam database titles table is read, to obtain the list of titles.
|
||||||
|
Reference in New Issue
Block a user