Browse Source

Introduced the Underline plugin.

Kamil Piechaczek 8 years ago
parent
commit
a5c95881df

+ 68 - 0
packages/ckeditor5-basic-styles/src/underline.js

@@ -0,0 +1,68 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module basic-styles/underline
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import UnderlineEngine from './underlineengine';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+import underlineIcon from '../theme/icons/underline.svg';
+
+/**
+ * The underline feature. It introduces the Underline button and the <kbd>Ctrl+U</kbd> keystroke.
+ *
+ * It uses the {@link module:basic-styles/underlineengine~UnderlineEngine underline engine feature}.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class Underline extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	static get requires() {
+		return [ UnderlineEngine ];
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	static get pluginName() {
+		return 'Underline';
+	}
+
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const editor = this.editor;
+		const t = editor.t;
+		const command = editor.commands.get( 'underline' );
+		const keystroke = 'CTRL+U';
+
+		// Add bold button to feature components.
+		editor.ui.componentFactory.add( 'underline', locale => {
+			const view = new ButtonView( locale );
+
+			view.set( {
+				label: t( 'Underline' ),
+				icon: underlineIcon,
+				keystroke,
+				tooltip: true
+			} );
+
+			view.bind( 'isOn', 'isEnabled' ).to( command, 'value', 'isEnabled' );
+
+			// Execute command.
+			this.listenTo( view, 'execute', () => editor.execute( 'underline' ) );
+
+			return view;
+		} );
+
+		// Set the Ctrl+U keystroke.
+		editor.keystrokes.set( keystroke, 'underline' );
+	}
+}

+ 53 - 0
packages/ckeditor5-basic-styles/src/underlineengine.js

@@ -0,0 +1,53 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/**
+ * @module basic-styles/underline
+ */
+
+import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
+import buildModelConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildmodelconverter';
+import buildViewConverter from '@ckeditor/ckeditor5-engine/src/conversion/buildviewconverter';
+import AttributeCommand from './attributecommand';
+
+const UNDERLINE = 'underline';
+
+/**
+ * The underline engine feature.
+ *
+ * It registers the `underline` command and introduces the `underline` attribute in the model which renders to the view
+ * as an `<u>` element.
+ *
+ * @extends module:core/plugin~Plugin
+ */
+export default class UnderlineEngine extends Plugin {
+	/**
+	 * @inheritDoc
+	 */
+	init() {
+		const editor = this.editor;
+		const data = editor.data;
+		const editing = editor.editing;
+
+		// Allow underline attribute on all inline nodes.
+		editor.document.schema.allow( { name: '$inline', attributes: UNDERLINE, inside: '$block' } );
+		// Temporary workaround. See https://github.com/ckeditor/ckeditor5/issues/477.
+		editor.document.schema.allow( { name: '$inline', attributes: UNDERLINE, inside: '$clipboardHolder' } );
+
+		// Build converter from model to view for data and editing pipelines.
+		buildModelConverter().for( data.modelToView, editing.modelToView )
+			.fromAttribute( UNDERLINE )
+			.toElement( 'u' );
+
+		// Build converter from view to model for data pipeline.
+		buildViewConverter().for( data.viewToModel )
+			.fromElement( 'u' )
+			.fromAttribute( 'style', { 'text-decoration': 'underline' } )
+			.toAttribute( UNDERLINE, true );
+
+		// Create underline command.
+		editor.commands.add( UNDERLINE, new AttributeCommand( editor, UNDERLINE ) );
+	}
+}

+ 1 - 1
packages/ckeditor5-basic-styles/tests/manual/basic-styles.html

@@ -1,3 +1,3 @@
 <div id="editor">
-	<p><i>This</i> is an <strong>editor</strong> instance.</p>
+	<p><i>This</i> is an <strong>editor</strong> <u>instance</u>.</p>
 </div>

+ 3 - 2
packages/ckeditor5-basic-styles/tests/manual/basic-styles.js

@@ -12,11 +12,12 @@ import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
 import Undo from '@ckeditor/ckeditor5-undo/src/undo';
 import Bold from '../../src/bold';
 import Italic from '../../src/italic';
+import Underline from '../../src/underline';
 
 ClassicEditor
 	.create( document.querySelector( '#editor' ), {
-		plugins: [ Enter, Typing, Paragraph, Undo, Bold, Italic ],
-		toolbar: [ 'bold', 'italic', 'undo', 'redo' ]
+		plugins: [ Enter, Typing, Paragraph, Undo, Bold, Italic, Underline ],
+		toolbar: [ 'bold', 'italic', 'underline', 'undo', 'redo' ]
 	} )
 	.then( editor => {
 		window.editor = editor;

+ 3 - 2
packages/ckeditor5-basic-styles/tests/manual/basic-styles.md

@@ -2,5 +2,6 @@
 
 1. The data should be loaded with:
   * italic "This",
-  * bold "editor".
-2. Test the bold and italic features live.
+  * bold "editor",
+  * underline "instance".
+2. Test the bold, italic and underline features live.

+ 95 - 0
packages/ckeditor5-basic-styles/tests/underline.js

@@ -0,0 +1,95 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals document */
+
+import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
+import Underline from '../src/underline';
+import UnderlineEngine from '../src/underlineengine';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard';
+
+testUtils.createSinonSandbox();
+
+describe( 'Underline', () => {
+	let editor, underlineView;
+
+	beforeEach( () => {
+		const editorElement = document.createElement( 'div' );
+		document.body.appendChild( editorElement );
+
+		return ClassicTestEditor
+			.create( editorElement, {
+				plugins: [ Underline ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+
+				underlineView = editor.ui.componentFactory.create( 'underline' );
+			} );
+	} );
+
+	afterEach( () => {
+		return editor.destroy();
+	} );
+
+	it( 'should be loaded', () => {
+		expect( editor.plugins.get( Underline ) ).to.be.instanceOf( Underline );
+	} );
+
+	it( 'should load UnderlineEngine', () => {
+		expect( editor.plugins.get( UnderlineEngine ) ).to.be.instanceOf( UnderlineEngine );
+	} );
+
+	it( 'should register underline feature component', () => {
+		expect( underlineView ).to.be.instanceOf( ButtonView );
+		expect( underlineView.isOn ).to.be.false;
+		expect( underlineView.label ).to.equal( 'Underline' );
+		expect( underlineView.icon ).to.match( /<svg / );
+		expect( underlineView.keystroke ).to.equal( 'CTRL+U' );
+	} );
+
+	it( 'should execute underline command on model execute event', () => {
+		const executeSpy = testUtils.sinon.spy( editor, 'execute' );
+
+		underlineView.fire( 'execute' );
+
+		sinon.assert.calledOnce( executeSpy );
+		sinon.assert.calledWithExactly( executeSpy, 'underline' );
+	} );
+
+	it( 'should bind model to underline command', () => {
+		const command = editor.commands.get( 'underline' );
+
+		expect( underlineView.isOn ).to.be.false;
+
+		expect( underlineView.isEnabled ).to.be.false;
+
+		command.value = true;
+		expect( underlineView.isOn ).to.be.true;
+
+		command.isEnabled = true;
+		expect( underlineView.isEnabled ).to.be.true;
+	} );
+
+	it( 'should set keystroke in the model', () => {
+		expect( underlineView.keystroke ).to.equal( 'CTRL+U' );
+	} );
+
+	it( 'should set editor keystroke', () => {
+		const spy = sinon.spy( editor, 'execute' );
+
+		const wasHandled = editor.keystrokes.press( {
+			keyCode: keyCodes.u,
+			ctrlKey: true,
+			preventDefault: sinon.spy(),
+			stopPropagation: sinon.spy()
+		} );
+
+		expect( wasHandled ).to.be.true;
+		expect( spy.calledOnce ).to.be.true;
+	} );
+} );

+ 91 - 0
packages/ckeditor5-basic-styles/tests/underlineengine.js

@@ -0,0 +1,91 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import UnderlineEngine from '../src/underlineengine';
+
+import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
+import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
+import AttributeCommand from '../src/attributecommand';
+
+import { getData as getModelData, setData as setModelData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
+import { getData as getViewData } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
+
+describe( 'UnderlineEngine', () => {
+	let editor, doc;
+
+	beforeEach( () => {
+		return VirtualTestEditor
+			.create( {
+				plugins: [ Paragraph, UnderlineEngine ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+
+				doc = editor.document;
+			} );
+	} );
+
+	afterEach( () => {
+		return editor.destroy();
+	} );
+
+	it( 'should be loaded', () => {
+		expect( editor.plugins.get( UnderlineEngine ) ).to.be.instanceOf( UnderlineEngine );
+	} );
+
+	it( 'should set proper schema rules', () => {
+		expect( doc.schema.check( { name: '$inline', attributes: 'underline', inside: '$root' } ) ).to.be.false;
+		expect( doc.schema.check( { name: '$inline', attributes: 'underline', inside: '$block' } ) ).to.be.true;
+		expect( doc.schema.check( { name: '$inline', attributes: 'underline', inside: '$clipboardHolder' } ) ).to.be.true;
+	} );
+
+	describe( 'command', () => {
+		it( 'should register underline command', () => {
+			const command = editor.commands.get( 'underline' );
+
+			expect( command ).to.be.instanceOf( AttributeCommand );
+			expect( command ).to.have.property( 'attributeKey', 'underline' );
+		} );
+	} );
+
+	describe( 'data pipeline conversions', () => {
+		it( 'should convert <u> to underline attribute', () => {
+			editor.setData( '<p><u>foo</u>bar</p>' );
+
+			expect( getModelData( doc, { withoutSelection: true } ) )
+				.to.equal( '<paragraph><$text underline="true">foo</$text>bar</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p><u>foo</u>bar</p>' );
+		} );
+
+		it( 'should convert text-decoration:underline to underline attribute', () => {
+			editor.setData( '<p><span style="text-decoration: underline;">foo</span>bar</p>' );
+
+			expect( getModelData( doc, { withoutSelection: true } ) )
+				.to.equal( '<paragraph><$text underline="true">foo</$text>bar</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p><u>foo</u>bar</p>' );
+		} );
+
+		it( 'should be integrated with autoparagraphing', () => {
+			// Incorrect results because autoparagraphing works incorrectly (issue in paragraph).
+			// https://github.com/ckeditor/ckeditor5-paragraph/issues/10
+
+			editor.setData( '<u>foo</u>bar' );
+
+			expect( getModelData( doc, { withoutSelection: true } ) ).to.equal( '<paragraph>foobar</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p>foobar</p>' );
+		} );
+	} );
+
+	describe( 'editing pipeline conversion', () => {
+		it( 'should convert attribute', () => {
+			setModelData( doc, '<paragraph><$text underline="true">foo</$text>bar</paragraph>' );
+
+			expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( '<p><u>foo</u>bar</p>' );
+		} );
+	} );
+} );

File diff suppressed because it is too large
+ 1 - 0
packages/ckeditor5-basic-styles/theme/icons/underline.svg