Nice Org to HTML pipeline

Table of Contents

tl;dr

This package generates pretty, responsive, websites from .org files and your choice of Emacs themes. You can optionally specify a header, footer, and additional CSS and JS to be included. To see the default output, for my chosen themes and with no header, footer or extras, view this README in your browser here. If you're already there, you can find the GitHub repo here.

With the README I also publish these samples, which include a list-specified header and footer:

About this package

nice-org-html defines an Emacs pipeline for exporting Org files, or publishing Org projects, to HTML. The generated HTML is optimized for readability and responsiveness, esp. of documents that include code, and makes it easy to achieve a custom look-and-feel, in part by leveraging the variety of themes available for Emacs. Under the hood, this is achieved by:

  1. Extracting colors from user-specified themes, inserting these in a CSS template (nice-org-html.css), and injecting the result into a <style> element.
  2. Inserting toggle-buttons for light- and dark-mode viewing, and a click-to-jump table-of-content view, governed by the included scripts (nice-org-html.js).
  3. Extending the default org-to-html export backend to include copy-to-clipboard buttons for source blocks.
  4. Injecting (optional) header and footer, CSS styling, and JS scripts.

How to use it

Setup

  • Get the source files on your load path. E.g. with MELPA and use-package:

    EMACS-LISP
    (require 'package)
    (add-to-list 'package-archives
                 '("melpa" . "http://melpa.org/packages/"))
    (package-initialize)
    
    (unless (package-installed-p 'use-package)
      (package-install 'use-package))
    
  • Add something like this to your configuration file:

    EMACS-LISP
    (use-package nice-org-html
      :hook (org-mode . nice-org-html-mode)
      :config
      (setq nice-org-html-theme-alist
            '((light . solo-jazz)
              (dark  . tomorrow-night-eighties)))
      (setq nice-org-html-default-mode 'dark)
      (setq nice-org-html-headline-bullets nil) 
    
  • If values for any of these variables are not specified, these defaults will be used:
    • Themes: tsdh-light and tsdh-dark distributed with GNU Emacs
    • Default viewing mode: 'query
      • This will check against the visitor's browser-set preference, and default to 'dark if no preference is specified. Anti-fingerprinting settings may prevent reading the browser setting, or cause the browser to mask it (usually, by showing a preference for 'light).
    • Headline bullets: 'nil – i.e. bullets will not be prefixed to headlines
      • To prefix headlines with the default bullets, instead use:

        EMACS-LISP
        (setq nice-org-html-headline-bullets t)
        
      • Or you can specify headline bullets with a plist like this (the defaults):

        EMACS-LISP
        (setq nice-org-html-headline-bullets
              '(:h1 "◉" :h2 "✸" :h3 "▷" :h4 "⦁" :h5 "○"))
        
  • Admonitions are the only HTML elements with colours guaranteed to be independent of your choice of source theme
    • Admonitions are rendered from these org special blocks: _note, _tip, _important, _warning, _caution

      Note

      This is a note

      Tip

      This is a tip

      Warning

      This is a warning

      Important

      This is important

      Caution

      This is a caution

    • The colours of these blocks can be custom-set to hex values by modifying the variable nice-org-html-clr-defs:

      ELISP
      (setq nice-org-html-clr-defs
            ((light . (:note "#0969da" :tip "#1a7f37" :important "#8250df" :warning "#9a6700" :caution "#cf222e"))
             (dark  . (:note "#0969da" :tip "#1a7f37" :important "#8250df" :warning "#9a6700" :caution "#cf222e"))))
      

      or declaring some value(s) for :clr-defs in a call to the nice-org-html-publishing-function macro, discussed below.

  • Further look-and-feel customization is handled through the nice-org-html-options plist. The following properties and values are supported (so far):

    Property Value Effect
    :layout "compact" Header and footer links always contained in drawer
    :layout "expanded" Header and footer links never contained in drawer
    :collapsing t Clicking headlines collapses / expands sections
    :src-lang t Source language displayed in header of source blocks

Exporting

  • After loading this package and hooking nice-org-html-mode to org-mode, when you open an Org buffer, nice-org-html-mode will activate.
  • If you then interactively export your Org buffer via the dispatcher (C-c C-e), you will be presented with options to export to 'nice HTML', in addition to the basic HTML export options.
  • When exporting to 'nice HTML', you will be prompted for (all optional) options plist, header and footer, and CSS and JS filepaths. If specified, the contents of those variables will be injected, accordingly, into the generated HTML.
    • CSS and JS must be paths (strings) to files with contents to be injected.
    • Header and footer may be a path to an HTML file, or a list of cons pairs.
    • A default HTML structure will be used for the header and/or footer, if a list of this form:

      EMACS-LISP
      '(("left" . uri0) ("link1" . "uri1") ... ("linkN" . "uriN"))
      
      • left will be displayed as anchor text, and hyperlinked if uri0 is non-nil.
      • link1linkN will be hyperlinks, collapsed into a drawer for mobile viewers.
  • These variables may also be set globally, and then used or overridden during interactive export. For example:

    EMACS-LISP
    (setq nice-org-html-header
          '(("title" . "/home.html")
            ("foo" . "/foo.html") ("bar" . "/bar.html")))
    ;; Or: (setq nice-org-html-header "path/to/your/header.html")
    
    (setq nice-org-html-footer
          '(("© author" . "mailto:a@b.c")
            ("oof" . "https://oof.a") ("rab" . "https://rab.b")))
    ;; Or: (setq nice-org-html-header "path/to/your/footer.html")
    
    (setq nice-org-html-css "path/to/your/style.css")
    
    (setq nice-org-html-js "path/to/your/script.js")
    
    (setq nice-org-html-options '(:layout "compact" :collapsing t))
    

Publishing

  • This package is particularly well-suited to publishing Org projects, as websites comprised of many linked pages. The included publishing function, nice-org-html-publish-to-html, relies on global values for all of the above variables. To use it, in specifying the value of org-publish-projects-alist, for a given project just specify:

    EMACS-LISP
    ;; ...
    :publishing-function #'nice-org-html-publish-to-html
    ;; ...
    
  • For more granular per-project configuration, there is a publishing-function-generating macro, nice-org-html-publishing-function, which takes values for the above configuration variables, and defines a publishing function unique to that invocation. Note: the options plist is constructed out of remaining arguments to this macro, so options should be specified directly. For example, your per-project configuration - i.e. the value of org-publish-projects-alist - may look something like this:

    EMACS-LISP
    `(("project-x/files"
       :base-extension "org"
       :base-directory "path/to/source-x/"       
       :publishing-directory "path/to/target-x/"
       ;; ...
       :publishing-function
       ,(nice-org-html-publishing-function
         :theme-alist ((light . spacemacs-light) (dark . spacemacs-dark))
         :default-mode dark
         :headline-bullets (:h1 "" :h2 "✸" :h3 "▷" :h4 "" :h5 "")
         :clr-defs ((light . (:note "#ffc0cb"))     ;; notes pink in light mode
                    (dark  . (:warning "#ff4800"))) ;; warnings orange in dark mode         
         :header "path/to/your/header.html"
         :footer "path/to/your/footer.html"
         :css "path/to/your/style.css"
         :js "path/to/your/script.js"
         :layout "compact"
         :collapsing t)))
    
    
    • Any variable for which values are not specified will take the global (or default) value.

Things to keep in mind

  • For downloaded themes, you must run M-x load-theme once before exporting or publishing, so that Emacs "recognizes" the theme as safe to load.
  • You can specify "" as the bullet for a headline level to omit bullets for that level.
  • The HTML specified by nice-org-html-header and nice-org-html-footer will inherit the package default styling, unless further styling for these is defined in the file specified by nice-org-html-css.
  • For easy CSS customization, the contents of each user-specified HTML file are wrapped together in a <div> element; with id = 'injected-header' and id = 'injected-footer', respectively, and both with class = 'injected'.
  • The CSS specified by nice-org-html-css may also use the CSS variables defined in nice-org-html.css, which ultimately refer to Emacs face attribute values determined by your chosen themes.
  • The CSS specified by nice-org-html-css may override the default styling.

Contributing

  • If you find a bug and want it fixed, please raise an issue.
  • If you would like to add or refine something feel free to:
    1. Fork the repo
    2. Clone your fork and develop / use it
    3. Create a pull request – I'll probably approve it
  • Note: there are so many themes for Emacs, that it's tough to make them all render nicely with a uniform mapping of face-attributes to CSS variables. But fear not! If you find that this package doesn't "play nice" with your preferred theme, there is a built-in mechanism for re-mapping variables on a per-theme basis, precisely to handle these outliers. Just raise an issue, or take a look at the CSS specific to 'leuven' themes and create a PR with something similar.

Credits

  • All of the theme developers, without which this package would be useless.
  • Shi Tianshu's org-html-themify provided the basic model for CSS interpolation.
  • Various stackoverflow posts were of great help, but alas, I've lost the links.