👻

Tiptapに入門してみた

2022/10/23に公開

はじめに

この記事では、簡単にWYSIWYGエディタを作成できるTiptapの基本的な使い方をハンズオン形式で解説していきます。

Tiptapとは?

https://tiptap.dev/

  • WYSIWYGエディタを作成できるライブラリ
  • 標準でエディタにスタイリングされていないため、UIを自由に実装できる
  • 共同編集機能も実装できるっぽい
  • Markdown記法はサポートしてない (https://tiptap.dev/guide/output#not-an-option-markdown)
    • GitHubでPRを書くときのようなMarkdownエディタはできない(TiptapはあくまでWYSIWYGエディタを構築するためのライブラリ)
    • Tiptap markdown というパッケージを使えばMarkdownエディタを作成できるかも?しれないが、よくわからない…
  • Markdown shortcuts があるので、キー入力にMarkdown記法を使うことはできる
    • 例として # + 半角スペース を入力すると、H1タグが生成・表示される

作成できるエディタのイメージ

以下のようなエディタを簡単に作成できます。

以前に同じWYSIWYGエディタを作成できるSlateというライブラリを使用したことがあるのですが、Tiptapのほうが簡単に実装できました。公式ドキュメントもわかりやすかったです。

Next.jsでハンズオン

1. Next.jsプロジェクトを作成

今回はTypeScriptを使用して作成します。

❯ npx create-next-app my-tiptap-project --ts
❯ cd my-tiptap-project

2. Tiptapをインストール

❯ yarn add @tiptap/react @tiptap/pm @tiptap/starter-kit

@tiptap/reactはTiptapをReact(Next.js)で使用するためのパッケージです。
@tiptap/starter-kit はエディタを構築する際に使用する主要な機能のパッケージを集めたスターターキットです。

3. Tiptapエディタを表示するコンポーネントを作成

プロジェクトディレクトリ直下に components/Tiptap.tsx を作成し、以下のコードを記述します。

components/Tiptap.tsx
import { EditorContent, useEditor } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";
import styles from "/styles/components/Tiptap.module.css";

export const Tiptap: React.FC = () => {
  const editor = useEditor({
    extensions: [StarterKit],
    content: "<p>Hello World! 🌎️</p>",
    editorProps: {
      attributes: {
        class: styles.editorContent,
      },
    },
  });

  if (!editor) return null;

  return (
    <div className={styles.container}>
      <EditorContent editor={editor} />
    </div>
  );
};

useEditor を 使用して Editor クラスのインスタンスを作成し、実装するエディタの設定を行います。
contentには初期値としてエディタに表示したいHTMLを記述します。
editorProps では <EditorContent /> にクラス名を付与しています。
extensions にはエディタに追加したい機能(パッケージ)を記述します。こちらには、先ほどインストールした Starterkit を追加します。

Starterkitについて

先ほどにも説明した通り、Starterkitは主要な機能のパッケージを集めたもので、これを extentions に追加するだけで下記の機能を使用できます。

Blockquote : <blockquote>タグを使用できる
BulletList : <ul>タグを使用できる
CodeBlock : <pre><code>タグによるコードブロックを使用できる
Document : <body>タグのようなもの
HardBreak : <br>タグで改行できる
Heading : <h1><h6>タグを使用できる
HorizonalRule : <hr>タグを使用できる
ListItem : <li>タグを使用できる
OrderedList : <ol>タグを使用できる
Paragraph : 段落を扱うために必要
Text : 何らかのテキストを扱う場合に必要
Bold : テキストを太字にする
Code : <code>タグを使用できる
Italic : テキストを斜体で表示する
Strike : テキストに打ち消し線を引く
History : エディタ編集の履歴をサポートする
Dropcursor
Gapcursor

4. /pages/index.tsx ファイルを更新

pages/index.tsx
import type { NextPage } from "next";
import { Tiptap } from "../components/Tiptap";
import styles from "../styles/Home.module.css";

const Home: NextPage = () => {
  return (
    <div className={styles.container}>
      <Tiptap />;
    </div>
  );
};

export default Home;

5. CSSでスタイリング

今回はSassを使ってスタイリングしていきます。

❯ yarn add sass --dev

/styles/Home.module.css/styles/Home.module.scssに変更し、下記を記述します。

styles/Home.module.scss
.container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100vw;
  height: 100vh;
}

新たに/styles/components/Tiptap.module.scssを作成します。

styles/components/Tiptap.module.scss
.container {
  width: 400px;
  height: 300px;
  border: 2px solid #19191a;
}

.editorContent {
  padding: 10px 20px;
  background-color: #ffff;
}

.editorContent:focus {
  outline: none;
}

.editorContent > * {
  margin-bottom: 15px;
}

.editorContent > ul,
.editorContent > ol {
  padding-left: 15px;
}

.editorContent > blockquote {
  border-left: 0.25em solid #e4ebee;
  padding: 0 1em;
}

.editorContent > code {
  color: #767b7d;
  background-color: rgba(#767b7d, 0.1);
}

.editorContent > pre {
  background: #19191a;
  color: #ffff;
  padding: 0.75rem 1rem;
  border-radius: 0.5rem;
}

.editorContent > pre > code {
  color: inherit;
  padding: 0;
  background: none;
  font-size: 0.8rem;
}

6. Next.jsを起動

ここまで実装すれば、下記のようなエディタが表示されます。
Markdown shortcuts によるキー入力で様々なノードやマークを作成できます。
例として、- + スペース でリストが作成されます。

7. メニューを追加

続いては、ボタンのクリックによりh1やリスト、打ち消し線などを表示できるように実装していきます。

Tiptapコンポーネントにボタンを追加していきます。

components/Tiptap.tsx
return (
    <>
      <div className={styles.menu}>
        <button
          className={styles.button}
          onClick={() =>
            editor.chain().focus().toggleHeading({ level: 1 }).run()
          }
        >
          h1
        </button>
        <button
          className={styles.button}
          onClick={() => editor.chain().focus().toggleBulletList().run()}
        >
          リスト
        </button>
        <button
          className={styles.button}
          onClick={() => editor.chain().focus().toggleCodeBlock().run()}
        >
          コードブロック
        </button>
        <button
          className={styles.button}
          onClick={() => editor.chain().focus().toggleStrike().run()}
        >
          打ち消し線
        </button>
        <button
          className={styles.button}
          onClick={() => editor.chain().focus().toggleItalic().run()}
        >
          斜体
        </button>
      </div>
      <div className={styles.container}>
        <EditorContent editor={editor} />
      </div>
    </>
  );

ボタンがクリックされたときにeditorから複数のコマンド(メソッド)を実行しています。
h1ボタンがクリックされたときの実行の流れは以下のようになります。
chain() : 複数のコマンドを1度に呼び出すコマンドチェーンを作成する
focus() : フォーカスをエディターに戻す
toggleHeading({ level: 1 }) : <h1>タグに変更する(既に<h1>のときは外す)
run() : 作成したコマンドチェーンを実行する

メニューのスタイリングを追加します。

styles/components/Tiptap.module.scss
.menu {
  display: flex;
  gap: 15px;
}

.button {
  border: 2px solid #19191a;
  border-radius: 6px;
  background-color: #ffff;
}

以上の実装が完了すると、下記のようになります。
ボタンをクリックすると、それぞれノードやマークが追加・適応されると思います。

8. エディタ内の文字数を表示する

次はエディタ内の文字数をリアルタイムで表示するUIを作成していきます。
この機能は Starterkit には入っていないので、こちらを参考にパッケージを追加する必要があります。

❯ yarn add @tiptap/extension-character-count

インストールが完了したらTiptap.tsx内でインポートし、useEditorextensionsに追加します。

components/Tiptap.tsx
const editor = useEditor({
    extensions: [StarterKit, CharacterCount],
    content: "<p>Hello World! 🌎️</p>",
    editorProps: {
      attributes: {
        class: styles.editorContent,
      },
    },
  });

下記のようにタグを追加すれば完成です。

components/Tiptap.tsx
<div className={styles.container}>
  <EditorContent editor={editor} />
</div>
<div>{editor.storage.characterCount.characters()}文字</div>

最後に

この記事ではWYSIWYGエディタを作成できるTiptapの基本的な使い方を解説しました。
WYSIWYGエディタと聞くと実装が難しいように思えますが、Tiptapを使えばサクッと実装できるのでオススメです。
今回作成したコードはGitHubに載せているので、よろしければご覧ください。

Discussion