Bläddra i källkod

Merge branch 'master' into t/ckeditor5/416b

Aleksander Nowodzinski 6 år sedan
förälder
incheckning
e79f87f8b6
100 ändrade filer med 2332 tillägg och 462 borttagningar
  1. 2 2
      packages/ckeditor5-core/.eslintrc.js
  2. 11 13
      packages/ckeditor5-core/.travis.yml
  3. 77 0
      packages/ckeditor5-core/CHANGELOG.md
  4. 1 1
      packages/ckeditor5-core/CONTRIBUTING.md
  5. 1 1
      packages/ckeditor5-core/LICENSE.md
  6. 1 2
      packages/ckeditor5-core/README.md
  7. 25 0
      packages/ckeditor5-core/lang/translations/af.po
  8. 12 1
      packages/ckeditor5-core/lang/translations/ar.po
  9. 12 1
      packages/ckeditor5-core/lang/translations/ast.po
  10. 25 0
      packages/ckeditor5-core/lang/translations/az.po
  11. 12 1
      packages/ckeditor5-core/lang/translations/bg.po
  12. 12 1
      packages/ckeditor5-core/lang/translations/ca.po
  13. 12 1
      packages/ckeditor5-core/lang/translations/cs.po
  14. 12 1
      packages/ckeditor5-core/lang/translations/da.po
  15. 12 1
      packages/ckeditor5-core/lang/translations/de.po
  16. 12 1
      packages/ckeditor5-core/lang/translations/el.po
  17. 12 1
      packages/ckeditor5-core/lang/translations/en-au.po
  18. 12 1
      packages/ckeditor5-core/lang/translations/en.po
  19. 12 1
      packages/ckeditor5-core/lang/translations/eo.po
  20. 12 1
      packages/ckeditor5-core/lang/translations/es.po
  21. 12 1
      packages/ckeditor5-core/lang/translations/et.po
  22. 12 1
      packages/ckeditor5-core/lang/translations/eu.po
  23. 12 1
      packages/ckeditor5-core/lang/translations/fa.po
  24. 12 1
      packages/ckeditor5-core/lang/translations/fi.po
  25. 12 1
      packages/ckeditor5-core/lang/translations/fr.po
  26. 12 1
      packages/ckeditor5-core/lang/translations/gl.po
  27. 12 1
      packages/ckeditor5-core/lang/translations/he.po
  28. 12 1
      packages/ckeditor5-core/lang/translations/hr.po
  29. 12 1
      packages/ckeditor5-core/lang/translations/hu.po
  30. 25 0
      packages/ckeditor5-core/lang/translations/id.po
  31. 12 1
      packages/ckeditor5-core/lang/translations/it.po
  32. 12 1
      packages/ckeditor5-core/lang/translations/ja.po
  33. 12 1
      packages/ckeditor5-core/lang/translations/km.po
  34. 12 1
      packages/ckeditor5-core/lang/translations/kn.po
  35. 12 1
      packages/ckeditor5-core/lang/translations/ko.po
  36. 12 1
      packages/ckeditor5-core/lang/translations/ku.po
  37. 25 0
      packages/ckeditor5-core/lang/translations/lt.po
  38. 12 1
      packages/ckeditor5-core/lang/translations/nb.po
  39. 12 1
      packages/ckeditor5-core/lang/translations/ne.po
  40. 12 1
      packages/ckeditor5-core/lang/translations/nl.po
  41. 12 1
      packages/ckeditor5-core/lang/translations/no.po
  42. 12 1
      packages/ckeditor5-core/lang/translations/oc.po
  43. 12 1
      packages/ckeditor5-core/lang/translations/pl.po
  44. 12 1
      packages/ckeditor5-core/lang/translations/pt-br.po
  45. 12 1
      packages/ckeditor5-core/lang/translations/pt.po
  46. 12 1
      packages/ckeditor5-core/lang/translations/ro.po
  47. 12 1
      packages/ckeditor5-core/lang/translations/ru.po
  48. 12 1
      packages/ckeditor5-core/lang/translations/sk.po
  49. 12 1
      packages/ckeditor5-core/lang/translations/sq.po
  50. 25 0
      packages/ckeditor5-core/lang/translations/sr-latn.po
  51. 25 0
      packages/ckeditor5-core/lang/translations/sr.po
  52. 12 1
      packages/ckeditor5-core/lang/translations/sv.po
  53. 12 1
      packages/ckeditor5-core/lang/translations/tr.po
  54. 12 1
      packages/ckeditor5-core/lang/translations/tt.po
  55. 12 1
      packages/ckeditor5-core/lang/translations/ug.po
  56. 12 1
      packages/ckeditor5-core/lang/translations/uk.po
  57. 12 1
      packages/ckeditor5-core/lang/translations/zh-cn.po
  58. 12 1
      packages/ckeditor5-core/lang/translations/zh.po
  59. 28 24
      packages/ckeditor5-core/package.json
  60. 73 8
      packages/ckeditor5-core/src/command.js
  61. 4 3
      packages/ckeditor5-core/src/commandcollection.js
  62. 2 2
      packages/ckeditor5-core/src/editingkeystrokehandler.js
  63. 41 69
      packages/ckeditor5-core/src/editor/editor.js
  64. 85 2
      packages/ckeditor5-core/src/editor/editorconfig.jsdoc
  65. 111 13
      packages/ckeditor5-core/src/editor/editorui.js
  66. 2 28
      packages/ckeditor5-core/src/editor/editorwithui.jsdoc
  67. 6 3
      packages/ckeditor5-core/src/editor/utils/attachtoform.js
  68. 10 5
      packages/ckeditor5-core/src/editor/utils/dataapimixin.js
  69. 6 3
      packages/ckeditor5-core/src/editor/utils/elementapimixin.js
  70. 50 0
      packages/ckeditor5-core/src/editor/utils/securesourceelement.js
  71. 99 0
      packages/ckeditor5-core/src/multicommand.js
  72. 5 5
      packages/ckeditor5-core/src/pendingactions.js
  73. 23 7
      packages/ckeditor5-core/src/plugin.js
  74. 100 18
      packages/ckeditor5-core/src/plugincollection.js
  75. 4 4
      packages/ckeditor5-core/tests/_utils-tests/articlepluginset.js
  76. 73 15
      packages/ckeditor5-core/tests/_utils-tests/classictesteditor.js
  77. 2 2
      packages/ckeditor5-core/tests/_utils-tests/createsinonsandbox.js
  78. 2 2
      packages/ckeditor5-core/tests/_utils-tests/modeltesteditor.js
  79. 2 2
      packages/ckeditor5-core/tests/_utils-tests/utils.js
  80. 38 2
      packages/ckeditor5-core/tests/_utils-tests/virtualtesteditor.js
  81. 2 2
      packages/ckeditor5-core/tests/_utils/articlepluginset.js
  82. 106 32
      packages/ckeditor5-core/tests/_utils/classictesteditor.js
  83. 155 0
      packages/ckeditor5-core/tests/_utils/memory.js
  84. 18 2
      packages/ckeditor5-core/tests/_utils/modeltesteditor.js
  85. 12 12
      packages/ckeditor5-core/tests/_utils/utils.js
  86. 17 2
      packages/ckeditor5-core/tests/_utils/virtualtesteditor.js
  87. 60 2
      packages/ckeditor5-core/tests/command.js
  88. 8 5
      packages/ckeditor5-core/tests/commandcollection.js
  89. 2 2
      packages/ckeditor5-core/tests/editingkeystrokehandler.js
  90. 150 88
      packages/ckeditor5-core/tests/editor/editor.js
  91. 123 13
      packages/ckeditor5-core/tests/editor/editorui.js
  92. 8 6
      packages/ckeditor5-core/tests/editor/utils/attachtoform.js
  93. 23 2
      packages/ckeditor5-core/tests/editor/utils/dataapimixin.js
  94. 8 4
      packages/ckeditor5-core/tests/editor/utils/elementapimixin.js
  95. 67 0
      packages/ckeditor5-core/tests/editor/utils/securesourceelement.js
  96. 2 2
      packages/ckeditor5-core/tests/manual/article.js
  97. 2 2
      packages/ckeditor5-core/tests/manual/formsubmit.js
  98. 2 2
      packages/ckeditor5-core/tests/manual/pendingactions.js
  99. 2 2
      packages/ckeditor5-core/tests/manual/readonly.js
  100. 4 0
      packages/ckeditor5-core/tests/manual/version-collision.html

+ 2 - 2
packages/ckeditor5-core/.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
packages/ckeditor5-core/.travis.yml

@@ -1,33 +1,31 @@
 sudo: required
 dist: trusty
 addons:
+  chrome: stable
   firefox: latest
-  apt:
-    sources:
-    - google-chrome
-    packages:
-    - google-chrome-stable
 language: node_js
 node_js:
 - '8'
 cache:
-- node_modules
+  yarn: true
 branches:
-   except:
-   - stable
+  except:
+  - stable
 before_install:
+- export START_TIME=$( date +%s )
 - export DISPLAY=:99.0
 - sh -e /etc/init.d/xvfb start
-- npm config set package-lock false
-- npm i -g npm@^5.7.1
+- npm i -g yarn
 install:
-- npm install @ckeditor/ckeditor5-dev-tests
+- yarn add @ckeditor/ckeditor5-dev-tests
 - ckeditor5-dev-tests-install-dependencies
 script:
 - ckeditor5-dev-tests-travis
 after_success:
 - ckeditor5-dev-tests-save-revision
+after_script:
+- export END_TIME=$( date +%s )
+- ckeditor5-dev-tests-notify-travis-status
 env:
   global:
-  - secure: uJiLhULzhBDdkuQrYW7k7H4l4HizyfCdnV9ORe2HXyNnWFnFfAhc2vD1FQ5182ldQXtUQMX51gM/YL5r1Sa2IO3EW/j0gyuznqYvM8KX4lU1hXUAap1ZHW7Zb/leVQfU76IH688rkGcpr2SwCG6/LglqunozwhyePuxBzH23whSrf5dGXjswnMUAjouavtm+YybPZY81uj181aFY7nbcWOCUkx7FGaIg5WqG27kkYuUCVDw977hlwSevL6K1AF1OMtfHTAt4NR2oRKtpcRsrBlVNFFCraVa+3E1DN/kcEMaNDFJ4737Zta6jK9irGdfOwvtt1t4Og2fWKtUNCix2Almjiu0t58miuHhZenB4xRm4kpnc5nzfu0x/Ur4R95OHbKMf/6Zvvd/2v4bHe0iFihZXB8qiXvqZwqVyXCH2pWzj+tFztwpvyfj+Z1k4LIuo3shbG9GlK8mSl0OfL9WdSR/g09NBRg1aJtEKsJreensTEO9esJ5dD7JksKo6VsESSAztN4v5xuavtPM+yT2g2vFWpwyY0/6/yxR8hlIo0//24t8be9Lm+G3k1f++Jc6ea8aFYUPF+O6kdZgqD0g2LoAp5e28CjXkMDoq/0l+Ab0obpaMjZlyEZ+S8X+8+kyICpyADmLTWP/Mwmik8ixzABgBz0903w4aExOl8mEi9ws=
-  - secure: tVEuv/7HAhriDudbEP0+30DvPV04pI3XoW3ibHDVdI7qhjtNH4AnYcqJOtI25wZTYr8hUquqkFNM7nl4sSoPPHEgdLL/c9U1bLkmTfQD4l+e6OPL8V2D9qStnSvswDMevtoCAVep+yKU7tDwL+ppNZeC9SX4xigo8MMC0qKhgx/q2CWIOlDVURFbFeEss+RI6nn1nxO+1kyyiZ1O0hyh5Xkn2u9h5ihV3SAeAHfZyvKvhyii74jSjHBHiapKkwUr4WSUkmKwuaqA2OppZXiDf5ah5nbjh1kX8u90jN+1yFH0Aor+M1Qdejs8A5sg9spOU2L0OXlw1wiLj0gzrGEQH6wbTR2T8nyIYYpivnuPUh19WK6FCABCbH3baxJwpZdYrd87Oi2EAPDS8obpNc/fZEj5XrZBVIf8Tk9wvaDrB4JSsyjp8U8G5eKVrY++sRdCTGxpP1W9LJlkVSTW8weX1FWWo6OWnZGo7+Q6xd63wVZ7/dc0TU/h9Ck9ekTeOAU2/to71EnPZzU4Ydv5R/BK1VK0PUIIThosFIvd7UwrnVzmgndovIAfas+XhxgpJCWN+9pXPA46OfFNyYl8naiLdJqCOpl40tWNCE6T8rC60seTUSrDJwGEForCfr3eu2fkl0x5syc7eOimHHECBIGk4dE3TwwIg2kf6PUHMIT433M=
+  - secure: TEv7Uuc8Xcd+AaR8W+s7qt/synWT60kR8/8QNiqBmdFDYC3LNdoI48V8vh8rIbdtYk1u6Ur+6pBLDC1+gMyA6+oAY5SVKdiep7JgJUkYEygCtr4chyiatbZezuKmWs9GxJ8awhZGeFuKNt/jwyVxMqYHDQ1t0Mb4+AyYB60h6o0Mx8IQ4rnmVmcCe45NZNe24a2a2ao5IX3Y3INrdXKBZ780mfDQrCWRa404MZ3ZoVdVeKoEkX3OiuKnsiCDIcKu89IVLRW7mq1kw84G8npDNbddU8sGtS6RdfSZ61M1jp1yHNXi/2/tLQyE0N8UF3bYHy9GzqKC6D4VGLLykGgv+yd95y0arwMMdi2gfrg3nE2Zehu/kuTRwMSBMwkhScZAhixYOjX/AFGd7EAmt4/wfBrNNCs29lCiOUpR7wC1ZSNCMbVxkNlAoI1J/2MoBKALqLdBfYGcH1XwfIwx/w62dqiE/zlnDYgdBursnpa0sGXMau1xAA+5ud+oHwmKTkMjKeszQC5YaWLS+qtTX/y2TnePvbzO8yTJrLDt+8tVAWsbUrWe9Uk+zuLNFxhNVmBbaQD3hhkRIm/ElCQROIA7tGRRyksuG63m1GBWZU6tIeiK6fitdXpgb+UpIru+Qc0p48XAz68zdslJg/4N3wcFuacNNdVqA3ZWd2Rsp8LO7t4=

+ 77 - 0
packages/ckeditor5-core/CHANGELOG.md

@@ -1,6 +1,83 @@
 Changelog
 =========
 
+## [12.2.1](https://github.com/ckeditor/ckeditor5-core/compare/v12.2.0...v12.2.1) (2019-07-10)
+
+Internal changes only (updated dependencies, documentation, etc.).
+
+
+## [12.2.0](https://github.com/ckeditor/ckeditor5-core/compare/v12.1.1...v12.2.0) (2019-07-04)
+
+### Features
+
+* Added an editor instance reference to the native editable DOM element under the `ckeditorInstance` property. Closes [ckeditor/ckeditor5#1838](https://github.com/ckeditor/ckeditor5/issues/1838). ([fa94600](https://github.com/ckeditor/ckeditor5-core/commit/fa94600))
+
+  Implemented the `EditorUI#setEditableElement()` method.
+  Deprecated the `EditorUI#_editableElements` property.
+* Introduced `MultiCommand` which acts as a composite command – it can group multiple commands under the hood. ([ebcbd01](https://github.com/ckeditor/ckeditor5-core/commit/ebcbd01))
+
+### Other changes
+
+* Introduce the `editor-wrong-element` error thrown when the editor is created over a wrong element. Closes [ckeditor/ckeditor5#1591](https://github.com/ckeditor/ckeditor5/issues/1591). ([9945fc6](https://github.com/ckeditor/ckeditor5-core/commit/9945fc6))
+* Updated translations. ([43f5b6e](https://github.com/ckeditor/ckeditor5-core/commit/43f5b6e))
+
+
+## [12.1.1](https://github.com/ckeditor/ckeditor5-core/compare/v12.1.0...v12.1.1) (2019-06-05)
+
+### Other changes
+
+* Updated translations. ([ed6bd3c](https://github.com/ckeditor/ckeditor5-core/commit/ed6bd3c))
+
+
+## [12.1.0](https://github.com/ckeditor/ckeditor5-core/compare/v12.0.0...v12.1.0) (2019-04-10)
+
+### Features
+
+* Introduced `Command#disable()` and `Command#enable()`. Closes [#165](https://github.com/ckeditor/ckeditor5-core/issues/165). ([030ca2b](https://github.com/ckeditor/ckeditor5-core/commit/030ca2b))
+
+### Other changes
+
+* Optimized icons. ([a5f8d34](https://github.com/ckeditor/ckeditor5-core/commit/a5f8d34))
+* Updated translations. ([2dedc43](https://github.com/ckeditor/ckeditor5-core/commit/2dedc43))
+
+
+## [12.0.0](https://github.com/ckeditor/ckeditor5-core/compare/v11.1.0...v12.0.0) (2019-02-28)
+
+### Bug fixes
+
+* Fixed memory leaks during editor initialization and destruction. Created helpers for testing memory usage. Closes [ckeditor/ckeditor5#1341](https://github.com/ckeditor/ckeditor5/issues/1341). ([11ca135](https://github.com/ckeditor/ckeditor5-core/commit/11ca135))
+
+### Other changes
+
+* Editor UI classes API refactoring. See breaking changes. Closes [ckeditor/ckeditor5#1449](https://github.com/ckeditor/ckeditor5/issues/1449). ([aca1ff1](https://github.com/ckeditor/ckeditor5-core/commit/aca1ff1))
+* Remove `editor#pluginsReady` event. Closes [ckeditor/ckeditor5#1477](https://github.com/ckeditor/ckeditor5/issues/1477). ([6d63538](https://github.com/ckeditor/ckeditor5-core/commit/6d63538))
+* The `Editor#getData()` method now accepts `options.trim` parameter. By default it will now return an empty string when the editor is empty (instead of returning `'<p>&nbsp;</p>'` as before). ([4f8abd1](https://github.com/ckeditor/ckeditor5-core/commit/4f8abd1))
+* Throw an error from `editor.plugins.get()` if the plugin is not loaded. Closes [#148](https://github.com/ckeditor/ckeditor5-core/issues/148). ([a56b47a](https://github.com/ckeditor/ckeditor5-core/commit/a56b47a))
+* Updated translations. ([4cf6f4f](https://github.com/ckeditor/ckeditor5-core/commit/4cf6f4f)) ([a8367a5](https://github.com/ckeditor/ckeditor5-core/commit/a8367a5)) ([0e09317](https://github.com/ckeditor/ckeditor5-core/commit/0e09317))
+
+### 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))
+* The `Editor#getData()` method now returns an empty string by default when editor content is empty (instead of returning `'<p>&nbsp;</p>'` as before).
+* The `editor#pluginsReady` event was removed. Use plugin `afterInit()` method instead.
+* Removed `EditorWithUI#element` property.  The `EditorUI#element` property should be used instead.
+* Removed `EditorWithUI#uiReady` event.  The `EditorUI#ready` event should be used instead.
+* Removed `view` parameter in `EditorUI` constructor. Only subclasses should use it without passing it further to `EditorUI`.
+* Removed `EditorUI#view` property. The `view` property from subclasses (like `ClassicEditorUI#view`) should be used directly instead.
+* The `editor.plugins.get()` will now throw an error if the plugin is not loaded. Use `editor.plugins.has()` to check if plugin is available.
+
+
+## [11.1.0](https://github.com/ckeditor/ckeditor5-core/compare/v11.0.1...v11.1.0) (2018-12-05)
+
+### Features
+
+* Implemented the `config.extraPlugins` option. Closes [#146](https://github.com/ckeditor/ckeditor5-core/issues/146). ([4b5c3d4](https://github.com/ckeditor/ckeditor5-core/commit/4b5c3d4))
+
+### Other changes
+
+* Improved SVG icons size. See [ckeditor/ckeditor5-theme-lark#206](https://github.com/ckeditor/ckeditor5-theme-lark/issues/206). ([c4795fb](https://github.com/ckeditor/ckeditor5-core/commit/c4795fb))
+
+
 ## [11.0.1](https://github.com/ckeditor/ckeditor5-core/compare/v11.0.0...v11.0.1) (2018-10-08)
 
 ### Other changes

+ 1 - 1
packages/ckeditor5-core/CONTRIBUTING.md

@@ -1,4 +1,4 @@
 Contributing
 ========================================
 
-Information about contributing can be found on the following page: <https://github.com/ckeditor/ckeditor5/blob/master/CONTRIBUTING.md>.
+See the [official contributors' guide to CKEditor 5](https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html) to learn more.

+ 1 - 1
packages/ckeditor5-core/LICENSE.md

@@ -2,7 +2,7 @@ Software License Agreement
 ==========================
 
 **CKEditor 5 Core Editor Architecture** – https://github.com/ckeditor/ckeditor5-core <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).
 

+ 1 - 2
packages/ckeditor5-core/README.md

@@ -4,7 +4,6 @@ CKEditor 5 core editor architecture
 [![Join the chat at https://gitter.im/ckeditor/ckeditor5](https://badges.gitter.im/ckeditor/ckeditor5.svg)](https://gitter.im/ckeditor/ckeditor5?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 [![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-core.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-core)
 [![Build Status](https://travis-ci.org/ckeditor/ckeditor5-core.svg?branch=master)](https://travis-ci.org/ckeditor/ckeditor5-core)
-[![BrowserStack Status](https://automate.browserstack.com/automate/badge.svg?badge_key=d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)](https://automate.browserstack.com/public-build/d3hvenZqQVZERFQ5d09FWXdyT0ozVXhLaVltRFRjTTUyZGpvQWNmWVhUUT0tLUZqNlJ1YWRUd0RvdEVOaEptM1B2Q0E9PQ==--c9d3dee40b9b4471ff3fb516d9ecf8d09292c7e0)
 [![Coverage Status](https://coveralls.io/repos/github/ckeditor/ckeditor5-core/badge.svg?branch=master)](https://coveralls.io/github/ckeditor/ckeditor5-core?branch=master)
 <br>
 [![Dependency Status](https://david-dm.org/ckeditor/ckeditor5-core/status.svg)](https://david-dm.org/ckeditor/ckeditor5-core)
@@ -20,4 +19,4 @@ Additionally, see the [`@ckeditor/ckeditor5-core` package](https://ckeditor.com/
 
 ## 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).

+ 25 - 0
packages/ckeditor5-core/lang/translations/af.po

@@ -0,0 +1,25 @@
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Afrikaans (https://www.transifex.com/ckeditor/teams/11143/af/)\n"
+"Language: af\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Label for the Save button."
+msgid "Save"
+msgstr "Berg"
+
+msgctxt "Label for the Cancel button."
+msgid "Cancel"
+msgstr "Kanselleer"

+ 12 - 1
packages/ckeditor5-core/lang/translations/ar.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Arabic (https://www.transifex.com/ckeditor/teams/11143/ar/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/ast.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Asturian (https://www.transifex.com/ckeditor/teams/11143/ast/)\n"

+ 25 - 0
packages/ckeditor5-core/lang/translations/az.po

@@ -0,0 +1,25 @@
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Azerbaijani (https://www.transifex.com/ckeditor/teams/11143/az/)\n"
+"Language: az\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Label for the Save button."
+msgid "Save"
+msgstr "Yadda saxla"
+
+msgctxt "Label for the Cancel button."
+msgid "Cancel"
+msgstr "İmtina et"

+ 12 - 1
packages/ckeditor5-core/lang/translations/bg.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Bulgarian (https://www.transifex.com/ckeditor/teams/11143/bg/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/ca.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Catalan (https://www.transifex.com/ckeditor/teams/11143/ca/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/cs.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Czech (https://www.transifex.com/ckeditor/teams/11143/cs/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/da.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Danish (https://www.transifex.com/ckeditor/teams/11143/da/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/de.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: German (https://www.transifex.com/ckeditor/teams/11143/de/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/el.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Greek (https://www.transifex.com/ckeditor/teams/11143/el/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/en-au.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: English (Australia) (https://www.transifex.com/ckeditor/teams/11143/en_AU/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/en.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language: \n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/eo.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Esperanto (https://www.transifex.com/ckeditor/teams/11143/eo/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/es.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Spanish (https://www.transifex.com/ckeditor/teams/11143/es/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/et.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Estonian (https://www.transifex.com/ckeditor/teams/11143/et/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/eu.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Basque (https://www.transifex.com/ckeditor/teams/11143/eu/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/fa.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Persian (https://www.transifex.com/ckeditor/teams/11143/fa/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/fi.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Finnish (https://www.transifex.com/ckeditor/teams/11143/fi/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/fr.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: French (https://www.transifex.com/ckeditor/teams/11143/fr/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/gl.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Galician (https://www.transifex.com/ckeditor/teams/11143/gl/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/he.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Hebrew (https://www.transifex.com/ckeditor/teams/11143/he/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/hr.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Croatian (https://www.transifex.com/ckeditor/teams/11143/hr/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/hu.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Hungarian (https://www.transifex.com/ckeditor/teams/11143/hu/)\n"

+ 25 - 0
packages/ckeditor5-core/lang/translations/id.po

@@ -0,0 +1,25 @@
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Indonesian (https://www.transifex.com/ckeditor/teams/11143/id/)\n"
+"Language: id\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "Label for the Save button."
+msgid "Save"
+msgstr "Simpan"
+
+msgctxt "Label for the Cancel button."
+msgid "Cancel"
+msgstr "Batal"

+ 12 - 1
packages/ckeditor5-core/lang/translations/it.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Italian (https://www.transifex.com/ckeditor/teams/11143/it/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/ja.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Japanese (https://www.transifex.com/ckeditor/teams/11143/ja/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/km.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Khmer (https://www.transifex.com/ckeditor/teams/11143/km/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/kn.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Kannada (https://www.transifex.com/ckeditor/teams/11143/kn/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/ko.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Korean (https://www.transifex.com/ckeditor/teams/11143/ko/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/ku.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Kurdish (https://www.transifex.com/ckeditor/teams/11143/ku/)\n"

+ 25 - 0
packages/ckeditor5-core/lang/translations/lt.po

@@ -0,0 +1,25 @@
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Lithuanian (https://www.transifex.com/ckeditor/teams/11143/lt/)\n"
+"Language: lt\n"
+"Plural-Forms: nplurals=4; plural=(n % 10 == 1 && (n % 100 > 19 || n % 100 < 11) ? 0 : (n % 10 >= 2 && n % 10 <=9) && (n % 100 > 19 || n % 100 < 11) ? 1 : n % 1 != 0 ? 2: 3);\n"
+
+msgctxt "Label for the Save button."
+msgid "Save"
+msgstr "Išsaugoti"
+
+msgctxt "Label for the Cancel button."
+msgid "Cancel"
+msgstr "Atšaukti"

+ 12 - 1
packages/ckeditor5-core/lang/translations/nb.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Norwegian Bokmål (https://www.transifex.com/ckeditor/teams/11143/nb/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/ne.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Nepali (Nepal) (https://www.transifex.com/ckeditor/teams/11143/ne_NP/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/nl.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Dutch (https://www.transifex.com/ckeditor/teams/11143/nl/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/no.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Norwegian (https://www.transifex.com/ckeditor/teams/11143/no/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/oc.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Occitan (post 1500) (https://www.transifex.com/ckeditor/teams/11143/oc/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/pl.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Polish (https://www.transifex.com/ckeditor/teams/11143/pl/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/pt-br.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Portuguese (Brazil) (https://www.transifex.com/ckeditor/teams/11143/pt_BR/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/pt.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Portuguese (https://www.transifex.com/ckeditor/teams/11143/pt/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/ro.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Romanian (https://www.transifex.com/ckeditor/teams/11143/ro/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/ru.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Russian (https://www.transifex.com/ckeditor/teams/11143/ru/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/sk.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Slovak (https://www.transifex.com/ckeditor/teams/11143/sk/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/sq.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Albanian (https://www.transifex.com/ckeditor/teams/11143/sq/)\n"

+ 25 - 0
packages/ckeditor5-core/lang/translations/sr-latn.po

@@ -0,0 +1,25 @@
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Serbian (Latin) (https://www.transifex.com/ckeditor/teams/11143/sr@latin/)\n"
+"Language: sr@latin\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+msgctxt "Label for the Save button."
+msgid "Save"
+msgstr "Sačuvaj"
+
+msgctxt "Label for the Cancel button."
+msgid "Cancel"
+msgstr "Odustani"

+ 25 - 0
packages/ckeditor5-core/lang/translations/sr.po

@@ -0,0 +1,25 @@
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Serbian (https://www.transifex.com/ckeditor/teams/11143/sr/)\n"
+"Language: sr\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+msgctxt "Label for the Save button."
+msgid "Save"
+msgstr "Сачувај"
+
+msgctxt "Label for the Cancel button."
+msgid "Cancel"
+msgstr "Одустани"

+ 12 - 1
packages/ckeditor5-core/lang/translations/sv.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Swedish (https://www.transifex.com/ckeditor/teams/11143/sv/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/tr.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Turkish (https://www.transifex.com/ckeditor/teams/11143/tr/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/tt.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Tatar (https://www.transifex.com/ckeditor/teams/11143/tt/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/ug.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Uighur (https://www.transifex.com/ckeditor/teams/11143/ug/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/uk.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Ukrainian (https://www.transifex.com/ckeditor/teams/11143/uk/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/zh-cn.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Chinese (China) (https://www.transifex.com/ckeditor/teams/11143/zh_CN/)\n"

+ 12 - 1
packages/ckeditor5-core/lang/translations/zh.po

@@ -1,4 +1,15 @@
-# Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+# Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+#
+#                                     !!! IMPORTANT !!!
+#
+#         Before you edit this file, please keep in mind that contributing to the project
+#                translations is possible ONLY via the Transifex online service.
+#
+#         To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+#                   To learn more, check out the official contributor's guide:
+#     https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
 msgid ""
 msgstr ""
 "Language-Team: Chinese (Taiwan) (https://www.transifex.com/ckeditor/teams/11143/zh_TW/)\n"

+ 28 - 24
packages/ckeditor5-core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@ckeditor/ckeditor5-core",
-  "version": "11.0.1",
+  "version": "12.2.1",
   "description": "The core architecture of CKEditor 5 – the best browser-based rich text editor.",
   "keywords": [
     "ckeditor5-lib",
@@ -21,36 +21,36 @@
     "framework"
   ],
   "dependencies": {
-    "@ckeditor/ckeditor5-engine": "^11.0.0",
-    "@ckeditor/ckeditor5-utils": "^11.0.0",
+    "@ckeditor/ckeditor5-engine": "^13.2.1",
+    "@ckeditor/ckeditor5-utils": "^13.0.1",
     "lodash-es": "^4.17.10"
   },
   "devDependencies": {
-    "@ckeditor/ckeditor5-autoformat": "^10.0.3",
-    "@ckeditor/ckeditor5-basic-styles": "^10.0.3",
-    "@ckeditor/ckeditor5-block-quote": "^10.1.0",
-    "@ckeditor/ckeditor5-editor-classic": "^11.0.1",
-    "@ckeditor/ckeditor5-essentials": "^10.1.2",
-    "@ckeditor/ckeditor5-heading": "^10.1.0",
-    "@ckeditor/ckeditor5-image": "^11.0.0",
-    "@ckeditor/ckeditor5-link": "^10.0.4",
-    "@ckeditor/ckeditor5-list": "^11.0.2",
-    "@ckeditor/ckeditor5-media-embed": "^10.0.0",
-    "@ckeditor/ckeditor5-paragraph": "^10.0.3",
-    "@ckeditor/ckeditor5-table": "^11.0.0",
-    "@ckeditor/ckeditor5-ui": "^11.1.0",
+    "@ckeditor/ckeditor5-autoformat": "^11.0.4",
+    "@ckeditor/ckeditor5-basic-styles": "^11.1.3",
+    "@ckeditor/ckeditor5-block-quote": "^11.1.2",
+    "@ckeditor/ckeditor5-editor-classic": "^12.1.3",
+    "@ckeditor/ckeditor5-essentials": "^11.0.4",
+    "@ckeditor/ckeditor5-heading": "^11.0.4",
+    "@ckeditor/ckeditor5-image": "^13.1.2",
+    "@ckeditor/ckeditor5-link": "^11.1.1",
+    "@ckeditor/ckeditor5-list": "^12.0.4",
+    "@ckeditor/ckeditor5-media-embed": "^11.1.3",
+    "@ckeditor/ckeditor5-paragraph": "^11.0.4",
+    "@ckeditor/ckeditor5-table": "^13.0.2",
+    "@ckeditor/ckeditor5-ui": "^13.0.2",
     "eslint": "^5.5.0",
-    "eslint-config-ckeditor5": "^1.0.7",
-    "husky": "^0.14.3",
+    "eslint-config-ckeditor5": "^2.0.0",
+    "husky": "^1.3.1",
     "lint-staged": "^7.0.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": "https://ckeditor.com",
+  "homepage": "https://ckeditor.com/ckeditor-5",
   "bugs": "https://github.com/ckeditor/ckeditor5-core/issues",
   "repository": {
     "type": "git",
@@ -62,8 +62,7 @@
     "theme"
   ],
   "scripts": {
-    "lint": "eslint --quiet '**/*.js'",
-    "precommit": "lint-staged"
+    "lint": "eslint --quiet '**/*.js'"
   },
   "lint-staged": {
     "**/*.js": [
@@ -73,5 +72,10 @@
   "eslintIgnore": [
     "src/lib/**",
     "packages/**"
-  ]
+  ],
+  "husky": {
+    "hooks": {
+      "pre-commit": "lint-staged"
+    }
+  }
 }

+ 73 - 8
packages/ckeditor5-core/src/command.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
  */
 
 /**
@@ -74,7 +74,7 @@ export default class Command {
 		 *			return () => {
 		 *				cmd.off( 'set:isEnabled', forceDisable );
 		 *				cmd.refresh();
-		 *			}
+		 *			};
 		 *
 		 *			function forceDisable( evt ) {
 		 *				evt.return = false;
@@ -96,6 +96,14 @@ export default class Command {
 		 */
 		this.set( 'isEnabled', false );
 
+		/**
+		 * Holds identifiers for {@link #forceDisabled} mechanism.
+		 *
+		 * @type {Set.<String>}
+		 * @private
+		 */
+		this._disableStack = new Set();
+
 		this.decorate( 'execute' );
 
 		// By default every command is refreshed when changes are applied to the model.
@@ -112,11 +120,9 @@ export default class Command {
 		// By default commands are disabled when the editor is in read-only mode.
 		this.listenTo( editor, 'change:isReadOnly', ( evt, name, value ) => {
 			if ( value ) {
-				this.on( 'set:isEnabled', forceDisable, { priority: 'highest' } );
-				this.isEnabled = false;
+				this.forceDisabled( 'readOnlyMode' );
 			} else {
-				this.off( 'set:isEnabled', forceDisable );
-				this.refresh();
+				this.clearForceDisabled( 'readOnlyMode' );
 			}
 		} );
 	}
@@ -132,6 +138,65 @@ export default class Command {
 		this.isEnabled = true;
 	}
 
+	/**
+	 * Disables the command.
+	 *
+	 * Command may be disabled by multiple features or algorithms (at once). When disabling a command, unique id should be passed
+	 * (e.g. feature name). The same identifier should be used when {@link #clearForceDisabled enabling back} the command.
+	 * The command becomes enabled only after all features {@link #clearForceDisabled enabled it back}.
+	 *
+	 * Disabling and enabling a command:
+	 *
+	 *		command.isEnabled; // -> true
+	 *		command.forceDisabled( 'MyFeature' );
+	 *		command.isEnabled; // -> false
+	 *		command.clearForceDisabled( 'MyFeature' );
+	 *		command.isEnabled; // -> true
+	 *
+	 * Command disabled by multiple features:
+	 *
+	 *		command.forceDisabled( 'MyFeature' );
+	 *		command.forceDisabled( 'OtherFeature' );
+	 *		command.clearForceDisabled( 'MyFeature' );
+	 *		command.isEnabled; // -> false
+	 *		command.clearForceDisabled( 'OtherFeature' );
+	 *		command.isEnabled; // -> true
+	 *
+	 * Multiple disabling with the same identifier is redundant:
+	 *
+	 *		command.forceDisabled( 'MyFeature' );
+	 *		command.forceDisabled( 'MyFeature' );
+	 *		command.clearForceDisabled( 'MyFeature' );
+	 *		command.isEnabled; // -> true
+	 *
+	 * **Note:** some commands or algorithms may have more complex logic when it comes to enabling or disabling certain commands,
+	 * so the command might be still disabled after {@link #clearForceDisabled} was used.
+	 *
+	 * @param {String} id Unique identifier for disabling. Use the same id when {@link #clearForceDisabled enabling back} the command.
+	 */
+	forceDisabled( id ) {
+		this._disableStack.add( id );
+
+		if ( this._disableStack.size == 1 ) {
+			this.on( 'set:isEnabled', forceDisable, { priority: 'highest' } );
+			this.isEnabled = false;
+		}
+	}
+
+	/**
+	 * Clears forced disable previously set through {@link #clearForceDisabled}. See {@link #clearForceDisabled}.
+	 *
+	 * @param {String} id Unique identifier, equal to the one passed in {@link #forceDisabled} call.
+	 */
+	clearForceDisabled( id ) {
+		this._disableStack.delete( id );
+
+		if ( this._disableStack.size == 0 ) {
+			this.off( 'set:isEnabled', forceDisable );
+			this.refresh();
+		}
+	}
+
 	/**
 	 * Executes the command.
 	 *
@@ -158,7 +223,7 @@ export default class Command {
 	 * Event fired by the {@link #execute} method. The command action is a listener to this event so it's
 	 * possible to change/cancel the behavior of the command by listening to this event.
 	 *
-	 * See {@link module:utils/observablemixin~ObservableMixin.decorate} for more information and samples.
+	 * See {@link module:utils/observablemixin~ObservableMixin#decorate} for more information and samples.
 	 *
 	 * **Note:** This event is fired even if command is disabled. However, it is automatically blocked
 	 * by a high priority listener in order to prevent command execution.

+ 4 - 3
packages/ckeditor5-core/src/commandcollection.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
  */
 
 /**
@@ -50,6 +50,7 @@ export default class CommandCollection {
 	 * Executes a command.
 	 *
 	 * @param {String} commandName The name of the command.
+	 * @param {*} [...commandParams] Command parameters.
 	 */
 	execute( commandName, ...args ) {
 		const command = this.get( commandName );
@@ -61,7 +62,7 @@ export default class CommandCollection {
 			 * @error commandcollection-command-not-found
 			 * @param {String} commandName Name of the command.
 			 */
-			throw new CKEditorError( 'commandcollection-command-not-found: Command does not exist.', { commandName } );
+			throw new CKEditorError( 'commandcollection-command-not-found: Command does not exist.', this, { commandName } );
 		}
 
 		command.execute( ...args );

+ 2 - 2
packages/ckeditor5-core/src/editingkeystrokehandler.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
  */
 
 /**

+ 41 - 69
packages/ckeditor5-core/src/editor/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
  */
 
 /**
@@ -20,8 +20,6 @@ import EditingKeystrokeHandler from '../editingkeystrokehandler';
 import ObservableMixin from '@ckeditor/ckeditor5-utils/src/observablemixin';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
 
-import '@ckeditor/ckeditor5-utils/src/version';
-
 /**
  * Class representing a basic, generic editor.
  *
@@ -40,6 +38,7 @@ import '@ckeditor/ckeditor5-utils/src/version';
  * the specific editor implements also the {@link module:core/editor/editorwithui~EditorWithUI} interface
  * (as most editor implementations do).
  *
+ * @abstract
  * @mixes module:utils/observablemixin~ObservableMixin
  */
 export default class Editor {
@@ -48,7 +47,7 @@ export default class Editor {
 	 *
 	 * Usually, not to be used directly. See the static {@link module:core/editor/editor~Editor.create `create()`} method.
 	 *
-	 * @param {Object} config The editor config.
+	 * @param {Object} [config] The editor config.
 	 */
 	constructor( config ) {
 		const availablePlugins = this.constructor.builtinPlugins;
@@ -176,13 +175,9 @@ export default class Editor {
 		 * @readonly
 		 * @member {module:engine/conversion/conversion~Conversion}
 		 */
-		this.conversion = new Conversion();
-
-		this.conversion.register( 'downcast', [ this.editing.downcastDispatcher, this.data.downcastDispatcher ] );
-		this.conversion.register( 'editingDowncast', [ this.editing.downcastDispatcher ] );
-		this.conversion.register( 'dataDowncast', [ this.data.downcastDispatcher ] );
-
-		this.conversion.register( 'upcast', [ this.data.upcastDispatcher ] );
+		this.conversion = new Conversion( [ this.editing.downcastDispatcher, this.data.downcastDispatcher ], this.data.upcastDispatcher );
+		this.conversion.addAlias( 'dataDowncast', this.data.downcastDispatcher );
+		this.conversion.addAlias( 'editingDowncast', this.editing.downcastDispatcher );
 
 		/**
 		 * Instance of the {@link module:core/editingkeystrokehandler~EditingKeystrokeHandler}.
@@ -216,35 +211,16 @@ export default class Editor {
 	/**
 	 * Loads and initializes plugins specified in the config.
 	 *
-	 * @returns {Promise} A promise which resolves once the initialization is completed.
+	 * @returns {Promise.<module:core/plugin~LoadedPlugins>} A promise which resolves
+	 * once the initialization is completed providing an array of loaded plugins.
 	 */
 	initPlugins() {
-		const that = this;
 		const config = this.config;
+		const plugins = config.get( 'plugins' ) || [];
+		const removePlugins = config.get( 'removePlugins' ) || [];
+		const extraPlugins = config.get( 'extraPlugins' ) || [];
 
-		return loadPlugins()
-			.then( loadedPlugins => {
-				return initPlugins( loadedPlugins, 'init' )
-					.then( () => initPlugins( loadedPlugins, 'afterInit' ) );
-			} )
-			.then( () => this.fire( 'pluginsReady' ) );
-
-		function loadPlugins() {
-			const plugins = config.get( 'plugins' ) || [];
-			const removePlugins = config.get( 'removePlugins' ) || [];
-
-			return that.plugins.load( plugins, removePlugins );
-		}
-
-		function initPlugins( loadedPlugins, method ) {
-			return loadedPlugins.reduce( ( promise, plugin ) => {
-				if ( !plugin[ method ] ) {
-					return promise;
-				}
-
-				return promise.then( plugin[ method ].bind( plugin ) );
-			}, Promise.resolve() );
-		}
+		return this.plugins.init( plugins.concat( extraPlugins ), removePlugins );
 	}
 
 	/**
@@ -295,44 +271,24 @@ export default class Editor {
 	/**
 	 * Creates and initializes a new editor instance.
 	 *
-	 * @param {Object} config The editor config. You can find the list of config options in
-	 * {@link module:core/editor/editorconfig~EditorConfig}.
-	 * @returns {Promise} Promise resolved once editor is ready.
-	 * @returns {module:core/editor/editor~Editor} return.editor The editor instance.
+	 * This is an abstract method. Every editor type needs to implement its own initialization logic.
+	 *
+	 * See the `create()` methods of the existing editor types to learn how to use them:
+	 *
+	 * * {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`}
+	 * * {@link module:editor-balloon/ballooneditor~BalloonEditor.create `BalloonEditor.create()`}
+	 * * {@link module:editor-decoupled/decouplededitor~DecoupledEditor.create `DecoupledEditor.create()`}
+	 * * {@link module:editor-inline/inlineeditor~InlineEditor.create `InlineEditor.create()`}
+	 *
+	 * @abstract
+	 * @method module:core/editor/editor~Editor.create
 	 */
-	static create( config ) {
-		return new Promise( resolve => {
-			const editor = new this( config );
-
-			resolve(
-				editor.initPlugins()
-					.then( () => {
-						editor.fire( 'dataReady' );
-						editor.fire( 'ready' );
-					} )
-					.then( () => editor )
-			);
-		} );
-	}
 }
 
 mix( Editor, ObservableMixin );
 
 /**
- * Fired after {@link #initPlugins plugins are initialized}.
- *
- * @event pluginsReady
- */
-
-/**
- * Fired when the data loaded to the editor is ready. If a specific editor doesn't load
- * any data initially, this event will be fired right before {@link #event:ready}.
- *
- * @event dataReady
- */
-
-/**
- * Fired when {@link #event:pluginsReady plugins}, and {@link #event:dataReady data} and all additional
+ * Fired when {@link module:engine/controller/datacontroller~DataController#event:ready data} and all additional
  * editor components are ready.
  *
  * Note: This event is most useful for plugin developers. When integrating the editor with your website or
@@ -356,6 +312,22 @@ mix( Editor, ObservableMixin );
  * @event destroy
  */
 
+/**
+ * This error is thrown when a user tries to use a `<textarea>` element to create a non-classic editor in it.
+ *
+ * Textarea element represents a plain-text and cannot be used as a editable root element with included CKEditor5.
+ * Content of an editor should be nicely present to the user and show him how it's going to looks like. Textarea element
+ * doesn't support such behavior.
+ *
+ * Only {@glink builds/guides/overview#classic-editor Classic Editor} has implemented a special system, which
+ * **replace** DOM element and load data from it
+ * ({@link module:editor-classic/classiceditor~ClassicEditor.create more information}). All other editors
+ * use an existing element, load data from it and make this element editable. Details about behaviour of each editor
+ * might be found in an associated description of a `create` method of each editor.
+ *
+ * @error editor-wrong-element
+ */
+
 /**
  * An array of plugins built into this editor class.
  * It is used in CKEditor 5 builds to provide a list of plugins which are later automatically initialized

+ 85 - 2
packages/ckeditor5-core/src/editor/editorconfig.jsdoc

@@ -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
  */
 
 /**
@@ -30,6 +30,35 @@
  * @interface EditorConfig
  */
 
+/**
+ * The initial editor data to be used instead of the provided element's HTML content.
+ *
+ *		ClassicEditor
+ *			.create( document.querySelector( '#editor' ), {
+ *				initialData: '<h2>Initial data</h2><p>Foo bar.</p>'
+ *			} )
+ *			.then( ... )
+ *			.catch( ... );
+ *
+ * By default, the editor is initialized with the content of the element on which this editor is initialized.
+ * See {@link module:core/editor/editor~Editor.create Editor.create()}.
+ *
+ * This configuration option lets you override initial data if an element is passed to `Editor.create()`.
+ * It is especially useful if it is difficult for your integration to put the data inside the HTML element.
+ *
+ * **Note:** If initial data is passed to `Editor.create()` as a parameter and, at the same time, via this configuration option,
+ * an error will be thrown as those two options exclude themselves.
+ *
+ * @member {String} module:core/editor/editorconfig~EditorConfig#initialData
+ */
+
+/**
+ * The `config.initialData` option cannot be used together with initial data passed as the first parameter of
+ * {@link module:core/editor/editor~Editor.create `Editor.create()`}.
+ *
+ * @error editor-create-initial-data
+ */
+
 /**
  * The list of plugins to load.
  *
@@ -57,9 +86,34 @@
  *			plugins: [ Essentials, Bold ]
  *		};
  *
+ * **Note:** To load additional plugins, you should use the {@link #extraPlugins `extraPlugins`} configuration.
+ * To narrow the list of loaded plugins, use the {@link #removePlugins `removePlugins`} configuration.
+ *
  * @member {Array.<String|Function>} module:core/editor/editorconfig~EditorConfig#plugins
  */
 
+/**
+ * The list of additional plugins to load along those already available in the
+ * {@glink builds/guides/overview editor build}. It extends the {@link #plugins `plugins`} configuration.
+ *
+ *		function MyPlugin( editor ) {
+ *			// ...
+ *		}
+ *
+ *		const config = {
+ *			extraPlugins: [ MyPlugin ]
+ *		};
+ *
+ * **Note:** This configuration works only for simple plugins which utilize the
+ * {@link module:core/plugin~PluginInterface plugin interface} and have no dependencies. To extend a
+ * build with complex features, create a {@glink builds/guides/development/custom-builds custom build}.
+ *
+ * **Note:** Make sure you include the new features in you toolbar configuration. Learn more
+ * about {@glink builds/guides/integration/configuration#toolbar-setup toolbar setup}.
+ *
+ * @member {Array.<Function>} module:core/editor/editorconfig~EditorConfig#extraPlugins
+ */
+
 /**
  * The list of plugins which should not be loaded despite being available in an {@glink builds/guides/overview editor build}.
  *
@@ -143,3 +197,32 @@
  *
  * @member {String} module:core/editor/editorconfig~EditorConfig#language
  */
+
+/**
+ * Specifies the text displayed in the editor when there is no content (editor is empty). It is intended to
+ * help users locate the editor in the application (form) and prompt them to input the content. Work similarly
+ * as to the native DOM
+ * [`placeholder` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#The_placeholder_attribute)
+ * used by inputs.
+ *
+ *		const config = {
+ *			placeholder: 'Type some text...'
+ *		};
+ *
+ * The placeholder text is displayed as a pseudo–element of an empty paragraph in the editor content.
+ * The paragraph has the `.ck-placeholder` CSS class and the `data-placeholder` attribute.
+ *
+ *		<p data-placeholder="Type some text..." class="ck-placeholder">
+ *			::before
+ *		</p>
+ *
+ * **Note**: Placeholder text can also be set using the `placeholder` attribute if a `<textarea>` is passed to
+ * the `create()` method, e.g. {@link module:editor-classic/classiceditor~ClassicEditor.create `ClassicEditor.create()`}.
+ *
+ * **Note**: This configuration has precedence over the value of the `placeholder` attribute of a `<textarea>`
+ * element passed to the `create()` method.
+ *
+ * See the {@glink features/editor-placeholder "Editor placeholder" guide} for more information and live examples.
+ *
+ * @member {String} module:core/editor/editorconfig~EditorConfig#placeholder
+ */

+ 111 - 13
packages/ckeditor5-core/src/editor/editorui.js

@@ -1,12 +1,14 @@
 /**
- * @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
  */
 
 /**
  * @module core/editor/editorui
  */
 
+/* globals console */
+
 import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory';
 import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
 
@@ -23,9 +25,8 @@ export default class EditorUI {
 	 * Creates an instance of the 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 ) {
+	constructor( editor ) {
 		/**
 		 * The editor that the UI belongs to.
 		 *
@@ -34,14 +35,6 @@ export default class EditorUI {
 		 */
 		this.editor = editor;
 
-		/**
-		 * The main (top–most) view of the editor UI.
-		 *
-		 * @readonly
-		 * @member {module:ui/editorui/editoruiview~EditorUIView} #view
-		 */
-		this.view = view;
-
 		/**
 		 * An instance of the {@link module:ui/componentfactory~ComponentFactory}, a registry used by plugins
 		 * to register factories of specific UI components.
@@ -60,10 +53,37 @@ export default class EditorUI {
 		 */
 		this.focusTracker = new FocusTracker();
 
+		/**
+		 * Stores all editable elements used by the editor instance.
+		 *
+		 * @private
+		 * @member {Map.<String,HTMLElement>}
+		 */
+		this._editableElementsMap = new Map();
+
 		// Informs UI components that should be refreshed after layout change.
 		this.listenTo( editor.editing.view.document, 'layoutChanged', () => this.update() );
 	}
 
+	/**
+	 * The main (outermost) DOM element of the editor UI.
+	 *
+	 * For example, in {@link module:editor-classic/classiceditor~ClassicEditor} it is a `<div>` which
+	 * wraps the editable element and the toolbar. In {@link module:editor-inline/inlineeditor~InlineEditor}
+	 * it is the editable element itself (as there is no other wrapper). However, in
+	 * {@link module:editor-decoupled/decouplededitor~DecoupledEditor} it is set to `null` because this editor does not
+	 * come with a single "main" HTML element (its editable element and toolbar are separate).
+	 *
+	 * This property can be understood as a shorthand for retrieving the element that a specific editor integration
+	 * considers to be its main DOM element.
+	 *
+	 * @readonly
+	 * @member {HTMLElement|null} #element
+	 */
+	get element() {
+		return null;
+	}
+
 	/**
 	 * Fires the {@link module:core/editor/editorui~EditorUI#event:update `update`} event.
 	 *
@@ -79,9 +99,87 @@ export default class EditorUI {
 	 */
 	destroy() {
 		this.stopListening();
-		this.view.destroy();
+
+		this.focusTracker.destroy();
+
+		// Clean–up the references to the CKEditor instance stored in the native editable DOM elements.
+		for ( const domElement of this._editableElementsMap.values() ) {
+			domElement.ckeditorInstance = null;
+		}
+
+		this._editableElementsMap = new Map();
+	}
+
+	/**
+	 * Store the native DOM editable element used by the editor under
+	 * a unique name.
+	 *
+	 * @param {String} rootName The unique name of the editable element.
+	 * @param {HTMLElement} domElement The native DOM editable element.
+	 */
+	setEditableElement( rootName, domElement ) {
+		this._editableElementsMap.set( rootName, domElement );
+
+		// Put a reference to the CKEditor instance in the editable native DOM element.
+		// It helps 3rd–party software (browser extensions, other libraries) access and recognize
+		// CKEditor 5 instances (editing roots) and use their API (there is no global editor
+		// instance registry).
+		if ( !domElement.ckeditorInstance ) {
+			domElement.ckeditorInstance = this.editor;
+		}
 	}
 
+	/**
+	 * Returns the editable editor element with the given name or null if editable does not exist.
+	 *
+	 * @param {String} [rootName=main] The editable name.
+	 * @returns {HTMLElement|undefined}
+	 */
+	getEditableElement( rootName = 'main' ) {
+		return this._editableElementsMap.get( rootName );
+	}
+
+	/**
+	 * Returns array of names of all editor editable elements.
+	 *
+	 * @returns {Iterable.<String>}
+	 */
+	getEditableElementsNames() {
+		return this._editableElementsMap.keys();
+	}
+
+	/**
+	 * Stores all editable elements used by the editor instance.
+	 *
+	 * @protected
+	 * @deprecated
+	 * @member {Map.<String,HTMLElement>}
+	 */
+	get _editableElements() {
+		/**
+		 * The {@link module:core/editor/editorui~EditorUI#_editableElements `EditorUI#_editableElements`} property has been
+		 * deprecated and will be removed in the near future. Please use {@link #setEditableElement `setEditableElement()`} and
+		 * {@link #getEditableElement `getEditableElement()`} methods instead.
+		 *
+		 * @error editor-ui-deprecated-editable-elements
+		 * @param {module:core/editor/editorui~EditorUI} editorUI Editor UI instance the deprecated property belongs to.
+		 */
+		console.warn(
+			'editor-ui-deprecated-editable-elements: ' +
+			'The EditorUI#_editableElements property has been deprecated and will be removed in the near future.',
+			{ editorUI: this } );
+
+		return this._editableElementsMap;
+	}
+
+	/**
+	 * Fired when the editor UI is ready.
+	 *
+	 * Fired before {@link module:engine/controller/datacontroller~DataController#event:ready}.
+	 *
+	 * @event ready
+	 */
+
 	/**
 	 * Fired whenever the UI (all related components) should be refreshed.
 	 *

+ 2 - 28
packages/ckeditor5-core/src/editor/editorwithui.jsdoc

@@ -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
  */
 
 /**
@@ -27,29 +27,3 @@
  * @readonly
  * @member {module:core/editor/editorui~EditorUI} #ui
  */
-
- /**
-  * The main (outermost) DOM element of the editor UI.
-  *
-  * For example, in {@link module:editor-classic/classiceditor~ClassicEditor} it is a `<div>` which
-  * wraps the editable element and the toolbar. In {@link module:editor-inline/inlineeditor~InlineEditor}
-  * it is the editable element itself (as there is no other wrapper). However, in
-  * {@link module:editor-decoupled/decouplededitor~DecoupledEditor} it is set to `null` because this editor does not
-  * come with a single "main" HTML element (its editable element and toolbar are separate).
-  *
-  * This property can be understood as a shorthand for retrieving the element that a specific editor integration
-  * considers to be its main DOM element. There are always other ways to access these elements, too
-  * (e.g. via {@link #ui `editor.ui`}).
-  *
-  * @readonly
-  * @member {HTMLElement|null} #element
-  */
-
-/**
- * Fired when the editor UI is ready.
- *
- * Fired after {@link module:core/editor/editor~Editor#event:pluginsReady} and before
- * {@link module:core/editor/editor~Editor#event:dataReady}.
- *
- * @event uiReady
- */

+ 6 - 3
packages/ckeditor5-core/src/editor/utils/attachtoform.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
  */
 
 import { isFunction } from 'lodash-es';
@@ -26,7 +26,10 @@ export default function attachToForm( editor ) {
 		 *
 		 * @error attachtoform-missing-elementapi-interface
 		 */
-		throw new CKEditorError( 'attachtoform-missing-elementapi-interface: Editor passed to attachToForm() must implement ElementApi.' );
+		throw new CKEditorError(
+			'attachtoform-missing-elementapi-interface: Editor passed to attachToForm() must implement ElementApi.',
+			editor
+		);
 	}
 
 	const sourceElement = editor.sourceElement;

+ 10 - 5
packages/ckeditor5-core/src/editor/utils/dataapimixin.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
  */
 
 /**
@@ -24,8 +24,8 @@ const DataApiMixin = {
 	/**
 	 * @inheritDoc
 	 */
-	getData() {
-		return this.data.get();
+	getData( options ) {
+		return this.data.get( options );
 	}
 };
 
@@ -67,9 +67,14 @@ export default DataApiMixin;
  * See the {@glink features/markdown Markdown output} guide for more details.
  *
  * Note: Not only is the format of the data configurable, but the type of the `getData()`'s return value does not
- * have to be a string either. You can e.g. return an object or a DOM `DocumentFragment`  if you consider this
+ * have to be a string either. You can e.g. return an object or a DOM `DocumentFragment` if you consider this
  * the right format for you.
  *
  * @method #getData
+ * @param {Object} [options]
+ * @param {String} [options.rootName='main'] Root name.
+ * @param {String} [options.trim='empty'] Whether returned data should be trimmed. This option is set to `'empty'` by default,
+ * which means that whenever editor content is considered empty, an empty string is returned. To turn off trimming
+ * use `'none'`. In such cases exact content will be returned (for example `'<p>&nbsp;</p>'` for an empty editor).
  * @returns {String} Output data.
  */

+ 6 - 3
packages/ckeditor5-core/src/editor/utils/elementapimixin.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
  */
 
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
@@ -30,7 +30,10 @@ const ElementApiMixin = {
 			 *
 			 * @error editor-missing-sourceelement
 			 */
-			throw new CKEditorError( 'editor-missing-sourceelement: Cannot update the source element of a detached editor.' );
+			throw new CKEditorError(
+				'editor-missing-sourceelement: Cannot update the source element of a detached editor.',
+				this
+			);
 		}
 
 		setDataInElement( this.sourceElement, this.data.get() );

+ 50 - 0
packages/ckeditor5-core/src/editor/utils/securesourceelement.js

@@ -0,0 +1,50 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+
+/**
+ * @module core/editor/utils/securesourceelement
+ */
+
+/**
+ * Marks the source element on which the editor was initialized. This prevents other editor instances from using this element.
+ *
+ * Running multiple editor instances on the same source element causes various issues and it is
+ * crucial this helper is called as soon as the source element is known to prevent collisions.
+ *
+ * @param {module:core/editor/editor~Editor} editor Editor instance.
+ */
+export default function secureSourceElement( editor ) {
+	const sourceElement = editor.sourceElement;
+
+	// If the editor was initialized without specifying an element, we don't need to secure anything.
+	if ( !sourceElement ) {
+		return;
+	}
+
+	if ( sourceElement.ckeditorInstance ) {
+		/**
+		 * A DOM element used to create the editor (e.g.
+		 * {@link module:editor-inline/inlineeditor~InlineEditor.create `InlineEditor.create()`})
+		 * has already been used to create another editor instance. Make sure each editor is
+		 * created with an unique DOM element.
+		 *
+		 * @error editor-source-element-already-used
+		 * @param {HTMLElement} element DOM element that caused the collision.
+		 */
+		throw new CKEditorError(
+			'editor-source-element-already-used: ' +
+			'The DOM element cannot be used to create multiple editor instances.',
+			editor
+		);
+	}
+
+	sourceElement.ckeditorInstance = editor;
+
+	editor.once( 'destroy', () => {
+		delete sourceElement.ckeditorInstance;
+	} );
+}

+ 99 - 0
packages/ckeditor5-core/src/multicommand.js

@@ -0,0 +1,99 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import Command from './command';
+
+/**
+ * @module core/multicommand
+ */
+
+/**
+ * A CKEditor command that aggregates other commands.
+ *
+ * This command is used to proxy multiple commands. The multi-command is enabled when
+ * at least one of its registered child commands is enabled.
+ * When executing a multi-command the first command that is enabled will be executed.
+ *
+ *		const multiCommand = new MultiCommand( editor );
+ *
+ *		const commandFoo = new Command( editor );
+ *		const commandBar = new Command( editor );
+ *
+ *		// Register child commands.
+ *		multiCommand.registerChildCommand( commandFoo );
+ *		multiCommand.registerChildCommand( commandBar );
+ *
+ *		// Enable one of the commands.
+ *		commandBar.isEnabled = true;
+ *
+ *		multiCommand.execute(); // Will execute commandBar.
+ *
+ * @extends module:core/command~Command
+ */
+export default class MultiCommand extends Command {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor ) {
+		super( editor );
+
+		/**
+		 * Registered child commands.
+		 *
+		 * @type {Array.<module:core/command~Command>}
+		 * @private
+		 */
+		this._childCommands = [];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	refresh() {
+		// Override base command refresh(): the command's state is changed when one of child commands changes states.
+	}
+
+	/**
+	 * Executes the first of it registered child commands.
+	 */
+	execute( ...args ) {
+		const command = this._getFirstEnabledCommand();
+
+		command.execute( args );
+	}
+
+	/**
+	 * Registers a child command.
+	 *
+	 * @param {module:core/command~Command} command
+	 */
+	registerChildCommand( command ) {
+		this._childCommands.push( command );
+
+		// Change multi command enabled state when one of registered commands changes state.
+		command.on( 'change:isEnabled', () => this._checkEnabled() );
+
+		this._checkEnabled();
+	}
+
+	/**
+	 * Checks if any of child commands is enabled.
+	 *
+	 * @private
+	 */
+	_checkEnabled() {
+		this.isEnabled = !!this._getFirstEnabledCommand();
+	}
+
+	/**
+	 * Returns a first enabled command or undefined if none of them is enabled.
+	 *
+	 * @returns {module:core/command~Command|undefined}
+	 * @private
+	 */
+	_getFirstEnabledCommand() {
+		return this._childCommands.find( command => command.isEnabled );
+	}
+}

+ 5 - 5
packages/ckeditor5-core/src/pendingactions.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
  */
 
 /**
@@ -99,7 +99,7 @@ export default class PendingActions extends Plugin {
 			 *
 			 * @error pendingactions-add-invalid-message
 			 */
-			throw new CKEditorError( 'pendingactions-add-invalid-message: The message must be a string.' );
+			throw new CKEditorError( 'pendingactions-add-invalid-message: The message must be a string.', this );
 		}
 
 		const action = Object.create( ObservableMixin );
@@ -122,9 +122,9 @@ export default class PendingActions extends Plugin {
 	}
 
 	/**
-	 * Returns the first action from the list.
+	 * Returns the first action from the list or null when list is empty
 	 *
-	 * returns {Object} The pending action object.
+	 * returns {Object|null} The pending action object.
 	 */
 	get first() {
 		return this._actions.get( 0 );

+ 23 - 7
packages/ckeditor5-core/src/plugin.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
  */
 
 /**
@@ -53,16 +53,15 @@ mix( Plugin, ObservableMixin );
 /**
  * The base interface for CKEditor plugins.
  *
- * In its minimal form it can be a simple function (it will be used as a constructor) that accepts
- * {@link module:core/editor/editor~Editor the editor} as a parameter.
- * It can also implement a few methods which, when present, will be used to properly initialize and destroy the plugin.
+ * In its minimal form a plugin can be a simple function that accepts {@link module:core/editor/editor~Editor the editor}
+ * as a parameter:
  *
  *		// A simple plugin that enables a data processor.
  *		function MyPlugin( editor ) {
  *			editor.data.processor = new MyDataProcessor();
  *		}
  *
- * In most cases, however, you will want to inherit from the {@link module:core/plugin~Plugin} class which implements the
+ * In most cases however, you will want to inherit from the {@link module:core/plugin~Plugin} class which implements the
  * {@link module:utils/observablemixin~ObservableMixin} and is, therefore, more convenient:
  *
  *		class MyPlugin extends Plugin {
@@ -70,12 +69,23 @@ mix( Plugin, ObservableMixin );
  *				// `listenTo()` and `editor` are available thanks to `Plugin`.
  *				// By using `listenTo()` you will ensure that the listener is removed when
  *				// the plugin is destroyed.
- *				this.listenTo( this.editor, 'dataReady', () => {
+ *				this.listenTo( this.editor.data, 'ready', () => {
  *					// Do something when the data is ready.
  *				} );
  *			}
  *		}
  *
+ * The plugin can also implement methods (e.g. {@link module:core/plugin~PluginInterface#init `init()`} or
+ * {@link module:core/plugin~PluginInterface#destroy `destroy()`}) which, when present, will be used to properly
+ * initialize and destroy the plugin.
+ *
+ * **Note:** When defined as a plain function, the plugin acts as a constructor and will be
+ * called in parallel with other plugins' {@link module:core/plugin~PluginInterface#constructor constructors}.
+ * This means the code of that plugin will be executed **before** {@link module:core/plugin~PluginInterface#init `init()`} and
+ * {@link module:core/plugin~PluginInterface#afterInit `afterInit()`} methods of other plugins and, for instance,
+ * you cannot use it to extend other plugins' {@glink framework/guides/architecture/editing-engine#schema schema}
+ * rules as they are defined later on during the `init()` stage.
+ *
  * @interface PluginInterface
  */
 
@@ -169,3 +179,9 @@ mix( Plugin, ObservableMixin );
  * @method #destroy
  * @returns {null|Promise}
  */
+
+/**
+ * Array of loaded plugins.
+ *
+ * @typedef {Array.<module:core/plugin~PluginInterface>} module:core/plugin~LoadedPlugins
+ */

+ 100 - 18
packages/ckeditor5-core/src/plugincollection.js

@@ -1,17 +1,23 @@
 /**
- * @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
  */
 
 /**
  * @module core/plugincollection
  */
 
-import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
-import log from '@ckeditor/ckeditor5-utils/src/log';
+/* globals console */
+
+import CKEditorError, { attachLinkToDocumentation } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+
+import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
+import mix from '@ckeditor/ckeditor5-utils/src/mix';
 
 /**
  * Manages a list of CKEditor plugins, including loading, resolving dependencies and initialization.
+ *
+ * @mixes module:utils/emittermixin~EmitterMixin
  */
 export default class PluginCollection {
 	/**
@@ -20,7 +26,7 @@ export default class PluginCollection {
 	 *
 	 * @param {module:core/editor/editor~Editor} editor
 	 * @param {Array.<Function>} [availablePlugins] Plugins (constructors) which the collection will be able to use
-	 * when {@link module:core/plugincollection~PluginCollection#load} is used with plugin names (strings, instead of constructors).
+	 * when {@link module:core/plugincollection~PluginCollection#init} is used with plugin names (strings, instead of constructors).
 	 * Usually, the editor will pass its built-in plugins to the collection so they can later be
 	 * used in `config.plugins` or `config.removePlugins` by names.
 	 */
@@ -72,26 +78,84 @@ export default class PluginCollection {
 	/**
 	 * Gets the plugin instance by its constructor or name.
 	 *
+	 *		// Check if 'Clipboard' plugin was loaded.
+	 *		if ( editor.plugins.has( 'Clipboard' ) ) {
+	 *			// Get clipboard plugin instance
+	 *			const clipboard = editor.plugins.get( 'Clipboard' );
+	 *
+	 *			this.listenTo( clipboard, 'inputTransformation', ( evt, data ) => {
+	 *				// Do something on clipboard input.
+	 *			} );
+	 *		}
+	 *
+	 * **Note**: This method will throw error if plugin is not loaded. Use `{@link #has editor.plugins.has()}`
+	 * to check if plugin is available.
+	 *
 	 * @param {Function|String} key The plugin constructor or {@link module:core/plugin~PluginInterface.pluginName name}.
 	 * @returns {module:core/plugin~PluginInterface}
 	 */
 	get( key ) {
-		return this._plugins.get( key );
+		const plugin = this._plugins.get( key );
+
+		if ( !plugin ) {
+			/**
+			 * The plugin is not loaded and could not be obtained.
+			 *
+			 * Plugin classes (constructors) need to be provided to the editor and must be loaded before they can be obtained from
+			 * the plugin collection.
+			 * This is usually done in CKEditor 5 builds by setting the {@link module:core/editor/editor~Editor.builtinPlugins}
+			 * property.
+			 *
+			 * **Note**: You can use `{@link module:core/plugincollection~PluginCollection#has editor.plugins.has()}`
+			 * to check if plugin was loaded.
+			 *
+			 * @error plugincollection-plugin-not-loaded
+			 * @param {String} plugin The name of the plugin which is not loaded.
+			 */
+			const errorMsg = 'plugincollection-plugin-not-loaded: The requested plugin is not loaded.';
+
+			let pluginName = key;
+
+			if ( typeof key == 'function' ) {
+				pluginName = key.pluginName || key.name;
+			}
+
+			throw new CKEditorError( errorMsg, this._editor, { plugin: pluginName } );
+		}
+
+		return plugin;
+	}
+
+	/**
+	 * Checks if plugin is loaded.
+	 *
+	 *		// Check if 'Clipboard' plugin was loaded.
+	 *		if ( editor.plugins.has( 'Clipboard' ) ) {
+	 *			// Now use clipboard plugin instance:
+	 *			const clipboard = editor.plugins.get( 'Clipboard' );
+	 *
+	 *			// ...
+	 *		}
+	 *
+	 * @param {Function|String} key The plugin constructor or {@link module:core/plugin~PluginInterface.pluginName name}.
+	 * @returns {Boolean}
+	 */
+	has( key ) {
+		return this._plugins.has( key );
 	}
 
 	/**
-	 * Loads a set of plugins and adds them to the collection.
+	 * Initializes a set of plugins and adds them to the collection.
 	 *
 	 * @param {Array.<Function|String>} plugins An array of {@link module:core/plugin~PluginInterface plugin constructors}
 	 * or {@link module:core/plugin~PluginInterface.pluginName plugin names}. The second option (names) works only if
 	 * `availablePlugins` were passed to the {@link #constructor}.
 	 * @param {Array.<String|Function>} [removePlugins] Names of plugins or plugin constructors
 	 * that should not be loaded (despite being specified in the `plugins` array).
-	 * @returns {Promise} A promise which gets resolved once all plugins are loaded and available in the
-	 * collection.
-	 * @returns {Promise.<Array.<module:core/plugin~PluginInterface>>} returns.loadedPlugins The array of loaded plugins.
+	 * @returns {Promise.<module:core/plugin~LoadedPlugins>} A promise which gets resolved once all plugins are loaded
+	 * and available in the collection.
 	 */
-	load( plugins, removePlugins = [] ) {
+	init( plugins, removePlugins = [] ) {
 		const that = this;
 		const editor = this._editor;
 		const loading = new Set();
@@ -126,12 +190,14 @@ export default class PluginCollection {
 			const errorMsg = 'plugincollection-plugin-not-found: Some plugins are not available and could not be loaded.';
 
 			// Log the error so it's more visible on the console. Hopefully, for better DX.
-			log.error( errorMsg, { plugins: missingPlugins } );
+			console.error( attachLinkToDocumentation( errorMsg ), { plugins: missingPlugins } );
 
-			return Promise.reject( new CKEditorError( errorMsg, { plugins: missingPlugins } ) );
+			return Promise.reject( new CKEditorError( errorMsg, this._editor, { plugins: missingPlugins } ) );
 		}
 
 		return Promise.all( pluginConstructors.map( loadPlugin ) )
+			.then( () => initPlugins( loaded, 'init' ) )
+			.then( () => initPlugins( loaded, 'afterInit' ) )
 			.then( () => loaded );
 
 		function loadPlugin( PluginConstructor ) {
@@ -140,7 +206,7 @@ export default class PluginCollection {
 			}
 
 			// The plugin is already loaded or being loaded - do nothing.
-			if ( that.get( PluginConstructor ) || loading.has( PluginConstructor ) ) {
+			if ( that._plugins.has( PluginConstructor ) || loading.has( PluginConstructor ) ) {
 				return;
 			}
 
@@ -166,12 +232,24 @@ export default class PluginCollection {
 					 * @error plugincollection-load
 					 * @param {String} plugin The name of the plugin that could not be loaded.
 					 */
-					log.error( 'plugincollection-load: It was not possible to load the plugin.', { plugin: PluginConstructor } );
+					console.error( attachLinkToDocumentation(
+						'plugincollection-load: It was not possible to load the plugin.'
+					), { plugin: PluginConstructor } );
 
 					throw err;
 				} );
 		}
 
+		function initPlugins( loadedPlugins, method ) {
+			return loadedPlugins.reduce( ( promise, plugin ) => {
+				if ( !plugin[ method ] ) {
+					return promise;
+				}
+
+				return promise.then( plugin[ method ].bind( plugin ) );
+			}, Promise.resolve() );
+		}
+
 		function instantiatePlugin( PluginConstructor ) {
 			return new Promise( resolve => {
 				loading.add( PluginConstructor );
@@ -191,6 +269,7 @@ export default class PluginCollection {
 							throw new CKEditorError(
 								'plugincollection-required: Cannot load a plugin because one of its dependencies is listed in' +
 								'the `removePlugins` option.',
+								editor,
 								{ plugin: RequiredPluginConstructor, requiredBy: PluginConstructor }
 							);
 						}
@@ -295,12 +374,15 @@ export default class PluginCollection {
 			 * @param {Function} plugin1 The first plugin constructor.
 			 * @param {Function} plugin2 The second plugin constructor.
 			 */
-			log.warn(
+			throw new CKEditorError(
 				'plugincollection-plugin-name-conflict: Two plugins with the same name were loaded.',
+				null,
 				{ pluginName, plugin1: this._plugins.get( pluginName ).constructor, plugin2: PluginConstructor }
 			);
-		} else {
-			this._plugins.set( pluginName, plugin );
 		}
+
+		this._plugins.set( pluginName, plugin );
 	}
 }
+
+mix( PluginCollection, EmitterMixin );

+ 4 - 4
packages/ckeditor5-core/tests/_utils-tests/articlepluginset.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
  */
 
 /* global document */
@@ -82,7 +82,7 @@ describe( 'ArticlePluginSet', () => {
 				'<li>OL List item 2</li>' +
 			'</ol>' +
 			'<figure class="image image-style-side">' +
-				'<img alt="bar" src="foo">' +
+				'<img alt="bar" src="/assets/sample.png">' +
 				'<figcaption>Caption</figcaption>' +
 			'</figure>' +
 			'<blockquote>' +
@@ -118,7 +118,7 @@ describe( 'ArticlePluginSet', () => {
 				'<li>OL List item 2</li>' +
 			'</ol>' +
 			'<figure class="image image-style-side">' +
-				'<img alt="bar" src="foo"></img>' +
+				'<img alt="bar" src="/assets/sample.png"></img>' +
 				'<figcaption>Caption</figcaption>' +
 			'</figure>' +
 			'<blockquote>' +

+ 73 - 15
packages/ckeditor5-core/tests/_utils-tests/classictesteditor.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 document */
@@ -10,6 +10,7 @@ import ClassicTestEditor from '../../tests/_utils/classictesteditor';
 
 import Plugin from '../../src/plugin';
 import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 
 import EditorUI from '../../src/editor/editorui';
 import BoxedEditorUIView from '@ckeditor/ckeditor5-ui/src/editorui/boxed/boxededitoruiview';
@@ -21,6 +22,7 @@ import RootElement from '@ckeditor/ckeditor5-engine/src/model/rootelement';
 
 import { getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 import testUtils from '../../tests/_utils/utils';
+import { assertCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
 
 describe( 'ClassicTestEditor', () => {
 	let editorElement;
@@ -32,13 +34,17 @@ describe( 'ClassicTestEditor', () => {
 		document.body.appendChild( editorElement );
 	} );
 
+	afterEach( () => {
+		editorElement.remove();
+	} );
+
 	describe( 'constructor()', () => {
 		it( 'creates an instance of editor', () => {
 			const editor = new ClassicTestEditor( editorElement, { foo: 1 } );
 
 			expect( editor ).to.be.instanceof( Editor );
 			expect( editor.config.get( 'foo' ) ).to.equal( 1 );
-			expect( editor.element ).to.equal( editorElement );
+			expect( editor.sourceElement ).to.equal( editorElement );
 			expect( editor.ui ).to.be.instanceOf( EditorUI );
 			expect( editor.ui.view ).to.be.instanceOf( BoxedEditorUIView );
 			expect( editor.data.processor ).to.be.instanceof( HtmlDataProcessor );
@@ -57,7 +63,7 @@ describe( 'ClassicTestEditor', () => {
 			expect( editor.ui ).to.be.instanceOf( EditorUI );
 			expect( editor.ui.view ).to.be.instanceOf( BoxedEditorUIView );
 			expect( editor.ui.view.isRendered ).to.be.false;
-			expect( editor.ui.view.editableElement ).to.be.undefined;
+			expect( editor.ui.getEditableElement() ).to.be.undefined;
 		} );
 
 		it( 'creates main root element', () => {
@@ -82,18 +88,20 @@ describe( 'ClassicTestEditor', () => {
 					expect( editor ).to.be.instanceof( ClassicTestEditor );
 
 					expect( editor.config.get( 'foo' ) ).to.equal( 1 );
-					expect( editor ).to.have.property( 'element', editorElement );
+					expect( editor.sourceElement ).to.equal( editorElement );
 				} );
 		} );
 
 		it( 'renders the view including #editable and sets #editableElement', () => {
 			return ClassicTestEditor.create( editorElement, { foo: 1 } )
 				.then( editor => {
-					const view = editor.ui.view;
+					const ui = editor.ui;
+					const view = ui.view;
 
 					expect( view.isRendered ).to.be.true;
-					expect( view.editableElement.tagName ).to.equal( 'DIV' );
-					expect( view.editableElement ).to.equal( view.editable.element );
+					expect( ui.getEditableElement().tagName ).to.equal( 'DIV' );
+					expect( ui.getEditableElement() ).to.equal( view.editable.element );
+					expect( view.editable.name ).to.equal( 'main' );
 				} );
 		} );
 
@@ -116,14 +124,13 @@ describe( 'ClassicTestEditor', () => {
 			const fired = [];
 
 			function spy( evt ) {
-				fired.push( evt.name );
+				fired.push( `${ evt.name }-${ evt.source.constructor.name.toLowerCase() }` );
 			}
 
 			class EventWatcher extends Plugin {
 				init() {
-					this.editor.on( 'pluginsReady', spy );
-					this.editor.on( 'uiReady', spy );
-					this.editor.on( 'dataReady', spy );
+					this.editor.ui.on( 'ready', spy );
+					this.editor.data.on( 'ready', spy );
 					this.editor.on( 'ready', spy );
 				}
 			}
@@ -133,7 +140,11 @@ describe( 'ClassicTestEditor', () => {
 					plugins: [ EventWatcher ]
 				} )
 				.then( editor => {
-					expect( fired ).to.deep.equal( [ 'pluginsReady', 'uiReady', 'dataReady', 'ready' ] );
+					expect( fired ).to.deep.equal( [
+						'ready-classictesteditorui',
+						'ready-datacontroller',
+						'ready-classictesteditor'
+					] );
 
 					return editor.destroy();
 				} );
@@ -170,6 +181,53 @@ describe( 'ClassicTestEditor', () => {
 					return editor.destroy();
 				} );
 		} );
+
+		it( 'initializes the data controller with `config.initialData` if this option is provided', () => {
+			return ClassicTestEditor.create( editorElement, { initialData: '<p>foo</p>', plugins: [ Paragraph ] } )
+				.then( editor => {
+					expect( editor.getData() ).to.equal( '<p>foo</p>' );
+
+					return editor.destroy();
+				} );
+		} );
+
+		it( 'initializes the data controller with an empty string if the `config.initialData` is not provided', () => {
+			return ClassicTestEditor.create( editorElement )
+				.then( editor => {
+					expect( editor.getData() ).to.equal( '' );
+
+					return editor.destroy();
+				} );
+		} );
+
+		it( 'initializes the data controller with the data from the source element', () => {
+			editorElement.innerHTML = '<p>foo</p>';
+
+			return ClassicTestEditor.create( editorElement, { plugins: [ Paragraph ] } )
+				.then( editor => {
+					expect( editor.getData() ).to.equal( '<p>foo</p>' );
+
+					return editor.destroy();
+				} );
+		} );
+
+		it( 'initializes the data controller with the data from the first argument if it is a string', () => {
+			return ClassicTestEditor.create( '<p>foo</p>', { plugins: [ Paragraph ] } )
+				.then( editor => {
+					expect( editor.getData() ).to.equal( '<p>foo</p>' );
+
+					return editor.destroy();
+				} );
+		} );
+
+		it( 'throws when the data is passed from as the first argument and as a `config.initialData` at the same time', () => {
+			return ClassicTestEditor.create( '<p>foo</p>', { initialData: '<p>bar</p>' } )
+				.then( () => {
+					throw new Error( 'It should throw an error' );
+				}, err => {
+					assertCKEditorError( err, /^editor-create-initial-data:/, null );
+				} );
+		} );
 	} );
 
 	describe( 'destroy()', () => {
@@ -190,11 +248,11 @@ describe( 'ClassicTestEditor', () => {
 		it( 'restores the editor element', () => {
 			return ClassicTestEditor.create( editorElement, { foo: 1 } )
 				.then( editor => {
-					expect( editor.element.style.display ).to.equal( 'none' );
+					expect( editor.sourceElement.style.display ).to.equal( 'none' );
 
 					return editor.destroy()
 						.then( () => {
-							expect( editor.element.style.display ).to.equal( '' );
+							expect( editor.sourceElement.style.display ).to.equal( '' );
 						} );
 				} );
 		} );

+ 2 - 2
packages/ckeditor5-core/tests/_utils-tests/createsinonsandbox.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
  */
 
 import testUtils from '../../tests/_utils/utils';

+ 2 - 2
packages/ckeditor5-core/tests/_utils-tests/modeltesteditor.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
  */
 
 import Editor from '../../src/editor/editor';

+ 2 - 2
packages/ckeditor5-core/tests/_utils-tests/utils.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
  */
 
 import testUtils from '../_utils/utils';

+ 38 - 2
packages/ckeditor5-core/tests/_utils-tests/virtualtesteditor.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
  */
 
 import Editor from '../../src/editor/editor';
@@ -9,6 +9,7 @@ import VirtualTestEditor from '../../tests/_utils/virtualtesteditor';
 import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
 import DataApiMixin from '../../src/editor/utils/dataapimixin';
 import RootElement from '@ckeditor/ckeditor5-engine/src/model/rootelement';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 
 import testUtils from '../../tests/_utils/utils';
 
@@ -34,4 +35,39 @@ describe( 'VirtualTestEditor', () => {
 			expect( testUtils.isMixed( VirtualTestEditor, DataApiMixin ) ).to.true;
 		} );
 	} );
+
+	describe( 'static create()', () => {
+		it( 'initializes the data controller with the `config.initialData`', () => {
+			return VirtualTestEditor.create( { initialData: '<p>foo</p>', plugins: [ Paragraph ] } )
+				.then( editor => {
+					expect( editor.getData() ).to.equal( '<p>foo</p>' );
+
+					return editor.destroy();
+				} );
+		} );
+
+		it( 'initializes the data controller with an empty string if the `config.initialData` is not provided', () => {
+			return VirtualTestEditor.create()
+				.then( editor => {
+					expect( editor.getData() ).to.equal( '' );
+
+					return editor.destroy();
+				} );
+		} );
+
+		it( 'fires the `data#ready` event once', () => {
+			const dataReadySpy = sinon.spy();
+
+			const Plugin = function( editor ) {
+				editor.data.on( 'ready', dataReadySpy );
+			};
+
+			return VirtualTestEditor.create( { plugins: [ Plugin ] } )
+				.then( editor => {
+					sinon.assert.calledOnce( dataReadySpy );
+
+					return editor.destroy();
+				} );
+		} );
+	} );
 } );

+ 2 - 2
packages/ckeditor5-core/tests/_utils/articlepluginset.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
  */
 
 /**

+ 106 - 32
packages/ckeditor5-core/tests/_utils/classictesteditor.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
  */
 
 import Editor from '../../src/editor/editor';
@@ -13,6 +13,8 @@ import ElementReplacer from '@ckeditor/ckeditor5-utils/src/elementreplacer';
 import InlineEditableUIView from '@ckeditor/ckeditor5-ui/src/editableui/inline/inlineeditableuiview';
 import getDataFromElement from '@ckeditor/ckeditor5-utils/src/dom/getdatafromelement';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
+import { isElement } from 'lodash-es';
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 
 /**
  * A simplified classic editor. Useful for testing features.
@@ -24,71 +26,143 @@ export default class ClassicTestEditor extends Editor {
 	/**
 	 * @inheritDoc
 	 */
-	constructor( element, config ) {
+	constructor( sourceElementOrData, config ) {
 		super( config );
 
-		// The element on which the editor has been initialized.
-		this.element = element;
+		if ( isElement( sourceElementOrData ) ) {
+			this.sourceElement = sourceElementOrData;
+		}
 
 		// Use the HTML data processor in this editor.
 		this.data.processor = new HtmlDataProcessor();
 
-		this.ui = new EditorUI( this, new BoxedEditorUIView( this.locale ) );
-
-		// Expose properties normally exposed by the ClassicEditorUI.
-		this.ui.view.editable = new InlineEditableUIView( this.ui.view.locale );
-
-		// A helper to easily replace the editor#element with editor.editable#element.
-		this._elementReplacer = new ElementReplacer();
-
 		// Create the ("main") root element of the model tree.
 		this.model.document.createRoot();
+
+		this.ui = new ClassicTestEditorUI( this, new BoxedEditorUIView( this.locale ) );
+
+		// Expose properties normally exposed by the ClassicEditorUI.
+		this.ui.view.editable = new InlineEditableUIView( this.ui.view.locale, this.editing.view );
 	}
 
 	/**
 	 * @inheritDoc
 	 */
 	destroy() {
-		this._elementReplacer.restore();
+		if ( this.sourceElement ) {
+			this.updateSourceElement();
+		}
+
 		this.ui.destroy();
 
 		return super.destroy();
 	}
 
 	/**
-	 * @inheritDoc
+	 * @param {HTMLElement|String} sourceElementOrData The DOM element that will be the source for the created editor
+	 * or the editor's initial data.
+	 * @param {module:core/editor/editorconfig~EditorConfig} [config] The editor configuration.
+	 * @returns {Promise} A promise resolved once the editor is ready. The promise resolves with the created editor instance.
 	 */
-	static create( element, config ) {
+	static create( sourceElementOrData, config = {} ) {
 		return new Promise( resolve => {
-			const editor = new this( element, config );
+			const editor = new this( sourceElementOrData, config );
 
 			resolve(
 				editor.initPlugins()
 					// Simulate EditorUI.init() (e.g. like in ClassicEditorUI). The ui#view
 					// should be rendered after plugins are initialized.
+					.then( () => editor.ui.init( isElement( sourceElementOrData ) ? sourceElementOrData : null ) )
+					.then( () => editor.editing.view.attachDomRoot( editor.ui.getEditableElement() ) )
 					.then( () => {
-						const view = editor.ui.view;
+						if ( !isElement( sourceElementOrData ) && config.initialData ) {
+							// Documented in core/editor/editorconfig.jsdoc.
+							throw new CKEditorError(
+								'editor-create-initial-data: ' +
+								'The config.initialData option cannot be used together with initial data passed in Editor.create().',
+								null
+							);
+						}
 
-						view.render();
-						view.main.add( view.editable );
-						view.editableElement = view.editable.element;
-					} )
-					.then( () => {
-						editor._elementReplacer.replace( element, editor.ui.view.element );
-						editor.fire( 'uiReady' );
-					} )
-					.then( () => editor.editing.view.attachDomRoot( editor.ui.view.editableElement ) )
-					.then( () => editor.data.init( getDataFromElement( element ) ) )
-					.then( () => {
-						editor.fire( 'dataReady' );
-						editor.state = 'ready';
-						editor.fire( 'ready' );
+						editor.data.init( config.initialData || getInitialData( sourceElementOrData ) );
 					} )
+					.then( () => editor.fire( 'ready' ) )
 					.then( () => editor )
 			);
 		} );
 	}
 }
 
+/**
+ * A simplified classic editor ui class.
+ *
+ * @memberOf tests.core._utils
+ * @extends core.editor.EditorUI
+ */
+class ClassicTestEditorUI extends EditorUI {
+	/**
+	 * @inheritDoc
+	 */
+	constructor( editor, view ) {
+		super( editor );
+
+		// A helper to easily replace the editor#element with editor.editable#element.
+		this._elementReplacer = new ElementReplacer();
+
+		this._view = view;
+	}
+
+	/**
+	 * The main (top–most) view of the editor UI.
+	 *
+	 * @readonly
+	 * @member {module:ui/editorui/editoruiview~EditorUIView} #view
+	 */
+	get view() {
+		return this._view;
+	}
+
+	/**
+	 * Initializes the UI.
+	 *
+	 * @param {HTMLElement|null} replacementElement The DOM element that will be the source for the created editor.
+	 */
+	init( replacementElement ) {
+		const view = this.view;
+		const editable = view.editable;
+		const editingView = this.editor.editing.view;
+		const editingRoot = editingView.document.getRoot();
+
+		editable.name = editingRoot.rootName;
+
+		view.render();
+
+		view.main.add( view.editable );
+
+		this.setEditableElement( 'main', view.editable.element );
+
+		if ( replacementElement ) {
+			this._elementReplacer.replace( replacementElement, view.element );
+		}
+
+		this.fire( 'ready' );
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	destroy() {
+		this._elementReplacer.restore();
+
+		this._view.destroy();
+
+		super.destroy();
+	}
+}
+
 mix( ClassicTestEditor, DataApiMixin );
 mix( ClassicTestEditor, ElementApiMixin );
+
+function getInitialData( sourceElementOrData ) {
+	return isElement( sourceElementOrData ) ? getDataFromElement( sourceElementOrData ) : sourceElementOrData;
+}

+ 155 - 0
packages/ckeditor5-core/tests/_utils/memory.js

@@ -0,0 +1,155 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* global window, document, setTimeout */
+
+const TEST_RETRIES = 2;
+const TEST_TIMEOUT = 5000;
+const GARBAGE_COLLECTOR_TIMEOUT = 500;
+
+/**
+ * Memory tests suite definition that:
+ *
+ * * skips tests when garbage collector is not available,
+ * * creates/destroys editor element (id = 'mem-editor').
+ *
+ * This method should be used with dedicated memory usage test case functions:
+ *
+ *        describe( 'editor', () => {
+ *			// Other tests.
+ *
+ *			describeMemoryUsage( () => {
+ *				testMemoryUsage( 'should not grow on multiple create/destroy', () => {
+ *					return ClassicEditor.create( document.querySelector( '#mem-editor' ) );
+ *				} );
+ *			} );
+ *		} );
+ *
+ * @param {Function} callback Callback with test suit body
+ */
+export function describeMemoryUsage( callback ) {
+	// Skip all memory tests due to https://github.com/ckeditor/ckeditor5/issues/1731.
+	describe.skip( 'memory usage', () => {
+		skipIfNoGarbageCollector();
+
+		beforeEach( createEditorElement );
+
+		afterEach( removeEditorElement );
+
+		callback();
+	} );
+}
+
+/**
+ * Single test case for memory usage test. Handles the memory leak test procedure.
+ *
+ * 1. Create and destroy the editor instance to pre-fill the memory with some cacheable data.
+ * 2. Record the heap size.
+ * 3. Create and destroy the editor 5 times.
+ * 4. Record the heap size and compare with the previous result.
+ * 5. Fail when exceeded a 1MB treshold (see code comments for why 1MB).
+ *
+ * See {@link describeMemoryUsage} function for usage details.
+ *
+ * @param {String} testName Name of a test case.
+ * @param {Function} createEditor Callback which creates editor and returns its `.create()` promise.
+ */
+export function testMemoryUsage( testName, createEditor ) {
+	it( testName, function() {
+		this.timeout( TEST_TIMEOUT );
+
+		// Unfortunately the tests fails from time to time so retry a failed tests.
+		this.retries( TEST_RETRIES );
+
+		return runTest( createEditor );
+	} );
+}
+
+// Runs a single test case.
+function runTest( createEditor ) {
+	let memoryAfterFirstStart;
+
+	return Promise
+		.resolve()
+		// Initialize the first editor before mesuring the heap size.
+		// A cold start may allocate a bit of memory on the module-level.
+		.then( createAndDestroy )
+		.then( () => {
+			return collectMemoryStats().then( mem => {
+				memoryAfterFirstStart = mem;
+			} );
+		} )
+		// Run create&destroy multiple times. Helps scaling up the issue.
+		.then( createAndDestroy ) // #1
+		.then( createAndDestroy ) // #2
+		.then( createAndDestroy ) // #3
+		.then( createAndDestroy ) // #4
+		.then( createAndDestroy ) // #5
+		.then( collectMemoryStats )
+		.then( memory => {
+			const memoryDifference = memory.usedJSHeapSize - memoryAfterFirstStart.usedJSHeapSize;
+
+			// While theoretically we should get 0KB when there's no memory leak, in reality,
+			// the results we get (when there are no leaks) vary from -500KB to 500KB (depending on which tests are executed).
+			// However, when we had memory leaks, memoryDifference was reaching 20MB,
+			// so, in order to detect significant memory leaks we can expect that the heap won't grow more than 1MB.
+			expect( memoryDifference, 'used heap size should not grow' ).to.be.at.most( 1e6 );
+		} );
+
+	function createAndDestroy() {
+		return Promise.resolve()
+			.then( createEditor )
+			.then( destroyEditor );
+	}
+}
+
+function createEditorElement() {
+	const editorElement = document.createElement( 'div' );
+	editorElement.id = 'mem-editor';
+
+	editorElement.innerHTML =
+		'<h2>Editor 1</h2>\n' +
+		'<p>This is an editor instance. And there\'s <a href="http://ckeditor.com">some link</a>.</p>';
+
+	document.body.appendChild( editorElement );
+}
+
+function removeEditorElement() {
+	document.getElementById( 'mem-editor' ).remove();
+}
+
+function destroyEditor( editor ) {
+	return editor.destroy();
+}
+
+function collectMemoryStats() {
+	return new Promise( resolve => {
+		// Enforce garbage collection before recording memory stats.
+		window.gc();
+
+		setTimeout( () => {
+			const memeInfo = window.performance.memory;
+
+			resolve( {
+				totalJSHeapSize: memeInfo.totalJSHeapSize,
+				usedJSHeapSize: memeInfo.usedJSHeapSize,
+				jsHeapSizeLimit: memeInfo.jsHeapSizeLimit
+			} );
+		}, GARBAGE_COLLECTOR_TIMEOUT );
+	} );
+}
+
+// Will skip test suite if not in compatible browser.
+// Currently on Google Chrome supports this method and must be run with proper flags:
+//
+// 		google-chrome -js-flags="--expose-gc"
+//
+function skipIfNoGarbageCollector() {
+	before( function() {
+		if ( !window.gc ) {
+			this.skip();
+		}
+	} );
+}

+ 18 - 2
packages/ckeditor5-core/tests/_utils/modeltesteditor.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
  */
 
 import Editor from '../../src/editor/editor';
@@ -29,6 +29,22 @@ export default class ModelTestEditor extends Editor {
 		// Create the ("main") root element of the model tree.
 		this.model.document.createRoot();
 	}
+
+	static create( config ) {
+		return new Promise( resolve => {
+			const editor = new this( config );
+
+			resolve(
+				editor.initPlugins()
+					.then( () => {
+						// Fire `data#ready` event manually as `data#init()` method is not used.
+						editor.data.fire( 'ready' );
+						editor.fire( 'ready' );
+					} )
+					.then( () => editor )
+			);
+		} );
+	}
 }
 
 mix( ModelTestEditor, DataApiMixin );

+ 12 - 12
packages/ckeditor5-core/tests/_utils/utils.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
  */
 
 /**
@@ -49,19 +49,19 @@ const utils = {
 	 *
 	 * Usage:
 	 *
-	 *      it( 'test', () => {
-	 *          // Test bootstrapping...
+	 *		it( 'test', () => {
+	 *			// Test bootstrapping...
 	 *
-	 *          const assertEdge = () => {
-	 *              // expect();
-	 *          };
+	 *			const assertEdge = () => {
+	 *				// expect();
+	 *			};
 	 *
-	 *          const assertAll = () => {
-	 *              // expect();
-	 *          };
+	 *			const assertAll = () => {
+	 *				// expect();
+	 *			};
 	 *
-	 *          testUtils.checkAssertions( assertEdge, assertAll );
-	 *      } );
+	 *			testUtils.checkAssertions( assertEdge, assertAll );
+	 *		} );
 	 *
 	 * @param {...Function} assertions Functions that will be executed.
 	 */

+ 17 - 2
packages/ckeditor5-core/tests/_utils/virtualtesteditor.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
  */
 
 import Editor from '../../src/editor/editor';
@@ -26,6 +26,21 @@ export default class VirtualTestEditor extends Editor {
 		// Create the ("main") root element of the model tree.
 		this.model.document.createRoot();
 	}
+
+	static create( config = {} ) {
+		return new Promise( resolve => {
+			const editor = new this( config );
+
+			resolve(
+				editor.initPlugins()
+					.then( () => editor.data.init( config.initialData || '' ) )
+					.then( () => {
+						editor.fire( 'ready' );
+						return editor;
+					} )
+			);
+		} );
+	}
 }
 
 mix( VirtualTestEditor, DataApiMixin );

+ 60 - 2
packages/ckeditor5-core/tests/command.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
  */
 
 import Command from '../src/command';
@@ -183,4 +183,62 @@ describe( 'Command', () => {
 			}
 		} );
 	} );
+
+	describe( 'forceDisabled() / clearForceDisabled()', () => {
+		it( 'forceDisabled() should disable the command', () => {
+			command.forceDisabled( 'foo' );
+			command.isEnabled = true;
+
+			expect( command.isEnabled ).to.be.false;
+		} );
+
+		it( 'clearForceDisabled() should enable the command', () => {
+			command.forceDisabled( 'foo' );
+			command.clearForceDisabled( 'foo' );
+
+			expect( command.isEnabled ).to.be.true;
+		} );
+
+		it( 'clearForceDisabled() used with wrong identifier should not enable the command', () => {
+			command.forceDisabled( 'foo' );
+			command.clearForceDisabled( 'bar' );
+			command.isEnabled = true;
+
+			expect( command.isEnabled ).to.be.false;
+		} );
+
+		it( 'using forceDisabled() twice with the same identifier should not have any effect', () => {
+			command.forceDisabled( 'foo' );
+			command.forceDisabled( 'foo' );
+			command.clearForceDisabled( 'foo' );
+
+			expect( command.isEnabled ).to.be.true;
+		} );
+
+		it( 'command is enabled only after all disables were cleared', () => {
+			command.forceDisabled( 'foo' );
+			command.forceDisabled( 'bar' );
+			command.clearForceDisabled( 'foo' );
+			command.isEnabled = true;
+
+			expect( command.isEnabled ).to.be.false;
+
+			command.clearForceDisabled( 'bar' );
+
+			expect( command.isEnabled ).to.be.true;
+		} );
+
+		it( 'command should remain disabled if isEnabled has a callback disabling it', () => {
+			command.on( 'set:isEnabled', evt => {
+				evt.return = false;
+				evt.stop();
+			} );
+
+			command.forceDisabled( 'foo' );
+			command.clearForceDisabled( 'foo' );
+			command.isEnabled = true;
+
+			expect( command.isEnabled ).to.be.false;
+		} );
+	} );
 } );

+ 8 - 5
packages/ckeditor5-core/tests/commandcollection.js

@@ -1,12 +1,12 @@
 /**
- * @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
  */
 
 import CommandCollection from '../src/commandcollection';
-import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import Command from '../src/command';
 import ModelTestEditor from './_utils/modeltesteditor';
+import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
 
 class SomeCommand extends Command {
 	execute() {}
@@ -56,9 +56,12 @@ describe( 'CommandCollection', () => {
 		} );
 
 		it( 'throws an error if command does not exist', () => {
-			expect( () => {
+			const command = new SomeCommand( editor );
+			collection.add( 'bar', command );
+
+			expectToThrowCKEditorError( () => {
 				collection.execute( 'foo' );
-			} ).to.throw( CKEditorError, /^commandcollection-command-not-found:/ );
+			}, /^commandcollection-command-not-found:/, editor );
 		} );
 	} );
 

+ 2 - 2
packages/ckeditor5-core/tests/editingkeystrokehandler.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
  */
 
 import VirtualTestEditor from '../tests/_utils/virtualtesteditor';

+ 150 - 88
packages/ckeditor5-core/tests/editor/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 window, setTimeout */
@@ -11,10 +11,26 @@ import Config from '@ckeditor/ckeditor5-utils/src/config';
 import EditingController from '@ckeditor/ckeditor5-engine/src/controller/editingcontroller';
 import PluginCollection from '../../src/plugincollection';
 import CommandCollection from '../../src/commandcollection';
-import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import Locale from '@ckeditor/ckeditor5-utils/src/locale';
 import Command from '../../src/command';
 import EditingKeystrokeHandler from '../../src/editingkeystrokehandler';
+import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
+
+class TestEditor extends Editor {
+	static create( config ) {
+		return new Promise( resolve => {
+			const editor = new this( config );
+
+			resolve(
+				editor.initPlugins()
+					.then( () => {
+						editor.fire( 'ready' );
+					} )
+					.then( () => editor )
+			);
+		} );
+	}
+}
 
 class PluginA extends Plugin {
 	constructor( editor ) {
@@ -96,8 +112,8 @@ class PluginF {
 
 describe( 'Editor', () => {
 	afterEach( () => {
-		delete Editor.builtinPlugins;
-		delete Editor.defaultConfig;
+		delete TestEditor.builtinPlugins;
+		delete TestEditor.defaultConfig;
 	} );
 
 	it( 'imports the version helper', () => {
@@ -106,7 +122,7 @@ describe( 'Editor', () => {
 
 	describe( 'constructor()', () => {
 		it( 'should create a new editor instance', () => {
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			expect( editor.config ).to.be.an.instanceof( Config );
 			expect( editor.commands ).to.be.an.instanceof( CommandCollection );
@@ -125,7 +141,7 @@ describe( 'Editor', () => {
 				}
 			};
 
-			const editor = new Editor( {
+			const editor = new TestEditor( {
 				bar: 'foo',
 				foo: {
 					c: 3
@@ -142,7 +158,7 @@ describe( 'Editor', () => {
 		} );
 
 		it( 'should bind editing.view.document#isReadOnly to the editor', () => {
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			editor.isReadOnly = false;
 
@@ -155,7 +171,7 @@ describe( 'Editor', () => {
 
 		it( 'should activate #keystrokes', () => {
 			const spy = sinon.spy( EditingKeystrokeHandler.prototype, 'listenTo' );
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			sinon.assert.calledWith( spy, editor.editing.view.document );
 		} );
@@ -163,7 +179,7 @@ describe( 'Editor', () => {
 
 	describe( 'plugins', () => {
 		it( 'should be empty on new editor', () => {
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			expect( getPlugins( editor ) ).to.be.empty;
 		} );
@@ -171,14 +187,14 @@ describe( 'Editor', () => {
 
 	describe( 'locale', () => {
 		it( 'is instantiated and t() is exposed', () => {
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			expect( editor.locale ).to.be.instanceof( Locale );
 			expect( editor.t ).to.equal( editor.locale.t );
 		} );
 
 		it( 'is configured with the config.language', () => {
-			const editor = new Editor( { language: 'pl' } );
+			const editor = new TestEditor( { language: 'pl' } );
 
 			expect( editor.locale.language ).to.equal( 'pl' );
 		} );
@@ -186,13 +202,13 @@ describe( 'Editor', () => {
 
 	describe( 'state', () => {
 		it( 'is `initializing` initially', () => {
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			expect( editor.state ).to.equal( 'initializing' );
 		} );
 
 		it( 'is `ready` after initialization chain', () => {
-			return Editor.create().then( editor => {
+			return TestEditor.create().then( editor => {
 				expect( editor.state ).to.equal( 'ready' );
 
 				return editor.destroy();
@@ -200,7 +216,7 @@ describe( 'Editor', () => {
 		} );
 
 		it( 'is `destroyed` after editor destroy', () => {
-			return Editor.create().then( editor => {
+			return TestEditor.create().then( editor => {
 				return editor.destroy().then( () => {
 					expect( editor.state ).to.equal( 'destroyed' );
 				} );
@@ -208,7 +224,7 @@ describe( 'Editor', () => {
 		} );
 
 		it( 'is observable', () => {
-			const editor = new Editor();
+			const editor = new TestEditor();
 			const spy = sinon.spy();
 
 			editor.on( 'change:state', spy );
@@ -219,7 +235,7 @@ describe( 'Editor', () => {
 		} );
 
 		it( 'reacts on #ready event', done => {
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			expect( editor.state ).to.equal( 'initializing' );
 
@@ -232,7 +248,7 @@ describe( 'Editor', () => {
 		} );
 
 		it( 'reacts on #destroy event', done => {
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			expect( editor.state ).to.equal( 'initializing' );
 
@@ -247,13 +263,13 @@ describe( 'Editor', () => {
 
 	describe( 'isReadOnly', () => {
 		it( 'is false initially', () => {
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			expect( editor.isReadOnly ).to.false;
 		} );
 
 		it( 'is observable', () => {
-			const editor = new Editor();
+			const editor = new TestEditor();
 			const spy = sinon.spy();
 
 			editor.on( 'change:isReadOnly', spy );
@@ -266,13 +282,13 @@ describe( 'Editor', () => {
 
 	describe( 'conversion', () => {
 		it( 'should have conversion property', () => {
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			expect( editor ).to.have.property( 'conversion' );
 		} );
 
 		it( 'should have defined default conversion groups', () => {
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			expect( () => {
 				// Would throw if any of this group won't exist.
@@ -286,7 +302,7 @@ describe( 'Editor', () => {
 
 	describe( 'destroy()', () => {
 		it( 'should fire "destroy"', () => {
-			return Editor.create().then( editor => {
+			return TestEditor.create().then( editor => {
 				const spy = sinon.spy();
 
 				editor.on( 'destroy', spy );
@@ -298,7 +314,7 @@ describe( 'Editor', () => {
 		} );
 
 		it( 'should destroy all components it initialized', () => {
-			return Editor.create().then( editor => {
+			return TestEditor.create().then( editor => {
 				const dataDestroySpy = sinon.spy( editor.data, 'destroy' );
 				const modelDestroySpy = sinon.spy( editor.model, 'destroy' );
 				const editingDestroySpy = sinon.spy( editor.editing, 'destroy' );
@@ -318,7 +334,7 @@ describe( 'Editor', () => {
 
 		it( 'should wait for the full init before destroying', done => {
 			const spy = sinon.spy();
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			editor.on( 'destroy', () => {
 				done();
@@ -338,7 +354,7 @@ describe( 'Editor', () => {
 				execute() {}
 			}
 
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			const command = new SomeCommand( editor );
 			sinon.spy( command, 'execute' );
@@ -350,17 +366,17 @@ describe( 'Editor', () => {
 		} );
 
 		it( 'should throw an error if specified command has not been added', () => {
-			const editor = new Editor();
+			const editor = new TestEditor();
 
-			expect( () => {
+			expectToThrowCKEditorError( () => {
 				editor.execute( 'command' );
-			} ).to.throw( CKEditorError, /^commandcollection-command-not-found:/ );
+			}, /^commandcollection-command-not-found:/, editor );
 		} );
 	} );
 
 	describe( 'create()', () => {
 		it( 'should return a promise that resolves properly', () => {
-			const promise = Editor.create();
+			const promise = TestEditor.create();
 
 			expect( promise ).to.be.an.instanceof( Promise );
 
@@ -368,7 +384,7 @@ describe( 'Editor', () => {
 		} );
 
 		it( 'loads plugins', () => {
-			return Editor.create( { plugins: [ PluginA ] } )
+			return TestEditor.create( { plugins: [ PluginA ] } )
 				.then( editor => {
 					expect( getPlugins( editor ).length ).to.equal( 1 );
 
@@ -376,7 +392,7 @@ describe( 'Editor', () => {
 				} );
 		} );
 
-		it( 'fires all events in the right order', () => {
+		it( 'fires ready event', () => {
 			const fired = [];
 
 			function spy( evt ) {
@@ -385,22 +401,20 @@ describe( 'Editor', () => {
 
 			class EventWatcher extends Plugin {
 				init() {
-					this.editor.on( 'pluginsReady', spy );
-					this.editor.on( 'dataReady', spy );
 					this.editor.on( 'ready', spy );
 				}
 			}
 
-			return Editor.create( { plugins: [ EventWatcher ] } )
+			return TestEditor.create( { plugins: [ EventWatcher ] } )
 				.then( () => {
-					expect( fired ).to.deep.equal( [ 'pluginsReady', 'dataReady', 'ready' ] );
+					expect( fired ).to.deep.equal( [ 'ready' ] );
 				} );
 		} );
 	} );
 
 	describe( 'initPlugins()', () => {
 		it( 'should load plugins', () => {
-			const editor = new Editor( {
+			const editor = new TestEditor( {
 				plugins: [ PluginA, PluginB ]
 			} );
 
@@ -415,13 +429,10 @@ describe( 'Editor', () => {
 		} );
 
 		it( 'should initialize plugins in the right order', () => {
-			const editor = new Editor( {
+			const editor = new TestEditor( {
 				plugins: [ PluginA, PluginD ]
 			} );
 
-			const pluginsReadySpy = sinon.spy().named( 'pluginsReady' );
-			editor.on( 'pluginsReady', pluginsReadySpy );
-
 			return editor.initPlugins()
 				.then( () => {
 					sinon.assert.callOrder(
@@ -432,8 +443,7 @@ describe( 'Editor', () => {
 						editor.plugins.get( PluginA ).afterInit,
 						editor.plugins.get( PluginB ).afterInit,
 						editor.plugins.get( PluginC ).afterInit,
-						editor.plugins.get( PluginD ).afterInit,
-						pluginsReadySpy
+						editor.plugins.get( PluginD ).afterInit
 					);
 				} );
 		} );
@@ -468,7 +478,7 @@ describe( 'Editor', () => {
 				}
 			}
 
-			const editor = new Editor( {
+			const editor = new TestEditor( {
 				plugins: [ PluginA, PluginSync ]
 			} );
 
@@ -514,7 +524,7 @@ describe( 'Editor', () => {
 				}
 			}
 
-			const editor = new Editor( {
+			const editor = new TestEditor( {
 				plugins: [ PluginA, PluginSync ]
 			} );
 
@@ -534,7 +544,7 @@ describe( 'Editor', () => {
 		it( 'should load plugins built in the Editor even if the passed config is empty', () => {
 			Editor.builtinPlugins = [ PluginA, PluginB, PluginC ];
 
-			const editor = new Editor();
+			const editor = new TestEditor();
 
 			return editor.initPlugins()
 				.then( () => {
@@ -549,7 +559,7 @@ describe( 'Editor', () => {
 		it( 'should load plugins provided in the config and should ignore plugins built in the Editor', () => {
 			Editor.builtinPlugins = [ PluginA, PluginB, PluginC, PluginD ];
 
-			const editor = new Editor( {
+			const editor = new TestEditor( {
 				plugins: [
 					'A'
 				]
@@ -568,7 +578,7 @@ describe( 'Editor', () => {
 
 			Editor.builtinPlugins = [ PluginA, PluginB, PluginC, PluginD ];
 
-			const editor = new Editor( {
+			const editor = new TestEditor( {
 				plugins: [
 					'A',
 					'B',
@@ -591,7 +601,7 @@ describe( 'Editor', () => {
 		it( 'should load plugins inherited from the base Editor', () => {
 			Editor.builtinPlugins = [ PluginA, PluginB, PluginC, PluginD ];
 
-			class CustomEditor extends Editor {}
+			class CustomEditor extends TestEditor {}
 
 			const editor = new CustomEditor( {
 				plugins: [
@@ -610,7 +620,7 @@ describe( 'Editor', () => {
 		} );
 
 		it( 'should load plugins build into Editor\'s subclass', () => {
-			class CustomEditor extends Editor {}
+			class CustomEditor extends TestEditor {}
 
 			CustomEditor.builtinPlugins = [ PluginA, PluginB, PluginC, PluginD ];
 
@@ -630,83 +640,135 @@ describe( 'Editor', () => {
 				} );
 		} );
 
-		it( 'should not load plugins specified in the config as "removePlugins"', () => {
-			const editor = new Editor( {
-				plugins: [ PluginA, PluginD ],
-				removePlugins: [ PluginD ]
+		describe( '"removePlugins" config', () => {
+			it( 'should prevent plugins from being loaded', () => {
+				const editor = new TestEditor( {
+					plugins: [ PluginA, PluginD ],
+					removePlugins: [ PluginD ]
+				} );
+
+				return editor.initPlugins()
+					.then( () => {
+						expect( getPlugins( editor ).length ).to.equal( 1 );
+						expect( editor.plugins.get( PluginA ) ).to.be.an.instanceof( Plugin );
+					} );
 			} );
 
-			return editor.initPlugins()
-				.then( () => {
-					expect( getPlugins( editor ).length ).to.equal( 1 );
-					expect( editor.plugins.get( PluginA ) ).to.be.an.instanceof( Plugin );
+			it( 'should not load plugins built in the Editor', () => {
+				Editor.builtinPlugins = [ PluginA, PluginD ];
+
+				const editor = new TestEditor( {
+					removePlugins: [ 'D' ]
+				} );
+
+				return editor.initPlugins()
+					.then( () => {
+						expect( getPlugins( editor ).length ).to.equal( 1 );
+						expect( editor.plugins.get( PluginA ) ).to.be.an.instanceof( Plugin );
+					} );
+			} );
+
+			it( 'should not load plugins build into Editor\'s subclass', () => {
+				class CustomEditor extends TestEditor {}
+
+				CustomEditor.builtinPlugins = [ PluginA, PluginD ];
+
+				const editor = new CustomEditor( {
+					removePlugins: [ 'D' ]
 				} );
+
+				return editor.initPlugins()
+					.then( () => {
+						expect( getPlugins( editor ).length ).to.equal( 1 );
+						expect( editor.plugins.get( PluginA ) ).to.not.be.undefined;
+					} );
+			} );
 		} );
 
-		it( 'should not load plugins built in the Editor when "removePlugins" option is specified', () => {
-			Editor.builtinPlugins = [ PluginA, PluginD ];
+		describe( '"extraPlugins" config', () => {
+			it( 'should load additional plugins', () => {
+				const editor = new TestEditor( {
+					plugins: [ PluginA, PluginC ],
+					extraPlugins: [ PluginB ]
+				} );
 
-			const editor = new Editor( {
-				removePlugins: [ 'D' ]
+				return editor.initPlugins()
+					.then( () => {
+						expect( getPlugins( editor ).length ).to.equal( 3 );
+						expect( editor.plugins.get( PluginB ) ).to.be.an.instanceof( Plugin );
+					} );
 			} );
 
-			return editor.initPlugins()
-				.then( () => {
-					expect( getPlugins( editor ).length ).to.equal( 1 );
-					expect( editor.plugins.get( PluginA ) ).to.be.an.instanceof( Plugin );
+			it( 'should not duplicate plugins', () => {
+				const editor = new TestEditor( {
+					plugins: [ PluginA, PluginB ],
+					extraPlugins: [ PluginB ]
 				} );
-		} );
 
-		it( 'should not load plugins build into Editor\'s subclass when "removePlugins" option is specified', () => {
-			class CustomEditor extends Editor {}
+				return editor.initPlugins()
+					.then( () => {
+						expect( getPlugins( editor ).length ).to.equal( 2 );
+						expect( editor.plugins.get( PluginB ) ).to.be.an.instanceof( Plugin );
+					} );
+			} );
 
-			CustomEditor.builtinPlugins = [ PluginA, PluginD ];
+			it( 'should not duplicate plugins built in the Editor', () => {
+				Editor.builtinPlugins = [ PluginA, PluginB ];
 
-			const editor = new CustomEditor( {
-				removePlugins: [ 'D' ]
+				const editor = new TestEditor( {
+					extraPlugins: [ 'B' ]
+				} );
+
+				return editor.initPlugins()
+					.then( () => {
+						expect( getPlugins( editor ).length ).to.equal( 2 );
+						expect( editor.plugins.get( PluginB ) ).to.be.an.instanceof( Plugin );
+					} );
 			} );
 
-			return editor.initPlugins()
-				.then( () => {
-					expect( getPlugins( editor ).length ).to.equal( 1 );
-					expect( editor.plugins.get( PluginA ) ).to.not.be.undefined;
+			it( 'should not duplicate plugins build into Editor\'s subclass', () => {
+				class CustomEditor extends TestEditor {}
+
+				CustomEditor.builtinPlugins = [ PluginA, PluginB ];
+
+				const editor = new CustomEditor( {
+					extraPlugins: [ 'B' ]
 				} );
+
+				return editor.initPlugins()
+					.then( () => {
+						expect( getPlugins( editor ).length ).to.equal( 2 );
+						expect( editor.plugins.get( PluginB ) ).to.be.an.instanceof( Plugin );
+					} );
+			} );
 		} );
 
 		it( 'should not call "afterInit" method if plugin does not have this method', () => {
-			const editor = new Editor( {
+			const editor = new TestEditor( {
 				plugins: [ PluginA, PluginE ]
 			} );
 
-			const pluginsReadySpy = sinon.spy().named( 'pluginsReady' );
-			editor.on( 'pluginsReady', pluginsReadySpy );
-
 			return editor.initPlugins()
 				.then( () => {
 					sinon.assert.callOrder(
 						editor.plugins.get( PluginA ).init,
 						editor.plugins.get( PluginE ).init,
-						editor.plugins.get( PluginA ).afterInit,
-						pluginsReadySpy
+						editor.plugins.get( PluginA ).afterInit
 					);
 				} );
 		} );
 
 		it( 'should not call "init" method if plugin does not have this method', () => {
-			const editor = new Editor( {
+			const editor = new TestEditor( {
 				plugins: [ PluginA, PluginF ]
 			} );
 
-			const pluginsReadySpy = sinon.spy().named( 'pluginsReady' );
-			editor.on( 'pluginsReady', pluginsReadySpy );
-
 			return editor.initPlugins()
 				.then( () => {
 					sinon.assert.callOrder(
 						editor.plugins.get( PluginA ).init,
 						editor.plugins.get( PluginA ).afterInit,
-						editor.plugins.get( PluginF ).afterInit,
-						pluginsReadySpy
+						editor.plugins.get( PluginF ).afterInit
 					);
 				} );
 		} );

+ 123 - 13
packages/ckeditor5-core/tests/editor/editorui.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
  */
 
 import EditorUI from '../../src/editor/editorui';
@@ -8,19 +8,19 @@ import Editor from '../../src/editor/editor';
 
 import FocusTracker from '@ckeditor/ckeditor5-utils/src/focustracker';
 import ComponentFactory from '@ckeditor/ckeditor5-ui/src/componentfactory';
-import View from '@ckeditor/ckeditor5-ui/src/view';
 
 import testUtils from '../_utils/utils';
 
+/* global document, console */
+
 describe( 'EditorUI', () => {
-	let editor, view, ui;
+	let editor, ui;
 
 	testUtils.createSinonSandbox();
 
 	beforeEach( () => {
 		editor = new Editor();
-		view = new View();
-		ui = new EditorUI( editor, view );
+		ui = new EditorUI( editor );
 	} );
 
 	afterEach( () => {
@@ -32,10 +32,6 @@ describe( 'EditorUI', () => {
 			expect( ui.editor ).to.equal( editor );
 		} );
 
-		it( 'should set #view', () => {
-			expect( ui.view ).to.equal( view );
-		} );
-
 		it( 'should create #componentFactory factory', () => {
 			expect( ui.componentFactory ).to.be.instanceOf( ComponentFactory );
 		} );
@@ -44,6 +40,10 @@ describe( 'EditorUI', () => {
 			expect( ui.focusTracker ).to.be.instanceOf( FocusTracker );
 		} );
 
+		it( 'should have #element getter', () => {
+			expect( ui.element ).to.null;
+		} );
+
 		it( 'should fire update event after viewDocument#layoutChanged', () => {
 			const spy = sinon.spy();
 
@@ -84,12 +84,122 @@ describe( 'EditorUI', () => {
 			sinon.assert.called( spy );
 		} );
 
-		it( 'should destroy the #view', () => {
-			const spy = sinon.spy( view, 'destroy' );
+		it( 'should reset editables array', () => {
+			ui.setEditableElement( 'foo', {} );
+			ui.setEditableElement( 'bar', {} );
+
+			expect( [ ...ui.getEditableElementsNames() ] ).to.deep.equal( [ 'foo', 'bar' ] );
 
 			ui.destroy();
 
-			sinon.assert.called( spy );
+			expect( [ ...ui.getEditableElementsNames() ] ).to.have.length( 0 );
+		} );
+
+		it( 'removes domElement#ckeditorInstance references from registered root elements', () => {
+			const fooElement = document.createElement( 'foo' );
+			const barElement = document.createElement( 'bar' );
+
+			ui.setEditableElement( 'foo', fooElement );
+			ui.setEditableElement( 'bar', barElement );
+
+			expect( fooElement.ckeditorInstance ).to.equal( editor );
+			expect( barElement.ckeditorInstance ).to.equal( editor );
+
+			ui.destroy();
+
+			expect( fooElement.ckeditorInstance ).to.be.null;
+			expect( barElement.ckeditorInstance ).to.be.null;
+		} );
+	} );
+
+	describe( 'setEditableElement()', () => {
+		it( 'should register the editable element under a name', () => {
+			const ui = new EditorUI( editor );
+			const element = document.createElement( 'div' );
+
+			ui.setEditableElement( 'main', element );
+
+			expect( ui.getEditableElement( 'main' ) ).to.equal( element );
+		} );
+
+		it( 'puts a reference to the editor instance in domElement#ckeditorInstance', () => {
+			const ui = new EditorUI( editor );
+			const element = document.createElement( 'div' );
+
+			ui.setEditableElement( 'main', element );
+
+			expect( element.ckeditorInstance ).to.equal( editor );
+		} );
+
+		it( 'does not override a reference to the editor instance in domElement#ckeditorInstance', () => {
+			const ui = new EditorUI( editor );
+			const element = document.createElement( 'div' );
+
+			element.ckeditorInstance = 'foo';
+
+			ui.setEditableElement( 'main', element );
+
+			expect( element.ckeditorInstance ).to.equal( 'foo' );
+		} );
+	} );
+
+	describe( 'getEditableElement()', () => {
+		it( 'should return editable element (default root name)', () => {
+			const ui = new EditorUI( editor );
+			const editableMock = { name: 'main', element: document.createElement( 'div' ) };
+
+			ui.setEditableElement( editableMock.name, editableMock.element );
+
+			expect( ui.getEditableElement() ).to.equal( editableMock.element );
+		} );
+
+		it( 'should return editable element (custom root name)', () => {
+			const ui = new EditorUI( editor );
+			const editableMock1 = { name: 'root1', element: document.createElement( 'div' ) };
+			const editableMock2 = { name: 'root2', element: document.createElement( 'p' ) };
+
+			ui.setEditableElement( editableMock1.name, editableMock1.element );
+			ui.setEditableElement( editableMock2.name, editableMock2.element );
+
+			expect( ui.getEditableElement( 'root1' ) ).to.equal( editableMock1.element );
+			expect( ui.getEditableElement( 'root2' ) ).to.equal( editableMock2.element );
+		} );
+
+		it( 'should return null if editable with specified name does not exist', () => {
+			const ui = new EditorUI( editor );
+
+			expect( ui.getEditableElement() ).to.be.undefined;
+		} );
+	} );
+
+	describe( 'getEditableElementsNames()', () => {
+		it( 'should return iterable object of names', () => {
+			const ui = new EditorUI( editor );
+			const editableMock1 = { name: 'main', element: document.createElement( 'div' ) };
+			const editableMock2 = { name: 'root2', element: document.createElement( 'p' ) };
+
+			ui.setEditableElement( editableMock1.name, editableMock1.element );
+			ui.setEditableElement( editableMock2.name, editableMock2.element );
+
+			const names = ui.getEditableElementsNames();
+			expect( names[ Symbol.iterator ] ).to.instanceof( Function );
+			expect( Array.from( names ) ).to.deep.equal( [ 'main', 'root2' ] );
+		} );
+
+		it( 'should return empty array if no editables', () => {
+			const ui = new EditorUI( editor );
+
+			expect( ui.getEditableElementsNames() ).to.be.empty;
+		} );
+	} );
+
+	describe( '_editableElements()', () => {
+		it( 'should warn about deprecation', () => {
+			const ui = new EditorUI( editor );
+			const stub = testUtils.sinon.stub( console, 'warn' );
+
+			expect( ui._editableElements ).to.be.instanceOf( Map );
+			sinon.assert.calledWithMatch( stub, 'editor-ui-deprecated-editable-elements' );
 		} );
 	} );
 } );

+ 8 - 6
packages/ckeditor5-core/tests/editor/utils/attachtoform.js

@@ -1,14 +1,14 @@
 /**
- * @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
  */
 
 import attachToForm from '../../../src/editor/utils/attachtoform';
 import ElementApiMixin from '../../../src/editor/utils/elementapimixin';
 import Editor from '../../../src/editor/editor';
 import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
-import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
+import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
 
 /* global document, Event */
 
@@ -47,9 +47,11 @@ describe( 'attachToForm()', () => {
 	} );
 
 	it( 'should throw an error when is used with editor without `ElementApiMixin`', () => {
-		expect( () => {
-			attachToForm( new Editor() );
-		} ).to.throw( CKEditorError, /^attachtoform-missing-elementapi-interface/ );
+		const editor = new Editor();
+
+		expectToThrowCKEditorError( () => {
+			attachToForm( editor );
+		}, /^attachtoform-missing-elementapi-interface/, editor );
 	} );
 
 	it( 'should update editor#element after the "submit" event', () => {

+ 23 - 2
packages/ckeditor5-core/tests/editor/utils/dataapimixin.js

@@ -1,11 +1,12 @@
 /**
- * @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
  */
 
 import DataApiMixin from '../../../src/editor/utils/dataapimixin';
 import Editor from '../../../src/editor/editor';
 import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
+import testUtils from '../../../tests/_utils/utils';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
 import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
@@ -40,6 +41,8 @@ describe( 'DataApiMixin', () => {
 	} );
 
 	describe( 'getData()', () => {
+		testUtils.createSinonSandbox();
+
 		it( 'should be added to editor interface', () => {
 			expect( editor ).have.property( 'getData' ).to.be.a( 'function' );
 		} );
@@ -49,5 +52,23 @@ describe( 'DataApiMixin', () => {
 
 			expect( editor.getData() ).to.equal( 'foo' );
 		} );
+
+		it( 'should get data of the second root', () => {
+			setData( editor.model, 'bar', { rootName: 'secondRoot' } );
+
+			expect( editor.getData( { rootName: 'secondRoot' } ) ).to.equal( 'bar' );
+		} );
+
+		it( 'should pass options object to data.get() method internally', () => {
+			const spy = testUtils.sinon.spy( editor.data, 'get' );
+			const options = { rootName: 'main', trim: 'none' };
+
+			setData( editor.model, 'foo' );
+
+			expect( editor.getData( options ) ).to.equal( 'foo' );
+
+			testUtils.sinon.assert.calledOnce( spy );
+			testUtils.sinon.assert.calledWith( spy, options );
+		} );
 	} );
 } );

+ 8 - 4
packages/ckeditor5-core/tests/editor/utils/elementapimixin.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
  */
 
 /* global document */
@@ -9,7 +9,7 @@ import ElementApiMixin from '../../../src/editor/utils/elementapimixin';
 import Editor from '../../../src/editor/editor';
 import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
-import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
 
 describe( 'ElementApiMixin', () => {
 	let editor;
@@ -46,7 +46,11 @@ describe( 'ElementApiMixin', () => {
 		} );
 
 		it( 'throws an error if "sourceElement" has not been set', () => {
-			expect( () => editor.updateSourceElement() ).to.throw( CKEditorError, /editor-missing-sourceelement/ );
+			expectToThrowCKEditorError(
+				() => editor.updateSourceElement(),
+				/editor-missing-sourceelement/,
+				editor
+			);
 		} );
 	} );
 } );

+ 67 - 0
packages/ckeditor5-core/tests/editor/utils/securesourceelement.js

@@ -0,0 +1,67 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* global document */
+
+import secureSourceElement from '../../../src/editor/utils/securesourceelement';
+import Editor from '../../../src/editor/editor';
+import { expectToThrowCKEditorError } from '@ckeditor/ckeditor5-utils/tests/_utils/utils';
+
+describe( 'secureSourceElement()', () => {
+	let editor, sourceElement;
+
+	beforeEach( () => {
+		class CustomEditor extends Editor {}
+
+		sourceElement = document.createElement( 'div' );
+		editor = new CustomEditor();
+
+		editor.sourceElement = sourceElement;
+		editor.state = 'ready';
+	} );
+
+	afterEach( () => {
+		if ( editor ) {
+			return editor.destroy();
+		}
+	} );
+
+	it( 'does not throw if the editor was not initialized using the source element', () => {
+		delete editor.sourceElement;
+
+		expect( () => {
+			secureSourceElement( editor );
+		} ).to.not.throw();
+	} );
+
+	it( 'does not throw if the editor was initialized using the element for the first time', () => {
+		expect( () => {
+			secureSourceElement( editor );
+		} ).to.not.throw();
+	} );
+
+	it( 'sets the property after initializing the editor', () => {
+		secureSourceElement( editor );
+
+		expect( sourceElement.ckeditorInstance ).to.equal( editor );
+	} );
+
+	it( 'removes the property after destroying the editor', () => {
+		secureSourceElement( editor );
+
+		return editor.destroy()
+			.then( () => {
+				expect( sourceElement.ckeditorInstance ).to.be.undefined;
+			} );
+	} );
+
+	it( 'throws an error if the same element was used twice', () => {
+		sourceElement.ckeditorInstance = 'foo';
+
+		expectToThrowCKEditorError( () => {
+			secureSourceElement( editor );
+		}, /^editor-source-element-already-used/, editor );
+	} );
+} );

+ 2 - 2
packages/ckeditor5-core/tests/manual/article.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
packages/ckeditor5-core/tests/manual/formsubmit.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
packages/ckeditor5-core/tests/manual/pendingactions.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, setTimeout */

+ 2 - 2
packages/ckeditor5-core/tests/manual/readonly.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 */

+ 4 - 0
packages/ckeditor5-core/tests/manual/version-collision.html

@@ -1,3 +1,7 @@
+<head>
+	<!-- "script-src 'unsafe-inline'" is required if we want to execute the inline <script> -->
+	<meta http-equiv="Content-Security-Policy" content="default-src 'none'; connect-src 'self' https://cksource.com http://*.cke-cs.com; script-src 'self' 'unsafe-inline' https://cksource.com; img-src * data:; style-src 'self' 'unsafe-inline'; frame-src *">
+</head>
 <script>
 	window.CKEDITOR_VERSION = 'the.colliding.version';
 </script>

Vissa filer visades inte eftersom för många filer har ändrats