Repository navigation
fix(mobile): three-digit list numbers no longer lose their first digit #19742
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
89a3832
72bdbd0
88d080d
a45f036
94ac683
158f0e8
7eb36bf
d6a2af4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import { describe, expect, it } from "vite-plus/test"; | ||
|
|
||
| import { wideOrderedListMarkerDigits } from "./orderedListMarkers"; | ||
|
|
||
| const numberedList = (count: number, marker = (index: number) => `${index + 1}.`) => | ||
| Array.from({ length: count }, (_, index) => `${marker(index)} item`).join("\n"); | ||
|
|
||
| describe("wideOrderedListMarkerDigits", () => { | ||
| it("returns 0 while every number fits in two digits", () => { | ||
| expect(wideOrderedListMarkerDigits("just a message")).toBe(0); | ||
| expect(wideOrderedListMarkerDigits("- one\n- two")).toBe(0); | ||
| expect(wideOrderedListMarkerDigits(numberedList(99))).toBe(0); | ||
| expect(wideOrderedListMarkerDigits("We shipped it in 2026. More later.")).toBe(0); | ||
| expect(wideOrderedListMarkerDigits("Version 1.2 is out")).toBe(0); | ||
| expect(wideOrderedListMarkerDigits(`${numberedList(60)}\n\ntext\n\n${numberedList(50)}`)).toBe( | ||
| 0, | ||
| ); | ||
| const withSubLists = Array.from( | ||
| { length: 60 }, | ||
| (_, index) => `${index + 1}. item\n 1. sub\n 2. sub`, | ||
| ).join("\n"); | ||
| expect(wideOrderedListMarkerDigits(withSubLists)).toBe(0); | ||
| }); | ||
|
|
||
| it("measures the widest rendered number", () => { | ||
| expect(wideOrderedListMarkerDigits(numberedList(130))).toBe(3); | ||
| expect(wideOrderedListMarkerDigits("1000) a")).toBe(4); | ||
| expect(wideOrderedListMarkerDigits("text\r\n\r\n100.\r\n")).toBe(3); | ||
| }); | ||
|
|
||
| it("covers numbers the renderer counts up to", () => { | ||
| expect(wideOrderedListMarkerDigits("99. first\n1. second")).toBe(3); | ||
| expect(wideOrderedListMarkerDigits(numberedList(101, () => "1."))).toBe(3); | ||
| expect(wideOrderedListMarkerDigits(`${numberedList(60)}\n\n${numberedList(50)}`)).toBe(3); | ||
| expect(wideOrderedListMarkerDigits("99. first\n[^note]: text\n1. second")).toBe(3); | ||
| expect(wideOrderedListMarkerDigits("007. agent")).toBe(0); | ||
| }); | ||
|
|
||
| it("finds lists nested in other blocks", () => { | ||
| expect(wideOrderedListMarkerDigits("- 100000. nested")).toBe(6); | ||
| expect(wideOrderedListMarkerDigits("> > 250. quoted")).toBe(3); | ||
| expect(wideOrderedListMarkerDigits("1. a\n\n 100. nested")).toBe(3); | ||
| expect(wideOrderedListMarkerDigits(" code\n\n100. item")).toBe(3); | ||
| }); | ||
|
|
||
| it("stays linear on long runs of bullets and spaces", () => { | ||
| expect(wideOrderedListMarkerDigits(`${"- ".repeat(40)}x`)).toBe(0); | ||
| }); | ||
|
|
||
| it("ignores fenced code", () => { | ||
| expect(wideOrderedListMarkerDigits("- a\n\n```\n123456789. example\n```")).toBe(0); | ||
| expect(wideOrderedListMarkerDigits("~~~~\n250. a\n~~~\n300. b\n~~~~\n1. c")).toBe(0); | ||
| expect(wideOrderedListMarkerDigits("```\n250. a\n```\n\n150. b")).toBe(3); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // A line the renderer could read as an ordered-list item, including one nested | ||
| // after quote or list markers. Group 1 is everything before the number, so its | ||
| // length tells nested lists apart. Indented code also matches; that only widens. | ||
| // Each repeat starts with a marker or `>`, which keeps matching linear. | ||
| const ORDERED_MARKER_LINE = | ||
| /^([ \t>]*(?:(?:[-+*]|\d{1,9}[.)])[ \t]+(?:>[ \t>]*)?)*)(\d{1,9})[.)](?:[ \t\r]|$)/; | ||
| const CODE_FENCE = /^ {0,3}(`{3,}|~{3,})/; | ||
|
|
||
| /** | ||
| * Digit count of the widest number the renderer could draw for an ordered list | ||
| * in `markdown`, when that is three or more digits; otherwise 0. | ||
| * | ||
| * Lists number their items up from the first marker, so each item counts as at | ||
| * least one more than the previous item of its list. This is one cheap pass | ||
| * while a reply streams. When a line is ambiguous it keeps counting, so it can | ||
| * overestimate (a slightly wider gutter) but never clip a number. | ||
| */ | ||
| export function wideOrderedListMarkerDigits(markdown: string): number { | ||
| // Last number per open list, keyed by the marker's column. | ||
| const lists = new Map<number, number>(); | ||
| let openFence: string | null = null; | ||
| let afterBlankLine = false; | ||
| let widest = 0; | ||
| for (const line of markdown.split("\n")) { | ||
| const fence = CODE_FENCE.exec(line)?.[1]; | ||
| if (openFence !== null) { | ||
| if (fence && fence[0] === openFence[0] && fence.length >= openFence.length) openFence = null; | ||
| continue; | ||
| } | ||
| if (fence) { | ||
| openFence = fence; | ||
| continue; | ||
| } | ||
| const marker = ORDERED_MARKER_LINE.exec(line); | ||
| if (marker) { | ||
| const column = marker[1]!.length; | ||
| for (const key of lists.keys()) if (key > column) lists.delete(key); | ||
| const number = Math.max(Number(marker[2]), (lists.get(column) ?? -1) + 1); | ||
| lists.set(column, number); | ||
| widest = Math.max(widest, number); | ||
| } else if (afterBlankLine && /^[^ \t\r]/.test(line)) { | ||
| // Unindented text after a blank line cannot belong to any list item. | ||
| lists.clear(); | ||
| } | ||
| afterBlankLine = line.trim() === ""; | ||
| } | ||
| const digits = String(widest).length; | ||
| return digits >= 3 ? digits : 0; | ||
|
Comment on lines
+22
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Restore the effective list-number calculation. For 🤖 Prompt for AI Agents |
||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.