diff --git a/src/extensions/yfm/Checkbox/actions.ts b/src/extensions/yfm/Checkbox/actions.ts index d564476c..e32f67ce 100644 --- a/src/extensions/yfm/Checkbox/actions.ts +++ b/src/extensions/yfm/Checkbox/actions.ts @@ -1,8 +1,8 @@ import {Fragment, Node, Schema} from 'prosemirror-model'; -import {Command, TextSelection} from 'prosemirror-state'; -import {findChildrenByType, findParentNodeOfType} from 'prosemirror-utils'; +import {Command, TextSelection, Transaction} from 'prosemirror-state'; import type {ActionSpec} from '../../../core'; +import {get$Cursor, isNodeSelection} from '../../../utils/selection'; import {pType} from '../../base/BaseSchema'; import {checkboxInputType, checkboxLabelType, checkboxType} from './utils'; @@ -14,42 +14,48 @@ const createCheckbox = (schema: Schema, content?: Fragment | Node | Node[]) => ]); export const addCheckboxCmd: Command = (state, dispatch) => { - const paragraph = findParentNodeOfType(pType(state.schema))(state.selection); - const checkboxParent = findParentNodeOfType(checkboxType(state.schema))(state.selection); - const parent = paragraph || checkboxParent; - - if (!parent) return false; - - const checkboxChild = findChildrenByType(parent.node, checkboxLabelType(state.schema)); - - if (checkboxChild.length) { - if (dispatch) { - const {tr} = state; - - tr.insert(parent.pos + parent.node.nodeSize, createCheckbox(state.schema, undefined)); + function insertCheckbox(tr: Transaction, pos: number, content?: Fragment): Transaction { + tr.insert(pos, createCheckbox(state.schema, content)); + return tr.setSelection(TextSelection.create(tr.doc, pos + 3)); // move cursor inside checkbox + } - tr.setSelection(new TextSelection(tr.doc.resolve(tr.selection.$from.after() + 4))); + if (isNodeSelection(state.selection) && rootOrNonComplex(state.selection.node)) { + const pos = state.selection.to; + dispatch?.(insertCheckbox(state.tr, pos).scrollIntoView()); + return true; + } - dispatch(tr); - } + const $cursor = get$Cursor(state.selection); + if (!$cursor) return false; + const inCheckbox = + $cursor.parent.type === checkboxLabelType(state.schema) && + $cursor.node($cursor.depth - 1).type === checkboxType(state.schema); + if (inCheckbox) { + const pos = $cursor.after($cursor.depth - 1); + dispatch?.(insertCheckbox(state.tr, pos).scrollIntoView()); return true; } - const {tr} = state; - - if (dispatch) { - tr.replaceWith( - parent.pos, - parent.pos + parent.node.nodeSize, - createCheckbox(state.schema, parent.node.content), - ); + if (!rootOrNonComplex($cursor.parent)) return false; - tr.setSelection(new TextSelection(tr.doc.resolve(tr.selection.$from.after() - 1))); + if (!dispatch) return true; - dispatch?.(tr); + const {tr} = state; + const inParagraph = $cursor.parent.type === pType(state.schema); + + if (inParagraph) { + const from = $cursor.before(), + to = $cursor.after(); + // replace para with checkbox with same content + tr.replaceWith(from, to, createCheckbox(state.schema, $cursor.parent.content)); + tr.setSelection(TextSelection.create(tr.doc, $cursor.pos + 2)); // save cursor position in text + } else { + const pos = $cursor.after(); + insertCheckbox(tr, pos); } + dispatch(tr.scrollIntoView()); return true; }; @@ -59,3 +65,8 @@ export const addCheckbox = (): ActionSpec => { run: addCheckboxCmd, }; }; + +function rootOrNonComplex(node: Node): boolean { + const {complex} = node.type.spec; + return !complex || complex === 'root'; +} diff --git a/src/utils/inputrules.ts b/src/utils/inputrules.ts index 682538f2..9f1bf62e 100644 --- a/src/utils/inputrules.ts +++ b/src/utils/inputrules.ts @@ -2,7 +2,7 @@ import {InputRule} from 'prosemirror-inputrules'; import {Fragment, Mark, MarkType, Node} from 'prosemirror-model'; import {EditorState, TextSelection} from 'prosemirror-state'; -import {codeType} from '../extensions'; +import {codeType} from '../extensions/markdown/specs'; import {isFunction} from '../lodash'; import {isMarkActive} from '../utils/marks'; // TODO: remove explicit import from code extension