Now that we finally have the nuts and bolts of our site complete we can move on to actually creating our blog. There’s a lot of code in this section, so I’ll be adding links to the relevant sections on the project’s Github page where appropriate. We are going to start by creating a form that will allow us to add an article to a our database. With that in mind let’s start off by creating our articles table:

php artisan make:migration create_articles_table --create=articles

Now we should find our new migration file in database/migrations so let’s open that up and add some fields for an article, for now we’ll just add some basic fields, which we will want to add to in future:

Schema::create('articles', function (Blueprint $table) {
    $table->increments('id');
    $table->string('title');
    $table->text('article');
    $table->text('formatted_article');
    $table->string('slug')->unique();
    $table->timestamp('published_on')->nullable();
    $table->timestamps();
});

It should be pretty obvious what each field is, so let’s create the table by doing:

php artisan migrate

Creating the Form

Now we have our table setup, we need to create a form to add a post, let’s start by creating a component called CreatePost.vue in resources/assets/js/admin/components/views and add it to our routes in resources/assets/js/admin/admin.js, so we end up with:

import Login from './components/views/Login.vue'
import Dashboard from './components/views/Dashboard.vue'
import CreatePost from './components/views/CreatePost.vue'

const routes = [
    { path: '/', component: Dashboard },
    { path: '/login', component: Login },
    { path: '/posts/create', component: CreatePost },
]

Now we just want to add that to our Navigation Menu – which you can view on the Project GitHub Page – so we can now navigate to our “Create Post” Page.

Because we have a route with more than one segment, we will need to adjust our Laravel route so that we can navigate directly to that page inside our browser, just like we did with the main website route, so we simply need to append the following where caluse to the admin route in routes/web.php:

Route::get('/admin/{vue?}', function(){
    return view('admin');
})->where('vue', '^(?!.*api).*$[\/\w\.-]*');

Right, let’s create our form. We want the form to be reusable so we can use it when we come to editing posts, so let’s create a new component called ArticleForm.vue in a new folder at resources/assets/js/admin/components/partials and let’s just quickly add a holding template:

<template>
    <div>
        <!-- Article Form Here -->
    </div>
</template>

And we can now add that to CreatePost.vue like so:

<template>
    <div>
        <article-form></article-form>
    </div>
</template>


<script type="text/javascript">
import ArticleForm from '@/admin/components/partials/ArticleForm.vue';
export default {
    components: {
        ArticleForm
    }
}
</script>

With that sorted, let’s start our ArticleForm component. I would like it to look like one continuous document, rather than a form, so I’m going to add some styling to remove input borders, I’m also going to use the Raleway font from Google fonts, so have included that in resources/assets/sass/app.scss so I have global access to it.

I’m also going to need some way to deal with code snippets, I’ll stick with the same code tags used by WordPress, which should make articles easier to migrate, but one thing I would find helpful is to have my snippets display with the “courier” font, so posts are easier to proof read, that means I want two different fonts in my article input which a textarea cannot handle, so I’ll need to use a content editable, which allows you to edit HTML content. One great thing about content editable is that it auto adjusts the content area height, which is exactly what I want. The downside is that we need to do a quite a bit of work with the Selection and Range APIs, which can be quite tricky. You can view the completed component on the GitHub page, but let’s look in detail at how the content editable works.

Exploring the Content Editable

If you look at the content editable markup you will see the following:

<div contenteditable="true"
     id="article" 
     ref="article" 
     @input="setArticle" 
     @keyup.221="isCode" 
     @keydown.ctrl.alt.83="addSnippet" 
     @keydown.9.prevent 
     @paste="formatSnippets()" 
     v-once>{{article}}</div>

The first thing you may notice is that I have assigned a ref, this allows me to access the element from inside my view model by doing this.$refs.article. Next you will see that I am calling a method called setArticle() on input, which looks like this:

setArticle(event) {
  this.article = event.target.innerText;
  this.emitInput();
},

This just sets the article data property to the content editable text as we type and emits an event, so we can receive the update in the parent.

Next you will see @keyup.221="isCode" this calls the isCode() method when a closing square bracket is entered. This method (which for some reason WordPress won’t let me include here) may look a bit complicated, but all we are doing is finding the opening square bracket and seeing if it is an opening code tag, if it is it calls the addSnippet() method which creates a “code” closing tag, adds the markup for formatting the snippet in “courier” and moves the cursor caret to the next line.

Then we have @keydown.ctrl.alt.83="addSnippet" this is a keyboard shortcut for ctrl + alt + s which calls the addSnippet() method directly, the addSnippet() method will also use any selected section of text and wrap that in the code tags and format it accordingly.

We also have @keydown.9.prevent which stops the tab key from focusing on the next object. I haven’t implemented tab functionality, I tend to indent with 2 spaces rather than a tab and write my articles in a text editor, so it’s not a priority for me and it would actually take quite a lot of work to implement.

After that we have a @paste="formatSnippets()" this listens for the paste event and calls the formatSnippets() method, which searches for any pasted code snippets and adds the appropriate “courier” formatting.

Finally we have v-once which means that we only render the content editable once, this is simply so we can add default values (via {{articles}}) but not track the changes, as this is being done by setArticle().

Phew! That’s what happens when you deal with content editable, however I’m now happy that this is suitable for my needs. It’s not perfect but I’m yet to encounter a content editable that is and I don’t want to go down that particular rabbit hole when this isn’t part of the publicly facing site.

Creating a Previewer

While we now have a nice input form, we don’t yet have any way to preview the article. To do that we are going to need to write a something for pretty printing our code snippets and we will need to include a markdown parser. The actual document parsing is going to be done server side, which will return our correctly formatted article. So let’s start by pulling in a markdown parser from composer, I like cebe/markdown so we will use that:

composer require cebe/markdown

Now we want to create a new API controller called ArticlesController:

php artisan make:controller Api\ArticlesController

And let’s create a method called preview() which will parse the markdown, we’ll sort out code snippets in a second:

public function preview(Request $request){
    $markdown = new GithubMarkdown();
    $markdown->html5 = true;

    return $markdown->parse($request->article);
}

Now we need to set up our API routes to the preview method, so in routes/api.php we will add:

<?
// ... Other routes ...
Route::middleware('auth:api')->post('/articles/preview', 'Api\ArticlesController@preview');

And then we need to add a preview tab to ArticleForm.vue and create a getPreview() method that calls our preview route and sets a data property named preview to the parsed article, which we are then rendering using v-html, you will also notice that I’ve added a tab property to track which tab is active, this tab is then bound to a v-show on each content block, so the content only shows when the relevant tab has been selected:.

Tabs

<div class="ui top attached tabular menu" style="margin-bottom:15px;">
  <a href="#" @click="tab = 'edit'" :class="[{active: tab === 'edit'},'item']">Edit</a>
  <a href="#" @click="getPreview" :class="[{active: tab === 'preview'},'item']">Preview</a>
</div>


<div v-show="tab === 'edit'">
   <!--edit tab content-->
</div>

<div v-show="tab === 'preview'">
   <!--preview tab content-->
</div>

Javascript

getPreview(){
  console.log('get preview');
  this.tab = 'preview';
  axios.post('/api/articles/preview',{article: this.article}).then(response => {
     this.preview = response.data;
  }).catch(error => {
    console.log(error);
  })
},

That sorts out the markdown, but we haven’t done anything with our code snippets, I don’t just want them shoved in code tags, I want them to be syntax highlighted and to do that I’m going to make a call to the public http://www.hilite.me api which will return our syntax highlighted code, but first I need to extract the snippets, I don’t want to clog up my controller with a bunch of methods, so let’s create a folder called “Services” in app/Http and add a new php class called ArticleServices.php and copy the preview() method code in ArticlesController over to a method called renderArticle():

<?php
namespace App\Http\Services;

use cebe\markdown\GithubMarkdown;

class ArticleService
{
   public function renderArticle($article) {
      $markdown = new GithubMarkdown();
      $markdown->html5 = true;

      return $markdown->parse($request->article);
   }
}

Now in ArticlesController we can type hint or ArticleService class in our constructor and Laravel will automatically inject the dependency for us, which will allow us to use the methods in our ArticlesService class in our ArticlesController, which now looks like this:

<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use cebe\markdown\GithubMarkdown;
use App\Http\Services\ArticleService;

class ArticlesController extends Controller
{

    protected $articleService;

    function __construct(ArticleService $articleService){
       $this->articleService = $articleService;
    }

    public function preview(Request $request){
        return $this->articleService->renderArticle($request->article);
    }

}

Now we can put all our rendering logic in our ArticleService class. I’m not going to go through all the PHP code, which you can check out on the project GitHub page, but I will explain how it works. Basically, we are using regex` to extract everything between the code tags, then we are sending each code snippet to the hilite.me api using Guzzle, we are then taking the response and replacing the non-formatted code snippets with the prettified code snippets, and I’ve added some extra markup so I can add some additional styles to snippets using css, once all the code snippets have been inserted, we parse the markdown and return the results:

preview_screenshot

Finally we need to be able to save the article in our database, so let’s start by making an Article model by doing:

php artisan make:model Article

That creates a new file at app/Article.php where we will add the $fillable array to our model which will allow us to save an article and let’s use the slug as our route key, so we can pass that instead of the id to our api routes, so our Article model looks like this:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    protected $fillable = [
      'title',
      'article',
      'formatted_article',
      'slug',
      'published_on'
    ];

    public function getRouteKeyName() {
        return 'slug';
    }
}

Now we’ve done that let’s add a store method to our ArticlesController:

<?php
// ...
public function store(Request $request){
    $request['formatted_article'] = $this->articleService->renderArticle($request->article);
    $request['slug'] = str_slug($request['title']);
    Article::create($request->all());
}

Then let’s create the route, we’ll use a resource route for this which will auto generate the CRUD routes for our API, so in routes/Api.php add:

Route::middleware('auth:api')->resource('/articles', 'Api\ArticlesController');

Now all we need to do is capture the “input” event being emitted from ArticleForm.vue in CreatePost.vue and add a “Publish” button that will save the article for us:

<template>
    <div>
        <article-form @input="setArticle"></article-form>
        <div class="ui divider"></div>
        <button class="ui primary button" type="submit" @click="saveArticle">Publish</button>
    </div>
</template>

<script type="text/javascript">
import ArticleForm from '@/admin/components/partials/ArticleForm.vue';
export default {
    components: {
        ArticleForm
    },
    methods: {
        setArticle(title, article) {
            this.title = title;
            this.article = article;
        },
        saveArticle() {
            axios.post('/api/articles', {
                title: this.title,
                article: this.article
            }).then(response => {
                console.log(response);
            });
        },
    },
    data() {
        return {
            title: '',
            article: ''
        }
    }
}
</script>

Now if you hit the “Publish” button the article will be saved, we don’t have any view set up yet to show us published articles, but you can check by doing:

php artisan tinker

then:

App\Article::first();

And you should see your saved article.

That’s it for part 7, next we will look at integrating Laravel’s Form Request validation with Vue.js and providing some feedback with Sweet Alert.

Advertisements