UP | HOME

Publishing an org-site

the goal

The raison d'être for this site is two-fold:

  1. I dislike almost every static site-generation tool out there. The least worst so far is Pelican, but I don't like dealing with Python programs, especially dealing with virtualenvs.
  2. My preference as to writing formats is, in order, org-mode → ReST → markdown. Pelican doesn't support org-mode, though. I also like writing math in my pages/posts when it comes up, and being able to easily integrate mathjax is great. \(f(x) = x^2\). See?

My goal is to be able to write as much in org-mode and publish directly.

Also, up front I'd like to thank Aaron for his publishing bits. It's been helpful.

how it's built

I keep my uConsole nearby most of the time as a companion device, and most of my org files are edited and published from there. I use org-roam for note capture, with specific pages and blog entries under their own headings. Git keeps the org files sync'd across machines; I was doing syncthing, but that led to a bunch of errors, and I don't want to run it in the background on the uConsole.

site structure

Update: The site no longer looks like; I had to keep the published directory structure the same as its source to get cross-project linking working.

My org directory is structure roughly like this:


~/org $ tree -d
.
├── entries
├── notes
├── pages
├── private
├── publish
│   ├── e
│   ├── m
│   ├── p
│   └── s
│       └── fonts
├── roam
└── static
    └── fonts

The private directory is everything that shouldn't be published, and publish is where all the hatemail1 ends up. For everything else, there's a corresponding directory under publish.

local directory publish directory notes
/ / non-recursive, mostly the index.
entries/ e/ blog entries
notes m/ misc. pages that shouldn't be mapped
pages p/ pages that should be mapped
roam n/ roam notebook
static s/ static files, mostly CSS and fonts

I really like the short-name variants, and maybe I'll just rename my local directories this way.

current tasks

As per my blog post, I'm trying to do as much with emacs as I can. I still need to figure out how to do some useful things:

  • [X] How do I remove my name and created from the footer? (Answer: org was caching things and not updating properly, but including :html-postamble nil in the alist seems to do the trick.)
  • [ ] How do I set up a custom template for blog entries?
  • [ ] How do I use org-roam? I'd like to get away from Obsidian, but I have (at current accounting) about 2,500 notes there.
  • [X] The cache seems to get buggered regularly.
  • [X] How do I get linking working?
  • [ ] Look into org-journal and maybe org-static-blog for the blog-specific part.
  • [ ] Learn a lot more about org than the dumb stuff I do with it now.
  • [ ] Fix footnote formatting (marginalia, even?)

For linking, specifically, you have to know that in my org directory, I used to have entries/ that publishes to /e/. In my root index.org, I have a link to file:entries/index.org, but it doesn't translate over in the generated HTML; it doesn't pick up that it's being sent to /e/. I ended up having to make the published directories the same name as the source directory. A minor annoyance (I really liked the /e, /p, etc naming scheme). So now, this link should work: blog index.

As for fixing the cache, it's a brute force solution but I now have the following elisp:

(global-set-key (kbd "C-c c")
                (lambda () (interactive)
                  (message "deleting org-timestamps")
                  (delete-directory (expand-file-name "~/.org-timestamps") t)))

It works, probably not ideal. Also not thrilled with the idea of auto-deleting a directory, but it should work. Until it's not. Fortunately, I have sync-thing backing up stuff.

org.el

;;; -*- lexical-binding: t; -*-
;;; publishing.el
;;;
;;; publishing my notes docs

(use-package simple-httpd
  :ensure t
  :config
  (setq httpd-root "~/org/publish") ; Set to your Org publish output directory
  (setq httpd-port 4000))                ; Default port

(let ((*httpd-server-running* nil))
  (defun httpd-toggle-server () (interactive)
         (if *httpd-server-running*
             (httpd-stop)
           (httpd-start))
         (setq *httpd-server-running* (not *httpd-server-running*))
         (not *httpd-server-running*)))

(use-package org-ref
  :ensure t
  :config
  (setq org-ref-bibliography-files '("~/org/static/references.bib")
        org-cite-global-bibliography '("~/org/static/references.bib")
        org-ref-default-bibliography "~/org/static/references.bib"
        org-cite-export-processors '((t csl "~/org/static/csl/ieee.csl"))
        org-ref-default-citation-style "ieee"
        org-ref-pdf-directory "~/org/static/pdf/"
        org-ref-notes-directory "~/org/"
        org-ref-csl-default-directory "~/org/static/csl/"))

(defun metacircular-publish () (interactive)
       (org-roam-db-sync)
       (org-publish-project "org" t))

(defun metacircular-publish-2 () (interactive)
       (org-roam-db-sync)
       (org-publish-project "org"))

(defun metacircular-upload () (interactive)
       (shell-command "rsync --delete-after -auqz ~/org/publish/ kyle@web.metacircular.net:/srv/www/metacircular/"))

(defun metacircular-deploy () (interactive)
       (metacircular-publish)
       (metacircular-upload))

(defun metacircular-deploy-2 () (interactive)
       (metacircular-publish-2)
       (metacircular-upload))

(defun visit-roam-index () (interactive)
       (find-file
        (localize-path "org/roam/index.org")))

(defun get-current-org-date-timestamp ()
  (format-time-string (car org-time-stamp-formats) (current-time)))

(defun get-current-org-datetime-timestamp ()
  (format-time-string (cdr org-time-stamp-formats) (current-time)))

(use-package org-roam
  :after org
  :custom
  (org-roam-directory (file-truename "~/org/roam/"))
  (org-roam-db-autosync-enable)
  (org-roam-capture-templates
   '(("d" "default" plain "%?"
      :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+title: ${title}\n#+date: %U\n#+options: toc:nil num:nil\n#+filetags:\n\n")
      :unnarrowed t)
     ("a" "article" plain "- Source: [[%^{Url}][%^{Title}]]\n- Author: /%^{Author}/\n- Year: /%^{Year}/\n\n* Highlights / Notes\n"
      :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+title: ${title}\n#+date: %U\n#+options: toc:nil num:nil\n#+filetags: article:\n\n")
      :unnarrowed t)
     ("b" "book" plain "- Title: %^{Title}\n- Author: /%^{Author}/\n- Year: /%^{Year}/\n\n* Highlights / Notes\n"
      :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+title: ${title}\n#+date: %U\n#+options: toc:nil num:nil\n#+filetags: book:\n\n")
      :unnarrowed t)
     ("p" "paper" plain "Cite: [cite:%&{citekey}]\n- Title: %^{Title}\n- Author: /%^{Author}/\n- Year: /%^{Year}/\n\n* Highlights / Notes\n"
      :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+title: ${title}\n#+date: %U\n#+options: toc:nil num:nil\n#+filetags: book:\n\n#+print_bibliography:\n\n#+cit_export: csl ../static/csl/ieee.csl\n#+bibliography: ../static/references.bib")
      :unnarrowed t)
     ("j" "project" plain "- Repo: [[%^{Url}][%^{title}]]\n\nOne sentence summary.\n\n** Tasks [/]"
      :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
                         "#+title: ${title}\n#+date: %(get-current-org-date-timestamp)\n#+options: toc:nil num:nil\n#+filetags: project\n\n")
      :unnarrowed t)))
  (org-roam-dailies-capture-templates
   '(("d" "default" entry
      "* %?"
      :target (file+head "%<%Y-%m-%d>.org"
                         "#+title: %(get-current-org-date-timestamp)\n"))))
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n g" . org-roam-graph)
         ("C-c n r" . org-roam-ref-add)
         ("C-c n s" . org-roam-db-sync)
         ("C-c n t" . org-roam-tag-add)
         ("C-c n i" . org-roam-node-insert)
         ("C-c n c" . org-roam-capture)
         ("C-c n j" . org-roam-dailies-capture-today)
         ("C-c n p" . metacircular-publish-2)
         ("C-c n P" . metacircular-publish)
         ("C-c n u" . metacircular-upload)
         ("C-c n d" . metacircular-deploy-2)
         ("C-c n D" . metacircular-deploy)
         ("C-c n v" . visit-roam-index)
         ("C-c n w" . httpd-toggle-server))
  :config
  (setq org-roam-completion-everywhere t)
  (setq org-roam-node-display-template
        (concat "${title:40} "
                (propertize "${tags:40}" 'face 'org-tag)
                "${file}"))
  (require 'org-roam-protocol))

(setq org-roam-dailies-directory (file-truename "~/org/roam/j/"))

(setq org-roam-dailies-capture-templates
      '(("d" "default" entry
         "* Tasks [0/1]\n  + [ ] %?\n\n* How did you improve your situation?"
         :target (file+head "%<%Y-%m-%d>.org"
                            "#+title: %<%Y-%m-%d>\n\n"))))

(defvar *org-remote-site* "/ssh:web.metacircular.net:/srv/www/metacircular/"
  "Where should org-mode files be published?")
(require 'ox-publish)
(require 'htmlize)
(setq org-html-head ""
      org-html-head-extra ""
      org-html-head-include-default-style nil
      org-html-head-include-scripts nil
      org-html-preamble nil
      org-html-postamble nil
      org-html-use-infojs nil)

;;; the org-site is split into several main components, listed
;;; below. Note that it publishes to a directory inside the org
;;; directory. For now, I'm doing this as I test out style and other
;;; things. Once I settle on a good route, I'll look at publishing to
;;; my web server directly.
(setq org-publish-project-alist
      '(

;;; static contains... static files. Notably, the CSS and fonts.
        ("org-site-static"
         :base-directory "~/org/static/"
         :base-extension "css\\|js\\|png\\|jpg\\|gif\\|txt\\|pdf\\|mp3\\|ogg\\|woff2\\|bib\\|csl"
         :publishing-directory "~/org/publish/static/"
         :recursive t
         :publishing-function org-publish-attachment)

;;; entries contains blog posts. Simple-as.
        ("org-site-entries"
         :author "kyle"
         :email "kyle@imladris"
         :base-directory "~/org/entries/"
         :exclude "\.~undo-tree~$"
         :html-doctype "html5"
         :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"/static/main.css\" />"
         :html-head-include-scripts nil
         :html-html5-fancy t
         :html-link-home "/"
         :html-link-up "/entries/"
         :html-postamble t
         :publishing-directory "~/org/publish/entries/"
         :publishing-function org-html-publish-to-html
         :auto-sitemap t
         :html-head-include-default-style nil
         :sitemap-filename "index.org"
         :sitemap-sort-files anti-chronologically
         :sitemap-title "Blog Posts")

;;; pages contains notes that should be sitemapped. They're scoped to a
;;; different directory accordingly.
        ("org-site-pages"
         :base-directory "~/org/pages/"
         :exclude "\.~undo-tree~$"
         :html-doctype "html5"
         :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"/static/main.css\" />"
         :html-head-include-scripts nil
         :html-html5-fancy t
         :html-link-home "/"
         :html-link-up "/pages/"
         :publishing-directory "~/org/publish/pages/"
         :publishing-function org-html-publish-to-html
         :auto-sitemap t
         :html-head-include-default-style nil
         :sitemap-filename "sitemap.org"
         :sitemap-title "metacircular pages")

;;; the site root is mostly the index page.
        ("org-site-root"
         :base-directory "~/org"
         :exclude "\.~undo-tree~$"
         :html-doctype "html5"
         :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"/static/main.css\" />"
         :html-head-include-default-style nil
         :html-head-include-scripts nil
         :html-html5-fancy t
         :html-link-home "/"
         :html-link-up "/"
         :html-postamble nil
         :publishing-directory "~/org/publish/"
         :recursive nil
         :with-author nil
         :with-date nil)

;;; notes contains a list of pages that shouldn't be sitemapped, and
;;; is scoped appropriately to a separate directory.
        ("org-site-notes"
         :base-directory "~/org/notes/"
         :exclude "\.~undo-tree~$"
         :html-doctype "html5"
         :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"/static/main.css\" />"
         :html-head-include-scripts nil
         :html-html5-fancy t
         :html-link-up "../"
         :html-link-home "http://metacircular.net/"
         :publishing-directory "~/org/publish/notes/"
         :publishing-function org-html-publish-to-html
         :html-head-include-default-style nil)

;;; org-site-static is the static files for the roam notebook, which
;;; is mostly the graph.
        ("org-site-roam-static"
         :base-directory "~/org/roam/"
         :base-extension "svg\\|png\\|jpg\\|gif\\|pdf\\|org"
         :publishing-directory "~/org/publish/roam/"
         :recursive t
         :publishing-function org-publish-attachment)

;;; roam is an experiment in linked note-taking. I'm using obsidian,
;;; but emacs is life.
        ("org-site-roam"
         :base-directory "~/org/roam/"
         :exclude "\.~undo-tree~$"
         :eval yes
         :html-doctype "html5"
         :html-head "<link rel=\"stylesheet\" type=\"text/css\" href=\"/static/main.css\" />"
         :html-head-include-scripts nil
         :html-link-home "/"
         :html-link-up "/roam/"
         :html-html5-fancy t
         :publishing-directory "~/org/publish/roam/"
         :publishing-function org-html-publish-to-html
         :html-head-include-default-style nil)

;;; org publishes everything at once.
         ("org"
          :components ("org-site-static"
                       "org-site-entries"
                       "org-site-pages"
                       "org-site-root"
                       "org-site-notes"
                       "org-site-roam-static"
                       "org-site-roam"))))

;;; org publishing keybindings: C-c
;;;   c to reset the cache
;;;   o to publish all projects
;;;   u to upload the published projects

;;; resetting the cache - needs to be done on occasion while tweaking
;;; the site.
(global-set-key (kbd "C-c c")
                (lambda () (interactive)
                  (message "deleting org-timestamps")
                  (delete-directory (expand-file-name "~/.org-timestamps") t)))

why

You'd only ask this if you've never used org-mode.

Footnotes:

1

hatemail is the correct pronounciation of HTML.