BlockNoteで文字数制限しようとしたら思ったより深くまで潜る必要があったので、せっかくなのでまとめてみる
BlockNoteの紹介
そもそも BlockNote とは、ブロックベースのリッチテキストエディタのnpmライブラリです。
国内ではあまり記事とかも書かれておらず、これから多く使われていくと嬉しいなと思っています。BlockNoteとても素敵なライブラリなので是非皆さん使ってみてください。
initial release が Mar 25, 2023 でした。
技術選定の話とかその他カスタマイズの話などの話は今回はスコープ外です。
手っ取り早く解決策だけ知りたい方は解決編からどうぞ
文字数を制限したい
BlockNoteをアプリケーションで使用するうえで、ユーザが入力する文字数の上限を設定する必要がありました。
入力された文章を無限に保存するわけにもいきませんし、少ない文章で使いたい事もあるかもしれません。入力文字数の上限を設定するのはとても大切です。
シンプルに考えました。
if (textLength >= textLimit) {
// 文字数の上限を超えているので、入力をキャンセル何かしらを行う
return;
}
こんな感じだろうと。
制限するには数えたい
文字数を制限するということは、文字数を数えるという事です。いや当たり前ですけど。
上のコードで言う所の text_length
を数えなければいけません。
数えるのはループすれば出来そう
BlockNoteはブロックベースのリッチテキストエディタなので、平文で文字列が格納されているわけではありません。
たとえばこんなテキストがあるとします。
この程度の文章でも内部的にはこんな感じのJSONが生成されます
長いので折りたたんだJSON
[
{
"id": "a2c57d5f-b949-4643-82bb-dfa277c7efb0",
"type": "paragraph",
"props": {
"textColor": "default",
"backgroundColor": "default",
"textAlignment": "left"
},
"content": [
{
"type": "text",
"text": "デモへようこそ!",
"styles": {}
}
],
"children": []
},
{
"id": "a866c2ea-628b-4d48-bcc7-7a607d44b9b1",
"type": "paragraph",
"props": {
"textColor": "default",
"backgroundColor": "default",
"textAlignment": "left"
},
"content": [],
"children": []
},
{
"id": "95cc8e80-68b4-4090-a068-d56a970a3e56",
"type": "paragraph",
"props": {
"textColor": "default",
"backgroundColor": "default",
"textAlignment": "left"
},
"content": [
{
"type": "text",
"text": "ブロック一覧:",
"styles": {
"bold": true
}
}
],
"children": []
},
{
"id": "e0d9b8c5-310c-45ee-bac3-7ba253761fb8",
"type": "paragraph",
"props": {
"textColor": "default",
"backgroundColor": "default",
"textAlignment": "left"
},
"content": [
{
"type": "text",
"text": "標準テキスト",
"styles": {}
}
],
"children": []
},
{
"id": "894c634c-afe2-40ce-a93b-0ba44dfbe91c",
"type": "heading",
"props": {
"textColor": "default",
"backgroundColor": "default",
"textAlignment": "left",
"level": 1
},
"content": [
{
"type": "text",
"text": "見出し1",
"styles": {}
}
],
"children": []
},
{
"id": "835b3f9e-290b-4c9f-9272-0248ba2a9079",
"type": "heading",
"props": {
"textColor": "default",
"backgroundColor": "default",
"textAlignment": "left",
"level": 2
},
"content": [
{
"type": "text",
"text": "見出し2",
"styles": {}
}
],
"children": []
},
]
とはいえよく見ると中身は jsonData[index]["content"][index]["text"]
にありそうです。これを数えれば合計の文字数は取得出来そう。
const fullText = jsonData
.flatMap((block) => block.content.map((c) => c.text))
.join("");
return fullText.length;
実際には使っていないのですが、こんな感じの処理で取得出来そうです。
文字数を超えたら入力を制限したい
文字数を取得するのは出来そうだったので、文字数が制限を超えたら入力をさせないような処理を入れようと思います。
ここはあまり調べずに思いつきで「 onChange
で return false
すればいけないかなー」とか思っていました。試してみました。
onChangeではできなかった
まあ出来ませんでした。それはそうですよねって感じがします。この場合の onChange
は変更された後のコールバックで、副作用というか事後の処理です。
こんな感じでやりましたが、特に何も起きません
const handleChange = () => {
return false;
};
return <BlockNoteView editor={editor} onChange={handleChange} />;
念の為 onChange
のコードを見てみましたが、 () => void
なので返り値見てないですね。はい。
変更の処理に割り込んで介入できるのはどこだろう
onChangeのコードを調べて「ここじゃない」ことに気づき、ついコードの海に潜り始めてしまいました
これは海に潜っている時の写真
早速ですが、onChangeから、実際にテキストが編集された時に介入できるポイント探してみます
ん? this._tiptapEditor.on("update", cb);
? onChange
に渡されたコールバック関数はthis._tiptapEditor
の update
イベントに渡されています。
Tiptap?
_tiptapEditor
ってもしかしてヘッドレスなリッチテキストエディタを開発系Saasとして提供しているTiptapの事か・・・?
更に潜ってみよう
ふむ、 BlockNoteTipTapEditor
か。これはいよいよ
おっと extends TiptapEditor
そうですよね、 @tiptap/core
ですよね。
めっちゃ dependencies
しとる
ということでBlockNoteはTiptapが公開している @tiptap
ライブラリにエディタ部分は依存していました。
Tiptapでできるんじゃないか?
Tiptapが公開しているコードに依存しているようです。ヘッドレスリッチテキストエディタを提供しており、UIを自在にハンドリングできるツールです。こんな豪華なツールをMITライセンスで公開されているのは助かりますね。
実際に文字列を受け取って変数に格納したり、フォーマットを定義していたりするのはtiptap側のようです。BlocNoteはモダンなブロックベースのエディタUIを定義している感じです。
ベースの機能っぽい文字数を数えたり、入力を制限する機能は tiptap
でなんとかする必要がありそうです。
では tiptap
に潜ってみましょう
これも海に潜っている時の写真
まずは公式のリファレンスから読んでみましたが使い方は理解できますが、文字数を数えている機能を見つけられず。(後に見つけられます)
コードを探してみます
ありそう
我々には検索という文明の利器があります。やってみましょう
limit length
でリポジトリを検索してみます
docs/api/extensions/character-count.md
というファイルがヒットしました。 character-count
なのできっと文字数が数えられるはずです。
数が数えられる上に上限の文字数も設定出来そうです。
標準の機能ではなく拡張機能として提供されていました。想定ユースケースが長文を基本としているのだと思います。
関連するドキュメントなどを集めて目を通しておきます
- CharacterCount – Tiptap
-
Dev toolkit for building collaborative editors – Tiptap
- CharacterCountを使ってTwitterの文字数みたいな割合をグラフィカルに表現してるサンプル
tiptapのextentionはどうblocknoteに使うのか
ここで、BlockNoteに戻ってきます。私はそもそもBlockNoteで文字数を数えて制限したいだけだったはずです。
tiptapのExtentionにCharacterCountというものが提供されている事がわかりましたが、それはどのように使えるのでしょう。BlockNoteで。
はじめの頃に _tiptapEditor
という変数がありました。その辺りを見てみましょう。
tiptapOptions
という値が渡されてインスタンス化されています。
extentions というのがいました。 newOptions._tiptapOptions?.extensions
という変数に入っているか、 extensions
に入っていれば動くようです。
extensions はBlockNoteで用意されているものなので追加は出来なさそうです。多分。( _tiptapOptions
で出来そうだったのであまり見てない)
長くなりましたが、解決はこちら
パッケージをインストールします
npm i @tiptap/extension-character-count
useCreateBlockNote
に渡せば文字数を制限できます。
import CharacterCount from '@tiptap/extension-character-count';
// ...
const editor = useCreateBlockNote({
_tiptapOptions: {
extensions: [
CharacterCount.configure({
limit,
}),
],
},
});
実際に動いているサンプルはこちらから
結論
ダイビングは非日常で楽しいので皆さんオススメです
ウミガメかわいい
Discussion