;; ----------------------------- ;; Package bootstrap (BATCH SAFE) ;; ----------------------------- (require 'package) (setq package-user-dir (expand-file-name "./.packages")) (setq package-archives '(("melpa" . "https://melpa.org/packages/") ("gnu" . "https://elpa.gnu.org/packages/"))) (package-initialize) (unless package-archive-contents (package-refresh-contents)) (unless (package-installed-p 'org-roam) (package-install 'org-roam)) (require 'ox-publish) (require 'org) (require 'ox-html) (require 'json) (require 'org-roam) (require 'ob-latex) (require 'seq) (require 'cl-lib) (use-package htmlize :ensure t) ;(setq org-export-with-broken-links t) (setq org-id-track-globally t) (setq org-id-locations-file "~/master-folder/org_files/org_roam/.org-id-locations") (org-id-update-id-locations) (defun z/org-html-wrap-content-for-stack (orig-fun contents info) "Wrap Org HTML #content in stack container, leaving postamble outside." (let* ((html (funcall orig-fun contents info)) (page (file-name-base (plist-get info :output-file))) (input-file (plist-get info :input-file)) ;; Extract planted date from filename (format: YYYYMMDDHHMMSS-title) (planted-date (if (string-match "^\\([0-9]\\{14\\}\\)" page) (let ((date-str (match-string 1 page))) (condition-case nil (format-time-string "%Y-%m-%d" (encode-time (string-to-number (substring date-str 12 14)) ; second (string-to-number (substring date-str 10 12)) ; minute (string-to-number (substring date-str 8 10)) ; hour (string-to-number (substring date-str 6 8)) ; day (string-to-number (substring date-str 4 6)) ; month (string-to-number (substring date-str 0 4)))) ; year (error "Unknown"))) ;; Fallback: try to get creation date from input file (let ((file-path (if input-file (expand-file-name input-file (plist-get info :base-directory)) nil))) (if (and file-path (file-exists-p file-path)) (format-time-string "%Y-%m-%d" (nth 4 (file-attributes file-path))) "Unknown")))) ;; Get last modified date (last tended to) (last-tended (let ((file-path (if input-file (expand-file-name input-file (plist-get info :base-directory)) nil))) (if (and file-path (file-exists-p file-path)) (format-time-string "%Y-%m-%d" (nth 5 (file-attributes file-path))) "Unknown"))) (stack-close "")) (setq html (replace-regexp-in-string "
" (format "
" page) html nil t)) ;; Wrap title with metadata section - use lambda to properly capture title ;; Handle titles with nested HTML tags by matching everything between opening and closing h1 tags ;; Use .* to match any characters (greedy) - will match up to the last which should be our closing tag (setq html (replace-regexp-in-string "

\\(.*\\)

" (lambda (match) (let ((title-text (match-string 1 match))) (format "

%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 " " ) (defvar z-preamble "
\"Site
Updated: %C
\"Web
" ) (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 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, with HTML attributes." (when (and (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 Backlinks\n") (insert "#+ATTR_HTML: :class backlinks-list\n") (dolist (bl backlinks) (insert (format "- %s\n" (plist-get bl :link)))))))) (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 "~/.emacs.d/.org-id-locations") (setq org-roam-directory "~/master-folder/org_files/org_roam/") (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 (expand-file-name "~/master-folder/org_files/org_roam/")) (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-publish-project-alist `( ("org-roam" :base-directory "~/master-folder/org_files/org_roam/" :base-extension "org" :publishing-directory "~/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 "~/master-folder/org_files/org_roam/assets/" :base-extension "css\\|js\\|png\\|jpg\\|gif\\|svg" :publishing-directory "~/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/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)) (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) (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)))