Publishing my Website with Org
Table of Contents
A literate programming file for publishing my website using org.
Introduction
While the Emacs community have a plethora of options for generating a static website from org-formatted files, I keep my pretty simple, and use the standard org-publish
feature.
While the following packages come with Emacs, they aren’t necessarily loaded:
(use-package org :config (require 'ox-html) (require 'ox-rss) (require 'ox-publish))
Render my code with my font colors:
(use-package htmlize :config ;; But I turn the source code coloring off to use a CSS stylesheet I ;; control, as otherwise, switching between dark and light themes ;; make the code difficult to read: (setq org-html-htmlize-output-type 'css))
Also, we need Jack, and his HTML prowess:
(use-package jack :straight (:host github :repo "tonyaldon/jack") :commands (jack-html))
Variable settings:
(setq org-publish-project-alist nil ; filled in below org-export-with-broken-links t org-mode-websrc-directory "~/website" org-mode-publishing-directory (concat (getenv "HOME") "/website-pub/"))
Since I have two specific websites, I create two variables for those destinations:
(setq ha-publishing-howardabrams (concat org-mode-publishing-directory "howardabrams") ha-publishing-howardism (concat org-mode-publishing-directory "howardisms"))
The Projects
I separate my website into distinct projects separately built:
blog-content
- The bulk of rendering my
website
org files into HTML blog-static
- Copies all assets, like images, in place
blog-rss
- Regenerate the feeder files
org-notes
- Optionally render a non-web site collection of notes.
The Website
Years of having two domain names can be confusing, but I have to keep them going now. My howardabrams.com
is essentially a single static page (oh, and email).
(add-to-list 'org-publish-project-alist `("website" :base-directory "~/dropbox/website-howardabrams" :publishing-directory ,ha-publishing-howardabrams))
The Blog
My main blog made up a collection of org files:
(add-to-list 'org-publish-project-alist `("blog-content" :base-directory ,org-mode-websrc-directory :base-extension "org" :publishing-directory ,org-mode-publishing-directory :recursive t :publishing-function org-html-publish-to-html :preparation-function org-mode-blog-prepare :export-with-tags nil :headline-levels 4 :auto-preamble nil :auto-postamble nil :auto-sitemap t :sitemap-title "Howardisms" :section-numbers nil :table-of-contents nil :with-toc nil :with-title t :with-author nil :with-creator nil :with-tags nil :with-smart-quotes t :html-doctype "html5" :html-html5-fancy t :html-head-include-default-style nil ;; :html-preamble org-mode-blog-preamble :html-postamble org-mode-blog-postamble ;; :html-postamble "<hr><div id='comments'></div>" :html-head-extra ,(jack-html '((:link (@ :rel "alternate" :type "application/rss+xml" :title "RSS" :href "https://www.howardism.org/index.xml")) (:link (@ :rel "stylesheet" :type "text/css" :href "http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700&subset=latin,latin-ext")) (:link (@ :rel "stylesheet" :type "text/css" :href "http://fonts.googleapis.com/css?family=Source+Serif+Pro:400,700&subset=latin,latin-ext")) (:link (@ :rel "stylesheet" :type "text/css" :href "http://fonts.googleapis.com/css?family=Source+Code+Pro:400,700")) (:link (@ :rel "stylesheet" :type "text/css" :href "/css/styles.css")) (:script (@ :type "text/javascript" :src "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js")) (:script (@ :src "/js/magic.js" :type "text/javascript")) (:meta (@ :name "viewport" :content "width=device-width, initial-scale=1")) (:link (@ :rel "shortcut icon" :href "/img/dragon-head.svg")) (:link (@ :rel "icon" :href "/img/dragon.svg")) (:link (@ :rel "me" :href "https://pdx.sh/@howard")) (:meta (@ :http-equiv "X-Clacks-Overhead" :content "Darol Allen")) (:meta (@ :http-equiv "X-Clacks-Overhead" :content "George Vanecek")) (:meta (@ :http-equiv "X-Clacks-Overhead" :content "Rick Cooper")) (:meta (@ :http-equiv "X-Clacks-Overhead" :content "Terry Pratchett"))))))
Why not break out the images and other static files into a separate project:
(add-to-list 'org-publish-project-alist `("blog-static" :base-directory ,org-mode-websrc-directory :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf\\|svg" :publishing-directory ,org-mode-publishing-directory :recursive t :publishing-function org-publish-attachment))
The RSS generation seems to be something I do later once I have my site working:
(add-to-list 'org-publish-project-alist `("blog-rss" :base-directory ,org-mode-websrc-directory :base-extension "org" :rss-image-url "https://howardism.org/img/dragon-head.png" :publishing-directory ,org-mode-publishing-directory :publishing-function (org-rss-publish-to-rss) :html-link-home "https://www.howardism.org/" :html-link-use-abs-url t :with-toc nil :exclude ".*" :include ("index.org")))
And let’s make some blends of the individual projects:
(add-to-list 'org-publish-project-alist `("blog" :components ("blog-content" "blog-static" "blog-rss")))
Technical Notes
I take notes on a variety of technical subjects, and since I can share these notes with others, I feel like I can publish those:
(add-to-list 'org-publish-project-alist `("tech-notes" :base-directory "~/technical/" :base-extension "org" :publishing-directory ,(concat org-mode-publishing-directory "notes/") :recursive t :publishing-function org-html-publish-to-html :headline-levels 4 :auto-preamble t :auto-sitemap t ; Generate sitemap.org automagically... :makeindex t :section-numbers nil :style ,(jack-html '(:link (@ :rel "stylesheet" :type "text/css" :href "../css/styles.css") :script (@ :type "text/javascript" :src "https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js") :link (@ :ref "stylesheet" :type "text/css" :href "http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/themes/smoothness/jquery-ui.css") :script (@ :type "text/javascript" :src "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js") :script (@ :type "text/javascript" :src "js/magic.js"))) :table-of-contents nil :with-title t :with-author nil :with-creator nil :with-tags nil))
As above, we can separate the publishing of the images and other static files:
(add-to-list 'org-publish-project-alist `("tech-notes-static" :base-directory "~/technical/" :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf\\|svg" :publishing-directory ,(concat org-mode-publishing-directory "/src/") :recursive t :publishing-function org-publish-attachment))
Literate Emacs Configuration
I’ve been committing my literate-style Emacs configuration for years now, and Github has rendered it well, but I felt I could publish this to my own web site as a cleaner version.
(add-to-list 'org-publish-project-alist `("hamacs" :base-directory "~/src/hamacs" :publishing-directory ,(concat org-mode-publishing-directory "hamacs/") :publishing-function org-html-publish-to-html :recursive t :auto-preamble nil :auto-sitemap nil :makeindex nil :section-numbers nil :html-head-include-default-style nil :html-head ,(jack-html '(:link (@ :rel "stylesheet" :type "text/css" :href "../css/styles.css"))) :html-head-extra nil :table-of-contents t :with-title t :with-author nil :with-creator nil :with-tags nil)) (add-to-list 'org-publish-project-alist `("hamacs-static" :base-directory "~/src/hamacs" :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf\\|svg" :publishing-directory ,(concat org-mode-publishing-directory "hamacs") :recursive t :publishing-function org-publish-attachment))
Airbnb
I have an ADU on my property that I rent out through Airbnb. The place is full of QR Codes that display everything from local restaurants to how to play the Raspberry Pi Arcade my son and I built.
(add-to-list 'org-publish-project-alist `("airbnb" :base-directory "~/website/airbnb" :publishing-directory ,(concat org-mode-publishing-directory "airbnb/") :publishing-function org-html-publish-to-html :recursive t :auto-preamble nil :auto-sitemap nil :makeindex nil :section-numbers nil :html-head-include-default-style nil :html-head ,(jack-html '(:link (@ :rel "stylesheet" :type "text/css" :href "airbnb.css"))) :html-head-extra nil :table-of-contents nil :with-title t :with-author nil :with-creator nil :with-tags nil)) (add-to-list 'org-publish-project-alist `("airbnb-static" :base-directory "~/website/airbnb" :base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf\\|svg" :publishing-directory ,(concat org-mode-publishing-directory "airbnb/") :recursive t :publishing-function org-publish-attachment))
Including Sections
In the project definitions, I reference a pre-
and postamble
that allow me to inject some standard HTML file headers and footers:
(defun org-mode-blog-preamble (options) "The function that creates the preamble top section for the blog. OPTIONS contains the property list from the org-mode export." (message "Preamble options: %s" (princ options)) (let ((base-directory (plist-get options :base-directory))) (org-babel-with-temp-filebuffer (expand-file-name "top-bar.html" base-directory) (buffer-string)))) (defun org-mode-blog-postamble (options) "The function that creates the postamble, or bottom section for the blog. OPTIONS contains the property list from the org-mode export." (let ((base-directory (plist-get options :base-directory))) (org-babel-with-temp-filebuffer (expand-file-name "bottom.html" base-directory) (buffer-string))))
Another helper function for the content of website is to make sure to update index.org
, so that the RSS gets generated.
(defun org-mode-blog-prepare (&optional options) "Change modification of `index.org' before publishing." (let* ((base-directory (plist-get options :base-directory)) (buffer (find-file-noselect (expand-file-name "index.org" base-directory) t))) (with-current-buffer buffer (set-buffer-modified-p t) (save-buffer 0)) (kill-buffer buffer)))
Uploading
Using rsync
to keep published files in sync with my website:
(defun ha-sync-site (project) "Sync PROJECT (an org publish project) with my website." (interactive (list (completing-read "Publish project: " org-publish-project-alist))) (let* ((host "gremlin.howardabrams.com") (conf (thread-last org-publish-project-alist (seq-filter (lambda (lst) (string-equal (car lst) project))) (car) (cdr))) (parent (plist-get conf :publishing-directory)) (combos (cond ((s-starts-with? "blog" project) '("Technical" "howardism" "Personal" "howardism" "RPG" "howardism" "index.html" "howardism" "about-me.html" "howardabrams")) ((s-starts-with? "tech" project) '("" "howardabrams")) ((s-starts-with? "hamacs" project) '("" "howardabrams")) ((s-starts-with? "airbnb" project) '("" "howardabrams"))))) ;; (dolist (tuple (seq-partition combos 2)) ;; (seq-let (src dest) tuple ;; (format "rsync -az %s/%s %s:%s" parent src host dest))) (thread-last (seq-partition combos 2) (seq-map (lambda (tuple) (seq-let (src dest) tuple (format "rsync -avz %s%s %s:%s" parent src host dest)))) (s-join "; ") (message) (async-shell-command))))
Workflows for Hamacs
A single function to publish and sync my literate initialization of Emacs.
The idea is that pushing a Git commit to this project, triggers a Forgejo Workflow, that exports the literate text as HTML to my website.
(defun hamacs-publishing-workflow () "Render and push a new web version of my Emacs initialization." (interactive) (org-publish-project "hamacs") (org-publish-project "hamacs-static") (ha-sync-site "hamacs"))
Keybindings
Make it easy to publish all projects or single project:
(ha-leader :keymaps 'org-mode-map "o p" '(:ignore t :which-key "publish") "o p a" '("all" . org-publish-all) "o p p" '("project" . org-publish-project) "o p s" '("sync site" . ha-sync-site) "o p h" '("hamacs" . (lambda () (interactive) (org-publish-project "hamacs") (sit-for 30) (ha-sync-site "hamacs"))))
And let’s put a leader key sequence for my favorite file on my website:
(ha-leader "f h" '(:ignore t :which-key "howards") "f h i" '("website index" . (lambda () (find-file (expand-file-name "index.org" "~/website")))))