プレーンテキスト貼り付け時の改行処理を段落区切りに修正
プレーンテキストをペーストした際に <br> タグが生成される問題を修正し、各行が独立した <p> タグとして挿入されるよう動作を統一しました。
背景
改行を含むプレーンテキストをペーストすると、段落ではなく <br> タグが生成されるという問題がありました。原因は marked(text, { breaks: true }) オプションで、このオプションが有効な場合、単一の \n はそのまま <br> タグに変換されます。Lexical の組み込み関数 $insertDataTransferForRichText は改行を段落区切りとして扱うため、カスタムの marked() 経由のペースト処理との間で動作が一致していませんでした。
この不一致により、ユーザーが期待する「各行が独立した段落として挿入される」という動作が実現されておらず、代わりに1つの段落内に <br> が積み重なる形になっていました。
技術的な変更
今回の変更の核心は、marked() に渡す前にテキストを正規化する処理の追加です。単一の \n を \n\n(二重改行)に変換することで、marked() が各行を独立した段落として解釈するようになります。
Diff として可視化されているコードは lexxy.js の Selection クラス内の2箇所への条件分岐追加ですが、PR の説明によると本質的な修正はプレーンテキストの改行正規化処理です。具体的には、marked() 呼び出し前に単一改行を二重改行へ変換する前処理が加わり、breaks: true オプションの副作用を回避しています。
変更前後の動作の対比は以下のとおりです:
-
変更前:
"line one\nline two"→<p>line one<br>line two</p> -
変更後:
"line one\nline two"→<p>line one</p><p>line two</p>
あわせて、Selection クラス内の #getNextNodeFromTextEnd と #getPreviousNodeFromTextStart への遷移条件も厳密化されています。テキスト末尾でない位置(offset < anchorNode.getTextContentSize())では次ノードへの遷移を抑制し、テキスト先頭でない位置(offset > 0)では前ノードへの遷移を抑制するガード節が追加されました。これにより、カーソルがノード境界にない場合の意図しないノード間移動が防止されます。
設計判断
breaks: true を無効化するのではなく、入力テキストを正規化するアプローチが採用されました。breaks: true オプション自体はブロッククオートなどの文脈で <br> を意図的に生成するユースケースに対応するために存在していたと考えられますが、今回のPRではそのユースケースが不正確であると判断し、テスト側でも期待値を <br> ありの HTML から段落区切りに変更しています。
テストファイル paste.test.js では、ブロッククオート内への貼り付けで <br> を期待していた60行超のテストケースが削除され、新たに単一・二重改行ペーストのリグレッションテストが Playwright で追加されました。既存テストを修正するのではなく削除・置換していることから、旧来の <br> 生成動作が仕様として維持する価値がないと判断されたことが読み取れます。
まとめ
この修正は、marked() の breaks: true オプションに起因する動作と Lexical ネイティブのペースト動作の乖離を解消したものです。入力の前処理で正規化を行う判断により、オプション変更による他の文脈への影響を最小化しつつ、ユーザーが直感的に期待する「改行=段落区切り」という動作を実現しています。