Ordered List Numbering Offsets Preserved Across Load and Save
Ordered lists that start from a non‑1 index now retain their numbering when the editor loads, saves, and re‑opens the content. The fix expands the HTML sanitizers to allow the start attribute on <ol> elements, ensuring round‑trip fidelity.
背景
Lexical imports ordered lists using the start attribute of <ol> but ignores the value attribute on <li> during conversion, renumbering items sequentially from the list's start. Consequently, any list with an offset loses that offset after a load‑save cycle.
The sanitization layer further compounded the issue: while value on <li> was allow‑listed on both client and server, the start attribute on <ol> was stripped by DOMPurify when computing the form value and by Loofah when persisting through Action Text. As a result, the offset disappeared from the stored HTML, leading to the observed bug.
This mismatch between import‑export handling and attribute allow‑lists caused ordered lists such as
<ol start="2">
<li value="2">Should be 2</li>
<li value="3">Should be 3</li>
</ol>
to render as 1, 2 after a reload.
技術的な変更
The fix introduces start into the allowed attribute sets for both client‑side DOMPurify and server‑side Loofah, preserving the semantic attribute throughout the editor lifecycle.
On the server, lib/lexxy/engine.rb extends ActionText::ContentHelper.allowed_attributes:
default_allowed_attributes = Class.new.include(ActionText::ContentHelper).new.sanitizer_allowed_attributes
ActionText::ContentHelper.allowed_attributes = default_allowed_attributes + %w[ controls poster data-language style value start ]
On the client, the FormatEscapeExtension now returns an allow‑list that includes <ol> with the start attribute alongside the existing <li> value rule:
get allowedElements() {
return [ { tag: "ol", attributes: [ "start" ] }, { tag: "li", attributes: [ "value" ] } ]
}
With these changes, the editor’s HTML round‑trip retains both start on <ol> and value on <li>, allowing Lexical to reconstruct the original numbering.
Automated tests verify the behavior. A browser test sets the editor value to a list with start="2" and asserts that the rendered HTML matches the input. A system test exercises the full edit‑save‑display‑re‑edit flow, confirming that the stored HTML includes ol[start='2'] and that the subsequent edit restores li[value] attributes.
設計判断
The chosen solution expands the existing allow‑list mechanism rather than introducing a new attribute handling pathway. By adding start to the permitted attribute sets, the change preserves backward compatibility and avoids altering Lexical’s import logic.
This approach also aligns client and server sanitizers, ensuring consistent HTML handling across the stack. The decision to keep the attribute allow‑listing centralized simplifies future maintenance: any additional semantic attributes for other nodes can follow the same pattern.
Overall, the fix demonstrates a minimal‑impact, surface‑level adjustment that resolves the bug without modifying core parsing or rendering code.