diff --git a/packages/core/src/extensions/index.ts b/packages/core/src/extensions/index.ts index d626fdc4..caaade84 100644 --- a/packages/core/src/extensions/index.ts +++ b/packages/core/src/extensions/index.ts @@ -14,6 +14,7 @@ export { SetNodeAttributes } from './setNodeAttributes' export { SetBlockType } from './setBlockType' export { SetContent } from './setContent' export { SinkListItem } from './sinkListItem' +export { SplitBlock } from './splitBlock' export { SplitListItem } from './splitListItem' export { ToggleBlockType } from './toggleBlockType' export { ToggleList } from './toggleList' diff --git a/packages/core/src/extensions/splitBlock.ts b/packages/core/src/extensions/splitBlock.ts new file mode 100644 index 00000000..b82be5e5 --- /dev/null +++ b/packages/core/src/extensions/splitBlock.ts @@ -0,0 +1,71 @@ +// import { +// baseKeymap, chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock, +// } from 'prosemirror-commands' +import { canSplit } from 'prosemirror-transform' +import { ContentMatch, Fragment } from 'prosemirror-model' +import { NodeSelection, TextSelection } from 'prosemirror-state' +import { Command } from '../Editor' +import { createExtension } from '../Extension' +// import getNodeType from '../utils/getNodeType' + +function defaultBlockAt(match: ContentMatch) { + for (let i = 0; i < match.edgeCount; i + 1) { + const { type } = match.edge(i) + // @ts-ignore + if (type.isTextblock && !type.hasRequiredAttrs()) return type + } + return null +} + +export const SplitBlock = createExtension({ + addCommands() { + return { + splitBlock: (copyAttributes = false): Command => ({ state, dispatch }) => { + // const type = getNodeType(typeOrName, state.schema) + + const { $from, $to } = state.selection + if (state.selection instanceof NodeSelection && state.selection.node.isBlock) { + if (!$from.parentOffset || !canSplit(state.doc, $from.pos)) return false + if (dispatch) dispatch(state.tr.split($from.pos).scrollIntoView()) + return true + } + + if (!$from.parent.isBlock) return false + + if (dispatch) { + const atEnd = $to.parentOffset === $to.parent.content.size + const { tr } = state + if (state.selection instanceof TextSelection) tr.deleteSelection() + const deflt = $from.depth === 0 ? null : defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1))) + let types = atEnd && deflt ? [{ type: deflt, attrs: copyAttributes ? $from.node().attrs : {} }] : null + // let types = atEnd && deflt ? [{ type: deflt }] : null + // @ts-ignore + let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types) + // @ts-ignore + if (!types && !can && canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt && [{ type: deflt }])) { + // @ts-ignore + types = [{ type: deflt, attrs: copyAttributes ? $from.node().attrs : {} }] + // types = [{ type: deflt }] + can = true + } + if (can) { + // @ts-ignore + tr.split(tr.mapping.map($from.pos), 1, types) + if (!atEnd && !$from.parentOffset && $from.parent.type !== deflt + // @ts-ignore + && $from.node(-1).canReplace($from.index(-1), $from.indexAfter(-1), Fragment.from(deflt.create(), $from.parent))) { tr.setNodeMarkup(tr.mapping.map($from.before()), deflt) } + } + dispatch(tr.scrollIntoView()) + } + + return true + }, + } + }, +}) + +declare module '../Editor' { + interface AllExtensions { + SplitBlock: typeof SplitBlock, + } +} diff --git a/packages/core/src/plugins/index.ts b/packages/core/src/plugins/index.ts index e36ac40d..3f630521 100644 --- a/packages/core/src/plugins/index.ts +++ b/packages/core/src/plugins/index.ts @@ -1,82 +1,16 @@ import { keymap } from 'prosemirror-keymap' -import { - baseKeymap, chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock, -} from 'prosemirror-commands' -import { canSplit } from 'prosemirror-transform' +import { baseKeymap } from 'prosemirror-commands' import { dropCursor } from 'prosemirror-dropcursor' import { gapCursor } from 'prosemirror-gapcursor' import { undoInputRule } from 'prosemirror-inputrules' -import { - EditorState, NodeSelection, TextSelection, Transaction, -} from 'prosemirror-state' -import { ContentMatch, Fragment } from 'prosemirror-model' import editable from './editable' import focus from './focus' -function defaultBlockAt(match: ContentMatch) { - for (let i = 0; i < match.edgeCount; i + 1) { - const { type } = match.edge(i) - // @ts-ignore - if (type.isTextblock && !type.hasRequiredAttrs()) return type - } - return null -} - -// eslint-disable-next-line -function customSplitBlock(state: EditorState, dispatch?: (tr: Transaction) => void) { - const { $from, $to } = state.selection - if (state.selection instanceof NodeSelection && state.selection.node.isBlock) { - if (!$from.parentOffset || !canSplit(state.doc, $from.pos)) return false - if (dispatch) dispatch(state.tr.split($from.pos).scrollIntoView()) - return true - } - - if (!$from.parent.isBlock) return false - - if (dispatch) { - const atEnd = $to.parentOffset === $to.parent.content.size - const { tr } = state - if (state.selection instanceof TextSelection) tr.deleteSelection() - const deflt = $from.depth === 0 ? null : defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1))) - let types = atEnd && deflt ? [{ type: deflt, attrs: $from.node().attrs }] : null - console.log(1, { types }) - // let types = atEnd && deflt ? [{ type: deflt }] : null - // @ts-ignore - let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types) - // @ts-ignore - if (!types && !can && canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt && [{ type: deflt }])) { - // @ts-ignore - types = [{ type: deflt, attrs: $from.node().attrs }] - console.log(2, { types }) - // types = [{ type: deflt }] - can = true - } - if (can) { - // @ts-ignore - tr.split(tr.mapping.map($from.pos), 1, types) - if (!atEnd && !$from.parentOffset && $from.parent.type !== deflt - // @ts-ignore - && $from.node(-1).canReplace($from.index(-1), $from.indexAfter(-1), Fragment.from(deflt.create(), $from.parent))) { tr.setNodeMarkup(tr.mapping.map($from.before()), deflt) } - } - dispatch(tr.scrollIntoView()) - } - return true -} - export default [ () => dropCursor(), () => gapCursor(), () => keymap({ Backspace: undoInputRule }), - () => keymap({ - ...baseKeymap, - Enter: chainCommands( - newlineInCode, - createParagraphNear, - liftEmptyBlock, - splitBlock, - // customSplitBlock, - ), - }), + () => keymap(baseKeymap), editable, focus, ] diff --git a/packages/extension-text-align/index.ts b/packages/extension-text-align/index.ts index e8e2bd26..ff218a97 100644 --- a/packages/extension-text-align/index.ts +++ b/packages/extension-text-align/index.ts @@ -43,6 +43,14 @@ const TextAlign = createExtension({ }, } }, + + addKeyboardShortcuts() { + return { + // TODO: use custom splitBlock only for `this.options.types` + // TODO: use complete default enter handler (chainCommand) with custom splitBlock + Enter: () => this.editor.splitBlock(true), + } + }, }) export default TextAlign