📖

Next.jsでMarkdownエディタを実装してみよう

2024/07/23に公開

はじめに

今回はreact-simplemde-editorreact-markdownというライブラリを使用してMarkdownエディタを作成します。

環境

  • Next.js - 14.2.5
  • react-simplemde-editor - 5.2.0
  • react-markdown - 9.0.1
  • remark-breaks - 4.0.0
  • remark-gfm - 4.0.0
  • github-markdown-css - 5.6.1
  • easymde - 2.18.0

実装

実装に必要なライブラリの説明

  • react-simplemde-editor (GitHub) EasyMDE をラップした React コンポーネントです。

  • react-markdown (GitHub) Markdown を React コンポーネントとしてレンダリングするライブラリです。

  • remark-breaks (GitHub) Markdown 内の改行を<br>タグに変換するプラグインです。

  • remark-gfm (GitHub) GitHub Flavored Markdown の機能をサポートする remark プラグインです。

  • github-markdown-css (GitHub) GitHub の Markdown スタイルを使用できる CSS ファイルです。

  • easymde (GitHub) 今回は css ファイルを使用します。

必要なライブラリをインストールします。

npm install react-markdown react-simplemde-editor easymde github-markdown-css remark-breaks remark-gfm

1. Markdownエディタ実装

import dynamic from "next/dynamic";
const ReactSimpleMdeEditor = dynamic(() => import("react-simplemde-editor"), {
  ssr: false,
});
import "easymde/dist/easymde.min.css";

export const MarkdownEditor = () => {
  const [markdownValue, setMarkdownValue] = useState("");

  const onChange = (value: string) => {
    setMarkdownValue(value);
  };

  return (
    <>
      <ReactSimpleMdeEditor value={markdownValue} onChange={onChange} />
    </>
  );
};

react-simplemde-editor はクライアントでしか動作しないツールです。サーバー上にはwindowオブジェクトがないため、サーバーでレンダリングしようとするとエラーが発生します。そこで、Next.jsのdynamic import{ ssr: false } オプションを使いクライアントでのみコンポーネントを読み込みされるようにします。

これで下のようなエディタが完成しました。

2. オプション追加

次はオプションを追加してみましょう。ReactSimpleMdeEditorコンポーネントにはEasyMDEのオプションをpropsとして渡すことができます。今回は以下のオプションを追加します。

追加するオプション
  • autofocus: true - エディタが読み込まれたら自動的にフォーカスします。
  • spellChecker: false - スペルチェック機能をオフにします。
  • autosave:
    • enabled: true - 自動保存機能を有効にします。
    • uniqueId: 'saved_content' - 保存データを識別するユニークなID。
    • delay: 1000 - 最後の入力から 1000 ミリ秒(1 秒)後に保存します。
+ import { useEffect, useMemo, useState } from 'react';
- import { useState } from 'react';
import dynamic from 'next/dynamic';
const ReactSimpleMdeEditor = dynamic(() => import('react-simplemde-editor'), { ssr: false });
import 'easymde/dist/easymde.min.css';

export const MarkdownEditor = () => {
  const [markdownValue, setMarkdownValue] = useState('');

+ useEffect(() => {
+   const savedContent = localStorage.getItem('smde_saved_content') ?? '';
+   setMarkdownValue(savedContent);
+ }, []);

  const onChange = (value: string) => {
    setMarkdownValue(value);
  };

+ const options = useMemo(() => {
+   return {
+     autofocus: true,
+     spellChecker: false,
+     autosave: {
+       enabled: true,
+       uniqueId: 'saved_content',
+       delay: 1000,
+     },
+   };
+ }, []);

  return (
    <>
      <ReactSimpleMdeEditor
        value={markdownValue}
        onChange={onChange}
+       options={options}
      />
    </>
  );
};

自動保存機能を有効にするとローカルストレージにsmde_saved_contentというキー名でエディタに入力した内容が保存されるので、初回レンダリング時に自動保存した内容をセットできるようにしました。

3. プレビュー機能の追加

最後にプレビュー画面を作成しましょう。

import { useEffect, useMemo, useState } from 'react';
import dynamic from 'next/dynamic';
const ReactSimpleMdeEditor = dynamic(() => import('react-simplemde-editor'), { ssr: false });
import 'easymde/dist/easymde.min.css';
+ import ReactMarkdown from 'react-markdown';
+ import breaks from 'remark-breaks';
+ import remarkGfm from 'remark-gfm';
+ import 'github-markdown-css/github-markdown.css';

export const MarkdownEditor = () => {
  const [markdownValue, setMarkdownValue] = useState('');

  useEffect(() => {
    const savedContent = localStorage.getItem('smde_saved_content') ?? '';
    setMarkdownValue(savedContent);
  }, []);

  const onChange = (value: string) => {
    setMarkdownValue(value);
  };

  const options = useMemo(() => {
    return {
      autofocus: true,
      spellChecker: false,
      autosave: {
        enabled: true,
        uniqueId: 'saved_content',
        delay: 1000,
      },
    };
  }, []);

  return (
    <>
      <ReactSimpleMdeEditor value={markdownValue} onChange={onChange} options={options} />
+     <h1 className="text-4xl font-bold mb-4 text-left pl-4">プレビュー</h1>
+     <div
+       className="markdown-body p-4 border border-gray-300 h-72 overflow-y-auto"
+       style={{ fontFamily: 'inherit', fontSize: 'inherit' }}
+     >
+       <ReactMarkdown remarkPlugins={[remarkGfm, breaks]}>{markdownValue}</ReactMarkdown>
+     </div>
    </>
  );
};

breaksを追加すると段落内の単一の改行をそのまま改行として扱うことができます。

remarkGfmを追加すると以下のような構文を変換できるようになります。

remark-gfmが変換する構文
  1. チェックリスト:
- [x] タスク1
- [ ] タスク2
- [ ] タスク3
  1. 打ち消し線:
~~このテキストは打ち消されています~~
  1. テーブル:
| ヘッダー1 | ヘッダー2 |
| --------- | --------- |
| セル1     | セル2     |
| セル3     | セル4     |

クラスにmarkdown-bodyをつけると、GitHubで表示されるMarkdownと同じ見た目で、Markdownコンテンツを表示できるようになります。

これで下のようなプレビュー付きエディタが完成しました。

さいごに

今回はreact-simplemde-editorreact-markdownを使用したMarkdownエディタの実装方法をご紹介させていただきました。

次回は今回作成したエディタのツールバーのカスタマイズと X (旧: Twitter) のツイート埋め込み機能を追加します。

次の記事

https://zenn.dev/milky/articles/nextjs-markdown

Discussion