diff --git a/.yarn/cache/@ckeditor-ckeditor5-editor-decoupled-patch-4948255868-bdaefd4a0b.zip b/.yarn/cache/@ckeditor-ckeditor5-editor-decoupled-patch-4948255868-bdaefd4a0b.zip new file mode 100644 index 0000000000..3d0898d66b Binary files /dev/null and b/.yarn/cache/@ckeditor-ckeditor5-editor-decoupled-patch-4948255868-bdaefd4a0b.zip differ diff --git a/.yarnrc.yml b/.yarnrc.yml index f53641d9a2..d41dbf526e 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -35,3 +35,5 @@ changesetIgnorePatterns: npmAlwaysAuth: false npmAuthToken: "${NPM_TOKEN-}" npmPublishAccess: "public" + +patchFolder: "./patches" diff --git a/Tests/IntegrationTests/Fixtures/1Dimension/richTextEditor.e2e.js b/Tests/IntegrationTests/Fixtures/1Dimension/richTextEditor.e2e.js index 7af31fa28c..35becede17 100644 --- a/Tests/IntegrationTests/Fixtures/1Dimension/richTextEditor.e2e.js +++ b/Tests/IntegrationTests/Fixtures/1Dimension/richTextEditor.e2e.js @@ -9,12 +9,12 @@ fixture`Rich text editor` .beforeEach(beforeEach) .afterEach(() => checkPropTypes()); -test('Can crop an image', async t => { +test('Secondary RTE editor works', async t => { const testContent = 'Test RTE content'; await Page.waitForIframeLoading(t); const rteInspectorEditor = await ReactSelector('InspectorEditorEnvelope').withProps('id', 'rte'); - const ckeContent = await Selector('.ck-content p'); + const ckeContent = await Selector('.ck-content'); await t .click(rteInspectorEditor.findReact('Button')); await t diff --git a/Tests/IntegrationTests/SharedNodeTypesPackage/NodeTypes/Content/InlineHeadline.yaml b/Tests/IntegrationTests/SharedNodeTypesPackage/NodeTypes/Content/InlineHeadline.yaml index dc8217b52f..1eb0eb5638 100644 --- a/Tests/IntegrationTests/SharedNodeTypesPackage/NodeTypes/Content/InlineHeadline.yaml +++ b/Tests/IntegrationTests/SharedNodeTypesPackage/NodeTypes/Content/InlineHeadline.yaml @@ -21,7 +21,7 @@ inlineEditable: true inline: editorOptions: - autoparagraph: false + isInline: true # we show it also in the inspector so we can easily see the raw text content inspector: group: 'default' diff --git a/package.json b/package.json index d4b48acbc2..c22807a38d 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "moment": "^2.20.1", "vfile-message": "^2.0.2", "isemail@3.2.0": "patch:isemail@npm:3.2.0#./patches/isemail-npm-3.2.0-browserified.patch", - "react-codemirror2@7.2.1": "patch:react-codemirror2@npm:7.2.1#./patches/react-codemirror2-npm-7.2.1-browserified.patch" + "react-codemirror2@7.2.1": "patch:react-codemirror2@npm:7.2.1#./patches/react-codemirror2-npm-7.2.1-browserified.patch", + "@ckeditor/ckeditor5-editor-decoupled@16.0.0": "patch:@ckeditor/ckeditor5-editor-decoupled@npm:16.0.0#./patches/@ckeditor-ckeditor5-editor-decoupled-npm-16.0.0-inline-mode.patch" }, "scripts": { "lint": "tsc --noemit && stylelint 'packages/*/src/**/*.css' && yarn eslint 'packages/*/src/**/*.{js,jsx,ts,tsx}'", diff --git a/packages/neos-ui-ckeditor5-bindings/package.json b/packages/neos-ui-ckeditor5-bindings/package.json index d410ed467f..8691059b93 100644 --- a/packages/neos-ui-ckeditor5-bindings/package.json +++ b/packages/neos-ui-ckeditor5-bindings/package.json @@ -8,7 +8,7 @@ "@ckeditor/ckeditor5-alignment": "^16.0.0", "@ckeditor/ckeditor5-basic-styles": "^16.0.0", "@ckeditor/ckeditor5-core": "^16.0.0", - "@ckeditor/ckeditor5-editor-decoupled": "^16.0.0", + "@ckeditor/ckeditor5-editor-decoupled": "16.0.0", "@ckeditor/ckeditor5-engine": "^16.0.0", "@ckeditor/ckeditor5-essentials": "^16.0.0", "@ckeditor/ckeditor5-heading": "^16.0.0", diff --git a/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js b/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js index fa7f56352a..caec7d982a 100644 --- a/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js +++ b/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js @@ -44,6 +44,16 @@ export const createEditor = store => async options => { propertyDomNode }); + const isInline = editorOptions?.isInline === true || + propertyDomNode.tagName === 'SPAN' || + propertyDomNode.tagName === 'H1' || + propertyDomNode.tagName === 'H2' || + propertyDomNode.tagName === 'H3' || + propertyDomNode.tagName === 'H4' || + propertyDomNode.tagName === 'H5' || + propertyDomNode.tagName === 'H6' || + propertyDomNode.tagName === 'P'; + class NeosEditor extends DecoupledEditor { constructor(...args) { super(...args); @@ -55,7 +65,7 @@ export const createEditor = store => async options => { } return NeosEditor - .create(propertyDomNode, ckEditorConfig) + .create(propertyDomNode, ckEditorConfig, /* this option is introduced via patch */ isInline) .then(editor => { editor.ui.focusTracker.on('change:isFocused', event => { if (event.source.isFocused) { diff --git a/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.js b/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.js index 3ce6672bcd..3ca7f3be31 100644 --- a/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.js +++ b/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.js @@ -4,20 +4,5 @@ export const cleanupContentBeforeCommit = content => { 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 96cc21e9a8..08e5603a64 100644 --- a/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.spec.js +++ b/packages/neos-ui-ckeditor5-bindings/src/cleanupContentBeforeCommit.spec.js @@ -8,27 +8,3 @@ 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 70e25db8af..f912217577 100644 --- a/packages/neos-ui-ckeditor5-bindings/src/manifest.config.js +++ b/packages/neos-ui-ckeditor5-bindings/src/manifest.config.js @@ -33,18 +33,6 @@ const addPlugin = (Plugin, isEnabled) => (ckEditorConfiguration, options) => { return ckEditorConfiguration; }; -// 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 disableAutoparagraph = (editorOptions, {propertyDomNode}) => - editorOptions?.autoparagraph === false || - propertyDomNode.tagName === 'SPAN' || - propertyDomNode.tagName === 'H1' || - propertyDomNode.tagName === 'H2' || - propertyDomNode.tagName === 'H3' || - propertyDomNode.tagName === 'H4' || - propertyDomNode.tagName === 'H5' || - propertyDomNode.tagName === 'H6'; - // // Create richtext editing toolbar registry // @@ -102,7 +90,8 @@ export default ckEditorRegistry => { // config.set('essentials', addPlugin(Essentials)); config.set('paragraph', addPlugin(Paragraph)); - config.set('disabledAutoparagraphMode', addPlugin(DisabledAutoparagraphMode, disableAutoparagraph)); + // @deprecated + config.set('disabledAutoparagraphMode', addPlugin(DisabledAutoparagraphMode, (editorOptions) => editorOptions?.autoparagraph === false)); 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/disabledAutoparagraphMode.js b/packages/neos-ui-ckeditor5-bindings/src/plugins/disabledAutoparagraphMode.js index 17d42ef41e..5956110ac5 100644 --- a/packages/neos-ui-ckeditor5-bindings/src/plugins/disabledAutoparagraphMode.js +++ b/packages/neos-ui-ckeditor5-bindings/src/plugins/disabledAutoparagraphMode.js @@ -1,10 +1,8 @@ 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 + * Legacy HACK -> our previous "inlineMode" the `autoparagraph: false` mode (from CKEditor 4) for backwards compatibility + * @deprecated in favour of the serious "inlineMode" */ export default class DisabledAutoparagraphMode extends Plugin { static get pluginName() { diff --git a/packages/neos-ui-ckeditor5-bindings/tests/manual/index.js b/packages/neos-ui-ckeditor5-bindings/tests/manual/index.js index 8b1d73e9b4..1113368d4b 100644 --- a/packages/neos-ui-ckeditor5-bindings/tests/manual/index.js +++ b/packages/neos-ui-ckeditor5-bindings/tests/manual/index.js @@ -34,7 +34,7 @@ createInlineEditor({ propertyDomNode: document.getElementById('input'), propertyName: 'test', editorOptions: { - autoparagraph: false, + isInline: true, formatting: { h1: true, h2: true, diff --git a/packages/neos-ui-editors/src/Editors/Image/index.js b/packages/neos-ui-editors/src/Editors/Image/index.js index 539b1fb24e..f94f7fa457 100644 --- a/packages/neos-ui-editors/src/Editors/Image/index.js +++ b/packages/neos-ui-editors/src/Editors/Image/index.js @@ -57,7 +57,7 @@ export default class ImageEditor extends Component { accept: PropTypes.string, siteNodePath: PropTypes.string.isRequired, - focusedNodePath: PropTypes.string.isRequired, + focusedNodePath: PropTypes.string.isRequired }; static defaultProps = { diff --git a/patches/@ckeditor-ckeditor5-editor-decoupled-npm-16.0.0-inline-mode.patch b/patches/@ckeditor-ckeditor5-editor-decoupled-npm-16.0.0-inline-mode.patch new file mode 100644 index 0000000000..0f5a8ba4f7 --- /dev/null +++ b/patches/@ckeditor-ckeditor5-editor-decoupled-npm-16.0.0-inline-mode.patch @@ -0,0 +1,64 @@ + +fixes https://github.com/neos/neos-ui/issues/3545 + +diff --git a/src/decouplededitor.js b/src/decouplededitor.js +index 807e531075bdbded8a34a15ddc5fe50370e0fd61..73afd7a95c8d6b9adf16fe48bf4774d0cfade361 100755 +--- a/src/decouplededitor.js ++++ b/src/decouplededitor.js +@@ -64,7 +64,7 @@ export default class DecoupledEditor extends Editor { + * {@link module:editor-balloon/ballooneditor~BalloonEditor.create `BalloonEditor.create()`}. + * @param {module:core/editor/editorconfig~EditorConfig} config The editor configuration. + */ +- constructor( sourceElementOrData, config ) { ++ constructor( sourceElementOrData, config, isInlineMode ) { + super( config ); + + if ( isElement( sourceElementOrData ) ) { +@@ -74,7 +74,25 @@ export default class DecoupledEditor extends Editor { + + this.data.processor = new HtmlDataProcessor(); + +- this.model.document.createRoot(); ++ if (isInlineMode === false) { ++ this.model.document.createRoot(); ++ } else { ++ // patched ala https://github.com/ckeditor/ckeditor5/issues/762#issuecomment-370762111 ++ ++ // we define paragraph as root instead of $root. This will give us no outer tags out of the box and also disable the splitting ++ this.model.document.createRoot('paragraph'); ++ ++ // it is enforced that the root cannot be splitted, but to make this obvious for other plugins we set isLimit ++ this.on('ready', () => this.model.schema.extend('paragraph', {isLimit: true})); ++ ++ // we redefine enter key to create soft breaks (
) instead of new paragraphs ++ this.editing.view.document.on('enter', (evt, data) => { ++ this.execute('shiftEnter'); ++ data.preventDefault(); ++ evt.stop(); ++ this.editing.view.scrollToTheSelection(); ++ }, {priority: 'high'}); ++ } + + const shouldToolbarGroupWhenFull = !this.config.get( 'toolbar.shouldNotGroupWhenFull' ); + const view = new DecoupledEditorUIView( this.locale, this.editing.view, { +@@ -218,9 +236,10 @@ export default class DecoupledEditor extends Editor { + * {@link module:editor-decoupled/decouplededitorui~DecoupledEditorUI#getEditableElement `editor.ui.getEditableElement()`}. + * + * @param {module:core/editor/editorconfig~EditorConfig} [config] The editor configuration. ++ * @param {Boolean} isInlineMode Patched inline mode https://github.com/ckeditor/ckeditor5/issues/762#issuecomment-370762111 + * @returns {Promise} A promise resolved once the editor is ready. The promise resolves with the created editor instance. + */ +- static create( sourceElementOrData, config = {} ) { ++ static create( sourceElementOrData, config = {}, isInlineMode ) { + return new Promise( resolve => { + const isHTMLElement = isElement( sourceElementOrData ); + +@@ -230,7 +249,7 @@ export default class DecoupledEditor extends Editor { + 'editor-wrong-element: This type of editor cannot be initialized inside