How to publish Emacs org files as xhtml
Table of Contents
- First things first: sharpen tools before good at work
- The basic foler structure for this project
- Configuration of the org publishing project on Emacs
- To publish
Everything should be made as simple as possible, but not any simpler – Albert Einstein
Org-mode is an effective plian text authoring system in Emacs with uniqe support for literate programming and reproducible research. With a markup language, Emacs Org is used for keeping notes, maintaining TODO lists, and project planning. Org mode can perform as a core source authoring system with export from its org files to various formats, such as HTML, LaTeX, Open Document, Markdown, and so forth. The features of Org-mode are as follows:
- Elegant Markup
- Structured Editing
- Superior Source Code
- Take Control of Tasks
- Capture Data From Anywhere
- Export and Publish
- Extremely Extensible
To facilitate content sharing and utilise the future-proof file format in plain text, in this project, we demonstrate how to export and publish Org-mode file to XHTML with different themes to create a static fancy website, hosted in GitHub.
First things first: sharpen tools before good at work
In this project, the basic tools we just needed are Emacs (V28.1) with the org-mode (V9.5.3) and a web broswer (Google Chrome).
The .emacs.d could be found: https://github.com/Ethanlinyf/General-Pure-Emacs
The basic foler structure for this project
The files and folders are orgnised as follows:
~/Org-site/ |- public/ | |- .git/ | |- img/ | |- theme/ | |- CSS/ | |- js/ | |- index.html | |- nav.html |- source/ | |- .git/ | |- img/ | |- theme/ | |- CSS/ | |- js/ | |- org/ | |- index.org | |- nav.org |- backup
Available and maintained through Git and GitHub
Maintance by Git and remote repository at GitHub
Version control by git for the folder ~/Org-site and create a private repository (ethanlinyf.github.io) in my GitHub (Ethanlinyf). Then synchronise the two subfolders to two branches:
In the folder, ~/Org-site/public/
echo "* Public Website" >> README.org git init git add README.org git commit -m "first commit" git branch -M main git remote add origin firstname.lastname@example.org:Ethanlinyf/ethanlinyf.github.io git push -u origin main
In the folder, ~/Org-site/source/
echo "* source" >> README.org git init git add README.org git commit -m "first commit" git branch -M source git remote add origin email@example.com:Ethanlinyf/ethanlinyf.github.io git push -u origin source
The following maintance for this project folder, we just need three steps for the two folders mapping to the two branches respectively: public & source:
git add . git commit -m "<commit message>" git push
Published through GitHub to make sharing available
GitHub Pages is available in public repositories with GitHub Free and GitHub Free for organizations, and in public and private repositories with GitHub Pro, GitHub Team, GitHub Enterprise Cloud, and GitHub Enterprise Server.
- register a domaian name A fency domain name is needed to replace the default one (ethanlinyf.github.io). In this project, "thethingsengion.org" is registered for this web service.
- GitHub Pages for web service
GitHub Pages is designed to host this project pages from the GitHub repository
This GitHub Pages site is currently being built from the main branch related to the public folder of the project folder structure, which is exported to be made public.
- Theme chooser
These css and js files were provided and maintained in this project. So, we needn't to specify a theme to publish this site with a Jekyll theme.
- Enforce HTTPS
With a layer of encryption to prevents others from snooping on or tampering with traffic to this website, HTTPS is enable for this project.
Configuration of the org publishing project on Emacs
To specify publishing behaviours from org to html, we have to setup the variable of org-publish-project-alist, which is defined in 'ox-publishing.el'.
There are two major components to publish org to xthml and one extra can be used to publish all with one command:
- The source components
- The static components
- The publish components
These three components will be included in the following configuration structure:
(require 'ox-publish) (setq org-publish-project-alist '( ;; The three components here ))
Settings for source components
In this setting, a publishing function "org-html-publish-to-html" to publish dynamic contents, wich refers to note-taken org files converted to xhtml files for publishing. The most important settings are as follows: The most important settings here are:
|base-directory||The components root directory|
|base-extension||Filename suffix without the dot|
|publishing-directory||The base directory where all our files will be published|
|recursive||If t, include subdirectories|
|publishing-function||How org should process the files in this component|
The html head could also be included, as well as the validation link. Other defualt settings, such as with-toc, headline-levels, can be specified by the export options templates, which will be further discussed.
("orgfiles" ;; source and public for this project. :base-directory "~/Org-site/source/org/" ;; the source org folder to export html files :publishing-directory "~/Org-site/public/" ;; publishing-directory for this project ;; :preparation-function ;; :complete-function :base-extension "org" ;; :exclude "PrivatePage.org" ;; regexp supported ;; :include :recursive t ;:auto-sitemap t ;:sitemap-filename "sitemap.org" ;:sitemap-title "Sitemap" ;:sitemap-sort-folders last ;;Publishing behaviours :publishing-function org-html-publish-to-html ;; Generic properties for this project :headline-levels 4 ;; org-export-headline-levels :language "en" ;; org-export-default-language :section-numbers nil ;; org-export-with-section-numbers :with-planning t ;; org-export-with-planning :with-priority t ;; org-export-with-priority ; ;; :with-tags not-in-toc ;; org-export-with-tags :with-toc t ;; org-export-with-toc :html-doctype "html5" ;; org-html-doctype ;; :html-metadata-timestamp-format "%Y-%m-%d" ;; org-html-metadata-timestamp-format :html-head-include-default-style nil ;; org-html-head-include-default-style :html-head-include-scripts nil ;; org-html-head-include-scripts ;; :html-head ;; "<link rel=\"shortcut icon\" href=\"themes/assets/icon.png\" type=\"img/x-icon\" /> ;; <link rel=\"stylesheet\" href=\"themes/style.css\" type=\"text/css\" /> ;; <script type=\"module\" src=\"themes/main.js\" defer></script>" ;; org-html-head :html-checkbox-type unicode ;; org-html-checkbox-type :html-indent t ;; org-html-indent ;; :html-link-home "index.html" ;; org-html-link-home ;; :html-link-up "uUP" ;; org-html-link-up :html-validation-link "<a href=\"http://www.thingsengine.org/\">ThingsEngine</a>" ;; org-html-validation-link )
Settings for static components
Static components include necessary files supporting to publish the coverted source component. Some files are css/js files and meida files. So, the needed funciton of publishing-function for this static component is "org-publish-attachment", just copying from :base-directory to :publishing-directory without changing them.
;; static assets ("org-site" :base-directory "~/Org-site/source/" :base-extension "js" :publishing-directory "~/Org-site/public/" :recursive nil :publishing-function org-publish-attachment ) ("images" :base-directory "~/Org-site/source/img" :base-extension any :publishing-directory "~/Org-site/public/img" :recursive t :publishing-function org-publish-attachment ) ("themes" :base-directory "~/Org-site/source/theme/" :base-extension any :publishing-directory "~/Org-site/public/theme/" :recursive t :publishing-function org-publish-attachment ) ;; reverse static assets ("rorg-site" :base-directory "~/Org-site/public/" :base-extension "js" :publishing-directory "~/Org-site/source/" :recursive nil :publishing-function org-publish-attachment ) ("rimages" :base-directory "~/Org-site/public/img/" :base-extension any :publishing-directory "~/Org-site/source/img/" :recursive t :publishing-function org-publish-attachment ) ("rthemes" :base-directory "~/Org-site/public/theme/" :base-extension any :publishing-directory "~/Org-site/source/theme/" :recursive t :publishing-function org-publish-attachment )
Settings for publish components
You may want to publish all with just one command, which can be set in the publish components with suffixes and basenames of the project.
("website" :components ("orgfiles" "org-site" "images" "themes")) ("statics" :components ("confs" "images" "themes")) ("rstatics" :components ("rorg-site" "rimages" "rthemes"))
Then, publish everything with M-x org-publish-project RET org RET recursively to ~/Org-site/public/.
Define directory-local variables for this project
To avoid pullute the export engine,the above settings can be redifined for this project in the certain directory "~/Org-site/source" and its subdirectories. See the following examples:
((org-mode . ( (org-export-in-background . t) (org-html-htmlize-output-type . inline-css) (org-html-head-include-default-style . nil) (org-html-head-include-scripts . nil) (org-publish-project-alist . (("orgfiles" :base-directory "~/Org-site/source/org/" ;; the source org folder to export ) ;; static assets ("org-site" :base-directory "~/Org-site/source/" :base-extension "js" :publishing-directory "~/Org-site/public/" :recursive nil :publishing-function org-publish-attachment ) ("rorg-site" :base-directory "~/Org-site/public/" :base-extension "js" :publishing-directory "~/Org-site/source/" :recursive nil :publishing-function org-publish-attachment ) ("website" :components ("orgfiles" "org-site" "images" "themes")) )) (eval . (progn (defun save-and-publish-website() "Save all buffers and publish." (interactive) (when (yes-or-no-p "Really save and publish current project?") (save-some-buffers t) (org-publish-project "website" t) (message "Site published done."))) (define-minor-mode auto-save-and-publish-file-mode "Toggle auto save and publish current file." :global nil :lighter "" (if auto-save-and-publish-file-mode ;; When the mode is enabled (progn (add-hook 'after-save-hook #'save-and-publish-file :append :local)) ;; When the mode is disabled (remove-hook 'after-save-hook #'save-and-publish-file :local))) (add-hook 'org-mode-hook #'auto-save-and-publish-file-mode))))))
See more details of this settings in the GitHub repository, "General-Pure-Emacs".
Needed functions to facilitate the publishing
To improve the efficiency, more functions are define to facilitate the publishing.
(defun save-and-publish-statics () "Just copy statics like js, css, and image file .etc." (interactive) (org-publish-project "statics" t) (message "Copy statics done.")) (defun save-and-publish-rstatics () "Just copy statics like js, css, and image file .etc. Which is a reverse operation of `save-and-publish-statics'." (interactive) (org-publish-project "rstatics" t) (message "Copy rstatics done.")) (defun save-and-publish-file () "Save current buffer and publish." (interactive) (save-buffer t) (org-publish-current-file t)) (defun delete-org-and-html () "Delete current org and the relative html when it exists." (interactive) (when (yes-or-no-p "Really delete current org and the relative html?") (let ((fileurl (concat "~/site/public/" (file-name-base (buffer-name)) ".html"))) (if (file-exists-p fileurl) (delete-file fileurl)) (delete-file (buffer-file-name)) (kill-this-buffer) (message "Delete org and the relative html done.")))) (defun just-delete-relative-html () "Just delete the relative html when it exists." (interactive) (when (yes-or-no-p "Really delete the relative html?") (let ((fileurl (concat "~/site/public/" (file-name-base (buffer-name)) ".html"))) (if (file-exists-p fileurl) (progn (delete-file fileurl) (message "Delete the relative html done.") ) (message "None relative html."))))) (defun preview-current-buffer-in-browser () "Open current buffer as html." (interactive) (let ((fileurl (concat "http://127.0.0.1:8080/" (file-name-base (buffer-name)) ".html"))) (save-and-publish-file) (unless (httpd-running-p) (httpd-start)) (browse-url fileurl)))
In Emacs, use the package "simple-httpd"
(use-package simple-httpd :ensure t :config (setq httpd-root "~/Org-site/public"))
And/Or, in the host system, use "Live Server"
There are two reasons for using this:
- Having the page reload automatically after changes to files can accelerate development.
You don't need to install any browser plugins or manually add code snippets to your pages for the reload functionality to work, see "How it works" sectionbelow for more information. If you don't want/need the live reload, you shouldprobably use something even simpler, like the following Python-based one-liner:
python -m SimpleHTTPServer
- How to use it