diff --git a/.eslintrc.js b/.eslintrc.js
index b5a22b0..88dfc81 100644
--- a/.eslintrc.js
+++ b/.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.
*/
diff --git a/LICENSE.md b/LICENSE.md
index ed3848d..a461d84 100644
--- a/LICENSE.md
+++ b/LICENSE.md
@@ -2,7 +2,7 @@ Software License Agreement
==========================
**CKEditor 5 Highlight Feature** – https://github.com/ckeditor/ckeditor5-highlight
-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:
diff --git a/docs/_snippets/features/build-highlight-source.html b/docs/_snippets/features/build-highlight-source.html
new file mode 100644
index 0000000..5626e61
--- /dev/null
+++ b/docs/_snippets/features/build-highlight-source.html
@@ -0,0 +1,27 @@
+
diff --git a/docs/_snippets/features/build-highlight-source.js b/docs/_snippets/features/build-highlight-source.js
new file mode 100644
index 0000000..278e78c
--- /dev/null
+++ b/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;
diff --git a/docs/_snippets/features/custom-highlight-options.html b/docs/_snippets/features/custom-highlight-options.html
new file mode 100644
index 0000000..7198cf6
--- /dev/null
+++ b/docs/_snippets/features/custom-highlight-options.html
@@ -0,0 +1,5 @@
+
+ Here are defined highlighters: green one and red one. +
+Highlight feature example.
++ Here are some markers: +
++ Here are some pens: + red pen and green one. +
+<$text highlight="yellowMarker">fo[o]$text>
' ); - 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, 'fo[]o
' ); 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, 'fo[]o
' ); expect( command ).to.have.property( 'isEnabled', true ); } ); + + it( 'is false when selection is on text which can not have highlight added', () => { + setData( model, 'abc<$text highlight="yellowMarker">foo[]bar$text>xyz
' ); - 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( 'abc<$text highlight="greenMarker">foo[]bar$text>xyz
' ); - expect( getData( doc ) ).to.equal( 'abc<$text highlight="yellowMarker">foo[]bar$text>xyz
' ); - 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( - 'abcfoo[]barxyz
' ); - it( 'should set highlight attribute on selected nodes when passed as parameter', () => { - setData( doc, 'a[]bc<$text highlight="yellowMarker">foobar$text>xyz
' ); + 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( - 'a[]bc<$text highlight="yellowMarker">foobar$text>xyz
' ); + + 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, 'abc<$text highlight="yellowMarker">foobar$text>xyz
[]
' ); + + 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, 'a[bc<$text highlight="yellowMarker">fo]obar$text>xyz
' ); + + expect( command.value ).to.be.undefined; + + command.execute( { value: 'yellowMarker' } ); + + expect( command.value ).to.equal( 'yellowMarker' ); + + expect( getData( model ) ).to.equal( 'a[<$text highlight="yellowMarker">bcfo]obar$text>xyz
' ); + } ); + + it( 'should set highlight attribute on selected node when passed as parameter (multiple nodes)', () => { + setData( + model, + 'abcabc[abc
' + + 'foofoofoo
' + + 'barbar]bar
' + ); + + command.execute( { value: 'yellowMarker' } ); + + expect( command.value ).to.equal( 'yellowMarker' ); + + expect( getData( model ) ).to.equal( + 'abcabc[<$text highlight="yellowMarker">abc$text>
' + + '<$text highlight="yellowMarker">foofoofoo$text>
' + + '<$text highlight="yellowMarker">barbar$text>]bar
' + ); + } ); + + it( 'should set highlight attribute on selected nodes when passed as parameter only on selected characters', () => { + setData( model, 'abc[<$text highlight="yellowMarker">foo]bar$text>xyz
' ); + + expect( command.value ).to.equal( 'yellowMarker' ); + + command.execute( { value: 'foo' } ); + + expect( getData( model ) ).to.equal( + 'abc[<$text highlight="foo">foo$text>]<$text highlight="yellowMarker">bar$text>xyz
' + ); + + expect( command.value ).to.equal( 'foo' ); + } ); + } ); } ); - it( 'should do nothing on collapsed range', () => { - setData( doc, 'abc<$text highlight="yellowMarker">foo[]bar$text>xyz
' ); + + expect( command.value ).to.equal( 'yellowMarker' ); - expect( command.value ).to.equal( 'marker' ); + command.execute(); - command.execute(); + expect( getData( model ) ).to.equal( 'abcfoo[]barxyz
' ); - expect( getData( doc ) ).to.equal( 'abc[<$text highlight="yellowMarker">foo]bar$text>xyz
' ); + + expect( command.value ).to.equal( 'yellowMarker' ); + + command.execute(); + + expect( getData( model ) ).to.equal( 'abc[foo]<$text highlight="yellowMarker">bar$text>xyz
' ); + + expect( command.value ).to.be.undefined; + } ); + } ); } ); } ); } ); diff --git a/tests/highlightediting.js b/tests/highlightediting.js index 3c32090..a915b11 100644 --- a/tests/highlightediting.js +++ b/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 = 'foo
'; - + const data = 'foo
'; editor.setData( data ); - expect( getModelData( doc ) ).to.equal( 'foo
' ); + editor.setData( 'foo
' ); - expect( getModelData( doc ) ).to.equal( 'foo
' ); + expect( getModelData( model ) ).to.equal( 'foo
' ); } ); it( 'should not convert undefined marker classes', () => { editor.setData( 'foo
' ); - expect( getModelData( doc ) ).to.equal( 'foo
' ); } ); it( 'should not convert marker without class', () => { editor.setData( 'foo
' ); - expect( getModelData( doc ) ).to.equal( 'foo
' ); } ); } ); describe( 'editing pipeline conversion', () => { it( 'should convert mark element with defined class', () => { - setModelData( doc, 'foo
' ); + expect( editor.getData() ).to.equal( 'foo
' ); } ); } ); 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' } + ] + } ); } ); } ); } ); diff --git a/tests/highlightui.js b/tests/highlightui.js new file mode 100644 index 0000000..dc30325 --- /dev/null +++ b/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(); + } ); + } + } ); + } ); +} ); diff --git a/tests/integration.js b/tests/integration.js index 1fd3f40..ded2d7a 100644 --- a/tests/integration.js +++ b/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, 'Highlight feature example.
++ Here are some markers: +
++ Here are some pens: + red pen and green one. +
+ +Highlight feature example.
-Here ares some markers: - yellow one, pink one and green one. +
+ Here are some markers:
-Here ares some pens: - red pen and blue one. +
+ Here are some pens: + red pen and green one.
+