From 72103e1152da39cbfcbc1082672cb62e84dd3458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=A2=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=B8=D0=BD=D1=86=D0=B5=D0=B2?= Date: Fri, 17 Mar 2023 12:42:15 +0100 Subject: [PATCH] fix: improvements for checkbox behavior --- .../yfm/Checkbox/CheckboxSpecs/spec.ts | 5 +- .../yfm/Checkbox/CheckboxSpecs/toYfm.ts | 8 +- src/extensions/yfm/Checkbox/index.scss | 2 - src/extensions/yfm/Checkbox/index.ts | 2 +- src/extensions/yfm/Checkbox/plugin.test.ts | 22 ++++- src/extensions/yfm/Checkbox/plugin.ts | 87 +++++++++++-------- 6 files changed, 79 insertions(+), 47 deletions(-) diff --git a/src/extensions/yfm/Checkbox/CheckboxSpecs/spec.ts b/src/extensions/yfm/Checkbox/CheckboxSpecs/spec.ts index 55efcd959..e305fc4a1 100644 --- a/src/extensions/yfm/Checkbox/CheckboxSpecs/spec.ts +++ b/src/extensions/yfm/Checkbox/CheckboxSpecs/spec.ts @@ -30,13 +30,16 @@ export const getSpec = ( id: {default: null}, checked: {default: null}, }, + toDOM(node) { + return ['div', node.attrs]; + }, selectable: false, allowSelection: false, complex: 'leaf', }, [CheckboxNode.Label]: { - content: 'text*', + content: 'inline*', group: 'block', parseDOM: [ { diff --git a/src/extensions/yfm/Checkbox/CheckboxSpecs/toYfm.ts b/src/extensions/yfm/Checkbox/CheckboxSpecs/toYfm.ts index 3774716f3..b7255969f 100644 --- a/src/extensions/yfm/Checkbox/CheckboxSpecs/toYfm.ts +++ b/src/extensions/yfm/Checkbox/CheckboxSpecs/toYfm.ts @@ -13,12 +13,16 @@ export const toYfm: Record = { state.write(`[${checked ? 'X' : ' '}] `); }, - [CheckboxNode.Label]: (state, node) => { - if (!node.content.size || node.textContent.trim().length === 0) { + [CheckboxNode.Label]: (state, node, _, idx) => { + if ((!node.content.size || node.textContent.trim().length === 0) && idx !== 0) { state.write(getPlaceholderContent(node)); return; } + if (!idx) { + state.write('[ ] '); + } + state.renderInline(node); }, }; diff --git a/src/extensions/yfm/Checkbox/index.scss b/src/extensions/yfm/Checkbox/index.scss index fee1a88e7..1d18277b4 100644 --- a/src/extensions/yfm/Checkbox/index.scss +++ b/src/extensions/yfm/Checkbox/index.scss @@ -2,8 +2,6 @@ display: flex; align-items: center; - margin-bottom: 15px; - &__label { display: inline-block; } diff --git a/src/extensions/yfm/Checkbox/index.ts b/src/extensions/yfm/Checkbox/index.ts index 7d67ce33f..0820c00f1 100644 --- a/src/extensions/yfm/Checkbox/index.ts +++ b/src/extensions/yfm/Checkbox/index.ts @@ -67,7 +67,7 @@ export const Checkbox: ExtensionAuto = (builder, opts) => { }); builder - .addPlugin(keymapPlugin) + .addPlugin(keymapPlugin, builder.Priority.High) .addAction(checkboxAction, () => addCheckbox()) .addInputRules(({schema}) => ({ rules: [ diff --git a/src/extensions/yfm/Checkbox/plugin.test.ts b/src/extensions/yfm/Checkbox/plugin.test.ts index 2be1881f1..9ee42d098 100644 --- a/src/extensions/yfm/Checkbox/plugin.test.ts +++ b/src/extensions/yfm/Checkbox/plugin.test.ts @@ -35,7 +35,7 @@ describe('checkbox', () => { state.selection = new TextSelection(state.doc.resolve(7)); - const {res, tr} = applyCommand(state, splitCheckbox); + const {res, tr} = applyCommand(state, splitCheckbox()); expect(res).toBe(true); expect(tr.doc).toMatchNode( @@ -54,7 +54,7 @@ describe('checkbox', () => { state.selection = new TextSelection(state.doc.resolve(10)); - const {res, tr} = applyCommand(state, splitCheckbox); + const {res, tr} = applyCommand(state, splitCheckbox()); expect(res).toBe(true); expect(tr.doc).toMatchNode( @@ -73,10 +73,26 @@ describe('checkbox', () => { state.selection = new TextSelection(state.doc.resolve(3)); - const {res, tr} = applyCommand(state, splitCheckbox); + const {res, tr} = applyCommand(state, splitCheckbox()); expect(res).toBe(true); expect(tr.doc).toMatchNode(doc(p())); }); + + it('should create new paragraph when argument is true', () => { + const state = EditorState.create({ + schema, + doc: doc(checkbox(checkboxInput(), checkboxLabel('text123'))), + }); + + state.selection = new TextSelection(state.doc.resolve(7)); + + const {res, tr} = applyCommand(state, splitCheckbox(true)); + + expect(res).toBe(true); + expect(tr.doc).toMatchNode( + doc(checkbox(checkboxInput(), checkboxLabel('text')), p('123')), + ); + }); }); }); diff --git a/src/extensions/yfm/Checkbox/plugin.ts b/src/extensions/yfm/Checkbox/plugin.ts index 5178bc7a6..dda760617 100644 --- a/src/extensions/yfm/Checkbox/plugin.ts +++ b/src/extensions/yfm/Checkbox/plugin.ts @@ -2,66 +2,76 @@ import {keymap} from 'prosemirror-keymap'; import {Fragment} from 'prosemirror-model'; import {Command, TextSelection} from 'prosemirror-state'; import {findParentNodeOfType} from 'prosemirror-utils'; +import {isWholeSelection} from '../../../utils/selection'; import {pType} from '../../base/BaseSchema'; import {checkboxInputType, checkboxLabelType, checkboxType} from './utils'; -export const splitCheckbox: Command = (state, dispatch) => { - const {$from, $to} = state.selection; - const {schema} = state; - const label = findParentNodeOfType(checkboxLabelType(schema))(state.selection); +export const splitCheckbox: (replaceWithParagraph?: boolean) => Command = + (replaceWithParagraph) => (state, dispatch) => { + const {$from, $to} = state.selection; + const {schema} = state; + const label = findParentNodeOfType(checkboxLabelType(schema))(state.selection); - if (!label) return false; + if (!label) return false; - const checkbox = findParentNodeOfType(checkboxType(schema))(state.selection); + const checkbox = findParentNodeOfType(checkboxType(schema))(state.selection); - if (label.node.content.size === 0 && checkbox) { - dispatch?.( - state.tr - .replaceWith( - checkbox.pos, - checkbox.pos + checkbox.node.nodeSize, - pType(schema).create(), - ) - .scrollIntoView(), - ); + if (label.node.content.size === 0 && checkbox) { + dispatch?.( + state.tr + .replaceWith( + checkbox.pos, + checkbox.pos + checkbox.node.nodeSize, + pType(schema).create(), + ) + .scrollIntoView(), + ); - return true; - } + return true; + } - if ($from.pos === $to.pos) { - const {tr} = state; + if ($from.pos === $to.pos) { + const {tr} = state; - const node = checkboxType(schema).create({}, [ - checkboxInputType(schema).create(), - checkboxLabelType(schema).create( - {}, - Fragment.from($from.parent.cut($from.parentOffset).content), - ), - ]); + const content = Fragment.from($from.parent.cut($from.parentOffset).content); - tr.insert($from.after(), [node]); + const node = replaceWithParagraph + ? pType(state.schema).create({}, content) + : checkboxType(schema).create({}, [ + checkboxInputType(schema).create(), + checkboxLabelType(schema).create({}, content), + ]); - tr.replace(label.start + $from.parentOffset, label.pos + label.node.nodeSize); - tr.setSelection(new TextSelection(tr.doc.resolve(tr.selection.$from.after() + 4))); - dispatch?.(tr); + tr.insert($from.after(), [node]); - return true; - } + tr.replace(label.start + $from.parentOffset, label.pos + label.node.nodeSize); + tr.setSelection( + new TextSelection( + tr.doc.resolve(tr.selection.$from.after() + (replaceWithParagraph ? 2 : 4)), + ), + ); + dispatch?.(tr); - return false; -}; + return true; + } + + return false; + }; const removeCheckbox: Command = (state, dispatch) => { const label = findParentNodeOfType(checkboxLabelType(state.schema))(state.selection); const checkbox = findParentNodeOfType(checkboxType(state.schema))(state.selection); + const {selection} = state; + const {from, to} = selection; + if (!label || !checkbox) { return false; } - const idx = state.selection.from - label.pos - 2; + const idx = from - label.pos - 2; - if (idx < 0) { + if (idx < 0 && from === to && !isWholeSelection(selection)) { const {tr} = state; dispatch?.( tr @@ -80,6 +90,7 @@ const removeCheckbox: Command = (state, dispatch) => { export const keymapPlugin = () => keymap({ - Enter: splitCheckbox, + Enter: splitCheckbox(), Backspace: removeCheckbox, + 'Shift-Enter': splitCheckbox(true), });