fido-node.github.io/posts/improve_code_blocks.html
2024-06-26 04:55:58 +00:00

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)
&quot;Translate SRC-BLOCK element into HTML.
CONTENTS is nil. INFO is a plist holding contextual information.&quot;
(let* (
(language (format &quot;language-%s&quot; (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 &apos;my-html &apos;html
:translate-alist &apos;(
(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)
&quot;Return t if the Org document represented by INFO has source code blocks.&quot;
(org-element-map (plist-get info :parse-tree) &apos;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 . &quot;true&quot;) (src . &quot;https://umami.dokutsu.xyz/script.js&quot;) (data-website-id . &quot;d52d9af1-0c7d-4531-84c6-0b9c2850011f&quot;)) ())
,(when has-src-blocks
`(nil ()
(link ((id . &quot;prism-theme&quot;) (rel . &quot;stylesheet&quot;) (type . &quot;text/css&quot;)))
(link ((rel . &quot;stylesheet&quot;) (type . &quot;text/css&quot;) (href . &quot;/resources/css/quirks.css&quot;)))
(script ((src . &quot;https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js&quot;)) ())
(script ((src . &quot;https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js&quot;)) ())
(script ((src . &quot;/resources/js/theme-selector.js&quot;)) ())
))
(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(&quot;prism-theme&quot;);
const darkTheme =
&quot;https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css&quot;;
const lightTheme =
&quot;https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-solarizedlight.min.css&quot;;
if (
window.matchMedia &amp;amp;&amp;amp;
window.matchMedia(&quot;(prefers-color-scheme: dark)&quot;).matches
) {
prismThemeLink.href = darkTheme;
} else {
prismThemeLink.href = lightTheme;
}
}
// Initial theme set
setPrismTheme();
// Listen for changes in the preferred color scheme
window
.matchMedia(&quot;(prefers-color-scheme: dark)&quot;)
.addEventListener(&quot;change&quot;, 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 &quot;~/.config/emacs/.local/straight/build-&quot; emacs-version &quot;/&quot;)))
(normal-top-level-add-subdirs-to-load-path))
(add-to-list &apos;custom-theme-load-path
(concat &quot;~/.config/emacs/.local/straight/build-&quot; emacs-version &quot;/doom-themes&quot;))
(add-to-list &apos;custom-theme-load-path (concat &quot;~/.config/emacs/.local/straight/build-&quot; emacs-version &quot;/base16-theme&quot;))
(add-to-list &apos;custom-theme-load-path (concat &quot;~/.config/emacs/.local/straight/build-&quot; emacs-version &quot;/moe-theme&quot;))
(require &apos;xml)
(require &apos;dom)
(require &apos;ox-publish)
(require &apos;ox-rss)
(require &apos;org)
(require &apos;esxml)
;; (require &apos;esxml-html)
;;
;;Variables
;;
(setq
my/url &quot;https://fidonode.me&quot;
my/web-export-path &quot;./public&quot;
my/blog-src-path &quot;./home/05 Blog&quot;
my/lang-substitution-map &apos;((&quot;elisp&quot; . &quot;lisp&quot;))
org-html-validation-link nil ;; Don&apos;t show validation link
org-html-htmlize-output-type nil
org-src-fontify-natively t)
;;
;;Templates
;;
(defun my/footer (info)
`(footer ((class . &quot;footer&quot;))
(hr () )
(small ()
(p () &quot;Alex Mikhailov&quot;)
(p () &quot;Built with: &quot;
(a ((href . &quot;https://www.gnu.org/software/emacs/&quot;)) &quot;GNU Emacs&quot;) &quot; &quot;
(a ((href . &quot;https://orgmode.org/&quot;)) &quot;Org Mode&quot;) &quot; &quot;
(a ((href . &quot;https://picocss.com/&quot;)) &quot;picocss&quot;)
)
)
))
(defun my/header (info)
(let ((title-str (org-export-data (plist-get info :title) info)))
`(header ((class . &quot;header&quot;))
(nav ()
(ul ()
(li ()
(strong () ,title-str)))
(ul ()
(li () (a ((href . &quot;/index.html&quot;)) &quot;About&quot;))
(li () (a ((href . &quot;/posts.html&quot;)) &quot;Blog&quot;))
(li () (a ((href . &quot;/rss.xml&quot;)) &quot;RSS&quot;))
)
))
)
)
(defun my/src-block (src-block contents info)
&quot;Translate SRC-BLOCK element into HTML.
CONTENTS is nil. INFO is a plist holding contextual information.&quot;
(let* (
(language (format &quot;language-%s&quot; (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 &quot;%s/%s&quot; script-directory base-directory-str)))
(img-link-str (format &quot;%s/resources/images/%s.png&quot; 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
&quot;&amp;lt;!DOCTYPE html&amp;gt;&quot;
(esxml-to-xml
`(html ((lang . &quot;en&quot;))
(head ()
(meta ((charset . &quot;utf-8&quot;)))
(meta ((author . &quot;Alex Mikhailov&quot;)))
(meta ((name . &quot;viewport&quot;)
(content . &quot;width=device-width, initial-scale=1, shrink-to-fit=no&quot;)))
(meta ((name . &quot;color-scheme&quot;) (content . &quot;light dark&quot;)))
(meta ((http-equiv . &quot;content-language&quot;) (content . &quot;en-us&quot;)))
;; OG block
;; &quot;Personal page with a blog about my technical adventures&quot;
(meta ((name . &quot;description&quot;) (content . ,description-str)))
(meta ((name . &quot;og:description&quot;) (content . ,description-str)))
(meta ((name . &quot;twitter:description&quot;) (content . ,description-str)))
(meta ((name . &quot;og:image&quot;) (content . ,img-link-str)))
(meta ((name . &quot;twitter:image&quot;) (content . ,img-link-str)))
(meta ((name . &quot;og:title&quot;) (content . ,title-str)))
(meta ((name . &quot;twitter:title&quot;) (content . ,title-str)))
(meta ((name . &quot;twitter:card&quot;) (content . &quot;summary_large_image&quot;)))
(link ((rel . &quot;icon&quot;) (type . &quot;image/x-icon&quot;) (href . &quot;/resources/favicon.ico&quot;)))
(link ((rel . &quot;stylesheet&quot;) (type . &quot;text/css&quot;) (href . &quot;/resources/css/pico.sand.min.css&quot;)))
(script ((defer . &quot;true&quot;) (src . &quot;https://umami.dokutsu.xyz/script.js&quot;) (data-website-id . &quot;d52d9af1-0c7d-4531-84c6-0b9c2850011f&quot;)) ())
,(when has-src-blocks
`(nil ()
(link ((id . &quot;prism-theme&quot;) (rel . &quot;stylesheet&quot;) (type . &quot;text/css&quot;)))
(link ((rel . &quot;stylesheet&quot;) (type . &quot;text/css&quot;) (href . &quot;/resources/css/quirks.css&quot;)))
(script ((src . &quot;https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js&quot;)) ())
(script ((src . &quot;https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js&quot;)) ())
(script ((src . &quot;/resources/js/theme-selector.js&quot;)) ())
)
)
(title () ,title-str)
)
(body ((class . &quot;line-numbers&quot;))
(main ((class . &quot;container&quot;))
,(my/header info)
(raw-string ,contents)
,(my/footer info)
)
))
))
))
(org-export-define-derived-backend &apos;my-html &apos;html
:translate-alist &apos;(
(template . my/template)
(src-block . my/src-block)
))
(defun my/publish-to-html (plist filename pub-dir)
&quot;Publish an Org file to HTML using the custom backend.&quot;
(org-publish-org-to &apos;my-html filename &quot;.html&quot; plist pub-dir))
;;
;;Sitemap/RSS
;;
(defun my/format-rss-feed-entry (entry style project)
&quot;Format ENTRY for the RSS feed.
ENTRY is a file name. STYLE is either &apos;list&apos; or &apos;tree&apos;.
PROJECT is the current project.&quot;
(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 &quot;%Y-%m-%d&quot; (org-publish-find-date entry project)))
(link (concat (file-name-sans-extension entry) &quot;.html&quot;)))
(with-temp-buffer
(org-mode)
(insert (format &quot;* [[file:%s][%s]]\n&quot; file title))
(org-set-property &quot;RSS_PERMALINK&quot; link)
(org-set-property &quot;RSS_TITLE&quot; title)
(org-set-property &quot;PUBDATE&quot; 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 &quot;\n&quot; first-two-lines)
(setq first-two-lines (substring first-two-lines 0 -1)))
(insert first-two-lines))
(goto-char (point-max))
(insert &quot;...&quot;)
(buffer-string))))
((eq style &apos;tree)
;; Return only last subdir.
(file-name-nondirectory (directory-file-name entry)))
(t entry)))
(defun my/format-rss-feed (title list)
&quot;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&apos;. PROJECT is the current project.&quot;
(concat &quot;#+TITLE: &quot; title &quot;\n&quot;
&quot;#+STARTUP: showall \n\n&quot;
(org-list-to-subtree list 1 &apos;(:icount &quot;&quot; :istart &quot;&quot;))))
(defun my/publish-to-rss (plist filename pub-dir)
&quot;Publish RSS with PLIST, only when FILENAME is &apos;rss.org&apos;.
PUB-DIR is when the output will be placed.&quot;
(if (equal &quot;rss.org&quot; (file-name-nondirectory filename))
(org-rss-publish-to-rss plist filename pub-dir)))
;;
;;Helpers
;;
(defun my/format-date-subtitle (file project)
&quot;Format the date found in FILE of PROJECT.&quot;
(format-time-string &quot;posted on %Y-%m-%d&quot; (org-publish-find-date file project)))
(defun my/pt (var)
&quot;Print the value and type of VAR.&quot;
(message &quot;Value: %S, Type: %s&quot; var (type-of var)))
(defun plist-keys (plist)
&quot;Return a list of keys in the property list PLIST.&quot;
(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))
&quot;The directory where the current script is located.&quot;)
(defun my/org-has-src-blocks-p (info)
&quot;Return t if the Org document represented by INFO has source code blocks.&quot;
(org-element-map (plist-get info :parse-tree) &apos;src-block
(lambda (src-block) t)
nil t))
(defun my/replace-substrings (input-string)
&quot;Replace substrings in INPUT-STRING according to SUBSTITUTION-MAP.&quot;
(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 &quot;static&quot;
:base-directory my/blog-src-path
:base-extension &quot;css\\|js\\|png\\|jpg\\|jpeg\\|gif\\|pdf\\|ico\\|txt&quot;
:publishing-directory my/web-export-path
:recursive t
:publishing-function &apos;org-publish-attachment
)
(list &quot;blog&quot;
:recursive t
:base-directory my/blog-src-path
:publishing-directory my/web-export-path
:publishing-function &apos;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 &quot;blog-rss&quot;
:author &quot;Alex M&quot;
:email &quot;iam@fidonode.me&quot;
:base-directory my/blog-src-path
:base-extension &quot;org&quot;
:recursive t
:exclude (regexp-opt &apos;(&quot;rss.org&quot; &quot;index.org&quot; &quot;404.org&quot; &quot;posts.org&quot;))
:publishing-function &apos;my/publish-to-rss
:publishing-directory my/web-export-path
:rss-extension &quot;xml&quot;
:html-link-home my/url
:html-link-use-abs-url t
:html-link-org-files-as-html t
:auto-sitemap t
:sitemap-filename &quot;rss.org&quot;
:sitemap-title &quot;rss&quot;
:sitemap-style &apos;list
:sitemap-sort-files &apos;anti-chronologically
:sitemap-function &apos;my/format-rss-feed
:sitemap-format-entry &apos;my/format-rss-feed-entry)
))
;; Generate the site output
(org-publish-all t)
(message &quot;Build complete!&quot;)
</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>