Org Publish Notes
Table of Contents
Publish Script
CSS
.org-doc { color: #7E85A0; } .org-keyword { color: #E63462; } .org-string { color: #C7EFCF; } .org-variable-name { color: #F8FBEF; font-weight: bold; } .org-function-name { color: #A8A6F2; } .org-constant { color: #F6B6C7; } .org-comment { color: #7E85A0; } .org-type { color: #FE5F55; font-weight: bold; } .org-warning { color: #C81947; } .timestamp { color: #1A1C23; font-size: smaller; } .timestamp { text-decoration: underline; } .timestamp:before { content: "Last Updated: "; } table { margin: auto; } body { color: #1A1C23; background-color: #f8fbef; font-family: "Liberation Sans", Helvetica, "Trebuchet MS", sans-serif; margin: auto; max-width: 72em; padding: 0 1em 0 1em; } pre { background-color: #333745; color: #f8fbef; } a { font-weight: bold; padding-right: .25em; } a:visited { color: black; } a:link { color: black; } ul { margin-top: 1em; padding-left: 1em; } li { margin-bottom: 1em; } #preamble { text-align: right; margin-left: auto; margin-bottom: 2em; max-width: 16em; } #preamble h4 { margin-bottom: 0.2em; padding-bottom: 0.2em; border-bottom: solid; } #postamble { max-width: 13.5em; margin-left: auto; margin-top: 4em; font-size: 0.7em; } img { max-width: 100%; } #content { max-width: 48em; } @media only screen and (min-width: 1450px) { body { font-size: 1.25em; } p { line-height: 1.5em; } #table-of-contents { position: fixed; left: 0; top: 0; font-size: 0.75em; padding: 1em; z-index: 1; display:flex; flex-direction: column; height: 100%; max-width: 14em; background-color: #f8fbef; } #table-of-contents h2 { font-size: 1em; } #text-table-of-contents, #table-of-contents:active #text-table-of-contents { display: block; overflow-y: auto; flex: 1; } }
Use package
Packages
(require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) (require 'use-package) (use-package ledger-mode :ensure t) (use-package lua-mode :ensure t) (use-package go-mode :ensure t) (use-package htmlize :ensure t)
Ox Publish
Setup
Require Org Publish Features
(require 'ox-publish)
Syntax Highlighting
- To get syntax highlighting for source code blocks I set the
htmlize
output type to CSS
(setq org-html-htmlize-output-type 'css)
Disable Heading Numbers
(setq org-export-with-section-numbers nil)
Table of Contents Heading Depth
(setq org-export-with-toc 1)
Link to Custom CSS
- I also create a head extra that includes the custom CSS
(defvar ajr-html-head-extra "\n<link rel='stylesheet' href='/css/main.css' />\n")
Nav Bar HTML Generation
- Wrote a few functions that take a list of cons pairs and generate an html nav bar
- The first element in the cons pair is the URL the second is the title
(defun ajr-nav (items) (let ((atags (apply #'concat (mapcar (lambda (item) (concat " " (ajr-nav-item (car item) (cdr item)) "\n")) items)))) (concat "<h4>Adam Richardson's Site</h4>\n" "<nav>\n" atags "</nav>\n"))) (defun ajr-nav-item (url title) (concat "<a href=\"" url "\">" title "</a>"))
Nav Bar Items
- I created variables for each nav bar item so they can be reused across multiple navs
(defvar ajr-nav-blog '("/" . "Blog")) (defvar ajr-nav-notes '("/notes/index.html" . "Notes")) (defvar ajr-nav-about '("/about.html" . "About")) (defvar ajr-nav-rss '("/rss.xml" . "RSS"))
Defining Preamble Variables
- The nav bars are going to be added to each page as
html-preamble
- This section of code creates variables that represent different nav bars for different sections of the published site
(defvar ajr-html-preamble (ajr-nav (list ajr-nav-blog ajr-nav-notes ajr-nav-about ajr-nav-rss)))
Defining Postamble Format
(defvar ajr-html-postamble " <p class=\"author\">Author: %a</p> <p class=\"date\">Date: %d</p>")
Publish Project alist
Posts
(list "org-site" :recursive t :base-directory "./" :exclude "notes\\|about" :publishing-directory "./public" :with-author "Adam Richardson" :with-email nil :auto-sitemap t :sitemap-title "Blog Posts" :sitemap-sort-folders 'ignore :sitemap-sort-files 'anti-chronologically :sitemap-filename "index.org" :sitemap-format-entry (lambda (file-or-dir style project) (if (equal file-or-dir "posts/") "**Welcome to my personal blog**" (concat (format-time-string "%Y-%m-%d" (org-publish-find-date file-or-dir project)) ": [[" (concat "file:" file-or-dir) "][" (org-publish-find-title file-or-dir project) "]]"))) :html-head-extra ajr-html-head-extra :html-preamble-format `(("en" ,ajr-html-preamble)) :html-preamble t :html-postamble-format `(("en" ,ajr-html-postamble)) :html-postamble nil :html-validation-link nil :publishing-function 'org-html-publish-to-html)
Notes
(list "org-site" :recursive t :base-directory "./notes" :exclude "posts/" :publishing-directory "./public/notes" :auto-sitemap t :sitemap-title "Notes" :sitemap-sort-files 'alphabetically :sitemap-filename "index.org" :html-head-extra ajr-html-head-extra :html-preamble-format `(("en" ,ajr-html-preamble)) :html-preamble t :html-postamble nil :html-validation-link nil :publishing-function 'org-html-publish-to-html)
Top Level
(list "org-site" :recursive nil :base-directory "./" :publishing-directory "./public/" :html-head-extra ajr-html-head-extra :html-preamble-format `(("en" ,ajr-html-preamble)) :html-preamble t :html-postamble nil :html-validation-link nil :publishing-function 'org-html-publish-to-html)
CSS
(list "org-static" :recursive t :base-directory "./css" :base-extension "css" :publishing-directory "./public/css" :publishing-function 'org-publish-attachment)
Assets
(list "org-static" :recursive t :base-directory "./" :base-extension "png\\|gif\\|jpg\\|jpeg\\|svg\\|webm\\|webp" :publishing-directory "./public/" :publishing-function 'org-publish-attachment)
Root Level Static Files
(list "org-static" :recursive t :base-directory "./" :base-extension "txt" :publishing-directory "./public/" :publishing-function 'org-publish-attachment)
Static HTML
(list "org-static" :recursive t :base-directory "./static-html" :base-extension "html\\|js\\|j5" :publishing-directory "./public/static-html" :publishing-function 'org-publish-attachment)
Actually Publishing
(org-publish-all t)
(message "Build Complete")
Generate RSS
- This series of Elisp will generate an rss.xml file in the public directory
Dependencies
(require 'org)
Constants
- These constants define the location of published html posts as well as the path to the org files for those posts
(defconst posts-html-dir "public/posts") (defconst posts-org-dir "posts") (defconst post-url-root "https://thales17.srht.site") (defconst channel-title "Adam Richardson's Site") (defconst channel-description "RSS feed of blog posts from Adam Richardson's site") (defconst rss-file "public/rss.xml") (defconst rfc-822-format "%a, %d %b %y %H:%M:%S %z")
Get RSS Link for a Post
(defun post--link (post-file) "Gets the RSS link for a post html file. The file is assumed to be just the name of the html file. No path is necessary." (concat post-url-root "/posts/" post-file))
Get the Org Path for a Post
(defun post--org-path (post-file) "Returns the relative path to the org file that corresponds to the html file." (concat posts-org-dir "/" (file-name-sans-extension post-file) ".org"))
Get the Date / Title / Description for a Post
(defun post--keyword-values (post-org-file) "Returns a list of keywords from the post org file. This looks for `date', `title' and `description' keyword values from the `post-org-file'. The format of the results list will match what `org-collect-keywords' returns." (with-temp-buffer (insert-file-contents post-org-file) (org-collect-keywords '("date" "title" "description"))))
Get the RSS Item Tag for a Post
(defun post--rss-item (post-file) "Returns the rss xml formatted item for a post html file. The `post-file' should just be the name of the file and not have any directory pathing." (let* ((keyword-alist (post--keyword-values (post--org-path post-file))) (title (cadr (assoc "TITLE" keyword-alist))) (org-date (org-timestamp-from-string (cadr (assoc "DATE" keyword-alist)))) (description (cadr (assoc "DESCRIPTION" keyword-alist)))) (concat " <item>\n" " <title>" title "</title>\n" " <link>" (post--link post-file) "</link>\n" " <pubDate>" (org-timestamp-format org-date rfc-822-format) "</pubDate>\n" " <description>" description "</description>\n" " </item>\n")))
Get the Emacs Internal Time for A Post
(defun post--date (post-file) "Returns the date property set in the corresponding org file for the `post-file'. The `post-file' is assumed to be an html file in the `posts-html-dir'." (let* ((keyword-alist (post--keyword-values (post--org-path post-file))) (org-date (cadr (assoc "DATE" keyword-alist)))) (org-timestamp-to-time (org-timestamp-from-string org-date))))
Generate the Full RSS XML
(defun post-gen-rss () (let ((sorted-posts (sort (directory-files posts-html-dir nil "\\.html$") (lambda (a b) (let ((a-date (post--date a)) (b-date (post--date b))) (time-less-p b-date a-date)))))) (concat "<rss version=\"2.0\">\n" " <channel>\n" " <title>" channel-title "</title>\n" " <description>" channel-description "</description>\n" (apply #'concat (mapcar #'post--rss-item sorted-posts)) " </channel>\n" "</rss>")))
Save the Contents of a String to File
(defun save-file (contents filename) (with-temp-buffer (insert contents) (write-file filename)))
Actually Generate RSS
(save-file (post-gen-rss) rss-file)