Skip to content

Commit cbcebf3

Browse files
authored
feat(Checkbox): improve checkbox insertion (#220)
New behavior of inserting checkbox: - Disabled: - when selecting any text - when cursor inside complex textblock (exclude checkboxes) - selection type is some other then TextSelection or NodeSelection - If cursor is in paragraph: replace current paragraph with checkbox with contents of paragraph - If node selection or if cursor is in another textblock or checkbox: insert new checkbox after current node, and move cursor inside new checkbox
1 parent 71813a2 commit cbcebf3

File tree

2 files changed

+40
-29
lines changed

2 files changed

+40
-29
lines changed
Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {Fragment, Node, Schema} from 'prosemirror-model';
2-
import {Command, TextSelection} from 'prosemirror-state';
3-
import {findChildrenByType, findParentNodeOfType} from 'prosemirror-utils';
2+
import {Command, TextSelection, Transaction} from 'prosemirror-state';
43

54
import type {ActionSpec} from '../../../core';
5+
import {get$Cursor, isNodeSelection} from '../../../utils/selection';
66
import {pType} from '../../base/BaseSchema';
77

88
import {checkboxInputType, checkboxLabelType, checkboxType} from './utils';
@@ -14,42 +14,48 @@ const createCheckbox = (schema: Schema, content?: Fragment | Node | Node[]) =>
1414
]);
1515

1616
export const addCheckboxCmd: Command = (state, dispatch) => {
17-
const paragraph = findParentNodeOfType(pType(state.schema))(state.selection);
18-
const checkboxParent = findParentNodeOfType(checkboxType(state.schema))(state.selection);
19-
const parent = paragraph || checkboxParent;
20-
21-
if (!parent) return false;
22-
23-
const checkboxChild = findChildrenByType(parent.node, checkboxLabelType(state.schema));
24-
25-
if (checkboxChild.length) {
26-
if (dispatch) {
27-
const {tr} = state;
28-
29-
tr.insert(parent.pos + parent.node.nodeSize, createCheckbox(state.schema, undefined));
17+
function insertCheckbox(tr: Transaction, pos: number, content?: Fragment): Transaction {
18+
tr.insert(pos, createCheckbox(state.schema, content));
19+
return tr.setSelection(TextSelection.create(tr.doc, pos + 3)); // move cursor inside checkbox
20+
}
3021

31-
tr.setSelection(new TextSelection(tr.doc.resolve(tr.selection.$from.after() + 4)));
22+
if (isNodeSelection(state.selection) && rootOrNonComplex(state.selection.node)) {
23+
const pos = state.selection.to;
24+
dispatch?.(insertCheckbox(state.tr, pos).scrollIntoView());
25+
return true;
26+
}
3227

33-
dispatch(tr);
34-
}
28+
const $cursor = get$Cursor(state.selection);
29+
if (!$cursor) return false;
3530

31+
const inCheckbox =
32+
$cursor.parent.type === checkboxLabelType(state.schema) &&
33+
$cursor.node($cursor.depth - 1).type === checkboxType(state.schema);
34+
if (inCheckbox) {
35+
const pos = $cursor.after($cursor.depth - 1);
36+
dispatch?.(insertCheckbox(state.tr, pos).scrollIntoView());
3637
return true;
3738
}
3839

39-
const {tr} = state;
40-
41-
if (dispatch) {
42-
tr.replaceWith(
43-
parent.pos,
44-
parent.pos + parent.node.nodeSize,
45-
createCheckbox(state.schema, parent.node.content),
46-
);
40+
if (!rootOrNonComplex($cursor.parent)) return false;
4741

48-
tr.setSelection(new TextSelection(tr.doc.resolve(tr.selection.$from.after() - 1)));
42+
if (!dispatch) return true;
4943

50-
dispatch?.(tr);
44+
const {tr} = state;
45+
const inParagraph = $cursor.parent.type === pType(state.schema);
46+
47+
if (inParagraph) {
48+
const from = $cursor.before(),
49+
to = $cursor.after();
50+
// replace para with checkbox with same content
51+
tr.replaceWith(from, to, createCheckbox(state.schema, $cursor.parent.content));
52+
tr.setSelection(TextSelection.create(tr.doc, $cursor.pos + 2)); // save cursor position in text
53+
} else {
54+
const pos = $cursor.after();
55+
insertCheckbox(tr, pos);
5156
}
5257

58+
dispatch(tr.scrollIntoView());
5359
return true;
5460
};
5561

@@ -59,3 +65,8 @@ export const addCheckbox = (): ActionSpec => {
5965
run: addCheckboxCmd,
6066
};
6167
};
68+
69+
function rootOrNonComplex(node: Node): boolean {
70+
const {complex} = node.type.spec;
71+
return !complex || complex === 'root';
72+
}

src/utils/inputrules.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {InputRule} from 'prosemirror-inputrules';
22
import {Fragment, Mark, MarkType, Node} from 'prosemirror-model';
33
import {EditorState, TextSelection} from 'prosemirror-state';
44

5-
import {codeType} from '../extensions';
5+
import {codeType} from '../extensions/markdown/specs';
66
import {isFunction} from '../lodash';
77
import {isMarkActive} from '../utils/marks';
88
// TODO: remove explicit import from code extension

0 commit comments

Comments
 (0)