搭建 Gatsby 博客五:实现草稿模式和上下篇

quote

"The first draft of anything is shit."

Ernest Hemingway

本文通过实现草稿模式和上下篇来进一步了解 Gatsby 的 Node APIs。

草稿模式

草稿模式即可以将文章保存为草稿而不被渲染出来。方式是在 front matters 中设置一个 draft 布尔域,以此域作为渲染参考。

这里有一个地方需要注意,前面文章提过,Markdown 插件需要所有文章中都有 draft 域且都是布尔类型才会生成相应的 GraphQL 查询。如果是新的博客这个问题不大,如果是迁移过来的,有两个解决方式,第一个是手动写个脚本给文章都补上域,另一个是利用 Gatsby 的 Node APIs 在 fields 上生成特定域,鲁棒性更好些。

自动生成域

观察 Remark 插件生成的 GraphQL 类型,我们可以发现,front matters 都被放在 frontmatter 域中,而与之同级的有一个前面文章提到过的 fields 域,用来放自定义生成的数据。

Gatsby 在生成 GraphQL 节点时提供了钩子 onCreateNode,我们利用这个钩子往 fields 中放自定义的数据。

编辑 /gatsby-node.js,如果是用了 starter 的话这里很可能已经有其它的代码,已有的不需要动,添加我们需要的即可。

exports.onCreateNode = ({ node, actions, getNode }) => {
  const { createNodeField } = actions

  if (node.internal.type === `MarkdownRemark`) {
    createNodeField({
      node,
      name: 'draft',
      value: Boolean(node.frontmatter.draft)
    })
  }
}

如此 fields 中就保证了会有 draft 这个域了。

过滤草稿

有了标记之后,在生成页面的地方我们就需要过滤草稿。

首先是普通的文章页面生成,这个是在 createPages 钩子中,如果你的博客只有文章用到 Markdown 的话,可以在 GraphQL 查询中直接过滤,否则我们用前面文章的方法,先取所有 Markdown 文件再根据渲染的模板来分别处理各种类型的文章。

注意我把模板域的名字换成了自己更习惯的 layout,原来的 starter 中应该叫 templateKey。修改其实也很简单,搜索所有文件替换关键字即可。

options
  .filter(
    (_, i) =>
      !(
        edges[i].node.frontmatter.layout === 'blog-post' &&
        edges[i].node.fields.draft
      )
  )
  .forEach(option => createPage(option))

我在主页中也列举了最近的几篇文章,这里也需要过滤草稿,可以直接在 GraphQL 中过滤。

query IndexQuery {
  latestPosts: allMarkdownRemark(
    sort: { order: DESC, fields: [frontmatter___date] }
    filter: {
      fields: { draft: { ne: true } }
      frontmatter: { layout: { eq: "blog-post" } }
    }
    limit: 5
  ) {
    edges {
      node {
        excerpt(pruneLength: 200)
        id
        fields {
          slug
        }
        frontmatter {
          title
          description
          layout
          date(formatString: "MMMM DD, YYYY")
        }
      }
    }
  }
}

其它地方同理。

上下篇

在文章页面中我们通常会加入上下篇来引导继续浏览。这里我们同样在 createPages 钩子中处理,但这回我们添加到 context 域中,这个域里的数据会作为 props 传到模板组件中。

createPage 生成文章页面前添加处理代码计算上下篇:

options
  .filter(
    (_, i) =>
      edges[i].node.frontmatter.layout === 'blog-post' &&
      !edges[i].node.fields.draft
  )
  .forEach((option, i, blogPostOptions) => {
    option.context.prev =
      i === 0
        ? null
        : {
          title: blogPostOptions[i - 1].title,
          path: blogPostOptions[i - 1].path
        }
    option.context.next =
      i === blogPostOptions.length - 1
        ? null
        : {
          title: blogPostOptions[i + 1].title,
          path: blogPostOptions[i + 1].path
        }
  })

然后在文章的 /src/templates/blog-post.js 组件里,接收 pageContext props,就可以使用上面传入的数据了。这是我的例子。

通过实现这几个功能我们了解了 Gatsby 页面生成的方式以及其 Node APIs 的基本使用。Gatsby 的功能远不止这些,官方文档写得非常详细,需要实现其它功能建议先去看看有无现有的例子。本系列到这里暂告一段落,谢谢你的阅读,希望能对你搭建 Gatsby 博客有所帮助。