Преглед изворни кода

Merge pull request #6 from ckeditor/t/3

Feature: Implemented the user interface of the highlight feature. Closes #3. Closes #4.
Aleksander Nowodzinski пре 8 година
родитељ
комит
4f9c7c09d5
33 измењених фајлова са 1395 додато и 247 уклоњено
  1. 1 1
      packages/ckeditor5-highlight/.eslintrc.js
  2. 1 1
      packages/ckeditor5-highlight/LICENSE.md
  3. 27 0
      packages/ckeditor5-highlight/docs/_snippets/features/build-highlight-source.html
  4. 14 0
      packages/ckeditor5-highlight/docs/_snippets/features/build-highlight-source.js
  5. 5 0
      packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-options.html
  6. 27 0
      packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-options.js
  7. 16 0
      packages/ckeditor5-highlight/docs/_snippets/features/highlight-buttons.html
  8. 23 0
      packages/ckeditor5-highlight/docs/_snippets/features/highlight-buttons.js
  9. 16 0
      packages/ckeditor5-highlight/docs/_snippets/features/highlight.html
  10. 22 0
      packages/ckeditor5-highlight/docs/_snippets/features/highlight.js
  11. 30 0
      packages/ckeditor5-highlight/docs/api/highlight.md
  12. 140 0
      packages/ckeditor5-highlight/docs/features/highlight.md
  13. 15 13
      packages/ckeditor5-highlight/package.json
  14. 99 2
      packages/ckeditor5-highlight/src/highlight.js
  15. 63 29
      packages/ckeditor5-highlight/src/highlightcommand.js
  16. 42 84
      packages/ckeditor5-highlight/src/highlightediting.js
  17. 258 2
      packages/ckeditor5-highlight/src/highlightui.js
  18. 1 1
      packages/ckeditor5-highlight/tests/highlight.js
  19. 178 54
      packages/ckeditor5-highlight/tests/highlightcommand.js
  20. 28 23
      packages/ckeditor5-highlight/tests/highlightediting.js
  21. 230 0
      packages/ckeditor5-highlight/tests/highlightui.js
  22. 15 15
      packages/ckeditor5-highlight/tests/integration.js
  23. 48 0
      packages/ckeditor5-highlight/tests/manual/highlight-buttons.html
  24. 26 0
      packages/ckeditor5-highlight/tests/manual/highlight-buttons.js
  25. 20 0
      packages/ckeditor5-highlight/tests/manual/highlight-buttons.md
  26. 27 12
      packages/ckeditor5-highlight/tests/manual/highlight.html
  27. 4 4
      packages/ckeditor5-highlight/tests/manual/highlight.js
  28. 7 6
      packages/ckeditor5-highlight/tests/manual/highlight.md
  29. BIN
      packages/ckeditor5-highlight/tests/manual/sample.jpg
  30. 9 0
      packages/ckeditor5-highlight/theme/highlight.css
  31. 1 0
      packages/ckeditor5-highlight/theme/icons/eraser.svg
  32. 1 0
      packages/ckeditor5-highlight/theme/icons/marker.svg
  33. 1 0
      packages/ckeditor5-highlight/theme/icons/pen.svg

+ 1 - 1
packages/ckeditor5-highlight/.eslintrc.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 

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

@@ -2,7 +2,7 @@ Software License Agreement
 ==========================
 
 **CKEditor 5 Highlight Feature** – https://github.com/ckeditor/ckeditor5-highlight <br>
-Copyright (c) 2003-2017, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
+Copyright (c) 2003-2018, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved.
 
 Licensed under the terms of any of the following licenses at your choice:
 

+ 27 - 0
packages/ckeditor5-highlight/docs/_snippets/features/build-highlight-source.html

@@ -0,0 +1,27 @@
+<style>
+	.marker-yellow {
+		background-color: #fdfd77;
+	}
+
+	.marker-green {
+		background-color: #63f963;
+	}
+
+	.marker-pink {
+		background-color: #fc7999;
+	}
+
+	.marker-blue {
+		background-color: #72cdfd;
+	}
+
+	.pen-red {
+		background-color: transparent;
+		color: #e91313;
+	}
+
+	.pen-green {
+		background-color: transparent;
+		color: #118800;
+	}
+</style>

+ 14 - 0
packages/ckeditor5-highlight/docs/_snippets/features/build-highlight-source.js

@@ -0,0 +1,14 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals window */
+
+import ClassicEditor from '@ckeditor/ckeditor5-build-classic/src/ckeditor';
+
+import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight';
+
+ClassicEditor.build.plugins.push( Highlight );
+
+window.ClassicEditor = ClassicEditor;

+ 5 - 0
packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-options.html

@@ -0,0 +1,5 @@
+<div id="snippet-custom-highlight-options">
+	<p>
+		Here are defined highlighters: <mark class="marker-green">green one</mark> and <mark class="pen-red">red one</mark>.
+	</p>
+</div>

+ 27 - 0
packages/ckeditor5-highlight/docs/_snippets/features/custom-highlight-options.js

@@ -0,0 +1,27 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals ClassicEditor, console, window, document */
+ClassicEditor
+	.create( document.querySelector( '#snippet-custom-highlight-options' ), {
+		toolbar: {
+			items: [
+				'headings', '|', 'bulletedList', 'numberedList', 'highlightDropdown', 'undo', 'redo'
+			],
+			viewportTopOffset: 60
+		},
+		highlight: {
+			options: [
+				{ model: 'greenMarker', class: 'marker-green', title: 'Green marker', color: '#66ff00', type: 'marker' },
+				{ model: 'redPen', class: 'pen-red', title: 'Red pen', color: '#e91313', type: 'pen' },
+			]
+		}
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 16 - 0
packages/ckeditor5-highlight/docs/_snippets/features/highlight-buttons.html

@@ -0,0 +1,16 @@
+<div id="snippet-highlight-buttons">
+	<p>Highlight feature example.</p>
+	<p>
+		Here are some markers:
+	</p>
+	<ul>
+		<li>the <mark class="marker-yellow">yellow</mark> one,</li>
+		<li>the <mark class="marker-pink">pink</mark> one,</li>
+		<li>the <mark class="marker-green">green</mark> one,</li>
+		<li>the <mark class="marker-blue">blue</mark> one</li>
+	</ul>
+	<p>
+		Here are some pens:
+		<mark class="pen-red">red pen</mark> and <mark class="pen-green">green one</mark>.
+	</p>
+</div>

+ 23 - 0
packages/ckeditor5-highlight/docs/_snippets/features/highlight-buttons.js

@@ -0,0 +1,23 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals ClassicEditor, console, window, document */
+
+ClassicEditor
+	.create( document.querySelector( '#snippet-highlight-buttons' ), {
+		toolbar: {
+			items: [
+				'headings', '|', 'highlight:marker', 'highlight:greenMarker', 'highlight:pinkMarker',
+				'highlight:greenPen', 'highlight:redPen', 'removeHighlight', '|', 'undo', 'redo'
+			],
+			viewportTopOffset: 60
+		}
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 16 - 0
packages/ckeditor5-highlight/docs/_snippets/features/highlight.html

@@ -0,0 +1,16 @@
+<div id="snippet-highlight">
+	<p>Highlight feature example.</p>
+	<p>
+		Here are some markers:
+	</p>
+	<ul>
+		<li>the <mark class="marker-yellow">yellow</mark> one,</li>
+		<li>the <mark class="marker-pink">pink</mark> one,</li>
+		<li>the <mark class="marker-green">green</mark> one,</li>
+		<li>the <mark class="marker-blue">blue</mark> one</li>
+	</ul>
+	<p>
+		Here are some pens:
+		<mark class="pen-red">red pen</mark> and <mark class="pen-green">green one</mark>.
+	</p>
+</div>

+ 22 - 0
packages/ckeditor5-highlight/docs/_snippets/features/highlight.js

@@ -0,0 +1,22 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals ClassicEditor, console, window, document */
+
+ClassicEditor
+	.create( document.querySelector( '#snippet-highlight' ), {
+		toolbar: {
+			items: [
+				'headings', '|', 'bulletedList', 'numberedList', 'highlightDropdown', 'undo', 'redo'
+			],
+			viewportTopOffset: 60
+		}
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 30 - 0
packages/ckeditor5-highlight/docs/api/highlight.md

@@ -0,0 +1,30 @@
+---
+category: api-reference
+---
+
+# CKEditor 5 highlight feature
+
+[![npm version](https://badge.fury.io/js/%40ckeditor%2Fckeditor5-highlight.svg)](https://www.npmjs.com/package/@ckeditor/ckeditor5-highlight)
+
+This package implements the highlight feature for CKEditor 5.
+
+## Documentation
+
+See the {@link features/highlight Highlight feature} guide and the {@link module:highlight/highlight~Highlight} plugin documentation.
+
+## Installation
+
+```bash
+npm install --save @ckeditor/ckeditor5-highlight
+```
+
+## Contribute
+
+The source code of this package is available on GitHub in https://github.com/ckeditor/ckeditor5-highlight.
+
+## External links
+
+* [`@ckeditor/ckeditor5-highlight` on npm](https://www.npmjs.com/package/@ckeditor/ckeditor5-highlight)
+* [`ckeditor/ckeditor5-highlight` on GitHub](https://github.com/ckeditor/ckeditor5-highlight)
+* [Issue tracker](https://github.com/ckeditor/ckeditor5-highlight/issues)
+* [Changelog](https://github.com/ckeditor/ckeditor5-highlight/blob/master/CHANGELOG.md)

+ 140 - 0
packages/ckeditor5-highlight/docs/features/highlight.md

@@ -0,0 +1,140 @@
+---
+title: Highlight
+category: features
+---
+
+{@snippet features/build-highlight-source}
+
+The {@link module:highlight/highlight~Highlight} feature offers a text marking tools that help content authors speed up their work, e.g. reviewing content or marking it for the future reference. It uses inline `<marker>` elements in the view, supports both markers (background color) and pens (text color), and comes with a flexible configuration.
+
+## Demo
+
+{@snippet features/highlight}
+
+## Configuring the highlight options
+
+It is possible to configure which highlight options are supported by the editor.
+You can use the {@link module:highlight/highlight~HighlightConfig#options `highlight.options`} configuration and define your own highlight styles.
+
+For example, the following editor supports only two styles (a green marker and a red pen):
+
+```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		highlight: {
+			options: [
+				{
+					model: 'greenMarker',
+					class: 'marker-green',
+					title: 'Green marker',
+					color: '#63f963',
+					type: 'marker'
+				},
+				{
+					model: 'redPen',
+					class: 'pen-red',
+					title: 'Red pen',
+					color: '#e91313',
+					type: 'pen'
+				}
+			]
+		},
+		toolbar: [
+			'headings', '|', 'bulletedList', 'numberedList', 'highlightDropdown', 'undo', 'redo'
+		]
+	} )
+	.then( ... )
+	.catch( ... );
+```
+
+{@snippet features/custom-highlight-options}
+
+Instead of using the (default) `highlightDropdown`, the feature also supports a configuration with separate buttons directly in the toolbar:
+
+```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		toolbar: {
+			items: [
+				'headings', '|', 'highlight:marker', 'highlight:greenMarker',
+				'highlight:pinkMarker', 'highlight:greenPen',
+				'highlight:redPen', 'removeHighlight', 'undo', 'redo'
+			]
+		}
+	} )
+	.then( ... )
+	.catch( ... );
+```
+
+{@snippet features/highlight-buttons}
+
+## Installation
+
+To add this feature to your editor install the [`@ckeditor/ckeditor5-highlight`](https://www.npmjs.com/package/@ckeditor/ckeditor5-highlight) package:
+
+```
+npm install --save @ckeditor/ckeditor5-highlight
+```
+
+And add it to your plugin list and the toolbar configuration:
+
+```js
+import Highlight from '@ckeditor/ckeditor5-highlight/src/highlight';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Highlight, ... ],
+		toolbar: [ 'highlightDropdown', ... ]
+	} )
+	.then( ... )
+	.catch( ... );
+```
+
+<info-box info>
+	Read more about {@link builds/guides/development/installing-plugins installing plugins}.
+</info-box>
+
+## Common API
+
+The {@link module:highlight/highlight~Highlight} plugin registers:
+
+* The `'highlightDropdown'` dropdown,
+* The {@link module:highlight/highlightcommand~HighlightCommand `'highlight'`} command.
+
+	The number of options and their names correspond to the {@link module:highlight/highlight~HighlightConfig#options `highlight.options`} configuration option.
+
+	You can change the highlight of the current selection by executing the command with a desired value:
+
+	```js
+	editor.execute( 'highlight', { value: 'yellowMarker' } );
+	```
+
+	The `value` corresponds to the `model` property in configuration object. For the default configuration:
+	```js
+	highlight.options = [
+		{ model: 'yellowMarker', class: 'marker-yellow', title: 'Yellow Marker', color: '#fdfd77', type: 'marker' },
+		{ model: 'greenMarker', class: 'marker-green', title: 'Green marker', color: '#63f963', type: 'marker' },
+		{ model: 'pinkMarker', class: 'marker-pink', title: 'Pink marker', color: '#fc7999', type: 'marker' },
+		{ model: 'blueMarker', class: 'marker-blue', title: 'Blue marker', color: '#72cdfd', type: 'marker' },
+		{ model: 'redPen', class: 'pen-red', title: 'Red pen', color: '#e91313', type: 'pen' },
+		{ model: 'greenPen', class: 'pen-green', title: 'Green pen', color: '#118800', type: 'pen' }
+	]
+	```
+
+	the `highlight` command will accept the corresponding strings as values:
+	- `'yellowMarker'` – available as a `'highlight:yellowMarker'` button,
+	- `'greenMarker'` – available as a `'highlight:greenMarker'` button,
+	- `'pinkMarker'` – available as a `'highlight:pinkMarker'` button,
+	- `'blueMarker'` – available as a `'highlight:blueMarker'` button,
+	- `'redPen'` – available as a `'highlight:redPen'` button,
+	- `'greenPen'` – available as a `'highlight:greenPen'` button.
+
+	passing an empty `value` will remove any `highlight` from the selection:
+
+	```js
+	editor.execute( 'highlight' );
+	```
+
+## Contribute
+
+The source code of the feature is available on GitHub in https://github.com/ckeditor/ckeditor5-highlight.

+ 15 - 13
packages/ckeditor5-highlight/package.json

@@ -7,22 +7,24 @@
     "ckeditor5-feature"
   ],
   "dependencies": {
-    "@ckeditor/ckeditor5-core": "^1.0.0-alpha.1",
-    "@ckeditor/ckeditor5-engine": "^1.0.0-alpha.1",
-    "@ckeditor/ckeditor5-ui": "^1.0.0-alpha.1"
+    "@ckeditor/ckeditor5-core": "^1.0.0-alpha.2",
+    "@ckeditor/ckeditor5-engine": "^1.0.0-alpha.2",
+    "@ckeditor/ckeditor5-ui": "^1.0.0-alpha.2"
   },
   "devDependencies": {
-    "@ckeditor/ckeditor5-block-quote": "^1.0.0-alpha.1",
-    "@ckeditor/ckeditor5-enter": "^1.0.0-alpha.1",
-    "@ckeditor/ckeditor5-heading": "^1.0.0-alpha.1",
-    "@ckeditor/ckeditor5-image": "^1.0.0-alpha.1",
-    "@ckeditor/ckeditor5-list": "^1.0.0-alpha.1",
-    "@ckeditor/ckeditor5-paragraph": "^1.0.0-alpha.1",
-    "@ckeditor/ckeditor5-typing": "^1.0.0-alpha.1",
-    "eslint": "^4.8.0",
-    "eslint-config-ckeditor5": "^1.0.6",
+    "@ckeditor/ckeditor5-block-quote": "^1.0.0-alpha.2",
+    "@ckeditor/ckeditor5-editor-classic": "^1.0.0-alpha.2",
+    "@ckeditor/ckeditor5-enter": "^1.0.0-alpha.2",
+    "@ckeditor/ckeditor5-heading": "^1.0.0-alpha.2",
+    "@ckeditor/ckeditor5-image": "^1.0.0-alpha.2",
+    "@ckeditor/ckeditor5-list": "^1.0.0-alpha.2",
+    "@ckeditor/ckeditor5-paragraph": "^1.0.0-alpha.2",
+    "@ckeditor/ckeditor5-typing": "^1.0.0-alpha.2",
+    "@ckeditor/ckeditor5-utils": "^1.0.0-alpha.2",
+    "eslint": "^4.15.0",
+    "eslint-config-ckeditor5": "^1.0.7",
     "husky": "^0.14.3",
-    "lint-staged": "^4.2.3"
+    "lint-staged": "^6.0.0"
   },
   "engines": {
     "node": ">=6.0.0",

+ 99 - 2
packages/ckeditor5-highlight/src/highlight.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -15,7 +15,10 @@ import HighlightUI from './highlightui';
 /**
  * The highlight plugin.
  *
- * It requires {@link module:highlight/highlightediting~HighlightEditing} and {@link module:highlight/highlightui~HighlightUI} plugins.
+ * It loads the {@link module:highlight/highlightediting~HighlightEditing} and
+ * {@link module:highlight/highlightui~HighlightUI} plugins.
+ *
+ * Read more about the feature in the {@glink api/highlight highlight package} page.
  *
  * @extends module:core/plugin~Plugin
  */
@@ -34,3 +37,97 @@ export default class Highlight extends Plugin {
 		return 'Highlight';
 	}
 }
+
+/**
+ * The highlight option descriptor. See the {@link module:highlight/highlight~HighlightConfig} to learn more.
+ *
+ *		{
+ *			model: 'pinkMarker',
+ *			class: 'marker-pink',
+ *			title: 'Pink Marker',
+ *			color: '#fc7999',
+ *			type: 'marker'
+ *		}
+ *
+ * @typedef {Object} module:highlight/highlight~HighlightOption
+ * @property {String} title The user-readable title of the option.
+ * @property {String} model The unique attribute value in the model.
+ * @property {String} color The color used for the highlighter. It should match the `class` CSS definition.
+ * The color is used in the user interface to represent the highlighter.
+ * @property {String} class The CSS class used on the `<mark>` element in the view. It should match the `color` setting.
+ * @property {'marker'|'pen'} type The type of highlighter:
+ * - `'marker'` – uses the `color` as a `background-color` style,
+ * - `'pen'` – uses the `color` as a font `color` style.
+ */
+
+/**
+ * The configuration of the {@link module:highlight/highlight~Highlight} feature.
+ *
+ * Read more in {@link module:highlight/highlight~HighlightConfig}.
+ *
+ * @member {module:highlight/highlight~HighlightConfig} module:core/editor/editorconfig~EditorConfig#highlight
+ */
+
+/**
+ * The configuration of the {@link module:highlight/highlight~Highlight Highlight feature}.
+ *
+ *		ClassicEditor
+ *			.create( editorElement, {
+ * 				highlight:  ... // Highlight feature config.
+ *			} )
+ *			.then( ... )
+ *			.catch( ... );
+ *
+ * See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
+ *
+ * @interface HighlightConfig
+ */
+
+/**
+ * The available highlighters options. The default value is:
+ *
+ *		options: [
+ *			{ model: 'yellowMarker', class: 'marker-yellow', title: 'Yellow marker', color: '#fdfd77', type: 'marker' },
+ *			{ model: 'greenMarker', class: 'marker-green', title: 'Green marker', color: '#63f963', type: 'marker' },
+ *			{ model: 'pinkMarker', class: 'marker-pink', title: 'Pink marker', color: '#fc7999', type: 'marker' },
+ *			{ model: 'blueMarker', class: 'marker-blue', title: 'Blue marker', color: '#72cdfd', type: 'marker' },
+ *			{ model: 'redPen', class: 'pen-red', title: 'Red pen', color: '#e91313', type: 'pen' },
+ *			{ model: 'greenPen', class: 'pen-green', title: 'Green pen', color: '#118800', type: 'pen' }
+ *		]
+ *
+ * There are two types of highlighters available:
+ * - `'marker'` - rendered as a `<mark>` element, styled with the `background-color`,
+ * - `'pen'` - rendered as a `<mark>` element, styled with the font `color`.
+ *
+ * **Note**: A style sheet with CSS classes is required for the configuration to work properly.
+ * The highlight feature does not provide the actual styles by itself.
+ *
+ * **Note**: It is recommended that the `color` value should correspond to the class in the content
+ * style sheet. It represents the highlighter in the user interface of the editor.
+ *
+ *		ClassicEditor
+ *			.create( editorElement, {
+ *				highlight: {
+ *					options: [
+ *						{
+ *							model: 'pinkMarker',
+ *							class: 'marker-pink',
+ *							title: 'Pink Marker',
+ *							color: '#fc7999',
+ *							type: 'marker'
+ *						},
+ *						{
+ *							model: 'redPen',
+ *							class: 'pen-red',
+ *							title: 'Red Pen',
+ *							color: '#e91313',
+ *							type: 'pen'
+ *						},
+ *					]
+ *				}
+ *		} )
+ *		.then( ... )
+ *		.catch( ... );
+ *
+ * @member {Array.<module:highlight/highlight~HighlightOption>} module:highlight/highlight~HighlightConfig#options
+ */

+ 63 - 29
packages/ckeditor5-highlight/src/highlightcommand.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -8,10 +8,17 @@
  */
 
 import Command from '@ckeditor/ckeditor5-core/src/command';
+import Range from '@ckeditor/ckeditor5-engine/src/model/range';
 
 /**
  * The highlight command. It is used by the {@link module:highlight/highlightediting~HighlightEditing highlight feature}
- * to apply text highlighting.
+ * to apply the text highlighting.
+ *
+ *		editor.execute( 'highlight', { value: 'greenMarker' } );
+ *
+ * **Note**: Executing the command without the value removes the attribute from the model. If the selection is collapsed
+ * inside a text with the highlight attribute, the command will remove the attribute from the entire range
+ * of that text.
  *
  * @extends module:core/command~Command
  */
@@ -20,10 +27,19 @@ export default class HighlightCommand extends Command {
 	 * @inheritDoc
 	 */
 	refresh() {
-		const doc = this.editor.document;
+		const model = this.editor.model;
+		const doc = model.document;
 
+		/**
+		 * A value indicating whether the command is active. If the selection has some highlight attribute,
+		 * it corresponds to the value of that attribute.
+		 *
+		 * @observable
+		 * @readonly
+		 * @member {undefined|String} module:highlight/highlightcommand~HighlightCommand#value
+		 */
 		this.value = doc.selection.getAttribute( 'highlight' );
-		this.isEnabled = doc.schema.checkAttributeInSelection( doc.selection, 'highlight' );
+		this.isEnabled = model.schema.checkAttributeInSelection( doc.selection, 'highlight' );
 	}
 
 	/**
@@ -31,38 +47,56 @@ export default class HighlightCommand extends Command {
 	 *
 	 * @protected
 	 * @param {Object} [options] Options for the executed command.
-	 * @param {String} options.class Name of highlighter class.
-	 * @param {module:engine/model/batch~Batch} [options.batch] A batch to collect all the change steps.
-	 * A new batch will be created if this option is not set.
+	 * @param {String} [options.value] a value to apply.
+	 *
+	 * @fires execute
 	 */
 	execute( options = {} ) {
-		const doc = this.editor.document;
-		const selection = doc.selection;
+		const model = this.editor.model;
+		const document = model.document;
+		const selection = document.selection;
+
+		const highlighter = options.value;
+
+		model.change( writer => {
+			const ranges = model.schema.getValidRanges( selection.getRanges(), 'highlight' );
+
+			if ( selection.isCollapsed ) {
+				const position = selection.getFirstPosition();
 
-		// Do not apply highlight no collapsed selection.
-		if ( selection.isCollapsed ) {
-			return;
-		}
+				// When selection is inside text with `highlight` attribute.
+				if ( selection.hasAttribute( 'highlight' ) ) {
+					// Find the full highlighted range.
+					const isSameHighlight = value => {
+						return value.item.hasAttribute( 'highlight' ) && value.item.getAttribute( 'highlight' ) === this.value;
+					};
 
-		doc.enqueueChanges( () => {
-			const ranges = doc.schema.getValidRanges( selection.getRanges(), 'highlight' );
-			const batch = options.batch || doc.batch();
+					const highlightStart = position.getLastMatchingPosition( isSameHighlight, { direction: 'backward' } );
+					const highlightEnd = position.getLastMatchingPosition( isSameHighlight );
 
-			for ( const range of ranges ) {
-				if ( options.class ) {
-					batch.setAttribute( range, 'highlight', options.class );
-				} else {
-					batch.removeAttribute( range, 'highlight' );
+					const highlightRange = new Range( highlightStart, highlightEnd );
+
+					// Then depending on current value...
+					if ( !highlighter || this.value === highlighter ) {
+						// ...remove attribute when passing highlighter different then current or executing "eraser".
+						writer.removeAttribute( 'highlight', highlightRange );
+						writer.removeSelectionAttribute( 'highlight' );
+					} else {
+						// ...update `highlight` value.
+						writer.setAttribute( 'highlight', highlighter, highlightRange );
+					}
+				} else if ( highlighter ) {
+					writer.setSelectionAttribute( 'highlight', highlighter );
+				}
+			} else {
+				for ( const range of ranges ) {
+					if ( highlighter ) {
+						writer.setAttribute( 'highlight', highlighter, range );
+					} else {
+						writer.removeAttribute( 'highlight', range );
+					}
 				}
 			}
 		} );
 	}
 }
-
-/**
- * Holds current highlight class. If there is no highlight in selection then value will be undefined.
- *
- * @observable
- * @readonly
- * @member {undefined|String} module:highlight/highlightcommand~HighlightCommand#value
- */

+ 42 - 84
packages/ckeditor5-highlight/src/highlightediting.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -9,15 +9,13 @@
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
-import buildViewConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildviewconverter';
-import buildModelConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildmodelconverter';
-
-import AttributeElement from '@ckeditor/ckeditor5-engine/src/view/attributeelement';
-
 import HighlightCommand from './highlightcommand';
 
 /**
- * The highlight editing feature. It introduces `highlight` command which allow to highlight selected text with defined 'marker' or 'pen'.
+ * The highlight editing feature. It introduces the {@link module:highlight/highlightcommand~HighlightCommand command} and the `highlight`
+ * attribute in the {@link module:engine/model/model~Model model} which renders in the {@link module:engine/view/view view}
+ * as a `<mark>` element with the class attribute (`<span class="marker-green">...</span>`) depending
+ * on the {@link module:highlight/highlight~HighlightConfig configuration}.
  *
  * @extends module:core/plugin~Plugin
  */
@@ -28,13 +26,16 @@ export default class HighlightEditing extends Plugin {
 	constructor( editor ) {
 		super( editor );
 
-		editor.config.define( 'highlight', [
-			{ class: 'marker', title: 'Marker', color: '#ffff66', type: 'marker' },
-			{ class: 'marker-green', title: 'Green Marker', color: '#66ff00', type: 'marker' },
-			{ class: 'marker-pink', title: 'Pink Marker', color: '#ff6fff', type: 'marker' },
-			{ class: 'pen-red', title: 'Red Pen', color: '#ff0000', type: 'pen' },
-			{ class: 'pen-blue', title: 'Blue Pen', color: '#0000ff', type: 'pen' }
-		] );
+		editor.config.define( 'highlight', {
+			options: [
+				{ model: 'yellowMarker', class: 'marker-yellow', title: 'Yellow marker', color: '#fdfd77', type: 'marker' },
+				{ model: 'greenMarker', class: 'marker-green', title: 'Green marker', color: '#63f963', type: 'marker' },
+				{ model: 'pinkMarker', class: 'marker-pink', title: 'Pink marker', color: '#fc7999', type: 'marker' },
+				{ model: 'blueMarker', class: 'marker-blue', title: 'Blue marker', color: '#72cdfd', type: 'marker' },
+				{ model: 'redPen', class: 'pen-red', title: 'Red pen', color: '#e91313', type: 'pen' },
+				{ model: 'greenPen', class: 'pen-green', title: 'Green pen', color: '#118800', type: 'pen' }
+			]
+		} );
 	}
 
 	/**
@@ -42,82 +43,39 @@ export default class HighlightEditing extends Plugin {
 	 */
 	init() {
 		const editor = this.editor;
-		const data = editor.data;
-		const editing = editor.editing;
-
-		// Allow highlight attribute on all elements
-		editor.document.schema.allow( { name: '$inline', attributes: 'highlight', inside: '$block' } );
-		// Temporary workaround. See https://github.com/ckeditor/ckeditor5/issues/477.
-		editor.document.schema.allow( { name: '$inline', attributes: 'highlight', inside: '$clipboardHolder' } );
 
-		// Convert highlight attribute to a mark element with associated class.
-		buildModelConverter()
-			.for( data.modelToView, editing.modelToView )
-			.fromAttribute( 'highlight' )
-			.toElement( data => new AttributeElement( 'mark', { class: data } ) );
+		// Allow highlight attribute on text nodes.
+		editor.model.schema.extend( '$text', { allowAttributes: 'highlight' } );
 
-		const configuredClasses = editor.config.get( 'highlight' ).map( config => config.class );
+		const options = editor.config.get( 'highlight.options' );
 
-		// Convert `mark` attribute with class name to model's highlight attribute.
-		buildViewConverter()
-			.for( data.viewToModel )
-			.fromElement( 'mark' )
-			.toAttribute( viewElement => {
-				for ( const className of viewElement.getClassNames() ) {
-					if ( configuredClasses.indexOf( className ) > -1 ) {
-						return { key: 'highlight', value: className };
-					}
-				}
-			} );
+		// Set-up the two-way conversion.
+		editor.conversion.attributeToElement( _buildDefinition( options ) );
 
 		editor.commands.add( 'highlight', new HighlightCommand( editor ) );
 	}
 }
 
-/**
- * Highlight option descriptor.
- *
- * @typedef {Object} module:highlight/highlightediting~HeadingOption
- * @property {String} class The class which is used to differentiate highlighters.
- * @property {String} title The user-readable title of the option.
- * @property {String} color Color used for highlighter. Should be coherent with CSS class definition.
- * @property {'marker'|'pen'} type The type of highlighter:
- * - "marker" - will use #color as background,
- * - "pen" - will use #color as font color.
- */
-
-/**
- * The configuration of the {@link module:highlight/highlightediting~HighlightEditing Highlight feature}.
- *
- * Read more in {@link module:highlight/highlightediting~HighlightEditingConfig}.
- *
- * @member {module:highlight/highlightediting~HighlightEditingConfig} module:core/editor/editorconfig~EditorConfig#highlight
- */
-
-/**
- * The configuration of the {@link module:highlight/highlightediting~HighlightEditing Highlight feature}.
- *
- *        ClassicEditor
- *            .create( editorElement, {
- * 				highlight:  ... // Highlight feature config.
- *			} )
- *            .then( ... )
- *            .catch( ... );
- *
- * See {@link module:core/editor/editorconfig~EditorConfig all editor options}.
- *
- * @interface HighlightEditingConfig
- */
+// Converts options array to a converter definition.
+//
+// @param {Array.<module:highlight/highlight~HighlightOption>} options Array with configured options.
+// @returns {module:engine/conversion/conversion~ConverterDefinition}
+function _buildDefinition( options ) {
+	const definition = {
+		model: {
+			key: 'highlight',
+			values: []
+		},
+		view: {}
+	};
+
+	for ( const option of options ) {
+		definition.model.values.push( option.model );
+		definition.view[ option.model ] = {
+			name: 'mark',
+			class: option.class
+		};
+	}
 
-/**
- * Available highlighters options.
- *
- * There are two types of highlighters:
- * - 'marker' - rendered as `<mark>` element with defined background color.
- * - 'pen' - rendered as `<mark>` element with defined foreground (font) color.
- *
- * Note: Each highlighter must have it's own CSS class defined to properly match content data. Also it is advised
- * that color value should match the values defined in content CSS stylesheet.
- *
- * @member {Array.<module:heading/heading~HeadingOption>} module:heading/heading~HeadingConfig#options
- */
+	return definition;
+}

+ 258 - 2
packages/ckeditor5-highlight/src/highlightui.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -10,13 +10,66 @@
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
 import HighlightEditing from './highlightediting';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+
+import markerIcon from './../theme/icons/marker.svg';
+import penIcon from './../theme/icons/pen.svg';
+import eraserIcon from './../theme/icons/eraser.svg';
+
+import ToolbarSeparatorView from '@ckeditor/ckeditor5-ui/src/toolbar/toolbarseparatorview';
+import SplitButtonView from '@ckeditor/ckeditor5-ui/src/dropdown/button/splitbuttonview';
+import { createDropdown, addToolbarToDropdown } from '@ckeditor/ckeditor5-ui/src/dropdown/utils';
+
+import './../theme/highlight.css';
 
 /**
- * The default Highlight UI plugin.
+ * The default Highlight UI plugin. It introduces:
+ * * the `'highlightDropdown'` drop-down,
+ * * `'removeHighlight'` and `'highlight:*'` buttons.
+ *
+ * The default configuration includes the following buttons:
+ * * `'highlight:marker'`
+ * * `'highlight:greenMarker'`
+ * * `'highlight:pinkMarker'`
+ * * `'highlight:blueMarker'`
+ * * `'highlight:redPen'`
+ * * `'highlight:greenPen'`
+ *
+ * See the {@link module:highlight/highlight~HighlightConfig#options configuration} to learn more
+ * about the defaults.
  *
  * @extends module:core/plugin~Plugin
  */
 export default class HighlightUI extends Plugin {
+	/**
+	 * Returns the localized option titles provided by the plugin.
+	 *
+	 * The following localized titles corresponding with default
+	 * {@link module:highlight/highlight~HighlightConfig#options} are available:
+	 *
+	 * * `'Yellow marker'`,
+	 * * `'Green marker'`,
+	 * * `'Pink marker'`,
+	 * * `'Blue marker'`,
+	 * * `'Red pen'`,
+	 * * `'Green pen'`.
+	 *
+	 * @readonly
+	 * @type {Object.<String,String>}
+	 */
+	get localizedOptionTitles() {
+		const t = this.editor.t;
+
+		return {
+			'Yellow marker': t( 'Yellow marker' ),
+			'Green marker': t( 'Green marker' ),
+			'Pink marker': t( 'Pink marker' ),
+			'Blue marker': t( 'Blue marker' ),
+			'Red pen': t( 'Red pen' ),
+			'Green pen': t( 'Green pen' )
+		};
+	}
+
 	/**
 	 * @inheritDoc
 	 */
@@ -30,4 +83,207 @@ export default class HighlightUI extends Plugin {
 	static get pluginName() {
 		return 'HighlightUI';
 	}
+
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const options = this.editor.config.get( 'highlight.options' );
+
+		for ( const option of options ) {
+			this._addHighlighterButton( option );
+		}
+
+		this._addRemoveHighlightButton();
+
+		this._addDropdown( options );
+	}
+
+	/**
+	 * Creates remove highlight button.
+	 *
+	 * @private
+	 */
+	_addRemoveHighlightButton() {
+		const t = this.editor.t;
+
+		this._addButton( 'removeHighlight', t( 'Remove highlighting' ), eraserIcon );
+	}
+
+	/**
+	 * Creates toolbar button from provided highlight option.
+	 *
+	 * @param {module:highlight/highlight~HighlightOption} option
+	 * @private
+	 */
+	_addHighlighterButton( option ) {
+		const command = this.editor.commands.get( 'highlight' );
+
+		// TODO: change naming
+		this._addButton( 'highlight:' + option.model, option.title, getIconForType( option.type ), option.model, decorateHighlightButton );
+
+		function decorateHighlightButton( button ) {
+			button.bind( 'isEnabled' ).to( command, 'isEnabled' );
+			button.bind( 'isOn' ).to( command, 'value', value => value === option.model );
+
+			button.extendTemplate( {
+				attributes: {
+					style: `color: ${ option.color }`,
+					class: 'ck-highlight-button'
+				}
+			} );
+		}
+	}
+
+	/**
+	 * Internal method for creating highlight buttons.
+	 *
+	 * @param {String} name Name of a button.
+	 * @param {String} label Label for button.
+	 * @param {String} icon Button's icon.
+	 * @param {Function} [decorateButton=()=>{}] Additional method for extending button.
+	 * @private
+	 */
+	_addButton( name, label, icon, value, decorateButton = () => {} ) {
+		const editor = this.editor;
+
+		editor.ui.componentFactory.add( name, locale => {
+			const buttonView = new ButtonView( locale );
+
+			const localized = this.localizedOptionTitles[ label ] ? this.localizedOptionTitles[ label ] : label;
+
+			buttonView.set( {
+				label: localized,
+				icon,
+				tooltip: true
+			} );
+
+			buttonView.on( 'execute', () => {
+				editor.execute( 'highlight', { value } );
+				editor.editing.view.focus();
+			} );
+
+			// Add additional behavior for buttonView.
+			decorateButton( buttonView );
+
+			return buttonView;
+		} );
+	}
+
+	/**
+	 * Creates split button dropdown UI from provided highlight options.
+	 *
+	 * @param {Array.<module:highlight/highlight~HighlightOption>} options
+	 * @private
+	 */
+	_addDropdown( options ) {
+		const editor = this.editor;
+		const t = editor.t;
+		const componentFactory = editor.ui.componentFactory;
+
+		const startingHighlighter = options[ 0 ];
+
+		const optionsMap = options.reduce( ( retVal, option ) => {
+			retVal[ option.model ] = option;
+
+			return retVal;
+		}, {} );
+
+		componentFactory.add( 'highlightDropdown', locale => {
+			const command = editor.commands.get( 'highlight' );
+			const dropdownView = createDropdown( locale, SplitButtonView );
+			const splitButtonView = dropdownView.buttonView;
+
+			splitButtonView.set( {
+				tooltip: t( 'Highlight' ),
+				// Holds last executed highlighter.
+				lastExecuted: startingHighlighter.model,
+				// Holds current highlighter to execute (might be different then last used).
+				commandValue: startingHighlighter.model
+			} );
+
+			// Dropdown button changes to selection (command.value):
+			// - If selection is in highlight it get active highlight appearance (icon, color) and is activated.
+			// - Otherwise it gets appearance (icon, color) of last executed highlight.
+			splitButtonView.bind( 'icon' ).to( command, 'value', value => getIconForType( getActiveOption( value, 'type' ) ) );
+			splitButtonView.bind( 'color' ).to( command, 'value', value => getActiveOption( value, 'color' ) );
+			splitButtonView.bind( 'commandValue' ).to( command, 'value', value => getActiveOption( value, 'model' ) );
+			splitButtonView.bind( 'isOn' ).to( command, 'value', value => !!value );
+
+			splitButtonView.delegate( 'execute' ).to( dropdownView );
+
+			splitButtonView.extendTemplate( {
+				attributes: {
+					class: 'ck-highlight-button'
+				}
+			} );
+
+			// Create buttons array.
+			const buttons = options.map( option => {
+				// Get existing highlighter button.
+				const buttonView = componentFactory.create( 'highlight:' + option.model );
+
+				// Update lastExecutedHighlight on execute.
+				this.listenTo( buttonView, 'execute', () => dropdownView.buttonView.set( { lastExecuted: option.model } ) );
+
+				return buttonView;
+			} );
+
+			// Make toolbar button enabled when any button in dropdown is enabled before adding separator and eraser.
+			dropdownView.bind( 'isEnabled' ).toMany( buttons, 'isEnabled', ( ...areEnabled ) => areEnabled.some( isEnabled => isEnabled ) );
+
+			// Add separator and eraser buttons to dropdown.
+			buttons.push( new ToolbarSeparatorView() );
+			buttons.push( componentFactory.create( 'removeHighlight' ) );
+
+			addToolbarToDropdown( dropdownView, buttons );
+
+			bindIconStyleToColor( dropdownView );
+
+			dropdownView.extendTemplate( {
+				attributes: {
+					class: [ 'ck-highlight-dropdown' ]
+				}
+			} );
+
+			// Execute current action from dropdown's split button action button.
+			splitButtonView.on( 'execute', () => {
+				editor.execute( 'highlight', { value: splitButtonView.commandValue } );
+				editor.editing.view.focus();
+			} );
+
+			// Returns active highlighter option depending on current command value.
+			// If current is not set or it is the same as last execute this method will return the option key (like icon or color)
+			// of last executed highlighter. Otherwise it will return option key for current one.
+			function getActiveOption( current, key ) {
+				const whichHighlighter = !current ||
+				current === splitButtonView.lastExecuted ? splitButtonView.lastExecuted : current;
+
+				return optionsMap[ whichHighlighter ][ key ];
+			}
+
+			return dropdownView;
+		} );
+	}
+}
+
+// Extends split button icon style to reflect last used button style.
+function bindIconStyleToColor( dropdownView ) {
+	const actionView = dropdownView.buttonView.actionView;
+
+	const bind = actionView.bindTemplate;
+
+	// Color will propagate to iconView.
+	actionView.extendTemplate( {
+		attributes: {
+			style: bind.to( 'color', color => `color:${ color }` )
+		}
+	} );
+
+	actionView.bind( 'color' ).to( dropdownView.buttonView, 'color' );
+}
+
+// Returns icon for given highlighter type.
+function getIconForType( type ) {
+	return type === 'marker' ? markerIcon : penIcon;
 }

+ 1 - 1
packages/ckeditor5-highlight/tests/highlight.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 

+ 178 - 54
packages/ckeditor5-highlight/tests/highlightcommand.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -8,22 +8,30 @@ import HighlightCommand from './../src/highlightcommand';
 import Command from '@ckeditor/ckeditor5-core/src/command';
 import ModelTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/modeltesteditor';
 import { getData, setData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import Position from '@ckeditor/ckeditor5-engine/src/model/position';
+import Range from '@ckeditor/ckeditor5-engine/src/model/range';
 
 describe( 'HighlightCommand', () => {
-	let editor, doc, command;
+	let editor, model, doc, root, command;
 
 	beforeEach( () => {
 		return ModelTestEditor.create()
 			.then( newEditor => {
-				doc = newEditor.document;
-				command = new HighlightCommand( newEditor );
 				editor = newEditor;
+				model = editor.model;
+				doc = model.document;
+				root = doc.getRoot();
 
+				command = new HighlightCommand( newEditor );
 				editor.commands.add( 'highlight', command );
 
-				doc.schema.registerItem( 'paragraph', '$block' );
-
-				doc.schema.allow( { name: '$inline', attributes: 'highlight', inside: '$block' } );
+				model.schema.register( 'p', { inheritAllFrom: '$block' } );
+				model.schema.addAttributeCheck( ( ctx, attributeName ) => {
+					// Allow 'highlight' on p>$text.
+					if ( ctx.endsWith( 'p $text' ) && attributeName == 'highlight' ) {
+						return true;
+					}
+				} );
 			} );
 	} );
 
@@ -38,94 +46,210 @@ describe( 'HighlightCommand', () => {
 
 	describe( 'value', () => {
 		it( 'is set to highlight attribute value when selection is in text with highlight attribute', () => {
-			setData( doc, '<paragraph><$text highlight="marker">fo[]o</$text></paragraph>' );
+			setData( model, '<p><$text highlight="yellowMarker">fo[o]</$text></p>' );
 
-			expect( command ).to.have.property( 'value', 'marker' );
+			expect( command ).to.have.property( 'value', 'yellowMarker' );
 		} );
 
 		it( 'is undefined when selection is not in text with highlight attribute', () => {
-			setData( doc, '<paragraph>fo[]o</paragraph>' );
+			setData( model, '<p>fo[]o</p>' );
 
 			expect( command ).to.have.property( 'value', undefined );
 		} );
 	} );
 
 	describe( 'isEnabled', () => {
+		beforeEach( () => {
+			model.schema.register( 'x', { inheritAllFrom: '$block' } );
+		} );
+
 		it( 'is true when selection is on text which can have highlight added', () => {
-			setData( doc, '<paragraph>fo[]o</paragraph>' );
+			setData( model, '<p>fo[]o</p>' );
 
 			expect( command ).to.have.property( 'isEnabled', true );
 		} );
+
+		it( 'is false when selection is on text which can not have highlight added', () => {
+			setData( model, '<x>fo[]o</x>' );
+			expect( command.isEnabled ).to.be.false;
+		} );
 	} );
 
 	describe( 'execute()', () => {
-		it( 'should add highlight attribute on selected nodes nodes when passed as parameter', () => {
-			setData( doc, '<paragraph>a[bc<$text highlight="marker">fo]obar</$text>xyz</paragraph>' );
+		describe( 'with option.value set', () => {
+			describe( 'on collapsed range', () => {
+				it( 'should change entire highlight when inside highlighted text', () => {
+					setData( model, '<p>abc<$text highlight="yellowMarker">foo[]bar</$text>xyz</p>' );
 
-			expect( command.value ).to.be.undefined;
+					expect( command.value ).to.equal( 'yellowMarker' );
 
-			command.execute( { class: 'marker' } );
+					command.execute( { value: 'greenMarker' } );
 
-			expect( command.value ).to.equal( 'marker' );
+					expect( getData( model ) ).to.equal( '<p>abc<$text highlight="greenMarker">foo[]bar</$text>xyz</p>' );
 
-			expect( getData( doc ) ).to.equal( '<paragraph>a[<$text highlight="marker">bcfo]obar</$text>xyz</paragraph>' );
-		} );
+					expect( command.value ).to.equal( 'greenMarker' );
+				} );
 
-		it( 'should add highlight attribute on selected nodes nodes when passed as parameter (multiple nodes)', () => {
-			setData(
-				doc,
-				'<paragraph>abcabc[abc</paragraph>' +
-				'<paragraph>foofoofoo</paragraph>' +
-				'<paragraph>barbar]bar</paragraph>'
-			);
+				it( 'should remove entire highlight when inside highlighted text of the same value', () => {
+					setData( model, '<p>abc<$text highlight="yellowMarker">foo[]bar</$text>xyz</p>' );
 
-			command.execute( { class: 'marker' } );
+					expect( command.value ).to.equal( 'yellowMarker' );
 
-			expect( command.value ).to.equal( 'marker' );
+					command.execute( { value: 'yellowMarker' } );
 
-			expect( getData( doc ) ).to.equal(
-				'<paragraph>abcabc[<$text highlight="marker">abc</$text></paragraph>' +
-				'<paragraph><$text highlight="marker">foofoofoo</$text></paragraph>' +
-				'<paragraph><$text highlight="marker">barbar</$text>]bar</paragraph>'
-			);
-		} );
+					expect( getData( model ) ).to.equal( '<p>abcfoo[]barxyz</p>' );
 
-		it( 'should set highlight attribute on selected nodes when passed as parameter', () => {
-			setData( doc, '<paragraph>abc[<$text highlight="marker">foo]bar</$text>xyz</paragraph>' );
+					expect( command.value ).to.be.undefined;
+				} );
 
-			expect( command.value ).to.equal( 'marker' );
+				it( 'should change selection attribute in non-empty parent', () => {
+					setData( model, '<p>a[]bc<$text highlight="yellowMarker">foobar</$text>xyz</p>' );
+					expect( command.value ).to.be.undefined;
 
-			command.execute( { class: 'foo' } );
+					command.execute( { value: 'foo' } );
+					expect( command.value ).to.equal( 'foo' );
 
-			expect( getData( doc ) ).to.equal(
-				'<paragraph>abc[<$text highlight="foo">foo</$text>]<$text highlight="marker">bar</$text>xyz</paragraph>'
-			);
+					expect( doc.selection.hasAttribute( 'highlight' ) ).to.be.true;
 
-			expect( command.value ).to.equal( 'foo' );
-		} );
+					command.execute();
+
+					expect( command.value ).to.be.undefined;
+					expect( doc.selection.hasAttribute( 'highlight' ) ).to.be.false;
+
+					// Execute remove highlight on selection without 'highlight' attribute should do nothing.
+					command.execute();
+
+					expect( command.value ).to.be.undefined;
+					expect( doc.selection.hasAttribute( 'highlight' ) ).to.be.false;
+				} );
+
+				it( 'should not store attribute change on selection if selection is collapsed in non-empty parent', () => {
+					setData( model, '<p>a[]bc<$text highlight="yellowMarker">foobar</$text>xyz</p>' );
+
+					command.execute( { value: 'foo' } );
+
+					// It should not save that bold was executed at position ( root, [ 0, 1 ] ).
+
+					model.change( writer => {
+						// Simulate clicking right arrow key by changing selection ranges.
+						writer.setSelection( [ new Range( new Position( root, [ 0, 2 ] ), new Position( root, [ 0, 2 ] ) ) ] );
+
+						// Get back to previous selection.
+						writer.setSelection( [ new Range( new Position( root, [ 0, 1 ] ), new Position( root, [ 0, 1 ] ) ) ] );
+					} );
+
+					expect( command.value ).to.be.undefined;
+				} );
+
+				it( 'should change selection attribute and store it if selection is collapsed in empty parent', () => {
+					setData( model, '<p>abc<$text highlight="yellowMarker">foobar</$text>xyz</p><p>[]</p>' );
+
+					expect( command.value ).to.be.undefined;
+
+					command.execute( { value: 'foo' } );
+
+					expect( command.value ).to.equal( 'foo' );
+					expect( doc.selection.hasAttribute( 'highlight' ) ).to.be.true;
+
+					// Attribute should be stored.
+					// Simulate clicking somewhere else in the editor.
+					model.change( writer => {
+						writer.setSelection( [ new Range( new Position( root, [ 0, 2 ] ), new Position( root, [ 0, 2 ] ) ) ] );
+					} );
 
-		it( 'should remove highlight attribute on selected nodes nodes when undefined passed as parameter', () => {
-			setData( doc, '<paragraph>abc[<$text highlight="marker">foo]bar</$text>xyz</paragraph>' );
+					expect( command.value ).to.be.undefined;
 
-			expect( command.value ).to.equal( 'marker' );
+					// Go back to where attribute was stored.
+					model.change( writer => {
+						writer.setSelection( [ new Range( new Position( root, [ 1, 0 ] ), new Position( root, [ 1, 0 ] ) ) ] );
+					} );
 
-			command.execute();
+					// Attribute should be restored.
+					expect( command.value ).to.equal( 'foo' );
 
-			expect( getData( doc ) ).to.equal( '<paragraph>abc[foo]<$text highlight="marker">bar</$text>xyz</paragraph>' );
+					command.execute();
 
-			expect( command.value ).to.be.undefined;
+					expect( command.value ).to.be.undefined;
+					expect( doc.selection.hasAttribute( 'highlight' ) ).to.be.false;
+				} );
+			} );
+
+			describe( 'on not collapsed range', () => {
+				it( 'should set highlight attribute on selected node when passed as parameter', () => {
+					setData( model, '<p>a[bc<$text highlight="yellowMarker">fo]obar</$text>xyz</p>' );
+
+					expect( command.value ).to.be.undefined;
+
+					command.execute( { value: 'yellowMarker' } );
+
+					expect( command.value ).to.equal( 'yellowMarker' );
+
+					expect( getData( model ) ).to.equal( '<p>a[<$text highlight="yellowMarker">bcfo]obar</$text>xyz</p>' );
+				} );
+
+				it( 'should set highlight attribute on selected node when passed as parameter (multiple nodes)', () => {
+					setData(
+						model,
+						'<p>abcabc[abc</p>' +
+						'<p>foofoofoo</p>' +
+						'<p>barbar]bar</p>'
+					);
+
+					command.execute( { value: 'yellowMarker' } );
+
+					expect( command.value ).to.equal( 'yellowMarker' );
+
+					expect( getData( model ) ).to.equal(
+						'<p>abcabc[<$text highlight="yellowMarker">abc</$text></p>' +
+						'<p><$text highlight="yellowMarker">foofoofoo</$text></p>' +
+						'<p><$text highlight="yellowMarker">barbar</$text>]bar</p>'
+					);
+				} );
+
+				it( 'should set highlight attribute on selected nodes when passed as parameter only on selected characters', () => {
+					setData( model, '<p>abc[<$text highlight="yellowMarker">foo]bar</$text>xyz</p>' );
+
+					expect( command.value ).to.equal( 'yellowMarker' );
+
+					command.execute( { value: 'foo' } );
+
+					expect( getData( model ) ).to.equal(
+						'<p>abc[<$text highlight="foo">foo</$text>]<$text highlight="yellowMarker">bar</$text>xyz</p>'
+					);
+
+					expect( command.value ).to.equal( 'foo' );
+				} );
+			} );
 		} );
 
-		it( 'should do nothing on collapsed range', () => {
-			setData( doc, '<paragraph>abc<$text highlight="marker">foo[]bar</$text>xyz</paragraph>' );
+		describe( 'with undefined option.value', () => {
+			describe( 'on collapsed range', () => {
+				it( 'should remove entire highlight when inside highlighted text', () => {
+					setData( model, '<p>abc<$text highlight="yellowMarker">foo[]bar</$text>xyz</p>' );
+
+					expect( command.value ).to.equal( 'yellowMarker' );
 
-			expect( command.value ).to.equal( 'marker' );
+					command.execute();
 
-			command.execute();
+					expect( getData( model ) ).to.equal( '<p>abcfoo[]barxyz</p>' );
 
-			expect( getData( doc ) ).to.equal( '<paragraph>abc<$text highlight="marker">foo[]bar</$text>xyz</paragraph>' );
+					expect( command.value ).to.be.undefined;
+				} );
+			} );
 
-			expect( command.value ).to.equal( 'marker' );
+			describe( 'on not collapsed range', () => {
+				it( 'should remove highlight attribute on selected node when undefined passed as parameter', () => {
+					setData( model, '<p>abc[<$text highlight="yellowMarker">foo]bar</$text>xyz</p>' );
+
+					expect( command.value ).to.equal( 'yellowMarker' );
+
+					command.execute();
+
+					expect( getData( model ) ).to.equal( '<p>abc[foo]<$text highlight="yellowMarker">bar</$text>xyz</p>' );
+
+					expect( command.value ).to.be.undefined;
+				} );
+			} );
 		} );
 	} );
 } );

+ 28 - 23
packages/ckeditor5-highlight/tests/highlightediting.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -12,7 +12,7 @@ import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtest
 import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
 describe( 'HighlightEditing', () => {
-	let editor, doc;
+	let editor, model;
 
 	beforeEach( () => {
 		return VirtualTestEditor
@@ -22,7 +22,7 @@ describe( 'HighlightEditing', () => {
 			.then( newEditor => {
 				editor = newEditor;
 
-				doc = editor.document;
+				model = editor.model;
 			} );
 	} );
 
@@ -31,63 +31,68 @@ describe( 'HighlightEditing', () => {
 	} );
 
 	it( 'should set proper schema rules', () => {
-		expect( doc.schema.check( { name: '$inline', attributes: 'highlight', inside: '$block' } ) ).to.be.true;
-		expect( doc.schema.check( { name: '$inline', attributes: 'highlight', inside: '$clipboardHolder' } ) ).to.be.true;
+		expect( editor.model.schema.checkAttribute( [ '$block', '$text' ], 'highlight' ) ).to.be.true;
+		expect( editor.model.schema.checkAttribute( [ '$clipboardHolder', '$text' ], 'highlight' ) ).to.be.true;
+
+		expect( editor.model.schema.checkAttribute( [ '$block' ], 'highlight' ) ).to.be.false;
 	} );
 
-	it( 'adds highlight commands', () => {
+	it( 'adds highlight command', () => {
 		expect( editor.commands.get( 'highlight' ) ).to.be.instanceOf( HighlightCommand );
 	} );
 
 	describe( 'data pipeline conversions', () => {
 		it( 'should convert defined marker classes', () => {
-			const data = '<p>f<mark class="marker">o</mark>o</p>';
-
+			const data = '<p>f<mark class="marker-yellow">o</mark>o</p>';
 			editor.setData( data );
 
-			expect( getModelData( doc ) ).to.equal( '<paragraph>[]f<$text highlight="marker">o</$text>o</paragraph>' );
+			expect( getModelData( model ) ).to.equal( '<paragraph>[]f<$text highlight="yellowMarker">o</$text>o</paragraph>' );
 			expect( editor.getData() ).to.equal( data );
 		} );
+
 		it( 'should convert only one defined marker classes', () => {
-			editor.setData( '<p>f<mark class="marker-green marker">o</mark>o</p>' );
+			editor.setData( '<p>f<mark class="marker-green marker-yellow">o</mark>o</p>' );
 
-			expect( getModelData( doc ) ).to.equal( '<paragraph>[]f<$text highlight="marker-green">o</$text>o</paragraph>' );
-			expect( editor.getData() ).to.equal( '<p>f<mark class="marker-green">o</mark>o</p>' );
+			expect( getModelData( model ) ).to.equal( '<paragraph>[]f<$text highlight="yellowMarker">o</$text>o</paragraph>' );
+			expect( editor.getData() ).to.equal( '<p>f<mark class="marker-yellow">o</mark>o</p>' );
 		} );
 
 		it( 'should not convert undefined marker classes', () => {
 			editor.setData( '<p>f<mark class="some-unknown-marker">o</mark>o</p>' );
 
-			expect( getModelData( doc ) ).to.equal( '<paragraph>[]foo</paragraph>' );
+			expect( getModelData( model ) ).to.equal( '<paragraph>[]foo</paragraph>' );
 			expect( editor.getData() ).to.equal( '<p>foo</p>' );
 		} );
 
 		it( 'should not convert marker without class', () => {
 			editor.setData( '<p>f<mark>o</mark>o</p>' );
 
-			expect( getModelData( doc ) ).to.equal( '<paragraph>[]foo</paragraph>' );
+			expect( getModelData( model ) ).to.equal( '<paragraph>[]foo</paragraph>' );
 			expect( editor.getData() ).to.equal( '<p>foo</p>' );
 		} );
 	} );
 
 	describe( 'editing pipeline conversion', () => {
 		it( 'should convert mark element with defined class', () => {
-			setModelData( doc, '<paragraph>f<$text highlight="marker">o</$text>o</paragraph>' );
+			setModelData( model, '<paragraph>f<$text highlight="yellowMarker">o</$text>o</paragraph>' );
 
-			expect( editor.getData() ).to.equal( '<p>f<mark class="marker">o</mark>o</p>' );
+			expect( editor.getData() ).to.equal( '<p>f<mark class="marker-yellow">o</mark>o</p>' );
 		} );
 	} );
 
 	describe( 'config', () => {
 		describe( 'default value', () => {
 			it( 'should be set', () => {
-				expect( editor.config.get( 'highlight' ) ).to.deep.equal( [
-					{ class: 'marker', title: 'Marker', color: '#ffff66', type: 'marker' },
-					{ class: 'marker-green', title: 'Green Marker', color: '#66ff00', type: 'marker' },
-					{ class: 'marker-pink', title: 'Pink Marker', color: '#ff6fff', type: 'marker' },
-					{ class: 'pen-red', title: 'Red Pen', color: '#ff0000', type: 'pen' },
-					{ class: 'pen-blue', title: 'Blue Pen', color: '#0000ff', type: 'pen' }
-				] );
+				expect( editor.config.get( 'highlight' ) ).to.deep.equal( {
+					options: [
+						{ model: 'yellowMarker', class: 'marker-yellow', title: 'Yellow marker', color: '#fdfd77', type: 'marker' },
+						{ model: 'greenMarker', class: 'marker-green', title: 'Green marker', color: '#63f963', type: 'marker' },
+						{ model: 'pinkMarker', class: 'marker-pink', title: 'Pink marker', color: '#fc7999', type: 'marker' },
+						{ model: 'blueMarker', class: 'marker-blue', title: 'Blue marker', color: '#72cdfd', type: 'marker' },
+						{ model: 'redPen', class: 'pen-red', title: 'Red pen', color: '#e91313', type: 'pen' },
+						{ model: 'greenPen', class: 'pen-green', title: 'Green pen', color: '#118800', type: 'pen' }
+					]
+				} );
 			} );
 		} );
 	} );

+ 230 - 0
packages/ckeditor5-highlight/tests/highlightui.js

@@ -0,0 +1,230 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* global document */
+
+import HighlightEditing from '../src/highlightediting';
+import HighlightUI from '../src/highlightui';
+
+import markerIcon from '../theme/icons/marker.svg';
+import penIcon from '../theme/icons/pen.svg';
+import eraserIcon from '../theme/icons/eraser.svg';
+
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import { _clear as clearTranslations, add as addTranslations } from '@ckeditor/ckeditor5-utils/src/translation-service';
+
+testUtils.createSinonSandbox();
+
+describe( 'HighlightUI', () => {
+	let editor, command, element;
+
+	before( () => {
+		addTranslations( 'en', {
+			'Highlight': 'Highlight',
+			'Yellow marker': 'Yellow marker',
+			'Green marker': 'Green marker',
+			'Pink marker': 'Pink marker',
+			'Red pen': 'Red pen',
+			'Blue pen': 'Blue pen',
+			'Remove highlighting': 'Remove highlighting'
+		} );
+
+		addTranslations( 'pl', {
+			'Highlight': 'Zakreślacz',
+			'Yellow marker': 'Żółty marker',
+			'Green marker': 'Zielony marker',
+			'Pink marker': 'Różowy marker',
+			'Blue marker': 'Niebieski marker',
+			'Red pen': 'Czerwony długopis',
+			'Green pen': 'Zielony długopis',
+			'Remove highlighting': 'Usuń zaznaczenie'
+		} );
+	} );
+
+	after( () => {
+		clearTranslations();
+	} );
+
+	beforeEach( () => {
+		element = document.createElement( 'div' );
+		document.body.appendChild( element );
+
+		return ClassicTestEditor
+			.create( element, {
+				plugins: [ HighlightEditing, HighlightUI ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+			} );
+	} );
+
+	afterEach( () => {
+		element.remove();
+
+		return editor.destroy();
+	} );
+
+	describe( 'highlight Dropdown', () => {
+		let dropdown;
+
+		beforeEach( () => {
+			command = editor.commands.get( 'highlight' );
+			dropdown = editor.ui.componentFactory.create( 'highlightDropdown' );
+		} );
+
+		it( 'button has the base properties', () => {
+			const button = dropdown.buttonView;
+
+			expect( button ).to.have.property( 'tooltip', 'Highlight' );
+			expect( button ).to.have.property( 'icon', markerIcon );
+		} );
+
+		it( 'should add custom CSS class to dropdown and dropdown buttons', () => {
+			dropdown.render();
+
+			expect( dropdown.element.classList.contains( 'ck-highlight-dropdown' ) ).to.be.true;
+			expect( dropdown.buttonView.element.classList.contains( 'ck-highlight-button' ) ).to.be.true;
+			// There should be 5 highlight buttons, one separator and highlight remove button in toolbar.
+			expect( dropdown.toolbarView.items.map( button => button.element.classList.contains( 'ck-highlight-button' ) ) )
+				.to.deep.equal( [ true, true, true, true, true, true, false, false ] );
+		} );
+
+		it( 'should have proper icons in dropdown', () => {
+			const toolbar = dropdown.toolbarView;
+
+			// Not in a selection with highlight.
+			command.value = undefined;
+
+			expect( toolbar.items.map( item => item.icon ) )
+				.to.deep.equal( [ markerIcon, markerIcon, markerIcon, markerIcon, penIcon, penIcon, undefined, eraserIcon ] );
+		} );
+
+		it( 'should activate current option in dropdown', () => {
+			const toolbar = dropdown.toolbarView;
+
+			// Not in a selection with highlight.
+			command.value = undefined;
+
+			expect( toolbar.items.map( item => item.isOn ) )
+				.to.deep.equal( [ false, false, false, false, false, false, undefined, false ] );
+
+			// Inside a selection with highlight.
+			command.value = 'greenMarker';
+
+			// The second item is 'greenMarker' highlighter.
+			expect( toolbar.items.map( item => item.isOn ) ).to.deep.equal( [ false, true, false, false, false, false, undefined, false ] );
+		} );
+
+		describe( 'toolbar button behavior', () => {
+			let button, buttons, options;
+
+			beforeEach( () => {
+				button = dropdown.buttonView;
+				buttons = dropdown.toolbarView.items.map( b => b );
+				options = editor.config.get( 'highlight.options' );
+			} );
+
+			function validateButton( which ) {
+				expect( button.icon ).to.equal( buttons[ which ].icon );
+				expect( button.actionView.color ).to.equal( options[ which ].color );
+			}
+
+			it( 'should have properties of first defined highlighter', () => {
+				validateButton( 0 );
+			} );
+
+			it( 'should change button on selection', () => {
+				command.value = 'redPen';
+
+				validateButton( 4 );
+
+				command.value = undefined;
+
+				validateButton( 0 );
+			} );
+
+			it( 'should change button on execute option', () => {
+				command.value = 'yellowMarker';
+				validateButton( 0 );
+
+				buttons[ 5 ].fire( 'execute' );
+				command.value = 'greenPen';
+
+				// Simulate selection moved to not highlighted text.
+				command.value = undefined;
+
+				validateButton( 5 );
+			} );
+
+			it( 'should focus view after command execution', () => {
+				const focusSpy = testUtils.sinon.spy( editor.editing.view, 'focus' );
+
+				dropdown.buttonView.commandName = 'highlight';
+				dropdown.buttonView.fire( 'execute' );
+
+				sinon.assert.calledOnce( focusSpy );
+			} );
+		} );
+
+		describe( 'model to command binding', () => {
+			it( 'isEnabled', () => {
+				command.isEnabled = false;
+
+				expect( dropdown.buttonView.isEnabled ).to.be.false;
+
+				command.isEnabled = true;
+				expect( dropdown.buttonView.isEnabled ).to.be.true;
+			} );
+		} );
+
+		describe( 'localization', () => {
+			beforeEach( () => {
+				return localizedEditor();
+			} );
+
+			it( 'works for the #buttonView', () => {
+				const buttonView = dropdown.buttonView;
+
+				expect( buttonView.tooltip ).to.equal( 'Zakreślacz' );
+			} );
+
+			it( 'works for the listView#items in the panel', () => {
+				const listView = dropdown.toolbarView;
+
+				expect( listView.items.map( item => item.label ).filter( label => !!label ) ).to.deep.equal( [
+					'Żółty marker',
+					'Zielony marker',
+					'Różowy marker',
+					'Niebieski marker',
+					'Czerwony długopis',
+					'Zielony długopis',
+					'Usuń zaznaczenie'
+				] );
+			} );
+
+			function localizedEditor() {
+				const editorElement = document.createElement( 'div' );
+				document.body.appendChild( editorElement );
+
+				return ClassicTestEditor
+					.create( editorElement, {
+						plugins: [ HighlightEditing, HighlightUI ],
+						toolbar: [ 'highlight' ],
+						language: 'pl'
+					} )
+					.then( newEditor => {
+						editor = newEditor;
+						dropdown = editor.ui.componentFactory.create( 'highlightDropdown' );
+						command = editor.commands.get( 'highlight' );
+
+						editorElement.remove();
+
+						return editor.destroy();
+					} );
+			}
+		} );
+	} );
+} );

+ 15 - 15
packages/ckeditor5-highlight/tests/integration.js

@@ -1,5 +1,5 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
@@ -19,7 +19,7 @@ import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictest
 import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
 
 describe( 'Highlight', () => {
-	let editor, doc, element;
+	let editor, model, element;
 
 	beforeEach( () => {
 		element = document.createElement( 'div' );
@@ -31,7 +31,7 @@ describe( 'Highlight', () => {
 			} )
 			.then( newEditor => {
 				editor = newEditor;
-				doc = editor.document;
+				model = editor.model;
 			} );
 	} );
 
@@ -42,27 +42,27 @@ describe( 'Highlight', () => {
 	} );
 
 	describe( 'compatibility with images', () => {
-		it( 'does not work inside image caption', () => {
-			setModelData( doc, '<image src="foo.png"><caption>foo[bar]baz</caption></image>' );
+		it( 'does work inside image caption', () => {
+			setModelData( model, '<image src="foo.png"><caption>foo[bar]baz</caption></image>' );
 
-			editor.execute( 'highlight', { class: 'marker' } );
+			editor.execute( 'highlight', { value: 'yellowMarker' } );
 
-			expect( getModelData( doc ) )
-				.to.equal( '<image src="foo.png"><caption>foo[<$text highlight="marker">bar</$text>]baz</caption></image>' );
+			expect( getModelData( model ) )
+				.to.equal( '<image src="foo.png"><caption>foo[<$text highlight="yellowMarker">bar</$text>]baz</caption></image>' );
 		} );
 
-		it( 'does not work on selection with image', () => {
+		it( 'does work on selection with image', () => {
 			setModelData(
-				doc,
+				model,
 				'<paragraph>foo[foo</paragraph><image src="foo.png"><caption>abc</caption></image><paragraph>bar]bar</paragraph>'
 			);
 
-			editor.execute( 'highlight', { class: 'marker' } );
+			editor.execute( 'highlight', { value: 'yellowMarker' } );
 
-			expect( getModelData( doc ) ).to.equal(
-				'<paragraph>foo[<$text highlight="marker">foo</$text></paragraph>' +
-				'<image src="foo.png"><caption><$text highlight="marker">abc</$text></caption></image>' +
-				'<paragraph><$text highlight="marker">bar</$text>]bar</paragraph>'
+			expect( getModelData( model ) ).to.equal(
+				'<paragraph>foo[<$text highlight="yellowMarker">foo</$text></paragraph>' +
+				'<image src="foo.png"><caption><$text highlight="yellowMarker">abc</$text></caption></image>' +
+				'<paragraph><$text highlight="yellowMarker">bar</$text>]bar</paragraph>'
 			);
 		} );
 	} );

+ 48 - 0
packages/ckeditor5-highlight/tests/manual/highlight-buttons.html

@@ -0,0 +1,48 @@
+<style>
+	.marker-yellow {
+		background-color: #fdfd77;
+	}
+
+	.marker-green {
+		background-color: #63f963;
+	}
+
+	.marker-pink {
+		background-color: #fc7999;
+	}
+
+	.marker-blue {
+		background-color: #72cdfd;
+	}
+
+	.pen-red {
+		background-color: transparent;
+		color: #e91313;
+	}
+
+	.pen-green {
+		background-color: transparent;
+		color: #118800;
+	}
+</style>
+
+<div id="editor">
+	<p>Highlight feature example.</p>
+	<p>
+		Here are some markers:
+	</p>
+	<ul>
+		<li>the <mark class="marker-yellow">yellow</mark> one,</li>
+		<li>the <mark class="marker-pink">pink</mark> one,</li>
+		<li>the <mark class="marker-green">green</mark> one,</li>
+		<li>the <mark class="marker-blue">blue</mark> one</li>
+	</ul>
+	<p>
+		Here are some pens:
+		<mark class="pen-red">red pen</mark> and <mark class="pen-green">green one</mark>.
+	</p>
+	<figure class="image">
+		<img src="sample.jpg" alt="CKEditor logo" />
+		<figcaption>Some image with caption and <mark class="marker-yellow">highlighted text</mark>.</figcaption>
+	</figure>
+</div>

+ 26 - 0
packages/ckeditor5-highlight/tests/manual/highlight-buttons.js

@@ -0,0 +1,26 @@
+/**
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals console, window, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
+import Highlight from '../../src/highlight';
+
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ ArticlePluginSet, Highlight ],
+		toolbar: [
+			'highlight:yellowMarker', 'highlight:greenMarker', 'highlight:pinkMarker', 'highlight:blueMarker',
+			'highlight:redPen', 'highlight:greenPen', 'removeHighlight',
+			'|', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'undo', 'redo'
+		]
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );

+ 20 - 0
packages/ckeditor5-highlight/tests/manual/highlight-buttons.md

@@ -0,0 +1,20 @@
+### Loading
+
+1. The data should be loaded with different markers and pens.
+2. The toolbar should have 5 highlight buttons and one remove highlight button.
+
+### Testing
+
+You should be able to:
+- see different markers class
+- manually invoke highlight command in console:
+
+```
+editor.execute( 'highlight', { value: 'yellowMarker' } );
+editor.execute( 'highlight', { value: 'greenMarker' } );
+editor.execute( 'highlight', { value: 'pinkMarker' } );
+editor.execute( 'highlight', { value: 'blueMarker' } );
+
+editor.execute( 'highlight', { value: 'redPen' } );
+editor.execute( 'highlight', { value: 'greenPen' } );
+```

+ 27 - 12
packages/ckeditor5-highlight/tests/manual/highlight.html

@@ -1,33 +1,48 @@
 <style>
-	.marker {
-		background-color: #ffff66;
+	.marker-yellow {
+		background-color: #fdfd77;
 	}
 
 	.marker-green {
-		background-color: #66ff00;
+		background-color: #63f963;
 	}
 
 	.marker-pink {
-		background-color: #ff6fff;
+		background-color: #fc7999;
+	}
+
+	.marker-blue {
+		background-color: #72cdfd;
 	}
 
 	.pen-red {
 		background-color: transparent;
-		color: #ff0000;
+		color: #e91313;
 	}
 
-	.pen-blue {
+	.pen-green {
 		background-color: transparent;
-		color: #0000ff;
+		color: #118800;
 	}
-
 </style>
+
 <div id="editor">
 	<p>Highlight feature example.</p>
-	<p>Here ares some markers:
-		<mark class="marker">yellow one</mark>, <mark class="marker-pink">pink one</mark> and <mark class="marker-green">green one</mark>.
+	<p>
+		Here are some markers:
 	</p>
-	<p>Here ares some pens:
-		<mark class="pen-red">red pen</mark> and <mark class="pen-blue">blue one</mark>.
+	<ul>
+		<li>the <mark class="marker-yellow">yellow</mark> one,</li>
+		<li>the <mark class="marker-pink">pink</mark> one,</li>
+		<li>the <mark class="marker-green">green</mark> one,</li>
+		<li>the <mark class="marker-blue">blue</mark> one</li>
+	</ul>
+	<p>
+		Here are some pens:
+		<mark class="pen-red">red pen</mark> and <mark class="pen-green">green one</mark>.
 	</p>
+	<figure class="image">
+		<img src="sample.jpg" alt="CKEditor logo" />
+		<figcaption>Some image with caption and <mark class="marker-yellow">highlighted text</mark>.</figcaption>
+	</figure>
 </div>

+ 4 - 4
packages/ckeditor5-highlight/tests/manual/highlight.js

@@ -1,19 +1,19 @@
 /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * @license Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
  * For licensing, see LICENSE.md.
  */
 
 /* globals console, window, document */
 
-import ClassicEditor from '../../../ckeditor5-editor-classic/src/classiceditor';
-import ArticlePluginSet from '../../../ckeditor5-core/tests/_utils/articlepluginset';
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
 import Highlight from '../../src/highlight';
 
 ClassicEditor
 	.create( document.querySelector( '#editor' ), {
 		plugins: [ ArticlePluginSet, Highlight ],
 		toolbar: [
-			'headings', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'undo', 'redo'
+			'headings', '|', 'highlightDropdown', 'bold', 'italic', 'link', 'bulletedList', 'numberedList', 'blockQuote', 'undo', 'redo'
 		]
 	} )
 	.then( editor => {

+ 7 - 6
packages/ckeditor5-highlight/tests/manual/highlight.md

@@ -9,10 +9,11 @@ You should be able to:
 - manually invoke highlight command in console:
 
 ```
-editor.execute( 'highlight', { class: 'marker' } );
-editor.execute( 'highlight', { class: 'marker-green' } );
-editor.execute( 'highlight', { class: 'marker-pink' } );
-	
-editor.execute( 'highlight', { class: 'pen-red' } );
-editor.execute( 'highlight', { class: 'pen-blue' } );	 
+editor.execute( 'highlight', { value: 'yellowMarker' } );
+editor.execute( 'highlight', { value: 'greenMarker' } );
+editor.execute( 'highlight', { value: 'pinkMarker' } );
+editor.execute( 'highlight', { value: 'blueMarker' } );
+
+editor.execute( 'highlight', { value: 'redPen' } );
+editor.execute( 'highlight', { value: 'greenPen' } );
 ```

BIN
packages/ckeditor5-highlight/tests/manual/sample.jpg


+ 9 - 0
packages/ckeditor5-highlight/theme/highlight.css

@@ -0,0 +1,9 @@
+/*
+ * Copyright (c) 2003-2018, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+.ck-highlight-button svg path:not(.ck-icon__fill) {
+	/* Do not inherit color from parent. */
+	fill: initial;
+}

+ 1 - 0
packages/ckeditor5-highlight/theme/icons/eraser.svg

@@ -0,0 +1 @@
+<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><g fill-rule="evenodd"><path d="M8.636 9.531l-2.758 3.94a.5.5 0 0 0 .122.696l3.224 2.284h1.314l2.636-3.736L8.636 9.53zm.288 8.451L5.14 15.396a2 2 0 0 1-.491-2.786l6.673-9.53a2 2 0 0 1 2.785-.49l3.742 2.62a2 2 0 0 1 .491 2.785l-7.269 10.053-2.147-.066z" fill-rule="nonzero"/><path d="M4 18h5.523v-1H4zm-2 0h1v-1H2z"/></g></svg>

+ 1 - 0
packages/ckeditor5-highlight/theme/icons/marker.svg

@@ -0,0 +1 @@
+<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path class="ck-icon__fill" d="M10.798 1.59L3.002 12.875l1.895 1.852 2.521 1.402 6.997-12.194z"/><path d="M2.556 16.727l.234-.348c-.297-.151-.462-.293-.498-.426-.036-.137.002-.416.115-.837.094-.25.15-.449.169-.595a4.495 4.495 0 0 0 0-.725c-.209-.621-.303-1.041-.284-1.26.02-.218.178-.506.475-.862l6.77-9.414c.539-.91 1.605-.85 3.199.18 1.594 1.032 2.188 1.928 1.784 2.686l-5.877 10.36c-.158.412-.333.673-.526.782-.193.108-.604.179-1.232.21-.362.131-.608.237-.738.318-.13.081-.305.238-.526.47-.293.265-.504.397-.632.397-.096 0-.27-.075-.524-.226l-.31.41-1.6-1.12zm-.279.415l1.575 1.103-.392.515H1.19l1.087-1.618zm8.1-13.656l-4.953 6.9L8.75 12.57l4.247-7.574c.175-.25-.188-.647-1.092-1.192-.903-.546-1.412-.652-1.528-.32zM8.244 18.5L9.59 17h9.406v1.5H8.245z" fill="#000"/></g></svg>

+ 1 - 0
packages/ckeditor5-highlight/theme/icons/pen.svg

@@ -0,0 +1 @@
+<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd"><path class="ck-icon__fill" d="M10.126 2.268L2.002 13.874l1.895 1.852 2.521 1.402L14.47 5.481l-1.543-2.568-2.801-.645z" fill="none"/><path d="M4.5 18.088l-2.645-1.852-.04-2.95-.006-.005.006-.008v-.025l.011.008L8.73 2.97c.165-.233.356-.417.567-.557l-1.212.308L4.604 7.9l-.83-.558 3.694-5.495 2.708-.69 1.65 1.145.046.018.85-1.216 2.16 1.512-.856 1.222c.828.967 1.144 2.141.432 3.158L7.55 17.286l.006.005-3.055.797H4.5zm-.634.166l-1.976.516-.026-1.918 2.002 1.402zM9.968 3.817l-.006-.004-6.123 9.184 3.277 2.294 6.108-9.162.005.003c.317-.452-.16-1.332-1.064-1.966-.891-.624-1.865-.776-2.197-.349zM8.245 18.5L9.59 17h9.406v1.5H8.245z" fill-rule="nonzero"/></svg>