Intro
If you’re working with code blocks and not using syntax highlighting, you're missing out on one of the simplest ways to make your life (and your users' lives) better. Who wants to stare at a dull, plain block of code? That’s where Highlight.js comes in to save the day. But wait! Trix editor, lovely as it is, doesn’t play nice out of the box with Highlight.js because it only wraps code blocks in <pre>. To get that sweet, sweet highlighting, you also need a <code> tag.
This guide will show you how to take control and fix that, plus we’ll make sure it works with Turbo in Rails 7 because, hey, we live in the future now.
The Problem
Trix editor does this slightly annoying thing where it wraps code only in <pre> tags. Highlight.js says, "Nope, I want my <code> too!" So we need to tweak Trix a bit to wrap code in both <pre><code>. Oh, and if you’re using Turbo (which you probably are in Rails 7), we have to make sure our fancy fix works with Turbo’s page loads.
The Plan
We’ll mess with the form submission process, hijacking it just before the data is sent to the server, to wrap those code blocks properly. And don't worry—we'll use the right event to make sure it plays nice with Turbo.
Step 1: Install Highlight.js (You Knew This Was Coming)
If you haven’t already installed Highlight.js, now’s the time:
With Yarn:
With Yarn:
yarn add highlight.js
If you're lazy and prefer CDNs:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
Pick your poison, but either way, Highlight.js will be ready to do its thing.
Step 2: Set Up Your Post Model for Rich Text
Trix editor works because of ActionText. You’ve probably already got this, but just in case, make sure your Post model is set up to handle rich text:
class Post < ApplicationRecord
has_rich_text :content
end
Boom! Done.
Step 3: Add Trix to the Form (Like the Boss You Are)
Now let’s throw the Trix editor into your form. Just use the rich_text_area helper like this in app/views/posts/_form.html.erb:
<%= form_with(model: @post, local: true) do |form| %>
<div class="field">
<%= form.label :title %>
<%= form.text_field :title %>
</div>
<div class="field">
<%= form.label :content %>
<%= form.rich_text_area :content %> <!-- Here's our Trix editor -->
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
Trix will do its thing, but we’re going to tweak it a bit.
Step 4: Hijack the Form Submission with JavaScript
Now for the magic! We’re going to grab the Trix content right before the form is submitted, look for any <pre> tags that are flying solo without <code>, and wrap those bad boys up properly.
Here’s the JavaScript that does the trick. Pop this into your app/javascript/application.js:
Here’s the JavaScript that does the trick. Pop this into your app/javascript/application.js:
document.addEventListener("turbo:load", function () {
// Grab the form (make sure you're selecting the right form on your page)
const form = document.querySelector("form");
if (form) {
// Listen for the form submit event
form.addEventListener("submit", function (event) {
// Find the Trix editor in the form
const trixEditor = form.querySelector("trix-editor");
if (trixEditor) {
// Get the editor's inner HTML
let content = trixEditor.editor.element.innerHTML;
// Wrap <pre> blocks that don't already have <code> in them
content = content.replace(/<pre>(?!<code>)([\s\S]*?)<\/pre>/g, '<pre><code>$1</code></pre>');
// Shove the modified content into a hidden input field
let hiddenInput = form.querySelector('input[name="post[content]"]');
if (!hiddenInput) {
hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'post[content]');
form.appendChild(hiddenInput);
}
hiddenInput.value = content;
// Disable the Trix editor so it doesn't send its own stuff
trixEditor.setAttribute('disabled', 'true');
}
});
}
});
Step 5: Render and Highlight the Code in Your View
Okay, so the form is working. Now we need to display that nicely highlighted code when someone views the post. In your app/views/posts/show.html.erb, add this:
<div class="post-content">
<%= raw @post.content.body %>
</div>
<script>
document.addEventListener("turbo:load", function () {
document.querySelectorAll("pre code").forEach((block) => {
hljs.highlightElement(block); // Highlight.js doing its thing
});
});
</script>
Boom. Now Highlight.js will do its job and make your code blocks look pretty.
Step 6: Test It!
Now, go make a post, add some code, and submit it. Your code blocks should be wrapped in <pre><code> like they’re supposed to be, and Highlight.js will apply all the fancy highlighting. Mission accomplished.
Wrap-up
That’s it! You’ve successfully bent Trix to your will, and your Rails 7 app now properly highlights code blocks without breaking a sweat. Plus, everything works smoothly with Turbo, so you’re not stuck with partial page load issues.