1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Tiptapでテキストエディタ実装できます

Last updated at Posted at 2024-02-02

はじめに

@Sicut_study さんの【Reactアプリ100本ノック】09 Memo をクリアするために、Tiptapを使用したので簡単に使い方をまとめます。

成果物

image.png

Tiptapとは

リッチなテキストエディタをつくることができるものです。

多くの拡張機能が用意されているのが特徴です。

標準でエディタにスタイリングされていないため、UIを自由に実装できます。(このことをヘッドレスっていうのかな?)

セットアップ

Tiptapは以下の環境での使用を提供しています。

  • Vanilla JavaScript
  • React
  • Next.js
  • Vue 3
  • Vue 2
  • Nuxt.js
  • Svelte
  • Alpine.js
  • PHP
  • CDN

私は今回Next.jsでセットアップをしていきます。

プロジェクト作成

ターミナル
npx create-next-app my-tiptap-project

作成したプロジェクトに移動

ターミナル
cd my-tiptap-project

インストール

tiptapのインストールのついでに、便利なスターターキットもインストールします。

ターミナル
npm install @tiptap/react @tiptap/pm @tiptap/starter-kit

Tiptap.tsxを作成

/components/Tiptap.tsxを作成して以下を貼り付けましょう。

components/Tiptap.tsx
'use client'

import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'

const Tiptap = () => {
  const editor = useEditor({
    extensions: [
      StarterKit,
    ],
    content: '<p>Hello World! 🌎️</p>',
  })

    if (!editor) {
        return null
    }

  return (
    <EditorContent editor={editor} />
  )
}

export default Tiptap

page.tsxを作成

先ほど作ったTiptap.tsxを表示するためにapp/page.tsxでインポートしましょう。

app/page.tsx
import Tiptap from '../components/Tiptap'

export default function Home() {
    return (
         <Tiptap />
    )
}

確認

正常に動いているかローカルサーバを立ち上げて確認しましょう。

ターミナル
npm run dev

下の画像のようになっていれば成功です。

image.png

ちょっといじる

このままではしょぼいのでちょっといじりましょう。

拡張機能

沢山の拡張機能があるのでいくつかご紹介します。

ToolMenu.tsxを作成

ツールを表示するコンポーネント/components/ToolMenu.tsxを作成します。

/componets/ToolMenu.tsx
import { Editor } from '@tiptap/react'
import {
    MdFormatBold,
    MdFormatStrikethrough,
    MdRedo,
    MdUndo,
} from 'react-icons/md'

const ToolMenu = ({ editor }: { editor: Editor }) => {
    if (!editor) {
        return null
    }

    return (
        <div className="flex flex-wrap gap-2 border-b border-gray-600 p-4 text-2xl">
            <button
                type="button"
                onClick={() => editor.chain().focus().toggleBold().run()}
                className={!editor.isActive('bold') ? 'opacity-20' : ''}
            >
                <MdFormatBold />
            </button>
            <button
                type="button"
                onClick={() => editor.chain().focus().toggleStrike().run()}
                className={!editor.isActive('strike') ? 'opacity-20' : ''}
            >
                <MdFormatStrikethrough />
            </button>
            <button
                onClick={() => editor.chain().focus().undo().run()}
                type="button"
            >
                <MdUndo />
            </button>
            <button
                onClick={() => editor.chain().focus().redo().run()}
                type="button"
            >
                <MdRedo />
            </button>
        </div>
    )
}

export default ToolMenu

buttononCilck属性に色々メソッドを割り当てることで様々な機能を使用できます。

今回は、コードの上から順に、

  • 太字
  • 取り消し線
  • 一つ戻る
  • 一つ進む

を実装しています。

各ボタンのアイコンにはReact Iconsを使用しています。

確認

再度ローカルサーバを立ち上げて正常に実装できているか確認しましょう。

下の画像のようにツールメニューに4つのアイコンが表示されていれば成功です。

image.png

stateで内容を管理する

エディタの内容をstateで管理したいことあると思います。

その方法を書いときます。

page.tsxを編集

app/page.tsx
+ 'use client'

import Tiptap from '@/features/Tiptap/components/Editor'
+ import { useState } from 'react'

export default function Home() {
+    const [sentence, setSentence] = useState<string>('初期値')
    return (
        <>
+            <p>state = {sentence}</p>
+            <Tiptap sentence={sentence} setSentence={setSentence} />
        </>
    )
}

useStateを使うので、先頭にuse clientを付けてクライアントコンポーネントにします。

pタグuseStateの内容を逐次表示します。

TipTapコンポーネントにstate更新用関数を渡します。

Tiptap.tsxを編集

components/Tiptap.tsx
'use client'

import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import ToolMenu from './ToolMenu'

+ type Props = {
+     sentence: string
+     setSentence: (sentence: string) => void
+ }

+ const Tiptap = ({ sentence, setSentence }: Props) => {
    const editor = useEditor({
        extensions: [StarterKit],
+       content: sentence,
        editorProps: {
            attributes: {
                class: 'prose prose-base m-5 focus:outline-none',
            },
        },
    })

+   if (editor) {
+       editor.on('update', () => {
+           setSentence(editor.getText())
+       })
+   }

    if (!editor) {
        return null
    }

    return (
        <div className="w-2/3 mt-10 mx-auto border-gray-500 border-2">
            <ToolMenu editor={editor} />
            <div className="p-3 overflow-y-scroll h-[70vh] overflow-hidden mt-3">
                <EditorContent editor={editor} />
            </div>
        </div>
    )
}

export default Tiptap

contentにはエディタの初期値を格納できるので、useStateで管理するためsentenceを入れています。

Tiptapが提供しているイベントの中に、updateというものがあります。

これを使うことでエディタが更新されたら、何かしらの関数を発火させることができます。

editor.getText()で現在のエディタの内容を文字列で取得し、setSentenceを使って、sentenceを更新しています。

確認

再度ローカルサーバを立ち上げて正常に実装できているか確認しましょう。

下の画像のようにエディタの内容と上のstateが連動していれば成功です。

image.png

おわりに

便利ものツールはどんどん利用しましょう。

参考にさせていただいた記事

この方のコードをかなり参考にさせていただきました。

1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?