prosemirror-flat-list
Version:
Powerful list support for ProseMirror
128 lines (117 loc) • 3.62 kB
text/typescript
import type { TagParseRule } from 'prosemirror-model'
import type { ListAttributes, ListKind } from '../types'
import { findCheckboxInListItem } from '../utils/find-checkbox-in-list-item'
import { parseInteger } from '../utils/parse-integer'
/**
* Returns a set of rules for parsing HTML into ProseMirror list nodes.
*
* @public @group Schema
*/
export function createParseDomRules(): readonly TagParseRule[] {
return [
{
tag: 'div[data-list-kind]',
getAttrs: (element): ListAttributes => {
if (typeof element === 'string') {
return {}
}
return {
kind: (element.getAttribute('data-list-kind') ||
'bullet') as ListKind,
order: parseInteger(element.getAttribute('data-list-order')),
checked: element.hasAttribute('data-list-checked'),
collapsed: element.hasAttribute('data-list-collapsed'),
}
},
},
{
tag: 'div[data-list]',
getAttrs: (element): ListAttributes => {
if (typeof element === 'string') {
return {}
}
return {
kind: (element.getAttribute('data-list-kind') ||
'bullet') as ListKind,
order: parseInteger(element.getAttribute('data-list-order')),
checked: element.hasAttribute('data-list-checked'),
collapsed: element.hasAttribute('data-list-collapsed'),
}
},
},
{
tag: 'ul > li',
getAttrs: (element): ListAttributes => {
if (typeof element !== 'string') {
const checkbox = findCheckboxInListItem(element)
if (checkbox) {
return {
kind: 'task',
checked: checkbox.hasAttribute('checked'),
}
}
if (
element.hasAttribute('data-task-list-item') ||
element.getAttribute('data-list-kind') === 'task'
) {
return {
kind: 'task',
checked:
element.hasAttribute('data-list-checked') ||
element.hasAttribute('data-checked'),
}
}
if (
element.hasAttribute('data-toggle-list-item') ||
element.getAttribute('data-list-kind') === 'toggle'
) {
return {
kind: 'toggle',
collapsed: element.hasAttribute('data-list-collapsed'),
}
}
if (element.firstChild?.nodeType === 3 /* document.TEXT_NODE */) {
const textContent = element.firstChild.textContent
if (textContent && /^\[[\sx|]]\s{1,2}/.test(textContent)) {
element.firstChild.textContent = textContent.replace(
/^\[[\sx|]]\s{1,2}/,
'',
)
return {
kind: 'task',
checked: textContent.startsWith('[x]'),
}
}
}
}
return {
kind: 'bullet',
}
},
},
{
tag: 'ol > li',
getAttrs: (element): ListAttributes => {
if (typeof element === 'string') {
return {
kind: 'ordered',
}
}
return {
kind: 'ordered',
order: parseInteger(element.getAttribute('data-list-order')),
}
},
},
{
// This rule is for handling nested lists copied from Dropbox Paper. It's
// technically invalid HTML structure.
tag: ':is(ul, ol) > :is(ul, ol)',
getAttrs: (): ListAttributes => {
return {
kind: 'bullet',
}
},
},
]
}