Browse Source

Merge branch 'tut/inline-widget' into stable

Docs: Added two new guides 🎉.
Piotrek Koszuliński 6 years ago
parent
commit
5dbe1c7bf9

BIN
docs/assets/img/tutorial-implementing-a-widget-1.png


BIN
docs/assets/img/tutorial-implementing-a-widget-2.png


BIN
docs/assets/img/tutorial-implementing-a-widget-3.png


BIN
docs/assets/img/tutorial-implementing-a-widget-4.png


BIN
docs/assets/img/tutorial-implementing-a-widget-4b.png


BIN
docs/assets/img/tutorial-implementing-a-widget-5.png


BIN
docs/assets/img/tutorial-implementing-a-widget-6.png


BIN
docs/assets/img/tutorial-implementing-a-widget-7.png


BIN
docs/assets/img/tutorial-implementing-an-inline-widget-1.png


BIN
docs/assets/img/tutorial-implementing-an-inline-widget-2.png


+ 2 - 1
docs/framework/guides/quick-start.md

@@ -66,6 +66,7 @@ module.exports = {
 				// Or /ckeditor5-[^/]+\/theme\/[^/]+\.css$/ if you want to limit this loader
 				// to CKEditor 5 theme only.
 				test: /\.css$/,
+
 				use: [
 					{
 						loader: 'style-loader',
@@ -81,7 +82,7 @@ module.exports = {
 							},
 							minify: true
 						} )
-					},
+					}
 				]
 			}
 		]

+ 970 - 0
docs/framework/guides/tutorials/implementing-a-block-widget.md

@@ -0,0 +1,970 @@
+---
+category: framework-tutorials
+order: 10
+---
+
+# Implementing a block widget
+
+In this tutorial you will learn how to implement a more complex CKEditor 5 plugin. We will build a "Simple box" feature which will allow the user to insert a custom box with a title and body fields into the document. We will use the widget utils and work with the model-view conversion in order to properly setup the behavior of this feature. Later on, we will create a UI which will allow to insert new simple boxes into the document via the toolbar button.
+
+<!-- TODO: and allow controlling simple box properties such as alignment and width. -->
+
+## Before you start
+
+While it is not strictly necessary to read the {@link framework/guides/quick-start Quick start} guide before going through this tutorial, it may help you to get more comfortable with CKEditor 5 framework before you will dive into this tutorial.
+
+We will also reference various parts of the {@link framework/guides/architecture/intro CKEditor 5 architecture} section as we go. While reading them is not necessary to finish this tutorial, we recommend reading those guides at some point to get a better understanding of the mechanisms used in this tutorial.
+
+## Let's start
+
+This guide assumes that you are familiar with npm and your project uses npm already. If not, see the [npm documentation](https://docs.npmjs.com/getting-started/what-is-npm) or call `npm init` in an empty directory and keep your fingers crossed.
+
+First, install packages needed to build and setup a basic CKEditor 5 instance.
+
+```bash
+npm install --save \
+	postcss-loader \
+	raw-loader \
+	style-loader \
+	webpack@4 \
+	webpack-cli@3 \
+	@ckeditor/ckeditor5-dev-utils \
+	@ckeditor/ckeditor5-editor-classic \
+	@ckeditor/ckeditor5-essentials \
+	@ckeditor/ckeditor5-paragraph \
+	@ckeditor/ckeditor5-heading \
+	@ckeditor/ckeditor5-list \
+	@ckeditor/ckeditor5-basic-styles \
+	@ckeditor/ckeditor5-theme-lark
+```
+
+Create minimal webpack configuration:
+
+```js
+// webpack.config.js
+
+'use strict';
+
+const path = require( 'path' );
+const { styles } = require( '@ckeditor/ckeditor5-dev-utils' );
+
+module.exports = {
+	entry: './app.js',
+
+	output: {
+		path: path.resolve( __dirname, 'dist' ),
+		filename: 'bundle.js'
+	},
+
+	module: {
+		rules: [
+			{
+				test: /\.svg$/,
+				use: [ 'raw-loader' ]
+			},
+			{
+				test: /\.css$/,
+				use: [
+					{
+						loader: 'style-loader',
+						options: {
+							singleton: true
+						}
+					},
+					{
+						loader: 'postcss-loader',
+						options: styles.getPostCssConfig( {
+							themeImporter: {
+								themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
+							},
+							minify: true
+						} )
+					},
+				]
+			}
+		]
+	},
+
+	// Useful for debugging.
+	devtool: 'source-map',
+
+	// By default webpack logs warnings if the bundle is bigger than 200kb.
+	performance: { hints: false }
+};
+```
+
+Create your project's entry point:
+
+```js
+// app.js
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import Heading from '@ckeditor/ckeditor5-heading/src/heading';
+import List from '@ckeditor/ckeditor5-list/src/list';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
+import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic ],
+		toolbar: [ 'heading', 'bold', 'italic', 'numberedList', 'bulletedList' ]
+	} )
+	.then( editor => {
+		console.log( 'Editor was initialized', editor );
+
+		// Expose for playing in the console.
+		window.editor = editor;
+	} )
+	.catch( error => {
+		console.error( error.stack );
+	} );
+```
+
+And an `index.html` page:
+
+```html
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8">
+		<title>CKEditor 5 Framework – Implementing a simple widget</title>
+	</head>
+	<body>
+		<div id="editor">
+			<p>Editor content goes here.</p>
+		</div>
+
+		<script src="dist/bundle.js"></script>
+	</body>
+</html>
+```
+
+Finally, let's build your project and see if everything worked well by opening the index page in your browser:
+
+```
+p@m /workspace/creating-a-plugin> ./node_modules/.bin/webpack --mode development
+Hash: a4a7cf092b8d69199848
+Version: webpack 4.28.4
+Time: 5467ms
+Built at: 2019-01-15 10:49:01
+        Asset      Size  Chunks                    Chunk Names
+    bundle.js  3.52 MiB    main  [emitted]  [big]  main
+bundle.js.map   3.2 MiB    main  [emitted]         main
+Entrypoint main [big] = bundle.js bundle.js.map
+[./app.js] 824 bytes {main} [built]
+[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {main} [built]
+[./node_modules/webpack/buildin/harmony-module.js] (webpack)/buildin/harmony-module.js 573 bytes {main} [built]
+    + 904 hidden modules
+```
+
+You should see a CKEditor 5 instance like this:
+
+{@img assets/img/tutorial-implementing-a-widget-1.png Screenshot of a classic editor initialized from source.}
+
+## Plugin structure
+
+Once the editor is up and running we can start implementing the plugin. All the code of a plugin can be kept in a single file, however, we recommend splitting its "editing" and "UI" layers and creating a master plugin which loads both. This way, we ensure a better separation of concerns and allow recomposing the features (e.g. picking the editing part of an existing feature but writing your own UI for it). All official CKEditor 5 plugins follow this pattern.
+
+Additionally, we will split code of commands, buttons and other "self-contained" components to separate files as well. In order to not mix up these files with your project's `app.js` and `webpack.config.js` files, let's create this directory structure:
+
+```
+├── app.js
+├── dist
+│   ├── bundle.js
+│   └── bundle.js.map
+├── index.html
+├── node_modules
+├── package.json
+├── simplebox
+│   ├── simplebox.js
+│   ├── simpleboxediting.js
+│   └── simpleboxui.js
+│
+│   ... the rest of plugin files go here as well
+│
+└── webpack.config.js
+```
+
+Let's now define the 3 plugins.
+
+First, the master (glue) plugin. Its role is to simply load the "editing" and "UI" parts.
+
+```js
+// simplebox/simplebox.js
+
+import SimpleBoxEditing from './simpleboxediting';
+import SimpleBoxUI from './simpleboxui';
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+export default class SimpleBox extends Plugin {
+	static get requires() {
+		return [ SimpleBoxEditing, SimpleBoxUI ];
+	}
+}
+```
+
+Now, the remaining two plugins:
+
+```js
+// simplebox/simpleboxui.js
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+export default class SimpleBoxUI extends Plugin {
+	init() {
+		console.log( 'SimpleBoxUI#init() got called' );
+	}
+}
+```
+
+```js
+// simplebox/simpleboxediting.js
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+export default class SimpleBoxEditing extends Plugin {
+	init() {
+		console.log( 'SimpleBoxEditing#init() got called' );
+	}
+}
+```
+
+Finally, we need to load the `SimpleBox` plugin in our `app.js` file:
+
+```js
+// app.js
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import Heading from '@ckeditor/ckeditor5-heading/src/heading';
+import List from '@ckeditor/ckeditor5-list/src/list';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
+import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
+
+import SimpleBox from './simplebox/simplebox';                                 // ADDED
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [
+			Essentials, Paragraph, Heading, List, Bold, Italic,
+			SimpleBox                                                          // ADDED
+		],
+		toolbar: [ 'heading', 'bold', 'italic', 'numberedList', 'bulletedList' ]
+	} )
+	.then( editor => {
+		console.log( 'Editor was initialized', editor );
+
+		// Expose for playing in the console.
+		window.editor = editor;
+	} )
+	.catch( error => {
+		console.error( error.stack );
+	} );
+```
+
+Rebuild your project, refresh the browser and you should see that the `SimpleBoxEditing` and `SmpleBoxUI` plugins were loaded:
+
+{@img assets/img/tutorial-implementing-a-widget-2.png Screenshot of a classic editor initialized from source with the SimpleBoxEditing#init() got called and SimpleBoxUI#init() got called messages on the console.}
+
+## The model and the view layers
+
+CKEditor 5 implements an MVC architecture and its custom data model, while still being a tree structure, does not map to the DOM 1:1. You can think about the model as about an even more semantical representation of the editor content, while the DOM is its one of the possible representations.
+
+<info-box>
+	Read more about the {@link framework/guides/architecture/editing-engine#overview editing engine architecture}.
+</info-box>
+
+Since our simple box feature is meant to be a box with a title and description fields, let's define its model representation as this:
+
+```html
+<simpleBox>
+	<simpleBoxTitle></simpleBoxTitle>
+	<simpleBoxDescription></simpleBoxDescription>
+</simpleBox>
+```
+
+### Defining the schema
+
+We need to start from defining the model's schema. We need to define there 3 elements and their types and allowed parent/children.
+
+<info-box>
+	Read more about the {@link framework/guides/architecture/editing-engine#schema schema}.
+</info-box>
+
+Let's update the `SimpleBoxEditing` plugin with this definition.
+
+```js
+// simplebox/simpleboxediting.js
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+export default class SimpleBoxEditing extends Plugin {
+	init() {
+		console.log( 'SimpleBoxEditing#init() got called' );
+
+		this._defineSchema();                                                  // ADDED
+	}
+
+	_defineSchema() {                                                          // ADDED
+		const schema = this.editor.model.schema;
+
+		schema.register( 'simpleBox', {
+			// Behaves like a self-contained object (e.g. an image).
+			isObject: true,
+
+			// Allow in places where other blocks are allowed (e.g. directly in the root).
+			allowWhere: '$block'
+		} );
+
+		schema.register( 'simpleBoxTitle', {
+			// Cannot be split or left by the caret.
+			isLimit: true,
+
+			allowIn: 'simpleBox',
+
+			// Allow content which is allowed in blocks (i.e. text with attributes).
+			allowContentOf: '$block'
+		} );
+
+		schema.register( 'simpleBoxDescription', {
+			// Cannot be split or left by the caret.
+			isLimit: true,
+
+			allowIn: 'simpleBox',
+
+			// Allow content which is allowed in the root (e.g. paragraphs).
+			allowContentOf: '$root'
+		} );
+	}
+}
+```
+
+Defining the schema will not have any effect on the editor just yet. It is an information which can be used by plugins and the editor engine to understand how actions like pressing the <kbd>Enter</kbd> key, clicking on an element, typing text, inserting an image, etc. should behave.
+
+For the simple box plugin to start doing anything we need to define model-view converters. Let's do that!
+
+### Defining converters
+
+Converters tell the editor how to convert the view to the model (e.g. when loading the data to the editor or handling pasted content) and how to render the model to the view (for editing purposes, or when retrieving editor data).
+
+<info-box>
+	Read more about the {@link framework/guides/architecture/editing-engine#conversion model-view conversion}.
+</info-box>
+
+This is the moment when we need to think how we want to render the `<simpleBox>` element and its children to the DOM (what user will see) and to the data. CKEditor 5 allows converting the model to a different structure for editing purposes and a different one to be stored as "data" or exchanged with other applications when copy-pasting the content. However, for simplicity, let's use the same representation in both pipelines for now.
+
+The structure in the view that we want to achieve:
+
+```html
+<section class="simple-box">
+	<h1 class="simple-box-title"></h1>
+	<div class="simple-box-description"></div>
+</section>
+```
+
+Let's use the {@link module:engine/conversion/conversion~Conversion#elementToElement `conversion.elementToElement()`} method to define all the converters.
+
+<info-box>
+	We can use this high-level two-way converters definition because we define the same converters for the {@link framework/guides/architecture/editing-engine#data-pipeline data} and {@link framework/guides/architecture/editing-engine#editing-pipeline editing} pipelines.
+
+	Later on we will switch to more fine-grained converters to get more control over the conversion.
+</info-box>
+
+We need to define converters for 3 model elements. Let's update the `SimpleBoxEditing` plugin with this code:
+
+```js
+// simplebox/simpleboxediting.js
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+export default class SimpleBoxEditing extends Plugin {
+	init() {
+		console.log( 'SimpleBoxEditing#init() got called' );
+
+		this._defineSchema();
+		this._defineConverters();                                              // ADDED
+	}
+
+	_defineSchema() {
+		// ...
+	}
+
+	_defineConverters() {                                                      // ADDED
+		const conversion = this.editor.conversion;
+
+		conversion.elementToElement( {
+			model: 'simpleBox',
+			view: {
+				name: 'section',
+				classes: 'simple-box'
+			}
+		} );
+
+		conversion.elementToElement( {
+			model: 'simpleBoxTitle',
+			view: {
+				name: 'h1',
+				classes: 'simple-box-title'
+			}
+		} );
+
+		conversion.elementToElement( {
+			model: 'simpleBoxDescription',
+			view: {
+				name: 'div',
+				classes: 'simple-box-description'
+			}
+		} );
+	}
+}
+```
+
+Once we have converters, we can try to see the simple box in action. We did not define yet a way to insert a new simple box into the document, so let's load it via the editor data. In order to do that, we need to modify the `index.html` file:
+
+```html
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8">
+		<title>CKEditor 5 Framework – Implementing a simple widget</title>
+
+		<style>
+			.simple-box {
+				padding: 10px;
+				margin: 1em 0;
+
+				background: rgba( 0, 0, 0, 0.1 );
+				border: solid 1px hsl(0, 0%, 77%);
+				border-radius: 2px;
+			}
+
+			.simple-box-title, .simple-box-description {
+				padding: 10px;
+				margin: 0;
+
+				background: #FFF;
+				border: solid 1px hsl(0, 0%, 77%);
+			}
+
+			.simple-box-title {
+				margin-bottom: 10px;
+			}
+		</style>
+	</head>
+	<body>
+		<div id="editor">
+			<p>This is a simple box:</p>
+
+			<section class="simple-box">
+				<h1 class="simple-box-title">Box title</h1>
+				<div class="simple-box-description">
+					<p>The description goes here.</p>
+					<ul>
+						<li>It can contain lists,</li>
+						<li>and other block elements like headings.</li>
+					</ul>
+				</div>
+			</section>
+		</div>
+
+		<script src="dist/bundle.js"></script>
+	</body>
+</html>
+```
+
+Rebuild your project and voila &mdash; that's your first simple box instance:
+
+{@img assets/img/tutorial-implementing-a-widget-3.png Screenshot of a classic editor with an instance of a simple box inside.}
+
+### What's in the model?
+
+The HTML that you have added to the `index.html` file is your editor's data. This is what `editor.getData()` would return. Also, for now, this also the DOM structure which is rendered by CKEditor 5's engine in the editable region:
+
+{@img assets/img/tutorial-implementing-a-widget-4.png Screenshot of a DOM structure of the simple box instance – it looks exactly like the data loaded into the editor.}
+
+However, what's in the model?
+
+To inspect the model structure you can use the [`@ckeditor/ckeditor5-inspector`](https://www.npmjs.com/package/@ckeditor/ckeditor5-inspector) util. It allows browsing the model and view structures as well as the list of commands.
+
+In order to enable CKEditor 5 Inspector for your editor, you need to first install it:
+
+```
+npm install --save-dev @ckeditor/ckeditor5-inspector
+```
+
+And now you need to load it in the `app.js` file:
+
+```js
+// app.js
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import Heading from '@ckeditor/ckeditor5-heading/src/heading';
+import List from '@ckeditor/ckeditor5-list/src/list';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
+import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
+
+import SimpleBox from './simplebox/simplebox';
+
+import CKEditorInspector from '@ckeditor/ckeditor5-inspector';                 // ADDED
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [
+			Essentials, Paragraph, Heading, List, Bold, Italic,
+			SimpleBox
+		],
+		toolbar: [ 'heading', 'bold', 'italic', 'numberedList', 'bulletedList' ]
+	} )
+	.then( editor => {
+		console.log( 'Editor was initialized', editor );
+
+		CKEditorInspector.attach( 'editor', editor );
+
+		window.editor = editor;
+	} )
+	.catch( error => {
+		console.error( error.stack );
+	} );
+```
+
+After rebuilding your project and refreshing the page you will see the inspector:
+
+{@img assets/img/tutorial-implementing-a-widget-4b.png Screenshot of a the simple box widget's structure displayed by CKEditor 5 Inspector.}
+
+You will see the following HTML-like string:
+
+```html
+<paragraph>[]This is a simple box:</paragraph>
+<simpleBox>
+	<simpleBoxTitle>Box title</simpleBoxTitle>
+	<simpleBoxDescription>
+		<paragraph>The description goes here.</paragraph>
+		<listItem listIndent="0" listType="bulleted">It can contain lists,</listItem>
+		<listItem listIndent="0" listType="bulleted">and other block elements like headings.</listItem>
+	</simpleBoxDescription>
+</simpleBox>
+```
+
+As you can see, this structure is quite different than the HTML input/output. If you look closely, you will also notice the `[]` characters in the first paragraph &mdash; that's selection position.
+
+Play a bit with editor features (bold, italic, headings, lists, selection) to see how the model structure changes.
+
+<info-box>
+	Another useful helpers are the `getData()` and `setData()` functions exposed by {@link module:engine/dev-utils/model model dev utils} and {@link module:engine/dev-utils/view view dev utils}. They allow stringifying the model/view structures, selections, ranges and positions as well as loading them from string. They are often use when writing tests.
+
+	Both tools are designed for prototyping, debugging, and testing purposes. Do not use them in production-grade code.
+</info-box>
+
+### Behavior before "widgetizing" simple box
+
+It is time to check if the simple box behaves like we would like it to. You can observe the following:
+
+* You can type text in the title, but pressing <kbd>Enter</kbd> will not split it and <kbd>Backspace</kbd> will not delete it entirely. That is because it was marked as an `isLimit` element in the schema.
+* You cannot apply a list in the title and cannot turn it into a heading (other than `<h1 class="simple-box-title">` which it is already). That is because it allows only the content that is allowed in other block elements (like paragraphs). You can, however, apply italic inside a title (because italic is allowed in other blocks).
+* The description behaves like the title, but it allows more content inside &mdash; lists and other headings.
+* If you try to select the entire simple box instance and press <kbd>Delete</kbd>, it will be deleted as a whole. The same when you copy and paste it. That is because it was marked as an `isObject` element in the schema.
+* You cannot easily select the entire simple box instance by clicking on it. Also, the cursor pointer does not change when you hover it. In other words, it seems a bit "dead". That is because we have not yet defined the view behavior yet.
+
+Pretty cool so far, right? With a very little code you were able to define a behavior of your simple box plugin which maintains integrity of those elements. The engine ensures that the user does not break those instances.
+
+Let's see what else we can improve.
+
+### Making simple box a widget
+
+<info-box>
+	If you are familiar with the {@link @ckeditor4 guide/dev_widgets Widget System of CKEditor 4} you will notice significant differences in how widgets are implemented in CKEditor 5.
+
+	CKEditor 4's implementation exposes a declarative API which controls the entire behavior of a widget (from its schema and internal model to the styles, clicking behavior, context menu and the dialog).
+
+	In CKEditor 5 the widget system was redesigned. Most of its responsibilities were taken over by the engine, some were extracted to a separate package ({@link api/widget `@ckeditor/ckeditor5-widget`}) and some have to be handled by other utils provided by CKEditor 5 framework.
+
+	CKEditor 5's implementation is, therefore, open for extensions and recomposition. You can choose those behaviors that you want (just like we did so far in this tutorial by defining a schema) and skip others or implement them by yourself.
+</info-box>
+
+The converters that we defined convert the model `<simpleBox*>` elements to plain {@link module:engine/view/containerelement~ContainerElement `ContainerElement`}s in the view (and back during upcasting).
+
+We want to change this behavior a bit so the structure created in the editing view is enhanced with the {@link module:widget/utils~toWidget `toWidget()`} and {@link module:widget/utils~toWidgetEditable `toWidgetEditable()`} utils. We do not want to affect the data view, though. Therefore, we will need to define converters for the editing and data downcasting separately.
+
+If you find the concept of downcasting and upcasting confusing, read the {@link framework/guides/architecture/editing-engine#conversion introduction to conversion}.
+
+Before we start coding, we need to install the {@link api/widget `@ckeditor/ckeditor5-widget`} package:
+
+```
+npm install --save @ckeditor/ckeditor5-widget
+```
+
+Now, let's revisit the `_defineConverters()` method that we defined earlier. We will use {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToElement `elementToElement()` upcast helper} and {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()` downcast helper} instead of the two-way `elementToElement()` converter helper.
+
+Additionally, we need to ensure that the {@link module:widget/widget~Widget `Widget`} plugin is loaded. If you omit it, the elements in the view will have all the classes (e.g. `ck-widget`) but there will be no "behaviors" loaded (e.g. clicking a widget will not select it).
+
+```js
+// simplebox/simpleboxediting.js
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+// ADDED 2 imports
+import { toWidget, toWidgetEditable } from '@ckeditor/ckeditor5-widget/src/utils';
+import Widget from '@ckeditor/ckeditor5-widget/src/widget';
+
+export default class SimpleBoxEditing extends Plugin {
+	static get requires() {                                                    // ADDED
+		return [ Widget ];
+	}
+
+	init() {
+		console.log( 'SimpleBoxEditing#init() got called' );
+
+		this._defineSchema();
+		this._defineConverters();
+	}
+
+	_defineSchema() {
+		// ...
+	}
+
+	_defineConverters() {                                                      // MODIFIED
+		const conversion = this.editor.conversion;
+
+		// <simpleBox> converters
+		conversion.for( 'upcast' ).elementToElement( {
+			model: 'simpleBox',
+			view: {
+				name: 'section',
+				classes: 'simple-box'
+			}
+		} );
+		conversion.for( 'dataDowncast' ).elementToElement( {
+			model: 'simpleBox',
+			view: {
+				name: 'section',
+				classes: 'simple-box'
+			}
+		} );
+		conversion.for( 'editingDowncast' ).elementToElement( {
+			model: 'simpleBox',
+			view: ( modelElement, viewWriter ) => {
+				const section = viewWriter.createContainerElement( 'section', { class: 'simple-box' } );
+
+				return toWidget( section, viewWriter, { label: 'simple box widget' } );
+			}
+		} );
+
+		// <simpleBoxTitle> converters
+		conversion.for( 'upcast' ).elementToElement( {
+			model: 'simpleBoxTitle',
+			view: {
+				name: 'h1',
+				classes: 'simple-box-title'
+			}
+		} );
+		conversion.for( 'dataDowncast' ).elementToElement( {
+			model: 'simpleBoxTitle',
+			view: {
+				name: 'h1',
+				classes: 'simple-box-title'
+			}
+		} );
+		conversion.for( 'editingDowncast' ).elementToElement( {
+			model: 'simpleBoxTitle',
+			view: ( modelElement, viewWriter ) => {
+				// Note: we use a more specialized createEditableElement() method here.
+				const h1 = viewWriter.createEditableElement( 'h1', { class: 'simple-box-title' } );
+
+				return toWidgetEditable( h1, viewWriter );
+			}
+		} );
+
+		// <simpleBoxDescription> converters
+		conversion.for( 'upcast' ).elementToElement( {
+			model: 'simpleBoxDescription',
+			view: {
+				name: 'div',
+				classes: 'simple-box-description'
+			}
+		} );
+		conversion.for( 'dataDowncast' ).elementToElement( {
+			model: 'simpleBoxDescription',
+			view: {
+				name: 'div',
+				classes: 'simple-box-description'
+			}
+		} );
+		conversion.for( 'editingDowncast' ).elementToElement( {
+			model: 'simpleBoxDescription',
+			view: ( modelElement, viewWriter ) => {
+				// Note: we use a more specialized createEditableElement() method here.
+				const div = viewWriter.createEditableElement( 'div', { class: 'simple-box-description' } );
+
+				return toWidgetEditable( div, viewWriter );
+			}
+		} );
+	}
+}
+```
+
+<info-box>
+	As you can see, the code became much more verbose and far longer. This is because we used lower-level converters. We plan to provide more handy widget conversion utils in the future. Read more (and 👍) in [this ticket](https://github.com/ckeditor/ckeditor5/issues/1228).
+</info-box>
+
+### Behavior after "widgetizing" simple box
+
+You can rebuild your project now and see how your simple box plugin has changed.
+
+{@img assets/img/tutorial-implementing-a-widget-5.png Screenshot of a widget's focus outline.}
+
+You should observe that:
+
+* The `<section>`, `<h1>`, and `<div>` elements have the `contentEditable` attribute on them (plus some classes). This attribute tells the browser whether an element is considered "editable". Passing element through `toWidget()` will make its content non-editable. Conversely, passing it through `toWidgetEditable()` will make its content editable again.
+* You can now click on the widget (the gray area) to select it. Once it is selected, it is easier to copy-paste it.
+* The widget and its nested editable regions react to hovering, selection, and focus (outline).
+
+In other words, the simple box instance became much more responsive.
+
+Additionally, if you call `editor.getData()` you will get the same HTML as before "widgetizing" simple box. That's thanks to using `toWidget()` and `toNestedEditable()` only in the `editingDowncast` pipeline.
+
+This is all that we need from the model and the view layers for now. In terms of "editability" and data input/output its fully functional. Let's now find a way to insert new simple boxes into the document!
+
+## Creating a command
+
+A {@link framework/guides/architecture/core-editor-architecture#commands command} is a combination of an action and a state. You can interact with most of the editor features by commands that they expose. This allows not only executing those features (e.g. bolding a fragment of text) but also checking if this action can be executed in the selection's current location as well as observing other state properties (such as whether the currently selected text is bolded).
+
+In case of simple box the situation is simple:
+
+* we need an "insert new simple box" action,
+* and "can we insert a new simple box here (at the current selection position)".
+
+Let's create a new file `insertsimpleboxcommand.js` in the `simplebox/` directory. We will use the {@link module:engine/model/model~Model#insertContent `model.insertContent()`} method which will be able to e.g. split a paragraph if you try to insert a simple box in the middle of it (which is not allowed by the schema.
+
+```js
+// simplebox/insertsimpleboxcommand.js
+
+import Command from '@ckeditor/ckeditor5-core/src/command';
+
+export default class InsertSimpleBoxCommand extends Command {
+	execute() {
+		this.editor.model.change( writer => {
+			// Insert <simpleBox>*</simpleBox> at the current selection position
+			// in a way which will result in creating a valid model structure.
+			this.editor.model.insertContent( createSimpleBox( writer ) );
+		} );
+	}
+
+	refresh() {
+		const model = this.editor.model;
+		const selection = model.document.selection;
+		const allowedIn = model.schema.findAllowedParent( 'simpleBox', selection.getFirstPosition() );
+
+		this.isEnabled = allowedIn !== null;
+	}
+}
+
+function createSimpleBox( writer ) {
+	const simpleBox = writer.createElement( 'simpleBox' );
+	const simpleBoxTitle = writer.createElement( 'simpleBoxTitle' );
+	const simpleBoxDescription = writer.createElement( 'simpleBoxDescription' );
+
+	writer.append( simpleBoxTitle, simpleBox );
+	writer.append( simpleBoxDescription, simpleBox );
+
+	// There must be at least one paragraph for the description to be editable.
+	// See https://github.com/ckeditor/ckeditor5/issues/1464.
+	writer.appendElement( 'paragraph', simpleBoxDescription );
+
+	return simpleBox;
+}
+```
+
+Import the command and register it in the `SimpleBoxEditing` plugin:
+
+```js
+// simplebox/simpleboxediting.js
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+import { toWidget, toWidgetEditable } from '@ckeditor/ckeditor5-widget/src/utils';
+import Widget from '@ckeditor/ckeditor5-widget/src/widget';
+
+import InsertSimpleBoxCommand from './insertsimpleboxcommand';                 // ADDED
+
+export default class SimpleBoxEditing extends Plugin {
+	static get requires() {
+		return [ Widget ];
+	}
+
+	init() {
+		console.log( 'SimpleBoxEditing#init() got called' );
+
+		this._defineSchema();
+		this._defineConverters();
+
+		// ADDED
+		this.editor.commands.add( 'insertSimpleBox', new InsertSimpleBoxCommand( this.editor ) );
+	}
+
+	_defineSchema() {
+		// ...
+	}
+
+	_defineConverters() {
+		// ...
+	}
+}
+```
+
+You can now execute this command in order to insert a new simple box. Calling:
+
+```js
+editor.execute( 'insertSimpleBox' );
+```
+
+Should result in:
+
+{@img assets/img/tutorial-implementing-a-widget-6.png Screenshot of a simple box instance inserted at the beginning of the editor content.}
+
+You can also try inspecting the `isEnabled` property value (or just checking it in CKEditor 5 Inspector):
+
+```js
+console.log( editor.commands.get( 'insertSimpleBox' ).isEnabled );
+```
+
+It is always `true` except when the selection is in one place &mdash; in other simple box's title. You can also observe that executing the command when the selection is in that place takes no effect.
+
+Let's change one more thing before we will move forward &mdash; let's disallow `simpleBox` inside `simpleBoxDescription` too. This can be done by {@link module:engine/model/schema~Schema#addChildCheck defining a custom child check}:
+
+```js
+// simplebox/simpleboxediting.js
+
+// ... imports
+
+export default class SimpleBoxEditing extends Plugin {
+	init() {
+		console.log( 'SimpleBoxEditing#init() got called' );
+
+		this._defineSchema();
+		this._defineConverters();
+
+		this.editor.commands.add( 'insertSimpleBox', new InsertSimpleBoxCommand( this.editor ) );
+	}
+
+	_defineSchema() {
+		const schema = this.editor.model.schema;
+
+		schema.register( 'simpleBox', {
+			// Behaves like a self-contained object (e.g. an image).
+			isObject: true,
+
+			// Allow in places where other blocks are allowed (e.g. directly in the root).
+			allowWhere: '$block'
+		} );
+
+		schema.register( 'simpleBoxTitle', {
+			// Cannot be split or left by the caret.
+			isLimit: true,
+
+			allowIn: 'simpleBox',
+
+			// Allow content which is allowed in blocks (i.e. text with attributes).
+			allowContentOf: '$block'
+		} );
+
+		schema.register( 'simpleBoxDescription', {
+			// Cannot be split or left by the caret.
+			isLimit: true,
+
+			allowIn: 'simpleBox',
+
+			// Allow content which is allowed in the root (e.g. paragraphs).
+			allowContentOf: '$root'
+		} );
+
+		// ADDED
+		schema.addChildCheck( ( context, childDefinition ) => {
+			if ( context.endsWith( 'simpleBoxDescription' ) && childDefinition.name == 'simpleBox' ) {
+				return false;
+			}
+		} );
+	}
+
+	_defineConverters() {
+		// ...
+	}
+}
+```
+
+Now, the command should be disabled also when the selection is inside the description of another simple box instance.
+
+## Creating a button
+
+It is time to allow editor users insert the widget into the content. The best way to do that is through a UI button in the toolbar. You can quickly create one using the {@link module:ui/button/buttonview~ButtonView `ButtonView`} class brought by the {@link framework/guides/architecture/ui-library UI framework} of CKEditor 5.
+
+The button should execute the [command](#creating-a-command) when clicked and become inactive if the widget cannot be inserted in some particular position of the selection ([as defined in the schema](#defining-the-schema)).
+
+Let us see what it looks like in practice and extend the `SimpleBoxUI` plugin [created earlier](#plugin-structure):
+
+```js
+// simplebox/simpleboxui.js
+
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+export default class SimpleBoxUI extends Plugin {
+	init() {
+		console.log( 'SimpleBoxUI#init() got called' );
+
+		const editor = this.editor;
+		const t = editor.t;
+
+		// The "simpleBox" button must be registered among UI components of the editor
+		// to be displayed in the toolbar.
+		editor.ui.componentFactory.add( 'simpleBox', locale => {
+			// The state of the button will be bound to the widget command.
+			const command = editor.commands.get( 'insertSimpleBox' );
+
+			// The button will be an instance of ButtonView.
+			const buttonView = new ButtonView( locale );
+
+			buttonView.set( {
+				// The t() function helps localize the editor. All strings enclosed in t() can be
+				// translated and change when the language of the editor changes.
+				label: t( 'Simple Box' ),
+				withText: true,
+				tooltip: true
+			} );
+
+			// Bind the state of the button to the command.
+			buttonView.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
+
+			// Execute the command when the button is clicked (executed).
+			this.listenTo( buttonView, 'execute', () => editor.execute( 'insertSimpleBox' ) );
+
+			return buttonView;
+		} );
+	}
+}
+```
+
+The last thing you need to do is tell the editor to display the button in the toolbar. To do that, you will need to slightly modify the code that runs the editor instance and include the button in the {@link module:core/editor/editorconfig~EditorConfig#toolbar toolbar configuration}:
+
+```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic ],
+		// Insert the "simpleBox" to the editor toolbar.
+		toolbar: [ 'heading', 'bold', 'italic', 'numberedList', 'bulletedList', 'simpleBox' ]
+	} )
+	.then( editor => {
+		// ...
+	} )
+	.catch( error => {
+		// ...
+	} );
+```
+
+Refresh the web page and try it yourself:
+
+{@img assets/img/tutorial-implementing-a-widget-7.png Screenshot of the simple box widget being inserted using the toolbar button.}

+ 697 - 0
docs/framework/guides/tutorials/implementing-an-inline-widget.md

@@ -0,0 +1,697 @@
+---
+category: framework-tutorials
+order: 10
+---
+
+# Implementing an inline widget
+
+In this tutorial you will learn how to implement an inline widget. We will build a "Placeholder" feature which allow the user to insert a predefined placeholders, like a date or a surname, into the document. We will use widget utils and conversion in order to define the behavior of this feature. Later on, we will use dropdown utils to create a dropdown which will allow inserting new placeholders. We will also learn how to use the editor configuration to define allowed placeholder names.
+
+## Before you start ⚠️
+
+This guide assumes that you are familiar with widgets concept introduced in the {@link framework/guides/tutorials/implementing-a-block-widget Implementing a block widget} tutorial. We will also reference various concepts from {@link framework/guides/architecture/intro CKEditor 5 architecture}.
+
+## Bootstrapping the project
+
+The overall project structure will be similar to this described in {@link framework/guides/tutorials/implementing-a-block-widget#lets-start Let's start} and {@link framework/guides/tutorials/implementing-a-block-widget#plugin-structure Plugin structure} sections.
+
+First, install required dependencies:
+
+```bash
+npm install --save \
+	postcss-loader \
+	raw-loader \
+	style-loader \
+	webpack@4 \
+	webpack-cli@3 \
+	@ckeditor/ckeditor5-basic-styles \
+	@ckeditor/ckeditor5-core \
+	@ckeditor/ckeditor5-dev-utils \
+	@ckeditor/ckeditor5-editor-classic \
+	@ckeditor/ckeditor5-essentials \
+	@ckeditor/ckeditor5-heading \
+	@ckeditor/ckeditor5-list \
+	@ckeditor/ckeditor5-paragraph \
+	@ckeditor/ckeditor5-theme-lark \
+	@ckeditor/ckeditor5-ui \
+	@ckeditor/ckeditor5-utils \
+	@ckeditor/ckeditor5-widget \
+	@ckeditor/ckeditor5-inspector
+```
+
+Create a minimal webpack configuration:
+
+```js
+// webpack.config.js
+
+'use strict';
+
+const path = require( 'path' );
+const { styles } = require( '@ckeditor/ckeditor5-dev-utils' );
+
+module.exports = {
+	entry: './app.js',
+
+	output: {
+		path: path.resolve( __dirname, 'dist' ),
+		filename: 'bundle.js'
+	},
+
+	module: {
+		rules: [
+			{
+				test: /\.svg$/,
+				use: [ 'raw-loader' ]
+			},
+			{
+				test: /\.css$/,
+				use: [
+					{
+						loader: 'style-loader',
+						options: {
+							singleton: true
+						}
+					},
+					{
+						loader: 'postcss-loader',
+						options: styles.getPostCssConfig( {
+							themeImporter: {
+								themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
+							},
+							minify: true
+						} )
+					},
+				]
+			}
+		]
+	},
+
+	// Useful for debugging.
+	devtool: 'source-map',
+
+	// By default webpack logs warnings if the bundle is bigger than 200kb.
+	performance: { hints: false }
+};
+```
+
+Add an `index.html` page:
+
+```html
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8">
+		<title>CKEditor 5 Framework – Implementing a simple widget</title>
+	</head>
+	<body>
+		<div id="editor">
+			<p>Editor content goes here.</p>
+		</div>
+
+		<script src="dist/bundle.js"></script>
+	</body>
+</html>
+```
+
+The application entry point (`app.js`):
+
+```js
+// app.js
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import Heading from '@ckeditor/ckeditor5-heading/src/heading';
+import List from '@ckeditor/ckeditor5-list/src/list';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
+import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
+
+import Placeholder from './placeholder/placeholder';
+
+import CKEditorInspector from '@ckeditor/ckeditor5-inspector';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic, Placeholder ],
+		toolbar: [ 'heading', 'bold', 'italic', 'numberedList', 'bulletedList' ]
+	} )
+	.then( editor => {
+		console.log( 'Editor was initialized', editor );
+
+		CKEditorInspector.attach( 'editor', editor );
+
+		// Expose for playing in the console.
+		window.editor = editor;
+	} )
+	.catch( error => {
+		console.error( error.stack );
+	} );
+```
+
+Before building the project we still need to define `Placeholder` plugin. The project will have a structure as below:
+
+```
+├── app.js
+├── dist
+│   ├── bundle.js
+│   └── bundle.js.map
+├── index.html
+├── node_modules
+├── package.json
+├── placeholder
+│   ├── placeholder.js
+│   ├── placeholdercommand.js
+│   ├── placeholderediting.js
+│   ├── placeholderui.js
+│   └── theme
+│       └── placeholder.css
+│
+│   ... the rest of plugin files go here as well
+│
+└── webpack.config.js
+```
+
+You can see that the placeholder feature has an established plugin structure: the master (glue) plugin (`placeholder/placeholder.js`), the "editing" (`placeholder/placeholderediting.js`) and the "ui" (`placeholder/placeholderui.js`) parts.
+
+The master (glue) plugin:
+
+```js
+// placeholder/placeholder.js
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+import PlaceholderEditing from './placeholderediting';
+import PlaceholderUI from './placeholderui';
+
+export default class Placeholder extends Plugin {
+	static get requires() {
+		return [ PlaceholderEditing, PlaceholderUI ];
+	}
+}
+```
+
+The UI part (empty for now):
+
+```js
+// placeholder/placeholderui.js
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+export default class PlaceholderUI extends Plugin {
+	init() {
+		console.log( 'PlaceholderUI#init() got called' );
+	}
+}
+```
+
+And the editing part (empty for now):
+
+```js
+// placeholder/placeholderediting.js
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+export default class PlaceholderEditing extends Plugin {
+	init() {
+		console.log( 'PlaceholderEditing#init() got called' );
+	}
+}
+```
+
+At this stage we can build the project and open it in the browser to verify if it is building correctly.
+
+## The model and the view layers
+
+The placeholder feature will be {@link module:engine/model/schema~SchemaItemDefinition defined as  an inline} (text-like) element so it will be inserted in other editor blocks, like `<paragraph>`, that allow text. The placeholder will have `name` attribute. This means that the model containing some text and a placeholder will look like this:
+
+```html
+<paragraph>
+	Hello <placeholder name="name"></placeholder>!
+</paragraph>
+```
+
+### Defining the schema
+
+The `<placeholder>` element should be treated as `$text` so it must be defined with `isInline: true`. We want to allow it wherever the `$text` is allowed so we add `allowWhere: '$text'`. Finally, we will also need the `name` attribute.
+
+We will also use this occasion to import the theme file (`theme/placeholder.css`).
+
+```js
+// placeholder/placeholderediting.js
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+import './theme/placeholder.css';                                              // ADDED
+
+export default class PlaceholderEditing extends Plugin {
+	init() {
+		console.log( 'PlaceholderEditing#init() got called' );
+
+		this._defineSchema();                                                  // ADDED
+	}
+
+	_defineSchema() {                                                          // ADDED
+		const schema = this.editor.model.schema;
+
+		schema.register( 'placeholder', {
+			// Allow wherever text is allowed:
+			allowWhere: '$text',
+
+			// The placeholder will acts as an inline node:
+			isInline: true,
+
+			// The inline-widget is self-contained so cannot be split by the caret and can be selected:
+			isObject: true,
+
+			// The placeholder can have many types, like date, name, surname, etc:
+			allowAttributes: [ 'name' ]
+		} );
+	}
+}
+```
+
+The schema is defined so now we can define the model-view converters.
+
+### Defining converters
+
+The HTML structure (data output) of the converter will be a `<span>` with a `placeholder` class. The text inside the `<span>` will the placeholder's name.
+
+```html
+<span class="placeholder">{name}</span>
+```
+
+* **Upcast conversion**. This view-to-model converter will look for `<span>`s with class `placeholder`, read the `<span>`'s text and create a model `<placeholder>` elements with the `name` attribute set accordingly.
+* **Downcast conversion**. The model-to-view conversion will be slightly different for "editing" and "data" pipelines as the "editing downcast" pipeline will use widget utilities to enable widget specific behavior in the editing view. In both pipelines the element will be rendered using the same structure.
+
+```js
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+// ADDED 2 imports
+import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
+import Widget from '@ckeditor/ckeditor5-widget/src/widget';
+
+import './theme/placeholder.css';
+
+export default class PlaceholderEditing extends Plugin {
+	static get requires() {                                                    // ADDED
+		return [ Widget ];
+	}
+
+	constructor( editor ) {
+		super( editor );
+
+		this._defineSchema();
+		this._defineConverters();                                              // ADDED
+	}
+
+	_defineSchema() {
+		// ...
+	}
+
+	_defineConverters() {                                                      // ADDED
+		const conversion = this.editor.conversion;
+
+		conversion.for( 'upcast' ).elementToElement( {
+			view: {
+				name: 'span',
+				classes: [ 'placeholder' ]
+			},
+			model: ( viewElement, modelWriter ) => {
+				// Extract the "name" from "{name}".
+				const name = viewElement.getChild( 0 ).data.slice( 1, -1 );
+
+				return modelWriter.createElement( 'placeholder', { name } );
+			}
+		} );
+
+		conversion.for( 'editingDowncast' ).elementToElement( {
+			model: 'placeholder',
+			view: ( modelItem, viewWriter ) => {
+				const widgetElement = createPlaceholderView( modelItem, viewWriter );
+
+				// Enable widget handling on placeholder element inside editing view.
+				return toWidget( widgetElement, viewWriter );
+			}
+		} );
+
+		conversion.for( 'dataDowncast' ).elementToElement( {
+			model: 'placeholder',
+			view: createPlaceholderView
+		} );
+
+		// Helper method for both downcast converters.
+		function createPlaceholderView( modelItem, viewWriter ) {
+			const name = modelItem.getAttribute( 'name' );
+
+			const placeholderView = viewWriter.createContainerElement( 'span', {
+				class: 'placeholder'
+			} );
+
+			// Insert the placeholder name (as a text).
+			const innerText = viewWriter.createText( '{' + name + '}' );
+			viewWriter.insert( viewWriter.createPositionAt( placeholderView, 0 ), innerText );
+
+			return placeholderView;
+		}
+	}
+}
+```
+
+### Feature styles
+
+As you could notice the editing part imports the `./theme/placeholder.css` CSS file which describes how the placeholder is displayed in th editing view:
+
+```css
+/* placeholder/theme/placeholder.css */
+
+[data-placeholder] {
+	background: #ffff00;
+	padding: 4px 2px;
+	outline-offset: -2px;
+	line-height: 1em;
+	margin: 0 1px;
+}
+
+[data-placeholder]::selection {
+	display: none;
+}
+```
+
+### Command
+
+A {@link framework/guides/architecture/core-editor-architecture#commands command} for placeholder feature will insert a `<placeholder>` element (if allowed by the schema) at the selection. The command will accept `options.value` parameter (other CKEditor 5's commands also uses this pattern) to set the placeholder's name.
+
+```js
+// placeholder/placeholdercommand.js
+
+import Command from '@ckeditor/ckeditor5-core/src/command';
+
+export default class PlaceholderCommand extends Command {
+	execute( { value } ) {
+		const editor = this.editor;
+
+		editor.model.change( writer => {
+			// Create <placeholder> elment with name attribute...
+			const placeholder = writer.createElement( 'placeholder', { name: value } );
+
+			// ... and insert it into the document.
+			editor.model.insertContent( placeholder );
+
+			// Put the selection on inserted element.
+			writer.setSelection( placeholder, 'on' );
+		} );
+	}
+
+	refresh() {
+		const model = this.editor.model;
+		const selection = model.document.selection;
+
+		const isAllowed = model.schema.checkChild( selection.focus.parent, 'placeholder' );
+
+		this.isEnabled = isAllowed;
+	}
+}
+```
+
+Import the created command and add it to editor's commands:
+
+```js
+// placeholder/placeholderediting.js
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+import { toWidget } from '@ckeditor/ckeditor5-widget/src/utils';
+import Widget from '@ckeditor/ckeditor5-widget/src/widget';
+
+import PlaceholderCommand from './placeholdercommand';                         // ADDED
+import './theme/placeholder.css';
+
+export default class PlaceholderEditing extends Plugin {
+	init() {
+		this._defineSchema();
+		this._defineConverters();
+
+		// ADDED
+		this.editor.commands.add( 'placeholder', new PlaceholderCommand( this.editor ) );
+	}
+
+	_defineSchema() {
+		// ...
+	}
+
+	_defineConverters() {
+		// ...
+	}
+}
+```
+
+### Let's see it!
+
+You can rebuild the project now and you should be able to execute the `placeholder` command to insert a new placeholder:
+
+```js
+editor.execute( 'placeholder', { value: 'time' } );
+```
+
+This should result in:
+
+{@img assets/img/tutorial-implementing-an-inline-widget-1.png Screenshot of a placeholder widget in action.}
+
+### Fixing position mapping
+
+If you play now more with the widget (e.g. try to select it by dragging the mouse from its right to left edge) you will see the following error logged on the console:
+
+```
+Uncaught CKEditorError: model-nodelist-offset-out-of-bounds: Given offset cannot be found in the node list.
+```
+
+This error is thrown because there is a difference in text node mapping between the model and the view due to the different structures:
+
+```html
+model:
+
+foo<placeholder name="time"></placeholder>bar
+
+view:
+
+foo<span class="placeholder">{name}</span>bar
+```
+
+You can say that in the view there is "more" text than in the model. This means that some positions in the view cannot automatically map to positions in the model. Namely &mdash; those are positions inside the `<span>` element.
+
+Fortunately, CKEditor 5 {@link module:engine/conversion/mapper~Mapper#viewToModelPosition allows customizing the mapping logic}. Also, since mapping to an empty model element is a pretty common scenario, there is a ready-to-use util {@link module:widget/utils~viewToModelPositionOutsideModelElement `viewToModelPositionOutsideModelElement()`} which we can use here like that:
+
+```js
+// placeholder/placeholderediting.js
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+// MODIFIED
+import {
+	toWidget,
+	viewToModelPositionOutsideModelElement
+} from '@ckeditor/ckeditor5-widget/src/utils';
+import Widget from '@ckeditor/ckeditor5-widget/src/widget';
+
+import PlaceholderCommand from './placeholdercommand';
+
+import './theme/placeholder.css';
+
+export default class PlaceholderEditing extends Plugin {
+	init() {
+		this._defineSchema();
+		this._defineConverters();
+
+		this.editor.commands.add( 'placeholder', new PlaceholderCommand( this.editor ) );
+
+		// ADDED
+		this.editor.editing.mapper.on(
+			'viewToModelPosition',
+			viewToModelPositionOutsideModelElement( this.editor.model, viewElement => viewElement.hasClass( 'placeholder' ) )
+		);
+	}
+
+	_defineSchema() {
+		// ...
+	}
+
+	_defineConverters() {
+		// ...
+	}
+}
+```
+
+After adding the custom mapping, the mapping will work perfectly. Every position inside the view `<span>` element will be mapped to a position outside `<placeholder>` in the model.
+
+## Creating the UI
+
+The UI part will provide a dropdown button from which user can select a placeholder to insert into the editor.
+
+The CKEditor 5 framework features helpers to create different {@link framework/guides/architecture/ui-library#dropdowns dropdowns} like toolbar or list dropdowns.
+
+In this tutorial we will create a dropdown with list of available placeholders.
+
+```js
+// placeholder/placeholderui.js
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+import { addListToDropdown, createDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
+
+import Collection from '@ckeditor/ckeditor5-utils/src/collection';
+import Model from '@ckeditor/ckeditor5-ui/src/model';
+
+export default class PlaceholderUI extends Plugin {
+	init() {
+		const editor = this.editor;
+		const t = editor.t;
+		const placeholderNames = [ 'date', 'first name', 'surname' ];
+
+		// The "placeholder" dropdown must be registered among UI components of the editor
+		// to be displayed in the toolbar.
+		editor.ui.componentFactory.add( 'placeholder', locale => {
+			const dropdownView = createDropdown( locale );
+
+			// Populate the list in the dropdown with items.
+			addListToDropdown( dropdownView, getDropdownItemsDefinitions( placeholderNames ) );
+
+			dropdownView.buttonView.set( {
+				// The t() function helps localize the editor. All strings enclosed in t() can be
+				// translated and change when the language of the editor changes.
+				label: t( 'Placeholder' ),
+				tooltip: true,
+				withText: true
+			} );
+
+			// Execute the command when the dropdown items is clicked (executed).
+			this.listenTo( dropdownView, 'execute', evt => {
+				editor.execute( 'placeholder', { value: evt.source.commandParam } );
+				editor.editing.view.focus();
+			} );
+
+			return dropdownView;
+		} );
+	}
+}
+
+function getDropdownItemsDefinitions( placeholderNames ) {
+	const itemDefinitions = new Collection();
+
+	for ( const name of placeholderNames ) {
+		const definition = {
+			type: 'button',
+			model: new Model( {
+				commandParam: name,
+				label: name,
+				withText: true
+			} )
+		};
+
+		// Add the item definition to the collection.
+		itemDefinitions.add( definition );
+	}
+
+	return itemDefinitions;
+}
+```
+
+Add the dropdown to the toolbar:
+
+```js
+// app.js
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import Heading from '@ckeditor/ckeditor5-heading/src/heading';
+import List from '@ckeditor/ckeditor5-list/src/list';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
+import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
+
+import Placeholder from './placeholder/placeholder';
+
+import CKEditorInspector from '@ckeditor/ckeditor5-inspector';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic, Placeholder ],
+
+		// Insert the "placeholder" dropdown to the editor toolbar.
+		toolbar: [ 'heading', 'bold', 'italic', 'numberedList', 'bulletedList', '|', 'placeholder' ]
+	} )
+	.then( editor => {
+		// ...
+	} )
+	.catch( error => {
+		// ...
+	} );
+```
+
+To make this plugin extensible, the types of placeholders will be read from editor configuration.
+
+The first step is to define placeholder configuration in the editing plugin:
+
+```js
+// ... imports
+
+export default class PlaceholderEditing extends Plugin {
+	constructor( editor ) {
+		super( editor );
+
+		this._defineSchema();
+		this._defineConverters();
+
+		this.editor.commands.add( 'placeholder', new PlaceholderCommand( this.editor ) );
+
+		this.editor.config.define( 'placeholder', {                                 // ADDED
+			types: [ 'date', 'first name', 'surname' ]
+		} );
+	}
+
+	_defineConverters() {
+		// ...
+	}
+
+	_defineSchema() {
+		// ...
+	}
+}
+```
+
+Now let's modify the UI plugin so it will read placeholder types from the configuration:
+
+```js
+// placeholder/placeholderui.js
+
+export default class PlaceholderUI extends Plugin {
+	init() {
+		const editor = this.editor;
+
+		const placeholderNames = editor.config.get( 'placeholder.types' );                  // CHANGED
+
+		editor.ui.componentFactory.add( 'placeholder', locale => {
+			// ...
+		} );
+	}
+}
+```
+
+Now the plugins is ready to accept configuration. Let's check how this works by adding `placeholder` configuration in editor's create method:
+
+```js
+// ... imports
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic, Widget, Placeholder ],
+		toolbar: [ 'heading', 'bold', 'italic', 'numberedList', 'bulletedList', '|', 'placeholder' ],
+		placeholder: {
+			types: [ 'model', 'make', 'color' ]                                             // ADDED
+		}
+	} )
+	// ...
+```
+
+Now if you open the dropdown in the toolbar you'll see new list of placeholders to insert.
+
+{@img assets/img/tutorial-implementing-an-inline-widget-2.png Screenshot of the placeholder widgets being inserted using the dropdown.}

+ 7 - 0
docs/umberto.json

@@ -126,6 +126,13 @@
 							"order": 100
 						},
 						{
+							"name": "Tutorials",
+							"id": "framework-tutorials",
+							"slug": "tutorials",
+							"order": 200
+						},
+
+						{
 							"name": "Deep dive",
 							"id": "framework-deep-dive",
 							"slug": "deep-dive",