Tiptapコードリーディング:Paragraph
前回
TiptapコードリーディングのParagprph編です。
少し雑に続けることが目標。
Paragraph
(version3)
少し複雑になってきました。Paragraphは馴染みある段落です。p要素に位置してます。
基本
まず基本的なところから、group: block
でcontent: inline*
です。つまり、Documentの直下に入り、Textノードなどのインライン要素を0個以上入れることができます。
次にparseとrenderを確認します
parseHTML() {
return [{ tag: 'p' }]
},
renderHTML({ HTMLAttributes }) {
return ['p', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]
},
parseDOM
parseHTMLはHTMLからノードに変換するメソッドです。
type ParseRule = TagParseRule | StyleParseRule
を戻り値に指定します。
TagParseRuleは{tag: 'p[data-color]'}
のようなCSS Selectorをtagプロパティで指定することで変換します。
ここで使えるgetAttrs
はDOMからアトリビュートを抜き出すためのものです。
今回はpタグをParagraphノードに変換しています。
renderHTML
renderHTMLは逆で、ノードからHTMLに変換します。
DOMOutputSpec
を戻り値に指定します。
公式の説明では以下のように述べられてます。
DOM構造の説明。文字列(テキストノードとして解釈される)、DOMノード(自身として解釈される)、{dom, contentDOM} オブジェクト、または配列のいずれかです。
配列はDOM要素を記述します。配列の最初の値は文字列で、DOM要素の名前を表します。必要に応じて名前空間URLとスペースでプレフィックスが付く場合があります。2番目の要素が単純なオブジェクトの場合、その要素の属性セットとして解釈されます。それ以降の要素(2番目の要素が属性オブジェクトでない場合を含む)は、DOM要素の子要素として解釈され、有効なDOMOutputSpec値または数値0でなければなりません。
数値0(発音は「ホール」)は、ノードの子ノードを挿入する位置を示すために使用されます。出力仕様で出現する場合、親ノード内の唯一の子要素でなければなりません。
ぱっと見よくわからんのですが、配列を受け取る・第2要素がオブジェクトなら属性として認識・それ以外は固定の子ノードとして認識・0は特殊で他の子ノードを挿入する位置です。
今回の例だと以下になります。
第1要素:"p" => pタグ
第2要素:HTMLAttributes
第3要素:0 => inline要素のテキストなどが子ノードとして挿入
renderHTMLの引数にHTMLAttributesを受け取れるのですが、これはノード内attrsのrenderHTMLを呼ばれたものがここに入ります。
これとoptionsで受け取ったものをマージしていますね。
options
Tiptapでは挙動をNodeを使用する側からoptionsで調整できます。
今回はHTMLAttributesを受け取れるようになっていて、classなどを当てれます。
Paragraph.configure({
HTMLAttributes: {
class: 'my-custom-class',
},
})
commands
addCommands() {
return {
setParagraph:
() =>
({ commands }) => {
return commands.setNode(this.name)
},
}
},
Tiptapではエディターへの操作をcommandsという一連の流れを規定したものを通じてします。
拡張機能ごとにcommandsを設定するので、ここではsetParagraph
という現在選択中の要素をParagraphノードに変換するコマンドを用意していますね。
commands.setNode(this.name)
について少し深掘りしますか。
Nodeの名前と属性を引数に受け取り、変換するみたいです。
この処理のコア部分はここでしょう。
return (
chain()
// try to convert node to default node if needed
.command(({ commands }) => {
const canSetBlock = setBlockType(type, { ...attributesToCopy, ...attributes })(state)
if (canSetBlock) {
return true
}
return commands.clearNodes()
})
.command(({ state: updatedState }) => {
return setBlockType(type, { ...attributesToCopy, ...attributes })(updatedState, dispatch)
})
.run()
)
まず1つ目のcommandの中では、setBlockType
で変換可能か試みています。(dispatchがないため)
ここら辺はProseMirrorの知識ですが、commandsはdispatchを引数に渡すとステートが更新され、無しだと可能かどうかの判定ができます。
また、setBlockTypeはProseMirrorが用意してるコマンドです。
こちらも別記事で読んでみます。
さて、そこで変換可能なら2つ目のコマンドにそのままチェーン、出来ないならclearNode
でデフォルトノード(大体の場合でParahraph)に変換してから、指定のノードにしています。
その他
addKeyboardShortcuts
でコマンドの追加、priority
でプラグインの登録順番・スキーマの順番を制御しています。
priority
が高いほど優先的に読み込まれます。例えば、リンク:priority-1000
, 太字:priority-10
だと、<a><b>link</b></a>
のように、aタグをより外側に配置できたりします。
さいごに
Paragraphだけでも結構機能が豊富でしたね。
次はText行きましょう。
Discussion