瀏覽代碼

Merge branch 'master' into t/1597

Piotrek Koszuliński 6 年之前
父節點
當前提交
3b63767fce

+ 12 - 0
README.md

@@ -541,6 +541,18 @@ See CKEditor 5 release blog posts [on the CKEditor blog](https://ckeditor.com/bl
 
 <tr>
 	<td>
+		<a href="https://github.com/ckeditor/ckeditor5-mention"><code>@ckeditor/ckeditor5-mention</code></a>
+	</td>
+	<td>
+		<a href="https://www.npmjs.com/package/@ckeditor/ckeditor5-mention"><img src="https://img.shields.io/npm/v/@ckeditor/ckeditor5-mention.svg" alt="@ckeditor/ckeditor5-mention npm package badge"></a>
+	</td>
+	<td>
+		The mention feature (autocomplete `@mentions`, `#tags`, etc.).
+	</td>
+</tr>
+
+<tr>
+	<td>
 		<a href="https://github.com/ckeditor/ckeditor5-paragraph"><code>@ckeditor/ckeditor5-paragraph</code></a>
 	</td>
 	<td>

+ 16 - 15
docs/_snippets/examples/balloon-block-editor.html

@@ -1,31 +1,32 @@
 <div id="snippet-balloon-block-editor">
-	<h2>Walking the capitals of Europe: Warsaw</h2>
+	<h2>Taj Mahal: A breathtaking ode to love</h2>
 
-	<figure class="media">
-		<oembed url="https://www.youtube.com/watch?v=WmWFwGLA1kQ"></oembed>
+	<figure class="image image-style-side" height="400">
+		<img src="%BASE_PATH%/assets/img/tajmahal.jpg" alt="Taj Mahal illustration.">
+		<figcaption>Taj Mahal with its poetic white marble tomb</figcaption>
 	</figure>
 
-	<p>If you enjoyed my previous articles in which we discussed wandering around <a href="https://en.wikipedia.org/wiki/Copenhagen">Copenhagen</a> and <a href="https://en.wikipedia.org/wiki/Vilnius">Vilnius</a>, you’ll definitely love exploring <a href="https://en.wikipedia.org/wiki/Warsaw">Warsaw</a>.</p>
+	<p>No trip to India is complete without visiting this spectacular monument, <a href="https://en.wikipedia.org/wiki/New7Wonders_of_the_World"><strong>counted among the Seven Wonders of the World</strong></a>.
+	</p>
 
-	<h3>Time to put comfy sandals on!</h3>
+	<p>Tourists frequently admit that Taj Mahal "simply cannot be described with words". And that’s probably true. The more you try the more speechless you become. Words give only a semblance of truth. The real truth about its beauty is revealed when you adore <strong>different shades of “Taj” depending on the time of the day</strong> or when you admire the exquisite inlay work in different corners of the façade.</p>
 
-	<p>Best time to visit the city is July and August, when it’s cool enough to not break a sweat and hot enough to enjoy summer. The city which has quite a combination of both old and modern textures is located by the river of Vistula.</p>
+	<h3>Masterpiece of the world’s heritage</h3>
 
-	<p>The historic <strong>Old Town</strong>, which was reconstructed after the World War II, with its late 18th century characteristics, is a must-see. You can start your walk from the <strong>Nowy Świat Street</strong> which will take you straight to the Old Town.</p>
+	<p>Taj Mahal is a mausoleum built in Agra between 1631 and 1648 by Emperor Shah Jahan <strong>in the memory of his beloved wife</strong>, Mumtaz Mahal, whose body lies there. It took 20 000 workers to complete and the excellence of this building is visible in every brick.</p>
 
-	<p>Then you can go to the <strong>Powiśle</strong> area and take a walk on the newly renovated promenade on the riverfront. There are also lots of cafes, bars and restaurants where you can shake off the exhaustion of the day. On Sundays, there are many parks where you can enjoy nature or listen to pianists from around the world playing Chopin.</p>
+	<p>In 1983, Taj Mahal was appointed <a href="https://en.wikipedia.org/wiki/World_Heritage_Site">UNESCO World Heritage Site</a> for being "the jewel of Muslim art in India and one of the universally admired masterpieces of the world's heritage".</p>
 
-	<p>For museum lovers, you can add these to your list:</p>
+	<p>If you like having a methodology for visiting historical places, here are the four elements on which we recommend to focus your attention:</p>
 
 	<ul>
-		<li><a href="http://www.polin.pl/en">POLIN</a></li>
-		<li><a href="http://www.1944.pl/en">Warsaw Uprising Museum</a></li>
-		<li><a href="http://chopin.museum/en">Fryderyk Chopin Museum</a></li>
+		<li>The tomb</li>
+		<li>The Decorations</li>
+		<li>The Garden</li>
+		<li>The Outlying buildings</li>
 	</ul>
 
-	<h3>Next destination</h3>
-
-	<p>We will go to Berlin and have a night's walk in the city that never sleeps! Make sure you subscribe to our newsletter!</p>
+	<p>The tomb is what immediately catches your eyesight. The <strong>white and soft marble</strong> embroidered with stones leaves you totally enchanted.</p>
 </div>
 
 <style>

+ 7 - 0
docs/_snippets/examples/multi-root-editor.html

@@ -35,3 +35,10 @@
 		</div>
 	</div>
 </div>
+
+<style>
+	/* Give the toolbar some space so it does not look like it belongs to the header root only. */
+	#snippet-inline-editor #toolbar {
+		margin-bottom: 1em;
+	}
+</style>

+ 55 - 3
docs/_snippets/examples/multi-root-editor.js

@@ -177,8 +177,33 @@ class MultirootEditorUI extends EditorUI {
 		const editor = this.editor;
 		const editingView = editor.editing.view;
 
+		let lastFocusedEditableElement;
+
 		view.render();
 
+		// Keep track of the last focused editable element. Knowing which one was focused
+		// is useful when the focus moves from editable to other UI components like balloons
+		// (especially inputs) but the editable remains the "focus context" (e.g. link balloon
+		// attached to a link in an editable). In this case, the editable should preserve visual
+		// focus styles.
+		this.focusTracker.on( 'change:focusedElement', ( evt, name, focusedElement ) => {
+			for ( const editable of this.view.editables ) {
+				if ( editable.element === focusedElement ) {
+					lastFocusedEditableElement = editable.element;
+				}
+			}
+		} );
+
+		// If the focus tracker loses focus, stop tracking the last focused editable element.
+		// Wherever the focus is restored, it will no longer be in the context of that editable
+		// because the focus "came from the outside", as opposed to the focus moving from one element
+		// to another withing the editor UI.
+		this.focusTracker.on( 'change:isFocused', ( evt, name, isFocused ) => {
+			if ( !isFocused ) {
+				lastFocusedEditableElement = null;
+			}
+		} );
+
 		for ( const editable of this.view.editables ) {
 			// The editable UI element in DOM is available for sure only after the editor UI view has been rendered.
 			// But it can be available earlier if a DOM element has been passed to DecoupledEditor.create().
@@ -200,7 +225,29 @@ class MultirootEditorUI extends EditorUI {
 			// it isn't), e.g. by setting the proper CSS class, visually announcing focus to the user.
 			// Doing otherwise will result in editable focus styles disappearing, once e.g. the
 			// toolbar gets focused.
-			editable.bind( 'isFocused' ).to( this.focusTracker );
+			editable.bind( 'isFocused' ).to( this.focusTracker, 'isFocused', this.focusTracker, 'focusedElement',
+				( isFocused, focusedElement ) => {
+					// When the focus tracker is blurred, it means the focus moved out of the editor UI.
+					// No editable will maintain focus then.
+					if ( !isFocused ) {
+						return false;
+					}
+
+					// If the focus tracker says the editor UI is focused and currently focused element
+					// is the editable, then the editable should be visually marked as focused too.
+					if ( focusedElement === editableElement ) {
+						return true;
+					}
+					// If the focus tracker says the editor UI is focused but the focused element is
+					// not an editable, it is possible that the editable is still (context–)focused.
+					// For instance, the focused element could be an input inside of a balloon attached
+					// to the content in the editable. In such case, the editable should remain _visually_
+					// focused even though technically the focus is somewhere else. The focus moved from
+					// the editable to the input but the focus context remained the same.
+					else {
+						return lastFocusedEditableElement === editableElement;
+					}
+				} );
 
 			// Bind the editable UI element to the editing view, making it an end– and entry–point
 			// of the editor's engine. This is where the engine meets the UI.
@@ -261,7 +308,7 @@ class MultirootEditorUI extends EditorUI {
 			const editingRoot = editingView.document.getRoot( editable.name );
 			const sourceElement = this.getEditableElement( editable.name );
 
-			const placeholderText = editor.config.get( 'placeholder' ) ||
+			const placeholderText = editor.config.get( 'placeholder' )[ editable.name ] ||
 				sourceElement && sourceElement.tagName.toLowerCase() === 'textarea' && sourceElement.getAttribute( 'placeholder' );
 
 			if ( placeholderText ) {
@@ -364,7 +411,12 @@ MultirootEditor
 				'mergeTableCells'
 			]
 		},
-		placeholder: 'Type your text here...',
+		placeholder: {
+			header: 'Header text goes here',
+			content: 'Type content here',
+			footerleft: 'Left footer content',
+			footerright: 'Right footer content'
+		},
 		cloudServices: CS_CONFIG
 	} )
 	.then( newEditor => {

+ 16 - 0
docs/_snippets/framework/development-tools/inspector.html

@@ -0,0 +1,16 @@
+<div id="snippet-classic-editor">
+	<h2>CKEditor 5 inspector demo</h2>
+
+	<p>Click the button below to enable the <a href="https://github.com/ckeditor/ckeditor5-inspector">CKEditor 5 inspector</a> for this editor instance.</p>
+
+	<p>Once you do this, you can:</p>
+
+	<ul>
+		<li>Browse and inspect the model and view structures.</li>
+		<li>Observe the selection position.</li>
+		<li>Check the list of commands and their state.</li>
+		<li>More features comming soon!</li>
+	</ul>
+</div>
+
+<button type="button" id="snippet-inspect-button">Inspect editor</button>

+ 29 - 0
docs/_snippets/framework/development-tools/inspector.js

@@ -0,0 +1,29 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals console, window, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-build-classic/src/ckeditor';
+import CKEditorInspector from '@ckeditor/ckeditor5-inspector';
+import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
+
+ClassicEditor
+	.create( document.querySelector( '#snippet-classic-editor' ), {
+		cloudServices: CS_CONFIG,
+		toolbar: {
+			viewportTopOffset: window.getViewportTopOffsetConfig()
+		}
+	} )
+	.then( editor => {
+		window.editor = editor;
+
+		document.querySelector( '#snippet-inspect-button' ).addEventListener( 'click', () => {
+			CKEditorInspector.attach( editor );
+		} );
+	} )
+	.catch( err => {
+		console.error( err );
+	} );
+

二進制
docs/assets/img/framework-development-tools-inspector.jpg


二進制
docs/assets/img/tajmahal.jpg


+ 29 - 16
docs/builds/guides/frameworks/angular.md

@@ -39,11 +39,10 @@ import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
 
 @NgModule( {
 	imports: [
-		...
 		CKEditorModule,
-		...
+		// ...
 	],
-	...
+	// ...
 } )
 ```
 
@@ -53,11 +52,11 @@ Import the editor build in your Angular component and assign it to a `public` pr
 import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
 
 @Component( {
-	...
+	// ...
 } )
 export class MyComponent {
 	public Editor = ClassicEditor;
-	...
+	// ...
 }
 ```
 
@@ -77,8 +76,7 @@ If you want to use the {@link framework/guides/document-editor Document editor b
 import * as DecoupledEditor from '@ckeditor/ckeditor5-build-decoupled-document';
 
 @Component( {
-	...
-} )
+	// ...
 export class MyComponent {
 	public Editor = DecoupledEditor;
 
@@ -105,13 +103,13 @@ The component implements the [`ControlValueAccessor`](https://angular.io/api/for
 
 	```ts
 	@Component( {
-		...
+		// ...
 	} )
 	export class MyComponent {
 		public model = {
 			editorData: '<p>Hello, world!</p>'
 		};
-		...
+		// ...
 	}
 	```
 
@@ -153,11 +151,11 @@ or a shared parent component's property
 
 ```ts
 @Component( {
-	...
+	// ...
 } )
 export class MyComponent {
 	public editorData = '<p>Hello, world!</p>';
-	...
+	// ...
 }
 ```
 
@@ -181,11 +179,11 @@ Controls the editor's {@link module:core/editor/editor~Editor#isReadOnly read–
 
 ```ts
 @Component( {
-	...
+	// ...
 } )
 export class MyComponent {
 	public isDisabled = false;
-	...
+	// ...
 	toggleDisabled() {
 		this.isDisabled = !this.isDisabled
 	}
@@ -222,7 +220,7 @@ import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
 import { ChangeEvent } from '@ckeditor/ckeditor5-angular/ckeditor.component';
 
 @Component( {
-	...
+	// ...
 } )
 export class MyComponent {
 	public Editor = ClassicEditor;
@@ -299,9 +297,24 @@ Then, add it in the `angular.json` configuration file.
 }
 ```
 
+### Setting the placeholder
+
+To display {@link features/editor-placeholder the placeholder} in the main editable element simply set the `placeholder` field in the CKEditor 5 component configuration:
+
+```ts
+@Component( {
+	// ...
+} )
+export class MyComponent {
+	public config = {
+		placeholder: 'Type the content here!'
+	}
+}
+```
+
 ## Localization
 
-CKEditor 5 can be localized in two steps.
+The CKEditor 5 component can be localized in two steps.
 
 ### 1. Loading translation files
 
@@ -333,7 +346,7 @@ Then, you need to configure the editor to use the given language:
 
 ```ts
 @Component( {
-	...
+	// ...
 } )
 export class MyComponent {
 	public Editor = ClassicEditor;

+ 2 - 0
docs/builds/guides/frameworks/vuejs.md

@@ -301,6 +301,8 @@ Now all you need to do is specify the list of rich text editor options (**includ
 </template>
 
 <script>
+	// ⚠️ NOTE: We don't use @ckeditor/ckeditor5-build-classic any more!
+	// Since we're building CKEditor from source, we use the source version of ClassicEditor.
 	import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
 
 	import EssentialsPlugin from '@ckeditor/ckeditor5-essentials/src/essentials';

+ 1 - 1
docs/builds/guides/migrate.md

@@ -80,7 +80,7 @@ Note: The number of options was reduced on purpose. We understood that configuri
 			<td><span id="allowedContent"><a href="/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-allowedContent">allowedContent</a></span></td>
 			<td>
 				<p>Extending the list of HTML tags or attributes that CKEditor should support can be achieved by writing a plugin that (ideally) provides also means to control (insert, edit, delete) such markup.</p>
-				<p>For more information on how to create plugins check the {@link framework/guides/quick-start#creating-a-simple-plugin Creating a simple plugin} article. Looking at the source code of CKEditor 5 plugins may also give you a lot of inspiration.</p>
+				<p>For more information on how to create plugins check the {@link framework/guides/creating-simple-plugin Creating a simple plugin} article. Looking at the source code of CKEditor 5 plugins may also give you a lot of inspiration.</p>
 			</td>
 		</tr>
 		<tr>

+ 1 - 1
docs/features/index.md

@@ -24,6 +24,6 @@ Please bear in mind that the stable version of CKEditor 5 has not been officiall
 ## How about creating your own features?
 
 Probably the most exciting features are the ones you can develop on top of CKEditor 5 Framework!
-We are slowly enhancing the {@link framework/index CKEditor 5 Framework documentation} together with {@link api/index API documentation}, hoping to give you a solid base for {@link framework/guides/quick-start#creating-a-simple-plugin creating custom features}.
+We are slowly enhancing the {@link framework/index CKEditor 5 Framework documentation} together with {@link api/index API documentation}, hoping to give you a solid base for {@link framework/guides/creating-simple-plugin creating custom features}.
 
 The official add-ons repository for CKEditor 4 reached an impressive number of over 300 add-ons created and published by the community. Now it is time for you to add your contributions to the brand new CKEditor 5!

+ 4 - 0
docs/framework/guides/architecture/core-editor-architecture.md

@@ -70,6 +70,10 @@ You can see how to implement a simple plugin in the {@link framework/guides/quic
 
 A command is a combination of an action (a callback) and a state (a set of properties). For instance, the `bold` command applies or removes the bold attribute from the selected text. If the text in which the selection is placed has bold applied already, the value of the command is `true`, `false` otherwise. If the `bold` command can be executed on the current selection, it is enabled. If not (because, for example, bold is not allowed in this place), it is disabled.
 
+<info-box>
+	We recommend using the official {@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} for development and debugging. It will give you tons of useful information about the state of the editor such as internal data structures, selection, commands, and many more.
+</info-box>
+
 All commands need to inherit from the {@link module:core/command~Command} class. Commands need to be added to the editor's {@link module:core/editor/editor~Editor#commands command collection} so they can be executed by using the {@link module:core/editor/editor~Editor#execute `Editor#execute()`} method.
 
 Take this example:

+ 4 - 0
docs/framework/guides/architecture/editing-engine.md

@@ -7,6 +7,10 @@ order: 30
 
 The [`@ckeditor/ckeditor5-engine`](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine) package is by far the biggest package of all. Therefore, this guide will only scratch the surface here by introducing the main architecture layers and concepts. More detailed guides will follow.
 
+<info-box>
+	We recommend using the official {@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} for development and debugging. It will give you tons of useful information about the state of the editor such as internal data structures, selection, commands, and many more.
+</info-box>
+
 ## Overview
 
 The editing engine implements an MVC architecture. The shape of it is not enforced by the engine itself but in most implementations it can be described by this diagram:

+ 8 - 2
docs/framework/guides/contributing/development-environment.md

@@ -139,14 +139,20 @@ git pull
 
 # To update pull changes to all the packages:
 mgit sync
+
+# To install missing dependencies:
+yarn install
 ```
 
-From time to time, if the list of dependencies in any of the packages changed, you will need to call Yarn again to symlink them:
+From time to time, if the list of dependencies in any of the packages changed, new package has been added to `ckeditor5` or you just want to make sure that you have the repository up to date, run the `reinstall` script:
 
 ```bash
-yarn install
+git pull
+yarn run reinstall
 ```
 
+The `reinstall` script first calls `yarn run clean` to remove `node_modules/` directories from all packages (including `ckeditor5`) and then `yarn run bootstrap` which is a shorthand for `mgit sync && yarn install`.
+
 ### Working with multiple repositories
 
 Mgit implements many useful commands, such as:

+ 4 - 0
docs/framework/guides/contributing/testing-environment.md

@@ -117,6 +117,10 @@ ClassicEditor
 	Do not forget to add all dependencies of your manual test as `devDependencies` (in `package.json`).
 </info-box>
 
+<info-box>
+	We recommend using the official {@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} for development and debugging. It will give you tons of useful information about the state of the editor such as internal data structures, selection, commands, and many more.
+</info-box>
+
 ## Test suite and CI
 
 To ensure the highest quality, we maintain a complete test suite with a stable 100% of code coverage for each of the packages. As of May 2018, this means over 8000 tests and the number is growing. Since every package is tested separately, we implement lower-level tests for libraries and higher-level tests for end-user features.

+ 294 - 0
docs/framework/guides/creating-simple-plugin.md

@@ -0,0 +1,294 @@
+---
+category: framework-guides
+order: 30
+---
+
+# Creating a simple plugin
+
+This guide will show you how to create a simple editor plugin.
+
+<info-box>
+	Before you get to work, you should check out the {@link framework/guides/quick-start "Quick start"} guide first and set up the framework and building tools.
+</info-box>
+
+CKEditor plugins need to implement the {@link module:core/plugin~PluginInterface}. The easiest way to do that is to inherit from the {@link module:core/plugin~Plugin base `Plugin` class}, however, you can also write simple constructor functions. This guide uses the former method.
+
+The plugin that you will write will use a part of the {@link features/image image feature} and will add a simple UI to it &mdash; an "Insert image" button that will open a prompt window asking for the image URL when clicked. Submitting the URL will result in inserting the image into the content and selecting it.
+
+<info-box>
+	We recommend using the official {@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} for development and debugging. It will give you tons of useful information about the state of the editor such as internal data structures, selection, commands, and many more.
+</info-box>
+
+## Step 1. Installing dependencies
+
+Start from installing necessary dependencies:
+
+* The [`@ckeditor/ckeditor5-image`](https://www.npmjs.com/package/@ckeditor/ckeditor5-image) package that contains the image feature (on which the plugin will rely).
+* The [`@ckeditor/ckeditor5-core`](https://www.npmjs.com/package/@ckeditor/ckeditor5-core) package which contains the {@link module:core/plugin~Plugin} and {@link module:core/command~Command} classes.
+* The [`@ckeditor/ckeditor5-ui`](https://www.npmjs.com/package/@ckeditor/ckeditor5-ui) package which contains the UI library and framework.
+
+```bash
+npm install --save @ckeditor/ckeditor5-image \
+	@ckeditor/ckeditor5-core \
+	@ckeditor/ckeditor5-ui
+```
+
+<info-box>
+	Most of the time, you will also want to install the [`@ckeditor/ckeditor5-engine`](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine) package (it contains the {@link framework/guides/architecture/editing-engine editing engine}). It was omitted in this guide because it is unnecessary for a simple plugin like this one.
+</info-box>
+
+Now, open the `app.js` file and start adding code there. Usually, when implementing more complex features you will want to split the code into multiple files (modules). However, to make this guide simpler the entire code will be kept in `app.js`.
+
+First thing to do will be to load the core of the image feature:
+
+```js
+import Image from '@ckeditor/ckeditor5-image/src/image';
+
+// ...
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		// Add Image to the plugin list.
+		plugins: [ Essentials, Paragraph, Bold, Italic, Image ],
+
+		// ...
+	} )
+	// ...
+```
+
+Save the file and run webpack. Refresh the page in your browser (**remember about the cache**) and... you should not see any changes. This is correct! The core of the image feature does not come with any UI, nor have you added any image to the initial HTML. Change this now:
+
+```html
+<div id="editor">
+	<p>Simple image:</p>
+
+	<figure class="image">
+		<img src="https://via.placeholder.com/1000x300/02c7cd/fff?text=Placeholder%20image" alt="CKEditor 5 rocks!">
+	</figure>
+</div>
+```
+
+{@img assets/img/framework-quick-start-classic-editor-with-image.png 837 Screenshot of a classic editor with bold, italic and image features.}
+
+<info-box>
+	Running webpack with the `-w` option will start it in the watch mode. This means that webpack will watch your files for changes and rebuild the application every time you save them.
+</info-box>
+
+## Step 2. Creating a plugin
+
+You can now start implementing your new plugin. Create the `InsertImage` plugin:
+
+```js
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+
+class InsertImage extends Plugin {
+	init() {
+		console.log( 'InsertImage was initialized' );
+	}
+}
+```
+
+And add your new plugin to the `config.plugins` array. After rebuilding the application and refreshing the page you should see "InsertImage was initialized" logged to the console.
+
+<info-box hint>
+	It was said that your `InsertImage` plugin relies on the image feature represented here by the `Image` plugin. You could add the `Image` plugin as a {@link module:core/plugin~PluginInterface#requires dependency} of the `InsertImage` plugin. This would make the editor initialize `Image` automatically before initializing `InsertImage`, so you would be able to remove `Image` from `config.plugins`.
+
+	However, this means that your plugin would be coupled with the `Image` plugin. This is unnecessary &mdash; they do not need to know about each other. And while it does not change anything in this simple example, it is a good practice to keep plugins as decoupled as possible.
+</info-box>
+
+## Step 3. Registering a button
+
+Create a button now:
+
+```js
+// This SVG file import will be handled by webpack's raw-text loader.
+// This means that imageIcon will hold the source SVG.
+import imageIcon from '@ckeditor/ckeditor5-core/theme/icons/image.svg';
+
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+
+class InsertImage extends Plugin {
+	init() {
+		const editor = this.editor;
+
+		editor.ui.componentFactory.add( 'insertImage', locale => {
+			const view = new ButtonView( locale );
+
+			view.set( {
+				label: 'Insert image',
+				icon: imageIcon,
+				tooltip: true
+			} );
+
+			// Callback executed once the image is clicked.
+			view.on( 'execute', () => {
+				const imageURL = prompt( 'Image URL' );
+			} );
+
+			return view;
+		} );
+	}
+}
+```
+
+And add `insertImage` to `config.toolbar`:
+
+```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		// ...
+
+		toolbar: [ 'bold', 'italic', 'insertImage' ]
+	} )
+	// ...
+```
+
+Rebuild the application and refresh the page. You should see a new button in the toolbar. Clicking the button should open a prompt window asking you for the image URL.
+
+## Step 4. Inserting a new image
+
+Now, expand the button's `#execute` event listener, so it will actually insert a new image into the content:
+
+```js
+class InsertImage extends Plugin {
+	init() {
+		const editor = this.editor;
+
+		editor.ui.componentFactory.add( 'insertImage', locale => {
+			const view = new ButtonView( locale );
+
+			view.set( {
+				label: 'Insert image',
+				icon: imageIcon,
+				tooltip: true
+			} );
+
+			// Callback executed once the image is clicked.
+			view.on( 'execute', () => {
+				const imageUrl = prompt( 'Image URL' );
+
+				editor.model.change( writer => {
+					const imageElement = writer.createElement( 'image', {
+						src: imageUrl
+					} );
+
+					// Insert the image in the current selection location.
+					editor.model.insertContent( imageElement, editor.model.document.selection );
+				} );
+			} );
+
+			return view;
+		} );
+	}
+}
+```
+
+If you refresh the page, you should now be able to insert new images into the content:
+
+{@img assets/img/framework-quick-start-classic-editor-insert-image.gif 640 Screencast of inserting a new image.}
+
+The image is fully functional, you can undo inserting by pressing <kbd>Ctrl</kbd>+<kbd>Z</kbd> and the image is always inserted as a block element (the paragraph that contains the selection is automatically split). This is all handled by the CKEditor 5 engine.
+
+<info-box>
+	As you can see, by clicking the button you are inserting an `<image src="...">` element into the model. The image feature is represented in the model as `<image>`, while in the view (i.e. the virtual DOM) and in the real DOM it is rendered as `<figure class="image"><img src="..."></figure>`.
+
+	The `<image>` to `<figure><img></figure>` transformation is called "conversion" and it requires a separate guide. However, as you can see in this example, it is a powerful mechanism because it allows non-1:1 mappings.
+</info-box>
+
+Congratulations! You have just created your first CKEditor 5 plugin!
+
+## Bonus. Enabling image captions
+
+Thanks to the fact that all plugins operate on the model and on the view and know as little about themselves as possible, you can easily enable image captions by simply loading the {@link module:image/imagecaption~ImageCaption} plugin:
+
+```js
+import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
+
+// ...
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		// Add ImageCaption to the plugin list.
+		plugins: [ Essentials, Paragraph, Bold, Italic, Image, InsertImage, ImageCaption ],
+
+		// ...
+	} )
+	// ...
+```
+
+This should be the result of the change:
+
+{@img assets/img/framework-quick-start-classic-editor-bonus.gif 640 Screencast of inserting a new image with a caption.}
+
+## Final code
+
+If you got lost at any point, this should be your final `app.js`:
+
+```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 Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
+import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
+import Image from '@ckeditor/ckeditor5-image/src/image';
+import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+
+import imageIcon from '@ckeditor/ckeditor5-core/theme/icons/image.svg';
+
+class InsertImage extends Plugin {
+	init() {
+		const editor = this.editor;
+
+		editor.ui.componentFactory.add( 'insertImage', locale => {
+			const view = new ButtonView( locale );
+
+			view.set( {
+				label: 'Insert image',
+				icon: imageIcon,
+				tooltip: true
+			} );
+
+			// Callback executed once the image is clicked.
+			view.on( 'execute', () => {
+				const imageUrl = prompt( 'Image URL' );
+
+				editor.model.change( writer => {
+					const imageElement = writer.createElement( 'image', {
+						src: imageUrl
+					} );
+
+					// Insert the image in the current selection location.
+					editor.model.insertContent( imageElement, editor.model.document.selection );
+				} );
+			} );
+
+			return view;
+		} );
+	}
+}
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Essentials, Paragraph, Bold, Italic, Image, InsertImage, ImageCaption ],
+		toolbar: [ 'bold', 'italic', 'insertImage' ]
+	} )
+	.then( editor => {
+		console.log( 'Editor was initialized', editor );
+	} )
+	.catch( error => {
+		console.error( error.stack );
+	} );
+```
+
+## What's next?
+
+If you would like to read more tutorials check out the following one:
+
+* {@link framework/guides/tutorials/implementing-a-block-widget Implementing a block widget}
+* {@link framework/guides/tutorials/implementing-an-inline-widget Implementing an inline widget}
+
+If you are more into reading about CKEditor 5's architecture, check out the {@link framework/guides/architecture/intro Introduction to CKEditor 5 architecture}.

+ 55 - 3
docs/framework/guides/custom-editor-creator.md

@@ -169,8 +169,33 @@ class MultirootEditorUI extends EditorUI {
 		const editor = this.editor;
 		const editingView = editor.editing.view;
 
+		let lastFocusedEditableElement;
+
 		view.render();
 
+		// Keep track of the last focused editable element. Knowing which one was focused
+		// is useful when the focus moves from editable to other UI components like balloons
+		// (especially inputs) but the editable remains the "focus context" (e.g. link balloon
+		// attached to a link in an editable). In this case, the editable should preserve visual
+		// focus styles.
+		this.focusTracker.on( 'change:focusedElement', ( evt, name, focusedElement ) => {
+			for ( const editable of this.view.editables ) {
+				if ( editable.element === focusedElement ) {
+					lastFocusedEditableElement = editable.element;
+				}
+			}
+		} );
+
+		// If the focus tracker loses focus, stop tracking the last focused editable element.
+		// Wherever the focus is restored, it will no longer be in the context of that editable
+		// because the focus "came from the outside", as opposed to the focus moving from one element
+		// to another withing the editor UI.
+		this.focusTracker.on( 'change:isFocused', ( evt, name, isFocused ) => {
+			if ( !isFocused ) {
+				lastFocusedEditableElement = null;
+			}
+		} );
+
 		for ( const editable of this.view.editables ) {
 			// The editable UI element in DOM is available for sure only after the editor UI view has been rendered.
 			// But it can be available earlier if a DOM element has been passed to DecoupledEditor.create().
@@ -192,7 +217,29 @@ class MultirootEditorUI extends EditorUI {
 			// it isn't), e.g. by setting the proper CSS class, visually announcing focus to the user.
 			// Doing otherwise will result in editable focus styles disappearing, once e.g. the
 			// toolbar gets focused.
-			editable.bind( 'isFocused' ).to( this.focusTracker );
+			editable.bind( 'isFocused' ).to( this.focusTracker, 'isFocused', this.focusTracker, 'focusedElement',
+				( isFocused, focusedElement ) => {
+					// When the focus tracker is blurred, it means the focus moved out of the editor UI.
+					// No editable will maintain focus then.
+					if ( !isFocused ) {
+						return false;
+					}
+
+					// If the focus tracker says the editor UI is focused and currently focused element
+					// is the editable, then the editable should be visually marked as focused too.
+					if ( focusedElement === editableElement ) {
+						return true;
+					}
+					// If the focus tracker says the editor UI is focused but the focused element is
+					// not an editable, it is possible that the editable is still (context–)focused.
+					// For instance, the focused element could be an input inside of a balloon attached
+					// to the content in the editable. In such case, the editable should remain _visually_
+					// focused even though technically the focus is somewhere else. The focus moved from
+					// the editable to the input but the focus context remained the same.
+					else {
+						return lastFocusedEditableElement === editableElement;
+					}
+				} );
 
 			// Bind the editable UI element to the editing view, making it an end– and entry–point
 			// of the editor's engine. This is where the engine meets the UI.
@@ -253,7 +300,7 @@ class MultirootEditorUI extends EditorUI {
 			const editingRoot = editingView.document.getRoot( editable.name );
 			const sourceElement = this.getEditableElement( editable.name );
 
-			const placeholderText = editor.config.get( 'placeholder' ) ||
+			const placeholderText = editor.config.get( 'placeholder' )[ editable.name ] ||
 				sourceElement && sourceElement.tagName.toLowerCase() === 'textarea' && sourceElement.getAttribute( 'placeholder' );
 
 			if ( placeholderText ) {
@@ -414,7 +461,12 @@ MultirootEditor
 				'mergeTableCells'
 			]
 		},
-		placeholder: 'Type your text here...'
+		placeholder: {
+			header: 'Header text goes here',
+			content: 'Type content here',
+			footerleft: 'Left footer content',
+			footerright: 'Right footer content'
+		},
 	} )
 	.then( newEditor => {
 		document.querySelector( '#toolbar' ).appendChild( newEditor.ui.view.toolbar.element );

+ 105 - 0
docs/framework/guides/development-tools.md

@@ -0,0 +1,105 @@
+---
+category: framework-guides
+order: 40
+---
+
+# Development tools
+
+In this guide you will learn about developer tools that will help you develop and debug editor plugins and features.
+
+## CKEditor 5 inspector
+
+The official [CKEditor 5 inspector](https://github.com/ckeditor/ckeditor5-inspector) provides rich debugging tools for editor internals like {@link framework/guides/architecture/editing-engine#model model}, {@link framework/guides/architecture/editing-engine#view view}, and  {@link framework/guides/architecture/core-editor-architecture#commands commands}.
+
+It allows observing changes to the data structures and the selection live in the editor which is particularly helpful when developing new editor features or getting to understand the existing ones.
+
+{@img assets/img/framework-development-tools-inspector.jpg Screenshot of a CKEditor 5 inspector attached to an editor instance.}
+
+### Importing the inspector
+
+You can import the inspector as an [`@ckeditor/ckeditor5-inspector`](https://www.npmjs.com/package/@ckeditor/ckeditor5-inspector) package into your project:
+
+```
+npm install --save-dev @ckeditor/ckeditor5-inspector
+```
+
+and then either import it as a module:
+
+```js
+import CKEditorInspector from '@ckeditor/ckeditor5-inspector';
+```
+
+or as a plain `<script>` tag in the HTML of your application:
+
+```html
+<script src="../node_modules/@ckeditor/ckeditor5-inspector/build/inspector.js"></script>
+```
+
+### Enabling the inspector
+
+Attach the inspector to the editor instance when {@link builds/guides/integration/basic-api#creating-an-editor created} using the `CKEditorInspector.attach()` method:
+
+```js
+ClassicEditor
+	.create( ... )
+	.then( editor => {
+		CKEditorInspector.attach( editor );
+	} )
+	.catch( error => {
+		console.error( error );
+	} );
+```
+
+The inspector will show up at the bottom of the screen.
+
+### Inspecting multiple editors
+
+You can inspect multiple editor instances at a time by calling `CKEditorInspector.attach()` for each one of them. Then you can switch the inspector context to inspect different editors.
+
+You can specify the name of the editor when attaching to make working with multiple instances easier:
+
+```js
+// Inspecting two editor instances at the same time.
+CKEditorInspector.attach( 'header-editor' editor );
+CKEditorInspector.attach( 'body-editor' editor );
+```
+
+The editor switcher is in the upper–right corner of the inspector panel.
+
+### Demo
+
+Click the <b>"Inspect editor"</b> button below to attach the inspector to the editor:
+
+{@snippet framework/development-tools/inspector}
+
+### Contributing to the inspector
+
+The source code of CKEditor 5 inspector and its issue tracker is available on GitHub in https://github.com/ckeditor/ckeditor5-inspector.
+
+## Testing helpers
+
+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} are useful development helpers.
+
+They allow "stringifying" {@link framework/guides/architecture/editing-engine#model model} and {@link framework/guides/architecture/editing-engine#view view} structures, selections, ranges, and positions as well as loading them from a string. They are often used when writing tests.
+
+<info-box>
+	Both tools are designed for prototyping, debugging, and testing purposes. Do not use them in production-grade code.
+</info-box>
+
+For instance, to take a peek at the editor model, you could use the {@link module:engine/dev-utils/model#static-function-getData `getData()`} helper:
+
+```js
+import { getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+
+// ...
+
+ClassicEditor
+	.create( '<p>Hello <b>world</b>!</p>' )
+	.then( editor => {
+		console.log( getData( editor.model ) );
+
+		// -> '<paragraph>[]Hello <$text bold="true">world</$text>!</paragraph>'
+	} );
+```
+
+See the helper documentation to learn more about useful options.

+ 9 - 269
docs/framework/guides/quick-start.md

@@ -5,7 +5,7 @@ order: 20
 
 # Quick start
 
-This guide will show you how to initialize the editor from source and how to create a simple plugin.
+This guide will show you how to initialize the editor from source.
 
 ## How to install the framework?
 
@@ -96,6 +96,8 @@ module.exports = {
 };
 ```
 
+## Creating an editor
+
 You can now install some of the CKEditor 5 Framework packages which will allow you to initialize a simple editor. You can start with the {@link examples/builds/classic-editor classic editor} with a small set of features.
 
 ```bash
@@ -191,6 +193,8 @@ bundle.js.map  2.39 MiB    main  [emitted]  main
     + 491 hidden modules
 ```
 
+## Running the editor
+
 Finally, it is time to create an HTML page:
 
 ```html
@@ -214,276 +218,12 @@ Open this page in your browser and you should see the editor up and running. Mak
 
 {@img assets/img/framework-quick-start-classic-editor.png 976 Screenshot of a classic editor with bold and italic features.}
 
-## Creating a simple plugin
-
-After you initilized the editor from source, you are ready to create your first CKEditor 5 plugin.
-
-CKEditor plugins need to implement the {@link module:core/plugin~PluginInterface}. The easiest way to do that is to inherit from the {@link module:core/plugin~Plugin base `Plugin` class}, however, you can also write simple constructor functions. This guide uses the former method.
-
-The plugin that you will write will use a part of the {@link features/image image feature} and will add a simple UI to it &mdash; an "Insert image" button that will open a prompt window asking for the image URL when clicked. Submitting the URL will result in inserting the image into the content and selecting it.
-
-### Step 1. Installing dependencies
-
-Start from installing necessary dependencies:
-
-* The [`@ckeditor/ckeditor5-image`](https://www.npmjs.com/package/@ckeditor/ckeditor5-image) package that contains the image feature (on which the plugin will rely).
-* The [`@ckeditor/ckeditor5-core`](https://www.npmjs.com/package/@ckeditor/ckeditor5-core) package which contains the {@link module:core/plugin~Plugin} and {@link module:core/command~Command} classes.
-* The [`@ckeditor/ckeditor5-ui`](https://www.npmjs.com/package/@ckeditor/ckeditor5-ui) package which contains the UI library and framework.
-
-```bash
-npm install --save @ckeditor/ckeditor5-image \
-	@ckeditor/ckeditor5-core \
-	@ckeditor/ckeditor5-ui
-```
-
-<info-box>
-	Most of the time, you will also want to install the [`@ckeditor/ckeditor5-engine`](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine) package (it contains the {@link framework/guides/architecture/editing-engine editing engine}). It was omitted in this guide because it is unnecessary for a simple plugin like this one.
-</info-box>
-
-Now, open the `app.js` file and start adding code there. Usually, when implementing more complex features you will want to split the code into multiple files (modules). However, to make this guide simpler the entire code will be kept in `app.js`.
-
-First thing to do will be to load the core of the image feature:
-
-```js
-import Image from '@ckeditor/ckeditor5-image/src/image';
-
-// ...
-
-ClassicEditor
-	.create( document.querySelector( '#editor' ), {
-		// Add Image to the plugin list.
-		plugins: [ Essentials, Paragraph, Bold, Italic, Image ],
-
-		// ...
-	} )
-	// ...
-```
-
-Save the file and run webpack. Refresh the page in your browser (**remember about the cache**) and... you should not see any changes. This is correct! The core of the image feature does not come with any UI, nor have you added any image to the initial HTML. Change this now:
-
-```html
-<div id="editor">
-	<p>Simple image:</p>
-
-	<figure class="image">
-		<img src="https://via.placeholder.com/1000x300/02c7cd/fff?text=Placeholder%20image" alt="CKEditor 5 rocks!">
-	</figure>
-</div>
-```
-
-{@img assets/img/framework-quick-start-classic-editor-with-image.png 837 Screenshot of a classic editor with bold, italic and image features.}
-
-<info-box>
-	Running webpack with the `-w` option will start it in the watch mode. This means that webpack will watch your files for changes and rebuild the application every time you save them.
-</info-box>
-
-### Step 2. Creating a plugin
-
-You can now start implementing your new plugin. Create the `InsertImage` plugin:
-
-```js
-import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-
-class InsertImage extends Plugin {
-	init() {
-		console.log( 'InsertImage was initialized' );
-	}
-}
-```
-
-And add your new plugin to the `config.plugins` array. After rebuilding the application and refreshing the page you should see "InsertImage was initialized" logged to the console.
-
-<info-box hint>
-	It was said that your `InsertImage` plugin relies on the image feature represented here by the `Image` plugin. You could add the `Image` plugin as a {@link module:core/plugin~PluginInterface#requires dependency} of the `InsertImage` plugin. This would make the editor initialize `Image` automatically before initializing `InsertImage`, so you would be able to remove `Image` from `config.plugins`.
-
-	However, this means that your plugin would be coupled with the `Image` plugin. This is unnecessary &mdash; they do not need to know about each other. And while it does not change anything in this simple example, it is a good practice to keep plugins as decoupled as possible.
-</info-box>
-
-### Step 3. Registering a button
-
-Create a button now:
-
-```js
-// This SVG file import will be handled by webpack's raw-text loader.
-// This means that imageIcon will hold the source SVG.
-import imageIcon from '@ckeditor/ckeditor5-core/theme/icons/image.svg';
-
-import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
-
-class InsertImage extends Plugin {
-	init() {
-		const editor = this.editor;
-
-		editor.ui.componentFactory.add( 'insertImage', locale => {
-			const view = new ButtonView( locale );
-
-			view.set( {
-				label: 'Insert image',
-				icon: imageIcon,
-				tooltip: true
-			} );
-
-			// Callback executed once the image is clicked.
-			view.on( 'execute', () => {
-				const imageURL = prompt( 'Image URL' );
-			} );
-
-			return view;
-		} );
-	}
-}
-```
-
-And add `insertImage` to `config.toolbar`:
-
-```js
-ClassicEditor
-	.create( document.querySelector( '#editor' ), {
-		// ...
-
-		toolbar: [ 'bold', 'italic', 'insertImage' ]
-	} )
-	// ...
-```
-
-Rebuild the application and refresh the page. You should see a new button in the toolbar. Clicking the button should open a prompt window asking you for the image URL.
-
-### Step 4. Inserting a new image
-
-Now, expand the button's `#execute` event listener, so it will actually insert a new image into the content:
-
-```js
-class InsertImage extends Plugin {
-	init() {
-		const editor = this.editor;
-
-		editor.ui.componentFactory.add( 'insertImage', locale => {
-			const view = new ButtonView( locale );
-
-			view.set( {
-				label: 'Insert image',
-				icon: imageIcon,
-				tooltip: true
-			} );
-
-			// Callback executed once the image is clicked.
-			view.on( 'execute', () => {
-				const imageUrl = prompt( 'Image URL' );
-
-				editor.model.change( writer => {
-					const imageElement = writer.createElement( 'image', {
-						src: imageUrl
-					} );
-
-					// Insert the image in the current selection location.
-					editor.model.insertContent( imageElement, editor.model.document.selection );
-				} );
-			} );
-
-			return view;
-		} );
-	}
-}
-```
-
-If you refresh the page, you should now be able to insert new images into the content:
-
-{@img assets/img/framework-quick-start-classic-editor-insert-image.gif 640 Screencast of inserting a new image.}
-
-The image is fully functional, you can undo inserting by pressing <kbd>Ctrl</kbd>+<kbd>Z</kbd> and the image is always inserted as a block element (the paragraph that contains the selection is automatically split). This is all handled by the CKEditor 5 engine.
-
 <info-box>
-	As you can see, by clicking the button you are inserting an `<image src="...">` element into the model. The image feature is represented in the model as `<image>`, while in the view (i.e. the virtual DOM) and in the real DOM it is rendered as `<figure class="image"><img src="..."></figure>`.
-
-	The `<image>` to `<figure><img></figure>` transformation is called "conversion" and it requires a separate guide. However, as you can see in this example, it is a powerful mechanism because it allows non-1:1 mappings.
+	We recommend using the official {@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} for development and debugging. It will give you tons of useful information about the state of the editor such as internal data structures, selection, commands, and many more.
 </info-box>
 
-Congratulations! You have just created your first CKEditor 5 plugin!
-
-### Bonus. Enabling image captions
-
-Thanks to the fact that all plugins operate on the model and on the view and know as little about themselves as possible, you can easily enable image captions by simply loading the {@link module:image/imagecaption~ImageCaption} plugin:
-
-```js
-import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
-
-// ...
-
-ClassicEditor
-	.create( document.querySelector( '#editor' ), {
-		// Add ImageCaption to the plugin list.
-		plugins: [ Essentials, Paragraph, Bold, Italic, Image, InsertImage, ImageCaption ],
-
-		// ...
-	} )
-	// ...
-```
-
-This should be the result of the change:
-
-{@img assets/img/framework-quick-start-classic-editor-bonus.gif 640 Screencast of inserting a new image with a caption.}
+## What's next?
 
-### Final code
-
-If you got lost at any point, this should be your final `app.js`:
-
-```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 Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
-import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
-import Image from '@ckeditor/ckeditor5-image/src/image';
-import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
-
-import Plugin from '@ckeditor/ckeditor5-core/src/plugin';;
-import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
-
-import imageIcon from '@ckeditor/ckeditor5-core/theme/icons/image.svg'
-
-class InsertImage extends Plugin {
-	init() {
-		const editor = this.editor;
-
-		editor.ui.componentFactory.add( 'insertImage', locale => {
-			const view = new ButtonView( locale );
-
-			view.set( {
-				label: 'Insert image',
-				icon: imageIcon,
-				tooltip: true
-			} );
-
-			// Callback executed once the image is clicked.
-			view.on( 'execute', () => {
-				const imageUrl = prompt( 'Image URL' );
-
-				editor.model.change( writer => {
-					const imageElement = writer.createElement( 'image', {
-						src: imageUrl
-					} );
-
-					// Insert the image in the current selection location.
-					editor.model.insertContent( imageElement, editor.model.document.selection );
-				} );
-			} );
-
-			return view;
-		} );
-	}
-}
-
-ClassicEditor
-	.create( document.querySelector( '#editor' ), {
-		plugins: [ Essentials, Paragraph, Bold, Italic, Image, InsertImage, ImageCaption ],
-		toolbar: [ 'bold', 'italic', 'insertImage' ]
-	} )
-	.then( editor => {
-		console.log( 'Editor was initialized', editor );
-	} )
-	.catch( error => {
-		console.error( error.stack );
-	} );
-```
+If you finished this guide, you should definitely check out the {@link framework/guides/creating-simple-plugin "Creating a simple plugin"} guide that will teach you some basics of developing features in the CKEditor 5 ecosystem.
 
+If you are more into reading about CKEditor 5's architecture, check out the {@link framework/guides/architecture/intro Introduction to CKEditor 5 architecture}.

+ 9 - 15
docs/framework/guides/tutorials/implementing-a-block-widget.md

@@ -487,15 +487,7 @@ The HTML that you have added to the `index.html` file is your editor's data. Thi
 
 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:
+To learn that, we recommend using the official {@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector}. Once {@link framework/guides/development-tools#ckeditor-5-inspector#installing-the-inspector installed}, you need to load it in the `app.js` file:
 
 ```js
 // app.js
@@ -534,7 +526,7 @@ ClassicEditor
 
 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.}
+{@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:
 
@@ -555,9 +547,7 @@ As you can see, this structure is quite different than the HTML input/output. If
 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.
+	You can also use some {@link framework/guides/development-tools#testing-helpers useful helpers like `getData()` and `setData()`} to learn more about the state of the editor model or write assertions in tests.
 </info-box>
 
 ### Behavior before "widgetizing" simple box
@@ -757,7 +747,7 @@ export default class InsertSimpleBoxCommand extends Command {
 	refresh() {
 		const model = this.editor.model;
 		const selection = model.document.selection;
-		const allowedIn = model.schema.findAllowedParent( 'simpleBox', selection.getFirstPosition() );
+		const allowedIn = model.schema.findAllowedParent( selection.getFirstPosition(), 'simpleBox' );
 
 		this.isEnabled = allowedIn !== null;
 	}
@@ -826,7 +816,7 @@ 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):
+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 );
@@ -842,6 +832,10 @@ Let's change one more thing before we will move forward &mdash; let's disallow `
 // ... imports
 
 export default class SimpleBoxEditing extends Plugin {
+	static get requires() {
+		return [ Widget ];
+	}
+
 	init() {
 		console.log( 'SimpleBoxEditing#init() got called' );
 

+ 25 - 4
docs/framework/guides/tutorials/implementing-an-inline-widget.md

@@ -297,8 +297,8 @@ export default class PlaceholderEditing extends Plugin {
 		return [ Widget ];
 	}
 
-	constructor( editor ) {
-		super( editor );
+	init() {
+		console.log( 'PlaceholderEditing#init() got called' );
 
 		this._defineSchema();
 		this._defineConverters();                                              // ADDED
@@ -427,7 +427,13 @@ import PlaceholderCommand from './placeholdercommand';                         /
 import './theme/placeholder.css';
 
 export default class PlaceholderEditing extends Plugin {
+	static get requires() {
+		return [ Widget ];
+	}
+
 	init() {
+		console.log( 'PlaceholderEditing#init() got called' );
+
 		this._defineSchema();
 		this._defineConverters();
 
@@ -498,7 +504,13 @@ import PlaceholderCommand from './placeholdercommand';
 import './theme/placeholder.css';
 
 export default class PlaceholderEditing extends Plugin {
+	static get requires() {
+		return [ Widget ];
+	}
+
 	init() {
+		console.log( 'PlaceholderEditing#init() got called' );
+
 		this._defineSchema();
 		this._defineConverters();
 
@@ -635,14 +647,23 @@ The first step is to define placeholder configuration in the editing plugin:
 // ... imports
 
 export default class PlaceholderEditing extends Plugin {
-	constructor( editor ) {
-		super( editor );
+	static get requires() {
+		return [ Widget ];
+	}
+
+	init() {
+		console.log( 'PlaceholderEditing#init() got called' );
 
 		this._defineSchema();
 		this._defineConverters();
 
 		this.editor.commands.add( 'placeholder', new PlaceholderCommand( this.editor ) );
 
+		this.editor.editing.mapper.on(
+			'viewToModelPosition',
+			viewToModelPositionOutsideModelElement( this.editor.model, viewElement => viewElement.hasClass( 'placeholder' ) )
+		);
+
 		this.editor.config.define( 'placeholder', {                                 // ADDED
 			types: [ 'date', 'first name', 'surname' ]
 		} );

+ 1 - 0
mgit.json

@@ -33,6 +33,7 @@
     "@ckeditor/ckeditor5-list": "ckeditor/ckeditor5-list",
     "@ckeditor/ckeditor5-markdown-gfm": "ckeditor/ckeditor5-markdown-gfm",
     "@ckeditor/ckeditor5-media-embed": "ckeditor/ckeditor5-media-embed",
+    "@ckeditor/ckeditor5-mention": "ckeditor/ckeditor5-mention",
     "@ckeditor/ckeditor5-paragraph": "ckeditor/ckeditor5-paragraph",
     "@ckeditor/ckeditor5-paste-from-office": "ckeditor/ckeditor5-paste-from-office",
     "@ckeditor/ckeditor5-table": "ckeditor/ckeditor5-table",

+ 6 - 4
package.json

@@ -32,9 +32,9 @@
     "@ckeditor/ckeditor5-build-classic": "^12.0.0",
     "@ckeditor/ckeditor5-build-decoupled-document": "^12.0.0",
     "@ckeditor/ckeditor5-build-inline": "^12.0.0",
+    "@ckeditor/ckeditor5-ckfinder": "^11.0.0",
     "@ckeditor/ckeditor5-clipboard": "^11.0.0",
     "@ckeditor/ckeditor5-cloud-services": "^11.0.0",
-    "@ckeditor/ckeditor5-ckfinder": "^11.0.0",
     "@ckeditor/ckeditor5-core": "^12.0.0",
     "@ckeditor/ckeditor5-easy-image": "^11.0.0",
     "@ckeditor/ckeditor5-editor-balloon": "^12.0.0",
@@ -52,6 +52,7 @@
     "@ckeditor/ckeditor5-list": "^12.0.0",
     "@ckeditor/ckeditor5-markdown-gfm": "^11.0.0",
     "@ckeditor/ckeditor5-media-embed": "^11.0.0",
+    "@ckeditor/ckeditor5-mention": "*",
     "@ckeditor/ckeditor5-paragraph": "^11.0.0",
     "@ckeditor/ckeditor5-paste-from-office": "^11.0.0",
     "@ckeditor/ckeditor5-table": "^12.0.0",
@@ -66,10 +67,11 @@
   "devDependencies": {
     "@ckeditor/ckeditor5-comments": "^2.0.0",
     "@ckeditor/ckeditor5-dev-docs": "^11.0.1",
-    "@ckeditor/ckeditor5-dev-env": "^14.0.1",
-    "@ckeditor/ckeditor5-dev-tests": "^16.1.0",
+    "@ckeditor/ckeditor5-dev-env": "^14.1.0",
+    "@ckeditor/ckeditor5-dev-tests": "^16.2.0",
     "@ckeditor/ckeditor5-dev-utils": "^12.0.1",
     "@ckeditor/ckeditor5-dev-webpack-plugin": "^8.0.1",
+    "@ckeditor/ckeditor5-inspector": "^1.2.0",
     "@ckeditor/ckeditor5-real-time-collaboration": "^12.1.0",
     "@ckeditor/ckeditor5-track-changes": "^0.1.0",
     "css-loader": "^1.0.0",
@@ -86,7 +88,7 @@
     "style-loader": "^0.23.0",
     "svgo": "^1.1.0",
     "uglifyjs-webpack-plugin": "^1.2.7",
-    "umberto": "^0.20.0",
+    "umberto": "^0.21.0",
     "webpack": "^4.15.0"
   },
   "engines": {

File diff suppressed because it is too large
+ 1969 - 2554
yarn.lock