Blog Main

Svelte, Strapi, Nginx (4)

Nils

25.03.2021 20:00:00

Markdown Support and code Highlighting

In the last post, I gave an example of how to use server-side fetching and rendering in Sapper and Svelte to create a blog page. Today we're going to wrap this up with another post focusing on the frontend.

TLDR

In this post, we are going to add support to write our posts using markdown and use Prism to make our code look prettier. Lastly, I will give an example of how markdown text using those two tools can look like.

This is going to be a shorter post compared to the other two but as said I wanted to wrap things up before going to the backend part.

Getting Ready

This tutorial is going to be based on the code we did in the previous post. If you are not experienced with Svelte and Sapper I advise you to look at this one first.

Showdown

What is Markdown

The first thing we are talking about is enabling markdown support for our blog content. In case you don't know what markdown is:

"Markdown is a lightweight markup language for creating formatted text using a plain-text editor." (source: Wikipedia)

In other words, it's an easy way to format text that can not only be easily made to look good in the browser but also be easily understood by every plain texteditor.

In case you want to know more about how markdown formatting looks like I can advise you on this Cheatsheet by Adam Pritchard.

Installing the prerequisites

For this, we are not writing the code completely from scratch. That would be a waste of time, given that there are many good open-source packages to freely use.

The package we're going to use is Showdown which is a free open source javascript package that does the job well.

To install it we have to go to open a terminal like git bash or bash and go to the directory that our Svelte project is in. There we run the following command:

npm install showdown

After this part is done its time to go to our blog page that renders the Content

This should look like this:

<script context="module">
    export async function preload({ params }) {
        // the `slug` parameter is available because
        // this file is called [slug].svelte
        const res = await this.fetch(`blog/${params.slug}.json`);
        const data = await res.json();

        if (res.status === 200) {
            return { post: data };
        } else {
            this.error(res.status, data.message);
        }
    }
</script>

<script>
    export let post;
</script>

<style>
    /*
        By default, CSS is locally scoped to the component,
        and any unused styles are dead-code-eliminated.
        In this page, Svelte can't know which elements are
        going to appear inside the {{{post.html}}} block,
        so we have to use the :global(...) modifier to target
        all elements inside .content
    */
    .content :global(h2) {
        font-size: 1.4em;
        font-weight: 500;
    }

    .content :global(pre) {
        background-color: #f9f9f9;
        box-shadow: inset 1px 1px 5px rgba(0, 0, 0, 0.05);
        padding: 0.5em;
        border-radius: 2px;
        overflow-x: auto;
    }

    .content :global(pre) :global(code) {
        background-color: transparent;
        padding: 0;
    }

    .content :global(ul) {
        line-height: 1.5;
    }

    .content :global(li) {
        margin: 0 0 0.5em 0;
    }

</style>

<svelte:head>
    <title>{post.title}</title>
</svelte:head>

<img class="headerImage" src={post.pictureSrc}>
<h1>{post.title}</h1>
<h2>{post.subHeader}</h2>

<div class="content">
    {@html post.html}
</div>

Here the first thing we will mostly do changes in the script tag but also a small edit in the content div:

<script>
    //Add the necessary packages
    import showdown from "showdown";
    import { beforeUpdate } from "svelte";
    export let post;

    //Create the converter and on load convert the content
    let converter = new showdown.Converter();
    let content = converter.makeHtml(post.html);

    //Ensure that the content is going to be updated when content changes
    beforeUpdate( () => {
        content = converter.makeHtml(post.html);
    })
</script>

Above we can see the changes in the script tag. We import the showdown converter from npm, as well as the before update hook that svelte offers.

This hook is called whenever the props (in our case the post variable) changes their value. We then update the content variable before the values are set to display the right data.

In addition to the beforeUpdate hook, we have to add the code outside, because beforeUpdate is callback is not called on the initial load.

Now the content variable is a html string that was converted from showdown. This needs to be loaded in the html below instead of the original html data of the post:

<div class="content">
    {@html post.html}
</div>

This finishes the markdown support.

The next step is to make the code highlighting look better.

Prismjs

For this I used Prismjs. In here two things have to be done.

  1. Get the CSS to set up the styling
  2. Get the javascript to add the right CSS classes to a code block that should be highlighted

Css

For the styling I advise, to go to The Prism.js page to generate a minified css element.

Just pick your theme, all the programming languages you want to be enabled, and download the CSS.

Javascript

Here we can also use the javascript from Prism.js directly, import that locally, and use the code this way.

I decided to instead use the npm package since the imports are quicker and its easier to update.

First, you should run the following command in the root folder of your project.

npm install prismjs

After this is installed we met all our perquisites to put those two together.

Putting things together

I put my CSS into src/css so the import call should be something like import "../../css/prism.css"

In addition we want to import prismjs and prismjs/plugins/normalize-whitespace/prism-normalize-whitespace.min.js to remove whitespaces and tabs in our code when not necessary. Removing this makes the markdown easier and faster to write.

We also need two new svelte hooks: afterUpdate and onMount.

The script tag should then look something like this.

<script>
    //Add the necessary packages
    import showdown from "showdown";
    import { afterUpdate, beforeUpdate, onMount } from "svelte";

    //New imports
    import "../../css/prism.css";
    import "prismjs";
    import 'prismjs/plugins/normalize-whitespace/prism-normalize-whitespace.min.js'

    export let post;

    //Create the converter and on load convert the content
    let converter = new showdown.Converter();
    let content = converter.makeHtml(post.html);

     onMount( () => { 
        Prism.plugins.NormalizeWhitespace.setDefaults({
            'remove-trailing': true,
            'remove-indent': true,
            'left-trim': true,
            'right-trim': true,
        });
        Prism.highlightAll()
    })

    //Ensure that the content is going to be updated when content changes
    beforeUpdate( () => {
        content = converter.makeHtml(post.html);
    })

    afterUpdate( () => {
        Prism.highlightAll()
    })
</script>

First, on the mount, we want to install the NormalizeWhitespace Plugin to make sure that our code is trimmed right. Then when this is set up it is as simple as calling Prism.highlightAll() to tell prisms to highlight all the HTML pre blocks that are there

Example Markdown

Once this is done all we need to do is write our markdown to make our blogpost look good. In here I provide some example that combines the markdown with the code highlighting

  
#H1 Tag
The above text is transformed into h1 while this is not

```javascript
let a = "This is javascript code that is going to be highlighted by prismjs
console.log(a)
```

<center>[logo]: https://github.com/adam-p/markdown-here/raw/master/src/common/images/icon48.png "Logo Title Text 2"</center>
The image is an image that is going to be centered with HTML directly in markdown
  

The above code shows general markdown capabilities and how a blog post could be written.

What's next?

In the next post, it is going to be time to build our backend. I will talk about Strapi and how it can be used to create a fast backend server that enables REST API calls as well as the use of Graphql together with a lot of possible database setups. Without much coding needed although possible for easy customization.