💡

【SimpleMde+Tailwind】プレビューつきのいいかんじなマークダウンエディタを作る

2025/02/25に公開

プレビュー機能付き!<Editor />と書くだけ!みたいな優れものを謳ったものもありましたが動いてくれなかったので自分で作ります。

基本的なcssにはtailwindを使っています。

依存関係をインストール

npm i react-simplemde-editor easymde marked marked-highlight highlight.js

実装

左: エディタ
右: プレビュー
となるように構成します。

Editor.tsx
import DOMPurify from "dompurify";
import "easymde/dist/easymde.min.css";
import hljs from "highlight.js";
import "highlight.js/styles/github-dark.min.css";
import { Marked } from "marked";
import { markedHighlight } from "marked-highlight";
import { useState } from "react";
import SimpleMde from "react-simplemde-editor";

export default function Editor() {
  const marked = new Marked(
    markedHighlight({
      langPrefix: "hljs language-",
      highlight(code: string, lang: string) {
        const language = hljs.getLanguage(lang) ? lang : "plaintext";
        return hljs.highlight(code, { language }).value;
      },
    }),
  );
  const [markdownValue, setMarkdownValue] = useState("Initial value");
  const onChange = (value: string) => {
    setMarkdownValue(value);
  };
  return (
    <div className="flex flex-row justify-center">
      <SimpleMde className="w-full flex-1 m-4" value={markdownValue} onChange={onChange} />
      <div className="flex-1 m-4">
        <div
          dangerouslySetInnerHTML={{
            __html: DOMPurify.sanitize(marked.parse(markdownValue) as string),
          }}
          className="w-full"
        ></div>
      </div>
    </div>
  );
}

cssを弄ってダークにする

デフォルトではライトテーマ向けの色になっているのでいじくります。

Editor.tsx
// パスは各自編集
+ import "../styles/easymde-custom.css";
easymde-custom.css
.CodeMirror {
	color: rgb(var(--colors-secondary-dark)) !important;
	border-radius: 8px !important;
	border-color: rgb(var(--border-color-line-dark)) !important;
	background-color: rgb(var(--background-color-bg-white-dark)) !important;
}

.CodeMirror-sided {
	border-radius: 0px !important;
}

.cm-s-easymde .CodeMirror-cursor {
	border-color: rgb(var(--border-color-line-dark)) !important;
}

.editor-toolbar {
	color: rgb(var(--colors-tertiary-dark)) !important;
	border: none !important;
	background-color: rgb(var(--background-color-bg-white-dark)) !important;
}

.separator {
	border-color: rgb(var(--border-color-line-dark)) !important;
}

.editor-toolbar > .active,
.editor-toolbar > button:hover,
.editor-preview pre,
.editor-preview-side,
.cm-s-easymde .cm-comment {
	color: rgb(var(--colors-secondary-dark)) !important;
	border-color: rgb(var(--border-color-line-dark)) !important;
	background-color: transparent !important;
}

.editor-preview {
	background-color: rgb(var(--background-color-bg-white-dark)) !important;
}

.cm-header-1 {
	font-weight: var(--font-weight-bold) !important;
	font-size: var(--text-3xl) !important;
}
.cm-header-2 {
	font-weight: var(--font-weight-bold) !important;
	font-size: var(--text-2xl) !important;
}
.cm-header-3 {
	font-weight: var(--font-weight-bold) !important;
	font-size: var(--text-xl) !important;
}
.cm-header-4 {
	font-weight: var(--font-weight-bold) !important;
	font-size: 1rem !important;
}

スペルエラーがうるさいので消す

easymde-custom.css
+.cm-spell-error {
+	background-color: rgba(0, 0, 0, 0) !important;
+}

ライトモード・ダークモードの両方に対応させたい人向け

参考: RIP21/React-Simplemde-Editor/issues/210

jsでcssを丸ごとexportさせて、appDarkModeの値に応じてstyleを切り替える方式です。
(appDarkModeの値は自分で取る必要あり)

App.tsx
import 'easymde/dist/easymde.min.css'
import easyMdeDark from '@/easymde/dark.js'
import easyMdeLight from '@/easymde/light.js'

const App = () => {
  // ...
  return (
    <>
      {appDarkMode && <style suppressHydrationWarning>{easyMdeDark}</style>}
      {!appDarkMode && <style suppressHydrationWarning>{easyMdeLight}</style>}
      {/* ... */}
    </>
  )
}
dark.js
export default `
.CodeMirror {
  color: rgb(var(--colors-secondary-dark)) !important;
  border-radius: 8px !important;
  border-color: rgb(var(--border-color-line-dark)) !important;
  background-color: rgb(var(--background-color-bg-white-dark)) !important;
}

.CodeMirror-sided {
  border-radius: 0px !important;
}

.cm-s-easymde .CodeMirror-cursor {
  border-color: rgb(var(--border-color-line-dark)) !important;
}

.editor-toolbar {
  color: rgb(var(--colors-tertiary-dark)) !important;
  border: none !important;
  background-color: rgb(var(--background-color-bg-white-dark)) !important;
}

.separator {
  border-color: rgb(var(--border-color-line-dark)) !important;
}

.editor-toolbar > .active,
.editor-toolbar > button:hover,
.editor-preview pre,
.editor-preview-side,
.cm-s-easymde .cm-comment {
  color: rgb(var(--colors-secondary-dark)) !important;
  border-color: rgb(var(--border-color-line-dark)) !important;
  background-color: transparent !important;
}

.editor-preview {
  background-color: rgb(var(--background-color-bg-white-dark)) !important;
}
`

light.jsはいい感じに作ってください(丸投げ)

Discussion