DEV Community

Sebastian Schürmanns
Sebastian Schürmanns

Posted on

Creating a Block Editor with Markdown and Vue.js

Editing blocks of content is highly popular these days. WordPress is one of the last kids on the block, other content management systems like AEM or Magnolia follow the concept of content components since more than a decade now. So while I coded my small flat file cms called as a side project, I wondered if some kind of block editing could also bridge the gap between markdown and the not so tech-savvy mainstream-editors.

If you want to build a new editor-experience, then you can use one of the new editor-frameworks like prosemirror or slate, which also provide a realtime-wysiwyg-mode for markdown. However, I am not a professional software-developer and these frameworks are pretty complicated to start with. But I started to use Vue.js some month ago and so I decided to code my own block-editing-experience with from scratch. Don't expect a super high level frontend editor here, it is more like an experimental hacking and it is tightly coupled to Typemill, but the result might still be interesting:

Block editing with markdown
The typemill block editor as of november 2018

It is a quite big project, so I cannot show much code here, but maybe some basic ideas of the block-editing with markdown.

Markdown are Blocks by Definition

I am not sure if you are aware that markdown works with blocks by definition, because markdown uses two linebreaks to separate each block (paragraph). So you can transform a valid and normalized markdown-file into a simple array of content blocks with just one line of PHP-code:

  $markdownArray = explode("\n\n", $markdown);

You have to deal with some edge-cases like code-blocks, but basically it is as simple as that.

A Backend Approach

With Vue.js you can fetch the markdown-array with an API in a JSON-format and then create your website in the frontend. I decided against that standard-way from two reasons:

  • I already use a highly interactive navigation built with vue, so the page renders very slow if you add the content-part on top of that.
  • If I transform markdown to html in the frontend, then I have to maintain and extend two libraries: one markdown-parser in the backend (I use parsedown with several extensions) and one in the frontend (vue markdown parser for example).

So I decided for an uncommon way and used the backend for most of the tasks. With a php-library like parsedown it is pretty easy to transform the markdown-blocks into html-blocks similar like this:

$parsedown  = new Parsedown();
$htmlArray = [];

foreach($markdownArray as $markdownBlock)
    $htmlArray[] = $parsedown->text($markdownBlock);    

In the frontend, I can print out each html-block with a simple loop. Rendering the page in the backend makes the initial page load pretty fast. At the same time, Vue.js enters the stage because I can print out each block into a vue component like this:

<?php foreach($htmlArray as $key => $htmlBlock): ?>

    <div class="blox" @click.prevent="setData( $event )" data-id="<?php echo $key; ?>"><?php echo $htmlBlock; ?></div>

<?php endforeach; ?>

The Vue-Part

The vue-part is pretty complex (and chaotic, sorry for that), so I cannot show code-examples but only explain the basic ideas. So basically I use three parts:

  • A vue app with some general tasks.
  • A content-component as a frame or wrapper for each content-block.
  • Dynamic components for each content-type (like paragraph, images and more).

The vue app loads the markdown-array in a JSON-format with an API-call after the page has loaded. I can connect the html on the page and the markdown in the vue-data with the ID now. I do this with the setData-method:

@click.prevent="setData( $event )"

With the data-id I can get the corresponding markdown part and use it in my content-component. You might think that this is a bit hacky, but I explained the reasons before...

The content-component is the frame that does all the general stuff: It gets the markdown-data from the vue-app for each block, it switches between the html-preview-mode and the markdown-edit-mode, and each content component has a button to save, cancel and delete a block. When I save the block, then I send the markdown-data to the backend. In the backend I store the block in the file (work with the ID again to get the appropriate block), transform the markdown block into html again and send it in the response back to the frontend.

The most exciting part are the dynamic component. This is the concept of vue, where you can switch a component dynamically:

<!-- Component changes when currentTabComponent changes -->
<component v-bind:is="currentTabComponent"></component>

Vue explains the concept here and also provides some fiddles to play with.

I use this concept to open different components for different content types. The basic content type is a text-paragraph and this opens in the standard textarea-component. But I just added another component for handling images and a lot more will follow. Handling images can become pretty painful in some content-management-systems, so I think I found a quite user-friendly way:

Image Component of Typemill

If you want to have a look (on typemill or github), then don't expect high level code, as I said I am a hobby-developer and not a professional.

Top comments (0)