Flask-PageDown: Markdown Editor Extension for Flask-WTF

Posted by
on under

(I can't help it. I keep coming up with ideas for cool Flask extensions.)

If you've asked or answered a question on Stack Overflow you have seen the editor that they use. You type your text using Markdown syntax in a standard text area HTML control and below it a preview is generated as you type. This editor is powered by an open source project called PageDown.

Today I'm introducing Flask-PageDown, a wrapper extension that makes it really easy to add this control to your Flask-WTF based forms.

Demo

Below is a an example that will give you an idea of what this extension can do in your own application. Type Markdown in the text editor below and see how it gets converted on the fly to HTML!

Installation & Configuration

You can install the extension with pip:

$ pip install flask-pagedown

The extension needs to be initialized after the application instance is created:

from flask_pagedown import PageDown

app = Flask(__name__)
pagedown = PageDown(app)

You can also use the init_app(app) format.

The Editor is supported via two Javascript files. To include these files in your HTML document you have to call pagedown.html_head() from inside the <head> element of your page:

<html>
    <head>
    {{ pagedown.html_head() }}
    </head>
    <body>
    ...
    </body>
</html>

The Javascript files are loaded from a CDN, the files do not need to be hosted by your application. If you prefer to host these files locally then replace the html_head() call with your own <script> tags for files Markdown.Converter.js and Markdown.Sanitizer.js (or their minified versions).

Creating a Form with a PageDown Field

The extension exports a PageDownField class that works exactly like a TextAreaField. Here is an example form that uses this field:

from flask_wtf import Form
from flask_pagedown.fields import PageDownField
from wtforms.fields import SubmitField

class PageDownFormExample(Form):
    pagedown = PageDownField('Enter your markdown')
    submit = SubmitField('Submit')

The field can be rendered to a template in the usual way. For example:

    <form method="POST">
        {{ form.pagedown(rows = 10) }}
        {{ form.submit }}
    </form>

The preview element does not need to be added, the extension does that for you.

Note that when a form with a PageDownField is submitted the raw Markdown text is submitted. The form does not receive the HTML preview. If you need to store the HTML in the server then you should use a server-side Markdown converter like Flask-Markdown.

Styles

You can define custom styles for the text area and preview elements. The text area can be referenced with the flask-pagedown-input CSS class. The CSS class for the preview is flask-pagedown-preview.

Feedback and Contributions Accepted

As always I look forward to your comments and suggestions. Please write below in the comments.

At this time the extension does not display a toolbar like the one on Stack Overflow. I will probably add that in a future release, but if you get to it first please send me a pull request on github.

Miguel

Become a Patron!

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

38 comments
  • #26 Miguel Grinberg said

    @ella: Yes, you would save the Markdown source, and optionally, you can also save the rendered version. When you need to show the text in a template, you return the rendered version. If the user wants to edit the text, you can use the markdown source.

  • #27 rouzip said

    that's great,thx!

  • #28 Jarriq R said

    Isnt that the same as this?

    HTML CODE
    <input id="text_input">

    <div id="window"></div>

    JS CODE
    $('#text_input').keyup(function() {
    $('#window').html($(this).val());
    });

  • #29 Miguel Grinberg said

    @Jerriq: I"m not sure I understand what you are saying. Same as what? You are just copying the text to the other div. Where is the Markdown translation?

  • #30 Ama said

    Hi Miguel, have you come around to adding the toolbar? I looked around the github and saw that a similar issue is open

  • #31 Miguel Grinberg said

    @Ama: No, I have not got around to add a toolbar yet. Sorry.

  • #32 xcv said

    Great!

  • #33 Michael said

    Hi Miguel,

    I love your stuff. I am interested in using something like this except with the ability to render mathematics. Something like MathJax. Is this possible.

    Thanks in advance,
    Michael

  • #34 Miguel Grinberg said

    @Michael: the Flask side is just the integration, all the work is done in JavaScript, so you need to find a JS library that does the rendering that you need.

  • #35 Jon Reynolds said

    Hi @Miguel

    I am building a web app that is using Bulma CSS styling. The basic html output of the "rendered" Markdown field does nto contain the classes I need to style it appropriately with Bulma.
    For example:

    # Heading 1

    renders as:

    <h1>Heading 1</h1>

    But Bulma requires a class of "title" with the H1 to render it properly. Do you know of anyway I can inject custom classes into the rendered output of Markdown?

    Also is this extension just to show a Live Preview? Is it any use for rendering a static page? E.g. user fills in the post using Markdown syntax, then that post is displayed as rendered HTML...like in SO.

    Thanks so much for the mega tutorial...I has been no end of help to get me started with Flask. Really gettig into it now.

    Cheers,

    Jon.

  • #36 Miguel Grinberg said

    @Jon: would it be possible to use selectors to style the markdown elements without having to directly apply classes? That would be the easiest way. If not you will need to parse the generated Markdown, in the client and very likely also in the server to add your classes.

    This extension deals with the client-side preview. You need to add your own Python based Markdown renderer to generate the final Markdown in the server.

  • #37 lcdtiv said

    Hi Miguel, did you ever get around to making a tutorial on writing Flask extensions? I've looked but cannot seem to find one yet. It would be really interesting to learn about this from a prolific extension-writer, especially since the documentation is quite sparse. In any case, thanks for your efforts with flask - I'm always learning from your work.

  • #38 Miguel Grinberg said

    @lcdtiv: I have not (yet!).

Leave a Comment