diff --git a/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.js b/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.js index a9b6d0c3f7..3ce6672bcd 100644 --- a/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.js +++ b/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.js @@ -1,9 +1,23 @@ - -// TODO: remove when this is fixed: https://github.com/ckeditor/ckeditor5/issues/401 /** @param {String} content */ export const cleanupContentBeforeCommit = content => { + // TODO: remove when this is fixed: https://github.com/ckeditor/ckeditor5/issues/401 if (content.match(/^<([a-z][a-z0-9]*)\b[^>]*> <\/\1>$/)) { return ''; } + + // We remove opening and closing span tags that are produced by the `DisabledAutoparagraphMode` plugin + if (content.startsWith('') && content.endsWith('')) { + const contentWithoutOuterSpan = content + .replace(/^/, '') + .replace(/<\/span>$/, ''); + + if (contentWithoutOuterSpan.includes('')) { + // In case there is still a span tag, we can be sure that the previously trimmed ones were belonging together, + // as it could be the case that multiple root paragraph/span elements were inserted into the ckeditor + // (which is currently not prevented if the html is modified from outside), so we will preserve the output. + return content; + } + return contentWithoutOuterSpan; + } return content; }; diff --git a/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.spec.js b/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.spec.js index 08e5603a64..4e2ef3ccc4 100644 --- a/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.spec.js +++ b/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.spec.js @@ -8,3 +8,28 @@ test('remove empty nbsp', () => { assertCleanedUpContent('

 

', ''); assertCleanedUpContent(' ', ''); }) + +describe('ckeditor DisabledAutoparagraphMode hack, cleanup outer spans', () => { + test('noop', () => { + assertCleanedUpContent('

', '

'); + + assertCleanedUpContent('', ''); + + assertCleanedUpContent('foo', 'foo'); + }) + + test('cleanup single root ', () => { + assertCleanedUpContent('', ''); + assertCleanedUpContent('foo', 'foo'); + }) + + + test('cleanup multiple root ', () => { + assertCleanedUpContent('foobar', 'foobar'); + }) + + test('cleanup root after other root', () => { + // in the case you had multiple paragraphs and a headline and switched to autoparagraph: false + assertCleanedUpContent('

foo

bar', '

foo

bar'); + }) +}) diff --git a/packages/neos-ui-ckeditor5-bindings/src/manifest.config.js b/packages/neos-ui-ckeditor5-bindings/src/manifest.config.js index 718fe1399a..0ef6c2391c 100644 --- a/packages/neos-ui-ckeditor5-bindings/src/manifest.config.js +++ b/packages/neos-ui-ckeditor5-bindings/src/manifest.config.js @@ -2,7 +2,7 @@ import CkEditorConfigRegistry from './registry/CkEditorConfigRegistry'; import {$add, $get, $or} from 'plow-js'; import {stripTags} from '@neos-project/utils-helpers'; -import InlineMode from './plugins/inlineMode'; +import DisabledAutoparagraphMode from './plugins/disabledAutoparagraphMode'; import Sub from './plugins/sub'; import Sup from './plugins/sup'; import LinkTargetBlank from './plugins/linkTargetBlank'; @@ -33,7 +33,7 @@ const addPlugin = (Plugin, isEnabled) => (ckEditorConfiguration, options) => { // If the editable is a span or a heading, we automatically disable paragraphs and enable the soft break mode // Also possible to force this behavior with `autoparagraph: false` -const disableParagraph = (editorOptions, {propertyDomNode}) => +const disableAutoparagraph = (editorOptions, {propertyDomNode}) => $get('autoparagraph', editorOptions) === false || propertyDomNode.tagName === 'SPAN' || propertyDomNode.tagName === 'H1' || @@ -100,7 +100,7 @@ export default ckEditorRegistry => { // config.set('essentials', addPlugin(Essentials)); config.set('paragraph', addPlugin(Paragraph)); - config.set('inlineMode', addPlugin(InlineMode, disableParagraph)); + config.set('disabledAutoparagraphMode', addPlugin(DisabledAutoparagraphMode, disableAutoparagraph)); config.set('sub', addPlugin(Sub, $get('formatting.sub'))); config.set('sup', addPlugin(Sup, $get('formatting.sup'))); config.set('bold', addPlugin(Bold, $get('formatting.strong'))); diff --git a/packages/neos-ui-ckeditor5-bindings/src/plugins/cleanupNeosInlineWrapper.js b/packages/neos-ui-ckeditor5-bindings/src/plugins/cleanupNeosInlineWrapper.js deleted file mode 100644 index 24498ce2d1..0000000000 --- a/packages/neos-ui-ckeditor5-bindings/src/plugins/cleanupNeosInlineWrapper.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * We remove opening and closing span tags that are produced by the inlineMode plugin - * - * @private only exported for testing - * @param {String} content - */ -export const cleanupNeosInlineWrapper = content => { - if (content.includes('')) { - let contentWithoutOuterInlineWrapper = content; - - if (content.startsWith('') && content.endsWith('')) { - contentWithoutOuterInlineWrapper = content - .replace(/^/, '') - .replace(/<\/neos-inline-wrapper>$/, ''); - } - - if (contentWithoutOuterInlineWrapper.includes('')) { - // in the case, multiple root paragraph elements were inserted into the ckeditor (wich is currently not prevented if the html is modified from outside) - // we have multiple root elements of type . We will convert all of them into spans. - return content - .replace(//g, '') - .replace(/<\/neos-inline-wrapper>/g, ''); - } - return contentWithoutOuterInlineWrapper; - } - return content; -}; diff --git a/packages/neos-ui-ckeditor5-bindings/src/plugins/cleanupNeosInlineWrapper.spec.js b/packages/neos-ui-ckeditor5-bindings/src/plugins/cleanupNeosInlineWrapper.spec.js deleted file mode 100644 index 4aa01d60c0..0000000000 --- a/packages/neos-ui-ckeditor5-bindings/src/plugins/cleanupNeosInlineWrapper.spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import {cleanupNeosInlineWrapper} from './cleanupNeosInlineWrapper'; - -const assertCleanedUpContent = (input, expected) => { - expect(cleanupNeosInlineWrapper(input)).toBe(expected); -} - -describe('ckeditor inline mode hack, cleanup ', () => { - test('noop', () => { - assertCleanedUpContent('

', '

'); - - assertCleanedUpContent('', ''); - }) - - test('cleanup single ', () => { - assertCleanedUpContent('', ''); - assertCleanedUpContent('foo', 'foo'); - - assertCleanedUpContent('foo', 'foo'); - }) - - test('cleanup multiple ', () => { - assertCleanedUpContent('foobar', 'foobar'); - - assertCleanedUpContent('foobar', 'foobar'); - }) - - test('cleanup after other root', () => { - // in the case you had multiple paragraphs and a headline and switched to autoparagrahp: false - assertCleanedUpContent('

foo

bar', '

foo

bar'); - }) -}) diff --git a/packages/neos-ui-ckeditor5-bindings/src/plugins/disabledAutoparagraphMode.js b/packages/neos-ui-ckeditor5-bindings/src/plugins/disabledAutoparagraphMode.js new file mode 100644 index 0000000000..dd87ff9497 --- /dev/null +++ b/packages/neos-ui-ckeditor5-bindings/src/plugins/disabledAutoparagraphMode.js @@ -0,0 +1,27 @@ +import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; + +/** + * HACK, since there is yet no native support for CKEditor 4 autoparagraph false + * see https://github.com/ckeditor/ckeditor5/issues/762 + * + * We will try to find a serious alternative see https://github.com/neos/neos-ui/pull/3553 + */ +export default class DisabledAutoparagraphMode extends Plugin { + static get pluginName() { + return 'DisabledAutoparagraphMode'; + } + init() { + const editor = this.editor; + + // we map paragraph model into plain element + editor.conversion.for('downcast').elementToElement({model: 'paragraph', view: 'span', converterPriority: 'high'}); + + // we redefine enter key to create soft breaks (
) instead of new paragraphs + editor.editing.view.document.on('enter', (evt, data) => { + editor.execute('shiftEnter'); + data.preventDefault(); + evt.stop(); + editor.editing.view.scrollToTheSelection(); + }, {priority: 'high'}); + } +} diff --git a/packages/neos-ui-ckeditor5-bindings/src/plugins/inlineMode.js b/packages/neos-ui-ckeditor5-bindings/src/plugins/inlineMode.js deleted file mode 100644 index 17b749d0d5..0000000000 --- a/packages/neos-ui-ckeditor5-bindings/src/plugins/inlineMode.js +++ /dev/null @@ -1,43 +0,0 @@ -import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; -import {cleanupNeosInlineWrapper} from './cleanupNeosInlineWrapper'; - -/** - * HACK, since there is yet no native support - * see https://github.com/ckeditor/ckeditor5/issues/762 - */ -export default class InlineMode extends Plugin { - static get pluginName() { - return 'InlineMode'; - } - init() { - const editor = this.editor; - - // we map paragraph model into plain element in edit mode - editor.conversion.for('editingDowncast').elementToElement({model: 'paragraph', view: 'span', converterPriority: 'high'}); - - // to avoid having a wrapping "span" tag, we will convert the outmost 'paragraph' ... - // see https://neos-project.slack.com/archives/C07QEQ1U2/p1687952441254759 - i could find a better solution - editor.conversion.for('dataDowncast').elementToElement({model: 'paragraph', view: ( modelElement, viewWriter ) => { - const parentIsRoot = modelElement.parent.is('$root'); - const hasAttributes = [...modelElement.getAttributes()].length !== 0; - if (!parentIsRoot || hasAttributes) { - return viewWriter.createContainerElement('span'); - } - return viewWriter.createContainerElement('neos-inline-wrapper'); - }, converterPriority: 'high'}); - - // ... and strip the custom tag 'neos-inline-wrapper' in a hacky cleanup in cleanupData - editor.data.decorate('get'); - editor.data.on('get', (event) => { - event.return = cleanupNeosInlineWrapper(event.return) - }); - - // we redefine enter key to create soft breaks (
) instead of new paragraphs - editor.editing.view.document.on('enter', (evt, data) => { - editor.execute('shiftEnter'); - data.preventDefault(); - evt.stop(); - editor.editing.view.scrollToTheSelection(); - }, {priority: 'high'}); - } -}