📚

Next.jsでMarkdown Editorを作りました。

2021/03/17に公開

Next.js(というかReactで)でMarkdown Editorを作ってみたので、今回はその記事です。

完成系は以下の通り。

完成系

ざっくり概要

使用技術

※デザインはzennのエディタを少しだけ参考にさせていただきました。(zennのエディタ大好きなので!!)

全体像

まず、画像を見ていただくとわかると思いますが、Titleの下の部分にmarkdownを記入する部分プレビューを表示する部分があります。

左側のmarkdownを記入する部分は、textareaタグにstyleを当てて作っています。一方、右側のプレビューはdivタグで作成しています。

textareaに入力した内容をどのようにプレビューに表示しているかというと、textareaのvalueをstateに持たせ、それをプレビュー用のコンポーネントに渡しているだけです。 このようにすることでプレビューで入力した内容が表示されます。

あとは、markdownで入力した内容を何かしらの方法でhtmlに変換して、プレビューで表示すればできます。

ここで登場するのがreact-markdownです。react-markdownは、markdownで記述した内容をhtmlに変換してくれるもので、かなり便利です。使い方に関してはREADMEに書かれているので、ここでは割愛します。

また、react-markdownでremark-gfmというプラグインを利用していますが、これはGitHubのmarkdownの書き方に従いますよってやつです。

コードと解説

tailwindを使っているので、少しclassNameが汚いですが、以下がコードになります。
PostForm.tsx

export default function PostForm() {
  const [markdown, setMarkdown] = useState();

  const setData = (e: any) => {
    e.preventDefault();

    setMarkdown(e.target.value);
  };

  return (
    <div className="post-form min-h-screen">
      <div className="h-screen flex flex-col">
        <div className="pl-9 pt-9">
          <a
            href="/"
            className="transition duration-500 flex items-center justify-center rounded-full hover:bg-white hover:shadow-xl"
            style={{ width: "50px", height: "50px" }}
          >
            <BsChevronLeft style={{ fontSize: "30px", fontWeight: "bold" }} />
          </a>
        </div>
        <h1 className="text-center font-bold text-4xl py-10">投稿を作成</h1>
        <div className="editor flex-grow flex-shrink">
          <form className="h-full">
            <input
              type="text"
              id="post-title"
              placeholder="Title"
              className="px-5 block mx-auto w-4/5 rounded-lg border h-14 text-2xl font-bold focus:outline-none mb-8 shadow-lg"
            />
            <div
              className="flex justify-between h-3/5"
              style={{ maxHeight: "300px" }}
            >
              <div className="w-1/2 ml-10">
                <textarea
                  name="md"
                  id="md"
                  placeholder="Markdownで記述"
                  className="markdown-form resize-none w-full h-full border shadow-xl mb-5 rounded-xl focus:outline-none py-4 px-2"
                  value={markdown}
                  onChange={setData}
                ></textarea>
              </div>
              <div className="w-1/2 mr-10">
                <PostPreview markdown={markdown} />
              </div>
            </div>
            <input
              type="submit"
              className="submit-post w-36 h-10 my-8 rounded-md font-bold block mx-auto hover:opacity-70"
            />
          </form>
        </div>
      </div>
    </div>
  );
}

PostPreview.tsx

import React from "react";
import ReactMarkdown from "react-markdown";
import gfm from "remark-gfm";

const PostPreview = (props) => {
  return (
    <div className="h-full w-full mr-10">
      <div className="markdown-preview h-full w-full border shadow-xl mb-5 rounded-xl py-4 px-2 overflow-y-scroll bg-white">
        <ReactMarkdown plugins={[gfm]} unwrapDisallowed={false}>
          {props.markdown}
        </ReactMarkdown>
      </div>
    </div>
  );
};

export default PostPreview;

今回の記事に関係ない部分は省略しました。型もanyです。

これらのコードのポイントで言うと、全体像の部分でも解説しましたが、PostPreviewコンポーネント(プレビューの部分)をformの中に設置して、display flexでtextareaと横並びになるようにしています。

また、PostPreviewに、markdownと言うstateを渡しています。このmarkdownと言うstateは、textareaに変更が加えられるたびに、onChangeで呼び出される関数setDatasetMarkdown(e.target.value)とすることで、stateが更新されるようになっています。

さらに、react-markdownが変換してくれたhtmlにstyleを当てるstylesheetも用意しておきます。

markdown.css
.markdown-preview h1,
.markdown-preview h2,
.markdown-preview h3,
.markdown-preview h4,
.markdown-preview h5,
.markdown-preview h6 {
  font-weight: 600;
}
.markdown-preview h2 {
  font-size: 1.5rem;
}
.markdown-preview h3 {
  font-size: 1.3rem;
}
.markdown-preview h4 {
  font-size: 1.1rem;
}
.markdown-preview h5 {
  font-size: 0.9rem;
}
.markdown-preview h6 {
  font-size: 0.7rem;
}
.markdown-preview p code {
  background-color: #ebebeb;
}
.markdown-preview p a {
  color: #0000ee;
}
.markdown-preview p a:visited {
  color: #551a8b;
}
.markdown-preview ul li {
  list-style: inside;
}
.markdown-preview ul li ul {
  margin-left: 1rem;
}
.markdown-preview ol li {
  list-style: inside;
  list-style-type: decimal;
}
.markdown-preview table {
  border: 1px solid #afadad;
}
.markdown-preview table thead {
  background-color: #ebebeb;
}
.markdown-preview table thead tr {
  border-bottom: 1px solid #afadad;
}
.markdown-preview table thead tr th {
  border-right: 1px solid #afadad;
}
.markdown-preview table thead tr th:last-child {
  border-right: none;
}
.markdown-preview tbody tr {
  border-bottom: 1px solid #afadad;
}
.markdown-preview tbody tr:last-child {
  border-bottom: none;
}
.markdown-preview tbody tr td {
  border-right: 1px solid #afadad;
}
.markdown-preview tbody tr td:last-child {
  border-right: none;
}
.markdown-preview blockquote {
  font-size: 0.95rem;
  margin: 1.4rem 0;
  border-left: 3px solid #b3bfc7;
  padding: 2px 0 2px 0.7em;
  color: #626e77;
}

sassでやればよかった。。。(全部に.markdown-preview書くの見にくいし面倒)

とりあえず、hタグ、テンプレートリテラル、blockquote、tableあたりはstykeを当てておきました。

まとめ

このような感じで、markdown editorを作成しました。markdown editorについて調べてみても、なんかしっくりくる記事に出会えなかったので自分で考えて作ったのですが、案外簡単でしたw

誰かの参考になれば幸いです。

参考

Electron + Next.js + Tailwind CSS で Markdown エディタを作った
Happy Hues

Discussion