ネストされたリストの番号崩れを `value` 属性の許可で修正
Lexxyエディタで番号付きリストをエクスポートした際、ネストされた子要素があると連番が崩れるバグを修正しました。<li> 要素の value 属性 をサニタイザの許可リストに追加し、エディタ内部でも扱えるようにすることで、正確な連番が保持されるようになります。
背景
ネストされた番号付きリストをエクスポートすると、連番が正しく出力されない問題がありました。PRの説明によると、子リストアイテムが存在する場合に番号が飛ぶ現象が発生しており、例として次のような崩れた出力が示されています。
1 First
2 Second
1 Second first
2 Second second
4 Third
「4 Third」が「3 Third」にならないのは、ネストされた子アイテムのカウントが親リストの連番に混入しているためです。Lexical(エディタのコアライブラリ)は各 <li> に value 属性で明示的な番号を付与することでこの問題を回避しますが、ActionTextのサニタイザがその属性を除去してしまうため、エクスポート時に情報が失われていました。
技術的な変更
変更は3か所に分かれており、サーバーサイドの許可リスト追加、クライアントサイドの許可要素定義、そしてテストの期待値更新で構成されています。
lib/lexxy/engine.rb では、ActionTextのサニタイザが許可する属性リストに value を追加しました。
変更前:
ActionText::ContentHelper.allowed_attributes = default_allowed_attributes + %w[ controls poster data-language style ]
変更後:
ActionText::ContentHelper.allowed_attributes = default_allowed_attributes + %w[ controls poster data-language style value ]
src/extensions/format_escape_extension.js では、allowedElements ゲッターを追加し、<li> タグの value 属性をエディタの許可要素として定義しました。
get allowedElements() {
return [ { tag: "li", attributes: [ "value" ] } ]
}
テストファイル群(block_formatting.test.js、escape_format.test.js、list_indentation.test.js 等)では、リストアイテムのHTML期待値がすべて value 属性付きに更新されました。たとえばフラットなリストでは各アイテムに通し番号が、ネストされたリストでは子リストの最初のアイテムが value="1" から再カウントされることが確認できます。
// 変更前
'<ol><li>Alpha</li><li>Bravo</li><li>Charlie</li></ol>'
// 変更後
'<ol><li value="1">Alpha</li><li value="2">Bravo</li><li value="3">Charlie</li></ol>'
テストの変更量が示すように、value 属性はすべてのリストアイテムに対して常に付与される仕様であり、オプションではありません。
設計判断
value 属性をサニタイザ許可リストに追加するというアプローチが採用されました。
value はHTML仕様でもともと <li> に定義された属性であり、番号付きリストにおけるアイテムの表示番号を上書きするために使われます。Lexicalがこの標準属性を活用してリスト番号を管理している設計上、Lexxyが同じ属性を正しく通過させるのは自然な対応です。ActionTextのデフォルトサニタイザは value を除去しますが、これはフォームのサブミット値などに使われる value への汎用的な防御であり、<li> 固有のセマンティクスを考慮していません。
クライアントサイドの allowedElements 定義はタグとアトリビュートを組み合わせた粒度で許可を宣言しており、<li> 以外の要素に value が伝播する範囲を最小化しています。
まとめ
本PRは、Lexicalが内部的に管理するリスト番号情報をActionTextのサニタイザが消去していたという、エディタとサニタイザ間のインピーダンスミスマッチを解消する修正です。value という標準HTML属性を明示的に許可することで、エクスポートされたHTMLにリストの順序情報が正確に保持されるようになりました。