"
page)
html
nil t))
;; Wrap title with metadata section — edit button removed
(setq html
(replace-regexp-in-string
"
\\(.*\\) "
(lambda (match)
(let ((title-text (match-string 1 match)))
(format
"
⤢
×
%s
planted
%s
tended
%s
"
title-text planted-date last-tended)))
html
nil t))
(setq html
(replace-regexp-in-string
"
"
(concat stack-close "\n
")
html
nil t))
html))
(advice-add 'org-html-template :around #'z/org-html-wrap-content-for-stack)
(add-to-list 'load-path default-directory)
(defvar z-shared-head
"
−
100%
+
Reset
Fit
✕ Close
Scroll to zoom · Drag to pan · Esc to close
"
)
(defvar z-preamble
"
"
)
(defvar z-postamble
"
")
(defvar z/build-full-rebuild t
"If non-nil, generate expensive artifacts like search-index.json.")
;; Babel language support
(org-babel-do-load-languages
'org-babel-load-languages
'((latex . t)
(org . t)))
(setq org-confirm-babel-evaluate nil ;; Disable confirmation for code block evaluation
org-export-with-smart-quotes t ;; turns " into curly quotes
;;org-html-self-link-headlines t ;; headlines link to self
org-html-validation-link nil ;; dont validate html (in order to check it is up to web standards)
org-html-inline-images t ;; self-expl
org-export-with-sub-superscripts t ;; render h_x as large h with subscript x
)
(load-file (expand-file-name "lisp/macros.el" default-directory))
;; Configure LaTeX image generation
(setq org-latex-create-formula-image-program 'imagemagick)
(defun z/org-html-mermaid-block (code _lang)
"Export a mermaid src block as a mermaid div."
(format "
\n%s\n
" code))
;; Hook into ox-html's src block export
(defun z/org-html-src-block-mermaid (orig-fun src-block contents info)
(let ((lang (org-element-property :language src-block)))
(if (string= lang "mermaid")
(let ((code (org-remove-indentation
(org-element-property :value src-block))))
(format "
\n%s\n
" code))
(funcall orig-fun src-block contents info))))
(advice-add 'org-html-src-block :around #'z/org-html-src-block-mermaid)
(defun my/get-org-roam-backlinks (filename)
"Return backlinks for the given org-roam file. FILENAME should be the full path to the org-roam file."
(save-excursion
(with-current-buffer (find-file-noselect filename)
(goto-char (point-min))
(if (and (featurep 'org-roam) (org-roam-node-at-point))
(let* ((node (org-roam-node-at-point))
(backlinks (org-roam-backlinks-get node)))
(mapcar (lambda (backlink)
(let ((source-node (org-roam-backlink-source-node backlink)))
(list
:file (org-roam-node-file source-node)
:title (org-roam-node-title source-node)
:content (with-current-buffer
(find-file-noselect (org-roam-node-file source-node))
(goto-char (org-roam-backlink-point backlink))
(thing-at-point 'sentence t))
:link (format "[[id:%s][%s]]"
(org-roam-node-id source-node)
(org-roam-node-title source-node)))))
backlinks)
)
nil))))
(defun my/org-export-insert-backlinks (_backend)
"Insert Org-roam backlinks before export."
(when (and (buffer-file-name)
(not (string-match-p "all-files\\.org$" (buffer-file-name)))
(featurep 'org-roam)
(org-roam-node-at-point))
(let ((backlinks (my/get-org-roam-backlinks (buffer-file-name))))
(when backlinks
(goto-char (point-max))
(insert "\n#+ATTR_HTML: :class backlinks-section :id backlinks\n")
(insert "* Backlinks\n")
(insert "#+ATTR_HTML: :class backlinks-list\n")
(dolist (bl backlinks)
(insert (format "- %s\n" (plist-get bl :link))))))))
(setq org-id-locations-file
(expand-file-name ".org-id-locations"
(getenv "HOME")))
(setq org-roam-directory site-root)
(org-id-update-id-locations)
(setq org-html-link-home "")
(setq org-html-link-up "")
(defun z/generate-all-files-org ()
"Generate all-files.org from all-files.json."
(let* ((base-dir site-root)
(json-file (expand-file-name "all-files.json" base-dir))
(org-file (expand-file-name "all-files.org" base-dir)))
(when (file-exists-p json-file)
(message "🧩 Generating all-files.org from JSON…")
(let* ((json-object-type 'alist)
(json-array-type 'list)
(json-key-type 'symbol)
(data (json-read-file json-file))
(groups (alist-get 'groups data)))
(with-temp-file org-file
(insert "#+title: All Files\n")
(insert "#+options: toc:nil num:nil\n\n")
(insert "#+begin_comment\n")
(insert "This file is auto-generated. Do not edit manually.\n")
(insert "#+end_comment\n\n")
(dolist (group groups)
(let ((letter (symbol-name (car group)))
(entries (cdr group)))
(insert (format "* %s\n" letter))
(dolist (entry entries)
(let ((id (alist-get 'id entry))
(title (alist-get 'title entry)))
(insert (format "- [[id:%s][%s]]\n" id title))))
(insert "\n")))))
(message "✅ all-files.org generated"))))
(setq org-html-mathjax-options
'((path "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js")
(scale 1.0)
(align "center")
(font "mathjax-modern")
(overflow "overflow")
(autonumber "AMS")
(multlinewidth "85%")
(tagside "right")
(tagindent ".8em")))
(setq org-html-mathjax-template
"
")
;; -------------------------------------------------------
;; MOV → MP4 conversion
;; Converts all .mov files found anywhere under assets/ to
;; .mp4 siblings IN THE SOURCE directory before publishing.
;; org-publish then copies both the .mov and the .mp4.
;; Skips conversion if an up-to-date .mp4 already exists.
;; -------------------------------------------------------
(defun z/convert-mov-to-mp4 (assets-dir)
"Recursively convert every .mov under ASSETS-DIR to an .mp4 sibling.
Skips files where the .mp4 already exists and is newer than the .mov."
(message "🎬 Scanning %s for .mov files…" assets-dir)
(let ((mov-files
(directory-files-recursively assets-dir "\\.mov$" nil)))
(if (null mov-files)
(message " (no .mov files found)")
(dolist (mov mov-files)
(let* ((mp4 (concat (file-name-sans-extension mov) ".mp4"))
(mov-mtime (float-time (nth 5 (file-attributes mov))))
(mp4-mtime (if (file-exists-p mp4)
(float-time (nth 5 (file-attributes mp4)))
-1)))
(if (> mp4-mtime mov-mtime)
(message " [skip] %s (mp4 is up to date)" (file-name-nondirectory mov))
(message " [convert] %s → %s"
(file-relative-name mov assets-dir)
(file-name-nondirectory mp4))
(let ((exit-code
(call-process "ffmpeg" nil nil nil
"-i" mov
"-c:v" "libx264"
"-preset" "fast"
"-crf" "23"
"-c:a" "aac"
"-b:a" "128k"
"-movflags" "+faststart"
"-y" mp4)))
(if (= exit-code 0)
(message " [done] %s" (file-name-nondirectory mp4))
(message " [ERROR] ffmpeg exited %d for %s"
exit-code (file-name-nondirectory mov))))))))))
(setq org-publish-project-alist
`(
("org-roam"
:base-directory "/home/zaine/master-folder/org_files/org_roam/"
:base-extension "org"
:publishing-directory "/home/zaine/master-folder/org_files/org_roam/output/"
:recursive t
:publishing-function org-html-publish-to-html
:with-author nil
:with-creator nil
:with-date t
:with-toc nil
:section-numbers nil
:html-preamble ,z-preamble
:html-postamble ,z-postamble
:html-head ,z-shared-head)
("org-static"
:base-directory "/home/zaine/master-folder/org_files/org_roam/assets/"
:base-extension "css\\|js\\|png\\|jpg\\|gif\\|svg\\|mp4\\|mp3\\|mov"
:publishing-directory "/home/zaine/master-folder/org_files/org_roam/output/assets/"
:recursive t
:publishing-function org-publish-attachment)))
(defun z/org-publish-quiet (project force)
"Publish PROJECT quietly. If FORCE is non-nil, force publishing."
(let ((inhibit-message t)
(message-log-max nil))
(org-publish project force)))
(defun z/refresh-org-roam ()
"Force Org-roam and Org ID caches to refresh (needed for synced notes)."
(message "starting to refresh...")
;; Update ID locations
(org-id-update-id-locations)
;; Rebuild roam database
(when (featurep 'org-roam)
(org-roam-db-clear-all)
(org-roam-db-sync))
(message "✅ Org-roam DB refreshed"))
(defun z/build-full ()
"Full rebuild: Org, assets, and search index."
(interactive)
(let* ((start-time (current-time)))
(message "Starting full rebuild at %s"
(format-time-string "%Y-%m-%d %H:%M:%S" start-time))
(org-id-update-id-locations)
;; Convert any .mov files before publishing so org-publish
;; picks up the .mp4 siblings automatically.
(z/convert-mov-to-mp4
"/home/zaine/master-folder/org_files/org_roam/assets/")
(setq z/build-full-rebuild t)
(z/generate-all-files-org)
(z/org-publish-quiet "org-roam" t)
(z/org-publish-quiet "org-static" t)
(let ((elapsed (float-time (time-subtract (current-time) start-time))))
(message "✅ Full rebuild complete in %.2f seconds" elapsed))))
(defun z/build-fast ()
"Fast rebuild: Org + assets only (no JSON)."
(interactive)
;; Convert MOVs in fast builds too — cheap (skips up-to-date files).
(z/convert-mov-to-mp4
"/home/zaine/master-folder/org_files/org_roam/assets/")
(setq z/build-full-rebuild nil)
(org-publish "org-roam" t)
(org-publish "org-static" t)
(message "⚡ Fast rebuild complete (no search index)"))
(cond
((member "--fast" command-line-args-left)
(z/build-fast))
(t
(z/build-full)))