Skip to content

feat(extension-list): support ordered list type attribute (a, A, i, I)#7905

Open
bdbch wants to merge 1 commit into
mainfrom
feature/3726-ordered-list-type-support
Open

feat(extension-list): support ordered list type attribute (a, A, i, I)#7905
bdbch wants to merge 1 commit into
mainfrom
feature/3726-ordered-list-type-support

Conversation

@bdbch
Copy link
Copy Markdown
Member

@bdbch bdbch commented Jun 1, 2026

Changes Overview

Ordered lists now support the HTML type attribute 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 type attribute already existed on the OrderedList node but had a buggy renderHTML (it passed type: null through). Fixed rendering, added CSS list-style-type parsing for Google Docs/Word pastes, and made joinPredicate type-aware so lists with different types don't merge.

Paste detection: Added a handlePaste plugin that detects typed ordered list markers in pasted plain text (e.g., a. Item, i) Item, I. One\nII. Two) and creates the proper orderedList node with the correct type attribute.

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

  • Added 53 unit tests across all new features: renderHTML, joinPredicate, HTML paste round-trip, CSS list-style-type parsing, markdown round-trip, marker detection utilities, and plain-text paste detection.
  • All 1212 existing tests pass.
  • Manually tested paste from Google Docs via demo page.

Verification Steps

  1. Paste an ordered list from Google Docs that uses roman numerals or letters.
  2. Set content with <ol type="a"> and verify it renders correctly in the editor.
  3. Type 1. after an existing type="a" list and verify they stay as separate lists.
  4. Create a markdown doc with a. Item and verify it round-trips correctly.

Additional Notes

Google Docs does not use the HTML type attribute on <ol>. Instead, it uses CSS list-style-type on <li> elements. We handle this by also checking the CSS in the parseHTML callback.

AI Usage

  • I have used AI tools (e.g., ChatGPT, Claude, Copilot) in creating this PR.
    • I used AI to help plan and implement the feature following TDD practices.
    • I used AI to write unit tests and the changeset.

Checklist

  • I have created a changeset for this PR.
  • My changes do not break the library.
  • I have added tests where applicable.
  • I have followed the project guidelines.
  • I have fixed any lint issues.

Related Issues

Closes #3726

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.
Copilot AI review requested due to automatic review settings June 1, 2026 13:38
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 1, 2026

🦋 Changeset detected

Latest commit: d9ff686

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 73 packages
Name Type
@tiptap/extension-list Major
@tiptap/core Major
@tiptap/extension-bullet-list Major
@tiptap/extension-ordered-list Major
@tiptap/extension-list-item Major
@tiptap/extension-list-keymap Major
@tiptap/extension-task-item Major
@tiptap/extension-task-list Major
@tiptap/extension-audio Major
@tiptap/extension-blockquote Major
@tiptap/extension-bold Major
@tiptap/extension-bubble-menu Major
@tiptap/extension-code-block-lowlight Major
@tiptap/extension-code-block Major
@tiptap/extension-code Major
@tiptap/extension-collaboration-caret Major
@tiptap/extension-collaboration Major
@tiptap/extension-details Major
@tiptap/extension-document Major
@tiptap/extension-drag-handle Major
@tiptap/extension-emoji Major
@tiptap/extension-file-handler Major
@tiptap/extension-floating-menu Major
@tiptap/extension-hard-break Major
@tiptap/extension-heading Major
@tiptap/extension-highlight Major
@tiptap/extension-horizontal-rule Major
@tiptap/extension-image Major
@tiptap/extension-invisible-characters Major
@tiptap/extension-italic Major
@tiptap/extension-link Major
@tiptap/extension-mathematics Major
@tiptap/extension-mention Major
@tiptap/extension-node-range Major
@tiptap/extension-paragraph Major
@tiptap/extension-strike Major
@tiptap/extension-subscript Major
@tiptap/extension-superscript Major
@tiptap/extension-table-of-contents Major
@tiptap/extension-table Major
@tiptap/extension-text-align Major
@tiptap/extension-text-style Major
@tiptap/extension-text Major
@tiptap/extension-twitch Major
@tiptap/extension-typography Major
@tiptap/extension-underline Major
@tiptap/extension-unique-id Major
@tiptap/extension-youtube Major
@tiptap/extensions Major
@tiptap/html Major
@tiptap/markdown Major
@tiptap/react Major
@tiptap/static-renderer Major
@tiptap/suggestion Major
@tiptap/vue-2 Major
@tiptap/vue-3 Major
@tiptap/starter-kit Major
@tiptap/extension-drag-handle-react Major
@tiptap/extension-drag-handle-vue-2 Major
@tiptap/extension-drag-handle-vue-3 Major
@tiptap/extension-table-cell Major
@tiptap/extension-table-header Major
@tiptap/extension-table-row Major
@tiptap/extension-color Major
@tiptap/extension-font-family Major
@tiptap/extension-character-count Major
@tiptap/extension-dropcursor Major
@tiptap/extension-focus Major
@tiptap/extension-gapcursor Major
@tiptap/extension-history Major
@tiptap/extension-placeholder Major
@tiptap/pm Major
@tiptap/server-ai-toolkit Major

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

@netlify
Copy link
Copy Markdown

netlify Bot commented Jun 1, 2026

Deploy Preview for tiptap-embed ready!

Name Link
🔨 Latest commit d9ff686
🔍 Latest deploy log https://app.netlify.com/projects/tiptap-embed/deploys/6a1d8b6b84455400082b1fb0
😎 Deploy Preview https://deploy-preview-7905--tiptap-embed.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 type on orderedList through HTML parsing/rendering and CSS list-style-type mapping.
  • Extend markdown tokenization/parsing and serialization to support typed ordered list markers (alpha/roman).
  • Add joining rules so ordered lists with different type values 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 }],
},
],
})),
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ordered lists do not support list types (a, A, i and I)

2 participants