【Gatsby.js】CMSを使わずにMarkdownファイルを作ってくれるツール

May 29, 2020

CMSを使わないでGatsbyのBLOG記事を書く時に面倒だったヘッダとファイル名を自動でいい感じにしてくれるツールをVue.jsで作成

gatsby_markdown_yaml_header

Gatsbyで記事を書く時のボトルネック

GatsbyのBLOGでは設定のようなものを各MarkdownファイルのヘッダにYAMLと言われる形式で記述する必要がある。

これをフロントマターと呼ぶ(Front-matter)

例えばこの記事では次のように入力している。

---
templateKey: blog-post
title: '【Gatsby.js】CMSを使わずにMarkdownファイルを作ってくれるツール'
date: 2020-05-30
description: 'CMSを使わないでGatsbyのBLOG記事を書く時に面倒だったヘッダとファイル名を自動でいい感じにしてくれるツールをVue.jsで作成'
draft: false
featuredimage: 
author: CrypticToilet
tags:
  - Gatsby.js
  - Vue.js
---

とにかくコレが非常に面倒。

なにこれ?

このYAMLをヘッダにする形式は調べた限りRという言語(統計学をやる人には定番の言語)を使ってドキュメントを書く「R Markdown」の記事しかヒットせず、BLOG界隈でもかなりマニアックな印象を受けた。

ともかくYAMLとは「データを表現するためのフォーマットの一つ」である。

YAMLのメリットは視認性が良いという理由から設定ファイル用のデータ形式に採用されているパターンが多い。

GatsbyMarkdownではこの表記法を使ってタイトルや日付を入れている。

テンプレートをコピペするだけじゃだめなんですか?

良い。

ぶっちゃけ、それで良い。

必要なところを入力していけば良い。

ただ、どうしても日付の手入力、tagのインデントミス、authorは一つしか選択できないのに2つ記入・・・などのミスが目出つ。

エンジニア的感覚だと、こういうヒューマンエラー因子を取り除きながらも1秒でも早く作業を終わらせられる仕組みは歓迎である。

更に面倒なのはファイル名の入力

通常BLOGというのはCMSで書いてファイル名もCMSが自動で出力してくれるものである。

ファイル名を日本語にするかIDのような物にするかは賛否ありどちらが良いという定説はない。

わざわざファイルを保存して、一回閉じて(閉じないとファイル名が変えられない)、タイトルと日付を入れて・・・これもまた面倒であり、ミスの温床である。

作ったもの

というわけで次のようなものを作った。

本体も公開しておくのでサンプルとしてor利用価値がありそうなら利用してもらいたい。

本体はこちら

動画サンプル

日付、タイトル、説明、tag、著者を入力するとYAML部分が入力済みのmdファイルをファイル名をいい感じにした状態でDL出来る。この動画のバージョンには無いが、後ほどファイル名に日付が入るよう変更した。

さすがに本文まで編集する機能はない。今使っているMarkdownエディタのTyporaが高機能なのでここは自前で用意する必要はない。

コードの一部紹介

特別難しいことはしていないので一部面白いところだけ紹介する。

ファイル名に使用できない文字列の排除

const regex = /[~=+;,!$'`\\^&\\@\\{\\}\\[\]\\#\\%\\.\\*<>:\\"/\\|?*]/g;
const file_title = this.title.replace(regex, "_");
console.log(this.title);

タイトルを直接ファイル名にするがそれだとファイル名に使用できない文字が含まれることが有るので正規表現で除去している。

ここではファイル名に使える記号も除去しているが「なんとなく見栄えが悪くなるから」という理由だけである。

ファイル名は直接URLとして動作するためより厳格なルールが必要そうにみえるが、URI文字にエンコードされるため日本語名なども普通に使える。あまり難しく考える必要はない。

変数をファイルに変換してダウンロードするコード

これもなんだかよく使うコード。

せっかくなのでメモ書き程度にここに置いておく。

出力したいテキストはoutputという変数に入っている。

//ファイルでDL
let blob = new Blob([output], { type: "text/plain" });
let link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.download = `${file_title}.md`;
link.click();

ダウンロードしたいデータ本体とファイル名以外は基本コピペで良い。データ形式がテキストではない場合だけ調べて変更。

このプログラムでできないこと

既に述べたがMarkdown本文のエディタ機能はない。

また、私のYAMLデータ専用であるため汎用性がない。

Gatsbyユーザー全体に対応する方法は調べていない(各ユーザの実装によって設定項目が変わる)のでもし需要がありそうだと判断した場合は作り直して公開したい。

あくまで各自で自前で用意したいという需要があれば参考にしてもらいたい。

全コード公開

Vuetifyを使っている以外は単一のコンポーネント。

プロジェクトを新規作成してVuetifyaddしたらHelloWorldにコピペすれば動くはずだ。余計なものもインストールしていない。

<template>
  <v-container>
    <v-row justify="center" align="center">
      <!-- 入力不要
      <v-col cols="6">
        <v-text-field label="Template Key" v-model="template_key" disabled></v-text-field>
      </v-col>
      <v-col cols="2">
        <v-switch v-model="draft_mode" class="ma-2" label="Draft Mode"></v-switch>
      </v-col>
      <v-col cols="10">
        <v-text-field label="featuredimage" v-model="featuredimage"></v-text-field>
      </v-col>-->
      <v-col cols="12" >
        <v-row justify="center" align="end">
          <v-date-picker v-model="publish_date"></v-date-picker>
          <div class="ml-4">
            <v-text-field label="Publish Date" v-model="publish_date" disabled></v-text-field>
            <v-btn small class="mt-4" @click="caliculate_date()">Now</v-btn>
          </div>
        </v-row>
      </v-col>
      <v-col cols="12">
        <v-text-field label="Title" v-model="title"></v-text-field>
      </v-col>
      <v-col cols="12">
        <v-text-field label="Description" v-model="description"></v-text-field>
      </v-col>

      <v-col cols="6">
        <v-text-field label="Tags" v-model="tag"></v-text-field>
      </v-col>
      <v-col cols="1">
        <v-btn @click="addTag(tag)">add</v-btn>
      </v-col>
      <v-col cols="5">
        <v-select :items="authors" v-model="author" label="Author"></v-select>
      </v-col>

      <v-col cols="12">
        <v-chip
          v-for="(tag,index) in tags"
          :key="index"
          class="ma-2"
          close
          color="green"
          outlined
          @click:close="deleteTag(index)"
        >{{tag}}</v-chip>
      </v-col>

      <v-btn color="success" class="ma-4" x-large @click="download()">download</v-btn>
    </v-row>
  </v-container>
</template>

<script>
export default {
  name: "HelloWorld",

  data: () => ({
    template_key: "blog-post",
    title: "",
    publish_date: "",
    description: "",
    featuredimage: "",
    tags: [],
    tag: "",
    draft_mode: false,
    author: "CrypticToilet",
    authors: ["CrypticToilet", "Neve1074"]
  }),
  methods: {
    download() {
      const regex = /[~=+;,!$'`\\^&\\@\\{\\}\\[\]\\#\\%\\.\\*<>:\\"/\\|?*]/g;
      const file_title = this.title.replace(regex, "_");
      console.log(this.title);

      let output = `---
templateKey: ${this.template_key}
title: '${this.title}'
date: ${this.publish_date}
description: '${this.description}'
draft: ${this.draft_mode}
featuredimage: ${this.featuredimage}
author: ${this.author}
tags:\n`;
      for (let tag of this.tags) {
        output += `  - ${tag}\n`;
      }

      output += `---\n`;

      console.log(output);

      //ファイルでDL
      let blob = new Blob([output], { type: "text/plain" });
      let link = document.createElement("a");
      link.href = window.URL.createObjectURL(blob);
      link.download = `${this.publish_date}-${file_title}.md`;
      link.click();
    },
    caliculate_date() {
      const date = new Date();
      this.publish_date = date.toISOString();
    },
    addTag(tag) {
      this.tags.push(tag);
      this.tag = "";
    },
    deleteTag(index) {
      //対象の配列を消去
      this.tags.splice(index, 1);
    }
  }
};
</script>
この記事をシェア:

author icon

仮想トイレ @CrypticToilet
プログラミングや仮想通貨のシステムトレードに関する情報を更新中!どんな情報を流しても詰まらないトイレ。