Org Roam and Nyxt: taking Zettelkasten notes from the web
Too long; didn't read
Org Roam V2 came out recently and it is time to take notes directly from the web (via Nyxt).
The problem
Sometimes I get an idea while I browse the web. Earlier on I discovered how much I like managing notes with the Zettelkasten method. So given I got this idea while using a web browser, I would need to fire my Emacs, copy the link, then copy the quote, finally write my idea about what I read. Each time, I would lose the flow of my reading. Org Roam people would say I could set up org-protocol for that, but, again, that is a bit cumbersome for me.
How difficult is to take an Org Roam note directly from my browser?
And there is a solution
Nyxt is the solution really! A browser that feels as malleable as Emacs. In previous blogs I have shown how to make Nyxt speak to Emacs and even how to capture Org notes. If that is possible, we should be able to capture an Org Roam note as well.
Nyxt comes with a nice prompt system to request text from the user. So I decided to make a function to ask for the title of the note and its contents. My aim is to take a note on the fly, so I expect to write no more than a line. That is why the prompt space should be enough to draft out what I have in mind.
Well let me show you how this looks before we get into the implementation details.
This is the snippet you can add to your Nyxt configuration that makes it happen.
(define-command-global org-roam-capture () "Org-roam-capture current page." (let ((quote (%copy)) (title (prompt :input (title (current-buffer)) :prompt "Title of note:" :sources (list (make-instance 'prompter:raw-source)))) (text (prompt :input "" :prompt "Note to take:" :sources (list (make-instance 'prompter:raw-source))))) (eval-in-emacs `(let ((file (on/make-filepath ,(car title) (current-time)))) (on/insert-org-roam-file file ,(car title) nil (list ,(render-url (url (current-buffer)))) ,(car text) ,quote) (find-file file) (org-id-get-create))) (echo "Org Roam Note stored!")))
You can see how we used the prompts. The prompt returns a list of
values, that is why we need (car title)
. If you select some text,
that will be inserted as well in the new note ((%copy)
). I also open
the note in Emacs as a reminder that I need to rework it later
((find-file file)
).
Actually, you can see that we use two functions here
on/insert-org-roam-file
and on/make-filepath
. These need to be
available in your Emacs.
This is how they look.
(defun on/slug-string (title) (let ((slug-trim-chars '(;; Combining Diacritical Marks https://www.unicode.org/charts/PDF/U0300.pdf 768 ; U+0300 COMBINING GRAVE ACCENT 769 ; U+0301 COMBINING ACUTE ACCENT 770 ; U+0302 COMBINING CIRCUMFLEX ACCENT 771 ; U+0303 COMBINING TILDE 772 ; U+0304 COMBINING MACRON 774 ; U+0306 COMBINING BREVE 775 ; U+0307 COMBINING DOT ABOVE 776 ; U+0308 COMBINING DIAERESIS 777 ; U+0309 COMBINING HOOK ABOVE 778 ; U+030A COMBINING RING ABOVE 780 ; U+030C COMBINING CARON 795 ; U+031B COMBINING HORN 803 ; U+0323 COMBINING DOT BELOW 804 ; U+0324 COMBINING DIAERESIS BELOW 805 ; U+0325 COMBINING RING BELOW 807 ; U+0327 COMBINING CEDILLA 813 ; U+032D COMBINING CIRCUMFLEX ACCENT BELOW 814 ; U+032E COMBINING BREVE BELOW 816 ; U+0330 COMBINING TILDE BELOW 817 ; U+0331 COMBINING MACRON BELOW ))) (cl-flet* ((nonspacing-mark-p (char) (memq char slug-trim-chars)) (strip-nonspacing-marks (s) (ucs-normalize-NFC-string (apply #'string (seq-remove #'nonspacing-mark-p (ucs-normalize-NFD-string s))))) (cl-replace (title pair) (replace-regexp-in-string (car pair) (cdr pair) title))) (let* ((pairs `(("[^[:alnum:][:digit:]]" . "_") ;; convert anything not alphanumeric ("__*" . "_") ;; remove sequential underscores ("^_" . "") ;; remove starting underscore ("_$" . ""))) ;; remove ending underscore (slug (-reduce-from #'cl-replace (strip-nonspacing-marks title) pairs))) (downcase slug))))) (defun on/make-filepath (title now &optional zone) "Make filename from note TITLE and NOW time (assumed in the current time ZONE)." (concat org-roam-directory (format-time-string "%Y%m%d%H%M%S_" now (or zone (current-time-zone))) (s-truncate 70 (on/slug-string title) "") ".org")) (defun on/insert-org-roam-file (file-path title &optional links sources text quote) "Insert org roam file in FILE-PATH with TITLE, LINKS, SOURCES, TEXT, QUOTE." (with-temp-file file-path (insert "* " title "\n" "\n" "- tags :: " (--reduce (concat acc ", " it) links) "\n" (if sources (concat "- source :: " (--reduce (concat acc ", " it) sources) "\n") "") "\n" (if text text "") "\n" "\n" (if quote (concat "#+begin_src text \n" quote "\n" "#+end_src") ""))) (with-file file-path (org-id-get-create) (save-buffer)))
These functions use my note template. A note with my template looks like the following.
* some note. - tags :: ?? - source :: some-link Idea. #+begin_src text Some quote #+end_src
You may not like that, so please edit on/insert-org-roam-file
as you
like (I attempted to go through org-roam-capture
for this, but it
still does not seem program friendly). Note also that on/slug-string
is reusing the (latest at the time of writing) org-roam
code to make
a file path.
And like that we have opened a world of note taking from our browser!
Conclusion
Here you go: note taking on the web! If you want a ready made solution for this, check out (and load) my hacks at emacs-with-nyxt. It will provide this command by default. Hope you will capture amazing ideas!
Happy note taking!