org-mode exports documents that include TeX snippets with a Javascript
header that pulls the MathJax library from an orgmode server. Their
distribution of MathJax is out of date and doesn’t support some of the
symbols in the amssymb
package. Instead of pulling MathJax from
orgmode, we’ll pull the latest version directly from a CDN.
(with-eval-after-load 'ox-html
(add-to-list 'org-html-mathjax-options '(path "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML")))
By default, the htmlize library generates inline-css when exporting to HTML. This is nice because the output is self-contained, but it also doesn’t interact well with CodeMirror.
Instead, we’ll have htmlize tag elements with class names. We generate the css class definitions for all of the font-faces used in this Emacs session and save them to a temporary file.
hl-line-face
isn’t defined (don’t know where this is supposed to
come from) and apparently we need it, else we get an error from
(org-html-htmlize-generate-css)
:
(make-face 'hl-line-face)
Now we can generate the CSS for all of the faces:
(setq org-html-htmlize-output-type "css")
(org-html-htmlize-generate-css)
(set-buffer "*html*")
;; Skip everything before `<!--`
(re-search-forward "<!--")
(delete-region (point-min) (point))
;; Don't use a black background for the body
(re-search-forward "^\s*body {")
(beginning-of-line)
(let ((body-start (point)))
(re-search-forward "}")
(end-of-line)
(delete-region body-start (point)))
;; Skip everything after `-->`
(re-search-forward "-->")
(beginning-of-line)
(delete-region (point) (point-max))
(buffer-substring (point-min) (point-max))
I took these rules from org-html-style-default
.
.title { text-align: center;
margin-bottom: .2em; }
.subtitle { text-align: center;
font-size: medium;
font-weight: bold;
margin-top:0; }
.todo { font-family: monospace; color: red; }
.done { font-family: monospace; color: green; }
.priority { font-family: monospace; color: orange; }
.tag { background-color: #eee; font-family: monospace;
padding: 2px; font-size: 80%; font-weight: normal; }
.timestamp { color: #bebebe; }
.timestamp-kwd { color: #5f9ea0; }
.org-right { margin-left: auto; margin-right: 0px; text-align: right; }
.org-left { margin-left: 0px; margin-right: auto; text-align: left; }
.org-center { margin-left: auto; margin-right: auto; text-align: center; }
.underline { text-decoration: underline; }
#postamble p, #preamble p { font-size: 90%; margin: .2em; }
p.verse { margin-left: 3%; }
ble { border-collapse:collapse; }
caption.t-above { caption-side: top; }
caption.t-bottom { caption-side: bottom; }
td, th { vertical-align:top; }
th.org-right { text-align: center; }
th.org-left { text-align: center; }
th.org-center { text-align: center; }
td.org-right { text-align: right; }
td.org-left { text-align: left; }
td.org-center { text-align: center; }
dt { font-weight: bold; }
.footpara { display: inline; }
.footdef { margin-bottom: 1em; }
.figure { padding: 1em; }
.figure p { text-align: center; }
.inlinetask {
padding: 10px;
border: 2px solid gray;
margin: 10px;
background: #ffffcc;
}
#org-div-home-and-up
{ text-align: right; font-size: 70%; white-space: nowrap; }
textarea { overflow-x: auto; }
.linenr { font-size: smaller }
.code-highlighted { background-color: #ffff00; }
.org-info-js_info-navigation { border-style: none; }
#org-info-js_console-label
{ font-size: 10px; font-weight: bold; white-space: nowrap; }
.org-info-js_search-highlight
{ background-color: #ffff00; color: #000000; font-weight: bold; }
.org-svg { width: 90%; }
pre {
border: 1px solid #ccc;
padding: 8pt;
font-family: monospace;
overflow: auto;
margin: 1.2em;
}
This rule doesn’t play well with CodeMirror blocks, so I moved it from
pre
to pre.example
.
pre.example { box-shadow: 3px 3px 3px #eee; }
The base CSS generated by org-mode includes rules to display src block labels for a ton of languages that I will never use. I pared down the list of languages to the ones that I may actually use.
pre.src {
position: relative;
overflow: visible;
padding-top: 1.2em;
}
pre.src:before {
display: none;
position: absolute;
color: black;
background-color: white;
top: -10px;
right: 10px;
padding: 3px;
border: 1px solid black;
}
pre.src:hover:before { display: inline;}
/* Languages per Org manual */
pre.src-clojure:before { content: 'Clojure'; }
pre.src-calc:before { content: 'Emacs Calc'; }
pre.src-emacs-lisp:before { content: 'Emacs Lisp'; }
pre.src-java:before { content: 'Java'; }
pre.src-latex:before { content: 'LaTeX'; }
pre.src-lisp:before { content: 'Lisp'; }
pre.src-org:before { content: 'Org mode'; }
pre.src-python:before { content: 'Python'; }
pre.src-R:before { content: 'R'; }
pre.src-ruby:before { content: 'Ruby'; }
pre.src-scheme:before { content: 'Scheme'; }
pre.src-sh:before { content: 'shell'; }
pre.src-sql:before { content: 'SQL'; }
pre.src-makefile:before { content: 'Makefile'; }
pre.src-perl:before { content: 'Perl'; }
pre.src-bash:before { content: 'bash'; }
Use “night-mode” for src blocks.
.org-src-container {
color: white; /* white text */
background-color: #121212; /* black (more or less) background */
}
Underline links and make them blue.
a { color: blue; }
a:visited { color: purple; }
a:hover { text-decoration: underline; }
Here’s the CSS I want added on top of everything else. For example, squeeze all the text into the middle of the page so the lines aren’t so long.
body {
font-family: Palatino, Garamond, Baskerville, "Times New Roman", Serif;
}
#content {
max-width:45em;
margin:auto;
padding:0 1em;
}
img {
max-width:100%;
max-height:100%;
}
There is no execute function for css, but we can tangle it to a temporary file. We’ll strip this file down to only the classes we actually need after we’ve generated the HTML.
(when (or (not (boundp 'blog/css-temp-file))
(not blog/css-temp-file) )
(setq-local blog/css-temp-file (make-temp-file "css")))
blog/css-temp-file
<<my-custom-css>>
/* BEGIN: Generated from faces */
<<generated-css-from-faces()>>
/* END: Generated from faces */
/* BEGIN: Org-mode base css */
<<org-base-css-1>>
<<org-base-css-2>>
<<org-base-css-3>>
<<org-base-css-4>>
<<org-base-css-5>>
/* END: Org-mode base css */
find build/posts/ -name "*.html" -print0 | xargs -0 -I {} \
perl -lne 'while ( /class="([^"]+)"/g ) { @classes = split(/\s+/, $1); print $_ foreach(@classes)}' {} | sort | uniq
Now we pare down the generated class definitions to only those that were actually referenced from an HTML file.
use warnings;
use strict;
# highlighting whitespace looks weird since I have dark Emacs theme
my @blacklist = ('org-highlight-indentation');
my %classes = map { @$_[0] => 1 } @$css_classes; # flatten array of arrays
foreach my $cls (@blacklist) { delete $classes{$cls}; }
open(my $fh, '<', $input_file) or die "Failed to get stylesheet: $!\n";
while (<$fh>) {
next if (/^\s*\.(.+) \{/ and not exists $classes{$1}) .. /\}/;
print;
}
close $fh;
(with-temp-buffer
(insert ugly-css)
(css-mode)
(indent-region (point-min) (point-max))
(write-file output-file))
(defvar blog/google-analytics-snippet "<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-90887327-1', 'auto');
ga('send', 'pageview');
</script>")
Don’t include a “Home” and “Up” link - we build our own navbar in the preamble.
(setq org-html-link-up ""
org-html-link-home ""
org-html-home/up-format "")
Put a “Home” link at the top of every page except the home page.
(defun website/build-preamble (info)
(let ((input-file (file-name-nondirectory (plist-get info :input-file))))
(if (not (string= "index.org" input-file))
"\
<ul style=\"list-style-type:none; margin:0; padding:0\">
<li><a href=\"../index.html\">Home</a><li>
</ul>")))
(setq org-html-preamble #'website/build-preamble)
This is an alist mapping source .org files to functions.
(setq blog/custom-html-publishing-functions nil)
If a given .org file has no entry in this alist, we’ll use org’s default html exporter.
(defun blog/export-post (plist filename pub-dir)
"Publish a post (a .org file) to HTML.
Uses a custom handler if one is specified, else fall back to the
default org-html export function.
FILENAME is the filename of the Org file to be published. PLIST
is the property list for the given project. PUB-DIR is the
publishing directory.
Return output file name."
(interactive "P")
(let ((export-handler (or (cdr (assoc filename blog/custom-html-publishing-functions))
'org-html-publish-to-html)))
(message "Publishing post '%s' using handler '%s'" filename (symbol-name export-handler))
(funcall export-handler plist filename pub-dir)))
This alist defines the project.
(setq org-publish-project-alist `(("home"
:base-directory "./src/"
:publishing-directory "./build/"
:base-extension "org"
:publishing-function blog/export-post
:recursive nil
:html-link-up ""
:html-link-home ""
:html-postamble nil
:html-head-include-default-style nil
:html-head-extra ,(concat "<link rel=\"stylesheet\" type=\"text/css\" href=\"../css/base.css\"/>\n"
blog/google-analytics-snippet))
("posts"
:base-directory "./src/posts"
:publishing-directory "./build/posts"
:base-extension "org"
:publishing-function blog/export-post
:recursive t
:html-link-up "../index.html"
:html-link-home ""
:html-postamble nil
:html-head-include-default-style nil
:html-head-extra ,(concat "<link rel=\"stylesheet\" type=\"text/css\" href=\"../css/base.css\"/>\n"
blog/google-analytics-snippet))
("js"
:base-directory "./src/js"
:publishing-directory "./build/js"
:base-extension "js"
:recursive t
:publishing-function org-publish-attachment)
("css"
:base-directory "./src/css"
:publishing-directory "./build/css"
:base-extension "css"
:recursive t
:publishing-function org-publish-attachment)
("assets"
:base-directory "./src/assets"
:publishing-directory "./build/assets"
:recursive t
:base-extension any
:publishing-function org-publish-attachment)
("website" :components ("home" "posts" "js" "css" "assets"))))
First, execute every emacs-lisp src-block under src/posts
. Some of
these src-blocks might register custom exporters in
blog/custom-html-publishing-functions
.
(defun org-babel-execute-buffer-only-lang (lang)
"Execute src blocks in buffer whose language is LANG"
(org-babel-map-executables nil
(when (equal lang (org-element-property :language (org-element-at-point)))
(org-babel-execute-src-block))))
(let ((org-src-files (directory-files-recursively (concat default-directory "src/posts") "^[^\.]+\.org$")))
(dolist (f org-src-files)
(progn
(find-file f)
(org-babel-execute-buffer-only-lang "emacs-lisp")
(org-babel-tangle))))
Tangle this file, too, since it contains the base stylesheet.
(org-babel-tangle)
And now we’re ready to publish.
(let ((force t))
(org-publish-project "website" force))
Now that we’ve generated the HTML, we know the exact set of CSS
classes that we actually need. We can remove all of the auto-generated
cruft that’s not used. (TODO: Use :completion-function
for this)
First let’s extract the list of CSS classes that are actually referenced.
Now we can strip CSS class definitions that aren’t used from our
temporary css file and pretty-print the output to
build/css/base.css
.