Skip to content

Commit

Permalink
feat(blog): readtime (#634)
Browse files Browse the repository at this point in the history
* feat(blog): readtime

Add readtime, image, share buttons.

resolves #631

* refactor(blog): move nuxt setup into nuxt article

* style(blog): escape file names

* fix: app name upper case package name

* refactor(blog): share networks list render

Co-authored-by: Damien Robinson <damien.robinson@xcommedia.com.au>
  • Loading branch information
shadow81627 and damienrobinson committed Sep 28, 2020
1 parent e580482 commit 33c8764
Show file tree
Hide file tree
Showing 11 changed files with 533 additions and 34 deletions.
Binary file added assets/img/blog/nuxt.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 0 additions & 2 deletions assets/scss/_variables.scss

This file was deleted.

58 changes: 58 additions & 0 deletions assets/scss/vuetify.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ $body-font-family: 'Nunito Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI',
Roboto, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol' !default;

$code-font-family: Consolas, Roboto Mono, monospace;

@import '~vuetify/src/styles/styles.sass';
@import '~/assets/css/print.css' print;

Expand Down Expand Up @@ -65,3 +67,59 @@ h4 {
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: #555;
}

.v-application .nuxt-content .nuxt-content-highlight code {
all: initial;
all: unset;
}

.nuxt-content-highlight {
font-size: 16px;
position: relative;
z-index: 1;
border-radius: 6px;
}

.nuxt-content-highlight > .filename {
right: 0;
top: 0;
position: absolute;
margin-right: 0.8rem;
font-size: 0.8rem;
color: rgba(203, 213, 224, 1);
font-weight: 300;
z-index: 10;
margin-top: 0.5rem;
}

.nuxt-content pre {
position: static;
border-radius: 6px;
font-size: 16px;
padding: 20px;
}

.nuxt-content p code,
.nuxt-content h2 code,
.nuxt-content h3 code {
color: #476582;
padding: 0.25rem 0.5rem;
margin: 0;
font-size: 0.85em;
background-color: rgba(27, 31, 35, 0.05);
border-radius: 3px;
}

.nuxt-content ul {
font-size: 16px;
margin-bottom: 30px;
word-spacing: 2px;
line-height: 32px;
display: block;
list-style-type: disc;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0px;
margin-inline-end: 0px;
padding-inline-start: 40px;
}
262 changes: 260 additions & 2 deletions content/blog/how-to-build-a-static-site-blog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: How to build a static site blog?
title: How to build a static site blog
date: 2020-09-22T08:09:37.548Z
description: Have you ever tried to set up a personal blog?

Expand All @@ -21,9 +21,267 @@ Any good content manager should let you use markdown. This is important because

While not necessary, some great bonus features of a great static site blog are free hosting, open source, and perfomance. I host my personal site on [Vercel](https://vercel.com/) since the free teir is super generous compared to [Netlify](https://www.netlify.com/). I am able to run all the builds I need for rapid prototyping, bandwidth is unlimited, the builds are fast with caching, and the CDN is super fast serving content in under 50ms.

## Getting Started

---

If you haven't used Nuxt before you can go read my [Nuxt](/blog/nuxt#getting-started) Post about how to get started.

Install npm packages:

```bash
npm i @nuxt/content prism-themes dayjs @mdi/js
npm i @nuxtjs/vuetify @aceforth/nuxt-optimized-images --save-dev
```

Add `@nuxt/content` to the `modules` and `@nuxtjs/vuetify` to `buildModules` list in `nuxt.config.js`:

```js\[nuxt.config.js]
modules: [
"@nuxt/content",
],
buildModules: [
"@nuxtjs/vuetify",
],
content: {
markdown: {
// `prism-themes` for language syntax highlighting
prism: {
theme: 'prism-themes/themes/prism-vsc-dark-plus.css',
},
},
},
```

## Project Structure

---

Create a folder with name `content` inside the project folder if not created by `@nuxt/content`. Then create a sub-folder with name `blog` inside the folder `content`. `content/blog` is the folder where we write all our blog posts in markdown files.

Similarly, create a sub-folder `blog` inside `pages` folder for a new page route `/blog`. Create `_slug.vue` and `index.vue` inside `pages/blog` folder.

Now the project folder structure will look something like this:

```txt
├── content
│ └── blog
│ ├── blog-1.md
│ └── blog-2.md
├── pages
│ └── blog
│ ├── _slug.vue
│ └── index.vue
```

## Blog Post List Page

Here, we will list down all the blog posts we create under `content/blog` folder. Displaying Title, Description and hero image for each blog post with Vuetify card components.

- Blog Post Image
- Blog Post Title
- Blog Post Description
- Blog Post created time fromated with [dayjs](https://day.js.org/)
- Time for reading blog post

```vue\[pages/blog/index.vue]
<template>
<div>
<BlogHero :title="heading"></BlogHero>
<v-container fluid style="max-width: 1785px">
<v-row>
<v-col
v-for="{ slug, title, description, date, body, image } of items"
:key="slug"
class="d-flex flex-column"
cols="12"
sm="6"
md="4"
lg="3"
>
<v-card
:to="`/blog/${encodeURIComponent(slug)}`"
class="flex d-flex flex-column justify-between"
>
<v-img :src="image" :aspect-ratio="16 / 9"
></v-img>
<v-card-title class="text-break text-wrap">
<h2>{{ title }}</h2>
</v-card-title>
<v-card-subtitle class="body-1">
<time :datetime="date">{{ formatDate(date) }}</time
><span> • </span
><time :datetime="`${readTime(JSON.stringify(body))}m`"
>{{ readTime(JSON.stringify(body)) }} min read</time
>
</v-card-subtitle>
<v-card-text class="body-1 align-self-end">
{{ description }}
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container>
</div>
</template>
<script>
import * as dayjs from 'dayjs';
export default {
async asyncData({ $content }) {
const items = await $content('blog').sortBy('date', 'desc').fetch();
return {
items,
};
},
data() {
return { heading: "Damien Robinson's Blog", total: 0, items: [] };
},
methods: {
formatDate(date) {
return dayjs(date).format('MMM D, YYYY');
},
readTime(content = '', wordsPerMinute = 50) {
const words = content.split(' ').length;
return Math.ceil(words / wordsPerMinute);
},
},
head() {
return {
title: this.heading,
};
},
};
</script>
```

The average reader can read 200 words per minute, for technical material the average reading rate is approx 50 to 75 words a minute. Since this is a tech blog we will go with 50 words per minute, using this number we can create compute the number of minutes it takes to read an article.[^1][] The `BlogHero` component can be found on [Github](https://github.com/shadow81627/daim/blob/master/components/sections/BlogHero.vue), it uses `@aceforth/nuxt-optimized-images` package to create source set and low quality placeholders.

## Blog Post Page

This is used for dynamic rendering of each blog post markdown file. We will fetch the content and format the front-matter of the blog post according to our requirement.

```vue\[pages/blog/\_slug.vue]
<template>
<div>
<BlogHero
:title="item.title"
:summary="item.description"
:src="item.image"
></BlogHero>
<v-container>
<v-row>
<v-col>
<div class="body-1">
<nuxt-content :document="item" />
</div>
</v-col>
</v-row>
<v-row>
<v-col class="d-flex flex-column justify-center">
<span class="font-italic"
>Published
<time :datetime="item.date">{{ formatDate(item.date) }}</time></span
>
</v-col>
<v-col sm="auto" cols="12">
<v-btn
icon
color="#757575"
target="_blank"
rel="noopener"
:href="`https://twitter.com/intent/tweet?text=${encodeURIComponent(
item.title,
)}%0A%0A${encodeURIComponent(
item.description,
)}&url=https://daim.dev${$route.path}`"
>
<v-icon>{{ mdiTwitter }}</v-icon>
</v-btn>
<v-btn
icon
color="#757575"
target="_blank"
rel="noopener"
:href="`https://www.linkedin.com/shareArticle/?mini=true&url=https://daim.dev${$route.path}`"
>
<v-icon>{{ mdiLinkedin }}</v-icon>
</v-btn>
<v-btn
icon
color="#757575"
target="_blank"
rel="noopener"
:href="`https://www.facebook.com/sharer/sharer.php?u=https://daim.dev${$route.path}&display=page`"
>
<v-icon>{{ mdiFacebook }}</v-icon>
</v-btn>
</v-col>
</v-row>
</v-container>
</div>
</template>
<script>
import * as dayjs from 'dayjs';
import { mdiFacebook, mdiTwitter, mdiLinkedin } from '@mdi/js';
import BlogHero from '~/components/sections/BlogHero';
export default {
components: { BlogHero },
async asyncData({ $content, route, error }) {
try {
const item = await $content('blog', route.params.slug).fetch();
return { item, ...item };
} catch {
error({ statusCode: 404 });
}
},
data: () => ({
item: {},
mdiFacebook,
mdiTwitter,
mdiLinkedin,
}),
methods: {
formatDate(date) {
return dayjs(date).format('MMMM D, YYYY');
},
},
head() {
return {
title: this.item.title,
meta: [
{
hid: 'description',
name: 'description',
content: this.item.description,
},
// Open Graph
{ hid: 'og:title', property: 'og:title', content: this.item.title },
{
hid: 'og:description',
property: 'og:description',
content: this.item.description,
},
{ hid: 'og:type', property: 'og:type', content: 'article' },
],
};
},
};
</script>
```

## Writing Blog Posts

Write the blog details in front-matter and everything else in markdown. You can even mix html along with markdown. But just follow the [rules](https://content.nuxtjs.org/writing#html) properly. If are want to have the option of using a rich text editor then adding [Netlify CMS](https://www.netlifycms.org/) is a good place to start.

## Conclusion

Now that you know how to build a static site blog with Nuxt, you're ready to share your awesome content with the internet without worrying about hosting and maintaining a Wordpress server.
Now that you know how to build a static site blog with Nuxt, you're ready to share your awesome content with the internet without worrying about hosting and maintaining a Wordpress server. This website is made with Nuxt JS, blog pages are made using nuxt content and styled using Vuetify. You can find the full source code on [Github](https://github.com/shadow81627/daim).

This blog post is inspired by [Pramod Devireddy](https://domarpdev.github.io/blog/create-medium-style-blog-theme/)

[^1]: https://secure.execuread.com/facts

<!-- ## Sources
Expand Down

1 comment on commit 33c8764

@vercel
Copy link

@vercel vercel bot commented on 33c8764 Sep 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.