diff --git a/packages/client/src/tiptap/extensions/title.tsx b/packages/client/src/tiptap/extensions/title.tsx index 452ec751..b4e74a9f 100644 --- a/packages/client/src/tiptap/extensions/title.tsx +++ b/packages/client/src/tiptap/extensions/title.tsx @@ -1,4 +1,7 @@ import { Node, mergeAttributes } from '@tiptap/core'; +import { Plugin, PluginKey } from 'prosemirror-state'; +import { isInTitle } from '../services/node'; +import { TextSelection } from 'prosemirror-state'; export interface TitleOptions { HTMLAttributes: Record; @@ -26,6 +29,7 @@ export const Title = Node.create({ }, }; }, + parseHTML() { return [ { @@ -37,4 +41,37 @@ export const Title = Node.create({ renderHTML({ HTMLAttributes }) { return ['p', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0]; }, + + addProseMirrorPlugins() { + return [ + new Plugin({ + key: new PluginKey(this.name), + props: { + handleKeyDown(view, evt) { + const { state, dispatch } = view; + + if (isInTitle(view.state) && evt.code === 'Enter') { + evt.preventDefault(); + + const paragraph = state.schema.nodes.paragraph; + + if (!paragraph) { + return; + } + + const $head = state.selection.$head; + const titleNode = $head.node($head.depth); + const insertPos = titleNode.firstChild.nodeSize + 1; + dispatch(state.tr.insert(insertPos, paragraph.create())); + + const newState = view.state; + const next = new TextSelection(newState.doc.resolve(insertPos + 1)); + dispatch(newState.tr.setSelection(next)); + return true; + } + }, + }, + }), + ]; + }, }); diff --git a/packages/client/src/tiptap/services/node.ts b/packages/client/src/tiptap/services/node.ts index 9511f4e8..949fddc2 100644 --- a/packages/client/src/tiptap/services/node.ts +++ b/packages/client/src/tiptap/services/node.ts @@ -1,4 +1,5 @@ import { Node } from 'prosemirror-model'; +import { EditorState } from 'prosemirror-state'; export function isTitleNode(node: Node): boolean { return node.type.name === 'title'; @@ -19,3 +20,12 @@ export function isTodoListNode(node: Node): boolean { export function isListNode(node: Node): boolean { return isBulletListNode(node) || isOrderedListNode(node) || isTodoListNode(node); } + +export function isInTitle(state: EditorState): boolean { + const $head = state.selection.$head; + for (let d = $head.depth; d > 0; d--) { + if ($head.node(d).type === state.schema.nodes.title) { + return true; + } + } +}