My Blog

A blog built with Bootstrap and Actiontext in Rails.

Topics

Trix Code-Block Fix with Stimulus

I had an interesting couple of days trying to add code markdown to Trix.  If I'm going to be blogging and showing code, I want it to look nice, right?  

I had done some searching and found a very helpful article that inspired me to attempt it myself.  I'm still pretty new to Rails, so I knew it wouldn't be easy.  First I tried to add a Javascript file in app/Javascript and add it into application.js.  However, I only have one page, this blog, that has 'trix-content', so it kind of broke things on other pages and the result was that it would log me out each time.  So, I decided to add the JS file only to my blogs layout page.  However, everything I did ended up with an error about the file not being in the asset pipeline.  I tried a bunch of different ways to add it via manifest.js, and assets.rb, but that never worked at all.  

In my eternal searching, I came across the idea to put it in as a Stimulus Controller, which is only added to a page when the specific tags are present.  Adding it to the page that way was pretty straightforward once I spent a little time with the Stimulus docs learning naming conventions and such, I was able to get some action on the page.  However, it wasn't what I wanted.  It was applying 5 code blocks to only the first blog on the page.  It would work sometimes on the blogs/show page, but usually required a refresh.  That would not do.  I eventually figured out that the 5 code blocks issue was due to the 5 blogs on a page.  I had the tags on the _blog partial and it was getting called 5 times and applied to the first blog every time, making 5 nested code blocks.  I had to do a little head-scratching and tinkering, to make it wait for the page to be fully loaded and to always fire on a change to the page.  There were a lot of errors due to the fact that if a blog doesn't have a code block, then the querySelectorAll brings back nothing and functions don't like to work on nothing.  So, with a few if statements to check for the presence of one or more code blocks on each and every blog, and figuring out the best place to put the stimulus data-controller and data-target tags, I got it to work very nicely.  I thought I would share my code and spin the wheels on the new toy a bit.  

app/javascript/controllers/trix_mark.js:
import { Controller } from '@hotwired/stimulus';
  

  export default class extends Controller {
  	static targets = ['source'];
  

  	connect() {
  		document.addEventListener(
  			'DOMContentLoaded',
  			this.applyFormattingToPreBlocks()
  		);
  	}
 
  	applyFormattingToPreBlocks = () => {
  		const trixElements = document.querySelectorAll('.trix-content');
  		trixElements.forEach((trixElement) => {
  			const preElements = trixElement.querySelectorAll('pre');
  			preElements.forEach((preElement) => {
  				if (preElement.length !== 0) {
  					const regex = /(?!lang\-\\w\*)lang-\w*\W*/gm;
  					const codeElement = document.createElement('code');
  					if (preElement.childNodes.length > 1) {
  						console.error(
  							'<pre> element contained nested inline elements (probably styling) and could not be processed. Please remove them and try again.'
  						);
  					}
  					let preElementTextNode = preElement.removeChild(
  						preElement.firstChild
  					);
  					let language = preElementTextNode.textContent.match(regex);
  					if (language) {
  						language = language[0].toString().trim();
  						preElementTextNode.textContent =
  							preElementTextNode.textContent.replace(language, '');
  						codeElement.classList.add(language, 'line-numbers');
  					}
  					codeElement.append(preElementTextNode);
  					preElement.append(codeElement);
  				}
  			});
 		});
  	};
  }
lang-javascript
In app/javascript/controllers/index.js:
import TrixMarkController from './trix_mark_controller.js';
application.register('trix-mark', TrixMarkController);
lang-javascript
app/views/layouts/blog: at the top of the body surrounding everything else.
 <div  data-controller="trix-mark">
   <div data-trix-mark-target="source">
lang-html
Add this code to the header of your layouts/*.erb page to make sure you get a full page load to trigger the code to execute:
<meta name="turbo-visit-control" content="reload">
lang-html
You will also need the CDN links for Prism so it can generate the nice color formatted code blocks.   They are specified in the article linked at the top of this post.

app/views/layouts/blogs:
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/themes/prism-okaidia.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/plugins/line-numbers/prism-line-numbers.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/components/prism-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/plugins/autoloader/prism-autoloader.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.17.1/plugins/line-numbers/prism-line-numbers.min.js"></script>
lang-html
And that's all it takes.  If you have stimulus in your rails app, then you should be able to just copy and paste this code.  If you want to look at the full code for this rails app, you can view it on github.

Comments