#+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 <http://gitlab.com/howardabrams>
  ;; 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:
  ;;            ~/src/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
  (use-package org
    :config
    (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
    :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))
#+end_src

Also, we need Jack, and his HTML prowess:
#+begin_src emacs-lisp
  (use-package jack
    :straight (:host github :repo "tonyaldon/jack")
    :commands (jack-html))
#+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
:PROPERTIES:
:ID:       7c4cb568-93a5-44e8-8758-4f415dccf232
:END:
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 "<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"))))))
#+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 "/src/")
           :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        "~/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))
#+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
** 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 [[https://www.howardabrams.com/hamacs][my website]].

#+begin_src emacs-lisp
  (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"))
#+end_src

* Keybindings
Make it easy to publish all projects or single project:
#+begin_src emacs-lisp
  (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
