Date
3 min read

How to save markdown using an editor and render it using Laravel

Table of contents:

Recommended packages to implement:

Markdown editor:

The Blade UI Kit package has a great out-of-the-box editor. The editor can optionally accept some parameters that can affect the styling and functionality of the editor. One of the editor’s features includes image uploads, this can be done using Spatie’s Media Library package. In this example, we will be associating images to the posts.

The editor is presented as a Blade component as per the below. We can then pass through the editor’s options using a ‘Post’ model. Just to note, Spatie’s package requires the model to preexist within the database, hence the conditionally checking.

{{-- Blade --}}
<x-easy-mde name="content" :options="$post->getEditorOptions()">
  {!! $post->content !!}
</x-easy-mde>
class Post extends Model
{
    // ...

    /**
     * @see https://github.com/Ionaru/easy-markdown-editor
     */
    public function getEditorOptions(): array
    {
        $options = [
            'minHeight' => '700px',
            'tabSize' => 4,
        ];

        if ($this->exists) {
            $options = array_merge($options, [
                'uploadImage' => true,
                'imageUploadEndpoint' => route('admin.posts.images.store', $this),
                'imageCSRFToken' => csrf_token(),
                'imageCSRFName' => '_token',
                'imagePathAbsolute' => false,
            ]);
        }

        return $options;
    }
}

Below is an example of a dedicated Controller that handles file uploads from the editor and the associating with a given post.

public function store(PostImageRequest $request, Post $post)
{
	$request->validate([
		'image' => 'required|file',
	]);

	$image = $post->addMediaFromRequest('image')
		->toMediaCollection('post_content_images');

	return response()->json([
		'data' => [
			'filePath' => $image->getPathRelativeToRoot(),
		],
	]);
}

Lastly, we can have another separate Controller for handling the saving of the post’s content. Also, it’s important to remove any unused images for the post to save on storage space.

$post->update($request->safe()->only([
	'content',
]));

$postContent = $post->content;

foreach ($post->getMedia('post_content_images') as $image) {
	if (! Str::contains($postContent, $image->getPathRelativeToRoot())) {
		$image->delete();
	}
}

Markdown presenter:

Now you’ll want to convert the markdown content. You can use Spatie’s markdown package to achieve this i.e.

{{-- Blade --}}
<x-markdown>{!! $post->content !!}</x-markdown>

The only annoyance here is that this component’s name conflicts with that of the Blade UI Kit package. A simple way around this is to just create a dedicated Blade component with a unique, non-conflicting name that just extends Spatie’s.

<?php

namespace App\View\Components;

use Spatie\LaravelMarkdown\MarkdownBladeComponent;

class Markdown extends MarkdownBladeComponent
{
    // ...
}

This could then be registered within a Service Provider, like the following:

use App\View\Components\Markdown;

/**
 * Bootstrap any application services.
 *
 * @return void
 */
public function boot()
{
	Blade::component('markdown-renderer', Markdown::class);
}

You may want to make use of Tailwind’s typography library to use the ‘prose’ class. This will nicely present and format the HTML for you.

{{-- Blade --}}
<x-markdown-renderer class="lg:flex-grow [max-width:unset] prose prose-img:w-full prose-img:rounded-md prose:pre:bg-secondary mt-12" theme="one-dark-pro">{!! $post->content !!}</x-markdown-renderer>

Lastly, be sure to reminder the following with regards to the markdown package:

  1. Make node executable on the server i.e. sudo ln -s "$(which node)" /usr/local/bin
  2. Ensure node_modules are present for the project on the server

Let's work together 🤝

Line
Christopher Kelker
Chriscreates