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

Upmerge 8.2 #3550

Merged
merged 17 commits into from
Jun 29, 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
Binary file not shown.
Binary file not shown.
24 changes: 24 additions & 0 deletions Resources/Private/Translations/nl/Main.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,30 @@
<source>For more information about the error please refer to the JavaScript console.</source>
<target state="translated">Meer informatie over deze fout kunt u vinden in de JavaScript console.</target>
</trans-unit>
<trans-unit id="errorBoundary.copyTechnicalDetails" xml:space="preserve">
<source>Copy technical details</source>
<target state="translated">Kopieer technische details</target>
</trans-unit>
<trans-unit id="errorBoundary.technicalDetailsCopied" xml:space="preserve">
<source>Technical details copied</source>
<target state="translated">Technische details gekopieerd</target>
</trans-unit>
<trans-unit id="errorBoundary.reloadUi" xml:space="preserve">
<source>Reload Neos UI</source>
<target state="translated">Neos UI herladen</target>
</trans-unit>
<trans-unit id="errorBoundary.title" xml:space="preserve">
<source>Sorry, but the Neos UI could not recover from this error.</source>
<target state="translated">Sorry, maar de Neos UI kon niet hersteld worden van deze fout.</target>
</trans-unit>
<trans-unit id="errorBoundary.description" xml:space="preserve">
<source>Please reload the application, or contact your system administrator with the given details.</source>
<target state="translated">Herlaad u alstublieft de applicatie of geef de verschafte details door aan uw systeembeheerder.</target>
</trans-unit>
<trans-unit id="errorBoundary.footer" xml:space="preserve">
<source>For more information about the error please refer to the JavaScript console.</source>
<target state="translated">Meer informatie over deze fout kunt u vinden in de JavaScript console.</target>
</trans-unit>
</body>
</file>
</xliff>
25 changes: 25 additions & 0 deletions Tests/IntegrationTests/Fixtures/1Dimension/createNewNodes.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,31 @@ test('Can create content node from inside InlineUI', async t => {
.switchToMainWindow();
});

test('Inline CKEditor mode `paragraph: false` works as expected', async t => {
subSection('Create an inline headline node');
await t
.click(Selector('#neos-ContentTree-ToggleContentTree'))
.click(Page.treeNode.withText('Content Collection (main)'))
.click(Selector('#neos-ContentTree-AddNode'))
.click(ReactSelector('NodeTypeItem').withProps({nodeType: {label: 'Inline_Headline_Test'}}));
await Page.waitForIframeLoading(t);

subSection('Insert text into the inline text and press enter');

await Page.waitForIframeLoading(t);
await t
.switchToIframe(contentIframeSelector)
.typeText(Selector('.test-inline-headline [contenteditable="true"]'), 'Foo Bar')
.click(Selector('.test-inline-headline [contenteditable="true"]'))
.pressKey('enter')
.typeText(Selector('.test-inline-headline [contenteditable="true"]'), 'Bun Buz')
.expect(Selector('.neos-contentcollection').withText('Foo Bar').exists).ok('Inserted text exists');

await t.switchToMainWindow();
await t.wait(500); // we debounce the change
await t.expect(ReactSelector('Inspector TextAreaEditor').withProps({ value: 'Foo Bar<br>Bun Buz'}).exists).ok('The TextAreaEditor mirrors the expected value')
});

test('Supports secondary inspector view for element editors', async t => {
const SelectNodeTypeModal = ReactSelector('SelectNodeType');
await t
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'Neos.TestNodeTypes:Content.InlineHeadline':
superTypes:
'Neos.Neos:Content': true
ui:
label: Inline_Headline_Test
icon: icon-header
position: 200
inspector:
groups:
default:
label: 'Default'
position: 5
icon: 'icon-cogs'

properties:
title:
type: string
ui:
reloadIfChanged: true

inlineEditable: true
inline:
editorOptions:
autoparagraph: false
# we show it also in the inspector so we can easily see the raw text content
inspector:
group: 'default'
editor: 'Neos.Neos/Inspector/Editors/TextAreaEditor'
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
prototype(Neos.TestNodeTypes:Content.InlineHeadline) < prototype(Neos.Neos:ContentComponent) {
title = Neos.Neos:Editable {
property = 'title'
}

renderer = afx`
<h1 class="test-inline-headline">{props.title}</h1>
`
}
13 changes: 11 additions & 2 deletions packages/neos-ui-ckeditor5-bindings/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@
"description": "Prepare CKEditor5 for the Neos CMS UI",
"private": true,
"main": "./src/manifest.js",
"scripts": {
"test": "yarn jest -w 2 --coverage",
"test:watch": "yarn jest --watch"
},
"devDependencies": {
"@neos-project/babel-preset-neos-ui": "workspace:*"
"@neos-project/babel-preset-neos-ui": "workspace:*",
"@neos-project/jest-preset-neos-ui": "workspace:*",
"esbuild": "~0.17.0"
},
"dependencies": {
"@ckeditor/ckeditor5-alignment": "^16.0.0",
Expand Down Expand Up @@ -38,5 +44,8 @@
"react": "^16.12.0",
"react-redux": "^7.1.3"
},
"license": "GNU GPLv3"
"license": "GNU GPLv3",
"jest": {
"preset": "@neos-project/jest-preset-neos-ui"
}
}
21 changes: 4 additions & 17 deletions packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,7 @@
import debounce from 'lodash.debounce';
import DecoupledEditor from '@ckeditor/ckeditor5-editor-decoupled/src/decouplededitor';
import {actions} from '@neos-project/neos-ui-redux-store';

// We remove opening and closing span tags that are produced by the inlineMode plugin
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.match(/^<span>/) && content.match(/<\/span>$/)) {
return content
.replace(/^<span>/, '')
.replace(/<\/span>$/, '');
}
return content;
};
import {cleanupContentBeforeCommit} from './cleanupContentBeforeCommit'

let currentEditor = null;
let editorConfig = {};
Expand Down Expand Up @@ -48,7 +34,7 @@ export const bootstrap = _editorConfig => {
editorConfig = _editorConfig;
};

export const createEditor = store => options => {
export const createEditor = store => async options => {
const {propertyDomNode, propertyName, editorOptions, globalRegistry, userPreferences, onChange} = options;
const ckEditorConfig = editorConfig.configRegistry.getCkeditorConfig({
editorOptions,
Expand All @@ -57,7 +43,7 @@ export const createEditor = store => options => {
propertyDomNode
});

DecoupledEditor
return DecoupledEditor
.create(propertyDomNode, ckEditorConfig)
.then(editor => {
editor.ui.focusTracker.on('change:isFocused', event => {
Expand All @@ -78,6 +64,7 @@ export const createEditor = store => options => {

editor.model.document.on('change', () => handleUserInteractionCallback());
editor.model.document.on('change:data', debounce(() => onChange(cleanupContentBeforeCommit(editor.getData())), 500, {maxWait: 5000}));
return editor;
}).catch(e => {
if (e instanceof TypeError && e.message.match(/Class constructor .* cannot be invoked without 'new'/)) {
console.error("Neos.Ui: Youre probably using a CKeditor plugin which needs to be rebuild.\nsee https://github.com/neos/neos-ui/issues/3287\n\nOriginal Error:\n\n" + e.stack);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// We remove opening and closing span tags that are produced by the inlineMode plugin
/** @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
@@ -0,0 +1,36 @@
import {cleanupContentBeforeCommit} from './cleanupContentBeforeCommit'

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

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>');
})
})
22 changes: 19 additions & 3 deletions packages/neos-ui-ckeditor5-bindings/src/plugins/inlineMode.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
import Plugin from '@ckeditor/ckeditor5-core/src/plugin';

/**
* 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 <span> element
editor.conversion.elementToElement({model: 'paragraph', view: 'span', converterPriority: 'high'});
// we map paragraph model into plain <span> element in edit mode
editor.conversion.for('editingDowncast').elementToElement({model: 'paragraph', view: 'span', converterPriority: 'high'});

// We redefine enter key to great soft breaks instead of paragraphs
// 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
// 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'});

// we redefine enter key to create soft breaks (<br>) instead of new paragraphs
editor.editing.view.document.on('enter', (evt, data) => {
editor.execute('shiftEnter');
data.preventDefault();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist
56 changes: 56 additions & 0 deletions packages/neos-ui-ckeditor5-bindings/tests/manual/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
const {sep} = require('path')

const esbuild = require('esbuild');

const isWatch = process.argv.includes("--watch");

/** @type {import("esbuild").BuildOptions} */
const options = {
entryPoints: ['./index.js'],
absWorkingDir: __dirname,
outdir: './dist',
sourcemap: true,
minify: false,
logLevel: 'info',
target: 'es2020',
color: true,
bundle: true,
loader: {
'.js': 'tsx',
'.svg': 'dataurl',
'.vanilla-css': 'css',
'.woff2': 'file'
},
plugins: [
{
name: 'neos-ui-build',
setup: ({onResolve, onLoad, resolve}) => {
// exclude CKEditor styles
// the filter must match the import statement - and as one usually uses relative paths we cannot look for `@ckeditor` here
// we are currently intercepting all `/\.css/` files, as this is the most accurate way and has nearly no impact on performance
onResolve({filter: /\.css$/, namespace: 'file'}, ({path, ...options}) => {
if (!options.importer.includes(`${sep}@ckeditor${sep}`)) {
return resolve(path, {...options, namespace: 'noRecurse'})
}
return {
external: true,
sideEffects: false
}
})

// load ckeditor icons as plain text and not via `.svg: dataurl`
// (currently neccessary for the table select handle icon)
onLoad({filter: /node_modules\/@ckeditor\/.*\.svg$/}, async ({path}) => ({
contents: (await require('fs/promises').readFile(path)).toString(),
loader: 'text'
}))
}
}
],
}

if (isWatch) {
esbuild.context(options).then((ctx) => ctx.watch())
} else {
esbuild.build(options)
}
21 changes: 21 additions & 0 deletions packages/neos-ui-ckeditor5-bindings/tests/manual/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./dist/index.js" defer></script>
<title>CKEditor Manual Test</title>
</head>
<body>
<h2>Test Neos + CKEditor Version <span id="ckVersion"></span></h2>

<h3>Input</h3>
<div id="input">Insert text here</div>

<h3>Output</h3>
<pre id="output">...</pre>

<h3>Enabled Commands (via editor options)</h3>
<p>You can run them via <code>editor.execute('bold')</code> or <code>editor.execute('heading', { value: 'heading1' })</code></p>
<pre id="enabledCommands"></pre>
</body>
</html>
Loading