add node view to task item

This commit is contained in:
Philipp Kühn
2020-10-30 14:55:48 +01:00
parent cf8956bca1
commit b28a322d8b
6 changed files with 75 additions and 15 deletions

View File

@@ -39,8 +39,8 @@ export default {
], ],
content: ` content: `
<ul data-type="task_list"> <ul data-type="task_list">
<li>A list item</li> <li data-type="task_item" data-checked="true">A list item</li>
<li>And another one</li> <li data-type="task_item" data-checked="false">And another one</li>
</ul> </ul>
`, `,
}) })

View File

@@ -8,6 +8,8 @@ import { Extensions, NodeViewRenderer } from './types'
import getSchema from './utils/getSchema' import getSchema from './utils/getSchema'
import getSchemaTypeByName from './utils/getSchemaTypeByName' import getSchemaTypeByName from './utils/getSchemaTypeByName'
import splitExtensions from './utils/splitExtensions' import splitExtensions from './utils/splitExtensions'
import getAttributesFromExtensions from './utils/getAttributesFromExtensions'
import getRenderedAttributes from './utils/getRenderedAttributes'
export default class ExtensionManager { export default class ExtensionManager {
@@ -97,14 +99,17 @@ export default class ExtensionManager {
} }
get nodeViews() { get nodeViews() {
const { editor } = this
const { nodeExtensions } = splitExtensions(this.extensions) const { nodeExtensions } = splitExtensions(this.extensions)
const allAttributes = getAttributesFromExtensions(this.extensions)
return Object.fromEntries(nodeExtensions return Object.fromEntries(nodeExtensions
.filter(extension => !!extension.addNodeView) .filter(extension => !!extension.addNodeView)
.map(extension => { .map(extension => {
const extensionAttributes = allAttributes.filter(attribute => attribute.type === extension.name)
const context = { const context = {
options: extension.options, options: extension.options,
editor: this.editor, editor,
type: getSchemaTypeByName(extension.name, this.schema), type: getSchemaTypeByName(extension.name, this.schema),
} }
@@ -115,12 +120,17 @@ export default class ExtensionManager {
view: EditorView, view: EditorView,
getPos: (() => number) | boolean, getPos: (() => number) | boolean,
decorations: Decoration[], decorations: Decoration[],
) => renderer({ ) => {
editor: this.editor, const attributes = getRenderedAttributes(node, extensionAttributes)
return renderer({
editor,
node, node,
getPos, getPos,
decorations, decorations,
attributes,
}) })
}
return [extension.name, nodeview] return [extension.name, nodeview]
})) }))

View File

@@ -8,7 +8,7 @@ import getNodeType from '../utils/getNodeType'
function isList(node: Node, schema: Schema) { function isList(node: Node, schema: Schema) {
return (node.type === schema.nodes.bullet_list return (node.type === schema.nodes.bullet_list
|| node.type === schema.nodes.ordered_list || node.type === schema.nodes.ordered_list
|| node.type === schema.nodes.todo_list) || node.type === schema.nodes.task_list)
} }
export const ToggleList = createExtension({ export const ToggleList = createExtension({

View File

@@ -48,7 +48,8 @@ export type NodeViewRendererProps = {
editor: Editor, editor: Editor,
node: Node, node: Node,
getPos: (() => number) | boolean, getPos: (() => number) | boolean,
decorations: Decoration[] decorations: Decoration[],
attributes: AnyObject,
} }
export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView export type NodeViewRenderer = (props: NodeViewRendererProps) => NodeView

View File

@@ -1,8 +1,8 @@
import { Node, Mark } from 'prosemirror-model' import { Node, Mark } from 'prosemirror-model'
import { ExtensionAttribute } from '../types' import { ExtensionAttribute, AnyObject } from '../types'
import mergeAttributes from './mergeAttributes' 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 return extensionAttributes
.filter(item => item.attribute.rendered) .filter(item => item.attribute.rendered)
.map(item => { .map(item => {

View File

@@ -22,8 +22,14 @@ const TaskItem = createNode({
addAttributes() { addAttributes() {
return { return {
done: { checked: {
default: false, default: false,
parseHTML: element => ({
checked: element.getAttribute('data-checked') === 'true',
}),
renderHTML: attributes => ({
'data-checked': attributes.checked,
}),
}, },
} }
}, },
@@ -42,12 +48,55 @@ const TaskItem = createNode({
}, },
addKeyboardShortcuts() { addKeyboardShortcuts() {
return { const shortcuts = {
Enter: () => this.editor.splitListItem('task_item'), Enter: () => this.editor.splitListItem('task_item'),
}
if (!this.options.nested) {
return shortcuts
}
return {
...shortcuts,
Tab: () => this.editor.sinkListItem('task_item'), Tab: () => this.editor.sinkListItem('task_item'),
'Shift-Tab': () => this.editor.liftListItem('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 export default TaskItem