#+title: Publishing my Website with Org #+author: Howard X. Abrams #+date: 2020-12-22 #+tags: emacs org A literate programming file for publishing my website using org. #+begin_src emacs-lisp :exports none ;;; org-publishing --- Publishing my website using org. -*- lexical-binding: t; -*- ;; ;; © 2020-2023 Howard X. Abrams ;; Licensed under a Creative Commons Attribution 4.0 International License. ;; See http://creativecommons.org/licenses/by/4.0/ ;; ;; Author: Howard X. Abrams ;; Maintainer: Howard X. Abrams ;; Created: December 22, 2020 ;; ;; This file is not part of GNU Emacs. ;; ;; *NB:* Do not edit this file. Instead, edit the original literate file at: ;; ~/other/hamacs/org-publishing.org ;; And tangle the file to recreate this one. ;; ;;; Code: #+end_src * 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: #+begin_src emacs-lisp :results silent (require 'ox-html) (require 'ox-rss) (require 'ox-publish) #+end_src Render my code with my font colors: #+begin_src emacs-lisp :results silent (use-package htmlize) #+end_src Variable settings: #+begin_src emacs-lisp (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/")) #+end_src Since I have two specific websites, I create two variables for those destinations: #+begin_src emacs-lisp (setq ha-publishing-howardabrams (concat org-mode-publishing-directory "howardabrams") ha-publishing-howardism (concat org-mode-publishing-directory "howardisms")) #+end_src * 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). #+begin_src emacs-lisp (add-to-list 'org-publish-project-alist `("website" :base-directory "~/dropbox/website-howardabrams" :publishing-directory ,ha-publishing-howardabrams)) #+end_src ** The Blog My main blog made up a collection of org files: #+begin_src emacs-lisp (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 "
" :html-head-extra ,(jack-html '((: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://emacs.ch/@howard")) (:meta (@ :http-equiv "X-Clacks-Overhead" :content "GNU Terry Pratchett")))))) #+end_src Why not break out the images and other static files into a separate project: #+begin_src emacs-lisp (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)) #+end_src The RSS generation seems to be something I do /later/ once I have my site working: #+begin_src emacs-lisp (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"))) #+end_src And let’s make some blends of the individual projects: #+begin_src emacs-lisp (add-to-list 'org-publish-project-alist `("blog" :components ("blog-content" "blog-static" "blog-rss"))) #+end_src ** 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: #+begin_src emacs-lisp (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)) #+end_src As above, we can separate the publishing of the images and other static files: #+begin_src emacs-lisp (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 "/other/") :recursive t :publishing-function org-publish-attachment)) #+end_src ** 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/. #+begin_src emacs-lisp (add-to-list 'org-publish-project-alist `("hamacs" :base-directory "~/other/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 "~/other/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)) #+end_src ** 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. #+begin_src emacs-lisp (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)) #+end_src * Including Sections In the project definitions, I reference a =pre-= and =postamble= that allow me to inject some standard HTML file headers and footers: #+begin_src emacs-lisp (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)))) #+end_src Another helper function for the content of website is to make sure to update =index.org=, so that the RSS gets generated. #+begin_src emacs-lisp (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))) #+end_src * Uploading Using =rsync= to keep published files in sync with my website: #+begin_src emacs-lisp (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)))) #+end_src * Keybindings Make it easy to publish all projects or single project: #+begin_src emacs-lisp (with-eval-after-load 'ha-org (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"))))) #+end_src And let's put a /leader key/ sequence for my favorite file on my website: #+begin_src emacs-lisp (ha-leader "f h" '(:ignore t :which-key "howards") "f h i" '("website index" . (lambda () (find-file (expand-file-name "index.org" "~/website"))))) #+end_src * Technical Artifacts :noexport: Let's =provide= a name so we can =require= it: #+begin_src emacs-lisp :exports none (provide 'ha-org-publishing) ;;; ha-org-publishing.el ends here #+end_src Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~ #+description: A literate programming version for publishing my website using org. #+property: header-args:sh :tangle no #+property: header-args:emacs-lisp :tangle yes #+property: header-args :results none :eval no-export :comments no mkdirp yes #+options: num:nil toc:t todo:nil tasks:nil tags:nil date:nil #+options: skip:nil author:nil email:nil creator:nil timestamp:nil #+infojs_opt: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js