diff --git a/docs/src/demos/Extensions/TaskList/index.vue b/docs/src/demos/Extensions/TaskList/index.vue index bae41d95..06d1f1be 100644 --- a/docs/src/demos/Extensions/TaskList/index.vue +++ b/docs/src/demos/Extensions/TaskList/index.vue @@ -39,8 +39,8 @@ export default { ], content: ` `, }) diff --git a/packages/core/src/ExtensionManager.ts b/packages/core/src/ExtensionManager.ts index 2d27f087..23b91aac 100644 --- a/packages/core/src/ExtensionManager.ts +++ b/packages/core/src/ExtensionManager.ts @@ -8,6 +8,8 @@ import { Extensions, NodeViewRenderer } from './types' import getSchema from './utils/getSchema' import getSchemaTypeByName from './utils/getSchemaTypeByName' import splitExtensions from './utils/splitExtensions' +import getAttributesFromExtensions from './utils/getAttributesFromExtensions' +import getRenderedAttributes from './utils/getRenderedAttributes' export default class ExtensionManager { @@ -97,14 +99,17 @@ export default class ExtensionManager { } get nodeViews() { + const { editor } = this const { nodeExtensions } = splitExtensions(this.extensions) + const allAttributes = getAttributesFromExtensions(this.extensions) return Object.fromEntries(nodeExtensions .filter(extension => !!extension.addNodeView) .map(extension => { + const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name) const context = { options: extension.options, - editor: this.editor, + editor, type: getSchemaTypeByName(extension.name, this.schema), } @@ -115,12 +120,17 @@ export default class ExtensionManager { view: EditorView, getPos: (() => number) | boolean, decorations: Decoration[], - ) => renderer({ - editor: this.editor, - node, - getPos, - decorations, - }) + ) => { + const attributes = getRenderedAttributes(node, extensionAttributes) + + return renderer({ + editor, + node, + getPos, + decorations, + attributes, + }) + } return [extension.name, nodeview] })) diff --git a/packages/core/src/extensions/toggleList.ts b/packages/core/src/extensions/toggleList.ts index 2f7a15d3..26f97ea5 100644 --- a/packages/core/src/extensions/toggleList.ts +++ b/packages/core/src/extensions/toggleList.ts @@ -8,7 +8,7 @@ import getNodeType from '../utils/getNodeType' function isList(node: Node, schema: Schema) { return (node.type === schema.nodes.bullet_list || node.type === schema.nodes.ordered_list - || node.type === schema.nodes.todo_list) + || node.type === schema.nodes.task_list) } export const ToggleList = createExtension({ diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index d1385106..e0f09679 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -48,7 +48,8 @@ export type NodeViewRendererProps = { editor: Editor, node: Node, getPos: (() => number) | boolean, - decorations: Decoration[] + decorations: Decoration[], + attributes: AnyObject, } export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView diff --git a/packages/core/src/utils/getRenderedAttributes.ts b/packages/core/src/utils/getRenderedAttributes.ts index da92012d..48fd7021 100644 --- a/packages/core/src/utils/getRenderedAttributes.ts +++ b/packages/core/src/utils/getRenderedAttributes.ts @@ -1,8 +1,8 @@ import { Node, Mark } from 'prosemirror-model' -import { ExtensionAttribute } from '../types' +import { ExtensionAttribute, AnyObject } from '../types' import mergeAttributes from './mergeAttributes' -export default function getRenderedAttributes(nodeOrMark: Node | Mark, extensionAttributes: ExtensionAttribute[]): { [key: string]: any } { +export default function getRenderedAttributes(nodeOrMark: Node | Mark, extensionAttributes: ExtensionAttribute[]): AnyObject { return extensionAttributes .filter(item => item.attribute.rendered) .map(item => { diff --git a/packages/extension-task-item/index.ts b/packages/extension-task-item/index.ts index 6ac32943..283296cf 100644 --- a/packages/extension-task-item/index.ts +++ b/packages/extension-task-item/index.ts @@ -22,8 +22,14 @@ const TaskItem = createNode({ addAttributes() { return { - done: { + checked: { default: false, + parseHTML: element => ({ + checked: element.getAttribute('data-checked') === 'true', + }), + renderHTML: attributes => ({ + 'data-checked': attributes.checked, + }), }, } }, @@ -42,12 +48,55 @@ const TaskItem = createNode({ }, addKeyboardShortcuts() { - return { + const shortcuts = { Enter: () => this.editor.splitListItem('task_item'), + } + + if (!this.options.nested) { + return shortcuts + } + + return { + ...shortcuts, Tab: () => this.editor.sinkListItem('task_item'), 'Shift-Tab': () => this.editor.liftListItem('task_item'), } }, + + addNodeView() { + return ({ attributes, getPos, editor }) => { + const { view } = editor + const listItem = document.createElement('li') + const checkbox = document.createElement('input') + const content = document.createElement('div') + + checkbox.type = 'checkbox' + checkbox.addEventListener('change', event => { + const { checked } = event.target as any + + if (typeof getPos === 'function') { + view.dispatch(view.state.tr.setNodeMarkup(getPos(), undefined, { + checked, + })) + } + }) + + if (attributes['data-checked'] === true) { + checkbox.setAttribute('checked', 'checked') + } + + listItem.append(checkbox, content) + + Object.entries(attributes).forEach(([key, value]) => { + listItem.setAttribute(key, value) + }) + + return { + dom: listItem, + contentDOM: content, + } + } + }, }) export default TaskItem