瀏覽代碼

Merge branch 'master' into t/924

Piotrek Koszuliński 6 年之前
父節點
當前提交
d4bae042ac
共有 100 個文件被更改,包括 5897 次插入683 次删除
  1. 2 2
      .eslintrc.js
  2. 11 13
      .travis.yml
  3. 314 1
      CHANGELOG.md
  4. 1 1
      LICENSE.md
  5. 75 3
      README.md
  6. 2 2
      docs/_snippets/build-classic-source.js
  7. 2 2
      docs/_snippets/builds/saving-data/autosave.js
  8. 2 2
      docs/_snippets/builds/saving-data/build-autosave-source.js
  9. 2 2
      docs/_snippets/builds/saving-data/manualsave.js
  10. 38 0
      docs/_snippets/examples/balloon-block-editor.html
  11. 21 0
      docs/_snippets/examples/balloon-block-editor.js
  12. 2 2
      docs/_snippets/examples/balloon-editor.js
  13. 2 2
      docs/_snippets/examples/classic-editor-short.js
  14. 2 2
      docs/_snippets/examples/classic-editor.js
  15. 2 2
      docs/_snippets/examples/custom-build.js
  16. 2 2
      docs/_snippets/examples/document-editor.js
  17. 2 2
      docs/_snippets/examples/inline-editor.js
  18. 44 0
      docs/_snippets/examples/multi-root-editor.html
  19. 430 0
      docs/_snippets/examples/multi-root-editor.js
  20. 2 2
      docs/_snippets/features/image-upload.js
  21. 10 0
      docs/_snippets/features/placeholder-custom.html
  22. 20 0
      docs/_snippets/features/placeholder-custom.js
  23. 3 0
      docs/_snippets/features/placeholder.html
  24. 20 0
      docs/_snippets/features/placeholder.js
  25. 2 2
      docs/_snippets/features/read-only.js
  26. 2 2
      docs/_snippets/features/ui-language.js
  27. 16 0
      docs/_snippets/framework/development-tools/inspector.html
  28. 29 0
      docs/_snippets/framework/development-tools/inspector.js
  29. 37 0
      docs/_snippets/framework/tutorials/block-widget.html
  30. 241 0
      docs/_snippets/framework/tutorials/block-widget.js
  31. 17 0
      docs/_snippets/framework/tutorials/inline-widget.html
  32. 213 0
      docs/_snippets/framework/tutorials/inline-widget.js
  33. 二進制
      docs/assets/img/editor-balloon-block.png
  34. 二進制
      docs/assets/img/fields.jpg
  35. 二進制
      docs/assets/img/framework-development-tools-inspector.jpg
  36. 66 62
      docs/assets/img/image-upload-animation.svg
  37. 二進制
      docs/assets/img/tajmahal.jpg
  38. 二進制
      docs/assets/img/tutorial-implementing-a-widget-1.png
  39. 二進制
      docs/assets/img/tutorial-implementing-a-widget-2.png
  40. 二進制
      docs/assets/img/tutorial-implementing-a-widget-3.png
  41. 二進制
      docs/assets/img/tutorial-implementing-a-widget-4.png
  42. 二進制
      docs/assets/img/tutorial-implementing-a-widget-4b.png
  43. 二進制
      docs/assets/img/tutorial-implementing-a-widget-5.png
  44. 二進制
      docs/assets/img/tutorial-implementing-a-widget-6.png
  45. 二進制
      docs/assets/img/tutorial-implementing-a-widget-7.png
  46. 二進制
      docs/assets/img/tutorial-implementing-an-inline-widget-1.png
  47. 二進制
      docs/assets/img/tutorial-implementing-an-inline-widget-2.png
  48. 6 2
      docs/assets/snippet-styles.css
  49. 2 2
      docs/assets/snippet.js
  50. 1 1
      docs/assets/styles.css
  51. 48 0
      docs/builds/guides/faq.md
  52. 50 36
      docs/builds/guides/frameworks/angular.md
  53. 61 27
      docs/builds/guides/frameworks/css.md
  54. 11 0
      docs/builds/guides/frameworks/overview.md
  55. 201 21
      docs/builds/guides/frameworks/react.md
  56. 35 25
      docs/builds/guides/frameworks/vuejs.md
  57. 7 4
      docs/builds/guides/integration/advanced-setup.md
  58. 2 0
      docs/builds/guides/integration/basic-api.md
  59. 70 0
      docs/builds/guides/integration/csp.md
  60. 2 0
      docs/builds/guides/integration/installation.md
  61. 14 0
      docs/builds/guides/integration/saving-data.md
  62. 21 6
      docs/builds/guides/migrate.md
  63. 9 0
      docs/builds/guides/overview.md
  64. 54 0
      docs/builds/guides/quick-start.md
  65. 1 1
      docs/builds/guides/support/browser-compatibility.md
  66. 1 1
      docs/builds/guides/support/license-and-legal.md
  67. 1 0
      docs/builds/index.md
  68. 13 0
      docs/examples/builds/balloon-block-editor.md
  69. 14 0
      docs/examples/framework/multi-root-editor.md
  70. 1 0
      docs/examples/index.md
  71. 72 0
      docs/features/editor-placeholder.md
  72. 6 0
      docs/features/image-upload.md
  73. 2 1
      docs/features/index.md
  74. 4 4
      docs/framework/guides/architecture/core-editor-architecture.md
  75. 4 0
      docs/framework/guides/architecture/editing-engine.md
  76. 1 0
      docs/framework/guides/architecture/intro.md
  77. 5 5
      docs/framework/guides/architecture/ui-library.md
  78. 9 2
      docs/framework/guides/contributing/development-environment.md
  79. 5 1
      docs/framework/guides/contributing/testing-environment.md
  80. 294 0
      docs/framework/guides/creating-simple-plugin.md
  81. 481 0
      docs/framework/guides/custom-editor-creator.md
  82. 105 0
      docs/framework/guides/development-tools.md
  83. 31 292
      docs/framework/guides/quick-start.md
  84. 1 1
      docs/framework/guides/support/license-and-legal.md
  85. 1216 0
      docs/framework/guides/tutorials/implementing-a-block-widget.md
  86. 944 0
      docs/framework/guides/tutorials/implementing-an-inline-widget.md
  87. 1 0
      docs/framework/index.md
  88. 1 0
      docs/index.md
  89. 20 4
      docs/umberto.json
  90. 7 1
      mgit.json
  91. 79 60
      package.json
  92. 8 4
      scripts/bump-year.js
  93. 6 3
      scripts/clean-up-svg-icons.sh
  94. 2 2
      scripts/docs/build-and-publish-nightly.js
  95. 2 2
      scripts/docs/build-and-publish.js
  96. 2 2
      scripts/docs/build-api-docs.js
  97. 5 3
      scripts/docs/build-docs.js
  98. 2 2
      scripts/docs/buildapi.js
  99. 2 2
      scripts/docs/getrealimportpath.js
  100. 321 58
      scripts/docs/snippetadapter.js

+ 2 - 2
.eslintrc.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* eslint-env node */

+ 11 - 13
.travis.yml

@@ -1,11 +1,7 @@
 sudo: required
 dist: trusty
 addons:
-  apt:
-    sources:
-    - google-chrome
-    packages:
-    - google-chrome-stable
+  chrome: stable
 language: node_js
 cache:
   yarn: true
@@ -14,23 +10,25 @@ node_js:
 cache:
 - node_modules
 before_install:
+- export START_TIME=$( date +%s )
 - export DISPLAY=:99.0
 - sh -e /etc/init.d/xvfb start
 - npm i -g yarn
 install:
 - yarn add mgit2 --ignore-workspace-root-check
 - mgit sync --resolver-url-template="https://github.com/\${ path }.git"
-- yarn
+- git checkout package.json yarn.lock
+- rm -rf node_modules
+- yarn install
 script:
 - yarn run test --reporter=dots
 - yarn run docs:api --validate-only
 - 'if [ $TRAVIS_TEST_RESULT -eq 0 ]; then
     travis_wait yarn run docs:build-and-publish-nightly;
   fi'
-notifications:
-  slack:
-    rooms:
-    - secure: NKUpSnmkmlRRua0URoQJjEqNfIHx6tuqJhVCbQ6vU5rdUdw6A43OfG7Qym3MRQlXymFuIF0MUsOrS6t0GeXE9hSpC+5ynF02E5wdTD1/R7Tjq0XEdx0mXJTYjuOQA16fvwMxm5p3Ub5uWpSH+gBtyG86UPCbVSxDvvmxHqResAw=
-    on_success: change
-    on_failure: always
-    on_pull_requests: false
+after_script:
+- export END_TIME=$( date +%s )
+- ckeditor5-dev-tests-notify-travis-status
+env:
+  global:
+  - secure: RO140EQDHmEOPJPikk8eCY5IdHpnEKGm41p5U1ewAbeZv1DpCG+rSumR2JdYl75kFAaZvCSm1NuVMM+kmYd+/z+LQbKj7QH5G/UHNho3H89blIU6WlJhT0YR5vclm9rvnEvOtxnfODca1Qrw+CaCoJks2o4VYbJB7mOBVNsh7Bc=

+ 314 - 1
CHANGELOG.md

@@ -1,11 +1,324 @@
 Changelog
 =========
 
+## [12.3.1](https://github.com/ckeditor/ckeditor5/compare/v12.3.0...v12.3.1) (2019-07-10)
+
+We are happy to report the release of CKEditor 5 v12.3.0 (and v12.3.1 with a [small fix](https://github.com/ckeditor/ckeditor5-typing/pull/209)). This release introduces several new features ([word count](https://ckeditor.com/docs/ckeditor5/latest/features/word-count.html), [automatic text transformations](https://ckeditor.com/docs/ckeditor5/latest/features/text-transformation.html), [ability to control link attributes such as `target`](https://ckeditor.com/docs/ckeditor5/latest/features/link.html#custom-link-attributes-decorators) and [block indentation](https://ckeditor.com/docs/ckeditor5/latest/features/indent.html)). It also brings improvements to existing features (e.g. the ["document colors" section](https://ckeditor.com/docs/ckeditor5/latest/features/font.html#documents-colors) in the font color picker dropdowns) and many bug fixes.
+
+Blog post coming soon...
+
+### Dependencies
+
+Patch releases (bug fixes, internal changes):
+
+* [@ckeditor/ckeditor5-adapter-ckfinder](https://www.npmjs.com/package/@ckeditor/ckeditor5-adapter-ckfinder): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-adapter-ckfinder/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-alignment](https://www.npmjs.com/package/@ckeditor/ckeditor5-alignment): v11.1.2 => [v11.1.3](https://github.com/ckeditor/ckeditor5-alignment/releases/tag/v11.1.3)
+* [@ckeditor/ckeditor5-autoformat](https://www.npmjs.com/package/@ckeditor/ckeditor5-autoformat): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-autoformat/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-autosave](https://www.npmjs.com/package/@ckeditor/ckeditor5-autosave): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-autosave/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-basic-styles](https://www.npmjs.com/package/@ckeditor/ckeditor5-basic-styles): v11.1.2 => [v11.1.3](https://github.com/ckeditor/ckeditor5-basic-styles/releases/tag/v11.1.3)
+* [@ckeditor/ckeditor5-block-quote](https://www.npmjs.com/package/@ckeditor/ckeditor5-block-quote): v11.1.1 => [v11.1.2](https://github.com/ckeditor/ckeditor5-block-quote/releases/tag/v11.1.2)
+* [@ckeditor/ckeditor5-build-balloon](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-balloon): v12.3.0 => [v12.3.1](https://github.com/ckeditor/ckeditor5-build-balloon/releases/tag/v12.3.1)
+* [@ckeditor/ckeditor5-build-balloon-block](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-balloon-block): v12.3.0 => [v12.3.1](https://github.com/ckeditor/ckeditor5-build-balloon-block/releases/tag/v12.3.1)
+* [@ckeditor/ckeditor5-build-classic](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-classic): v12.3.0 => [v12.3.1](https://github.com/ckeditor/ckeditor5-build-classic/releases/tag/v12.3.1)
+* [@ckeditor/ckeditor5-build-decoupled-document](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-decoupled-document): v12.3.0 => [v12.3.1](https://github.com/ckeditor/ckeditor5-build-decoupled-document/releases/tag/v12.3.1)
+* [@ckeditor/ckeditor5-build-inline](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-inline): v12.3.0 => [v12.3.1](https://github.com/ckeditor/ckeditor5-build-inline/releases/tag/v12.3.1)
+* [@ckeditor/ckeditor5-ckfinder](https://www.npmjs.com/package/@ckeditor/ckeditor5-ckfinder): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-ckfinder/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-clipboard](https://www.npmjs.com/package/@ckeditor/ckeditor5-clipboard): v12.0.0 => [v12.0.1](https://github.com/ckeditor/ckeditor5-clipboard/releases/tag/v12.0.1)
+* [@ckeditor/ckeditor5-cloud-services](https://www.npmjs.com/package/@ckeditor/ckeditor5-cloud-services): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-cloud-services/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-core](https://www.npmjs.com/package/@ckeditor/ckeditor5-core): v12.2.0 => [v12.2.1](https://github.com/ckeditor/ckeditor5-core/releases/tag/v12.2.1)
+* [@ckeditor/ckeditor5-easy-image](https://www.npmjs.com/package/@ckeditor/ckeditor5-easy-image): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-easy-image/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-editor-balloon](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-balloon): v12.2.0 => [v12.2.1](https://github.com/ckeditor/ckeditor5-editor-balloon/releases/tag/v12.2.1)
+* [@ckeditor/ckeditor5-editor-classic](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-classic): v12.1.2 => [v12.1.3](https://github.com/ckeditor/ckeditor5-editor-classic/releases/tag/v12.1.3)
+* [@ckeditor/ckeditor5-editor-decoupled](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-decoupled): v12.2.0 => [v12.2.1](https://github.com/ckeditor/ckeditor5-editor-decoupled/releases/tag/v12.2.1)
+* [@ckeditor/ckeditor5-editor-inline](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-inline): v12.2.0 => [v12.2.1](https://github.com/ckeditor/ckeditor5-editor-inline/releases/tag/v12.2.1)
+* [@ckeditor/ckeditor5-engine](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine): v13.2.0 => [v13.2.1](https://github.com/ckeditor/ckeditor5-engine/releases/tag/v13.2.1)
+* [@ckeditor/ckeditor5-enter](https://www.npmjs.com/package/@ckeditor/ckeditor5-enter): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-enter/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-essentials](https://www.npmjs.com/package/@ckeditor/ckeditor5-essentials): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-essentials/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-font](https://www.npmjs.com/package/@ckeditor/ckeditor5-font): v11.2.0 => [v11.2.1](https://github.com/ckeditor/ckeditor5-font/releases/tag/v11.2.1)
+* [@ckeditor/ckeditor5-heading](https://www.npmjs.com/package/@ckeditor/ckeditor5-heading): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-heading/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-highlight](https://www.npmjs.com/package/@ckeditor/ckeditor5-highlight): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-highlight/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-image](https://www.npmjs.com/package/@ckeditor/ckeditor5-image): v13.1.1 => [v13.1.2](https://github.com/ckeditor/ckeditor5-image/releases/tag/v13.1.2)
+* [@ckeditor/ckeditor5-indent](https://www.npmjs.com/package/@ckeditor/ckeditor5-indent): v10.0.0 => [v10.0.1](https://github.com/ckeditor/ckeditor5-indent/releases/tag/v10.0.1)
+* [@ckeditor/ckeditor5-link](https://www.npmjs.com/package/@ckeditor/ckeditor5-link): v11.1.0 => [v11.1.1](https://github.com/ckeditor/ckeditor5-link/releases/tag/v11.1.1)
+* [@ckeditor/ckeditor5-list](https://www.npmjs.com/package/@ckeditor/ckeditor5-list): v12.0.3 => [v12.0.4](https://github.com/ckeditor/ckeditor5-list/releases/tag/v12.0.4)
+* [@ckeditor/ckeditor5-markdown-gfm](https://www.npmjs.com/package/@ckeditor/ckeditor5-markdown-gfm): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-markdown-gfm/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-media-embed](https://www.npmjs.com/package/@ckeditor/ckeditor5-media-embed): v11.1.2 => [v11.1.3](https://github.com/ckeditor/ckeditor5-media-embed/releases/tag/v11.1.3)
+* [@ckeditor/ckeditor5-mention](https://www.npmjs.com/package/@ckeditor/ckeditor5-mention): v12.0.0 => [v12.0.1](https://github.com/ckeditor/ckeditor5-mention/releases/tag/v12.0.1)
+* [@ckeditor/ckeditor5-paragraph](https://www.npmjs.com/package/@ckeditor/ckeditor5-paragraph): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-paragraph/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-paste-from-office](https://www.npmjs.com/package/@ckeditor/ckeditor5-paste-from-office): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-paste-from-office/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-remove-format](https://www.npmjs.com/package/@ckeditor/ckeditor5-remove-format): v10.0.2 => [v10.0.3](https://github.com/ckeditor/ckeditor5-remove-format/releases/tag/v10.0.3)
+* [@ckeditor/ckeditor5-table](https://www.npmjs.com/package/@ckeditor/ckeditor5-table): v13.0.1 => [v13.0.2](https://github.com/ckeditor/ckeditor5-table/releases/tag/v13.0.2)
+* [@ckeditor/ckeditor5-theme-lark](https://www.npmjs.com/package/@ckeditor/ckeditor5-theme-lark): v14.1.0 => [v14.1.1](https://github.com/ckeditor/ckeditor5-theme-lark/releases/tag/v14.1.1)
+* [@ckeditor/ckeditor5-typing](https://www.npmjs.com/package/@ckeditor/ckeditor5-typing): v12.1.0 => [v12.1.1](https://github.com/ckeditor/ckeditor5-typing/releases/tag/v12.1.1)
+* [@ckeditor/ckeditor5-ui](https://www.npmjs.com/package/@ckeditor/ckeditor5-ui): v13.0.1 => [v13.0.2](https://github.com/ckeditor/ckeditor5-ui/releases/tag/v13.0.2)
+* [@ckeditor/ckeditor5-undo](https://www.npmjs.com/package/@ckeditor/ckeditor5-undo): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-undo/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-upload](https://www.npmjs.com/package/@ckeditor/ckeditor5-upload): v11.1.0 => [v11.1.1](https://github.com/ckeditor/ckeditor5-upload/releases/tag/v11.1.1)
+* [@ckeditor/ckeditor5-utils](https://www.npmjs.com/package/@ckeditor/ckeditor5-utils): v13.0.0 => [v13.0.1](https://github.com/ckeditor/ckeditor5-utils/releases/tag/v13.0.1)
+* [@ckeditor/ckeditor5-watchdog](https://www.npmjs.com/package/@ckeditor/ckeditor5-watchdog): v10.0.0 => [v10.0.1](https://github.com/ckeditor/ckeditor5-watchdog/releases/tag/v10.0.1)
+* [@ckeditor/ckeditor5-widget](https://www.npmjs.com/package/@ckeditor/ckeditor5-widget): v11.0.3 => [v11.0.4](https://github.com/ckeditor/ckeditor5-widget/releases/tag/v11.0.4)
+* [@ckeditor/ckeditor5-word-count](https://www.npmjs.com/package/@ckeditor/ckeditor5-word-count): v10.0.0 => [v10.0.1](https://github.com/ckeditor/ckeditor5-word-count/releases/tag/v10.0.1)
+
+
+## [12.3.0](https://github.com/ckeditor/ckeditor5/compare/v12.2.0...v12.3.0) (2019-07-04)
+
+### Dependencies
+
+New packages:
+
+* [@ckeditor/ckeditor5-indent](https://www.npmjs.com/package/@ckeditor/ckeditor5-indent): [v10.0.0](https://github.com/ckeditor/ckeditor5-indent/releases/tag/v10.0.0)
+* [@ckeditor/ckeditor5-watchdog](https://www.npmjs.com/package/@ckeditor/ckeditor5-watchdog): [v10.0.0](https://github.com/ckeditor/ckeditor5-watchdog/releases/tag/v10.0.0)
+* [@ckeditor/ckeditor5-word-count](https://www.npmjs.com/package/@ckeditor/ckeditor5-word-count): [v10.0.0](https://github.com/ckeditor/ckeditor5-word-count/releases/tag/v10.0.0)
+
+Major releases (contain breaking changes):
+
+* [@ckeditor/ckeditor5-clipboard](https://www.npmjs.com/package/@ckeditor/ckeditor5-clipboard): v11.0.2 => [v12.0.0](https://github.com/ckeditor/ckeditor5-clipboard/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-mention](https://www.npmjs.com/package/@ckeditor/ckeditor5-mention): v11.0.0 => [v12.0.0](https://github.com/ckeditor/ckeditor5-mention/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-utils](https://www.npmjs.com/package/@ckeditor/ckeditor5-utils): v12.1.1 => [v13.0.0](https://github.com/ckeditor/ckeditor5-utils/releases/tag/v13.0.0)
+
+Minor releases:
+
+* [@ckeditor/ckeditor5-build-balloon](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-balloon): v12.2.0 => [v12.3.0](https://github.com/ckeditor/ckeditor5-build-balloon/releases/tag/v12.3.0)
+* [@ckeditor/ckeditor5-build-balloon-block](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-balloon-block): v12.2.0 => [v12.3.0](https://github.com/ckeditor/ckeditor5-build-balloon-block/releases/tag/v12.3.0)
+* [@ckeditor/ckeditor5-build-classic](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-classic): v12.2.0 => [v12.3.0](https://github.com/ckeditor/ckeditor5-build-classic/releases/tag/v12.3.0)
+* [@ckeditor/ckeditor5-build-decoupled-document](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-decoupled-document): v12.2.0 => [v12.3.0](https://github.com/ckeditor/ckeditor5-build-decoupled-document/releases/tag/v12.3.0)
+* [@ckeditor/ckeditor5-build-inline](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-inline): v12.2.0 => [v12.3.0](https://github.com/ckeditor/ckeditor5-build-inline/releases/tag/v12.3.0)
+* [@ckeditor/ckeditor5-core](https://www.npmjs.com/package/@ckeditor/ckeditor5-core): v12.1.1 => [v12.2.0](https://github.com/ckeditor/ckeditor5-core/releases/tag/v12.2.0)
+* [@ckeditor/ckeditor5-editor-balloon](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-balloon): v12.1.1 => [v12.2.0](https://github.com/ckeditor/ckeditor5-editor-balloon/releases/tag/v12.2.0)
+* [@ckeditor/ckeditor5-editor-decoupled](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-decoupled): v12.1.1 => [v12.2.0](https://github.com/ckeditor/ckeditor5-editor-decoupled/releases/tag/v12.2.0)
+* [@ckeditor/ckeditor5-editor-inline](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-inline): v12.1.1 => [v12.2.0](https://github.com/ckeditor/ckeditor5-editor-inline/releases/tag/v12.2.0)
+* [@ckeditor/ckeditor5-engine](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine): v13.1.1 => [v13.2.0](https://github.com/ckeditor/ckeditor5-engine/releases/tag/v13.2.0)
+* [@ckeditor/ckeditor5-link](https://www.npmjs.com/package/@ckeditor/ckeditor5-link): v11.0.2 => [v11.1.0](https://github.com/ckeditor/ckeditor5-link/releases/tag/v11.1.0)
+* [@ckeditor/ckeditor5-theme-lark](https://www.npmjs.com/package/@ckeditor/ckeditor5-theme-lark): v14.0.0 => [v14.1.0](https://github.com/ckeditor/ckeditor5-theme-lark/releases/tag/v14.1.0)
+* [@ckeditor/ckeditor5-typing](https://www.npmjs.com/package/@ckeditor/ckeditor5-typing): v12.0.2 => [v12.1.0](https://github.com/ckeditor/ckeditor5-typing/releases/tag/v12.1.0)
+* [@ckeditor/ckeditor5-upload](https://www.npmjs.com/package/@ckeditor/ckeditor5-upload): v11.0.2 => [v11.1.0](https://github.com/ckeditor/ckeditor5-upload/releases/tag/v11.1.0)
+
+Patch releases (bug fixes, internal changes):
+
+* [@ckeditor/ckeditor5-adapter-ckfinder](https://www.npmjs.com/package/@ckeditor/ckeditor5-adapter-ckfinder): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-adapter-ckfinder/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-alignment](https://www.npmjs.com/package/@ckeditor/ckeditor5-alignment): v11.1.1 => [v11.1.2](https://github.com/ckeditor/ckeditor5-alignment/releases/tag/v11.1.2)
+* [@ckeditor/ckeditor5-autoformat](https://www.npmjs.com/package/@ckeditor/ckeditor5-autoformat): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-autoformat/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-autosave](https://www.npmjs.com/package/@ckeditor/ckeditor5-autosave): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-autosave/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-basic-styles](https://www.npmjs.com/package/@ckeditor/ckeditor5-basic-styles): v11.1.1 => [v11.1.2](https://github.com/ckeditor/ckeditor5-basic-styles/releases/tag/v11.1.2)
+* [@ckeditor/ckeditor5-block-quote](https://www.npmjs.com/package/@ckeditor/ckeditor5-block-quote): v11.1.0 => [v11.1.1](https://github.com/ckeditor/ckeditor5-block-quote/releases/tag/v11.1.1)
+* [@ckeditor/ckeditor5-ckfinder](https://www.npmjs.com/package/@ckeditor/ckeditor5-ckfinder): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-ckfinder/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-cloud-services](https://www.npmjs.com/package/@ckeditor/ckeditor5-cloud-services): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-cloud-services/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-easy-image](https://www.npmjs.com/package/@ckeditor/ckeditor5-easy-image): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-easy-image/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-editor-classic](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-classic): v12.1.1 => [v12.1.2](https://github.com/ckeditor/ckeditor5-editor-classic/releases/tag/v12.1.2)
+* [@ckeditor/ckeditor5-enter](https://www.npmjs.com/package/@ckeditor/ckeditor5-enter): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-enter/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-essentials](https://www.npmjs.com/package/@ckeditor/ckeditor5-essentials): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-essentials/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-font](https://www.npmjs.com/package/@ckeditor/ckeditor5-font): v11.1.1 => [v11.1.2](https://github.com/ckeditor/ckeditor5-font/releases/tag/v11.1.2)
+* [@ckeditor/ckeditor5-heading](https://www.npmjs.com/package/@ckeditor/ckeditor5-heading): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-heading/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-highlight](https://www.npmjs.com/package/@ckeditor/ckeditor5-highlight): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-highlight/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-image](https://www.npmjs.com/package/@ckeditor/ckeditor5-image): v13.1.0 => [v13.1.1](https://github.com/ckeditor/ckeditor5-image/releases/tag/v13.1.1)
+* [@ckeditor/ckeditor5-list](https://www.npmjs.com/package/@ckeditor/ckeditor5-list): v12.0.2 => [v12.0.3](https://github.com/ckeditor/ckeditor5-list/releases/tag/v12.0.3)
+* [@ckeditor/ckeditor5-markdown-gfm](https://www.npmjs.com/package/@ckeditor/ckeditor5-markdown-gfm): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-markdown-gfm/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-media-embed](https://www.npmjs.com/package/@ckeditor/ckeditor5-media-embed): v11.1.1 => [v11.1.2](https://github.com/ckeditor/ckeditor5-media-embed/releases/tag/v11.1.2)
+* [@ckeditor/ckeditor5-paragraph](https://www.npmjs.com/package/@ckeditor/ckeditor5-paragraph): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-paragraph/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-paste-from-office](https://www.npmjs.com/package/@ckeditor/ckeditor5-paste-from-office): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-paste-from-office/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-remove-format](https://www.npmjs.com/package/@ckeditor/ckeditor5-remove-format): v10.0.1 => [v10.0.2](https://github.com/ckeditor/ckeditor5-remove-format/releases/tag/v10.0.2)
+* [@ckeditor/ckeditor5-table](https://www.npmjs.com/package/@ckeditor/ckeditor5-table): v13.0.0 => [v13.0.1](https://github.com/ckeditor/ckeditor5-table/releases/tag/v13.0.1)
+* [@ckeditor/ckeditor5-ui](https://www.npmjs.com/package/@ckeditor/ckeditor5-ui): v13.0.0 => [v13.0.1](https://github.com/ckeditor/ckeditor5-ui/releases/tag/v13.0.1)
+* [@ckeditor/ckeditor5-undo](https://www.npmjs.com/package/@ckeditor/ckeditor5-undo): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-undo/releases/tag/v11.0.3)
+* [@ckeditor/ckeditor5-widget](https://www.npmjs.com/package/@ckeditor/ckeditor5-widget): v11.0.2 => [v11.0.3](https://github.com/ckeditor/ckeditor5-widget/releases/tag/v11.0.3)
+
+
+## [12.2.0](https://github.com/ckeditor/ckeditor5/compare/v12.1.0...v12.2.0) (2019-06-05)
+
+We are happy to report the release of CKEditor 5 v12.2.0. This is a minor release with many bug fixes and a new UI feature which allows to navigating between multiple balloons.
+
+**Note:** The `config.table.toolbar` property that had been deprecated last year has now been completely removed. Use [`config.table.contentToolbar`](https://ckeditor.com/docs/ckeditor5/latest/api/module_table_table-TableConfig.html#member-contentToolbar) instead.
+
+Read more in the blog post: https://ckeditor.com/blog/CKEditor-5-v12.2.0-with-mobile-friendly-comments-mode/
+
+### Dependencies
+
+Major releases (contain breaking changes):
+
+* [@ckeditor/ckeditor5-mention](https://www.npmjs.com/package/@ckeditor/ckeditor5-mention): v10.0.0 => [v11.0.0](https://github.com/ckeditor/ckeditor5-mention/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-table](https://www.npmjs.com/package/@ckeditor/ckeditor5-table): v12.0.1 => [v13.0.0](https://github.com/ckeditor/ckeditor5-table/releases/tag/v13.0.0)
+* [@ckeditor/ckeditor5-theme-lark](https://www.npmjs.com/package/@ckeditor/ckeditor5-theme-lark): v13.0.1 => [v14.0.0](https://github.com/ckeditor/ckeditor5-theme-lark/releases/tag/v14.0.0)
+* [@ckeditor/ckeditor5-ui](https://www.npmjs.com/package/@ckeditor/ckeditor5-ui): v12.1.0 => [v13.0.0](https://github.com/ckeditor/ckeditor5-ui/releases/tag/v13.0.0)
+
+Minor releases:
+
+* [@ckeditor/ckeditor5-block-quote](https://www.npmjs.com/package/@ckeditor/ckeditor5-block-quote): v11.0.1 => [v11.1.0](https://github.com/ckeditor/ckeditor5-block-quote/releases/tag/v11.1.0)
+* [@ckeditor/ckeditor5-build-balloon](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-balloon): v12.1.0 => [v12.2.0](https://github.com/ckeditor/ckeditor5-build-balloon/releases/tag/v12.2.0)
+* [@ckeditor/ckeditor5-build-balloon-block](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-balloon-block): v12.1.0 => [v12.2.0](https://github.com/ckeditor/ckeditor5-build-balloon-block/releases/tag/v12.2.0)
+* [@ckeditor/ckeditor5-build-classic](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-classic): v12.1.0 => [v12.2.0](https://github.com/ckeditor/ckeditor5-build-classic/releases/tag/v12.2.0)
+* [@ckeditor/ckeditor5-build-decoupled-document](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-decoupled-document): v12.1.0 => [v12.2.0](https://github.com/ckeditor/ckeditor5-build-decoupled-document/releases/tag/v12.2.0)
+* [@ckeditor/ckeditor5-build-inline](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-inline): v12.1.0 => [v12.2.0](https://github.com/ckeditor/ckeditor5-build-inline/releases/tag/v12.2.0)
+* [@ckeditor/ckeditor5-image](https://www.npmjs.com/package/@ckeditor/ckeditor5-image): v13.0.1 => [v13.1.0](https://github.com/ckeditor/ckeditor5-image/releases/tag/v13.1.0)
+
+Patch releases (bug fixes, internal changes):
+
+* [@ckeditor/ckeditor5-adapter-ckfinder](https://www.npmjs.com/package/@ckeditor/ckeditor5-adapter-ckfinder): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-adapter-ckfinder/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-alignment](https://www.npmjs.com/package/@ckeditor/ckeditor5-alignment): v11.1.0 => [v11.1.1](https://github.com/ckeditor/ckeditor5-alignment/releases/tag/v11.1.1)
+* [@ckeditor/ckeditor5-autoformat](https://www.npmjs.com/package/@ckeditor/ckeditor5-autoformat): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-autoformat/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-autosave](https://www.npmjs.com/package/@ckeditor/ckeditor5-autosave): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-autosave/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-basic-styles](https://www.npmjs.com/package/@ckeditor/ckeditor5-basic-styles): v11.1.0 => [v11.1.1](https://github.com/ckeditor/ckeditor5-basic-styles/releases/tag/v11.1.1)
+* [@ckeditor/ckeditor5-ckfinder](https://www.npmjs.com/package/@ckeditor/ckeditor5-ckfinder): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-ckfinder/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-clipboard](https://www.npmjs.com/package/@ckeditor/ckeditor5-clipboard): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-clipboard/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-cloud-services](https://www.npmjs.com/package/@ckeditor/ckeditor5-cloud-services): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-cloud-services/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-core](https://www.npmjs.com/package/@ckeditor/ckeditor5-core): v12.1.0 => [v12.1.1](https://github.com/ckeditor/ckeditor5-core/releases/tag/v12.1.1)
+* [@ckeditor/ckeditor5-easy-image](https://www.npmjs.com/package/@ckeditor/ckeditor5-easy-image): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-easy-image/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-editor-balloon](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-balloon): v12.1.0 => [v12.1.1](https://github.com/ckeditor/ckeditor5-editor-balloon/releases/tag/v12.1.1)
+* [@ckeditor/ckeditor5-editor-classic](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-classic): v12.1.0 => [v12.1.1](https://github.com/ckeditor/ckeditor5-editor-classic/releases/tag/v12.1.1)
+* [@ckeditor/ckeditor5-editor-decoupled](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-decoupled): v12.1.0 => [v12.1.1](https://github.com/ckeditor/ckeditor5-editor-decoupled/releases/tag/v12.1.1)
+* [@ckeditor/ckeditor5-editor-inline](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-inline): v12.1.0 => [v12.1.1](https://github.com/ckeditor/ckeditor5-editor-inline/releases/tag/v12.1.1)
+* [@ckeditor/ckeditor5-engine](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine): v13.1.0 => [v13.1.1](https://github.com/ckeditor/ckeditor5-engine/releases/tag/v13.1.1)
+* [@ckeditor/ckeditor5-enter](https://www.npmjs.com/package/@ckeditor/ckeditor5-enter): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-enter/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-essentials](https://www.npmjs.com/package/@ckeditor/ckeditor5-essentials): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-essentials/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-font](https://www.npmjs.com/package/@ckeditor/ckeditor5-font): v11.1.0 => [v11.1.1](https://github.com/ckeditor/ckeditor5-font/releases/tag/v11.1.1)
+* [@ckeditor/ckeditor5-heading](https://www.npmjs.com/package/@ckeditor/ckeditor5-heading): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-heading/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-highlight](https://www.npmjs.com/package/@ckeditor/ckeditor5-highlight): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-highlight/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-link](https://www.npmjs.com/package/@ckeditor/ckeditor5-link): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-link/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-list](https://www.npmjs.com/package/@ckeditor/ckeditor5-list): v12.0.1 => [v12.0.2](https://github.com/ckeditor/ckeditor5-list/releases/tag/v12.0.2)
+* [@ckeditor/ckeditor5-markdown-gfm](https://www.npmjs.com/package/@ckeditor/ckeditor5-markdown-gfm): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-markdown-gfm/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-media-embed](https://www.npmjs.com/package/@ckeditor/ckeditor5-media-embed): v11.1.0 => [v11.1.1](https://github.com/ckeditor/ckeditor5-media-embed/releases/tag/v11.1.1)
+* [@ckeditor/ckeditor5-paragraph](https://www.npmjs.com/package/@ckeditor/ckeditor5-paragraph): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-paragraph/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-paste-from-office](https://www.npmjs.com/package/@ckeditor/ckeditor5-paste-from-office): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-paste-from-office/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-remove-format](https://www.npmjs.com/package/@ckeditor/ckeditor5-remove-format): v10.0.0 => [v10.0.1](https://github.com/ckeditor/ckeditor5-remove-format/releases/tag/v10.0.1)
+* [@ckeditor/ckeditor5-typing](https://www.npmjs.com/package/@ckeditor/ckeditor5-typing): v12.0.1 => [v12.0.2](https://github.com/ckeditor/ckeditor5-typing/releases/tag/v12.0.2)
+* [@ckeditor/ckeditor5-undo](https://www.npmjs.com/package/@ckeditor/ckeditor5-undo): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-undo/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-upload](https://www.npmjs.com/package/@ckeditor/ckeditor5-upload): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-upload/releases/tag/v11.0.2)
+* [@ckeditor/ckeditor5-utils](https://www.npmjs.com/package/@ckeditor/ckeditor5-utils): v12.1.0 => [v12.1.1](https://github.com/ckeditor/ckeditor5-utils/releases/tag/v12.1.1)
+* [@ckeditor/ckeditor5-widget](https://www.npmjs.com/package/@ckeditor/ckeditor5-widget): v11.0.1 => [v11.0.2](https://github.com/ckeditor/ckeditor5-widget/releases/tag/v11.0.2)
+
+
+## [12.1.0](https://github.com/ckeditor/ckeditor5/compare/v12.0.0...v12.1.0) (2019-04-10)
+
+We are happy to report the release of CKEditor 5 v12.1.0. This release introduces 3 new features ([mentions](https://ckeditor.com/docs/ckeditor5/latest/features/mentions.html), [font color and background color](https://ckeditor.com/docs/ckeditor5/latest/features/font.html) and [remove format](https://ckeditor.com/docs/ckeditor5/latest/features/remove-format.html)).
+
+Read more in the blog post: https://ckeditor.com/blog/CKEditor-5-v12.1.0-with-mentions-font-color-and-remove-formatting-released/
+
+### Dependencies
+
+New packages:
+
+* [@ckeditor/ckeditor5-mention](https://www.npmjs.com/package/@ckeditor/ckeditor5-mention): [v10.0.0](https://github.com/ckeditor/ckeditor5-mention/releases/tag/v10.0.0)
+* [@ckeditor/ckeditor5-remove-format](https://www.npmjs.com/package/@ckeditor/ckeditor5-remove-format): [v10.0.0](https://github.com/ckeditor/ckeditor5-remove-format/releases/tag/v10.0.0)
+
+Minor releases:
+
+* [@ckeditor/ckeditor5-alignment](https://www.npmjs.com/package/@ckeditor/ckeditor5-alignment): v11.0.0 => [v11.1.0](https://github.com/ckeditor/ckeditor5-alignment/releases/tag/v11.1.0)
+* [@ckeditor/ckeditor5-basic-styles](https://www.npmjs.com/package/@ckeditor/ckeditor5-basic-styles): v11.0.0 => [v11.1.0](https://github.com/ckeditor/ckeditor5-basic-styles/releases/tag/v11.1.0)
+* [@ckeditor/ckeditor5-build-balloon](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-balloon): v12.0.0 => [v12.1.0](https://github.com/ckeditor/ckeditor5-build-balloon/releases/tag/v12.1.0)
+* [@ckeditor/ckeditor5-build-balloon-block](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-balloon-block): v12.0.0 => [v12.1.0](https://github.com/ckeditor/ckeditor5-build-balloon-block/releases/tag/v12.1.0)
+* [@ckeditor/ckeditor5-build-classic](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-classic): v12.0.0 => [v12.1.0](https://github.com/ckeditor/ckeditor5-build-classic/releases/tag/v12.1.0)
+* [@ckeditor/ckeditor5-build-decoupled-document](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-decoupled-document): v12.0.0 => [v12.1.0](https://github.com/ckeditor/ckeditor5-build-decoupled-document/releases/tag/v12.1.0)
+* [@ckeditor/ckeditor5-build-inline](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-inline): v12.0.0 => [v12.1.0](https://github.com/ckeditor/ckeditor5-build-inline/releases/tag/v12.1.0)
+* [@ckeditor/ckeditor5-core](https://www.npmjs.com/package/@ckeditor/ckeditor5-core): v12.0.0 => [v12.1.0](https://github.com/ckeditor/ckeditor5-core/releases/tag/v12.1.0)
+* [@ckeditor/ckeditor5-editor-balloon](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-balloon): v12.0.0 => [v12.1.0](https://github.com/ckeditor/ckeditor5-editor-balloon/releases/tag/v12.1.0)
+* [@ckeditor/ckeditor5-editor-classic](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-classic): v12.0.0 => [v12.1.0](https://github.com/ckeditor/ckeditor5-editor-classic/releases/tag/v12.1.0)
+* [@ckeditor/ckeditor5-editor-decoupled](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-decoupled): v12.0.0 => [v12.1.0](https://github.com/ckeditor/ckeditor5-editor-decoupled/releases/tag/v12.1.0)
+* [@ckeditor/ckeditor5-editor-inline](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-inline): v12.0.0 => [v12.1.0](https://github.com/ckeditor/ckeditor5-editor-inline/releases/tag/v12.1.0)
+* [@ckeditor/ckeditor5-engine](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine): v13.0.0 => [v13.1.0](https://github.com/ckeditor/ckeditor5-engine/releases/tag/v13.1.0)
+* [@ckeditor/ckeditor5-font](https://www.npmjs.com/package/@ckeditor/ckeditor5-font): v11.0.0 => [v11.1.0](https://github.com/ckeditor/ckeditor5-font/releases/tag/v11.1.0)
+* [@ckeditor/ckeditor5-media-embed](https://www.npmjs.com/package/@ckeditor/ckeditor5-media-embed): v11.0.0 => [v11.1.0](https://github.com/ckeditor/ckeditor5-media-embed/releases/tag/v11.1.0)
+* [@ckeditor/ckeditor5-ui](https://www.npmjs.com/package/@ckeditor/ckeditor5-ui): v12.0.0 => [v12.1.0](https://github.com/ckeditor/ckeditor5-ui/releases/tag/v12.1.0)
+* [@ckeditor/ckeditor5-utils](https://www.npmjs.com/package/@ckeditor/ckeditor5-utils): v12.0.0 => [v12.1.0](https://github.com/ckeditor/ckeditor5-utils/releases/tag/v12.1.0)
+
+Patch releases (bug fixes, internal changes):
+
+* [@ckeditor/ckeditor5-adapter-ckfinder](https://www.npmjs.com/package/@ckeditor/ckeditor5-adapter-ckfinder): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-adapter-ckfinder/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-autoformat](https://www.npmjs.com/package/@ckeditor/ckeditor5-autoformat): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-autoformat/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-autosave](https://www.npmjs.com/package/@ckeditor/ckeditor5-autosave): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-autosave/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-block-quote](https://www.npmjs.com/package/@ckeditor/ckeditor5-block-quote): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-block-quote/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-ckfinder](https://www.npmjs.com/package/@ckeditor/ckeditor5-ckfinder): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-ckfinder/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-clipboard](https://www.npmjs.com/package/@ckeditor/ckeditor5-clipboard): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-clipboard/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-cloud-services](https://www.npmjs.com/package/@ckeditor/ckeditor5-cloud-services): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-cloud-services/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-easy-image](https://www.npmjs.com/package/@ckeditor/ckeditor5-easy-image): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-easy-image/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-enter](https://www.npmjs.com/package/@ckeditor/ckeditor5-enter): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-enter/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-essentials](https://www.npmjs.com/package/@ckeditor/ckeditor5-essentials): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-essentials/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-heading](https://www.npmjs.com/package/@ckeditor/ckeditor5-heading): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-heading/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-highlight](https://www.npmjs.com/package/@ckeditor/ckeditor5-highlight): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-highlight/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-image](https://www.npmjs.com/package/@ckeditor/ckeditor5-image): v13.0.0 => [v13.0.1](https://github.com/ckeditor/ckeditor5-image/releases/tag/v13.0.1)
+* [@ckeditor/ckeditor5-link](https://www.npmjs.com/package/@ckeditor/ckeditor5-link): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-link/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-list](https://www.npmjs.com/package/@ckeditor/ckeditor5-list): v12.0.0 => [v12.0.1](https://github.com/ckeditor/ckeditor5-list/releases/tag/v12.0.1)
+* [@ckeditor/ckeditor5-markdown-gfm](https://www.npmjs.com/package/@ckeditor/ckeditor5-markdown-gfm): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-markdown-gfm/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-paragraph](https://www.npmjs.com/package/@ckeditor/ckeditor5-paragraph): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-paragraph/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-paste-from-office](https://www.npmjs.com/package/@ckeditor/ckeditor5-paste-from-office): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-paste-from-office/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-table](https://www.npmjs.com/package/@ckeditor/ckeditor5-table): v12.0.0 => [v12.0.1](https://github.com/ckeditor/ckeditor5-table/releases/tag/v12.0.1)
+* [@ckeditor/ckeditor5-theme-lark](https://www.npmjs.com/package/@ckeditor/ckeditor5-theme-lark): v13.0.0 => [v13.0.1](https://github.com/ckeditor/ckeditor5-theme-lark/releases/tag/v13.0.1)
+* [@ckeditor/ckeditor5-typing](https://www.npmjs.com/package/@ckeditor/ckeditor5-typing): v12.0.0 => [v12.0.1](https://github.com/ckeditor/ckeditor5-typing/releases/tag/v12.0.1)
+* [@ckeditor/ckeditor5-undo](https://www.npmjs.com/package/@ckeditor/ckeditor5-undo): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-undo/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-upload](https://www.npmjs.com/package/@ckeditor/ckeditor5-upload): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-upload/releases/tag/v11.0.1)
+* [@ckeditor/ckeditor5-widget](https://www.npmjs.com/package/@ckeditor/ckeditor5-widget): v11.0.0 => [v11.0.1](https://github.com/ckeditor/ckeditor5-widget/releases/tag/v11.0.1)
+
+
+## [12.0.0](https://github.com/ckeditor/ckeditor5/compare/v11.2.0...v12.0.0) (2019-02-28)
+
+We are happy to report the release of CKEditor 5 v12.0.0. This release introduces a new editor (called "[Balloon block editor](https://ckeditor.com/docs/ckeditor5/latest/examples/builds/balloon-block-editor.html)"), the [editor content placeholder](https://ckeditor.com/docs/ckeditor5/latest/features/editor-placeholder.html) and support for inline widgets (watch [this PR](https://github.com/ckeditor/ckeditor5/pull/1587) for updates). In addition to that we enabled media embeds and images in tables and resolved the issue where `editor.getData()` returned `<p>&nbsp;</p>` for empty content (now it returns an empty string in this case).
+
+Besides new features, this release contains many improvements to stability, [performance](https://github.com/ckeditor/ckeditor5-utils/issues/269) and API. The last group of changes contain many breaking ones. Make sure to read the notes below.
+
+Read more in the blog post: https://ckeditor.com/blog/CKEditor-5-v12.0.0-with-inline-widgets-and-distraction-free-editor-released/
+
+**Important information for integration developers:** The `editor.getData()` method will return an empty string if the editor is empty (instead of returning `<p>&nbsp;</p>`). Also, if you relied on `editor.ui.view.editable`, you will now need to use `editor.ui.getEditableElement()` instead. You may also want to read the below sections and the [Migration guide](https://github.com/ckeditor/ckeditor5/issues/1582) to learn more.
+
+**Important information for plugin developers:** The most important change that will affect your plugins is the removal of the `upcast-converters.js` and `downcast-converters.js` modules. You can now find those methods directly on the object returned by [`editor.conversion.for()`](https://ckeditor.com/docs/ckeditor5/latest/api/module_engine_conversion_conversion-Conversion.html#function-for). Other than that, see the changes described in the next section, the [engine's changelog](https://github.com/ckeditor/ckeditor5-engine/releases/tag/v13.0.0) and read the [Migration guide](https://github.com/ckeditor/ckeditor5/issues/1582) for the details.
+
+**Important information for custom editor developers:** We cleaned up the base editor interfaces and classes (`EditorWithUI`, `EditorUI`, `EditorUIView`, `EditableUIView`) and straightened responsibilities between the UI and the engine (the engine is now the one responsible for managing editable element classes). These changes means that your custom editor implementations will need to be updated. Read more in the [Migration guide](https://github.com/ckeditor/ckeditor5/issues/1582).
+
+### Dependencies
+
+New packages:
+
+* [@ckeditor/ckeditor5-build-balloon-block](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-balloon-block): [v12.0.0](https://github.com/ckeditor/ckeditor5-build-balloon-block/releases/tag/v12.0.0)
+
+Major releases (contain breaking changes):
+
+* [@ckeditor/ckeditor5-adapter-ckfinder](https://www.npmjs.com/package/@ckeditor/ckeditor5-adapter-ckfinder): v10.0.4 => [v11.0.0](https://github.com/ckeditor/ckeditor5-adapter-ckfinder/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-alignment](https://www.npmjs.com/package/@ckeditor/ckeditor5-alignment): v10.0.4 => [v11.0.0](https://github.com/ckeditor/ckeditor5-alignment/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-autoformat](https://www.npmjs.com/package/@ckeditor/ckeditor5-autoformat): v10.0.4 => [v11.0.0](https://github.com/ckeditor/ckeditor5-autoformat/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-autosave](https://www.npmjs.com/package/@ckeditor/ckeditor5-autosave): v10.0.2 => [v11.0.0](https://github.com/ckeditor/ckeditor5-autosave/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-basic-styles](https://www.npmjs.com/package/@ckeditor/ckeditor5-basic-styles): v10.1.0 => [v11.0.0](https://github.com/ckeditor/ckeditor5-basic-styles/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-block-quote](https://www.npmjs.com/package/@ckeditor/ckeditor5-block-quote): v10.1.1 => [v11.0.0](https://github.com/ckeditor/ckeditor5-block-quote/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-build-balloon](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-balloon): v11.2.0 => [v12.0.0](https://github.com/ckeditor/ckeditor5-build-balloon/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-build-classic](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-classic): v11.2.0 => [v12.0.0](https://github.com/ckeditor/ckeditor5-build-classic/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-build-decoupled-document](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-decoupled-document): v11.2.0 => [v12.0.0](https://github.com/ckeditor/ckeditor5-build-decoupled-document/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-build-inline](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-inline): v11.2.0 => [v12.0.0](https://github.com/ckeditor/ckeditor5-build-inline/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-ckfinder](https://www.npmjs.com/package/@ckeditor/ckeditor5-ckfinder): v10.0.0 => [v11.0.0](https://github.com/ckeditor/ckeditor5-ckfinder/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-clipboard](https://www.npmjs.com/package/@ckeditor/ckeditor5-clipboard): v10.0.4 => [v11.0.0](https://github.com/ckeditor/ckeditor5-clipboard/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-cloud-services](https://www.npmjs.com/package/@ckeditor/ckeditor5-cloud-services): v10.1.1 => [v11.0.0](https://github.com/ckeditor/ckeditor5-cloud-services/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-core](https://www.npmjs.com/package/@ckeditor/ckeditor5-core): v11.1.0 => [v12.0.0](https://github.com/ckeditor/ckeditor5-core/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-easy-image](https://www.npmjs.com/package/@ckeditor/ckeditor5-easy-image): v10.0.4 => [v11.0.0](https://github.com/ckeditor/ckeditor5-easy-image/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-editor-balloon](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-balloon): v11.0.2 => [v12.0.0](https://github.com/ckeditor/ckeditor5-editor-balloon/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-editor-classic](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-classic): v11.0.2 => [v12.0.0](https://github.com/ckeditor/ckeditor5-editor-classic/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-editor-decoupled](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-decoupled): v11.0.2 => [v12.0.0](https://github.com/ckeditor/ckeditor5-editor-decoupled/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-editor-inline](https://www.npmjs.com/package/@ckeditor/ckeditor5-editor-inline): v11.0.2 => [v12.0.0](https://github.com/ckeditor/ckeditor5-editor-inline/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-engine](https://www.npmjs.com/package/@ckeditor/ckeditor5-engine): v12.0.0 => [v13.0.0](https://github.com/ckeditor/ckeditor5-engine/releases/tag/v13.0.0)
+* [@ckeditor/ckeditor5-enter](https://www.npmjs.com/package/@ckeditor/ckeditor5-enter): v10.1.3 => [v11.0.0](https://github.com/ckeditor/ckeditor5-enter/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-essentials](https://www.npmjs.com/package/@ckeditor/ckeditor5-essentials): v10.1.3 => [v11.0.0](https://github.com/ckeditor/ckeditor5-essentials/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-font](https://www.npmjs.com/package/@ckeditor/ckeditor5-font): v10.0.4 => [v11.0.0](https://github.com/ckeditor/ckeditor5-font/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-heading](https://www.npmjs.com/package/@ckeditor/ckeditor5-heading): v10.1.1 => [v11.0.0](https://github.com/ckeditor/ckeditor5-heading/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-highlight](https://www.npmjs.com/package/@ckeditor/ckeditor5-highlight): v10.0.4 => [v11.0.0](https://github.com/ckeditor/ckeditor5-highlight/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-image](https://www.npmjs.com/package/@ckeditor/ckeditor5-image): v12.0.0 => [v13.0.0](https://github.com/ckeditor/ckeditor5-image/releases/tag/v13.0.0)
+* [@ckeditor/ckeditor5-link](https://www.npmjs.com/package/@ckeditor/ckeditor5-link): v10.1.0 => [v11.0.0](https://github.com/ckeditor/ckeditor5-link/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-list](https://www.npmjs.com/package/@ckeditor/ckeditor5-list): v11.0.3 => [v12.0.0](https://github.com/ckeditor/ckeditor5-list/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-markdown-gfm](https://www.npmjs.com/package/@ckeditor/ckeditor5-markdown-gfm): v10.0.4 => [v11.0.0](https://github.com/ckeditor/ckeditor5-markdown-gfm/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-media-embed](https://www.npmjs.com/package/@ckeditor/ckeditor5-media-embed): v10.1.0 => [v11.0.0](https://github.com/ckeditor/ckeditor5-media-embed/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-paragraph](https://www.npmjs.com/package/@ckeditor/ckeditor5-paragraph): v10.0.4 => [v11.0.0](https://github.com/ckeditor/ckeditor5-paragraph/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-paste-from-office](https://www.npmjs.com/package/@ckeditor/ckeditor5-paste-from-office): v10.0.0 => [v11.0.0](https://github.com/ckeditor/ckeditor5-paste-from-office/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-table](https://www.npmjs.com/package/@ckeditor/ckeditor5-table): v11.0.1 => [v12.0.0](https://github.com/ckeditor/ckeditor5-table/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-theme-lark](https://www.npmjs.com/package/@ckeditor/ckeditor5-theme-lark): v12.0.0 => [v13.0.0](https://github.com/ckeditor/ckeditor5-theme-lark/releases/tag/v13.0.0)
+* [@ckeditor/ckeditor5-typing](https://www.npmjs.com/package/@ckeditor/ckeditor5-typing): v11.0.2 => [v12.0.0](https://github.com/ckeditor/ckeditor5-typing/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-ui](https://www.npmjs.com/package/@ckeditor/ckeditor5-ui): v11.2.0 => [v12.0.0](https://github.com/ckeditor/ckeditor5-ui/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-undo](https://www.npmjs.com/package/@ckeditor/ckeditor5-undo): v10.0.4 => [v11.0.0](https://github.com/ckeditor/ckeditor5-undo/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-upload](https://www.npmjs.com/package/@ckeditor/ckeditor5-upload): v10.0.4 => [v11.0.0](https://github.com/ckeditor/ckeditor5-upload/releases/tag/v11.0.0)
+* [@ckeditor/ckeditor5-utils](https://www.npmjs.com/package/@ckeditor/ckeditor5-utils): v11.1.0 => [v12.0.0](https://github.com/ckeditor/ckeditor5-utils/releases/tag/v12.0.0)
+* [@ckeditor/ckeditor5-widget](https://www.npmjs.com/package/@ckeditor/ckeditor5-widget): v10.3.1 => [v11.0.0](https://github.com/ckeditor/ckeditor5-widget/releases/tag/v11.0.0)
+
+### BREAKING CHANGES
+
+* Upgraded minimal versions of Node to `8.0.0` and npm to `5.7.1`. See: [ckeditor/ckeditor5#1507](https://github.com/ckeditor/ckeditor5/issues/1507). ([612ea3c](https://github.com/ckeditor/ckeditor5-cloud-services/commit/612ea3c))
+
+
 ## [11.2.0](https://github.com/ckeditor/ckeditor5/compare/v11.1.1...v11.2.0) (2018-12-05)
 
 We are happy to report the release of CKEditor 5 v11.2.0. This editor version brings the long-awaited [support for paste from Office](https://ckeditor.com/docs/ckeditor5/latest/features/paste-from-word.html) (e.g. from Microsoft Word), [integration with CKFinder file manager](https://ckeditor.com/docs/ckeditor5/latest/features/ckfinder.html), improved [image upload documentation](https://ckeditor.com/docs/ckeditor5/latest/features/image-upload.html), improved [editor UI on mobile devices](https://github.com/ckeditor/ckeditor5/issues/416#issuecomment-430246472), as well as many smaller features and improvements.
 
-Blog post is comming soon...
+Read more in the blog post: https://ckeditor.com/blog/CKEditor-5-v11.2.0-with-paste-from-Word-and-file-manager-support-released/
 
 **Important information for plugin developers:** We would like to let you know about imporant breaking changes in the `@ckeditor/ckeditor5-engine` package. Read more about them in the [`@ckeditor/ckeditor5-engine@v12.0.0` release notes](https://github.com/ckeditor/ckeditor5-engine/releases/tag/v12.0.0).
 

+ 1 - 1
LICENSE.md

@@ -2,7 +2,7 @@ Software License Agreement
 ==========================
 
 **CKEditor 5** – https://github.com/ckeditor/ckeditor5 <br>
-Copyright (c) 2003-2018, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
+Copyright (c) 2003-2019, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
 
 Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).
 

+ 75 - 3
README.md

@@ -4,7 +4,6 @@ CKEditor 5 [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?styl
 [![npm version](https://badge.fury.io/js/ckeditor5.svg)](https://www.npmjs.com/package/ckeditor5)
 
 [![Build Status](https://travis-ci.org/ckeditor/ckeditor5.svg?branch=master)](https://travis-ci.org/ckeditor/ckeditor5)
-[![BrowserStack Status](https://automate.browserstack.com/automate/badge.svg?badge_key=d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)](https://automate.browserstack.com/public-build/d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)
 [![Dependency Status](https://img.shields.io/david/ckeditor/ckeditor5.svg)](https://david-dm.org/ckeditor/ckeditor5)
 [![devDependency Status](https://img.shields.io/david/dev/ckeditor/ckeditor5.svg)](https://david-dm.org/ckeditor/ckeditor5?type=dev)
 
@@ -18,7 +17,7 @@ A set of ready-to-use rich text editors created with a powerful framework. Made
 
 ## ⚠ This package does not contain any source code
 
-CKEditor 5 is distributed as [four ready-to-use rich text editor builds](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#available-builds) which you can [install from npm](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/installation.html#npm).
+CKEditor 5 is distributed as [several ready-to-use rich text editor builds](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#available-builds) which you can [install from npm](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/integration/installation.html#npm).
 
 You can also [customize the existing builds](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/development/custom-builds.html) or build your own editors and features by using the [CKEditor 5 Framework](https://ckeditor.com/docs/ckeditor5/latest/framework/guides/overview.html).
 
@@ -52,6 +51,7 @@ The following CKEditor 5 Builds are currently available:
 * [Classic editor](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#classic-editor)
 * [Inline editor](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#inline-editor)
 * [Balloon editor](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#balloon-editor)
+* [Balloon block editor](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#balloon-block-editor)
 * [Document editor](https://ckeditor.com/docs/ckeditor5/latest/builds/guides/overview.html#document-editor)
 
 #### Example
@@ -220,6 +220,18 @@ See CKEditor 5 release blog posts [on the CKEditor blog](https://ckeditor.com/bl
 	</td>
 </tr>
 
+<tr>
+	<td>
+		<a href="https://github.com/ckeditor/ckeditor5-watchdog"><code>@ckeditor/ckeditor5-watchdog</code></a>
+	</td>
+	<td>
+		<a href="https://www.npmjs.com/package/@ckeditor/ckeditor5-watchdog"><img src="https://img.shields.io/npm/v/@ckeditor/ckeditor5-watchdog.svg" alt="@ckeditor/ckeditor5-watchdog npm package badge"></a>
+	</td>
+	<td>
+		The watchdog feature, which keeps CKEditor 5 editors running.
+	</td>
+</tr>
+
 <tr>
 	<td>
 		<a href="https://github.com/ckeditor/ckeditor5-cloud-services"><code>@ckeditor/ckeditor5-cloud-services</code></a>
@@ -283,6 +295,18 @@ See CKEditor 5 release blog posts [on the CKEditor blog](https://ckeditor.com/bl
 	</td>
 </tr>
 
+<tr>
+	<td>
+		<a href="https://github.com/ckeditor/ckeditor5-build-balloon-block"><code>@ckeditor/ckeditor5-build-balloon-block</code></a>
+	</td>
+	<td>
+		<a href="https://www.npmjs.com/package/@ckeditor/ckeditor5-build-balloon-block"><img src="https://img.shields.io/npm/v/@ckeditor/ckeditor5-build-balloon-block.svg" alt="@ckeditor/ckeditor5-build-balloon-block npm package badge"></a>
+	</td>
+	<td>
+		The balloon editor with a block toolbar (Slack–like) build.
+	</td>
+</tr>
+
 <tr>
 	<td>
 		<a href="https://github.com/ckeditor/ckeditor5-build-decoupled-document"><code>@ckeditor/ckeditor5-build-decoupled-document</code></a>
@@ -478,6 +502,18 @@ See CKEditor 5 release blog posts [on the CKEditor blog](https://ckeditor.com/bl
 	</td>
 </tr>
 
+<tr>
+	<td>
+		<a href="https://github.com/ckeditor/ckeditor5-indent"><code>@ckeditor/ckeditor5-indent</code></a>
+	</td>
+	<td>
+		<a href="https://www.npmjs.com/package/@ckeditor/ckeditor5-indent"><img src="https://img.shields.io/npm/v/@ckeditor/ckeditor5-indent.svg" alt="@ckeditor/ckeditor5-indent npm package badge"></a>
+	</td>
+	<td>
+		The indentation (of lists and blocks) feature.
+	</td>
+</tr>
+
 <tr>
 	<td>
 		<a href="https://github.com/ckeditor/ckeditor5-link"><code>@ckeditor/ckeditor5-link</code></a>
@@ -526,6 +562,18 @@ See CKEditor 5 release blog posts [on the CKEditor blog](https://ckeditor.com/bl
 	</td>
 </tr>
 
+<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>
@@ -550,6 +598,18 @@ See CKEditor 5 release blog posts [on the CKEditor blog](https://ckeditor.com/bl
 	</td>
 </tr>
 
+<tr>
+	<td>
+		<a href="https://github.com/ckeditor/ckeditor5-remove-format"><code>@ckeditor/ckeditor5-remove-format</code></a>
+	</td>
+	<td>
+		<a href="https://www.npmjs.com/package/@ckeditor/ckeditor5-remove-format"><img src="https://img.shields.io/npm/v/@ckeditor/ckeditor5-remove-format.svg" alt="@ckeditor/ckeditor5-remove-format npm package badge"></a>
+	</td>
+	<td>
+		The remove format feature.
+	</td>
+</tr>
+
 <tr>
 	<td>
 		<a href="https://github.com/ckeditor/ckeditor5-table"><code>@ckeditor/ckeditor5-table</code></a>
@@ -586,6 +646,18 @@ See CKEditor 5 release blog posts [on the CKEditor blog](https://ckeditor.com/bl
 	</td>
 </tr>
 
+<tr>
+	<td>
+		<a href="https://github.com/ckeditor/ckeditor5-word-count"><code>@ckeditor/ckeditor5-word-count</code></a>
+	</td>
+	<td>
+		<a href="https://www.npmjs.com/package/@ckeditor/ckeditor5-word-count"><img src="https://img.shields.io/npm/v/@ckeditor/ckeditor5-word-count.svg" alt="@ckeditor/ckeditor5-word-count npm package badge"></a>
+	</td>
+	<td>
+		The word count feature.
+	</td>
+</tr>
+
 </tbody>
 </table>
 
@@ -681,4 +753,4 @@ See CKEditor 5 release blog posts [on the CKEditor blog](https://ckeditor.com/bl
 
 ## License
 
-Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). For full details about the license, please check the LICENSE.md file.
+Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). For full details about the license, please check the `LICENSE.md` file or [https://ckeditor.com/legal/ckeditor-oss-license](https://ckeditor.com/legal/ckeditor-oss-license).

+ 2 - 2
docs/_snippets/build-classic-source.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals window */

+ 2 - 2
docs/_snippets/builds/saving-data/autosave.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals ClassicEditor, console, window, document, setTimeout */

+ 2 - 2
docs/_snippets/builds/saving-data/build-autosave-source.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals window */

+ 2 - 2
docs/_snippets/builds/saving-data/manualsave.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals ClassicEditor, console, window, document, setTimeout */

+ 38 - 0
docs/_snippets/examples/balloon-block-editor.html

@@ -0,0 +1,38 @@
+<div id="snippet-balloon-block-editor">
+	<h2>Taj Mahal: A breathtaking ode to love</h2>
+
+	<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>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>
+
+	<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>
+
+	<h3>Masterpiece of the world’s heritage</h3>
+
+	<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>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>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>The tomb</li>
+		<li>The Decorations</li>
+		<li>The Garden</li>
+		<li>The Outlying buildings</li>
+	</ul>
+
+	<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>
+	/* Restrict the width of the editor to isolate it from the content of the guide. */
+	#snippet-balloon-block-editor {
+		margin-left: 5%;
+		margin-right: 5%;
+	}
+</style>

+ 21 - 0
docs/_snippets/examples/balloon-block-editor.js

@@ -0,0 +1,21 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console, window, document */
+
+import BalloonEditor from '@ckeditor/ckeditor5-build-balloon-block/src/ckeditor';
+
+import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
+
+BalloonEditor
+	.create( document.querySelector( '#snippet-balloon-block-editor' ), {
+		cloudServices: CS_CONFIG
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err );
+	} );

+ 2 - 2
docs/_snippets/examples/balloon-editor.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals console, window, document */

+ 2 - 2
docs/_snippets/examples/classic-editor-short.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals ClassicEditor, console, window, document */

+ 2 - 2
docs/_snippets/examples/classic-editor.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals ClassicEditor, console, window, document */

+ 2 - 2
docs/_snippets/examples/custom-build.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals console, window, document */

+ 2 - 2
docs/_snippets/examples/document-editor.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals console, window, document */

+ 2 - 2
docs/_snippets/examples/inline-editor.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals console, window, document */

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

@@ -0,0 +1,44 @@
+<div id="snippet-inline-editor">
+	<div id="toolbar"></div>
+
+	<header id="header">
+		<h2>Gone traveling</h2>
+		<h3>Monthly travel news and inspiration</h3>
+	</header>
+
+	<div id="content">
+		<h3>Destination of the Month</h3>
+
+		<h4>Valletta</h4>
+
+		<figure class="image image-style-align-right">
+			<img alt="Picture of a sunlit facade of a Maltan building." src="%BASE_PATH%/assets/img/malta.jpg">
+			<figcaption>It's siesta time in Valletta.</figcaption>
+		</figure>
+
+		<p>The capital city of <a href="https://en.wikipedia.org/wiki/Malta" target="_blank" rel="external">Malta</a> is the top destination this summer. It’s home to a cutting-edge contemporary architecture, baroque masterpieces, delicious local cuisine and at least 8 months of sun. It’s also a top destination for filmmakers, so you can take a tour through locations familiar to you from Game of Thrones, Gladiator, Troy and many more.</p>
+	</div>
+
+	<div class="demo-row">
+		<div class="demo-row__half">
+			<div id="footer-left">
+				<h3>The three greatest things you learn from traveling</h3>
+				<p><a href="#">Find out more</a></p>
+			</div>
+		</div>
+
+		<div class="demo-row__half">
+			<div id="footer-right">
+				<h3>Walking the capitals of Europe: Warsaw</h3>
+				<p><a href="#">Find out more</a></p>
+			</div>
+		</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>

+ 430 - 0
docs/_snippets/examples/multi-root-editor.js

@@ -0,0 +1,430 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console:false, document, window */
+
+// Multiroot editor dependencies.
+import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
+import DataApiMixin from '@ckeditor/ckeditor5-core/src/editor/utils/dataapimixin';
+import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
+import getDataFromElement from '@ckeditor/ckeditor5-utils/src/dom/getdatafromelement';
+import setDataInElement from '@ckeditor/ckeditor5-utils/src/dom/setdatainelement';
+import mix from '@ckeditor/ckeditor5-utils/src/mix';
+import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui';
+import enableToolbarKeyboardFocus from '@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus';
+import normalizeToolbarConfig from '@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig';
+import { enablePlaceholder } from '@ckeditor/ckeditor5-engine/src/view/placeholder';
+import EditorUIView from '@ckeditor/ckeditor5-ui/src/editorui/editoruiview';
+import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview';
+import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview';
+import Template from '@ckeditor/ckeditor5-ui/src/template';
+
+// Editor sample dependencies.
+import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
+import Heading from '@ckeditor/ckeditor5-heading/src/heading';
+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 List from '@ckeditor/ckeditor5-list/src/list';
+import Link from '@ckeditor/ckeditor5-link/src/link';
+import BlockQuote from '@ckeditor/ckeditor5-block-quote/src/blockquote';
+import Image from '@ckeditor/ckeditor5-image/src/image';
+import ImageCaption from '@ckeditor/ckeditor5-image/src/imagecaption';
+import ImageStyle from '@ckeditor/ckeditor5-image/src/imagestyle';
+import ImageToolbar from '@ckeditor/ckeditor5-image/src/imagetoolbar';
+import ImageUpload from '@ckeditor/ckeditor5-image/src/imageupload';
+import Table from '@ckeditor/ckeditor5-table/src/table';
+import TableToolbar from '@ckeditor/ckeditor5-table/src/tabletoolbar';
+import MediaEmbed from '@ckeditor/ckeditor5-media-embed/src/mediaembed';
+import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage';
+import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
+
+/**
+ * The multiroot editor implementation. It provides an inline editables and a toolbar.
+ *
+ * Unlike other editors, the toolbar is not rendered automatically and needs to be attached to the DOM manually.
+ *
+ * This type of an editor is dedicated to integrations which require a customized UI with an open
+ * structure, allowing developers to specify the exact location of the interface.
+ *
+ * @mixes module:core/editor/utils/dataapimixin~DataApiMixin
+ * @implements module:core/editor/editorwithui~EditorWithUI
+ * @extends module:core/editor/editor~Editor
+ */
+class MultirootEditor extends Editor {
+	/**
+	 * Creates an instance of the multiroot editor.
+	 *
+	 * **Note:** Do not use the constructor to create editor instances. Use the static `MultirootEditor.create()` method instead.
+	 *
+	 * @protected
+	 * @param {Object.<String,HTMLElement>} sourceElements The list of DOM elements that will be the source
+	 * for the created editor (on which the editor will be initialized).
+	 * @param {module:core/editor/editorconfig~EditorConfig} config The editor configuration.
+	 */
+	constructor( sourceElements, config ) {
+		super( config );
+
+		this.data.processor = new HtmlDataProcessor();
+
+		// Create root and UIView element for each editable container.
+		for ( const rootName of Object.keys( sourceElements ) ) {
+			this.model.document.createRoot( '$root', rootName );
+		}
+
+		this.ui = new MultirootEditorUI( this, new MultirootEditorUIView( this.locale, this.editing.view, sourceElements ) );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	destroy() {
+		// Cache the data and editable DOM elements, then destroy.
+		// It's safe to assume that the model->view conversion will not work after super.destroy(),
+		// same as `ui.getEditableElement()` method will not return editables.
+		const data = {};
+		const editables = {};
+		const editablesNames = Array.from( this.ui.getEditableElementsNames() );
+
+		for ( const rootName of editablesNames ) {
+			data[ rootName ] = this.getData( { rootName } );
+			editables[ rootName ] = this.ui.getEditableElement( rootName );
+		}
+
+		this.ui.destroy();
+
+		return super.destroy()
+			.then( () => {
+				for ( const rootName of editablesNames ) {
+					setDataInElement( editables[ rootName ], data[ rootName ] );
+				}
+			} );
+	}
+
+	/**
+	 * Creates a multiroot editor instance.
+	 *
+	 * @param {Object.<String,HTMLElement>} sourceElements The list of DOM elements that will be the source
+	 * for the created editor (on which the editor will be initialized).
+	 * @param {module:core/editor/editorconfig~EditorConfig} config The editor configuration.
+	 * @returns {Promise} A promise resolved once the editor is ready. The promise returns the created multiroot editor instance.
+	 */
+	static create( sourceElements, config ) {
+		return new Promise( resolve => {
+			const editor = new this( sourceElements, config );
+
+			resolve(
+				editor.initPlugins()
+					.then( () => editor.ui.init() )
+					.then( () => {
+						const initialData = {};
+
+						// Create initial data object containing data from all roots.
+						for ( const rootName of Object.keys( sourceElements ) ) {
+							initialData[ rootName ] = getDataFromElement( sourceElements[ rootName ] );
+						}
+
+						return editor.data.init( initialData );
+					} )
+					.then( () => editor.fire( 'ready' ) )
+					.then( () => editor )
+			);
+		} );
+	}
+}
+
+mix( MultirootEditor, DataApiMixin );
+
+/**
+ * The multiroot editor UI class.
+ *
+ * @extends module:core/editor/editorui~EditorUI
+ */
+class MultirootEditorUI extends EditorUI {
+	/**
+	 * Creates an instance of the multiroot editor UI class.
+	 *
+	 * @param {module:core/editor/editor~Editor} editor The editor instance.
+	 * @param {module:ui/editorui/editoruiview~EditorUIView} view The view of the UI.
+	 */
+	constructor( editor, view ) {
+		super( editor );
+
+		/**
+		 * The main (top–most) view of the editor UI.
+		 *
+		 * @readonly
+		 * @member {module:ui/editorui/editoruiview~EditorUIView} #view
+		 */
+		this.view = view;
+
+		/**
+		 * A normalized `config.toolbar` object.
+		 *
+		 * @type {Object}
+		 * @private
+		 */
+		this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) );
+	}
+
+	/**
+	 * Initializes the UI.
+	 */
+	init() {
+		const view = this.view;
+		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().
+			const editableElement = editable.element;
+
+			// Register the editable UI view in the editor. A single editor instance can aggregate multiple
+			// editable areas (roots) but the decoupled editor has only one.
+			this.setEditableElement( editable.name, editableElement );
+
+			// Let the global focus tracker know that the editable UI element is focusable and
+			// belongs to the editor. From now on, the focus tracker will sustain the editor focus
+			// as long as the editable is focused (e.g. the user is typing).
+			this.focusTracker.add( editableElement );
+
+			// Let the editable UI element respond to the changes in the global editor focus
+			// tracker. It has been added to the same tracker a few lines above but, in reality, there are
+			// many focusable areas in the editor, like balloons, toolbars or dropdowns and as long
+			// as they have focus, the editable should act like it is focused too (although technically
+			// 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, '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.
+			editingView.attachDomRoot( editableElement, editable.name );
+		}
+
+		this._initPlaceholder();
+		this._initToolbar();
+		this.fire( 'ready' );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	destroy() {
+		const view = this.view;
+		const editingView = this.editor.editing.view;
+
+		for ( const editable of this.view.editables ) {
+			editingView.detachDomRoot( editable.name );
+		}
+
+		view.destroy();
+
+		super.destroy();
+	}
+
+	/**
+	 * Initializes the inline editor toolbar and its panel.
+	 *
+	 * @private
+	 */
+	_initToolbar() {
+		const editor = this.editor;
+		const view = this.view;
+		const toolbar = view.toolbar;
+
+		toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
+
+		enableToolbarKeyboardFocus( {
+			origin: editor.editing.view,
+			originFocusTracker: this.focusTracker,
+			originKeystrokeHandler: editor.keystrokes,
+			toolbar
+		} );
+	}
+
+	/**
+	 * Enable the placeholder text on the editing root, if any was configured.
+	 *
+	 * @private
+	 */
+	_initPlaceholder() {
+		const editor = this.editor;
+		const editingView = editor.editing.view;
+
+		for ( const editable of this.view.editables ) {
+			const editingRoot = editingView.document.getRoot( editable.name );
+			const sourceElement = this.getEditableElement( editable.name );
+
+			const placeholderText = editor.config.get( 'placeholder' )[ editable.name ] ||
+				sourceElement && sourceElement.tagName.toLowerCase() === 'textarea' && sourceElement.getAttribute( 'placeholder' );
+
+			if ( placeholderText ) {
+				enablePlaceholder( {
+					view: editingView,
+					element: editingRoot,
+					text: placeholderText,
+					isDirectHost: false
+				} );
+			}
+		}
+	}
+}
+
+/**
+ * The multiroot editor UI view. It is a virtual view providing an inline editable, but without
+ * any specific arrangement of the components in the DOM.
+ *
+ * @extends module:ui/editorui/editoruiview~EditorUIView
+ */
+class MultirootEditorUIView extends EditorUIView {
+	/**
+	 * Creates an instance of the multiroot editor UI view.
+	 *
+	 * @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance.
+	 * @param {module:engine/view/view~View} editingView The editing view instance this view is related to.
+	 * @param {Object.<String,HTMLElement>} editableElements The list of editable elements, containing name and html element
+	 * for each editable.
+	 */
+	constructor( locale, editingView, editableElements ) {
+		super( locale );
+
+		/**
+		 * The main toolbar of the decoupled editor UI.
+		 *
+		 * @readonly
+		 * @member {module:ui/toolbar/toolbarview~ToolbarView}
+		 */
+		this.toolbar = new ToolbarView( locale );
+
+		/**
+		 * The editables of the multiroot editor UI.
+		 *
+		 * @readonly
+		 * @member {Array.<module:ui/editableui/inline/inlineeditableuiview~InlineEditableUIView>}
+		 */
+		this.editables = [];
+
+		// Create InlineEditableUIView instance for each editable.
+		for ( const editableName of Object.keys( editableElements ) ) {
+			const editable = new InlineEditableUIView( locale, editingView, editableElements[ editableName ] );
+
+			editable.name = editableName;
+			this.editables.push( editable );
+		}
+
+		// This toolbar may be placed anywhere in the page so things like font size need to be reset in it.
+		// Also because of the above, make sure the toolbar supports rounded corners.
+		Template.extend( this.toolbar.template, {
+			attributes: {
+				class: [
+					'ck-reset_all',
+					'ck-rounded-corners'
+				]
+			}
+		} );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	render() {
+		super.render();
+
+		this.registerChild( this.editables );
+		this.registerChild( [ this.toolbar ] );
+	}
+}
+
+// Initialize editor
+MultirootEditor
+	.create( {
+		header: document.querySelector( '#header' ),
+		content: document.querySelector( '#content' ),
+		footerleft: document.querySelector( '#footer-left' ),
+		footerright: document.querySelector( '#footer-right' )
+	}, {
+		plugins: [ Essentials, Paragraph, Heading, Bold, Italic, List, Link, BlockQuote, Image, ImageCaption,
+			ImageStyle, ImageToolbar, ImageUpload, Table, TableToolbar, MediaEmbed, EasyImage ],
+		toolbar: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'imageUpload', 'blockQuote',
+			'insertTable', 'mediaEmbed', 'undo', 'redo' ],
+		image: {
+			toolbar: [ 'imageTextAlternative', '|', 'imageStyle:alignLeft', 'imageStyle:full', 'imageStyle:alignRight' ],
+			styles: [ 'full', 'alignLeft', 'alignRight' ]
+		},
+		table: {
+			contentToolbar: [
+				'tableColumn',
+				'tableRow',
+				'mergeTableCells'
+			]
+		},
+		placeholder: {
+			header: 'Header text goes here',
+			content: 'Type content here',
+			footerleft: 'Left footer content',
+			footerright: 'Right footer content'
+		},
+		cloudServices: CS_CONFIG
+	} )
+	.then( newEditor => {
+		document.querySelector( '#toolbar' ).appendChild( newEditor.ui.view.toolbar.element );
+
+		window.editor = newEditor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );
+

+ 2 - 2
docs/_snippets/features/image-upload.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals ClassicEditor, console, window, document */

+ 10 - 0
docs/_snippets/features/placeholder-custom.html

@@ -0,0 +1,10 @@
+<style>
+#snippet-placeholder-custom + .ck.ck-editor .ck-editor__editable > .ck-placeholder::before {
+    color: #d21714;
+    font-family: Georgia;
+}
+</style>
+
+<div id="snippet-placeholder-custom">
+	<p></p>
+</div>

+ 20 - 0
docs/_snippets/features/placeholder-custom.js

@@ -0,0 +1,20 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console, window, document, ClassicEditor */
+
+import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
+
+ClassicEditor
+	.create( document.querySelector( '#snippet-placeholder-custom' ), {
+		cloudServices: CS_CONFIG,
+		placeholder: 'Type the content here!'
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 3 - 0
docs/_snippets/features/placeholder.html

@@ -0,0 +1,3 @@
+<div id="snippet-placeholder">
+	<p></p>
+</div>

+ 20 - 0
docs/_snippets/features/placeholder.js

@@ -0,0 +1,20 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console, window, document, ClassicEditor */
+
+import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
+
+ClassicEditor
+	.create( document.querySelector( '#snippet-placeholder' ), {
+		cloudServices: CS_CONFIG,
+		placeholder: 'Type the content here!'
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 2 - 2
docs/_snippets/features/read-only.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals ClassicEditor, console, window, document */

+ 2 - 2
docs/_snippets/features/ui-language.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals console, window, document */

+ 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 coming 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 or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* 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 );
+	} );
+

+ 37 - 0
docs/_snippets/framework/tutorials/block-widget.html

@@ -0,0 +1,37 @@
+<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>
+
+<div id="snippet-block-widget">
+	<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>

+ 241 - 0
docs/_snippets/framework/tutorials/block-widget.js

@@ -0,0 +1,241 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console, window, document */
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
+import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
+import Heading from '@ckeditor/ckeditor5-heading/src/heading';
+import List from '@ckeditor/ckeditor5-list/src/list';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+
+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 Command from '@ckeditor/ckeditor5-core/src/command';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+
+class SimpleBox extends Plugin {
+	static get requires() {
+		return [ SimpleBoxEditing, SimpleBoxUI ];
+	}
+}
+
+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 the 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;
+		} );
+	}
+}
+
+class SimpleBoxEditing extends Plugin {
+	static get requires() {
+		return [ Widget ];
+	}
+
+	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'
+		} );
+
+		schema.addChildCheck( ( context, childDefinition ) => {
+			if ( context.endsWith( 'simpleBoxDescription' ) && childDefinition.name == 'simpleBox' ) {
+				return false;
+			}
+		} );
+	}
+
+	_defineConverters() {
+		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: You 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: You use a more specialized createEditableElement() method here.
+				const div = viewWriter.createEditableElement( 'div', { class: 'simple-box-description' } );
+
+				return toWidgetEditable( div, viewWriter );
+			}
+		} );
+	}
+}
+
+class InsertSimpleBoxCommand extends Command {
+	execute() {
+		this.editor.model.change( writer => {
+			// Insert <simpleBox>*</simpleBox> at the current selection position
+			// in a way that 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( selection.getFirstPosition(), 'simpleBox' );
+
+		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;
+}
+
+ClassicEditor
+	.create( document.querySelector( '#snippet-block-widget' ), {
+		plugins: [ Essentials, Bold, Italic, Heading, List, Paragraph, SimpleBox ],
+		toolbar: {
+			items: [ 'heading', '|', 'bold', 'italic', 'numberedList', 'bulletedList', 'simpleBox' ],
+			viewportTopOffset: window.getViewportTopOffsetConfig()
+		}
+	} )
+	.then( editor => {
+		console.log( 'Editor was initialized', editor );
+
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 17 - 0
docs/_snippets/framework/tutorials/inline-widget.html

@@ -0,0 +1,17 @@
+<style>
+	.placeholder {
+		background: #ffff00;
+		padding: 4px 2px;
+		outline-offset: -2px;
+		line-height: 1em;
+		margin: 0 1px;
+	}
+
+	.placeholder::selection {
+		display: none;
+	}
+</style>
+
+<div id="snippet-inline-widget">
+	<p>Hello <span class="placeholder">{first name}</span> <span class="placeholder">{surname}</span>!</p>
+</div>

+ 213 - 0
docs/_snippets/framework/tutorials/inline-widget.js

@@ -0,0 +1,213 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+/* globals console, window, document */
+
+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 Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import { toWidget, viewToModelPositionOutsideModelElement } from '@ckeditor/ckeditor5-widget/src/utils';
+import Widget from '@ckeditor/ckeditor5-widget/src/widget';
+import Command from '@ckeditor/ckeditor5-core/src/command';
+
+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';
+
+class PlaceholderCommand extends Command {
+	execute( { value } ) {
+		const editor = this.editor;
+
+		editor.model.change( writer => {
+			// Create a <placeholder> elment with the "name" attribute...
+			const placeholder = writer.createElement( 'placeholder', { name: value } );
+
+			// ... and insert it into the document.
+			editor.model.insertContent( placeholder );
+
+			// Put the selection on the 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;
+	}
+}
+
+class Placeholder extends Plugin {
+	static get requires() {
+		return [ PlaceholderEditing, PlaceholderUI ];
+	}
+}
+
+class PlaceholderUI extends Plugin {
+	init() {
+		const editor = this.editor;
+		const t = editor.t;
+		const placeholderNames = editor.config.get( 'placeholderConfig.types' );
+
+		// The "placeholder" dropdown must be registered among the 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 item 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;
+}
+
+class PlaceholderEditing extends Plugin {
+	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( 'placeholderConfig', {
+			types: [ 'date', 'first name', 'surname' ]
+		} );
+	}
+
+	_defineSchema() {
+		const schema = this.editor.model.schema;
+
+		schema.register( 'placeholder', {
+			// Allow wherever text is allowed:
+			allowWhere: '$text',
+
+			// The placeholder will act as an inline node:
+			isInline: true,
+
+			// The inline widget is self-contained so it cannot be split by the caret and it can be selected:
+			isObject: true,
+
+			// The placeholder can have many types, like date, name, surname, etc:
+			allowAttributes: [ 'name' ]
+		} );
+	}
+
+	_defineConverters() {
+		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 a placeholder element inside the 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;
+		}
+	}
+}
+
+ClassicEditor
+	.create( document.querySelector( '#snippet-inline-widget' ), {
+		plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic, Placeholder ],
+		toolbar: [ 'heading', '|', 'bold', 'italic', 'numberedList', 'bulletedList', '|', 'placeholder' ],
+		placeholderConfig: {
+			types: [ 'date', 'color', 'first name', 'surname' ]
+		}
+	} )
+	.then( editor => {
+		console.log( 'Editor was initialized', editor );
+
+		// Expose for playing in the console.
+		window.editor = editor;
+	} )
+	.catch( error => {
+		console.error( error.stack );
+	} );

二進制
docs/assets/img/editor-balloon-block.png


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


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


+ 66 - 62
docs/assets/img/image-upload-animation.svg

@@ -1,15 +1,17 @@
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="230px" height="130px" viewBox="0 0 230 130" opacity="1">
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Keyshape -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="230px" height="130px" viewBox="0 0 230 130" opacity="1" style="background: rgba(172,39,166,0.00);">
     <style>
 @keyframes a0_f { 0% { fill: #f9f9f9; } 0.1848% { fill: #f9f9f9; animation-timing-function: cubic-bezier(0,0,1,1); } 100% { fill: #f9f9f9; } }
 @keyframes info-text_t { 0% { transform: translate(141.5px,42px); } 24.9537% { transform: translate(141.5px,42px); animation-timing-function: cubic-bezier(0.42,0,1,1); } 29.5748% { transform: translate(141.5px,52px); } 100% { transform: translate(141.5px,52px); } }
 @keyframes info-text_o { 0% { opacity: 1; } 24.9538% { opacity: 1; animation-timing-function: cubic-bezier(0.42,0,1,1); } 29.5749% { opacity: 0; } 100% { opacity: 0; } }
-@keyframes a1_t { 0% { transform: scale(1,1); } 9.6118% { transform: scale(1,1); } 11.0905% { transform: scale(1.12,1.12); animation-timing-function: cubic-bezier(0,0,1,1); } 18.1146% { transform: scale(1.12,1.12); } 100% { transform: scale(1.12,1.12); } }
+@keyframes a1_t { 0% { transform: translate(209.5px,61.205px); } 11.0905% { transform: translate(209.5px,61.205px); animation-timing-function: cubic-bezier(0.317162,0,0.657134,0.381326); } 12.8465% { transform: translate(197.111099px,61.26231px); animation-timing-function: cubic-bezier(0.344056,0.237806,0.692483,0.612935); } 14.6025% { transform: translate(167.83226px,62.996719px); animation-timing-function: cubic-bezier(0.406594,0.360596,0.759551,0.732218); } 16.3585% { transform: translate(127.77684px,69.044304px); animation-timing-function: cubic-bezier(0.473064,0.456373,0.81911,0.811193); } 17.2365% { transform: translate(104.655914px,74.488602px); animation-timing-function: cubic-bezier(0.654384,0.647001,1,1); } 18.1146% { transform: translate(79.4551px,82.0677px); } 100% { transform: translate(79.4551px,82.0677px); } }
+@keyframes a2_t { 0% { transform: scale(1,1); } 9.6118% { transform: scale(1,1); } 11.0905% { transform: scale(1.12,1.12); animation-timing-function: cubic-bezier(0,0,1,1); } 18.1146% { transform: scale(1.12,1.12); } 100% { transform: scale(1.12,1.12); } }
 @keyframes photo-file_t { 0% { transform: translate(-9.5px,-11.205px); } 11.0905% { transform: translate(-9.5px,-11.205px); } 18.1146% { transform: translate(-9.5px,-11.205px); } 100% { transform: translate(-9.5px,-11.205px); } }
 @keyframes photo-file_o { 0% { opacity: 1; } 23.6599% { opacity: 1; } 26.2477% { opacity: 0; } 100% { opacity: 0; } }
-@keyframes photo-file_mo { 0% { offset-distance: 0%; } 11.0906% { offset-distance: 0%; animation-timing-function: cubic-bezier(0.42,0,1,1); } 18.1146% { offset-distance: 100%; } 100% { offset-distance: 100%; } }
 @keyframes file_1_txt-2_o { 0% { opacity: 1; } 11.0906% { opacity: 1; } 13.3087% { opacity: 0; } 100% { opacity: 0; } }
+@keyframes cursor_t { 0% { transform: translate(205px,18px); animation-timing-function: cubic-bezier(0.42,0,1,1); } 11.0905% { transform: translate(213px,63px); animation-timing-function: cubic-bezier(0.317162,0,0.657134,0.381326); } 12.8465% { transform: translate(200.631226px,63.280777px); animation-timing-function: cubic-bezier(0.344056,0.237806,0.692483,0.612935); } 14.6025% { transform: translate(171.394882px,65.339584px); animation-timing-function: cubic-bezier(0.406594,0.360596,0.759551,0.732218); } 16.3585% { transform: translate(131.370697px,71.396652px); animation-timing-function: cubic-bezier(0.473064,0.456373,0.81911,0.811193); } 17.2365% { transform: translate(108.226913px,76.643463px); animation-timing-function: cubic-bezier(0.654384,0.647001,1,1); } 18.1146% { transform: translate(82.9551px,83.8627px); } 100% { transform: translate(82.9551px,83.8627px); } }
 @keyframes cursor_o { 0% { opacity: 0; } 5.1756% { opacity: 1; } 18.1146% { opacity: 1; } 23.6599% { opacity: 0; } 100% { opacity: 0; } }
-@keyframes cursor_mo { 0% { offset-distance: 0%; animation-timing-function: cubic-bezier(0.42,0,1,1); } 11.0906% { offset-distance: 25.695%; animation-timing-function: cubic-bezier(0.42,0,1,1); } 18.1146% { offset-distance: 100%; } 100% { offset-distance: 100%; } }
 @keyframes photo-main_o { 0% { opacity: 0; } 26.4325% { opacity: 0; animation-timing-function: cubic-bezier(0.42,0,1,1); } 29.7597% { opacity: 0.2; } 62.8466% { opacity: 0.2; } 66.5434% { opacity: 1; } 100% { opacity: 1; } }
 @keyframes photo-box_t { 0% { transform: translate(139px,44px); } 69.8706% { transform: translate(139px,44px); } 75.4158% { transform: translate(139px,39px); } 100% { transform: translate(139px,39px); } }
 @keyframes photo-box_o { 0% { opacity: 0; } 69.8706% { opacity: 0; } 75.4159% { opacity: 1; } 100% { opacity: 1; } }
@@ -19,95 +21,97 @@
 @keyframes info-2_o { 0% { opacity: 0; } 75.4159% { opacity: 0; } 80.9612% { opacity: 1; } 100% { opacity: 1; } }
 @keyframes info-3_t { 0% { transform: translate(140px,90px); } 78.5582% { transform: translate(140px,90px); } 84.1035% { transform: translate(140px,85px); } 100% { transform: translate(140px,85px); } }
 @keyframes info-3_o { 0% { opacity: 0; } 78.5582% { opacity: 0; } 84.1035% { opacity: 1; } 100% { opacity: 1; } }
-@keyframes loading_t { 0% { transform: translate(28px,66px); } 35.8595% { transform: translate(28px,66px); } 100% { transform: translate(28px,66px); } }
+@keyframes loading_t { 0% { transform: translate(28px,66px) scale(1.09,1.09) translate(0px,0px); } 35.8595% { transform: translate(28px,66px) scale(1.09,1.09) translate(0px,0px); } 51.0166% { transform: translate(28px,66px) scale(49,1.09) translate(0px,0px); } 100% { transform: translate(28px,66px) scale(49,1.09) translate(0px,0px); } }
 @keyframes loading_o { 0% { opacity: 0; } 33.6414% { opacity: 0; } 35.8595% { opacity: 1; animation-timing-function: cubic-bezier(0.42,0,1,1); } 51.0166% { opacity: 1; animation-timing-function: cubic-bezier(0.42,0,1,1); } 55.4529% { opacity: 0; } 100% { opacity: 0; } }
-@keyframes loading_w { 0% { width: 2px; } 33.6414% { width: 2px; } 35.8595% { width: 2px; animation-timing-function: cubic-bezier(0.42,0,1,1); } 51.0166% { width: 98px; } 100% { width: 98px; } }
 @keyframes check_t { 0% { transform: translate(125px,67px) scale(2,2) translate(-3px,-3px); } 55.4528% { transform: translate(125px,67px) scale(2,2) translate(-3px,-3px); animation-timing-function: cubic-bezier(0.42,0,1,1); } 59.5194% { transform: translate(125px,67px) scale(1,1) translate(-3px,-3px); } 100% { transform: translate(125px,67px) scale(1,1) translate(-3px,-3px); } }
 @keyframes check_o { 0% { opacity: 0; } 55.4529% { opacity: 0; animation-timing-function: cubic-bezier(0.42,0,1,1); } 59.5194% { opacity: 1; } 70.9797% { opacity: 1; } 74.122% { opacity: 0; } 100% { opacity: 0; } }
     </style>
     <title>Artboard-1</title>
     <desc>Created with Sketch.</desc>
+    <rect width="230" height="130" fill="#f9f9f9" stroke="none" transform="translate(115,65) translate(-115,-65)" style="animation: 9.017s linear infinite both a0_f;"/>
     <g id="background" fill-rule="nonzero" stroke="none" stroke-width="1" fill="none" transform="translate(10,10)">
         <g id="window">
-            <rect id="Rectangle" stroke="#DDDDDD" fill="#FFFFFF" width="173" height="104" transform="translate(0,0) translate(0.5,0.5)"></rect>
-            <rect id="Rectangle-4" stroke="#DDDDDD" fill="#DDDDDD" width="173" height="12" transform="translate(0,0) translate(0.5,0.5)"></rect>
-            <ellipse id="Oval" fill="#BCBCBC" rx="2.5" ry="2.5" transform="translate(0,0) translate(8.5,6.5)"></ellipse>
-            <ellipse id="Oval-2" fill="#BCBCBC" rx="2.5" ry="2.5" transform="translate(0,0) translate(15.5,6.5)"></ellipse>
-            <ellipse id="Oval-3" fill="#BCBCBC" rx="2.5" ry="2.5" transform="translate(0,0) translate(22.5,6.5)"></ellipse>
+            <rect id="Rectangle" stroke="#DDDDDD" fill="#FFFFFF" width="173" height="104" transform="translate(0,0) translate(0.5,0.5)"/>
+            <rect id="Rectangle-4" stroke="#DDDDDD" fill="#DDDDDD" width="173" height="12" transform="translate(0,0) translate(0.5,0.5)"/>
+            <ellipse id="Oval" fill="#BCBCBC" rx="2.5" ry="2.5" transform="translate(0,0) translate(8.5,6.5)"/>
+            <ellipse id="Oval-2" fill="#BCBCBC" rx="2.5" ry="2.5" transform="translate(0,0) translate(15.5,6.5)"/>
+            <ellipse id="Oval-3" fill="#BCBCBC" rx="2.5" ry="2.5" transform="translate(0,0) translate(22.5,6.5)"/>
         </g>
         <g id="editor" stroke="#DDDDDD" transform="translate(13,26)">
-            <rect id="Rectangle-5" fill="#FFFFFF" width="107" height="64" transform="translate(0,0) translate(0.5,0.5)"></rect>
-            <rect id="Rectangle-6" fill="#FFFFFF" width="107" height="54" transform="translate(0,0) translate(0.5,10.5)"></rect>
-            <path id="Line" d="M7,6L11,6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
-            <path id="Line-2" d="M15,6L19,6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
-            <path id="Line-3" d="M23,6L27,6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
-            <path id="Line-4" d="M31,6L35,6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
-            <path id="Line-5" d="M7,19L100,19" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
-            <path id="Line-6" d="M7,24L88,24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
+            <rect id="Rectangle-5" fill="#FFFFFF" width="107" height="64" transform="translate(0,0) translate(0.5,0.5)"/>
+            <rect id="Rectangle-6" fill="#FFFFFF" width="107" height="54" transform="translate(0,0) translate(0.5,10.5)"/>
+            <path id="Line" d="M7,6L11,6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+            <path id="Line-2" d="M15,6L19,6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+            <path id="Line-3" d="M23,6L27,6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+            <path id="Line-4" d="M31,6L35,6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+            <path id="Line-5" d="M7,19L100,19" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+            <path id="Line-6" d="M7,24L88,24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
         </g>
-        <rect id="info" fill="#DDDDDD" width="39" height="65" transform="translate(0,0) translate(126,26)"></rect>
+        <rect id="info" fill="#DDDDDD" width="39" height="65" transform="translate(0,0) translate(126,26)"/>
     </g>
-    <g id="info-text" fill-rule="nonzero" stroke="#BCBCBC" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" fill="none" opacity="1" transform="translate(141.5,42)" style="animation: info-text_t 9.017s linear infinite both, info-text_o 9.017s linear infinite both;">
-        <path id="Line-7" d="M0,1L28,1"></path>
-        <path id="Line-8" d="M0,6L28,6"></path>
-        <path id="Line-9" d="M0,11L28,11"></path>
-        <path id="Line-10" d="M0,16L23,16"></path>
+    <g id="info-text" fill-rule="nonzero" stroke="#BCBCBC" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" fill="none" opacity="1" transform="translate(141.5,42)" style="animation: 9.017s linear infinite both info-text_t, 9.017s linear infinite both info-text_o;">
+        <path id="Line-7" d="M0,1L28,1"/>
+        <path id="Line-8" d="M0,6L28,6"/>
+        <path id="Line-9" d="M0,11L28,11"/>
+        <path id="Line-10" d="M0,16L23,16"/>
     </g>
     <g id="photo-file-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" transform="translate(200,80)">
-        <path id="file_1_txt" d="M0,22.25L19,22.25" stroke="#DDDDDD" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill-rule="nonzero"></path>
+        <path id="file_1_txt" d="M0,22.25L19,22.25" stroke="#DDDDDD" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill-rule="nonzero"/>
         <g id="file_1" transform="translate(1,0)">
-            <path id="Rectangle-2" fill="#FFFFFF" fill-rule="nonzero" d="M1,1L10.8838,1L15,4.9668L15,17L1,17Z"></path>
-            <path id="Shape" d="M15.4995,4.136L11.9877,0.728C11.7672,0.380865,11.3808,0.16687,10.9621,0.16L1.52615,0.16C0.846419,0.16,0.295385,0.697258,0.295385,1.36L0.295385,16.56C0.295385,17.2227,0.846419,17.76,1.52615,17.76L14.6544,17.76C14.9934,17.8075,15.3356,17.6965,15.5778,17.4603C15.82,17.2242,15.9338,16.8906,15.8851,16.56L15.8851,5.36C15.9168,4.91948,15.7793,4.48323,15.4995,4.136L15.4995,4.136ZM12.1928,2.608L13.7846,4.16L12.1928,4.16L12.1928,2.608ZM10.921,1.36L10.921,1.384L10.9456,1.408L10.9456,5.36L14.6379,5.36L14.6379,9.64L12.48,8.44C12.2284,8.29984,11.9106,8.34982,11.7169,8.56L8.29949,12.24L5.42769,11.2C5.20212,11.1193,4.94899,11.1739,4.77949,11.34L1.52205,14.516L1.52205,1.36L10.921,1.36ZM1.52615,16.56L1.52615,16.2L1.55077,16.2L5.37436,12.472L8.28308,13.52C8.51859,13.6046,8.78321,13.5413,8.95179,13.36L12.3159,9.736L14.6544,11.04L14.6544,16.56L1.52615,16.56Z" fill="#BCBCBC"></path>
-            <path id="Shape-2" d="M5.22256,8.56C6.35546,8.56,7.27385,7.66457,7.27385,6.56C7.27385,5.45543,6.35546,4.56,5.22256,4.56C4.08967,4.56,3.17128,5.45543,3.17128,6.56C3.17128,7.66457,4.08967,8.56,5.22256,8.56L5.22256,8.56ZM5.22256,5.76C5.67572,5.76,6.04308,6.11817,6.04308,6.56C6.04308,7.00183,5.67572,7.36,5.22256,7.36C4.76941,7.36,4.40205,7.00183,4.40205,6.56C4.40205,6.11817,4.76941,5.76,5.22256,5.76Z" fill="#BCBCBC"></path>
+            <path id="Rectangle-2" fill="#FFFFFF" fill-rule="nonzero" d="M1,1L10.8838,1L15,4.9668L15,17L1,17Z"/>
+            <path id="Shape" d="M15.4995,4.136L11.9877,0.728C11.7672,0.380865,11.3808,0.16687,10.9621,0.16L1.52615,0.16C0.846419,0.16,0.295385,0.697258,0.295385,1.36L0.295385,16.56C0.295385,17.2227,0.846419,17.76,1.52615,17.76L14.6544,17.76C14.9934,17.8075,15.3356,17.6965,15.5778,17.4603C15.82,17.2242,15.9338,16.8906,15.8851,16.56L15.8851,5.36C15.9168,4.91948,15.7793,4.48323,15.4995,4.136L15.4995,4.136ZM12.1928,2.608L13.7846,4.16L12.1928,4.16L12.1928,2.608ZM10.921,1.36L10.921,1.384L10.9456,1.408L10.9456,5.36L14.6379,5.36L14.6379,9.64L12.48,8.44C12.2284,8.29984,11.9106,8.34982,11.7169,8.56L8.29949,12.24L5.42769,11.2C5.20212,11.1193,4.94899,11.1739,4.77949,11.34L1.52205,14.516L1.52205,1.36L10.921,1.36ZM1.52615,16.56L1.52615,16.2L1.55077,16.2L5.37436,12.472L8.28308,13.52C8.51859,13.6046,8.78321,13.5413,8.95179,13.36L12.3159,9.736L14.6544,11.04L14.6544,16.56L1.52615,16.56Z" fill="#BCBCBC"/>
+            <path id="Shape-2" d="M5.22256,8.56C6.35546,8.56,7.27385,7.66457,7.27385,6.56C7.27385,5.45543,6.35546,4.56,5.22256,4.56C4.08967,4.56,3.17128,5.45543,3.17128,6.56C3.17128,7.66457,4.08967,8.56,5.22256,8.56L5.22256,8.56ZM5.22256,5.76C5.67572,5.76,6.04308,6.11817,6.04308,6.56C6.04308,7.00183,5.67572,7.36,5.22256,7.36C4.76941,7.36,4.40205,7.00183,4.40205,6.56C4.40205,6.11817,4.76941,5.76,5.22256,5.76Z" fill="#BCBCBC"/>
         </g>
     </g>
-    <g id="photo-file" style="animation: a1_t 9.017s linear infinite both ,photo-file_mo 9.017s linear infinite both; offset-path: path('M209.5,61.205L209.5,61.205C165.469,60.6972,122.131,67.7722,79.4551,82.0677L79.4551,82.0677'); offset-rotate: 0deg;">
-        <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="1" transform=" translate(-9.5,-11.205)" style="animation: photo-file_t 9.017s linear infinite both, photo-file_o 9.017s linear infinite both;">
-            <path id="file_1_txt-2" d="M0,22.25L19,22.25" stroke="#DDDDDD" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill-rule="nonzero" opacity="1" style="animation: file_1_txt-2_o 9.017s linear infinite both;"></path>
-            <g id="file_1-2" transform="translate(1,0)">
-                <path id="Rectangle-2-2" fill="#FFFFFF" fill-rule="nonzero" d="M1,1L10.8838,1L15,4.9668L15,17L1,17Z"></path>
-                <path id="Shape-3" d="M15.4995,4.136L11.9877,0.728C11.7672,0.380865,11.3808,0.16687,10.9621,0.16L1.52615,0.16C0.846419,0.16,0.295385,0.697258,0.295385,1.36L0.295385,16.56C0.295385,17.2227,0.846419,17.76,1.52615,17.76L14.6544,17.76C14.9934,17.8075,15.3356,17.6965,15.5778,17.4603C15.82,17.2242,15.9338,16.8906,15.8851,16.56L15.8851,5.36C15.9168,4.91948,15.7793,4.48323,15.4995,4.136L15.4995,4.136ZM12.1928,2.608L13.7846,4.16L12.1928,4.16L12.1928,2.608ZM10.921,1.36L10.921,1.384L10.9456,1.408L10.9456,5.36L14.6379,5.36L14.6379,9.64L12.48,8.44C12.2284,8.29984,11.9106,8.34982,11.7169,8.56L8.29949,12.24L5.42769,11.2C5.20212,11.1193,4.94899,11.1739,4.77949,11.34L1.52205,14.516L1.52205,1.36L10.921,1.36ZM1.52615,16.56L1.52615,16.2L1.55077,16.2L5.37436,12.472L8.28308,13.52C8.51859,13.6046,8.78321,13.5413,8.95179,13.36L12.3159,9.736L14.6544,11.04L14.6544,16.56L1.52615,16.56Z" fill="#BCBCBC"></path>
-                <path id="Shape-4" d="M5.22256,8.56C6.35546,8.56,7.27385,7.66457,7.27385,6.56C7.27385,5.45543,6.35546,4.56,5.22256,4.56C4.08967,4.56,3.17128,5.45543,3.17128,6.56C3.17128,7.66457,4.08967,8.56,5.22256,8.56L5.22256,8.56ZM5.22256,5.76C5.67572,5.76,6.04308,6.11817,6.04308,6.56C6.04308,7.00183,5.67572,7.36,5.22256,7.36C4.76941,7.36,4.40205,7.00183,4.40205,6.56C4.40205,6.11817,4.76941,5.76,5.22256,5.76Z" fill="#BCBCBC"></path>
+    <g id="photo-file" style="animation: 9.017s linear infinite both a1_t;">
+        <g style="animation: 9.017s linear infinite both a2_t;">
+            <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="1" transform="translate(209.5,61.205) translate(-9.5,-11.205)" style="animation: 9.017s linear infinite both photo-file_t, 9.017s linear infinite both photo-file_o;">
+                <path id="file_1_txt-2" d="M0,22.25L19,22.25" stroke="#DDDDDD" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill-rule="nonzero" opacity="1" style="animation: 9.017s linear infinite both file_1_txt-2_o;"/>
+                <g id="file_1-2" transform="translate(1,0)">
+                    <path id="Rectangle-2-2" fill="#FFFFFF" fill-rule="nonzero" d="M1,1L10.8838,1L15,4.9668L15,17L1,17Z"/>
+                    <path id="Shape-3" d="M15.4995,4.136L11.9877,0.728C11.7672,0.380865,11.3808,0.16687,10.9621,0.16L1.52615,0.16C0.846419,0.16,0.295385,0.697258,0.295385,1.36L0.295385,16.56C0.295385,17.2227,0.846419,17.76,1.52615,17.76L14.6544,17.76C14.9934,17.8075,15.3356,17.6965,15.5778,17.4603C15.82,17.2242,15.9338,16.8906,15.8851,16.56L15.8851,5.36C15.9168,4.91948,15.7793,4.48323,15.4995,4.136L15.4995,4.136ZM12.1928,2.608L13.7846,4.16L12.1928,4.16L12.1928,2.608ZM10.921,1.36L10.921,1.384L10.9456,1.408L10.9456,5.36L14.6379,5.36L14.6379,9.64L12.48,8.44C12.2284,8.29984,11.9106,8.34982,11.7169,8.56L8.29949,12.24L5.42769,11.2C5.20212,11.1193,4.94899,11.1739,4.77949,11.34L1.52205,14.516L1.52205,1.36L10.921,1.36ZM1.52615,16.56L1.52615,16.2L1.55077,16.2L5.37436,12.472L8.28308,13.52C8.51859,13.6046,8.78321,13.5413,8.95179,13.36L12.3159,9.736L14.6544,11.04L14.6544,16.56L1.52615,16.56Z" fill="#BCBCBC"/>
+                    <path id="Shape-4" d="M5.22256,8.56C6.35546,8.56,7.27385,7.66457,7.27385,6.56C7.27385,5.45543,6.35546,4.56,5.22256,4.56C4.08967,4.56,3.17128,5.45543,3.17128,6.56C3.17128,7.66457,4.08967,8.56,5.22256,8.56L5.22256,8.56ZM5.22256,5.76C5.67572,5.76,6.04308,6.11817,6.04308,6.56C6.04308,7.00183,5.67572,7.36,5.22256,7.36C4.76941,7.36,4.40205,7.00183,4.40205,6.56C4.40205,6.11817,4.76941,5.76,5.22256,5.76Z" fill="#BCBCBC"/>
+                </g>
             </g>
         </g>
     </g>
-    <g id="cursor" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0" style="animation: cursor_o 9.017s linear infinite both, cursor_mo 9.017s linear infinite both; offset-path: path('M205,18L213,63C169,63.3804,125.652,70.3368,82.9551,83.8627L82.9551,83.8627'); offset-rotate: 0deg;">
+    <g id="cursor" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0" transform="translate(205,18)" style="animation: 9.017s linear infinite both cursor_t, 9.017s linear infinite both cursor_o;">
         <g id="Rectangle-237">
-            <path stroke-opacity="0.8" stroke="#FFFFFF" stroke-width="0.7" d="M5.99271,6.24868L8.17168,9.12618L6.60572,10.3885L4.43083,7.51638L2.3803,10.7049L-0.543274,-0.710555L9.47456,4.97196L5.99271,6.24868Z" fill="#363B3E" fill-rule="evenodd"></path>
+            <path stroke-opacity="0.8" stroke="#FFFFFF" stroke-width="0.7" d="M5.99271,6.24868L8.17168,9.12618L6.60572,10.3885L4.43083,7.51638L2.3803,10.7049L-0.543274,-0.710555L9.47456,4.97196L5.99271,6.24868Z" fill="#363B3E" fill-rule="evenodd"/>
         </g>
     </g>
-    <g id="photo-main" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0" transform="translate(28,66)" style="animation: photo-main_o 9.017s linear infinite both;">
-        <rect id="Rectangle-7" fill="#EAEAEA" width="98" height="30"></rect>
+    <g id="photo-main" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0" transform="translate(28,66)" style="animation: 9.017s linear infinite both photo-main_o;">
+        <rect id="Rectangle-7" fill="#EAEAEA" width="98" height="30"/>
         <g id="icon---image" fill="#AEAEAE" transform="translate(40,10)">
-            <path id="Shape-5" d="M17.8008,10.1437L13.2697,5.16338C13.1203,5.00563,12.8963,4.89296,12.6224,4.89296C12.3734,4.89296,12.1245,5.00563,11.9751,5.16338L11.3776,5.8169C11.3527,5.83944,11.3278,5.88451,11.3029,5.90704C11.1535,6.04225,10.9544,6.13239,10.7303,6.13239C10.5062,6.13239,10.332,6.06479,10.1826,5.92958C10.1328,5.88451,10.083,5.83944,10.0581,5.79437L7.84232,3.36056C7.64315,3.13521,7.3444,3,6.99585,3C6.6473,3,6.34855,3.13521,6.14938,3.36056L0.224066,9.87324L0.124481,9.98592C0.0497925,10.0986,0,10.2113,0,10.3465C0,10.707,0.323651,11,0.721992,11L0.721992,11L2.39004,11L2.93776,11L3.06224,11L7.86722,11L9.13693,11L9.56017,11L9.65975,11L13.2697,11L15.6598,11L15.7593,11L16.5311,11L17.4523,11C17.751,11,18,10.7746,18,10.5042C18,10.3915,17.9751,10.3014,17.9004,10.2113L17.8008,10.1437L17.8008,10.1437Z"></path>
-            <path id="Shape-6" d="M17,0C15.9041,0,15,0.90411,15,2C15,3.09589,15.9041,4,17,4C18.0959,4,19,3.09589,19,2C19,0.90411,18.0959,0,17,0L17,0Z"></path>
+            <path id="Shape-5" d="M17.8008,10.1437L13.2697,5.16338C13.1203,5.00563,12.8963,4.89296,12.6224,4.89296C12.3734,4.89296,12.1245,5.00563,11.9751,5.16338L11.3776,5.8169C11.3527,5.83944,11.3278,5.88451,11.3029,5.90704C11.1535,6.04225,10.9544,6.13239,10.7303,6.13239C10.5062,6.13239,10.332,6.06479,10.1826,5.92958C10.1328,5.88451,10.083,5.83944,10.0581,5.79437L7.84232,3.36056C7.64315,3.13521,7.3444,3,6.99585,3C6.6473,3,6.34855,3.13521,6.14938,3.36056L0.224066,9.87324L0.124481,9.98592C0.0497925,10.0986,0,10.2113,0,10.3465C0,10.707,0.323651,11,0.721992,11L0.721992,11L2.39004,11L2.93776,11L3.06224,11L7.86722,11L9.13693,11L9.56017,11L9.65975,11L13.2697,11L15.6598,11L15.7593,11L16.5311,11L17.4523,11C17.751,11,18,10.7746,18,10.5042C18,10.3915,17.9751,10.3014,17.9004,10.2113L17.8008,10.1437L17.8008,10.1437Z"/>
+            <path id="Shape-6" d="M17,0C15.9041,0,15,0.90411,15,2C15,3.09589,15.9041,4,17,4C18.0959,4,19,3.09589,19,2C19,0.90411,18.0959,0,17,0L17,0Z"/>
         </g>
     </g>
-    <g id="photo-box" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0" transform="translate(139,44)" style="animation: photo-box_t 9.017s linear infinite both, photo-box_o 9.017s linear infinite both;">
-        <rect id="Rectangle-8" fill="#BCBCBC" width="33" height="20"></rect>
+    <g id="photo-box" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" opacity="0" transform="translate(139,44)" style="animation: 9.017s linear infinite both photo-box_t, 9.017s linear infinite both photo-box_o;">
+        <rect id="Rectangle-8" fill="#BCBCBC" width="33" height="20"/>
         <g id="icon---image-2" fill="#979494" transform="translate(12,7)">
-            <path id="Shape-7" d="M8.90041,5.57183L6.63485,3.08169C6.56017,3.00282,6.44813,2.94648,6.3112,2.94648C6.18672,2.94648,6.06224,3.00282,5.98755,3.08169L5.6888,3.40845C5.67635,3.41972,5.6639,3.44225,5.65145,3.45352C5.57676,3.52113,5.47718,3.5662,5.36515,3.5662C5.25311,3.5662,5.16598,3.53239,5.09129,3.46479C5.06639,3.44225,5.04149,3.41972,5.02905,3.39718L3.92116,2.18028C3.82158,2.06761,3.6722,2,3.49793,2C3.32365,2,3.17427,2.06761,3.07469,2.18028L0.112033,5.43662L0.0622407,5.49296C0.0248963,5.5493,0,5.60563,0,5.67324C0,5.85352,0.161826,6,0.360996,6L0.360996,6L1.19502,6L1.46888,6L1.53112,6L3.93361,6L4.56846,6L4.78008,6L4.82988,6L6.63485,6L7.82988,6L7.87967,6L8.26556,6L8.72614,6C8.87552,6,9,5.88732,9,5.75211C9,5.69577,8.98755,5.6507,8.95021,5.60563L8.90041,5.57183L8.90041,5.57183Z"></path>
-            <path id="Shape-8" d="M9,0C8.45205,0,8,0.452055,8,1C8,1.54795,8.45205,2,9,2C9.54795,2,10,1.54795,10,1C10,0.452055,9.54795,0,9,0L9,0Z"></path>
+            <path id="Shape-7" d="M8.90041,5.57183L6.63485,3.08169C6.56017,3.00282,6.44813,2.94648,6.3112,2.94648C6.18672,2.94648,6.06224,3.00282,5.98755,3.08169L5.6888,3.40845C5.67635,3.41972,5.6639,3.44225,5.65145,3.45352C5.57676,3.52113,5.47718,3.5662,5.36515,3.5662C5.25311,3.5662,5.16598,3.53239,5.09129,3.46479C5.06639,3.44225,5.04149,3.41972,5.02905,3.39718L3.92116,2.18028C3.82158,2.06761,3.6722,2,3.49793,2C3.32365,2,3.17427,2.06761,3.07469,2.18028L0.112033,5.43662L0.0622407,5.49296C0.0248963,5.5493,0,5.60563,0,5.67324C0,5.85352,0.161826,6,0.360996,6L0.360996,6L1.19502,6L1.46888,6L1.53112,6L3.93361,6L4.56846,6L4.78008,6L4.82988,6L6.63485,6L7.82988,6L7.87967,6L8.26556,6L8.72614,6C8.87552,6,9,5.88732,9,5.75211C9,5.69577,8.98755,5.6507,8.95021,5.60563L8.90041,5.57183L8.90041,5.57183Z"/>
+            <path id="Shape-8" d="M9,0C8.45205,0,8,0.452055,8,1C8,1.54795,8.45205,2,9,2C9.54795,2,10,1.54795,10,1C10,0.452055,9.54795,0,9,0L9,0Z"/>
         </g>
     </g>
-    <g id="info-1" fill-rule="nonzero" stroke="none" stroke-width="1" fill="none" opacity="0" transform="translate(140,68)" style="animation: info-1_t 9.017s linear infinite both, info-1_o 9.017s linear infinite both;">
-        <path id="Line-11" d="M10,3L29,3" stroke="#BCBCBC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
-        <path id="Line-12" d="M10,6L25,6" stroke="#BCBCBC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
-        <rect id="Rectangle-3" fill="#BCBCBC" width="7" height="9" rx="2"></rect>
+    <g id="info-1" fill-rule="nonzero" stroke="none" stroke-width="1" fill="none" opacity="0" transform="translate(140,68)" style="animation: 9.017s linear infinite both info-1_t, 9.017s linear infinite both info-1_o;">
+        <path id="Line-11" d="M10,3L29,3" stroke="#BCBCBC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+        <path id="Line-12" d="M10,6L25,6" stroke="#BCBCBC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+        <rect id="Rectangle-3" fill="#BCBCBC" width="7" height="9" rx="2"/>
     </g>
-    <g id="info-2" fill-rule="nonzero" stroke="none" stroke-width="1" fill="none" opacity="0" transform="translate(140,79)" style="animation: info-2_t 9.017s linear infinite both, info-2_o 9.017s linear infinite both;">
-        <path id="Line-13" d="M10,3L29,3" stroke="#BCBCBC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
-        <path id="Line-14" d="M10,6L25,6" stroke="#BCBCBC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
-        <rect id="Rectangle-3-2" fill="#BCBCBC" width="7" height="9" rx="2"></rect>
+    <g id="info-2" fill-rule="nonzero" stroke="none" stroke-width="1" fill="none" opacity="0" transform="translate(140,79)" style="animation: 9.017s linear infinite both info-2_t, 9.017s linear infinite both info-2_o;">
+        <path id="Line-13" d="M10,3L29,3" stroke="#BCBCBC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+        <path id="Line-14" d="M10,6L25,6" stroke="#BCBCBC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+        <rect id="Rectangle-3-2" fill="#BCBCBC" width="7" height="9" rx="2"/>
     </g>
-    <g id="info-3" fill-rule="nonzero" stroke="none" stroke-width="1" fill="none" opacity="0" transform="translate(140,90)" style="animation: info-3_t 9.017s linear infinite both, info-3_o 9.017s linear infinite both;">
-        <path id="Line-15" d="M10,3L29,3" stroke="#BCBCBC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
-        <path id="Line-16" d="M10,6L25,6" stroke="#BCBCBC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
-        <rect id="Rectangle-3-3" fill="#BCBCBC" width="7" height="9" rx="2"></rect>
+    <g id="info-3" fill-rule="nonzero" stroke="none" stroke-width="1" fill="none" opacity="0" transform="translate(140,90)" style="animation: 9.017s linear infinite both info-3_t, 9.017s linear infinite both info-3_o;">
+        <path id="Line-15" d="M10,3L29,3" stroke="#BCBCBC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+        <path id="Line-16" d="M10,6L25,6" stroke="#BCBCBC" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
+        <rect id="Rectangle-3-3" fill="#BCBCBC" width="7" height="9" rx="2"/>
     </g>
-    <rect id="loading" fill="#17C119" fill-rule="nonzero" width="2" height="2" stroke="none" stroke-width="1" opacity="0" transform="translate(77,67) translate(-49,-1)" style="animation: loading_t 9.017s linear infinite both, loading_o 9.017s linear infinite both, loading_w 9.017s linear infinite both;"></rect>
-    <g id="check" fill-rule="nonzero" stroke="none" stroke-width="1" fill="none" opacity="0" transform="translate(125,67) scale(2,2) translate(-3,-3)" style="animation: check_t 9.017s linear infinite both, check_o 9.017s linear infinite both;">
-        <path id="checked" d="M3,0C1.34584,0,0,1.34584,0,3C0,4.65416,1.34584,6,3,6C4.65416,6,6,4.65416,6,3C6,1.34584,4.65416,0,3,0Z" fill="#17C119"></path>
-        <path id="Shape-9" d="M4.13277,2.05687L2.4742,3.53397L1.86899,2.98894C1.7843,2.91311,1.64844,2.91311,1.56374,2.98736C1.47905,3.06161,1.47905,3.18483,1.56198,3.26066L2.31892,3.94313C2.36127,3.98104,2.41597,4,2.47243,4C2.52713,4,2.58183,3.98104,2.62417,3.94313L4.43626,2.33017C4.52095,2.25434,4.52095,2.1327,4.43802,2.05687C4.35333,1.98104,4.21747,1.98104,4.13277,2.05687Z" stroke="#FFFFFF" stroke-width="0.2" fill="#FFFFFF"></path>
+    <rect id="loading" fill="#17C119" fill-rule="nonzero" width="2" height="2" stroke="none" stroke-width="1" opacity="0" transform="translate(28,66) scale(1.09,1.09)" style="animation: 9.017s linear infinite both loading_t, 9.017s linear infinite both loading_o;"/>
+    <g id="check" fill-rule="nonzero" stroke="none" stroke-width="1" fill="none" opacity="0" transform="translate(125,67) scale(2,2) translate(-3,-3)" style="animation: 9.017s linear infinite both check_t, 9.017s linear infinite both check_o;">
+        <path id="checked" d="M3,0C1.34584,0,0,1.34584,0,3C0,4.65416,1.34584,6,3,6C4.65416,6,6,4.65416,6,3C6,1.34584,4.65416,0,3,0Z" fill="#17C119"/>
+        <path id="Shape-9" d="M4.13277,2.05687L2.4742,3.53397L1.86899,2.98894C1.7843,2.91311,1.64844,2.91311,1.56374,2.98736C1.47905,3.06161,1.47905,3.18483,1.56198,3.26066L2.31892,3.94313C2.36127,3.98104,2.41597,4,2.47243,4C2.52713,4,2.58183,3.98104,2.62417,3.94313L4.43626,2.33017C4.52095,2.25434,4.52095,2.1327,4.43802,2.05687C4.35333,1.98104,4.21747,1.98104,4.13277,2.05687Z" stroke="#FFFFFF" stroke-width="0.2" fill="#FFFFFF"/>
     </g>
 </svg>

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


二進制
docs/assets/img/tutorial-implementing-a-widget-1.png


二進制
docs/assets/img/tutorial-implementing-a-widget-2.png


二進制
docs/assets/img/tutorial-implementing-a-widget-3.png


二進制
docs/assets/img/tutorial-implementing-a-widget-4.png


二進制
docs/assets/img/tutorial-implementing-a-widget-4b.png


二進制
docs/assets/img/tutorial-implementing-a-widget-5.png


二進制
docs/assets/img/tutorial-implementing-a-widget-6.png


二進制
docs/assets/img/tutorial-implementing-a-widget-7.png


二進制
docs/assets/img/tutorial-implementing-an-inline-widget-1.png


二進制
docs/assets/img/tutorial-implementing-an-inline-widget-2.png


+ 6 - 2
docs/assets/snippet-styles.css

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  */
 
 :root {
@@ -128,7 +128,11 @@ It breaks CKEditor 5 (see how <p><code>[]</code></p> looks). */
 	padding: 2rem;
 }
 
-#snippet-inline-editor .ck-content:not(.ck-focused) {
+/* Visually separate inline editors from example description.
+https://github.com/ckeditor/ckeditor5/issues/1545 */
+#snippet-inline-editor .ck-content:not(.ck-focused),
+#snippet-balloon-editor.ck-content:not(.ck-focused),
+#snippet-balloon-block-editor.ck-content:not(.ck-focused) {
 	border: 1px solid rgba(0, 0, 0, 0.15);
 }
 

+ 2 - 2
docs/assets/snippet.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* globals window, document */

+ 1 - 1
docs/assets/styles.css

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
  */
 
 /* https://github.com/ckeditor/ckeditor5/issues/913 */

+ 48 - 0
docs/builds/guides/faq.md

@@ -111,3 +111,51 @@ plugins: [
 You can also use the relative path which is resolved relative to the resource that imports `bold.svg` (the {@link module:basic-styles/bold/boldui~BoldUI `BoldUI`} class file in this scenario).
 
 Learn more about {@link builds/guides/integration/advanced-setup#webpack-configuration building CKEditor 5 using webpack}.
+
+## How to get the editor instance object from the DOM element?
+
+If you have a reference to the editor editable's DOM element (that's the one with the `.ck-editor__editable` class and the `contenteditable` attribute), you can access the editor instance this editable element belongs to using the `ckeditorInstance` property:
+
+```html
+<!-- The editable element in the editor's DOM structure. -->
+<div class="... ck-editor__editable ..." contenteditable="true">
+	<!-- Editable content. -->
+</div>
+```
+
+```js
+// A reference to the editor editable element in DOM.
+const domEditableElement = document.querySelector( '.ck-editor__editable' );
+
+// Get the editor instance from the editable element.
+const editorInstance = domEditableElement.ckeditorInstance;
+
+// Use the editor instance API.
+editorInstance.setData( '<p>Hello world!<p>' );
+```
+
+## How to add an attribute to the editor editable in DOM?
+
+If you have a reference to the editor instance, simply use the {@link framework/guides/architecture/editing-engine#changing-the-view `change()`} method of the view and set the new attribute via the {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer}:
+
+```js
+editor.editing.view.change( writer => {
+	const viewEditableRoot = editor.editing.view.document.getRoot();
+
+	writer.setAttribute( 'myAttribute', 'value', viewEditableRoot );
+} );
+```
+
+If you do not have the reference to the editor instance but you have access to the editable element in DOM, you can [access it using the `ckeditorInstance` property](#how-to-get-the-editor-instance-object-from-the-dom-element) and then use the same API to set the attribute:
+
+```js
+const domEditableElement = document.querySelector( '.ck-editor__editable' );
+const editorInstance = domEditableElement.ckeditorInstance;
+
+editorInstance.editing.view.change( writer => {
+	// Map the editable element in DOM to the editable element in editor's view.
+	const viewEditableRoot = editorInstance.editing.view.domConverter.mapDomToView( domEditableElement );
+
+	writer.setAttribute( 'myAttribute', 'value', viewEditableRoot );
+} );
+```

+ 50 - 36
docs/builds/guides/frameworks/angular.md

@@ -1,10 +1,10 @@
 ---
-menu-title: Angular 2+ component
+menu-title: Angular component
 category: builds-integration-frameworks
 order: 20
 ---
 
-# Rich text editor component for Angular 2+
+# Rich text editor component for Angular
 
 [![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-angular.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-angular)
 
@@ -18,7 +18,7 @@ Currently, the CKEditor 5 component for Angular supports integrating CKEditor 5
 
 ## Quick start
 
-In your existing Angular project, install the [CKEditor 5 WYSIWYG editor component for Angular 2+](https://www.npmjs.com/package/@ckeditor/ckeditor5-angular):
+In your existing Angular project, install the [CKEditor 5 WYSIWYG editor component for Angular](https://www.npmjs.com/package/@ckeditor/ckeditor5-angular):
 
 ```bash
 npm install --save @ckeditor/ckeditor5-angular
@@ -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;
-	...
+	// ...
 }
 ```
 
@@ -71,21 +70,20 @@ Rebuild your application and CKEditor 5 should greet you with "Hello, world!".
 
 ### Note: Using the Document editor build
 
-If you want to use the {@link framework/guides/document-editor Document editor build}, you need to {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create add the toolbar to the DOM manually}.
+If you want to use the {@link framework/guides/document-editor document editor build}, you need to {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create add the toolbar to the DOM manually}.
 
 ```ts
 import * as DecoupledEditor from '@ckeditor/ckeditor5-build-decoupled-document';
 
 @Component( {
-	...
-} )
+	// ...
 export class MyComponent {
 	public Editor = DecoupledEditor;
 
 	public onReady( editor ) {
-		editor.ui.view.editable.element.parentElement.insertBefore(
+		editor.ui.getEditableElement().parentElement.insertBefore(
 			editor.ui.view.toolbar.element,
-			editor.ui.view.editable.element
+			editor.ui.getEditableElement()
 		);
 	}
 }
@@ -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>'
 		};
-		...
+		// ...
 	}
 	```
 
@@ -123,7 +121,7 @@ The component implements the [`ControlValueAccessor`](https://angular.io/api/for
 
 ## Supported `@Input` properties
 
-The following `@Input` properties are supported by the CKEditor 5 component for Angular 2+:
+The following `@Input` properties are supported by the CKEditor 5 rich text editor component for Angular:
 
 ### `editor` (required)
 
@@ -153,11 +151,11 @@ or a shared parent component's property
 
 ```ts
 @Component( {
-	...
+	// ...
 } )
 export class MyComponent {
 	public editorData = '<p>Hello, world!</p>';
-	...
+	// ...
 }
 ```
 
@@ -167,7 +165,7 @@ export class MyComponent {
 
 ### `tagName`
 
-Specifies the tag name of the HTML element on which the editor will be created.
+The tag name of the HTML element on which the rich text editor will be created.
 
 The default tag is `<div>`.
 
@@ -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
 	}
@@ -202,16 +200,17 @@ export class MyComponent {
 
 ## Supported `@Output` properties
 
-The following `@Output` properties are supported by the CKEditor 5 component for Angular 2+:
+The following `@Output` properties are supported by the CKEditor 5 rich text editor component for Angular:
 
 ### `ready`
 
-Fired when the editor is ready. It corresponds with the [`editor#ready`](https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html#event-ready) event. Fired with the editor instance.
+Fired when the editor is ready. It corresponds with the [`editor#ready`](https://ckeditor.com/docs/ckeditor5/latest/api/module_core_editor_editor-Editor.html#event-ready) event.
+It is fired with the editor instance.
 
 ### `change`
 
 Fired when the content of the editor has changed. It corresponds with the {@link module:engine/model/document~Document#event:change:data `editor.model.document#change:data`} event.
-Fired with an object containing the editor and the CKEditor 5 `change:data` event object.
+It is fired with an object containing the editor and the CKEditor 5 `change:data` event object.
 
 ```html
 <ckeditor [editor]="Editor" (change)="onChange($event)"></ckeditor>
@@ -222,7 +221,7 @@ import * as ClassicEditor from '@ckeditor/ckeditor5-build-classic';
 import { ChangeEvent } from '@ckeditor/ckeditor5-angular/ckeditor.component';
 
 @Component( {
-	...
+	// ...
 } )
 export class MyComponent {
 	public Editor = ClassicEditor;
@@ -239,30 +238,30 @@ export class MyComponent {
 ### `blur`
 
 Fired when the editing view of the editor is blurred. It corresponds with the {@link module:engine/view/document~Document#event:blur `editor.editing.view.document#blur`} event.
-Fired with an object containing the editor and the CKEditor 5 `blur` event data.
+It is fired with an object containing the editor and the CKEditor 5 `blur` event data.
 
 ### `focus`
 
 Fired when the editing view of the editor is focused. It corresponds with the {@link module:engine/view/document~Document#event:focus `editor.editing.view.document#focus`} event.
-Fired with an object containing the editor and the CKEditor 5 `focus` event data.
+It is fired with an object containing the editor and the CKEditor 5 `focus` event data.
 
 ## Styling
 
-The CKEditor 5 component for Angular can be styled using the component stylesheet or using a global stylesheet. Let's see how to set the CKEditor 5 component's height using these two approaches.
+The CKEditor 5 rich text editor component for Angular can be styled using the component stylesheet or using a global stylesheet. See how to set the CKEditor 5 component's height using these two approaches.
 
 ### Setting the height via the component stylesheet
 
-First, create a (S)CSS file in the parent component's directory and style the given editor's part preceded by the `:host` and `::ng-deep` pseudo selectors.
+First, create a (S)CSS file in the parent component's directory and style the given editor's part preceded by the `:host` and `::ng-deep` pseudo selectors:
 
 ```css
 /* src/app/app.component.css */
 
-:host ::ng-deep .ck-editor__editable {
+:host ::ng-deep .ck-editor__editable_inline {
 	min-height: 500px;
 }
 ```
 
-Then in the parent component add the relative path to the above stylesheet.
+Then in the parent component add the relative path to the above stylesheet:
 
 ```ts
 /* src/app/app.component.ts */
@@ -280,12 +279,12 @@ To style the component using a global stylesheet, first, create it:
 ```css
 /* src/styles.css */
 
-.ck-editor__editable {
+.ck-editor__editable_inline {
 	min-height: 500px;
 }
 ```
 
-Then, add it in the `angular.json` configuration file.
+Then, add it in the `angular.json` configuration file:
 
 ```json
 "architect": {
@@ -299,9 +298,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, set the `placeholder` field in the CKEditor 5 rich text editor 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 rich text editor component can be localized in two steps.
 
 ### 1. Loading translation files
 
@@ -333,7 +347,7 @@ Then, you need to configure the editor to use the given language:
 
 ```ts
 @Component( {
-	...
+	// ...
 } )
 export class MyComponent {
 	public Editor = ClassicEditor;
@@ -347,4 +361,4 @@ For advanced usage see the {@link features/ui-language Setting UI language} guid
 
 ## Contributing and reporting issues
 
-The source code of the rich text editor component for Angular 2+ is available on GitHub in https://github.com/ckeditor/ckeditor5-angular.
+The source code of the CKEditor 5 rich text editor component for Angular is available on GitHub in https://github.com/ckeditor/ckeditor5-angular.

+ 61 - 27
docs/builds/guides/frameworks/css.md

@@ -1,30 +1,32 @@
 ---
-menu-title: CSS Frameworks
+menu-title: CSS frameworks
 category: builds-integration-frameworks
 order: 50
 ---
 
-# Compatibility with CSS Frameworks
+# Compatibility with CSS frameworks
 
 CKEditor 5 is compatible with most of the popular CSS frameworks. However, to properly integrate with some of them, additional tweaks may be necessary. This is mostly due to the fact that:
-* CSS frameworks often use higher [CSS Specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) in their style sheets and override default editor styles, distorting the user interface,
-* modal components of various UI frameworks use high `z-index` values in their styles and render over (cover) the UI of CKEditor 5,
-* framework modals use aggressive focus management policy which breaks the input fields in the editor (e.g. link input).
+* CSS frameworks often use a higher [CSS Specificity](https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity) in their style sheets and override default editor styles, distorting the user interface.
+* Modal components of various UI frameworks use high `z-index` values in their styles and render over (cover) the UI of CKEditor 5.
+* Framework modals use aggressive focus management policy which breaks the input fields in the rich-text editor (e.g. the link input).
 
-In this guide, you will learn how to address those integration issues and use the CKEditor 5 WYSIWYG editor with the most popular front–end frameworks.
+In this guide, you will learn how to address these integration issues and use the CKEditor 5 WYSIWYG editor with the most popular front–end frameworks.
 
 ## Compatibility with Bootstrap
 
-We noticed that [Bootstrap](https://getbootstrap.com) modals cover the UI of the editor and break the input fields. Knowing that you will need to take the following steps to get CKEditor 5 working in the Bootstrap environment:
+### Bootstrap modals
 
-* configure the `z-index` of the floating editor UI (e.g. balloons) so it is displayed over the Bootstrap overlay,
-* configure Bootstrap so it stops "stealing" the focus from the rich text editor input fields.
+We noticed that [Bootstrap](https://getbootstrap.com) modals cover the UI of the rich-text editor and break the input fields. Knowing that, you will need to take the following steps to get CKEditor 5 working in the Bootstrap environment:
+
+* Configure the `z-index` of the floating editor UI (e.g. balloons) so it is displayed over the Bootstrap overlay.
+* Configure Bootstrap so it stops "stealing" the focus from the rich-text editor input fields.
 
 To address the first issue, add the following styles to your application:
 
 ```css
 /*
- * Configure the z-index of the editor's UI, so when inside a Bootstrap
+ * Configure the z-index of the editor UI, so when inside a Bootstrap
  * modal, it will be rendered over the modal.
  */
 :root {
@@ -33,7 +35,7 @@ To address the first issue, add the following styles to your application:
 }
 ```
 
-Pass the [`focus: false`](https://getbootstrap.com/docs/4.1/components/modal/#options) option to Bootstrap's `modal()` function to fix the second issue:
+Pass the [`focus: false`](https://getbootstrap.com/docs/4.1/components/modal/#options) option to Bootstrap `modal()` function to fix the second issue:
 
 ```js
 $( '#modal-container' ).modal( {
@@ -41,15 +43,29 @@ $( '#modal-container' ).modal( {
 } );
 ```
 
-[Check out the demo](https://codepen.io/ckeditor/pen/vzvgOe).
+[Check out the demo of CKEditor 5 rich-text editor working correctly with Bootstrap](https://codepen.io/ckeditor/pen/vzvgOe).
+
+### Bootstrap table styles
+
+There is also a known [issue](https://github.com/ckeditor/ckeditor5-table/issues/154) concerning table styles brought by Bootstrap breaking the table (widget) layout during editing. If you do not want any additional space around edited tables when using Bootstrap, add the following styles to your application:
+
+```css
+/*
+ * Override the width of the table set by Bootstrap content styles.
+ * See: https://github.com/ckeditor/ckeditor5-table/issues/154.
+ */
+.ck-content .table {
+	width: auto;
+}
+```
 
 ## Compatibility with Foundation
 
-CKEditor 5 requires some minor adjustments to the `z-index` of the UI to work property with [Foundation](https://foundation.zurb.com/sites.html) (also with [Reveal](https://foundation.zurb.com/sites/docs/reveal.html) modal).
+CKEditor 5 requires some minor adjustments to the `z-index` of the UI to work properly with [Foundation](https://foundation.zurb.com/sites.html) (and with the [Reveal](https://foundation.zurb.com/sites/docs/reveal.html) modal, too).
 
 ```css
 /*
- * Configure the z-index of the editor's UI, so when inside a Reveal modal,
+ * Configure the z-index of the editor UI, so when inside a Reveal modal,
  * it will be rendered over the modal.
  */
 :root {
@@ -58,22 +74,23 @@ CKEditor 5 requires some minor adjustments to the `z-index` of the UI to work pr
 }
 ```
 
-[Check out the demo](https://codepen.io/ckeditor/pen/VqXYQq).
+[Check out the demo of CKEditor 5 rich-text editor working correctly with Foundation](https://codepen.io/ckeditor/pen/VqXYQq).
 
 ## Compatibility with Materialize
 
 If you want to use CKEditor 5 with [Materialize.css](https://materializecss.com/) you will need to take the following steps:
 
-* configure the base `z-index` of the floating editor UI so it is displayed over the Materialize modals,
-* bring back the default `.ck-input` class appearance (because Materialize overrides it with higher specificity),
-* configure modals so they stop "stealing" the focus from the rich text editor input fields.
+* Configure the base `z-index` of the floating editor UI so it is displayed over the Materialize modals.
+* Bring back the default `.ck-input` class appearance (because Materialize overrides it with a higher specificity).
+* Bring back the default `<ul>` and `<li>` appearance (because Materialize overrides it).
+* Configure modals so they stop "stealing" the focus from the rich-text editor input fields.
 
 Use the following CSS to address the issues with the `z-index` and selector specificity:
 
 ```css
 /*
- * Configure the z-index of the editor's UI, so when inside a Materialize modal,
- * it will be rendered over the modal.
+ * Configure the z-index of the editor UI, so when inside a Materialize
+ * modal, it will be rendered over the modal.
  */
 :root {
 	--ck-z-default: 100;
@@ -81,8 +98,8 @@ Use the following CSS to address the issues with the `z-index` and selector spec
 }
 
 /*
- * Bring back the default CKEditor 5 input appearance by overriding high–specificity styles
- * brought by materialize.css.
+ * Bring back the default CKEditor 5 input appearance by overriding
+ * high–specificity styles brought by materialize.css.
  *
  * See: https://github.com/Dogfalo/materialize/blob/v1-dev/sass/components/forms/_input-fields.scss#L10-L40
  */
@@ -107,18 +124,35 @@ Use the following CSS to address the issues with the `z-index` and selector spec
 }
 ```
 
-To change the behavior of the modals and prevent them from "stealing" the focus, use [`dismissible: false`](https://materializecss.com/modals.html#options) option.
+```css
+/*
+ * Bring back the default <ul> and <li> appearance.
+ *
+ * See: https://github.com/Dogfalo/materialize/blob/v1-dev/sass/components/_global.scss#L28-L37
+ */
+.ck.ck-content ul,
+.ck.ck-content ul li {
+  list-style-type: inherit;
+}
+
+.ck.ck-content ul {
+  /* Default user agent stylesheet, you can change it to your needs. */
+  padding-left: 40px;
+}
+```
+
+To change the behavior of the modals and prevent them from "stealing" the focus, use the [`dismissible: false`](https://materializecss.com/modals.html#options) option.
 
 ```js
 M.Modal.init( modal, { dismissible: false } );
 
-// Or "jQuery way".
+// Or "jQuery way":
 $( '#modal-container' ).modal( {
 	dismissible: false
 } );
 ```
 
-[Check out the demo](https://codepen.io/ckeditor/pen/gZebwy).
+[Check out the demo of CKEditor 5 rich-text editor working correctly with Materialize.css](https://codepen.io/ckeditor/pen/gZebwy).
 
 ## Compatibility with Semantic-UI
 
@@ -126,7 +160,7 @@ CKEditor 5 works properly with [Semantic-UI](https://semantic-ui.com/) after a s
 
 ```css
 /*
- * Configure the z-index of the editor's UI, so when inside a Semantic-UI modal,
+ * Configure the z-index of the editor UI, so when inside a Semantic-UI modal,
  * it will be rendered over the modal.
  */
 :root {
@@ -135,4 +169,4 @@ CKEditor 5 works properly with [Semantic-UI](https://semantic-ui.com/) after a s
 }
 ```
 
-[Check out the demo](https://codepen.io/ckeditor/pen/OrZBpV).
+[Check out the demo of CKEditor 5 rich-text editor working correctly with Semantic-UI](https://codepen.io/ckeditor/pen/OrZBpV).

+ 11 - 0
docs/builds/guides/frameworks/overview.md

@@ -41,3 +41,14 @@ We plan to provide more integrations with time. We would like to [hear your idea
 Starting from version 11.0.0 CKEditor 5 is compatible with Electron. Using CKEditor 5 in Electron applications does not require any additional steps.
 
 Check out a [sweet screencast of CKEditor 5 with real-time collaborative editing in Electron](https://twitter.com/ckeditor/status/1016627687568363520).
+
+## Compatibility with Ionic and Cordova
+
+CKEditor 5 is compatible with [Ionic framework](https://ionicframework.com/) and [Cordova](https://cordova.apache.org/). However, Ionic disables `user-select` by default, what [blocks typing via the virtual keyboard](https://github.com/ckeditor/ckeditor5/issues/701). It's required to set this property to `text` on the CKEditor component. Just update your stylesheet with the following code:
+
+```css
+ckeditor {
+	-webkit-user-select: text;
+	user-select: text
+}
+```

+ 201 - 21
docs/builds/guides/frameworks/react.md

@@ -14,17 +14,7 @@ The easiest way to use CKEditor 5 in your React application is by choosing one o
 
 ## Quick start
 
-<info-box>
-	### Compatibility with `create-react-app@2`
-
-	The latest version of `create-react-app` (2.x) does not work with CKEditor 5 Builds due to a [bug (most likely in Babel)](https://github.com/facebook/create-react-app/issues/5387).
-
-	If you want to use CKEditor 5 with an application created via `create-react-app@2` you will need to [eject the configuration even in the development mode](https://github.com/facebook/create-react-app/issues/5387#issuecomment-429255695).
-
-	Until the above issue is resolved, in this guide we will use `create-react-app@1` which does not require ejecting the configuration when testing your application in the development mode. You will have to, however, eject it anyway to [build your app for production](#note-building-for-production).
-</info-box>
-
-Install the [CKEditor 5 WYSIWYG editor component for React](https://www.npmjs.com/package/@ckeditor/ckeditor5-react) and the build of your choice.
+Install the [CKEditor 5 WYSIWYG editor component for React](https://www.npmjs.com/package/@ckeditor/ckeditor5-react) and the {@link builds/guides/overview#available-builds editor build of your choice}.
 
 Assuming that you picked [`@ckeditor/ckeditor5-build-classic`](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-classic):
 
@@ -105,7 +95,7 @@ There are two main ways to do that.
 
 ### Note: Building for production
 
-If you work with `create-react-app` or use a custom configuration for you application but still employ `webpack@3`, you will need to adjust the `UglifyJsPlugin` options to make CKEditor 5 compatible with this setup. CKEditor 5 builds use ES6 so the default JavaScript minifier of `webpack@3` and `create-react-app` is not able to digest them.
+If you still work with `create-react-app@1` or use a custom configuration for you application that still uses `webpack@3`, you will need to adjust the `UglifyJsPlugin` option to make CKEditor 5 compatible with this setup. CKEditor 5 builds use ES6 so the default JavaScript minifier of `webpack@3` and `create-react-app@1` is not able to digest them.
 
 To do that, you need to first [eject the configuration](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-run-eject) from the setup created via `create-react-app` (assuming that you use it):
 
@@ -115,8 +105,6 @@ npm run eject
 
 Then, you can customize `UglifyJsPlugin` options in the webpack configuration. Read how to do this [here](#changes-required-in-webpacks-production-configuration).
 
-**Note**: The latest `webpack@4` comes with a version of `UglifyJsPlugin` which supports ES6 out of the box. Also, the React community works on allowing importing ES6 libraries into your applications, so this step will soon be no longer required.
-
 ### Note: Using the Document editor build
 
 If you use the {@link framework/guides/document-editor Document editor}, you need to {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create add the toolbar to the DOM manually}:
@@ -134,9 +122,9 @@ class App extends Component {
                         console.log( 'Editor is ready to use!', editor );
 
                         // Insert the toolbar before the editable area.
-                        editor.ui.view.editable.element.parentElement.insertBefore(
+                        editor.ui.getEditableElement().parentElement.insertBefore(
                             editor.ui.view.toolbar.element,
-                            editor.ui.view.editable.element
+                            editor.ui.getEditableElement()
                         );
                     } }
                     onChange={ ( event, editor ) => console.log( { event, editor } ) }
@@ -157,10 +145,202 @@ Integrating the rich text editor from source allows you to use the full power of
 
 This guide assumes that you are using [Create React App CLI](https://github.com/facebook/create-react-app) as your boilerplate and it goes through adjusting it to fit CKEditor 5 needs. If you use your custom webpack setup, please read more about {@link builds/guides/integration/advanced-setup#scenario-2-building-from-source including CKEditor 5 built from source}.
 
+### Using `create-react-app@2`
+
+The configuration needs to be ejected to make it possible to customize the webpack configuration. In order to be able to build CKEditor 5 from source, you need to tell webpack how to handle CKEditor 5's SVG and CSS files (by adding loaders configuration). Additionally, you need to exclude the CKEditor 5 source from existing loaders.
+
+<info-box>
+  You can see all the changes described below in this example project: https://github.com/ckeditor/ckeditor5-react-example/commits/master.
+</info-box>
+
+Create a sample application using `create-react-app@2` first:
+
+```bash
+npx create-react-app ckeditor5-react-example && cd ckeditor5-react-example
+```
+
+Now you can eject the configuration (you can find more information about ejecting [here](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-run-eject)):
+
+```bash
+yarn eject
+
+# For some strange reasons this is needed, too
+# (https://github.com/facebook/create-react-app/issues/6099).
+yarn add @babel/plugin-transform-react-jsx @babel/plugin-transform-react-jsx-self
+```
+
+#### Installing missing dependencies
+
+Before you start modifying the webpack configuration, first install some CKEditor 5 dependencies that you will need:
+
+```bash
+yarn add \
+  raw-loader \
+  @ckeditor/ckeditor5-dev-utils \
+  @ckeditor/ckeditor5-theme-lark \
+  @ckeditor/ckeditor5-react \
+  @ckeditor/ckeditor5-editor-classic \
+  @ckeditor/ckeditor5-essentials \
+  @ckeditor/ckeditor5-paragraph \
+  @ckeditor/ckeditor5-basic-styles
+```
+
+#### Modifying webpack configuration
+
+Once you ejected the configuration and installed dependencies, you can now edit the webpack configuration (`config/webpack.config.js`).
+
+First, import an object that creates the configuration for PostCSS:
+
+```js
+const { styles } = require( '@ckeditor/ckeditor5-dev-utils' );
+```
+
+Then, add two new elements to the exported object under the `module.rules` array (as new items of the `oneOf` array). These are SVG and CSS loaders required to handle the CKEditor 5 source:
+
+```js
+{
+  test: /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
+  use: [ 'raw-loader' ]
+},
+{
+  test: /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css/,
+  use: [
+    {
+      loader: 'style-loader',
+      options: {
+        singleton: true
+      }
+    },
+    {
+      loader: 'postcss-loader',
+      options: styles.getPostCssConfig( {
+        themeImporter: {
+          themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
+        },
+        minify: true
+      } )
+    }
+  ]
+},
+```
+
+Now you need to exclude CSS files used by CKEditor 5 from the project's CSS loader.
+
+First, find a loader that starts its definition with the following code: `test: cssRegex` and modify it:
+
+```js
+{
+  test: cssRegex,
+  exclude: [
+    cssModuleRegex,
+    /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css/,
+  ],
+  // (...)
+}
+```
+
+Below it, you will find another loader that handles CSS files. You need to disable it for CKEditor 5 CSS as well. Its definition starts with `test: cssModuleRegex`:
+
+```js
+{
+  test: cssModuleRegex,
+  exclude: [
+    /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css/,
+  ],
+  // (...)
+}
+```
+
+Finally, exclude CKEditor 5 SVG and CSS files from `file-loader` . Find the last item in the `module.rules` array which should be the `file-loader` configuration, and modify it so it looks like this:
+
+```js
+{
+  loader: require.resolve('file-loader'),
+  // Exclude `js` files to keep the "css" loader working as it injects
+  // its runtime that would otherwise be processed through the "file" loader.
+  // Also exclude `html` and `json` extensions so they get processed
+  // by webpack's internal loaders.
+  exclude: [
+    /\.(js|mjs|jsx|ts|tsx)$/,
+    /\.html$/,
+    /\.json$/,
+    /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/,
+    /ckeditor5-[^/\\]+[/\\]theme[/\\].+\.css/
+  ],
+  options: {
+    name: 'static/media/[name].[hash:8].[ext]',
+  }
+}
+```
+
+#### Using CKEditor 5 source
+
+Once your configuration is updated, you can now use CKEditor 5 directly from source. Test it by editing `src/App.js`:
+
+```jsx
+import React, { Component } from 'react';
+
+import CKEditor from '@ckeditor/ckeditor5-react';
+
+// NOTE: Use the editor from source (not a build)!
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+
+import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
+import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+
+const editorConfiguration = {
+  plugins: [ Essentials, Bold, Italic, Paragraph ],
+  toolbar: [ 'bold', 'italic' ]
+};
+
+class App extends Component {
+  render() {
+    return (
+      <div className="App">
+        <h2>Using CKEditor 5 from source in React</h2>
+        <CKEditor
+          editor={ ClassicEditor }
+          config={ editorConfiguration }
+          data="<p>Hello from CKEditor 5!</p>"
+          onInit={ editor => {
+            // You can store the "editor" and use when it is needed.
+            console.log( 'Editor is ready to use!', editor );
+          } }
+          onChange={ ( event, editor ) => {
+            const data = editor.getData();
+            console.log( { event, editor, data } );
+          } }
+          onBlur={ editor => {
+            console.log( 'Blur.', editor );
+          } }
+          onFocus={ editor => {
+            console.log( 'Focus.', editor );
+          } }
+        />
+      </div>
+    );
+  }
+}
+
+export default App;
+```
+
+Finally, you can see your application live:
+
+```
+yarn start
+```
+
+You can read more about using CKEditor 5 from source in the @link builds/guides/integration/advanced-setup#scenario-2-building-from-source Advanced setup guide}.
+
+### Using `create-react-app@1`
+
 Install React CLI:
 
 ```bash
-npm install -g create-react-app@1
+npm install -g create-react-app@^1.0.0
 ```
 
 Create your project using the CLI and go to the project's directory:
@@ -175,13 +355,13 @@ Now you can eject the configuration:
 npm run eject
 ```
 
-The configuration needs to be ejected in order to be able to customize webpack configuration. To build CKEditor 5 from source you must load inline SVG images and handle CKEditor 5's CSS as well as correctly minify the ES6 source.
+The configuration needs to be ejected to make it possible to customize the webpack configuration. To be able to build CKEditor 5 from source you must load inline SVG images and handle CKEditor 5's CSS as well as correctly minify the ES6 source.
 
 <info-box>
 	You can find more information about ejecting [here](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#npm-run-eject).
 </info-box>
 
-### Changes required in webpack's production configuration
+#### Changes required in webpack's production configuration
 
 At this stage, if you tried to build your application with CKEditor 5 source included, you would get the following error:
 
@@ -243,7 +423,7 @@ new UglifyJsWebpackPlugin( {
 } )
 ```
 
-### Changes required in both webpack configurations
+#### Changes required in both webpack configurations
 
 In order to build your application properly, you need to modify your webpack configuration files. After ejecting they are located at:
 
@@ -341,7 +521,7 @@ npm install --save \
 	@ckeditor/ckeditor5-paragraph
 ```
 
-### Using CKEditor 5 source
+#### Using CKEditor 5 source
 
 Now you can use the `<CKEditor>` component together with {@link framework/guides/overview CKEditor 5 Framework}:
 

+ 35 - 25
docs/builds/guides/frameworks/vuejs.md

@@ -21,7 +21,7 @@ Additionally, you can [integrate CKEditor 5 from source](#using-ckeditor-from-so
 
 ## Quick start
 
-Install the [CKEditor 5 WYSIWYG editor component for Vue.js](https://www.npmjs.com/package/@ckeditor/ckeditor5-vue) and the {@link builds/guides/overview#available-builds build of your choice}.
+Install the [CKEditor 5 WYSIWYG editor component for Vue.js](https://www.npmjs.com/package/@ckeditor/ckeditor5-vue) and the {@link builds/guides/overview#available-builds editor build of your choice}.
 
 Assuming that you picked [`@ckeditor/ckeditor5-build-classic`](https://www.npmjs.com/package/@ckeditor/ckeditor5-build-classic):
 
@@ -199,6 +199,7 @@ npm install --save \
 Edit the `vue.config.js` file and use the following configuration. If the file is not present, create it in the root of the application (i.e. next to `package.json`):
 
 ```js
+const path = require( 'path' );
 const CKEditorWebpackPlugin = require( '@ckeditor/ckeditor5-dev-webpack-plugin' );
 const { styles } = require( '@ckeditor/ckeditor5-dev-utils' );
 
@@ -220,24 +221,11 @@ module.exports = {
 		]
 	},
 
-	css: {
-		loaderOptions: {
-			// Various modules in the CKEditor source code import .css files.
-			// These files must be transpiled using PostCSS in order to load properly.
-			postcss: styles.getPostCssConfig( {
-				themeImporter: {
-					themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' )
-				},
-				minify: true
-			} )
-		}
-	},
-
+	// Vue CLI would normally use its own loader to load .svg and .css files, however:
+	//	1. The icons used by CKEditor must be loaded using raw-loader,
+	//	2. The CSS used by CKEditor must be transpiled using PostCSS to load properly.
 	chainWebpack: config => {
-		// Vue CLI would normally use its own loader to load .svg files. The icons used by
-		// CKEditor should be loaded using raw-loader instead.
-
-		// Get the default rule for *.svg files.
+		// (1.) To handle editor icons, get the default rule for *.svg files first:
 		const svgRule = config.module.rule( 'svg' );
 
 		// Then you can either:
@@ -247,7 +235,7 @@ module.exports = {
 		//		svgRule.uses.clear();
 		//
 		// * or exclude ckeditor directory from node_modules:
-		svgRule.exclude.add( __dirname + '/node_modules/@ckeditor' );
+		svgRule.exclude.add( path.join( __dirname, 'node_modules', '@ckeditor' ) );
 
 		// Add an entry for *.svg files belonging to CKEditor. You can either:
 		//
@@ -261,10 +249,30 @@ module.exports = {
 			.test( /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/ )
 			.use( 'raw-loader' )
 			.loader( 'raw-loader' );
+
+		// (2.) Transpile the .css files imported by the editor using PostCSS.
+		// Make sure only the CSS belonging to ckeditor5-* packages is processed this way.
+		config.module
+			.rule( 'cke-css' )
+			.test( /ckeditor5-[^/\\]+[/\\].+\.css$/ )
+			.use( 'postcss-loader' )
+			.loader( 'postcss-loader' )
+			.tap( () => {
+				return styles.getPostCssConfig( {
+					themeImporter: {
+						themePath: require.resolve( '@ckeditor/ckeditor5-theme-lark' ),
+					},
+					minify: true
+				} );
+			} );
 	}
 };
 ```
 
+<info-box>
+    By default, the Vue CLI uses [`file-loader`](https://webpack.js.org/loaders/file-loader/) for all SVG files. The `file-loader` copies the file to the output directory and resolves imports into URLs. The CKEditor's UI components use SVG {@link module:ui/icon/iconview~IconView#content source directly} so the theme icons must be loaded using [`raw-loader`](https://webpack.js.org/loaders/raw-loader). If your project uses different approach then CKEditor's UI library you must create different webpack loader rules for your project SVG files and CKEditor's ones.
+</info-box>
+
 ### Using the editor from source
 
 Having configured `vue.config.js`, you can choose the building blocks of your editor. Install the packages necessary for your integration:
@@ -274,9 +282,9 @@ npm install --save \
 	@ckeditor/ckeditor5-editor-classic \
 	@ckeditor/ckeditor5-essentials \
 	@ckeditor/ckeditor5-basic-styles \
-	@ckeditor/ckeditor5-basic-styles \
 	@ckeditor/ckeditor5-link \
-	@ckeditor/ckeditor5-paragraph
+	@ckeditor/ckeditor5-paragraph \
+	@ckeditor/ckeditor5-theme-lark
 ```
 
 You can use more packages, depending on which features are needed in your application.
@@ -301,6 +309,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';
@@ -368,9 +378,9 @@ Since accessing the editor toolbar is not possible until after the editor instan
 		methods: {
 			onReady( editor )  {
 				// Insert the toolbar before the editable area.
-				editor.ui.view.editable.element.parentElement.insertBefore(
+				editor.ui.getEditableElement().parentElement.insertBefore(
 					editor.ui.view.toolbar.element,
-					editor.ui.view.editable.element
+					editor.ui.getEditableElement()
 				);
 			}
 		}
@@ -413,7 +423,7 @@ This directive specifies the editor to be used by the component. It must directl
 
 ### `tag-name`
 
-By default, the editor component creates a `<div>` container which is used as an element passed to the editor (e.g. {@link module:editor-classic/classiceditor~ClassicEditor#element `ClassicEditor#element`}). The element can be configured, so for example to create a `<textarea>`, use the following directive:
+By default, the editor component creates a `<div>` container which is used as an element passed to the editor (e.g. {@link module:editor-classic/classiceditorui~ClassicEditorUI#element `ClassicEditor#element`}). The element can be configured, so for example to create a `<textarea>`, use the following directive:
 
 ```html
 <ckeditor :editor="editor" tag-name="textarea"></ckeditor>
@@ -510,7 +520,7 @@ Specifies the {@link module:core/editor/editorconfig~EditorConfig configuration}
 			return {
 				editor: ClassicEditor,
 				editorConfig: {
-					toolbar: [ 'bold', 'italic', '|' 'link' ]
+					toolbar: [ 'bold', 'italic', '|', 'link' ]
 				}
 			};
 		}

+ 7 - 4
docs/builds/guides/integration/advanced-setup.md

@@ -291,9 +291,12 @@ ClassicEditor
 			ImageCaptionPlugin,
 			ImageStylePlugin,
 			ImageToolbarPlugin,
+			EasyImagePlugin,
+			ImageUploadPlugin,
 			LinkPlugin,
 			ListPlugin,
-			ParagraphPlugin
+			ParagraphPlugin,
+			UploadAdapterPlugin
 		],
 
 		// So is the rest of the default configuration.
@@ -408,7 +411,7 @@ However, it may happen that your environment requires ES5. For instance, if you
 In order to create an ES5 build of CKEditor 5 you can use [Babel](https://babeljs.io/):
 
 ```bash
-npm install --save babel-loader babel-core babel-preset-env regenerator-runtime
+npm install --save babel-loader @babel/core @babel/preset-env regenerator-runtime
 ```
 
 Then, add this item to webpack [`module.rules`](https://webpack.js.org/configuration/module/#module-rules) section:
@@ -417,12 +420,12 @@ Then, add this item to webpack [`module.rules`](https://webpack.js.org/configura
 module: {
 	rules: [
 		{
-			test: /\.js$/,
+			test: /ckeditor5-[^\/\\]+[\/\\].*\.js$/,
 			use: [
 				{
 					loader: 'babel-loader',
 					options: {
-						presets: [ require( 'babel-preset-env' ) ]
+						presets: [ require( '@babel/preset-env' ) ]
 					}
 				}
 			]

+ 2 - 0
docs/builds/guides/integration/basic-api.md

@@ -170,9 +170,11 @@ Once destroyed, resources used by the editor instance are released and the origi
 
 {@link module:engine/model/document~Document#change:data `Document#change:data`}.
 
+```js
 editor.model.document.on( 'change:data', () => {
     console.log( 'The data has changed!' );
 } );
+```
 
 This event is fired when the document changes in such a way which is "visible" in the editor data. There is also a group of changes, like selection position changes, marker changes which do not affect the result of `editor.getData()`. To listen to all these changes, you can use a wider {@link module:engine/model/document~Document#change `Document#change`} event.
 

+ 70 - 0
docs/builds/guides/integration/csp.md

@@ -0,0 +1,70 @@
+---
+# Scope:
+# * Guide developers through the Content Security Policy directives that have an impact on the editor.
+# * List the recommended Content Security Policy settings.
+# * List the strictest working Content Security Policy settings.
+
+category: builds-integration
+order: 70
+---
+
+# Content Security Policy
+
+CKEditor 5 is compatible with applications that use [<abbr title="Content Security Policy">CSP</abbr> rules](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) and helps developers build a secure web.
+
+## Recommended CSP configuration
+
+The recommended CSP configuration that allows the rich-text editor to run out–of–the–box with all standard features using the content like images or media from external hosts looks as follows:
+
+```
+default-src 'none'; connect-src 'self'; script-src 'self'; img-src * data:; style-src 'self' 'unsafe-inline'; frame-src *
+```
+
+## Impact of CSP on editor features
+
+Some CSP directives have an impact on certain rich-text editor features. Here is the round-up of directives and their specific roles in the editor:
+
+* `default-src 'none'`: Resets the policy and blocks everything. All successive directives work as a whitelist. By itself, as long as it is followed by other directives, it has no impact on the editor.
+* `connect-src 'self'`
+	* Allows the {@link features/image-upload editor upload features} to use [`XMLHttpReqests`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) (Ajax) to upload files to the server, for instance, when an image is pasted or dropped into the editor content. The `'self`' value ensures the requests remain within the same host.
+	* Allows {@link builds/guides/integration/saving-data#autosave-feature auto–saving editor data} using `XMLHttpRequest`.
+
+	**Note**: To use [CKEditor Cloud Services](https://ckeditor.com/ckeditor-cloud-services/), include the `http://*.cke-cs.com` domain in the `connect-src` directive, for instance: `connect-src 'self' http://*.cke-cs.com`.
+* `script-src 'self'`: Allows the execution of JavaScript from the current host only and can be applied only if the CKEditor 5 script file (`<script src="[ckeditor-build-path]/ckeditor.js"></script>`) is also served from that host.
+
+	**Note**: If CKEditor 5 is served from another host, for example the {@link builds/guides/integration/installation#cdn official CDN}, make sure the value of `script-src` includes that host (`script-src 'self' https://cdn.ckeditor.com`).
+* `img-src * data:`
+	* The `*` directive value allows images in the editor content to come from any hosts.
+	* The `data:` value allows:
+		* Pasting {@link features/image-upload images from the clipboard} and {@link features/paste-from-word from Microsoft Word} into the editor content. Pasted images are usually represented as Base64–encoded strings (`<img src="data:..." />`) and without `data:` they cannot be displayed and uploaded.
+		* Displaying the {@link features/media-embed media embed} feature placeholders for the inserted media.
+
+	**Note**: Use the more strict `img-src 'self'` if all images in the editor content are hosted from the same domain and you do **not** want to enable the {@link features/media-embed media embed} and {@link features/paste-from-word paste from Word} features.
+* `style-src 'self' 'unsafe-inline'`: `'unsafe-inline'` is necessary for:
+	* webpack's [style-loader](https://github.com/webpack-contrib/style-loader) to load the {@link framework/guides/theme-customization#styles-processing-and-bundling editor UI styles}.
+
+	**Note**: You can {@link builds/guides/integration/advanced-setup#option-extracting-css extract styles to a separate `.css` file} during the editor building process and remove this directive.
+	* Certain editor content styles to work properly. For instance, you are going to need it if you want to enable such editor features as {@link features/font font} or {@link features/text-alignment text alignment} or any other feature that uses inline `style="..."` attributes in the content.
+* `frame-src *`: Necessary for the {@link features/media-embed media embed} feature to load media with previews (containing `<iframe>`).
+
+	**Note**: Use the more strict `frame-src 'self'` if all the media in the edited content come from the same domain as your application.
+
+<info-box>
+	A different set of Content Security Policy directives might be necessary to run {@link features/ckfinder CKFinder} along with CKEditor 5. Check out the file manager [documentation](https://ckeditor.com/docs/ckfinder/ckfinder3/#!/guide/dev_integration-section-csp-directives-required-by-ckfinder) to learn more.
+</info-box>
+
+## Strictest working configuration
+
+Knowing the role of each directive, the strictest set of rules that allows CKEditor 5 to run is as follows:
+
+```
+default-src 'none'; connect-src 'self'; script-src 'self'; img-src 'self'; style-src 'self'; frame-src 'self'
+```
+
+This comes with some trade–offs, though. For example, it requires you to:
+
+* Use an editor build with the {@link builds/guides/integration/advanced-setup#option-extracting-css styles extracted to a separate `.css` file} (no style-loader).
+* Load images in the content from the same host.
+* Load previewable media in the content from the same host.
+* Give up certain features that use inline styles like {@link features/font font} or {@link features/text-alignment text alignment}.
+* Give up pasting images from the clipboard or {@link features/paste-from-word from Word}.

+ 2 - 0
docs/builds/guides/integration/installation.md

@@ -37,6 +37,8 @@ npm install --save @ckeditor/ckeditor5-build-inline
 # Or:
 npm install --save @ckeditor/ckeditor5-build-balloon
 # Or:
+npm install --save @ckeditor/ckeditor5-build-balloon-block
+# Or:
 npm install --save @ckeditor/ckeditor5-build-decoupled-document
 ```
 

+ 14 - 0
docs/builds/guides/integration/saving-data.md

@@ -154,6 +154,20 @@ It also listens to the native [`window#beforeunload`](https://developer.mozilla.
 
 This automatically secures you from the user leaving the page before the content is saved or some ongoing actions like image upload did not finish.
 
+The minimum time period between two save actions can be configured using the {@link module:autosave/autosave~AutosaveConfig#waitingTime `config.waitingTime`} property to not overload the backend. 1 second is the default waiting time before the next save action if nothing has changed in the meantime after the editor data has changed.
+
+```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		autosave: {
+			waitingTime: 5000, // in ms
+			save( editor ) {}
+		},
+
+		// ... other configuration options
+	} );
+```
+
 ### Demo
 
 This demo shows a simple integration of the editor with a fake HTTP server (which needs 1000ms to save the content).

+ 21 - 6
docs/builds/guides/migrate.md

@@ -68,6 +68,18 @@ The following table presents configuration options available in CKEditor 4 and t
 
 Note: The number of options was reduced on purpose. We understood that configuring CKEditor 4 was a bit too troublesome due to the number of configuration options available (over 240). Sometimes they were definitely too low-level, also many times they were so infrequently used that it did not justify the increased level of the application complexity. This is why when designing CKEditor 5 from scratch, we decided to come with a simplified editor, with well-thought default behavior, based on the results of the [Editor Recommendations](http://ckeditor.github.io/editor-recommendations/) project.
 
+<style>
+/* See: https://github.com/ckeditor/ckeditor5/issues/1718. */
+.docsearch-txt {
+	table-layout: fixed;
+}
+
+.docsearch-txt tr th:nth-child( 1 ),
+.docsearch-txt tr td:nth-child( 1 ) {
+	width: 250px;
+}
+</style>
+
 <table class="docsearch-txt">
 	<thead>
 		<tr>
@@ -80,7 +92,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>
@@ -90,7 +102,7 @@ Note: The number of options was reduced on purpose. We understood that configuri
 		<tr>
 			<td><a href="/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-autoGrow_bottomSpace">autoGrow_bottomSpace</a> <br> <a href="/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-autoGrow_maxHeight">autoGrow_maxHeight</a> <br> <a href="/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-autoGrow_minHeight">autoGrow_minHeight</a> <br> <a href="/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-autoGrow_onStartup">autoGrow_onStartup</a></td>
 			<td>
-				<p>Classic editor (CKEditor 5) no longer encapsulates the editing area in an <code>&lt;iframe&gt;</code>, which means that the height (and similar options) of the editing area can be easily controlled with CSS. For example the <code>minHeight</code> and <code>maxHeight</code> settings can be achieved with <code>.ck-content { min-height:200px; max-height:400px; }</code>.</p>
+				<p>Classic editor (CKEditor 5) no longer encapsulates the editing area in an <code>&lt;iframe&gt;</code>, which means that the height (and similar options) of the editing area can be easily controlled with CSS. For example the <code>minHeight</code> and <code>maxHeight</code> settings can be achieved with <code>.ck-editor__editable_inline { min-height:200px; max-height:400px; }</code>.</p>
 				<p> See also <a href="https://stackoverflow.com/questions/46559354/how-to-set-the-height-of-ckeditor-5-classic-editor" target="_blank" rel="noopener">How to set the height of CKEditor 5 (Classic editor)</a>.</p>
 			</td>
 		</tr>
@@ -210,8 +222,11 @@ Note: The number of options was reduced on purpose. We understood that configuri
 		<tr>
 			<td><a href="/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-disableNativeSpellChecker">disableNativeSpellChecker</a></td>
 			<td>
-				<p>Unavailable, however, in case of Inline and Balloon editors can be done by setting the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/spellcheck" target="_blank" rel="noopener"><code>spellcheck</code></a> attribute directly on the element where CKEditor should be enabled.</p>
-				<p>For Classic editor, call <code>editor.ui.view.editable.editableElement.setAttribute(&apos;spellcheck&apos;, &apos;false&apos;);</code> after creating the editor.</p>
+				<p>Unavailable, however, in case of inline and balloon editors can be done by setting the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/spellcheck" target="_blank" rel="noopener"><code>spellcheck</code></a> attribute directly on the element where CKEditor should be enabled.</p>
+				<p>For the classic and decoupled editors, call:</p>
+				<pre><code>editor.editing.view.change( writer => {
+	writer.setAttribute( 'spellcheck', 'false', editor.editing.view.document.getRoot() );
+} );</code></pre>
 			</td>
 		</tr>
 		<tr>
@@ -333,7 +348,7 @@ Note: The number of options was reduced on purpose. We understood that configuri
 		<tr>
 			<td><a href="/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-height">height</a></td>
 			<td>
-				<p>Classic editor (CKEditor 5) no longer encapsulates the editing area in an <code>&lt;iframe&gt;</code>, which means that the height (and similar options) of the editing area can be easily controlled with CSS. For example the height setting can be achieved with <code>.ck-content { height:400px; }</code>.</p>
+				<p>Classic editor (CKEditor 5) no longer encapsulates the editing area in an <code>&lt;iframe&gt;</code>, which means that the height (and similar options) of the editing area can be easily controlled with CSS. For example the height setting can be achieved with <code>.ck-editor__editable_inline { height:400px; }</code>.</p>
 				<p>See also <a href="https://stackoverflow.com/questions/46559354/how-to-set-the-height-of-ckeditor-5-classic-editor" target="_blank" rel="noopener">How to set the height of CKEditor 5 (Classic editor)</a>.</p>
 			</td>
 		</tr>
@@ -575,7 +590,7 @@ Note: The number of options was reduced on purpose. We understood that configuri
 		<tr>
 			<td><a href="/docs/ckeditor4/latest/api/CKEDITOR_config.html#cfg-width">width</a></td>
 			<td>
-				<p>Classic editor (CKEditor 5) no longer encapsulates the editing area in an <code>&lt;iframe&gt;</code>, which means that the height (and similar options) of the editing area can be easily controlled with CSS. For example, the width setting can be achieved with <code>.ck-content { width:400px; }</code>.</p>
+				<p>Classic editor (CKEditor 5) no longer encapsulates the editing area in an <code>&lt;iframe&gt;</code>, which means that the height (and similar options) of the editing area can be easily controlled with CSS. For instance, to set the width of the Classic editor, use <code>.ck-editor { width:400px; }</code>. Setting the width of other editors which do not enclose the toolbar and the editable in a box (Inline editor, Balloon editor, etc.) can be achieved using <code>.ck-editor__editable_inline { width:400px; }</code>.</p>
 				<p>See also <a href="https://stackoverflow.com/questions/46559354/how-to-set-the-height-of-ckeditor-5-classic-editor" target="_blank" rel="noopener">How to set the height of CKEditor 5 (Classic editor)</a>.</p>
 			</td>
 		</tr>

+ 9 - 0
docs/builds/guides/overview.md

@@ -20,6 +20,7 @@ The following CKEditor 5 Builds are currently available:
  * [Classic editor](#classic-editor)
  * [Inline editor](#inline-editor)
  * [Balloon editor](#balloon-editor)
+ * [Balloon block editor](#balloon-block-editor)
  * [Document editor](#document-editor)
 
 ### Classic editor
@@ -56,6 +57,14 @@ Balloon editor is very similar to inline editor. The difference between them is
 
 To try it out online, check the {@link examples/builds/balloon-editor balloon editor example}. Jump to {@link builds/guides/quick-start#balloon-editor Quick start} to start using it.
 
+### Balloon block editor
+
+Balloon block is essentially the [balloon editor](#balloon-editor) with an extra block toolbar which can be accessed using the button attached to the editable content area and following the selection in the document. The toolbar gives an access to additional, block–level editing features.
+
+{@img assets/img/editor-balloon-block.png 813 Screenshot of a balloon block toolbar editor.}
+
+To try it out online, check the {@link examples/builds/balloon-block-editor balloon block editor example}. Jump to {@link builds/guides/quick-start#balloon-block-editor Quick start} to start using it.
+
 ### Document editor
 
 The document editor is focused on rich text editing experience similar to the native word processors. It works best for creating documents which are usually later printed or exported to PDF files.

+ 54 - 0
docs/builds/guides/quick-start.md

@@ -173,6 +173,60 @@ Call the {@link module:editor-balloon/ballooneditor~BalloonEditor#create `Balloo
 </html>
 ```
 
+## Balloon block editor
+
+In your HTML page add an element that CKEditor should make editable:
+
+```html
+<div id="editor"></div>
+```
+
+Load the balloon block editor build (here [CDN](https://cdn.ckeditor.com/) location is used):
+
+```html
+<script src="https://cdn.ckeditor.com/ckeditor5/{@var ckeditor5-version}/balloon-block/ckeditor.js"></script>
+```
+
+Call the {@link module:editor-balloon/ballooneditor~BalloonEditor#create `BalloonEditor.create()`} method.
+
+```html
+<script>
+	BalloonEditor
+		.create( document.querySelector( '#editor' ) )
+		.catch( error => {
+			console.error( error );
+		} );
+</script>
+```
+
+**Note:** You can configure the block toolbar items using the {@link module:core/editor/editorconfig~EditorConfig#blockToolbar `config.blockToolbar`} option.
+
+### Example
+
+```html
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="utf-8">
+	<title>CKEditor 5 – Balloon block editor</title>
+	<script src="https://cdn.ckeditor.com/ckeditor5/{@var ckeditor5-version}/balloon-block/ckeditor.js"></script>
+</head>
+<body>
+	<h1>Balloon editor</h1>
+	<div id="editor">
+		<p>This is some sample content.</p>
+	</div>
+	<script>
+		BalloonEditor
+			.create( document.querySelector( '#editor' ) )
+			.catch( error => {
+				console.error( error );
+			} );
+	</script>
+</body>
+</html>
+```
+
 ## Document editor
 
 Load the document editor build (here [CDN](https://cdn.ckeditor.com/) location is used):

+ 1 - 1
docs/builds/guides/support/browser-compatibility.md

@@ -42,4 +42,4 @@ CKEditor 5 is currently supported in the following mobile environments:
 
 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 October 2018, this means over 9600 tests and the number is growing.
 
-Such an extensive test suite requires a proper continuous integration service. We use [Travis CI](https://travis-ci.com/) as a build platform and [BrowserStack](https://www.browserstack.com/) to be able to run tests on all browsers. These services ensure seamless and fast developer experience and allow us to focus on the job.
+Such an extensive test suite requires a proper continuous integration service. We use [Travis CI](https://travis-ci.com/) as a build platform. This service ensures seamless and fast developer experience and allow us to focus on the job.

+ 1 - 1
docs/builds/guides/support/license-and-legal.md

@@ -10,7 +10,7 @@ order: 20
 
 The following legal notices apply to CKEditor 5 Builds and all software from CKEditor 5 Ecosystem included with it.
 
-Copyright (c) 2003-2018, CKSource Frederico Knabben. All rights reserved.
+Copyright (c) 2003-2019, CKSource Frederico Knabben. All rights reserved.
 
 Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html).
 

+ 1 - 0
docs/builds/index.md

@@ -2,6 +2,7 @@
 category: builds
 order: 10
 toc: false
+feedback-widget: false
 meta-title: CKEditor 5 Builds documentation
 meta-description: Learn how to install, integrate, configure and develop CKEditor 5 Builds. Browse through API documentation and online samples.
 ---

+ 13 - 0
docs/examples/builds/balloon-block-editor.md

@@ -0,0 +1,13 @@
+---
+category: examples-builds
+order: 30
+---
+
+# Balloon block editor
+
+{@link builds/guides/overview#balloon-block-editor Balloon block editor} lets you create your content directly in its target location with the help of two toolbars:
+
+* a balloon toolbar that appears next to the selected editable document element (offering inline content formatting tools),
+* a {@link features/blocktoolbar block toolbar} accessible using a button attached to the editable content area and following the selection in the document (bringing additional block formatting tools).
+
+{@snippet examples/balloon-block-editor}

+ 14 - 0
docs/examples/framework/multi-root-editor.md

@@ -0,0 +1,14 @@
+---
+category: examples-framework
+order: 30
+---
+
+# Multi-root editor
+
+The main difference between a multi-root editor and using multiple separate editors (like in the {@link examples/builds/inline-editor inline editor demo}) is the fact that in a multi-root editor all editable areas belong to the same editor instance, share the same toolbar and create one undo stack.
+
+Out of the box, CKEditor 5 does not offer a ready-to-use multi-root editor yet. However, such an editor can be implemented by using the {@link framework/guides/overview CKEditor 5 Framework}.
+
+Check out the {@link framework/guides/custom-editor-creator "Implementing a custom editor creator" guide} which contains the source code of the demo below.
+
+{@snippet examples/multi-root-editor}

+ 1 - 0
docs/examples/index.md

@@ -2,6 +2,7 @@
 category: examples
 order: 10
 toc: false
+feedback-widget: false
 meta-title: CKEditor 5 documentation - online examples
 meta-description: Navigate through CKEditor 5 examples to see what you are able to create using this rich text editing framework.
 ---

+ 72 - 0
docs/features/editor-placeholder.md

@@ -0,0 +1,72 @@
+---
+category: features
+---
+
+{@snippet build-classic-source}
+
+# Editor placeholder
+
+CKEditor 5 can display a configurable placeholder text when the content is empty. The placeholder helps users locate the editor in the application and prompts to input the content. It works similarly to the native DOM [`placeholder` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#The_placeholder_attribute) used by inputs.
+
+See the demo of the placeholder feature:
+
+{@snippet features/placeholder}
+
+## Configuring the placeholder
+
+There are two different ways of configuring the editor placeholder text:
+
+### Using the `placeholder` attribute of a textarea
+
+Set the `placeholder` attribute on a `<textarea>` element passed to the `Editor.create()` method (for instance {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`}) to configure the placeholder:
+
+```html
+<textarea id="editor" placeholder="Type the content here!"></textarea>
+```
+
+```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ) )
+	.then( editor => {
+		console.log( editor );
+	} )
+	.catch( error => {
+		console.error( error );
+	} );
+```
+
+### Using the editor configuration
+
+You can use the {@link module:core/editor/editorconfig~EditorConfig#placeholder `editor.config.placeholder`} configuration option:
+
+* when no element was passed into `Editor.create()` method,
+* when the element passed into `Editor.create()` was not a `<textarea>` (for instance, a `<div>` element),
+* to override the `placeholder` text of a `<textarea>`, if one was passed into `Editor.create()` but the placeholder text should be different.
+
+```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		placeholder: 'Type the content here!'
+	} )
+	.then( editor => {
+		console.log( editor );
+	} )
+	.catch( error => {
+		console.error( error );
+	} );
+```
+
+## Styling the placeholder
+
+The editor placeholder text is displayed using a CSS pseudo–element (`::before`) related to the first empty element in the editor content (usually a paragraph). You can use the following snippet to change the appearance of the placeholder:
+
+```css
+.ck.ck-editor__editable > .ck-placeholder::before {
+    color: #d21714;
+    font-family: Georgia;
+}
+```
+
+{@snippet features/placeholder-custom}
+
+**Note**: The `.ck-placeholder` class is also used to display placeholders in other places, for instance, {@link features/image#image-captions image captions}. Make sure your custom styles apply to the right subset of placeholders.

+ 6 - 0
docs/features/image-upload.md

@@ -68,6 +68,12 @@ There are two ways you can integrate CKEditor 5 with the CKFinder file manager:
 
 {@link features/ckfinder **Learn how to integrate CKEditor 5 with CKFinder in your project**}.
 
+### Base64
+
+The {@link features/base64-upload-adapter Base64 upload feature} converts images inserted into the editor into [Base64 strings](https://en.wikipedia.org/wiki/Base64) in the {@link builds/guides/integration/saving-data editor output}.
+
+{@link features/base64-upload-adapter **Learn how to use Base64–encoded images in CKEditor 5**}.
+
 ## Implementing your own upload adapter
 
 CKEditor 5 provides an open API that allows you to develop your own upload adapters. Tailored to your project, a custom adapter will allow you to take the **full control** over the process of sending the files to the server as well as passing the response from the server (e.g. the URL to the saved file) back to the WYSIWYG editor.

+ 2 - 1
docs/features/index.md

@@ -2,6 +2,7 @@
 category: features
 order: 10
 toc: false
+feedback-widget: false
 ---
 
 # Features
@@ -23,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 - 4
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:
@@ -222,10 +226,6 @@ command.on( 'change:value', ( evt, propertyName, newValue, oldValue ) => {
 command.value = true; // -> 'value has changed from undefined to true'
 ```
 
-<info-box>
-	Observable properties are marked in API documentation strings with the `@observable` keyword but we do not mark them in {@link api/index API documentation} ([yet](https://github.com/ckeditor/ckeditor5-dev/issues/285)).
-</info-box>
-
 Observables have one more feature which is widely used by the editor (especially in the UI library) &mdash; the ability to bind the value of one object's property to the value of some other property or properties (of one or more objects). This, of course, can also be processed by callbacks.
 
 Assuming that `target` and `source` are observables and that used properties are observable:

+ 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:

+ 1 - 0
docs/framework/guides/architecture/intro.md

@@ -2,6 +2,7 @@
 category: framework-architecture
 order: 10
 toc: false
+feedback-widget: false
 ---
 
 # Introduction

+ 5 - 5
docs/framework/guides/architecture/ui-library.md

@@ -16,7 +16,7 @@ Views use [templates](#templates) to build the UI. They also provide observable
 A simple input view class can be defined as follows:
 
 ```js
-class SampleInputView extends View {
+class SimpleInputView extends View {
 	constructor( locale ) {
 		super( locale );
 
@@ -66,8 +66,8 @@ class ParentView extends View {
 	constructor( locale ) {
 		super( locale );
 
-		const childA = new SampleInputView( locale );
-		const childB = new SampleInputView( locale );
+		const childA = new SimpleInputView( locale );
+		const childB = new SimpleInputView( locale );
 
 		this.setTemplate( {
 			tag: 'div',
@@ -90,7 +90,7 @@ document.body.appendChild( parent.element );
 It is also possible to create standalone views that do not belong to any collection. They must be {@link module:ui/view~View#render rendered} before injection into the DOM:
 
 ```js
-const view = new SampleInputView( locale );
+const view = new SimpleInputView( locale );
 
 view.render();
 
@@ -191,7 +191,7 @@ Similarly, when rendered, the template also takes care of DOM events. A binding
 
 Views are organized into {@link module:ui/viewcollection~ViewCollection collections} which manage their elements and propagate DOM events even further. Adding or removing a view in a collection moves the {@link module:ui/view~View#element view's element} in the DOM to reflect the position.
 
-Each editor UI has a {@link module:core/editor/editorui~EditorUI#view root view}, which can be found under `editor.ui.view`. Such view usually defines the container element of the editor and undermost view collections that other features can populate.
+Each editor UI has a "root view" (e.g. {@link e.g. {@link module:editor-classic/classiceditorui~ClassicEditorUI#view `ClassicEditor#view`}), which can be found under `editor.ui.view`. Such view usually defines the container element of the editor and undermost view collections that other features can populate.
 
 For instance, the `BoxedEditorUiView` class defines two collections:
 * {@link module:ui/editorui/boxed/boxededitoruiview~BoxedEditorUIView#top} &ndash; A collection that hosts the toolbar.

+ 9 - 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:
@@ -205,6 +211,7 @@ This task accepts the following arguments:
 
 * `--skip-api` &mdash; Skips building the API documentation (which takes the majority of the total time).
 * `--skip-snippets` &mdash; Skips building live snippets.
+* `--snippets=snippet-name` &mdash; Whitelist snippets to build (accepts glob patterns).
 * `--skip-validation` &mdash; Skips the final link validation.
 * `--watch` &mdash; Runs the documentation generator in a watch mode. It covers guides (it does not cover API docs).
 * `--production` &mdash; Minifies the assets and performs other actions which are unnecessary during CKEditor 5 development.

+ 5 - 1
docs/framework/guides/contributing/testing-environment.md

@@ -117,11 +117,15 @@ 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.
 
-Such an extensive test suite requires a proper continuous integration service. We use [Travis CI](https://travis-ci.com/) as a build platform and [BrowserStack](https://www.browserstack.com/) to be able to run tests on all browsers. These services ensure seamless and fast developer experience and allow us to focus on the job.
+Such an extensive test suite requires a proper continuous integration service. We use [Travis CI](https://travis-ci.com/) as a build platform. This service ensures seamless and fast developer experience and allow us to focus on the job.
 
 Besides automated tests, we also maintain a smaller set of manual tests. They help us verify whether something unexpected happens that might have been missed by the automated tests.
 

+ 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 rich-text editor plugin for CKEditor 5.
+
+<info-box>
+	Before you get to work, you should check out the {@link framework/guides/quick-start Quick start} guide first to 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 the 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`.
+
+The 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 the 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 the CKEditor 5 architecture, check out the {@link framework/guides/architecture/intro Introduction to CKEditor 5 architecture}.

+ 481 - 0
docs/framework/guides/custom-editor-creator.md

@@ -0,0 +1,481 @@
+---
+menu-title: Creating custom editor
+category: framework-deep-dive-ui
+order: 40
+---
+
+# Implementing a custom editor creator
+
+The flexible architecture of CKEditor 5 allows creating completely custom editors. Not only the {@link examples/theme-customization theme styling can be changed} or the {@link examples/custom-ui UI redesigned} but also the entire editor initialization process can be modified allowing to create new editor types. Thanks to that, apart from the standard editors (like {@link examples/builds/classic-editor classic}, {@link examples/builds/inline-editor inline}, {@link examples/builds/balloon-editor balloon} or {@link examples/builds/document-editor document}), custom types like a {@link examples/framework/multi-root-editor **multi-root editor**} can be created.
+
+This guide goes through the process of implementing a custom, multi-root editor. You can check out the {@link examples/framework/multi-root-editor demo of the multi-root editor} too.
+
+## Editor class
+
+The `*Editor` class is the main class of each editor type. It initializes the whole editor and its UI parts. The custom creator class should extend the {@link module:core/editor/editor~Editor base `Editor` class}. In case of a multi-root editor it may look like below:
+
+```js
+import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
+import DataApiMixin from '@ckeditor/ckeditor5-core/src/editor/utils/dataapimixin';
+import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
+import getDataFromElement from '@ckeditor/ckeditor5-utils/src/dom/getdatafromelement';
+import setDataInElement from '@ckeditor/ckeditor5-utils/src/dom/setdatainelement';
+import mix from '@ckeditor/ckeditor5-utils/src/mix';
+
+/**
+ * The multi-root editor implementation. It provides an inline editables and a toolbar.
+ *
+ * Unlike other editors, the toolbar is not rendered automatically and needs to be attached to the DOM manually.
+ *
+ * This type of an editor is dedicated to integrations which require a customized UI with an open
+ * structure, allowing developers to specify the exact location of the interface.
+ *
+ * @mixes module:core/editor/utils/dataapimixin~DataApiMixin
+ * @implements module:core/editor/editorwithui~EditorWithUI
+ * @extends module:core/editor/editor~Editor
+ */
+class MultirootEditor extends Editor {
+	/**
+	 * Creates an instance of the multi-root editor.
+	 *
+	 * **Note:** Do not use the constructor to create editor instances. Use the static `MultirootEditor.create()` method instead.
+	 *
+	 * @protected
+	 * @param {Object.<String,HTMLElement>} sourceElements The list of DOM elements that will be the source
+	 * for the created editor (on which the editor will be initialized).
+	 * @param {module:core/editor/editorconfig~EditorConfig} config The editor configuration.
+	 */
+	constructor( sourceElements, config ) {
+		super( config );
+
+		this.data.processor = new HtmlDataProcessor();
+
+		// Create root and UIView element for each editable container.
+		for ( const rootName of Object.keys( sourceElements ) ) {
+			this.model.document.createRoot( '$root', rootName );
+		}
+
+		this.ui = new MultirootEditorUI( this, new MultirootEditorUIView( this.locale, this.editing.view, sourceElements ) );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	destroy() {
+		// Cache the data and editable DOM elements, then destroy.
+		// It's safe to assume that the model->view conversion will not work after super.destroy(),
+		// same as `ui.getEditableElement()` method will not return editables.
+		const data = {};
+		const editables = {};
+		const editablesNames = Array.from( this.ui.getEditableElementsNames() );
+
+		for ( const rootName of editablesNames ) {
+			data[ rootName ] = this.getData( { rootName } );
+			editables[ rootName ] = this.ui.getEditableElement( rootName );
+		}
+
+		this.ui.destroy();
+
+		return super.destroy()
+			.then( () => {
+				for ( const rootName of editablesNames ) {
+					setDataInElement( editables[ rootName ], data[ rootName ] );
+				}
+			} );
+	}
+
+	/**
+	 * Creates a multi-root editor instance.
+	 *
+	 * @param {Object.<String,HTMLElement>} sourceElements The list of DOM elements that will be the source
+	 * for the created editor (on which the editor will be initialized).
+	 * @param {module:core/editor/editorconfig~EditorConfig} config The editor configuration.
+	 * @returns {Promise} A promise resolved once the editor is ready. The promise returns the created multi-root editor instance.
+	 */
+	static create( sourceElements, config ) {
+		return new Promise( resolve => {
+			const editor = new this( sourceElements, config );
+
+			resolve(
+				editor.initPlugins()
+					.then( () => editor.ui.init() )
+					.then( () => {
+						const initialData = {};
+
+						// Create initial data object containing data from all roots.
+						for ( const rootName of Object.keys( sourceElements ) ) {
+							initialData[ rootName ] = getDataFromElement( sourceElements[ rootName ] );
+						}
+
+						return editor.data.init( initialData );
+					} )
+					.then( () => editor.fire( 'ready' ) )
+					.then( () => editor )
+			);
+		} );
+	}
+}
+
+mix( MultirootEditor, DataApiMixin );
+```
+
+## EditorUI class
+
+The `*EditorUI` class is the main UI class which initializes UI components (the main view and the toolbar) and sets up mechanisms like focus tracker or placeholder management. The custom `*EditorUI` class should extend the {@link module:core/editor/editorui~EditorUI base `EditorUI` class} like below:
+
+```js
+import EditorUI from '@ckeditor/ckeditor5-core/src/editor/editorui';
+import enableToolbarKeyboardFocus from '@ckeditor/ckeditor5-ui/src/toolbar/enabletoolbarkeyboardfocus';
+import normalizeToolbarConfig from '@ckeditor/ckeditor5-ui/src/toolbar/normalizetoolbarconfig';
+import { enablePlaceholder } from '@ckeditor/ckeditor5-engine/src/view/placeholder';
+
+/**
+ * The multi-root editor UI class.
+ *
+ * @extends module:core/editor/editorui~EditorUI
+ */
+class MultirootEditorUI extends EditorUI {
+	/**
+	 * Creates an instance of the multi-root editor UI class.
+	 *
+	 * @param {module:core/editor/editor~Editor} editor The editor instance.
+	 * @param {module:ui/editorui/editoruiview~EditorUIView} view The view of the UI.
+	 */
+	constructor( editor, view ) {
+		super( editor );
+
+		/**
+		 * The main (top–most) view of the editor UI.
+		 *
+		 * @readonly
+		 * @member {module:ui/editorui/editoruiview~EditorUIView} #view
+		 */
+		this.view = view;
+
+		/**
+		 * A normalized `config.toolbar` object.
+		 *
+		 * @type {Object}
+		 * @private
+		 */
+		this._toolbarConfig = normalizeToolbarConfig( editor.config.get( 'toolbar' ) );
+	}
+
+	/**
+	 * Initializes the UI.
+	 */
+	init() {
+		const view = this.view;
+		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().
+			const editableElement = editable.element;
+
+			// Register the editable UI view in the editor. A single editor instance can aggregate multiple
+			// editable areas (roots) but the decoupled editor has only one.
+			this.setEditableElement( editable.name, editableElement );
+
+			// Let the global focus tracker know that the editable UI element is focusable and
+			// belongs to the editor. From now on, the focus tracker will sustain the editor focus
+			// as long as the editable is focused (e.g. the user is typing).
+			this.focusTracker.add( editableElement );
+
+			// Let the editable UI element respond to the changes in the global editor focus
+			// tracker. It has been added to the same tracker a few lines above but, in reality, there are
+			// many focusable areas in the editor, like balloons, toolbars or dropdowns and as long
+			// as they have focus, the editable should act like it is focused too (although technically
+			// 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, '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.
+			editingView.attachDomRoot( editableElement, editable.name );
+		}
+
+		this._initPlaceholder();
+		this._initToolbar();
+		this.fire( 'ready' );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	destroy() {
+		const view = this.view;
+		const editingView = this.editor.editing.view;
+
+		for ( const editable of this.view.editables ) {
+			editingView.detachDomRoot( editable.name );
+		}
+
+		view.destroy();
+
+		super.destroy();
+	}
+
+	/**
+	 * Initializes the inline editor toolbar and its panel.
+	 *
+	 * @private
+	 */
+	_initToolbar() {
+		const editor = this.editor;
+		const view = this.view;
+		const toolbar = view.toolbar;
+
+		toolbar.fillFromConfig( this._toolbarConfig.items, this.componentFactory );
+
+		enableToolbarKeyboardFocus( {
+			origin: editor.editing.view,
+			originFocusTracker: this.focusTracker,
+			originKeystrokeHandler: editor.keystrokes,
+			toolbar
+		} );
+	}
+
+	/**
+	 * Enable the placeholder text on the editing root, if any was configured.
+	 *
+	 * @private
+	 */
+	_initPlaceholder() {
+		const editor = this.editor;
+		const editingView = editor.editing.view;
+
+		for ( const editable of this.view.editables ) {
+			const editingRoot = editingView.document.getRoot( editable.name );
+			const sourceElement = this.getEditableElement( editable.name );
+
+			const placeholderText = editor.config.get( 'placeholder' )[ editable.name ] ||
+				sourceElement && sourceElement.tagName.toLowerCase() === 'textarea' && sourceElement.getAttribute( 'placeholder' );
+
+			if ( placeholderText ) {
+				enablePlaceholder( {
+					view: editingView,
+					element: editingRoot,
+					text: placeholderText,
+					isDirectHost: false
+				} );
+			}
+		}
+	}
+}
+```
+
+## EditorUIView class
+
+Finally, the `*EditorUIView` class is responsible for registering and handling all editables and creating the editor toolbar. The custom `*EditorUIView` class should extend the {@link module:ui/editorui/editoruiview~EditorUIView base `EditorUIView` class}:
+
+```js
+import EditorUIView from '@ckeditor/ckeditor5-ui/src/editorui/editoruiview';
+import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview';
+import ToolbarView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarview';
+import Template from '@ckeditor/ckeditor5-ui/src/template';
+
+/**
+ * The multi-root editor UI view. It is a virtual view providing an inline editable, but without
+ * any specific arrangement of the components in the DOM.
+ *
+ * @extends module:ui/editorui/editoruiview~EditorUIView
+ */
+class MultirootEditorUIView extends EditorUIView {
+	/**
+	 * Creates an instance of the multi-root editor UI view.
+	 *
+	 * @param {module:utils/locale~Locale} locale The {@link module:core/editor/editor~Editor#locale} instance.
+	 * @param {module:engine/view/view~View} editingView The editing view instance this view is related to.
+	 * @param {Object.<String,HTMLElement>} editableElements The list of editable elements, containing name and html element
+	 * for each editable.
+	 */
+	constructor( locale, editingView, editableElements ) {
+		super( locale );
+
+		/**
+		 * The main toolbar of the decoupled editor UI.
+		 *
+		 * @readonly
+		 * @member {module:ui/toolbar/toolbarview~ToolbarView}
+		 */
+		this.toolbar = new ToolbarView( locale );
+
+		/**
+		 * The editables of the multi-root editor UI.
+		 *
+		 * @readonly
+		 * @member {Array.<module:ui/editableui/inline/inlineeditableuiview~InlineEditableUIView>}
+		 */
+		this.editables = [];
+
+		// Create InlineEditableUIView instance for each editable.
+		for ( const editableName of Object.keys( editableElements ) ) {
+			const editable = new InlineEditableUIView( locale, editingView, editableElements[ editableName ] );
+
+			editable.name = editableName;
+			this.editables.push( editable );
+		}
+
+		// This toolbar may be placed anywhere in the page so things like font size need to be reset in it.
+		// Also because of the above, make sure the toolbar supports rounded corners.
+		Template.extend( this.toolbar.template, {
+			attributes: {
+				class: [
+					'ck-reset_all',
+					'ck-rounded-corners'
+				]
+			}
+		} );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	render() {
+		super.render();
+
+		this.registerChild( this.editables );
+		this.registerChild( [ this.toolbar ] );
+	}
+}
+```
+
+## Initializing custom editor instance
+
+Now with the multi-root editor creator ready, the fully usable editor instance can be created.
+
+With HTML like:
+
+```html
+<div id="toolbar"></div>
+
+<header id="header">
+	<h2>Gone traveling</h2>
+	<h3>Monthly travel news and inspiration</h3>
+</header>
+
+<div id="content">
+	<h3>Destination of the Month</h3>
+
+	<h4>Valletta</h4>
+
+	<figure class="image image-style-align-right">
+		<img alt="Picture of a sunlit facade of a Maltan building." src="%BASE_PATH%/assets/img/malta.jpg">
+		<figcaption>It's siesta time in Valletta.</figcaption>
+	</figure>
+
+	<p>The capital city of <a href="https://en.wikipedia.org/wiki/Malta" target="_blank" rel="external">Malta</a> is the top destination this summer. It’s home to a cutting-edge contemporary architecture, baroque masterpieces, delicious local cuisine and at least 8 months of sun. It’s also a top destination for filmmakers, so you can take a tour through locations familiar to you from Game of Thrones, Gladiator, Troy and many more.</p>
+</div>
+
+<div class="demo-row">
+	<div class="demo-row__half">
+		<div id="footer-left">
+			<h3>The three greatest things you learn from traveling</h3>
+			<p><a href="#">Find out more</a></p>
+		</div>
+	</div>
+
+	<div class="demo-row__half">
+		<div id="footer-right">
+			<h3>Walking the capitals of Europe: Warsaw</h3>
+			<p><a href="#">Find out more</a></p>
+		</div>
+	</div>
+</div>
+```
+
+The editor can be initialized with the code below:
+
+```js
+MultirootEditor
+	.create( {
+		header: document.querySelector( '#header' ),
+		content: document.querySelector( '#content' ),
+		footerleft: document.querySelector( '#footer-left' ),
+		footerright: document.querySelector( '#footer-right' )
+	}, {
+		plugins: [ Essentials, Paragraph, Heading, Bold, Italic, List, Link, BlockQuote, Image, ImageCaption,
+			ImageStyle, ImageToolbar, ImageUpload, Table, TableToolbar, MediaEmbed, EasyImage ],
+		toolbar: [ 'heading', '|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'imageUpload', 'blockQuote',
+			'insertTable', 'mediaEmbed', 'undo', 'redo' ],
+		image: {
+			toolbar: [ 'imageTextAlternative', '|', 'imageStyle:alignLeft', 'imageStyle:full', 'imageStyle:alignRight' ],
+			styles: [ 'full', 'alignLeft', 'alignRight' ]
+		},
+		table: {
+			contentToolbar: [
+				'tableColumn',
+				'tableRow',
+				'mergeTableCells'
+			]
+		},
+		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 );
+
+		window.editor = newEditor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );
+```
+
+This will create the exact same editor as the one used on the {@link examples/framework/multi-root-editor multi-root editor editor example page}.

+ 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 CKEditor 5 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 you to observe changes to the data structures and the selection live in the editor, which is particularly helpful when developing new rich-text editor features or getting to understand the existing ones.
+
+{@img assets/img/framework-development-tools-inspector.jpg Screenshot of the CKEditor 5 inspector attached to a WYSIWYG 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:
+
+```bash
+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 CKEditor 5 instances at a time by calling `CKEditorInspector.attach()` for each one of them. Then you can switch the inspector context to inspect different editor instances.
+
+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 developer utilities} and {@link module:engine/dev-utils/view view developer utilities} are useful development helpers.
+
+They allow for "stringifying" the {@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 for 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.

+ 31 - 292
docs/framework/guides/quick-start.md

@@ -5,11 +5,11 @@ 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 CKEditor 5 rich-text editor from source.
 
 ## How to install the framework?
 
-The framework is made of several [npm packages](https://npmjs.com). To install it you need:
+The CKEditor 5 Framework is made of several [npm packages](https://npmjs.com). To install it you need:
 
 * [Node.js](https://nodejs.org/en/) 6.9.0+
 * npm 4+ (**note:** some npm 5+ versions were known to cause [problems](https://github.com/npm/npm/issues/16991), especially with deduplicating packages; upgrade npm when in doubt)
@@ -22,7 +22,7 @@ Besides Node.js and npm you also need [webpack@4](https://webpack.js.org) with a
 
 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 CKEditor 5.
+First, install packages needed to build CKEditor 5:
 
 ```bash
 npm install --save \
@@ -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
 						} )
-					},
+					}
 				]
 			}
 		]
@@ -95,7 +96,9 @@ module.exports = {
 };
 ```
 
-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.
+## Creating an editor
+
+You can now install some of the CKEditor 5 Framework packages which will allow you to initialize a simple rich-text editor. You can start with the {@link examples/builds/classic-editor classic editor} with a small set of features.
 
 ```bash
 npm install --save \
@@ -116,7 +119,7 @@ Based on these packages you can create a simple application.
 <info-box warning>
 	Note that in this guide the editor class is used directly (i.e. we use `@ckeditor/ckeditor5-editor-classic` instead of `@ckeditor/ckeditor5-build-classic`).
 
-	We do not use any of the {@link builds/guides/overview builds} because adding new plugins to them requires rebuilding them anyway. This can be done by {@link builds/guides/integration/installing-plugins customizing a build} or by including CKEditor 5 source into your application (like in this guide).
+	No {@link builds/guides/overview editor builds} are used because adding new plugins to them requires rebuilding them anyway. This can be done by {@link builds/guides/integration/installing-plugins customizing a build} or by including CKEditor 5 source into your application (like in this guide).
 </info-box>
 
 ```js
@@ -147,30 +150,28 @@ You can now run webpack to build the application. To do that, call the `webpack`
 ./node_modules/.bin/webpack --mode development
 ```
 
-<info-box>
-	You can also install `webpack-cli` globally (using `npm install -g`) and run it via a globally available `webpack`.
+You can also install `webpack-cli` globally (using `npm install -g`) and run it via a globally available `webpack`.
 
-	Alternatively, you can add it as an [npm script](https://docs.npmjs.com/misc/scripts):
+Alternatively, you can add it as an [npm script](https://docs.npmjs.com/misc/scripts):
 
-	```js
-	"scripts": {
-		"build": "webpack --mode development"
-	}
-	```
+```js
+"scripts": {
+	"build": "webpack --mode development"
+}
+```
 
-	And use it via:
+And use it with:
 
-	```bash
-	yarn run build
-	```
+```bash
+yarn run build
+```
 
-	npm adds `./node_modules/.bin/` to the `PATH` automatically, so in this case you do not need to install `webpack-cli` globally.
-</info-box>
+npm adds `./node_modules/.bin/` to the `PATH` automatically, so in this case you do not need to install `webpack-cli` globally.
 
 <info-box>
-	Use `webpack --mode production` if you want to build a minified and optimized application. See more at https://webpack.js.org/concepts/mode/.
+	Use `webpack --mode production` if you want to build a minified and optimized application. See more in the [webpack documentation](https://webpack.js.org/concepts/mode/).
 
-	**Note:** Prior to version 1.2.7 `uglifyjs-webpack-plugin` (the default minifier used by webpack) had a bug which caused webpack to crash with the following error: `TypeError: Assignment to constant variable.`. If you experienced this error, make sure that your `node_modules` contains an up to date version of this package (and that webpack uses this version).
+	**Note:** Prior to version 1.2.7, `uglifyjs-webpack-plugin` (the default minifier used by webpack) had a bug which caused webpack to crash with the following error: `TypeError: Assignment to constant variable.`. If you experienced this error, make sure that your `node_modules` contains an up-to-date version of this package (and that webpack uses this version).
 </info-box>
 
 If everything worked correctly, you should see:
@@ -190,6 +191,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
@@ -209,280 +212,16 @@ Finally, it is time to create an HTML page:
 </html>
 ```
 
-Open this page in your browser and you should see the editor up and running. Make sure to check the browser console in case anything seems wrong.
-
-{@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
+Open this page in your browser and you should see the simple WYSIWYG editor up and running. Make sure to check the browser console in case anything seems wrong.
 
-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
-```
+{@img assets/img/framework-quick-start-classic-editor.png 976 Screenshot of CKEditor 5 classic editor with bold and italic features.}
 
 <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.
+	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>
 
-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';
-
-// ...
+## What's next?
 
-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 );
-	} );
-```
+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 the CKEditor 5 architecture, check out the {@link framework/guides/architecture/intro Introduction to CKEditor 5 architecture}.

+ 1 - 1
docs/framework/guides/support/license-and-legal.md

@@ -10,7 +10,7 @@ order: 20
 
 The following legal notices apply to CKEditor 5 Framework and all software from CKEditor 5 Ecosystem included with it.
 
-Copyright (c) 2003-2018, CKSource Frederico Knabben. All rights reserved.
+Copyright (c) 2003-2019, CKSource Frederico Knabben. All rights reserved.
 
 Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/old-licenses/gpl-2.0.html).
 

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

@@ -0,0 +1,1216 @@
+---
+category: framework-tutorials
+order: 10
+---
+
+# Implementing a block widget
+
+In this tutorial, you will learn how to implement a more complex CKEditor 5 plugin. 
+
+You 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. You will use the widget utilities and work with the model-view conversion in order to properly set up the behavior of this feature. Later on, you will create a UI which will allow for inserting new simple boxes into the document with the toolbar button.
+
+<info-box>
+	If you want to see the final product of this tutorial before you plunge in, check out the [demo](#demo).
+</info-box>
+
+<!-- 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 dive into this tutorial.
+
+The tutorial will also reference various parts of the {@link framework/guides/architecture/intro CKEditor 5 architecture} section as you go. While reading them is not necessary to finish this tutorial, it is recommended to read these 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 set up 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 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 }
+};
+```
+
+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, build your project:
+
+```bash
+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
+```
+
+And now see if everything worked well by opening the index page in your browser. 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 you can start implementing the plugin. The entire plugin code can be kept in a single file, however, it is recommended to split its "editing" and "UI" layers and create a master plugin which loads both. This way, you ensure better separation of concerns and allow for 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, you will split the code of commands, buttons and other "self-contained" components to separate files, too. In order not to mix up these files with your project's `app.js` and `webpack.config.js` files, 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 goes here as well
+│
+└── webpack.config.js
+```
+
+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, you need to load the `SimpleBox` plugin in your `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 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 one of its possible representations.
+
+<info-box>
+	Read more about the {@link framework/guides/architecture/editing-engine#overview editing engine architecture}.
+</info-box>
+
+Since your simple box feature is meant to be a box with a title and description fields, define its model representation like this:
+
+```html
+<simpleBox>
+	<simpleBoxTitle></simpleBoxTitle>
+	<simpleBoxDescription></simpleBoxDescription>
+</simpleBox>
+```
+
+### Defining the schema
+
+You need to start with defining the model's schema. You need to define 3 elements and their types as well as allowed parent/children.
+
+<info-box>
+	Read more about the {@link framework/guides/architecture/editing-engine#schema schema}.
+</info-box>
+
+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 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 you need to define model-view converters. Do that now!
+
+### 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 the 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 you need to think about how you want to render the `<simpleBox>` element and its children to the DOM (what the 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, use the same representation in both pipelines for now.
+
+The structure in the view that you want to achieve:
+
+```html
+<section class="simple-box">
+	<h1 class="simple-box-title"></h1>
+	<div class="simple-box-description"></div>
+</section>
+```
+
+Use the {@link module:engine/conversion/conversion~Conversion#elementToElement `conversion.elementToElement()`} method to define all the converters.
+
+<info-box>
+	You can use this high-level two-way converters definition because you 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 you will switch to more fine-grained converters to get more control over the conversion.
+</info-box>
+
+You need to define converters for 3 model elements. 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 you have converters, you can try to see the simple box in action. You have not defined a way to insert a new simple box into the document yet, so load it via the editor data. In order to do that, you 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 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 the CKEditor 5 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 learn that, use 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
+
+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; this is the selection position.
+
+Play a bit with the editor features (bold, italic, headings, lists, selection) to see how the model structure changes.
+
+<info-box>
+	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
+
+It is time to check if the simple box behaves like you 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. This 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). This is because it allows only the content that is allowed in other block elements (like paragraphs). You can, however, apply italic inside the 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. This 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". This is because you have not defined the view behavior yet.
+
+Pretty cool so far, right? With a very little code, you were able to define the behavior of your simple box plugin which maintains the integrity of these elements. The engine ensures that the user does not break these instances.
+
+See what else you can improve.
+
+### Making simple box a widget
+
+<info-box>
+	If you are familiar with the {@link @ckeditor4 guide/dev/deep_dive/widgets/README Widget System of CKEditor 4} you will notice significant differences in how widgets are implemented in CKEditor 5.
+
+	CKEditor 4 implementation exposes a declarative API that 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 utilities provided by CKEditor 5 Framework.
+
+	CKEditor 5 implementation is, therefore, open for extensions and recomposition. You can choose the behaviors that you want (just like you did so far in this tutorial by defining a schema) and skip others or implement them by yourself.
+</info-box>
+
+The converters that you defined convert the model `<simpleBox*>` elements to plain {@link module:engine/view/containerelement~ContainerElement `ContainerElement`}s in the view (and back during upcasting).
+
+You 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()`} utilities. You do not want to affect the data view, though. Therefore, you 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 you start coding, you need to install the {@link api/widget `@ckeditor/ckeditor5-widget`} package:
+
+```bash
+npm install --save @ckeditor/ckeditor5-widget
+```
+
+Now it is time to revisit the `_defineConverters()` method that you defined earlier. You will use the {@link module:engine/conversion/upcasthelpers~UpcastHelpers#elementToElement `elementToElement()` upcast helper} and the {@link module:engine/conversion/downcasthelpers~DowncastHelpers#elementToElement `elementToElement()` downcast helper} instead of the two-way `elementToElement()` converter helper.
+
+Additionally, you 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: You 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: You 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 you used lower-level converters. We plan to provide more handy widget conversion utilities 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 the 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 the element through `toWidget()` will make its content non-editable. Conversely, passing it through `toWidgetEditable()` will make its content editable again.
+* You can now click 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" the simple box. This is thanks to using `toWidget()` and `toNestedEditable()` only in the `editingDowncast` pipeline.
+
+This is all that you need from the model and the view layers for now. In terms of "editability" and data input/output it is fully functional. 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 these 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 the simple box the situation is simple:
+
+* you need an "insert a new simple box" action,
+* and "can you insert a new simple box here (at the current selection position)".
+
+Create a new file `insertsimpleboxcommand.js` in the `simplebox/` directory. You will use the {@link module:engine/model/model~Model#insertContent `model.insertContent()`} method which will be able to, for example, 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 that 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( selection.getFirstPosition(), 'simpleBox' );
+
+		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.
+
+Change one more thing before you move forward &mdash; 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 {
+	static get requires() {
+		return [ Widget ];
+	}
+
+	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 the editor users to 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 into some particular position of the selection ([as defined in the schema](#defining-the-schema)).
+
+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 the 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, SimpleBox ],
+		// Insert the "simpleBox" button into 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.}
+
+
+## Demo
+
+You can see the block widget implementation in action in the editor below. You can also check out the full [source code](#full-source-code) of this tutorial if you want to develop your own block widgets.
+
+{@snippet framework/tutorials/block-widget}
+
+## Full source code
+
+The following code contains a complete implementation of the `SimpleBox` plugin (and all its dependencies) and the code to run the editor. You can paste it into the [`app.js`](#plugin-structure) file and it will run out–of–the–box:
+
+```js
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import Essentials from '@ckeditor/ckeditor5-essentials/src/essentials';
+import Bold from '@ckeditor/ckeditor5-basic-styles/src/bold';
+import Italic from '@ckeditor/ckeditor5-basic-styles/src/italic';
+import Heading from '@ckeditor/ckeditor5-heading/src/heading';
+import List from '@ckeditor/ckeditor5-list/src/list';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+
+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 Command from '@ckeditor/ckeditor5-core/src/command';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+
+class SimpleBox extends Plugin {
+	static get requires() {
+		return [ SimpleBoxEditing, SimpleBoxUI ];
+	}
+}
+
+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 the 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;
+		} );
+	}
+}
+
+class SimpleBoxEditing extends Plugin {
+	static get requires() {
+		return [ Widget ];
+	}
+
+	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'
+		} );
+
+		schema.addChildCheck( ( context, childDefinition ) => {
+			if ( context.endsWith( 'simpleBoxDescription' ) && childDefinition.name == 'simpleBox' ) {
+				return false;
+			}
+		} );
+	}
+
+	_defineConverters() {
+		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: You 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: You use a more specialized createEditableElement() method here.
+				const div = viewWriter.createEditableElement( 'div', { class: 'simple-box-description' } );
+
+				return toWidgetEditable( div, viewWriter );
+			}
+		} );
+	}
+}
+
+class InsertSimpleBoxCommand extends Command {
+	execute() {
+		this.editor.model.change( writer => {
+			// Insert <simpleBox>*</simpleBox> at the current selection position
+			// in a way that 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( selection.getFirstPosition(), 'simpleBox' );
+
+		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;
+}
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Essentials, Bold, Italic, Heading, List, Paragraph, SimpleBox ],
+		toolbar: [ 'heading', '|', 'bold', 'italic', 'numberedList', 'bulletedList', 'simpleBox' ],
+	} )
+	.then( editor => {
+		console.log( 'Editor was initialized', editor );
+
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );
+```

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

@@ -0,0 +1,944 @@
+---
+category: framework-tutorials
+order: 10
+---
+
+# Implementing an inline widget
+
+In this tutorial, you will learn how to implement an inline widget.
+
+You will build a "placeholder" feature that allows the users to insert predefined placeholders, like a date or a surname, into the document. You will use widget utilities and conversion in order to define the behavior of this feature. Later on, you will use dropdown utilities to create a dropdown that will allow for inserting new placeholders. You will also learn how to use the editor configuration to define allowed placeholder names.
+
+<info-box>
+	If you want to see the final product of this tutorial before you plunge in, check out the [demo](#demo).
+</info-box>
+
+## Before you start ⚠️
+
+This guide assumes that you are familiar with the widgets concept introduced in the {@link framework/guides/tutorials/implementing-a-block-widget Implementing a block widget} tutorial. The tutorial will also reference various concepts from the {@link framework/guides/architecture/intro CKEditor 5 architecture}.
+
+## Bootstrapping the project
+
+The overall project structure will be similar to one 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 of the "Implementing a block widget" tutorial.
+
+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 you still need to define the `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 the 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 you 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 into other editor blocks, like `<paragraph>`, that allow text. The placeholder will have a `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`. You want to allow it wherever the `$text` is allowed so you add `allowWhere: '$text'`. Finally, you will also need the `name` attribute.
+
+You will also use this opportunity 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 act as an inline node:
+			isInline: true,
+
+			// The inline widget is self-contained so it 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 you 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 be the placeholder's name.
+
+```html
+<span class="placeholder">{name}</span>
+```
+
+* **Upcast conversion**. This view-to-model converter will look for `<span>`s with the `placeholder` class, read the `<span>`'s text and create 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 ];
+	}
+
+	init() {
+		console.log( 'PlaceholderEditing#init() got called' );
+
+		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 a placeholder element inside the 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 */
+
+.placeholder {
+	background: #ffff00;
+	padding: 4px 2px;
+	outline-offset: -2px;
+	line-height: 1em;
+	margin: 0 1px;
+}
+
+.placeholder::selection {
+	display: none;
+}
+```
+
+### Command
+
+The {@link framework/guides/architecture/core-editor-architecture#commands command} for the placeholder feature will insert a `<placeholder>` element (if allowed by the schema) at the selection. The command will accept the `options.value` parameter (other CKEditor 5 commands also use this pattern) to set the placeholder 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 a <placeholder> elment with the "name" attribute...
+			const placeholder = writer.createElement( 'placeholder', { name: value } );
+
+			// ... and insert it into the document.
+			editor.model.insertContent( placeholder );
+
+			// Put the selection on the 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 the editor 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 {
+	static get requires() {
+		return [ Widget ];
+	}
+
+	init() {
+		console.log( 'PlaceholderEditing#init() got called' );
+
+		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. 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 in CKEditor 5 WYSIWYG editor.}
+
+### Fixing position mapping
+
+If you play more with the widget (e.g. try to select it by dragging the mouse from its right to the left edge) you will see the following error logged to 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 could 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 utility {@link module:widget/utils~viewToModelPositionOutsideModelElement `viewToModelPositionOutsideModelElement()`} that you 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 {
+	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 ) );
+
+		// 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 the `<placeholder>` in the model.
+
+## Creating the UI
+
+The UI part will provide a dropdown button from which the user can select a placeholder to insert into the editor.
+
+CKEditor 5 Framework includes helpers to create different {@link framework/guides/architecture/ui-library#dropdowns dropdowns} like toolbar or list dropdowns.
+
+In this tutorial, you will create a dropdown with a 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 the 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 item 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 into the editor toolbar.
+		toolbar: [ 'heading', 'bold', 'italic', 'numberedList', 'bulletedList', '|', 'placeholder' ]
+	} )
+	.then( editor => {
+		// ...
+	} )
+	.catch( error => {
+		// ...
+	} );
+```
+
+To make this plugin extensible, placeholder types will be read from the editor configuration.
+
+The first step is to define the placeholder configuration in the editing plugin:
+
+```js
+// ... imports
+
+export default class PlaceholderEditing extends Plugin {
+	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( 'placeholderConfig', {                           // ADDED
+			types: [ 'date', 'first name', 'surname' ]
+		} );
+	}
+
+	_defineConverters() {
+		// ...
+	}
+
+	_defineSchema() {
+		// ...
+	}
+}
+```
+
+Now 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( 'placeholderConfig.types' );            // CHANGED
+
+		editor.ui.componentFactory.add( 'placeholder', locale => {
+			// ...
+		} );
+	}
+}
+```
+
+The plugin is now ready to accept the configuration. Check how this works by adding the `placeholderConfig` configuration in the 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' ],
+		placeholderConfig: {
+			types: [ 'date', 'color', 'first name', 'surname' ]                             // ADDED
+		}
+	} )
+	// ...
+```
+
+If you open the dropdown in the toolbar, you will see a 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 in CKEditor 5 WYSIWYG editor.}
+
+## Demo
+
+You can see the placeholder widget implementation in action in the editor below. You can also check out the full [source code](#full-source-code) of this tutorial if you want to develop your own inline widgets.
+
+{@snippet framework/tutorials/inline-widget}
+
+## Full source code
+
+The following code snippet contains a complete implementation of the `Placeholder` plugin (and all its dependencies) and the code to run the editor. You can paste it into the `app.js` file and it will run out–of–the–box:
+
+```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 Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import { toWidget, viewToModelPositionOutsideModelElement } from '@ckeditor/ckeditor5-widget/src/utils';
+import Widget from '@ckeditor/ckeditor5-widget/src/widget';
+import Command from '@ckeditor/ckeditor5-core/src/command';
+
+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';
+
+class Placeholder extends Plugin {
+	static get requires() {
+		return [ PlaceholderEditing, PlaceholderUI ];
+	}
+}
+
+class PlaceholderCommand extends Command {
+	execute( { value } ) {
+		const editor = this.editor;
+
+		editor.model.change( writer => {
+			// Create a <placeholder> elment with the "name" attribute...
+			const placeholder = writer.createElement( 'placeholder', { name: value } );
+
+			// ... and insert it into the document.
+			editor.model.insertContent( placeholder );
+
+			// Put the selection on the 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;
+	}
+}
+
+class PlaceholderUI extends Plugin {
+	init() {
+		const editor = this.editor;
+		const t = editor.t;
+		const placeholderNames = editor.config.get( 'placeholderConfig.types' );
+
+		// The "placeholder" dropdown must be registered among the 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 item 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;
+}
+
+class PlaceholderEditing extends Plugin {
+	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( 'placeholderConfig', {
+			types: [ 'date', 'first name', 'surname' ]
+		} );
+	}
+
+	_defineSchema() {
+		const schema = this.editor.model.schema;
+
+		schema.register( 'placeholder', {
+			// Allow wherever text is allowed:
+			allowWhere: '$text',
+
+			// The placeholder will act as an inline node:
+			isInline: true,
+
+			// The inline widget is self-contained so it cannot be split by the caret and it can be selected:
+			isObject: true,
+
+			// The placeholder can have many types, like date, name, surname, etc:
+			allowAttributes: [ 'name' ]
+		} );
+	}
+
+	_defineConverters() {
+		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 a placeholder element inside the 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;
+		}
+	}
+}
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Essentials, Paragraph, Heading, List, Bold, Italic, Placeholder ],
+		toolbar: [ 'heading', '|', 'bold', 'italic', 'numberedList', 'bulletedList', '|', 'placeholder' ],
+		placeholderConfig: {
+			types: [ 'date', 'color', 'first name', 'surname' ]
+		}
+	} )
+	.then( editor => {
+		console.log( 'Editor was initialized', editor );
+
+		// Expose for playing in the console.
+		window.editor = editor;
+	} )
+	.catch( error => {
+		console.error( error.stack );
+	} );
+```

+ 1 - 0
docs/framework/index.md

@@ -2,6 +2,7 @@
 category: framework
 order: 10
 toc: false
+feedback-widget: false
 meta-title: CKEditor 5 Framework documentation
 meta-description: Learn how to install, integrate, configure and develop CKEditor 5 Framework. Browse through API documentation and online samples.
 ---

+ 1 - 0
docs/index.md

@@ -1,6 +1,7 @@
 ---
 toc: false
 sitenav: false
+feedback-widget: false
 meta-title: CKEditor 5 documentation
 meta-description: Learn how to install, integrate, configure and develop CKEditor 5. Browse through API documentation and online samples.
 ---

+ 20 - 4
docs/umberto.json

@@ -15,13 +15,16 @@
 		"framework/guides/ui/external-ui.html": "framework/guides/deep-dive/ui/external-ui.html",
 		"framework/guides/ui/document-editor.html": "framework/guides/deep-dive/ui/document-editor.html",
 		"features/collaboration/overview.html": "features/collaboration/real-time-collaboration/real-time-collaboration.html",
-		"features/collaboration/collaborative-editing.html": "features/collaboration/real-time-collaboration/real-time-collaborative-editing.html",
-		"features/collaboration/collaborative-comments.html": "features/collaboration/real-time-collaboration/real-time-collaborative-comments.html",
+		"features/collaboration/collaborative-editing.html": "features/collaboration/real-time-collaboration/real-time-collaboration-integration.html",
+		"features/collaboration/collaborative-comments.html": "features/collaboration/real-time-collaboration/real-time-collaboration-integration.html",
 		"features/collaboration/presence-list.html": "features/collaboration/real-time-collaboration/users-in-real-time-collaboration.html",
 		"features/collaboration/comment-only-mode.html": "features/collaboration/comments/comment-only-mode.html",
+		"features/collaboration/real-time-collaboration/real-time-collaborative-comments.html": "features/collaboration/real-time-collaboration/real-time-collaboration-integration.html",
+		"features/collaboration/real-time-collaboration/real-time-collaborative-editing.html": "features/collaboration/real-time-collaboration/real-time-collaboration-integration.html",
 		"features/image-upload.html": "features/image-upload/image-upload.html",
 		"features/ckfinder.html": "features/image-upload/ckfinder.html",
-		"features/easy-image.html": "features/image-upload/easy-image.html"
+		"features/easy-image.html": "features/image-upload/easy-image.html",
+		"features/collaboration/comments/integrate-comments-with-application.html": "features/collaboration/comments/comments-integration.html"
 	},
 	"scripts": {
 		"snippet-adapter": "../scripts/docs/snippetadapter",
@@ -124,6 +127,13 @@
 							"slug": "architecture",
 							"order": 100
 						},
+						{
+							"name": "Tutorials",
+							"id": "framework-tutorials",
+							"slug": "tutorials",
+							"order": 200
+						},
+
 						{
 							"name": "Deep dive",
 							"id": "framework-deep-dive",
@@ -187,11 +197,17 @@
 							"slug": "comments",
 							"order": 100
 						},
+						{
+							"name": "Track changes",
+							"id": "features-track-changes",
+							"slug": "track-changes",
+							"order": 200
+						},
 						{
 							"name": "Real-time collaboration",
 							"id": "features-real-time-collaboration",
 							"slug": "real-time-collaboration",
-							"order": 200
+							"order": 300
 						}
 					]
 				},

+ 7 - 1
mgit.json

@@ -13,6 +13,7 @@
     "@ckeditor/ckeditor5-basic-styles": "ckeditor/ckeditor5-basic-styles",
     "@ckeditor/ckeditor5-block-quote": "ckeditor/ckeditor5-block-quote",
     "@ckeditor/ckeditor5-build-balloon": "ckeditor/ckeditor5-build-balloon",
+    "@ckeditor/ckeditor5-build-balloon-block": "ckeditor/ckeditor5-build-balloon-block",
     "@ckeditor/ckeditor5-build-classic": "ckeditor/ckeditor5-build-classic",
     "@ckeditor/ckeditor5-build-decoupled-document": "ckeditor/ckeditor5-build-decoupled-document",
     "@ckeditor/ckeditor5-build-inline": "ckeditor/ckeditor5-build-inline",
@@ -32,12 +33,15 @@
     "@ckeditor/ckeditor5-heading": "ckeditor/ckeditor5-heading",
     "@ckeditor/ckeditor5-highlight": "ckeditor/ckeditor5-highlight",
     "@ckeditor/ckeditor5-image": "ckeditor/ckeditor5-image",
+    "@ckeditor/ckeditor5-indent": "ckeditor/ckeditor5-indent",
     "@ckeditor/ckeditor5-link": "ckeditor/ckeditor5-link",
     "@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-remove-format": "ckeditor/ckeditor5-remove-format",
     "@ckeditor/ckeditor5-table": "ckeditor/ckeditor5-table",
     "@ckeditor/ckeditor5-theme-lark": "ckeditor/ckeditor5-theme-lark",
     "@ckeditor/ckeditor5-typing": "ckeditor/ckeditor5-typing",
@@ -45,7 +49,9 @@
     "@ckeditor/ckeditor5-undo": "ckeditor/ckeditor5-undo",
     "@ckeditor/ckeditor5-upload": "ckeditor/ckeditor5-upload",
     "@ckeditor/ckeditor5-utils": "ckeditor/ckeditor5-utils",
-    "@ckeditor/ckeditor5-widget": "ckeditor/ckeditor5-widget"
+    "@ckeditor/ckeditor5-widget": "ckeditor/ckeditor5-widget",
+    "@ckeditor/ckeditor5-watchdog": "ckeditor/ckeditor5-watchdog",
+    "@ckeditor/ckeditor5-word-count": "ckeditor/ckeditor5-word-count"
   },
   "overrideDirectoryNames": {
     "@ckeditor/ckeditor-cloud-services-core": "ckeditor5-cloud-services-core"

+ 79 - 60
package.json

@@ -1,6 +1,6 @@
 {
   "name": "ckeditor5",
-  "version": "11.2.0",
+  "version": "12.3.1",
   "description": "The development environment of CKEditor 5 – the best browser-based rich text editor.",
   "private": true,
   "keywords": [
@@ -21,80 +21,89 @@
     "framework"
   ],
   "dependencies": {
-    "@ckeditor/ckeditor-cloud-services-core": "^0.2.0",
-    "@ckeditor/ckeditor5-adapter-ckfinder": "^10.0.4",
-    "@ckeditor/ckeditor5-alignment": "^10.0.4",
-    "@ckeditor/ckeditor5-autoformat": "^10.0.4",
-    "@ckeditor/ckeditor5-autosave": "^10.0.2",
-    "@ckeditor/ckeditor5-basic-styles": "^10.1.0",
-    "@ckeditor/ckeditor5-block-quote": "^10.1.1",
-    "@ckeditor/ckeditor5-build-balloon": "^11.2.0",
-    "@ckeditor/ckeditor5-build-classic": "^11.2.0",
-    "@ckeditor/ckeditor5-build-decoupled-document": "^11.2.0",
-    "@ckeditor/ckeditor5-build-inline": "^11.2.0",
-    "@ckeditor/ckeditor5-clipboard": "^10.0.4",
-    "@ckeditor/ckeditor5-cloud-services": "^10.1.1",
-    "@ckeditor/ckeditor5-ckfinder": "^10.0.0",
-    "@ckeditor/ckeditor5-core": "^11.1.0",
-    "@ckeditor/ckeditor5-easy-image": "^10.0.4",
-    "@ckeditor/ckeditor5-editor-balloon": "^11.0.2",
-    "@ckeditor/ckeditor5-editor-classic": "^11.0.2",
-    "@ckeditor/ckeditor5-editor-decoupled": "^11.0.2",
-    "@ckeditor/ckeditor5-editor-inline": "^11.0.2",
-    "@ckeditor/ckeditor5-engine": "^12.0.0",
-    "@ckeditor/ckeditor5-enter": "^10.1.3",
-    "@ckeditor/ckeditor5-essentials": "^10.1.3",
-    "@ckeditor/ckeditor5-font": "^10.0.4",
-    "@ckeditor/ckeditor5-heading": "^10.1.1",
-    "@ckeditor/ckeditor5-highlight": "^10.0.4",
-    "@ckeditor/ckeditor5-image": "^12.0.0",
-    "@ckeditor/ckeditor5-link": "^10.1.0",
-    "@ckeditor/ckeditor5-list": "^11.0.3",
-    "@ckeditor/ckeditor5-markdown-gfm": "^10.0.4",
-    "@ckeditor/ckeditor5-media-embed": "^10.1.0",
-    "@ckeditor/ckeditor5-paragraph": "^10.0.4",
-    "@ckeditor/ckeditor5-paste-from-office": "^10.0.0",
-    "@ckeditor/ckeditor5-table": "^11.0.1",
-    "@ckeditor/ckeditor5-theme-lark": "^12.0.0",
-    "@ckeditor/ckeditor5-typing": "^11.0.2",
-    "@ckeditor/ckeditor5-ui": "^11.2.0",
-    "@ckeditor/ckeditor5-undo": "^10.0.4",
-    "@ckeditor/ckeditor5-upload": "^10.0.4",
-    "@ckeditor/ckeditor5-utils": "^11.1.0",
-    "@ckeditor/ckeditor5-widget": "^10.3.1"
+    "@ckeditor/ckeditor-cloud-services-core": "^4.0.0",
+    "@ckeditor/ckeditor5-adapter-ckfinder": "^11.0.4",
+    "@ckeditor/ckeditor5-alignment": "^11.1.3",
+    "@ckeditor/ckeditor5-autoformat": "^11.0.4",
+    "@ckeditor/ckeditor5-autosave": "^11.0.4",
+    "@ckeditor/ckeditor5-basic-styles": "^11.1.3",
+    "@ckeditor/ckeditor5-block-quote": "^11.1.2",
+    "@ckeditor/ckeditor5-build-balloon": "^12.3.1",
+    "@ckeditor/ckeditor5-build-balloon-block": "^12.3.1",
+    "@ckeditor/ckeditor5-build-classic": "^12.3.1",
+    "@ckeditor/ckeditor5-build-decoupled-document": "^12.3.1",
+    "@ckeditor/ckeditor5-build-inline": "^12.3.1",
+    "@ckeditor/ckeditor5-ckfinder": "^11.0.4",
+    "@ckeditor/ckeditor5-clipboard": "^12.0.1",
+    "@ckeditor/ckeditor5-cloud-services": "^11.0.4",
+    "@ckeditor/ckeditor5-core": "^12.2.1",
+    "@ckeditor/ckeditor5-easy-image": "^11.0.4",
+    "@ckeditor/ckeditor5-editor-balloon": "^12.2.1",
+    "@ckeditor/ckeditor5-editor-classic": "^12.1.3",
+    "@ckeditor/ckeditor5-editor-decoupled": "^12.2.1",
+    "@ckeditor/ckeditor5-editor-inline": "^12.2.1",
+    "@ckeditor/ckeditor5-engine": "^13.2.1",
+    "@ckeditor/ckeditor5-enter": "^11.0.4",
+    "@ckeditor/ckeditor5-essentials": "^11.0.4",
+    "@ckeditor/ckeditor5-font": "^11.2.1",
+    "@ckeditor/ckeditor5-heading": "^11.0.4",
+    "@ckeditor/ckeditor5-highlight": "^11.0.4",
+    "@ckeditor/ckeditor5-image": "^13.1.2",
+    "@ckeditor/ckeditor5-indent": "^10.0.1",
+    "@ckeditor/ckeditor5-link": "^11.1.1",
+    "@ckeditor/ckeditor5-list": "^12.0.4",
+    "@ckeditor/ckeditor5-markdown-gfm": "^11.0.4",
+    "@ckeditor/ckeditor5-media-embed": "^11.1.3",
+    "@ckeditor/ckeditor5-mention": "^12.0.1",
+    "@ckeditor/ckeditor5-paragraph": "^11.0.4",
+    "@ckeditor/ckeditor5-paste-from-office": "^11.0.4",
+    "@ckeditor/ckeditor5-remove-format": "^10.0.3",
+    "@ckeditor/ckeditor5-table": "^13.0.2",
+    "@ckeditor/ckeditor5-theme-lark": "^14.1.1",
+    "@ckeditor/ckeditor5-typing": "^12.1.1",
+    "@ckeditor/ckeditor5-ui": "^13.0.2",
+    "@ckeditor/ckeditor5-undo": "^11.0.4",
+    "@ckeditor/ckeditor5-upload": "^11.1.1",
+    "@ckeditor/ckeditor5-utils": "^13.0.1",
+    "@ckeditor/ckeditor5-watchdog": "^10.0.1",
+    "@ckeditor/ckeditor5-widget": "^11.0.4",
+    "@ckeditor/ckeditor5-word-count": "^10.0.1"
   },
   "devDependencies": {
-    "@ckeditor/ckeditor5-real-time-collaboration": "^12.0.1",
-    "@ckeditor/ckeditor5-comments": "^1.0.1",
-    "@ckeditor/ckeditor5-dev-docs": "^10.0.3",
-    "@ckeditor/ckeditor5-dev-env": "^13.0.2",
-    "@ckeditor/ckeditor5-dev-tests": "^15.0.1",
-    "@ckeditor/ckeditor5-dev-utils": "^11.0.1",
-    "@ckeditor/ckeditor5-dev-webpack-plugin": "^7.0.1",
+    "@ckeditor/ckeditor5-comments": "^3.2.0",
+    "@ckeditor/ckeditor5-dev-docs": "^11.0.2",
+    "@ckeditor/ckeditor5-dev-env": "^14.1.1",
+    "@ckeditor/ckeditor5-dev-tests": "^16.4.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.3.0",
+    "@ckeditor/ckeditor5-track-changes": "^1.0.0",
     "css-loader": "^1.0.0",
     "eslint": "^5.5.0",
-    "eslint-config-ckeditor5": "^1.0.9",
+    "eslint-config-ckeditor5": "^2.0.0",
     "glob": "^7.1.2",
-    "husky": "^0.14.3",
+    "husky": "^1.3.1",
     "lint-staged": "^7.0.0",
     "mgit2": "^0.10.0",
     "mini-css-extract-plugin": "^0.4.0",
     "minimatch": "^3.0.4",
     "postcss-loader": "^3.0.0",
+    "progress-bar-webpack-plugin": "^1.12.1",
     "raw-loader": "^1.0.0",
     "style-loader": "^0.23.0",
     "svgo": "^1.1.0",
     "uglifyjs-webpack-plugin": "^1.2.7",
-    "umberto": "^0.14.0",
+    "umberto": "^1.0.0",
     "webpack": "^4.15.0"
   },
   "engines": {
-    "node": ">=6.9.0",
-    "npm": ">=3.0.0"
+    "node": ">=8.0.0",
+    "npm": ">=5.7.1"
   },
   "author": "CKSource (http://cksource.com/)",
   "license": "GPL-2.0-or-later",
-  "homepage": "http://ckeditor.com",
+  "homepage": "https://ckeditor.com/ckeditor-5",
   "bugs": "https://github.com/ckeditor/ckeditor5/issues",
   "repository": {
     "type": "git",
@@ -102,14 +111,13 @@
   },
   "scripts": {
     "lint": "eslint --quiet '**/*.js'",
-    "precommit": "lint-staged",
     "test": "node --max_old_space_size=4096 node_modules/@ckeditor/ckeditor5-dev-tests/bin/test.js",
     "manual": "node --max_old_space_size=4096 node_modules/@ckeditor/ckeditor5-dev-tests/bin/test-manual.js",
     "bootstrap": "mgit sync && yarn install",
     "clean": "rm -rf node_modules && mgit exec 'rm -rf node_modules'",
     "reset": "rm -rf ./packages ./node_modules && yarn run bootstrap",
     "reinstall": "yarn run clean && yarn run bootstrap",
-    "docs": "node ./scripts/docs/build-docs.js",
+    "docs": "node --max-old-space-size=4096 ./scripts/docs/build-docs.js",
     "docs:api": "node ./scripts/docs/build-api-docs.js",
     "docs:build-and-publish": "node ./scripts/docs/build-and-publish.js",
     "docs:build-and-publish-nightly": "node ./scripts/docs/build-and-publish-nightly.js",
@@ -133,7 +141,18 @@
     "coverage/**",
     "packages/**"
   ],
-  "workspaces": [
-    "packages/*"
-  ]
+  "workspaces": {
+    "packages": [
+      "packages/*",
+      "."
+    ],
+    "nohoist": [
+      "**/husky"
+    ]
+  },
+  "husky": {
+    "hooks": {
+      "pre-commit": "lint-staged"
+    }
+  }
 }

+ 8 - 4
scripts/bump-year.js

@@ -1,8 +1,8 @@
 #!/usr/bin/env node
 
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* eslint-env node */
@@ -60,10 +60,14 @@ function updateYear( err, fileNames ) {
 				// License headers are only required in JS files.
 				// Also, the file might have already been updated.
 				if ( fileName.endsWith( '.js' ) && !data.match( regexp ) ) {
-					console.warn( `The file "${ fileName }" misses a license header.` );
+					console.warn( `The file "${ process.cwd() }/${ fileName }" misses a license header.` );
 				}
 			} else {
-				fs.writeFile( fileName, updatedData );
+				fs.writeFile( fileName, updatedData, err => {
+					if ( err ) {
+						throw err;
+					}
+				} );
 			}
 		} );
 	} );

+ 6 - 3
scripts/clean-up-svg-icons.sh

@@ -1,13 +1,16 @@
 #!/bin/bash
 
-# @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
-# For licensing, see LICENSE.md.
+# @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+# For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
 
 # Cleans up and optimizes SVG files using the SVGO utility.
 # The configuration file is located in svgo.config.json.
 #
 # Usage:
-#	npm run clean-up-svg-icons path/to/icons/*.svg
+#	yarn run clean-up-svg-icons path/to/icons/foo.svg
+#
+# To optimize the entire project:
+#	yarn clean-up-svg-icons packages/**/theme/icons
 
 for i in "$@"
 do

+ 2 - 2
scripts/docs/build-and-publish-nightly.js

@@ -1,8 +1,8 @@
 #!/usr/bin/env node
 
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* eslint-env node */

+ 2 - 2
scripts/docs/build-and-publish.js

@@ -1,8 +1,8 @@
 #!/usr/bin/env node
 
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* eslint-env node */

+ 2 - 2
scripts/docs/build-api-docs.js

@@ -1,8 +1,8 @@
 #!/usr/bin/env node
 
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* eslint-env node */

+ 5 - 3
scripts/docs/build-docs.js

@@ -1,8 +1,8 @@
 #!/usr/bin/env node
 
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* eslint-env node */
@@ -17,6 +17,7 @@ const skipValidation = process.argv.includes( '--skip-validation' );
 const production = process.argv.includes( '--production' );
 const watch = process.argv.includes( '--watch' );
 const verbose = process.argv.includes( '--verbose' );
+const whitelistedSnippets = process.argv.find( item => item.startsWith( '--snippets=' ) );
 
 buildDocs();
 
@@ -52,7 +53,8 @@ function runUmberto( options ) {
 		skipLiveSnippets: options.skipLiveSnippets,
 		skipValidation: options.skipValidation,
 		snippetOptions: {
-			production: options.production
+			production: options.production,
+			whitelistedSnippets: whitelistedSnippets ? whitelistedSnippets.replace( '--snippets=', '' ).split( ',' ) : []
 		},
 		skipApi: options.skipApi,
 		verbose: options.verbose,

+ 2 - 2
scripts/docs/buildapi.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* eslint-env node */

+ 2 - 2
scripts/docs/getrealimportpath.js

@@ -1,6 +1,6 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* eslint-env node */

+ 321 - 58
scripts/docs/snippetadapter.js

@@ -1,98 +1,275 @@
 /**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  */
 
 /* eslint-env node */
 
 const path = require( 'path' );
 const fs = require( 'fs' );
+const minimatch = require( 'minimatch' );
 const webpack = require( 'webpack' );
 const { bundler, styles } = require( '@ckeditor/ckeditor5-dev-utils' );
 const CKEditorWebpackPlugin = require( '@ckeditor/ckeditor5-dev-webpack-plugin' );
 const MiniCssExtractPlugin = require( 'mini-css-extract-plugin' );
 const UglifyJsWebpackPlugin = require( 'uglifyjs-webpack-plugin' );
+const ProgressBarPlugin = require( 'progress-bar-webpack-plugin' );
 
-const webpackProcesses = new Map();
+const DEFAULT_LANGUAGE = 'en';
 
-module.exports = function snippetAdapter( data ) {
-	if ( !data.snippetSource.js ) {
-		throw new Error( `Missing snippet source for "${ data.snippetPath }".` );
+/**
+ * @param {Set.<Snippet>} snippets Snippet collection extracted from documentation files.
+ * @param {Object} options
+ * @param {Boolean} options.production Whether to build snippets in production mode.
+ * @param {Array.<String>|undefined} options.whitelistedSnippets An array that contains glob patterns.
+ * @param {Object.<String, Function>} umbertoHelpers
+ * @returns {Promise}
+ */
+module.exports = function snippetAdapter( snippets, options, umbertoHelpers ) {
+	const { getSnippetPlaceholder, getSnippetSourcePaths } = umbertoHelpers;
+	const snippetsDependencies = new Map();
+
+	// For each snippet, load its config. If the snippet has defined dependencies, load those as well.
+	for ( const snippetData of snippets ) {
+		if ( !snippetData.snippetSources.js ) {
+			throw new Error( `Missing snippet source for "${ snippetData.snippetName }".` );
+		}
+
+		snippetData.snippetConfig = readSnippetConfig( snippetData.snippetSources.js );
+		snippetData.snippetConfig.language = snippetData.snippetConfig.language || DEFAULT_LANGUAGE;
+
+		// If, in order to work, a snippet requires another snippet to be built, and the other snippet
+		// isn't included in any guide via `{@snippet ...}`, then that other snippet need to be marked
+		// as a dependency of the first one. Example – bootstrap UI uses an iframe, and inside that iframe we
+		// need a JS file. That JS file needs to be built, even though it's not a real snippet (and it's not used
+		// via {@snippet}).
+		if ( snippetData.snippetConfig.dependencies ) {
+			for ( const dependencyName of snippetData.snippetConfig.dependencies ) {
+				// Do not load the same dependency more than once.
+				if ( snippetsDependencies.has( dependencyName ) ) {
+					continue;
+				}
+
+				// Find a root path where to look for the snippet's sources. We just want to pass it through Webpack.
+				const snippetBasePathRegExp = new RegExp( snippetData.snippetName.replace( /\//g, '\\/' ) + '.*$' );
+				const snippetBasePath = snippetData.snippetSources.js.replace( snippetBasePathRegExp, '' );
+
+				const dependencySnippet = {
+					snippetSources: getSnippetSourcePaths( snippetBasePath, dependencyName ),
+					snippetName: dependencyName,
+					outputPath: snippetData.outputPath,
+					destinationPath: snippetData.destinationPath,
+					requiredFor: snippetData
+				};
+
+				if ( !dependencySnippet.snippetSources.js ) {
+					throw new Error( `Missing snippet source for "${ dependencySnippet.snippetName }".` );
+				}
+
+				dependencySnippet.snippetConfig = readSnippetConfig( dependencySnippet.snippetSources.js );
+				dependencySnippet.snippetConfig.language = dependencySnippet.snippetConfig.language || DEFAULT_LANGUAGE;
+
+				snippetsDependencies.set( dependencyName, dependencySnippet );
+			}
+		}
 	}
 
-	const snippetConfig = readSnippetConfig( data.snippetSource.js );
-	const outputPath = path.join( data.outputPath, data.snippetPath );
+	// Add all dependencies to the snippet collection.
+	for ( const snippetData of snippetsDependencies.values() ) {
+		snippets.add( snippetData );
+	}
 
-	const webpackConfig = getWebpackConfig( {
-		entry: data.snippetSource.js,
-		outputPath,
-		language: snippetConfig.language,
-		production: data.options.production
-	} );
+	// Remove snippets that do not match to patterns specified in `options.whitelistedSnippets`.
+	if ( options.whitelistedSnippets ) {
+		filterWhitelistedSnippets( snippets, options.whitelistedSnippets );
+	}
+
+	console.log( `Building ${ snippets.size } snippets...` );
+
+	const groupedSnippetsByLanguage = {};
+
+	// Group snippets by language. There is no way to build different languages in a single Webpack process.
+	// Webpack must be called as many times as different languages are being used in snippets.
+	for ( const snippetData of snippets ) {
+		if ( !groupedSnippetsByLanguage[ snippetData.snippetConfig.language ] ) {
+			groupedSnippetsByLanguage[ snippetData.snippetConfig.language ] = new Set();
+		}
+
+		groupedSnippetsByLanguage[ snippetData.snippetConfig.language ].add( snippetData );
+	}
+
+	// For each language prepare own Webpack configuration.
+	const webpackConfigs = Object.keys( groupedSnippetsByLanguage )
+		.map( language => {
+			return getWebpackConfig( groupedSnippetsByLanguage[ language ], {
+				language,
+				production: options.production,
+				definitions: options.definitions || {}
+			} );
+		} );
 
-	let promise;
+	let promise = Promise.resolve();
 
-	// See #530.
-	if ( webpackProcesses.has( outputPath ) ) {
-		promise = webpackProcesses.get( outputPath );
-	} else {
-		promise = runWebpack( webpackConfig );
-		webpackProcesses.set( outputPath, promise );
+	// Nothing to build.
+	if ( !webpackConfigs.length ) {
+		return promise;
+	}
+
+	for ( const config of webpackConfigs ) {
+		promise = promise.then( () => runWebpack( config ) );
 	}
 
 	return promise
 		.then( () => {
-			const wasCSSGenerated = fs.existsSync( path.join( outputPath, 'snippet.css' ) );
-			const cssFiles = [
-				path.join( data.basePath, 'assets', 'snippet-styles.css' )
-			];
-
-			// CSS may not be generated by Webpack if a snippet's JS file didn't import any SCSS files.
-			if ( wasCSSGenerated ) {
-				cssFiles.unshift( path.join( data.relativeOutputPath, data.snippetPath, 'snippet.css' ) );
+			// Group snippets by destination path in order to attach required HTML code and assets (CSS and JS).
+			const groupedSnippetsByDestinationPath = {};
+
+			for ( const snippetData of snippets ) {
+				if ( !groupedSnippetsByDestinationPath[ snippetData.destinationPath ] ) {
+					groupedSnippetsByDestinationPath[ snippetData.destinationPath ] = new Set();
+				}
+
+				groupedSnippetsByDestinationPath[ snippetData.destinationPath ].add( snippetData );
 			}
 
-			// If the snippet is a dependency of a parent snippet, append JS and CSS to HTML and save to disk.
-			if ( data.isDependency ) {
-				let htmlFile = fs.readFileSync( data.snippetSource.html ).toString();
+			// For every page that contains at least one snippet, we need to replace Umberto comments with HTML code.
+			for ( const destinationPath of Object.keys( groupedSnippetsByDestinationPath ) ) {
+				const snippetsOnPage = groupedSnippetsByDestinationPath[ destinationPath ];
+
+				// Assets required for the all snippets.
+				const cssFiles = [];
+				const jsFiles = [];
+
+				let content = fs.readFileSync( destinationPath ).toString();
+
+				for ( const snippetData of snippetsOnPage ) {
+					// CSS may not be generated by Webpack if a snippet's JS file didn't import any CSS files.
+					const wasCSSGenerated = fs.existsSync( path.join( snippetData.outputPath, snippetData.snippetName, 'snippet.css' ) );
+
+					// If the snippet is a dependency, append JS and CSS to HTML, save to disk and continue.
+					if ( snippetData.requiredFor ) {
+						let htmlFile = fs.readFileSync( snippetData.snippetSources.html ).toString();
+
+						if ( wasCSSGenerated ) {
+							htmlFile += '<link rel="stylesheet" href="snippet.css" type="text/css">';
+						}
+
+						htmlFile += '<script src="snippet.js"></script>';
+
+						fs.writeFileSync( path.join( snippetData.outputPath, snippetData.snippetName, 'snippet.html' ), htmlFile );
 
-				if ( wasCSSGenerated ) {
-					htmlFile += '<link rel="stylesheet" href="snippet.css" type="text/css">';
+						continue;
+					}
+
+					let snippetHTML = fs.readFileSync( snippetData.snippetSources.html ).toString();
+
+					if ( snippetHTML.trim() ) {
+						snippetHTML = snippetHTML.replace( /%BASE_PATH%/g, snippetData.basePath );
+						snippetHTML = `<div class="live-snippet">${ snippetHTML }</div>`;
+					}
+
+					content = content.replace( getSnippetPlaceholder( snippetData.snippetName ), snippetHTML );
+
+					jsFiles.push( path.join( snippetData.basePath, 'assets', 'snippet.js' ) );
+					jsFiles.push( path.join( snippetData.relativeOutputPath, snippetData.snippetName, 'snippet.js' ) );
+
+					cssFiles.push( path.join( snippetData.basePath, 'assets', 'snippet-styles.css' ) );
+
+					if ( wasCSSGenerated ) {
+						cssFiles.unshift( path.join( snippetData.relativeOutputPath, snippetData.snippetName, 'snippet.css' ) );
+					}
 				}
 
-				htmlFile += '<script src="snippet.js"></script>';
+				const cssImportsHTML = getHTMLImports( cssFiles, importPath => {
+					return `    <link rel="stylesheet" href="${ importPath }" type="text/css">`;
+				} );
 
-				fs.writeFileSync( path.join( outputPath, 'snippet.html' ), htmlFile );
-			}
+				const jsImportsHTML = getHTMLImports( jsFiles, importPath => {
+					return `    <script src="${ importPath }"></script>`;
+				} );
 
-			return {
-				html: fs.readFileSync( data.snippetSource.html ),
-				assets: {
-					js: [
-						// Load snippet helpers first.
-						path.join( data.basePath, 'assets', 'snippet.js' ),
-
-						// Then load the actual snippet code.
-						path.join( data.relativeOutputPath, data.snippetPath, 'snippet.js' )
-					],
-					css: cssFiles
-				},
-				dependencies: snippetConfig.dependencies
-			};
+				content = content.replace( '<!--UMBERTO: SNIPPET: CSS-->', cssImportsHTML );
+				content = content.replace( '<!--UMBERTO: SNIPPET: JS-->', jsImportsHTML );
+
+				fs.writeFileSync( destinationPath, content );
+			}
+		} )
+		.then( () => {
+			console.log( `Finished building ${ snippets.size } snippets.` );
 		} );
 };
 
-function getWebpackConfig( config ) {
-	return {
+/**
+ * Removes snippets that names do not match to patterns specified in `whitelistedSnippets` array.
+ *
+ * @param {Set.<Snippet>} snippets Snippet collection extracted from documentation files.
+ * @param {Array.<String>|undefined} whitelistedSnippets Snippet patterns that should be built.
+ */
+function filterWhitelistedSnippets( snippets, whitelistedSnippets ) {
+	if ( !whitelistedSnippets.length ) {
+		return;
+	}
+
+	const snippetsToBuild = new Set();
+
+	// Find all snippets that matched to specified criteria.
+	for ( const snippetData of snippets ) {
+		const shouldBeBuilt = whitelistedSnippets.some( pattern => {
+			return minimatch( snippetData.snippetName, pattern ) || snippetData.snippetName.includes( pattern );
+		} );
+
+		if ( shouldBeBuilt ) {
+			snippetsToBuild.add( snippetData );
+		}
+	}
+
+	// Find all dependencies that are required for whitelisted snippets.
+	for ( const snippetData of snippets ) {
+		if ( snippetsToBuild.has( snippetData ) ) {
+			continue;
+		}
+
+		if ( snippetData.requiredFor && snippetsToBuild.has( snippetData.requiredFor ) ) {
+			snippetsToBuild.add( snippetData );
+		}
+	}
+
+	// Remove snippets that won't be built and aren't dependencies of other snippets.
+	for ( const snippetData of snippets ) {
+		if ( !snippetsToBuild.has( snippetData ) ) {
+			snippets.delete( snippetData );
+		}
+	}
+}
+
+/**
+ * Prepares configuration for Webpack.
+ *
+ * @param {Set.<Snippet>} snippets Snippet collection extracted from documentation files.
+ * @param {Object} config
+ * @param {String} config.language Language for the build.
+ * @param {Boolean} config.production Whether to build for production.
+ * @param {Object} config.definitions
+ * @returns {Object}
+ */
+function getWebpackConfig( snippets, config ) {
+	// Stringify all definitions values. The `DefinePlugin` injects definition values as they are so we need to stringify them,
+	// so they will become real strings in the generated code. See https://webpack.js.org/plugins/define-plugin/ for more information.
+	const definitions = {};
+
+	for ( const definitionKey in config.definitions ) {
+		definitions[ definitionKey ] = JSON.stringify( config.definitions[ definitionKey ] );
+	}
+
+	const webpackConfig = {
 		mode: config.production ? 'production' : 'development',
 
 		devtool: 'source-map',
 
-		entry: config.entry,
+		entry: {},
 
 		output: {
-			path: config.outputPath,
-			filename: 'snippet.js'
+			filename: '[name]/snippet.js'
 		},
 
 		optimization: {
@@ -110,13 +287,17 @@ function getWebpackConfig( config ) {
 		},
 
 		plugins: [
-			new MiniCssExtractPlugin( { filename: 'snippet.css' } ),
+			new MiniCssExtractPlugin( { filename: '[name]/snippet.css' } ),
 			new CKEditorWebpackPlugin( {
-				language: config.language || 'en'
+				language: config.language
 			} ),
 			new webpack.BannerPlugin( {
 				banner: bundler.getLicenseBanner(),
 				raw: true
+			} ),
+			new webpack.DefinePlugin( definitions ),
+			new ProgressBarPlugin( {
+				format: `Building snippets for language "${ config.language }": :percent (:msg)`,
 			} )
 		],
 
@@ -155,8 +336,28 @@ function getWebpackConfig( config ) {
 			]
 		}
 	};
+
+	for ( const snippetData of snippets ) {
+		if ( !webpackConfig.output.path ) {
+			webpackConfig.output.path = snippetData.outputPath;
+		}
+
+		if ( webpackConfig.entry[ snippetData.snippetName ] ) {
+			continue;
+		}
+
+		webpackConfig.entry[ snippetData.snippetName ] = snippetData.snippetSources.js;
+	}
+
+	return webpackConfig;
 }
 
+/**
+ * Builds snippets.
+ *
+ * @param {Object} webpackConfig
+ * @returns {Promise}
+ */
 function runWebpack( webpackConfig ) {
 	return new Promise( ( resolve, reject ) => {
 		webpack( webpackConfig, ( err, stats ) => {
@@ -171,6 +372,9 @@ function runWebpack( webpackConfig ) {
 	} );
 }
 
+/**
+ * @returns {Array.<String>}
+ */
 function getModuleResolvePaths() {
 	return [
 		path.resolve( __dirname, '..', '..', 'node_modules' ),
@@ -178,6 +382,12 @@ function getModuleResolvePaths() {
 	];
 }
 
+/**
+ * Reads the snippet's configuration.
+ *
+ * @param {String} snippetSourcePath An absolute path to the file.
+ * @returns {Object}
+ */
 function readSnippetConfig( snippetSourcePath ) {
 	const snippetSource = fs.readFileSync( snippetSourcePath ).toString();
 
@@ -189,3 +399,56 @@ function readSnippetConfig( snippetSourcePath ) {
 
 	return JSON.parse( configSourceMatch[ 1 ] );
 }
+
+/**
+ * Removes duplicated entries specified in `files` array and map those entires using `mapFunction`.
+ *
+ * @param {Array.<String>} files Paths collection.
+ * @param {Function} mapFunction Function that should return a string.
+ * @returns {String}
+ */
+function getHTMLImports( files, mapFunction ) {
+	return [ ...new Set( files ) ]
+		.map( mapFunction )
+		.join( '\n' )
+		.replace( /^\s+/, '' );
+}
+
+/**
+ * @typedef {Object} Snippet
+ *
+ * @property {SnippetSource} snippetSources Sources of the snippet.
+ *
+ * @property {String} snippetName Name of the snippet. Defined directly after `@snippet` tag.
+ *
+ * @property {String} outputPath An absolute path where to write file produced by the `snippetAdapter`.
+ *
+ * @property {String} destinationPath An absolute path to the file where the snippet is being used.
+ *
+ * @property {SnippetConfiguration} snippetConfig={} Additional configuration of the snippet. It's being read from the snippet's source.
+ *
+ * @property {String} [basePath] Relative path from the processed file to the root of the documentation.
+ *
+ * @property {String} [relativeOutputPath] The same like `basePath` but for the output path (where processed file will be saved).
+ *
+ * @property {Snippet|undefined} [requiredFor] If the value is instance of `Snippet`, current snippet requires
+ * the snippet defined as `requiredFor` to work.
+ */
+
+/**
+ * @typedef {Object} SnippetSource
+ *
+ * @property {String} html An absolute path to the HTML sample.
+ *
+ * @property {String} css An absolute path to the CSS sample.
+ *
+ * @property {String} js An absolute path to the JS sample.
+ */
+
+/**
+ * @typedef {Object} SnippetConfiguration
+ *
+ * @property {String} [language] A language that will be used for building the editor.
+ *
+ * @property {Array.<String>} [dependencies] Names of samples that are required to working.
+ */

Some files were not shown because too many files changed in this diff