Make PrismJS Editable

Cover image from vecteezy
In this article, we will go over how you can make PrismJS code blocks editable.
Introduction
PrismJS can be used to add syntax highlighting to code blocks on our website. For a personal project of mine, composersiation #ShamelessPlug, I needed to allow the user to paste in their own (docker-compose) YAML files. So let’s take a look at how we can let a user to first edit a code block and make sure.
index.html
So our HTML will look something like this.
Note: When I refer to “code block” I am referring to entire thing including the
pre
and thecode
tags.
<head>
<link
rel="stylesheet"
type="text/css"
href="stylesheets/prism.css"
rel="stylesheet"
/>
</head>
...
<pre
onPaste="setTimeout(function() {onPaste();}, 0)"
id="editable"
contenteditable
>
<code id="yaml" class="language-yaml"></code>
</pre>
<script src="javascript/prism.js"></script>
In this file we import the prism.css
stylesheet, there are many themes you can choose from in this example we will use the default theme. We will also import prism.js
, these are the two files required to use PrismJS.
<pre
onPaste="setTimeout(function() {onPaste();}, 0)"
id="editable"
contenteditable
>
<code id="yaml" class="language-yaml"></code>
</pre>
Next, we create the code block on the web page. Note the class on the code
tag is language-yaml
. To use PrismJS we need to give the code
a tag a class of language-x
where x is the language we want syntax highlighting for. You can find a full list of supported languages here.
To allow users to paste and edit the code block we add contenteditable
to the pre
tag. The reason we add it to the pre
tag and not the code
tag is when PrismJS has run it will edit the code
block to include span
's and other HTML elements, to do the syntax highlighting it makes it a lot harder for the user to copy and paste when you edit the code
tag as a pose to pre
tag.
The pre
tag also has onPaste="setTimeout(function() {onPaste();}, 0)"
this means that after the user has pasted into the pre
tag this function will be called. In this case, we call a function called onPaste()
. However we use a setTimeout
so that the browser has enough time to update the pre
tag, else the pre
/code
tags will still contain the previous text before the paste.
JavaScript
Now the user can paste directly into the code block. How do we force a re-render? Let’s take a look at onPaste
function which is called every time the user paste's into our code block.
function onPaste() {
const editable = document.getElementById("editable");
const dockerCompose = editable.innerText;
editable.innerHTML = '<code id="yaml" class="language-yaml"></code>';
const yaml = document.getElementById("yaml");
yaml.innerHTML = Prism.highlight(
dockerCompose,
Prism.languages.yaml,
"yaml"
);
}
So first we get the editable
element (our pre
tag). Next, we get the innerText of said element. This should be the new content the user wants to paste into the pre
tag. Sometimes when you copy/paste into the code block the old code
tag gets deleted so just in case we add the code
tag back in. As this is where PrismJS will render our "new" YAML "code" in. This is done like so editable.innerHTML = '<code id="yaml" class="language-yaml"></code>';
, this code replaces all the "children" of the pre
tag with this new code block. Next we get the code
tag with id yaml
.
yaml.innerHTML = Prism.highlight(
dockerCompose,
Prism.languages.yaml,
"yaml"
);
Finally, the main part of our code which actually highlights our code. We pass the newly pasted YAML it’s stored in dockerCompose
variable. Next we tell Prism what language to use Prism.languages.yaml
(this is the language grammar and finally, we pass the language name in this case, YAML. Then we set this as the innerHTML
of the code
tag.
That’s it! Now when the user paste’s in new YAML code, it’ll be automatically syntax highlighted by PrismJS. This process can of course, be used for AJAX content as well. If you make an API request and the API responds with code that needs to be syntax highlighted.
Note: The code in this project isn’t particularly clean, it’s mostly all in one file. This is just to make the example a bit easier to follow in reality you would likely split this into multiple files.