Code Blocks and Markdown with the Webflow CMS

How we added Markdown support to our Webflow blog

At [Daily.co](http://daily.co) we use [Webflow](https://webflow.com/) for our product website and our blog.

We love Webflow. It's a tool with a big sweet spot. It's easy to use, flexible, powerful, and "just works." Webflow has almost everything we could ever want in a web publishing and management tool, and whenever we've run up against the edges of the Webflow feature set it's been easy to add a little bit of custom code.

Recently, we started publishing more blog posts that include [code samples](https://www.daily.co/blog/implementing-api-billing-with-stripe), and realized that Webflow doesn't directly support code blocks and syntax highlighting in their [Content Management System](https://webflow.com/cms). So we added that feature! While we were at it, we hacked together markdown rendering support, too. Here's how we did it.

Our blog content lives inside Webflow's [Content Management System](https://webflow.com/cms). The Webflow CMS interface includes a rich text editor. For most of our blog posts, we just copy and paste text right from wherever we've drafted it into the Webflow rich text editor. (Yay, rich text copy and paste standards!)

However, for blog posts that have sample code embedded in them, it's really nice to format that code with syntax highlighting. We tried a few ways to do that using CSS, but didn't come up with styling that was quite what we wanted.

Inspired by a [blog post](https://www.sheda.ltd/posts/how-to-embed-github-gists-into-a-webflow-page) from [Mike Ebinum](https://twitter.com/mikeebinum), we realized we could add some javascript to our Webflow CMS template and do complete code syntax highlighting on the client side!

Enter [Prism](https://prismjs.com/), a syntax highlighting library, and [Postscribe](https://github.com/krux/postscribe), a small library that streamlines DOM rewrites.

Any code block that we want Prism to format, we mark as `CODE` with a special string on the first line, like this:

   -- CODE language-js --
    -- CODE language-js --
   // Here's some code that Prism will highlight
   // according to javascript syntax rules. We will
   // pass the "language-js" part of the special string
   // through to Prism to indicate what syntax rules to
   // use. Prism supports lots of languages (see the docs)
   // and also some additional special classes, like
   // "line-numbers". So you could do this, too, for example:
   // -- CODE language-js line-numbers --
   
   function foo () {}

In our Webflow CMS template we load the Prism and Postscribe libraries. (You can build custom versions of the Prism library with support for only the languages that you need, using [this nifty tool](https://prismjs.com/download.html#themes=prism-solarizedlight&languages=markup+css+clike+javascript).)

   -- CODE language-markup --
   <head>
   ...
     <script src="https://cdnjs.cloudflare.com/ajax/libs/postscribe/2.0.8/postscribe.min.js"></script>
     <script src="https://s3-us-west-2.amazonaws.com/daily-web/prism.js"></script>
     <link href="https://s3-us-west-2.amazonaws.com/daily-web/prism.css" rel="stylesheet" type="text/css" />
   
     <style>
       /* make prism-formatted code block font a little
          smaller than the default */
       pre[class*="language-"] {
       font-size: 0.8em;
       line-height: 0.8em;
     }
   </head>

And add this javascript down at the bottom of our page body, inside a `<script>` tag

   -- CODE language-js line-numbers --
   function formatCode(el) {
       // find our magic declaration string. if we don't find it,
       // do nothing
       let match = el.innerHTML.match(/--\s*CODE\s+(.*)\s*--/),
           classNames, codeEl;
       if (match && match[1]) {
         classNames = match[1];
       } else {
         return;
       }
       // strip off the magic string, everything preceding it, and
       // all leading and trailing whitespace
       let txt = el.innerHTML.substring(match.index+match[0].length);
       el.innerHTML = '';
       if (classNames.match(/language-markup/)) {
         codeEl = el.appendChild(document.createElement('script'));
         codeEl.type = 'text/plain';
       } else {
         codeEl = el.appendChild(document.createElement('pre')).
                     appendChild(document.createElement('code'));
       }
       codeEl.className = classNames;
       codeEl.innerHTML = txt;
     }
   
     let snips = $('p:contains("CODE")');
     snips.toArray().forEach(formatCode);
     Prism.highlightAll();

There are just a couple of subtleties here. First, Webflow loads jquery by default, so we didn't need to explicitly load jquery in our template's `<head>`. Second, we're  using Prism's [unescaped markup plugin](https://prismjs.com/plugins/unescaped-markup/) to format code blocks of type `language-markup`. The `if` statement in line 15 determines whether we rewrite the DOM to replace paragraphs with nested `<pre><code>` tags (as is generally the case for Prism), or with `<script>` tags (for unescaped markup).

Here's a [demo on repl.it](https://repl.it/@kwindla/webflow-code-highlighting) of the above code.

But why stop there? We also added in support for inserting github gists. (This was what the  sample code from Mike Ebinum that originally inspired us, did, and we figured github gists might be useful in the future.)

Then we realized that often, our draft blog posts start life as markdown-formatted text. And markdown allows for some formatting that the Webflow rich text editor doesn't handle. For example, nested lists. (I'm addicted to using nested lists!)

So we took the same approach — client-side formatting of parts of the DOM — and added markdown support to our blog workflow.

First, in Webflow, we added another text content area to our CMS template.

The idea is that if we are authoring a blog post, we can do it as rich text, or as markdown.

Our markdown field in the Webflow CMS

We use the [Showdown markdown converter](https://github.com/showdownjs/showdown) to turn markdown into HTML. We load it in our document `<head>` just like we load Prism and Postscribe.

The markdown content area in the Webflow CMS editor outputs HTML. (It's a rich text field, from Webflow's perspective.) So in our client-side code we convert from the RTF/HTML generated by Webflow to bare markdown, and then from there Showdown coverts that markdown back to HTML!

   -- CODE language-js --
   function rtfToMarkdown(el) {
       let txt = el.innerHTML;
       txt = txt.replace(/<p>/g, '');
       txt = txt.replace(/<\/p>/g, '\n\n');
       txt = txt.replace(/<br>/g, '\n');
       txt = txt.replace(/&nbsp;/g, ' ');
       txt = txt.replace(/&lt;/g, '<');
       txt = txt.replace(/&gt;/g, '>');
       txt = txt.replace(/&amp;lt;/g, '&lt;');
       // console.log(txt);
       return txt;
     }
   
     function markdownToHtml(txt) {
       let converter = new showdown.Converter({
         noHeaderId: true,
         headerLevelStart: 2,
         literalMidWordUnderscores: true
       });
       let html = converter.makeHtml(txt);
       // console.log(html);
       return html;
     }
   
     // if there are elements with a class named 'markdown'
     // attached, first convert their content to html
     let markdowns = $('.markdown');
     markdowns.toArray().forEach((el) => {
       let txt = rtfToMarkdown(el),
           html = markdownToHtml(txt);
       el.innerHTML = html;
     });

And, of course, we make sure that our code syntax highlighting is still supported in "markdown mode." Here's [a repl.it demo](https://repl.it/@kwindla/webflow-markdown) showing all the pieces together. Note the nested lists. :-)

It turns out that the markdown support we added to our Webflow CMS template makes it easy for us to collaboratively edit draft posts in [Notion](https://www.notion.so/product). We do more and more of our product planning in Notion, so it's natural for us to draft code-heavy tutorial blog posts in the same tool.

To transfer content from Notion to Webflow, we just "select all" twice in Notion to select all of the blocks of content on a page. Then in Webflow, we "paste as plain text" into the markdown content block. That's it! So easy.

The only things that don't work automagically are images. We still have to add images into the Webflow post by hand. But when [Notion launches an API](https://www.notion.so/Notion-for-Teams-f6f02f30e70c4fcdbad04760354d8f79#87a26ecd7fe44733aa1e4c4f88969a02), we'll probably be able to automate those, too!

We hope this has been a useful show-and-tell about how we extended Webflow's CMS for our workflows and particular blogging needs. If anything's not clear, or you have any suggestions for how to improve on what we've done, please tweet to us at [@trydaily](https://twitter.com/trydaily).

Daily.co is a complete platform for 1-click video calling.
Learn more about our products: Daily.co API, a video calling API for developers; Daily.co TV for conference rooms and always-on portals; or our free no download browser video calls.

Recent posts