Facebook製エディタLexicalを試してみたよ!(2) 機能追加してみた!
Facebook(Meta)製のエディタライブラリのLexicalについて、もうちょっと調べる時間がもらえたので第2弾です!
前回の記事で書いた通り、やっぱり公式のプレイグラウンドのようなツールバーのUIは無いっぽいです。
ライブラリにはエディタの機能だけがあって、必要なUIは各自で作ってもらうという方針なのかなと思いました。
sandboxからツールバーのコードを拝借してきて設置したところ、無事にプレイグラウンドと同じものが動きました!
Nodeについて
Lexicalエディタの中ではすべての要素がNodeという単位で管理されているようです。
プラグインを使ってh1とかリンクとか色々な要素を使うためには、まず使いたい種類のノードを初期設定で指定する必要があるみたい。
ノードの指定を外すと、処理は通るけど機能はしませんでした。
import { HeadingNode, QuoteNode } from "@lexical/rich-text"
import { AutoLinkNode, LinkNode } from "@lexical/link"
const Editor: NextPage = () => {
// エディタ設定
const initialConfig:any = {
theme: ExampleTheme,
onError,
// 使いたいノードを指定する
nodes: [
HeadingNode,
QuoteNode,
AutoLinkNode,
LinkNode
]
};
return (
<>
<LexicalComposer initialConfig={initialConfig}>
リストやテーブルやコードブロックなど、それぞれ対応したノードが用意されていました。
独自で機能を作りたい時は、カスタムノードを自作する必要があるみたいです。
そこで、公式ドキュメントにあったテキストカラーを変更するカスタムノードを作って、機能を追加してみようと思います!!
ノードの種類
Lexicalのノードは基本となる5種類(Root, LineBreak, Element, Text, Decorator)があって、そのうちカスタムノードに使えるのは ElementNode
TextNode
DecoratorNode
の3種類だそうです。
ElementNode
ElementNode
は h1 や p などのブロックレベル要素とリンクなどのインライン要素に使われるそうです。
ブロックレベルとインラインて全部やん!と思ったんですが、strong や u などの装飾については TextNode に該当するらしいです。どうやって使い分けるんでしょう? 属性値を使うかどうかみたいな?
TextNode
TextNode
はテキストを装飾するためのノードのようです。
format
mode
style
のプロパティを持っています。
format は装飾の種類で、bold, italic, underline, strikethrough, code, subscript, superscript が指定できます。
mode は、たぶん削除に関するルールだと思います。
token
は変更不可で、削除するときは一度に消えます。
inert
もtokenと同じく変更不可ですが、カーソルを中に入れて部分選択することもできなくなるようです。
segment
は単語区切りで削除されるようです。
ドキュメントには書いていませんでしたが、たぶん1文字ずつ削除される 未指定
っていう状態がデフォルトであるんでしょうね。nullを指定すればいいのかな?
style はCSSが記述できるようです。
DecoratorNode
DecoratorNode
はリファレンスを読んでもよくわかりませんでした。
また詳しいことがわかったら報告します。
カスタムノードの作り方
基本ルール
ノードクラスのプロパティは、__
から始まるというルールがあるそうです。
export class ColoredNode extends TextNode {
__color: string;
ノードには getType
と clone
というクラスメソッドが必須とのことです。
getTypeは、ノードの名称を返します。マニュアルには特に書いていませんでしたが、たぶんエディタの中で一意でないとダメなのかなと思いました。
static getType(): string {
return 'colored';
}
cloneは、ノードを複製するための処理を書きます。エディタの中でコピー&ペーストする時に働くようです。
static clone(node: ColoredNode): ColoredNode {
return new ColoredNode(node.__text, node.__color, node.__key);
}
コンストラクタについて
ノードのコンストラクタは、constructor(text: string, key?: NodeKey)
というのが基本形のようです。
constructor(text: string, key?: NodeKey): void {
super(text, key);
}
text
はTextNodeの時はテキストそのものを指すみたいです。ElementNodeの時はこの引数が何に使われているのか分かりませんでした。HeadingNodeでは初期化時に h1
などの文字列が渡されてました。
key
はnullableで、HeadingNodeの初期化時は未指定でした。cloneの時しか使わないかもですね。カスタムノードでコンストラクタの引数が増えた時は、key
を最後に置いておくもののようです。
createDom
と updateDom
createDom
と updateDom
は、そのノードに対応したDOM(HTMLエレメント)を作るためのメソッドです。
createDom はDOMの初期化メソッドで、HTMLElement
を返します。
createDOM(): HTMLElement {
const dom = document.createElement('p');
return dom;
}
updateDom はDOMを更新した時に createDom で作り直す必要があるかどうかを true/false
で返すみたいです。
現時点ではどういう場面でtrueを返すべきなのか分かりませんでした・・・
updateDOM(prevNode: ColoredNode, dom: HTMLElement): boolean {
return false;
}
テキストカラーを変更するカスタムノードを作る
今回はTextNodeを拡張して、styleプロパティを使ってCSSでテキストカラーを指定できるカスタムノードを作りたいと思います!
ノードを作る
ここは公式サイトの通り。
export class ColoredNode extends TextNode {
__color: string;
constructor(text: string, color: string, key?: NodeKey): void {
super(text, key);
this.__color = color;
}
static getType(): string {
return 'colored';
}
static clone(node: ColoredNode): ColoredNode {
return new ColoredNode(node.__text, node.__color, node.__key);
}
createDOM(config: EditorConfig): HTMLElement {
const element = super.createDOM(config);
element.style.color = this.__color;
return element;
}
updateDOM(
prevNode: ColoredNode,
dom: HTMLElement,
config: EditorConfig,
): boolean {
const isUpdated = super.updateDOM(prevNode, dom, config);
if (prevNode.__color !== this.__color) {
dom.style.color = this.__color;
}
return isUpdated;
}
}
ツールバーを拡張する
まずはツールバーにカラー変更ボタンを作ります。
こんな感じ。
<Divider />
<button
onClick={()=>onColorSelect("black")}
className="toolbar-item spaced">
</button>
<button
onClick={()=>onColorSelect("red")}
className="toolbar-item spaced">
</button>
<button
onClick={()=>onColorSelect("green")}
className="toolbar-item spaced">
</button>
<button
onClick={()=>onColorSelect("blue")}
className="toolbar-item spaced">
</button>
次に、選択した部分をさっき作ったカスタムノード ColoredNode
に置き換える処理を書きます。
const onColorSelect = (color:string) => {
editor.update(() => {
const selection:any = $getSelection();
if ($isRangeSelection(selection)) {
const text = selection.getTextContent()
const colored = new ColoredNode(text, color)
selection.removeText()
selection.insertNodes([colored])
}
})
}
これで準備OKです!
できあがり!
実際にこの機能を使ってみると、ちゃんとテキストの色が変わりました! やった!
だけど、このままだと実用はできません。
この方法だと、選択した部分にboldとかの装飾があったら、装飾が消えてしまうんです。
あと、色を変えたテキストにboldなどの装飾をかけても、やっぱり色が戻ってしまいました。
うまくいきませんね。
もっと上手な方法があるはずなんですが・・・
これでも、かなりがんばったんですけどね!
今回はこれでせいいっぱい。
やっぱり公式サイトのリファレンスが充実するまでは、まだまだ手を出すのは早いのかなと思いました!
手探りしんどい!!!
Author And Source
この問題について(Facebook製エディタLexicalを試してみたよ!(2) 機能追加してみた!), 我々は、より多くの情報をここで見つけました https://zenn.dev/haruru/articles/12206977dc0c48著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol