Skip to content

Commit 12d0702

Browse files
authored
fix: improvements for checkbox behavior (#87)
1 parent 74dd90c commit 12d0702

File tree

6 files changed

+79
-47
lines changed

6 files changed

+79
-47
lines changed

src/extensions/yfm/Checkbox/CheckboxSpecs/spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,16 @@ export const getSpec = (
3030
id: {default: null},
3131
checked: {default: null},
3232
},
33+
toDOM(node) {
34+
return ['div', node.attrs];
35+
},
3336
selectable: false,
3437
allowSelection: false,
3538
complex: 'leaf',
3639
},
3740

3841
[CheckboxNode.Label]: {
39-
content: 'text*',
42+
content: 'inline*',
4043
group: 'block',
4144
parseDOM: [
4245
{

src/extensions/yfm/Checkbox/CheckboxSpecs/toYfm.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,16 @@ export const toYfm: Record<CheckboxNode, SerializerNodeToken> = {
1313
state.write(`[${checked ? 'X' : ' '}] `);
1414
},
1515

16-
[CheckboxNode.Label]: (state, node) => {
17-
if (!node.content.size || node.textContent.trim().length === 0) {
16+
[CheckboxNode.Label]: (state, node, _, idx) => {
17+
if ((!node.content.size || node.textContent.trim().length === 0) && idx !== 0) {
1818
state.write(getPlaceholderContent(node));
1919
return;
2020
}
2121

22+
if (!idx) {
23+
state.write('[ ] ');
24+
}
25+
2226
state.renderInline(node);
2327
},
2428
};

src/extensions/yfm/Checkbox/index.scss

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
display: flex;
33
align-items: center;
44

5-
margin-bottom: 15px;
6-
75
&__label {
86
display: inline-block;
97
}

src/extensions/yfm/Checkbox/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const Checkbox: ExtensionAuto<CheckboxOptions> = (builder, opts) => {
6767
});
6868

6969
builder
70-
.addPlugin(keymapPlugin)
70+
.addPlugin(keymapPlugin, builder.Priority.High)
7171
.addAction(checkboxAction, () => addCheckbox())
7272
.addInputRules(({schema}) => ({
7373
rules: [

src/extensions/yfm/Checkbox/plugin.test.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe('checkbox', () => {
3535

3636
state.selection = new TextSelection(state.doc.resolve(7));
3737

38-
const {res, tr} = applyCommand(state, splitCheckbox);
38+
const {res, tr} = applyCommand(state, splitCheckbox());
3939

4040
expect(res).toBe(true);
4141
expect(tr.doc).toMatchNode(
@@ -54,7 +54,7 @@ describe('checkbox', () => {
5454

5555
state.selection = new TextSelection(state.doc.resolve(10));
5656

57-
const {res, tr} = applyCommand(state, splitCheckbox);
57+
const {res, tr} = applyCommand(state, splitCheckbox());
5858

5959
expect(res).toBe(true);
6060
expect(tr.doc).toMatchNode(
@@ -73,10 +73,26 @@ describe('checkbox', () => {
7373

7474
state.selection = new TextSelection(state.doc.resolve(3));
7575

76-
const {res, tr} = applyCommand(state, splitCheckbox);
76+
const {res, tr} = applyCommand(state, splitCheckbox());
7777

7878
expect(res).toBe(true);
7979
expect(tr.doc).toMatchNode(doc(p()));
8080
});
81+
82+
it('should create new paragraph when argument is true', () => {
83+
const state = EditorState.create({
84+
schema,
85+
doc: doc(checkbox(checkboxInput(), checkboxLabel('text123'))),
86+
});
87+
88+
state.selection = new TextSelection(state.doc.resolve(7));
89+
90+
const {res, tr} = applyCommand(state, splitCheckbox(true));
91+
92+
expect(res).toBe(true);
93+
expect(tr.doc).toMatchNode(
94+
doc(checkbox(checkboxInput(), checkboxLabel('text')), p('123')),
95+
);
96+
});
8197
});
8298
});

src/extensions/yfm/Checkbox/plugin.ts

Lines changed: 49 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,66 +2,76 @@ import {keymap} from 'prosemirror-keymap';
22
import {Fragment} from 'prosemirror-model';
33
import {Command, TextSelection} from 'prosemirror-state';
44
import {findParentNodeOfType} from 'prosemirror-utils';
5+
import {isWholeSelection} from '../../../utils/selection';
56
import {pType} from '../../base/BaseSchema';
67
import {checkboxInputType, checkboxLabelType, checkboxType} from './utils';
78

8-
export const splitCheckbox: Command = (state, dispatch) => {
9-
const {$from, $to} = state.selection;
10-
const {schema} = state;
11-
const label = findParentNodeOfType(checkboxLabelType(schema))(state.selection);
9+
export const splitCheckbox: (replaceWithParagraph?: boolean) => Command =
10+
(replaceWithParagraph) => (state, dispatch) => {
11+
const {$from, $to} = state.selection;
12+
const {schema} = state;
13+
const label = findParentNodeOfType(checkboxLabelType(schema))(state.selection);
1214

13-
if (!label) return false;
15+
if (!label) return false;
1416

15-
const checkbox = findParentNodeOfType(checkboxType(schema))(state.selection);
17+
const checkbox = findParentNodeOfType(checkboxType(schema))(state.selection);
1618

17-
if (label.node.content.size === 0 && checkbox) {
18-
dispatch?.(
19-
state.tr
20-
.replaceWith(
21-
checkbox.pos,
22-
checkbox.pos + checkbox.node.nodeSize,
23-
pType(schema).create(),
24-
)
25-
.scrollIntoView(),
26-
);
19+
if (label.node.content.size === 0 && checkbox) {
20+
dispatch?.(
21+
state.tr
22+
.replaceWith(
23+
checkbox.pos,
24+
checkbox.pos + checkbox.node.nodeSize,
25+
pType(schema).create(),
26+
)
27+
.scrollIntoView(),
28+
);
2729

28-
return true;
29-
}
30+
return true;
31+
}
3032

31-
if ($from.pos === $to.pos) {
32-
const {tr} = state;
33+
if ($from.pos === $to.pos) {
34+
const {tr} = state;
3335

34-
const node = checkboxType(schema).create({}, [
35-
checkboxInputType(schema).create(),
36-
checkboxLabelType(schema).create(
37-
{},
38-
Fragment.from($from.parent.cut($from.parentOffset).content),
39-
),
40-
]);
36+
const content = Fragment.from($from.parent.cut($from.parentOffset).content);
4137

42-
tr.insert($from.after(), [node]);
38+
const node = replaceWithParagraph
39+
? pType(state.schema).create({}, content)
40+
: checkboxType(schema).create({}, [
41+
checkboxInputType(schema).create(),
42+
checkboxLabelType(schema).create({}, content),
43+
]);
4344

44-
tr.replace(label.start + $from.parentOffset, label.pos + label.node.nodeSize);
45-
tr.setSelection(new TextSelection(tr.doc.resolve(tr.selection.$from.after() + 4)));
46-
dispatch?.(tr);
45+
tr.insert($from.after(), [node]);
4746

48-
return true;
49-
}
47+
tr.replace(label.start + $from.parentOffset, label.pos + label.node.nodeSize);
48+
tr.setSelection(
49+
new TextSelection(
50+
tr.doc.resolve(tr.selection.$from.after() + (replaceWithParagraph ? 2 : 4)),
51+
),
52+
);
53+
dispatch?.(tr);
5054

51-
return false;
52-
};
55+
return true;
56+
}
57+
58+
return false;
59+
};
5360

5461
const removeCheckbox: Command = (state, dispatch) => {
5562
const label = findParentNodeOfType(checkboxLabelType(state.schema))(state.selection);
5663
const checkbox = findParentNodeOfType(checkboxType(state.schema))(state.selection);
5764

65+
const {selection} = state;
66+
const {from, to} = selection;
67+
5868
if (!label || !checkbox) {
5969
return false;
6070
}
6171

62-
const idx = state.selection.from - label.pos - 2;
72+
const idx = from - label.pos - 2;
6373

64-
if (idx < 0) {
74+
if (idx < 0 && from === to && !isWholeSelection(selection)) {
6575
const {tr} = state;
6676
dispatch?.(
6777
tr
@@ -80,6 +90,7 @@ const removeCheckbox: Command = (state, dispatch) => {
8090

8191
export const keymapPlugin = () =>
8292
keymap({
83-
Enter: splitCheckbox,
93+
Enter: splitCheckbox(),
8494
Backspace: removeCheckbox,
95+
'Shift-Enter': splitCheckbox(true),
8596
});

0 commit comments

Comments
 (0)