DEV Community

Cover image for Parsing DEV markdown within a Laravel Tailwind application
Erika Heidi for Sourcegraph

Posted on • Updated on

Parsing DEV markdown within a Laravel Tailwind application

In a previous part of our series, we saw how to create an Artisan command to import your latest DEV articles and save them as markdown files within the storage folder of your Laravel application. The only thing missing now is to exhibit these posts in the main page of the demo application.

In this post, you'll learn how to parse the DEV markdown using the librarianphp/parsed library, which enables support for liquid tags in addition to parsing the front matter to obtain metadata related to the post. You can also watch the recap video on YouTube.

Final result - Headless DEV blog demo in Laravel + TailwindCSS

Disclaimer: this methodology works only with posts that are created with the classic markdown DEV editor, which uses a Jekyll front matter within the markdown body of the article. If you use the rich editor, you'll need to generate a valid front matter based on the returning API information for each article, before saving them as .md files. This is not covered within this series.

Preparation

Before moving along, make sure you have followed all steps in the previous tutorials of the series in order to bootstrap your Laravel application and development environment, to create the basic front-end view, and to create an Artisan command to import posts from DEV. This will require you to have Docker, Curl, and Git installed on your system.

This tutorial assumes that you have an alias to ./vendor/bin/sail on the root of the application folder, so that you can run Sail with ./sail. You can create such an alias with the following commands, executed from the application root folder:

ln -s ./vendor/bin/sail sail
chmod +x sail
Enter fullscreen mode Exit fullscreen mode

If you haven't yet, bring your environment up with:

./vendor/bin/sail up -d
Enter fullscreen mode Exit fullscreen mode

This command will start up your environment and keep it running in the background. Your Laravel application should now be available at http://localhost from your browser.

1. Installing Parsed via Composer

To parse the content imported from DEV, we'll use librarianphp/parsed, a library based on league/commonmark that parses the metadata content from traditional DEV posts that use the Jekyll front matter, in addition to parsing liquid tags and allowing you to implement your own custom liquid tags.

Run the following command to include librarianphp/parsed as a Composer dependency:

./sail composer require librarianphp/parsed
Enter fullscreen mode Exit fullscreen mode

Once the dependencies are installed, you can move on to the next step.

2. Fetching the list of posts from local markdown files

If you followed along with all tutorials in this series, you should now have your DEV articles as .md files in the local application storage folder, storage/app. Now you'll need to create a list with your most recent posts and make the list available to the front-end view so that you can exhibit your posts on the main application page.

We'll need to modify the routes/web.php file, since that is where you have your main route defined. Open this file in your code editor.

The following code uses the glob() function to loop through all files in the specified directory, which is defined with the help of the storage_path() helper function. This function will return the absolute path for directories inside your storage folder. When specifying the search pattern, we use a /*.md filter to make sure we skip any files that aren't markdown files. Then, we create a Content object by loading the contents from each file, and add that object to an array called articles. We then use the krsort method to order the array in reverse order, which will bring your latest articles first in the list. We also use the array_slice() function to keep a fixed number of results, defined by the variable limit which is set to 10 by default. Finally, we return the template view, passing in the list of articles so that it can be used in a loop.

You can replace the current content on your routes/web.php with the following:

<?php

use Illuminate\Support\Facades\Route;
use Parsed\Content;
use Parsed\ContentParser;

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
   $articles = [];
   $limit = 10;

   foreach (glob(storage_path('app') . '/*.md') as $file) {
       $article = new Content(file_get_contents($file));
       $article->parse(new ContentParser());

       $articles[] = $article;
   }

   krsort($articles);
   $articles = array_slice($articles, 0, $limit);
   return view('index', [
       'articles' => $articles
   ]);
});

Enter fullscreen mode Exit fullscreen mode

Save the file when you're done.

3. Listing the posts on the application's main page

If you reload the application on your browser now, nothing has changed, since you still need to update your main front-end view to list the content obtained in the previous step. The content was passed along to the front-end view in an array named articles, so we'll need to loop through this array in order to show each article's summary.

Open the index view file at resources/views/index.blade.php on your code editor. Locate the main content area, where the example posts are statically defined. You can remove the static posts because you're going to replace them with a foreach Blade loop.

Each item in the articles array is an object of type Parsed\Content. This object is a wrapper around markdown content using the Jekyll front matter format to define metadata about the content, such as title, cover image, and tags. All items defined in the content's front matter are available through a method called frontMatterGet(). To check if a metadata information exists, you can use the method frontMatterHas().

The following piece of Blade-compatible code loops through the articles array to exhibit a summary of each post. It checks whether a cover_image property is set within the frontmatter of the article. When this property is not present, it will use a random image from Lorem Picsum to illustrate the post. For simplicity, the code assumes that title, description, and tags are always set:

   @foreach ($articles as $article)
       <div class="mb-10">
           <img src=@if ($article->frontMatterHas('cover_image'))
                      "{{ $article->frontMatterGet('cover_image') }}"
                    @else "https://picsum.photos/1000/420"
                    @endif alt="Post header image" class="rounded-lg my-4"
            />
           <h1 class="text-4xl mb-4">{{ $article->frontMatterGet('title') }}</h1>
           <p class="text-md">{{ $article->frontMatterGet('description') }}</p>
           <p>Posted in: <span class="text-gray-700 font-bold">{{ $article->frontMatterGet('tags') }}</span></p>
       </div>
   @endforeach

Enter fullscreen mode Exit fullscreen mode

Here is the fully updated index.blade.php file for your reference:

<!doctype html>

<html lang="en">
<head>
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0" />

   <title>My DEV Blog</title>
   <meta name="description" content="My DEV Blog">
   <meta name="author" content="Sourcegraph">

   <meta property="og:title" content="A headless blog in Laravel">
   <meta property="og:type" content="website">
   <meta property="og:url" content="https://github.com/sourcegraph-community/laravel-dev-blog">
   <meta property="og:description" content="A demo Laravel blog">

   <link href="{{ asset('css/app.css') }}" rel="stylesheet">

</head>

<body class="bg-gradient-to-r from-blue-600 via-pink-500 to-purple-900">

   <div class="container text-white mx-auto mt-6">
       <div class="grid grid-cols-3 gap-4">
           <div>
               <h1 class="text-3xl">My DEV Blog: latest posts</h1>
           </div>
           <div class="col-span-2 text-right">
               menu
           </div>
       </div>
   </div>

   <div class="container mx-auto px-10 py-10 bg-gray-100 my-10 text-gray-600 rounded-md shadow-md">
       <div class="grid grid-cols-3 gap-8">
           <div class="col-span-2">
               @foreach ($articles as $article)
                   <div class="mb-10">
                       <img src=@if ($article->frontMatterHas('cover_image'))
                                   "{{ $article->frontMatterGet('cover_image') }}"
                                @else "https://picsum.photos/1000/420"
                                @endif alt="Post header image" class="rounded-lg my-4"
                        />
                       <h1 class="text-4xl mb-4">{{ $article->frontMatterGet('title') }}</h1>
                       <p class="text-md">{{ $article->frontMatterGet('description') }}</p>
                       <p>Posted in: <span class="text-gray-700 font-bold">{{ $article->frontMatterGet('tags') }}</span></p>
                   </div>
               @endforeach
           </div>

           <div>
               <img src="https://picsum.photos/100" class="rounded-full mx-auto p-4" alt="avatar"/>
               <p class="text-gray-700 text-xl mb-10">Hi, I'm a demo Laravel application built with Tailwind CSS, pulling content from DEV.to.
                   You can find my code <a class="text-purple-800 font-bold" href="https://github.com/sourcegraph-community/laravel-dev-blog" title="Laravel DEV blog demo on GitHub">here</a> and you can learn more about me <a class="text-purple-800 font-bold" href="https://dev.to/sourcegraph/creating-a-new-laravel-application-with-sail-and-docker-no-php-required-4c2n" title="Getting started with Laravel tutorial series">here</a>.</p>

               <h2 class="text-gray-400 text-2xl">Links</h2>

               <ul class="py-2">
                   <li class="py-2"><a class="px-1 py-2 bg-purple-300" href="https://github.com/sourcegraph-community/laravel-dev-blog">Demo Laravel DEV Blog on GitHub</a></li>
                   <li class="py-2"><a class="px-1 py-2 bg-pink-400" href="https://dev.to/sourcegraph/creating-a-new-laravel-application-with-sail-and-docker-no-php-required-4c2n">Getting started with Laravel on DEV</a></li>
                   <li class="py-2"><a class="px-1 py-2 bg-purple-300" href="https://laravel.com/docs/8.x">Laravel Documentation</a></li>
                   <li class="py-2"><a class="px-1 py-2 bg-pink-400"  href="https://tailwindcss.com/docs/installation">TailwindCSS Documentation</a></li>
                   <li class="py-2"><a class="px-1 py-2 bg-purple-300" href="https://php.net">Official PHP Documentation</a></li>
                   <li class="py-2"><a class="px-1 py-2 bg-pink-400" href="https://sourcegraph.com">Sourcegraph Code Search</a></li>
               </ul>
           </div>
       </div>
   </div>

</body>
</html>

Enter fullscreen mode Exit fullscreen mode

The sidebar was also slightly updated with a few resourceful links to improve the overall appearance of the page. Once you save the template and reload the application on your browser, you should see a page similar to this, but showcasing your own DEV articles:

Animated gif showing the final result of the demo headless DEV blog built with Laravel and TailwindCSS

Conclusion

In this article, which is the fourth and last part of our Getting started with Laravel series, we've seen how to create a collection of DEV.to markdown posts and parse them using the librarianphp/parsed library.

The demo application is open source and fully available on GitHub. Feel free to clone or fork the project to create a custom version that better suits your needs.

Helpful Resources

Check the following list with useful resources to learn more about the open source projects and libraries used in this series:


You can follow Sourcegraph on Twitch to be notified when we go live. Follow our YouTube channel for the full recap videos of our livestreams.

Discussion (0)