Skip to main content

ListExtension

Summary​

Adds ordered lists, unordered lists, and checklists to the editor.

Usage​

Installation​

This extension is installed for you when you install the main remirror package.

You can use the imports in the following way:

import { ListExtension } from 'remirror/extensions';

The extension is provided by the @remirror/extension-list package.

Examples​

Source code
import '@remirror/styles/all.css';

import React from 'react';
import {
BulletListExtension,
HardBreakExtension,
HeadingExtension,
LinkExtension,
OrderedListExtension,
TaskListExtension,
} from 'remirror/extensions';
import { EditorComponent, Remirror, ThemeProvider, useRemirror } from '@remirror/react';
import { ListButtonGroup, Toolbar } from '@remirror/react-ui';

const Basic = (): JSX.Element => {
const { manager, state } = useRemirror({
extensions,
content,
stringHandler: 'html',
});

return (
<ThemeProvider>
<Remirror manager={manager} initialContent={state}>
<Toolbar>
<ListButtonGroup />
</Toolbar>
<EditorComponent />
</Remirror>
</ThemeProvider>
);
};

const extensions = () => [
new BulletListExtension(),
new OrderedListExtension(),
new TaskListExtension(),
new HeadingExtension(),
new LinkExtension(),
/**
* `HardBreakExtension` allows us to create a newline inside paragraphs .
* e.g. in a list item
*/
new HardBreakExtension(),
];

const html = String.raw; // Just for better editor support

const content = html`
<ul>
<li>first unordered list item</li>
<li>second unordered list item</li>
</ul>
<ol>
<li>first ordered list item</li>
<li>second ordered list item</li>
</ol>
<ul data-task-list>
<li data-task-list-item>first task list item</li>
<li data-task-list-item data-checked>second task list item</li>
</ul>

<h2>Nested bullet list:</h2>

<ul>
<li>A</li>
<li>B</li>
<li>
C
<ul>
<li>C.A</li>
<li>
C.B
<ul>
<li>C.B.A</li>
<li>C.B.B</li>
<li>C.B.C</li>
</ul>
</li>
<li>C.C</li>
</ul>
</li>
</ul>

<h2>Nested ordered list:</h2>

<ol>
<li>A</li>
<li>B</li>
<li>
C
<ol>
<li>C.A</li>
<li>
C.B
<ol>
<li>C.B.A</li>
<li>C.B.B</li>
<li>C.B.C</li>
</ol>
</li>
<li>C.C</li>
</ol>
</li>
</ol>

<h2>Nested task list:</h2>

<ul data-task-list>
<li data-task-list-item data-checked>A</li>
<li data-task-list-item>B</li>
<li data-task-list-item data-checked>
C
<ul data-checked data-task-list>
<li data-task-list-item>C.A</li>
<li data-task-list-item>
C.B
<ul data-task-list>
<li data-task-list-item data-checked>C.B.A</li>
<li data-task-list-item data-checked>C.B.B</li>
<li data-task-list-item>C.B.C</li>
</ul>
</li>
<li>C.C</li>
</ul>
</li>
</ul>

<h2>Nested mixed list:</h2>

<ul>
<li>A</li>
<li>B</li>
<li>
C
<ul data-task-list>
<li data-task-list-item>C.A</li>
<li data-task-list-item data-checked>
C.B
<ol>
<li>C.B.A</li>
<li>C.B.B</li>
<li>C.B.C</li>
</ol>
</li>
<li data-task-list-item data-checked>C.C</li>
</ul>
</li>
</ul>
`;

export default Basic;
Source code
import '@remirror/styles/all.css';

import React from 'react';
import {
BulletListExtension,
HardBreakExtension,
HeadingExtension,
OrderedListExtension,
TaskListExtension,
} from 'remirror/extensions';
import { EditorComponent, Remirror, ThemeProvider, useRemirror } from '@remirror/react';

const Collapsible = (): JSX.Element => {
const { manager, state } = useRemirror({
extensions: extensionsWithSpine,
content,
stringHandler: 'html',
});

return (
<ThemeProvider>
<Remirror manager={manager} initialContent={state}>
<EditorComponent />
</Remirror>
</ThemeProvider>
);
};

const extensionsWithSpine = () => [
new BulletListExtension({ enableSpine: true }),
new OrderedListExtension(),
new TaskListExtension(),
new HeadingExtension(),
new HardBreakExtension(),
];

const html = String.raw; // Just for better editor support

const content = html`
<h2>Nested bullet list:</h2>

<ul>
<li>A</li>
<li>B</li>
<li>
C
<ul>
<li>C.A</li>
<li>
C.B
<ul>
<li>C.B.A</li>
<li>C.B.B</li>
<li>C.B.C</li>
</ul>
</li>
<li>C.C</li>
</ul>
</li>
</ul>
`;

export default Collapsible;
Source code
import '@remirror/styles/all.css';

import React from 'react';
import {
ExtensionTag,
findParentNode,
KeyBindingProps,
KeyBindings,
PlainExtension,
ProsemirrorNode,
} from 'remirror';
import {
BulletListExtension,
CodeExtension,
HeadingExtension,
OrderedListExtension,
TaskListExtension,
wrapSelectedItems,
} from 'remirror/extensions';
import { EditorComponent, Remirror, ThemeProvider, useRemirror } from '@remirror/react';

const extensions = () => [
new BulletListExtension(),
new OrderedListExtension(),
new TaskListExtension(),
new HeadingExtension(),
new ToggleListItemExtension(),
new CodeExtension(),
];

const html = String.raw; // Just for better editor support
const content = html`
<ul>
<li>
<p>
Press <code>Ctrl-Enter</code> (or <code>Command-Enter</code> on macOS) to toggle selected
list item type
</p>
</li>
<li>
<p>between bullet, ordered, checked, and unchecked.</p>
<ul>
<li>
<p>
You can also change a list item type by adding <code>- </code>, <code>1. </code>,
<code>[x] </code>, or <code>[ ] </code> at the begin of a list item
</p>
</li>
</ul>
</li>
</ul>
`;

const Toggleable = (): JSX.Element => {
const { manager, state } = useRemirror({
extensions: extensions,
content,
stringHandler: 'html',
});

return (
<ThemeProvider>
<Remirror manager={manager} initialContent={state}>
<EditorComponent />
</Remirror>
</ThemeProvider>
);
};

function isListItemNode(node: ProsemirrorNode): boolean {
return !!node.type.spec.group?.includes(ExtensionTag.ListItemNode);
}

class ToggleListItemExtension extends PlainExtension {
readonly name = 'toggleListItem';

createKeymap(): KeyBindings {
return {
'Mod-Enter': (props): boolean => this.toggleListType(props),
};
}

private toggleListType({ state: { schema }, tr, dispatch }: KeyBindingProps): boolean {
const foundListItem = findParentNode({
selection: tr.selection,
predicate: isListItemNode,
});

if (!foundListItem) {
return false;
}

const { node: listItem } = foundListItem;

const list = tr.doc.resolve(foundListItem.pos).parent;

// cover ordered list item to bullet list item
if (list.type.name === 'orderedList') {
wrapSelectedItems({
listType: schema.nodes.bulletList!,
itemType: schema.nodes.listItem!,
tr,
});
dispatch?.(tr);
return true;
}

// cover bullet list item to unchecked task item
else if (list.type.name === 'bulletList') {
wrapSelectedItems({
listType: schema.nodes.taskList!,
itemType: schema.nodes.taskListItem!,
tr,
});
dispatch?.(tr);
return true;
}

// cover uncheck task item to checked task item
else if (listItem.type.name === 'taskListItem' && !listItem.attrs.checked) {
this.store.commands.toggleCheckboxChecked();
return true;
}

// cover check task item to ordered list item
else if (listItem.type.name === 'taskListItem' && !!listItem.attrs.checked) {
wrapSelectedItems({
listType: schema.nodes.orderedList!,
itemType: schema.nodes.listItem!,
tr,
});
dispatch?.(tr);
return true;
}

return false;
}
}

export default Toggleable;

API​