Skip to content

Commit

Permalink
Merge pull request neos#3552 from crydotsnake/9.0
Browse files Browse the repository at this point in the history
Upmerge 9.0
  • Loading branch information
mhsdesign authored Jun 29, 2023
2 parents 4becfc1 + 8523f6e commit a6d78fe
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 20 deletions.
24 changes: 24 additions & 0 deletions Resources/Private/Translations/nl/Main.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,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.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>
<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>
</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>
`
}
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,5 +1,9 @@
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';
Expand All @@ -8,10 +12,22 @@ export default class InlineMode extends Plugin {
init() {
const {editor} = this;

// 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>
53 changes: 53 additions & 0 deletions packages/neos-ui-ckeditor5-bindings/tests/manual/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import initializeConfigRegistry from '../../src/manifest.config';
import {bootstrap, createEditor} from '../../src/ckEditorApi';

import { SynchronousRegistry, SynchronousMetaRegistry } from '@neos-project/neos-ui-extensibility';

const fakeGlobalRegistry = new SynchronousMetaRegistry();

// I18n Registry
class FakeI18NRegistry extends SynchronousRegistry {
translate(key) {
return key;
}
}
fakeGlobalRegistry.set('i18n', new FakeI18NRegistry());

const configRegistry = initializeConfigRegistry(new SynchronousRegistry());

bootstrap({
setFormattingUnderCursor: () => {
document.getElementById('enabledCommands').innerText = [...window.editor.commands.names()].join(', ')
},
setCurrentlyEditedPropertyName: () => {},
toolbarItems: [],
configRegistry
})

const fakeStore = {
dispatch: () => {}
}

const createInlineEditor = createEditor(fakeStore);

createInlineEditor({
propertyDomNode: document.getElementById('input'),
propertyName: 'test',
editorOptions: {
autoparagraph: false,
formatting: {
h1: true,
h2: true,
strong: true
}
},
globalRegistry: fakeGlobalRegistry,
userPreferences: {},
onChange: (content) => {
document.getElementById('output').innerText = content;
}
}).then(editor => {
document.getElementById('ckVersion').innerText = CKEDITOR_VERSION;

window.editor = editor
})

0 comments on commit a6d78fe

Please sign in to comment.