来把 org-roam 笔记发布出去吧
org-roam 很好,不过笔记积累久了就会产生分享欲,想要在线发布出去……最近摸爬滚打几天终于是弄出来了……
方案选择
首先先去网上搜索了一遍,发现还是有一些前人踩路的……
-
Neil’s Digital Garden 这个网页弄得很漂亮,完成度也很高,然后具体一看源代码,通篇 JavaScript ……额,倒不是说 JS 坏话啦,不过我现在没打算在学 JS 的,
而且 npm 确实很折磨人。25 EDIT: 研究过后发现只是一点简单的在浏览器运行的 js (just javascript) 脚本来调用他人预先写好的功能,不知道当初我为什么会觉得很难……How I publish my wiki with org-publish 所使用的代码也确实有点复杂了(我,纯纯菜狐)。
-
My Org Roam Notes Workflow - Hugo Cisneros 这篇文章讲的也不错,不过其博客主题源代码并没有和 org-roam 相关的,好像只有一些框架。好吧,也知道弄 Emacs 相关的作业没那么好抄的,心理准备还是有的。
不过这个博客讲了如何使用 Python 分析 org-roam 的数据库解析出关系图谱,但我现在的想法是只把我一部分笔记发布出去,而我又想通过 Drone 这个 CI 平台自动构建网站,所以也用不到了……
-
最后试了试 jethrokuan/braindump 方案,这个也是 org-roam 作者在用的,所以也算是钦定方案了(
但实际其采用的脚本我无法使用,可能和我用的 Doom Emacs batch 环境有问题吧,也不知道怎么解决(我,究极菜狐)……所以最后只能自己慢慢折腾了!
准备
首先要先下载 ox-hugo 包,先把 org-mode 所用的 .org 文件转换成 .md 再给 hugo 解析……没错,Emacser 就是这么自由,虽说 hugo 原生就支持 org 文件,不过 Markdown 支持更优秀的话用 Markdown 也不是不行……(反正也是自动转换的)。
代码
(setq org-hugo-base-dir "~/Documents/roam-publish/")
(defun my/org-roam-filter-by-tag (tag-name)
(lambda (node)
(member tag-name (org-roam-node-tags node))))
(defun my/org-roam-list-notes-by-tag (tag-name)
(mapcar #'org-roam-node-file
(seq-filter
(my/org-roam-filter-by-tag tag-name)
(org-roam-node-list))))
(defun my/org-roam-export-all ()
"Re-exports all Org-roam files to Hugo markdown."
(interactive)
(dolist (org-file (my/org-roam-list-notes-by-tag "publish"))
(with-current-buffer (find-file org-file)
(my/org-roam-publish t))))
(defun my/insert--backlinks (&optional heading-func)
(if-let ((backlinks (org-roam-backlinks-get (org-roam-node-at-point) :unique t)))
(save-excursion
(when heading-func
(funcall heading-func))
(insert " 反向链接\n")
(dolist (backlink backlinks)
(let* ((source-node (org-roam-backlink-source-node backlink))
(point (org-roam-backlink-point backlink)))
(insert
(format "- [[id:%s][%s]]\n"
(org-roam-node-id source-node)
(org-roam-node-title source-node))))))))
(defun my/insert-backlinks ()
(goto-char (point-min))
(org-shifttab 0)
(while (= 0 (org-next-visible-heading 1))
(when (assoc "ID" (org-entry-properties))
(my/insert--backlinks
(lambda ()
(org-insert-subheading t)))))
(goto-char (point-min))
(my/insert--backlinks
(lambda ()
(goto-char (point-max))
(insert "* "))))
(defun my/extract-org-roam-id (origin-filename)
(save-excursion
(goto-char (point-min))
(let ((matche '()))
(while (re-search-forward "\\[\\[id:\\(.*?\\)]\\[.*?\\]\\]" nil t)
(when-let* ((uuid (match-string-no-properties 1))
(roam-node (org-roam-node-from-id uuid))
(file-name (car (org-roam-id-find uuid))))
(when (member "publish" (org-roam-node-tags roam-node))
(push file-name matche))))
(delete origin-filename (seq-uniq matche)))))
(defun my/org-roam-publish (&optional skip-publish skip-update-backlinks)
"Publish current file"
(interactive)
(goto-char (point-min))
(org-roam-update-org-id-locations)
(org-roam-tag-add (list "publish"))
(unless skip-update-backlinks
(org-roam-set-keyword "hugo_lastmod" (format-time-string "%Y-%m-%d %H:%M:%S")))
(save-buffer)
(let ((publish-content (buffer-string))
(origin-filename (buffer-file-name))
(filename (concat (file-name-base (buffer-file-name)) ".org")))
(with-temp-buffer
(erase-buffer)
(insert publish-content)
(org-mode)
(org-roam-set-keyword "EXPORT_FILE_NAME" filename)
(org-roam-tag-remove (list "publish"))
(my/insert-backlinks)
(unless skip-update-backlinks
(let ((back-files (my/extract-org-roam-id origin-filename)))
(dolist (back-file back-files)
(message back-file)
(with-current-buffer (get-file-buffer back-file)
(my/org-roam-publish t t)))))
(org-hugo-export-wim-to-md)))
(unless skip-publish
(async-shell-command (concat
"cd " org-hugo-base-dir
" && " "git add ."
(if (yes-or-no-p "Push now?")
(concat " && " "git commit -m '[post] new post'"
" && " "git push"))) "*Messages*"))
(message "publish new post!"))
最后一个函数就是完全自己写的了,完全没有技术含量,无非就是把一些函数堆叠起来,不过真心好用 …… 论 Emacs 的易折腾性
。
25 EDIT: 经过几年发展也算是有点技术含量了,更懂该堆什么函数了,之前用的方案还要用 python 解析 hugo 生成出来的 html 文件插入反链,但是这次一看其实 org-roam 包默认就很方便拿出反链。现在方案是只对单个文件进行发布然后更新相应的反链和元数据,将 ox-hugo 生成的 markdown 文件作为一个中间层(世界法则,遇事不决加中间层)将需要改动的量压到最小,不便之处就是需要对应环境来运行导出函数。
究极折腾
路径问题
然后 Hugo 默认配置下找不到路径将会直接报错退出,对于 roam 场景下经常附个链接又不打算先写的情况很不友好,搜索下来发现官方文档已经有具体方案了。
Links and Cross References | Hugo
只要在配置文件写入 refLinksErrorLevel = "WARNING"
这行配置就行了,配置成警告而不报错导致中止。
搜索问题
主题自带的搜索无法搜索中文,不过相比 Emacs 相关问题,Hugo 相关真是好找太多了
然后参考了这个方案 Hugo JS Searching with Fuse.js · GitHub ,评论区下面也有人做了中文优化的 Hugo JS Searching with Fuse.js · GitHub ,能抄作业就是爽啦!
评论问题
其实到这里直接 hugo 命令生成静态文件就好了,但趁着折腾劲没过去还得再造会儿~
然后打算为自己这个网站加一个评论区,这次不像我博客那样选用要用 GitHub 的方案了,毕竟 GitHub 对完全没技术背景的人来说挺不友好的,所以打算这次选一个完全不用任何额外条件评论功能。
方案是选择是 Isso ,一个 Python + Sqlite3 的评论实现,总之安装过程就是直接 docker-compose 拉起来就完事了,不过最后嵌入到网站实际使用发现就有麻烦了,因为这个主题选择采用动态加载方式,第二篇文章会加载到侧面,而这里的评论区是不会加载的……鼓捣了半天终于摸索出一个解决方案……
根据官网文档 Advanced integration ,解决方案是加载文档时将原来评论区移除,然后附加到新位置上,然后用
window.Isso.init()
window.Isso.fetchComments()
重新加载评论框…,然后不要忘了在博客模板文件里配置的时候将 section 设置成
<section id="isso-thread" data-title="{{ .Title }}" data-isso-id="{{ .RelPermalink }}">
按路径加载的模式,这样才能确保新评论框加载时没有沿用到旧评论框。
反向链接
模板自带一个反向链接面板,够是够用的,不过 org-roam-v2 新增了酷炫的任意块链接功能怎么能错过呢?所以鼓捣了一下,用 Python bs4 包解析 hugo 生成的 HTML 文件里的内部链接,然后在计算出反链再塞回到原来的文件里…… 25 EDIT: 现在改用纯 elisp 方案啦!
具体代码非常稀烂……都是大循环直接搞定的……要是真想借鉴的话我也放在了我的 Gitea 仓库上…… 25 EDIT: 现在是 forgejo 仓库啦!
结语
总之就是十二分折腾,估计燃尽了我几周的能量吧……
评论