From 791c0592007bed05a0bd952c0326b51f9c6ad5bc Mon Sep 17 00:00:00 2001 From: Jethro Kuan Date: Thu, 13 Feb 2020 00:25:45 +0800 Subject: [PATCH] Simplify org-roam-insert and org-roam-find-file (#62) * Simplify org-roam-insert and org-roam-find-file See #59. * Add docs for org-roam automatic filenaming * Update installation instructions --- doc/configuration.md | 51 ++++++++++++++++ doc/installation.md | 5 +- mkdocs.yml | 1 + org-roam.el | 135 ++++++++++++++++--------------------------- 4 files changed, 107 insertions(+), 85 deletions(-) create mode 100644 doc/configuration.md diff --git a/doc/configuration.md b/doc/configuration.md new file mode 100644 index 0000000..61e4d01 --- /dev/null +++ b/doc/configuration.md @@ -0,0 +1,51 @@ +To ensure that Org-roam remains manageable, the number of +configuration options is deliberately kept small. However, we have +attempted to accommodate as many usage styles as possible. + +In this section, we'll go over the main customization options +available to Org-Roam. This section is *crucial*. We need to exploit +the flexibility of Emacs, and mould our tools exactly to our liking. + +All of Org-roam's customization options can be viewed via `M-x +customize-group org-roam`. + +## Org-roam Files + +These customization options revolve around the Org files created and +managed by Org-roam. + +### Automatically Creating Files Using Timestamp + +A common hassle is ensuring that files are uniquely named within the +Org-roam directory. Org-roam's default workflow utilizes the title of +Org files in all of its main commands (`org-roam-insert`, +`org-roam-find-file`). Hence, having any unique file name is a decent +option, and the default workflow uses the timestamp as the filename. + +The format of the filename is specified by the string +`org-roam-file-format`, which defaults to `"%Y%m%d%H%M%S"`. To see +valid specifications, see the help (`C-h f`) for `format-time-string`. + +There are several reasons for keeping filenames meaningful. For +example, one may wish to publish the Org files, and some publishing +methods such as Org-publish use the file names as slugs for the URLs. + +If you wish to maintain manual control of filenames, set +`org-roam-use-timestamp-as-filename` to `nil`: + +```emacs-lisp +(setq org-roam-use-timestamp-as-filename nil) +``` + +When this setting is turned off, the user is instead manually prompted +for a filename. It is then the user's responsibility to ensure that +the file names are unique. + +### Autopopulating Titles + +The default workflow uses the title of the Org file in several +commands. The title is specified via the `#+TITLE:` attribute, +typically near the top of the file. The option +`org-roam-autopopulate-title` defaults to `t`. When true, the title +attribute is automatically inserted into the files created via +org-roam commands. Setting it to `nil` will disable this behaviour. diff --git a/doc/installation.md b/doc/installation.md index 87b25a5..e41c9cf 100644 --- a/doc/installation.md +++ b/doc/installation.md @@ -10,7 +10,6 @@ The recommended method is using [use-package][use-package] and :straight (:host github :repo "jethrokuan/org-roam") :custom (org-roam-directory "/path/to/org-files/") - (org-roam-link-representation 'title) ;; or keep it as 'id :bind ("C-c n l" . org-roam) ("C-c n t" . org-roam-today) @@ -31,5 +30,9 @@ git clone https://github.com/jethrokuan/org-roam/ ~/.emacs.d/elisp/org-roam (require 'org-roam) ``` +There are a number of important configuration options, that greatly +affect the Roam workflow. Do look through them at the +[Configuration](configuration.md) page. + [use-package]: https://github.com/jwiegley/use-package [straight]: https://github.com/raxod502/straight.el diff --git a/mkdocs.yml b/mkdocs.yml index dd18247..a758989 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,6 +7,7 @@ nav: - Home: index.md - A Tour of Org-Roam: tour.md - Installation: installation.md +- Configuration: configuration.md - Ecosystem: ecosystem.md - Similar Packages: comparison.md markdown_extensions: diff --git a/org-roam.el b/org-roam.el index 85be003..e789908 100644 --- a/org-roam.el +++ b/org-roam.el @@ -33,26 +33,8 @@ Valid values are (const right)) :group 'org-roam) -(defcustom org-roam-link-representation 'id - "The value used to represent an org-roam link. - -Valid values are - * file, - * title." - :type '(choice (const id) - (const title)) - :group 'org-roam) - -(defcustom org-roam-timestamped-files nil - "Whether to use timestamps to generate unique filenames." - :type 'boolean - :group 'org-roam) - -(defcustom org-roam-timestamp-format "%Y-%m-%d%H%M%S" - "The timestamp format to use filenames.") - -(defcustom org-roam-link-id-format "ยง%s" - "The format string used when inserting org-roam links that use id." +(defcustom org-roam-file-format "%Y%m%d%H%M%S" + "The timestamp format to use filenames." :type 'string :group 'org-roam) @@ -61,6 +43,9 @@ Valid values are :type 'string :group 'org-roam) +(defcustom org-roam-use-timestamp-as-filename t + "Whether to use timestamp as a file name. If not true, prompt for a file name each time.") + (defcustom org-roam-autopopulate-title t "Whether to autopopulate the title." :type 'boolean :group 'org-roam) @@ -133,13 +118,13 @@ If called interactively, then PARENTS is non-nil." (f-child-of-p (file-truename (buffer-file-name (current-buffer))) org-roam-directory))) -(defun org-roam--get-title (file) - "Return title of `FILE'. - -It first tries the cache. If the cache does not contain the file, -it will return the title by loading the file." +(defun org-roam--get-title-from-cache (file) + "Return title of `FILE' from the cache." (or (gethash file org-roam-titles-cache) - (org-roam--extract-file-title file))) + (progn + (unless org-roam-cache-initialized + (user-error "The Org-Roam caches aren't built! Please run org-roam--build-cache-async")) + nil))) (defun org-roam--find-files (dir) "Return all org-roam files in `DIR'." @@ -177,20 +162,15 @@ If `ABSOLUTE', return the absolute file-path. Else, return the relative file-pat (file-relative-name absolute-file-path (file-truename org-roam-directory))))) -(defun org-roam--get-id (file-path) - "Convert `FILE-PATH' to the org-roam id." - (file-name-sans-extension - (file-relative-name - (file-truename file-path) - (file-truename org-roam-directory)))) +(defun org-roam--get-title-or-slug (file-path) + "Convert `FILE-PATH' to the file title, if it exists. Else, return the path." + (or (org-roam--get-title-from-cache file-path) + (-> file-path + (file-relative-name (file-truename org-roam-directory)) + (file-name-sans-extension)))) -(defun org-roam--get-title-or-id (file-path) - "Convert `FILE-PATH' to the file title, if it exists. Else, return the id." - (or (org-roam--get-title file-path) - (org-roam--get-id file-path))) - -(defun org-roam--title-to-id (title) - "Convert TITLE to id." +(defun org-roam--title-to-slug (title) + "Convert TITLE to a filename-suitable slug." (let* ((s (s-downcase title)) (s (replace-regexp-in-string "[^a-zA-Z0-9_ ]" "" s)) (s (s-split " " s)) @@ -232,9 +212,20 @@ If not provided, derive the title from the file name." (org-roam--make-file file-path)) (find-file file-path))) -(defun org-roam--get-new-id () - "Return a new ID, generated from the current time." - (format-time-string org-roam-timestamp-format (current-time))) +(defun org-roam--get-new-id (&optional title) + "Return a new ID, generated from the current time. + +Optionally pass it the title, for a smart file name." + (if org-roam-use-timestamp-as-filename + (format-time-string org-roam-file-format (current-time)) + (let* ((slug (read-string "Enter ID (without extension): " + (if title + (org-roam--title-to-slug title) + ""))) + (file-path (org-roam--get-file-path slug t))) + (if (file-exists-p file-path) + (user-error "There's already a file at %s") + slug)))) (defun org-roam-new-file () "Quickly create a new file, using the current timestamp." @@ -243,58 +234,34 @@ If not provided, derive the title from the file name." ;;; Inserting org-roam links (defun org-roam-insert () - "Insert an org-roam link." + "Find an org-roam file, and insert a relative org link to it at point." (interactive) - (pcase org-roam-link-representation - ('id (org-roam--insert-id)) - ('title (org-roam--insert-title)))) - -(defun org-roam--insert-title () - "Find `ID', and insert a relative org link to it at point." (let* ((completions (mapcar (lambda (file) - (list (org-roam--get-title-or-id file) - (org-roam--get-id file))) + (list (org-roam--get-title-or-slug file) + file)) (org-roam--find-all-files))) (title (completing-read "File: " completions)) - (id (cadr (assoc title completions)))) - (when (not id) - (if org-roam-timestamped-files - (setq id (org-roam--get-new-id))) - (read-string "Enter new file id: " (org-roam--title-to-id title))) - (let ((file-path (org-roam--get-file-path id))) - (unless (file-exists-p file-path) - (org-roam--make-file file-path title)) - (insert (format "[[%s][%s]]" - (concat "file:" file-path) - (format org-roam-link-title-format title)))))) - -(defun org-roam--insert-id () - "Find `ID', and insert a relative org link to it at point." - (let* ((id (completing-read "File: " (mapcar #'org-roam--get-id (org-roam--find-all-files)))) - (file-path (org-roam--get-file-path id))) + (file-path (or (cadr (assoc title completions)) + (org-roam--get-new-id title)))) (unless (file-exists-p file-path) - (org-roam--make-file file-path)) + (org-roam--make-file file-path title)) (insert (format "[[%s][%s]]" (concat "file:" file-path) - (format org-roam-link-id-format id))))) + (format org-roam-link-title-format title))))) ;;; Finding org-roam files (defun org-roam-find-file () "Find and open an org-roam file." (interactive) (let* ((completions (mapcar (lambda (file) - (list (org-roam--get-title-or-id file) file)) + (list (org-roam--get-title-or-slug file) file)) (org-roam--find-all-files))) - (title-or-id (completing-read "File: " completions)) - (file-path (cadr (assoc title-or-id completions)))) - (unless file-path - (let ((id (if org-roam-timestamped-files - (org-roam--get-new-id) - (read-string "Enter new file id: " - (org-roam--title-to-id title-or-id))))) - (setq file-path (org-roam--get-file-path id t)))) + (title-or-slug (completing-read "File: " completions)) + (file-path (or (cadr (assoc title-or-slug completions)) + (org-roam--get-file-path + (org-roam--get-new-id title-or-slug) t)))) (unless (file-exists-p file-path) - (org-roam--make-file file-path title-or-id)) + (org-roam--make-file file-path title-or-slug)) (find-file file-path))) ;;; Building the org-roam cache (asynchronously) @@ -536,7 +503,7 @@ This is equivalent to removing the node from the graph." (defun org-roam-update (file-path) "Show the backlinks for given org file for file at `FILE-PATH'." (org-roam--ensure-cache-built) - (let ((buffer-title (org-roam--get-title-or-id file-path))) + (let ((buffer-title (org-roam--get-title-or-slug file-path))) (with-current-buffer org-roam-buffer (let ((inhibit-read-only t)) (erase-buffer) @@ -553,7 +520,7 @@ This is equivalent to removing the node from the graph." (maphash (lambda (file-from contents) (insert (format "** [[file:%s][%s]]\n" file-from - (org-roam--get-title-or-id file-from))) + (org-roam--get-title-or-slug file-from))) (dolist (content contents) (insert (concat (propertize (s-trim (s-replace "\n" " " content)) 'font-lock-face 'org-block) @@ -657,15 +624,15 @@ This needs to be quick/infrequent, because this is run at (mapcar (lambda (file) (insert (format " \"%s\" [URL=\"roam://%s\"];\n" - (org-roam--get-id file) + (org-roam--get-title-or-slug file) file))) (org-roam--find-all-files)) (maphash (lambda (from-link to-links) (dolist (to-link to-links) (insert (format " \"%s\" -> \"%s\";\n" - (org-roam--get-id from-link) - (org-roam--get-id to-link)))) + (org-roam--get-title-or-slug from-link) + (org-roam--get-title-or-slug to-link)))) ) org-roam-forward-links-cache) (insert "}")