feat(extension-list): support ordered list type attribute (a, A, i, I)#7905
Open
bdbch wants to merge 1 commit into
Open
feat(extension-list): support ordered list type attribute (a, A, i, I)#7905bdbch wants to merge 1 commit into
bdbch wants to merge 1 commit into
Conversation
Preserve ol type (a, A, i, I) through HTML, Markdown, and paste operations. Detect list style from the HTML type attribute and from CSS list-style-type, and treat default numeric type (null/"1") equivalently. Prevent joining lists with different types. Add roman/alpha utilities, demo examples, and comprehensive tests and changeset updates.
🦋 Changeset detectedLatest commit: d9ff686 The changes in this PR will be included in the next version bump. This PR includes changesets to release 73 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
✅ Deploy Preview for tiptap-embed ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds end-to-end support for ordered list marker styles via the HTML type attribute (a, A, i, I) across HTML parsing/rendering, markdown parsing/serialization, and paste handling (including external editors), to address loss of ordered list types.
Changes:
- Preserve
typeonorderedListthrough HTML parsing/rendering and CSSlist-style-typemapping. - Extend markdown tokenization/parsing and serialization to support typed ordered list markers (alpha/roman).
- Add joining rules so ordered lists with different
typevalues don’t merge, plus demos/tests/changeset coverage.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/extension-list/src/ordered-list/utils.ts | Expands markdown ordered-list item detection/collection and carries a typeMarker through nested list token building. |
| packages/extension-list/src/ordered-list/roman.ts | Introduces roman/alpha marker utilities (toRoman, getListMarker, detectMarkerType). |
| packages/extension-list/src/ordered-list/ordered-list.ts | Adds HTML/CSS type parsing, fixes renderHTML attribute emission, updates markdown tokenizer/parser, and adds a plain-text paste plugin + type-aware input-rule joining. |
| packages/extension-list/src/ordered-list/index.ts | Re-exports roman/marker utilities from the ordered-list entry point. |
| packages/extension-list/src/item/list-item.ts | Updates markdown serialization to render correct ordered list markers based on parent type/start. |
| packages/extension-list/tests/orderedListType.spec.ts | Adds extensive unit tests for type round-trips, markdown utilities, and paste-detection behaviors. |
| packages/core/src/commands/toggleList.ts | Prevents list joining when adjacent ordered lists have incompatible type attributes. |
| demos/src/Nodes/OrderedList/Vue/index.vue | Updates demo content to include type="a" and type="I" examples. |
| demos/src/Nodes/OrderedList/React/index.jsx | Updates demo content to include type="a" and type="I" examples. |
| demos/src/Nodes/OrderedList/index.spec.ts | Adds e2e checks for preserving type in HTML output. |
| .changeset/2026-06-01-ordered-list-type-attribute.md | Documents the user-facing feature and bumps affected packages. |
Comment on lines
+58
to
+70
| // Explicit alpha markers: only "a" and "A" are valid | ||
| if (marker === 'a') { | ||
| return 'a' | ||
| } | ||
|
|
||
| if (marker === 'A') { | ||
| return 'A' | ||
| } | ||
|
|
||
| // Roman numeral markers (single or multi-character) | ||
| if (/^[ivxlcdmIVXLCDM]+$/.test(marker)) { | ||
| return marker === marker.toLowerCase() ? 'i' : 'I' | ||
| } |
Comment on lines
+296
to
+300
| handlePaste: (view, event) => { | ||
| const text = event.clipboardData?.getData('text/plain') | ||
| if (!text) { | ||
| return false | ||
| } |
Comment on lines
+324
to
+327
| // All lines match — create an ordered list | ||
| const type = detectMarkerType(parsedItems[0].marker) | ||
| const nodeAttrs = type ? { type } : {} | ||
|
|
Comment on lines
43
to
+47
| // oxlint-disable-next-line prefer-string-starts-ends-with | ||
| /^\d+\.\s+/.test(trimmedLine) || | ||
| // oxlint-disable-next-line prefer-string-starts-ends-with | ||
| /^[aAivxlcdmIVXLCDM]{1,5}[.)]\s+/.test(trimmedLine) || | ||
| // oxlint-disable-next-line prefer-string-starts-ends-with |
Comment on lines
+112
to
+121
| const [, indent, marker, _separator, content] = match | ||
| const indentLevel = indent.length | ||
| const number = parseInt(marker, 10) | ||
|
|
||
| // Detect if marker is NaN (not a number) — it's a typed marker like "a" or "iii" | ||
| const markerType = isNaN(number) ? detectMarkerType(marker) : undefined | ||
|
|
||
| // For ordered list numbering, use the position index if marker is non-numeric | ||
| const itemNumber = isNaN(number) ? 1 : number | ||
|
|
Comment on lines
228
to
231
| start: (src: string) => { | ||
| const match = src.match(/^(\s*)(\d+)\.\s+/) | ||
| const match = src.match(/^(\s*)(\d+|[a-zA-Z]|[ivxlcdmIVXLCDM]{2,15})([.)])\s+/) | ||
| const index = match?.index | ||
| return index !== undefined ? index : -1 |
Comment on lines
+269
to
+299
| function simulatePasteBuild(text: string): JSONContent | null { | ||
| const typedListLineRegex = /^([a-zA-Z]|\d+|[ivxlcdmIVXLCDM]{2,8})([.)])\s+(.+)$/ | ||
| const lines = text.split('\n').filter(l => l.trim().length > 0) | ||
|
|
||
| const parsedItems: Array<{ marker: string; content: string }> = [] | ||
|
|
||
| for (const line of lines) { | ||
| const match = line.trim().match(typedListLineRegex) | ||
| if (!match) return null | ||
| parsedItems.push({ | ||
| marker: match[1], | ||
| content: match[3], | ||
| }) | ||
| } | ||
|
|
||
| // Import detectMarkerType dynamically to test the actual function | ||
| // We'll use the same logic as the paste handler | ||
| // (detectMarkerType is already tested separately above) | ||
| return { | ||
| type: 'orderedList', | ||
| attrs: {}, | ||
| content: parsedItems.map(item => ({ | ||
| type: 'listItem', | ||
| content: [ | ||
| { | ||
| type: 'paragraph', | ||
| content: [{ type: 'text', text: item.content }], | ||
| }, | ||
| ], | ||
| })), | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes Overview
Ordered lists now support the HTML
typeattribute for alphabetical (a,A) and roman numeral (i,I) list markers. The type is preserved through HTML round-trips, paste from external editors (Google Docs, Word, LibreOffice), and markdown parsing/serialization.Implementation Approach
We added support in three layers:
Core model: The
typeattribute already existed on the OrderedList node but had a buggyrenderHTML(it passedtype: nullthrough). Fixed rendering, added CSSlist-style-typeparsing for Google Docs/Word pastes, and madejoinPredicatetype-aware so lists with different types don't merge.Paste detection: Added a
handlePasteplugin that detects typed ordered list markers in pasted plain text (e.g.,a. Item,i) Item,I. One\nII. Two) and creates the properorderedListnode with the correcttypeattribute.Markdown: Added roman numeral utilities (
toRoman,detectMarkerType,getListMarker), updated the markdown tokenizer and parser to extract type from typed markers, and updated the serializer to render the correct prefix (e.g.,a.,I.,ii.).Testing Done
Verification Steps
<ol type="a">and verify it renders correctly in the editor.1.after an existingtype="a"list and verify they stay as separate lists.a. Itemand verify it round-trips correctly.Additional Notes
Google Docs does not use the HTML
typeattribute on<ol>. Instead, it uses CSSlist-style-typeon<li>elements. We handle this by also checking the CSS in theparseHTMLcallback.AI Usage
Checklist
Related Issues
Closes #3726