Emacs markdown-mode in-browser preview with GitHub’s theme
posted on 2022-09-18
Just for the reference - a snippet that configures markdown-mode
so the export and live in-browser preview (using impatient-mode
) are using the same CSS template (in this case on of GitHub’s).
Some of the solutions out there use JavaScript library to render the markdown but this will work against impatient-mode’s ability to keep the preview scrolled where you had scrolled in the web browser. So this approach uses the markdown rendering on the Emacs side and just wraps the output sent to impatient-mode in proper HTML that references CSS theme file.
First markdown-mode
configuration and making sure the markdown-export
function will produce output referencing the CSS:
(use-package markdown-mode :hook ((markdown-mode . auto-fill-mode)) :mode ((".md\\'" . gfm-mode)) :config (setq markdown-enable-wiki-links t markdown-italic-underscore t markdown-asymmetric-header t markdown-make-gfm-checkboxes-buttons t markdown-gfm-uppercase-checkbox t markdown-enable-math t markdown-content-type "application/xhtml+xml" markdown-css-paths '("https://cdn.jsdelivr.net/npm/github-markdown-css/github-markdown.min.css") markdown-xhtml-header-content " <style> body { box-sizing: border-box; max-width: 740px; width: 100%; margin: 40px auto; padding: 0 10px; } </style> <script> document.addEventListener('DOMContentLoaded', () => { document.body.classList.add('markdown-body'); }); </script> " ))
Next goes a filter function for impatient-mode
to produce the same output:
(defun markdown-filter-impatient-mode (buffer) "Markdown filter for impatient-mode" (princ (with-temp-buffer (let ((tmpname (buffer-name))) (set-buffer buffer) (set-buffer (markdown tmpname)) (format " <!DOCTYPE html> <html> <head> <title>Markdown Preview</title> <meta name='viewport' content= 'width=device-width, initial-scale=1'> <link rel='stylesheet' href= 'https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/4.0.0/github-markdown.min.css' integrity= 'sha512-Oy18vBnbSJkXTndr2n6lDMO5NN31UljR8e/ICzVPrGpSud4Gkckb8yUpqhKuUNoE+o9gAb4O/rAxxw1ojyUVzg==' crossorigin='anonymous'> <!-- https://github.com/sindresorhus/github-markdown-css --> <link rel='stylesheet' href= 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/styles/github.min.css'> <!-- https://highlightjs.org --> <style> .markdown-body { box-sizing: border-box; margin: 0 auto; max-width: 980px; min-width: 200px; padding: 45px; } @media (max-width: 767px) { .markdown-body { padding: 15px; } } </style> </head> <body> <article class='markdown-body'> %s </article> <script src= 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.2.0/highlight.min.js'></script> <script> hljs.highlightAll(); </script> </body> </html>" (buffer-string)))) (current-buffer)))
And the last is a utility function we can call while editing markdown
file to start the local webserver, set up impatient-mode’s filter and visit the preview in the web browser:
(defun impatient-markdown-preview () (interactive) (impatient-mode) (imp-set-user-filter `markdown-filter-impatient-mode) (httpd-start) (imp-visit-buffer))
You can now bind impatient-markdown-preview
function or execute it from the M-x
command prompt. Thanks to impatient-mode’s the preview will keep updating as you type.
Happy hacking!