Neos Conference 2019: Neos + CKEditor 5 = Love

Neos Conference 2019: Neos + CKEditor 5 = Love

30c0b6f50f67163bee8500aa4115d126?s=128

Sebastian Kurfürst

May 10, 2019
Tweet

Transcript

  1. + = Neos CKEditor 5 Love @reinmarpl @skurfuerst

  2. None
  3. Our Editing Journey

  4. CKEditor 4 CKEditor 4 Editing Abstractions + APIs + User

    Interface
  5. User Interface APIs Editing Abstractions CKEditor 5 Neos User Interface

  6. Extending CKEditor

  7. I want to set a highlight class on my selection

  8. CKEditor 5 crash course

  9. Everything is a plugin

  10. class MyFeature extends Plugin { init() { // I'm a

    plugin! } } function MyFeature( editor ) { // I'm a plugin as well! }
  11. MyFeature == MyFeatureEditing + MyFeatureUI

  12. MyFeature == MyFeatureEditing + MyFeatureUI

  13. paragraph paragraph image caption This is very { bold: true

    } important. Nice view! { src: … } Model MV architecture
  14. p p figure img This is very important. { src:

    … } View strong figcaption Nice view! { class: ‘image’ } paragraph paragraph image caption This is very { bold: true } important. Nice view! { src: … } Model
  15. br p p figure img This is very important. {

    src: … } DOM strong figcaption Nice   view! { class: ‘image’ } p p figure img This is very important. { src: … } View strong figcaption Nice view! { class: ‘image’ } paragraph paragraph image caption This is very { bold: true } important. Nice view! { src: … } Model
  16. br p p figure img This is very important. {

    src: … } DOM strong figcaption Nice   view! { class: ‘image’ } p p figure img This is very important. { src: … } View strong figcaption Nice view! { class: ‘image’ } paragraph paragraph image caption This is very { bold: true } important. Nice view! { src: … } Model
  17. MyFeatureEditing == ? • Schema • Converters (model ←→ view)

    • Commands • Misc (keystrokes, observers, ...)
  18. Schema schema.register( 'myBlock', { allowIn: [ '$root', 'section' ], allowAttributes:

    'textAlignment' } ); schema.extend( '$text', { allowAttributes: 'myHighlight' } );
  19. Converters model → view • for editing • getting data

    view → model • loading data • pasting
  20. Commands action + state

  21. import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import AttributeCommand from '@ckeditor/ckeditor5-basic-styles/src/attributecommand'; export default

    class Example extends Plugin { init() { this.editor.model.schema.extend('$text', { allowAttributes: ‘highlight' }); const config = { model: 'highlight', view: { name: 'span', classes: 'example-class', styles: { 'background-color': '#00adee', } } }; this.editor.conversion.attributeToElement(config); this.editor.commands.add( ‘highlight', new AttributeCommand(this.editor, ‘highlight’) ); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
  22. Integration into Neos

  23. { "scripts": { "build": "neos-react-scripts build", "watch": "neos-react-scripts watch" },

    "devDependencies": { "@neos-project/neos-ui-extensibility": "*" }, "neos": { "buildTargetDirectory": "../../Public/CustomStylingForCkEditor" }, "dependencies": { "@ckeditor/ckeditor5-basic-styles": "^10.0.0", "@ckeditor/ckeditor5-core": "^10.0.0" } } package.json
  24. require('./manifest'); index.js

  25. UI Bootstrap Sequence Registry time UI plugins loaded plugin manifest

    backend loaded core manifest start user interface registry frozen read from registry immutable
  26. the manifest fills the registry at UI startup

  27. import manifest from '@neos-project/neos-ui-extensibility'; manifest('Neos.Neos.Ui.ExtensibilityExamples:CustomStylingForCkEditor', {}, globalRegistry => { //

    1. register CKEditor Plugin // TODO // 2. Add Button to toolbar // TODO } ); manifest.js
  28. // 1. register CKEditor Plugin, but only if the Node

    Type Formatting option is enabled const addExamplePluginToCkEditorConfiguration = (ckEditorConfiguration, options) => { if ($get(['formatting', 'Neos.Neos.Ui.ExtensibilityExamples:MyCustomSpan'], options.editorOptions)) { ckEditorConfiguration.plugins = ckEditorConfiguration.plugins || []; return $add('plugins', ExamplePlugin, ckEditorConfiguration); } return ckEditorConfiguration; }; const config = globalRegistry.get('ckEditor5').get('config'); config.set('exampleExtension', addExamplePluginToCkEditorConfiguration); manifest.js 1 2 3 4 5 6 7 8 9 10
  29. manifest.js // 2. Add Button to toolbar const richtextToolbar =

    globalRegistry.get('ckEditor5').get('richtextToolbar'); richtextToolbar.set('exampleExtension', { // the path in isActive must match the commandName, to ensure the active state // of the button automatically toggles. isActive: $get('highlightCommand'), isVisible: $get(['formatting', 'Neos.Neos.Ui.ExtensibilityExamples:MyCustomSpan']), component: ExampleButton, icon: 'plus-square', tooltip: 'Mark a span', }, 'before strong'); 1 2 3 4 5 6 7 8 9 10 11
  30. import React, {PureComponent} from 'react'; import {IconButton} from '@neos-project/react-ui-components'; import

    {executeCommand} from '@neos-project/neos-ui-ckeditor5-bindings'; export default class ExampleButton extends PureComponent { handleClick = () => { executeCommand('highlight'); }; render() { return <IconButton onClick={this.handleClick} isActive={Boolean(this.props.isActive)} icon={this.props.icon} />; } } ExampleButton.js 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
  31. ExamplePlugin.js 1 2 3 4 5 6 7 8 9

    10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; import AttributeCommand from '@ckeditor/ckeditor5-basic-styles/src/attributecommand'; export default class Example extends Plugin { init() { this.editor.model.schema.extend('$text', { allowAttributes: ‘highlight' }); const config = { model: 'highlight', view: { name: 'span', classes: 'example-class', styles: { 'background-color': '#00adee', } } }; this.editor.conversion.attributeToElement(config); this.editor.commands.add( ‘highlight', new AttributeCommand(this.editor, ‘highlight’) ); } }
  32. ExampleButton.js React ExamplePlugin.js CKEditor 5 API manifest.js package.json

  33. None
  34. basic extensibility https://github.com/neos/neos-ui-extensibility-examples CustomStylingForEditor

  35. slack.neos.io #neos-ui discuss.neos.io

  36. One more thing™

  37. Real-time collaborative editing

  38. Editing UX

  39. Neos UX issues No shared undo stack No “big selection”

    (copy paste, styling) No caret jumping from node to node 3-step “I want to insert image in the middle of this text node”
  40. ContentCollection Headline Text Image Caption Now

  41. ContentCollection Headline Text Image Editor 1 Editor 2 Editor 3

    Now
  42. Nested editor 1 ContentCollection Headline Text Image Editor 1 Better

  43. Easy wins ✅ Shared undo stack ✅ Big selection possible

    ✅ Caret jumping between nodes ✅ Easy to insert image in the middle of a text
  44. Challenges ⚠ Much closer integration ⚠ Fusion-controlled rendering ⚠ Roundtrips

    to the server (asynchronous conversion) ⚠ Transparent node boundaries
  45. How?!

  46. Caption ContentCollection Headline Text Image <p>…</p> <p>…</p> <h1>…</h1> paragraph …

    heading1 … neos-headline neos-text neos-image neos-caption Caption paragraph … Editor model
  47. Caption ContentCollection Headline Text Image <p>…</p> <p>…</p> <h1>…</h1> paragraph …

    heading1 … neos-headline neos-text neos-image neos-caption Caption paragraph … Editor model
  48. Demo time!

  49. const NodeTypes = { // ... Text: { type: ‘textBlock’,

    render() { return parse( ` <section class="node-text"> <div data-editable="main"></div> </section> ` ); } }, // ... };
  50. const NodeTypes = { // ... Image: { type: ‘objectBlock',

    render( properties ) { return parse( ` <figure class="node-image align-${ properties.align }"> <img src="${ properties.url }" alt="${ properties.alt }"> <figcaption data-editable="caption"></figcaption> </figure> ` ); } }, // ... };
  51. [ { name: 'Text', uid: 'fc48c911-f1c5-4145-a4b7-f2d11534efbb', editables: { main: `<h3>Lorem

    ipsum"</h3><p>Dolor""..."</p>` } }, { name: 'Image', uid: '74b269ab-587d-411c-b4b9-2f4cda3d74f4', editables: { caption: '<p>A photo of a kitten."</p>' }, properties: { url: 'http:"//placekitten.com/800/300', alt: 'Random kitten' } } ]
  52. None
  53. None
  54. None
  55. None
  56. None
  57. How can I help? feedback working integrations

  58. Code https://github.com/Reinmar/ckeditor5-structured-editing

  59. Conclusions it was fun! and it works!

  60. vision: offline editing

  61. it's a lot of fun!

  62. @reinmarpl @skurfuerst