Where parallels cross

Interesting bits of life

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!

Comments