Typos hapen. We striving to correct them. Hover on the marked words for instant correction suggestions or click the dialog icon in the bottom right corner to have the whole text proofread at once.
You can also paste your own text here to have its spelling and grammar checked.
diff --git a/docs/_snippets/features/wproofreader.js b/docs/_snippets/features/wproofreader.js
index 0a96d824907..855b4dbac74 100644
--- a/docs/_snippets/features/wproofreader.js
+++ b/docs/_snippets/features/wproofreader.js
@@ -3,15 +3,61 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
-/* globals ClassicEditor, console, window, document */
+/* globals console, window, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
+import EasyImage from '@ckeditor/ckeditor5-easy-image/src/easyimage';
+import WProofreader from '@webspellchecker/wproofreader-ckeditor5/src/wproofreader';
import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
ClassicEditor
.create( document.querySelector( '#snippet-wproofreader' ), {
+ plugins: [ ArticlePluginSet, EasyImage, WProofreader ],
+ wproofreader: {
+ serviceId: '1:Eebp63-lWHbt2-ASpHy4-AYUpy2-fo3mk4-sKrza1-NsuXy4-I1XZC2-0u2F54-aqYWd1-l3Qf14-umd',
+ srcUrl: 'https://svc.webspellchecker.net/spellcheck31/wscbundle/wscbundle.js'
+ },
cloudServices: CS_CONFIG,
toolbar: {
+ items: [
+ 'heading',
+ '|',
+ 'bold',
+ 'italic',
+ 'bulletedList',
+ 'numberedList',
+ '|',
+ 'outdent',
+ 'indent',
+ '|',
+ 'blockQuote',
+ 'link',
+ 'mediaEmbed',
+ 'insertTable',
+ '|',
+ 'undo',
+ 'redo'
+ ],
viewportTopOffset: window.getViewportTopOffsetConfig()
+ },
+ image: {
+ styles: [
+ 'full',
+ 'alignLeft',
+ 'alignRight'
+ ],
+ toolbar: [
+ 'imageStyle:alignLeft',
+ 'imageStyle:full',
+ 'imageStyle:alignRight',
+ '|',
+ 'imageTextAlternative'
+ ]
+ },
+ table: {
+ contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
}
} )
.then( editor => {
diff --git a/docs/features/spelling-and-grammar-checking.md b/docs/features/spelling-and-grammar-checking.md
index 418644792b1..c8a8724c0ae 100644
--- a/docs/features/spelling-and-grammar-checking.md
+++ b/docs/features/spelling-and-grammar-checking.md
@@ -5,8 +5,6 @@ menu-title: Spelling and grammar checking
# Proofreading, spelling and grammar checking
-{@snippet build-classic-source}
-
The spell checker for CKEditor 5 is a commercial solution provided by our partner, [WebSpellChecker](https://webspellchecker.com/). You can report any issues in its [GitHub repository](https://github.com/WebSpellChecker/wproofreader). The license can be purchased [here](https://ckeditor.com/contact/).
@@ -29,63 +27,84 @@ There are also over 150 additional languages and specialized dictionaries such a
## Installation
-WProofreader is installed separately from CKEditor 5 and does not need to be combined into an editor build as other features. To use this tool, it is necessary to load the WProofreader script on your page and provide the configuration.
+WProofreader is delivered as a CKEditor 5 plugin, so it could be combined into an editor build as other features. To add this feature to your rich-text editor, install the [`@webspellchecker/wproofreader-ckeditor5`](https://www.npmjs.com/package/@webspellchecker/wproofreader-ckeditor5) package:
+
+```bash
+npm install --save @webspellchecker/wproofreader-ckeditor5
+```
+
+Then, add it to your plugin list:
+
+```js
+import WProofreader from '@webspellchecker/wproofreader-ckeditor5/src/wproofreader';
+// ...
+
+ClassicEditor
+ .create( editorElement, {
+ plugins: [ ..., WProofreader ],
+ // ...
+ } )
+ .then( ... )
+ .catch( ... );
+```
+
+
+ Read more about {@link builds/guides/integration/installing-plugins installing plugins}.
+
-The proofreader can be used either as a [cloud solution](#wproofreader-cloud) or [hosted on your own server](#wproofreader-server).
+At this step, it is required to provide a proper configuration. The proofreader can be used either as a [cloud solution](#wproofreader-cloud) or [hosted on your own server](#wproofreader-server).
### WProofreader Cloud
After signing up for a [trial or paid version](https://ckeditor.com/contact/), you will receive your service ID which is used to activate the service.
-Add the following configuration to your page:
-
-```html
-
-```
+Add the following configuration to your editor:
-And then load the proofreader script:
+```js
+import WProofreader from '@webspellchecker/wproofreader-ckeditor5/src/wproofreader';
+// ...
-```html
-
+ClassicEditor
+ .create( editorElement, {
+ plugins: [ ..., WProofreader ],
+ wproofreader: {
+ serviceId: 'your-service-ID',
+ srcUrl: 'https://svc.webspellchecker.net/spellcheck31/wscbundle/wscbundle.js'
+ }
+ } )
```
-Refer to the [official documentation](https://github.com/WebSpellChecker/wproofreader#wproofreader-cloud) for more details about the cloud setup and available configuration options.
+Refer to the [official documentation](https://github.com/WebSpellChecker/wproofreader-ckeditor5#install-instructions) for more details about the cloud setup and available configuration options.
### WProofreader Server
After signing up for a [trial or paid version](https://ckeditor.com/contact/), you will receive access to the WebSpellChecker Server package to install on your own server.
-You will need to add the following configuration to your page:
-
-```html
-
-```
-
-Then specify the path to the service on your page:
-
-```html
-
+You will need to add the following configuration to your editor:
+
+```js
+import WProofreader from '@webspellchecker/wproofreader-ckeditor5/src/wproofreader';
+// ...
+
+ClassicEditor
+ .create( editorElement, {
+ plugins: [ ..., WProofreader ],
+ wproofreader: {
+ serviceProtocol: 'https',
+ serviceHost: 'localhost',
+ servicePort: '2880',
+ servicePath: '/',
+ srcUrl: 'https://host_name/virtual_directory/wscbundle/wscbundle.js'
+ }
+ } )
```
-Refer to the [official documentation](https://github.com/WebSpellChecker/wproofreader#wproofreader-server) for more details about the server setup and available configuration options.
+Refer to the [official documentation](https://github.com/WebSpellChecker/wproofreader-ckeditor5#install-instructions) for more details about the server setup and available configuration options.
## Configuration
-WProofreader configuration is set outside the CKEditor 5 configuration. Refer to the [WProofreader API](http://dev.webspellchecker.net/api/wscbundle/) for further information.
+WProofreader configuration is set inside the CKEditor 5 configuration in `wproofreader` object. Refer to the [WProofreader API](https://webspellchecker.com/docs/api/wscbundle/Options.html) for further information.
## Contribute
-You can report issues and request features in the [official WProofreader repository](https://github.com/WebSpellChecker/wproofreader/issues).
+You can report issues and request features in the [official WProofreader for CKEditor 5 repository](https://github.com/WebSpellChecker/wproofreader-ckeditor5/issues).
diff --git a/docs/framework/guides/architecture/editing-engine.md b/docs/framework/guides/architecture/editing-engine.md
index abdd9b101d9..606eede6d73 100644
--- a/docs/framework/guides/architecture/editing-engine.md
+++ b/docs/framework/guides/architecture/editing-engine.md
@@ -226,14 +226,14 @@ editor.data; // The data pipeline (DataController).
### Element types and custom data
-The structure of the view resembles the structure in the DOM very closely. The semantics of HTML is defined in its specification. The view structure comes "DTD-free", so in order to provide additional information and to better express the semantics of the content, the view structure implements 6 element types ({@link module:engine/view/containerelement~ContainerElement}, {@link module:engine/view/attributeelement~AttributeElement}, {@link module:engine/view/emptyelement~EmptyElement}, {@link module:engine/view/rawelement~RawElement}, {@link module:engine/view/uielement~UIElement}, and {@link module:engine/view/editableelement~EditableElement}) and so called {@link module:engine/view/element~Element#getCustomProperty "custom properties"} (i.e. custom element properties which are not rendered). This additional information provided by editor features is then used by the {@link module:engine/view/renderer~Renderer} and [converters](#conversion).
+The structure of the view resembles the structure in the DOM very closely. The semantics of HTML is defined in its specification. The view structure comes "DTD-free", so in order to provide additional information and to better express the semantics of the content, the view structure implements 6 element types ({@link module:engine/view/containerelement~ContainerElement}, {@link module:engine/view/attributeelement~AttributeElement}, {@link module:engine/view/emptyelement~EmptyElement}, {@link module:engine/view/rawelement~RawElement}, {@link module:engine/view/uielement~UIElement}, and {@link module:engine/view/editableelement~EditableElement}) and so called {@link module:engine/view/element~Element#getCustomProperty "custom properties"} (i.e. custom element properties which are not rendered). This additional information provided by the editor features is then used by the {@link module:engine/view/renderer~Renderer} and [converters](#conversion).
The element types can be defined as follows:
* **Container element** – The elements that build the structure of the content. Used for block elements such as `
`, `
`, `
`, `
`, etc.
* **Attribute element** – The elements that cannot hold container elements inside them. Most model text attributes are converted to view attribute elements. They are used mostly for inline styling elements such as ``, ``, ``, ``. Similar attribute elements are flattened by the view writer, so e.g. `x` would automatically be optimized to `x`.
* **Empty element** – The elements that must not have any child nodes, for example ``.
-* **UI elements** – The elements that are not a part of the "data" but need to be "inlined" in the content. They are ignored by the selection (it jumps over them) and the view writer in general. The contents of these elements and events coming from them are filtered out, too.
+* **UI element** – The elements that are not a part of the "data" but need to be "inlined" in the content. They are ignored by the selection (it jumps over them) and the view writer in general. The contents of these elements and events coming from them are filtered out, too.
* **Raw element** – The elements that work as data containers ("wrappers", "sandboxes") but their children are transparent to the editor. Useful when non-standard data must be rendered but the editor should not be concerned what it is and how it works. Users cannot put the selection inside a raw element, split it into smaller chunks or directly modify its content.
* **Editable element** – The elements used as "nested editables" of non-editable fragments of the content, for example a caption in the image widget, where the `
` wrapping the image is not editable (it is a widget) and the `` inside it is an editable element.
diff --git a/package.json b/package.json
index 47a6c3d3562..6874bfe4755 100644
--- a/package.json
+++ b/package.json
@@ -86,6 +86,7 @@
"@ckeditor/ckeditor5-react": "^2.1.0",
"@ckeditor/ckeditor5-real-time-collaboration": "^21.0.0",
"@ckeditor/ckeditor5-track-changes": "^21.0.0",
+ "@webspellchecker/wproofreader-ckeditor5": "^1.0.5",
"@wiris/mathtype-ckeditor5": "7.20.0",
"babel-standalone": "^6.26.0",
"coveralls": "^3.1.0",
diff --git a/packages/ckeditor5-adapter-ckfinder/lang/translations/ko.po b/packages/ckeditor5-adapter-ckfinder/lang/translations/ko.po
index ce88f251290..9091420381e 100644
--- a/packages/ckeditor5-adapter-ckfinder/lang/translations/ko.po
+++ b/packages/ckeditor5-adapter-ckfinder/lang/translations/ko.po
@@ -18,4 +18,4 @@ msgstr ""
msgctxt "A generic error message displayed on upload failure. The file name is concatenated to this text."
msgid "Cannot upload file:"
-msgstr "파일 업로드 불가"
+msgstr "파일 업로드할 수 없음: "
diff --git a/packages/ckeditor5-alignment/lang/translations/ko.po b/packages/ckeditor5-alignment/lang/translations/ko.po
index 08bd7992789..eee9fc740ca 100644
--- a/packages/ckeditor5-alignment/lang/translations/ko.po
+++ b/packages/ckeditor5-alignment/lang/translations/ko.po
@@ -18,19 +18,19 @@ msgstr ""
msgctxt "Toolbar button tooltip for aligning the text to the left."
msgid "Align left"
-msgstr "왼쪽 맞춤"
+msgstr "왼쪽 정렬"
msgctxt "Toolbar button tooltip for aligning the text to the right."
msgid "Align right"
-msgstr "오른쪽 맞춤"
+msgstr "오른쪽 정렬"
msgctxt "Toolbar button tooltip for aligning the text to center."
msgid "Align center"
-msgstr "가운데 맞춤"
+msgstr "가운데 정렬"
msgctxt "Toolbar button tooltip for making the text justified."
msgid "Justify"
-msgstr "양쪽 맞춤"
+msgstr "양쪽 정렬"
msgctxt "Dropdown button tooltip for the text alignment feature."
msgid "Text alignment"
diff --git a/packages/ckeditor5-alignment/lang/translations/zh.po b/packages/ckeditor5-alignment/lang/translations/zh.po
index 12fc8d2ac6e..8224a1435a1 100644
--- a/packages/ckeditor5-alignment/lang/translations/zh.po
+++ b/packages/ckeditor5-alignment/lang/translations/zh.po
@@ -38,4 +38,4 @@ msgstr "文字對齊"
msgctxt "Label used by assistive technologies describing the text alignment feature toolbar."
msgid "Text alignment toolbar"
-msgstr ""
+msgstr "文字對齊"
diff --git a/packages/ckeditor5-autoformat/docs/features/autoformat.md b/packages/ckeditor5-autoformat/docs/features/autoformat.md
index c0472bdd71d..d5a1fccd0fb 100644
--- a/packages/ckeditor5-autoformat/docs/features/autoformat.md
+++ b/packages/ckeditor5-autoformat/docs/features/autoformat.md
@@ -42,8 +42,9 @@ Example:
In addition to enabling automatic text formatting, you may want to check the following productivity features:
-* {@link features/text-transformation Automatic text transformation} – It enables automatic turning snippets such as `(tm)` into `™` and `"foo"` into `“foo”`.
-* {@link features/mentions Mentions} – It brings support for smart autocompletion.
+* {@link features/text-transformation Automatic text transformation} – Enables automatic turning snippets such as `(tm)` into `™` and `"foo"` into `“foo”`.
+* {@link features/link#autolink-feature Autolink} – Turns the links and email addresses typed or pasted into the editor into active URLs.
+* {@link features/mentions Mentions} – Brings support for smart autocompletion.
## Installation
diff --git a/packages/ckeditor5-autoformat/tests/blockautoformatediting.js b/packages/ckeditor5-autoformat/tests/blockautoformatediting.js
index 38e376de881..32c22ab0c97 100644
--- a/packages/ckeditor5-autoformat/tests/blockautoformatediting.js
+++ b/packages/ckeditor5-autoformat/tests/blockautoformatediting.js
@@ -167,7 +167,7 @@ describe( 'blockAutoformatEditing', () => {
editor.conversion.for( 'downcast' )
.elementToElement( {
model: 'softBreak',
- view: ( modelElement, viewWriter ) => viewWriter.createEmptyElement( 'br' )
+ view: ( modelElement, { writer } ) => writer.createEmptyElement( 'br' )
} );
const spy = testUtils.sinon.spy();
@@ -207,7 +207,7 @@ describe( 'blockAutoformatEditing', () => {
editor.conversion.for( 'downcast' )
.elementToElement( {
model: 'softBreak',
- view: ( modelElement, viewWriter ) => viewWriter.createEmptyElement( 'br' )
+ view: ( modelElement, { writer } ) => writer.createEmptyElement( 'br' )
} );
const spy = testUtils.sinon.spy();
diff --git a/packages/ckeditor5-autosave/lang/translations/ko.po b/packages/ckeditor5-autosave/lang/translations/ko.po
index 64f5357b34d..588c8aa359f 100644
--- a/packages/ckeditor5-autosave/lang/translations/ko.po
+++ b/packages/ckeditor5-autosave/lang/translations/ko.po
@@ -18,4 +18,4 @@ msgstr ""
msgctxt "A message that the data is being saved."
msgid "Saving changes"
-msgstr "변경사항 저장"
+msgstr "변경된 내용을 저장하고 있습니다"
diff --git a/packages/ckeditor5-basic-styles/lang/translations/ko.po b/packages/ckeditor5-basic-styles/lang/translations/ko.po
index de5f573dde6..9f463a40292 100644
--- a/packages/ckeditor5-basic-styles/lang/translations/ko.po
+++ b/packages/ckeditor5-basic-styles/lang/translations/ko.po
@@ -30,7 +30,7 @@ msgstr "밑줄"
msgctxt "Toolbar button tooltip for the Code feature."
msgid "Code"
-msgstr "소스"
+msgstr "코드"
msgctxt "Toolbar button tooltip for the Strikethrough feature."
msgid "Strikethrough"
diff --git a/packages/ckeditor5-ckfinder/lang/translations/ko.po b/packages/ckeditor5-ckfinder/lang/translations/ko.po
new file mode 100644
index 00000000000..ffc168b5c69
--- /dev/null
+++ b/packages/ckeditor5-ckfinder/lang/translations/ko.po
@@ -0,0 +1,37 @@
+# Copyright (c) 2003-2020, 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"
+"Language: ko\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "Toolbar button tooltip for inserting an image or file via a CKFinder file browser."
+msgid "Insert image or file"
+msgstr "사진이나 파일을 삽입"
+
+msgctxt "Error message displayed when inserting a resized version of an image failed."
+msgid "Could not obtain resized image URL."
+msgstr "크기가 조절된 사진의 URL을 가져오지 못했습니다."
+
+msgctxt "Title of a notification displayed when inserting a resized version of an image failed."
+msgid "Selecting resized image failed"
+msgstr "크기가 조절된 이미지 선택 실패"
+
+msgctxt "Error message displayed when an image cannot be inserted at the current position."
+msgid "Could not insert image at the current position."
+msgstr "현재 위치에 사진을 삽입할 수 없습니다."
+
+msgctxt "Title of a notification displayed when an image cannot be inserted at the current position."
+msgid "Inserting image failed"
+msgstr "사진 삽입 실패"
diff --git a/packages/ckeditor5-code-block/lang/translations/ko.po b/packages/ckeditor5-code-block/lang/translations/ko.po
new file mode 100644
index 00000000000..1f3bf78d7e9
--- /dev/null
+++ b/packages/ckeditor5-code-block/lang/translations/ko.po
@@ -0,0 +1,25 @@
+# Copyright (c) 2003-2020, 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"
+"Language: ko\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "A label of the button that allows inserting a new code block into the editor content."
+msgid "Insert code block"
+msgstr "코드 블럭 삽입"
+
+msgctxt "A language of the code block in the editor content when no specific programming language is associated with it."
+msgid "Plain text"
+msgstr "평문"
diff --git a/packages/ckeditor5-code-block/lang/translations/zh.po b/packages/ckeditor5-code-block/lang/translations/zh.po
new file mode 100644
index 00000000000..bc7cd08ff23
--- /dev/null
+++ b/packages/ckeditor5-code-block/lang/translations/zh.po
@@ -0,0 +1,25 @@
+# Copyright (c) 2003-2020, 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"
+"Language: zh_TW\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "A label of the button that allows inserting a new code block into the editor content."
+msgid "Insert code block"
+msgstr "插入程式碼區塊"
+
+msgctxt "A language of the code block in the editor content when no specific programming language is associated with it."
+msgid "Plain text"
+msgstr "純文字"
diff --git a/packages/ckeditor5-core/lang/translations/ko.po b/packages/ckeditor5-core/lang/translations/ko.po
index 3be062cc2f2..dae881cba36 100644
--- a/packages/ckeditor5-core/lang/translations/ko.po
+++ b/packages/ckeditor5-core/lang/translations/ko.po
@@ -26,4 +26,4 @@ msgstr "취소"
msgctxt "The label used by a button next to the color palette in the color picker that removes the color (resets it to an empty value, example usages in font color or table properties)."
msgid "Remove color"
-msgstr "색상 지우기"
+msgstr "색깔 제거"
diff --git a/packages/ckeditor5-core/src/editor/utils/dataapimixin.js b/packages/ckeditor5-core/src/editor/utils/dataapimixin.js
index 057d8bb8883..be18fb1d6fd 100644
--- a/packages/ckeditor5-core/src/editor/utils/dataapimixin.js
+++ b/packages/ckeditor5-core/src/editor/utils/dataapimixin.js
@@ -71,7 +71,8 @@ export default DataApiMixin;
* the right format for you.
*
* @method #getData
- * @param {Object} [options]
+ * @param {Object} [options] Additional configuration for the retrieved data.
+ * Editor features may introduce more configuration options that can be set through this parameter.
* @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
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-unsafe-link-class.html b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-unsafe-link-class.html
index e89fc38bb4e..fedd11bacf5 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-unsafe-link-class.html
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-unsafe-link-class.html
@@ -8,6 +8,6 @@
All links in this editor that do not use the HTTPS protocol
- have a custom .unsafe-link CSS class that marks them red.
+ have a custom .unsafe-link CSS class that marks them with a predefined graphic differentiator.
Edit the URL of the links using "http://" or "https://" to see them marked as "safe" or "unsafe".
Special section A: It has set "style" and "id" attributes.
+
Special section A: It has both the "style" and "id" attributes set.
Regular content of the editor.
-
Special section B: It has set "style", "id" and spellcheck="false" attributes.
+
Special section B: It has the "style", "id" and spellcheck="false" attributes set.
This section disables the native browser spellchecker.
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.html b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.html
index 0c58bd5e388..8ddc1601430 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.html
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.html
@@ -1,27 +1,45 @@
diff --git a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-extending-output.md b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-extending-output.md
index 9ad66ca6726..1e160d216b6 100644
--- a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-extending-output.md
+++ b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-extending-output.md
@@ -16,17 +16,17 @@ If you want to learn how to load some extra content (element, attributes, classe
### Code architecture
-It is recommended that the code that customizes the editor data and editing pipelines is delivered as {@link framework/guides/architecture/core-editor-architecture#plugins plugins} and all examples in this guide follow this convention.
+It is recommended for the code that customizes the editor data and editing pipelines to be delivered as {@link framework/guides/architecture/core-editor-architecture#plugins plugins} and all examples in this guide follow this convention.
Also for the sake of simplicity all examples use the same {@link module:editor-classic/classiceditor~ClassicEditor `ClassicEditor`}, but keep in mind that code snippets will work with other editors, too.
-Finally, none of the converters covered in this guide require to import any module from CKEditor 5 Framework, hence, you can write them without rebuilding the editor. In other words, such converters can easily be added to existing {@link builds/guides/overview CKEditor 5 builds}.
+Finally, none of the converters covered in this guide requires to import any modules from CKEditor 5 Framework, hence, you can write them without rebuilding the editor. In other words, such converters can easily be added to existing {@link builds/guides/overview CKEditor 5 builds}.
### Granular converters
You can create separate converters for the data and editing (downcast) pipelines. The former (`dataDowncast`) will customize the data in the editor output (e.g. when {@link builds/guides/integration/saving-data#manually-retrieving-the-data obtaining the editor data}). The latter (`editingDowncast`) will only work for the content of the editor when editing.
-If you do not want to complicate your conversion, you can just add a single (`downcast`) converter which will apply both to the data and the editing view. We did that in all examples to keep them simple but keep in mind you have options:
+If you do not want to complicate your conversion, you can just add a single (`downcast`) converter which will apply both to the data and the editing view. We did that in all the examples to keep them simple but keep in mind you have several options:
```js
// Adds a conversion dispatcher for the editing downcast pipeline only.
@@ -47,32 +47,32 @@ editor.conversion.for( 'downcast' ).add( dispatcher => {
### CKEditor 5 inspector
-{@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} is an invaluable help when working with the model and view structures. It allows browsing their structure and checking selection positions like in typical browser developer tools. Make sure to enable the inspector when playing with CKEditor 5.
+The {@link framework/guides/development-tools#ckeditor-5-inspector CKEditor 5 inspector} is an invaluable help when working with the model and view structures. It allows browsing their structure and checking selection positions like in typical browser developer tools. Make sure to enable the inspector when playing with CKEditor 5.
## Adding a CSS class to inline elements
In this example all links (`...`) get the `.my-green-link` CSS class. This includes all links in the editor output (`editor.getData()`) and all links in the edited content (existing and future ones).
-
- Note that the same behavior can be obtained with {@link features/link#custom-link-attributes-decorators link decorators}:
-
- ```js
- ClassicEditor
- .create( ..., {
- // ...
- link: {
- decorators: {
- addGreenLink: {
- mode: 'automatic',
- attributes: {
- class: 'my-green-link'
- }
+
+Note that the same behavior can be obtained with {@link features/link#custom-link-attributes-decorators link decorators}:
+
+```js
+ClassicEditor
+ .create( ..., {
+ // ...
+ link: {
+ decorators: {
+ addGreenLink: {
+ mode: 'automatic',
+ attributes: {
+ class: 'my-green-link'
}
}
}
- } )
- ```
-
+ }
+ } )
+```
+
{@snippet framework/extending-content-add-link-class}
@@ -136,21 +136,20 @@ Add some CSS styles for `.my-green-link` to see the customization in action:
## Adding an HTML attribute to certain inline elements
-In this example all links (`...`) that do not have "ckeditor.com" in their `href="..."` get the `target="_blank"` attribute. This includes all links in the editor output (`editor.getData()`) and all links in the edited content (existing and future ones).
+In this example all the links (`...`) that do not have "ckeditor.com" in their `href="..."` get the `target="_blank"` attribute. This includes all links in the editor output (`editor.getData()`) and all links in the edited content (existing and future ones).
-
- Note that similar behavior can be obtained with {@link module:link/link~LinkConfig#addTargetToExternalLinks link decorators}:
-
- ```js
- ClassicEditor
- .create( ..., {
- // ...
- link: {
- addTargetToExternalLinks: true
- }
- } )
- ```
-
+
+Note that similar behavior can be obtained with {@link module:link/link~LinkConfig#addTargetToExternalLinks link decorators}:
+
+```js
+ClassicEditor
+ .create( ..., {
+ // ...
+ link: {
+ addTargetToExternalLinks: true
+ }
+ } )
+```
{@snippet framework/extending-content-add-external-link-target}
@@ -216,27 +215,27 @@ a[target="_blank"]::after {
In this example all links (`...`) that do not have `https://` in their `href="..."` attribute get the `.unsafe-link` CSS class. This includes all links in the editor output (`editor.getData()`) and all links in the edited content (existing and future ones).
-
- Note that the same behavior can be obtained with {@link features/link#custom-link-attributes-decorators link decorators}:
-
- ```js
- ClassicEditor
- .create( ..., {
- // ...
- link: {
- decorators: {
- markUnsafeLink: {
- mode: 'automatic',
- callback: url => /^(http:)?\/\//.test( url ),
- attributes: {
- class: 'unsafe-link'
- }
+
+Note that the same behavior can be obtained with {@link features/link#custom-link-attributes-decorators link decorators}:
+
+```js
+ClassicEditor
+ .create( ..., {
+ // ...
+ link: {
+ decorators: {
+ markUnsafeLink: {
+ mode: 'automatic',
+ callback: url => /^(http:)?\/\//.test( url ),
+ attributes: {
+ class: 'unsafe-link'
}
}
}
- } )
- ```
-
+ }
+ } )
+```
+
{@snippet framework/extending-content-add-unsafe-link-class}
@@ -302,7 +301,7 @@ Add some CSS styles for "unsafe" links to make them visible:
## Adding a CSS class to block elements
-In this example all second–level headings (`
...
`) get the `.my-heading` CSS class. This includes all heading elements in the editor output (`editor.getData()`) and in the edited content (existing and future ones).
+In this example all second–level headings (`
...
`) get the `.my-heading` CSS class. This includes all the heading elements in the editor output (`editor.getData()`) and in the edited content (existing and future ones).
{@snippet framework/extending-content-add-heading-class}
diff --git a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-introduction.md b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-introduction.md
index d6c3545d08a..3464af18ff9 100644
--- a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-introduction.md
+++ b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-introduction.md
@@ -12,13 +12,13 @@ order: 10
This guide extends the {@link framework/guides/architecture/editing-engine introduction to CKEditor 5 editing engine architecture}. Therefore, we highly recommend reading the former guide first.
-In this guide we will dive deeper into some of the conversion concepts related to model attributes.
+In this guide you will dive deeper into some of the conversion concepts.
## Inline and block content
-Generally speaking, there are two main types of the content in the editor view and data output: inline and block.
+Generally speaking, there are two main types of content in the editor view and data output: inline and block.
-The inline content means elements like ``, `` or ``. Unlike `
`) loaded into the editor content will preserve their attributes. All the DOM attributes will be stored in the editor model as corresponding attributes.
+In this example the `
` elements (`
...
`) loaded into the editor content will preserve their attributes. All the DOM attributes will be stored in the editor model as corresponding attributes.
{@snippet framework/extending-content-allow-div-attributes}
@@ -128,10 +127,10 @@ All attributes are allowed on `
` elements thanks to custom "upcast" and "do
Allowing every possible attribute on a `
` element in the model is done by adding an {@link module:engine/model/schema~Schema#addAttributeCheck addAttributeCheck()} callback.
- Allowing every attribute on `
` elements might introduce security issues — including XSS attacks. The production code should use only application-related attributes and/or properly encode data.
+ Allowing every attribute on `
` elements might introduce security issues — including XSS attacks. The production code should use only application-related attributes and/or properly encode the data.
-Adding "upcast" and "downcast" converters for the `
` element is enough for cases where its attributes do not change. If the attributes in the model are modified, these `elementToElement()` converters will not be called as the `
` is already converted. To overcome this, a lower-level API is used.
+Adding "upcast" and "downcast" converters for the `
` element is enough for these cases where its attributes do not change. If the attributes in the model are modified, however, these `elementToElement()` converters will not be called as the `
` is already converted. To overcome this, a lower-level API is used.
Instead of using predefined converters, the {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event-attribute `attribute`} event listener is registered for the "downcast" dispatcher.
@@ -150,7 +149,7 @@ function ConvertDivAttributes( editor ) {
}
} );
- // View-to-model converter converting a view
with all its attributes to the model.
+ // The view-to-model converter converting a view
with all its attributes to the model.
editor.conversion.for( 'upcast' ).elementToElement( {
view: 'div',
model: ( viewElement, modelWriter ) => {
@@ -158,13 +157,13 @@ function ConvertDivAttributes( editor ) {
}
} );
- // Model-to-view converter for the
element (attributes are converted separately).
+ // The model-to-view converter for the
element (attributes are converted separately).
editor.conversion.for( 'downcast' ).elementToElement( {
model: 'div',
view: 'div'
} );
- // Model-to-view converter for
attributes.
+ // The model-to-view converter for
attributes.
// Note that a lower-level, event-based API is used here.
editor.conversion.for( 'downcast' ).add( dispatcher => {
dispatcher.on( 'attribute', ( evt, data, conversionApi ) => {
@@ -229,8 +228,8 @@ function HandleFontSizeValue( editor ) {
value: viewElement => {
const value = parseFloat( viewElement.getStyle( 'font-size' ) ).toFixed( 0 );
- // It might be needed to further convert the value to meet business requirements.
- // In the sample the font size is configured to handle only the sizes:
+ // It might be necessary to further convert the value to meet business requirements.
+ // In the sample the font size is configured to handle only these sizes:
// 12, 14, 'default', 18, 20, 22, 24, 26, 28, 30
// Other sizes will be converted to the model but the UI might not be aware of them.
@@ -241,7 +240,7 @@ function HandleFontSizeValue( editor ) {
converterPriority: 'high'
} );
- // Add a special converter for the font size feature to convert all (even not configured)
+ // Add a special converter for the font size feature to convert all (even the not configured)
// model attribute values.
editor.conversion.for( 'downcast' ).attributeToElement( {
model: {
@@ -278,7 +277,7 @@ ClassicEditor
## Adding extra attributes to elements contained in a figure
-The {@link features/image Image} and {@link features/table Table} features wrap view elements (`` for Image nad `
` for Table) in `
`. During the downcast conversion, the model element is mapped to `
` and not the inner element. In such cases the default `conversion.attributeToAttribute()` conversion helpers could lose information about the element that the attribute should be set on.
+The {@link features/image image} and {@link features/table table} features wrap view elements (`` for image and `
` for table, respectively) in a `
` element. During the downcast conversion, the model element is mapped to `
` and not the inner element. In such cases the default `conversion.attributeToAttribute()` conversion helpers could lose information about the element that the attribute should be set on.
To overcome this limitation it is sufficient to write a custom converter that adds custom attributes to elements already converted by base features. The key point is to add these converters with a lower priority than the base converters so they will be called after the base ones.
@@ -288,11 +287,11 @@ The sample below is extensible. To add your own attributes to preserve, just add
```js
/**
- * Plugin that converts custom attributes for elements that are wrapped in
in the view.
+ * A plugin that converts custom attributes for elements that are wrapped in
in the view.
*/
class CustomFigureAttributes {
/**
- * Plugin's constructor - receives editor instance on creation.
+ * Plugin's constructor - receives an editor instance on creation.
*/
constructor( editor ) {
// Save reference to the editor.
@@ -300,9 +299,9 @@ class CustomFigureAttributes {
}
/**
- * Setups conversion and extends table & image features schema.
+ * Sets the conversion up and extends the table & image features schema.
*
- * Schema extending must be done in the “afterInit()” call because plugins define their schema in “init()“.
+ * Schema extending must be done in the "afterInit()" call because plugins define their schema in "init()".
*/
afterInit() {
const editor = this.editor;
@@ -320,23 +319,24 @@ class CustomFigureAttributes {
}
/**
- * Sets up a conversion that preservers classes on and
elements.
+ * Sets up a conversion that preserves classes on and
elements.
*/
function setupCustomClassConversion( viewElementName, modelElementName, editor ) {
- // The 'customClass' attribute will store custom classes from the data in the model so schema definitions allow this attribute.
+ // The 'customClass' attribute stores custom classes from the data in the model so that schema definitions allow this attribute.
editor.model.schema.extend( modelElementName, { allowAttributes: [ 'customClass' ] } );
- // Define upcast converters for the and
elements with a "low" priority so they are run after the default converters.
+ // Defines upcast converters for the and
elements with a "low" priority so they are run after the default converters.
editor.conversion.for( 'upcast' ).add( upcastCustomClasses( viewElementName ), { priority: 'low' } );
- // Define downcast converters for a model element with a "low" priority so they are run after the default converters.
- // Use `downcastCustomClassesToFigure` if you'd like to keep your classes on
element or `downcastCustomClassesToChild` if you'd like to keep your classes on a
child element, i.e. .
+ // Defines downcast converters for a model element with a "low" priority so they are run after the default converters.
+ // Use `downcastCustomClassesToFigure` if you want to keep your classes on
element or `downcastCustomClassesToChild`
+ // if you would like to keep your classes on a
child element, i.e. .
editor.conversion.for( 'downcast' ).add( downcastCustomClassesToFigure( modelElementName ), { priority: 'low' } );
// editor.conversion.for( 'downcast' ).add( downcastCustomClassesToChild( viewElementName, modelElementName ), { priority: 'low' } );
}
/**
- * Sets up a conversion for a custom attribute on view elements contained inside a
.
+ * Sets up a conversion for a custom attribute on the view elements contained inside a
.
*
* This method:
* - Adds proper schema rules.
@@ -344,7 +344,7 @@ function setupCustomClassConversion( viewElementName, modelElementName, editor )
* - Adds a downcast converter.
*/
function setupCustomAttributeConversion( viewElementName, modelElementName, viewAttribute, editor ) {
- // Extend the schema to store an attribute in the model.
+ // Extends the schema to store an attribute in the model.
const modelAttribute = `custom${ viewAttribute }`;
editor.model.schema.extend( modelElementName, { allowAttributes: [ modelAttribute ] } );
diff --git a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/schema.md b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/schema.md
index 9854fa07105..9be18b0b72d 100644
--- a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/schema.md
+++ b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/schema.md
@@ -11,9 +11,9 @@ This article assumes that you have already read the {@link framework/guides/arch
The editor's schema is available in the {@link module:engine/model/model~Model#schema `editor.model.schema`} property. It defines allowed model structures (how model elements can be nested), allowed attributes (of both elements and text nodes), and other characteristics (inline vs. block, atomicity in regards of external actions). This information is later used by editing features and the editing engine to decide how to process the model, where to enable features, etc.
-Schema rules can be defined by using the {@link module:engine/model/schema~Schema#register `Schema#register()`} or {@link module:engine/model/schema~Schema#extend `Schema#extend()`} methods. The former can be used only once for a given item name which ensures that only a single editing feature can introduce this item. Similarly, `extend()` can only be used for defined items.
+Schema rules can be defined by using the {@link module:engine/model/schema~Schema#register `Schema#register()`} or the {@link module:engine/model/schema~Schema#extend `Schema#extend()`} methods. The former can be used only once for a given item name which ensures that only a single editing feature can introduce this item. Similarly, `extend()` can only be used for defined items.
-Elements and attributes are checked by features separately by using the {@link module:engine/model/schema~Schema#checkChild `Schema#checkChild()`} and {@link module:engine/model/schema~Schema#checkAttribute `Schema#checkAttribute()`} methods.
+Elements and attributes are checked by features separately by using the {@link module:engine/model/schema~Schema#checkChild `Schema#checkChild()`} and the {@link module:engine/model/schema~Schema#checkAttribute `Schema#checkAttribute()`} methods.
## Defining allowed structures
@@ -47,7 +47,7 @@ While this would be incorrect:
## Defining additional semantics
-In addition to setting allowed structures, the schema can also define additional traits of model elements. By using the `is*` properties, a feature author may declare how a certain element should be treated by other features and the engine.
+In addition to setting allowed structures, the schema can also define additional traits of model elements. By using the `is*` properties, a feature author may declare how a certain element should be treated by other features and by the engine.
Here is a table listing various model elements and their properties registered in the schema:
@@ -292,7 +292,7 @@ The engine and various features then check it via {@link module:engine/model/sch
For an image caption like in the example above it does not make much sense to select the caption box, then copy or drag it somewhere else.
-A caption without the image that it describes makes little sense. However, the image is more self-sufficient. Most likely users should be able to select the entire image (with all its internals), then copy or move it around. The {@link module:engine/model/schema~SchemaItemDefinition#isObject `isObject`} property should be used to mark such behavior.
+A caption without the image it describes makes little sense. The image, however, is more self-sufficient. Most likely users should be able to select the entire image (with all its internals), then copy or move it around. The {@link module:engine/model/schema~SchemaItemDefinition#isObject `isObject`} property should be used to mark such behavior.
```js
schema.register( 'myImage', {
@@ -324,7 +324,7 @@ It is important to remember that a block should not allow another block inside.
In the editor, all HTML formatting elements such as `` or `` are represented by text attributes. Therefore, inline model elements are not supposed to be used for these scenarios.
-Currently, the {@link module:engine/model/schema~SchemaItemDefinition#isInline `isInline`} property is used for the `$text` token (so, text nodes) and elements such as `` or placeholder elements such as in the {@link framework/guides/tutorials/implementing-an-inline-widget Implementing an inline widget} tutorial.
+Currently, the {@link module:engine/model/schema~SchemaItemDefinition#isInline `isInline`} property is used for the `$text` token (so, text nodes) and elements such as `` or placeholder elements such as described in the {@link framework/guides/tutorials/implementing-an-inline-widget Implementing an inline widget} tutorial.
The support for inline elements in CKEditor 5 is so far limited to self-contained elements. Because of this, all elements marked with `isInline` should also be marked with `isObject`.
@@ -401,7 +401,7 @@ schema.register( 'paragraph', {
} );
```
-Which can be read as:
+And this can be read as:
* The `` element will be allowed in elements in which `<$block>` is allowed (e.g. in `<$root>`).
* The `` element will allow all nodes that are allowed in `<$block>` (e.g. `$text`).
@@ -427,7 +427,7 @@ The side effect of such a definition inheritance is that now `
` is a
## Defining advanced rules in `checkChild()` callbacks
-The {@link module:engine/model/schema~Schema#checkChild `Schema#checkChild()`} method which is the base method used to check whether some element is allowed in a given structure is {@link module:utils/observablemixin~ObservableMixin#decorate a decorated method}. It means that you can add listeners to implement your specific rules which are not limited by the {@link module:engine/model/schema~SchemaItemDefinition declarative `SchemaItemDefinition` API}.
+The {@link module:engine/model/schema~Schema#checkChild `Schema#checkChild()`} method which is the a base method used to check whether some element is allowed in a given structure is {@link module:utils/observablemixin~ObservableMixin#decorate a decorated method}. It means that you can add listeners to implement your specific rules which are not limited by the {@link module:engine/model/schema~SchemaItemDefinition declarative `SchemaItemDefinition` API}.
These listeners can be added either by listening directly to the {@link module:engine/model/schema~Schema#event:checkChild} event or by using the handy {@link module:engine/model/schema~Schema#addChildCheck `Schema#addChildCheck()`} method.
@@ -485,7 +485,7 @@ While this is a relatively simple scenario (unlike most real-time collaborative
Therefore, if your editor needs to implement such rules, you should do that through {@link module:engine/model/document~Document#registerPostFixer model's post-fixers} fixing incorrect content or actively prevent such situations (e.g. by disabling certain features). It means that these constraints will be defined specifically for your scenario by your code which makes their implementation much easier.
-To sum up, the answer to who and how should implement additional constraints is: Your features or your editor through the CKEditor 5 API.
+To sum up, the answer to who and how should implement additional constraints is: your features or your editor through the CKEditor 5 API.
## Who checks the schema?
diff --git a/packages/ckeditor5-engine/src/controller/datacontroller.js b/packages/ckeditor5-engine/src/controller/datacontroller.js
index e779e3da0c6..8f536d88c21 100644
--- a/packages/ckeditor5-engine/src/controller/datacontroller.js
+++ b/packages/ckeditor5-engine/src/controller/datacontroller.js
@@ -24,6 +24,7 @@ import ViewDocument from '../view/document';
import ViewDowncastWriter from '../view/downcastwriter';
import ModelRange from '../model/range';
+import { autoParagraphEmptyRoots } from '../model/utils/autoparagraphing';
/**
* Controller for the data pipeline. The data pipeline controls how data is retrieved from the document
@@ -140,21 +141,28 @@ export default class DataController {
this.on( 'init', () => {
this.fire( 'ready' );
}, { priority: 'lowest' } );
+
+ // Fix empty roots after DataController is 'ready' (note that init method could be decorated and stopped).
+ // We need to handle this event because initial data could be empty and post-fixer would not get triggered.
+ this.on( 'ready', () => {
+ this.model.enqueueChange( 'transparent', autoParagraphEmptyRoots );
+ }, { priority: 'lowest' } );
}
/**
* Returns the model's data converted by downcast dispatchers attached to {@link #downcastDispatcher} and
* formatted by the {@link #processor data processor}.
*
- * @param {Object} [options]
+ * @param {Object} [options] Additional configuration for the retrieved data. `DataController` provides two optional
+ * properties: `rootName` and `trim`. Other properties of this object are specified by various editor features.
* @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 whenever editor content is considered empty, an empty string will be returned. To turn off trimming completely
* use `'none'`. In such cases exact content will be returned (for example `
` for an empty editor).
* @returns {String} Output data.
*/
- get( options ) {
- const { rootName = 'main', trim = 'empty' } = options || {};
+ get( options = {} ) {
+ const { rootName = 'main', trim = 'empty' } = options;
if ( !this._checkIfRootsExists( [ rootName ] ) ) {
/**
@@ -177,7 +185,7 @@ export default class DataController {
return '';
}
- return this.stringify( root );
+ return this.stringify( root, options );
}
/**
@@ -187,11 +195,12 @@ export default class DataController {
*
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} modelElementOrFragment
* Element whose content will be stringified.
+ * @param {Object} [options] Additional configuration passed to the conversion process.
* @returns {String} Output data.
*/
- stringify( modelElementOrFragment ) {
+ stringify( modelElementOrFragment, options ) {
// Model -> view.
- const viewDocumentFragment = this.toView( modelElementOrFragment );
+ const viewDocumentFragment = this.toView( modelElementOrFragment, options );
// View -> data.
return this.processor.toData( viewDocumentFragment );
@@ -205,9 +214,11 @@ export default class DataController {
*
* @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} modelElementOrFragment
* Element or document fragment whose content will be converted.
+ * @param {Object} [options] Additional configuration that will be available through
+ * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi#options} during the conversion process.
* @returns {module:engine/view/documentfragment~DocumentFragment} Output view DocumentFragment.
*/
- toView( modelElementOrFragment ) {
+ toView( modelElementOrFragment, options ) {
const viewDocument = this.viewDocument;
const viewWriter = this._viewWriter;
@@ -220,6 +231,9 @@ export default class DataController {
this.mapper.bindElements( modelElementOrFragment, viewDocumentFragment );
+ // Make additional options available during conversion process through `conversionApi`.
+ this.downcastDispatcher.conversionApi.options = options;
+
// We have no view controller and rendering to DOM in DataController so view.change() block is not used here.
this.downcastDispatcher.convertInsert( modelRange, viewWriter );
@@ -233,6 +247,9 @@ export default class DataController {
}
}
+ // Clean `conversionApi`.
+ delete this.downcastDispatcher.conversionApi.options;
+
return viewDocumentFragment;
}
diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js
index aa2ab403506..1fdc18a22f4 100644
--- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js
+++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js
@@ -11,7 +11,6 @@ import Consumable from './modelconsumable';
import Range from '../model/range';
import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
import mix from '@ckeditor/ckeditor5-utils/src/mix';
-import { extend } from 'lodash-es';
/**
* Downcast dispatcher is a central point of downcasting (conversion from the model to the view), which is a process of reacting to changes
@@ -115,7 +114,7 @@ export default class DowncastDispatcher {
*
* @member {module:engine/conversion/downcastdispatcher~DowncastConversionApi}
*/
- this.conversionApi = extend( { dispatcher: this }, conversionApi );
+ this.conversionApi = Object.assign( { dispatcher: this }, conversionApi );
}
/**
@@ -669,3 +668,9 @@ function shouldMarkerChangeBeConverted( modelPosition, marker, mapper ) {
*
* @member {module:engine/view/downcastwriter~DowncastWriter} #writer
*/
+
+/**
+ * An object with an additional configuration which can be used during conversion process. Available only for data downcast conversion.
+ *
+ * @member {Object} #options
+ */
diff --git a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js
index 59fabb004b7..20a75fd6f56 100644
--- a/packages/ckeditor5-engine/src/conversion/downcasthelpers.js
+++ b/packages/ckeditor5-engine/src/conversion/downcasthelpers.js
@@ -52,8 +52,10 @@ export default class DowncastHelpers extends ConversionHelpers {
*
* editor.conversion.for( 'downcast' ).elementToElement( {
* model: 'heading',
- * view: ( modelElement, viewWriter ) => {
- * return viewWriter.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) )
+ * view: ( modelElement, conversionApi ) => {
+ * const { writer } = conversionApi;
+ *
+ * return writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) );
* }
* } );
*
@@ -64,7 +66,7 @@ export default class DowncastHelpers extends ConversionHelpers {
* @param {Object} config Conversion configuration.
* @param {String} config.model The name of the model element to convert.
* @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function
- * that takes the model element and {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer}
+ * that takes the model element and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
* as parameters and returns a view container element.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
*/
@@ -120,8 +122,10 @@ export default class DowncastHelpers extends ConversionHelpers {
*
* editor.conversion.for( 'downcast' ).attributeToElement( {
* model: 'bold',
- * view: ( modelAttributeValue, viewWriter ) => {
- * return viewWriter.createAttributeElement( 'span', {
+ * view: ( modelAttributeValue, conversionApi ) => {
+ * const { writer } = conversionApi;
+ *
+ * return writer.createAttributeElement( 'span', {
* style: 'font-weight:' + modelAttributeValue
* } );
* }
@@ -132,8 +136,10 @@ export default class DowncastHelpers extends ConversionHelpers {
* key: 'color',
* name: '$text'
* },
- * view: ( modelAttributeValue, viewWriter ) => {
- * return viewWriter.createAttributeElement( 'span', {
+ * view: ( modelAttributeValue, conversionApi ) => {
+ * const { writer } = conversionApi;
+ *
+ * return writer.createAttributeElement( 'span', {
* style: 'color:' + modelAttributeValue
* } );
* }
@@ -147,9 +153,10 @@ export default class DowncastHelpers extends ConversionHelpers {
* @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values }` object. `values` is an array
* of `String`s with possible values if the model attribute is an enumerable.
* @param {module:engine/view/elementdefinition~ElementDefinition|Function|Object} config.view A view element definition or a function
- * that takes the model attribute value and {@link module:engine/view/downcastwriter~DowncastWriter view downcast writer}
- * as parameters and returns a view attribute element. If `config.model.values` is
- * given, `config.view` should be an object assigning values from `config.model.values` to view element definitions or functions.
+ * that takes the model attribute value and
+ * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as parameters and returns a view
+ * attribute element. If `config.model.values` is given, `config.view` should be an object assigning values from `config.model.values`
+ * to view element definitions or functions.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
*/
@@ -201,7 +208,10 @@ export default class DowncastHelpers extends ConversionHelpers {
*
* editor.conversion.for( 'downcast' ).attributeToAttribute( {
* model: 'styled',
- * view: modelAttributeValue => ( { key: 'class', value: 'styled-' + modelAttributeValue } )
+ * view: modelAttributeValue => ( {
+ * key: 'class',
+ * value: 'styled-' + modelAttributeValue
+ * } )
* } );
*
* **Note**: Downcasting to a style property requires providing `value` as an object:
@@ -225,7 +235,8 @@ export default class DowncastHelpers extends ConversionHelpers {
* @param {String|Object} config.model The key of the attribute to convert from or a `{ key, values, [ name ] }` object describing
* the attribute key, possible values and, optionally, an element name to convert from.
* @param {String|Object|Function} config.view A view attribute key, or a `{ key, value }` object or a function that takes
- * the model attribute value and returns a `{ key, value }` object. If `key` is `'class'`, `value` can be a `String` or an
+ * the model attribute value and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
+ * as parameters and returns a `{ key, value }` object. If `key` is `'class'`, `value` can be a `String` or an
* array of `String`s. If `key` is `'style'`, `value` is an object with key-value pairs. In other cases, `value` is a `String`.
* If `config.model.values` is set, `config.view` should be an object assigning values from `config.model.values` to
* `{ key, value }` objects or a functions.
@@ -269,8 +280,10 @@ export default class DowncastHelpers extends ConversionHelpers {
*
* editor.conversion.for( 'editingDowncast' ).markerToElement( {
* model: 'search',
- * view: ( markerData, viewWriter ) => {
- * return viewWriter.createUIElement( 'span', {
+ * view: ( markerData, conversionApi ) => {
+ * const { writer } = conversionApi;
+ *
+ * return writer.createUIElement( 'span', {
* 'data-marker': 'search',
* 'data-start': markerData.isOpening
* } );
@@ -278,7 +291,8 @@ export default class DowncastHelpers extends ConversionHelpers {
* } );
*
* If a function is passed as the `config.view` parameter, it will be used to generate both boundary elements. The function
- * receives the `data` object as a parameter and should return an instance of the
+ * receives the `data` object and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
+ * as a parameters and should return an instance of the
* {@link module:engine/view/uielement~UIElement view UI element}. The `data` object and
* {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi `conversionApi`} are passed from
* {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker}. Additionally,
@@ -291,8 +305,9 @@ export default class DowncastHelpers extends ConversionHelpers {
* @method #markerToElement
* @param {Object} config Conversion configuration.
* @param {String} config.model The name of the model marker (or model marker group) to convert.
- * @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function
- * that takes the model marker data as a parameter and returns a view UI element.
+ * @param {module:engine/view/elementdefinition~ElementDefinition|Function} config.view A view element definition or a function that
+ * takes the model marker data and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
+ * as a parameters and returns a view UI element.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
*/
@@ -329,7 +344,7 @@ export default class DowncastHelpers extends ConversionHelpers {
*
* editor.conversion.for( 'downcast' ).markerToHighlight( {
* model: 'comment',
- * view: data => {
+ * view: ( data, converstionApi ) => {
* // Assuming that the marker name is in a form of comment:commentType.
* const commentType = data.markerName.split( ':' )[ 1 ];
*
@@ -340,7 +355,8 @@ export default class DowncastHelpers extends ConversionHelpers {
* } );
*
* If a function is passed as the `config.view` parameter, it will be used to generate the highlight descriptor. The function
- * receives the `data` object as a parameter and should return a
+ * receives the `data` object and {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API}
+ * as a parameters and should return a
* {@link module:engine/conversion/downcasthelpers~HighlightDescriptor highlight descriptor}.
* The `data` object properties are passed from {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker}.
*
@@ -351,7 +367,9 @@ export default class DowncastHelpers extends ConversionHelpers {
* @param {Object} config Conversion configuration.
* @param {String} config.model The name of the model marker (or model marker group) to convert.
* @param {module:engine/conversion/downcasthelpers~HighlightDescriptor|Function} config.view A highlight descriptor
- * that will be used for highlighting or a function that takes the model marker data as a parameter and returns a highlight descriptor.
+ * that will be used for highlighting or a function that takes the model marker data and
+ * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as a parameters
+ * and returns a highlight descriptor.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
*/
@@ -464,8 +482,9 @@ export default class DowncastHelpers extends ConversionHelpers {
* @method #markerToData
* @param {Object} config Conversion configuration.
* @param {String} config.model The name of the model marker (or model marker group) to convert.
- * @param {Function} [config.view] A function that takes the model marker name as a parameter and returns an object with the `group`
- * and `name` properties.
+ * @param {Function} [config.view] A function that takes the model marker name and
+ * {@link module:engine/conversion/downcastdispatcher~DowncastConversionApi downcast conversion API} as a parameters
+ * and returns an object with the `group` and `name` properties.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
*/
@@ -703,10 +722,10 @@ export function wrap( elementCreator ) {
return ( evt, data, conversionApi ) => {
// Recreate current wrapping node. It will be used to unwrap view range if the attribute value has changed
// or the attribute was removed.
- const oldViewElement = elementCreator( data.attributeOldValue, conversionApi.writer );
+ const oldViewElement = elementCreator( data.attributeOldValue, conversionApi );
// Create node to wrap with.
- const newViewElement = elementCreator( data.attributeNewValue, conversionApi.writer );
+ const newViewElement = elementCreator( data.attributeNewValue, conversionApi );
if ( !oldViewElement && !newViewElement ) {
return;
@@ -766,7 +785,7 @@ export function wrap( elementCreator ) {
*/
export function insertElement( elementCreator ) {
return ( evt, data, conversionApi ) => {
- const viewElement = elementCreator( data.item, conversionApi.writer );
+ const viewElement = elementCreator( data.item, conversionApi );
if ( !viewElement ) {
return;
@@ -803,10 +822,10 @@ export function insertUIElement( elementCreator ) {
// Create two view elements. One will be inserted at the beginning of marker, one at the end.
// If marker is collapsed, only "opening" element will be inserted.
data.isOpening = true;
- const viewStartElement = elementCreator( data, conversionApi.writer );
+ const viewStartElement = elementCreator( data, conversionApi );
data.isOpening = false;
- const viewEndElement = elementCreator( data, conversionApi.writer );
+ const viewEndElement = elementCreator( data, conversionApi );
if ( !viewStartElement || !viewEndElement ) {
return;
@@ -880,7 +899,7 @@ function removeUIElement() {
// @returns {Function} Add marker converter.
function insertMarkerData( viewCreator ) {
return ( evt, data, conversionApi ) => {
- const viewMarkerData = viewCreator( data.markerName );
+ const viewMarkerData = viewCreator( data.markerName, conversionApi );
if ( !viewMarkerData ) {
return;
@@ -961,7 +980,7 @@ function insertMarkerAsElement( position, isStart, conversionApi, data, viewMark
// @returns {Function} Remove marker converter.
function removeMarkerData( viewCreator ) {
return ( evt, data, conversionApi ) => {
- const viewData = viewCreator( data.markerName );
+ const viewData = viewCreator( data.markerName, conversionApi );
if ( !viewData ) {
return;
@@ -1036,8 +1055,8 @@ function removeMarkerData( viewCreator ) {
// @returns {Function} Set/change attribute converter.
function changeAttribute( attributeCreator ) {
return ( evt, data, conversionApi ) => {
- const oldAttribute = attributeCreator( data.attributeOldValue, data );
- const newAttribute = attributeCreator( data.attributeNewValue, data );
+ const oldAttribute = attributeCreator( data.attributeOldValue, conversionApi );
+ const newAttribute = attributeCreator( data.attributeNewValue, conversionApi );
if ( !oldAttribute && !newAttribute ) {
return;
@@ -1487,7 +1506,7 @@ function normalizeToElementConfig( view, viewElementType ) {
return view;
}
- return ( modelData, viewWriter ) => createViewElementFromDefinition( view, viewWriter, viewElementType );
+ return ( modelData, conversionApi ) => createViewElementFromDefinition( view, conversionApi, viewElementType );
}
// Creates a view element instance from the provided {@link module:engine/view/elementdefinition~ElementDefinition} and class.
@@ -1496,13 +1515,14 @@ function normalizeToElementConfig( view, viewElementType ) {
// @param {module:engine/view/downcastwriter~DowncastWriter} viewWriter
// @param {'container'|'attribute'|'ui'} viewElementType
// @returns {module:engine/view/element~Element}
-function createViewElementFromDefinition( viewElementDefinition, viewWriter, viewElementType ) {
+function createViewElementFromDefinition( viewElementDefinition, conversionApi, viewElementType ) {
if ( typeof viewElementDefinition == 'string' ) {
// If `viewElementDefinition` is given as a `String`, normalize it to an object with `name` property.
viewElementDefinition = { name: viewElementDefinition };
}
let element;
+ const viewWriter = conversionApi.writer;
const attributes = Object.assign( {}, viewElementDefinition.attributes );
if ( viewElementType == 'container' ) {
@@ -1543,11 +1563,11 @@ function createViewElementFromDefinition( viewElementDefinition, viewWriter, vie
function getFromAttributeCreator( config ) {
if ( config.model.values ) {
- return ( modelAttributeValue, viewWriter ) => {
+ return ( modelAttributeValue, conversionApi ) => {
const view = config.view[ modelAttributeValue ];
if ( view ) {
- return view( modelAttributeValue, viewWriter );
+ return view( modelAttributeValue, conversionApi );
}
return null;
diff --git a/packages/ckeditor5-engine/src/conversion/upcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/upcastdispatcher.js
index 76ced552ea1..82f1ccacf4b 100644
--- a/packages/ckeditor5-engine/src/conversion/upcastdispatcher.js
+++ b/packages/ckeditor5-engine/src/conversion/upcastdispatcher.js
@@ -11,6 +11,7 @@ import ViewConsumable from './viewconsumable';
import ModelRange from '../model/range';
import ModelPosition from '../model/position';
import { SchemaContext } from '../model/schema';
+import { isParagraphable, wrapInParagraph } from '../model/utils/autoparagraphing';
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
@@ -349,22 +350,32 @@ export default class UpcastDispatcher {
* @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#splitToAllowedParent
*/
_splitToAllowedParent( node, modelCursor ) {
+ const { schema, writer } = this.conversionApi;
+
// Try to find allowed parent.
- const allowedParent = this.conversionApi.schema.findAllowedParent( modelCursor, node );
+ let allowedParent = schema.findAllowedParent( modelCursor, node );
- // When there is no parent that allows to insert node then return `null`.
- if ( !allowedParent ) {
- return null;
- }
+ if ( allowedParent ) {
+ // When current position parent allows to insert node then return this position.
+ if ( allowedParent === modelCursor.parent ) {
+ return { position: modelCursor };
+ }
- // When current position parent allows to insert node then return this position.
- if ( allowedParent === modelCursor.parent ) {
- return { position: modelCursor };
+ // When allowed parent is in context tree (it's outside the converted tree).
+ if ( this._modelCursor.parent.getAncestors().includes( allowedParent ) ) {
+ allowedParent = null;
+ }
}
- // When allowed parent is in context tree.
- if ( this._modelCursor.parent.getAncestors().includes( allowedParent ) ) {
- return null;
+ if ( !allowedParent ) {
+ // Check if the node wrapped with a paragraph would be accepted by the schema.
+ if ( !isParagraphable( modelCursor, node, schema ) ) {
+ return null;
+ }
+
+ return {
+ position: wrapInParagraph( modelCursor, writer )
+ };
}
// Split element to allowed parent.
diff --git a/packages/ckeditor5-engine/src/conversion/upcasthelpers.js b/packages/ckeditor5-engine/src/conversion/upcasthelpers.js
index 95ccde9c6c8..656b142e47e 100644
--- a/packages/ckeditor5-engine/src/conversion/upcasthelpers.js
+++ b/packages/ckeditor5-engine/src/conversion/upcasthelpers.js
@@ -10,6 +10,7 @@ import { cloneDeep } from 'lodash-es';
import { attachLinkToDocumentation } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
+import { isParagraphable, wrapInParagraph } from '../model/utils/autoparagraphing';
/* global console */
@@ -59,7 +60,9 @@ export default class UpcastHelpers extends ConversionHelpers {
* name: 'p',
* classes: 'heading'
* },
- * model: ( viewElement, modelWriter ) => {
+ * model: ( viewElement, conversionApi ) => {
+ * const modelWriter = conversionApi.writer;
+ *
* return modelWriter.createElement( 'heading', { level: viewElement.getAttribute( 'data-level' ) } );
* }
* } );
@@ -71,8 +74,9 @@ export default class UpcastHelpers extends ConversionHelpers {
* @param {Object} config Conversion configuration.
* @param {module:engine/view/matcher~MatcherPattern} [config.view] Pattern matching all view elements which should be converted. If not
* set, the converter will fire for every view element.
- * @param {String|module:engine/model/element~Element|Function} config.model Name of the model element, a model element
- * instance or a function that takes a view element and returns a model element. The model element will be inserted in the model.
+ * @param {String|module:engine/model/element~Element|Function} config.model Name of the model element, a model element instance or a
+ * function that takes a view element and {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi upcast conversion API}
+ * and returns a model element. The model element will be inserted in the model.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
*/
@@ -135,7 +139,7 @@ export default class UpcastHelpers extends ConversionHelpers {
* },
* model: {
* key: 'fontSize',
- * value: viewElement => {
+ * value: ( viewElement, conversionApi ) => {
* const fontSize = viewElement.getStyle( 'font-size' );
* const value = fontSize.substr( 0, fontSize.length - 2 );
*
@@ -157,7 +161,8 @@ export default class UpcastHelpers extends ConversionHelpers {
* @param {Object} config Conversion configuration.
* @param {module:engine/view/matcher~MatcherPattern} config.view Pattern matching all view elements which should be converted.
* @param {String|Object} config.model Model attribute key or an object with `key` and `value` properties, describing
- * the model attribute. `value` property may be set as a function that takes a view element and returns the value.
+ * the model attribute. `value` property may be set as a function that takes a view element and
+ * {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi upcast conversion API} and returns the value.
* If `String` is given, the model attribute value will be set to `true`.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.
* @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
@@ -231,7 +236,7 @@ export default class UpcastHelpers extends ConversionHelpers {
* },
* model: {
* key: 'styled'
- * value: viewElement => {
+ * value: ( viewElement, conversionApi ) => {
* const regexp = /styled-([\S]+)/;
* const match = viewElement.getAttribute( 'class' ).match( regexp );
*
@@ -263,7 +268,7 @@ export default class UpcastHelpers extends ConversionHelpers {
* },
* model: {
* key: 'lineHeight',
- * value: viewElement => viewElement.getStyle( 'line-height' )
+ * value: ( viewElement, conversionApi ) => viewElement.getStyle( 'line-height' )
* }
* } );
*
@@ -278,7 +283,8 @@ export default class UpcastHelpers extends ConversionHelpers {
* property specifying a view element name from/on which the attribute should be converted. `value` can be given as a `String`,
* a `RegExp` or a function callback, that takes view attribute value as the only parameter and returns `Boolean`.
* @param {String|Object} config.model Model attribute key or an object with `key` and `value` properties, describing
- * the model attribute. `value` property may be set as a function that takes a view element and returns the value.
+ * the model attribute. `value` property may be set as a function that takes a view element and
+ * {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi upcast conversion API} and returns the value.
* If `String` is given, the model attribute value will be same as view attribute value.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.
* @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
@@ -310,7 +316,7 @@ export default class UpcastHelpers extends ConversionHelpers {
*
* editor.conversion.for( 'upcast' ).elementToMarker( {
* view: 'marker-search',
- * model: viewElement => 'comment:' + viewElement.getAttribute( 'data-comment-id' )
+ * model: ( viewElement, conversionApi ) => 'comment:' + viewElement.getAttribute( 'data-comment-id' )
* } );
*
* editor.conversion.for( 'upcast' ).elementToMarker( {
@@ -400,13 +406,13 @@ export default class UpcastHelpers extends ConversionHelpers {
* // Using a custom function which is the same as the default conversion:
* editor.conversion.for( 'upcast' ).dataToMarker( {
* view: 'comment',
- * model: name => 'comment:' + name,
+ * model: ( name, conversionApi ) => 'comment:' + name,
* } );
*
* // Using the converter priority:
* editor.conversion.for( 'upcast' ).dataToMarker( {
* view: 'comment',
- * model: name => 'comment:' + name,
+ * model: ( name, conversionApi ) => 'comment:' + name,
* converterPriority: 'high'
* } );
*
@@ -416,8 +422,8 @@ export default class UpcastHelpers extends ConversionHelpers {
* @method #dataToMarker
* @param {Object} config Conversion configuration.
* @param {String} config.view The marker group name to convert.
- * @param {Function} [config.model] A function that takes the `name` part from the view element or attribute and returns the marker
- * name.
+ * @param {Function} [config.model] A function that takes the `name` part from the view element or attribute and
+ * {@link module:engine/conversion/upcastdispatcher~UpcastConversionApi upcast conversion API} and returns the marker name.
* @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
* @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
*/
@@ -459,20 +465,33 @@ export function convertToModelFragment() {
* @returns {Function} {@link module:engine/view/text~Text View text} converter.
*/
export function convertText() {
- return ( evt, data, conversionApi ) => {
- if ( conversionApi.schema.checkChild( data.modelCursor, '$text' ) ) {
- if ( conversionApi.consumable.consume( data.viewItem ) ) {
- const text = conversionApi.writer.createText( data.viewItem.data );
+ return ( evt, data, { schema, consumable, writer } ) => {
+ let position = data.modelCursor;
- conversionApi.writer.insert( text, data.modelCursor );
+ // When node is already converted then do nothing.
+ if ( !consumable.test( data.viewItem ) ) {
+ return;
+ }
- data.modelRange = conversionApi.writer.createRange(
- data.modelCursor,
- data.modelCursor.getShiftedBy( text.offsetSize )
- );
- data.modelCursor = data.modelRange.end;
+ if ( !schema.checkChild( position, '$text' ) ) {
+ if ( !isParagraphable( position, '$text', schema ) ) {
+ return;
}
+
+ position = wrapInParagraph( position, writer );
}
+
+ consumable.consume( data.viewItem );
+
+ const text = writer.createText( data.viewItem.data );
+
+ writer.insert( text, position );
+
+ data.modelRange = writer.createRange(
+ position,
+ position.getShiftedBy( text.offsetSize )
+ );
+ data.modelCursor = data.modelRange.end;
};
}
@@ -697,7 +716,7 @@ function upcastAttributeToMarker( config ) {
function addMarkerElements( position, markerViewNames ) {
for ( const markerViewName of markerViewNames ) {
- const markerName = config.model( markerViewName );
+ const markerName = config.model( markerViewName, conversionApi );
const element = conversionApi.writer.createElement( '$marker', { 'data-name': markerName } );
conversionApi.writer.insert( element, position );
@@ -754,7 +773,7 @@ function prepareToElementConverter( config ) {
return;
}
- const modelElement = getModelElement( config.model, data.viewItem, conversionApi.writer );
+ const modelElement = getModelElement( config.model, data.viewItem, conversionApi );
if ( !modelElement ) {
return;
@@ -775,12 +794,12 @@ function prepareToElementConverter( config ) {
//
// @param {String|Function|module:engine/model/element~Element} model Model conversion configuration.
// @param {module:engine/view/node~Node} input The converted view node.
-// @param {module:engine/model/writer~Writer} writer A writer instance to use to create the model element.
-function getModelElement( model, input, writer ) {
+// @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi The upcast conversion API.
+function getModelElement( model, input, conversionApi ) {
if ( model instanceof Function ) {
- return model( input, writer );
+ return model( input, conversionApi );
} else {
- return writer.createElement( model );
+ return conversionApi.writer.createElement( model );
}
}
@@ -858,7 +877,8 @@ function prepareToAttributeConverter( config, shallow ) {
}
const modelKey = config.model.key;
- const modelValue = typeof config.model.value == 'function' ? config.model.value( data.viewItem ) : config.model.value;
+ const modelValue = typeof config.model.value == 'function' ?
+ config.model.value( data.viewItem, conversionApi ) : config.model.value;
// Do not convert if attribute building function returned falsy value.
if ( modelValue === null ) {
@@ -939,10 +959,10 @@ function setAttributeOn( modelRange, modelAttribute, shallow, conversionApi ) {
function normalizeElementToMarkerConfig( config ) {
const oldModel = config.model;
- config.model = ( viewElement, modelWriter ) => {
- const markerName = typeof oldModel == 'string' ? oldModel : oldModel( viewElement );
+ config.model = ( viewElement, conversionApi ) => {
+ const markerName = typeof oldModel == 'string' ? oldModel : oldModel( viewElement, conversionApi );
- return modelWriter.createElement( '$marker', { 'data-name': markerName } );
+ return conversionApi.writer.createElement( '$marker', { 'data-name': markerName } );
};
}
@@ -956,11 +976,11 @@ function normalizeDataToMarkerConfig( config, type ) {
// Upcast and elements.
configForElements.view = config.view + '-' + type;
- configForElements.model = ( viewElement, modelWriter ) => {
+ configForElements.model = ( viewElement, conversionApi ) => {
const viewName = viewElement.getAttribute( 'name' );
- const markerName = config.model( viewName );
+ const markerName = config.model( viewName, conversionApi );
- return modelWriter.createElement( '$marker', { 'data-name': markerName } );
+ return conversionApi.writer.createElement( '$marker', { 'data-name': markerName } );
};
return configForElements;
diff --git a/packages/ckeditor5-engine/src/dev-utils/model.js b/packages/ckeditor5-engine/src/dev-utils/model.js
index 57e497e6978..57cde34a686 100644
--- a/packages/ckeditor5-engine/src/dev-utils/model.js
+++ b/packages/ckeditor5-engine/src/dev-utils/model.js
@@ -229,8 +229,8 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu
downcastDispatcher.on( 'insert:$text', insertText() );
downcastDispatcher.on( 'attribute', ( evt, data, conversionApi ) => {
if ( data.item instanceof ModelSelection || data.item instanceof DocumentSelection || data.item.is( '$textProxy' ) ) {
- const converter = wrap( ( modelAttributeValue, viewWriter ) => {
- return viewWriter.createAttributeElement(
+ const converter = wrap( ( modelAttributeValue, { writer } ) => {
+ return writer.createAttributeElement(
'model-text-with-attributes',
{ [ data.attributeKey ]: stringifyAttributeValue( modelAttributeValue ) }
);
@@ -248,7 +248,7 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu
downcastDispatcher.on( 'selection', convertRangeSelection() );
downcastDispatcher.on( 'selection', convertCollapsedSelection() );
- downcastDispatcher.on( 'addMarker', insertUIElement( ( data, writer ) => {
+ downcastDispatcher.on( 'addMarker', insertUIElement( ( data, { writer } ) => {
const name = data.markerName + ':' + ( data.isOpening ? 'start' : 'end' );
return writer.createUIElement( name );
diff --git a/packages/ckeditor5-engine/src/dev-utils/view.js b/packages/ckeditor5-engine/src/dev-utils/view.js
index 82e8ded214a..2937fbcf70f 100644
--- a/packages/ckeditor5-engine/src/dev-utils/view.js
+++ b/packages/ckeditor5-engine/src/dev-utils/view.js
@@ -51,9 +51,9 @@ const allowedTypes = {
* the default `main` name will be used.
* @param {Boolean} [options.showType=false] When set to `true`, the type of elements will be printed (``
* instead of `
`, `` instead of `` and `` instead of ``).
- * @param {Boolean} [options.showPriority=false] When set to `true`, attribute element's priority will be printed
+ * @param {Boolean} [options.showPriority=false] When set to `true`, the attribute element's priority will be printed
* (``, ``).
- * @param {Boolean} [options.showAttributeElementId=false] When set to `true`, attribute element's id will be printed
+ * @param {Boolean} [options.showAttributeElementId=false] When set to `true`, the attribute element's ID will be printed
* (``).
* @param {Boolean} [options.renderUIElements=false] When set to `true`, the inner content of each
* {@link module:engine/view/uielement~UIElement} will be printed.
diff --git a/packages/ckeditor5-engine/src/model/model.js b/packages/ckeditor5-engine/src/model/model.js
index c8a0e9850ab..bbb93457114 100644
--- a/packages/ckeditor5-engine/src/model/model.js
+++ b/packages/ckeditor5-engine/src/model/model.js
@@ -25,6 +25,7 @@ import deleteContent from './utils/deletecontent';
import modifySelection from './utils/modifyselection';
import getSelectedContent from './utils/getselectedcontent';
import { injectSelectionPostFixer } from './utils/selection-post-fixer';
+import { autoParagraphEmptyRoots } from './utils/autoparagraphing';
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
// @if CK_DEBUG_ENGINE // const { dumpTrees } = require( '../dev-utils/utils' );
@@ -122,6 +123,9 @@ export default class Model {
injectSelectionPostFixer( this );
+ // Post-fixer which takes care of adding empty paragraph elements to the empty roots.
+ this.document.registerPostFixer( autoParagraphEmptyRoots );
+
// @if CK_DEBUG_ENGINE // this.on( 'applyOperation', () => {
// @if CK_DEBUG_ENGINE // dumpTrees( this.document, this.document.version );
// @if CK_DEBUG_ENGINE // }, { priority: 'lowest' } );
diff --git a/packages/ckeditor5-engine/src/model/utils/autoparagraphing.js b/packages/ckeditor5-engine/src/model/utils/autoparagraphing.js
new file mode 100644
index 00000000000..c058b675247
--- /dev/null
+++ b/packages/ckeditor5-engine/src/model/utils/autoparagraphing.js
@@ -0,0 +1,77 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/**
+ * @module engine/model/utils/autoparagraphing
+ */
+
+/**
+ * Fixes all empty roots.
+ *
+ * @protected
+ * @param {module:engine/model/writer~Writer} writer The model writer.
+ * @returns {Boolean} `true` if any change has been applied, `false` otherwise.
+ */
+export function autoParagraphEmptyRoots( writer ) {
+ const { schema, document } = writer.model;
+
+ for ( const rootName of document.getRootNames() ) {
+ const root = document.getRoot( rootName );
+
+ if ( root.isEmpty && !schema.checkChild( root, '$text' ) ) {
+ // If paragraph element is allowed in the root, create paragraph element.
+ if ( schema.checkChild( root, 'paragraph' ) ) {
+ writer.insertElement( 'paragraph', root );
+
+ // Other roots will get fixed in the next post-fixer round. Those will be triggered
+ // in the same batch no matter if this method was triggered by the post-fixing or not
+ // (the above insertElement call will trigger the post-fixers).
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Checks if the given node wrapped with a paragraph would be accepted by the schema in the given position.
+ *
+ * @protected
+ * @param {module:engine/model/position~Position} position The position at which to check.
+ * @param {module:engine/model/node~Node|String} nodeOrType The child node or child type to check.
+ * @param {module:engine/model/schema~Schema} schema A schema instance used for element validation.
+ */
+export function isParagraphable( position, nodeOrType, schema ) {
+ const context = schema.createContext( position );
+
+ // When paragraph is allowed in this context...
+ if ( !schema.checkChild( context, 'paragraph' ) ) {
+ return false;
+ }
+
+ // And a node would be allowed in this paragraph...
+ if ( !schema.checkChild( context.push( 'paragraph' ), nodeOrType ) ) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Inserts a new paragraph at the given position and returns a position inside that paragraph.
+ *
+ * @protected
+ * @param {module:engine/model/position~Position} position The position where a paragraph should be inserted.
+ * @param {module:engine/model/writer~Writer} writer The model writer.
+ * @returns {module:engine/model/position~Position} Position inside the created paragraph.
+ */
+export function wrapInParagraph( position, writer ) {
+ const paragraph = writer.createElement( 'paragraph' );
+
+ writer.insert( paragraph, position );
+
+ return writer.createPositionAt( paragraph, 0 );
+}
diff --git a/packages/ckeditor5-engine/src/view/domconverter.js b/packages/ckeditor5-engine/src/view/domconverter.js
index 4beac20d1ac..f3d8beab359 100644
--- a/packages/ckeditor5-engine/src/view/domconverter.js
+++ b/packages/ckeditor5-engine/src/view/domconverter.js
@@ -32,20 +32,21 @@ const BR_FILLER_REF = BR_FILLER( document );
* `DomConverter` is a set of tools to do transformations between DOM nodes and view nodes. It also handles
* {@link module:engine/view/domconverter~DomConverter#bindElements bindings} between these nodes.
*
- * The instance of `DOMConverter` is available under {@link module:engine/view/view~View#domConverter `editor.editing.view.domConverter`}.
+ * An instance of the DOM converter is available under
+ * {@link module:engine/view/view~View#domConverter `editor.editing.view.domConverter`}.
*
- * `DomConverter` does not check which nodes should be rendered (use {@link module:engine/view/renderer~Renderer}), does not keep a
- * state of a tree nor keeps synchronization between tree view and DOM tree (use {@link module:engine/view/document~Document}).
+ * The DOM converter does not check which nodes should be rendered (use {@link module:engine/view/renderer~Renderer}), does not keep the
+ * state of a tree nor keeps the synchronization between the tree view and the DOM tree (use {@link module:engine/view/document~Document}).
*
- * `DomConverter` keeps DOM elements to View element bindings, so when the converter gets destroyed, the bindings are lost.
+ * The DOM converter keeps DOM elements to view element bindings, so when the converter gets destroyed, the bindings are lost.
* Two converters will keep separate binding maps, so one tree view can be bound with two DOM trees.
*/
export default class DomConverter {
/**
- * Creates DOM converter.
+ * Creates a DOM converter.
*
* @param {module:engine/view/document~Document} document The view document instance.
- * @param {Object} options Object with configuration options.
+ * @param {Object} options An object with configuration options.
* @param {module:engine/view/filler~BlockFillerMode} [options.blockFillerMode='br'] The type of the block filler to use.
*/
constructor( document, options = {} ) {
@@ -56,7 +57,7 @@ export default class DomConverter {
this.document = document;
/**
- * The mode of a block filler used by DOM converter.
+ * The mode of a block filler used by the DOM converter.
*
* @readonly
* @member {'br'|'nbsp'} module:engine/view/domconverter~DomConverter#blockFillerMode
@@ -86,7 +87,7 @@ export default class DomConverter {
/**
* Block {@link module:engine/view/filler filler} creator, which is used to create all block fillers during the
- * view to DOM conversion and to recognize block fillers during the DOM to view conversion.
+ * view-to-DOM conversion and to recognize block fillers during the DOM-to-view conversion.
*
* @readonly
* @private
@@ -95,7 +96,7 @@ export default class DomConverter {
this._blockFiller = this.blockFillerMode == 'br' ? BR_FILLER : NBSP_FILLER;
/**
- * DOM to View mapping.
+ * The DOM-to-view mapping.
*
* @private
* @member {WeakMap} module:engine/view/domconverter~DomConverter#_domToViewMapping
@@ -103,7 +104,7 @@ export default class DomConverter {
this._domToViewMapping = new WeakMap();
/**
- * View to DOM mapping.
+ * The view-to-DOM mapping.
*
* @private
* @member {WeakMap} module:engine/view/domconverter~DomConverter#_viewToDomMapping
@@ -111,7 +112,7 @@ export default class DomConverter {
this._viewToDomMapping = new WeakMap();
/**
- * Holds mapping between fake selection containers and corresponding view selections.
+ * Holds the mapping between fake selection containers and corresponding view selections.
*
* @private
* @member {WeakMap} module:engine/view/domconverter~DomConverter#_fakeSelectionMapping
@@ -894,15 +895,15 @@ export default class DomConverter {
}
/**
- * Checks if given selection's boundaries are at correct places.
+ * Checks if the given selection's boundaries are at correct places.
*
* The following places are considered as incorrect for selection boundaries:
*
- * * before or in the middle of the inline filler sequence,
+ * * before or in the middle of an inline filler sequence,
* * inside a DOM element which represents {@link module:engine/view/uielement~UIElement a view UI element},
* * inside a DOM element which represents {@link module:engine/view/rawelement~RawElement a view raw element}.
*
- * @param {Selection} domSelection DOM Selection object to be checked.
+ * @param {Selection} domSelection The DOM selection object to be checked.
* @returns {Boolean} `true` if the given selection is at a correct place, `false` otherwise.
*/
isDomSelectionCorrect( domSelection ) {
diff --git a/packages/ckeditor5-engine/src/view/downcastwriter.js b/packages/ckeditor5-engine/src/view/downcastwriter.js
index cf0e83eb1ac..f9ebbc58b1f 100644
--- a/packages/ckeditor5-engine/src/view/downcastwriter.js
+++ b/packages/ckeditor5-engine/src/view/downcastwriter.js
@@ -155,7 +155,7 @@ export default class DowncastWriter {
}
/**
- * Creates new {@link module:engine/view/attributeelement~AttributeElement}.
+ * Creates a new {@link module:engine/view/attributeelement~AttributeElement}.
*
* writer.createAttributeElement( 'strong' );
* writer.createAttributeElement( 'a', { href: 'foo.bar' } );
@@ -188,7 +188,7 @@ export default class DowncastWriter {
}
/**
- * Creates new {@link module:engine/view/containerelement~ContainerElement}.
+ * Creates a new {@link module:engine/view/containerelement~ContainerElement}.
*
* writer.createContainerElement( 'p' );
*
@@ -210,7 +210,7 @@ export default class DowncastWriter {
}
/**
- * Creates new {@link module:engine/view/editableelement~EditableElement}.
+ * Creates a new {@link module:engine/view/editableelement~EditableElement}.
*
* writer.createEditableElement( 'div' );
* writer.createEditableElement( 'div', { id: 'foo-1234' } );
@@ -230,7 +230,7 @@ export default class DowncastWriter {
}
/**
- * Creates new {@link module:engine/view/emptyelement~EmptyElement}.
+ * Creates a new {@link module:engine/view/emptyelement~EmptyElement}.
*
* writer.createEmptyElement( 'img' );
* writer.createEmptyElement( 'img', { id: 'foo-1234' } );
@@ -244,12 +244,12 @@ export default class DowncastWriter {
}
/**
- * Creates new {@link module:engine/view/uielement~UIElement}.
+ * Creates a new {@link module:engine/view/uielement~UIElement}.
*
* writer.createUIElement( 'span' );
* writer.createUIElement( 'span', { id: 'foo-1234' } );
*
- * Custom render function can be provided as third parameter:
+ * A custom render function can be provided as the third parameter:
*
* writer.createUIElement( 'span', null, function( domDocument ) {
* const domElement = this.toDomElement( domDocument );
@@ -263,10 +263,10 @@ export default class DowncastWriter {
*
* You should not use UI elements as data containers. Check out {@link #createRawElement} instead.
*
- * @param {String} name Name of the element.
- * @param {Object} [attributes] Elements attributes.
- * @param {Function} [renderFunction] Custom render function.
- * @returns {module:engine/view/uielement~UIElement} Created element.
+ * @param {String} name The name of the element.
+ * @param {Object} [attributes] Element attributes.
+ * @param {Function} [renderFunction] A custom render function.
+ * @returns {module:engine/view/uielement~UIElement} The created element.
*/
createUIElement( name, attributes, renderFunction ) {
const uiElement = new UIElement( this.document, name, attributes );
@@ -288,18 +288,19 @@ export default class DowncastWriter {
* Raw elements work as data containers ("wrappers", "sandboxes") but their children are not managed or
* even recognized by the editor. This encapsulation allows integrations to maintain custom DOM structures
* in the editor content without, for instance, worrying about compatibility with other editor features.
- * Raw elements make a perfect tool for integration with external frameworks and data sources.
+ * Raw elements are a perfect tool for integration with external frameworks and data sources.
*
- * Unlike {@link #createUIElement ui elements}, raw elements act like a "real" editor content (similar to
+ * Unlike {@link #createUIElement UI elements}, raw elements act like "real" editor content (similar to
* {@link module:engine/view/containerelement~ContainerElement} or {@link module:engine/view/emptyelement~EmptyElement}),
* and they are considered by the editor selection.
*
- * You should not use raw elements to render UI in the editor content. Check out {@link #createUIElement `#createUIElement()`} instead.
+ * You should not use raw elements to render the UI in the editor content. Check out {@link #createUIElement `#createUIElement()`}
+ * instead.
*
- * @param {String} name Name of the element.
- * @param {Object} [attributes] Elements attributes.
- * @param {Function} [renderFunction] Custom render function.
- * @returns {module:engine/view/rawelement~RawElement} Created element.
+ * @param {String} name The name of the element.
+ * @param {Object} [attributes] Element attributes.
+ * @param {Function} [renderFunction] A custom render function.
+ * @returns {module:engine/view/rawelement~RawElement} The created element.
*/
createRawElement( name, attributes, renderFunction ) {
const rawElement = new RawElement( this.document, name, attributes );
@@ -310,12 +311,12 @@ export default class DowncastWriter {
}
/**
- * Adds or overwrite element's attribute with a specified key and value.
+ * Adds or overwrites the element's attribute with a specified key and value.
*
* writer.setAttribute( 'href', 'http://ckeditor.com', linkElement );
*
- * @param {String} key Attribute key.
- * @param {String} value Attribute value.
+ * @param {String} key The attribute key.
+ * @param {String} value The attribute value.
* @param {module:engine/view/element~Element} element
*/
setAttribute( key, value, element ) {
diff --git a/packages/ckeditor5-engine/src/view/rawelement.js b/packages/ckeditor5-engine/src/view/rawelement.js
index b770163d3bf..1ac5d76c265 100644
--- a/packages/ckeditor5-engine/src/view/rawelement.js
+++ b/packages/ckeditor5-engine/src/view/rawelement.js
@@ -14,41 +14,41 @@ import Node from './node';
/**
* The raw element class.
*
- * Raw elements work as data containers ("wrappers", "sandboxes") but their children are not managed or
+ * The raw elements work as data containers ("wrappers", "sandboxes") but their children are not managed or
* even recognized by the editor. This encapsulation allows integrations to maintain custom DOM structures
* in the editor content without, for instance, worrying about compatibility with other editor features.
- * Raw elements make a perfect tool for integration with external frameworks and data sources.
+ * Raw elements are a perfect tool for integration with external frameworks and data sources.
*
- * Unlike {@link module:engine/view/uielement~UIElement ui elements}, raw elements act like a real editor
+ * Unlike {@link module:engine/view/uielement~UIElement UI elements}, raw elements act like real editor
* content (similar to {@link module:engine/view/containerelement~ContainerElement} or
* {@link module:engine/view/emptyelement~EmptyElement}), they are considered by the editor selection and
* {@link module:widget/utils~toWidget they can work as widgets}.
*
- * To create a new raw element use the
+ * To create a new raw element, use the
* {@link module:engine/view/downcastwriter~DowncastWriter#createRawElement `downcastWriter#createRawElement()`} method.
*
* @extends module:engine/view/element~Element
*/
export default class RawElement extends Element {
/**
- * Creates new instance of RawElement.
+ * Creates a new instance of a raw element.
*
- * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-rawelement-cannot-add` when `children` parameter
- * is passed, to inform that usage of `RawElement` is incorrect (adding child nodes to `RawElement` is forbidden).
+ * Throws the `view-rawelement-cannot-add` {@link module:utils/ckeditorerror~CKEditorError CKEditorError} when the `children`
+ * parameter is passed to inform that the usage of `RawElement` is incorrect (adding child nodes to `RawElement` is forbidden).
*
* @see module:engine/view/downcastwriter~DowncastWriter#createRawElement
* @protected
* @param {module:engine/view/document~Document} document The document instance to which this element belongs.
- * @param {String} name Node name.
- * @param {Object|Iterable} [attrs] Collection of attributes.
+ * @param {String} name A node name.
+ * @param {Object|Iterable} [attrs] The collection of attributes.
* @param {module:engine/view/node~Node|Iterable.} [children]
- * A list of nodes to be inserted into created element.
+ * A list of nodes to be inserted into the created element.
*/
constructor( document, name, attrs, children ) {
super( document, name, attrs, children );
/**
- * Returns `null` because filler is not needed for RawElements.
+ * Returns `null` because filler is not needed for raw elements.
*
* @method #getFillerOffset
* @returns {null} Always returns null.
@@ -72,15 +72,15 @@ export default class RawElement extends Element {
* Assuming that the object being checked is a raw element, you can also check its
* {@link module:engine/view/rawelement~RawElement#name name}:
*
- * rawElement.is( 'img' ); // -> true if this is a img element
+ * rawElement.is( 'img' ); // -> true if this is an img element
* rawElement.is( 'rawElement', 'img' ); // -> same as above
* text.is( 'img' ); -> false
*
* {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
*
- * @param {String} type Type to check when `name` parameter is present.
+ * @param {String} type The type to check when the `name` parameter is present.
* Otherwise, it acts like the `name` parameter.
- * @param {String} [name] Element name.
+ * @param {String} [name] The element name.
* @returns {Boolean}
*/
is( type, name = null ) {
@@ -99,9 +99,9 @@ export default class RawElement extends Element {
}
/**
- * Overrides {@link module:engine/view/element~Element#_insertChild} method.
- * Throws {@link module:utils/ckeditorerror~CKEditorError CKEditorError} `view-rawelement-cannot-add` to prevent
- * adding any child nodes to a `RawElement`.
+ * Overrides the {@link module:engine/view/element~Element#_insertChild} method.
+ * Throws the `view-rawelement-cannot-add` {@link module:utils/ckeditorerror~CKEditorError CKEditorError} to prevent
+ * adding any child nodes to a raw element.
*
* @protected
*/
@@ -120,11 +120,11 @@ export default class RawElement extends Element {
}
/**
- * Allows rendering the children of a {@link module:engine/view/rawelement~RawElement} on the DOM level.
+ * This allows rendering the children of a {@link module:engine/view/rawelement~RawElement} on the DOM level.
* This method is called by the {@link module:engine/view/domconverter~DomConverter} with the raw DOM element
- * passed as an argument leaving the number and shape of the children up to the integrator.
+ * passed as an argument, leaving the number and shape of the children up to the integrator.
*
- * This method **must be defined** for the `RawElement` to work:
+ * This method **must be defined** for the raw element to work:
*
* const myRawElement = downcastWriter.createRawElement( 'div' );
*
@@ -137,7 +137,7 @@ export default class RawElement extends Element {
*/
}
-// Returns `null` because block filler is not needed for RawElements.
+// Returns `null` because block filler is not needed for raw elements.
//
// @returns {null}
function getFillerOffset() {
diff --git a/packages/ckeditor5-engine/tests/controller/datacontroller.js b/packages/ckeditor5-engine/tests/controller/datacontroller.js
index e21c1bf2967..94647822dab 100644
--- a/packages/ckeditor5-engine/tests/controller/datacontroller.js
+++ b/packages/ckeditor5-engine/tests/controller/datacontroller.js
@@ -155,8 +155,8 @@ describe( 'DataController', () => {
const viewFragment = new ViewDocumentFragment( viewDocument, [ parseView( 'foo' ) ] );
- // Model fragment in root.
- expect( stringify( data.toModel( viewFragment ) ) ).to.equal( '' );
+ // Model fragment in root (note that it is auto-paragraphed because $text is not allowed directly in $root).
+ expect( stringify( data.toModel( viewFragment ) ) ).to.equal( 'foo' );
// Model fragment in inline root.
expect( stringify( data.toModel( viewFragment, [ 'inlineRoot' ] ) ) ).to.equal( 'foo' );
@@ -454,6 +454,82 @@ describe( 'DataController', () => {
data.get( { rootName: 'nonexistent' } );
}, /datacontroller-get-non-existent-root:/ );
} );
+
+ it( 'should allow to provide additional options for retrieving data - insert conversion', () => {
+ schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+
+ data.downcastDispatcher.on( 'insert:paragraph', ( evt, data, conversionApi ) => {
+ conversionApi.consumable.consume( data.item, 'insert' );
+
+ const viewPosition = conversionApi.mapper.toViewPosition( data.range.start );
+ const viewElement = conversionApi.writer.createContainerElement( 'p', {
+ attribute: conversionApi.options.attributeValue
+ } );
+
+ conversionApi.mapper.bindElements( data.item, viewElement );
+ conversionApi.writer.insert( viewPosition, viewElement );
+ }, { priority: 'high' } );
+
+ setData( model, 'foo' );
+
+ expect( data.get( { attributeValue: 'foo' } ) ).to.equal( '