Build a live markdown editor with VueJS
You will need Node installed on your machine. Some knowledge of JavaScript will be helpful.
Introduction
As developers, we sometimes love to conveniently create easy-to-read documentation so as to ease the stress that accompanies styling at the beginning. For this, you need an easy tool such as the Markdown editor. This enables you to create a H1 ( for example) by simply adding a # before the header.
Together in this tutorial we’ll build a simple, yet very effective realtime markdown editor application with Vue and powered by Pusher. This app will be used to convert raw markdown into proper HTML. It will have two separate sections:
- To the left, where you write and view raw markdown
- And the right, where you can easily view the converted markdown
A quick look at what we’ll build:
Prerequisites
Ensure that you have Node.js and npm installed on your machine. A quick overview of other core technologies we will be using in this tutorial include:
-
Vue: a progressive JavaScript framework for building applications
-
Pusher: a Node.js client to interact with the Pusher REST API
-
Marked: a low-level markdown compiler for parsing markdown without caching or blocking for long periods of time.
💡 I am using @vue/cli 2.0 ****for this project
Setting up the project
We’ll use Vue-cli to setup our project, so run the command below to have it installed globally on your machine:
npm install -g @vue/cli
or
yarn global add @vue/cli
You can verify that Vue is properly installed by running:
vue --version
This will output the current version installed on your machine, just like this:
Now to generate our project, type the following command:
vue init webpack vue-markdown // version 2
or
vue create vue-markdown // version 3
Executing the command above will bring up a couple of questions, you can accept the default and proceed. Once the installation process is completed, you will now have a new project named vue-markdown
installed in your project directory.
Running the application
Next, we’ll run the application:
npm start // version 2
or
npm run serve // version 3
This will start the application on the http://localhost:8080. Visit that link:
Installing server dependencies
Run the following commands to install the dependencies required for this project:
npm install --save pusher pusher-js marked
npm install --save body-parser cors dotenv express
Pusher account setup
Head over to Pusher and sign up for a free account, if you don’t already have one. Log in to create a new application by clicking on the Channels apps on the sidebar. Obtain your application credentials as we will need to use them later in this post.
A little more about how Pusher works
Pusher allows you communicate between different parts of your application in realtime. It can be a notification you wish to show your users or the price of a product which people are bidding on currently. Whatever it is that needs constant updating, you can (and maybe should) use pusher for it.
By default, Pusher allows you bind to events on the client-side (listen to events on your browser, app, etc) and then trigger events on the server-side (send broadcasts to all listeners from the server). However, pusher has this really cool super amazing feature called private channels that allows you trigger events from the client side. You have to turn it on and perform a few actions to use it.
- Your channel name on the client-side must be prefixed with
private-
- All your event names on the client-side must be prefixed with
client-
- You must authenticate the Pusher subscription before you can trigger events on the client-side.
So, from your Pusher app dashboard, go to App settings and enable client events before you continue with this guide.
Now you are ready. You can read more about private channels.
Environment variables
Create a file name .env
in the root directory of your application and add your application credentials as obtained from your Pusher dashboard as follows:
PUSHER_APP_ID=YOUR_APP_ID
PUSHER_APP_KEY=YOUR_APP_KEY
PUSHER_APP_SECRET=YOUR_APP_SECRET
PUSHER_APP_CLUSTER=CLUSTER
Ensure that you replace YOUR_APP_ID
, YOUR_APP_KEY
, YOUR_APP_SECRET
and CLUSTER
placeholders with the appropriate credentials.
Setting up the server
The main objective of this application is to be able to process and convert a raw markdown to HTML in realtime from all browsers, to effectively achieve this, we’ll use Express to set up a simple server and use Pusher to broadcast the converted markdown to all the client on a specific channel.
So create a server.js
file in the root directory of your application and add the following code snippet to it:
// server.js
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const Pusher = require('pusher');
require('dotenv').config();
const app = express();
app.use(cors());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
const pusher = new Pusher({
appId: process.env.PUSHER_APP_ID,
key: process.env.PUSHER_APP_KEY,
secret: process.env.PUSHER_APP_SECRET,
cluster: process.env.PUSHER_APP_CLUSTER,
encrypted: true
});
app.post('/pusher/auth', function(req, res) {
var socketId = req.body.socket_id;
var channel = req.body.channel_name;
var auth = pusher.authenticate(socketId, channel);
res.send(auth);
});
var port = process.env.PORT || 3000;
app.listen(port);
console.log("Listening on 3000")
First, we basically loaded all the necessary middlewares for the Express server and configured Pusher using the credentials we added to our environment variables earlier.
Our client application will need to make an API call to a specified endpoint in order to authenticate our pusher connection and ensure we can run a private channel on the frontend. Pusher has an authenticate()
function that does that for us.
Open another terminal and start the server on http://localhost:3000 with:
node server
This will log a message to the console as shown below. This is to indicate that the server has been started successfully:
Creating the home component
For the purpose of this application we’ll create a new component, so navigate to ./src/components
and create a new file named HomeComponent.vue
within it. Once you are done, paste in the code below:
// ./src/components/HomeComponent.vue
<template>
<div>
<div class="title">
<h2>{{ title }}</h2>
</div>
<div>
<div class="row">
<div class="col-md-6">
<textarea v-model="markdown" name="" id="" cols="80" rows="15" @keyup="postMark"></textarea>
</div>
<div id="preview" class="col-md-6" v-html="compiledMarkdown"></div>
</div>
</div>
</div>
</template>
<script>
import axios from "axios";
import pusher from "pusher";
export default {
name: "Home",
data() {
return {
title: "Realtime Markdown Editor",
markdown: "",
channel : {}
};
},
};
</script>
<style>
.title {
margin-bottom: 40px;
}
#preview {
border: 2px solid;
text-align: left;
}
</style>
Here, within the template section of the component above, we created two separate columns: a textarea where the raw markdown will be written and a second column to preview the compiled markdown in realtime.
Furthermore, we proceeded to added a little bit of styling to the application.
Enabling realtime editing
We want to send updates to our document immediately the user makes them. This is the whole essence of adding realtime functionality to begin with. With private channels, we can skip the entire step of sending the update to the server and then triggering an event from the server. We can do all of that from the client now.
Open ./src/components/HomeComponent.vue
and define the Pusher application:
// ./src/components/HomeComponent.vue
...
<script>
import marked from "marked";
import pusher from "pusher";
export default {
...
created() {
let pusher = new Pusher("YOUR_APP_KEY", {
cluster: "CLUSTER",
encrypted: true
authEndpoint: 'http://localhost:3000/pusher/auth',
});
this.channel = pusher.subscribe("private-markdown");
this.channel.bind("client-new-text", data => {
this.markdown = data;
});
},
// We will generate the markdown and trigger events here
...
};
</script>
...
In the created
method, we have defined the Pusher application, subscribed to a private channel and bound the channel to the an event. Now, we are ready to listen to any data exchange that will happen across that channel.
Do ensure that you replace the
YOUR_APP_KEY
andCLUSTER
with the appropriate credential. Also replace theauthEndpoint
with the endpoint you defined for your application.
Generating markdown
To generate markdown from the input we make, add the following code to the file:
// ./src/components/HomeComponent.vue
...
<script>
...
export default {
...
computed : {
compiledMarkdown: function () {
return marked(this.markdown, { sanitize: true })
}
},
// We will listen for changes to the document here
...
};
</script>
...
The computed
data attribute compiledMarkdown
will always be updated as the content of markdown
changes. This is good for us so we can see changes immediately.
Trigger event with changes
Now, let’s send the changes we make to the document to everyone following it
// ./src/components/HomeComponent.vue
...
<script>
...
export default {
...
methods: {
postMark: function(e) {
const text = e.target.value;
this.channel.trigger("client-new-text", text);
}
}
...
};
</script>
...
Wrapping up
Navigate to ./src/App.vue
file and include the created HomeComponent.vue
file within it:
// ./src/App.vue
<template>
<div id="app">
<homeComponent/>
</div>
</template>
<script>
import HomeComponent from "./components/HomeComponent";
export default {
name: "App",
components: {
HomeComponent
}
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
And finally, open the index.html
file and update as shown below:
// ./index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Vue Realtime Markdown</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">Navbar</a>
</nav>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="https://js.pusher.com/4.1/pusher.min.js"></script>
</body>
</html>
We included the CDN file for bootstrap, added a navigation bar and also included the script file for Pusher.
Test the application
Restart the application by running npm start
from the terminal and don’t forget to also start the server by opening another terminal within your project folder with node server
.
Conclusion
In this tutorial, you have learned how to conveniently build a simple markdown editor application using Vue and Pusher to enhance the realtime functionality. I hope you found this helpful. You can find the source code for the demo here on GitHub.
11 October 2018
by Christian Nwamba