;; build.el — org-publish configuration for personal site (require 'org) (require 'ox-publish) (require 'ox-html) (setq org-publish-use-timestamps-flag nil org-export-with-broken-links t org-html-validation-link nil org-html-head-include-default-style nil org-html-head-include-scripts nil) (setq site-nav "") (defun blog-extract-title (file) "Extract #+TITLE: value from FILE." (with-temp-buffer (insert-file-contents file) (goto-char (point-min)) (if (re-search-forward "^#\\+TITLE:[ \t]*\\(.+\\)$" nil t) (string-trim (match-string 1)) (file-name-base file)))) (defun blog-extract-excerpt (file &optional max-chars) "Extract first body paragraph from FILE, up to MAX-CHARS characters." (let ((max (or max-chars 250))) (with-temp-buffer (insert-file-contents file) (goto-char (point-min)) ;; Skip metadata lines and headings (while (and (not (eobp)) (looking-at "^\\(#\\+\\|\\*\\|[ \t]*$\\)")) (forward-line 1)) (let ((start (point))) ;; Collect lines until blank line or heading (while (and (not (eobp)) (not (looking-at "^\\(\\*\\|[ \t]*$\\)"))) (forward-line 1)) (let ((text (string-trim (buffer-substring-no-properties start (point))))) ;; Collapse whitespace (setq text (replace-regexp-in-string "[ \t\n]+" " " text)) (if (> (length text) max) (concat (substring text 0 max) "...") text)))))) (defun blog-generate-listings (_project) "Scan content/blog/ and generate recent-posts.org and all-posts.org." (let* ((blog-dir "./content/blog") (gen-dir "./content/generated") (pattern (concat blog-dir "/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-*.org")) (files (sort (file-expand-wildcards pattern t) (lambda (a b) (string> (file-name-base a) (file-name-base b))))) (make-entry (lambda (file) (let* ((base (file-name-base file)) (date (substring base 0 10)) (title (blog-extract-title file)) (excerpt (blog-extract-excerpt file)) (link (concat "file:../blog/" base ".org"))) (concat "*[[" link "][" title "]]* — " date "\n\n" excerpt "\n\n" "[[" link "][Read more →]]\n\n" "-----\n\n"))))) (make-directory gen-dir t) (with-temp-file (concat gen-dir "/recent-posts.org") (dolist (file (seq-take files 3)) (insert (funcall make-entry file)))) (with-temp-file (concat gen-dir "/all-posts.org") (dolist (file files) (insert (funcall make-entry file)))))) (setq org-publish-project-alist `(("site-pages" :base-directory "./content" :base-extension "org" :publishing-directory "./public" :recursive t :exclude "generated/" :publishing-function org-html-publish-to-html :preparation-function blog-generate-listings :html-head-include-default-style nil :html-head-include-scripts nil :html-head "" :html-preamble ,site-nav :html-postamble nil :with-author nil :with-creator nil :with-timestamps nil :section-numbers nil :with-toc nil) ("site-static" :base-directory "./static" :base-extension "css\\|js\\|png\\|jpg\\|jpeg\\|gif\\|svg\\|ico\\|woff2\\|woff\\|ttf" :publishing-directory "./public" :recursive t :publishing-function org-publish-attachment) ("site" :components ("site-pages" "site-static")))) (org-publish "site" t)