リスト内の@メンション後のBackspaceでアウトデントが発生するバグを修正
リストアイテム内のインラインメンションの直後でBackspaceを押すと、メンションが選択される代わりにリストがアウトデントされるバグが修正されました。#collapseListItemToParagraphメソッドにデコレータノードの位置チェックを追加することで、インラインメンションとブロック添付ファイルの2つのケースを正しく区別できるようになります。
背景
リスト内でBackspaceを押したとき、カーソル直前のノードがデコレータノードかどうかによって処理が分岐する設計になっていました。しかし、#collapseListItemToParagraphメソッドはデコレータノードがリストアイテムの「外側にあるブロック添付ファイル」のケースだけを想定して実装されており、「リストアイテムの内側にあるインラインメンション」のケースを考慮していませんでした。
コメントにも明記されていた通り、このメソッドの元々の目的は「添付ファイルの直後にあるリストをBackspaceで削除するとき、添付ファイルが誤って選択されないようにリストアイテムをパラグラフに畳み込む」ことです。ところがこのロジックが、リストアイテムの中にインラインメンションが含まれるケースでも無条件に発動し、意図しないアウトデントを引き起こしていました。
技術的な変更
#collapseListItemToParagraphメソッドにデコレータノードへの参照を渡し、そのノードがリストアイテムの子孫であるかを確認してから処理を続行するよう変更されました。
変更前:
if (this.#collapseListItemToParagraph()) return true
#collapseListItemToParagraph() {
const anchorNode = $getSelection()?.anchor?.getNode()
const listItem = anchorNode && $getNearestNodeOfType(anchorNode, ListItemNode)
if (!listItem) return false
const listNode = $getNearestNodeOfType(listItem, ListNode)
if (!listNode) return false
// ...
}
変更後:
if (this.#collapseListItemToParagraph(node)) return true
#collapseListItemToParagraph(decoratorNode) {
const anchorNode = $getSelection()?.anchor?.getNode()
const listItem = anchorNode && $getNearestNodeOfType(anchorNode, ListItemNode)
if (!listItem) return false
if (listItem.isParentOf(decoratorNode)) return false
const listNode = $getNearestNodeOfType(listItem, ListNode)
if (!listNode) return false
// ...
}
変更の核心は listItem.isParentOf(decoratorNode) の1行です。デコレータノードがリストアイテムの子孫であれば false を即座に返してメソッドを抜け、呼び出し元での通常のデコレータ選択処理へ移行します。インラインメンションはリストアイテムの内側に存在するため、このガード節に引っかかり、誤ったアウトデントが抑制されます。一方、ブロック添付ファイルはリストの外側にあるため、isParentOfがfalseを返して既存のアウトデント抑止ロジックが従来通り動作します。
テストケースもtest/browser/tests/mention_deletion.test.jsに追加されました。リストアイテム内にメンションのみを含むHTMLをセットアップし、JavaScriptで精確なカーソル位置を制御した上で、2回のBackspace後にリスト構造(<ul>)が維持されていること、かつメンションに.node--selectedクラスが付与されることをアサートしています。
設計判断
デコレータノードへの参照をメソッドに引数として渡す方式が採用されました。
メソッド内部でデコレータノードを再取得する方法もありえましたが、呼び出し元ではすでに node として参照が確立されているため、それを引数として渡すほうが処理の重複を避けられます。また、「デコレータノードとリストアイテムの親子関係」という判定を#collapseListItemToParagraphの責務として内包させることで、呼び出し元のコードをシンプルに保っています。
メソッドのコメントも更新されており、「ブロック添付ファイルがリストの前にある場合にのみ適用し、リストアイテム内のインラインメンションには適用しない」という意図が明示されました。これはコードの正確性だけでなく、次の開発者がこのロジックを読み解く際の理解コストも下げる変更です。
まとめ
今回の修正は、#collapseListItemToParagraphが暗黙的に仮定していた「デコレータノードはリストの外側にある」という前提を明示的なガード節として表現したものです。1行のチェックを追加するだけで、ブロック添付ファイルに対する既存の動作を保ちながら、インラインメンションに対する誤ったアウトデントを解消しています。