next.jsで暗記用赤シートアプリを作ってみた

4 min read

ブログの記事を書く際にマークダウンってすごくお手頃で便利だな〜と思ってたんですが、最近資格試験などの勉強にも使えたらもっと便利なのにな〜と思い自分のサイトで作り、それを一つのWebアプリとして切り出したものを紹介します。
何か新しいことや特殊なことをやっているわけではないのですが、next.jsのチュートリアルを終えてこれから何か作ってみようと思っている人の参考程度になれば良いかな〜と思っています。

完成品はこんな感じです。

https://transpared.vercel.app/

作業の流れとしてはマークダウンエディタで書いたテキストをクエリパラメータで渡してHTMLにパースするという簡単な流れになります。

ディレクトリマップ

URL 内容
/index 穴埋め問題の文章を書くエディタ
/howto 使い方
/transparent /indexで書いた文章の指定した単語を隠した状態で表示する。

index

実装は以下の通りです。
エディタで入力された内容はクエリパラメータで渡します。その処理がhandleRoute()で実装されています。

index.js
import Layout from "../components/layout";
import { useState } from "react";
import { useRouter } from 'next/router';

import MarkDownEditor from "../components/markdownEditor";

export default function Home() {
  const router = useRouter(); 
  const [markdown, setMarkdown] = useState("");

  const handleRoute = () => {
    if (markdown == "") {
      return;
    }
    router.push({
      pathname:"/transparent",   //URL
      query: {md :markdown} //検索クエリ
    });
  }

  return (
    <Layout>
      <main className="my-8 mx-3">
        <MarkDownEditor func={ (e) => setMarkdown(e) }/>
        <button className="bg-red-500 text-gray-100 text-lg font-bold w-full py-4 rounded-md shadow-md" onClick={ handleRoute }>作成</button>
      </main>
    </Layout>
  )
}

マークダウンエディタは

https://www.npmjs.com/package/react-simplemde-editor

というEasyMDEをreact用にラップしたライブラリを使用します。
エディタをWeb上に実装するのは本来難しいですがこれを使うことで実装の自由度は下がりますが簡単に実装できます。
これを使いMarkDownEditorというコンポーネントを作成してindexに渡しています。

言い忘れていましたが`隠す文章`の用にバッククォートで囲んだものを隠すように後でCSSでスタイルをいじります。これは本来のマークダウンではコード表示の意味で使われます。それをCSSで赤シートの際の赤ペンで書いた文字の役割に変えるわけです。
なのでエディタはツールバーだけ少し変更してバッククォートの入力をエディタからもできるようにしています。

components/markdownEditor.js
import SimpleMDE from 'react-simplemde-editor';
import 'easymde/dist/easymde.min.css';

const toolbar = [
    '|',
    'bold',
    'italic',
    'heading',
    'code',
    '|',
    'quote',
    'unordered-list',
    'ordered-list',
    '|',
    'link',
    'image',
    '|',
    'preview',
    'side-by-side',
    'fullscreen',
    '|',
    'guide',
]

const MarkDownEditor = (props) => {

    const handleChange = (e) => {
        props.func(e);
    }

    return(
        <SimpleMDE onChange={(e) => handleChange(e)} options={{toolbar:toolbar, minHeight:"500px"}}/>
    )
}
export default MarkDownEditor;

transparent

ここではindexからクエリパラメータを使い取得したマークダウンの内容をパースしてHTMLに変換します。ちなみにクエリパラメータで渡すようにした理由はDBなどを使わずにユーザーが返還後の内容をブックマークなどで残しておけるようにしたかったからです。

HTMLに変換するのには

https://www.npmjs.com/package/react-markdown

を使います。また、スタイルとしてgit-hubのスタイルを適用するために

https://www.npmjs.com/package/github-markdown-css

https://github.com/remarkjs/remark-gfm

を使用しています。

transparent.js
import { useRouter } from 'next/router';
import Layout from "../components/layout";
import ReactMarkdown from "react-markdown";
import 'github-markdown-css';
import gfm from 'remark-gfm';

export default function Transparent() {
    const router = useRouter();

    return (
        <Layout>
            <main 
                className="markdown-body m-3 question"
            >
                <ReactMarkdown 
                    className="dark:text-white"
                    plugins={[gfm]} 
                    source={ router.query.md }
                />
            </main>
        </Layout>
    )
}

また以下のようにcssでホバーした時だけ隠している部分が表示されるようにします。適用範囲を限定するためquestionクラスがついているものだけに適用するようにしています(今回は必要ありませんが...)。

globals.css
.question code {
  color: rgba(0, 0, 0, 0);
}

.question code::before {
  content: "Q)";
  font-size: small;
  vertical-align: top;
  color: black;
}

.question code:hover {
  color: #DC2626;
}

これだけで完成です。あまり手間はかからないのでこれからnext.jsを使ってみようという方の参考にはなるかなと思います。