Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/text.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMax <max@nextcloud.com>2022-02-15 10:02:07 +0300
committerMax <max@nextcloud.com>2022-03-02 15:26:11 +0300
commit0afc3236c2d773791de05d457367c73faf113ca0 (patch)
treee6c78d3228e7b915645e605f5cf414f6002a160b /src
parent69a9acce3a66b13dd7a22d2eef3b2e285ec91a71 (diff)
fix: indicator of the task list.
See #2018. Use tiptap TaskList and TaskItem. Markdown-it happily mixes tasks and bullet points in the same list. Tiptap lists are strictly separated. Split bullet and tasks into BulletList and TaskList in markdown-io. Just like this will turn into three different lists: * one - two + three This will now also turn into three different lists with the middle one being a task list: * first list * [ ] todo * [x] done * third list Signed-off-by: Max <max@nextcloud.com>
Diffstat (limited to 'src')
-rw-r--r--src/EditorFactory.js8
-rw-r--r--src/markdownit/index.js2
-rw-r--r--src/markdownit/splitMixedLists.js87
-rw-r--r--src/mixins/menubar.js7
-rw-r--r--src/nodes/BulletList.js16
-rw-r--r--src/nodes/ListItem.js203
-rw-r--r--src/nodes/TaskItem.js134
-rw-r--r--src/nodes/TaskList.js40
-rw-r--r--src/nodes/index.js6
-rw-r--r--src/tests/extensions/Markdown.spec.js8
-rw-r--r--src/tests/markdown.spec.js8
-rw-r--r--src/tests/markdownit.spec.js33
-rw-r--r--src/tests/nodes/TaskItem.spec.js (renamed from src/tests/nodes/ListItem.spec.js)12
13 files changed, 334 insertions, 230 deletions
diff --git a/src/EditorFactory.js b/src/EditorFactory.js
index af4ff61f1..808a679c2 100644
--- a/src/EditorFactory.js
+++ b/src/EditorFactory.js
@@ -28,6 +28,7 @@ import History from '@tiptap/extension-history'
import Blockquote from '@tiptap/extension-blockquote'
import Placeholder from '@tiptap/extension-placeholder'
import OrderedList from '@tiptap/extension-ordered-list'
+import ListItem from '@tiptap/extension-list-item'
import CodeBlock from '@tiptap/extension-code-block'
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight'
import HorizontalRule from '@tiptap/extension-horizontal-rule'
@@ -38,10 +39,11 @@ import { Strong, Italic, Strike, Link, Underline } from './marks'
import {
Image,
PlainTextDocument,
- ListItem,
- BulletList,
TrailingNode,
Heading,
+ BulletList,
+ TaskList,
+ TaskItem,
} from './nodes'
import { Markdown, Emoji } from './extensions'
import { translate as t } from '@nextcloud/l10n'
@@ -85,6 +87,8 @@ const createEditor = ({ content, onCreate, onUpdate, extensions, enableRichEditi
HorizontalRule,
OrderedList,
ListItem,
+ TaskList,
+ TaskItem,
Underline,
Image.configure({ currentDirectory, inline: true }),
Emoji.configure({
diff --git a/src/markdownit/index.js b/src/markdownit/index.js
index b3314fabd..aaeafa4c9 100644
--- a/src/markdownit/index.js
+++ b/src/markdownit/index.js
@@ -1,10 +1,12 @@
import MarkdownIt from 'markdown-it'
import taskLists from 'markdown-it-task-lists'
import underline from './underline'
+import splitMixedLists from './splitMixedLists'
const markdownit = MarkdownIt('commonmark', { html: false, breaks: false })
.enable('strikethrough')
.use(taskLists, { enable: true, labelAfter: true })
+ .use(splitMixedLists)
.use(underline)
export default markdownit
diff --git a/src/markdownit/splitMixedLists.js b/src/markdownit/splitMixedLists.js
new file mode 100644
index 000000000..cb709af33
--- /dev/null
+++ b/src/markdownit/splitMixedLists.js
@@ -0,0 +1,87 @@
+/*
+ * @copyright Copyright (c) 2022 Max <max@nextcloud.com>
+ *
+ * @author Max <max@nextcloud.com>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/**
+ * @param {object} md Markdown object
+ */
+export default function splitMixedLists(md) {
+ md.core.ruler.after('github-task-lists', 'split-mixed-task-lists', state => {
+ const tokens = state.tokens
+
+ for (let i = 0; i < tokens.length; i++) {
+ const token = tokens[i]
+ if (token.attrGet('class') !== 'contains-task-list') {
+ continue
+ }
+ const firstChild = tokens[i + 1]
+ const startsWithTask = firstChild.attrGet('class') === 'task-list-item'
+ if (!startsWithTask) {
+ token.attrs.splice(token.attrIndex('class'))
+ if (token.attrs.length === 0) {
+ token.attrs = null
+ }
+ }
+ const splitBefore = findChildOf(tokens, i, child => {
+ return child.nesting === 1
+ && child.attrGet('class') !== firstChild.attrGet('class')
+ })
+ if (splitBefore > i) {
+ splitListAt(tokens, splitBefore, state.Token)
+ }
+ }
+
+ return false
+ })
+}
+
+/**
+ * @param {Array} tokens - all the tokens in the doc
+ * @param {number} index - index into the tokens array where to split
+ * @param {object} TokenConstructor - constructor provided by Markdown-it
+ */
+function splitListAt(tokens, index, TokenConstructor) {
+ const closeList = new TokenConstructor('bullet_list_close', 'ul', -1)
+ closeList.block = true
+ const openList = new TokenConstructor('bullet_list_open', 'ul', 1)
+ openList.attrSet('class', 'contains-task-list')
+ openList.block = true
+ tokens.splice(index, 0, closeList, openList)
+}
+
+/**
+ * @param {Array} tokens - all the tokens in the doc
+ * @param {number} parentIndex - index of the parent in the tokens array
+ * @param {Function} predicate - test function returned child needs to pass
+ */
+function findChildOf(tokens, parentIndex, predicate) {
+ const searchLevel = tokens[parentIndex].level + 1
+ for (let i = parentIndex + 1; i < tokens.length; i++) {
+ const token = tokens[i]
+ if (token.level < searchLevel) {
+ return -1
+ }
+ if ((token.level === searchLevel) && predicate(tokens[i])) {
+ return i
+ }
+ }
+ return -1
+}
diff --git a/src/mixins/menubar.js b/src/mixins/menubar.js
index 91a8eb6d0..e9bebe69f 100644
--- a/src/mixins/menubar.js
+++ b/src/mixins/menubar.js
@@ -138,7 +138,7 @@ export default [
class: 'icon-ul',
isActive: 'bulletList',
action: (command) => {
- return command.bulletListItem()
+ return command.toggleBulletList()
},
},
{
@@ -154,9 +154,8 @@ export default [
{
label: t('text', 'ToDo list'),
class: 'icon-checkmark',
- // Do we want to indicate that the current item is a todo item?
- // isActive: ['listItem', { type: 1 }],
- action: (command) => command.todo_item(),
+ isActive: 'taskList',
+ action: (command) => command.toggleTaskList(),
},
{
label: t('text', 'Blockquote'),
diff --git a/src/nodes/BulletList.js b/src/nodes/BulletList.js
index 0e22a0ec7..4d4d0f537 100644
--- a/src/nodes/BulletList.js
+++ b/src/nodes/BulletList.js
@@ -21,16 +21,22 @@
*/
import TiptapBulletList from '@tiptap/extension-bullet-list'
+import { listInputRule } from '../commands'
-/* We want to allow for mixed lists with todo items and bullet points.
- * Therefore the list input rules are handled in the ListItem node.
- * This way we can make sure that "- [ ]" can still trigger todo list items
- * even inside a list with bullet points.
+/* We want to allow for `* [ ]` as an input rule for bullet lists.
+ * Therefore the list input rules need to check the input
+ * until the first char after the space.
+ * Only there we know the user is not trying to create a task list.
*/
const BulletList = TiptapBulletList.extend({
addInputRules() {
- return []
+ return [
+ listInputRule(
+ /^\s*([-+*])\s([^\s[])$/,
+ this.type
+ ),
+ ]
},
})
diff --git a/src/nodes/ListItem.js b/src/nodes/ListItem.js
deleted file mode 100644
index 6594929e8..000000000
--- a/src/nodes/ListItem.js
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
- *
- * @author Julius Härtl <jus@bitgrid.net>
- *
- * @license GNU AGPL version 3 or any later version
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
-
-import TipTapListItem from '@tiptap/extension-list-item'
-import { wrappingInputRule, mergeAttributes } from '@tiptap/core'
-import { Plugin } from 'prosemirror-state'
-import { findParentNode, findParentNodeClosestToPos } from 'prosemirror-utils'
-import { listInputRule } from '../commands'
-
-const TYPES = {
- BULLET: 0,
- CHECKBOX: 1,
-}
-
-const getParentList = (schema, selection) => {
- return findParentNode(function(node) {
- return node.type === schema.nodes.listItem
- })(selection)
-}
-
-const ListItem = TipTapListItem.extend({
-
- addOptions() {
- return {
- nested: true,
- }
- },
-
- addAttributes() {
- return {
- done: {
- default: false,
- },
- type: {
- default: TYPES.BULLET,
- },
- }
- },
-
- draggable: false,
-
- content: 'paragraph block*',
-
- renderHTML({ node, HTMLAttributes }) {
- if (node.attrs.type === TYPES.BULLET) {
- return ['li', HTMLAttributes, 0]
- }
- const listAttributes = { class: 'checkbox-item' }
- const checkboxAttributes = { type: 'checkbox', class: '', contenteditable: false }
- if (node.attrs.done) {
- checkboxAttributes.checked = true
- listAttributes.class += ' checked'
- }
- return [
- 'li',
- mergeAttributes(HTMLAttributes, listAttributes),
- [
- 'input',
- checkboxAttributes,
- ],
- [
- 'label',
- 0,
- ],
- ]
- },
-
- parseHTML: [
- {
- priority: 100,
- tag: 'li',
- getAttrs: el => {
- const checkbox = el.querySelector('input[type=checkbox]')
- return { done: checkbox && checkbox.checked, type: checkbox ? TYPES.CHECKBOX : TYPES.BULLET }
- },
- },
- ],
-
- toMarkdown: (state, node) => {
- if (node.attrs.type === TYPES.CHECKBOX) {
- state.write(`[${node.attrs.done ? 'x' : ' '}] `)
- }
- state.renderContent(node)
- },
-
- addCommands() {
- return {
- bulletListItem: () => ({ commands }) => {
- return commands.toggleList('bulletList', 'listItem')
- },
- // TODO: understand this, maybe fix it and / or tweak it.
- todo_item: () => ({ chain, commands, state }) => {
- const schema = state.schema
- const selection = state.selection
- const $from = selection.$from
- const $to = selection.$to
- const range = $from.blockRange($to)
-
- if (!range) {
- return false
- }
-
- let parentList = getParentList(schema, selection)
-
- const start = (typeof parentList === 'undefined')
- ? chain().bulletListItem()
- : chain()
-
- start
- .command(({ tr }) => {
- if (typeof parentList === 'undefined') {
- parentList = getParentList(schema, tr.selection)
- }
-
- if (typeof parentList === 'undefined') {
- return false
- }
- if (parentList.node.attrs.type === TYPES.CHECKBOX) {
- return commands.toggleList('bulletList', 'listItem')
- }
-
- tr.doc.nodesBetween(tr.selection.from, tr.selection.to, (node, pos) => {
- if (node.type === schema.nodes.listItem) {
- tr.setNodeMarkup(pos, node.type, {
- type: parentList.node.attrs.type === TYPES.CHECKBOX ? TYPES.BULLET : TYPES.CHECKBOX,
- })
- }
- })
- tr.scrollIntoView()
- })
- .run()
- return true
- },
- }
- },
-
- addInputRules() {
- return [
- wrappingInputRule({
- find: /^\s*([-+*])\s(\[(x|X| ?)\])\s$/,
- type: this.type,
- getAttributes: match => ({
- type: TYPES.CHECKBOX,
- done: 'xX'.includes(match[match.length - 1]),
- }),
- }),
- listInputRule(
- /^\s*([-+*])\s([^\s[])$/,
- this.type,
- _match => ({ type: TYPES.BULLET }),
- ),
- ]
- },
-
- addProseMirrorPlugins() {
- return [
- new Plugin({
- props: {
- handleClick: (view, pos, event) => {
- const state = view.state
- const schema = state.schema
-
- const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY })
- const position = state.doc.resolve(coordinates.pos)
- const parentList = findParentNodeClosestToPos(position, function(node) {
- return node.type === schema.nodes.listItem
- })
- const isListClicked = event.target.tagName.toLowerCase() === 'li'
- if (typeof parentList === 'undefined' || parentList.node.attrs.type !== TYPES.CHECKBOX || !isListClicked) {
- return
- }
-
- const tr = state.tr
- tr.setNodeMarkup(parentList.pos, schema.nodes.listItem, { done: !parentList.node.attrs.done, type: TYPES.CHECKBOX })
- view.dispatch(tr)
- },
- },
- }),
- ]
- },
-
-})
-
-export default ListItem
diff --git a/src/nodes/TaskItem.js b/src/nodes/TaskItem.js
new file mode 100644
index 000000000..74db3e82a
--- /dev/null
+++ b/src/nodes/TaskItem.js
@@ -0,0 +1,134 @@
+/*
+ * @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import TipTapTaskItem from '@tiptap/extension-task-item'
+import { wrappingInputRule, mergeAttributes } from '@tiptap/core'
+import { Plugin } from 'prosemirror-state'
+import { findParentNodeClosestToPos } from 'prosemirror-utils'
+
+const TaskItem = TipTapTaskItem.extend({
+
+ addOptions() {
+ return {
+ nested: true,
+ HTMLAttributes: {},
+ }
+ },
+
+ draggable: false,
+
+ content: 'paragraph block*',
+
+ addAttributes() {
+ const adjust = { ...this.parent() }
+ adjust.checked.parseHTML = el => {
+ return el.querySelector('input[type=checkbox]')?.checked
+ }
+ return adjust
+ },
+
+ parseHTML: [
+ {
+ priority: 101,
+ tag: 'li',
+ getAttrs: el => {
+ const checkbox = el.querySelector('input[type=checkbox]')
+ return checkbox
+ },
+ context: 'taskList/',
+ },
+ ],
+
+ renderHTML({ node, HTMLAttributes }) {
+ const listAttributes = { class: 'checkbox-item' }
+ const checkboxAttributes = { type: 'checkbox', class: '', contenteditable: false }
+ if (node.attrs.checked) {
+ checkboxAttributes.checked = true
+ listAttributes.class += ' checked'
+ }
+ return [
+ 'li',
+ mergeAttributes(HTMLAttributes, listAttributes),
+ [
+ 'input',
+ checkboxAttributes,
+ ],
+ [
+ 'label',
+ 0,
+ ],
+ ]
+ },
+
+ // overwrite the parent node view so renderHTML gets used
+ addNodeView: false,
+
+ toMarkdown: (state, node) => {
+ state.write(`[${node.attrs.checked ? 'x' : ' '}] `)
+ state.renderContent(node)
+ },
+
+ addInputRules() {
+ return [
+ ...this.parent(),
+ wrappingInputRule({
+ find: /^\s*([-+*])\s(\[(x|X| ?)\])\s$/,
+ type: this.type,
+ getAttributes: match => ({
+ checked: 'xX'.includes(match[match.length - 1]),
+ }),
+ }),
+ ]
+ },
+
+ addProseMirrorPlugins() {
+ return [
+ new Plugin({
+ props: {
+ handleClick: (view, pos, event) => {
+ const state = view.state
+ const schema = state.schema
+
+ const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY })
+ const position = state.doc.resolve(coordinates.pos)
+ const parentList = findParentNodeClosestToPos(position, function(node) {
+ return node.type === schema.nodes.taskItem
+ || node.type === schema.nodes.listItem
+ })
+ const isListClicked = event.target.tagName.toLowerCase() === 'li'
+ if (!isListClicked
+ || !parentList
+ || parentList.node.type !== schema.nodes.taskItem) {
+ return
+ }
+ const tr = state.tr
+ tr.setNodeMarkup(parentList.pos, schema.nodes.taskItem, { checked: !parentList.node.attrs.checked })
+ view.dispatch(tr)
+ },
+ },
+ }),
+ ]
+ },
+
+})
+
+export default TaskItem
diff --git a/src/nodes/TaskList.js b/src/nodes/TaskList.js
new file mode 100644
index 000000000..5bb4be796
--- /dev/null
+++ b/src/nodes/TaskList.js
@@ -0,0 +1,40 @@
+/*
+ * @copyright Copyright (c) 2020 Julius Härtl <jus@bitgrid.net>
+ *
+ * @author Julius Härtl <jus@bitgrid.net>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+import TiptapTaskList from '@tiptap/extension-task-list'
+
+const TaskList = TiptapTaskList.extend({
+
+ parseHTML: [
+ {
+ priority: 100,
+ tag: 'ul.contains-task-list',
+ },
+ ],
+
+ toMarkdown: (state, node) => {
+ state.renderList(node, ' ', () => (node.attrs.bullet || '*') + ' ')
+ },
+
+})
+
+export default TaskList
diff --git a/src/nodes/index.js b/src/nodes/index.js
index c0ebf5aac..4340f05c9 100644
--- a/src/nodes/index.js
+++ b/src/nodes/index.js
@@ -22,16 +22,18 @@
import Image from './Image'
import PlainTextDocument from './PlainTextDocument'
-import ListItem from './ListItem'
import BulletList from './BulletList'
+import TaskItem from './TaskItem'
+import TaskList from './TaskList'
import TrailingNode from './TrailingNode'
import Heading from './Heading'
export {
Image,
PlainTextDocument,
- ListItem,
BulletList,
+ TaskItem,
+ TaskList,
TrailingNode,
Heading,
}
diff --git a/src/tests/extensions/Markdown.spec.js b/src/tests/extensions/Markdown.spec.js
index d4a2b80d1..14bc77fd5 100644
--- a/src/tests/extensions/Markdown.spec.js
+++ b/src/tests/extensions/Markdown.spec.js
@@ -1,7 +1,7 @@
import { Markdown } from './../../extensions';
import { createMarkdownSerializer } from './../../extensions/Markdown';
import Underline from './../../marks/Underline';
-import { BulletList, ListItem } from './../../nodes'
+import { TaskList, TaskItem } from './../../nodes'
import Image from '@tiptap/extension-image'
import { getExtensionField } from '@tiptap/core'
import createEditor from './../createEditor'
@@ -40,11 +40,11 @@ describe('Markdown extension integrated in the editor', () => {
it('serializes nodes according to their spec', () => {
const editor = createEditor({
- content: '<p><ul><li>Hello</li></ul></p>',
- extensions: [Markdown, BulletList, ListItem],
+ content: '<p><ul class="contains-task-list"><li><input type="checkbox">Hello</li></ul></p>',
+ extensions: [Markdown, TaskList, TaskItem],
})
const serializer = createMarkdownSerializer(editor.schema)
- expect(serializer.serialize(editor.state.doc)).toBe('\n* Hello')
+ expect(serializer.serialize(editor.state.doc)).toBe('\n* [ ] Hello')
})
it('serializes nodes with the default prosemirror way', () => {
diff --git a/src/tests/markdown.spec.js b/src/tests/markdown.spec.js
index 9cbc4f46f..1dbce6097 100644
--- a/src/tests/markdown.spec.js
+++ b/src/tests/markdown.spec.js
@@ -131,9 +131,9 @@ describe('Markdown serializer from html', () => {
expect(markdownThroughEditorHtml('<p><img src="image" alt="description" /></p>')).toBe('![description](image)')
})
test('checkboxes', () => {
- expect(markdownThroughEditorHtml('<ul><li><input type="checkbox" checked /><label>foo</label></li></ul>')).toBe('* [x] foo')
- expect(markdownThroughEditorHtml('<ul><li><input type="checkbox" /><label>test</label></li></ul>')).toBe('* [ ] test')
- expect(markdownThroughEditorHtml('<ul><li><input type="checkbox" checked /><div><h2>Test</h2><p><strong>content</strong></p></div></li></ul>')).toBe('* [x] Test\n\n **content**')
- expect(markdownThroughEditorHtml('<ul><li><input type="checkbox" checked /><p>Test</p><h1>Block level headline</h1></li></ul>')).toBe('* [x] Test\n\n # Block level headline')
+ expect(markdownThroughEditorHtml('<ul class="contains-task-list"><li><input type="checkbox" checked /><label>foo</label></li></ul>')).toBe('* [x] foo')
+ expect(markdownThroughEditorHtml('<ul class="contains-task-list"><li><input type="checkbox" /><label>test</label></li></ul>')).toBe('* [ ] test')
+ expect(markdownThroughEditorHtml('<ul class="contains-task-list"><li><input type="checkbox" checked /><div><h2>Test</h2><p><strong>content</strong></p></div></li></ul>')).toBe('* [x] Test\n\n **content**')
+ expect(markdownThroughEditorHtml('<ul class="contains-task-list"><li><input type="checkbox" checked /><p>Test</p><h1>Block level headline</h1></li></ul>')).toBe('* [x] Test\n\n # Block level headline')
})
})
diff --git a/src/tests/markdownit.spec.js b/src/tests/markdownit.spec.js
new file mode 100644
index 000000000..08d5cb0a9
--- /dev/null
+++ b/src/tests/markdownit.spec.js
@@ -0,0 +1,33 @@
+import markdownit from './../markdownit'
+
+describe('markdownit', () => {
+
+ it('renders task lists', () => {
+ const rendered = markdownit.render('* [ ] task\n* not a task')
+ expect(rendered).toBe(stripIndent(`
+ <ul class="contains-task-list">
+ <li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> task</li>
+ </ul>
+ <ul>
+ <li>not a task</li>
+ </ul>
+`))
+ })
+
+ it('renders bullet and task lists separately', () => {
+ const rendered = markdownit.render('* not a task\n* [ ] task')
+ expect(rendered).toBe(stripIndent(`
+ <ul>
+ <li>not a task</li>
+ </ul>
+ <ul class="contains-task-list">
+ <li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> task</li>
+ </ul>
+`))
+ })
+
+})
+
+function stripIndent(content) {
+ return content.replace(/\t/g, '').replace('\n','')
+}
diff --git a/src/tests/nodes/ListItem.spec.js b/src/tests/nodes/TaskItem.spec.js
index f9ef09136..a1137317f 100644
--- a/src/tests/nodes/ListItem.spec.js
+++ b/src/tests/nodes/TaskItem.spec.js
@@ -1,20 +1,20 @@
-import { BulletList, ListItem } from './../../nodes'
+import { TaskList, TaskItem } from './../../nodes'
import Markdown from './../../extensions/Markdown'
import { getExtensionField } from '@tiptap/core'
import createEditor from './../createEditor'
-describe('ListItem extension', () => {
+describe('TaskItem extension', () => {
it('exposes toMarkdown function', () => {
- const toMarkdown = getExtensionField(ListItem, 'toMarkdown', ListItem)
+ const toMarkdown = getExtensionField(TaskItem, 'toMarkdown', TaskItem)
expect(typeof toMarkdown).toEqual('function')
})
it('exposes the toMarkdown function in the prosemirror schema', () => {
const editor = createEditor({
- extensions: [Markdown, BulletList, ListItem]
+ extensions: [Markdown, TaskList, TaskItem]
})
- const listItem = editor.schema.nodes.listItem
- expect(listItem.spec.toMarkdown).toBeDefined()
+ const taskItem = editor.schema.nodes.taskItem
+ expect(taskItem.spec.toMarkdown).toBeDefined()
})
})