publish.el (7187B)
1 ;;; publish.el --- generate and publish my blog -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2026 Shivakumar 4 5 ;; Author: Shivakumar <shivakumar AT disroot DOT org> 6 ;; Keywords: org-mode, html 7 8 ;; This program is free software; you can redistribute it and/or modify 9 ;; it under the terms of the GNU General Public License as published by 10 ;; the Free Software Foundation, either version 3 of the License, or 11 ;; (at your option) any later version. 12 13 ;; This program is distributed in the hope that it will be useful, 14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 ;; GNU General Public License for more details. 17 18 ;; You should have received a copy of the GNU General Public License 19 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 20 21 ;;; Commentary: 22 23 ;; This file is used to generate my HTMLs from my Org files for my blog 24 ;; website. These functions works for my liking and my setup, but it can 25 ;; be helpful for others to refer. 26 27 ;;; Code: 28 29 (require 'org) 30 (require 'ox-publish) 31 (require 'htmlize) 32 33 (defvar my/root 34 (file-name-directory (or load-file-name buffer-file-name))) 35 36 (defun my/preamble-path (info) 37 (let ((rel (file-relative-name (plist-get info :input-file) 38 (expand-file-name "org/" my/root)))) 39 (cond ((string= rel "index.org") "~") 40 ((string= rel "contact.org") "~/contact") 41 ((string-prefix-p "blog/" rel) "~/blog") 42 (t (concat "~/" (or (file-name-directory rel) "")))))) 43 44 (defun my/preamble (info) 45 (format "<header id=\"site-header\"> 46 <div class=\"site-title\"> 47 [ shivakumar@ntgs %s ]$ <span class=\"cursor\">\u2588</span> 48 </div> 49 <nav id=\"site-nav\"> 50 <a href=\"/\"><span class=\"bracket\">[</span> HOME <span class=\"bracket\">]</span></a> 51 <a href=\"/blog/\"><span class=\"bracket\">[</span> BLOG <span class=\"bracket\">]</span></a> 52 <a href=\"https://git.ntgs.me/\"><span class=\"bracket\">[</span> GIT <span class=\"bracket\">]</span></a> 53 <a href=\"/contact.html\"><span class=\"bracket\">[</span> CONTACT <span class=\"bracket\">]</span></a> 54 55 <input id=\"light-mode\" class=\"theme-toggle\" type=\"checkbox\"> 56 <label for=\"light-mode\" class=\"theme-label\"> 57 <svg class=\"icon icon-sun\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"> 58 <circle cx=\"12\" cy=\"12\" r=\"4\"></circle> 59 <path d=\"M12 2v2\"></path> 60 <path d=\"M12 20v2\"></path> 61 <path d=\"m4.93 4.93 1.41 1.41\"></path> 62 <path d=\"m17.66 17.66 1.41 1.41\"></path> 63 <path d=\"M2 12h2\"></path> 64 <path d=\"M20 12h2\"></path> 65 <path d=\"m6.34 17.66-1.41 1.41\"></path> 66 <path d=\"m19.07 4.93-1.41 1.41\"></path> 67 </svg> 68 <svg class=\"icon icon-moon\" viewBox=\"0 0 24 24\" aria-hidden=\"true\"> 69 <path d=\"M21 12.79A9 9 0 1 1 11.21 3 70 A7 7 0 0 0 21 12.79z\"></path> 71 </svg> 72 73 </label> 74 </nav> 75 </header> 76 " (my/preamble-path info))) 77 78 (setq org-export-use-babel nil) 79 80 (defun my/tab-title (output backend info) 81 "Wrap the <title> Org already rendered as \"X | ntgs.me\"." 82 (when (org-export-derived-backend-p backend 'html) 83 (let* ((rel (file-relative-name (plist-get info :input-file) 84 (expand-file-name "org/" my/root))) 85 (new (cond ((string= rel "index.org") "~") 86 ((string= rel "contact.org") "contact") 87 ((string= rel "blog/index.org") "blog") 88 (t "\\1")))) 89 (replace-regexp-in-string 90 "<title>\\([^<]*\\)</title>" 91 (format "<title>%s | ntgs.me</title>" new) 92 output)))) 93 94 (add-to-list 'org-export-filter-final-output-functions #'my/tab-title) 95 96 (defun my/post-date (output backend info) 97 (when (org-export-derived-backend-p backend 'html) 98 (let ((date (org-export-data (org-export-get-date info "%Y-%m-%d") info))) 99 (if (string-empty-p date) 100 output 101 (replace-regexp-in-string 102 "\\(<h1 class=\"title\">\\(?:.\\|\n\\)*?</h1>\\)" 103 (format "\\1\n<p class=\"post-date\">posted on %s</p>" date) 104 output t))))) 105 106 (add-to-list 'org-export-filter-final-output-functions #'my/post-date) 107 108 (defun my/sitemap-entry (entry _style project) 109 (if (string= entry "index.org") 110 "" 111 (let ((date (format-time-string "%Y-%m-%d" (org-publish-find-date entry project)))) 112 (format "@@html:<a class=\"entry-title\" href=\"/blog/%s.html\">%s</a> <time class=\"post-date\" datetime=\"%s\">%s</time>@@" 113 (file-name-sans-extension entry) 114 (org-html-encode-plain-text (org-publish-find-title entry project)) 115 date date)))) 116 117 (defun my/sitemap (_title list) 118 (concat (mapconcat (lambda (item) (concat "- " (car item))) 119 (cl-remove-if (lambda (item) (string-empty-p (car item))) 120 (cdr list)) 121 "\n") 122 "\n")) 123 124 (defun my/publish-page (plist filename pub-dir) 125 (unless (string= (file-name-nondirectory filename) "sitemap.org") 126 (org-html-publish-to-html plist filename pub-dir))) 127 128 (defvar my/html-opts 129 '(:publishing-function org-html-publish-to-html 130 :html-preamble my/preamble 131 :html-postamble "<footer> <hr> <p>Created by a human. 132 This site is hosted on OpenBSD via httpd. 133 This <a href=\"https://git.ntgs.me/site\">website source code</a>. 134 </p> 135 <p> 136 <a class=\"cc-icons\" href=\"https://creativecommons.org/licenses/by/4.0/\" 137 rel=\"license\" aria-label=\"Creative Commons Attribution 4.0 license\"> 138 <span class=\"cc-icon cc\"></span> 139 <span class=\"cc-icon by\"></span> 140 </a> 141 CC BY-4.0 142 Copyright © 2026 Shivakumar. 143 </p> 144 </footer>" 145 :html-head-include-default-style nil 146 :html-head-include-scripts nil 147 :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"/style.css\" /> 148 <script src=\"/theme.js\" defer></script>" 149 :section-numbers nil 150 :with-toc nil)) 151 152 (setq org-publish-project-alist 153 `(("pages" 154 :base-directory ,(expand-file-name "org/" my/root) 155 :base-extension "org" 156 :recursive nil 157 :publishing-directory ,(expand-file-name "html/" my/root) 158 ,@my/html-opts) 159 160 ("blog" 161 :base-directory ,(expand-file-name "org/blog/" my/root) 162 :base-extension "org" 163 :recursive nil 164 :publishing-directory ,(expand-file-name "html/blog/" my/root) 165 :auto-sitemap t 166 :sitemap-filename "../sitemap.org" 167 :sitemap-function my/sitemap 168 :sitemap-sort-files anti-chronologically 169 :sitemap-format-entry my/sitemap-entry 170 ,@my/html-opts) 171 172 ("static" 173 :base-directory ,(expand-file-name "org/" my/root) 174 :base-extension "css\\|js\\|woff2\\|svg" 175 :recursive t 176 :publishing-directory ,(expand-file-name "html/" my/root) 177 :publishing-function org-publish-attachment) 178 179 ("site" 180 :components ("blog" "pages" "static")))) 181 182 ;;;###autoload 183 (defun publish-blog () 184 (interactive) 185 (org-publish "site" t) 186 (message "Site published to %s" (expand-file-name "html/" my/root))) 187 188 (provide 'publish) 189 ;;; publish.el ends here