8
0
Просмотр исходного кода

Merge remote-tracking branch 'origin/master' into i/7794

Kamil Piechaczek 5 лет назад
Родитель
Сommit
8934a3ebed
100 измененных файлов с 1620 добавлено и 3101 удалено
  1. 0 8
      docs/_snippets/features/wproofreader.html
  2. 47 1
      docs/_snippets/features/wproofreader.js
  3. 58 39
      docs/features/spelling-and-grammar-checking.md
  4. 2 2
      docs/framework/guides/architecture/editing-engine.md
  5. 1 0
      package.json
  6. 1 1
      packages/ckeditor5-adapter-ckfinder/lang/translations/ko.po
  7. 4 4
      packages/ckeditor5-alignment/lang/translations/ko.po
  8. 1 1
      packages/ckeditor5-alignment/lang/translations/zh.po
  9. 3 2
      packages/ckeditor5-autoformat/docs/features/autoformat.md
  10. 2 2
      packages/ckeditor5-autoformat/tests/blockautoformatediting.js
  11. 1 1
      packages/ckeditor5-autosave/lang/translations/ko.po
  12. 1 1
      packages/ckeditor5-basic-styles/lang/translations/ko.po
  13. 37 0
      packages/ckeditor5-ckfinder/lang/translations/ko.po
  14. 25 0
      packages/ckeditor5-code-block/lang/translations/ko.po
  15. 25 0
      packages/ckeditor5-code-block/lang/translations/zh.po
  16. 1 1
      packages/ckeditor5-core/lang/translations/ko.po
  17. 2 1
      packages/ckeditor5-core/src/editor/utils/dataapimixin.js
  18. 1 1
      packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-unsafe-link-class.html
  19. 2 2
      packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-div-attributes.html
  20. 26 8
      packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.html
  21. 53 54
      packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-extending-output.md
  22. 9 9
      packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-introduction.md
  23. 43 43
      packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-preserving-custom-content.md
  24. 8 8
      packages/ckeditor5-engine/docs/framework/guides/deep-dive/schema.md
  25. 24 7
      packages/ckeditor5-engine/src/controller/datacontroller.js
  26. 7 2
      packages/ckeditor5-engine/src/conversion/downcastdispatcher.js
  27. 55 35
      packages/ckeditor5-engine/src/conversion/downcasthelpers.js
  28. 22 11
      packages/ckeditor5-engine/src/conversion/upcastdispatcher.js
  29. 56 36
      packages/ckeditor5-engine/src/conversion/upcasthelpers.js
  30. 3 3
      packages/ckeditor5-engine/src/dev-utils/model.js
  31. 2 2
      packages/ckeditor5-engine/src/dev-utils/view.js
  32. 4 0
      packages/ckeditor5-engine/src/model/model.js
  33. 77 0
      packages/ckeditor5-engine/src/model/utils/autoparagraphing.js
  34. 15 14
      packages/ckeditor5-engine/src/view/domconverter.js
  35. 21 20
      packages/ckeditor5-engine/src/view/downcastwriter.js
  36. 21 21
      packages/ckeditor5-engine/src/view/rawelement.js
  37. 126 2
      packages/ckeditor5-engine/tests/controller/datacontroller.js
  38. 40 64
      packages/ckeditor5-engine/tests/conversion/downcasthelpers.js
  39. 37 0
      packages/ckeditor5-engine/tests/conversion/upcastdispatcher.js
  40. 51 7
      packages/ckeditor5-engine/tests/conversion/upcasthelpers.js
  41. 8 3
      packages/ckeditor5-engine/tests/model/utils/deletecontent.js
  42. 2 1
      packages/ckeditor5-engine/tests/model/utils/selection-post-fixer.js
  43. 1 1
      packages/ckeditor5-enter/src/shiftenter.js
  44. 4 4
      packages/ckeditor5-font/lang/translations/ko.po
  45. 1 1
      packages/ckeditor5-font/lang/translations/zh.po
  46. 1 1
      packages/ckeditor5-font/src/fontfamily/fontfamilyediting.js
  47. 1 1
      packages/ckeditor5-font/src/fontsize/fontsizeediting.js
  48. 1 1
      packages/ckeditor5-font/src/utils.js
  49. 1 1
      packages/ckeditor5-font/tests/utils.js
  50. 7 7
      packages/ckeditor5-heading/lang/translations/ko.po
  51. 2 2
      packages/ckeditor5-heading/lang/translations/zh.po
  52. 53 0
      packages/ckeditor5-highlight/lang/translations/ko.po
  53. 1 1
      packages/ckeditor5-highlight/lang/translations/zh.po
  54. 21 0
      packages/ckeditor5-horizontal-line/lang/translations/zh.po
  55. 9 9
      packages/ckeditor5-horizontal-line/src/horizontallineediting.js
  56. 7 7
      packages/ckeditor5-image/lang/translations/de.po
  57. 13 13
      packages/ckeditor5-image/lang/translations/ko.po
  58. 5 5
      packages/ckeditor5-image/lang/translations/sr-latn.po
  59. 5 5
      packages/ckeditor5-image/lang/translations/sr.po
  60. 6 6
      packages/ckeditor5-image/lang/translations/zh.po
  61. 3 3
      packages/ckeditor5-image/src/image/imageediting.js
  62. 3 3
      packages/ckeditor5-image/tests/image/converters.js
  63. 3 4
      packages/ckeditor5-image/tests/imagetoolbar.js
  64. 2 2
      packages/ckeditor5-indent/lang/translations/ko.po
  65. 1 1
      packages/ckeditor5-link/lang/translations/ko.po
  66. 1 1
      packages/ckeditor5-link/lang/translations/zh.po
  67. 3 3
      packages/ckeditor5-link/src/linkediting.js
  68. 2 1
      packages/ckeditor5-link/src/utils.js
  69. 4 2
      packages/ckeditor5-link/tests/utils.js
  70. 3 3
      packages/ckeditor5-list/lang/translations/ko.po
  71. 1 1
      packages/ckeditor5-list/lang/translations/zh.po
  72. 2 2
      packages/ckeditor5-list/tests/todolistediting.js
  73. 4 1
      packages/ckeditor5-markdown-gfm/package.json
  74. 17 14
      packages/ckeditor5-markdown-gfm/src/gfmdataprocessor.js
  75. 93 0
      packages/ckeditor5-markdown-gfm/src/html2markdown/html2markdown.js
  76. 0 1286
      packages/ckeditor5-markdown-gfm/src/lib/marked/marked.js
  77. 0 175
      packages/ckeditor5-markdown-gfm/src/lib/marked/renderer.js
  78. 0 68
      packages/ckeditor5-markdown-gfm/src/lib/to-markdown/converters.js
  79. 0 785
      packages/ckeditor5-markdown-gfm/src/lib/to-markdown/to-markdown.js
  80. 32 0
      packages/ckeditor5-markdown-gfm/src/markdown2html/markdown2html.js
  81. 26 3
      packages/ckeditor5-markdown-gfm/tests/_utils/utils.js
  82. 9 9
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/blockquotes.js
  83. 54 18
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/code.js
  84. 41 7
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/escaping.js
  85. 12 2
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/headers.js
  86. 26 26
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/horizontal-rules.js
  87. 26 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/html.js
  88. 3 3
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/images.js
  89. 47 125
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/links.js
  90. 42 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/lists.js
  91. 6 3
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/manual/gfmdataprocessor.js
  92. 3 42
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/paragraphs.js
  93. 2 2
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/strikethrough.js
  94. 12 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/strong-emphasis.js
  95. 2 2
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/tables.js
  96. 50 0
      packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/text.js
  97. 3 3
      packages/ckeditor5-media-embed/docs/features/media-embed.md
  98. 4 4
      packages/ckeditor5-media-embed/lang/translations/ko.po
  99. 6 6
      packages/ckeditor5-media-embed/lang/translations/zh.po
  100. 9 9
      packages/ckeditor5-media-embed/src/mediaembedediting.js

+ 0 - 8
docs/_snippets/features/wproofreader.html

@@ -1,11 +1,3 @@
-<script type="text/javascript" src="https://svc.webspellchecker.net/spellcheck31/wscbundle/wscbundle.js"></script>
-<script>
-	window.WEBSPELLCHECKER_CONFIG = {
-		autoSearch: true,
-		enableGrammar: true,
-		serviceId: '1:Eebp63-lWHbt2-ASpHy4-AYUpy2-fo3mk4-sKrza1-NsuXy4-I1XZC2-0u2F54-aqYWd1-l3Qf14-umd'
-	};
-</script>
 <div id="snippet-wproofreader">
 <div id="snippet-wproofreader">
 	<p>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.</p>
 	<p>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.</p>
 	<p>You can also paste your own text here to have its spelling and grammar checked.</p>
 	<p>You can also paste your own text here to have its spelling and grammar checked.</p>

+ 47 - 1
docs/_snippets/features/wproofreader.js

@@ -3,15 +3,61 @@
  * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
  * 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';
 import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
 
 
 ClassicEditor
 ClassicEditor
 	.create( document.querySelector( '#snippet-wproofreader' ), {
 	.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,
 		cloudServices: CS_CONFIG,
 		toolbar: {
 		toolbar: {
+			items: [
+				'heading',
+				'|',
+				'bold',
+				'italic',
+				'bulletedList',
+				'numberedList',
+				'|',
+				'outdent',
+				'indent',
+				'|',
+				'blockQuote',
+				'link',
+				'mediaEmbed',
+				'insertTable',
+				'|',
+				'undo',
+				'redo'
+			],
 			viewportTopOffset: window.getViewportTopOffsetConfig()
 			viewportTopOffset: window.getViewportTopOffsetConfig()
+		},
+		image: {
+			styles: [
+				'full',
+				'alignLeft',
+				'alignRight'
+			],
+			toolbar: [
+				'imageStyle:alignLeft',
+				'imageStyle:full',
+				'imageStyle:alignRight',
+				'|',
+				'imageTextAlternative'
+			]
+		},
+		table: {
+			contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ]
 		}
 		}
 	} )
 	} )
 	.then( editor => {
 	.then( editor => {

+ 58 - 39
docs/features/spelling-and-grammar-checking.md

@@ -5,8 +5,6 @@ menu-title: Spelling and grammar checking
 
 
 # Proofreading, spelling and grammar checking
 # Proofreading, spelling and grammar checking
 
 
-{@snippet build-classic-source}
-
 <info-box>
 <info-box>
 	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/).
 	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/).
 </info-box>
 </info-box>
@@ -29,63 +27,84 @@ There are also over 150 additional languages and specialized dictionaries such a
 
 
 ## Installation
 ## 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( ... );
+```
+
+<info-box info>
+	Read more about {@link builds/guides/integration/installing-plugins installing plugins}.
+</info-box>
 
 
-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
 ### 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.
 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
-<script>
-	window.WEBSPELLCHECKER_CONFIG = {
-		autoSearch: true,
-		enableGrammar: true,
-		serviceId: 'your-service-ID'
-	};
-</script>
-```
+Add the following configuration to your editor:
 
 
-And then load the proofreader script:
+```js
+import WProofreader from '@webspellchecker/wproofreader-ckeditor5/src/wproofreader';
+// ...
 
 
-```html
-<script src="https://svc.webspellchecker.net/spellcheck31/wscbundle/wscbundle.js"></script>
+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
 ### 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.
 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
-<script>
-	window.WEBSPELLCHECKER_CONFIG = {
-		autoSearch: true,
-		enableGrammar: true,
-		servicePort: '2880',
-		servicePath: '/'
-	};
-</script>
-```
-
-Then specify the path to the service on your page:
-
-```html
-<script src="http(s)://your_host_name/spellcheck/wscbundle/wscbundle.js"></script>
+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
 ## 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
 ## 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).

+ 2 - 2
docs/framework/guides/architecture/editing-engine.md

@@ -226,14 +226,14 @@ editor.data;                    // The data pipeline (DataController).
 
 
 ### Element types and custom data
 ### 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:
 The element types can be defined as follows:
 
 
 * **Container element** &ndash; The elements that build the structure of the content. Used for block elements such as `<p>`, `<h1>`, `<blockQuote>`, `<li>`, etc.
 * **Container element** &ndash; The elements that build the structure of the content. Used for block elements such as `<p>`, `<h1>`, `<blockQuote>`, `<li>`, etc.
 * **Attribute element** &ndash; 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 `<strong>`, `<i>`, `<a>`, `<code>`. Similar attribute elements are flattened by the view writer, so e.g. `<a href="..."><a class="bar">x</a></a>` would automatically be optimized to `<a href="..." class="bar">x</a>`.
 * **Attribute element** &ndash; 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 `<strong>`, `<i>`, `<a>`, `<code>`. Similar attribute elements are flattened by the view writer, so e.g. `<a href="..."><a class="bar">x</a></a>` would automatically be optimized to `<a href="..." class="bar">x</a>`.
 * **Empty element** &ndash; The elements that must not have any child nodes, for example `<img>`.
 * **Empty element** &ndash; The elements that must not have any child nodes, for example `<img>`.
-* **UI elements** &ndash; 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** &ndash; 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** &ndash; 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.
 * **Raw element** &ndash; 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** &ndash; The elements used as "nested editables" of non-editable fragments of the content, for example a caption in the image widget, where the `<figure>` wrapping the image is not editable (it is a widget) and the `<figcaption>` inside it is an editable element.
 * **Editable element** &ndash; The elements used as "nested editables" of non-editable fragments of the content, for example a caption in the image widget, where the `<figure>` wrapping the image is not editable (it is a widget) and the `<figcaption>` inside it is an editable element.
 
 

+ 1 - 0
package.json

@@ -86,6 +86,7 @@
     "@ckeditor/ckeditor5-react": "^2.1.0",
     "@ckeditor/ckeditor5-react": "^2.1.0",
     "@ckeditor/ckeditor5-real-time-collaboration": "^21.0.0",
     "@ckeditor/ckeditor5-real-time-collaboration": "^21.0.0",
     "@ckeditor/ckeditor5-track-changes": "^21.0.0",
     "@ckeditor/ckeditor5-track-changes": "^21.0.0",
+    "@webspellchecker/wproofreader-ckeditor5": "^1.0.5",
     "@wiris/mathtype-ckeditor5": "7.20.0",
     "@wiris/mathtype-ckeditor5": "7.20.0",
     "babel-standalone": "^6.26.0",
     "babel-standalone": "^6.26.0",
     "coveralls": "^3.1.0",
     "coveralls": "^3.1.0",

+ 1 - 1
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."
 msgctxt "A generic error message displayed on upload failure. The file name is concatenated to this text."
 msgid "Cannot upload file:"
 msgid "Cannot upload file:"
-msgstr "파일 업로드 불가"
+msgstr "파일 업로드할 수 없음: "

+ 4 - 4
packages/ckeditor5-alignment/lang/translations/ko.po

@@ -18,19 +18,19 @@ msgstr ""
 
 
 msgctxt "Toolbar button tooltip for aligning the text to the left."
 msgctxt "Toolbar button tooltip for aligning the text to the left."
 msgid "Align left"
 msgid "Align left"
-msgstr "왼쪽 맞춤"
+msgstr "왼쪽 정렬"
 
 
 msgctxt "Toolbar button tooltip for aligning the text to the right."
 msgctxt "Toolbar button tooltip for aligning the text to the right."
 msgid "Align right"
 msgid "Align right"
-msgstr "오른쪽 맞춤"
+msgstr "오른쪽 정렬"
 
 
 msgctxt "Toolbar button tooltip for aligning the text to center."
 msgctxt "Toolbar button tooltip for aligning the text to center."
 msgid "Align center"
 msgid "Align center"
-msgstr "가운데 맞춤"
+msgstr "가운데 정렬"
 
 
 msgctxt "Toolbar button tooltip for making the text justified."
 msgctxt "Toolbar button tooltip for making the text justified."
 msgid "Justify"
 msgid "Justify"
-msgstr "양쪽 맞춤"
+msgstr "양쪽 정렬"
 
 
 msgctxt "Dropdown button tooltip for the text alignment feature."
 msgctxt "Dropdown button tooltip for the text alignment feature."
 msgid "Text alignment"
 msgid "Text alignment"

+ 1 - 1
packages/ckeditor5-alignment/lang/translations/zh.po

@@ -38,4 +38,4 @@ msgstr "文字對齊"
 
 
 msgctxt "Label used by assistive technologies describing the text alignment feature toolbar."
 msgctxt "Label used by assistive technologies describing the text alignment feature toolbar."
 msgid "Text alignment toolbar"
 msgid "Text alignment toolbar"
-msgstr ""
+msgstr "文字對齊"

+ 3 - 2
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:
 In addition to enabling automatic text formatting, you may want to check the following productivity features:
 
 
-* {@link features/text-transformation Automatic text transformation} &ndash; It enables automatic turning snippets such as `(tm)` into `™` and `"foo"` into `“foo”`.
-* {@link features/mentions Mentions} &ndash; It brings support for smart autocompletion.
+* {@link features/text-transformation Automatic text transformation} &ndash; Enables automatic turning snippets such as `(tm)` into `™` and `"foo"` into `“foo”`.
+* {@link features/link#autolink-feature Autolink} &ndash; Turns the links and email addresses typed or pasted into the editor into active URLs.
+* {@link features/mentions Mentions} &ndash; Brings support for smart autocompletion.
 
 
 ## Installation
 ## Installation
 
 

+ 2 - 2
packages/ckeditor5-autoformat/tests/blockautoformatediting.js

@@ -167,7 +167,7 @@ describe( 'blockAutoformatEditing', () => {
 			editor.conversion.for( 'downcast' )
 			editor.conversion.for( 'downcast' )
 				.elementToElement( {
 				.elementToElement( {
 					model: 'softBreak',
 					model: 'softBreak',
-					view: ( modelElement, viewWriter ) => viewWriter.createEmptyElement( 'br' )
+					view: ( modelElement, { writer } ) => writer.createEmptyElement( 'br' )
 				} );
 				} );
 
 
 			const spy = testUtils.sinon.spy();
 			const spy = testUtils.sinon.spy();
@@ -207,7 +207,7 @@ describe( 'blockAutoformatEditing', () => {
 			editor.conversion.for( 'downcast' )
 			editor.conversion.for( 'downcast' )
 				.elementToElement( {
 				.elementToElement( {
 					model: 'softBreak',
 					model: 'softBreak',
-					view: ( modelElement, viewWriter ) => viewWriter.createEmptyElement( 'br' )
+					view: ( modelElement, { writer } ) => writer.createEmptyElement( 'br' )
 				} );
 				} );
 
 
 			const spy = testUtils.sinon.spy();
 			const spy = testUtils.sinon.spy();

+ 1 - 1
packages/ckeditor5-autosave/lang/translations/ko.po

@@ -18,4 +18,4 @@ msgstr ""
 
 
 msgctxt "A message that the data is being saved."
 msgctxt "A message that the data is being saved."
 msgid "Saving changes"
 msgid "Saving changes"
-msgstr "변경사항 저장"
+msgstr "변경된 내용을 저장하고 있습니다"

+ 1 - 1
packages/ckeditor5-basic-styles/lang/translations/ko.po

@@ -30,7 +30,7 @@ msgstr "밑줄"
 
 
 msgctxt "Toolbar button tooltip for the Code feature."
 msgctxt "Toolbar button tooltip for the Code feature."
 msgid "Code"
 msgid "Code"
-msgstr "소스"
+msgstr "코드"
 
 
 msgctxt "Toolbar button tooltip for the Strikethrough feature."
 msgctxt "Toolbar button tooltip for the Strikethrough feature."
 msgid "Strikethrough"
 msgid "Strikethrough"

+ 37 - 0
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 "사진 삽입 실패"

+ 25 - 0
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 "평문"

+ 25 - 0
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 "純文字"

+ 1 - 1
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)."
 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"
 msgid "Remove color"
-msgstr "색상 지우기"
+msgstr "색깔 제거"

+ 2 - 1
packages/ckeditor5-core/src/editor/utils/dataapimixin.js

@@ -71,7 +71,8 @@ export default DataApiMixin;
  * the right format for you.
  * the right format for you.
  *
  *
  * @method #getData
  * @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.rootName='main'] Root name.
  * @param {String} [options.trim='empty'] Whether returned data should be trimmed. This option is set to `'empty'` by default,
  * @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
  * which means that whenever editor content is considered empty, an empty string is returned. To turn off trimming

+ 1 - 1
packages/ckeditor5-engine/docs/_snippets/framework/extending-content-add-unsafe-link-class.html

@@ -8,6 +8,6 @@
 
 
 <div id="snippet-link-unsafe-classes">
 <div id="snippet-link-unsafe-classes">
 	<p>All links in this <a href="https://ckeditor.com">editor</a> that do not use the <a href="http://developer.mozilla.org/en-US/docs/Glossary/https">HTTPS</a> protocol
 	<p>All links in this <a href="https://ckeditor.com">editor</a> that do not use the <a href="http://developer.mozilla.org/en-US/docs/Glossary/https">HTTPS</a> protocol
-	have a custom <code>.unsafe-link</code> CSS <a href="http://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class">class</a> that marks them red.</p>
+	have a custom <code>.unsafe-link</code> CSS <a href="http://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class">class</a> that marks them with a predefined graphic differentiator.</p>
 	<p>Edit the URL of the links using "http://" or "https://" to see them marked as "safe" or "unsafe".</p>
 	<p>Edit the URL of the links using "http://" or "https://" to see them marked as "safe" or "unsafe".</p>
 </div>
 </div>

+ 2 - 2
packages/ckeditor5-engine/docs/_snippets/framework/extending-content-allow-div-attributes.html

@@ -1,12 +1,12 @@
 <div id="snippet-div-attributes">
 <div id="snippet-div-attributes">
 	<div id="special-section-a" style="background: #eafbd7;padding:.8em .8em 0;margin-bottom:.8em;border:1px solid #8bc34a;">
 	<div id="special-section-a" style="background: #eafbd7;padding:.8em .8em 0;margin-bottom:.8em;border:1px solid #8bc34a;">
-		<p><strong>Special section A</strong>: It has set "style" and "id" attributes.</p>
+		<p><strong>Special section A</strong>: It has both the "style" and "id" attributes set.</p>
 	</div>
 	</div>
 
 
 	<p>Regular content of the editor.</p>
 	<p>Regular content of the editor.</p>
 
 
 	<div id="special-section-b" style="background: #ffeed0;padding:.8em .8em 0;margin-bottom:.8em;border:1px solid #ff8f00;" spellcheck="false">
 	<div id="special-section-b" style="background: #ffeed0;padding:.8em .8em 0;margin-bottom:.8em;border:1px solid #ff8f00;" spellcheck="false">
-		<p><strong>Special section B</strong>: It has set "style", "id" and spellcheck="false" attributes.</p>
+		<p><strong>Special section B</strong>: It has the "style", "id" and spellcheck="false" attributes set.</p>
 		<p>This section disables the native browser spellchecker.</p>
 		<p>This section disables the native browser spellchecker.</p>
 	</div>
 	</div>
 </div>
 </div>

+ 26 - 8
packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.html

@@ -1,27 +1,45 @@
 <style>
 <style>
 	.info-box {
 	.info-box {
+		background: hsl(0, 0%, 45%);
 		border: 1px solid hsl(0, 0%, 80%);
 		border: 1px solid hsl(0, 0%, 80%);
 		padding: 1em;
 		padding: 1em;
-		background: hsl(0, 0%, 45%);
+		border-radius: 8px;
+	}
+
+	.info-box-title {
+		margin-bottom: 0.5em;
+		font-size: 1.4em;
+		font-weight: bold;
+		color: inherit;
 	}
 	}
 
 
 	.info-box-warning {
 	.info-box-warning {
-		background: hsl(64, 74%, 85%);
+		background: hsl(48, 100%, 87%);
+		border: 1px solid hsl(47, 100%, 62%);
+	}
+
+	.info-box-warning .info-box-title  {
+		color: hsl(47, 100%, 32%)
 	}
 	}
 
 
 	.info-box-info {
 	.info-box-info {
-		background: hsl(205, 100%, 90%);
+		background: hsl(205, 100%, 95%);
+		border: 1px solid hsl(205, 91%, 82%);
 	}
 	}
 
 
-	.info-box-title {
-		margin-bottom: 1em;
-		font-weight: bold;
-		color: inherit;
+	.info-box-info .info-box-title  {
+		color: hsl(204, 79%, 41%)
 	}
 	}
 
 
 	.info-box-content {
 	.info-box-content {
-		padding: 0 1em;
+		padding: 8px 10px;
 		background: hsl(0, 0%, 100%);
 		background: hsl(0, 0%, 100%);
+		border-radius: 6px;
+	}
+
+	.info-box-content p:only-child,
+	.info-box-content p:last-child {
+		margin-bottom: 0;
 	}
 	}
 </style>
 </style>
 
 

+ 53 - 54
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
 ### 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.
 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
 ### 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.
 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
 ```js
 // Adds a conversion dispatcher for the editing downcast pipeline only.
 // Adds a conversion dispatcher for the editing downcast pipeline only.
@@ -47,32 +47,32 @@ editor.conversion.for( 'downcast' ).add( dispatcher => {
 
 
 ### CKEditor 5 inspector
 ### 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
 ## Adding a CSS class to inline elements
 
 
 In this example all links (`<a href="...">...</a>`) 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).
 In this example all links (`<a href="...">...</a>`) 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).
 
 
-<info-box>
-	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'
 					}
 					}
 				}
 				}
 			}
 			}
-		} )
-	```
-</info-box>
+		}
+	} )
+```
+
 
 
 {@snippet framework/extending-content-add-link-class}
 {@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
 ## Adding an HTML attribute to certain inline elements
 
 
-In this example all links (`<a href="...">...</a>`) 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 (`<a href="...">...</a>`) 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).
 
 
-<info-box>
-	Note that similar behavior can be obtained with {@link module:link/link~LinkConfig#addTargetToExternalLinks link decorators}:
-
-	```js
-	ClassicEditor
-		.create( ..., {
-			// ...
-			link: {
-				addTargetToExternalLinks: true
-			}
-		} )
-	```
-</info-box>
+
+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}
 {@snippet framework/extending-content-add-external-link-target}
 
 
@@ -216,27 +215,27 @@ a[target="_blank"]::after {
 
 
 In this example all links (`<a href="...">...</a>`) 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).
 In this example all links (`<a href="...">...</a>`) 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).
 
 
-<info-box>
-	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'
 					}
 					}
 				}
 				}
 			}
 			}
-		} )
-	```
-</info-box>
+		}
+	} )
+```
+
 
 
 {@snippet framework/extending-content-add-unsafe-link-class}
 {@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
 ## Adding a CSS class to block elements
 
 
-In this example all second–level headings (`<h2>...</h2>`) 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 (`<h2>...</h2>`) 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}
 {@snippet framework/extending-content-add-heading-class}
 
 

+ 9 - 9
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.
 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
 ## 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 `<strong>`, `<a>` or `<span>`. Unlike `<p>`, `<blockquote>` or `<div>`, inline elements do not structure the data. Instead, they mark some text in a specific (visual and semantical) way. These elements are a characteristic of a text. For instance, you could say that some part of the text is bold, or is linked, etc.. This concept has its reflection in the model of the rich-text editor where `<a>` or `<strong>` are not represented as elements. Instead, they are attributes of the text.
+The inline content means elements like `<strong>`, `<a>` or `<span>`. Unlike `<p>`, `<blockquote>` or `<div>`, inline elements do not structure the data. Instead, they format some text in a specific (visual and semantical) way. These elements are a characteristic of a text. For instance, you could say that some part of the text is bold, or is linked, etc. This concept has its reflection in the model of the rich-text editor where `<a>` or `<strong>` are not represented as elements. Instead, they are the attributes of the text.
 
 
 For example &mdash; in the model, you might have a `<paragraph>` element with the "Foo bar" text, where "bar" has the `bold` attribute set to `true`. A pseudo–code of this *model* data structure could look as follows:
 For example &mdash; in the model, you might have a `<paragraph>` element with the "Foo bar" text, where "bar" has the `bold` attribute set to `true`. A pseudo–code of this *model* data structure could look as follows:
 
 
@@ -30,7 +30,7 @@ For example &mdash; in the model, you might have a `<paragraph>` element with th
 ```
 ```
 
 
 <info-box>
 <info-box>
-	Throughout the rest of this guide the following, shorter convention will be used to represent model text attributes:
+	Throughout the rest of this guide the following, shorter convention will be used to represent model text attributes for the sake of clarity:
 
 
 	```html
 	```html
 	<paragraph>Foo <$text bold="true">bar</$text></paragraph>
 	<paragraph>Foo <$text bold="true">bar</$text></paragraph>
@@ -39,11 +39,11 @@ For example &mdash; in the model, you might have a `<paragraph>` element with th
 
 
 Note that there is no `<strong>` or any other additional element there, it is just some text with an attribute.
 Note that there is no `<strong>` or any other additional element there, it is just some text with an attribute.
 
 
-So, when does this text become wrapped with a `<strong>` element? This happens during the conversion to the view. It is also important to know which type of a view element needs to be used. In case of elements that represent inline formatting, this should be an {@link module:engine/view/attributeelement~AttributeElement}.
+So, when does this text become wrapped with a `<strong>` element? This happens during the conversion to the view. It is also important to know what type of a view element needs to be used. In the case of the elements that represent inline formatting, this should be an {@link module:engine/view/attributeelement~AttributeElement}.
 
 
 ## Conversion of multiple text attributes
 ## Conversion of multiple text attributes
 
 
-A model text node may have multiple attributes (e.g. be bolded and linked) and all of them are converted to their respective view elements by independent converters.
+A model text node may have multiple attributes (e.g. be bolded and linked) and all of them are converted into their respective view elements by independent converters.
 
 
 Keep in mind that in the model, attributes do not have any specific order. This is contrary to the editor view or HTML output, where inline elements are nested in one another. Fortunately, the nesting happens automatically during the conversion from the model to the view. This makes working in the model simpler, as features do not need to take care of breaking or rearranging elements in the model.
 Keep in mind that in the model, attributes do not have any specific order. This is contrary to the editor view or HTML output, where inline elements are nested in one another. Fortunately, the nesting happens automatically during the conversion from the model to the view. This makes working in the model simpler, as features do not need to take care of breaking or rearranging elements in the model.
 
 
@@ -73,7 +73,7 @@ Note that the `<a>` element is converted in such way that it always becomes the
 </p>
 </p>
 ```
 ```
 
 
-There are two links with the same `href` attribute next to each other in the generated view (editor output), which is semantically wrong. To make sure that it never happens, the view element that represents a link must have a *priority* defined. Most elements, like for instance `<strong>`, do not care about it and stick to the default priority (`10`). The {@link features/link link feature} ensures that all view `<a>` elements have the priority set to `5` so they are kept outside other elements.
+There are two links with the same `href` attribute next to each other in the generated view (editor output), which is semantically wrong. To make sure that it never happens, the view element that represents a link must have a *priority* defined. Most elements, like for instance `<strong>`, do not care about it and stick to the default priority (`10`). The {@link features/link link feature} ensures that all `<a>` view elements have the priority set to `5` therefore they are kept outside other elements.
 
 
 ## Merging attribute elements during conversion
 ## Merging attribute elements during conversion
 
 
@@ -112,9 +112,9 @@ But this is not the most optimal output you can get from the rich-text editor. W
 </p>
 </p>
 ```
 ```
 
 
-Obviously a single `<span>` makes more sense. And thanks to the merging mechanism built into the conversion process, this would be the actual result of the conversion.
+Obviously a single `<span>` makes more sense. And thanks to the merging mechanism built into the conversion process, this would be the actual output of the conversion.
 
 
-Why is it so? In the above scenario, two model attributes are converted to `<span>` elements. When the first attribute (say, `fontFamily`) is converted, there is no `<span>` in the view yet. So the first `<span>` is added with the `style` attribute. But then, when `fontSize` is converted, the `<span>` is already in the view. The {@link module:engine/view/downcastwriter~DowncastWriter downcast writer} recognizes it and checks whether the elements can be merged, following these 3 rules:
+Why is it so? In the above scenario, two model attributes are converted to `<span>` elements. When the first attribute (say, `fontFamily`) is converted, there is no `<span>` in the view yet. So the first `<span>` is added with the `style` attribute. But then, when `fontSize` is converted, the `<span>` is already in the view. The {@link module:engine/view/downcastwriter~DowncastWriter downcast writer} recognizes it and checks whether these elements can be merged, following these 3 rules:
 
 
 1. Both elements must have the same {@link module:engine/view/element~Element#name name}.
 1. Both elements must have the same {@link module:engine/view/element~Element#name name}.
 2. Both elements must have the same {@link module:engine/view/attributeelement~AttributeElement#priority priority}.
 2. Both elements must have the same {@link module:engine/view/attributeelement~AttributeElement#priority priority}.

+ 43 - 43
packages/ckeditor5-engine/docs/framework/guides/deep-dive/conversion-preserving-custom-content.md

@@ -16,42 +16,41 @@ Eventually, this knowledge will allow you to create your custom features on top
 
 
 ### Code architecture
 ### 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.
 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}.
 
 
 ### CKEditor 5 inspector
 ### 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.
 
 
 ## Loading content with a custom attribute
 ## Loading content with a custom attribute
 
 
-In this example links (`<a href="...">...</a>`) loaded into the editor content will preserve their `target` attribute, which is not supported by the {@link features/link Link} feature. The DOM `target` attribute will be stored in the editor model as a `linkTarget` attribute.
+In this example the links (`<a href="...">...</a>`) loaded into the editor content will preserve their `target` attribute, which is by default *not* supported by the {@link features/link Link} feature. The DOM `target` attribute will be stored in the editor model as a `linkTarget` attribute.
 
 
-Unlike the {@link framework/guides/deep-dive/conversion-extending-output#adding-an-html-attribute-to-certain-inline-elements downcast–only solution}, this approach does not change the content loaded into the editor. Links without the `target` attribute will not get one and links with the attribute will preserve its value.
+Unlike the {@link framework/guides/deep-dive/conversion-extending-output#adding-an-html-attribute-to-certain-inline-elements downcast–only solution}, this approach does not change the content loaded into the editor. Any links without the `target` attribute will not get one while all the links with the attribute will preserve its value.
 
 
-<info-box>
-	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'
 					}
 					}
 				}
 				}
 			}
 			}
-		} )
-	```
-</info-box>
+		}
+	} )
+```
 
 
 {@snippet framework/extending-content-allow-link-target}
 {@snippet framework/extending-content-allow-link-target}
 
 
@@ -119,7 +118,7 @@ a[target]::after {
 
 
 ## Loading content with all attributes
 ## Loading content with all attributes
 
 
-In this example `<div>` elements (`<div>...</div>`) 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 `<div>` elements (`<div>...</div>`) 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}
 {@snippet framework/extending-content-allow-div-attributes}
 
 
@@ -128,10 +127,10 @@ All attributes are allowed on `<div>` elements thanks to custom "upcast" and "do
 Allowing every possible attribute on a `<div>` element in the model is done by adding an {@link module:engine/model/schema~Schema#addAttributeCheck addAttributeCheck()} callback.
 Allowing every possible attribute on a `<div>` element in the model is done by adding an {@link module:engine/model/schema~Schema#addAttributeCheck addAttributeCheck()} callback.
 
 
 <info-box>
 <info-box>
-	Allowing every attribute on `<div>` elements might introduce security issues &mdash; including XSS attacks. The production code should use only application-related attributes and/or properly encode data.
+	Allowing every attribute on `<div>` elements might introduce security issues &mdash; including XSS attacks. The production code should use only application-related attributes and/or properly encode the data.
 </info-box>
 </info-box>
 
 
-Adding "upcast" and "downcast" converters for the `<div>` 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 `<div>` is already converted. To overcome this, a lower-level API is used.
+Adding "upcast" and "downcast" converters for the `<div>` 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 `<div>` 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.
 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 <div> with all its attributes to the model.
+	// The view-to-model converter converting a view <div> with all its attributes to the model.
 	editor.conversion.for( 'upcast' ).elementToElement( {
 	editor.conversion.for( 'upcast' ).elementToElement( {
 		view: 'div',
 		view: 'div',
 		model: ( viewElement, modelWriter ) => {
 		model: ( viewElement, modelWriter ) => {
@@ -158,13 +157,13 @@ function ConvertDivAttributes( editor ) {
 		}
 		}
 	} );
 	} );
 
 
-	// Model-to-view converter for the <div> element (attributes are converted separately).
+	// The model-to-view converter for the <div> element (attributes are converted separately).
 	editor.conversion.for( 'downcast' ).elementToElement( {
 	editor.conversion.for( 'downcast' ).elementToElement( {
 		model: 'div',
 		model: 'div',
 		view: 'div'
 		view: 'div'
 	} );
 	} );
 
 
-	// Model-to-view converter for <div> attributes.
+	// The model-to-view converter for <div> attributes.
 	// Note that a lower-level, event-based API is used here.
 	// Note that a lower-level, event-based API is used here.
 	editor.conversion.for( 'downcast' ).add( dispatcher => {
 	editor.conversion.for( 'downcast' ).add( dispatcher => {
 		dispatcher.on( 'attribute', ( evt, data, conversionApi ) => {
 		dispatcher.on( 'attribute', ( evt, data, conversionApi ) => {
@@ -229,8 +228,8 @@ function HandleFontSizeValue( editor ) {
 			value: viewElement => {
 			value: viewElement => {
 				const value = parseFloat( viewElement.getStyle( 'font-size' ) ).toFixed( 0 );
 				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
 				// 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.
 				// 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'
 		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.
 	// model attribute values.
 	editor.conversion.for( 'downcast' ).attributeToElement( {
 	editor.conversion.for( 'downcast' ).attributeToElement( {
 		model: {
 		model: {
@@ -278,7 +277,7 @@ ClassicEditor
 
 
 ## Adding extra attributes to elements contained in a figure
 ## Adding extra attributes to elements contained in a figure
 
 
-The {@link features/image Image} and {@link features/table Table} features wrap view elements (`<img>` for Image nad `<table>` for Table) in `<figure>`. During the downcast conversion, the model element is mapped to `<figure>` 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 (`<img>` for image and `<table>` for table, respectively) in a `<figure>` element. During the downcast conversion, the model element is mapped to `<figure>` 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.
 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
 ```js
 /**
 /**
- * Plugin that converts custom attributes for elements that are wrapped in <figure> in the view.
+ * A plugin that converts custom attributes for elements that are wrapped in <figure> in the view.
  */
  */
 class CustomFigureAttributes {
 class CustomFigureAttributes {
 	/**
 	/**
-	 * Plugin's constructor - receives editor instance on creation.
+	 * Plugin's constructor - receives an editor instance on creation.
 	 */
 	 */
 	constructor( editor ) {
 	constructor( editor ) {
 		// Save reference to the 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() {
 	afterInit() {
 		const editor = this.editor;
 		const editor = this.editor;
@@ -320,23 +319,24 @@ class CustomFigureAttributes {
 }
 }
 
 
 /**
 /**
- * Sets up a conversion that preservers classes on <img> and <table> elements.
+ * Sets up a conversion that preserves classes on <img> and <table> elements.
  */
  */
 function setupCustomClassConversion( viewElementName, modelElementName, editor ) {
 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' ] } );
 	editor.model.schema.extend( modelElementName, { allowAttributes: [ 'customClass' ] } );
 
 
-	// Define upcast converters for the <img> and <table> elements with a "low" priority so they are run after the default converters.
+	// Defines upcast converters for the <img> and <table> elements with a "low" priority so they are run after the default converters.
 	editor.conversion.for( 'upcast' ).add( upcastCustomClasses( viewElementName ), { priority: 'low' } );
 	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 <figure> element or `downcastCustomClassesToChild` if you'd like to keep your classes on a <figure> child element, i.e. <img>.
+	// 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 <figure> element or `downcastCustomClassesToChild`
+	// if you would like to keep your classes on a <figure> child element, i.e. <img>.
 	editor.conversion.for( 'downcast' ).add( downcastCustomClassesToFigure( modelElementName ), { priority: 'low' } );
 	editor.conversion.for( 'downcast' ).add( downcastCustomClassesToFigure( modelElementName ), { priority: 'low' } );
 	// editor.conversion.for( 'downcast' ).add( downcastCustomClassesToChild( viewElementName, 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 <figure>.
+ * Sets up a conversion for a custom attribute on the view elements contained inside a <figure>.
  *
  *
  * This method:
  * This method:
  * - Adds proper schema rules.
  * - Adds proper schema rules.
@@ -344,7 +344,7 @@ function setupCustomClassConversion( viewElementName, modelElementName, editor )
  * - Adds a downcast converter.
  * - Adds a downcast converter.
  */
  */
 function setupCustomAttributeConversion( viewElementName, modelElementName, viewAttribute, editor ) {
 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 }`;
 	const modelAttribute = `custom${ viewAttribute }`;
 
 
 	editor.model.schema.extend( modelElementName, { allowAttributes: [ modelAttribute ] } );
 	editor.model.schema.extend( modelElementName, { allowAttributes: [ modelAttribute ] } );

+ 8 - 8
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.
 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
 ## Defining allowed structures
 
 
@@ -47,7 +47,7 @@ While this would be incorrect:
 
 
 ## Defining additional semantics
 ## 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:
 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.
 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
 ```js
 schema.register( 'myImage', {
 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 `<strong>` or `<code>` are represented by text attributes. Therefore, inline model elements are not supposed to be used for these scenarios.
 In the editor, all HTML formatting elements such as `<strong>` or `<code>` 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 `<softBreak>` 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 `<softBreak>` 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`.
 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 `<paragraph>` element will be allowed in elements in which `<$block>` is allowed (e.g. in `<$root>`).
 * The `<paragraph>` element will be allowed in elements in which `<$block>` is allowed (e.g. in `<$root>`).
 * The `<paragraph>` element will allow all nodes that are allowed in `<$block>` (e.g. `$text`).
 * The `<paragraph>` 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 `<blockQuote>` is a
 
 
 ## Defining advanced rules in `checkChild()` callbacks
 ## 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.
 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.
 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?
 ## Who checks the schema?
 
 

+ 24 - 7
packages/ckeditor5-engine/src/controller/datacontroller.js

@@ -24,6 +24,7 @@ import ViewDocument from '../view/document';
 import ViewDowncastWriter from '../view/downcastwriter';
 import ViewDowncastWriter from '../view/downcastwriter';
 
 
 import ModelRange from '../model/range';
 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
  * 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.on( 'init', () => {
 			this.fire( 'ready' );
 			this.fire( 'ready' );
 		}, { priority: 'lowest' } );
 		}, { 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
 	 * Returns the model's data converted by downcast dispatchers attached to {@link #downcastDispatcher} and
 	 * formatted by the {@link #processor data processor}.
 	 * 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.rootName='main'] Root name.
 	 * @param {String} [options.trim='empty'] Whether returned data should be trimmed. This option is set to `empty` by default,
 	 * @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
 	 * 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 `<p>&nbsp;</p>` for an empty editor).
 	 * use `'none'`. In such cases exact content will be returned (for example `<p>&nbsp;</p>` for an empty editor).
 	 * @returns {String} Output data.
 	 * @returns {String} Output data.
 	 */
 	 */
-	get( options ) {
-		const { rootName = 'main', trim = 'empty' } = options || {};
+	get( options = {} ) {
+		const { rootName = 'main', trim = 'empty' } = options;
 
 
 		if ( !this._checkIfRootsExists( [ rootName ] ) ) {
 		if ( !this._checkIfRootsExists( [ rootName ] ) ) {
 			/**
 			/**
@@ -177,7 +185,7 @@ export default class DataController {
 			return '';
 			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
 	 * @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} modelElementOrFragment
 	 * Element whose content will be stringified.
 	 * Element whose content will be stringified.
+	 * @param {Object} [options] Additional configuration passed to the conversion process.
 	 * @returns {String} Output data.
 	 * @returns {String} Output data.
 	 */
 	 */
-	stringify( modelElementOrFragment ) {
+	stringify( modelElementOrFragment, options ) {
 		// Model -> view.
 		// Model -> view.
-		const viewDocumentFragment = this.toView( modelElementOrFragment );
+		const viewDocumentFragment = this.toView( modelElementOrFragment, options );
 
 
 		// View -> data.
 		// View -> data.
 		return this.processor.toData( viewDocumentFragment );
 		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
 	 * @param {module:engine/model/element~Element|module:engine/model/documentfragment~DocumentFragment} modelElementOrFragment
 	 * Element or document fragment whose content will be converted.
 	 * 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.
 	 * @returns {module:engine/view/documentfragment~DocumentFragment} Output view DocumentFragment.
 	 */
 	 */
-	toView( modelElementOrFragment ) {
+	toView( modelElementOrFragment, options ) {
 		const viewDocument = this.viewDocument;
 		const viewDocument = this.viewDocument;
 		const viewWriter = this._viewWriter;
 		const viewWriter = this._viewWriter;
 
 
@@ -220,6 +231,9 @@ export default class DataController {
 
 
 		this.mapper.bindElements( modelElementOrFragment, viewDocumentFragment );
 		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.
 		// We have no view controller and rendering to DOM in DataController so view.change() block is not used here.
 		this.downcastDispatcher.convertInsert( modelRange, viewWriter );
 		this.downcastDispatcher.convertInsert( modelRange, viewWriter );
 
 
@@ -233,6 +247,9 @@ export default class DataController {
 			}
 			}
 		}
 		}
 
 
+		// Clean `conversionApi`.
+		delete this.downcastDispatcher.conversionApi.options;
+
 		return viewDocumentFragment;
 		return viewDocumentFragment;
 	}
 	}
 
 

+ 7 - 2
packages/ckeditor5-engine/src/conversion/downcastdispatcher.js

@@ -11,7 +11,6 @@ import Consumable from './modelconsumable';
 import Range from '../model/range';
 import Range from '../model/range';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 import mix from '@ckeditor/ckeditor5-utils/src/mix';
 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
  * 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}
 		 * @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
  * @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
+ */

+ 55 - 35
packages/ckeditor5-engine/src/conversion/downcasthelpers.js

@@ -52,8 +52,10 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *
 	 *
 	 *		editor.conversion.for( 'downcast' ).elementToElement( {
 	 *		editor.conversion.for( 'downcast' ).elementToElement( {
 	 *			model: 'heading',
 	 *			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 {Object} config Conversion configuration.
 	 * @param {String} config.model The name of the model element to convert.
 	 * @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
 	 * @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.
 	 * as parameters and returns a view container element.
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 */
 	 */
@@ -120,8 +122,10 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *
 	 *
 	 *		editor.conversion.for( 'downcast' ).attributeToElement( {
 	 *		editor.conversion.for( 'downcast' ).attributeToElement( {
 	 *			model: 'bold',
 	 *			model: 'bold',
-	 *			view: ( modelAttributeValue, viewWriter ) => {
-	 *				return viewWriter.createAttributeElement( 'span', {
+	 *			view: ( modelAttributeValue, conversionApi ) => {
+	 *				const { writer } = conversionApi;
+	 *
+	 *				return writer.createAttributeElement( 'span', {
 	 *					style: 'font-weight:' + modelAttributeValue
 	 *					style: 'font-weight:' + modelAttributeValue
 	 *				} );
 	 *				} );
 	 *			}
 	 *			}
@@ -132,8 +136,10 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *				key: 'color',
 	 *				key: 'color',
 	 *				name: '$text'
 	 *				name: '$text'
 	 *			},
 	 *			},
-	 *			view: ( modelAttributeValue, viewWriter ) => {
-	 *				return viewWriter.createAttributeElement( 'span', {
+	 *			view: ( modelAttributeValue, conversionApi ) => {
+	 *				const { writer } = conversionApi;
+	 *
+	 *				return writer.createAttributeElement( 'span', {
 	 *					style: 'color:' + modelAttributeValue
 	 *					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
 	 * @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.
 	 * 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
 	 * @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.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 */
 	 */
@@ -201,7 +208,10 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *
 	 *
 	 *		editor.conversion.for( 'downcast' ).attributeToAttribute( {
 	 *		editor.conversion.for( 'downcast' ).attributeToAttribute( {
 	 *			model: 'styled',
 	 *			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:
 	 * **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
 	 * @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.
 	 * 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
 	 * @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`.
 	 * 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
 	 * 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.
 	 * `{ key, value }` objects or a functions.
@@ -269,8 +280,10 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *
 	 *
 	 *		editor.conversion.for( 'editingDowncast' ).markerToElement( {
 	 *		editor.conversion.for( 'editingDowncast' ).markerToElement( {
 	 *			model: 'search',
 	 *			model: 'search',
-	 *			view: ( markerData, viewWriter ) => {
-	 *				return viewWriter.createUIElement( 'span', {
+	 *			view: ( markerData, conversionApi ) => {
+	 *				const { writer } = conversionApi;
+	 *
+	 *				return writer.createUIElement( 'span', {
 	 *					'data-marker': 'search',
 	 *					'data-marker': 'search',
 	 *					'data-start': markerData.isOpening
 	 *					'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
 	 * 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/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~DowncastConversionApi `conversionApi`} are passed from
 	 * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker}. Additionally,
 	 * {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker}. Additionally,
@@ -291,8 +305,9 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 * @method #markerToElement
 	 * @method #markerToElement
 	 * @param {Object} config Conversion configuration.
 	 * @param {Object} config Conversion configuration.
 	 * @param {String} config.model The name of the model marker (or model marker group) to convert.
 	 * @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.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 */
 	 */
@@ -329,7 +344,7 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 *
 	 *
 	 *		editor.conversion.for( 'downcast' ).markerToHighlight( {
 	 *		editor.conversion.for( 'downcast' ).markerToHighlight( {
 	 *			model: 'comment',
 	 *			model: 'comment',
-	 *			view: data => {
+	 *			view: ( data, converstionApi ) => {
 	 *				// Assuming that the marker name is in a form of comment:commentType.
 	 *				// Assuming that the marker name is in a form of comment:commentType.
 	 *				const commentType = data.markerName.split( ':' )[ 1 ];
 	 *				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
 	 * 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}.
 	 * {@link module:engine/conversion/downcasthelpers~HighlightDescriptor highlight descriptor}.
 	 * The `data` object properties are passed from {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#event:addMarker}.
 	 * 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 {Object} config Conversion configuration.
 	 * @param {String} config.model The name of the model marker (or model marker group) to convert.
 	 * @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
 	 * @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.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 */
 	 */
@@ -464,8 +482,9 @@ export default class DowncastHelpers extends ConversionHelpers {
 	 * @method #markerToData
 	 * @method #markerToData
 	 * @param {Object} config Conversion configuration.
 	 * @param {Object} config Conversion configuration.
 	 * @param {String} config.model The name of the model marker (or model marker group) to convert.
 	 * @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.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 * @returns {module:engine/conversion/downcasthelpers~DowncastHelpers}
 	 */
 	 */
@@ -703,10 +722,10 @@ export function wrap( elementCreator ) {
 	return ( evt, data, conversionApi ) => {
 	return ( evt, data, conversionApi ) => {
 		// Recreate current wrapping node. It will be used to unwrap view range if the attribute value has changed
 		// Recreate current wrapping node. It will be used to unwrap view range if the attribute value has changed
 		// or the attribute was removed.
 		// or the attribute was removed.
-		const oldViewElement = elementCreator( data.attributeOldValue, conversionApi.writer );
+		const oldViewElement = elementCreator( data.attributeOldValue, conversionApi );
 
 
 		// Create node to wrap with.
 		// Create node to wrap with.
-		const newViewElement = elementCreator( data.attributeNewValue, conversionApi.writer );
+		const newViewElement = elementCreator( data.attributeNewValue, conversionApi );
 
 
 		if ( !oldViewElement && !newViewElement ) {
 		if ( !oldViewElement && !newViewElement ) {
 			return;
 			return;
@@ -766,7 +785,7 @@ export function wrap( elementCreator ) {
  */
  */
 export function insertElement( elementCreator ) {
 export function insertElement( elementCreator ) {
 	return ( evt, data, conversionApi ) => {
 	return ( evt, data, conversionApi ) => {
-		const viewElement = elementCreator( data.item, conversionApi.writer );
+		const viewElement = elementCreator( data.item, conversionApi );
 
 
 		if ( !viewElement ) {
 		if ( !viewElement ) {
 			return;
 			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.
 		// 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.
 		// If marker is collapsed, only "opening" element will be inserted.
 		data.isOpening = true;
 		data.isOpening = true;
-		const viewStartElement = elementCreator( data, conversionApi.writer );
+		const viewStartElement = elementCreator( data, conversionApi );
 
 
 		data.isOpening = false;
 		data.isOpening = false;
-		const viewEndElement = elementCreator( data, conversionApi.writer );
+		const viewEndElement = elementCreator( data, conversionApi );
 
 
 		if ( !viewStartElement || !viewEndElement ) {
 		if ( !viewStartElement || !viewEndElement ) {
 			return;
 			return;
@@ -880,7 +899,7 @@ function removeUIElement() {
 // @returns {Function} Add marker converter.
 // @returns {Function} Add marker converter.
 function insertMarkerData( viewCreator ) {
 function insertMarkerData( viewCreator ) {
 	return ( evt, data, conversionApi ) => {
 	return ( evt, data, conversionApi ) => {
-		const viewMarkerData = viewCreator( data.markerName );
+		const viewMarkerData = viewCreator( data.markerName, conversionApi );
 
 
 		if ( !viewMarkerData ) {
 		if ( !viewMarkerData ) {
 			return;
 			return;
@@ -961,7 +980,7 @@ function insertMarkerAsElement( position, isStart, conversionApi, data, viewMark
 // @returns {Function} Remove marker converter.
 // @returns {Function} Remove marker converter.
 function removeMarkerData( viewCreator ) {
 function removeMarkerData( viewCreator ) {
 	return ( evt, data, conversionApi ) => {
 	return ( evt, data, conversionApi ) => {
-		const viewData = viewCreator( data.markerName );
+		const viewData = viewCreator( data.markerName, conversionApi );
 
 
 		if ( !viewData ) {
 		if ( !viewData ) {
 			return;
 			return;
@@ -1036,8 +1055,8 @@ function removeMarkerData( viewCreator ) {
 // @returns {Function} Set/change attribute converter.
 // @returns {Function} Set/change attribute converter.
 function changeAttribute( attributeCreator ) {
 function changeAttribute( attributeCreator ) {
 	return ( evt, data, conversionApi ) => {
 	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 ) {
 		if ( !oldAttribute && !newAttribute ) {
 			return;
 			return;
@@ -1487,7 +1506,7 @@ function normalizeToElementConfig( view, viewElementType ) {
 		return view;
 		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.
 // 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 {module:engine/view/downcastwriter~DowncastWriter} viewWriter
 // @param {'container'|'attribute'|'ui'} viewElementType
 // @param {'container'|'attribute'|'ui'} viewElementType
 // @returns {module:engine/view/element~Element}
 // @returns {module:engine/view/element~Element}
-function createViewElementFromDefinition( viewElementDefinition, viewWriter, viewElementType ) {
+function createViewElementFromDefinition( viewElementDefinition, conversionApi, viewElementType ) {
 	if ( typeof viewElementDefinition == 'string' ) {
 	if ( typeof viewElementDefinition == 'string' ) {
 		// If `viewElementDefinition` is given as a `String`, normalize it to an object with `name` property.
 		// If `viewElementDefinition` is given as a `String`, normalize it to an object with `name` property.
 		viewElementDefinition = { name: viewElementDefinition };
 		viewElementDefinition = { name: viewElementDefinition };
 	}
 	}
 
 
 	let element;
 	let element;
+	const viewWriter = conversionApi.writer;
 	const attributes = Object.assign( {}, viewElementDefinition.attributes );
 	const attributes = Object.assign( {}, viewElementDefinition.attributes );
 
 
 	if ( viewElementType == 'container' ) {
 	if ( viewElementType == 'container' ) {
@@ -1543,11 +1563,11 @@ function createViewElementFromDefinition( viewElementDefinition, viewWriter, vie
 
 
 function getFromAttributeCreator( config ) {
 function getFromAttributeCreator( config ) {
 	if ( config.model.values ) {
 	if ( config.model.values ) {
-		return ( modelAttributeValue, viewWriter ) => {
+		return ( modelAttributeValue, conversionApi ) => {
 			const view = config.view[ modelAttributeValue ];
 			const view = config.view[ modelAttributeValue ];
 
 
 			if ( view ) {
 			if ( view ) {
-				return view( modelAttributeValue, viewWriter );
+				return view( modelAttributeValue, conversionApi );
 			}
 			}
 
 
 			return null;
 			return null;

+ 22 - 11
packages/ckeditor5-engine/src/conversion/upcastdispatcher.js

@@ -11,6 +11,7 @@ import ViewConsumable from './viewconsumable';
 import ModelRange from '../model/range';
 import ModelRange from '../model/range';
 import ModelPosition from '../model/position';
 import ModelPosition from '../model/position';
 import { SchemaContext } from '../model/schema';
 import { SchemaContext } from '../model/schema';
+import { isParagraphable, wrapInParagraph } from '../model/utils/autoparagraphing';
 
 
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
 import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
@@ -349,22 +350,32 @@ export default class UpcastDispatcher {
 	 * @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#splitToAllowedParent
 	 * @see module:engine/conversion/upcastdispatcher~UpcastConversionApi#splitToAllowedParent
 	 */
 	 */
 	_splitToAllowedParent( node, modelCursor ) {
 	_splitToAllowedParent( node, modelCursor ) {
+		const { schema, writer } = this.conversionApi;
+
 		// Try to find allowed parent.
 		// 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.
 		// Split element to allowed parent.

+ 56 - 36
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 { attachLinkToDocumentation } from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 
 
 import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
 import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
+import { isParagraphable, wrapInParagraph } from '../model/utils/autoparagraphing';
 
 
 /* global console */
 /* global console */
 
 
@@ -59,7 +60,9 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 *				name: 'p',
 	 *				name: 'p',
 	 *				classes: 'heading'
 	 *				classes: 'heading'
 	 * 			},
 	 * 			},
-	 * 			model: ( viewElement, modelWriter ) => {
+	 * 			model: ( viewElement, conversionApi ) => {
+	 * 				const modelWriter = conversionApi.writer;
+	 *
 	 * 				return modelWriter.createElement( 'heading', { level: viewElement.getAttribute( 'data-level' ) } );
 	 * 				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 {Object} config Conversion configuration.
 	 * @param {module:engine/view/matcher~MatcherPattern} [config.view] Pattern matching all view elements which should be converted. If not
 	 * @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.
 	 * 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.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
 	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
 	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
 	 */
 	 */
@@ -135,7 +139,7 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 *			},
 	 *			},
 	 *			model: {
 	 *			model: {
 	 *				key: 'fontSize',
 	 *				key: 'fontSize',
-	 *				value: viewElement => {
+	 *				value: ( viewElement, conversionApi ) => {
 	 *					const fontSize = viewElement.getStyle( 'font-size' );
 	 *					const fontSize = viewElement.getStyle( 'font-size' );
 	 *					const value = fontSize.substr( 0, fontSize.length - 2 );
 	 *					const value = fontSize.substr( 0, fontSize.length - 2 );
 	 *
 	 *
@@ -157,7 +161,8 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 * @param {Object} config Conversion configuration.
 	 * @param {Object} config Conversion configuration.
 	 * @param {module:engine/view/matcher~MatcherPattern} config.view Pattern matching all view elements which should be converted.
 	 * @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
 	 * @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`.
 	 * If `String` is given, the model attribute value will be set to `true`.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.
 	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
 	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
@@ -231,7 +236,7 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 *			},
 	 *			},
 	 *			model: {
 	 *			model: {
 	 *				key: 'styled'
 	 *				key: 'styled'
-	 *				value: viewElement => {
+	 *				value: ( viewElement, conversionApi ) => {
 	 *					const regexp = /styled-([\S]+)/;
 	 *					const regexp = /styled-([\S]+)/;
 	 *					const match = viewElement.getAttribute( 'class' ).match( regexp );
 	 *					const match = viewElement.getAttribute( 'class' ).match( regexp );
 	 *
 	 *
@@ -263,7 +268,7 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 *			},
 	 *			},
 	 *			model: {
 	 *			model: {
 	 *				key: 'lineHeight',
 	 *				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`,
 	 * 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`.
 	 * 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
 	 * @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.
 	 * 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.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='low'] Converter priority.
 	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
 	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
@@ -310,7 +316,7 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 *
 	 *
 	 *		editor.conversion.for( 'upcast' ).elementToMarker( {
 	 *		editor.conversion.for( 'upcast' ).elementToMarker( {
 	 *			view: 'marker-search',
 	 *			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( {
 	 *		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:
 	 *		// Using a custom function which is the same as the default conversion:
 	 *		editor.conversion.for( 'upcast' ).dataToMarker( {
 	 *		editor.conversion.for( 'upcast' ).dataToMarker( {
 	 *			view: 'comment',
 	 *			view: 'comment',
-	 *			model: name => 'comment:' + name,
+	 *			model: ( name, conversionApi ) => 'comment:' + name,
 	 *		} );
 	 *		} );
 	 *
 	 *
 	 *		// Using the converter priority:
 	 *		// Using the converter priority:
 	 *		editor.conversion.for( 'upcast' ).dataToMarker( {
 	 *		editor.conversion.for( 'upcast' ).dataToMarker( {
 	 *			view: 'comment',
 	 *			view: 'comment',
-	 *			model: name => 'comment:' + name,
+	 *			model: ( name, conversionApi ) => 'comment:' + name,
 	 *			converterPriority: 'high'
 	 *			converterPriority: 'high'
 	 *		} );
 	 *		} );
 	 *
 	 *
@@ -416,8 +422,8 @@ export default class UpcastHelpers extends ConversionHelpers {
 	 * @method #dataToMarker
 	 * @method #dataToMarker
 	 * @param {Object} config Conversion configuration.
 	 * @param {Object} config Conversion configuration.
 	 * @param {String} config.view The marker group name to convert.
 	 * @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.
 	 * @param {module:utils/priorities~PriorityString} [config.converterPriority='normal'] Converter priority.
 	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
 	 * @returns {module:engine/conversion/upcasthelpers~UpcastHelpers}
 	 */
 	 */
@@ -459,20 +465,33 @@ export function convertToModelFragment() {
  * @returns {Function} {@link module:engine/view/text~Text View text} converter.
  * @returns {Function} {@link module:engine/view/text~Text View text} converter.
  */
  */
 export function convertText() {
 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 ) {
 		function addMarkerElements( position, markerViewNames ) {
 			for ( const markerViewName of 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 } );
 				const element = conversionApi.writer.createElement( '$marker', { 'data-name': markerName } );
 
 
 				conversionApi.writer.insert( element, position );
 				conversionApi.writer.insert( element, position );
@@ -754,7 +773,7 @@ function prepareToElementConverter( config ) {
 			return;
 			return;
 		}
 		}
 
 
-		const modelElement = getModelElement( config.model, data.viewItem, conversionApi.writer );
+		const modelElement = getModelElement( config.model, data.viewItem, conversionApi );
 
 
 		if ( !modelElement ) {
 		if ( !modelElement ) {
 			return;
 			return;
@@ -775,12 +794,12 @@ function prepareToElementConverter( config ) {
 //
 //
 // @param {String|Function|module:engine/model/element~Element} model Model conversion configuration.
 // @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/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 ) {
 	if ( model instanceof Function ) {
-		return model( input, writer );
+		return model( input, conversionApi );
 	} else {
 	} else {
-		return writer.createElement( model );
+		return conversionApi.writer.createElement( model );
 	}
 	}
 }
 }
 
 
@@ -858,7 +877,8 @@ function prepareToAttributeConverter( config, shallow ) {
 		}
 		}
 
 
 		const modelKey = config.model.key;
 		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.
 		// Do not convert if attribute building function returned falsy value.
 		if ( modelValue === null ) {
 		if ( modelValue === null ) {
@@ -939,10 +959,10 @@ function setAttributeOn( modelRange, modelAttribute, shallow, conversionApi ) {
 function normalizeElementToMarkerConfig( config ) {
 function normalizeElementToMarkerConfig( config ) {
 	const oldModel = config.model;
 	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 <markerGroup-start> and <markerGroup-end> elements.
 	// Upcast <markerGroup-start> and <markerGroup-end> elements.
 	configForElements.view = config.view + '-' + type;
 	configForElements.view = config.view + '-' + type;
 
 
-	configForElements.model = ( viewElement, modelWriter ) => {
+	configForElements.model = ( viewElement, conversionApi ) => {
 		const viewName = viewElement.getAttribute( 'name' );
 		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;
 	return configForElements;

+ 3 - 3
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( 'insert:$text', insertText() );
 	downcastDispatcher.on( 'attribute', ( evt, data, conversionApi ) => {
 	downcastDispatcher.on( 'attribute', ( evt, data, conversionApi ) => {
 		if ( data.item instanceof ModelSelection || data.item instanceof DocumentSelection || data.item.is( '$textProxy' ) ) {
 		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',
 					'model-text-with-attributes',
 					{ [ data.attributeKey ]: stringifyAttributeValue( modelAttributeValue ) }
 					{ [ data.attributeKey ]: stringifyAttributeValue( modelAttributeValue ) }
 				);
 				);
@@ -248,7 +248,7 @@ export function stringify( node, selectionOrPositionOrRange = null, markers = nu
 
 
 	downcastDispatcher.on( 'selection', convertRangeSelection() );
 	downcastDispatcher.on( 'selection', convertRangeSelection() );
 	downcastDispatcher.on( 'selection', convertCollapsedSelection() );
 	downcastDispatcher.on( 'selection', convertCollapsedSelection() );
-	downcastDispatcher.on( 'addMarker', insertUIElement( ( data, writer ) => {
+	downcastDispatcher.on( 'addMarker', insertUIElement( ( data, { writer } ) => {
 		const name = data.markerName + ':' + ( data.isOpening ? 'start' : 'end' );
 		const name = data.markerName + ':' + ( data.isOpening ? 'start' : 'end' );
 
 
 		return writer.createUIElement( name );
 		return writer.createUIElement( name );

+ 2 - 2
packages/ckeditor5-engine/src/dev-utils/view.js

@@ -51,9 +51,9 @@ const allowedTypes = {
  * the default `main` name will be used.
  * the default `main` name will be used.
  * @param {Boolean} [options.showType=false] When set to `true`, the type of elements will be printed (`<container:p>`
  * @param {Boolean} [options.showType=false] When set to `true`, the type of elements will be printed (`<container:p>`
  * instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
  * instead of `<p>`, `<attribute:b>` instead of `<b>` and `<empty:img>` instead of `<img>`).
- * @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
  * (`<span view-priority="12">`, `<b view-priority="10">`).
  * (`<span view-priority="12">`, `<b view-priority="10">`).
- * @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
  * (`<span id="marker:foo">`).
  * (`<span id="marker:foo">`).
  * @param {Boolean} [options.renderUIElements=false] When set to `true`, the inner content of each
  * @param {Boolean} [options.renderUIElements=false] When set to `true`, the inner content of each
  * {@link module:engine/view/uielement~UIElement} will be printed.
  * {@link module:engine/view/uielement~UIElement} will be printed.

+ 4 - 0
packages/ckeditor5-engine/src/model/model.js

@@ -25,6 +25,7 @@ import deleteContent from './utils/deletecontent';
 import modifySelection from './utils/modifyselection';
 import modifySelection from './utils/modifyselection';
 import getSelectedContent from './utils/getselectedcontent';
 import getSelectedContent from './utils/getselectedcontent';
 import { injectSelectionPostFixer } from './utils/selection-post-fixer';
 import { injectSelectionPostFixer } from './utils/selection-post-fixer';
+import { autoParagraphEmptyRoots } from './utils/autoparagraphing';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
 
 
 // @if CK_DEBUG_ENGINE // const { dumpTrees } = require( '../dev-utils/utils' );
 // @if CK_DEBUG_ENGINE // const { dumpTrees } = require( '../dev-utils/utils' );
@@ -122,6 +123,9 @@ export default class Model {
 
 
 		injectSelectionPostFixer( this );
 		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 // this.on( 'applyOperation', () => {
 		// @if CK_DEBUG_ENGINE // 	dumpTrees( this.document, this.document.version );
 		// @if CK_DEBUG_ENGINE // 	dumpTrees( this.document, this.document.version );
 		// @if CK_DEBUG_ENGINE // }, { priority: 'lowest' } );
 		// @if CK_DEBUG_ENGINE // }, { priority: 'lowest' } );

+ 77 - 0
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 );
+}

+ 15 - 14
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
  * `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.
  * {@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.
  * Two converters will keep separate binding maps, so one tree view can be bound with two DOM trees.
  */
  */
 export default class DomConverter {
 export default class DomConverter {
 	/**
 	/**
-	 * Creates DOM converter.
+	 * Creates a DOM converter.
 	 *
 	 *
 	 * @param {module:engine/view/document~Document} document The view document instance.
 	 * @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.
 	 * @param {module:engine/view/filler~BlockFillerMode} [options.blockFillerMode='br'] The type of the block filler to use.
 	 */
 	 */
 	constructor( document, options = {} ) {
 	constructor( document, options = {} ) {
@@ -56,7 +57,7 @@ export default class DomConverter {
 		this.document = document;
 		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
 		 * @readonly
 		 * @member {'br'|'nbsp'} module:engine/view/domconverter~DomConverter#blockFillerMode
 		 * @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
 		 * 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
 		 * @readonly
 		 * @private
 		 * @private
@@ -95,7 +96,7 @@ export default class DomConverter {
 		this._blockFiller = this.blockFillerMode == 'br' ? BR_FILLER : NBSP_FILLER;
 		this._blockFiller = this.blockFillerMode == 'br' ? BR_FILLER : NBSP_FILLER;
 
 
 		/**
 		/**
-		 * DOM to View mapping.
+		 * The DOM-to-view mapping.
 		 *
 		 *
 		 * @private
 		 * @private
 		 * @member {WeakMap} module:engine/view/domconverter~DomConverter#_domToViewMapping
 		 * @member {WeakMap} module:engine/view/domconverter~DomConverter#_domToViewMapping
@@ -103,7 +104,7 @@ export default class DomConverter {
 		this._domToViewMapping = new WeakMap();
 		this._domToViewMapping = new WeakMap();
 
 
 		/**
 		/**
-		 * View to DOM mapping.
+		 * The view-to-DOM mapping.
 		 *
 		 *
 		 * @private
 		 * @private
 		 * @member {WeakMap} module:engine/view/domconverter~DomConverter#_viewToDomMapping
 		 * @member {WeakMap} module:engine/view/domconverter~DomConverter#_viewToDomMapping
@@ -111,7 +112,7 @@ export default class DomConverter {
 		this._viewToDomMapping = new WeakMap();
 		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
 		 * @private
 		 * @member {WeakMap} module:engine/view/domconverter~DomConverter#_fakeSelectionMapping
 		 * @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:
 	 * 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/uielement~UIElement a view UI element},
 	 * * inside a DOM element which represents {@link module:engine/view/rawelement~RawElement a view raw 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.
 	 * @returns {Boolean} `true` if the given selection is at a correct place, `false` otherwise.
 	 */
 	 */
 	isDomSelectionCorrect( domSelection ) {
 	isDomSelectionCorrect( domSelection ) {

+ 21 - 20
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( 'strong' );
 	 *		writer.createAttributeElement( 'a', { href: 'foo.bar' } );
 	 *		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' );
 	 *		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' );
 	 *		writer.createEditableElement( 'div', { id: 'foo-1234' } );
 	 *		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' );
 	 *		writer.createEmptyElement( 'img', { id: 'foo-1234' } );
 	 *		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' );
 	 *		writer.createUIElement( 'span', { id: 'foo-1234' } );
 	 *		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 ) {
 	 *		writer.createUIElement( 'span', null, function( domDocument ) {
 	 *			const domElement = this.toDomElement( 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.
 	 * 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 ) {
 	createUIElement( name, attributes, renderFunction ) {
 		const uiElement = new UIElement( this.document, name, attributes );
 		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
 	 * 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
 	 * 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.
 	 * 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}),
 	 * {@link module:engine/view/containerelement~ContainerElement} or {@link module:engine/view/emptyelement~EmptyElement}),
 	 * and they are considered by the editor selection.
 	 * 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 ) {
 	createRawElement( name, attributes, renderFunction ) {
 		const rawElement = new RawElement( this.document, name, attributes );
 		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 );
 	 *		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
 	 * @param {module:engine/view/element~Element} element
 	 */
 	 */
 	setAttribute( key, value, element ) {
 	setAttribute( key, value, element ) {

+ 21 - 21
packages/ckeditor5-engine/src/view/rawelement.js

@@ -14,41 +14,41 @@ import Node from './node';
 /**
 /**
  * The raw element class.
  * 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
  * 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.
  * 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
  * 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:engine/view/emptyelement~EmptyElement}), they are considered by the editor selection and
  * {@link module:widget/utils~toWidget they can work as widgets}.
  * {@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.
  * {@link module:engine/view/downcastwriter~DowncastWriter#createRawElement `downcastWriter#createRawElement()`} method.
  *
  *
  * @extends module:engine/view/element~Element
  * @extends module:engine/view/element~Element
  */
  */
 export default class RawElement extends 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
 	 * @see module:engine/view/downcastwriter~DowncastWriter#createRawElement
 	 * @protected
 	 * @protected
 	 * @param {module:engine/view/document~Document} document The document instance to which this element belongs.
 	 * @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.<module:engine/view/node~Node>} [children]
 	 * @param {module:engine/view/node~Node|Iterable.<module:engine/view/node~Node>} [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 ) {
 	constructor( document, name, attrs, children ) {
 		super( 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
 		 * @method #getFillerOffset
 		 * @returns {null} Always returns null.
 		 * @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
 	 * Assuming that the object being checked is a raw element, you can also check its
 	 * {@link module:engine/view/rawelement~RawElement#name name}:
 	 * {@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
 	 *		rawElement.is( 'rawElement', 'img' ); // -> same as above
 	 *		text.is( 'img' ); -> false
 	 *		text.is( 'img' ); -> false
 	 *
 	 *
 	 * {@link module:engine/view/node~Node#is Check the entire list of view objects} which implement the `is()` method.
 	 * {@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.
 	 * Otherwise, it acts like the `name` parameter.
-	 * @param {String} [name] Element name.
+	 * @param {String} [name] The element name.
 	 * @returns {Boolean}
 	 * @returns {Boolean}
 	 */
 	 */
 	is( type, name = null ) {
 	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
 	 * @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
 	 * 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' );
 	 *		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}
 // @returns {null}
 function getFillerOffset() {
 function getFillerOffset() {

+ 126 - 2
packages/ckeditor5-engine/tests/controller/datacontroller.js

@@ -155,8 +155,8 @@ describe( 'DataController', () => {
 
 
 			const viewFragment = new ViewDocumentFragment( viewDocument, [ parseView( 'foo' ) ] );
 			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( '<paragraph>foo</paragraph>' );
 
 
 			// Model fragment in inline root.
 			// Model fragment in inline root.
 			expect( stringify( data.toModel( viewFragment, [ 'inlineRoot' ] ) ) ).to.equal( 'foo' );
 			expect( stringify( data.toModel( viewFragment, [ 'inlineRoot' ] ) ) ).to.equal( 'foo' );
@@ -454,6 +454,82 @@ describe( 'DataController', () => {
 				data.get( { rootName: 'nonexistent' } );
 				data.get( { rootName: 'nonexistent' } );
 			}, /datacontroller-get-non-existent-root:/ );
 			}, /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, '<paragraph>foo</paragraph>' );
+
+			expect( data.get( { attributeValue: 'foo' } ) ).to.equal( '<p attribute="foo">foo</p>' );
+			expect( data.get( { attributeValue: 'bar' } ) ).to.equal( '<p attribute="bar">foo</p>' );
+		} );
+
+		it( 'should allow to provide additional options for retrieving data - attribute conversion', () => {
+			schema.register( 'paragraph', { inheritAllFrom: '$block', allowAttributes: [ 'foo' ] } );
+			downcastHelpers.elementToElement( { model: 'paragraph', view: 'p' } );
+
+			data.downcastDispatcher.on( 'attribute:foo', ( evt, data, conversionApi ) => {
+				if ( data.attributeNewValue === conversionApi.options.skipAttribute ) {
+					return;
+				}
+
+				const viewRange = conversionApi.mapper.toViewRange( data.range );
+				const viewElement = conversionApi.writer.createAttributeElement( data.attributeNewValue );
+
+				conversionApi.writer.wrap( viewRange, viewElement );
+			} );
+
+			setData( model, '<paragraph>f<$text foo="a">o</$text>ob<$text foo="b">a</$text>r</paragraph>' );
+
+			expect( data.get() ).to.equal( '<p>f<a>o</a>ob<b>a</b>r</p>' );
+			expect( data.get( { skipAttribute: 'a' } ) ).to.equal( '<p>foob<b>a</b>r</p>' );
+			expect( data.get( { skipAttribute: 'b' } ) ).to.equal( '<p>f<a>o</a>obar</p>' );
+		} );
+
+		it( 'should allow to provide additional options for retrieving data - addMarker conversion', () => {
+			schema.register( 'paragraph', { inheritAllFrom: '$block' } );
+			downcastHelpers.elementToElement( { model: 'paragraph', view: 'p' } );
+
+			data.downcastDispatcher.on( 'addMarker', ( evt, data, conversionApi ) => {
+				if ( conversionApi.options.skipMarker ) {
+					return;
+				}
+
+				const viewElement = conversionApi.writer.createAttributeElement( 'marker' );
+				const viewRange = conversionApi.mapper.toViewRange( data.markerRange );
+
+				conversionApi.writer.wrap( viewRange, viewElement );
+			} );
+
+			setData( model, '<paragraph>foo</paragraph>' );
+
+			const root = model.document.getRoot();
+
+			model.change( writer => {
+				const start = writer.createPositionFromPath( root, [ 0, 1 ] );
+				const end = writer.createPositionFromPath( root, [ 0, 2 ] );
+
+				writer.addMarker( 'marker', {
+					range: writer.createRange( start, end ),
+					usingOperation: false
+				} );
+			} );
+
+			expect( data.get( { skipMarker: false } ) ).to.equal( '<p>f<marker>o</marker>o</p>' );
+			expect( data.get( { skipMarker: true } ) ).to.equal( '<p>foo</p>' );
+		} );
 	} );
 	} );
 
 
 	describe( 'stringify()', () => {
 	describe( 'stringify()', () => {
@@ -478,6 +554,24 @@ describe( 'DataController', () => {
 
 
 			expect( data.stringify( modelDocumentFragment ) ).to.equal( '<p>foo</p><p>bar</p>' );
 			expect( data.stringify( modelDocumentFragment ) ).to.equal( '<p>foo</p><p>bar</p>' );
 		} );
 		} );
+
+		it( 'should allow to provide additional options to the conversion process', () => {
+			const spy = sinon.spy();
+
+			data.downcastDispatcher.on( 'insert:paragraph', ( evt, data, conversionApi ) => {
+				spy( conversionApi.options );
+			}, { priority: 'high' } );
+
+			const modelDocumentFragment = parseModel( '<paragraph>foo</paragraph><paragraph>bar</paragraph>', schema );
+
+			const options = { foo: 'bar' };
+
+			data.stringify( modelDocumentFragment );
+			expect( spy.lastCall.args[ 0 ] ).to.not.equal( options );
+
+			data.stringify( modelDocumentFragment, options );
+			expect( spy.lastCall.args[ 0 ] ).to.equal( options );
+		} );
 	} );
 	} );
 
 
 	describe( 'toView()', () => {
 	describe( 'toView()', () => {
@@ -590,6 +684,36 @@ describe( 'DataController', () => {
 			expect( mappedViewRange.end.nodeBefore ).to.equal( firstViewElement );
 			expect( mappedViewRange.end.nodeBefore ).to.equal( firstViewElement );
 			expect( mappedViewRange.end.nodeAfter ).to.equal( viewDocumentFragment.getChild( 1 ) );
 			expect( mappedViewRange.end.nodeAfter ).to.equal( viewDocumentFragment.getChild( 1 ) );
 		} );
 		} );
+
+		it( 'should allow to provide additional options to the conversion process', () => {
+			const root = model.document.getRoot();
+			const spy = sinon.spy();
+
+			data.downcastDispatcher.on( 'insert:paragraph', ( evt, data, conversionApi ) => {
+				spy( conversionApi.options );
+			}, { priority: 'high' } );
+
+			data.downcastDispatcher.on( 'addMarker:marker', ( evt, data, conversionApi ) => {
+				spy( conversionApi.options );
+			}, { priority: 'high' } );
+
+			setData( model, '<paragraph>foo</paragraph>' );
+
+			model.change( writer => {
+				writer.addMarker( 'marker', {
+					range: model.createRange( model.createPositionFromPath( root, [ 0, 1 ] ) ),
+					usingOperation: false
+				} );
+			} );
+
+			const options = { foo: 'bar' };
+
+			data.toView( root, options );
+
+			sinon.assert.calledTwice( spy );
+			expect( spy.firstCall.args[ 0 ] ).to.equal( options );
+			expect( spy.lastCall.args[ 0 ] ).to.equal( options );
+		} );
 	} );
 	} );
 
 
 	describe( 'destroy()', () => {
 	describe( 'destroy()', () => {

+ 40 - 64
packages/ckeditor5-engine/tests/conversion/downcasthelpers.js

@@ -104,7 +104,7 @@ describe( 'DowncastHelpers', () => {
 		it( 'config.view is a function', () => {
 		it( 'config.view is a function', () => {
 			downcastHelpers.elementToElement( {
 			downcastHelpers.elementToElement( {
 				model: 'heading',
 				model: 'heading',
-				view: ( modelElement, viewWriter ) => viewWriter.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) )
+				view: ( modelElement, { writer } ) => writer.createContainerElement( 'h' + modelElement.getAttribute( 'level' ) )
 			} );
 			} );
 
 
 			model.change( writer => {
 			model.change( writer => {
@@ -225,8 +225,8 @@ describe( 'DowncastHelpers', () => {
 		it( 'config.view is a function', () => {
 		it( 'config.view is a function', () => {
 			downcastHelpers.attributeToElement( {
 			downcastHelpers.attributeToElement( {
 				model: 'bold',
 				model: 'bold',
-				view: ( modelAttributeValue, viewWriter ) => {
-					return viewWriter.createAttributeElement( 'span', { style: 'font-weight:' + modelAttributeValue } );
+				view: ( modelAttributeValue, { writer } ) => {
+					return writer.createAttributeElement( 'span', { style: 'font-weight:' + modelAttributeValue } );
 				}
 				}
 			} );
 			} );
 
 
@@ -243,15 +243,15 @@ describe( 'DowncastHelpers', () => {
 					key: 'color',
 					key: 'color',
 					name: '$text'
 					name: '$text'
 				},
 				},
-				view: ( modelAttributeValue, viewWriter ) => {
-					return viewWriter.createAttributeElement( 'span', { style: 'color:' + modelAttributeValue } );
+				view: ( modelAttributeValue, { writer } ) => {
+					return writer.createAttributeElement( 'span', { style: 'color:' + modelAttributeValue } );
 				}
 				}
 			} );
 			} );
 
 
 			downcastHelpers.elementToElement( {
 			downcastHelpers.elementToElement( {
 				model: 'smiley',
 				model: 'smiley',
-				view: ( modelElement, viewWriter ) => {
-					return viewWriter.createEmptyElement( 'img', {
+				view: ( modelElement, { writer } ) => {
+					return writer.createEmptyElement( 'img', {
 						src: 'smile.jpg',
 						src: 'smile.jpg',
 						class: 'smiley'
 						class: 'smiley'
 					} );
 					} );
@@ -283,7 +283,7 @@ describe( 'DowncastHelpers', () => {
 
 
 			downcastHelpers.attributeToElement( {
 			downcastHelpers.attributeToElement( {
 				model: 'bold',
 				model: 'bold',
-				view: ( modelAttributeValue, viewWriter ) => viewWriter.createAttributeElement( 'b' )
+				view: ( modelAttributeValue, { writer } ) => writer.createAttributeElement( 'b' )
 			} );
 			} );
 
 
 			model.change( writer => {
 			model.change( writer => {
@@ -304,9 +304,9 @@ describe( 'DowncastHelpers', () => {
 
 
 			downcastHelpers.attributeToElement( {
 			downcastHelpers.attributeToElement( {
 				model: 'style',
 				model: 'style',
-				view: ( modelAttributeValue, viewWriter ) => {
+				view: ( modelAttributeValue, { writer } ) => {
 					if ( modelAttributeValue == 'bold' ) {
 					if ( modelAttributeValue == 'bold' ) {
-						return viewWriter.createAttributeElement( 'b' );
+						return writer.createAttributeElement( 'b' );
 					}
 					}
 				}
 				}
 			} );
 			} );
@@ -333,8 +333,8 @@ describe( 'DowncastHelpers', () => {
 
 
 			downcastHelpers.attributeToElement( {
 			downcastHelpers.attributeToElement( {
 				model: 'link',
 				model: 'link',
-				view: ( modelAttributeValue, viewWriter ) => {
-					return viewWriter.createAttributeElement( 'a', { href: modelAttributeValue } );
+				view: ( modelAttributeValue, { writer } ) => {
+					return writer.createAttributeElement( 'a', { href: modelAttributeValue } );
 				}
 				}
 			} );
 			} );
 
 
@@ -357,7 +357,7 @@ describe( 'DowncastHelpers', () => {
 
 
 			downcastHelpers.attributeToElement( {
 			downcastHelpers.attributeToElement( {
 				model: 'bold',
 				model: 'bold',
-				view: ( modelAttributeValue, viewWriter ) => viewWriter.createAttributeElement( 'b' )
+				view: ( modelAttributeValue, { writer } ) => writer.createAttributeElement( 'b' )
 			} );
 			} );
 
 
 			model.change( writer => {
 			model.change( writer => {
@@ -378,11 +378,11 @@ describe( 'DowncastHelpers', () => {
 
 
 			downcastHelpers.attributeToElement( {
 			downcastHelpers.attributeToElement( {
 				model: 'bold',
 				model: 'bold',
-				view: ( modelAttributeValue, viewWriter ) => viewWriter.createAttributeElement( 'b' )
+				view: ( modelAttributeValue, { writer } ) => writer.createAttributeElement( 'b' )
 			} );
 			} );
 			downcastHelpers.attributeToElement( {
 			downcastHelpers.attributeToElement( {
 				model: 'bold',
 				model: 'bold',
-				view: ( modelAttributeValue, viewWriter ) => viewWriter.createAttributeElement( 'strong' ),
+				view: ( modelAttributeValue, { writer } ) => writer.createAttributeElement( 'strong' ),
 				converterPriority: 'high'
 				converterPriority: 'high'
 			} );
 			} );
 
 
@@ -423,7 +423,7 @@ describe( 'DowncastHelpers', () => {
 			downcastHelpers.elementToElement( { model: 'image', view: 'img' } );
 			downcastHelpers.elementToElement( { model: 'image', view: 'img' } );
 			downcastHelpers.elementToElement( {
 			downcastHelpers.elementToElement( {
 				model: 'paragraph',
 				model: 'paragraph',
-				view: ( modelItem, viewWriter ) => viewWriter.createContainerElement( 'p' )
+				view: ( modelItem, { writer } ) => writer.createContainerElement( 'p' )
 			} );
 			} );
 
 
 			downcastHelpers.attributeToAttribute( {
 			downcastHelpers.attributeToAttribute( {
@@ -605,7 +605,12 @@ describe( 'DowncastHelpers', () => {
 		it( 'config.view is a function', () => {
 		it( 'config.view is a function', () => {
 			downcastHelpers.attributeToAttribute( {
 			downcastHelpers.attributeToAttribute( {
 				model: 'styled',
 				model: 'styled',
-				view: attributeValue => ( { key: 'class', value: 'styled-' + attributeValue } )
+				view: ( attributeValue, conversionApi ) => {
+					// To ensure conversion API is provided.
+					expect( conversionApi.writer ).to.instanceof( DowncastWriter );
+
+					return { key: 'class', value: 'styled-' + attributeValue };
+				}
 			} );
 			} );
 
 
 			model.change( writer => {
 			model.change( writer => {
@@ -677,41 +682,6 @@ describe( 'DowncastHelpers', () => {
 			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
 			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p></div>' );
 		} );
 		} );
 
 
-		it( 'should convert insert/change/remove with attribute generating function as a parameter', () => {
-			downcastHelpers.elementToElement( { model: 'div', view: 'div' } );
-			downcastHelpers.attributeToAttribute( {
-				model: 'theme',
-				view: ( value, data ) => {
-					if ( data.item instanceof ModelElement && data.item.childCount > 0 ) {
-						value += ' fix-content';
-					}
-
-					return { key: 'class', value };
-				}
-			} );
-
-			const modelParagraph = new ModelElement( 'paragraph', { theme: 'nice' }, new ModelText( 'foobar' ) );
-			const modelDiv = new ModelElement( 'div', { theme: 'nice' } );
-
-			model.change( writer => {
-				writer.insert( [ modelParagraph, modelDiv ], modelRootStart );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p class="nice fix-content">foobar</p><div class="nice"></div></div>' );
-
-			model.change( writer => {
-				writer.setAttribute( 'theme', 'awesome', modelParagraph );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p class="awesome fix-content">foobar</p><div class="nice"></div></div>' );
-
-			model.change( writer => {
-				writer.removeAttribute( 'theme', modelParagraph );
-			} );
-
-			expect( viewToString( viewRoot ) ).to.equal( '<div><p>foobar</p><div class="nice"></div></div>' );
-		} );
-
 		it( 'should be possible to override setAttribute', () => {
 		it( 'should be possible to override setAttribute', () => {
 			downcastHelpers.attributeToAttribute( {
 			downcastHelpers.attributeToAttribute( {
 				model: 'class',
 				model: 'class',
@@ -807,8 +777,8 @@ describe( 'DowncastHelpers', () => {
 		it( 'config.view is a function', () => {
 		it( 'config.view is a function', () => {
 			downcastHelpers.markerToElement( {
 			downcastHelpers.markerToElement( {
 				model: 'search',
 				model: 'search',
-				view: ( data, viewWriter ) => {
-					return viewWriter.createUIElement( 'span', { 'data-marker': 'search', 'data-start': data.isOpening } );
+				view: ( data, { writer } ) => {
+					return writer.createUIElement( 'span', { 'data-marker': 'search', 'data-start': data.isOpening } );
 				}
 				}
 			} );
 			} );
 
 
@@ -838,7 +808,7 @@ describe( 'DowncastHelpers', () => {
 			it( 'should insert and remove ui element', () => {
 			it( 'should insert and remove ui element', () => {
 				downcastHelpers.markerToElement( {
 				downcastHelpers.markerToElement( {
 					model: 'marker',
 					model: 'marker',
-					view: ( data, viewWriter ) => viewWriter.createUIElement( 'span', { 'class': 'marker' } )
+					view: ( data, { writer } ) => writer.createUIElement( 'span', { 'class': 'marker' } )
 				} );
 				} );
 
 
 				model.change( writer => {
 				model.change( writer => {
@@ -859,7 +829,7 @@ describe( 'DowncastHelpers', () => {
 
 
 				downcastHelpers.markerToElement( {
 				downcastHelpers.markerToElement( {
 					model: 'marker',
 					model: 'marker',
-					view: ( data, viewWriter ) => viewWriter.createUIElement( 'span', { 'class': 'marker' } )
+					view: ( data, { writer } ) => writer.createUIElement( 'span', { 'class': 'marker' } )
 				} );
 				} );
 
 
 				controller.downcastDispatcher.on( 'addMarker:marker', ( evt, data, conversionApi ) => {
 				controller.downcastDispatcher.on( 'addMarker:marker', ( evt, data, conversionApi ) => {
@@ -911,7 +881,7 @@ describe( 'DowncastHelpers', () => {
 			it( 'should insert and remove ui element - element as a creator', () => {
 			it( 'should insert and remove ui element - element as a creator', () => {
 				downcastHelpers.markerToElement( {
 				downcastHelpers.markerToElement( {
 					model: 'marker',
 					model: 'marker',
-					view: ( data, viewWriter ) => viewWriter.createUIElement( 'span', { 'class': 'marker' } )
+					view: ( data, { writer } ) => writer.createUIElement( 'span', { 'class': 'marker' } )
 				} );
 				} );
 
 
 				model.change( writer => {
 				model.change( writer => {
@@ -931,7 +901,7 @@ describe( 'DowncastHelpers', () => {
 			it( 'should insert and remove ui element - function as a creator', () => {
 			it( 'should insert and remove ui element - function as a creator', () => {
 				downcastHelpers.markerToElement( {
 				downcastHelpers.markerToElement( {
 					model: 'marker',
 					model: 'marker',
-					view: ( data, viewWriter ) => viewWriter.createUIElement( 'span', { 'class': data.markerName } )
+					view: ( data, { writer } ) => writer.createUIElement( 'span', { 'class': data.markerName } )
 				} );
 				} );
 
 
 				model.change( writer => {
 				model.change( writer => {
@@ -951,12 +921,12 @@ describe( 'DowncastHelpers', () => {
 			it( 'should insert and remove different opening and ending element', () => {
 			it( 'should insert and remove different opening and ending element', () => {
 				downcastHelpers.markerToElement( {
 				downcastHelpers.markerToElement( {
 					model: 'marker',
 					model: 'marker',
-					view: ( data, viewWriter ) => {
+					view: ( data, { writer } ) => {
 						if ( data.isOpening ) {
 						if ( data.isOpening ) {
-							return viewWriter.createUIElement( 'span', { 'class': data.markerName, 'data-start': true } );
+							return writer.createUIElement( 'span', { 'class': data.markerName, 'data-start': true } );
 						}
 						}
 
 
-						return viewWriter.createUIElement( 'span', { 'class': data.markerName, 'data-end': true } );
+						return writer.createUIElement( 'span', { 'class': data.markerName, 'data-end': true } );
 					}
 					}
 				} );
 				} );
 
 
@@ -980,7 +950,7 @@ describe( 'DowncastHelpers', () => {
 
 
 				downcastHelpers.markerToElement( {
 				downcastHelpers.markerToElement( {
 					model: 'marker',
 					model: 'marker',
-					view: ( data, viewWriter ) => viewWriter.createUIElement( 'span', { 'class': 'marker' } )
+					view: ( data, { writer } ) => writer.createUIElement( 'span', { 'class': 'marker' } )
 				} );
 				} );
 				controller.downcastDispatcher.on( 'addMarker:marker', ( evt, data, conversionApi ) => {
 				controller.downcastDispatcher.on( 'addMarker:marker', ( evt, data, conversionApi ) => {
 					conversionApi.consumable.consume( data.item, 'addMarker:marker' );
 					conversionApi.consumable.consume( data.item, 'addMarker:marker' );
@@ -1316,9 +1286,12 @@ describe( 'DowncastHelpers', () => {
 
 
 			downcastHelpers.markerToData( {
 			downcastHelpers.markerToData( {
 				model: 'group',
 				model: 'group',
-				view: markerName => {
+				view: ( markerName, conversionApi ) => {
 					const namePart = markerName.split( ':' )[ 1 ];
 					const namePart = markerName.split( ':' )[ 1 ];
 
 
+					// To ensure conversion API is provided.
+					expect( conversionApi.writer ).to.instanceof( DowncastWriter );
+
 					return {
 					return {
 						group: 'g',
 						group: 'g',
 						name: namePart + '_' + customData[ namePart ]
 						name: namePart + '_' + customData[ namePart ]
@@ -1510,9 +1483,12 @@ describe( 'DowncastHelpers', () => {
 		it( 'config.view is a function', () => {
 		it( 'config.view is a function', () => {
 			downcastHelpers.markerToHighlight( {
 			downcastHelpers.markerToHighlight( {
 				model: 'comment',
 				model: 'comment',
-				view: data => {
+				view: ( data, conversionApi ) => {
 					const commentType = data.markerName.split( ':' )[ 1 ];
 					const commentType = data.markerName.split( ':' )[ 1 ];
 
 
+					// To ensure conversion API is provided.
+					expect( conversionApi.writer ).to.instanceof( DowncastWriter );
+
 					return {
 					return {
 						classes: [ 'comment', 'comment-' + commentType ]
 						classes: [ 'comment', 'comment-' + commentType ]
 					};
 					};

+ 37 - 0
packages/ckeditor5-engine/tests/conversion/upcastdispatcher.js

@@ -605,6 +605,43 @@ describe( 'UpcastDispatcher', () => {
 				sinon.assert.calledOnce( spy );
 				sinon.assert.calledOnce( spy );
 			} );
 			} );
 
 
+			it(
+				'should auto-paragraph an element if it is not allowed at the insertion position' +
+				'but would be inserted if auto-paragraphed',
+				() => {
+					const spy = sinon.spy();
+
+					model.schema.register( 'section', {
+						allowIn: '$root'
+					} );
+					model.schema.register( 'span', {
+						allowIn: 'paragraph'
+					} );
+					model.schema.extend( 'paragraph', {
+						allowIn: 'section'
+					} );
+
+					dispatcher.on( 'documentFragment', ( evt, data, conversionApi ) => {
+						const section = conversionApi.writer.createElement( 'section' );
+						const position = ModelPosition._createAt( section, 0 );
+
+						const span = conversionApi.writer.createElement( 'span' );
+
+						const result = conversionApi.splitToAllowedParent( span, position );
+
+						expect( section.getChild( 0 ).name ).to.equal( 'paragraph' );
+						expect( result ).to.deep.equal( {
+							position: ModelPosition._createAt( section.getChild( 0 ), 0 )
+						} );
+
+						spy();
+					} );
+
+					model.change( writer => dispatcher.convert( new ViewDocumentFragment( viewDocument ), writer ) );
+					sinon.assert.calledOnce( spy );
+				}
+			);
+
 			it( 'should return null if element is not allowed in position and any of ancestors', () => {
 			it( 'should return null if element is not allowed in position and any of ancestors', () => {
 				const spy = sinon.spy();
 				const spy = sinon.spy();
 
 

+ 51 - 7
packages/ckeditor5-engine/tests/conversion/upcasthelpers.js

@@ -29,6 +29,7 @@ import Mapper from '../../src/conversion/mapper';
 import ViewSelection from '../../src/view/selection';
 import ViewSelection from '../../src/view/selection';
 import ViewRange from '../../src/view/range';
 import ViewRange from '../../src/view/range';
 import { StylesProcessor } from '../../src/view/stylesmap';
 import { StylesProcessor } from '../../src/view/stylesmap';
+import Writer from '../../src/model/writer';
 
 
 /* globals console */
 /* globals console */
 
 
@@ -108,8 +109,8 @@ describe( 'UpcastHelpers', () => {
 					name: 'p',
 					name: 'p',
 					classes: 'heading'
 					classes: 'heading'
 				},
 				},
-				model: ( viewElement, modelWriter ) => {
-					return modelWriter.createElement( 'heading', { level: viewElement.getAttribute( 'data-level' ) } );
+				model: ( viewElement, { writer } ) => {
+					return writer.createElement( 'heading', { level: viewElement.getAttribute( 'data-level' ) } );
 				}
 				}
 			} );
 			} );
 
 
@@ -250,10 +251,13 @@ describe( 'UpcastHelpers', () => {
 				},
 				},
 				model: {
 				model: {
 					key: 'fontSize',
 					key: 'fontSize',
-					value: viewElement => {
+					value: ( viewElement, conversionApi ) => {
 						const fontSize = viewElement.getStyle( 'font-size' );
 						const fontSize = viewElement.getStyle( 'font-size' );
 						const value = fontSize.substr( 0, fontSize.length - 2 );
 						const value = fontSize.substr( 0, fontSize.length - 2 );
 
 
+						// To ensure conversion API is provided.
+						expect( conversionApi.writer ).to.instanceof( Writer );
+
 						if ( value <= 10 ) {
 						if ( value <= 10 ) {
 							return 'small';
 							return 'small';
 						} else if ( value > 12 ) {
 						} else if ( value > 12 ) {
@@ -504,10 +508,13 @@ describe( 'UpcastHelpers', () => {
 				},
 				},
 				model: {
 				model: {
 					key: 'styled',
 					key: 'styled',
-					value: viewElement => {
+					value: ( viewElement, conversionApi ) => {
 						const regexp = /styled-([\S]+)/;
 						const regexp = /styled-([\S]+)/;
 						const match = viewElement.getAttribute( 'class' ).match( regexp );
 						const match = viewElement.getAttribute( 'class' ).match( regexp );
 
 
+						// To ensure conversion API is provided.
+						expect( conversionApi.writer ).to.instanceof( Writer );
+
 						return match[ 1 ];
 						return match[ 1 ];
 					}
 					}
 				}
 				}
@@ -660,7 +667,12 @@ describe( 'UpcastHelpers', () => {
 		it( 'config.model is a function', () => {
 		it( 'config.model is a function', () => {
 			upcastHelpers.elementToMarker( {
 			upcastHelpers.elementToMarker( {
 				view: 'comment',
 				view: 'comment',
-				model: viewElement => 'comment:' + viewElement.getAttribute( 'data-comment-id' )
+				model: ( viewElement, conversionApi ) => {
+					// To ensure conversion API is provided.
+					expect( conversionApi.writer ).to.instanceof( Writer );
+
+					return 'comment:' + viewElement.getAttribute( 'data-comment-id' );
+				}
 			} );
 			} );
 
 
 			const frag = new ViewDocumentFragment( viewDocument, [
 			const frag = new ViewDocumentFragment( viewDocument, [
@@ -837,7 +849,14 @@ describe( 'UpcastHelpers', () => {
 		} );
 		} );
 
 
 		it( 'conversion callback, mixed, multiple markers, name', () => {
 		it( 'conversion callback, mixed, multiple markers, name', () => {
-			upcastHelpers.dataToMarker( { view: 'g', model: name => 'group:' + name.split( '_' )[ 0 ] } );
+			upcastHelpers.dataToMarker( {
+				view: 'g',
+				model: ( name, conversionApi ) => {
+					// To ensure conversion API is provided.
+					expect( conversionApi.writer ).to.instanceof( Writer );
+
+					return 'group:' + name.split( '_' )[ 0 ];
+				} } );
 
 
 			expectResult(
 			expectResult(
 				viewParse(
 				viewParse(
@@ -949,7 +968,7 @@ describe( 'upcast-converters', () => {
 
 
 		it( 'should not convert text if it is wrong with schema', () => {
 		it( 'should not convert text if it is wrong with schema', () => {
 			schema.addChildCheck( ( ctx, childDef ) => {
 			schema.addChildCheck( ( ctx, childDef ) => {
-				if ( childDef.name == '$text' && ctx.endsWith( '$root' ) ) {
+				if ( ( childDef.name == '$text' || childDef.name == 'paragraph' ) && ctx.endsWith( '$root' ) ) {
 					return false;
 					return false;
 				}
 				}
 			} );
 			} );
@@ -969,6 +988,31 @@ describe( 'upcast-converters', () => {
 			expect( conversionResult.getChild( 0 ).data ).to.equal( 'foobar' );
 			expect( conversionResult.getChild( 0 ).data ).to.equal( 'foobar' );
 		} );
 		} );
 
 
+		it( 'should auto-paragraph a text if it is not allowed at the insertion position but would be inserted if auto-paragraphed', () => {
+			schema.addChildCheck( ( ctx, childDef ) => {
+				if ( childDef.name == '$text' && ctx.endsWith( '$root' ) ) {
+					return false;
+				}
+			} );
+
+			const viewText = new ViewText( viewDocument, 'foobar' );
+			dispatcher.on( 'text', convertText() );
+			let conversionResult = model.change( writer => dispatcher.convert( viewText, writer, context ) );
+
+			expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
+			expect( conversionResult.childCount ).to.equal( 1 );
+			expect( conversionResult.getChild( 0 ).name ).to.equal( 'paragraph' );
+			expect( conversionResult.getNodeByPath( [ 0, 0 ] ) ).to.be.instanceof( ModelText );
+			expect( conversionResult.getNodeByPath( [ 0, 0 ] ).data ).to.equal( 'foobar' );
+
+			conversionResult = model.change( writer => dispatcher.convert( viewText, writer, [ '$block' ] ) );
+
+			expect( conversionResult ).to.be.instanceof( ModelDocumentFragment );
+			expect( conversionResult.childCount ).to.equal( 1 );
+			expect( conversionResult.getChild( 0 ) ).to.be.instanceof( ModelText );
+			expect( conversionResult.getChild( 0 ).data ).to.equal( 'foobar' );
+		} );
+
 		it( 'should support unicode', () => {
 		it( 'should support unicode', () => {
 			const viewText = new ViewText( viewDocument, 'நிலைக்கு' );
 			const viewText = new ViewText( viewDocument, 'நிலைக்கு' );
 
 

+ 8 - 3
packages/ckeditor5-engine/tests/model/utils/deletecontent.js

@@ -941,10 +941,15 @@ describe( 'DataController utils', () => {
 					{ rootName: 'bodyRoot' }
 					{ rootName: 'bodyRoot' }
 				);
 				);
 
 
-				deleteContent( model, doc.selection, { doNotAutoparagraph: true } );
+				// This must be tested inside a change block to check results before the post-fixers get triggered.
+				model.change( () => {
+					deleteContent( model, doc.selection, { doNotAutoparagraph: true } );
 
 
-				expect( getData( model, { rootName: 'bodyRoot' } ) )
-					.to.equal( '[]' );
+					expect( getData( model, { rootName: 'bodyRoot' } ) ).to.equal( '[]' );
+				} );
+
+				// Note that auto-paragraphing post-fixer injected a paragraph into the empty root.
+				expect( getData( model, { rootName: 'bodyRoot' } ) ).to.equal( '<paragraph>[]</paragraph>' );
 			} );
 			} );
 		} );
 		} );
 
 

+ 2 - 1
packages/ckeditor5-engine/tests/model/utils/selection-post-fixer.js

@@ -75,7 +75,8 @@ describe( 'Selection post-fixer', () => {
 		it( 'should not crash if there is no correct position for model selection', () => {
 		it( 'should not crash if there is no correct position for model selection', () => {
 			setModelData( model, '' );
 			setModelData( model, '' );
 
 
-			expect( getModelData( model ) ).to.equal( '[]' );
+			// Note that auto-paragraphing post-fixer injected a paragraph into the empty root.
+			expect( getModelData( model ) ).to.equal( '<paragraph>[]</paragraph>' );
 		} );
 		} );
 
 
 		it( 'should react to structure changes', () => {
 		it( 'should react to structure changes', () => {

+ 1 - 1
packages/ckeditor5-enter/src/shiftenter.js

@@ -51,7 +51,7 @@ export default class ShiftEnter extends Plugin {
 		conversion.for( 'downcast' )
 		conversion.for( 'downcast' )
 			.elementToElement( {
 			.elementToElement( {
 				model: 'softBreak',
 				model: 'softBreak',
-				view: ( modelElement, viewWriter ) => viewWriter.createEmptyElement( 'br' )
+				view: ( modelElement, { writer } ) => writer.createEmptyElement( 'br' )
 			} );
 			} );
 
 
 		view.addObserver( EnterObserver );
 		view.addObserver( EnterObserver );

+ 4 - 4
packages/ckeditor5-font/lang/translations/ko.po

@@ -38,7 +38,7 @@ msgstr "매우 큰"
 
 
 msgctxt "Tooltip for the font family dropdown."
 msgctxt "Tooltip for the font family dropdown."
 msgid "Font Family"
 msgid "Font Family"
-msgstr "글꼴"
+msgstr "글꼴 집합"
 
 
 msgctxt "Dropdown option label for the default font family."
 msgctxt "Dropdown option label for the default font family."
 msgid "Default"
 msgid "Default"
@@ -46,12 +46,12 @@ msgstr "기본"
 
 
 msgctxt "Label of a button that allows selecting a font color."
 msgctxt "Label of a button that allows selecting a font color."
 msgid "Font Color"
 msgid "Font Color"
-msgstr "글자 색"
+msgstr "글자 색"
 
 
 msgctxt "Label of a button that allows selecting a font background color."
 msgctxt "Label of a button that allows selecting a font background color."
 msgid "Font Background Color"
 msgid "Font Background Color"
-msgstr "글자 배경색"
+msgstr "글자 배경 "
 
 
 msgctxt "Title of a color picker section containing the colors currently used in the document."
 msgctxt "Title of a color picker section containing the colors currently used in the document."
 msgid "Document colors"
 msgid "Document colors"
-msgstr "문서 색들"
+msgstr "문서 색들"

+ 1 - 1
packages/ckeditor5-font/lang/translations/zh.po

@@ -54,4 +54,4 @@ msgstr "前景顏色"
 
 
 msgctxt "Title of a color picker section containing the colors currently used in the document."
 msgctxt "Title of a color picker section containing the colors currently used in the document."
 msgid "Document colors"
 msgid "Document colors"
-msgstr ""
+msgstr "文件顏色"

+ 1 - 1
packages/ckeditor5-font/src/fontfamily/fontfamilyediting.js

@@ -92,7 +92,7 @@ export default class FontFamilyEditing extends Plugin {
 
 
 		editor.conversion.for( 'downcast' ).attributeToElement( {
 		editor.conversion.for( 'downcast' ).attributeToElement( {
 			model: FONT_FAMILY,
 			model: FONT_FAMILY,
-			view: ( attributeValue, writer ) => {
+			view: ( attributeValue, { writer } ) => {
 				return writer.createAttributeElement( 'span', { style: 'font-family:' + attributeValue }, { priority: 7 } );
 				return writer.createAttributeElement( 'span', { style: 'font-family:' + attributeValue }, { priority: 7 } );
 			}
 			}
 		} );
 		} );

+ 1 - 1
packages/ckeditor5-font/src/fontsize/fontsizeediting.js

@@ -117,7 +117,7 @@ export default class FontSizeEditing extends Plugin {
 
 
 		editor.conversion.for( 'downcast' ).attributeToElement( {
 		editor.conversion.for( 'downcast' ).attributeToElement( {
 			model: FONT_SIZE,
 			model: FONT_SIZE,
-			view: ( attributeValue, writer ) => {
+			view: ( attributeValue, { writer } ) => {
 				if ( !attributeValue ) {
 				if ( !attributeValue ) {
 					return;
 					return;
 				}
 				}

+ 1 - 1
packages/ckeditor5-font/src/utils.js

@@ -82,7 +82,7 @@ export function renderUpcastAttribute( styleAttr ) {
  * @param {String} styleAttr
  * @param {String} styleAttr
  */
  */
 export function renderDowncastElement( styleAttr ) {
 export function renderDowncastElement( styleAttr ) {
-	return ( modelAttributeValue, viewWriter ) => viewWriter.createAttributeElement( 'span', {
+	return ( modelAttributeValue, { writer } ) => writer.createAttributeElement( 'span', {
 		style: `${ styleAttr }:${ modelAttributeValue }`
 		style: `${ styleAttr }:${ modelAttributeValue }`
 	}, { priority: 7 } );
 	}, { priority: 7 } );
 }
 }

+ 1 - 1
packages/ckeditor5-font/tests/utils.js

@@ -60,7 +60,7 @@ describe( 'utils', () => {
 			const fake = testUtils.sinon.fake();
 			const fake = testUtils.sinon.fake();
 			const fakeViewWriter = { createAttributeElement: fake };
 			const fakeViewWriter = { createAttributeElement: fake };
 
 
-			downcastViewConverterFn( 'blue', fakeViewWriter );
+			downcastViewConverterFn( 'blue', { writer: fakeViewWriter } );
 
 
 			sinon.assert.calledWithExactly( fake, 'span', { style: 'color:blue' }, { priority: 7 } );
 			sinon.assert.calledWithExactly( fake, 'span', { style: 'color:blue' }, { priority: 7 } );
 		} );
 		} );

+ 7 - 7
packages/ckeditor5-heading/lang/translations/ko.po

@@ -30,31 +30,31 @@ msgstr "제목 선택"
 
 
 msgctxt "Dropdown option label for the heading level 1 format."
 msgctxt "Dropdown option label for the heading level 1 format."
 msgid "Heading 1"
 msgid "Heading 1"
-msgstr "제목1"
+msgstr "제목 1"
 
 
 msgctxt "Dropdown option label for the heading level 2 format."
 msgctxt "Dropdown option label for the heading level 2 format."
 msgid "Heading 2"
 msgid "Heading 2"
-msgstr "제목2"
+msgstr "제목 2"
 
 
 msgctxt "Dropdown option label for the heading level 3 format."
 msgctxt "Dropdown option label for the heading level 3 format."
 msgid "Heading 3"
 msgid "Heading 3"
-msgstr "제목3"
+msgstr "제목 3"
 
 
 msgctxt "Dropdown option label for the heading level 4 format."
 msgctxt "Dropdown option label for the heading level 4 format."
 msgid "Heading 4"
 msgid "Heading 4"
-msgstr "제목4"
+msgstr "제목 4"
 
 
 msgctxt "Dropdown option label for the heading level 5 format."
 msgctxt "Dropdown option label for the heading level 5 format."
 msgid "Heading 5"
 msgid "Heading 5"
-msgstr "제목5"
+msgstr "제목 5"
 
 
 msgctxt "Dropdown option label for the heading level 6 format."
 msgctxt "Dropdown option label for the heading level 6 format."
 msgid "Heading 6"
 msgid "Heading 6"
-msgstr "제목6"
+msgstr "제목 6"
 
 
 msgctxt "A default value of the placeholder for the content title."
 msgctxt "A default value of the placeholder for the content title."
 msgid "Type your title"
 msgid "Type your title"
-msgstr "제목 입력"
+msgstr "제목 입력해주세요"
 
 
 msgctxt "A default value of the placeholder for the content body."
 msgctxt "A default value of the placeholder for the content body."
 msgid "Type or paste your content here."
 msgid "Type or paste your content here."

+ 2 - 2
packages/ckeditor5-heading/lang/translations/zh.po

@@ -54,8 +54,8 @@ msgstr "標題 6"
 
 
 msgctxt "A default value of the placeholder for the content title."
 msgctxt "A default value of the placeholder for the content title."
 msgid "Type your title"
 msgid "Type your title"
-msgstr ""
+msgstr "輸入你的標題"
 
 
 msgctxt "A default value of the placeholder for the content body."
 msgctxt "A default value of the placeholder for the content body."
 msgid "Type or paste your content here."
 msgid "Type or paste your content here."
-msgstr ""
+msgstr "在此輸入或貼上你的內容。"

+ 53 - 0
packages/ckeditor5-highlight/lang/translations/ko.po

@@ -0,0 +1,53 @@
+# 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 applying yellow marker (text highlight)."
+msgid "Yellow marker"
+msgstr "노란색 마커"
+
+msgctxt "Toolbar button tooltip for applying green marker (text highlight)."
+msgid "Green marker"
+msgstr "초록색 마커"
+
+msgctxt "Toolbar button tooltip for applying pink marker (text highlight)."
+msgid "Pink marker"
+msgstr "분홍색 마커"
+
+msgctxt "Toolbar button tooltip for applying blue marker (text highlight)."
+msgid "Blue marker"
+msgstr "파란색 마커"
+
+msgctxt "Toolbar button tooltip for applying red pen (text color)."
+msgid "Red pen"
+msgstr "빨간색 펜"
+
+msgctxt "Toolbar button tooltip for applying green pen (text color)."
+msgid "Green pen"
+msgstr "초록색 펜"
+
+msgctxt "Toolbar button tooltip for removing text highlight."
+msgid "Remove highlight"
+msgstr "강조 제거"
+
+msgctxt "Toolbar button tooltip for the text highlight feature."
+msgid "Highlight"
+msgstr "강조"
+
+msgctxt "Label used by assistive technologies describing the highlight feature toolbar."
+msgid "Text highlight toolbar"
+msgstr "글자 강조 툴바"

+ 1 - 1
packages/ckeditor5-highlight/lang/translations/zh.po

@@ -50,4 +50,4 @@ msgstr "高亮"
 
 
 msgctxt "Label used by assistive technologies describing the highlight feature toolbar."
 msgctxt "Label used by assistive technologies describing the highlight feature toolbar."
 msgid "Text highlight toolbar"
 msgid "Text highlight toolbar"
-msgstr ""
+msgstr "高亮"

+ 21 - 0
packages/ckeditor5-horizontal-line/lang/translations/zh.po

@@ -0,0 +1,21 @@
+# 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 "Horizontal line"
+msgid "Horizontal line"
+msgstr "水平線"

+ 9 - 9
packages/ckeditor5-horizontal-line/src/horizontallineediting.js

@@ -42,24 +42,24 @@ export default class HorizontalLineEditing extends Plugin {
 
 
 		conversion.for( 'dataDowncast' ).elementToElement( {
 		conversion.for( 'dataDowncast' ).elementToElement( {
 			model: 'horizontalLine',
 			model: 'horizontalLine',
-			view: ( modelElement, viewWriter ) => {
-				return viewWriter.createEmptyElement( 'hr' );
+			view: ( modelElement, { writer } ) => {
+				return writer.createEmptyElement( 'hr' );
 			}
 			}
 		} );
 		} );
 
 
 		conversion.for( 'editingDowncast' ).elementToElement( {
 		conversion.for( 'editingDowncast' ).elementToElement( {
 			model: 'horizontalLine',
 			model: 'horizontalLine',
-			view: ( modelElement, viewWriter ) => {
+			view: ( modelElement, { writer } ) => {
 				const label = t( 'Horizontal line' );
 				const label = t( 'Horizontal line' );
-				const viewWrapper = viewWriter.createContainerElement( 'div' );
-				const viewHrElement = viewWriter.createEmptyElement( 'hr' );
+				const viewWrapper = writer.createContainerElement( 'div' );
+				const viewHrElement = writer.createEmptyElement( 'hr' );
 
 
-				viewWriter.addClass( 'ck-horizontal-line', viewWrapper );
-				viewWriter.setCustomProperty( 'hr', true, viewWrapper );
+				writer.addClass( 'ck-horizontal-line', viewWrapper );
+				writer.setCustomProperty( 'hr', true, viewWrapper );
 
 
-				viewWriter.insert( viewWriter.createPositionAt( viewWrapper, 0 ), viewHrElement );
+				writer.insert( writer.createPositionAt( viewWrapper, 0 ), viewHrElement );
 
 
-				return toHorizontalLineWidget( viewWrapper, viewWriter, label );
+				return toHorizontalLineWidget( viewWrapper, writer, label );
 			}
 			}
 		} );
 		} );
 
 

+ 7 - 7
packages/ckeditor5-image/lang/translations/de.po

@@ -42,11 +42,11 @@ msgstr "rechtsbündiges Bild"
 
 
 msgctxt "Label for the Change image text alternative button."
 msgctxt "Label for the Change image text alternative button."
 msgid "Change image text alternative"
 msgid "Change image text alternative"
-msgstr "Alternativ Text ändern"
+msgstr "Alternativtext ändern"
 
 
 msgctxt "Label for the image text alternative input."
 msgctxt "Label for the image text alternative input."
 msgid "Text alternative"
 msgid "Text alternative"
-msgstr "Textalternative"
+msgstr "Alternativtext"
 
 
 msgctxt "Placeholder text for image caption displayed when caption is empty."
 msgctxt "Placeholder text for image caption displayed when caption is empty."
 msgid "Enter image caption"
 msgid "Enter image caption"
@@ -66,20 +66,20 @@ msgstr "Bild Werkzeugleiste"
 
 
 msgctxt "The label used for the dropdown in the image toolbar containing defined resize options"
 msgctxt "The label used for the dropdown in the image toolbar containing defined resize options"
 msgid "Resize image"
 msgid "Resize image"
-msgstr ""
+msgstr "Bildgröße ändern"
 
 
 msgctxt "The label used for the standalone resize options buttons in the image toolbar"
 msgctxt "The label used for the standalone resize options buttons in the image toolbar"
 msgid "Resize image to %0"
 msgid "Resize image to %0"
-msgstr ""
+msgstr "Bildgröße ändern in %0"
 
 
 msgctxt "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers"
 msgctxt "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers"
 msgid "Resize image to the original size"
 msgid "Resize image to the original size"
-msgstr ""
+msgstr "Bild in Originalgröße ändern"
 
 
 msgctxt "Default label for the resize option that resets the size of the image."
 msgctxt "Default label for the resize option that resets the size of the image."
 msgid "Original"
 msgid "Original"
-msgstr ""
+msgstr "Original"
 
 
 msgctxt "The accessibility label of the image resize dropdown list for the screen readers."
 msgctxt "The accessibility label of the image resize dropdown list for the screen readers."
 msgid "Image resize list"
 msgid "Image resize list"
-msgstr ""
+msgstr "Bildgrößen-Liste"

+ 13 - 13
packages/ckeditor5-image/lang/translations/ko.po

@@ -18,15 +18,15 @@ msgstr ""
 
 
 msgctxt "Label for the image widget."
 msgctxt "Label for the image widget."
 msgid "image widget"
 msgid "image widget"
-msgstr "이미지 위젯"
+msgstr "사진 위젯"
 
 
 msgctxt "Label for the Side image option."
 msgctxt "Label for the Side image option."
 msgid "Side image"
 msgid "Side image"
-msgstr "내부 우측 정렬"
+msgstr "본문 옆에 배치"
 
 
 msgctxt "Label for the Full size image option."
 msgctxt "Label for the Full size image option."
 msgid "Full size image"
 msgid "Full size image"
-msgstr "문서 너비"
+msgstr "꽉 찬 크기"
 
 
 msgctxt "Label for the Left aligned image option"
 msgctxt "Label for the Left aligned image option"
 msgid "Left aligned image"
 msgid "Left aligned image"
@@ -42,19 +42,19 @@ msgstr "오른쪽 정렬"
 
 
 msgctxt "Label for the Change image text alternative button."
 msgctxt "Label for the Change image text alternative button."
 msgid "Change image text alternative"
 msgid "Change image text alternative"
-msgstr "대체 텍스트 변경"
+msgstr "대체 문구 변경"
 
 
 msgctxt "Label for the image text alternative input."
 msgctxt "Label for the image text alternative input."
 msgid "Text alternative"
 msgid "Text alternative"
-msgstr "대체 텍스트"
+msgstr "대체 문구"
 
 
 msgctxt "Placeholder text for image caption displayed when caption is empty."
 msgctxt "Placeholder text for image caption displayed when caption is empty."
 msgid "Enter image caption"
 msgid "Enter image caption"
-msgstr "이미지 설명을 입력하세요"
+msgstr "사진 설명을 입력하세요"
 
 
 msgctxt "Label for the insert image toolbar button."
 msgctxt "Label for the insert image toolbar button."
 msgid "Insert image"
 msgid "Insert image"
-msgstr "이미지 삽입"
+msgstr "사진 삽입"
 
 
 msgctxt "Title of the notification displayed when upload fails."
 msgctxt "Title of the notification displayed when upload fails."
 msgid "Upload failed"
 msgid "Upload failed"
@@ -62,24 +62,24 @@ msgstr "업로드 실패"
 
 
 msgctxt "The label used by assistive technologies describing an image toolbar attached to an image widget."
 msgctxt "The label used by assistive technologies describing an image toolbar attached to an image widget."
 msgid "Image toolbar"
 msgid "Image toolbar"
-msgstr "이미지 툴바"
+msgstr "사진 툴바"
 
 
 msgctxt "The label used for the dropdown in the image toolbar containing defined resize options"
 msgctxt "The label used for the dropdown in the image toolbar containing defined resize options"
 msgid "Resize image"
 msgid "Resize image"
-msgstr ""
+msgstr "사진 크기 조절"
 
 
 msgctxt "The label used for the standalone resize options buttons in the image toolbar"
 msgctxt "The label used for the standalone resize options buttons in the image toolbar"
 msgid "Resize image to %0"
 msgid "Resize image to %0"
-msgstr ""
+msgstr "사진의 크기를 %0으로 조절"
 
 
 msgctxt "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers"
 msgctxt "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers"
 msgid "Resize image to the original size"
 msgid "Resize image to the original size"
-msgstr ""
+msgstr "사진을 원래 크기로 돌려놓기"
 
 
 msgctxt "Default label for the resize option that resets the size of the image."
 msgctxt "Default label for the resize option that resets the size of the image."
 msgid "Original"
 msgid "Original"
-msgstr ""
+msgstr "원본"
 
 
 msgctxt "The accessibility label of the image resize dropdown list for the screen readers."
 msgctxt "The accessibility label of the image resize dropdown list for the screen readers."
 msgid "Image resize list"
 msgid "Image resize list"
-msgstr ""
+msgstr "사진 크기 목록"

+ 5 - 5
packages/ckeditor5-image/lang/translations/sr-latn.po

@@ -66,20 +66,20 @@ msgstr "Slika traka sa alatkama"
 
 
 msgctxt "The label used for the dropdown in the image toolbar containing defined resize options"
 msgctxt "The label used for the dropdown in the image toolbar containing defined resize options"
 msgid "Resize image"
 msgid "Resize image"
-msgstr ""
+msgstr "Promenite veličinu slike"
 
 
 msgctxt "The label used for the standalone resize options buttons in the image toolbar"
 msgctxt "The label used for the standalone resize options buttons in the image toolbar"
 msgid "Resize image to %0"
 msgid "Resize image to %0"
-msgstr ""
+msgstr "Promenite veličinu slike na% 0"
 
 
 msgctxt "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers"
 msgctxt "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers"
 msgid "Resize image to the original size"
 msgid "Resize image to the original size"
-msgstr ""
+msgstr "Promenite veličinu slike do originalne veličine"
 
 
 msgctxt "Default label for the resize option that resets the size of the image."
 msgctxt "Default label for the resize option that resets the size of the image."
 msgid "Original"
 msgid "Original"
-msgstr ""
+msgstr "Original"
 
 
 msgctxt "The accessibility label of the image resize dropdown list for the screen readers."
 msgctxt "The accessibility label of the image resize dropdown list for the screen readers."
 msgid "Image resize list"
 msgid "Image resize list"
-msgstr ""
+msgstr "Lista veličine slike"

+ 5 - 5
packages/ckeditor5-image/lang/translations/sr.po

@@ -66,20 +66,20 @@ msgstr "Слика трака са алтакама"
 
 
 msgctxt "The label used for the dropdown in the image toolbar containing defined resize options"
 msgctxt "The label used for the dropdown in the image toolbar containing defined resize options"
 msgid "Resize image"
 msgid "Resize image"
-msgstr ""
+msgstr "Промените величину слике"
 
 
 msgctxt "The label used for the standalone resize options buttons in the image toolbar"
 msgctxt "The label used for the standalone resize options buttons in the image toolbar"
 msgid "Resize image to %0"
 msgid "Resize image to %0"
-msgstr ""
+msgstr "Промените величину слике на% 0"
 
 
 msgctxt "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers"
 msgctxt "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers"
 msgid "Resize image to the original size"
 msgid "Resize image to the original size"
-msgstr ""
+msgstr "Промените величину слике до оригиналне величине"
 
 
 msgctxt "Default label for the resize option that resets the size of the image."
 msgctxt "Default label for the resize option that resets the size of the image."
 msgid "Original"
 msgid "Original"
-msgstr ""
+msgstr "Оригинал"
 
 
 msgctxt "The accessibility label of the image resize dropdown list for the screen readers."
 msgctxt "The accessibility label of the image resize dropdown list for the screen readers."
 msgid "Image resize list"
 msgid "Image resize list"
-msgstr ""
+msgstr "Листа величине слике"

+ 6 - 6
packages/ckeditor5-image/lang/translations/zh.po

@@ -62,24 +62,24 @@ msgstr "上傳失敗"
 
 
 msgctxt "The label used by assistive technologies describing an image toolbar attached to an image widget."
 msgctxt "The label used by assistive technologies describing an image toolbar attached to an image widget."
 msgid "Image toolbar"
 msgid "Image toolbar"
-msgstr ""
+msgstr "圖片工具"
 
 
 msgctxt "The label used for the dropdown in the image toolbar containing defined resize options"
 msgctxt "The label used for the dropdown in the image toolbar containing defined resize options"
 msgid "Resize image"
 msgid "Resize image"
-msgstr ""
+msgstr "縮放圖片"
 
 
 msgctxt "The label used for the standalone resize options buttons in the image toolbar"
 msgctxt "The label used for the standalone resize options buttons in the image toolbar"
 msgid "Resize image to %0"
 msgid "Resize image to %0"
-msgstr ""
+msgstr "縮放圖片到 %0"
 
 
 msgctxt "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers"
 msgctxt "The accessibility label of the standalone image resize reset option button in the image toolbar for the screen readers"
 msgid "Resize image to the original size"
 msgid "Resize image to the original size"
-msgstr ""
+msgstr "縮放圖片到原始尺寸"
 
 
 msgctxt "Default label for the resize option that resets the size of the image."
 msgctxt "Default label for the resize option that resets the size of the image."
 msgid "Original"
 msgid "Original"
-msgstr ""
+msgstr "原始圖片"
 
 
 msgctxt "The accessibility label of the image resize dropdown list for the screen readers."
 msgctxt "The accessibility label of the image resize dropdown list for the screen readers."
 msgid "Image resize list"
 msgid "Image resize list"
-msgstr ""
+msgstr "圖片縮放清單"

+ 3 - 3
packages/ckeditor5-image/src/image/imageediting.js

@@ -61,12 +61,12 @@ export default class ImageEditing extends Plugin {
 
 
 		conversion.for( 'dataDowncast' ).elementToElement( {
 		conversion.for( 'dataDowncast' ).elementToElement( {
 			model: 'image',
 			model: 'image',
-			view: ( modelElement, viewWriter ) => createImageViewElement( viewWriter )
+			view: ( modelElement, { writer } ) => createImageViewElement( writer )
 		} );
 		} );
 
 
 		conversion.for( 'editingDowncast' ).elementToElement( {
 		conversion.for( 'editingDowncast' ).elementToElement( {
 			model: 'image',
 			model: 'image',
-			view: ( modelElement, viewWriter ) => toImageWidget( createImageViewElement( viewWriter ), viewWriter, t( 'image widget' ) )
+			view: ( modelElement, { writer } ) => toImageWidget( createImageViewElement( writer ), writer, t( 'image widget' ) )
 		} );
 		} );
 
 
 		conversion.for( 'downcast' )
 		conversion.for( 'downcast' )
@@ -82,7 +82,7 @@ export default class ImageEditing extends Plugin {
 						src: true
 						src: true
 					}
 					}
 				},
 				},
-				model: ( viewImage, modelWriter ) => modelWriter.createElement( 'image', { src: viewImage.getAttribute( 'src' ) } )
+				model: ( viewImage, { writer } ) => writer.createElement( 'image', { src: viewImage.getAttribute( 'src' ) } )
 			} )
 			} )
 			.attributeToAttribute( {
 			.attributeToAttribute( {
 				view: {
 				view: {

+ 3 - 3
packages/ckeditor5-image/tests/image/converters.js

@@ -37,8 +37,8 @@ describe( 'Image converters', () => {
 					isBlock: true
 					isBlock: true
 				} );
 				} );
 
 
-				const editingElementCreator = ( modelElement, viewWriter ) =>
-					toImageWidget( createImageViewElement( viewWriter ), viewWriter, '' );
+				const editingElementCreator = ( modelElement, { writer } ) =>
+					toImageWidget( createImageViewElement( writer ), writer, '' );
 
 
 				editor.conversion.for( 'editingDowncast' ).elementToElement( {
 				editor.conversion.for( 'editingDowncast' ).elementToElement( {
 					model: 'image',
 					model: 'image',
@@ -75,7 +75,7 @@ describe( 'Image converters', () => {
 							src: true
 							src: true
 						}
 						}
 					},
 					},
-					model: ( viewImage, writer ) => {
+					model: ( viewImage, { writer } ) => {
 						imgConverterCalled = true;
 						imgConverterCalled = true;
 
 
 						return writer.createElement( 'image', { src: viewImage.getAttribute( 'src' ) } );
 						return writer.createElement( 'image', { src: viewImage.getAttribute( 'src' ) } );

+ 3 - 4
packages/ckeditor5-image/tests/imagetoolbar.js

@@ -83,10 +83,9 @@ describe( 'ImageToolbar', () => {
 
 
 			setData( model, '[<image src=""></image>]' );
 			setData( model, '[<image src=""></image>]' );
 
 
-			sinon.assert.calledWithMatch( spy, {
-				view: toolbar,
-				balloonClassName: 'ck-toolbar-container'
-			} );
+			sinon.assert.calledWithMatch( spy, sinon.match( ( { balloonClassName, view } ) => {
+				return view === toolbar && balloonClassName === 'ck-toolbar-container';
+			} ) );
 		} );
 		} );
 
 
 		it( 'should set aria-label attribute', () => {
 		it( 'should set aria-label attribute', () => {

+ 2 - 2
packages/ckeditor5-indent/lang/translations/ko.po

@@ -18,8 +18,8 @@ msgstr ""
 
 
 msgctxt "Toolbar button tooltip for the increase indentation feature."
 msgctxt "Toolbar button tooltip for the increase indentation feature."
 msgid "Increase indent"
 msgid "Increase indent"
-msgstr "들여쓰기"
+msgstr "들여쓰기 늘리기"
 
 
 msgctxt "Toolbar button tooltip for the decrease indentation feature."
 msgctxt "Toolbar button tooltip for the decrease indentation feature."
 msgid "Decrease indent"
 msgid "Decrease indent"
-msgstr "내어쓰기"
+msgstr "들여쓰기 줄이기"

+ 1 - 1
packages/ckeditor5-link/lang/translations/ko.po

@@ -30,7 +30,7 @@ msgstr "링크 주소"
 
 
 msgctxt "Label for the image link button."
 msgctxt "Label for the image link button."
 msgid "Link image"
 msgid "Link image"
-msgstr ""
+msgstr "사진 링크"
 
 
 msgctxt "Button opening the Link URL editing balloon."
 msgctxt "Button opening the Link URL editing balloon."
 msgid "Edit link"
 msgid "Edit link"

+ 1 - 1
packages/ckeditor5-link/lang/translations/zh.po

@@ -30,7 +30,7 @@ msgstr "連結˙ URL"
 
 
 msgctxt "Label for the image link button."
 msgctxt "Label for the image link button."
 msgid "Link image"
 msgid "Link image"
-msgstr ""
+msgstr "圖片連結"
 
 
 msgctxt "Button opening the Link URL editing balloon."
 msgctxt "Button opening the Link URL editing balloon."
 msgid "Edit link"
 msgid "Edit link"

+ 3 - 3
packages/ckeditor5-link/src/linkediting.js

@@ -75,8 +75,8 @@ export default class LinkEditing extends Plugin {
 			.attributeToElement( { model: 'linkHref', view: createLinkElement } );
 			.attributeToElement( { model: 'linkHref', view: createLinkElement } );
 
 
 		editor.conversion.for( 'editingDowncast' )
 		editor.conversion.for( 'editingDowncast' )
-			.attributeToElement( { model: 'linkHref', view: ( href, writer ) => {
-				return createLinkElement( ensureSafeUrl( href ), writer );
+			.attributeToElement( { model: 'linkHref', view: ( href, conversionApi ) => {
+				return createLinkElement( ensureSafeUrl( href ), conversionApi );
 			} } );
 			} } );
 
 
 		editor.conversion.for( 'upcast' )
 		editor.conversion.for( 'upcast' )
@@ -190,7 +190,7 @@ export default class LinkEditing extends Plugin {
 
 
 			editor.conversion.for( 'downcast' ).attributeToElement( {
 			editor.conversion.for( 'downcast' ).attributeToElement( {
 				model: decorator.id,
 				model: decorator.id,
-				view: ( manualDecoratorName, writer ) => {
+				view: ( manualDecoratorName, { writer } ) => {
 					if ( manualDecoratorName ) {
 					if ( manualDecoratorName ) {
 						const attributes = manualDecorators.get( decorator.id ).attributes;
 						const attributes = manualDecorators.get( decorator.id ).attributes;
 						const element = writer.createAttributeElement( 'a', attributes, { priority: 5 } );
 						const element = writer.createAttributeElement( 'a', attributes, { priority: 5 } );

+ 2 - 1
packages/ckeditor5-link/src/utils.js

@@ -31,9 +31,10 @@ export function isLinkElement( node ) {
  * Creates link {@link module:engine/view/attributeelement~AttributeElement} with the provided `href` attribute.
  * Creates link {@link module:engine/view/attributeelement~AttributeElement} with the provided `href` attribute.
  *
  *
  * @param {String} href
  * @param {String} href
+ * @param {module:engine/conversion/downcastdispatcher~DowncastConversionApi} conversionApi
  * @returns {module:engine/view/attributeelement~AttributeElement}
  * @returns {module:engine/view/attributeelement~AttributeElement}
  */
  */
-export function createLinkElement( href, writer ) {
+export function createLinkElement( href, { writer } ) {
 	// Priority 5 - https://github.com/ckeditor/ckeditor5-link/issues/121.
 	// Priority 5 - https://github.com/ckeditor/ckeditor5-link/issues/121.
 	const linkElement = writer.createAttributeElement( 'a', { href }, { priority: 5 } );
 	const linkElement = writer.createAttributeElement( 'a', { href }, { priority: 5 } );
 	writer.setCustomProperty( 'link', true, linkElement );
 	writer.setCustomProperty( 'link', true, linkElement );

+ 4 - 2
packages/ckeditor5-link/tests/utils.js

@@ -15,7 +15,8 @@ import { createLinkElement, isLinkElement, ensureSafeUrl, normalizeDecorators, i
 describe( 'utils', () => {
 describe( 'utils', () => {
 	describe( 'isLinkElement()', () => {
 	describe( 'isLinkElement()', () => {
 		it( 'should return true for elements created by createLinkElement', () => {
 		it( 'should return true for elements created by createLinkElement', () => {
-			const element = createLinkElement( 'http://ckeditor.com', new ViewDowncastWriter( new ViewDocument() ) );
+			const writer = new ViewDowncastWriter( new ViewDocument() );
+			const element = createLinkElement( 'http://ckeditor.com', { writer } );
 
 
 			expect( isLinkElement( element ) ).to.be.true;
 			expect( isLinkElement( element ) ).to.be.true;
 		} );
 		} );
@@ -35,7 +36,8 @@ describe( 'utils', () => {
 
 
 	describe( 'createLinkElement()', () => {
 	describe( 'createLinkElement()', () => {
 		it( 'should create link AttributeElement', () => {
 		it( 'should create link AttributeElement', () => {
-			const element = createLinkElement( 'http://cksource.com', new ViewDowncastWriter( new ViewDocument() ) );
+			const writer = new ViewDowncastWriter( new ViewDocument() );
+			const element = createLinkElement( 'http://cksource.com', { writer } );
 
 
 			expect( isLinkElement( element ) ).to.be.true;
 			expect( isLinkElement( element ) ).to.be.true;
 			expect( element.priority ).to.equal( 5 );
 			expect( element.priority ).to.equal( 5 );

+ 3 - 3
packages/ckeditor5-list/lang/translations/ko.po

@@ -18,12 +18,12 @@ msgstr ""
 
 
 msgctxt "Toolbar button tooltip for the Numbered List feature."
 msgctxt "Toolbar button tooltip for the Numbered List feature."
 msgid "Numbered List"
 msgid "Numbered List"
-msgstr "번호매기기"
+msgstr "번호 목록"
 
 
 msgctxt "Toolbar button tooltip for the Bulleted List feature."
 msgctxt "Toolbar button tooltip for the Bulleted List feature."
 msgid "Bulleted List"
 msgid "Bulleted List"
-msgstr "글머리기호"
+msgstr "불릿 목록"
 
 
 msgctxt "Toolbar button tooltip for the To-do List feature."
 msgctxt "Toolbar button tooltip for the To-do List feature."
 msgid "To-do List"
 msgid "To-do List"
-msgstr "할일 목록"
+msgstr "확인 목록"

+ 1 - 1
packages/ckeditor5-list/lang/translations/zh.po

@@ -26,4 +26,4 @@ msgstr "符號清單"
 
 
 msgctxt "Toolbar button tooltip for the To-do List feature."
 msgctxt "Toolbar button tooltip for the To-do List feature."
 msgid "To-do List"
 msgid "To-do List"
-msgstr ""
+msgstr "代辦清單"

+ 2 - 2
packages/ckeditor5-list/tests/todolistediting.js

@@ -559,12 +559,12 @@ describe( 'TodoListEditing', () => {
 
 
 			editor.conversion.for( 'downcast' ).markerToElement( {
 			editor.conversion.for( 'downcast' ).markerToElement( {
 				model: 'element1',
 				model: 'element1',
-				view: ( data, writer ) => writer.createUIElement( 'element1' )
+				view: ( data, { writer } ) => writer.createUIElement( 'element1' )
 			} );
 			} );
 
 
 			editor.conversion.for( 'downcast' ).markerToElement( {
 			editor.conversion.for( 'downcast' ).markerToElement( {
 				model: 'element2',
 				model: 'element2',
-				view: ( data, writer ) => writer.createUIElement( 'element2' )
+				view: ( data, { writer } ) => writer.createUIElement( 'element2' )
 			} );
 			} );
 
 
 			editor.conversion.for( 'downcast' ).markerToHighlight( {
 			editor.conversion.for( 'downcast' ).markerToHighlight( {

+ 4 - 1
packages/ckeditor5-markdown-gfm/package.json

@@ -10,7 +10,10 @@
     "ckeditor5-plugin"
     "ckeditor5-plugin"
   ],
   ],
   "dependencies": {
   "dependencies": {
-    "@ckeditor/ckeditor5-engine": "^21.0.0"
+    "@ckeditor/ckeditor5-engine": "^21.0.0",
+    "marked": "^0.7.0",
+    "turndown": "^6.0.0",
+    "turndown-plugin-gfm": "^1.0.2"
   },
   },
   "engines": {
   "engines": {
     "node": ">=12.0.0",
     "node": ">=12.0.0",

+ 17 - 14
packages/ckeditor5-markdown-gfm/src/gfmdataprocessor.js

@@ -7,11 +7,10 @@
  * @module markdown-gfm/gfmdataprocessor
  * @module markdown-gfm/gfmdataprocessor
  */
  */
 
 
-import marked from './lib/marked/marked';
-import toMarkdown from './lib/to-markdown/to-markdown';
 import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
 import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
-import GFMRenderer from './lib/marked/renderer';
-import converters from './lib/to-markdown/converters';
+
+import markdown2html from './markdown2html/markdown2html';
+import html2markdown, { turndownService } from './html2markdown/html2markdown';
 
 
 /**
 /**
  * This data processor implementation uses GitHub Flavored Markdown as input/output data.
  * This data processor implementation uses GitHub Flavored Markdown as input/output data.
@@ -36,6 +35,18 @@ export default class GFMDataProcessor {
 		this._htmlDP = new HtmlDataProcessor( document );
 		this._htmlDP = new HtmlDataProcessor( document );
 	}
 	}
 
 
+	/**
+	 * Keeps the specified element in the output as HTML. This is useful if the editor contains
+	 * features that produce HTML that are not part of the markdon standards.
+	 *
+	 * By default, all HTML tags are removed.
+	 *
+	 * @param element {String} The element name to be kept.
+	 */
+	keepHtml( element ) {
+		turndownService.keep( [ element ] );
+	}
+
 	/**
 	/**
 	 * Converts the provided Markdown string to view tree.
 	 * Converts the provided Markdown string to view tree.
 	 *
 	 *
@@ -43,14 +54,7 @@ export default class GFMDataProcessor {
 	 * @returns {module:engine/view/documentfragment~DocumentFragment} The converted view element.
 	 * @returns {module:engine/view/documentfragment~DocumentFragment} The converted view element.
 	 */
 	 */
 	toView( data ) {
 	toView( data ) {
-		const html = marked.parse( data, {
-			gfm: true,
-			breaks: true,
-			tables: true,
-			xhtml: true,
-			renderer: new GFMRenderer()
-		} );
-
+		const html = markdown2html( data );
 		return this._htmlDP.toView( html );
 		return this._htmlDP.toView( html );
 	}
 	}
 
 
@@ -63,7 +67,6 @@ export default class GFMDataProcessor {
 	 */
 	 */
 	toData( viewFragment ) {
 	toData( viewFragment ) {
 		const html = this._htmlDP.toData( viewFragment );
 		const html = this._htmlDP.toData( viewFragment );
-
-		return toMarkdown( html, { gfm: true, converters } );
+		return html2markdown( html );
 	}
 	}
 }
 }

+ 93 - 0
packages/ckeditor5-markdown-gfm/src/html2markdown/html2markdown.js

@@ -0,0 +1,93 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module markdown-gfm/html2markdown
+ */
+
+import TurndownService from 'turndown';
+import { gfm } from 'turndown-plugin-gfm';
+
+// Override the original escape method by not escaping links.
+const originalEscape = TurndownService.prototype.escape;
+
+function escape( string ) {
+	string = originalEscape( string );
+
+	// Escape "<".
+	string = string.replace( /</g, '\\<' );
+
+	return string;
+}
+
+// eslint-disable-next-line max-len
+const regex = /\b(?:https?:\/\/|www\.)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()[\]{};:'".,<>?«»“”‘’])/g;
+
+TurndownService.prototype.escape = function( string ) {
+	// Urls should not be escaped. Our strategy is using a regex to find them and escape everything
+	// which is out of the matches parts.
+
+	let escaped = '';
+	let lastLinkEnd = 0;
+
+	for ( const match of string.matchAll( regex ) ) {
+		const index = match.index;
+
+		// Append the substring between the last match and the current one (if anything).
+		if ( index > lastLinkEnd ) {
+			escaped += escape( string.substring( lastLinkEnd, index ) );
+		}
+
+		const matchedURL = match[ 0 ];
+
+		escaped += matchedURL;
+
+		lastLinkEnd = index + matchedURL.length;
+	}
+
+	// Add text after the last link or at the string start if no matches.
+	if ( lastLinkEnd < string.length ) {
+		escaped += escape( string.substring( lastLinkEnd, string.length ) );
+	}
+
+	return escaped;
+};
+
+const turndownService = new TurndownService( {
+	codeBlockStyle: 'fenced',
+	hr: '---',
+	headingStyle: 'atx'
+} );
+
+turndownService.use( [
+	gfm,
+	todoList
+] );
+
+/**
+ * Parses HTML to a markdown.
+ *
+ * @param {String} html
+ * @returns {String}
+ */
+export default function html2markdown( html ) {
+	return turndownService.turndown( html );
+}
+
+export { turndownService };
+
+// This is a copy of the original taskListItems rule from turdown-plugin-gfm, with minor changes.
+function todoList( turndownService ) {
+	turndownService.addRule( 'taskListItems', {
+		filter( node ) {
+			return node.type === 'checkbox' &&
+				// Changes here as CKEditor outputs a deeper structure.
+				( node.parentNode.nodeName === 'LI' || node.parentNode.parentNode.nodeName === 'LI' );
+		},
+		replacement( content, node ) {
+			return ( node.checked ? '[x]' : '[ ]' ) + ' ';
+		}
+	} );
+}

+ 0 - 1286
packages/ckeditor5-markdown-gfm/src/lib/marked/marked.js

@@ -1,1286 +0,0 @@
-/**
- * marked - a markdown parser
- * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
- * https://github.com/chjj/marked
- */
-
-;(function() {
-
-/**
- * Block-Level Grammar
- */
-
-var block = {
-  newline: /^\n+/,
-  code: /^( {4}[^\n]+\n*)+/,
-  fences: noop,
-  hr: /^( *[-*_]){3,} *(?:\n+|$)/,
-  heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
-  nptable: noop,
-  lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
-  blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,
-  list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
-  html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
-  def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
-  table: noop,
-  paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
-  text: /^[^\n]+/
-};
-
-block.bullet = /(?:[*+-]|\d+\.)/;
-block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
-block.item = replace(block.item, 'gm')
-  (/bull/g, block.bullet)
-  ();
-
-block.list = replace(block.list)
-  (/bull/g, block.bullet)
-  ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))')
-  ('def', '\\n+(?=' + block.def.source + ')')
-  ();
-
-block.blockquote = replace(block.blockquote)
-  ('def', block.def)
-  ();
-
-block._tag = '(?!(?:'
-  + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
-  + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
-  + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b';
-
-block.html = replace(block.html)
-  ('comment', /<!--[\s\S]*?-->/)
-  ('closed', /<(tag)[\s\S]+?<\/\1>/)
-  ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
-  (/tag/g, block._tag)
-  ();
-
-block.paragraph = replace(block.paragraph)
-  ('hr', block.hr)
-  ('heading', block.heading)
-  ('lheading', block.lheading)
-  ('blockquote', block.blockquote)
-  ('tag', '<' + block._tag)
-  ('def', block.def)
-  ();
-
-/**
- * Normal Block Grammar
- */
-
-block.normal = merge({}, block);
-
-/**
- * GFM Block Grammar
- */
-
-block.gfm = merge({}, block.normal, {
-  fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,
-  paragraph: /^/,
-  heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/
-});
-
-block.gfm.paragraph = replace(block.paragraph)
-  ('(?!', '(?!'
-    + block.gfm.fences.source.replace('\\1', '\\2') + '|'
-    + block.list.source.replace('\\1', '\\3') + '|')
-  ();
-
-/**
- * GFM + Tables Block Grammar
- */
-
-block.tables = merge({}, block.gfm, {
-  nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
-  table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
-});
-
-/**
- * Block Lexer
- */
-
-function Lexer(options) {
-  this.tokens = [];
-  this.tokens.links = {};
-  this.options = options || marked.defaults;
-  this.rules = block.normal;
-
-  if (this.options.gfm) {
-    if (this.options.tables) {
-      this.rules = block.tables;
-    } else {
-      this.rules = block.gfm;
-    }
-  }
-}
-
-/**
- * Expose Block Rules
- */
-
-Lexer.rules = block;
-
-/**
- * Static Lex Method
- */
-
-Lexer.lex = function(src, options) {
-  var lexer = new Lexer(options);
-  return lexer.lex(src);
-};
-
-/**
- * Preprocessing
- */
-
-Lexer.prototype.lex = function(src) {
-  src = src
-    .replace(/\r\n|\r/g, '\n')
-    .replace(/\t/g, '    ')
-    .replace(/\u00a0/g, ' ')
-    .replace(/\u2424/g, '\n');
-
-  return this.token(src, true);
-};
-
-/**
- * Lexing
- */
-
-Lexer.prototype.token = function(src, top, bq) {
-  var src = src.replace(/^ +$/gm, '')
-    , next
-    , loose
-    , cap
-    , bull
-    , b
-    , item
-    , space
-    , i
-    , l;
-
-  while (src) {
-    // newline
-    if (cap = this.rules.newline.exec(src)) {
-      src = src.substring(cap[0].length);
-      if (cap[0].length > 1) {
-        this.tokens.push({
-          type: 'space'
-        });
-      }
-    }
-
-    // code
-    if (cap = this.rules.code.exec(src)) {
-      src = src.substring(cap[0].length);
-      cap = cap[0].replace(/^ {4}/gm, '');
-      this.tokens.push({
-        type: 'code',
-        text: !this.options.pedantic
-          ? cap.replace(/\n+$/, '')
-          : cap
-      });
-      continue;
-    }
-
-    // fences (gfm)
-    if (cap = this.rules.fences.exec(src)) {
-      src = src.substring(cap[0].length);
-      this.tokens.push({
-        type: 'code',
-        lang: cap[2],
-        text: cap[3] || ''
-      });
-      continue;
-    }
-
-    // heading
-    if (cap = this.rules.heading.exec(src)) {
-      src = src.substring(cap[0].length);
-      this.tokens.push({
-        type: 'heading',
-        depth: cap[1].length,
-        text: cap[2]
-      });
-      continue;
-    }
-
-    // table no leading pipe (gfm)
-    if (top && (cap = this.rules.nptable.exec(src))) {
-      src = src.substring(cap[0].length);
-
-      item = {
-        type: 'table',
-        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
-        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
-        cells: cap[3].replace(/\n$/, '').split('\n')
-      };
-
-      for (i = 0; i < item.align.length; i++) {
-        if (/^ *-+: *$/.test(item.align[i])) {
-          item.align[i] = 'right';
-        } else if (/^ *:-+: *$/.test(item.align[i])) {
-          item.align[i] = 'center';
-        } else if (/^ *:-+ *$/.test(item.align[i])) {
-          item.align[i] = 'left';
-        } else {
-          item.align[i] = null;
-        }
-      }
-
-      for (i = 0; i < item.cells.length; i++) {
-        item.cells[i] = item.cells[i].split(/ *\| */);
-      }
-
-      this.tokens.push(item);
-
-      continue;
-    }
-
-    // lheading
-    if (cap = this.rules.lheading.exec(src)) {
-      src = src.substring(cap[0].length);
-      this.tokens.push({
-        type: 'heading',
-        depth: cap[2] === '=' ? 1 : 2,
-        text: cap[1]
-      });
-      continue;
-    }
-
-    // hr
-    if (cap = this.rules.hr.exec(src)) {
-      src = src.substring(cap[0].length);
-      this.tokens.push({
-        type: 'hr'
-      });
-      continue;
-    }
-
-    // blockquote
-    if (cap = this.rules.blockquote.exec(src)) {
-      src = src.substring(cap[0].length);
-
-      this.tokens.push({
-        type: 'blockquote_start'
-      });
-
-      cap = cap[0].replace(/^ *> ?/gm, '');
-
-      // Pass `top` to keep the current
-      // "toplevel" state. This is exactly
-      // how markdown.pl works.
-      this.token(cap, top, true);
-
-      this.tokens.push({
-        type: 'blockquote_end'
-      });
-
-      continue;
-    }
-
-    // list
-    if (cap = this.rules.list.exec(src)) {
-      src = src.substring(cap[0].length);
-      bull = cap[2];
-
-      this.tokens.push({
-        type: 'list_start',
-        ordered: bull.length > 1
-      });
-
-      // Get each top-level item.
-      cap = cap[0].match(this.rules.item);
-
-      next = false;
-      l = cap.length;
-      i = 0;
-
-      for (; i < l; i++) {
-        item = cap[i];
-
-        // Remove the list item's bullet
-        // so it is seen as the next token.
-        space = item.length;
-        item = item.replace(/^ *([*+-]|\d+\.) +/, '');
-
-        // Outdent whatever the
-        // list item contains. Hacky.
-        if (~item.indexOf('\n ')) {
-          space -= item.length;
-          item = !this.options.pedantic
-            ? item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '')
-            : item.replace(/^ {1,4}/gm, '');
-        }
-
-        // Determine whether the next list item belongs here.
-        // Backpedal if it does not belong in this list.
-        if (this.options.smartLists && i !== l - 1) {
-          b = block.bullet.exec(cap[i + 1])[0];
-          if (bull !== b && !(bull.length > 1 && b.length > 1)) {
-            src = cap.slice(i + 1).join('\n') + src;
-            i = l - 1;
-          }
-        }
-
-        // Determine whether item is loose or not.
-        // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/
-        // for discount behavior.
-        loose = next || /\n\n(?!\s*$)/.test(item);
-        if (i !== l - 1) {
-          next = item.charAt(item.length - 1) === '\n';
-          if (!loose) loose = next;
-        }
-
-        this.tokens.push({
-          type: loose
-            ? 'loose_item_start'
-            : 'list_item_start'
-        });
-
-        // Recurse.
-        this.token(item, false, bq);
-
-        this.tokens.push({
-          type: 'list_item_end'
-        });
-      }
-
-      this.tokens.push({
-        type: 'list_end'
-      });
-
-      continue;
-    }
-
-    // html
-    if (cap = this.rules.html.exec(src)) {
-      src = src.substring(cap[0].length);
-      this.tokens.push({
-        type: this.options.sanitize
-          ? 'paragraph'
-          : 'html',
-        pre: !this.options.sanitizer
-          && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
-        text: cap[0]
-      });
-      continue;
-    }
-
-    // def
-    if ((!bq && top) && (cap = this.rules.def.exec(src))) {
-      src = src.substring(cap[0].length);
-      this.tokens.links[cap[1].toLowerCase()] = {
-        href: cap[2],
-        title: cap[3]
-      };
-      continue;
-    }
-
-    // table (gfm)
-    if (top && (cap = this.rules.table.exec(src))) {
-      src = src.substring(cap[0].length);
-
-      item = {
-        type: 'table',
-        header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */),
-        align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */),
-        cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n')
-      };
-
-      for (i = 0; i < item.align.length; i++) {
-        if (/^ *-+: *$/.test(item.align[i])) {
-          item.align[i] = 'right';
-        } else if (/^ *:-+: *$/.test(item.align[i])) {
-          item.align[i] = 'center';
-        } else if (/^ *:-+ *$/.test(item.align[i])) {
-          item.align[i] = 'left';
-        } else {
-          item.align[i] = null;
-        }
-      }
-
-      for (i = 0; i < item.cells.length; i++) {
-        item.cells[i] = item.cells[i]
-          .replace(/^ *\| *| *\| *$/g, '')
-          .split(/ *\| */);
-      }
-
-      this.tokens.push(item);
-
-      continue;
-    }
-
-    // top-level paragraph
-    if (top && (cap = this.rules.paragraph.exec(src))) {
-      src = src.substring(cap[0].length);
-      this.tokens.push({
-        type: 'paragraph',
-        text: cap[1].charAt(cap[1].length - 1) === '\n'
-          ? cap[1].slice(0, -1)
-          : cap[1]
-      });
-      continue;
-    }
-
-    // text
-    if (cap = this.rules.text.exec(src)) {
-      // Top-level should never reach here.
-      src = src.substring(cap[0].length);
-      this.tokens.push({
-        type: 'text',
-        text: cap[0]
-      });
-      continue;
-    }
-
-    if (src) {
-      throw new
-        Error('Infinite loop on byte: ' + src.charCodeAt(0));
-    }
-  }
-
-  return this.tokens;
-};
-
-/**
- * Inline-Level Grammar
- */
-
-var inline = {
-  escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
-  autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
-  url: noop,
-  tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
-  link: /^!?\[(inside)\]\(href\)/,
-  reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
-  nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
-  strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,
-  em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,
-  code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
-  br: /^ {2,}\n(?!\s*$)/,
-  del: noop,
-  text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
-};
-
-inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;
-inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
-
-inline.link = replace(inline.link)
-  ('inside', inline._inside)
-  ('href', inline._href)
-  ();
-
-inline.reflink = replace(inline.reflink)
-  ('inside', inline._inside)
-  ();
-
-/**
- * Normal Inline Grammar
- */
-
-inline.normal = merge({}, inline);
-
-/**
- * Pedantic Inline Grammar
- */
-
-inline.pedantic = merge({}, inline.normal, {
-  strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
-  em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
-});
-
-/**
- * GFM Inline Grammar
- */
-
-inline.gfm = merge({}, inline.normal, {
-  escape: replace(inline.escape)('])', '~|])')(),
-  url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
-  del: /^~~(?=\S)([\s\S]*?\S)~~/,
-  text: replace(inline.text)
-    (']|', '~]|')
-    ('|', '|https?://|')
-    ()
-});
-
-/**
- * GFM + Line Breaks Inline Grammar
- */
-
-inline.breaks = merge({}, inline.gfm, {
-  br: replace(inline.br)('{2,}', '*')(),
-  text: replace(inline.gfm.text)('{2,}', '*')()
-});
-
-/**
- * Inline Lexer & Compiler
- */
-
-function InlineLexer(links, options) {
-  this.options = options || marked.defaults;
-  this.links = links;
-  this.rules = inline.normal;
-  this.renderer = this.options.renderer || new Renderer;
-  this.renderer.options = this.options;
-
-  if (!this.links) {
-    throw new
-      Error('Tokens array requires a `links` property.');
-  }
-
-  if (this.options.gfm) {
-    if (this.options.breaks) {
-      this.rules = inline.breaks;
-    } else {
-      this.rules = inline.gfm;
-    }
-  } else if (this.options.pedantic) {
-    this.rules = inline.pedantic;
-  }
-}
-
-/**
- * Expose Inline Rules
- */
-
-InlineLexer.rules = inline;
-
-/**
- * Static Lexing/Compiling Method
- */
-
-InlineLexer.output = function(src, links, options) {
-  var inline = new InlineLexer(links, options);
-  return inline.output(src);
-};
-
-/**
- * Lexing/Compiling
- */
-
-InlineLexer.prototype.output = function(src) {
-  var out = ''
-    , link
-    , text
-    , href
-    , cap;
-
-  while (src) {
-    // escape
-    if (cap = this.rules.escape.exec(src)) {
-      src = src.substring(cap[0].length);
-      out += cap[1];
-      continue;
-    }
-
-    // autolink
-    if (cap = this.rules.autolink.exec(src)) {
-      src = src.substring(cap[0].length);
-      if (cap[2] === '@') {
-        text = cap[1].charAt(6) === ':'
-          ? this.mangle(cap[1].substring(7))
-          : this.mangle(cap[1]);
-        href = this.mangle('mailto:') + text;
-      } else {
-        text = escape(cap[1]);
-        href = text;
-      }
-      out += this.renderer.link(href, null, text);
-      continue;
-    }
-
-    // url (gfm)
-    if (!this.inLink && (cap = this.rules.url.exec(src))) {
-      src = src.substring(cap[0].length);
-      text = escape(cap[1]);
-      href = text;
-      out += this.renderer.link(href, null, text);
-      continue;
-    }
-
-    // tag
-    if (cap = this.rules.tag.exec(src)) {
-      if (!this.inLink && /^<a /i.test(cap[0])) {
-        this.inLink = true;
-      } else if (this.inLink && /^<\/a>/i.test(cap[0])) {
-        this.inLink = false;
-      }
-      src = src.substring(cap[0].length);
-      out += this.options.sanitize
-        ? this.options.sanitizer
-          ? this.options.sanitizer(cap[0])
-          : escape(cap[0])
-        : cap[0]
-      continue;
-    }
-
-    // link
-    if (cap = this.rules.link.exec(src)) {
-      src = src.substring(cap[0].length);
-      this.inLink = true;
-      out += this.outputLink(cap, {
-        href: cap[2],
-        title: cap[3]
-      });
-      this.inLink = false;
-      continue;
-    }
-
-    // reflink, nolink
-    if ((cap = this.rules.reflink.exec(src))
-        || (cap = this.rules.nolink.exec(src))) {
-      src = src.substring(cap[0].length);
-      link = (cap[2] || cap[1]).replace(/\s+/g, ' ');
-      link = this.links[link.toLowerCase()];
-      if (!link || !link.href) {
-        out += cap[0].charAt(0);
-        src = cap[0].substring(1) + src;
-        continue;
-      }
-      this.inLink = true;
-      out += this.outputLink(cap, link);
-      this.inLink = false;
-      continue;
-    }
-
-    // strong
-    if (cap = this.rules.strong.exec(src)) {
-      src = src.substring(cap[0].length);
-      out += this.renderer.strong(this.output(cap[2] || cap[1]));
-      continue;
-    }
-
-    // em
-    if (cap = this.rules.em.exec(src)) {
-      src = src.substring(cap[0].length);
-      out += this.renderer.em(this.output(cap[2] || cap[1]));
-      continue;
-    }
-
-    // code
-    if (cap = this.rules.code.exec(src)) {
-      src = src.substring(cap[0].length);
-      out += this.renderer.codespan(escape(cap[2], true));
-      continue;
-    }
-
-    // br
-    if (cap = this.rules.br.exec(src)) {
-      src = src.substring(cap[0].length);
-      out += this.renderer.br();
-      continue;
-    }
-
-    // del (gfm)
-    if (cap = this.rules.del.exec(src)) {
-      src = src.substring(cap[0].length);
-      out += this.renderer.del(this.output(cap[1]));
-      continue;
-    }
-
-    // text
-    if (cap = this.rules.text.exec(src)) {
-      src = src.substring(cap[0].length);
-      out += this.renderer.text(escape(this.smartypants(cap[0])));
-      continue;
-    }
-
-    if (src) {
-      throw new
-        Error('Infinite loop on byte: ' + src.charCodeAt(0));
-    }
-  }
-
-  return out;
-};
-
-/**
- * Compile Link
- */
-
-InlineLexer.prototype.outputLink = function(cap, link) {
-  var href = escape(link.href)
-    , title = link.title ? escape(link.title) : null;
-
-  return cap[0].charAt(0) !== '!'
-    ? this.renderer.link(href, title, this.output(cap[1]))
-    : this.renderer.image(href, title, escape(cap[1]));
-};
-
-/**
- * Smartypants Transformations
- */
-
-InlineLexer.prototype.smartypants = function(text) {
-  if (!this.options.smartypants) return text;
-  return text
-    // em-dashes
-    .replace(/---/g, '\u2014')
-    // en-dashes
-    .replace(/--/g, '\u2013')
-    // opening singles
-    .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
-    // closing singles & apostrophes
-    .replace(/'/g, '\u2019')
-    // opening doubles
-    .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
-    // closing doubles
-    .replace(/"/g, '\u201d')
-    // ellipses
-    .replace(/\.{3}/g, '\u2026');
-};
-
-/**
- * Mangle Links
- */
-
-InlineLexer.prototype.mangle = function(text) {
-  if (!this.options.mangle) return text;
-  var out = ''
-    , l = text.length
-    , i = 0
-    , ch;
-
-  for (; i < l; i++) {
-    ch = text.charCodeAt(i);
-    if (Math.random() > 0.5) {
-      ch = 'x' + ch.toString(16);
-    }
-    out += '&#' + ch + ';';
-  }
-
-  return out;
-};
-
-/**
- * Renderer
- */
-
-function Renderer(options) {
-  this.options = options || { };
-}
-
-Renderer.prototype.code = function(code, lang, escaped) {
-  if (this.options.highlight) {
-    var out = this.options.highlight(code, lang);
-    if (out != null && out !== code) {
-      escaped = true;
-      code = out;
-    }
-  }
-
-  if (!lang) {
-    return '<pre><code>'
-      + (escaped ? code : escape(code, true))
-      + '\n</code></pre>';
-  }
-
-  return '<pre><code class="'
-    + this.options.langPrefix
-    + escape(lang, true)
-    + '">'
-    + (escaped ? code : escape(code, true))
-    + '\n</code></pre>\n';
-};
-
-Renderer.prototype.blockquote = function(quote) {
-  return '<blockquote>\n' + quote + '</blockquote>\n';
-};
-
-Renderer.prototype.html = function(html) {
-  return html;
-};
-
-Renderer.prototype.heading = function(text, level, raw) {
-  return '<h'
-    + level
-    + ' id="'
-    + this.options.headerPrefix
-    + raw.toLowerCase().replace(/[^\w]+/g, '-')
-    + '">'
-    + text
-    + '</h'
-    + level
-    + '>\n';
-};
-
-Renderer.prototype.hr = function() {
-  return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
-};
-
-Renderer.prototype.list = function(body, ordered) {
-  var type = ordered ? 'ol' : 'ul';
-  return '<' + type + '>\n' + body + '</' + type + '>\n';
-};
-
-Renderer.prototype.listitem = function(text) {
-  return '<li>' + text + '</li>\n';
-};
-
-Renderer.prototype.paragraph = function(text) {
-  return '<p>' + text + '</p>\n';
-};
-
-Renderer.prototype.table = function(header, body) {
-  return '<table>\n'
-    + '<thead>\n'
-    + header
-    + '</thead>\n'
-    + '<tbody>\n'
-    + body
-    + '</tbody>\n'
-    + '</table>\n';
-};
-
-Renderer.prototype.tablerow = function(content) {
-  return '<tr>\n' + content + '</tr>\n';
-};
-
-Renderer.prototype.tablecell = function(content, flags) {
-  var type = flags.header ? 'th' : 'td';
-  var tag = flags.align
-    ? '<' + type + ' style="text-align:' + flags.align + '">'
-    : '<' + type + '>';
-  return tag + content + '</' + type + '>\n';
-};
-
-// span level renderer
-Renderer.prototype.strong = function(text) {
-  return '<strong>' + text + '</strong>';
-};
-
-Renderer.prototype.em = function(text) {
-  return '<em>' + text + '</em>';
-};
-
-Renderer.prototype.codespan = function(text) {
-  return '<code>' + text + '</code>';
-};
-
-Renderer.prototype.br = function() {
-  return this.options.xhtml ? '<br/>' : '<br>';
-};
-
-Renderer.prototype.del = function(text) {
-  return '<del>' + text + '</del>';
-};
-
-Renderer.prototype.link = function(href, title, text) {
-  if (this.options.sanitize) {
-    try {
-      var prot = decodeURIComponent(unescape(href))
-        .replace(/[^\w:]/g, '')
-        .toLowerCase();
-    } catch (e) {
-      return '';
-    }
-    if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0) {
-      return '';
-    }
-  }
-  var out = '<a href="' + href + '"';
-  if (title) {
-    out += ' title="' + title + '"';
-  }
-  out += '>' + text + '</a>';
-  return out;
-};
-
-Renderer.prototype.image = function(href, title, text) {
-  var out = '<img src="' + href + '" alt="' + text + '"';
-  if (title) {
-    out += ' title="' + title + '"';
-  }
-  out += this.options.xhtml ? '/>' : '>';
-  return out;
-};
-
-Renderer.prototype.text = function(text) {
-  return text;
-};
-
-/**
- * Parsing & Compiling
- */
-
-function Parser(options) {
-  this.tokens = [];
-  this.token = null;
-  this.options = options || marked.defaults;
-  this.options.renderer = this.options.renderer || new Renderer;
-  this.renderer = this.options.renderer;
-  this.renderer.options = this.options;
-}
-
-/**
- * Static Parse Method
- */
-
-Parser.parse = function(src, options, renderer) {
-  var parser = new Parser(options, renderer);
-  return parser.parse(src);
-};
-
-/**
- * Parse Loop
- */
-
-Parser.prototype.parse = function(src) {
-  this.inline = new InlineLexer(src.links, this.options, this.renderer);
-  this.tokens = src.reverse();
-
-  var out = '';
-  while (this.next()) {
-    out += this.tok();
-  }
-
-  return out;
-};
-
-/**
- * Next Token
- */
-
-Parser.prototype.next = function() {
-  return this.token = this.tokens.pop();
-};
-
-/**
- * Preview Next Token
- */
-
-Parser.prototype.peek = function() {
-  return this.tokens[this.tokens.length - 1] || 0;
-};
-
-/**
- * Parse Text Tokens
- */
-
-Parser.prototype.parseText = function() {
-  var body = this.token.text;
-
-  while (this.peek().type === 'text') {
-    body += '\n' + this.next().text;
-  }
-
-  return this.inline.output(body);
-};
-
-/**
- * Parse Current Token
- */
-
-Parser.prototype.tok = function() {
-  switch (this.token.type) {
-    case 'space': {
-      return '';
-    }
-    case 'hr': {
-      return this.renderer.hr();
-    }
-    case 'heading': {
-      return this.renderer.heading(
-        this.inline.output(this.token.text),
-        this.token.depth,
-        this.token.text);
-    }
-    case 'code': {
-      return this.renderer.code(this.token.text,
-        this.token.lang,
-        this.token.escaped);
-    }
-    case 'table': {
-      var header = ''
-        , body = ''
-        , i
-        , row
-        , cell
-        , flags
-        , j;
-
-      // header
-      cell = '';
-      for (i = 0; i < this.token.header.length; i++) {
-        flags = { header: true, align: this.token.align[i] };
-        cell += this.renderer.tablecell(
-          this.inline.output(this.token.header[i]),
-          { header: true, align: this.token.align[i] }
-        );
-      }
-      header += this.renderer.tablerow(cell);
-
-      for (i = 0; i < this.token.cells.length; i++) {
-        row = this.token.cells[i];
-
-        cell = '';
-        for (j = 0; j < row.length; j++) {
-          cell += this.renderer.tablecell(
-            this.inline.output(row[j]),
-            { header: false, align: this.token.align[j] }
-          );
-        }
-
-        body += this.renderer.tablerow(cell);
-      }
-      return this.renderer.table(header, body);
-    }
-    case 'blockquote_start': {
-      var body = '';
-
-      while (this.next().type !== 'blockquote_end') {
-        body += this.tok();
-      }
-
-      return this.renderer.blockquote(body);
-    }
-    case 'list_start': {
-      var body = ''
-        , ordered = this.token.ordered;
-
-      while (this.next().type !== 'list_end') {
-        body += this.tok();
-      }
-
-      return this.renderer.list(body, ordered);
-    }
-    case 'list_item_start': {
-      var body = '';
-
-      while (this.next().type !== 'list_item_end') {
-        body += this.token.type === 'text'
-          ? this.parseText()
-          : this.tok();
-      }
-
-      return this.renderer.listitem(body);
-    }
-    case 'loose_item_start': {
-      var body = '';
-
-      while (this.next().type !== 'list_item_end') {
-        body += this.tok();
-      }
-
-      return this.renderer.listitem(body);
-    }
-    case 'html': {
-      var html = !this.token.pre && !this.options.pedantic
-        ? this.inline.output(this.token.text)
-        : this.token.text;
-      return this.renderer.html(html);
-    }
-    case 'paragraph': {
-      return this.renderer.paragraph(this.inline.output(this.token.text));
-    }
-    case 'text': {
-      return this.renderer.paragraph(this.parseText());
-    }
-  }
-};
-
-/**
- * Helpers
- */
-
-function escape(html, encode) {
-  return html
-    .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
-    .replace(/</g, '&lt;')
-    .replace(/>/g, '&gt;')
-    .replace(/"/g, '&quot;')
-    .replace(/'/g, '&#39;');
-}
-
-function unescape(html) {
-	// explicitly match decimal, hex, and named HTML entities
-  return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/g, function(_, n) {
-    n = n.toLowerCase();
-    if (n === 'colon') return ':';
-    if (n.charAt(0) === '#') {
-      return n.charAt(1) === 'x'
-        ? String.fromCharCode(parseInt(n.substring(2), 16))
-        : String.fromCharCode(+n.substring(1));
-    }
-    return '';
-  });
-}
-
-function replace(regex, opt) {
-  regex = regex.source;
-  opt = opt || '';
-  return function self(name, val) {
-    if (!name) return new RegExp(regex, opt);
-    val = val.source || val;
-    val = val.replace(/(^|[^\[])\^/g, '$1');
-    regex = regex.replace(name, val);
-    return self;
-  };
-}
-
-function noop() {}
-noop.exec = noop;
-
-function merge(obj) {
-  var i = 1
-    , target
-    , key;
-
-  for (; i < arguments.length; i++) {
-    target = arguments[i];
-    for (key in target) {
-      if (Object.prototype.hasOwnProperty.call(target, key)) {
-        obj[key] = target[key];
-      }
-    }
-  }
-
-  return obj;
-}
-
-
-/**
- * Marked
- */
-
-function marked(src, opt, callback) {
-  if (callback || typeof opt === 'function') {
-    if (!callback) {
-      callback = opt;
-      opt = null;
-    }
-
-    opt = merge({}, marked.defaults, opt || {});
-
-    var highlight = opt.highlight
-      , tokens
-      , pending
-      , i = 0;
-
-    try {
-      tokens = Lexer.lex(src, opt)
-    } catch (e) {
-      return callback(e);
-    }
-
-    pending = tokens.length;
-
-    var done = function(err) {
-      if (err) {
-        opt.highlight = highlight;
-        return callback(err);
-      }
-
-      var out;
-
-      try {
-        out = Parser.parse(tokens, opt);
-      } catch (e) {
-        err = e;
-      }
-
-      opt.highlight = highlight;
-
-      return err
-        ? callback(err)
-        : callback(null, out);
-    };
-
-    if (!highlight || highlight.length < 3) {
-      return done();
-    }
-
-    delete opt.highlight;
-
-    if (!pending) return done();
-
-    for (; i < tokens.length; i++) {
-      (function(token) {
-        if (token.type !== 'code') {
-          return --pending || done();
-        }
-        return highlight(token.text, token.lang, function(err, code) {
-          if (err) return done(err);
-          if (code == null || code === token.text) {
-            return --pending || done();
-          }
-          token.text = code;
-          token.escaped = true;
-          --pending || done();
-        });
-      })(tokens[i]);
-    }
-
-    return;
-  }
-  try {
-    if (opt) opt = merge({}, marked.defaults, opt);
-    return Parser.parse(Lexer.lex(src, opt), opt);
-  } catch (e) {
-    e.message += '\nPlease report this to https://github.com/chjj/marked.';
-    if ((opt || marked.defaults).silent) {
-      return '<p>An error occured:</p><pre>'
-        + escape(e.message + '', true)
-        + '</pre>';
-    }
-    throw e;
-  }
-}
-
-/**
- * Options
- */
-
-marked.options =
-marked.setOptions = function(opt) {
-  merge(marked.defaults, opt);
-  return marked;
-};
-
-marked.defaults = {
-  gfm: true,
-  tables: true,
-  breaks: false,
-  pedantic: false,
-  sanitize: false,
-  sanitizer: null,
-  mangle: true,
-  smartLists: false,
-  silent: false,
-  highlight: null,
-  langPrefix: 'lang-',
-  smartypants: false,
-  headerPrefix: '',
-  renderer: new Renderer,
-  xhtml: false
-};
-
-/**
- * Expose
- */
-
-marked.Parser = Parser;
-marked.parser = Parser.parse;
-
-marked.Renderer = Renderer;
-
-marked.Lexer = Lexer;
-marked.lexer = Lexer.lex;
-
-marked.InlineLexer = InlineLexer;
-marked.inlineLexer = InlineLexer.output;
-
-marked.parse = marked;
-
-if (typeof module !== 'undefined' && typeof exports === 'object') {
-  module.exports = marked;
-} else if (typeof define === 'function' && define.amd) {
-  define(function() { return marked; });
-} else {
-  this.marked = marked;
-}
-
-}).call(function() {
-  return this || (typeof window !== 'undefined' ? window : global);
-}());

+ 0 - 175
packages/ckeditor5-markdown-gfm/src/lib/marked/renderer.js

@@ -1,175 +0,0 @@
-/**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-/**
- * Original marked.js library renderer with fixes:
- * - No formatting for output HTML string &mdash; all newlines between tags are removed to create clean output.
- * - Changed long string concatenations to ES5 template strings.
- * - Changed code style.
- *
- * @see {@link https://github.com/chjj/marked#renderer} Methods description.
- * @param options
- * @constructor
- */
-function Renderer( options ) {
-	this.options = options || {};
-}
-
-Renderer.prototype.code = function( code, lang, escaped ) {
-	if ( this.options.highlight ) {
-		const out = this.options.highlight( code, lang );
-
-		if ( out !== null && out !== code ) {
-			escaped = true;
-			code = out;
-		}
-	}
-
-	if ( !lang ) {
-		return `<pre><code>${ escaped ? code : escape( code, true ) }</code></pre>`;
-	}
-
-	const cssClass = this.options.langPrefix + escape( lang, true );
-
-	return `<pre><code class="${ cssClass }">${ escaped ? code : escape( code, true ) }</code></pre>`;
-};
-
-Renderer.prototype.blockquote = function( quote ) {
-	return `<blockquote>${ quote }</blockquote>`;
-};
-
-Renderer.prototype.html = function( html ) {
-	return html;
-};
-
-Renderer.prototype.heading = function( text, level, raw ) {
-	return `<h${ level }>${ text }</h${ level }>`;
-};
-
-Renderer.prototype.hr = function() {
-	return this.options.xhtml ? '<hr/>' : '<hr>';
-};
-
-Renderer.prototype.list = function( body, ordered ) {
-	const type = ordered ? 'ol' : 'ul';
-
-	return `<${ type }>${ body }</${ type }>`;
-};
-
-Renderer.prototype.listitem = function( text ) {
-	return `<li>${ text }</li>`;
-};
-
-Renderer.prototype.paragraph = function( text ) {
-	return `<p>${ text }</p>`;
-};
-
-Renderer.prototype.table = function( header, body ) {
-	return `<table><thead>${ header }</thead><tbody>${ body }</tbody></table>`;
-};
-
-Renderer.prototype.tablerow = function( content ) {
-	return '<tr>' + content + '</tr>';
-};
-
-Renderer.prototype.tablecell = function( content, flags ) {
-	const type = flags.header ? 'th' : 'td';
-	const tag = flags.align ? `<${ type } align="${ flags.align }">` : `<${ type }>`;
-
-	return tag + content + `</${ type }>`;
-};
-
-// span level renderer
-Renderer.prototype.strong = function( text ) {
-	return `<strong>${ text }</strong>`;
-};
-
-Renderer.prototype.em = function( text ) {
-	return `<em>${ text }</em>`;
-};
-
-Renderer.prototype.codespan = function( text ) {
-	return `<code>${ text.trim() }</code>`;
-};
-
-Renderer.prototype.br = function() {
-	return this.options.xhtml ? '<br/>' : '<br>';
-};
-
-Renderer.prototype.del = function( text ) {
-	return `<del>${ text }</del>`;
-};
-
-Renderer.prototype.link = function( href, title, text ) {
-	if ( this.options.sanitize ) {
-		let prot;
-
-		try {
-			prot = decodeURIComponent( unescape( href ) )
-				.replace( /[^\w:]/g, '' )
-				.toLowerCase();
-		} catch ( e ) {
-			return '';
-		}
-
-		if ( prot.indexOf( 'javascript:' ) === 0 || prot.indexOf( 'vbscript:' ) === 0 ) { // jshint ignore:line
-			return '';
-		}
-	}
-
-	let out = '<a href="' + href + '"';
-
-	if ( title ) {
-		out += ' title="' + title + '"';
-	}
-	out += '>' + text + '</a>';
-
-	return out;
-};
-
-Renderer.prototype.image = function( href, title, text ) {
-	let out = '<img src="' + href + '" alt="' + text + '"';
-
-	if ( title ) {
-		out += ' title="' + title + '"';
-	}
-	out += this.options.xhtml ? '/>' : '>';
-
-	return out;
-};
-
-Renderer.prototype.text = function( text ) {
-	return text;
-};
-
-export default Renderer;
-
-function escape( html, encode ) {
-	return html
-		.replace( !encode ? /&(?!#?\w+;)/g : /&/g, '&amp;' )
-		.replace( /</g, '&lt;' )
-		.replace( />/g, '&gt;' )
-		.replace( /"/g, '&quot;' )
-		.replace( /'/g, '&#39;' );
-}
-
-function unescape( html ) {
-	// explicitly match decimal, hex, and named HTML entities
-	return html.replace( /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/g, function( _, n ) {
-		n = n.toLowerCase();
-
-		if ( n === 'colon' ) {
-			return ':';
-		}
-
-		if ( n.charAt( 0 ) === '#' ) {
-			return n.charAt( 1 ) === 'x' ?
-				String.fromCharCode( parseInt( n.substring( 2 ), 16 ) ) :
-				String.fromCharCode( +n.substring( 1 ) ); // jscs:ignore
-		}
-
-		return '';
-	} );
-}

+ 0 - 68
packages/ckeditor5-markdown-gfm/src/lib/to-markdown/converters.js

@@ -1,68 +0,0 @@
-/**
- * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
-
-// Exports an array with custom converters used by to-markdown library.
-export default [
-	// Converting code blocks with class name matching output from marked library.
-	{
-		filter: ( node ) =>  {
-			const regexp = /lang-(.+)/;
-
-			return node.nodeName === 'PRE' &&
-				node.firstChild &&
-				node.firstChild.nodeName === 'CODE' &&
-				regexp.test( node.firstChild.className );
-		},
-		replacement: ( content, node ) => {
-			const regexp = /lang-(.+)/;
-			const lang = regexp.exec( node.firstChild.className )[ 1 ];
-
-			return '\n\n``` ' + lang + '\n' + node.firstChild.textContent + '\n```\n\n';
-		}
-	},
-	// Converting empty links.
-	{
-		filter: ( node ) => {
-			return node.nodeName === 'A' && !node.getAttribute( 'href' );
-		},
-
-		replacement: ( content, node ) => {
-			const title = node.title ? `"${node.title}"` : '';
-
-			return `[${ content }](${ title })`;
-		}
-	},
-	// Headers - fixing newline at the beginning.
-	{
-		filter: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6' ],
-		replacement: ( content, node ) => {
-			const hLevel = node.nodeName.charAt( 1 );
-			let hPrefix = '';
-
-			for ( let i = 0; i < hLevel; i++ ) {
-				hPrefix += '#';
-			}
-
-			return hPrefix + ' ' + content + '\n';
-		}
-	},
-	// Inline code - fixing backticks inside code blocks.
-	{
-		filter: ( node ) => {
-			const hasSiblings = node.previousSibling || node.nextSibling;
-			const isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings;
-
-			return node.nodeName === 'CODE' && !isCodeBlock;
-		},
-		replacement: ( content ) => {
-			// If content starts or ends with backtick - use double backtick.
-			if ( content.indexOf( '`' ) > -1 ) {
-				return '`` ' + content + ' ``';
-			}
-
-			return '`' + content + '`';
-		}
-	}
-];

+ 0 - 785
packages/ckeditor5-markdown-gfm/src/lib/to-markdown/to-markdown.js

@@ -1,785 +0,0 @@
-(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.toMarkdown = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
-	/*
-	 * to-markdown - an HTML to Markdown converter
-	 *
-	 * Copyright 2011+, Dom Christie
-	 * Licenced under the MIT licence
-	 *
-	 */
-
-	'use strict'
-
-	var toMarkdown
-	var converters
-	var mdConverters = require('./lib/md-converters')
-	var gfmConverters = require('./lib/gfm-converters')
-	var HtmlParser = require('./lib/html-parser')
-	var collapse = require('collapse-whitespace')
-
-	/*
-	 * Utilities
-	 */
-
-	var blocks = ['address', 'article', 'aside', 'audio', 'blockquote', 'body',
-		'canvas', 'center', 'dd', 'dir', 'div', 'dl', 'dt', 'fieldset', 'figcaption',
-		'figure', 'footer', 'form', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
-		'header', 'hgroup', 'hr', 'html', 'isindex', 'li', 'main', 'menu', 'nav',
-		'noframes', 'noscript', 'ol', 'output', 'p', 'pre', 'section', 'table',
-		'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'ul'
-	]
-
-	function isBlock (node) {
-		return blocks.indexOf(node.nodeName.toLowerCase()) !== -1
-	}
-
-	var voids = [
-		'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input',
-		'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr'
-	]
-
-	function isVoid (node) {
-		return voids.indexOf(node.nodeName.toLowerCase()) !== -1
-	}
-
-	function htmlToDom (string) {
-		var tree = new HtmlParser().parseFromString(string, 'text/html')
-		collapse(tree.documentElement, isBlock)
-		return tree
-	}
-
-	/*
-	 * Flattens DOM tree into single array
-	 */
-
-	function bfsOrder (node) {
-		var inqueue = [node]
-		var outqueue = []
-		var elem
-		var children
-		var i
-
-		while (inqueue.length > 0) {
-			elem = inqueue.shift()
-			outqueue.push(elem)
-			children = elem.childNodes
-			for (i = 0; i < children.length; i++) {
-				if (children[i].nodeType === 1) inqueue.push(children[i])
-			}
-		}
-		outqueue.shift()
-		return outqueue
-	}
-
-	/*
-	 * Contructs a Markdown string of replacement text for a given node
-	 */
-
-	function getContent (node) {
-		var text = ''
-		for (var i = 0; i < node.childNodes.length; i++) {
-			if (node.childNodes[i].nodeType === 1) {
-				text += node.childNodes[i]._replacement
-			} else if (node.childNodes[i].nodeType === 3) {
-				text += node.childNodes[i].data
-			} else continue
-		}
-		return text
-	}
-
-	/*
-	 * Returns the HTML string of an element with its contents converted
-	 */
-
-	function outer (node, content) {
-		return node.cloneNode(false).outerHTML.replace('><', '>' + content + '<')
-	}
-
-	function canConvert (node, filter) {
-		if (typeof filter === 'string') {
-			return filter === node.nodeName.toLowerCase()
-		}
-		if (Array.isArray(filter)) {
-			return filter.indexOf(node.nodeName.toLowerCase()) !== -1
-		} else if (typeof filter === 'function') {
-			return filter.call(toMarkdown, node)
-		} else {
-			throw new TypeError('`filter` needs to be a string, array, or function')
-		}
-	}
-
-	function isFlankedByWhitespace (side, node) {
-		var sibling
-		var regExp
-		var isFlanked
-
-		if (side === 'left') {
-			sibling = node.previousSibling
-			regExp = / $/
-		} else {
-			sibling = node.nextSibling
-			regExp = /^ /
-		}
-
-		if (sibling) {
-			if (sibling.nodeType === 3) {
-				isFlanked = regExp.test(sibling.nodeValue)
-			} else if (sibling.nodeType === 1 && !isBlock(sibling)) {
-				isFlanked = regExp.test(sibling.textContent)
-			}
-		}
-		return isFlanked
-	}
-
-	function flankingWhitespace (node) {
-		var leading = ''
-		var trailing = ''
-
-		if (!isBlock(node)) {
-			var hasLeading = /^[ \r\n\t]/.test(node.innerHTML)
-			var hasTrailing = /[ \r\n\t]$/.test(node.innerHTML)
-
-			if (hasLeading && !isFlankedByWhitespace('left', node)) {
-				leading = ' '
-			}
-			if (hasTrailing && !isFlankedByWhitespace('right', node)) {
-				trailing = ' '
-			}
-		}
-
-		return { leading: leading, trailing: trailing }
-	}
-
-	/*
-	 * Finds a Markdown converter, gets the replacement, and sets it on
-	 * `_replacement`
-	 */
-
-	function process (node) {
-		var replacement
-		var content = getContent(node)
-
-		// Remove blank nodes
-		if (!isVoid(node) && !/A|TH|TD/.test(node.nodeName) && /^\s*$/i.test(content)) {
-			node._replacement = ''
-			return
-		}
-
-		for (var i = 0; i < converters.length; i++) {
-			var converter = converters[i]
-
-			if (canConvert(node, converter.filter)) {
-				if (typeof converter.replacement !== 'function') {
-					throw new TypeError(
-						'`replacement` needs to be a function that returns a string'
-					)
-				}
-
-				var whitespace = flankingWhitespace(node)
-
-				if (whitespace.leading || whitespace.trailing) {
-					content = content.trim()
-				}
-				replacement = whitespace.leading +
-					converter.replacement.call(toMarkdown, content, node) +
-					whitespace.trailing
-				break
-			}
-		}
-
-		node._replacement = replacement
-	}
-
-	toMarkdown = function (input, options) {
-		options = options || {}
-
-		if (typeof input !== 'string') {
-			throw new TypeError(input + ' is not a string')
-		}
-
-		// Escape potential ol triggers
-		input = input.replace(/(\d+)\. /g, '$1\\. ')
-
-		var clone = htmlToDom(input).body
-		var nodes = bfsOrder(clone)
-		var output
-
-		converters = mdConverters.slice(0)
-		if (options.gfm) {
-			converters = gfmConverters.concat(converters)
-		}
-
-		if (options.converters) {
-			converters = options.converters.concat(converters)
-		}
-
-		// Process through nodes in reverse (so deepest child elements are first).
-		for (var i = nodes.length - 1; i >= 0; i--) {
-			process(nodes[i])
-		}
-		output = getContent(clone)
-
-		return output.replace(/^[\t\r\n]+|[\t\r\n\s]+$/g, '')
-			.replace(/\n\s+\n/g, '\n\n')
-			.replace(/\n{3,}/g, '\n\n')
-	}
-
-	toMarkdown.isBlock = isBlock
-	toMarkdown.isVoid = isVoid
-	toMarkdown.outer = outer
-
-	module.exports = toMarkdown
-
-},{"./lib/gfm-converters":2,"./lib/html-parser":3,"./lib/md-converters":4,"collapse-whitespace":7}],2:[function(require,module,exports){
-	'use strict'
-
-	function cell (content, node) {
-		var index = Array.prototype.indexOf.call(node.parentNode.childNodes, node)
-		var prefix = ' '
-		if (index === 0) prefix = '| '
-		return prefix + content + ' |'
-	}
-
-	var highlightRegEx = /highlight highlight-(\S+)/
-
-	module.exports = [
-		{
-			filter: 'br',
-			replacement: function () {
-				return '\n'
-			}
-		},
-		{
-			filter: ['del', 's', 'strike'],
-			replacement: function (content) {
-				return '~~' + content + '~~'
-			}
-		},
-
-		{
-			filter: function (node) {
-				return node.type === 'checkbox' && node.parentNode.nodeName === 'LI'
-			},
-			replacement: function (content, node) {
-				return (node.checked ? '[x]' : '[ ]') + ' '
-			}
-		},
-
-		{
-			filter: ['th', 'td'],
-			replacement: function (content, node) {
-				return cell(content, node)
-			}
-		},
-
-		{
-			filter: 'tr',
-			replacement: function (content, node) {
-				var borderCells = ''
-				var alignMap = { left: ':--', right: '--:', center: ':-:' }
-
-				if (node.parentNode.nodeName === 'THEAD') {
-					for (var i = 0; i < node.childNodes.length; i++) {
-						var align = node.childNodes[i].attributes.align
-						var border = '---'
-
-						if (align) border = alignMap[align.value] || border
-
-						borderCells += cell(border, node.childNodes[i])
-					}
-				}
-				return '\n' + content + (borderCells ? '\n' + borderCells : '')
-			}
-		},
-
-		{
-			filter: 'table',
-			replacement: function (content) {
-				return '\n\n' + content + '\n\n'
-			}
-		},
-
-		{
-			filter: ['thead', 'tbody', 'tfoot'],
-			replacement: function (content) {
-				return content
-			}
-		},
-
-		// Fenced code blocks
-		{
-			filter: function (node) {
-				return node.nodeName === 'PRE' &&
-					node.firstChild &&
-					node.firstChild.nodeName === 'CODE'
-			},
-			replacement: function (content, node) {
-				return '\n\n```\n' + node.firstChild.textContent + '\n```\n\n'
-			}
-		},
-
-		// Syntax-highlighted code blocks
-		{
-			filter: function (node) {
-				return node.nodeName === 'PRE' &&
-					node.parentNode.nodeName === 'DIV' &&
-					highlightRegEx.test(node.parentNode.className)
-			},
-			replacement: function (content, node) {
-				var language = node.parentNode.className.match(highlightRegEx)[1]
-				return '\n\n```' + language + '\n' + node.textContent + '\n```\n\n'
-			}
-		},
-
-		{
-			filter: function (node) {
-				return node.nodeName === 'DIV' &&
-					highlightRegEx.test(node.className)
-			},
-			replacement: function (content) {
-				return '\n\n' + content + '\n\n'
-			}
-		}
-	]
-
-},{}],3:[function(require,module,exports){
-	/*
-	 * Set up window for Node.js
-	 */
-
-	var _window = (typeof window !== 'undefined' ? window : this)
-
-	/*
-	 * Parsing HTML strings
-	 */
-
-	function canParseHtmlNatively () {
-		var Parser = _window.DOMParser
-		var canParse = false
-
-		// Adapted from https://gist.github.com/1129031
-		// Firefox/Opera/IE throw errors on unsupported types
-		try {
-			// WebKit returns null on unsupported types
-			if (new Parser().parseFromString('', 'text/html')) {
-				canParse = true
-			}
-		} catch (e) {}
-
-		return canParse
-	}
-
-	function createHtmlParser () {
-		var Parser = function () {}
-
-		// For Node.js environments
-		if (typeof document === 'undefined') {
-			var jsdom = require('jsdom')
-			Parser.prototype.parseFromString = function (string) {
-				return jsdom.jsdom(string, {
-					plugins: {
-						FetchExternalResources: [],
-						ProcessExternalResources: false
-					}
-				})
-			}
-		} else {
-			if (!shouldUseActiveX()) {
-				Parser.prototype.parseFromString = function (string) {
-					var doc = document.implementation.createHTMLDocument('')
-					doc.open()
-					doc.write(string)
-					doc.close()
-					return doc
-				}
-			} else {
-				Parser.prototype.parseFromString = function (string) {
-					var doc = new window.ActiveXObject('htmlfile')
-					doc.designMode = 'on' // disable on-page scripts
-					doc.open()
-					doc.write(string)
-					doc.close()
-					return doc
-				}
-			}
-		}
-		return Parser
-	}
-
-	function shouldUseActiveX () {
-		var useActiveX = false
-
-		try {
-			document.implementation.createHTMLDocument('').open()
-		} catch (e) {
-			if (window.ActiveXObject) useActiveX = true
-		}
-
-		return useActiveX
-	}
-
-	module.exports = canParseHtmlNatively() ? _window.DOMParser : createHtmlParser()
-
-},{"jsdom":6}],4:[function(require,module,exports){
-	'use strict'
-
-	module.exports = [
-		{
-			filter: 'p',
-			replacement: function (content) {
-				return '\n\n' + content + '\n\n'
-			}
-		},
-
-		{
-			filter: 'br',
-			replacement: function () {
-				return '  \n'
-			}
-		},
-
-		{
-			filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
-			replacement: function (content, node) {
-				var hLevel = node.nodeName.charAt(1)
-				var hPrefix = ''
-				for (var i = 0; i < hLevel; i++) {
-					hPrefix += '#'
-				}
-				return '\n\n' + hPrefix + ' ' + content + '\n\n'
-			}
-		},
-
-		{
-			filter: 'hr',
-			replacement: function () {
-				return '\n\n* * *\n\n'
-			}
-		},
-
-		{
-			filter: ['em', 'i'],
-			replacement: function (content) {
-				return '_' + content + '_'
-			}
-		},
-
-		{
-			filter: ['strong', 'b'],
-			replacement: function (content) {
-				return '**' + content + '**'
-			}
-		},
-
-		// Inline code
-		{
-			filter: function (node) {
-				var hasSiblings = node.previousSibling || node.nextSibling
-				var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings
-
-				return node.nodeName === 'CODE' && !isCodeBlock
-			},
-			replacement: function (content) {
-				return '`' + content + '`'
-			}
-		},
-
-		{
-			filter: function (node) {
-				return node.nodeName === 'A' && node.getAttribute('href')
-			},
-			replacement: function (content, node) {
-				var titlePart = node.title ? ' "' + node.title + '"' : ''
-				return '[' + content + '](' + node.getAttribute('href') + titlePart + ')'
-			}
-		},
-
-		{
-			filter: 'img',
-			replacement: function (content, node) {
-				var alt = node.alt || ''
-				var src = node.getAttribute('src') || ''
-				var title = node.title || ''
-				var titlePart = title ? ' "' + title + '"' : ''
-				return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : ''
-			}
-		},
-
-		// Code blocks
-		{
-			filter: function (node) {
-				return node.nodeName === 'PRE' && node.firstChild.nodeName === 'CODE'
-			},
-			replacement: function (content, node) {
-				return '\n\n    ' + node.firstChild.textContent.replace(/\n/g, '\n    ') + '\n\n'
-			}
-		},
-
-		{
-			filter: 'blockquote',
-			replacement: function (content) {
-				content = content.trim()
-				content = content.replace(/\n{3,}/g, '\n\n')
-				content = content.replace(/^/gm, '> ')
-				return '\n\n' + content + '\n\n'
-			}
-		},
-
-		{
-			filter: 'li',
-			replacement: function (content, node) {
-				content = content.replace(/^\s+/, '').replace(/\n/gm, '\n    ')
-				var prefix = '*   '
-				var parent = node.parentNode
-				var index = Array.prototype.indexOf.call(parent.children, node) + 1
-
-				prefix = /ol/i.test(parent.nodeName) ? index + '.  ' : '*   '
-				return prefix + content
-			}
-		},
-
-		{
-			filter: ['ul', 'ol'],
-			replacement: function (content, node) {
-				var strings = []
-				for (var i = 0; i < node.childNodes.length; i++) {
-					strings.push(node.childNodes[i]._replacement)
-				}
-
-				if (/li/i.test(node.parentNode.nodeName)) {
-					return '\n' + strings.join('\n')
-				}
-				return '\n\n' + strings.join('\n') + '\n\n'
-			}
-		},
-
-		{
-			filter: function (node) {
-				return this.isBlock(node)
-			},
-			replacement: function (content, node) {
-				return '\n\n' + this.outer(node, content) + '\n\n'
-			}
-		},
-
-		// Anything else!
-		{
-			filter: function () {
-				return true
-			},
-			replacement: function (content, node) {
-				return this.outer(node, content)
-			}
-		}
-	]
-
-},{}],5:[function(require,module,exports){
-	/**
-	 * This file automatically generated from `build.js`.
-	 * Do not manually edit.
-	 */
-
-	module.exports = [
-		"address",
-		"article",
-		"aside",
-		"audio",
-		"blockquote",
-		"canvas",
-		"dd",
-		"div",
-		"dl",
-		"fieldset",
-		"figcaption",
-		"figure",
-		"footer",
-		"form",
-		"h1",
-		"h2",
-		"h3",
-		"h4",
-		"h5",
-		"h6",
-		"header",
-		"hgroup",
-		"hr",
-		"main",
-		"nav",
-		"noscript",
-		"ol",
-		"output",
-		"p",
-		"pre",
-		"section",
-		"table",
-		"tfoot",
-		"ul",
-		"video"
-	];
-
-},{}],6:[function(require,module,exports){
-
-},{}],7:[function(require,module,exports){
-	'use strict';
-
-	var voidElements = require('void-elements');
-	Object.keys(voidElements).forEach(function (name) {
-		voidElements[name.toUpperCase()] = 1;
-	});
-
-	var blockElements = {};
-	require('block-elements').forEach(function (name) {
-		blockElements[name.toUpperCase()] = 1;
-	});
-
-	/**
-	 * isBlockElem(node) determines if the given node is a block element.
-	 *
-	 * @param {Node} node
-	 * @returns {Boolean}
-	 */
-	function isBlockElem(node) {
-		return !!(node && blockElements[node.nodeName]);
-	}
-
-	/**
-	 * isVoid(node) determines if the given node is a void element.
-	 *
-	 * @param {Node} node
-	 * @returns {Boolean}
-	 */
-	function isVoid(node) {
-		return !!(node && voidElements[node.nodeName]);
-	}
-
-	/**
-	 * whitespace(elem [, isBlock]) removes extraneous whitespace from an
-	 * the given element. The function isBlock may optionally be passed in
-	 * to determine whether or not an element is a block element; if none
-	 * is provided, defaults to using the list of block elements provided
-	 * by the `block-elements` module.
-	 *
-	 * @param {Node} elem
-	 * @param {Function} blockTest
-	 */
-	function collapseWhitespace(elem, isBlock) {
-		if (!elem.firstChild || elem.nodeName === 'PRE') return;
-
-		if (typeof isBlock !== 'function') {
-			isBlock = isBlockElem;
-		}
-
-		var prevText = null;
-		var prevVoid = false;
-
-		var prev = null;
-		var node = next(prev, elem);
-
-		while (node !== elem) {
-			if (node.nodeType === 3) {
-				// Node.TEXT_NODE
-				var text = node.data.replace(/[ \r\n\t]+/g, ' ');
-
-				if ((!prevText || / $/.test(prevText.data)) && !prevVoid && text[0] === ' ') {
-					text = text.substr(1);
-				}
-
-				// `text` might be empty at this point.
-				if (!text) {
-					node = remove(node);
-					continue;
-				}
-
-				node.data = text;
-				prevText = node;
-			} else if (node.nodeType === 1) {
-				// Node.ELEMENT_NODE
-				if (isBlock(node) || node.nodeName === 'BR') {
-					if (prevText) {
-						prevText.data = prevText.data.replace(/ $/, '');
-					}
-
-					prevText = null;
-					prevVoid = false;
-				} else if (isVoid(node)) {
-					// Avoid trimming space around non-block, non-BR void elements.
-					prevText = null;
-					prevVoid = true;
-				}
-			} else {
-				node = remove(node);
-				continue;
-			}
-
-			var nextNode = next(prev, node);
-			prev = node;
-			node = nextNode;
-		}
-
-		if (prevText) {
-			prevText.data = prevText.data.replace(/ $/, '');
-			if (!prevText.data) {
-				remove(prevText);
-			}
-		}
-	}
-
-	/**
-	 * remove(node) removes the given node from the DOM and returns the
-	 * next node in the sequence.
-	 *
-	 * @param {Node} node
-	 * @returns {Node} node
-	 */
-	function remove(node) {
-		var next = node.nextSibling || node.parentNode;
-
-		node.parentNode.removeChild(node);
-
-		return next;
-	}
-
-	/**
-	 * next(prev, current) returns the next node in the sequence, given the
-	 * current and previous nodes.
-	 *
-	 * @param {Node} prev
-	 * @param {Node} current
-	 * @returns {Node}
-	 */
-	function next(prev, current) {
-		if (prev && prev.parentNode === current || current.nodeName === 'PRE') {
-			return current.nextSibling || current.parentNode;
-		}
-
-		return current.firstChild || current.nextSibling || current.parentNode;
-	}
-
-	module.exports = collapseWhitespace;
-
-},{"block-elements":5,"void-elements":8}],8:[function(require,module,exports){
-	/**
-	 * This file automatically generated from `pre-publish.js`.
-	 * Do not manually edit.
-	 */
-
-	module.exports = {
-		"area": true,
-		"base": true,
-		"br": true,
-		"col": true,
-		"embed": true,
-		"hr": true,
-		"img": true,
-		"input": true,
-		"keygen": true,
-		"link": true,
-		"menuitem": true,
-		"meta": true,
-		"param": true,
-		"source": true,
-		"track": true,
-		"wbr": true
-	};
-
-},{}]},{},[1])(1)
-});

+ 32 - 0
packages/ckeditor5-markdown-gfm/src/markdown2html/markdown2html.js

@@ -0,0 +1,32 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module markdown-gfm/markdown2html
+ */
+
+import marked from 'marked';
+
+/**
+ * Parses markdown string to an HTML.
+ *
+ * @param {String} markdown
+ * @returns {String}
+ */
+export default function markdown2html( markdown ) {
+	return marked.parse( markdown, {
+		gfm: true,
+		breaks: true,
+		tables: true,
+		xhtml: true,
+		headerIds: false
+	} );
+}
+
+export { marked };
+
+// Disable the autolink rule in the lexer (point it to a regex that always fail).
+marked.InlineLexer.rules.breaks.autolink = /^\b$/;
+marked.InlineLexer.rules.breaks.url = /^\b$/;

+ 26 - 3
packages/ckeditor5-markdown-gfm/tests/_utils/utils.js

@@ -14,18 +14,41 @@ import { StylesProcessor } from '@ckeditor/ckeditor5-engine/src/view/stylesmap';
  * @param {String} markdown Markdown to be processed to view.
  * @param {String} markdown Markdown to be processed to view.
  * @param {String} viewString Expected view structure.
  * @param {String} viewString Expected view structure.
  * @param {String} [normalizedMarkdown] When converting back to the markdown it might be different than provided input
  * @param {String} [normalizedMarkdown] When converting back to the markdown it might be different than provided input
+ * @param {Object} [options] Additional options.
+ * @param {Function} [options.setup] A function that receives the data processor instance before its execution.
  * markdown string (which will be used if this parameter is not provided).
  * markdown string (which will be used if this parameter is not provided).
  */
  */
-export function testDataProcessor( markdown, viewString, normalizedMarkdown ) {
+export function testDataProcessor( markdown, viewString, normalizedMarkdown, options ) {
 	const viewDocument = new ViewDocument( new StylesProcessor() );
 	const viewDocument = new ViewDocument( new StylesProcessor() );
+
 	const dataProcessor = new MarkdownDataProcessor( viewDocument );
 	const dataProcessor = new MarkdownDataProcessor( viewDocument );
+
+	if ( options && options.setup ) {
+		options.setup( dataProcessor );
+	}
 	const viewFragment = dataProcessor.toView( markdown );
 	const viewFragment = dataProcessor.toView( markdown );
 
 
+	const html = cleanHtml( stringify( viewFragment ) );
+
 	// Check if view has correct data.
 	// Check if view has correct data.
-	expect( stringify( viewFragment ) ).to.equal( viewString );
+	expect( html ).to.equal( viewString );
 
 
 	// Check if converting back gives the same result.
 	// Check if converting back gives the same result.
 	const normalized = typeof normalizedMarkdown !== 'undefined' ? normalizedMarkdown : markdown;
 	const normalized = typeof normalizedMarkdown !== 'undefined' ? normalizedMarkdown : markdown;
 
 
-	expect( dataProcessor.toData( viewFragment ) ).to.equal( normalized );
+	expect( cleanMarkdown( dataProcessor.toData( viewFragment ) ) ).to.equal( normalized );
+}
+
+function cleanHtml( html ) {
+	// Space between table elements.
+	html = html.replace( /(th|td|tr)>\s+<(\/?(?:th|td|tr))/g, '$1><$2' );
+	return html;
+}
+
+function cleanMarkdown( markdown ) {
+	// Trim spaces at the end of the lines.
+	markdown = markdown.replace( / +$/gm, '' );
+	// Trim linebreak at the very beginning.
+	markdown = markdown.replace( /^\s+/g, '' );
+	return markdown;
 }
 }

+ 9 - 9
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/blockquotes.js

@@ -23,9 +23,9 @@ describe( 'GFMDataProcessor', () => {
 		it( 'should process nested blockquotes', () => {
 		it( 'should process nested blockquotes', () => {
 			testDataProcessor(
 			testDataProcessor(
 				'> foo\n' +
 				'> foo\n' +
-				'> \n' +
+				'>\n' +
 				'> > bar\n' +
 				'> > bar\n' +
-				'> \n' +
+				'>\n' +
 				'> foo',
 				'> foo',
 
 
 				// GitHub is rendering as:
 				// GitHub is rendering as:
@@ -51,7 +51,7 @@ describe( 'GFMDataProcessor', () => {
 		it( 'should process list within a blockquote', () => {
 		it( 'should process list within a blockquote', () => {
 			testDataProcessor(
 			testDataProcessor(
 				'> A list within a blockquote:\n' +
 				'> A list within a blockquote:\n' +
-				'> \n' +
+				'>\n' +
 				'> *   asterisk 1\n' +
 				'> *   asterisk 1\n' +
 				'> *   asterisk 2\n' +
 				'> *   asterisk 2\n' +
 				'> *   asterisk 3',
 				'> *   asterisk 3',
@@ -80,13 +80,13 @@ describe( 'GFMDataProcessor', () => {
 		it( 'should process blockquotes with code inside with ```', () => {
 		it( 'should process blockquotes with code inside with ```', () => {
 			testDataProcessor(
 			testDataProcessor(
 				'> Example 1:\n' +
 				'> Example 1:\n' +
-				'> \n' +
+				'>\n' +
 				'> ```\n' +
 				'> ```\n' +
 				'> code 1\n' +
 				'> code 1\n' +
 				'> ```\n' +
 				'> ```\n' +
-				'> \n' +
+				'>\n' +
 				'> Example 2:\n' +
 				'> Example 2:\n' +
-				'> \n' +
+				'>\n' +
 				'> ```\n' +
 				'> ```\n' +
 				'> code 2\n' +
 				'> code 2\n' +
 				'> ```',
 				'> ```',
@@ -159,13 +159,13 @@ describe( 'GFMDataProcessor', () => {
 
 
 				// When converting back to data, DataProcessor will normalize tabs to ```.
 				// When converting back to data, DataProcessor will normalize tabs to ```.
 				'> Example 1:\n' +
 				'> Example 1:\n' +
-				'> \n' +
+				'>\n' +
 				'> ```\n' +
 				'> ```\n' +
 				'> code 1\n' +
 				'> code 1\n' +
 				'> ```\n' +
 				'> ```\n' +
-				'> \n' +
+				'>\n' +
 				'> Example 2:\n' +
 				'> Example 2:\n' +
-				'> \n' +
+				'>\n' +
 				'> ```\n' +
 				'> ```\n' +
 				'> code 2\n' +
 				'> code 2\n' +
 				'> ```'
 				'> ```'

+ 54 - 18
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/code.js

@@ -45,7 +45,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should properly process backticks inside code spans #2', () => {
 		it( 'should properly process backticks inside code spans #2', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'`` some `backticks` inside ``',
+				'``some `backticks` inside``',
 
 
 				'<p><code>some `backticks` inside</code></p>'
 				'<p><code>some `backticks` inside</code></p>'
 			);
 			);
@@ -142,17 +142,17 @@ describe( 'GFMDataProcessor', () => {
 				'<pre><code>the lines in this block  \n' +
 				'<pre><code>the lines in this block  \n' +
 				'all contain trailing spaces  </code></pre>',
 				'all contain trailing spaces  </code></pre>',
 
 
-				// When converting back tabs are normalized to ```.
+				// When converting back tabs are normalized to ```, while the test function remove trailing spaces.
 				'```\n' +
 				'```\n' +
-				'the lines in this block  \n' +
-				'all contain trailing spaces  \n' +
+				'the lines in this block\n' +
+				'all contain trailing spaces\n' +
 				'```'
 				'```'
 			);
 			);
 		} );
 		} );
 
 
 		it( 'should process code block with language name', () => {
 		it( 'should process code block with language name', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'``` js\n' +
+				'```js\n' +
 				'var a = \'hello\';\n' +
 				'var a = \'hello\';\n' +
 				'console.log(a + \' world\');\n' +
 				'console.log(a + \' world\');\n' +
 				'```',
 				'```',
@@ -160,7 +160,7 @@ describe( 'GFMDataProcessor', () => {
 				// GitHub is rendering as special html with syntax highlighting.
 				// GitHub is rendering as special html with syntax highlighting.
 				// We will need to handle this separately by some feature.
 				// We will need to handle this separately by some feature.
 
 
-				'<pre><code class="lang-js">var a = \'hello\';\n' +
+				'<pre><code class="language-js">var a = \'hello\';\n' +
 				'console.log(a + \' world\');</code></pre>'
 				'console.log(a + \' world\');</code></pre>'
 			);
 			);
 		} );
 		} );
@@ -174,11 +174,11 @@ describe( 'GFMDataProcessor', () => {
 				// GitHub is rendering as special html with syntax highlighting.
 				// GitHub is rendering as special html with syntax highlighting.
 				// We will need to handle this separately by some feature.
 				// We will need to handle this separately by some feature.
 
 
-				'<pre><code class="lang-bash">#!/bin/bash</code></pre>',
+				'<pre><code class="language-bash">#!/bin/bash</code></pre>',
 
 
 				// When converting back ~~~ are normalized to ```.
 				// When converting back ~~~ are normalized to ```.
 
 
-				'``` bash\n' +
+				'```bash\n' +
 				'#!/bin/bash\n' +
 				'#!/bin/bash\n' +
 				'```'
 				'```'
 			);
 			);
@@ -186,7 +186,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process code block with language name and using ``````` as delimiter', () => {
 		it( 'should process code block with language name and using ``````` as delimiter', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'``````` js\n' +
+				'```````js\n' +
 				'var a = \'hello\';\n' +
 				'var a = \'hello\';\n' +
 				'console.log(a + \' world\');\n' +
 				'console.log(a + \' world\');\n' +
 				'```````',
 				'```````',
@@ -194,12 +194,12 @@ describe( 'GFMDataProcessor', () => {
 				// GitHub is rendering as special html with syntax highlighting.
 				// GitHub is rendering as special html with syntax highlighting.
 				// We will need to handle this separately by some feature.
 				// We will need to handle this separately by some feature.
 
 
-				'<pre><code class="lang-js">var a = \'hello\';\n' +
+				'<pre><code class="language-js">var a = \'hello\';\n' +
 				'console.log(a + \' world\');</code></pre>',
 				'console.log(a + \' world\');</code></pre>',
 
 
 				// When converting back ``````` are normalized to ```.
 				// When converting back ``````` are normalized to ```.
 
 
-				'``` js\n' +
+				'```js\n' +
 				'var a = \'hello\';\n' +
 				'var a = \'hello\';\n' +
 				'console.log(a + \' world\');\n' +
 				'console.log(a + \' world\');\n' +
 				'```'
 				'```'
@@ -216,12 +216,12 @@ describe( 'GFMDataProcessor', () => {
 				// GitHub is rendering as special html with syntax highlighting.
 				// GitHub is rendering as special html with syntax highlighting.
 				// We will need to handle this separately by some feature.
 				// We will need to handle this separately by some feature.
 
 
-				'<pre><code class="lang-js">var a = \'hello\';\n' +
+				'<pre><code class="language-js">var a = \'hello\';\n' +
 				'console.log(a + \' world\');</code></pre>',
 				'console.log(a + \' world\');</code></pre>',
 
 
 				// When converting back ~~~~~~~~~~ are normalized to ```.
 				// When converting back ~~~~~~~~~~ are normalized to ```.
 
 
-				'``` js\n' +
+				'```js\n' +
 				'var a = \'hello\';\n' +
 				'var a = \'hello\';\n' +
 				'console.log(a + \' world\');\n' +
 				'console.log(a + \' world\');\n' +
 				'```'
 				'```'
@@ -230,13 +230,13 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process empty code block', () => {
 		it( 'should process empty code block', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'``` js\n' +
+				'```js\n' +
 				'```',
 				'```',
 
 
 				// GitHub is rendering as special html with syntax highlighting.
 				// GitHub is rendering as special html with syntax highlighting.
 				// We will need to handle this separately by some feature.
 				// We will need to handle this separately by some feature.
 
 
-				'<pre><code class="lang-js"></code></pre>',
+				'<pre><code class="language-js"></code></pre>',
 
 
 				// When converting back, empty code blocks will be removed.
 				// When converting back, empty code blocks will be removed.
 				// This might be an issue when switching from source to editor
 				// This might be an issue when switching from source to editor
@@ -247,14 +247,14 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process code block with empty line', () => {
 		it( 'should process code block with empty line', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'``` js\n' +
+				'```js\n' +
 				'\n' +
 				'\n' +
 				'```',
 				'```',
 
 
 				// GitHub is rendering as special html with syntax highlighting.
 				// GitHub is rendering as special html with syntax highlighting.
 				// We will need to handle this separately by some feature.
 				// We will need to handle this separately by some feature.
 
 
-				'<pre><code class="lang-js"></code></pre>',
+				'<pre><code class="language-js"></code></pre>',
 
 
 				// When converting back, empty code blocks will be removed.
 				// When converting back, empty code blocks will be removed.
 				// This might be an issue when switching from source to editor
 				// This might be an issue when switching from source to editor
@@ -273,7 +273,43 @@ describe( 'GFMDataProcessor', () => {
 				'<p><code>code `` code ```</code></p>',
 				'<p><code>code `` code ```</code></p>',
 
 
 				// When converting back ````` will be normalized to ``.
 				// When converting back ````` will be normalized to ``.
-				'`` code `` code ``` ``'
+				'`code `` code ``` `'
+			);
+		} );
+
+		it( 'should handle triple ticks inside code', () => {
+			testDataProcessor(
+				'````\n' +
+				'```\n' +
+				'Code\n' +
+				'```\n' +
+				'````',
+
+				'<pre><code>' +
+				'```\n' +
+				'Code\n' +
+				'```' +
+				'</code></pre>'
+			);
+		} );
+
+		it( 'should handle triple and quatruple ticks inside code', () => {
+			testDataProcessor(
+				'`````\n' +
+				'````\n' +
+				'```\n' +
+				'Code\n' +
+				'```\n' +
+				'````\n' +
+				'`````',
+
+				'<pre><code>' +
+				'````\n' +
+				'```\n' +
+				'Code\n' +
+				'```\n' +
+				'````' +
+				'</code></pre>'
 			);
 			);
 		} );
 		} );
 	} );
 	} );

+ 41 - 7
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/escaping.js

@@ -5,6 +5,7 @@
 
 
 import MarkdownDataProcessor from '../../src/gfmdataprocessor';
 import MarkdownDataProcessor from '../../src/gfmdataprocessor';
 import { stringify } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 import { stringify } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
+import { testDataProcessor } from '../../tests/_utils/utils';
 import ViewDocument from '@ckeditor/ckeditor5-engine/src/view/document';
 import ViewDocument from '@ckeditor/ckeditor5-engine/src/view/document';
 import { StylesProcessor } from '@ckeditor/ckeditor5-engine/src/view/stylesmap';
 import { StylesProcessor } from '@ckeditor/ckeditor5-engine/src/view/stylesmap';
 
 
@@ -26,15 +27,15 @@ const testCases = {
 };
 };
 
 
 describe( 'GFMDataProcessor', () => {
 describe( 'GFMDataProcessor', () => {
-	let dataProcessor;
-
-	beforeEach( () => {
-		const viewDocument = new ViewDocument( new StylesProcessor() );
-		dataProcessor = new MarkdownDataProcessor( viewDocument );
-	} );
-
 	describe( 'escaping', () => {
 	describe( 'escaping', () => {
 		describe( 'toView', () => {
 		describe( 'toView', () => {
+			let dataProcessor;
+
+			beforeEach( () => {
+				const viewDocument = new ViewDocument( new StylesProcessor() );
+				dataProcessor = new MarkdownDataProcessor( viewDocument );
+			} );
+
 			for ( const key in testCases ) {
 			for ( const key in testCases ) {
 				const test = testCases[ key ].test;
 				const test = testCases[ key ].test;
 				const result = testCases[ key ].result;
 				const result = testCases[ key ].result;
@@ -70,5 +71,38 @@ describe( 'GFMDataProcessor', () => {
 				expect( stringify( documentFragment ) ).to.equal( '<pre><code>\\`</code></pre>' );
 				expect( stringify( documentFragment ) ).to.equal( '<pre><code>\\`</code></pre>' );
 			} );
 			} );
 		} );
 		} );
+
+		describe( 'HTML', () => {
+			// To note that the test util inlines entities in text nodes, hence the expected HTML in these tests
+			// contain the raw characters but we "know" that those are text nodes and therefore should be converted
+			// back to entities when outputting markdown.
+
+			it( 'should escape <', () => {
+				testDataProcessor( '\\<', '<p><</p>' );
+			} );
+
+			it( 'should escape HTML as text', () => {
+				testDataProcessor( '\\<h1>Test\\</h1>', '<p><h1>Test</h1></p>' );
+			} );
+
+			it( 'should not escape \\< inside inline code', () => {
+				testDataProcessor( '`\\<`', '<p><code>\\<</code></p>' );
+			} );
+
+			it( 'should not touch escape-like HTML inside code blocks', () => {
+				testDataProcessor(
+					'```\n' +
+					'\\<h1>Test\\</h1>\n' +
+					'```',
+					'<pre><code>' +
+					'\\<h1>Test\\</h1>' +
+					'</code></pre>' );
+			} );
+
+			// Necessary test as we're overriding Turndown's escape(). Just to be sure.
+			it( 'should still escape markdown characters', () => {
+				testDataProcessor( '\\* \\_', '<p>* _</p>' );
+			} );
+		} );
 	} );
 	} );
 } );
 } );

+ 12 - 2
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/headers.js

@@ -95,7 +95,11 @@ describe( 'GFMDataProcessor', () => {
 				'# header\n' +
 				'# header\n' +
 				'# header',
 				'# header',
 
 
-				'<h1>header</h1><h1>header</h1>'
+				'<h1>header</h1><h1>header</h1>',
+
+				'# header\n' +
+				'\n' +
+				'# header'
 			);
 			);
 		} );
 		} );
 
 
@@ -105,7 +109,13 @@ describe( 'GFMDataProcessor', () => {
 				'## header\n' +
 				'## header\n' +
 				'### header',
 				'### header',
 
 
-				'<h1>header</h1><h2>header</h2><h3>header</h3>'
+				'<h1>header</h1><h2>header</h2><h3>header</h3>',
+
+				'# header\n' +
+				'\n' +
+				'## header\n' +
+				'\n' +
+				'### header'
 			);
 			);
 		} );
 		} );
 
 

+ 26 - 26
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/horizontal-rules.js

@@ -6,24 +6,24 @@
 import { testDataProcessor } from '../_utils/utils';
 import { testDataProcessor } from '../_utils/utils';
 
 
 describe( 'GFMDataProcessor', () => {
 describe( 'GFMDataProcessor', () => {
-	// Horizontal rules are are always rendered by GitHub as <hr> and normalized when converting
-	// back to * * *.
+	// Horizontal rules are always rendered by GitHub as <hr> and normalized when converting
+	// back to ---.
 	describe( 'horizontal rules', () => {
 	describe( 'horizontal rules', () => {
 		describe( 'dashes', () => {
 		describe( 'dashes', () => {
 			it( '#1', () => {
 			it( '#1', () => {
-				testDataProcessor( '---', '<hr></hr>', '* * *' );
+				testDataProcessor( '---', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#2', () => {
 			it( '#2', () => {
-				testDataProcessor( ' ---', '<hr></hr>', '* * *' );
+				testDataProcessor( ' ---', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#3', () => {
 			it( '#3', () => {
-				testDataProcessor( '  ---', '<hr></hr>', '* * *' );
+				testDataProcessor( '  ---', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#4', () => {
 			it( '#4', () => {
-				testDataProcessor( '   ---', '<hr></hr>', '* * *' );
+				testDataProcessor( '   ---', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#5 - code', () => {
 			it( '#5 - code', () => {
@@ -43,19 +43,19 @@ describe( 'GFMDataProcessor', () => {
 
 
 		describe( 'dashes with spaces', () => {
 		describe( 'dashes with spaces', () => {
 			it( '#1', () => {
 			it( '#1', () => {
-				testDataProcessor( '- - -', '<hr></hr>', '* * *' );
+				testDataProcessor( '- - -', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#2', () => {
 			it( '#2', () => {
-				testDataProcessor( ' - - -', '<hr></hr>', '* * *' );
+				testDataProcessor( ' - - -', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#3', () => {
 			it( '#3', () => {
-				testDataProcessor( '  - - -', '<hr></hr>', '* * *' );
+				testDataProcessor( '  - - -', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#4', () => {
 			it( '#4', () => {
-				testDataProcessor( '   - - -', '<hr></hr>', '* * *' );
+				testDataProcessor( '   - - -', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#5 - code', () => {
 			it( '#5 - code', () => {
@@ -75,19 +75,19 @@ describe( 'GFMDataProcessor', () => {
 
 
 		describe( 'asterisks', () => {
 		describe( 'asterisks', () => {
 			it( '#1', () => {
 			it( '#1', () => {
-				testDataProcessor( '***', '<hr></hr>', '* * *' );
+				testDataProcessor( '***', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#2', () => {
 			it( '#2', () => {
-				testDataProcessor( ' ***', '<hr></hr>', '* * *' );
+				testDataProcessor( ' ***', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#3', () => {
 			it( '#3', () => {
-				testDataProcessor( '  ***', '<hr></hr>', '* * *' );
+				testDataProcessor( '  ***', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#4', () => {
 			it( '#4', () => {
-				testDataProcessor( '   ***', '<hr></hr>', '* * *' );
+				testDataProcessor( '   ***', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#5 - code', () => {
 			it( '#5 - code', () => {
@@ -107,19 +107,19 @@ describe( 'GFMDataProcessor', () => {
 
 
 		describe( 'asterisks with spaces', () => {
 		describe( 'asterisks with spaces', () => {
 			it( '#1', () => {
 			it( '#1', () => {
-				testDataProcessor( '* * *', '<hr></hr>', '* * *' );
+				testDataProcessor( '* * *', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#2', () => {
 			it( '#2', () => {
-				testDataProcessor( ' * * *', '<hr></hr>', '* * *' );
+				testDataProcessor( ' * * *', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#3', () => {
 			it( '#3', () => {
-				testDataProcessor( '  * * *', '<hr></hr>', '* * *' );
+				testDataProcessor( '  * * *', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#4', () => {
 			it( '#4', () => {
-				testDataProcessor( '   * * *', '<hr></hr>', '* * *' );
+				testDataProcessor( '   * * *', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#5 - code', () => {
 			it( '#5 - code', () => {
@@ -139,19 +139,19 @@ describe( 'GFMDataProcessor', () => {
 
 
 		describe( 'underscores', () => {
 		describe( 'underscores', () => {
 			it( '#1', () => {
 			it( '#1', () => {
-				testDataProcessor( '___', '<hr></hr>', '* * *' );
+				testDataProcessor( '___', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#2', () => {
 			it( '#2', () => {
-				testDataProcessor( ' ___', '<hr></hr>', '* * *' );
+				testDataProcessor( ' ___', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#3', () => {
 			it( '#3', () => {
-				testDataProcessor( '  ___', '<hr></hr>', '* * *' );
+				testDataProcessor( '  ___', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#4', () => {
 			it( '#4', () => {
-				testDataProcessor( '   ___', '<hr></hr>', '* * *' );
+				testDataProcessor( '   ___', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#5 - code', () => {
 			it( '#5 - code', () => {
@@ -171,19 +171,19 @@ describe( 'GFMDataProcessor', () => {
 
 
 		describe( 'underscores with spaces', () => {
 		describe( 'underscores with spaces', () => {
 			it( '#1', () => {
 			it( '#1', () => {
-				testDataProcessor( '_ _ _', '<hr></hr>', '* * *' );
+				testDataProcessor( '_ _ _', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#2', () => {
 			it( '#2', () => {
-				testDataProcessor( ' _ _ _', '<hr></hr>', '* * *' );
+				testDataProcessor( ' _ _ _', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#3', () => {
 			it( '#3', () => {
-				testDataProcessor( '  _ _ _', '<hr></hr>', '* * *' );
+				testDataProcessor( '  _ _ _', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#4', () => {
 			it( '#4', () => {
-				testDataProcessor( '   _ _ _', '<hr></hr>', '* * *' );
+				testDataProcessor( '   _ _ _', '<hr></hr>', '---' );
 			} );
 			} );
 
 
 			it( '#5 - code', () => {
 			it( '#5 - code', () => {

+ 26 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/html.js

@@ -0,0 +1,26 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import { testDataProcessor } from '../_utils/utils';
+
+describe( 'GFMDataProcessor', () => {
+	describe( 'html', () => {
+		it( 'should keep html', () => {
+			testDataProcessor(
+				'test with <keep>html</keep> and <notkeep>not html</notkeep>',
+
+				'<p>test with <keep>html</keep> and <notkeep>not html</notkeep></p>',
+
+				'test with <keep>html</keep> and not html',
+
+				{
+					setup: dataProcessor => {
+						dataProcessor.keepHtml( 'keep' );
+					}
+				}
+			);
+		} );
+	} );
+} );

+ 3 - 3
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/images.js

@@ -35,7 +35,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process referenced images', () => {
 		it( 'should process referenced images', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'![alt text][logo]\n' +
+				'![alt text][logo]\n\n' +
 				'[logo]: http://example.com/image.png "title text"',
 				'[logo]: http://example.com/image.png "title text"',
 
 
 				'<p><img alt="alt text" src="http://example.com/image.png" title="title text"></img></p>',
 				'<p><img alt="alt text" src="http://example.com/image.png" title="title text"></img></p>',
@@ -47,7 +47,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process referenced images without title', () => {
 		it( 'should process referenced images without title', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'![alt text][logo]\n' +
+				'![alt text][logo]\n\n' +
 				'[logo]: http://example.com/image.png',
 				'[logo]: http://example.com/image.png',
 
 
 				'<p><img alt="alt text" src="http://example.com/image.png"></img></p>',
 				'<p><img alt="alt text" src="http://example.com/image.png"></img></p>',
@@ -59,7 +59,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process referenced images without alt text', () => {
 		it( 'should process referenced images without alt text', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'![][logo]\n' +
+				'![][logo]\n\n' +
 				'[logo]: http://example.com/image.png "title text"',
 				'[logo]: http://example.com/image.png "title text"',
 
 
 				'<p><img alt="" src="http://example.com/image.png" title="title text"></img></p>',
 				'<p><img alt="" src="http://example.com/image.png" title="title text"></img></p>',

+ 47 - 125
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/links.js

@@ -7,57 +7,34 @@ import { testDataProcessor } from '../_utils/utils';
 
 
 describe( 'GFMDataProcessor', () => {
 describe( 'GFMDataProcessor', () => {
 	describe( 'links', () => {
 	describe( 'links', () => {
-		it( 'should autolink', () => {
-			testDataProcessor(
-				'Link: <http://example.com/>.',
-				'<p>Link: <a href="http://example.com/">http://example.com/</a>.</p>',
-
-				// When converting back it will be represented as standard markdown link.
-				'Link: [http://example.com/](http://example.com/).'
-			);
-		} );
-
-		it( 'should autolink #2', () => {
+		it( 'should not autolink', () => {
 			testDataProcessor(
 			testDataProcessor(
 				'Link: http://example.com/.',
 				'Link: http://example.com/.',
-				'<p>Link: <a href="http://example.com/">http://example.com/</a>.</p>',
-
-				// When converting back it will be represented as standard markdown link.
-				'Link: [http://example.com/](http://example.com/).'
+				'<p>Link: http://example.com/.</p>'
 			);
 			);
 		} );
 		} );
 
 
-		it( 'should autolink with params', () => {
+		it( 'should not autolink with params', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'Link: <http://example.com/?foo=1&bar=2>.',
-				'<p>Link: <a href="http://example.com/?foo=1&bar=2">http://example.com/?foo=1&bar=2</a>.</p>',
-
-				// When converting back it will be represented as standard markdown link.
-				'Link: [http://example.com/?foo=1&bar=2](http://example.com/?foo=1&bar=2).'
+				'Link: http://example.com/?foo=1&bar=2.',
+				'<p>Link: http://example.com/?foo=1&bar=2.</p>'
 			);
 			);
 		} );
 		} );
 
 
-		it( 'should autolink inside list', () => {
+		it( 'should not autolink inside list', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'* <http://example.com/>',
-
-				'<ul><li><a href="http://example.com/">http://example.com/</a></li></ul>',
-
-				// When converting back it will be represented as standard markdown link.
-				'*   [http://example.com/](http://example.com/)'
+				'*   http://example.com/',
+				'<ul><li>http://example.com/</li></ul>'
 			);
 			);
 		} );
 		} );
 
 
-		it( 'should autolink inside blockquote', () => {
+		it( 'should not autolink inside blockquote', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'> Blockquoted: <http://example.com/>',
+				'> Blockquoted: http://example.com/',
 
 
 				'<blockquote>' +
 				'<blockquote>' +
-				'<p>Blockquoted: <a href="http://example.com/">http://example.com/</a></p>' +
-				'</blockquote>',
-
-				// When converting back it will be represented as standard markdown link.
-				'> Blockquoted: [http://example.com/](http://example.com/)'
+				'<p>Blockquoted: http://example.com/</p>' +
+				'</blockquote>'
 			);
 			);
 		} );
 		} );
 
 
@@ -138,37 +115,17 @@ describe( 'GFMDataProcessor', () => {
 			);
 			);
 		} );
 		} );
 
 
-		it( 'should process inline links with spaces in URL', () => {
-			testDataProcessor(
-				'[URL and title]( /url/has space )',
-				'<p><a href="/url/has space">URL and title</a></p>',
-
-				// When converting back unneeded spaces will be removed.
-				'[URL and title](/url/has space)'
-			);
-		} );
-
-		it( 'should process inline links with titles and spaces in URL', () => {
-			testDataProcessor(
-				'[URL and title]( /url/has space/ "url has space and title")',
-				'<p><a href="/url/has space/" title="url has space and title">URL and title</a></p>',
-
-				// When converting back unneeded spaces will be removed.
-				'[URL and title](/url/has space/ "url has space and title")'
-			);
-		} );
-
-		it( 'should process empty link', () => {
-			testDataProcessor(
-				'[Empty]()',
-
-				'<p><a href="">Empty</a></p>'
-			);
-		} );
+		// it( 'should process empty link', () => {
+		// 	testDataProcessor(
+		// 		'[Empty]()',
+		//
+		// 		'<p><a href="">Empty</a></p>'
+		// 	);
+		// } );
 
 
 		it( 'should process reference links', () => {
 		it( 'should process reference links', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'Foo [bar] [1].\n' +
+				'Foo [bar][1].\n\n' +
 				'[1]: /url/  "Title"',
 				'[1]: /url/  "Title"',
 
 
 				'<p>Foo <a href="/url/" title="Title">bar</a>.</p>',
 				'<p>Foo <a href="/url/" title="Title">bar</a>.</p>',
@@ -181,19 +138,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process reference links - without space', () => {
 		it( 'should process reference links - without space', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'Foo [bar][1].\n' +
-				'[1]: /url/  "Title"',
-
-				'<p>Foo <a href="/url/" title="Title">bar</a>.</p>',
-
-				'Foo [bar](/url/ "Title").'
-			);
-		} );
-
-		it( 'should process reference links - with newline', () => {
-			testDataProcessor(
-				'Foo [bar]\n' +
-				'[1].\n' +
+				'Foo [bar][1].\n\n' +
 				'[1]: /url/  "Title"',
 				'[1]: /url/  "Title"',
 
 
 				'<p>Foo <a href="/url/" title="Title">bar</a>.</p>',
 				'<p>Foo <a href="/url/" title="Title">bar</a>.</p>',
@@ -204,18 +149,18 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process reference links - with embedded brackets', () => {
 		it( 'should process reference links - with embedded brackets', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'With [embedded [brackets]] [b].\n' +
+				'With [embedded [brackets]][b].\n\n' +
 				'[b]: /url/',
 				'[b]: /url/',
 
 
 				'<p>With <a href="/url/">embedded [brackets]</a>.</p>',
 				'<p>With <a href="/url/">embedded [brackets]</a>.</p>',
 
 
-				'With [embedded [brackets]](/url/).'
+				'With [embedded \\[brackets\\]](/url/).'
 			);
 			);
 		} );
 		} );
 
 
 		it( 'should process reference links - with reference indented once', () => {
 		it( 'should process reference links - with reference indented once', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'Indented [once][].\n' +
+				'Indented [once][].\n\n' +
 				' [once]: /url',
 				' [once]: /url',
 
 
 				'<p>Indented <a href="/url">once</a>.</p>',
 				'<p>Indented <a href="/url">once</a>.</p>',
@@ -226,7 +171,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process reference links - with reference indented twice', () => {
 		it( 'should process reference links - with reference indented twice', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'Indented [twice][].\n' +
+				'Indented [twice][].\n\n' +
 				'  [twice]: /url',
 				'  [twice]: /url',
 
 
 				'<p>Indented <a href="/url">twice</a>.</p>',
 				'<p>Indented <a href="/url">twice</a>.</p>',
@@ -237,7 +182,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process reference links - with reference indented three times', () => {
 		it( 'should process reference links - with reference indented three times', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'Indented [trice][].\n' +
+				'Indented [trice][].\n\n' +
 				'   [trice]: /url',
 				'   [trice]: /url',
 
 
 				'<p>Indented <a href="/url">trice</a>.</p>',
 				'<p>Indented <a href="/url">trice</a>.</p>',
@@ -246,28 +191,9 @@ describe( 'GFMDataProcessor', () => {
 			);
 			);
 		} );
 		} );
 
 
-		it( 'should NOT process reference links - with reference indented four times', () => {
-			testDataProcessor(
-				'Indented [four][].\n' +
-				'    [four]: /url',
-
-				// GitHub renders it as:
-				// <p>Indented [four][].<br>
-				// [four]: /url</p>
-				// Marked converts it to the code block.
-				'<p>Indented [four][].</p><pre><code>[four]: /url</code></pre>',
-
-				'Indented [four][].\n' +
-				'\n' +
-				'```\n' +
-				'[four]: /url\n' +
-				'```'
-			);
-		} );
-
 		it( 'should process reference links when title and reference are same #1', () => {
 		it( 'should process reference links when title and reference are same #1', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'[this] [this]\n' +
+				'[this][this]\n\n' +
 				'[this]: foo',
 				'[this]: foo',
 
 
 				'<p><a href="foo">this</a></p>',
 				'<p><a href="foo">this</a></p>',
@@ -278,7 +204,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process reference links when title and reference are same #2', () => {
 		it( 'should process reference links when title and reference are same #2', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'[this][this]\n' +
+				'[this][this]\n\n' +
 				'[this]: foo',
 				'[this]: foo',
 
 
 				'<p><a href="foo">this</a></p>',
 				'<p><a href="foo">this</a></p>',
@@ -289,7 +215,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process reference links when only title is provided and is same as reference #1', () => {
 		it( 'should process reference links when only title is provided and is same as reference #1', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'[this] []\n' +
+				'[this][]\n\n' +
 				'[this]: foo',
 				'[this]: foo',
 
 
 				'<p><a href="foo">this</a></p>',
 				'<p><a href="foo">this</a></p>',
@@ -300,7 +226,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process reference links when only title is provided and is same as reference #2', () => {
 		it( 'should process reference links when only title is provided and is same as reference #2', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'[this][]\n' +
+				'[this][]\n\n' +
 				'[this]: foo',
 				'[this]: foo',
 
 
 				'<p><a href="foo">this</a></p>',
 				'<p><a href="foo">this</a></p>',
@@ -311,7 +237,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process reference links when only title is provided and is same as reference #3', () => {
 		it( 'should process reference links when only title is provided and is same as reference #3', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'[this]\n' +
+				'[this]\n\n' +
 				'[this]: foo',
 				'[this]: foo',
 
 
 				'<p><a href="foo">this</a></p>',
 				'<p><a href="foo">this</a></p>',
@@ -321,18 +247,12 @@ describe( 'GFMDataProcessor', () => {
 		} );
 		} );
 
 
 		it( 'should not process reference links when reference is not found #1', () => {
 		it( 'should not process reference links when reference is not found #1', () => {
-			testDataProcessor(
-				'[this] []',
-
-				'<p>[this] []</p>'
-			);
-		} );
-
-		it( 'should not process reference links when reference is not found #2', () => {
 			testDataProcessor(
 			testDataProcessor(
 				'[this][]',
 				'[this][]',
 
 
-				'<p>[this][]</p>'
+				'<p>[this][]</p>',
+
+				'\\[this\\]\\[\\]'
 			);
 			);
 		} );
 		} );
 
 
@@ -340,35 +260,37 @@ describe( 'GFMDataProcessor', () => {
 			testDataProcessor(
 			testDataProcessor(
 				'[this]',
 				'[this]',
 
 
-				'<p>[this]</p>'
+				'<p>[this]</p>',
+
+				'\\[this\\]'
 			);
 			);
 		} );
 		} );
 
 
 		it( 'should process reference links nested in brackets #1', () => {
 		it( 'should process reference links nested in brackets #1', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'[a reference inside [this][]]\n' +
+				'[a reference inside [this][]]\n\n' +
 				'[this]: foo',
 				'[this]: foo',
 
 
 				'<p>[a reference inside <a href="foo">this</a>]</p>',
 				'<p>[a reference inside <a href="foo">this</a>]</p>',
 
 
-				'[a reference inside [this](foo)]'
+				'\\[a reference inside [this](foo)\\]'
 			);
 			);
 		} );
 		} );
 
 
 		it( 'should process reference links nested in brackets #2', () => {
 		it( 'should process reference links nested in brackets #2', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'[a reference inside [this]]\n' +
+				'[a reference inside [this]]\n\n' +
 				'[this]: foo',
 				'[this]: foo',
 
 
 				'<p>[a reference inside <a href="foo">this</a>]</p>',
 				'<p>[a reference inside <a href="foo">this</a>]</p>',
 
 
-				'[a reference inside [this](foo)]'
+				'\\[a reference inside [this](foo)\\]'
 			);
 			);
 		} );
 		} );
 
 
 		it( 'should not process reference links when title is same as reference but reference is different', () => {
 		it( 'should not process reference links when title is same as reference but reference is different', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'[this](/something/else/)\n' +
+				'[this](/something/else/)\n\n' +
 				'[this]: foo',
 				'[this]: foo',
 
 
 				'<p><a href="/something/else/">this</a></p>',
 				'<p><a href="/something/else/">this</a></p>',
@@ -379,19 +301,19 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should not process reference links suppressed by backslashes', () => {
 		it( 'should not process reference links suppressed by backslashes', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'Suppress \\[this] and [this\\].\n' +
+				'Suppress \\[this] and [this\\].\n\n' +
 				'[this]: foo',
 				'[this]: foo',
 
 
 				'<p>Suppress [this] and [this].</p>',
 				'<p>Suppress [this] and [this].</p>',
 
 
-				'Suppress [this] and [this].'
+				'Suppress \\[this\\] and \\[this\\].'
 			);
 			);
 		} );
 		} );
 
 
 		it( 'should process reference links when used across multiple lines #1', () => {
 		it( 'should process reference links when used across multiple lines #1', () => {
 			testDataProcessor(
 			testDataProcessor(
 				'This is [multiline\n' +
 				'This is [multiline\n' +
-				'reference]\n' +
+				'reference]\n\n' +
 				'[multiline reference]: foo',
 				'[multiline reference]: foo',
 
 
 				'<p>This is <a href="foo">multiline<br></br>reference</a></p>',
 				'<p>This is <a href="foo">multiline<br></br>reference</a></p>',
@@ -404,7 +326,7 @@ describe( 'GFMDataProcessor', () => {
 		it( 'should process reference links when used across multiple lines #2', () => {
 		it( 'should process reference links when used across multiple lines #2', () => {
 			testDataProcessor(
 			testDataProcessor(
 				'This is [multiline \n' +
 				'This is [multiline \n' +
-				'reference]\n' +
+				'reference]\n\n' +
 				'[multiline reference]: foo',
 				'[multiline reference]: foo',
 
 
 				'<p>This is <a href="foo">multiline<br></br>reference</a></p>',
 				'<p>This is <a href="foo">multiline<br></br>reference</a></p>',
@@ -416,7 +338,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process reference links case-insensitive', () => {
 		it( 'should process reference links case-insensitive', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'[hi]\n' +
+				'[hi]\n\n' +
 				'[HI]: /url',
 				'[HI]: /url',
 
 
 				'<p><a href="/url">hi</a></p>',
 				'<p><a href="/url">hi</a></p>',

+ 42 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/lists.js

@@ -4,6 +4,10 @@
  */
  */
 
 
 import { testDataProcessor } from '../_utils/utils';
 import { testDataProcessor } from '../_utils/utils';
+import MarkdownDataProcessor from '../../src/gfmdataprocessor';
+import HtmlDataProcessor from '@ckeditor/ckeditor5-engine/src/dataprocessor/htmldataprocessor';
+import ViewDocument from '@ckeditor/ckeditor5-engine/src/view/document';
+import { StylesProcessor } from '@ckeditor/ckeditor5-engine/src/view/stylesmap';
 
 
 describe( 'GFMDataProcessor', () => {
 describe( 'GFMDataProcessor', () => {
 	describe( 'lists', () => {
 	describe( 'lists', () => {
@@ -340,4 +344,42 @@ describe( 'GFMDataProcessor', () => {
 			);
 			);
 		} );
 		} );
 	} );
 	} );
+
+	describe( 'todo lists', () => {
+		it( 'should process todo lists', () => {
+			testDataProcessor(
+				'*   [ ] Item 1\n' +
+				'*   [x] Item 2',
+
+				'<ul>' +
+					'<li><input disabled="" type="checkbox"></input>Item 1</li>' +
+					'<li><input checked="" disabled="" type="checkbox"></input>Item 2</li>' +
+				'</ul>' );
+		} );
+
+		it( 'should process the HTML produced by the todo list feature', () => {
+			const viewDocument = new ViewDocument( new StylesProcessor() );
+
+			const htmlDataProcessor = new HtmlDataProcessor( viewDocument );
+			const mdDataProcessor = new MarkdownDataProcessor( viewDocument );
+
+			const viewFragment = htmlDataProcessor.toView(
+				'<ul class="todo-list">' +
+					'<li><label class="todo-list__label">' +
+						'<input type="checkbox" disabled="disabled">' +
+						'<span class="todo-list__label__description">Item 1</span>' +
+					'</label></li>' +
+					'<li><label class="todo-list__label">' +
+						'<input type="checkbox" disabled="disabled" checked="checked">' +
+						'<span class="todo-list__label__description">Item 2</span>' +
+					'</label></li>' +
+				'</ul>'
+			);
+
+			expect( mdDataProcessor.toData( viewFragment ) ).to.equal(
+				'*   [ ] Item 1\n' +
+				'*   [x] Item 2'
+			);
+		} );
+	} );
 } );
 } );

+ 6 - 3
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/manual/gfmdataprocessor.js

@@ -5,17 +5,20 @@
 
 
 /* globals document */
 /* globals document */
 
 
-import MarkdownDataProcessor from '../../../src/gfmdataprocessor';
+import Document from '@ckeditor/ckeditor5-engine/src/view/document';
+import { StylesProcessor } from '@ckeditor/ckeditor5-engine/src/view/stylesmap';
 import { stringify, parse } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 import { stringify, parse } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
 
 
+import MarkdownDataProcessor from '../../../src/gfmdataprocessor';
+
 const markdownTextArea = document.getElementById( 'markdown' );
 const markdownTextArea = document.getElementById( 'markdown' );
 const viewTextArea = document.getElementById( 'view' );
 const viewTextArea = document.getElementById( 'view' );
-const dataProcessor = new MarkdownDataProcessor();
+const dataProcessor = new MarkdownDataProcessor( new Document( new StylesProcessor() ) );
 
 
 document.getElementById( 'button_to_view' ).addEventListener( 'click', convertToView );
 document.getElementById( 'button_to_view' ).addEventListener( 'click', convertToView );
 document.getElementById( 'button_to_md' ).addEventListener( 'click', convertToMarkdown );
 document.getElementById( 'button_to_md' ).addEventListener( 'click', convertToMarkdown );
 
 
-markdownTextArea.value = '### Header\n\nFoo bar baz biz.';
+markdownTextArea.value = '### Header 3\n\nTodo:\n\n* [ ] Test me';
 convertToView();
 convertToView();
 
 
 function convertToView() {
 function convertToView() {

+ 3 - 42
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/paragraphs.js

@@ -40,25 +40,6 @@ describe( 'GFMDataProcessor', () => {
 				// <h1>header</h1>
 				// <h1>header</h1>
 				'<p>single line</p><h1>header</h1>',
 				'<p>single line</p><h1>header</h1>',
 
 
-				// To-markdown always put 2 empty lines after paragraph.
-				'single line\n\n' +
-				'# header'
-			);
-		} );
-
-		it( 'with header after #2', () => {
-			testDataProcessor(
-				'single line\n' +
-				'header\n' +
-				'===',
-
-				// GitHub is rendering as:
-				// <p>single line</p>
-				//
-				// <h1>header</h1>
-				'<p>single line</p><h1>header</h1>',
-
-				// To-markdown always put 2 empty lines after paragraph and normalize header to #.
 				'single line\n' +
 				'single line\n' +
 				'\n' +
 				'\n' +
 				'# header'
 				'# header'
@@ -78,17 +59,16 @@ describe( 'GFMDataProcessor', () => {
 				// </blockquote>
 				// </blockquote>
 				'<p>single line</p><blockquote><p>quote</p></blockquote>',
 				'<p>single line</p><blockquote><p>quote</p></blockquote>',
 
 
-				// To-markdown always put 2 empty lines after paragraph.
-				'single line\n' +
+				'single line' +
 				'\n' +
 				'\n' +
-				'> quote'
+				'\n> quote'
 			);
 			);
 		} );
 		} );
 
 
 		it( 'with list after', () => {
 		it( 'with list after', () => {
 			testDataProcessor(
 			testDataProcessor(
 				'single line\n' +
 				'single line\n' +
-				'* item',
+				'*   item',
 
 
 				// GitHub is rendering as:
 				// GitHub is rendering as:
 				// <p>single line</p>
 				// <p>single line</p>
@@ -98,29 +78,10 @@ describe( 'GFMDataProcessor', () => {
 				// </ul>
 				// </ul>
 				'<p>single line</p><ul><li>item</li></ul>',
 				'<p>single line</p><ul><li>item</li></ul>',
 
 
-				// To-markdown always put 2 empty lines after paragraph.
 				'single line\n' +
 				'single line\n' +
 				'\n' +
 				'\n' +
 				'*   item'
 				'*   item'
 			);
 			);
 		} );
 		} );
-
-		it( 'with div element after', () => {
-			testDataProcessor(
-				'single line\n' +
-				'<div>div element</div>',
-
-				// GitHub is rendering as:
-				// <p>single line</p>
-				//
-				// <div>div element</div>
-				'<p>single line</p><div>div element</div>',
-
-				// To-markdown always put 2 empty lines after paragraph.
-				'single line\n' +
-				'\n' +
-				'<div>div element</div>'
-			);
-		} );
 	} );
 	} );
 } );
 } );

+ 2 - 2
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/strikethrough.js

@@ -9,7 +9,7 @@ describe( 'GFMDataProcessor', () => {
 	describe( 'Strikethrough', () => {
 	describe( 'Strikethrough', () => {
 		it( 'should process strikethrough text', () => {
 		it( 'should process strikethrough text', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'~~deleted~~',
+				'~deleted~',
 
 
 				'<p><del>deleted</del></p>'
 				'<p><del>deleted</del></p>'
 			);
 			);
@@ -17,7 +17,7 @@ describe( 'GFMDataProcessor', () => {
 
 
 		it( 'should process strikethrough inside text', () => {
 		it( 'should process strikethrough inside text', () => {
 			testDataProcessor(
 			testDataProcessor(
-				'This is ~~deleted content~~.',
+				'This is ~deleted content~.',
 
 
 				'<p>This is <del>deleted content</del>.</p>'
 				'<p>This is <del>deleted content</del>.</p>'
 			);
 			);

+ 12 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/strong-emphasis.js

@@ -78,6 +78,18 @@ describe( 'GFMDataProcessor', () => {
 			testDataProcessor(
 			testDataProcessor(
 				'This should_not_be_emp.',
 				'This should_not_be_emp.',
 
 
+				'<p>This should_not_be_emp.</p>',
+
+				// Turndow escape markdown markup characters used inside text.
+				'This should\\_not\\_be\\_emp.'
+			);
+		} );
+
+		it( 'should not render escape marks', () => {
+			testDataProcessor(
+				// Following the previous test.
+				'This should\\_not\\_be\\_emp.',
+
 				'<p>This should_not_be_emp.</p>'
 				'<p>This should_not_be_emp.</p>'
 			);
 			);
 		} );
 		} );

+ 2 - 2
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/tables.js

@@ -119,7 +119,7 @@ describe( 'GFMDataProcessor', () => {
 			testDataProcessor(
 			testDataProcessor(
 				'Header 1|Header 2|Header 3|Header 4\n' +
 				'Header 1|Header 2|Header 3|Header 4\n' +
 				':-------|:------:|-------:|--------\n' +
 				':-------|:------:|-------:|--------\n' +
-				'*Cell 1*  |**Cell 2**  |~~Cell 3~~  |Cell 4',
+				'*Cell 1*  |**Cell 2**  |~Cell 3~  |Cell 4',
 
 
 				'<table>' +
 				'<table>' +
 					'<thead>' +
 					'<thead>' +
@@ -151,7 +151,7 @@ describe( 'GFMDataProcessor', () => {
 				// After converting back it will be normalized.
 				// After converting back it will be normalized.
 				'| Header 1 | Header 2 | Header 3 | Header 4 |\n' +
 				'| Header 1 | Header 2 | Header 3 | Header 4 |\n' +
 				'| :-- | :-: | --: | --- |\n' +
 				'| :-- | :-: | --: | --- |\n' +
-				'| _Cell 1_ | **Cell 2** | ~~Cell 3~~ | Cell 4 |'
+				'| _Cell 1_ | **Cell 2** | ~Cell 3~ | Cell 4 |'
 			);
 			);
 		} );
 		} );
 	} );
 	} );

+ 50 - 0
packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/text.js

@@ -0,0 +1,50 @@
+/**
+ * @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import { testDataProcessor } from '../_utils/utils';
+
+describe( 'GFMDataProcessor', () => {
+	describe( 'text', () => {
+		describe( 'urls', () => {
+			it( 'should not escape urls', () => {
+				testDataProcessor(
+					'escape\\_this https://test.com/do_[not]-escape escape\\_this',
+					'<p>escape_this https://test.com/do_[not]-escape escape_this</p>'
+				);
+			} );
+
+			it( 'should not escape urls (data escaped between urls)', () => {
+				testDataProcessor(
+					'escape\\_this https://test.com/do_[not]-escape escape\\_this https://test.com/do_[not]-escape',
+					'<p>escape_this https://test.com/do_[not]-escape escape_this https://test.com/do_[not]-escape</p>'
+				);
+			} );
+
+			it( 'should not escape urls (at start)', () => {
+				testDataProcessor(
+					'https://test.com/do_[not]-escape escape\\_this',
+					'<p>https://test.com/do_[not]-escape escape_this</p>'
+				);
+			} );
+
+			it( 'should not escape urls (at end)', () => {
+				testDataProcessor(
+					'escape\\_this https://test.com/do_[not]-escape',
+					'<p>escape_this https://test.com/do_[not]-escape</p>'
+				);
+			} );
+
+			[
+				'https://test.com/do_[not]-escape',
+				'http://test.com/do_[not]-escape',
+				'www.test.com/do_[not]-escape'
+			].forEach( url => {
+				it( `should not escape urls (${ url })`, () => {
+					testDataProcessor( url, `<p>${ url }</p>` );
+				} );
+			} );
+		} );
+	} );
+} );

+ 3 - 3
packages/ckeditor5-media-embed/docs/features/media-embed.md

@@ -10,7 +10,7 @@ The {@link module:media-embed/mediaembed~MediaEmbed} feature brings support for
 
 
 ## Demo
 ## Demo
 
 
-You can use the "Insert media" button in the toolbar to embed media like the following examples. You can also paste the media URL directly into the editor content and it will be [automatically embedded](#automatic-media-embed-on-paste).
+You can use the "Insert media" button in the toolbar to embed media like in the following examples. You can also paste the media URL directly into the editor content and it will be [automatically embedded](#automatic-media-embed-on-paste).
 
 
 * <input class="example-input" type="text" value="https://www.youtube.com/watch?v=H08tGjXNHO4">
 * <input class="example-input" type="text" value="https://www.youtube.com/watch?v=H08tGjXNHO4">
 * <input class="example-input" type="text" value="https://open.spotify.com/album/2IXlgvecaDqOeF3viUZnPI?si=ogVw7KlcQAGZKK4Jz9QzvA">
 * <input class="example-input" type="text" value="https://open.spotify.com/album/2IXlgvecaDqOeF3viUZnPI?si=ogVw7KlcQAGZKK4Jz9QzvA">
@@ -74,7 +74,7 @@ Thanks to the ability to hardcode this URL to HTML transformation, the media emb
 
 
 ### Non-previewable media
 ### Non-previewable media
 
 
-Unfortunately, to show previews of media such as tweets, Instagram photos or Facebook posts, the editor would need to retrieve the content of those from an external service. Some of these media providers expose [oEmbed endpoints](https://oembed.com/) but not all and those endpoint responses often require further processing to be embeddable. Most importantly, though, the media embed feature is often not able to request those services due to [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy).
+Unfortunately, to show previews of media such as tweets, Instagram photos or Facebook posts, the editor would need to retrieve the content of these from an external service. Some of these media providers expose [oEmbed endpoints](https://oembed.com/) but not all and those endpoint responses often require further processing to be embeddable. Most importantly, though, the media embed feature is often not able to request those services due to [same-origin policy](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy).
 
 
 The above limitations can be overcome with the help of proxy services like Iframely or Embedly. However, the media embed feature [does not support asynchronous preview providers](https://github.com/ckeditor/ckeditor5-media-embed/issues/16) yet. Therefore, to still allow embedding tweets or Instagram photos, we chose to:
 The above limitations can be overcome with the help of proxy services like Iframely or Embedly. However, the media embed feature [does not support asynchronous preview providers](https://github.com/ckeditor/ckeditor5-media-embed/issues/16) yet. Therefore, to still allow embedding tweets or Instagram photos, we chose to:
 
 
@@ -145,7 +145,7 @@ Names of providers **without previews**:
 * `'facebook'`.
 * `'facebook'`.
 
 
 <info-box notice>
 <info-box notice>
-	The default media provider configuration does not support all possible media URLs, only the most common are included. Services like Iframely or Embedly support thousands of media providers and it is up to you to define which you want to allow.
+	The default media provider configuration does not support all possible media URLs &mdash; only the most common are included. Services like Iframely or Embedly support thousands of media providers and it is up to you to define which you want to allow.
 </info-box>
 </info-box>
 
 
 #### Extending media providers
 #### Extending media providers

+ 4 - 4
packages/ckeditor5-media-embed/lang/translations/ko.po

@@ -26,19 +26,19 @@ msgstr "미디어 URL"
 
 
 msgctxt "The help text displayed under the media URL input helping users to discover the interface."
 msgctxt "The help text displayed under the media URL input helping users to discover the interface."
 msgid "Paste the media URL in the input."
 msgid "Paste the media URL in the input."
-msgstr "미디어 URL을 입력해주세요."
+msgstr "미디어 URL을 입력해주세요."
 
 
 msgctxt "The tip displayed next to the media URL input informing about an easier way of embedding."
 msgctxt "The tip displayed next to the media URL input informing about an easier way of embedding."
 msgid "Tip: Paste the URL into the content to embed faster."
 msgid "Tip: Paste the URL into the content to embed faster."
-msgstr "Tip: URL을 복사 후 붙여넣기하면 더 빠릅니다."
+msgstr "팁: URL을 붙여넣기하면 더 빨리 삽입할 수 있습니다."
 
 
 msgctxt "An error message that informs about an empty value in the URL input."
 msgctxt "An error message that informs about an empty value in the URL input."
 msgid "The URL must not be empty."
 msgid "The URL must not be empty."
-msgstr "URL이 비어있습니다."
+msgstr "URL이 비어있을 수 없습니다."
 
 
 msgctxt "An error message that informs about unsupported media URL."
 msgctxt "An error message that informs about unsupported media URL."
 msgid "This media URL is not supported."
 msgid "This media URL is not supported."
-msgstr "이 URL은 지원되지 않습니다."
+msgstr "이 미디어 URL은 지원되지 않습니다."
 
 
 msgctxt "Toolbar button tooltip for the Media Embed feature."
 msgctxt "Toolbar button tooltip for the Media Embed feature."
 msgid "Insert media"
 msgid "Insert media"

+ 6 - 6
packages/ckeditor5-media-embed/lang/translations/zh.po

@@ -22,23 +22,23 @@ msgstr "影音小工具"
 
 
 msgctxt "Label for the URL input in the Media Embed URL editing balloon."
 msgctxt "Label for the URL input in the Media Embed URL editing balloon."
 msgid "Media URL"
 msgid "Media URL"
-msgstr "影音URL"
+msgstr "影音網址"
 
 
 msgctxt "The help text displayed under the media URL input helping users to discover the interface."
 msgctxt "The help text displayed under the media URL input helping users to discover the interface."
 msgid "Paste the media URL in the input."
 msgid "Paste the media URL in the input."
-msgstr "在輸入框貼上影音URL。"
+msgstr "在輸入框貼上影音網址。"
 
 
 msgctxt "The tip displayed next to the media URL input informing about an easier way of embedding."
 msgctxt "The tip displayed next to the media URL input informing about an easier way of embedding."
 msgid "Tip: Paste the URL into the content to embed faster."
 msgid "Tip: Paste the URL into the content to embed faster."
-msgstr "提示:在內容貼上URL更快崁入。"
+msgstr "提示:在內容貼上網址更快崁入。"
 
 
 msgctxt "An error message that informs about an empty value in the URL input."
 msgctxt "An error message that informs about an empty value in the URL input."
 msgid "The URL must not be empty."
 msgid "The URL must not be empty."
-msgstr "URL不能空白。"
+msgstr "網址不能空白。"
 
 
 msgctxt "An error message that informs about unsupported media URL."
 msgctxt "An error message that informs about unsupported media URL."
 msgid "This media URL is not supported."
 msgid "This media URL is not supported."
-msgstr "影音URL不支援。"
+msgstr "不支援此影音網址。"
 
 
 msgctxt "Toolbar button tooltip for the Media Embed feature."
 msgctxt "Toolbar button tooltip for the Media Embed feature."
 msgid "Insert media"
 msgid "Insert media"
@@ -46,4 +46,4 @@ msgstr "插入影音"
 
 
 msgctxt "The label used by assistive technologies describing an image toolbar attached to an image widget."
 msgctxt "The label used by assistive technologies describing an image toolbar attached to an image widget."
 msgid "Media toolbar"
 msgid "Media toolbar"
-msgstr ""
+msgstr "影音工具"

+ 9 - 9
packages/ckeditor5-media-embed/src/mediaembedediting.js

@@ -177,10 +177,10 @@ export default class MediaEmbedEditing extends Plugin {
 		// Model -> Data
 		// Model -> Data
 		conversion.for( 'dataDowncast' ).elementToElement( {
 		conversion.for( 'dataDowncast' ).elementToElement( {
 			model: 'media',
 			model: 'media',
-			view: ( modelElement, viewWriter ) => {
+			view: ( modelElement, { writer } ) => {
 				const url = modelElement.getAttribute( 'url' );
 				const url = modelElement.getAttribute( 'url' );
 
 
-				return createMediaFigureElement( viewWriter, registry, url, {
+				return createMediaFigureElement( writer, registry, url, {
 					renderMediaPreview: url && renderMediaPreview
 					renderMediaPreview: url && renderMediaPreview
 				} );
 				} );
 			}
 			}
@@ -195,13 +195,13 @@ export default class MediaEmbedEditing extends Plugin {
 		// Model -> View (element)
 		// Model -> View (element)
 		conversion.for( 'editingDowncast' ).elementToElement( {
 		conversion.for( 'editingDowncast' ).elementToElement( {
 			model: 'media',
 			model: 'media',
-			view: ( modelElement, viewWriter ) => {
+			view: ( modelElement, { writer } ) => {
 				const url = modelElement.getAttribute( 'url' );
 				const url = modelElement.getAttribute( 'url' );
-				const figure = createMediaFigureElement( viewWriter, registry, url, {
+				const figure = createMediaFigureElement( writer, registry, url, {
 					renderForEditingView: true
 					renderForEditingView: true
 				} );
 				} );
 
 
-				return toMediaWidget( figure, viewWriter, t( 'media widget' ) );
+				return toMediaWidget( figure, writer, t( 'media widget' ) );
 			}
 			}
 		} );
 		} );
 
 
@@ -221,11 +221,11 @@ export default class MediaEmbedEditing extends Plugin {
 						url: true
 						url: true
 					}
 					}
 				},
 				},
-				model: ( viewMedia, modelWriter ) => {
+				model: ( viewMedia, { writer } ) => {
 					const url = viewMedia.getAttribute( 'url' );
 					const url = viewMedia.getAttribute( 'url' );
 
 
 					if ( registry.hasMedia( url ) ) {
 					if ( registry.hasMedia( url ) ) {
-						return modelWriter.createElement( 'media', { url } );
+						return writer.createElement( 'media', { url } );
 					}
 					}
 				}
 				}
 			} )
 			} )
@@ -237,11 +237,11 @@ export default class MediaEmbedEditing extends Plugin {
 						'data-oembed-url': true
 						'data-oembed-url': true
 					}
 					}
 				},
 				},
-				model: ( viewMedia, modelWriter ) => {
+				model: ( viewMedia, { writer } ) => {
 					const url = viewMedia.getAttribute( 'data-oembed-url' );
 					const url = viewMedia.getAttribute( 'data-oembed-url' );
 
 
 					if ( registry.hasMedia( url ) ) {
 					if ( registry.hasMedia( url ) ) {
-						return modelWriter.createElement( 'media', { url } );
+						return writer.createElement( 'media', { url } );
 					}
 					}
 				}
 				}
 			} );
 			} );

Некоторые файлы не были показаны из-за большого количества измененных файлов