nuxt-community / blog-module

Build your blog with @nuxt
132 stars 16 forks source link

Improve documentation #3

Open adam-lynch opened 6 years ago

adam-lynch commented 6 years ago

Could the documentation be improved please? This seems to be a lot more powerful than the README suggests but I'm not sure what the options do exactly or how I can customize the HTML outputted for each post preview, for example.

This question is available on Nuxt.js community (#c3)
adam-lynch commented 6 years ago

how I can customize the HTML outputted for each post preview, for example.

@bskimball maybe you can shed some light on this? (Since you mentioned changing the templates in #2)

znck commented 6 years ago

I'll spend some time on the repository next month. I'll improve documentation then.

bskimball commented 6 years ago

@adam-lynch I ended up not using the blog-module, because I wanted to customize the templates and use vuex to store info about the posts. I did use the blog module as a base for what I was trying to do. I also used this article https://www.vuejsradar.com/static-vue-cms-part-3/ as well as look at the source code for nuxtent. I also use the markdownit module. I have been meaning to upload to github as an example for others.

Here is a basic summary:

nuxt.config.js

/**
 *  use node fs and slugify to generate the routes based off of the files in the top-level blog directory
 *  you can name the directory anything
 **/
...
  generate: {
    routes: function () {
      return require('fs').readdirSync('blog').map(function (file) {
        return '/blog/' + require('slugify')(file.replace(/\.md$/, ''))
      })
    }
  }
...

docs on generate can be found here --> https://nuxtjs.org/api/configuration-generate

Then in my vuex store, I do something similar with node on build so I can have access to the posts in my global store.

store/index.js

/**
 * the key here is to use if (process.server)
 * I'm also using front-matter to grab metadata from the markdown files
 **/
...
    actions: {
      nuxtServerInit () {
        if (process.server) {
          const fs = require('fs')
          const files = fs.readdirSync('blog')
          const posts = files.map((file) => {
            let post = fm(fs.readFileSync(`blog/${file}`, 'utf8'))
            post.filename = file
            post.created = new Date(fs.statSync(`blog/${file}`).ctime)
            post.slug = slugify(file.replace(/\.md$/, ''), {lower: true})
            post.url = `/blog/${post.slug}`
            return post
          })
          this.dispatch('loadPosts', posts)
        }
      },
      loadPosts ({commit}, posts) {
        const sorted = posts.sort((a, b) => {
          if (a === b) {
            return 0
          }
          return (a.created < b.created) ? 1 : -1
        })
        commit('updatePosts', sorted)
      },
      loadPost ({commit}, slug) {
        const post = this.state.posts.find((post) => {
          return post.slug === slug
        })
        commit('updatePost', post)
      }
    }
...

Because of the store and template integration it made more since to not use blog-module

finally, I have a template for the posts

pages/blog/_post.vue

<template>
    <el-row style="padding: 0 15%">
        <el-col>
            <base-page-title>{{post.attributes.title}}</base-page-title>
            <br>
            <small><i class="el-icon-date"></i> {{createdAt}}</small>
            <div v-html="postFile"></div>
        </el-col>
    </el-row>
</template>

<script>
    import {Col, Row} from 'element-ui'
    import {format} from 'date-fns'
    import BasePageTitle from '~/components/BasePageTitle'

    export default {
      components: {
        'el-row': Row,
        'el-col': Col,
        'base-page-title': BasePageTitle
      },
      fetch ({store, params}) {
        store.dispatch('loadPost', params.post)
      },
      head () {
        return {
          title: this.postTitle
        }
      },
      computed: {
        post () {
          return this.$store.getters.getPost
        },
        postFile () {
          if (this.post.filename) {
            return require(`~/blog/${this.post.filename}`)
          } else {
            return require('~/blog/not-found.md')
          }
        },
        createdAt () {
          return format(new Date(this.post.created), 'MMMM Do[,] YYYY')
        },
        postTitle () {
          return `Blog :: ${this.post.attributes.title}`
        }
      }
    }
</script>

I may have missed some parts but you should be able to get it figured out.

adam-lynch commented 6 years ago

@bskimball

I ended up not using the blog-module... I did use the blog module as a base for what I was trying to do.

I was thinking I'd probably do the same actually.

I also use the markdownit module

FYI there's a vue-markdown which uses markdown-it under the hood.


Thanks a lot for that! I'll go for something similar for sure. I might not use Vuex to begin with because I don't think I need it yet. Thanks again.

bskimball commented 6 years ago

@adam-lynch yeah, I'm aware of vue-markdown. I'm using nuxtjs/markdownit which is here --> https://github.com/nuxt-community/modules/tree/master/packages/markdownit

with the following options plus front-matter

  markdownit: {
    preset: 'default',
    linkify: true,
    breaks: true,
    use: [
      'markdown-it-container',
      'markdown-it-attrs',
      'markdown-it-meta'
    ]
  }

The main reason I chose to use Vuex is so I could list recent blog posts in the sidebar on any page.

adam-lynch commented 6 years ago

Nice, thanks

tcurdt commented 5 years ago

@bskimball You don't have the full site on github by any chance? Seems like the blog plugin was abandoned so I am still researching my options.

bskimball commented 5 years ago

@tcurdt I currently do not. I can try to put one up at some point.

I have changed my process a little bit, where I extracted the function that reads the markdown files from the directory called "content"

Right now my in my Vuex store I have

// store/index.js

function sortByPublished(a, b) {
  if (a === b) {
    return 0;
  }
  return a.attributes.published < b.attributes.published ? 1 : -1;
}

export const state = () => ({
  posts: [],
  post: {}
});

export const getters = {
  getPost: state => {
    return state.post;
  },
  allPosts: state => {
    const posts = state.posts.filter(post => post.slug !== "not-found");
    return posts.sort((a, b) => sortByPublished(a, b));
  }
};

export const mutations = {
  updatePosts(state, posts) {
    state.posts = posts;
  },
  updatePost(state, post) {
    state.post = post;
  }
};

export const actions = {
  nuxtServerInit() {
    if (process.server) {
      const markdownFromDirectory = require("../utils/markdownFromDirectory");
      this.dispatch("loadPosts", markdownFromDirectory("blog"));
    }
  },
  loadPosts({ commit }, posts) {
    commit("updatePosts", posts);
  },
  loadPost({ commit, state }, slug) {
    const { posts } = state;
    commit("updatePost", posts.find(post => post.slug === slug));
  }
};

Then the function that reads from the content directory

// utils/markdownFromDirectory.js

const slugify = require("slugify");
const fs = require("fs");
const fm = require("front-matter");

module.exports = function(directory) {
  return fs.readdirSync(`./content/${directory}`).map(file => {
    return {
      ...fm(fs.readFileSync(`./content/${directory}/${file}`, "utf8")),
      filename: file,
      get created() {
        return new Date(
          fs.statSync(`./content/${directory}/${file}`, "utf8").ctime
        );
      },
      get slug() {
        return slugify(file.replace(/\.md$/, ""), { lower: true });
      },
      get href() {
        return `/${directory}/${this.slug}`;
      }
    };
  });
};

Then the Post template

// pages/blog/_post.vue

<template>
  <div class="post mb-6">
    <h1 class="text-blue-darker">{{ post.attributes.title }}</h1>
    <div class="flex -mx-2 mbitems-center text-grey-darker text-sm">
      <div>{{ createdAt }}</div>
    </div>
    <div class="flex -mx-2 mb-4 items-center text-grey-darker text-sm">
      <div>{{ post.attributes.author }}</div>
    </div>
    <div class="mb-2">
      <img
        v-lazy="post.attributes.featuredImage"
        :alt="`${post.title} featured image`"
      />
    </div>
    <div v-html="postFile"></div>
  </div>
</template>

<script>
export default {
  layout: "post",
  fetch({ store, params }) {
    store.dispatch("loadPost", params.post);
  },
  head() {
    return {
      title: this.postTitle
    };
  },
  computed: {
    post() {
      return this.$store.getters.getPost;
    },
    postFile() {
      if (this.post.filename) {
        return require(`~/content/blog/${this.post.filename}`);
      } else {
        return require("~/content/blog/not-found.md");
      }
    },
    createdAt() {
      return format(new Date(this.post.created), "MMMM Do[,] YYYY");
    },
    postTitle() {
      return `Blog :: ${this.post.attributes.title}`;
    }
  }
};
</script>

If I find time I will upload a nuxt blog example to github

tcurdt commented 5 years ago

@bskimball So IIUC you are using dynamic routes to create the posts and feed the store manually on startup.

I also want co-located post assets. So my content structure is:

content/
  posts/
    slug1/
      index.md
      someimage.jpg

I don't think that's possible to do with your approach, is it?

bskimball commented 5 years ago

With your structure, you would have to adjust the markdownFromDirectory function. My setup in this scenario is:

content/
    posts/
        my-first-post.md
        my-second-post.md
static/
    images/
        someImage.jpg

In your markdown file you would reference your image as /images/someImage.jpg. In your store in the nuxtServerInit would look like this

  nuxtServerInit() {
    if (process.server) {
      const markdownFromDirectory = require("../utils/markdownFromDirectory");
      this.dispatch("loadPosts", markdownFromDirectory("posts"));
    }
  },

The markdownFromDirectory function assumes the directory is directly under the content folder, and that the file name matches the slug. Also, with images in nuxt, I always put them in the static folder so they can be referenced relative to the url

tcurdt commented 5 years ago

Yes, adjusting the markdownFromDirectory is no problem - but having co-located images is the tricky part. I don't want to shove them all into the static folder but keep them with the posts.

bskimball commented 5 years ago

Only way I would know to do that inside nuxt, is hook into the builder and move them to a directory on render https://nuxtjs.org/api/internals-renderer or maybe some kind of custom built middleware. I just chose the path of least resistance and put them in the static directory

Another option would be to build your own api (using express, feathers, adonis etc.) that reads the markdown files and presents them as json, along with a path to the image and serve the image from your api. Of course there are also a ton of other options.