Files
org_web/lisp/build-html.el
gitea-actions a0c78929b3
All checks were successful
Build Org Website / build (push) Successful in 50s
Refactor platform UI and data flow across core pages
2026-07-30 12:22:44 +01:00

157 lines
6.6 KiB
EmacsLisp
Executable File

;;; build-html.el --- HTML export customisation -*- lexical-binding: t; -*-
;;; Code:
(require 'org)
(require 'ox-html)
(require 'subr-x)
(require 'tags)
(require 'sidenotes)
(require 'build-comments)
(setq org-export-global-macros
(append
org-export-global-macros
'(("sidenote"
. "@@html:<label for=\"sn$1\" class=\"margin-toggle sidenote-number\"></label>\
<input type=\"checkbox\" id=\"sn$1\" class=\"margin-toggle\"/>\
<span class=\"sidenote\">$2</span>@@")
("epigraph"
. "@@html:<div class=\"epigraph\"><blockquote>$1<footer>$2</footer></blockquote></div>@@")
("epigraph_single"
. "@@html:<div class=\"epigraph\"><blockquote>$1</blockquote></div>@@")
("epigraph3"
. "@@html:<div class=\"epigraph\"><blockquote>$1<footer>$2, <cite>$3</cite></footer></blockquote></div>@@")
("kbd"
. "@@html:<kbd>$1</kbd>@@@@latex:\\texttt{$1}@@")
("margimg"
. "@@html:<aside class=\"marginnote\"><figure class=\"mn-fig\">\
<img src=\"$1\" alt=\"$2\" class=\"mn-img\" loading=\"lazy\" decoding=\"async\"/>$3\
</figure></aside>@@")
("countdown"
. "@@html:<time class=\"countdown\" datetime=\"$1\" data-label=\"$2\"></time>@@"))))
(defvar z/preamble
"<div class=\"banner-header\" role=\"banner\">
<a class=\"site-brand\" href=\"/\" aria-label=\"Home\">
<img src=\"/assets/images/gr.png\" alt=\"\" class=\"banner-logo\" />
<span class=\"site-brand__text\">zxh</span>
</a>
<nav class=\"site-nav\" aria-label=\"Primary\">
<a href=\"/\">Home</a>
<a href=\"/blogs/blogs-list.html\">Blogs</a>
<a href=\"/posts/career/career-list.html\">Career</a>
<a href=\"/play/play.html\">Play</a>
<a href=\"https://zone.zainezq.com\">Dashboard</a>
</nav>
<div class=\"site-actions\">
<label class=\"site-search\" for=\"search-input\">
<span class=\"visually-hidden\">Search notes</span>
<input type=\"search\" id=\"search-input\" placeholder=\"Search notes\" aria-label=\"Search notes\" />
</label>
<button id=\"search-btn\" aria-label=\"Search\" type=\"button\">Search</button>
<button class=\"theme-toggle\" id=\"theme-toggle\" type=\"button\" aria-label=\"Switch theme\">Theme</button>
</div>
</div>
<div id=\"updated\">Updated: %C</div>
")
(defvar z/postamble
"<footer>
<div class=\"copyright-container\">
<div class=\"copyright\">
Copyright &copy; 2022-2026 Zaine Qayyum. All rights reserved unless otherwise noted.</div></div>
<div class=\"generated\">
Created with %c on <a href=\"https://www.archlinux.org/\">Arch</a> \
<a href=\"https://www.gnu.org\">GNU</a>/<a href=\"https://www.kernel.org/\">Linux</a>
</div>
</footer>")
(defun z/org-html-src-block-mermaid (orig-fun src-block contents info)
"Export Mermaid source blocks as Mermaid divs."
(let ((lang (org-element-property :language src-block)))
(if (string= lang "mermaid")
(let ((code (org-remove-indentation
(org-element-property :value src-block))))
(format "<div class=\"mermaid\">\n%s\n</div>" code))
(funcall orig-fun src-block contents info))))
(advice-add 'org-html-src-block :around #'z/org-html-src-block-mermaid)
(defun z/org-html-add-body-classes (output backend info)
"Add layout-related classes to <body> based on file metadata."
(if (and (org-export-derived-backend-p backend 'html)
(not (and (fboundp 'z/rpg-standalone-file-p)
(z/rpg-standalone-file-p info))))
(let* ((input-file (plist-get info :input-file))
(classes
(delq nil
(list
(when (and input-file (z/no-sidenotes-file-p input-file))
"no-sidenotes")))))
(if classes
(replace-regexp-in-string
"<body\\([^>]*\\)>"
(format "<body\\1 class=\"%s\">"
(string-join classes " "))
output)
output))
output))
(add-to-list 'org-export-filter-final-output-functions
#'z/org-html-add-body-classes)
(add-to-list 'org-export-filter-body-functions
#'z/org-html-insert-comments-into-body)
(defconst z/rpg-standalone-start "<!-- RPG-STANDALONE-START -->")
(defconst z/rpg-standalone-end "<!-- RPG-STANDALONE-END -->")
(defun z/rpg-standalone-file-p (info)
"Return non-nil when INFO describes the dedicated RPG page."
(let ((input-file (plist-get info :input-file)))
(and input-file
(string= (file-truename input-file)
(file-truename (site-path "play/rpg.org"))))))
(defun z/render-rpg-standalone (output backend info)
"Replace normal site chrome with a dedicated game document for the RPG."
(if (and (org-export-derived-backend-p backend 'html)
(z/rpg-standalone-file-p info))
(let* ((start (string-match (regexp-quote z/rpg-standalone-start) output))
(end (and start (string-match (regexp-quote z/rpg-standalone-end) output start))))
(if (and start end)
(let* ((body-start (+ start (length z/rpg-standalone-start)))
(body (substring output body-start end))
(body (replace-regexp-in-string
"<link rel=\"stylesheet\" href=\"/assets/styles/pages/princess-lima-rpg.css[^\"]*\" />"
""
body)))
(concat
"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n"
"<meta charset=\"utf-8\" />\n"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\" />\n"
"<meta name=\"theme-color\" content=\"#080a12\" />\n"
"<meta name=\"description\" content=\"A standalone fantasy RPG about rescuing Princess Lima.\" />\n"
"<title>Rescue Princess Lima</title>\n"
"<link rel=\"icon\" href=\"/assets/icons/icons8-film-tape-100.png\" />\n"
"<link rel=\"stylesheet\" href=\"/assets/styles/pages/princess-lima-rpg.css?v=princess-lima-1.1.0\" />\n"
"</head>\n<body class=\"princess-lima-page\">\n"
body
"\n</body>\n</html>\n"))
output))
output))
(add-to-list 'org-export-filter-final-output-functions
#'z/render-rpg-standalone)
(org-export-define-derived-backend 'z-html 'html
:filters-alist '((:filter-final-output . z/insert-filetags-after-title)))
(defun z/z-publish-to-html (plist filename pub-dir)
"Publish FILENAME to HTML using the z-html backend."
(org-publish-org-to 'z-html filename ".html" plist pub-dir))
(provide 'build-html)
;;; build-html.el ends here