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 "<h1 class=\"subtitle\">%s</h2>" 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" :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