Skip to content

Latest commit

 

History

History
567 lines (480 loc) · 17.9 KB

project.org

File metadata and controls

567 lines (480 loc) · 17.9 KB

Project configuration

Base

Latex

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")))

CSS

HTML-ized output

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))

Default org-mode styling

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; }

My custom css

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%;
}

Combining all styling rules

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 */

Deleting unused rules

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))

Google Analytics

(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>")

Home and Up

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 "")

Preamble

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)

Custom Exports

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)))

Project Definition

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"))))

Publishing

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.