ソフト改行を含むテキストへのリスト適用を行単位で正確に処理
Shift+Enterで挿入されるソフト改行(<br>)を含む段落に対してリスト書式を適用すると、段落全体が単一のリストアイテムになってしまうバグが修正されました。この修正により、選択した行のみをリストアイテムとして変換する正確な動作が実現しています。
背景
Lexxyエディタでは、ブロッククォート書式の適用時に同様の問題が既に修正されており、そのための splitParagraphsAtLineBreaks メソッドが Contents クラスに存在していました。しかしリスト書式の適用においては、この前処理が行われていませんでした。
具体的には、<p>First line<br>Second line<br>Third line</p> のようにソフト改行で区切られたテキストで「Second line」を選択してリスト書式を適用すると、期待される <p>First line</p><ul><li>Second line</li></ul><p>Third line</p> ではなく、段落全体が単一リストアイテムとして扱われていました。ブロッククォートの既存修正と同じアプローチをリスト書式にも適用することで、この問題を解消しています。
技術的な変更
変更の核心は、リスト書式のディスパッチ処理を CommandDispatcher から Contents クラスへ移動し、その前処理として段落分割を挿入した点です。
command_dispatcher.js の変更前:
// バレットリスト
this.editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined)
// 番号付きリスト
this.editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined)
command_dispatcher.js の変更後:
// バレットリスト
this.contents.applyUnorderedListFormat()
// 番号付きリスト
this.contents.applyOrderedListFormat()
INSERT_ORDERED_LIST_COMMAND と INSERT_UNORDERED_LIST_COMMAND のインポートも command_dispatcher.js から contents.js へ移動しています。
Contents クラスには以下のメソッドが追加されました:
applyUnorderedListFormat() {
this.#splitParagraphsAtLineBreaksUnlessInsideList()
this.editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined)
}
applyOrderedListFormat() {
this.#splitParagraphsAtLineBreaksUnlessInsideList()
this.editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined)
}
#splitParagraphsAtLineBreaksUnlessInsideList() {
if (this.selection.isInsideList) return
const selection = $getSelection()
if (!$isRangeSelection(selection)) return
this.#splitParagraphsAtLineBreaks(selection)
}
さらに、既存の #splitParagraphsAtLineBreaks メソッド内部でも改善が行われています。選択範囲のアンカー・フォーカスノードの参照方法が、ノードキー(getKey())からトップレベル要素(getTopLevelElement())の取得に変更されました。これにより、ネストされたノード構造においても選択範囲の境界を正確に判定できます。
設計判断
リスト内での動作を保護するガード節の設計が重要なポイントです。#splitParagraphsAtLineBreaksUnlessInsideList は this.selection.isInsideList が true の場合に即座に処理を終了します。これにより、リストアイテム内でShift+Enterを押した際に行われる改行(<br>)の挿入という既存の動作が壊れないようになっています。リスト書式の「適用」操作と「内部での編集」操作を、同一コードパスを通じながら条件分岐で適切に振り分ける設計です。
責務の集約という観点も見て取れます。変更前はリストコマンドのディスパッチが CommandDispatcher の責務でしたが、「段落分割してからリストを適用する」という複合操作を Contents クラスに集約することで、コマンドのディスパッチに関する知識(インポートを含む)も Contents 側に一元化されています。ブロッククォートと同じパターンに揃えることで、書式適用ロジックの一貫性も高まっています。
まとめ
本PRは、ブロッククォートで実績のある段落分割アプローチをリスト書式にも適用した変更です。ディスパッチロジックを Contents クラスへ移管するリファクタリングと、リスト内外での動作を正確に分岐するガード節の追加により、Shift+Enterの文脈によって異なる動作を一貫した設計で実現しています。