From 1150813b525c5be972139e23f1ded3ef47ea31e3 Mon Sep 17 00:00:00 2001 From: John McLear Date: Thu, 2 Apr 2026 04:28:51 +0100 Subject: [PATCH] fix: reset list numbering when switching from unordered to ordered list When an ordered list followed directly after an unordered list (no blank line between), all OL items showed "1" instead of incrementing. This was because renumberList's applyNumberList function counted bullet items in the position counter, so the first OL item got start=3 (after 2 bullet items) instead of start=1, preventing the CSS counter-reset class from being applied. The fix resets the position counter when the list type changes at the same level (e.g., bullet -> number). Fixes: https://github.com/ether/etherpad-lite/issues/5160 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/static/js/ace2_inner.ts | 9 +++++ .../frontend-new/specs/ordered_list.spec.ts | 38 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/static/js/ace2_inner.ts b/src/static/js/ace2_inner.ts index 654042d6963..00f436186ae 100644 --- a/src/static/js/ace2_inner.ts +++ b/src/static/js/ace2_inner.ts @@ -2307,14 +2307,23 @@ function Ace2Inner(editorInfo, cssManagers) { let position = 1; let curLevel = level; let listType; + let prevType = ''; // loop over the lines while ((listType = getLineListType(line))) { // apply new num listType = /([a-z]+)([0-9]+)/.exec(listType); curLevel = Number(listType[2]); + const curType = listType[1]; if (isNaN(curLevel) || listType[0] === 'indent') { return line; } else if (curLevel === level) { + // Reset position when switching between list types at the same level + // (e.g., bullet -> number). See https://github.com/ether/etherpad-lite/issues/5160 + if (prevType && prevType !== curType) { + position = 1; + } + prevType = curType; + buildKeepRange(rep, builder, loc, (loc = [line, 0])); buildKeepRange(rep, builder, loc, (loc = [line, 1]), [ ['start', position], diff --git a/src/tests/frontend-new/specs/ordered_list.spec.ts b/src/tests/frontend-new/specs/ordered_list.spec.ts index 04e996e666f..ffec6fbe39b 100644 --- a/src/tests/frontend-new/specs/ordered_list.spec.ts +++ b/src/tests/frontend-new/specs/ordered_list.spec.ts @@ -54,6 +54,44 @@ test.describe('ordered_list.js', function () { }); }); + // Regression test for https://github.com/ether/etherpad-lite/issues/5160 + test('issue #5160 ordered list increments correctly after unordered list', async function ({page}) { + const padBody = await getPadBody(page); + await clearPadContent(page); + + // Create two unordered list items + const $insertUnorderedButton = page.locator('.buttonicon-insertunorderedlist'); + await $insertUnorderedButton.click({force: true}); + await writeToPad(page, 'bullet a'); + await page.keyboard.press('Enter'); + await writeToPad(page, 'bullet b'); + await page.keyboard.press('Enter'); + + // Now switch to ordered list for the next items + const $insertOrderedButton = page.locator('.buttonicon-insertorderedlist'); + await $insertOrderedButton.click({force: true}); + await writeToPad(page, 'number 1'); + await page.keyboard.press('Enter'); + await writeToPad(page, 'number 2'); + await page.keyboard.press('Enter'); + await writeToPad(page, 'number 3'); + + // Wait for renumbering + await page.waitForTimeout(500); + + // The first ordered list item (line 3) should have start=1 + const thirdLine = padBody.locator('div').nth(2); + await expect(thirdLine.locator('ol')).toHaveAttribute('start', '1', {timeout: 5000}); + + // The second ordered list item (line 4) should have start=2 + const fourthLine = padBody.locator('div').nth(3); + await expect(fourthLine.locator('ol')).toHaveAttribute('start', '2', {timeout: 5000}); + + // The third ordered list item (line 5) should have start=3 + const fifthLine = padBody.locator('div').nth(4); + await expect(fifthLine.locator('ol')).toHaveAttribute('start', '3', {timeout: 5000}); + }); + test.describe('Pressing Tab in an OL increases and decreases indentation', function () { test('indent and de-indent list item with keypress', async function ({page}) {