424 lines
18 KiB
HTML
424 lines
18 KiB
HTML
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"/><meta author="Alex Mikhailov"/><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/><meta name="color-scheme" content="light dark"/><meta http-equiv="content-language" content="en-us"/><meta name="description" content="Use prime.js for code syntax highlighting"/><meta name="og:description" content="Use prime.js for code syntax highlighting"/><meta name="twitter:description" content="Use prime.js for code syntax highlighting"/><meta name="og:image" content="https://fidonode.me/resources/images/posts/improve_code_blocks.org.png"/><meta name="twitter:image" content="https://fidonode.me/resources/images/posts/improve_code_blocks.org.png"/><meta name="og:title" content="Improve code blocks"/><meta name="twitter:title" content="Improve code blocks"/><meta name="twitter:card" content="summary_large_image"/><link rel="icon" type="image/x-icon" href="/resources/favicon.ico"/><link rel="stylesheet" type="text/css" href="/resources/css/pico.sand.min.css"/><script defer="true" src="https://umami.dokutsu.xyz/script.js" data-website-id="d52d9af1-0c7d-4531-84c6-0b9c2850011f"></script><nil><link id="highlight-theme" rel="stylesheet" type="text/css"/><script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script><script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/languages/bash.min.js"></script><script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/languages/lisp.min.js"></script><script src="/resources/js/theme-selector.js"></script></nil><title>Improve code blocks</title></head><body><main class="container"><header class="header"><nav><ul><li><strong>Improve code blocks</strong></li></ul><ul><li><a href="/index.html">About</a></li><li><a href="/posts.html">Blog</a></li><li><a href="/rss.xml">RSS</a></li></ul></nav></header><div id="table-of-contents">
|
|
<h2>Table of Contents</h2>
|
|
<div id="text-table-of-contents">
|
|
<ul>
|
|
<li><a href="#org506f58f">What is the problem with default highlighting?</a></li>
|
|
<li><a href="#orgbf2207a">Prism.js</a>
|
|
<ul>
|
|
<li><a href="#orgb19a9c7">Change code block template</a></li>
|
|
<li><a href="#orge91c35a">Plug Prism.js</a></li>
|
|
<li><a href="#orgbaa05e2">Respect prefers-color-scheme</a></li>
|
|
</ul>
|
|
</li>
|
|
<li><a href="#org8ecf679">Whole config</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<div id="outline-container-org506f58f" class="outline-2">
|
|
<h2 id="org506f58f">What is the problem with default highlighting?</h2>
|
|
</div>
|
|
<div id="outline-container-orgbf2207a" class="outline-2">
|
|
<h2 id="orgbf2207a">Prism.js</h2>
|
|
<div class="outline-text-2" id="text-orgbf2207a">
|
|
</div>
|
|
<div id="outline-container-orgb19a9c7" class="outline-3">
|
|
<h3 id="orgb19a9c7">Change code block template</h3>
|
|
<div class="outline-text-3" id="text-orgb19a9c7">
|
|
<pre><code class="language-lisp">(defun my/src-block (src-block contents info)
|
|
"Translate SRC-BLOCK element into HTML.
|
|
CONTENTS is nil. INFO is a plist holding contextual information."
|
|
(let* (
|
|
(language (format "language-%s" (org-element-property :language src-block)))
|
|
(code (org-element-property :value src-block)))
|
|
(esxml-to-xml
|
|
`(pre ()
|
|
(code ((class . ,language))
|
|
,(org-html-encode-plain-text code)
|
|
))
|
|
)
|
|
)
|
|
)
|
|
|
|
(org-export-define-derived-backend 'my-html 'html
|
|
:translate-alist '(
|
|
(template . my/template)
|
|
(src-block . my/src-block)
|
|
))
|
|
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
<div id="outline-container-orge91c35a" class="outline-3">
|
|
<h3 id="orge91c35a">Plug Prism.js</h3>
|
|
<div class="outline-text-3" id="text-orge91c35a">
|
|
<pre><code class="language-lisp">(defun my/org-has-src-blocks-p (info)
|
|
"Return t if the Org document represented by INFO has source code blocks."
|
|
(org-element-map (plist-get info :parse-tree) 'src-block
|
|
(lambda (src-block) t)
|
|
nil t))
|
|
|
|
(defun my/template (contents info)
|
|
|
|
(let* ((title-str (org-export-data (plist-get info :title) info))
|
|
...
|
|
(has-src-blocks (my/org-has-src-blocks-p info)))
|
|
...
|
|
|
|
(script ((defer . "true") (src . "https://umami.dokutsu.xyz/script.js") (data-website-id . "d52d9af1-0c7d-4531-84c6-0b9c2850011f")) ())
|
|
,(when has-src-blocks
|
|
`(nil ()
|
|
(link ((id . "prism-theme") (rel . "stylesheet") (type . "text/css")))
|
|
(link ((rel . "stylesheet") (type . "text/css") (href . "/resources/css/quirks.css")))
|
|
(script ((src . "https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js")) ())
|
|
(script ((src . "https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js")) ())
|
|
(script ((src . "/resources/js/theme-selector.js")) ())
|
|
))
|
|
(title () ,title-str)
|
|
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
<div id="outline-container-orgbaa05e2" class="outline-3">
|
|
<h3 id="orgbaa05e2">Respect prefers-color-scheme</h3>
|
|
<div class="outline-text-3" id="text-orgbaa05e2">
|
|
<pre><code class="language-javascript">function setPrismTheme() {
|
|
const prismThemeLink = document.getElementById("prism-theme");
|
|
const darkTheme =
|
|
"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css";
|
|
const lightTheme =
|
|
"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-solarizedlight.min.css";
|
|
if (
|
|
window.matchMedia &amp;&amp;
|
|
window.matchMedia("(prefers-color-scheme: dark)").matches
|
|
) {
|
|
prismThemeLink.href = darkTheme;
|
|
} else {
|
|
prismThemeLink.href = lightTheme;
|
|
}
|
|
}
|
|
|
|
// Initial theme set
|
|
setPrismTheme();
|
|
|
|
// Listen for changes in the preferred color scheme
|
|
window
|
|
.matchMedia("(prefers-color-scheme: dark)")
|
|
.addEventListener("change", setPrismTheme);
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="outline-container-org8ecf679" class="outline-2">
|
|
<h2 id="org8ecf679">Whole config</h2>
|
|
<div class="outline-text-2" id="text-org8ecf679">
|
|
<p>
|
|
In between posts I've switched from <code>sxml</code> to <code>esxml</code> so here is the current config.
|
|
</p>
|
|
<pre><code class="language-lisp">;; Load the publishing system
|
|
;; Configure environment
|
|
;;
|
|
(setq debug-on-error t)
|
|
|
|
(let ((default-directory (concat "~/.config/emacs/.local/straight/build-" emacs-version "/")))
|
|
(normal-top-level-add-subdirs-to-load-path))
|
|
|
|
(add-to-list 'custom-theme-load-path
|
|
(concat "~/.config/emacs/.local/straight/build-" emacs-version "/doom-themes"))
|
|
(add-to-list 'custom-theme-load-path (concat "~/.config/emacs/.local/straight/build-" emacs-version "/base16-theme"))
|
|
(add-to-list 'custom-theme-load-path (concat "~/.config/emacs/.local/straight/build-" emacs-version "/moe-theme"))
|
|
|
|
|
|
(require 'xml)
|
|
(require 'dom)
|
|
(require 'ox-publish)
|
|
(require 'ox-rss)
|
|
(require 'org)
|
|
(require 'esxml)
|
|
;; (require 'esxml-html)
|
|
|
|
;;
|
|
;;Variables
|
|
;;
|
|
(setq
|
|
my/url "https://fidonode.me"
|
|
my/web-export-path "./public"
|
|
my/blog-src-path "./home/05 Blog"
|
|
my/lang-substitution-map '(("elisp" . "lisp"))
|
|
org-html-validation-link nil ;; Don't show validation link
|
|
org-html-htmlize-output-type nil
|
|
org-src-fontify-natively t)
|
|
|
|
;;
|
|
;;Templates
|
|
;;
|
|
(defun my/footer (info)
|
|
`(footer ((class . "footer"))
|
|
(hr () )
|
|
(small ()
|
|
(p () "Alex Mikhailov")
|
|
(p () "Built with: "
|
|
(a ((href . "https://www.gnu.org/software/emacs/")) "GNU Emacs") " "
|
|
(a ((href . "https://orgmode.org/")) "Org Mode") " "
|
|
(a ((href . "https://picocss.com/")) "picocss")
|
|
)
|
|
)
|
|
))
|
|
|
|
(defun my/header (info)
|
|
(let ((title-str (org-export-data (plist-get info :title) info)))
|
|
`(header ((class . "header"))
|
|
(nav ()
|
|
(ul ()
|
|
(li ()
|
|
(strong () ,title-str)))
|
|
(ul ()
|
|
(li () (a ((href . "/index.html")) "About"))
|
|
(li () (a ((href . "/posts.html")) "Blog"))
|
|
(li () (a ((href . "/rss.xml")) "RSS"))
|
|
)
|
|
))
|
|
)
|
|
)
|
|
|
|
(defun my/src-block (src-block contents info)
|
|
"Translate SRC-BLOCK element into HTML.
|
|
CONTENTS is nil. INFO is a plist holding contextual information."
|
|
(let* (
|
|
(language (format "language-%s" (org-element-property :language src-block)))
|
|
(code (org-element-property :value src-block)))
|
|
(esxml-to-xml
|
|
`(pre ()
|
|
(code ((class . ,language))
|
|
,(org-html-encode-plain-text code)
|
|
))
|
|
)
|
|
)
|
|
)
|
|
|
|
|
|
(defun my/template (contents info)
|
|
|
|
(let* ((title-str (org-export-data (plist-get info :title) info))
|
|
(description-str (org-export-data (plist-get info :description) info))
|
|
(file-path-str (org-export-data (plist-get info :input-file) info))
|
|
(base-directory-str (org-export-data (plist-get info :base-directory) info))
|
|
(file-name-str (file-relative-name file-path-str (format "%s/%s" script-directory base-directory-str)))
|
|
(img-link-str (format "%s/resources/images/%s.png" my/url file-name-str))
|
|
(has-src-blocks (my/org-has-src-blocks-p info)))
|
|
|
|
(set-text-properties 0 (length title-str) nil title-str)
|
|
(set-text-properties 0 (length description-str) nil description-str)
|
|
(set-text-properties 0 (length img-link-str) nil img-link-str)
|
|
|
|
(concat
|
|
"&lt;!DOCTYPE html&gt;"
|
|
(esxml-to-xml
|
|
`(html ((lang . "en"))
|
|
(head ()
|
|
(meta ((charset . "utf-8")))
|
|
(meta ((author . "Alex Mikhailov")))
|
|
(meta ((name . "viewport")
|
|
(content . "width=device-width, initial-scale=1, shrink-to-fit=no")))
|
|
(meta ((name . "color-scheme") (content . "light dark")))
|
|
(meta ((http-equiv . "content-language") (content . "en-us")))
|
|
;; OG block
|
|
;; "Personal page with a blog about my technical adventures"
|
|
(meta ((name . "description") (content . ,description-str)))
|
|
(meta ((name . "og:description") (content . ,description-str)))
|
|
(meta ((name . "twitter:description") (content . ,description-str)))
|
|
|
|
(meta ((name . "og:image") (content . ,img-link-str)))
|
|
(meta ((name . "twitter:image") (content . ,img-link-str)))
|
|
|
|
(meta ((name . "og:title") (content . ,title-str)))
|
|
(meta ((name . "twitter:title") (content . ,title-str)))
|
|
|
|
(meta ((name . "twitter:card") (content . "summary_large_image")))
|
|
|
|
(link ((rel . "icon") (type . "image/x-icon") (href . "/resources/favicon.ico")))
|
|
(link ((rel . "stylesheet") (type . "text/css") (href . "/resources/css/pico.sand.min.css")))
|
|
(script ((defer . "true") (src . "https://umami.dokutsu.xyz/script.js") (data-website-id . "d52d9af1-0c7d-4531-84c6-0b9c2850011f")) ())
|
|
,(when has-src-blocks
|
|
`(nil ()
|
|
(link ((id . "prism-theme") (rel . "stylesheet") (type . "text/css")))
|
|
(link ((rel . "stylesheet") (type . "text/css") (href . "/resources/css/quirks.css")))
|
|
(script ((src . "https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js")) ())
|
|
(script ((src . "https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js")) ())
|
|
(script ((src . "/resources/js/theme-selector.js")) ())
|
|
)
|
|
)
|
|
(title () ,title-str)
|
|
)
|
|
(body ((class . "line-numbers"))
|
|
(main ((class . "container"))
|
|
,(my/header info)
|
|
(raw-string ,contents)
|
|
,(my/footer info)
|
|
)
|
|
))
|
|
))
|
|
))
|
|
|
|
|
|
(org-export-define-derived-backend 'my-html 'html
|
|
:translate-alist '(
|
|
(template . my/template)
|
|
(src-block . my/src-block)
|
|
))
|
|
|
|
(defun my/publish-to-html (plist filename pub-dir)
|
|
"Publish an Org file to HTML using the custom backend."
|
|
(org-publish-org-to 'my-html filename ".html" plist pub-dir))
|
|
;;
|
|
;;Sitemap/RSS
|
|
;;
|
|
(defun my/format-rss-feed-entry (entry style project)
|
|
"Format ENTRY for the RSS feed.
|
|
ENTRY is a file name. STYLE is either 'list' or 'tree'.
|
|
PROJECT is the current project."
|
|
(cond ((not (directory-name-p entry))
|
|
(let* ((file (org-publish--expand-file-name entry project))
|
|
(title (org-publish-find-title entry project))
|
|
(date (format-time-string "%Y-%m-%d" (org-publish-find-date entry project)))
|
|
(link (concat (file-name-sans-extension entry) ".html")))
|
|
(with-temp-buffer
|
|
(org-mode)
|
|
(insert (format "* [[file:%s][%s]]\n" file title))
|
|
(org-set-property "RSS_PERMALINK" link)
|
|
(org-set-property "RSS_TITLE" title)
|
|
(org-set-property "PUBDATE" date)
|
|
(let ((first-two-lines (with-temp-buffer
|
|
(insert-file-contents file)
|
|
(buffer-substring-no-properties
|
|
(point-min)
|
|
(progn (forward-line 2) (point))))))
|
|
(if (string-suffix-p "\n" first-two-lines)
|
|
(setq first-two-lines (substring first-two-lines 0 -1)))
|
|
(insert first-two-lines))
|
|
(goto-char (point-max))
|
|
(insert "...")
|
|
(buffer-string))))
|
|
((eq style 'tree)
|
|
;; Return only last subdir.
|
|
(file-name-nondirectory (directory-file-name entry)))
|
|
(t entry)))
|
|
|
|
(defun my/format-rss-feed (title list)
|
|
"Generate RSS feed, as a string.
|
|
TITLE is the title of the RSS feed. LIST is an internal
|
|
representation for the files to include, as returned by
|
|
`org-list-to-lisp'. PROJECT is the current project."
|
|
(concat "#+TITLE: " title "\n"
|
|
"#+STARTUP: showall \n\n"
|
|
(org-list-to-subtree list 1 '(:icount "" :istart ""))))
|
|
|
|
(defun my/publish-to-rss (plist filename pub-dir)
|
|
"Publish RSS with PLIST, only when FILENAME is 'rss.org'.
|
|
PUB-DIR is when the output will be placed."
|
|
(if (equal "rss.org" (file-name-nondirectory filename))
|
|
(org-rss-publish-to-rss plist filename pub-dir)))
|
|
|
|
;;
|
|
;;Helpers
|
|
;;
|
|
(defun my/format-date-subtitle (file project)
|
|
"Format the date found in FILE of PROJECT."
|
|
(format-time-string "posted on %Y-%m-%d" (org-publish-find-date file project)))
|
|
|
|
(defun my/pt (var)
|
|
"Print the value and type of VAR."
|
|
(message "Value: %S, Type: %s" var (type-of var)))
|
|
|
|
(defun plist-keys (plist)
|
|
"Return a list of keys in the property list PLIST."
|
|
(let (keys)
|
|
(while plist
|
|
(setq keys (cons (car plist) keys))
|
|
(setq plist (cddr plist)))
|
|
(nreverse keys)))
|
|
|
|
(defvar script-directory
|
|
(file-name-directory (or load-file-name buffer-file-name))
|
|
"The directory where the current script is located.")
|
|
|
|
(defun my/org-has-src-blocks-p (info)
|
|
"Return t if the Org document represented by INFO has source code blocks."
|
|
(org-element-map (plist-get info :parse-tree) 'src-block
|
|
(lambda (src-block) t)
|
|
nil t))
|
|
|
|
(defun my/replace-substrings (input-string)
|
|
"Replace substrings in INPUT-STRING according to SUBSTITUTION-MAP."
|
|
(let ((output-string input-string))
|
|
(dolist (pair my/lang-substitution-map)
|
|
(let ((old (regexp-quote (car pair)))
|
|
(new (cdr pair)))
|
|
(setq output-string (replace-regexp-in-string old new output-string))))
|
|
output-string))
|
|
|
|
;;
|
|
;;Clear folder with results
|
|
;;
|
|
(when (file-directory-p my/web-export-path)
|
|
(delete-directory my/web-export-path t))
|
|
(mkdir my/web-export-path)
|
|
|
|
|
|
;;
|
|
;;Main blog configuration
|
|
;;
|
|
(setq org-publish-project-alist
|
|
(list
|
|
(list "static"
|
|
:base-directory my/blog-src-path
|
|
:base-extension "css\\|js\\|png\\|jpg\\|jpeg\\|gif\\|pdf\\|ico\\|txt"
|
|
:publishing-directory my/web-export-path
|
|
:recursive t
|
|
:publishing-function 'org-publish-attachment
|
|
)
|
|
(list "blog"
|
|
:recursive t
|
|
:base-directory my/blog-src-path
|
|
:publishing-directory my/web-export-path
|
|
:publishing-function 'my/publish-to-html
|
|
:html-html5-fancy t
|
|
:htmlized-source t
|
|
:with-author nil
|
|
:with-creator t
|
|
:with-toc t
|
|
:section-numbers nil
|
|
:time-stamp-file nil
|
|
)
|
|
(list "blog-rss"
|
|
:author "Alex M"
|
|
:email "iam@fidonode.me"
|
|
:base-directory my/blog-src-path
|
|
:base-extension "org"
|
|
:recursive t
|
|
:exclude (regexp-opt '("rss.org" "index.org" "404.org" "posts.org"))
|
|
:publishing-function 'my/publish-to-rss
|
|
:publishing-directory my/web-export-path
|
|
:rss-extension "xml"
|
|
:html-link-home my/url
|
|
:html-link-use-abs-url t
|
|
:html-link-org-files-as-html t
|
|
:auto-sitemap t
|
|
:sitemap-filename "rss.org"
|
|
:sitemap-title "rss"
|
|
:sitemap-style 'list
|
|
:sitemap-sort-files 'anti-chronologically
|
|
:sitemap-function 'my/format-rss-feed
|
|
:sitemap-format-entry 'my/format-rss-feed-entry)
|
|
))
|
|
|
|
|
|
;; Generate the site output
|
|
(org-publish-all t)
|
|
|
|
(message "Build complete!")
|
|
</code></pre>
|
|
</div>
|
|
</div>
|
|
<footer class="footer"><hr/><small><p>Alex Mikhailov</p><p>Built with: <a href="https://www.gnu.org/software/emacs/">GNU Emacs</a> <a href="https://orgmode.org/">Org Mode</a> <a href="https://picocss.com/">picocss</a></p></small></footer></main></body></html>
|