Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUGFIX: 3557 placeholder plugin with autoparagraph false #3558

Merged
merged 4 commits into from
Jul 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import debounce from 'lodash.debounce';
import DecoupledEditor from '@ckeditor/ckeditor5-editor-decoupled/src/decouplededitor';
import {actions} from '@neos-project/neos-ui-redux-store';
import {cleanupContentBeforeCommit} from './cleanupContentBeforeCommit'
import './placeholder.vanilla-css'; // eslint-disable-line no-unused-vars

let currentEditor = null;
let editorConfig = {};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,9 @@
// We remove opening and closing span tags that are produced by the inlineMode plugin

// 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[^>]*>&nbsp;<\/\1>$/)) {
return '';
}

if (content.includes('<neos-inline-wrapper>')) {
let contentWithoutOuterInlineWrapper = content;

if (content.startsWith('<neos-inline-wrapper>') && content.endsWith('</neos-inline-wrapper>')) {
contentWithoutOuterInlineWrapper = content
.replace(/^<neos-inline-wrapper>/, '')
.replace(/<\/neos-inline-wrapper>$/, '');
}

if (contentWithoutOuterInlineWrapper.includes('<neos-inline-wrapper>')) {
// 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 <neos-inline-wrapper>. We will convert all of them into spans.
return content
.replace(/<neos-inline-wrapper>/g, '<span>')
.replace(/<\/neos-inline-wrapper>/g, '</span>');
}
return contentWithoutOuterInlineWrapper;
}
return content;
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {cleanupContentBeforeCommit} from './cleanupContentBeforeCommit'
import {cleanupContentBeforeCommit} from './cleanupContentBeforeCommit';

const assertCleanedUpContent = (input, expected) => {
expect(cleanupContentBeforeCommit(input)).toBe(expected);
Expand All @@ -8,29 +8,3 @@ test('remove empty nbsp', () => {
assertCleanedUpContent('<p>&nbsp;</p>', '');
assertCleanedUpContent('<span>&nbsp;</span>', '');
})

describe('ckeditor inline mode hack, cleanup <neos-inline-wrapper>', () => {
test('noop', () => {
assertCleanedUpContent('<p></p>', '<p></p>');

assertCleanedUpContent('', '');
})

test('cleanup single <neos-inline-wrapper>', () => {
assertCleanedUpContent('<neos-inline-wrapper></neos-inline-wrapper>', '');
assertCleanedUpContent('<neos-inline-wrapper>foo</neos-inline-wrapper>', 'foo');

assertCleanedUpContent('<neos-inline-wrapper><span>foo</span></neos-inline-wrapper>', '<span>foo</span>');
})

test('cleanup multiple <neos-inline-wrapper>', () => {
assertCleanedUpContent('<neos-inline-wrapper>foo</neos-inline-wrapper><neos-inline-wrapper>bar</neos-inline-wrapper>', '<span>foo</span><span>bar</span>');

assertCleanedUpContent('<neos-inline-wrapper>foo</neos-inline-wrapper><neos-inline-wrapper>bar</neos-inline-wrapper>', '<span>foo</span><span>bar</span>');
})

test('cleanup <neos-inline-wrapper> after other root', () => {
// in the case you had multiple paragraphs and a headline and switched to autoparagrahp: false
assertCleanedUpContent('<h1>foo</h1><neos-inline-wrapper>bar</neos-inline-wrapper>', '<h1>foo</h1><span>bar</span>');
})
})
16 changes: 10 additions & 6 deletions packages/neos-ui-ckeditor5-bindings/src/manifest.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import CkEditorConfigRegistry from './registry/CkEditorConfigRegistry';
import {$add, $get, $or} from 'plow-js';
import {stripTags} from '@neos-project/utils-helpers';

import NeosPlaceholder from './plugins/neosPlaceholder';
import InlineMode from './plugins/inlineMode';
import Sub from './plugins/sub';
import Sup from './plugins/sup';
Expand Down Expand Up @@ -81,13 +81,18 @@ export default ckEditorRegistry => {

//
// Base CKE configuration
// - configuration of language
// - and placeholder feature see https://ckeditor.com/docs/ckeditor5/16.0.0/api/module_core_editor_editorconfig-EditorConfig.html#member-placeholder
//
config.set('baseConfiguration', (ckEditorConfiguration, {globalRegistry, editorOptions, userPreferences}) => {
const i18nRegistry = globalRegistry.get('i18n');
return Object.assign(ckEditorConfiguration, {
language: String($get('interfaceLanguage', userPreferences)),
neosPlaceholder: unescape(i18nRegistry.translate($get('placeholder', editorOptions) || ''))
});
const placeholder = $get('placeholder', editorOptions);
mhsdesign marked this conversation as resolved.
Show resolved Hide resolved
return {
...ckEditorConfiguration,
// stripTags, because we allow `<p>Edit text here</p>` as placeholder for legacy
placeholder: placeholder ? stripTags(i18nRegistry.translate(placeholder)) : undefined,
language: String($get('interfaceLanguage', userPreferences))
};
});

//
Expand All @@ -96,7 +101,6 @@ export default ckEditorRegistry => {
config.set('essentials', addPlugin(Essentials));
config.set('paragraph', addPlugin(Paragraph));
config.set('inlineMode', addPlugin(InlineMode, disableParagraph));
config.set('neosPlaceholder', addPlugin(NeosPlaceholder));
config.set('sub', addPlugin(Sub, $get('formatting.sub')));
config.set('sup', addPlugin(Sup, $get('formatting.sup')));
config.set('bold', addPlugin(Bold, $get('formatting.strong')));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.ck.ck-placeholder:before, .ck .ck-placeholder:before {
content: attr(data-placeholder);

/* See ckeditor/ckeditor5#469. */
pointer-events: none;

color: #999;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* 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('<neos-inline-wrapper>')) {
let contentWithoutOuterInlineWrapper = content;

if (content.startsWith('<neos-inline-wrapper>') && content.endsWith('</neos-inline-wrapper>')) {
contentWithoutOuterInlineWrapper = content
.replace(/^<neos-inline-wrapper>/, '')
.replace(/<\/neos-inline-wrapper>$/, '');
}

if (contentWithoutOuterInlineWrapper.includes('<neos-inline-wrapper>')) {
// 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 <neos-inline-wrapper>. We will convert all of them into spans.
return content
.replace(/<neos-inline-wrapper>/g, '<span>')
.replace(/<\/neos-inline-wrapper>/g, '</span>');
}
return contentWithoutOuterInlineWrapper;
}
return content;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {cleanupNeosInlineWrapper} from './cleanupNeosInlineWrapper';

const assertCleanedUpContent = (input, expected) => {
expect(cleanupNeosInlineWrapper(input)).toBe(expected);
}

describe('ckeditor inline mode hack, cleanup <neos-inline-wrapper>', () => {
test('noop', () => {
assertCleanedUpContent('<p></p>', '<p></p>');

assertCleanedUpContent('', '');
})

test('cleanup single <neos-inline-wrapper>', () => {
assertCleanedUpContent('<neos-inline-wrapper></neos-inline-wrapper>', '');
assertCleanedUpContent('<neos-inline-wrapper>foo</neos-inline-wrapper>', 'foo');

assertCleanedUpContent('<neos-inline-wrapper><span>foo</span></neos-inline-wrapper>', '<span>foo</span>');
})

test('cleanup multiple <neos-inline-wrapper>', () => {
assertCleanedUpContent('<neos-inline-wrapper>foo</neos-inline-wrapper><neos-inline-wrapper>bar</neos-inline-wrapper>', '<span>foo</span><span>bar</span>');

assertCleanedUpContent('<neos-inline-wrapper>foo</neos-inline-wrapper><neos-inline-wrapper>bar</neos-inline-wrapper>', '<span>foo</span><span>bar</span>');
})

test('cleanup <neos-inline-wrapper> after other root', () => {
// in the case you had multiple paragraphs and a headline and switched to autoparagrahp: false
assertCleanedUpContent('<h1>foo</h1><neos-inline-wrapper>bar</neos-inline-wrapper>', '<h1>foo</h1><span>bar</span>');
})
})
10 changes: 8 additions & 2 deletions packages/neos-ui-ckeditor5-bindings/src/plugins/inlineMode.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
import {cleanupNeosInlineWrapper} from './cleanupNeosInlineWrapper';

/**
* HACK, since there is yet no native support
Expand All @@ -14,8 +15,7 @@ export default class InlineMode extends Plugin {
// we map paragraph model into plain <span> 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' and strip the custom tag 'neos-inline-wrapper'
// in a hacky cleanup in cleanupContentBeforeCommit
// 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');
Expand All @@ -26,6 +26,12 @@ export default class InlineMode extends Plugin {
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 (<br>) instead of new paragraphs
editor.editing.view.document.on('enter', (evt, data) => {
editor.execute('shiftEnter');
Expand Down
75 changes: 0 additions & 75 deletions packages/neos-ui-ckeditor5-bindings/src/plugins/neosPlaceholder.js

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./dist/index.js" defer></script>
<link rel="stylesheet" href="./dist/index.css">
<title>CKEditor Manual Test</title>
</head>
<body>
Expand Down