2020-05-12 19:06:19 +00:00
|
|
|
|
;;; org-roam-capture.el --- Capture functionality -*- coding: utf-8; lexical-binding: t; -*-
|
2020-03-28 13:16:28 +00:00
|
|
|
|
|
|
|
|
|
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
|
|
|
|
|
|
|
|
|
|
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
2020-05-10 05:48:16 +00:00
|
|
|
|
;; URL: https://github.com/org-roam/org-roam
|
2020-03-28 13:16:28 +00:00
|
|
|
|
;; Keywords: org-mode, roam, convenience
|
2020-07-26 17:03:35 +00:00
|
|
|
|
;; Version: 1.2.1
|
2020-05-27 05:20:06 +00:00
|
|
|
|
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.0"))
|
2020-03-28 13:16:28 +00:00
|
|
|
|
|
|
|
|
|
;; This file is NOT part of GNU Emacs.
|
|
|
|
|
|
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
|
|
|
;; it under the terms of the GNU General Public License as published by
|
|
|
|
|
;; the Free Software Foundation; either version 3, or (at your option)
|
|
|
|
|
;; any later version.
|
|
|
|
|
;;
|
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
|
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
;; GNU General Public License for more details.
|
|
|
|
|
;;
|
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
|
|
|
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
|
|
|
|
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
|
|
|
;; Boston, MA 02110-1301, USA.
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
;;
|
|
|
|
|
;; This library provides capture functionality for org-roam
|
|
|
|
|
;;; Code:
|
|
|
|
|
;;;; Library Requires
|
|
|
|
|
(require 'org-capture)
|
2020-07-27 18:16:39 +00:00
|
|
|
|
(require 'org-roam-macs)
|
2020-03-28 13:16:28 +00:00
|
|
|
|
(require 'dash)
|
|
|
|
|
(require 's)
|
2020-05-09 10:08:08 +00:00
|
|
|
|
(require 'cl-lib)
|
2020-03-28 13:16:28 +00:00
|
|
|
|
|
|
|
|
|
;; Declarations
|
|
|
|
|
(defvar org-roam-encrypt-files)
|
|
|
|
|
(defvar org-roam-directory)
|
2020-05-28 04:25:19 +00:00
|
|
|
|
(defvar org-roam-mode)
|
2020-06-17 06:58:11 +00:00
|
|
|
|
(defvar org-roam-title-to-slug-function)
|
2020-04-07 05:53:22 +00:00
|
|
|
|
(declare-function org-roam--get-title-path-completions "org-roam")
|
|
|
|
|
(declare-function org-roam--get-ref-path-completions "org-roam")
|
|
|
|
|
(declare-function org-roam--file-path-from-id "org-roam")
|
2020-06-16 07:37:03 +00:00
|
|
|
|
(declare-function org-roam--find-file "org-roam")
|
2020-04-07 05:53:22 +00:00
|
|
|
|
(declare-function org-roam--format-link "org-roam")
|
2020-05-28 04:25:19 +00:00
|
|
|
|
(declare-function org-roam-mode "org-roam")
|
2020-04-07 05:53:22 +00:00
|
|
|
|
(declare-function org-roam-completion--completing-read "org-roam-completion")
|
2020-03-28 13:16:28 +00:00
|
|
|
|
|
|
|
|
|
(defvar org-roam-capture--file-name-default "%<%Y%m%d%H%M%S>"
|
|
|
|
|
"The default file name format for Org-roam templates.")
|
|
|
|
|
|
2020-06-07 05:55:07 +00:00
|
|
|
|
(defvar org-roam-capture--header-default "#+title: ${title}\n"
|
2020-03-28 13:16:28 +00:00
|
|
|
|
"The default capture header for Org-roam templates.")
|
|
|
|
|
|
|
|
|
|
(defvar org-roam-capture--file-path nil
|
|
|
|
|
"The file path for the Org-roam capture.
|
|
|
|
|
This variable is set during the Org-roam capture process.")
|
|
|
|
|
|
|
|
|
|
(defvar org-roam-capture--info nil
|
|
|
|
|
"An alist of additional information passed to the Org-roam template.
|
|
|
|
|
This variable is populated dynamically, and is only non-nil
|
|
|
|
|
during the Org-roam capture process.")
|
|
|
|
|
|
|
|
|
|
(defvar org-roam-capture--context nil
|
|
|
|
|
"A symbol, that reflects the context for obtaining the exact point in a file.
|
|
|
|
|
This variable is populated dynamically, and is only active during
|
|
|
|
|
an Org-roam capture process.
|
|
|
|
|
|
|
|
|
|
The `title' context is used in `org-roam-insert' and
|
|
|
|
|
`org-roam-find-file', where the capture process is triggered upon
|
|
|
|
|
trying to create a new file without that `title'.
|
|
|
|
|
|
|
|
|
|
The `ref' context is used by `org-roam-protocol', where the
|
|
|
|
|
capture process is triggered upon trying to find or create a new
|
|
|
|
|
note with the given `ref'.")
|
|
|
|
|
|
|
|
|
|
(defvar org-roam-capture-additional-template-props nil
|
|
|
|
|
"Additional props to be added to the Org-roam template.")
|
|
|
|
|
|
|
|
|
|
(defconst org-roam-capture--template-keywords '(:file-name :head)
|
|
|
|
|
"Keywords used in `org-roam-capture-templates' specific to Org-roam.")
|
|
|
|
|
|
2020-07-11 18:48:10 +00:00
|
|
|
|
(defcustom org-roam-capture-templates
|
2020-03-28 13:16:28 +00:00
|
|
|
|
'(("d" "default" plain (function org-roam-capture--get-point)
|
|
|
|
|
"%?"
|
|
|
|
|
:file-name "%<%Y%m%d%H%M%S>-${slug}"
|
2020-06-07 05:55:07 +00:00
|
|
|
|
:head "#+title: ${title}\n"
|
2020-03-28 13:16:28 +00:00
|
|
|
|
:unnarrowed t))
|
|
|
|
|
"Capture templates for Org-roam.
|
2020-07-11 18:48:10 +00:00
|
|
|
|
The Org-roam capture-templates builds on the default behaviours of
|
|
|
|
|
`org-capture-templates' by expanding them in 3 areas:
|
2020-03-28 13:16:28 +00:00
|
|
|
|
|
2020-07-11 18:48:10 +00:00
|
|
|
|
1. Template-expansion capabilities are extended with additional
|
|
|
|
|
custom syntax. See `org-roam-capture--fill-template' for more
|
|
|
|
|
details.
|
2020-03-28 13:16:28 +00:00
|
|
|
|
|
2020-07-11 18:48:10 +00:00
|
|
|
|
2. The `:file-name' key is added, which defines the naming format
|
|
|
|
|
to use when creating new notes. This file-name is relative to
|
|
|
|
|
`org-roam-directory', and is without the file-extension.
|
2020-03-28 13:16:28 +00:00
|
|
|
|
|
|
|
|
|
3. The `:head' key is added, which contains the template that is
|
2020-07-11 18:48:10 +00:00
|
|
|
|
inserted upon the creation of a new file. This is where you
|
2020-07-27 09:43:23 +00:00
|
|
|
|
your note metadata should go.
|
2020-07-11 18:48:10 +00:00
|
|
|
|
|
|
|
|
|
Each template should have the following structure:
|
|
|
|
|
|
|
|
|
|
\(KEY DESCRIPTION `plain' `(function org-roam-capture--get-point)'
|
|
|
|
|
TEMPLATE
|
|
|
|
|
`:file-name' FILENAME-FORMAT
|
|
|
|
|
`:head' HEADER-FORMAT
|
|
|
|
|
`:unnarrowed t'
|
|
|
|
|
OPTIONS-PLIST)
|
|
|
|
|
|
|
|
|
|
The elements of a template-entry and their placement are the same
|
|
|
|
|
as in `org-capture-templates', except that the entry type must
|
|
|
|
|
always be the symbol `plain', and that the target must always be
|
|
|
|
|
the list `(function org-roam-capture--get-point)'.
|
|
|
|
|
|
|
|
|
|
Org-roam requires the plist elements `:file-name' and `:head' to
|
|
|
|
|
be present, and it’s recommended that `:unnarrowed' be set to t."
|
|
|
|
|
:group 'org-roam
|
|
|
|
|
;; Adapted from `org-capture-templates'
|
|
|
|
|
:type
|
|
|
|
|
'(repeat
|
|
|
|
|
(choice :value ("d" "default" plain (function org-roam-capture--get-point)
|
|
|
|
|
"%?"
|
|
|
|
|
:file-name "%<%Y%m%d%H%M%S>-${slug}"
|
|
|
|
|
:head "#+title: ${title}\n"
|
|
|
|
|
:unnarrowed t)
|
|
|
|
|
(list :tag "Multikey description"
|
|
|
|
|
(string :tag "Keys ")
|
|
|
|
|
(string :tag "Description"))
|
|
|
|
|
(list :tag "Template entry"
|
|
|
|
|
(string :tag "Keys ")
|
|
|
|
|
(string :tag "Description ")
|
|
|
|
|
(const :format "" plain)
|
|
|
|
|
(const :format "" (function org-roam-capture--get-point))
|
|
|
|
|
(choice :tag "Template "
|
|
|
|
|
(string :tag "String"
|
|
|
|
|
:format "String:\n \
|
|
|
|
|
Template string :\n%v")
|
|
|
|
|
(list :tag "File"
|
|
|
|
|
(const :format "" file)
|
|
|
|
|
(file :tag "Template file "))
|
|
|
|
|
(list :tag "Function"
|
|
|
|
|
(const :format "" function)
|
|
|
|
|
(function :tag "Template function ")))
|
|
|
|
|
(const :format "File name format :" :file-name)
|
|
|
|
|
(string :format " %v" :value "#+title: ${title}\n")
|
|
|
|
|
(const :format "Header format :" :head)
|
|
|
|
|
(string :format "\n%v" :value "%<%Y%m%d%H%M%S>-${slug}")
|
|
|
|
|
(const :format "" :unnarrowed) (const :format "" t)
|
|
|
|
|
(plist :inline t
|
|
|
|
|
:tag "Options"
|
|
|
|
|
;; Give the most common options as checkboxes
|
|
|
|
|
:options
|
|
|
|
|
(((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 " :table-line-pos) (string))
|
|
|
|
|
((const :format "%v " :kill-buffer) (const t))))))))
|
2020-03-28 13:16:28 +00:00
|
|
|
|
|
2020-07-26 07:15:56 +00:00
|
|
|
|
(defcustom org-roam-capture-immediate-template
|
2020-06-15 10:34:27 +00:00
|
|
|
|
(append (car org-roam-capture-templates) '(:immediate-finish t))
|
|
|
|
|
"Capture template to use for immediate captures in Org-roam.
|
|
|
|
|
This is a single template, so do not enclose it into a list.
|
2020-07-26 07:15:56 +00:00
|
|
|
|
See `org-roam-capture-templates' for details on templates."
|
|
|
|
|
:group 'org-roam
|
|
|
|
|
;; Adapted from `org-capture-templates'
|
|
|
|
|
:type
|
|
|
|
|
'(list :tag "Template entry"
|
|
|
|
|
:value ("d" "default" plain (function org-roam-capture--get-point)
|
|
|
|
|
"%?"
|
|
|
|
|
:file-name "%<%Y%m%d%H%M%S>-${slug}"
|
|
|
|
|
:head "#+title: ${title}\n"
|
|
|
|
|
:unnarrowed t
|
|
|
|
|
:immediate-finish t)
|
|
|
|
|
(string :tag "Keys ")
|
|
|
|
|
(string :tag "Description ")
|
|
|
|
|
(const :format "" plain)
|
|
|
|
|
(const :format "" (function org-roam-capture--get-point))
|
|
|
|
|
(choice :tag "Template "
|
|
|
|
|
(string :tag "String"
|
|
|
|
|
:format "String:\n \
|
|
|
|
|
Template string :\n%v")
|
|
|
|
|
(list :tag "File"
|
|
|
|
|
(const :format "" file)
|
|
|
|
|
(file :tag "Template file "))
|
|
|
|
|
(list :tag "Function"
|
|
|
|
|
(const :format "" function)
|
|
|
|
|
(function :tag "Template function ")))
|
|
|
|
|
(const :format "File name format :" :file-name)
|
|
|
|
|
(string :format " %v" :value "#+title: ${title}\n")
|
|
|
|
|
(const :format "Header format :" :head)
|
|
|
|
|
(string :format "\n%v" :value "%<%Y%m%d%H%M%S>-${slug}")
|
|
|
|
|
(const :format "" :unnarrowed) (const :format "" t)
|
|
|
|
|
(const :format "" :immediate-finish) (const :format "" t)
|
|
|
|
|
(plist :inline t
|
|
|
|
|
:tag "Options"
|
|
|
|
|
;; Give the most common options as checkboxes
|
|
|
|
|
:options
|
|
|
|
|
(((const :format "%v " :prepend) (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 " :table-line-pos) (string))
|
|
|
|
|
((const :format "%v " :kill-buffer) (const t))))))
|
2020-06-15 10:34:27 +00:00
|
|
|
|
|
2020-07-26 07:15:56 +00:00
|
|
|
|
(defcustom org-roam-capture-ref-templates
|
2020-03-28 13:16:28 +00:00
|
|
|
|
'(("r" "ref" plain (function org-roam-capture--get-point)
|
2020-07-26 07:15:56 +00:00
|
|
|
|
"%?"
|
2020-03-28 13:16:28 +00:00
|
|
|
|
:file-name "${slug}"
|
2020-07-26 07:15:56 +00:00
|
|
|
|
:head "#+title: ${title}\n#+roam_key: ${ref}\n"
|
2020-03-28 13:16:28 +00:00
|
|
|
|
:unnarrowed t))
|
|
|
|
|
"The Org-roam templates used during a capture from the roam-ref protocol.
|
2020-07-26 07:15:56 +00:00
|
|
|
|
Details on how to specify for the template is given in `org-roam-capture-templates'."
|
|
|
|
|
:group 'org-roam
|
|
|
|
|
;; Adapted from `org-capture-templates'
|
|
|
|
|
:type
|
|
|
|
|
'(repeat
|
|
|
|
|
(choice :value ("d" "default" plain (function org-roam-capture--get-point)
|
|
|
|
|
"%?"
|
|
|
|
|
:file-name "${slug}"
|
|
|
|
|
:head "#+title: ${title}\n#+roam_key: ${ref}\n"
|
|
|
|
|
:unnarrowed t)
|
|
|
|
|
(list :tag "Multikey description"
|
|
|
|
|
(string :tag "Keys ")
|
|
|
|
|
(string :tag "Description"))
|
|
|
|
|
(list :tag "Template entry"
|
|
|
|
|
(string :tag "Keys ")
|
|
|
|
|
(string :tag "Description ")
|
|
|
|
|
(const :format "" plain)
|
|
|
|
|
(const :format "" (function org-roam-capture--get-point))
|
|
|
|
|
(choice :tag "Template "
|
|
|
|
|
(string :tag "String"
|
|
|
|
|
:format "String:\n \
|
|
|
|
|
Template string :\n%v")
|
|
|
|
|
(list :tag "File"
|
|
|
|
|
(const :format "" file)
|
|
|
|
|
(file :tag "Template file "))
|
|
|
|
|
(list :tag "Function"
|
|
|
|
|
(const :format "" function)
|
|
|
|
|
(function :tag "Template function ")))
|
|
|
|
|
(const :format "File name format :" :file-name)
|
|
|
|
|
(string :format " %v" :value "#+title: ${title}\n")
|
|
|
|
|
(const :format "Header format :" :head)
|
|
|
|
|
(string :format "\n%v" :value "%<%Y%m%d%H%M%S>-${slug}")
|
|
|
|
|
(const :format "" :unnarrowed) (const :format "" t)
|
|
|
|
|
(plist :inline t
|
|
|
|
|
:tag "Options"
|
|
|
|
|
;; Give the most common options as checkboxes
|
|
|
|
|
:options
|
|
|
|
|
(((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 " :table-line-pos) (string))
|
|
|
|
|
((const :format "%v " :kill-buffer) (const t))))))))
|
2020-03-28 13:16:28 +00:00
|
|
|
|
|
2020-07-26 16:21:41 +00:00
|
|
|
|
(defun org-roam-capture-p ()
|
|
|
|
|
"Return t if the current capture process is an Org-roam capture.
|
|
|
|
|
This function is to only be called when org-capture-plist is
|
|
|
|
|
valid for the capture (i.e. initialization, and finalization of
|
|
|
|
|
the capture)."
|
|
|
|
|
(plist-get org-capture-plist :org-roam))
|
|
|
|
|
|
2020-03-28 13:16:28 +00:00
|
|
|
|
(defun org-roam-capture--get (keyword)
|
2020-07-26 16:21:41 +00:00
|
|
|
|
"Get the value for KEYWORD from the `org-roam-capture-template'."
|
2020-03-28 13:16:28 +00:00
|
|
|
|
(plist-get (plist-get org-capture-plist :org-roam) keyword))
|
|
|
|
|
|
|
|
|
|
(defun org-roam-capture--put (&rest stuff)
|
2020-07-26 16:21:41 +00:00
|
|
|
|
"Put properties from STUFF into the `org-roam-capture-template'."
|
2020-03-28 13:16:28 +00:00
|
|
|
|
(let ((p (plist-get org-capture-plist :org-roam)))
|
|
|
|
|
(while stuff
|
2020-07-26 16:21:41 +00:00
|
|
|
|
(setq p (plist-put p (pop stuff) (pop stuff))))
|
2020-03-28 13:16:28 +00:00
|
|
|
|
(setq org-capture-plist
|
|
|
|
|
(plist-put org-capture-plist :org-roam p))))
|
|
|
|
|
|
2020-07-26 16:21:41 +00:00
|
|
|
|
;; FIXME: Pending upstream patch
|
|
|
|
|
;; https://orgmode.org/list/87h7tv9pkm.fsf@hidden/T/#u
|
|
|
|
|
;;
|
|
|
|
|
;; Org-capture's behaviour right now is that `org-capture-plist' is valid only
|
|
|
|
|
;; during the initialization of the Org-capture buffer. The value of
|
|
|
|
|
;; `org-capture-plist' is saved into buffer-local `org-capture-current-plist'.
|
|
|
|
|
;; However, the value for that particular capture is no longer accessible for
|
|
|
|
|
;; hooks in `org-capture-after-finalize-hook', since the capture buffer has been
|
|
|
|
|
;; cleaned up.
|
|
|
|
|
;;
|
|
|
|
|
;; This advice restores the global `org-capture-plist' during finalization, so
|
|
|
|
|
;; the plist is valid during both initialization and finalization of the
|
|
|
|
|
;; capture.
|
|
|
|
|
(defun org-roam-capture--update-plist (&optional _)
|
|
|
|
|
"Update global plist from local var."
|
|
|
|
|
(setq org-capture-plist org-capture-current-plist))
|
|
|
|
|
|
|
|
|
|
(advice-add 'org-capture-finalize :before #'org-roam-capture--update-plist)
|
|
|
|
|
|
|
|
|
|
(defun org-roam-capture--finalize ()
|
|
|
|
|
"Finalize the `org-roam-capture' process."
|
2020-07-27 18:16:39 +00:00
|
|
|
|
(let* ((finalize (org-roam-capture--get :finalize))
|
|
|
|
|
;; In case any regions were shielded before, unshield them
|
|
|
|
|
(region (when-let ((region (org-roam-capture--get :region)))
|
|
|
|
|
(org-roam-unshield-region (car region) (cdr region))))
|
|
|
|
|
(beg (car region))
|
|
|
|
|
(end (cdr region)))
|
|
|
|
|
(unless org-note-abort
|
|
|
|
|
(pcase finalize
|
|
|
|
|
('find-file
|
|
|
|
|
(when-let ((file-path (org-roam-capture--get :file-path)))
|
|
|
|
|
(org-roam--find-file file-path)
|
|
|
|
|
(run-hooks 'org-roam-capture-after-find-file-hook)))
|
|
|
|
|
('insert-link
|
|
|
|
|
(when-let* ((mkr (org-roam-capture--get :insert-at))
|
|
|
|
|
(buf (marker-buffer mkr)))
|
|
|
|
|
(with-current-buffer buf
|
|
|
|
|
(when region
|
|
|
|
|
(delete-region (car region) (cdr region)))
|
|
|
|
|
(let ((path (org-roam-capture--get :file-path))
|
|
|
|
|
(desc (org-roam-capture--get :link-description)))
|
|
|
|
|
(if (eq (point) (marker-position mkr))
|
|
|
|
|
(insert (org-roam--format-link path desc))
|
|
|
|
|
(org-with-point-at mkr
|
|
|
|
|
(insert (org-roam--format-link path desc))))))))))
|
|
|
|
|
(when region
|
|
|
|
|
(set-marker beg nil)
|
|
|
|
|
(set-marker end nil))
|
|
|
|
|
(org-roam-capture--save-file-maybe)
|
|
|
|
|
(remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--finalize)))
|
2020-07-26 16:21:41 +00:00
|
|
|
|
|
|
|
|
|
(defun org-roam-capture--install-finalize ()
|
|
|
|
|
"Install `org-roam-capture--finalize' if the capture is an Org-roam capture."
|
|
|
|
|
(when (org-roam-capture-p)
|
|
|
|
|
(add-hook 'org-capture-after-finalize-hook #'org-roam-capture--finalize)))
|
|
|
|
|
|
|
|
|
|
(add-hook 'org-capture-prepare-finalize-hook #'org-roam-capture--install-finalize)
|
2020-04-10 09:03:49 +00:00
|
|
|
|
|
2020-07-20 14:14:21 +00:00
|
|
|
|
(defun org-roam-capture--fill-template (str)
|
2020-07-26 16:21:41 +00:00
|
|
|
|
"Expand the template STR, returning the string.
|
2020-03-28 13:16:28 +00:00
|
|
|
|
This is an extension of org-capture's template expansion.
|
|
|
|
|
|
2020-07-20 14:14:21 +00:00
|
|
|
|
First, it expands ${var} occurrences in STR, using `org-roam-capture--info'.
|
2020-03-28 13:16:28 +00:00
|
|
|
|
If there is a ${var} with no matching var in the alist, the value
|
|
|
|
|
of var is prompted for via `completing-read'.
|
|
|
|
|
|
|
|
|
|
Next, it expands the remaining template string using
|
|
|
|
|
`org-capture-fill-template'."
|
|
|
|
|
(-> str
|
|
|
|
|
(s-format (lambda (key)
|
2020-07-20 14:14:21 +00:00
|
|
|
|
(or (s--aget org-roam-capture--info key)
|
|
|
|
|
(when-let ((val (completing-read (format "%s: " key) nil)))
|
|
|
|
|
(push (cons key val) org-roam-capture--info)
|
|
|
|
|
val))) nil)
|
2020-03-28 13:16:28 +00:00
|
|
|
|
(org-capture-fill-template)))
|
|
|
|
|
|
2020-07-26 16:21:41 +00:00
|
|
|
|
(defun org-roam-capture--save-file-maybe ()
|
2020-03-28 13:16:28 +00:00
|
|
|
|
"Save the file conditionally.
|
|
|
|
|
The file is saved if the original value of :no-save is not t and
|
|
|
|
|
`org-note-abort' is not t. It is added to
|
|
|
|
|
`org-capture-after-finalize-hook'."
|
|
|
|
|
(cond
|
|
|
|
|
((and (org-roam-capture--get :new-file)
|
|
|
|
|
org-note-abort)
|
|
|
|
|
(with-current-buffer (org-capture-get :buffer)
|
|
|
|
|
(set-buffer-modified-p nil)
|
|
|
|
|
(kill-buffer)))
|
|
|
|
|
((and (not (org-roam-capture--get :orig-no-save))
|
|
|
|
|
(not org-note-abort))
|
|
|
|
|
(with-current-buffer (org-capture-get :buffer)
|
2020-07-26 16:21:41 +00:00
|
|
|
|
(save-buffer)))))
|
2020-03-28 13:16:28 +00:00
|
|
|
|
|
|
|
|
|
(defun org-roam-capture--new-file ()
|
|
|
|
|
"Return the path to the new file during an Org-roam capture.
|
|
|
|
|
|
|
|
|
|
This function reads the file-name attribute of the currently
|
|
|
|
|
active Org-roam template.
|
|
|
|
|
|
|
|
|
|
If the file path already exists, it throw an error.
|
|
|
|
|
|
|
|
|
|
Else, to insert the header content in the file, `org-capture'
|
|
|
|
|
prepends the `:head' property of the Org-roam capture template.
|
|
|
|
|
|
|
|
|
|
To prevent the creation of a new file if the capture process is
|
|
|
|
|
aborted, we do the following:
|
|
|
|
|
|
|
|
|
|
1. Save the original value of the capture template's :no-save.
|
|
|
|
|
|
|
|
|
|
2. Set the capture template's :no-save to t.
|
|
|
|
|
|
2020-07-26 16:21:41 +00:00
|
|
|
|
3. Add a function on `org-capture-before-finalize-hook' that saves
|
2020-03-28 13:16:28 +00:00
|
|
|
|
the file if the original value of :no-save is not t and
|
|
|
|
|
`org-note-abort' is not t."
|
|
|
|
|
(let* ((name-templ (or (org-roam-capture--get :file-name)
|
|
|
|
|
org-roam-capture--file-name-default))
|
|
|
|
|
(new-id (s-trim (org-roam-capture--fill-template
|
2020-07-20 14:14:21 +00:00
|
|
|
|
name-templ)))
|
2020-03-28 13:16:28 +00:00
|
|
|
|
(file-path (org-roam--file-path-from-id new-id))
|
|
|
|
|
(roam-head (or (org-roam-capture--get :head)
|
|
|
|
|
org-roam-capture--header-default))
|
|
|
|
|
(org-template (org-capture-get :template))
|
|
|
|
|
(roam-template (concat roam-head org-template)))
|
2020-04-16 19:25:16 +00:00
|
|
|
|
(unless (file-exists-p file-path)
|
2020-04-17 04:39:03 +00:00
|
|
|
|
(make-directory (file-name-directory file-path) t)
|
2020-04-16 19:25:16 +00:00
|
|
|
|
(org-roam-capture--put :orig-no-save (org-capture-get :no-save)
|
|
|
|
|
:new-file t)
|
|
|
|
|
(org-capture-put :template
|
2020-04-17 04:39:03 +00:00
|
|
|
|
;; Fixes org-capture-place-plain-text throwing 'invalid search bound'
|
|
|
|
|
;; when both :unnarowed t and "%?" is missing from the template string;
|
|
|
|
|
;; may become unnecessary when the upstream bug is fixed
|
|
|
|
|
(if (s-contains-p "%?" roam-template)
|
|
|
|
|
roam-template
|
|
|
|
|
(concat roam-template "%?"))
|
|
|
|
|
:type 'plain
|
|
|
|
|
:no-save t))
|
2020-03-28 13:16:28 +00:00
|
|
|
|
file-path))
|
|
|
|
|
|
|
|
|
|
(defun org-roam-capture--get-point ()
|
|
|
|
|
"Return 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.
|
|
|
|
|
|
2020-04-16 19:25:16 +00:00
|
|
|
|
If the search is via daily notes, 'time will be passed via
|
|
|
|
|
`org-roam-capture--info'. This is used to alter the default time
|
|
|
|
|
in `org-capture-templates'.
|
|
|
|
|
|
2020-03-28 13:16:28 +00:00
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
This function is used solely in Org-roam's capture templates: see
|
|
|
|
|
`org-roam-capture-templates'."
|
|
|
|
|
(let ((file-path (pcase org-roam-capture--context
|
2020-03-29 09:29:57 +00:00
|
|
|
|
('capture
|
|
|
|
|
(or (cdr (assoc 'file org-roam-capture--info))
|
|
|
|
|
(org-roam-capture--new-file)))
|
2020-03-28 13:16:28 +00:00
|
|
|
|
('title
|
|
|
|
|
(org-roam-capture--new-file))
|
2020-04-16 19:25:16 +00:00
|
|
|
|
('dailies
|
|
|
|
|
(org-capture-put :default-time (cdr (assoc 'time org-roam-capture--info)))
|
|
|
|
|
(org-roam-capture--new-file))
|
2020-03-28 13:16:28 +00:00
|
|
|
|
('ref
|
|
|
|
|
(let ((completions (org-roam--get-ref-path-completions))
|
|
|
|
|
(ref (cdr (assoc 'ref org-roam-capture--info))))
|
2020-05-21 05:14:06 +00:00
|
|
|
|
(if-let ((pl (cdr (assoc ref completions))))
|
|
|
|
|
(plist-get pl :path)
|
|
|
|
|
(org-roam-capture--new-file))))
|
2020-03-28 13:16:28 +00:00
|
|
|
|
(_ (error "Invalid org-roam-capture-context")))))
|
2020-07-20 14:14:21 +00:00
|
|
|
|
(org-capture-put :template
|
|
|
|
|
(org-roam-capture--fill-template (org-capture-get :template)))
|
2020-03-28 13:16:28 +00:00
|
|
|
|
(org-roam-capture--put :file-path file-path)
|
|
|
|
|
(while org-roam-capture-additional-template-props
|
|
|
|
|
(let ((prop (pop org-roam-capture-additional-template-props))
|
|
|
|
|
(val (pop org-roam-capture-additional-template-props)))
|
|
|
|
|
(org-roam-capture--put prop val)))
|
|
|
|
|
(set-buffer (org-capture-target-buffer file-path))
|
|
|
|
|
(widen)
|
|
|
|
|
(goto-char (point-max))))
|
|
|
|
|
|
|
|
|
|
(defun org-roam-capture--convert-template (template)
|
|
|
|
|
"Convert TEMPLATE from Org-roam syntax to `org-capture-templates' syntax."
|
2020-05-09 10:08:08 +00:00
|
|
|
|
(pcase template
|
|
|
|
|
(`(,_key ,_description) template)
|
|
|
|
|
(`(,key ,description ,type ,target . ,rest)
|
|
|
|
|
(let ((converted `(,key ,description ,type ,target
|
|
|
|
|
,(unless (keywordp (car rest)) (pop rest))))
|
|
|
|
|
org-roam-plist
|
|
|
|
|
options)
|
|
|
|
|
(while rest
|
|
|
|
|
(let* ((key (pop rest))
|
|
|
|
|
(val (pop rest))
|
|
|
|
|
(custom (member key org-roam-capture--template-keywords)))
|
|
|
|
|
(push val (if custom org-roam-plist options))
|
|
|
|
|
(push key (if custom org-roam-plist options))))
|
|
|
|
|
(append converted options `(:org-roam ,org-roam-plist))))
|
|
|
|
|
(_ (user-error "Invalid capture template format: %s" template))))
|
2020-03-28 13:16:28 +00:00
|
|
|
|
|
2020-04-19 14:45:09 +00:00
|
|
|
|
(defcustom org-roam-capture-after-find-file-hook nil
|
|
|
|
|
"Hook that is run right after an Org-roam capture process is finalized.
|
|
|
|
|
Suitable for moving point."
|
|
|
|
|
:group 'org-roam
|
|
|
|
|
:type 'hook)
|
|
|
|
|
|
2020-07-01 15:24:40 +00:00
|
|
|
|
(defcustom org-roam-capture-function #'org-capture
|
|
|
|
|
"Function that is invoked to start the `org-capture' process."
|
|
|
|
|
:group 'org-roam
|
|
|
|
|
:type 'function)
|
|
|
|
|
|
2020-05-04 07:17:39 +00:00
|
|
|
|
(defun org-roam-capture--capture (&optional goto keys)
|
2020-03-28 13:16:28 +00:00
|
|
|
|
"Create a new file, and return the path to the edited file.
|
|
|
|
|
The templates are defined at `org-roam-capture-templates'. The
|
|
|
|
|
GOTO and KEYS argument have the same functionality as
|
|
|
|
|
`org-capture'."
|
2020-07-01 15:24:40 +00:00
|
|
|
|
(let* ((org-capture-templates (mapcar #'org-roam-capture--convert-template org-roam-capture-templates))
|
|
|
|
|
(one-template-p (= (length org-capture-templates) 1))
|
|
|
|
|
org-capture-templates-contexts)
|
|
|
|
|
(when one-template-p
|
2020-03-28 13:16:28 +00:00
|
|
|
|
(setq keys (caar org-capture-templates)))
|
2020-07-01 15:24:40 +00:00
|
|
|
|
(if (or one-template-p
|
|
|
|
|
(eq org-roam-capture-function 'org-capture))
|
|
|
|
|
(org-capture goto keys)
|
|
|
|
|
(funcall-interactively org-roam-capture-function))))
|
2020-03-28 13:16:28 +00:00
|
|
|
|
|
2020-04-07 05:53:22 +00:00
|
|
|
|
;;;###autoload
|
|
|
|
|
(defun org-roam-capture ()
|
|
|
|
|
"Launches an `org-capture' process for a new or existing note.
|
|
|
|
|
This uses the templates defined at `org-roam-capture-templates'."
|
|
|
|
|
(interactive)
|
2020-05-28 04:25:19 +00:00
|
|
|
|
(unless org-roam-mode (org-roam-mode))
|
2020-04-07 05:53:22 +00:00
|
|
|
|
(let* ((completions (org-roam--get-title-path-completions))
|
2020-05-15 08:10:11 +00:00
|
|
|
|
(title-with-keys (org-roam-completion--completing-read "File: "
|
|
|
|
|
completions))
|
2020-05-16 10:17:37 +00:00
|
|
|
|
(res (cdr (assoc title-with-keys completions)))
|
2020-05-17 15:36:15 +00:00
|
|
|
|
(title (or (plist-get res :title) title-with-keys))
|
2020-06-07 05:29:26 +00:00
|
|
|
|
(file-path (plist-get res :path)))
|
2020-04-07 05:53:22 +00:00
|
|
|
|
(let ((org-roam-capture--info (list (cons 'title title)
|
2020-06-17 06:58:11 +00:00
|
|
|
|
(cons 'slug (funcall org-roam-title-to-slug-function title))
|
2020-04-07 05:53:22 +00:00
|
|
|
|
(cons 'file file-path)))
|
|
|
|
|
(org-roam-capture--context 'capture))
|
2020-05-09 10:08:08 +00:00
|
|
|
|
(condition-case err
|
|
|
|
|
(org-roam-capture--capture)
|
|
|
|
|
(error (user-error "%s. Please adjust `org-roam-capture-templates'"
|
|
|
|
|
(error-message-string err)))))))
|
2020-04-07 05:53:22 +00:00
|
|
|
|
|
2020-03-28 13:16:28 +00:00
|
|
|
|
(provide 'org-roam-capture)
|
|
|
|
|
|
|
|
|
|
;;; org-roam-capture.el ends here
|