orchard
mirrorEvery site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.
blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics
1---
2title: Using CodeMirror to show formatted code in Wagtail
3slug: using-codemirror-to-show-formatted-code-in-wagtail
4date: 2022-06-11
5publish_date: 2022-06-11
6tags: coding, webdev
7description: Going through all the steps to use CodeMirror with Wagtail to show formatted code on the frontend of your site.
8cover_image: codemirror-website.webp
9---
10
11There are many ways to get code syntax highlighting on a website not the least of which is [CodeMirror](https://codemirror.net/5/). CodeMirror is a fairly full featured browser text editor so it may not make sense to use it for every project but I find it a very nice user experience. A few of the features I enjoy include:
12
13* The ability to use hotkeys, like `cmd + a` or `ctrl + a`, to highlight everything in the CodeMirror text area without grabbing the entire page
14* Being able to set read only mode with a single option
15* Line numbers with a single option
16* And lots of language support for syntax highlighting
17
18Note that at the time of writing this CodeMirror 6 has released but I'm only focusing on CodeMirror 5. I found CodeMirror 6 to be buggy and lacking in themes and language options as right now. I'm sure that will change in the future.
19
20To get started install CodeMirror. I'm using `yarn` with `webpack` but this can easily be converted to almost any bundler you want to use:
21
22`yarn add [email protected]`
23
24After that's done make a script called `code.js` and make sure it gets included in your bundle.
25
26```javascript
27import CodeMirror from "codemirror/lib/codemirror.js";
28
29import "codemirror/mode/python/python.js";
30import "codemirror/mode/javascript/javascript.js";
31import "codemirror/mode/htmlmixed/htmlmixed.js";
32import "codemirror/mode/css/css.js";
33import "codemirror/mode/shell/shell.js";
34
35import "codemirror/lib/codemirror.css";
36import "codemirror/theme/material.css";
37
38
39document.addEventListener("DOMContentLoaded", () => {
40 const blockCode = document.querySelectorAll(".block-code");
41
42 if (blockCode) {
43 Array.prototype.forEach.call(blockCode, (block) => {
44 const textarea = block.querySelector("textarea");
45 CodeMirror.fromTextArea(textarea, {
46 theme: "material",
47 lineNumbers: true,
48 lineWrapping: true,
49 readOnly: true,
50 viewportMargin: Infinity,
51 mode: textarea.dataset.language,
52 });
53 });
54 }
55});
56```
57
58A couple of notes:
59
60* You can import as many or as few modes as you want, I'm only importing what my blog generally uses
61* You can also use any theme you want in the `theme` folder, I'm currently using `material` but that may change
62
63In conjunction with this I've added some custom CSS mostly to allow line wrapping and automatic height adjustments. I've aptly called this file `code.css`.
64
65```css
66.CodeMirror {
67 margin: 2rem auto;
68 height: auto;
69 max-width: 1000px;
70}
71
72.CodeMirror-line {
73 margin: 0;
74}
75
76.CodeMirror-wrap pre {
77 word-break: break-word;
78}
79```
80
81After these two files are bundled and loaded into your website they will spawn a CodeMirror instance anywhere there is the following HTML block.
82
83```html
84{% if block.block_type == 'code' %}
85 <div class="block-code">
86 <textarea data-language="{{ block.value.language }}">{{ block.value.text }}</textarea>
87 </div>
88{% endif %}
89```
90
91Note the Django template tags here, you can use the above code anywhere up to this point and just remove the Django template tags and insert whatever tags you want. Now for the Wagtail portion. If you can't tell from the above Wagtail block code I'm using a `StreamField` for this. You can add a new `code` block to any `StreamField`.
92
93```python
94body = StreamField([
95 ('code', StructBlock([
96 ('language', ChoiceBlock(choices=[
97 ('python', 'Python'),
98 ('javascript', 'Javascript'),
99 ('htmlmixed', 'HTML'),
100 ('css', 'CSS'),
101 ('shell', 'Shell'),
102 ])),
103 ('text', TextBlock()),
104 ])),
105])
106```
107
108And you're done! We have a language choice block which is easily expanded upon for more languages in the future. Also make sure to include the new language mode in our `code.js` file. A feature improvement I've considered is having CodeMirror load into the `TextBlock` on my Wagtail admin panel for an even better admin experience, but for now I'll leave it at this.