【Next.js】MarkdownエディタにD&Dしたらプレビューに表示させたい
こんにちは、Zenn初投稿な者です。
react-md-editor
でD&Dしたら画像をプレビューに表示するのに結構ハマったので、備忘録的にまとめます。
完成形
react-md-editor is 何?
react-md-editor
は、多機能かつ高い拡張性を持つReact用のMarkdownエディタを提供します。
Next.jsもサポートされているため、今回はその設定方法も見ていきましょう。
yarn install
Next.jsのプロジェクトが立ち上がっていることを前提に、追加で必要なパッケージをインストールします。
yarn install @uiw/react-markdown-preview @uiw/react-md-editor next-remove-imports
next.config.jsのカスタマイズ
お次に、react-md-editor
をNext.jsで使用できるように、next.config.js
を編集します。
const removeImports = require("next-remove-imports");
module.exports = removeImports({
reactStrictMode: true
});
ここでは、node_modules
にあるすべてのパッケージから、すべての .less
/.css
/.scss
/.sass
/.styl
のimportを削除しています。
確かこれをしないと「node_modulesからのCSSのimportがあるんですけど!?」って感じでnextに怒られた気がします(ガバ
さて、実はもう一つ設定しなければいけないことがあるんですが、それは今は一旦置いておきます。
Markdownエディタを作る
さて、では実際にreact-md-editor
を使っていきましょう。
と、その前に今回使うディレクトリ構成を先に載せておきます。
root
|
├─ components
| |
| └─ MdEditor
| | index.tsx
| |
| └─ utils
| fileUploader.tsx
| insertToTextArea.tsx
| onImagePasted.tsx
|
└─ pages
index.tsx
ではまず、./components/MdEditor/index.tsx
を作りましょう。
import MDEditor from "@uiw/react-md-editor";
import { useState } from "react";
import "@uiw/react-md-editor/markdown-editor.css";
import "@uiw/react-markdown-preview/markdown.css";
const MdEditor = () => {
const [markdown, setMarkdown] = useState<string | undefined>();
return (
<div data-color-mode="light">
<MDEditor
value={markdown}
onChange={(value) => {
setMarkdown(value);
}}
height={440}
textareaProps={{
placeholder: "Fill in your markdown for the coolest of the cool."
}}
hideToolbar
/>
</div>
);
};
export default MdEditor;
そうしたら、./pages/index.tsx
を次のように編集します。
import dynamic from "next/dynamic";
const MdEditor = dynamic(import("../components/MdEditor"), {
ssr: false,
loading: () => <div>now loading</div>
});
const IndexPage = () => <MdEditor />;
export default IndexPage;
これでyarn dev
をしてみると、Markdownエディタがルートに表示されるはずです。
ここでのポイントはdynamic()
です。
ここで<MdEditor>
をSSRによってレンダリングしないように設定することで、react-md-editor/MDEditor
をNext.jsのSSR下でも使用できるようにしています。
D&D時にファイルをプレビューに表示する
さて、それではここにD&D時の処理を追加していきましょう。
react-md-editor
はeasyMDE
のようにimageUploadFunction()
のようなものはないので、onPaste
とonDrop
を併用して実装していきます。
onImagePasted.tsx
、insertToTextArea.tsx
、fileUploader.tsx
を以下のように編集してください。
import type { SetStateAction } from "react";
import fileUpload from "./fileUploader";
import insertToTextArea from "./insertToTextArea";
const onImagePasted = async (
dataTransfer: DataTransfer,
setMarkdown: (value: SetStateAction<string | undefined>) => void
) => {
const files: File[] = [];
for (let index = 0; index < dataTransfer.items.length; index += 1) {
const file = dataTransfer.files.item(index);
if (file) {
files.push(file);
}
}
await Promise.all(
files.map(async (file) => {
const url = await fileUpload(file);
const insertedMarkdown = insertToTextArea(``);
if (!insertedMarkdown) {
return;
}
setMarkdown(insertedMarkdown);
})
);
};
export default onImagePasted;
const insertToTextArea = (intsertString: string) => {
const textarea = document.querySelector("textarea");
if (!textarea) {
return null;
}
let sentence = textarea.value;
const len = sentence.length;
const pos = textarea.selectionStart;
const end = textarea.selectionEnd;
const front = sentence.slice(0, pos);
const back = sentence.slice(pos, len);
sentence = front + intsertString + back;
textarea.value = sentence;
textarea.selectionEnd = end + intsertString.length;
return sentence;
};
export default insertToTextArea;
const fileUploader = (file: File) => {
const imageURL = URL.createObjectURL(file);
return imageURL;
};
export default fileUploader;
各関数の役割は、以下の通りです。
- onImagePasted()
-
File
の抽出とリスト化、アップロードの実行
-
- insertToTextArea()
- 元あったカーソルの位置に

を挿入する
- 元あったカーソルの位置に
- fileUploader()
- ファイルのアップロード
今回はお試しということで、fileUploader()
はオンラインストレージサービスと接続したりはしていません。
実際に本番で使用するときは、ここの関数を適宜変更してください。
そうしたら、./components/MdEditor/index.tsx
を再度以下のように編集してください。
import MDEditor from "@uiw/react-md-editor";
import { useState } from "react";
+ import onImagePasted from "./utils/onImagePasted";
import "@uiw/react-md-editor/markdown-editor.css";
import "@uiw/react-markdown-preview/markdown.css";
const MdEditor = () => {
const [markdown, setMarkdown] = useState<string | undefined>();
return (
<div data-color-mode="light">
<MDEditor
value={markdown}
onChange={(value) => {
setMarkdown(value);
}}
+ onPaste={async (event) => {
+ await onImagePasted(event.clipboardData, setMarkdown);
+ }}
+ onDrop={async (event) => {
+ await onImagePasted(event.dataTransfer, setMarkdown);
+ }}
height={440}
textareaProps={{
placeholder: "Fill in your markdown for the coolest of the cool."
}}
hideToolbar
/>
</div>
);
};
export default MdEditor;
これで実装は完了です。
再びブラウザを確認すると、D&DとCopy&Pasteで画像が表示できるMarkdownエディタが表示されているはずです。
おわりに
今回はreact-md-editor
をNext.jsのSSR下で使えるようにし、D&D・Copy&Pasteで画像を表示させる処理を実装しました。
個人的に、SSR関連で何か怒られたときはdynamic()
でssr: false
をしておけば、大体直るもんだと信じています。
でも一度に全てのコンポーネントがマウントされないのは、それはそれでUXが良いわけではないので、安易にdynamic()
に頼らない生き方をしたいと思った今日この頃でした。
またね。
Discussion
素敵な記事をありがとうございます。
私もこの記事を見ながらマークダウンエディタを作っているところです。
next.config.jsのカスタマイズ
にあるこの文がとても気になりますので、教えていただけないでしょうか。記事の中に書かれていましたらどの部分を指すのか教えていただけると幸いです。