diff --git a/docs/basic-formatting.md b/docs/basic-formatting.md
index 3bf9110c..56cac949 100644
--- a/docs/basic-formatting.md
+++ b/docs/basic-formatting.md
@@ -38,7 +38,7 @@ const markdown = "> This is a quote"
## Lists
-The Lists plugin enables the usage of ordered and unordered lists, including multiple levels of nesting.
+The Lists plugin enables the usage of ordered, unordered and check lists, including multiple levels of nesting.
```tsx
diff --git a/docs/live-demo-contents.md b/docs/live-demo-contents.md
index e248a8b7..875a4866 100644
--- a/docs/live-demo-contents.md
+++ b/docs/live-demo-contents.md
@@ -14,6 +14,7 @@ In here, you can find the following markdown elements:
* Lists
* Unordered
* Ordered
+ * Check lists
* And nested ;)
* Links
* Bold/Italic/Underline formatting
diff --git a/package-lock.json b/package-lock.json
index 7fa1fe69..25a0eee6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -44,6 +44,7 @@
"mdast-util-from-markdown": "^1.3.0",
"mdast-util-frontmatter": "1.0.1",
"mdast-util-gfm-table": "^1.0.7",
+ "mdast-util-gfm-task-list-item": "1.0.2",
"mdast-util-mdx": "2.0.1",
"mdast-util-mdx-jsx": "^2.1.4",
"mdast-util-to-hast": "^12.3.0",
@@ -51,6 +52,7 @@
"micromark-extension-directive": "2.2.0",
"micromark-extension-frontmatter": "1.1.0",
"micromark-extension-gfm-table": "^1.0.6",
+ "micromark-extension-gfm-task-list-item": "1.0.5",
"micromark-extension-mdxjs": "1.0.1",
"react-hook-form": "^7.44.2",
"unidiff": "^1.0.2"
@@ -11793,7 +11795,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz",
"integrity": "sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==",
- "dev": true,
"dependencies": {
"@types/mdast": "^3.0.0",
"mdast-util-to-markdown": "^1.3.0"
@@ -12355,7 +12356,6 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz",
"integrity": "sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==",
- "dev": true,
"dependencies": {
"micromark-factory-space": "^1.0.0",
"micromark-util-character": "^1.0.0",
diff --git a/package.json b/package.json
index 6f70de32..995a3404 100644
--- a/package.json
+++ b/package.json
@@ -70,6 +70,7 @@
"mdast-util-from-markdown": "^1.3.0",
"mdast-util-frontmatter": "1.0.1",
"mdast-util-gfm-table": "^1.0.7",
+ "mdast-util-gfm-task-list-item": "1.0.2",
"mdast-util-mdx": "2.0.1",
"mdast-util-mdx-jsx": "^2.1.4",
"mdast-util-to-hast": "^12.3.0",
@@ -77,6 +78,7 @@
"micromark-extension-directive": "2.2.0",
"micromark-extension-frontmatter": "1.1.0",
"micromark-extension-gfm-table": "^1.0.6",
+ "micromark-extension-gfm-task-list-item": "1.0.5",
"micromark-extension-mdxjs": "1.0.1",
"react-hook-form": "^7.44.2",
"unidiff": "^1.0.2"
diff --git a/src/examples/basics.tsx b/src/examples/basics.tsx
index abbf9c84..af1e3319 100644
--- a/src/examples/basics.tsx
+++ b/src/examples/basics.tsx
@@ -124,6 +124,10 @@ const listsMarkdown = `
1. more
2. more
+
+* [x] Walk the dog
+* [ ] Watch movie
+* [ ] Have dinner with family
`
export function Lists() {
diff --git a/src/icons/format_list_checked.svg b/src/icons/format_list_checked.svg
new file mode 100644
index 00000000..fdfab2c4
--- /dev/null
+++ b/src/icons/format_list_checked.svg
@@ -0,0 +1,10 @@
+
diff --git a/src/plugins/lists/LexicalListItemVisitor.ts b/src/plugins/lists/LexicalListItemVisitor.ts
index 6f7a6b71..da2da14b 100644
--- a/src/plugins/lists/LexicalListItemVisitor.ts
+++ b/src/plugins/lists/LexicalListItemVisitor.ts
@@ -16,6 +16,7 @@ export const LexicalListItemVisitor: LexicalExportVisitor = {
testNode: 'listItem',
- visitNode({ actions }) {
- actions.addAndStepInto($createListItemNode())
+ visitNode({ mdastNode, actions }) {
+ actions.addAndStepInto($createListItemNode(mdastNode.checked ?? undefined))
}
}
diff --git a/src/plugins/lists/MdastListVisitor.ts b/src/plugins/lists/MdastListVisitor.ts
index 068169d7..a4d07ce6 100644
--- a/src/plugins/lists/MdastListVisitor.ts
+++ b/src/plugins/lists/MdastListVisitor.ts
@@ -6,7 +6,8 @@ import { MdastImportVisitor } from '../../importMarkdownToLexical'
export const MdastListVisitor: MdastImportVisitor = {
testNode: 'list',
visitNode: function ({ mdastNode, lexicalParent, actions }): void {
- const lexicalNode = $createListNode(mdastNode.ordered ? 'number' : 'bullet')
+ const listType = mdastNode.children?.some((e) => typeof e.checked === 'boolean') ? 'check' : mdastNode.ordered ? 'number' : 'bullet'
+ const lexicalNode = $createListNode(listType)
if ($isListItemNode(lexicalParent)) {
const dedicatedParent = $createListItemNode()
diff --git a/src/plugins/lists/index.ts b/src/plugins/lists/index.ts
index c4925893..818b94cf 100644
--- a/src/plugins/lists/index.ts
+++ b/src/plugins/lists/index.ts
@@ -5,6 +5,7 @@ import { MdastListItemVisitor } from './MdastListItemVisitor'
import { LexicalListVisitor } from './LexicalListVisitor'
import { LexicalListItemVisitor } from './LexicalListItemVisitor'
import {
+ INSERT_CHECK_LIST_COMMAND,
INSERT_ORDERED_LIST_COMMAND,
INSERT_UNORDERED_LIST_COMMAND,
ListItemNode,
@@ -16,12 +17,18 @@ import { $isRootOrShadowRoot, LexicalCommand, RangeSelection } from 'lexical'
import { $getListDepth, $isListItemNode, $isListNode } from '@lexical/list'
import { $getSelection, $isElementNode, $isRangeSelection, COMMAND_PRIORITY_CRITICAL, ElementNode, INDENT_CONTENT_COMMAND } from 'lexical'
import { TabIndentationPlugin } from '@lexical/react/LexicalTabIndentationPlugin.js'
+import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin.js'
import { ListPlugin } from '@lexical/react/LexicalListPlugin.js'
+
import { $findMatchingParent, $getNearestNodeOfType } from '@lexical/utils'
+import { gfmTaskListItem } from 'micromark-extension-gfm-task-list-item'
+import { gfmTaskListItemFromMarkdown, gfmTaskListItemToMarkdown } from 'mdast-util-gfm-task-list-item'
+
const ListTypeCommandMap = new Map>([
['number', INSERT_ORDERED_LIST_COMMAND],
['bullet', INSERT_UNORDERED_LIST_COMMAND],
+ ['check', INSERT_CHECK_LIST_COMMAND],
['', REMOVE_LIST_COMMAND]
])
@@ -82,16 +89,20 @@ export const [
systemSpec: listsSystem,
init: (realm) => {
+ realm.pubKey('addMdastExtension', gfmTaskListItemFromMarkdown)
+ realm.pubKey('addSyntaxExtension', gfmTaskListItem)
realm.pubKey('addImportVisitor', MdastListVisitor)
realm.pubKey('addImportVisitor', MdastListItemVisitor)
realm.pubKey('addLexicalNode', ListItemNode)
realm.pubKey('addLexicalNode', ListNode)
realm.pubKey('addExportVisitor', LexicalListVisitor)
realm.pubKey('addExportVisitor', LexicalListItemVisitor)
+ realm.pubKey('addToMarkdownExtension', gfmTaskListItemToMarkdown)
realm.getKeyValue('rootEditor')?.registerCommand(INDENT_CONTENT_COMMAND, () => !isIndentPermitted(7), COMMAND_PRIORITY_CRITICAL)
realm.pubKey('addComposerChild', TabIndentationPlugin)
realm.pubKey('addComposerChild', ListPlugin)
+ realm.pubKey('addComposerChild', CheckListPlugin)
}
})
diff --git a/src/plugins/markdown-shortcut/index.tsx b/src/plugins/markdown-shortcut/index.tsx
index 9bdf9ad5..bac8ca72 100644
--- a/src/plugins/markdown-shortcut/index.tsx
+++ b/src/plugins/markdown-shortcut/index.tsx
@@ -12,7 +12,8 @@ import {
ORDERED_LIST,
QUOTE,
TextFormatTransformer,
- UNORDERED_LIST
+ UNORDERED_LIST,
+ CHECK_LIST
} from '@lexical/markdown'
import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin.js'
import React from 'react'
@@ -97,7 +98,7 @@ function pickTransformersForActivePlugins(pluginIds: string[], allowedHeadingLev
transformers.push(LINK)
}
if (pluginIds.includes('lists')) {
- transformers.push(ORDERED_LIST, UNORDERED_LIST)
+ transformers.push(ORDERED_LIST, UNORDERED_LIST, CHECK_LIST)
}
if (pluginIds.includes('codeblock')) {
diff --git a/src/plugins/table/index.ts b/src/plugins/table/index.ts
index e5174e1d..6990fdff 100644
--- a/src/plugins/table/index.ts
+++ b/src/plugins/table/index.ts
@@ -18,30 +18,30 @@ type InsertTablePayload = {
columns?: number
}
-function seedTable(rows: number = 1, columns : number = 1): Mdast.Table {
+function seedTable(rows: number = 1, columns: number = 1): Mdast.Table {
const table: Mdast.Table = {
type: 'table',
children: []
- };
+ }
for (let i = 0; i < rows; i++) {
const tableRow: Mdast.TableRow = {
type: 'tableRow',
children: []
- };
+ }
for (let j = 0; j < columns; j++) {
const cell: Mdast.TableCell = {
type: 'tableCell',
children: []
- };
- tableRow.children.push(cell);
+ }
+ tableRow.children.push(cell)
}
- table.children.push(tableRow);
+ table.children.push(tableRow)
}
- return table;
+ return table
}
/** @internal */
@@ -52,7 +52,7 @@ export const tableSystem = system(
r.link(
r.pipe(
insertTable,
- r.o.map(({rows, columns}) => {
+ r.o.map(({ rows, columns }) => {
return () => $createTableNode(seedTable(rows, columns))
})
),
diff --git a/src/plugins/toolbar/components/InsertTable.tsx b/src/plugins/toolbar/components/InsertTable.tsx
index 402a0582..7aa29638 100644
--- a/src/plugins/toolbar/components/InsertTable.tsx
+++ b/src/plugins/toolbar/components/InsertTable.tsx
@@ -14,7 +14,7 @@ export const InsertTable: React.FC = () => {
{
- insertTable({rows: 3, columns: 3})
+ insertTable({ rows: 3, columns: 3 })
}}
>
diff --git a/src/plugins/toolbar/components/ListsToggle.tsx b/src/plugins/toolbar/components/ListsToggle.tsx
index 1792aa9a..815cf569 100644
--- a/src/plugins/toolbar/components/ListsToggle.tsx
+++ b/src/plugins/toolbar/components/ListsToggle.tsx
@@ -1,6 +1,7 @@
import React from 'react'
import BulletedListIcon from '../../../icons/format_list_bulleted.svg'
import NumberedListIcon from '../../../icons/format_list_numbered.svg'
+import CheckedListIcon from '../../../icons/format_list_checked.svg'
import { listsPluginHooks } from '../../lists'
import { SingleChoiceToggleGroup } from '.././primitives/toolbar'
@@ -17,7 +18,8 @@ export const ListsToggle: React.FC = () => {
value={currentListType || ''}
items={[
{ title: 'Bulleted list', contents: , value: 'bullet' },
- { title: 'Numbered list', contents: , value: 'number' }
+ { title: 'Numbered list', contents: , value: 'number' },
+ { title: 'Check list', contents: , value: 'check' }
]}
onChange={applyListType}
/>
diff --git a/src/styles/lexical-theme.module.css b/src/styles/lexical-theme.module.css
index e82a9491..a97ff826 100644
--- a/src/styles/lexical-theme.module.css
+++ b/src/styles/lexical-theme.module.css
@@ -60,6 +60,79 @@
list-style:none;
}
+.listitem {
+ margin: var(--spacing-2) 0;
+}
+
+.listItemChecked,
+.listItemUnchecked {
+ position: relative;
+ margin-left: 0;
+ margin-right: 0;
+ margin-inline-start: -1rem;
+ padding-left: var(--spacing-6);
+ padding-right: var(--spacing-6);
+ list-style-type: none;
+ outline: none;
+}
+
+.listItemChecked {
+ text-decoration: line-through;
+}
+
+.listItemUnchecked:before,
+.listItemChecked:before {
+ content: '';
+ width: var(--spacing-4);
+ height: var(--spacing-4);
+ top: 0;
+ left: 0;
+ cursor: pointer;
+ display: block;
+ background-size: cover;
+ position: absolute;
+}
+
+.listItemUnchecked[dir='rtl']:before,
+.listItemChecked[dir='rtl']:before {
+ left: auto;
+ right: 0;
+}
+
+.listItemUnchecked:focus:before,
+.listItemChecked:focus:before {
+ box-shadow: 0 0 0 2px var(--accentBgActive);
+ border-radius: var(--radius-small);
+}
+
+.listItemUnchecked:before {
+ border: 1px solid var(--baseBorder);
+ border-radius: var(--radius-small);
+}
+
+.listItemChecked:before {
+ border: 1px solid var(--accentBorder);
+ border-radius: var(--radius-small);
+ background-color: var(--accentSolid);
+ background-repeat: no-repeat;
+}
+
+.listItemChecked:after {
+ content: '';
+ cursor: pointer;
+ border-color: var(--baseBase);
+ border-style: solid;
+ position: absolute;
+ display: block;
+ top: var(--spacing-0_5);
+ width: var(--spacing-1);
+ left: var(--spacing-1_5);
+ right: var(--spacing-1_5);
+ height: var(--spacing-2);
+ transform: rotate(45deg);
+ border-width: 0 var(--spacing-0_5) var(--spacing-0_5) 0;
+}
+
.admonitionDanger, .admonitionInfo, .admonitionNote, .admonitionTip, .admonitionCaution {
padding: var(--spacing-2);
margin-top: var(--spacing-2);
diff --git a/src/styles/lexicalTheme.ts b/src/styles/lexicalTheme.ts
index fa9532a2..d939a779 100644
--- a/src/styles/lexicalTheme.ts
+++ b/src/styles/lexicalTheme.ts
@@ -14,6 +14,9 @@ export const lexicalTheme: EditorThemeClasses = {
},
list: {
+ listitem: styles.listitem,
+ listitemChecked: styles.listItemChecked,
+ listitemUnchecked: styles.listItemUnchecked,
nested: {
listitem: styles.nestedListItem
}