Skip to content

WordPress SimpleMDE Markdown Block

Markdown Gutenberg Block

I really like Markdown. It's the easiest way I've found to quickly write blog posts. And while WordPress has bumped up its support for Markdown, it's still not as good as some of the other programs out there.

While working on another project I came across SimpleMDE which is an outstanding JavaScript Markdown editor. And it seemed like a perfect fit for editing blog posts.

And that's what this plugin is. It wraps SimpleMDE in a Gutenberg block so you can edit your posts in Markdown and have it converted back to HTML when displayed.

Installation

Download the latest release from GitHub.

Login to your WordPress site and click on Add New Plugin and then Upload Plugin.

Upload the zip file you downloaded from GitHub and activate the plugin.

Once activated you should have a Markdown block in your WordPress editor.

markdown-block.php

Most of the block is handled through JavaScript, but it does take a little bit of PHP to get everything setup.

<?php

/**
 * Plugin Name:         Markdown Gutenberg Block
 * Plugin URI:          https://github.com/ryannutt/wordpress-markdown-gutenberg-block/
 * Description:         Adds a block to WordPress where you can use the SimpleMDE editor to write posts using Markdown
 * Version:           0.1.1
 * Requires at least:   5.2
 * Requires PHP:        7.2
 * Author:              Ryan Nutt
 * Author URI:          https://www.nutt.net
 * License:             GPL v2 or later
 * License URI:         https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:         markdown-gutenberg-block
 * Domain Path:         /lang
 */

namespace Aelora\WordPress\MarkdownBlock;

MarkDownBlock::init();

class MarkDownBlock
{
     private static $version = null;

     /**
      * Initializes the plugin
      */
     public static function init()
     {
          add_action('init', [self::class, 'register_block']);
     }


     public static function register_block()
     {
          wp_register_script(
               'aelora-simplemde',
               'https://cdnjs.cloudflare.com/ajax/libs/simplemde/1.11.2/simplemde.min.js',
               [],
               null,
               false
          );
          wp_register_script(
               'aelora-markdown',
               plugins_url('js/dist/markdown-block.min.js', __FILE__),
               ['jquery', 'wp-blocks', 'aelora-simplemde'],
               self::version(),
               false
          );

          wp_register_style('aelora-simplemde', 'https://cdnjs.cloudflare.com/ajax/libs/simplemde/1.11.2/simplemde.min.css', [], null);
          wp_register_style('aelora-markdown', plugins_url('css/dist/markdown-block.min.css', __FILE__), [], self::version());

          register_block_type('aelora/markdown-block', [
               'editor_script' => ['aelora-markdown'],
               'editor_style' => ['aelora-simplemde', 'aelora-markdown'],
               'render_callback' => [self::class, 'render_callback']
          ]);
     }

     /**
      * Render the content on client side
      * 
      * @param mixed $props The properties coming from Gutenberg
      */
     public static function render_callback($props)
     {
          return '<div>' . $props['html'] . '</div>';
     }

     /**
      * Gets the version from package.json
      */
     public static function version()
     {
          if (self::$version === null) {
               $json = json_decode(file_get_contents(__DIR__ . '/package.json'), true);
               if (empty($json) || empty($json['version'])) {
                    self::$version = false;
               } else {
                    self::$version = $json['version'];
               }
          }
          return self::$version;
     }
}

block.js

This is where most of the block happens.

/**
 * Source for adding the Markdown Gutenberg block to WordPress
 */

(function () {
    var __ = wp.i18n.__;
    var createElement = wp.element.createElement;
    var registerBlockType = wp.blocks.registerBlockType;

    var markdownIcon = wp.element.createElement('svg', {
            width: 16,
            height: 16
        },
        wp.element.createElement('path', {
            d: "M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"
        })
    );

    /**
     * Register block
     *
     * @param  {string}   name     Block name.
     * @param  {Object}   settings Block settings.
     * @return {?WPBlock}          Block itself, if registered successfully,
     *                             otherwise "undefined".
     */
    registerBlockType(
        'aelora/markdown-block', {
            title: __('Markdown', 'markdown-gutenberg-block'),
            icon: markdownIcon,
            category: 'common',
            description: __('Edit posts in markdown with the SimpleMDE editor', 'markdown-gutenberg-block'),
            attributes: {
                markdown: {
                    default: ''
                },
                html: {
                    default: ''
                }
            },
            supports: {
                html: false,
                reusable: true
            },

            // Defines the block within the editor.
            edit: function (props) {
                var el = wp.element.createElement;

                var textarea = el('textarea', {
                    style: {
                        display: 'none'
                    }
                }, props.attributes.markdown);

                var editor = el('div', {
                    'data-markdown': 'editor'
                }, textarea);

                var preview = el('div', {
                    'data-markdown-preview': props.clientId,
                    'data-markdown': 'preview'
                }, '');

                var loaderImage = createElement('img', {
                    src: 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
                    onLoad: function (evt) {
                        var simplemde = new SimpleMDE({
                            element: jQuery('div[data-block="' + props.clientId + '"] textarea')[0],
                            toolbar: false,
                            spellChecker: false,
                            status: false
                        });

                        /* I'm doing this with jQuery instead of loading it directly because it was escaping
                            when put in the contents of the preview div
                        */
                        jQuery('div[data-markdown-preview="' + props.clientId + '"]').html(props.attributes.html);

                        /* Load the contents into properties for the block */
                        simplemde.codemirror.on("change", function () {
                            var renderedHTML = simplemde.options.previewRender(simplemde.value());
                            props.setAttributes({
                                markdown: simplemde.value(),
                                html: renderedHTML
                            });
                            jQuery('div[data-markdown-preview="' + props.clientId + '"]').html(renderedHTML);
                        });

                        /* Watch for changes in block class to see if we need to refresh the codemirror */
                        var observer = new MutationObserver(function (mutationsList) {
                            var el = jQuery('div#block-' + props.clientId);
                            if (el.hasClass('is-selected')) {
                                simplemde.codemirror.refresh();
                            }
                        });
                        observer.observe(jQuery('div#block-' + props.clientId)[0], {
                            attributes: true,
                            attributeOldValue: true,
                            attributeFilter: ['class']
                        });
                    }
                });

                return el('div', {}, editor, preview, loaderImage);
            },

            // Defines the saved block.
            // save: function (props) {
            //     return null;
            // }
        }
    );
})();

markdown-block.css

And then just a touch of SCSS to make everything look right.

/**
 * Base SASS file for Markdown Gutenberg block plugin
 */

div {
    [data-type="aelora/markdown-block"] {

        &.is-selected,
        &.is-typing {
            div[data-markdown="editor"] {
                display: block;
            }

            div[data-markdown="preview"] {
                display: none;
            }
        }

        div[data-markdown="preview"] {
            display: block;

            * {
                background: transparent !important;
            }
        }

        div[data-markdown="editor"] {
            display: none;
            width: 100%;
            font-family: monospace;

            textarea: {
                display: none;
            }

            * {
                // background: transparent !important;
            }
        }
    }
}

GitHub Repo

This is all on a GitHub repo if you’re interested.

Published inCoding

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *