mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-20 01:55:51 +00:00
fix: preserve ordered list numbering across bullet interruptions in export (#7470)
* fix: preserve ordered list numbering across unordered list interruptions in export When ordered lists were interrupted by unordered lists, each new <ol> segment started at 1 instead of continuing the previous numbering. Track running counts per indent level and emit start attributes. Fixes #6471 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: respect explicit start attributes and reset counters per level - line.start takes priority over counter-based continuation when present - Counter is seeded from line.start to keep subsequent continuations aligned - Counters for closed indent levels are cleared when list depth decreases Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ef0b257d3e
commit
ac118cfde7
2 changed files with 136 additions and 12 deletions
|
|
@ -309,6 +309,9 @@ const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string
|
|||
}
|
||||
|
||||
let openLists: openList[] = [];
|
||||
// Track running ordered-list item counts per indent level so that when an <ol>
|
||||
// is reopened after an unordered-list interruption we can emit start="N".
|
||||
const olItemCounts: MapArrayType<number> = {};
|
||||
for (let i = 0; i < textLines.length; i++) {
|
||||
let context;
|
||||
const line = _analyzeLine(textLines[i], attribLines[i], apool);
|
||||
|
|
@ -388,10 +391,25 @@ const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string
|
|||
}
|
||||
|
||||
if (line.listTypeName === 'number') {
|
||||
if (line.start) {
|
||||
pieces.push(`<ol start="${Number(line.start)}" class="${line.listTypeName}">`);
|
||||
} else {
|
||||
if (olItemCounts[line.listLevel] != null && olItemCounts[line.listLevel] > 0) {
|
||||
// Continue numbering after an unordered-list interruption
|
||||
const startNum = olItemCounts[line.listLevel] + 1;
|
||||
pieces.push(`<ol start="${startNum}" class="${line.listTypeName}">`);
|
||||
} else if (olItemCounts[line.listLevel] != null) {
|
||||
// Counter exists but is 0 — level was explicitly reset (e.g. a
|
||||
// nested list was closed). Start fresh without a start attribute.
|
||||
pieces.push(`<ol class="${line.listTypeName}">`);
|
||||
} else {
|
||||
// No counter yet. Use explicit start attribute when present
|
||||
// (e.g. from import or internal logic) and seed the counter so
|
||||
// subsequent continuations stay aligned.
|
||||
const explicitStart = Number(line.start);
|
||||
if (Number.isFinite(explicitStart) && explicitStart > 0) {
|
||||
pieces.push(`<ol start="${explicitStart}" class="${line.listTypeName}">`);
|
||||
olItemCounts[line.listLevel] = explicitStart - 1;
|
||||
} else {
|
||||
pieces.push(`<ol class="${line.listTypeName}">`);
|
||||
}
|
||||
}
|
||||
} else if (line.listTypeName === 'indent') {
|
||||
// Indent lines are plain indented text, not list items.
|
||||
|
|
@ -406,6 +424,14 @@ const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string
|
|||
// if we're going up a level we shouldn't be adding..
|
||||
if (context.lineContent) {
|
||||
pieces.push('<li>', context.lineContent);
|
||||
// Track ordered-list item counts so we can continue numbering after interruptions
|
||||
if (line.listTypeName === 'number') {
|
||||
if (!olItemCounts[line.listLevel]) {
|
||||
olItemCounts[line.listLevel] = 1;
|
||||
} else {
|
||||
olItemCounts[line.listLevel]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To close list elements
|
||||
|
|
@ -431,6 +457,8 @@ const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string
|
|||
if (nextLine && nextLine.listLevel) {
|
||||
nextLevel = nextLine.listLevel;
|
||||
}
|
||||
// The actual depth the next line lives at (ignoring type changes)
|
||||
const actualNextLevel = (nextLine && nextLine.listLevel) ? nextLine.listLevel : 0;
|
||||
if (nextLine && line.listTypeName !== nextLine.listTypeName) {
|
||||
nextLevel = 0;
|
||||
}
|
||||
|
|
@ -438,6 +466,15 @@ const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string
|
|||
for (let diff = nextLevel; diff < line.listLevel; diff++) {
|
||||
openLists = openLists.filter((el) => el.level !== diff && el.type !== line.listTypeName);
|
||||
|
||||
// Reset counter for levels that are genuinely closing (depth decrease),
|
||||
// not merely changing type at the same depth. Type changes should
|
||||
// preserve counters so numbering can continue after interruptions.
|
||||
// Use 0 as sentinel (not delete) so the ol-opening logic knows this
|
||||
// level was explicitly reset and won't fall back to line.start.
|
||||
if (diff + 1 > actualNextLevel) {
|
||||
olItemCounts[diff + 1] = 0;
|
||||
}
|
||||
|
||||
if (pieces[pieces.length - 1].indexOf('</ul') === 0 ||
|
||||
pieces[pieces.length - 1].indexOf('</ol') === 0) {
|
||||
pieces.push('</li>');
|
||||
|
|
@ -451,6 +488,10 @@ const getHTMLFromAtext = async (pad:PadType, atext: AText, authorColors?: string
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// outside any list — reset ordered-list counters for all levels
|
||||
for (const key of Object.keys(olItemCounts)) {
|
||||
delete olItemCounts[key];
|
||||
}
|
||||
// outside any list, need to close line.listLevel of lists
|
||||
context = {
|
||||
line,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue