The Publishing Script of this Website
Using org-mode to write a publishing script as a literate program
This page is the HTML exported version of the script that generates this
website. The script is written in emacs-lisp, but is using org-mode to write it
out as a literate program. When generating, all emacs-lisp source code that you
see on this page is tangled from the original org-file to a file called
publish.el.
Table of Contents
- 1. Package Archives
- 2. Dependencies
- 3. Configuration
- 4. Helper functions
- 5. Filter out links to
index.html - 6. Replace auto-generated org-ids
- 7. Comment Section
- 8. Footnotes Section workaround
- 9. TODO Keyword Lists
- 10. RSS Feed Generation
- 11. Tagging System
- 12. Prepare publishing function
- 13. Custom Export Function
- 14. Filter: Insert Header
- 15. Org-publish Customization
- 16. org-publish
- 17. Code snippets
- 18. Resources
1. Package Archives
;; set package install dir to local directory
(require 'package)
(setq package-user-dir (expand-file-name "./.packages"))
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
("elpa" . "https://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")))
(package-initialize)
(unless package-archive-contents (package-refresh-contents))
2. Dependencies
(package-install 'htmlize) ; enable export of #+begin_src code blocks
(package-install 'glsl-mode) ; for coloring glsl src blocks
(package-install 'dash) ; used by org-id solution
(require 'cl-lib)
(require 'glsl-mode) ; syntax highlighting for glsl source blocks
(require 'ox-publish)
(require 'org)
; for :IGNORE: headlines
(package-install 'org-contrib) ; for ox-extra
(require 'ox-extra)
(ox-extras-activate '(latex-header-blocks ignore-headlines))
(require 'ob-emacs-lisp)
; NOTE: not needed for exporting roam notes (?)
;(package-install 'org-roam)
;(require 'org-roam)
(add-to-list 'load-path "~/dev/org-slide")
(require 'org-slide)
3. Configuration
(setq
keywords '("TITLE" "DATE" "DESCRIPTION" "IMAGE" "TAGS[]" "FILETAGS") ; keywords to parse from .org files
org-html-htmlize-output-type 'css
org-export-allow-bind-keywords t ; Allows #+BIND: in a buffer
org-confirm-babel-evaluate nil ; needed to enable org-babel src-block execution from a script
org-src-fontify-natively t)
; set footnotes to be h3, everything else is default
(setq org-html-footnotes-section
"<div id=\"footnotes\">\n<h3 class=\"footnotes\">%s</h3>\n<div id=\"text-footnotes\">\n%s\n</div>\n</div>")
4. Helper functions
(defun get-org-files (directory)
"Return a list of .org files in DIRECTORY excluding 'index.org'."
(cl-remove-if
(lambda (file) (string-equal (concat directory "/" "index.org") file))
(directory-files-recursively directory "\\.org$")))
(defun get-org-file-keywords (file)
(with-temp-buffer
(insert-file-contents file)
(list file (org-collect-keywords keywords))))
(defun sort-keyword-list-by-date (keyword-list &optional new-to-old)
"Sort the list by the date value of the form <YYYY-MM-DD HH:MM>"
(sort keyword-list
(lambda (a b)
(let* ((date-str-a (replace-regexp-in-string "<\\[\\]>" "" (cadr (assoc "DATE" (cadr a)))))
(date-str-b (replace-regexp-in-string "<\\[\\]>" "" (cadr (assoc "DATE" (cadr b))))))
(if new-to-old
(string> date-str-a date-str-b)
(string< date-str-a date-str-b))))))
(defun article-marked-for-noexport-p (article)
(string-match-p (regexp-quote "noexport") (cadr (assoc "TAGS[]" (cadr article)))))
(setq tags-to-ignore '("noexport"
"economics"
"philosophy"
"music"
"law"
"germany"
"psychology"
"modeling"
"business"
"engineering"
"physics"))
(defun roam-note-marked-for-noexport-p (article)
"Return non-nil if the ARTICLE has one of the tags in `tags-to-ignore` in its FILETAGS."
(let* ((filetags (cadr (assoc "FILETAGS" (cadr article))))
(re (regexp-opt tags-to-ignore)))
(when filetags (string-match-p re filetags))))
5. Filter out links to index.html
Replace links to e.g. "article/index.html" to just "article".
(defun filter-out-index-html (transcoded-data-string backend communication-channel-plist)
(when (org-export-derived-backend-p backend 'html)
; NOTE "/index.html" doesn't get replaced in case of internal links for some reason...
(string-replace "index.html" ""
(string-replace "/index.html" "" (substring-no-properties transcoded-data-string)))))
(add-to-list 'org-export-filter-link-functions 'filter-out-index-html)
6. Replace auto-generated org-ids
Normally org-mode randomly generates an org-id for every heading and uses those
links, which causes a bunch of noise in commits. This filter strips out all
org-ids from exported html files. We can instead use the :CUSTOM_ID: property
for a heading that we want to link to.
NOTE: this seems to break caching using org-timestamps.
See:
- zzamboni.org | How to easily create and use human-readable IDs in Org mode an…
- GitHub - alphapapa/unpackaged.el: Export to HTML with useful anchors
(require 'easy-mmode)
(require 'dash)
(define-minor-mode unpackaged/org-export-html-with-useful-ids-mode
"Attempt to export Org as HTML with useful link IDs.
stead of random IDs like \"#orga1b2c3\", use heading titles,
de unique when necessary."
:global t
(if unpackaged/org-export-html-with-useful-ids-mode
(advice-add #'org-export-get-reference :override #'unpackaged/org-export-get-reference)
(advice-remove #'org-export-get-reference #'unpackaged/org-export-get-reference)))
(defun unpackaged/org-export-get-reference (datum info)
"Like `org-export-get-reference', except uses heading titles instead of random numbers."
(let ((cache (plist-get info :internal-references)))
(or (car (rassq datum cache))
(let* ((crossrefs (plist-get info :crossrefs))
(cells (org-export-search-cells datum))
;; Preserve any pre-existing association between
;; a search cell and a reference, i.e., when some
;; previously published document referenced a location
;; within current file (see
;; `org-publish-resolve-external-link').
;;
;; However, there is no guarantee that search cells are
;; unique, e.g., there might be duplicate custom ID or
;; two headings with the same title in the file.
;;
;; As a consequence, before re-using any reference to
;; an element or object, we check that it doesn't refer
;; to a previous element or object.
(new (or (cl-some
(lambda (cell)
(let ((stored (cdr (assoc cell crossrefs))))
(when stored
(let ((old (org-export-format-reference stored)))
(and (not (assoc old cache)) stored)))))
cells)
(when (org-element-property :raw-value datum)
;; Heading with a title
(unpackaged/org-export-new-title-reference datum cache))
;; NOTE: This probably breaks some Org Export
;; feature, but if it does what I need, fine.
(org-export-format-reference
(org-export-new-reference cache))))
(reference-string new))
;; Cache contains both data already associated to
;; a reference and in-use internal references, so as to make
;; unique references.
(dolist (cell cells) (push (cons cell new) cache))
;; Retain a direct association between reference string and
;; DATUM since (1) not every object or element can be given
;; a search cell (2) it permits quick lookup.
(push (cons reference-string datum) cache)
(plist-put info :internal-references cache)
reference-string))))
(defun unpackaged/org-export-new-title-reference (datum cache)
"Return new reference for DATUM that is unique in CACHE."
(cl-macrolet ((inc-suffixf (place)
`(progn
(string-match (rx bos
(minimal-match (group (1+ anything)))
(optional "--" (group (1+ digit)))
eos)
,place)
;; HACK: `s1' instead of a gensym.
(-let* (((s1 suffix) (list (match-string 1 ,place)
(match-string 2 ,place)))
(suffix (if suffix
(string-to-number suffix)
0)))
(setf ,place (format "%s--%s" s1 (cl-incf suffix)))))))
(let* ((title (org-element-property :raw-value datum))
(ref (url-hexify-string (substring-no-properties title)))
(parent (org-element-property :parent datum)))
(while (--any (equal ref (car it))
cache)
;; Title not unique: make it so.
(if parent
;; Append ancestor title.
(setf title (concat (org-element-property :raw-value parent)
"--" title)
ref (url-hexify-string (substring-no-properties title))
parent (org-element-property :parent parent))
;; No more ancestors: add and increment a number.
(inc-suffixf ref)))
ref)))
(unpackaged/org-export-html-with-useful-ids-mode)
7. Comment Section
Adds a GitHub Issues-based comment section using utterances. Only applies to
articles that are marked with the keyword #+COMMENTS: t.
7.1. Loading animation script
Following javascript animates the comment section title until the lazy-loaded comment section has loaded in.
const commentSectionTitle = document.getElementById('comment-section-title');
const commentsDiv = document.getElementById('comment-section');
commentSectionTitle.style.animation = 'loading 0.6s infinite alternate';
document.addEventListener('DOMContentLoaded', function() {
const observer = new MutationObserver(function(mutationsList) {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
for (let node of mutation.addedNodes) {
if (node.nodeName === 'DIV') {
for (const child of node.children) {
if (child.tagName === 'IFRAME') {
child.addEventListener('load', function() {
commentSectionTitle.style.animation = 'none';
});
}
}
}
}
}
}
});
observer.observe(commentsDiv, { childList: true });
});
7.2. HTML Layout
Note the usage of single quotes instead of double quotes for attribute values. This way we can use noweb to include the html without having to escape strings.
<hr>
<div id='comment-section'>
<h3 id='comment-section-title'>Comments</h3>
<script src='https://utteranc.es/client.js'
repo='dandersch/andersch.dev'
issue-term='pathname'
label='.💬'
theme='photon-dark'
crossorigin='anonymous'
async>
</script>
<script type='text/javascript'>
<<comment-loading-animation-js>>
</script>
</div>
7.3. Elisp variable
(setq comment-section-html "<<comment-section-html>>" )
8. Footnotes Section workaround
If we include above HTML at the very end of an org-file using #+BEGIN_EXPORT
html, org-mode will still append the footnotes section below that (if the
article ever used [fn::footnote text]). As a workaround, we define below
function to later add as a filter-hook to insert the HTML at the very end.
; needed because otherwise footnotes will be below the comment section
(defun insert-comment-section (contents html-backend info)
(when (string-match "</main>" contents)
(replace-match (concat comment-section-html "</main>") t t contents 0)))
9. TODO Keyword Lists
Fills the primary datastructure of this script of the form:
("article.org" (("TITLE" "Article Title") ("TAGS" "tag1 tag2")))
; TODO put together to a single keyword-list
;(setq keyword-list
; '(
; ("article" '("article.org" (("TITLE" "Article Title") ("TAGS" "tag1 tag2"))))
; ("project" '("project1.org" (("TITLE" "Article Title") ("TAGS" "tag1 tag2")))
; )
(setq article-keyword-list '())
(setq project-keyword-list '())
(setq other-keyword-list '())
(setq notes-keyword-list '())
; usage:
; (cadr (assoc "TITLE" (cadr (assoc "article" article)))
; NOTE workaround to pass keyword-list to a source-block in an org file
; (else "Symbol’s function definition is void" error when publishing)
(defun get-article-keyword-list () article-keyword-list)
(defun get-project-keyword-list () project-keyword-list)
(defun get-other-keyword-list () other-keyword-list)
(defun get-notes-keyword-list () notes-keyword-list)
; fill & sort keyword-lists for project/, article/, other/
(defun fill-keyword-lists ()
(dolist (article (get-org-files "article"))
(let ((article-keywords (get-org-file-keywords article)))
(unless (article-marked-for-noexport-p article-keywords)
(push (get-org-file-keywords article) article-keyword-list))))
(setq article-keyword-list (sort-keyword-list-by-date article-keyword-list t))
(dolist (project (get-org-files "project"))
(let ((project-keywords (get-org-file-keywords project)))
(unless (article-marked-for-noexport-p project-keywords)
(push (get-org-file-keywords project) project-keyword-list))))
(setq project-keyword-list (sort-keyword-list-by-date project-keyword-list t))
(dolist (other (get-org-files "other"))
(let ((other-keywords (get-org-file-keywords other)))
(unless (article-marked-for-noexport-p other-keywords)
(push (get-org-file-keywords other) other-keyword-list))))
(setq other-keyword-list (sort-keyword-list-by-date other-keyword-list t))
; article-keyword-list == (cdr (assoc "article" keyword-list))
;(setq keyword-list `(,(cons "article" article-keyword-list)
; ,(cons "project" project-keyword-list)
; ,(cons "other" other-keyword-list))))
; TODO append
(setq keyword-list '())
(setq keyword-list (append keyword-list
`(("article" . ,article-keyword-list)
("project" . ,project-keyword-list)
("other" . ,other-keyword-list)))))
; fill & sort keyword-lists for notes/ (called by roam project)
(defun roam-fill-keyword-lists ()
(dolist (note (get-org-files org-roam-directory))
(let ((notes-keywords (get-org-file-keywords note)))
(unless (roam-note-marked-for-noexport-p notes-keywords)
(push (get-org-file-keywords note) notes-keyword-list))))
(setq notes-keyword-list (sort-keyword-list-by-date notes-keyword-list t))
; article-keyword-list == (cdr (assoc "article" keyword-list))
(setq keyword-list `(,(cons "notes" notes-keyword-list)))
)
10. RSS Feed Generation
Generates a simple rss feed for articles specifically.
(defun generate-main-rss-feed ()
; rss header, check with https://validator.w3.org/feed/
(with-temp-file "feed.xml"
(insert
(let* ((website-title "andersch.dev")
(homepage "https://andersch.dev")
(rss-filepath "/feed.xml"))
(concat "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n"
"<channel>\n"
(format "<title>%s</title>\n" website-title)
"<!-- <lastBuildDate>Wed, 15 Dec 2021 00:00:00 +0000</lastBuildDate> -->\n" ; TODO insert todays date
(format "<atom:link href=\"%s%s\" rel=\"self\" type=\"application/rss+xml\"/>\n" homepage rss-filepath)
(format "<link>%s</link>\n" homepage)
"<description>Stuff on programming</description>\n"
"<language>en-us</language>\n"))))
; rss entries
(dolist (article article-keyword-list)
(write-region
(format
(concat "<item>\n"
"<title>%s</title>\n"
"<link>%s</link>\n"
"<guid>%s</guid>\n"
"<description>\n"
"<p>%s</p>\n"
"<img src=\"https://andersch.dev/%s\"/>\n"
"</description>\n"
"<pubDate>%s</pubDate>\n</item>\n")
(cadr (assoc "TITLE" (cadr article)))
(concat "https://andersch.dev/" (string-replace "/index.org" "" (car article)))
(concat "https://andersch.dev/" (string-replace "/index.org" "" (car article)))
(cadr (assoc "DESCRIPTION" (cadr article)))
(concat (string-replace "index.org" "" (car article)) (cadr (assoc "IMAGE" (cadr article))))
(format-time-string "%a, %d %b %Y %H:%M:%S %z" (seconds-to-time (org-time-string-to-time (cadr (assoc "DATE" (cadr article))))))
)
nil "feed.xml" 'append))
; rss ending
(write-region "</channel>\n</rss>" nil "feed.xml" 'append))
11. Tagging System
Generates a tag.org for every unique tag across all articles.
(defun generate-tag-files ()
; collect all tags
(setq article-tags '())
(dolist (article article-keyword-list)
(setq article-tags (append (split-string (cadr (assoc "TAGS[]" (cadr article))) " +") article-tags)))
(delete-dups article-tags)
(setq project-tags '())
(dolist (project project-keyword-list)
(setq project-tags (append (split-string (cadr (assoc "TAGS[]" (cadr project))) " +") project-tags)))
(delete-dups project-tags)
(setq other-tags '())
(dolist (other other-keyword-list)
;(print other) ; ("other/publish/index.org" (("TITLE" "The Script ") ("DESCRIPTION" "...") ("DATE" "<..>") ("IMAGE" "preview.png") ("TAGS[]" "lisp org web")))
(setq other-tags (append (split-string (cadr (assoc "TAGS[]" (cadr other))) " +") other-tags)))
(delete-dups other-tags)
(setq notes-tags '())
(dolist (notes notes-keyword-list)
(setq notes-tags (cl-remove-if #'string-empty-p (append (split-string (cadr (assoc "FILETAGS" (cadr notes))) ":") notes-tags))))
(delete-dups notes-tags)
(setq all-tags '())
(setq all-tags (cl-concatenate 'list article-tags project-tags other-tags notes-tags))
(delete-dups all-tags)
; generate .org files for all tags
(dolist (tag all-tags)
(with-temp-file (format "tag/%s.org" tag)
(insert (format "#+TITLE: Pages tagged %s\n" tag))))
; append "* Articles" headline
(dolist (tag article-tags)
(write-region (format "* Articles tagged ~%s~\n" tag) nil (format "tag/%s.org" tag) 'append))
; add entry of an article to its tag.org's
(dolist (article article-keyword-list)
(dolist (tag (split-string (cadr (assoc "TAGS[]" (cadr article))) " +"))
(write-region (format "- [[../%s][%s]]\n" (car article) (cadr (assoc "TITLE" (cadr article))))
nil (format "tag/%s.org" tag) 'append)))
; append "* Projects" headline
(dolist (tag project-tags)
(write-region (format "* Projects tagged ~%s~\n" tag) nil (format "tag/%s.org" tag) 'append))
; add entry of a project to its tag.org's
(dolist (project project-keyword-list)
(dolist (tag (split-string (cadr (assoc "TAGS[]" (cadr project))) " +"))
(write-region (format "- [[../%s][%s]]\n" (car project) (cadr (assoc "TITLE" (cadr project))))
nil (format "tag/%s.org" tag) 'append)))
; append "* Other" headline
(dolist (tag other-tags)
(write-region (format "* Other tagged ~%s~\n" tag) nil (format "tag/%s.org" tag) 'append))
; add entry of other to its tag.org's
(dolist (other other-keyword-list)
(dolist (tag (split-string (cadr (assoc "TAGS[]" (cadr other))) " +"))
(write-region (format "- [[../%s][%s]]\n" (car other) (cadr (assoc "TITLE" (cadr other))))
nil (format "tag/%s.org" tag) 'append)))
; append "* Notes" headline
(dolist (tag notes-tags)
(write-region (format "* Notes tagged ~%s~\n" tag) nil (format "tag/%s.org" tag) 'append))
; add entry of notes to its tag.org's
(dolist (notes notes-keyword-list)
(dolist (tag (cl-remove-if #'string-empty-p (split-string (cadr (assoc "FILETAGS" (cadr notes))) ":")))
; TODO hardcoded string-replace
(write-region (format "- [[../%s][%s]]\n" (string-replace "~/org/roam" "notes" (car notes)) (cadr (assoc "TITLE" (cadr notes))))
nil (format "tag/%s.org" tag) 'append)))
)
12. Prepare publishing function
Gets called by org-publish before the main publishing step.
(defun prepare-publishing (project-properties)
(fill-keyword-lists)
(generate-main-rss-feed)
(generate-tag-files))
(defun roam-prepare-publishing (project-properties)
(roam-fill-keyword-lists))
13. Custom Export Function
(defun org-html-publish-to-html-noexport (plist filename pub-dir)
"Publish an org file to HTML except one's that are tagged noexport"
; 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
(let ((notes-keywords (get-org-file-keywords filename)))
(if (roam-note-marked-for-noexport-p notes-keywords)
nil
(org-html-publish-to-html plist filename pub-dir)))
)
14. Filter: Insert Header
(defun add-title-headline (backend)
; insert after :PROPERTY: drawer
(if (string-match-p ":PROPERTIES:" (thing-at-point 'line t))
(progn (search-forward ":END:" nil t) (forward-line 1)))
(unless (string= (org-get-title) "andersch.dev") ; NOTE: hardcoded workaround
(when (eq backend 'html)
(let* ((tags (if org-file-tags org-file-tags nil))
(date (cadar (org-collect-keywords '("DATE"))))
(description (cadar (org-collect-keywords '("DESCRIPTION"))))
(title (org-get-title)))
(insert "\n#+begin_export html\n")
(when (or tags date)
(insert "<div class=\"tags-date-box\">")
(if date (insert (format "<div class=\"date\"><span class=\"timestamp\">%s</span></div>" date)))
(when tags
(insert "<div class=\"tags\"><code>[ ")
(dolist (tag tags) (insert (format "<a href=\"/tag/%s.html\">%s</a> " tag tag)))
(insert "]</code></div>"))
(insert "</div>"))
(when title (insert (format "<h1>%s</h1>" title)))
(when description (insert (format "<p class=\"subtitle\">%s</p>" description)))
(insert "\n#+end_export\n")
)
)
))
(add-hook 'org-export-before-processing-functions #'add-title-headline)
15. Org-publish Customization
See here for exporter-specific properties and use (describe-variable
'org-publish-project-alist) for documentation on general options.
15.1. Project: andersch.dev
(setq andersch-dev
(list "andersch.dev"
:recursive t
:base-directory "./"
:publishing-directory "./"
:publishing-function 'org-html-publish-to-html ;; may be a list of functions
:preparation-function 'prepare-publishing ;; called before publishing
; :completion-function ;; called afterwards
; :base-extension ;; extension of source files
; :html-extension "" ;; extension of generated html files (without dot)
:exclude (regexp-opt '("code.org" "README.org" "publish.org")) ;; regex of files to exclude
; :include ;; list of files to include
; :html-doctype "html5" ;; default is "xhtml-strict"
:html-divs '((preamble "header" "top")
(content "main" "content")
(postamble "footer" "postamble"))
:html-html5-fancy t
; TODO head defined else where and noweb it here
:html-head (concat "<link rel=\"icon\" type=\"image/x-icon\" href=\"/favicon.ico\">\n"
"<link rel=\"stylesheet\" href=\"/style.css\">\n"
; NOTE import ubuntu font for now TODO embed in repo
"<link rel=\"stylesheet\" type=\"text/css\" href=\"https://fonts.googleapis.com/css?family=Ubuntu:regular,bold&subset=Latin\">"
"<script type=\"text/javascript\" src=\"/script.js\" defer></script>"
)
:html-preamble t
:html-preamble-format `(("en" ,(with-temp-buffer (insert-file-contents "header.html") (buffer-string))))
:html-postamble nil ;; don't insert a footer with a date etc.
:html-link-home ""
:html-head-include-default-style t
:html-self-link-headlines t ; headings contain hyperlinks to themselves
:auto-sitemap nil ;; https://orgmode.org/manual/Site-map.html
; :sitemap-filename "sitemap.org"
; :sitemap-title
; :sitemap-style 'tree ;; list or tree
; :sitemap-sort-files 'anti-chronologically
:exclude-tags org-export-exclude-tags
:html-prefer-user-labels t ;; prefer CUSTOM_ID over auto-generated id's
:html-format-headline-function org-html-format-headline-function
; function will be called with six arguments:
; TODO the todo keyword (string or nil).
; TODO-TYPE the type of todo (symbol: todo, done, nil)
; PRIORITY the priority of the headline (integer or nil)
; TEXT the main headline text (string).
; TAGS the tags (string or nil).
; INFO the export options (plist).
:makeindex nil ;; https://orgmode.org/manual/Generating-an-index.html
:with-title nil ;; we include our own header
:with-tags nil ;; * headline :tag:
:with-author nil
:with-creator nil ;; don't include emacs and org versions in footer
:with-toc nil ;; no table of contents
:section-numbers nil ;; no section numbers for headings
:html-validation-link nil ;; don't show validation link
:time-stamp-file nil ;; don't include "Created: <timestamp>" in footer
:with-date nil))
15.2. Project: roam.andersch.dev
Wiki portion of the site as a separate project.
(setq org-roam-directory "~/org/roam") ; NOTE not part of the repo
(setq roam-andersch-dev-images
(list "roam.andersch.dev-images"
:base-directory org-roam-directory
:base-extension "jpg\\|gif\\|png\\|svg\\|wasm\\|js"
:publishing-directory "./notes/"
:publishing-function 'org-publish-attachment))
(setq roam-andersch-dev
(list "roam.andersch.dev"
:recursive nil
:base-directory org-roam-directory
:publishing-directory "./notes/"
:publishing-function 'org-html-publish-to-html-noexport
:preparation-function 'roam-prepare-publishing
:html-divs '((preamble "header" "top")
(content "main" "content")
(postamble "footer" "postamble"))
:html-html5-fancy t
; TODO head defined else where and noweb it here
:html-head (concat "<title>andersch.dev</title>\n"
"<link rel=\"icon\" type=\"image/x-icon\" href=\"/favicon.ico\">\n"
"<link rel=\"stylesheet\" href=\"/style.css\">\n"
; NOTE import ubuntu font for now TODO embed in repo
"<link rel=\"stylesheet\" type=\"text/css\" href=\"https://fonts.googleapis.com/css?family=Ubuntu:regular,bold&subset=Latin\">"
"<script type=\"text/javascript\" src=\"/script.js\" defer></script>"
)
:exclude-tags org-export-exclude-tags
:html-self-link-headlines t ;; headings contain hyperlinks to themselves
:html-prefer-user-labels t ;; prefer CUSTOM_ID over auto-generated id's
:html-preamble t
:html-preamble-format `(("en" ,(with-temp-buffer (insert-file-contents "header.html") (buffer-string))))
:html-postamble nil ;; don't insert a footer with a date etc.
:auto-sitemap nil
:makeindex nil ;; https://orgmode.org/manual/Generating-an-index.html
:with-title nil
:with-author nil
:with-creator nil ;; don't include emacs and org versions in footer
:with-toc nil ;; no table of contents
:section-numbers nil ;; no section numbers for headings
:html-validation-link nil ;; don't show validation link
:time-stamp-file nil ;; don't include "Created: <timestamp>" in footer
:with-date nil))
;; TODO roam-andersch-dev-attachment
15.3. Add projects to org-publish-project-alist
(setq org-publish-project-alist (list andersch-dev roam-andersch-dev-images roam-andersch-dev))
16. org-publish
; caching
(setq org-publish-timestamp-directory "./.org-timestamps/")
(org-publish-remove-all-timestamps) ; call to avoid caching, NOTE: required now because of our org-id replacement
; NOTE: if broken links, try to run (org-roam-update-org-id-locations)
(setq org-id-locations-file "/home/da/org/.orgids") ; should fix broken links
(setq org-export-with-broken-links nil) ; NOTE might be needed for broken roam ID links...
; enable caching for roam.andersch.dev
(org-publish-initialize-cache "roam.andersch.dev")
(setq org-publish-use-timestamps-flag t)
(org-publish "roam.andersch.dev-images")
(org-publish "roam.andersch.dev")
(message "Build complete: roam.andersch.dev")
; NOTE caching causes problems with updating titles etc., so we reset the cache before publishing
(setq org-publish-use-timestamps-flag nil)
(org-publish "andersch.dev" t)
(message "Build complete: andersch.dev")
17. Code snippets
17.1. Generate Article Image Preview
TODO only used to generate preview image, move to filter function