プレーンテキスト貼り付け時の改行処理を段落区切りに修正

basecamp/lexxy

プレーンテキストをペーストした際に <br> タグが生成される問題を修正し、各行が独立した <p> タグとして挿入されるよう動作を統一しました。

背景

改行を含むプレーンテキストをペーストすると、段落ではなく <br> タグが生成されるという問題がありました。原因は marked(text, { breaks: true }) オプションで、このオプションが有効な場合、単一の \n はそのまま <br> タグに変換されます。Lexical の組み込み関数 $insertDataTransferForRichText は改行を段落区切りとして扱うため、カスタムの marked() 経由のペースト処理との間で動作が一致していませんでした。

この不一致により、ユーザーが期待する「各行が独立した段落として挿入される」という動作が実現されておらず、代わりに1つの段落内に <br> が積み重なる形になっていました。

技術的な変更

今回の変更の核心は、marked() に渡す前にテキストを正規化する処理の追加です。単一の \n\n\n(二重改行)に変換することで、marked() が各行を独立した段落として解釈するようになります。

Diff として可視化されているコードは lexxy.jsSelection クラス内の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 ネイティブのペースト動作の乖離を解消したものです。入力の前処理で正規化を行う判断により、オプション変更による他の文脈への影響を最小化しつつ、ユーザーが直感的に期待する「改行=段落区切り」という動作を実現しています。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
9456d6aa

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)、背景・技術的な変更・設計判断(各論)、まとめ(結論)という「総論→各論→結論」の構成が明確であり、ガイドラインを完全に満たしています。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

PR番号のリンク記法がガイドライン指定の `[#123](URL)` 形式ではなく、`[PR #848](URL)` となっています。内容理解に影響はありませんが、形式の統一が望まれます。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Lexicalやmarked.jsに関する知識を前提とした記述であり、専門のエンジニアという対象読者に適合した技術レベルと表現です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論で構成され、各段落がトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、高い可読性を確保しています。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事は`lexxy.js`のガード節追加や`paste.test.js`のテストケース削除といったDiffの内容を正確に説明しています。Diffにない正規化処理のコードを引用せず、PR Descriptionに基づき説明している点も適切です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

PRで使われている`marked`, `$insertDataTransferForRichText`, `Playwright`などの用語を正確に使用しており、技術用語の誤用は見られません。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

「単一の`\n`を`\n\n`に変換する」という修正の核心や、`Selection`クラスへの変更の意図など、技術的な説明はPR情報と整合しており、論理的かつ正確です。

事実の突合 ⚠ WARNING

PR情報による主張の裏付け(ハルシネーション検出)

「設計判断」セクションに、「`breaks: true` オプション自体は...存在していたと考えられます」というPR情報にない推測が含まれています。妥当な推論ですが、ガイドラインでは推測は原則として許可されていません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号「#848」や、削除されたテストケースの行数「60行超」など、記事に含まれる数値や固有名詞はPR情報と一致しており正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトル「プレーンテキスト貼り付け時の改行処理を段落区切りに修正」は、PRのタイトルと内容を的確に要約しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事には、バージョン情報やリリース日程など、PR情報に基づかない外部知識は含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

PRで過去形で述べられている問題点を、記事でも「問題がありました」と過去形で記述するなど、時間表現は正確です。