Add LaTeX Support & Code Highlight In Wagtail

Table of Contents

Wagtail Tutorial Series:

To get the latest learning resource for Wagtail 4, please check Build Blog With Wagtail CMS (4.0.0)

  1. Create Wagtail Project
  2. Dockerizing Wagtail App
  3. Add Blog Models to Wagtail
  4. How to write Wagtail page template
  5. Add Bootstrap Theme to Wagtail
  6. How to use StreamField in Wagtail
  7. Wagtail Routable Page
  8. Add pagination component to Wagtail
  9. Customize Wagtail Page URL
  10. Add Full Text Search to Wagtail
  11. Add Markdown Support to Wagtail
  12. Add LaTeX Support & Code Highlight In Wagtail
  13. How to Build Form Page in Wagtail
  14. How to Create and Manage Menus in Wagtail
  15. Wagtail SEO Guide
  16. Source code: https://github.com/AccordBox/wagtail-tailwind-blog

Wagtail Tips:

  1. Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
  2. Wagtail Tip #2: How to Export & Restore Wagtail Site

Write style in Wagtail:

  1. How to use SCSS/SASS in your Django project (Python Way)
  2. How to use SCSS/SASS in your Django project (NPM Way)

Other Wagtail Topics:

  1. How to make Wagtail project have good coding style
  2. How to do A/B Testing in Wagtail CMS 
  3. How to build a landing page using Wagtail CMS 
  4. How to support multi-language in Wagtail CMS 

More Wagtail articles and eBooks written by me

Objective

By the end of this chapter, you should be able to:

  1. Customize Markdown Renderer
  2. Add Latex support to the Markdown

Background

In the previous chapter, we have used wagtail-markdown to help us write content in Markdown and render it to HTML.

The markdown renderer is built on python-markdown, you can check Github source code to learn more.

We can also add extension to change, extend the behavior of the parser without having to edit the actual source files. Some extensions have been included with python-markdown, you can check the official doc to get more detail. extension

Here we will add extension to make our post page support Latex.

LaTeX is a document preparation system for high-quality typesetting which includes features designed for the production of technical and scientific documentation

MathJax is an open-source JavaScript display engine for LaTeX, MathML, and AsciiMath notation that works in all modern browsers

So here is the workflow:

  1. We tell python-markdown content in $...$ and $$...$$ is Latex
  2. We use MathJax to display the Latex content on the browser.

Render Markdown

├── blog
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── blocks.py
│   ├── md_converter
│   │   ├── __init__.py
│   │   ├── mdx
│   │   │   ├── __init__.py
│   │   │   └── mdx_mathjax.py
│   │   └── utils.py

Notes:

  1. In blog app, we created a Python package md_converter
  2. mdx contains the Markdown extension.
  3. We can create the directory and empty file here and I will talk about them in a bit.

Update blog/md_converter/utils.py

import markdown


def render_markdown(value):
    html = markdown.markdown(
        value,
        extensions=[
            'extra',
            'codehilite',
            'blog.md_converter.mdx.mdx_mathjax',
        ],
        extension_configs={
            'codehilite': [
                ('guess_lang', False),
            ]
        },
        output_format='html5'
    )
    return html

Notes:

  1. Here we created render_markdown function, it would run Python markdown to render the markdown content to HTML
  2. The extra and codehilite are built-in extension from Python markdown
  3. 'blog.md_converter.mdx.mdx_mathjax' is the custom extension we build for Latex support.

Create blog/md_converter/mdx/mdx_mathjax.py

"""
https://github.com/mayoff/python-markdown-mathjax
"""
import markdown
import html


class MathJaxPattern(markdown.inlinepatterns.Pattern):
    def __init__(self, md):
        markdown.inlinepatterns.Pattern.__init__(
            self, r'(?<!\\)(\$\$?)(.+?)\2', md)

    def handleMatch(self, m):
        # Pass the math code through, unmodified except for basic entity
        # substitutions.
        # Stored in htmlStash so it doesn't get further processed by Markdown.
        text = html.escape(m.group(2) + m.group(3) + m.group(2))
        return self.markdown.htmlStash.store(text)


class MathJaxExtension(markdown.Extension):
    def extendMarkdown(self, md, md_globals):
        # Needs to come before escape matching because \ is pretty important
        # in LaTeX
        md.inlinePatterns.add('mathjax', MathJaxPattern(md), '<escape')


def makeExtension(configs=[]):
    return MathJaxExtension(configs)

Notes:

  1. The key point here is the regex expression (?<!\\)(\$\$?)(.+?)\2, it would recognize $...$ and $$...$$, so python-markdown would not process the content inside $...$ and $$...$$.
  2. In most cases, people would like to use $...$ for inline math and $$...$$ for multi-line math.

Templates

Update blog/templatetags/blogapp_tags.py

from blog.md_converter.utils import render_markdown


@register.filter(name='markdown')
def markdown(value):
    return render_markdown(value)

We created a custom Django filter markdown

Update wagtail_bootstrap_blog/templates/blog/components/streamfield.html

{% load static wagtailcore_tags blogapp_tags %}

{% with blocks=page.body %}
  {% for block in blocks %}
    {% if block.block_type == 'h1' %}
      <h1>{{ block.value }}</h1>
    {% elif block.block_type == 'h2' %}
      <h2>{{ block.value }}</h2>
    {% elif block.block_type == 'paragraph' %}
      {{ block.value|richtext }}
    {% elif block.block_type == 'image_text' %}
      {% include 'blog/blocks/image_text.html' with block=block only %}
    {% elif block.block_type == 'image_carousel' %}
      {% include 'blog/blocks/image_carousel.html' with block=block only %}
    {% elif block.block_type == 'markdown' %}
      {{ block.value|markdown|safe }}
    {% else %}
      <section class="block-{{ block.block_type }}">
        {{ block }}
      </section>
    {% endif %}
  {% endfor %}
{% endwith %}

Notes:

  1. For markdown type, we use the custom markdown filter to process it and render it to HTML.
  2. By default, Django would do auto-escape, we can disable this behavior with the safe filter. You can check Django doc: safe tag to learn more.
  3. If you want to sanitize html generated from markdown, please check bleach and this code

MathJax

Update wagtail_bootstrap_blog/templates/blog/post_page.html

{% extends "base.html" %}

{% load wagtailcore_tags wagtailimages_tags blogapp_tags static %}

{% block extra_css %}
  <link rel="stylesheet" href="{% static "css/code_theme.css" %}" />
{% endblock %}

{% block content %}

{% endblock %}

{% block extra_js %}
  {# If you want to use Latex with Markdown #}
  <script type="text/x-mathjax-config">
      MathJax.Hub.Config({
        extensions: ["tex2jax.js"],
        jax: ["input/TeX", "output/HTML-CSS"],
        tex2jax: {
          inlineMath: [['$','$']],
          displayMath: [['$$','$$']] ,
          processEscapes: true
        },
        "HTML-CSS": { availableFonts: ["TeX"] }
      });
  </script>
  <script type="text/javascript"
          src="//cdn.mathjax.org/mathjax/latest/MathJax.js">
  </script>
{% endblock %}

Notes:

  1. We added MathJax code to the extra_js block.
  2. What you should notice is the inlineMath and displayMath in the config section. Let's take a look at the final result.

Wagtail Tutorial Series:

To get the latest learning resource for Wagtail 4, please check Build Blog With Wagtail CMS (4.0.0)

  1. Create Wagtail Project
  2. Dockerizing Wagtail App
  3. Add Blog Models to Wagtail
  4. How to write Wagtail page template
  5. Add Bootstrap Theme to Wagtail
  6. How to use StreamField in Wagtail
  7. Wagtail Routable Page
  8. Add pagination component to Wagtail
  9. Customize Wagtail Page URL
  10. Add Full Text Search to Wagtail
  11. Add Markdown Support to Wagtail
  12. Add LaTeX Support & Code Highlight In Wagtail
  13. How to Build Form Page in Wagtail
  14. How to Create and Manage Menus in Wagtail
  15. Wagtail SEO Guide
  16. Source code: https://github.com/AccordBox/wagtail-tailwind-blog

Wagtail Tips:

  1. Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
  2. Wagtail Tip #2: How to Export & Restore Wagtail Site

Write style in Wagtail:

  1. How to use SCSS/SASS in your Django project (Python Way)
  2. How to use SCSS/SASS in your Django project (NPM Way)

Other Wagtail Topics:

  1. How to make Wagtail project have good coding style
  2. How to do A/B Testing in Wagtail CMS 
  3. How to build a landing page using Wagtail CMS 
  4. How to support multi-language in Wagtail CMS 

More Wagtail articles and eBooks written by me

Launch Products Faster with Django

SaaS Hammer helps you launch products in faster way. It contains all the foundations you need so you can focus on your product.

Michael Yin

Michael is a Full Stack Developer from China who loves writing code, tutorials about Django, and modern frontend tech.

He has published some ebooks on leanpub and tech course on testdriven.io.

He is also the founder of the AccordBox which provides the web development services.

Django SaaS Template

It aims to save your time and money building your product

Learn More

Build Jamstack web app with Next.js and Wagtail CMS.

Read More
© 2018 - 2023 AccordBox