Powered By Org
This blog is not an Org tutorial. It is a quick walkthrough of my QoL Org publishing config, which you may use to make your life easier, or to learn some tips and tricks. I may also add, that all of the config down below is put in my ~/.emacs.d/init.el file.
What is Org?
Org Mode is a major mode inside Emacs, which is mainly used as a note-taking system. Org has been my go-to system for blogging. Org Mode was written in Emacs Lisp. Emacs Lisp is a dialect made for Emacs, and it it used strictly for the implementation of function in Emacs. Most of Emacs is written in Elisp (Emacs Lisp), and the remainder, as well as the Lisp interpreter is written in C.
Org Mode is a highly customizable and extensible major mode for Emacs, and it has been implemented in Emacs for many years now (in the beginning, it was a third party package). Org Mode is, in my opinion, the best tool for note-taking, planning and authoring many things.
Some people might say, that "Org is just a better Markdown format", which also holds true, and I am not going to argue regarding that.
Extending Org Mode, we get Org Publish, which is yet another highly customizable framework, built on top of Org. With Org Publish
, you can do several things, mainly: publish to HTML, PDF, RSS, etc with ease, applying your custom PATHs, stylings and formatting.
How I write Org
This part contains all my personal settings in Org. You may skip this chapter if you're not interested in the visual aspects of Org. The Org Publishing part will come later.
Function declarations
First of all, I like to write Org in a nicely set, centered view. I also like to disable many useless features in document writing, such as display line numbering and visual lines mode. I do that by calling those functions assigned to a hook, using my own declared function.
(defun gybfuns/pretty-funs ()
(writeroom-mode 1)
(display-line-numbers-mode -1)
(visual-line-mode 1))
(add-hook 'org-mode-hook 'gybfuns/pretty-funs)
Org Mode Folds
I like to open all my documents having all the headings folded by default:
(setq org-startup-folded t)
This makes the whole document look nice and tidy on startup.
Org Indent Mode
org-startup-indented
is a must have for clean Org documents. I used to manually indent headers by spaces, and that caused many indentation issues. This little function allows us to easily put headings indented, without actual white spaces. It's all a buffer trick, but it makes the whole document more readable.
(setq org-startup-indented t)
Org Mode Descriptive Links
By default, I have Org Mode links be "folded". Whenever I need to edit link in Org Mode, or I just want to see how the full text looks like, I press C-c o
. This is, of course, just a quick way to call org-toggle-link-display
.
(setq org-descriptive-links t)
(global-set-key (kbd "C-c o") 'org-toggle-link-display)
Example: I might have a link in Org Mode appear as hello.org
. I cant point my cursor there, and open it. However, if I'd like to see where the link actually takes me, I press C-c o
and I see [[file:~/hello.org][hello.org]]
Org Mode Heights
From info file:
(with-eval-after-load FILE &rest BODY)
Execute BODY after FILE is loaded.
FILE is normally a feature name, but it can also be a file name…
We can see that with-eval-after-load
just sets the face attribute :height
of Org headings and Org titles to 1.1 and 2.5 respectively:
(with-eval-after-load 'org-faces
(dolist (face '(
org-level-1
org-level-2
org-level-3))
(set-face-attribute face nil :height 1.1)))
(with-eval-after-load 'org-faces
(dolist (face '(
org-document-title))
(set-face-attribute face nil :height 2.5)))
The last bit makes the title appear very large.
Draft Mode
This isn't an actual mode. This quick function just activates org-mode upon entering any file with the file extension .draft
.
(add-to-list 'auto-mode-alist '("\\.draft\\'" . org-mode))
Actual Org Publishing Part
Default export language
(setq org-export-default-language "en")
HTML title enhanced
(defun gyb-org/html-title-enhanced (func contents info)
"Insert author and dynamic current date into the exported HTML title in Org Mode."
(let* ((original-html (funcall func contents info)) ; Calling original html
(author (or (save-excursion
(goto-char (point-min))
(when (re-search-forward "^#\\+AUTHOR:[ \t]+\\(.*\\)$" nil t)
(match-string 1)))
"Béla Gyenes")) ; Default author name if pinfo author is not set
)
;; Title with author name
(if (string-match "<h1 class=\"title\">\\(.*?\\)</h1>" original-html)
(replace-match (concat "<div class=\"document-header\">"
"<h1 class=\"title\">\\1</h1>" ; Existing title in place
(format "<div class=\"author\">%s</div>"
author)
"</div>") t nil original-html)
original-html)))
(advice-add 'org-html-template :around #'gyb-org/html-title-enhanced)
HTML Table of Contents Title
(defun gyb-org/html-toc (depth info &optional scope)
"Custom Table of Contents title based on Org file language info."
(setq org-latex-toc-command "\\tableofcontents \\clearpage")
(let* ((lang (or (plist-get info :language) "en")) ;; Default language if we didn't set it
(toc-title (cond
((string= lang "ru") "оглавление")
((string= lang "en") "Contents")
((string= lang "hu") "Tartalomjegyzék")
(t "Contents"))) ; Default to this
(toc-entries
(mapcar (lambda (headline)
(cons (org-html--format-toc-headline headline info)
(org-export-get-relative-level headline info)))
(org-export-collect-headlines info depth scope))))
(message "Language in info plist: '%s', TOC title: '%s'" lang toc-title)
(when toc-entries
(let ((toc (concat "<div id=\"text-table-of-contents\" role=\"doc-toc\">"
(org-html--toc-text toc-entries)
"</div>\n")))
(if scope toc
(let ((outer-tag (if (org-html--html5-fancy-p info)
"nav"
"div")))
(concat (format "<%s id=\"table-of-contents\" role=\"doc-toc\">\n" outer-tag)
(let ((top-level (plist-get info :html-toplevel-hlevel)))
(format "<h%d>%s</h%d>\n"
top-level
toc-title
top-level))
toc
(format "</%s>\n" outer-tag))))))))
Handling source blocks (programming languages syntax)
(defun gyb-org/html-handle-src-blocks (src-block contents info)
"Wrapping src blocks in <pre><code>%s</pre></code> HTML tags."
(let* ((lang (org-element-property :language src-block))
(class (if lang (format "language-%s" lang) "language-unknown"))
(code (org-element-property :value src-block)))
(format "<pre><code class=\"%s\">%s</code></pre>"
class
(org-html-encode-plain-text code))))
(defun gyb-org/html-src-blocks (src-block contents info)
"Overriding default org html source blocks."
(if (org-element-property :language src-block)
(gyb-org/html-handle-src-blocks src-block contents info)
;; If no programming language was specified as ~src~, fall back to default export behaviour.
(org-html-src-block src-block contents info)))
;; Overriding default org-html-src-block function
(advice-add 'org-html-src-block :override 'gyb-org/html-src-blocks)
Preamble
(defun gyb/org-html-preamble (info)
"HTML preamble with links"
(let* ((file-path (plist-get info :input-file))
(pdf-link (replace-regexp-in-string "\\.org$" ".pdf" (file-name-nondirectory file-path))))
(concat
"<div class='hb'>"
(if (string-match "/hu/" file-path)
(format "<a href='/index.html'>Főoldal</a> | <a href='./i.html'>Overview</a>")
(format "<a href='/index.html'>Home</a> | <a href='./i.html'>Sitemap</a>"))
"</div>"
)))
Postamble
This is basically my footer.
(defun gyb/html-footer (info)
"Custom footer"
(concat
"<footer>"
"<div id='footer-1'>"
"<a href='/index.html'>Home</a> ~ "
"<a href='/links.html'>Links</a> ~ "
"<a href='/contact.html'>Contact</a> ~ "
"<a href='/sitemap.html'>Sitemap</a> ~ "
"<a href='/webring.html'>Webring</a> ~ "
"<a href='/simulations/index.html'>Sim</a> | "
"<a href='https://github.com/UncleBela'>GitHub</a> ~ "
"<a href='https://www.youtube.com/@gyenesxyz'>YouTube</a>"
"</div>"
"<div id='footer-2'>"
"<p>Béla Gyenes ~ <a href='mailto:bela@gyenes.xyz'>bela@gyenes.xyz</a></p>"
"</div>"
"</footer>"))
Custom macros for every Org document
;; Define Macros in Org Publish
(defun gyb/add-org-macros (&rest args)
"Add custom global macros for every Org export."
(setq-local org-export-global-macros
'(("today" . "(eval (format-time-string \"%Y / %m / %d\"))")
("color" . "@@html:<font color=\"$1\">$2</font>@@"))))
;; Overriding originalo function
(advice-add 'org-export-as :before #'gyb/add-org-macros)
Actual org-publish settings
;; Actual publishing process
(require 'ox-publish)
(require 'ox-latex)
(setq my/root-directory "~/Projects/Websites/gyenes.xyz/")
(setq org-publish-project-alist
`(("notes"
:base-directory ,(expand-file-name "src/" my/root-directory)
:base-extension "org"
:publishing-directory ,(expand-file-name "public/" my/root-directory)
:recursive t
:publishing-function org-html-publish-to-html
:headline-levels 4
:section-numbers nil
:auto-preamble t
:with-toc t
:language "en"
:html-head "<link rel='stylesheet' href='/styles/notes.css'>"
:html-head-extra "<link rel='icon' href='/imgs/favicon.ico' type='image/gif'><script src='/scripts/notes.js'></script><link rel='stylesheet' href='/styles/prism.css'/><script src='/scripts/prism.js'></script>"
:html-preamble gyb/org-html-preamble
:html-postamble gyb/html-footer
:html-head-include-default-style nil
:html-doctype "html5"
:html-html5-fancy t
:author "Béla Gyenes"
:email "bela@gyenes.xyz"
)
("static"
:base-directory ,(expand-file-name "src/" my/root-directory)
:base-extension "css\\|js\\|png\\|jpg\\|gif\\|mp3\\|ogg\\|swf\\|svg\\|wav\\|aac"
:publishing-directory ,(expand-file-name "public/" my/root-directory)
:recursive t
:publishing-function org-publish-attachment)
;; rss will come here
("blog" :components ("notes" "static" "pdf"))))
(global-set-key (kbd "<f1> <f1>") 'org-publish-current-file)
(global-set-key (kbd "<f1> <f2>") 'org-publish)
\(\LaTeX\) fuckery
If, for some reason, I chose to export my note to PDF, this ensures that right after the Table of Contents is rendered, a new page insertion is put there as well.
(setq org-latex-toc-command "\\tableofcontents\n\\clearpage")