site

The source code for ntgs.me website
Log | Files | Refs | README | LICENSE

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