diff --git a/packages/volto-slate/news/6570.bugfix b/packages/volto-slate/news/6570.bugfix new file mode 100644 index 0000000000..9032558059 --- /dev/null +++ b/packages/volto-slate/news/6570.bugfix @@ -0,0 +1 @@ +In `RichTextWidget` and `HtmlSlateWidget`, fix breaking a list by typing Enter. @nileshgulia1 \ No newline at end of file diff --git a/packages/volto-slate/src/blocks/Text/extensions/breakListInWidget.js b/packages/volto-slate/src/blocks/Text/extensions/breakListInWidget.js new file mode 100644 index 0000000000..15b4422439 --- /dev/null +++ b/packages/volto-slate/src/blocks/Text/extensions/breakListInWidget.js @@ -0,0 +1,67 @@ +import { Editor, Range, Transforms } from 'slate'; + +import config from '@plone/volto/registry'; +import { isCursorAtBlockEnd } from '@plone/volto-slate/utils/selection'; +import { getCurrentListItem } from '@plone/volto-slate/utils/lists'; +import { createEmptyParagraph } from '@plone/volto-slate/utils/blocks'; + +export const breakListInWidget = (editor) => { + const { insertBreak } = editor; + + editor.insertBreak = () => { + if (!(editor.selection && Range.isCollapsed(editor.selection))) { + insertBreak(); + return false; + } + + const { slate } = config.settings; + const { anchor } = editor.selection; + + const ref = Editor.rangeRef(editor, editor.selection, { + affinity: 'inward', + }); + + const [listItem, listItemPath] = getCurrentListItem(editor); + if (listItem) { + if (Editor.string(editor, listItemPath)) { + Transforms.splitNodes(editor, { + at: editor.selection, + match: (node) => node.type === slate.listItemType, + always: true, + }); + + return true; + } + } + + const [parent] = Editor.parent(editor, anchor.path); + + if (parent.type !== slate.listItemType || anchor.offset > 0) { + insertBreak(); + return; + } + + Editor.deleteBackward(editor, { unit: 'line' }); + // also account for empty nodes [{text: ''}] + if (Editor.isEmpty(editor, parent)) { + Transforms.removeNodes(editor, { at: ref.current }); + + Transforms.insertNodes(editor, createEmptyParagraph(), { + at: [editor.children.length], + }); + Transforms.select(editor, Editor.end(editor, [])); + + return true; + } + + Transforms.removeNodes(editor, { at: ref.current }); + + if (isCursorAtBlockEnd(editor)) { + Editor.insertNode(editor, createEmptyParagraph()); + return true; + } + return true; + }; + + return editor; +}; diff --git a/packages/volto-slate/src/blocks/Text/extensions/index.js b/packages/volto-slate/src/blocks/Text/extensions/index.js index 06b79dfe7a..981aa3ab73 100644 --- a/packages/volto-slate/src/blocks/Text/extensions/index.js +++ b/packages/volto-slate/src/blocks/Text/extensions/index.js @@ -4,3 +4,4 @@ export * from './breakList'; export * from './withLists'; export * from './isSelected'; export * from './normalizeExternalData'; +export * from './breakListInWidget'; diff --git a/packages/volto-slate/src/blocks/Text/index.jsx b/packages/volto-slate/src/blocks/Text/index.jsx index 64c0a8017f..d8e4993e51 100644 --- a/packages/volto-slate/src/blocks/Text/index.jsx +++ b/packages/volto-slate/src/blocks/Text/index.jsx @@ -22,6 +22,7 @@ import { import { withDeleteSelectionOnEnter } from '@plone/volto-slate/editor/extensions'; import { breakList, + breakListInWidget, withDeserializers, withLists, withSplitBlocksOnBreak, @@ -47,6 +48,7 @@ export default function applyConfig(config) { breakList, normalizeExternalData, ], + slateWidgetExtensions: [breakListInWidget], // Pluggable handlers for the onKeyDown event of // Order matters here. A handler can return `true` to stop executing any diff --git a/packages/volto-slate/src/widgets/HtmlSlateWidget.jsx b/packages/volto-slate/src/widgets/HtmlSlateWidget.jsx index cdd37527ea..226d17e4e6 100644 --- a/packages/volto-slate/src/widgets/HtmlSlateWidget.jsx +++ b/packages/volto-slate/src/widgets/HtmlSlateWidget.jsx @@ -12,6 +12,7 @@ import { defineMessages, injectIntl } from 'react-intl'; import { FormFieldWrapper } from '@plone/volto/components/manage/Widgets'; import SlateEditor from '@plone/volto-slate/editor/SlateEditor'; import { serializeNodes } from '@plone/volto-slate/editor/render'; +import { handleKeyDetached } from '@plone/volto-slate/blocks/Text/keyboard'; import { makeEditor } from '@plone/volto-slate/utils/editor'; import deserialize from '@plone/volto-slate/editor/deserialize'; @@ -19,6 +20,8 @@ import { createEmptyParagraph, normalizeExternalData, } from '@plone/volto-slate/utils'; +import config from '@plone/volto/registry'; + import { ErrorBoundary } from './ErrorBoundary'; import './style.css'; @@ -44,6 +47,8 @@ const HtmlSlateWidget = (props) => { intl, } = props; + const { slateWidgetExtensions } = config.settings.slate; + const [selected, setSelected] = React.useState(focus); const editor = React.useMemo(() => makeEditor(), []); @@ -127,7 +132,10 @@ const HtmlSlateWidget = (props) => { block={block} selected={selected} properties={properties} + extensions={slateWidgetExtensions} + onKeyDown={handleKeyDetached} placeholder={placeholder} + editableProps={{ 'aria-multiline': 'true' }} /> diff --git a/packages/volto-slate/src/widgets/RichTextWidget.jsx b/packages/volto-slate/src/widgets/RichTextWidget.jsx index 6cc151ebc6..f26817fa28 100644 --- a/packages/volto-slate/src/widgets/RichTextWidget.jsx +++ b/packages/volto-slate/src/widgets/RichTextWidget.jsx @@ -7,7 +7,9 @@ import React from 'react'; import isUndefined from 'lodash/isUndefined'; import isString from 'lodash/isString'; import { FormFieldWrapper } from '@plone/volto/components/manage/Widgets'; +import { handleKeyDetached } from '@plone/volto-slate/blocks/Text/keyboard'; import SlateEditor from '@plone/volto-slate/editor/SlateEditor'; +import config from '@plone/volto/registry'; import { createEmptyParagraph, createParagraph } from '../utils/blocks'; @@ -37,6 +39,7 @@ const SlateRichTextWidget = (props) => { readOnly = false, } = props; const [selected, setSelected] = React.useState(focus); + const { slateWidgetExtensions } = config.settings.slate; return ( @@ -62,7 +65,10 @@ const SlateRichTextWidget = (props) => { block={block} selected={selected} properties={properties} + extensions={slateWidgetExtensions} + onKeyDown={handleKeyDetached} placeholder={placeholder} + editableProps={{ 'aria-multiline': 'true' }} /> diff --git a/packages/volto/cypress/tests/coresandbox/fields.js b/packages/volto/cypress/tests/coresandbox/fields.js index 14c27281b0..e5069abf78 100644 --- a/packages/volto/cypress/tests/coresandbox/fields.js +++ b/packages/volto/cypress/tests/coresandbox/fields.js @@ -105,6 +105,33 @@ context('Special fields Acceptance Tests', () => { '

hello world

', ); }); + + it('break list on empty li element', () => { + cy.intercept('PATCH', '/**/document').as('save'); + cy.getSlate().click(); + cy.get('.button .block-add-button').click({ force: true }); + cy.get('.blocks-chooser .mostUsed .button.testBlock').click(); + cy.get('#fieldset-default-field-label-html').click(); + cy.get('.slate_wysiwyg_box [contenteditable=true]') + .type('hello welcome to plone') + .scrollIntoView(); + + cy.setSlateSelection('hello'); + + cy.wait(1000); // th + cy.get('.slate-inline-toolbar').should('be.visible'); + cy.clickSlateButton('Bulleted list'); + cy.get('.slate_wysiwyg_box [contenteditable=true]').should( + 'have.descendants', + 'ul li', + ); + cy.setSlateCursor('plone').type('{enter}').type('{enter}'); + + cy.get('#toolbar-save').click(); + cy.wait('@save'); + + cy.get('.test-block').should('contain.text', '

'); + }); }); describe('ObjectListWidget', () => { diff --git a/packages/volto/news/6570.internal b/packages/volto/news/6570.internal new file mode 100644 index 0000000000..71c415192b --- /dev/null +++ b/packages/volto/news/6570.internal @@ -0,0 +1 @@ +Test(cypress): fix breaking a list by typing Enter refs- #6586 @nileshgulia1 \ No newline at end of file