Parcourir la source

Merge pull request #55 from ckeditor/t/52

Feature: Introduced the `Code` plugin. Closes #52.
Piotrek Koszuliński il y a 8 ans
Parent
commit
964c0f30fb

+ 2 - 1
packages/ckeditor5-basic-styles/lang/contexts.json

@@ -1,5 +1,6 @@
 {
 	"Bold": "Toolbar button tooltip for the Bold feature.",
 	"Italic": "Toolbar button tooltip for the Italic feature.",
-	"Underline": "Toolbar button tooltip for the Underline feature."
+	"Underline": "Toolbar button tooltip for the Underline feature.",
+	"Code": "Toolbar button tooltip for the Code feature."
 }

+ 2 - 1
packages/ckeditor5-basic-styles/package.json

@@ -6,7 +6,8 @@
   "dependencies": {
     "@ckeditor/ckeditor5-core": "^0.9.0",
     "@ckeditor/ckeditor5-engine": "^0.11.0",
-    "@ckeditor/ckeditor5-ui": "^0.10.0"
+    "@ckeditor/ckeditor5-ui": "^0.10.0",
+    "@ckeditor/ckeditor5-theme-lark": "^0.9.0"
   },
   "devDependencies": {
     "@ckeditor/ckeditor5-dev-lint": "^3.1.0",

+ 65 - 0
packages/ckeditor5-basic-styles/src/code.js

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

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

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

+ 75 - 0
packages/ckeditor5-basic-styles/tests/code.js

@@ -0,0 +1,75 @@
+/**
+ * @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 Code from '../src/code';
+import CodeEngine from '../src/codeengine';
+import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview';
+import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
+
+testUtils.createSinonSandbox();
+
+describe( 'Code', () => {
+	let editor, codeView;
+
+	beforeEach( () => {
+		const editorElement = document.createElement( 'div' );
+		document.body.appendChild( editorElement );
+
+		return ClassicTestEditor
+			.create( editorElement, {
+				plugins: [ Code ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+
+				codeView = editor.ui.componentFactory.create( 'code' );
+			} );
+	} );
+
+	afterEach( () => {
+		return editor.destroy();
+	} );
+
+	it( 'should be loaded', () => {
+		expect( editor.plugins.get( Code ) ).to.be.instanceOf( Code );
+	} );
+
+	it( 'should load CodeEngine', () => {
+		expect( editor.plugins.get( CodeEngine ) ).to.be.instanceOf( CodeEngine );
+	} );
+
+	it( 'should register code feature component', () => {
+		expect( codeView ).to.be.instanceOf( ButtonView );
+		expect( codeView.isOn ).to.be.false;
+		expect( codeView.label ).to.equal( 'Code' );
+		expect( codeView.icon ).to.match( /<svg / );
+	} );
+
+	it( 'should execute code command on model execute event', () => {
+		const executeSpy = testUtils.sinon.spy( editor, 'execute' );
+
+		codeView.fire( 'execute' );
+
+		sinon.assert.calledOnce( executeSpy );
+		sinon.assert.calledWithExactly( executeSpy, 'code' );
+	} );
+
+	it( 'should bind model to code command', () => {
+		const command = editor.commands.get( 'code' );
+
+		expect( codeView.isOn ).to.be.false;
+
+		expect( codeView.isEnabled ).to.be.false;
+
+		command.value = true;
+		expect( codeView.isOn ).to.be.true;
+
+		command.isEnabled = true;
+		expect( codeView.isEnabled ).to.be.true;
+	} );
+} );

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

@@ -0,0 +1,91 @@
+/**
+ * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+import CodeEngine from '../src/codeengine';
+
+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( 'CodeEngine', () => {
+	let editor, doc;
+
+	beforeEach( () => {
+		return VirtualTestEditor
+			.create( {
+				plugins: [ Paragraph, CodeEngine ]
+			} )
+			.then( newEditor => {
+				editor = newEditor;
+
+				doc = editor.document;
+			} );
+	} );
+
+	afterEach( () => {
+		return editor.destroy();
+	} );
+
+	it( 'should be loaded', () => {
+		expect( editor.plugins.get( CodeEngine ) ).to.be.instanceOf( CodeEngine );
+	} );
+
+	it( 'should set proper schema rules', () => {
+		expect( doc.schema.check( { name: '$inline', attributes: 'code', inside: '$root' } ) ).to.be.false;
+		expect( doc.schema.check( { name: '$inline', attributes: 'code', inside: '$block' } ) ).to.be.true;
+		expect( doc.schema.check( { name: '$inline', attributes: 'code', inside: '$clipboardHolder' } ) ).to.be.true;
+	} );
+
+	describe( 'command', () => {
+		it( 'should register code command', () => {
+			const command = editor.commands.get( 'code' );
+
+			expect( command ).to.be.instanceOf( AttributeCommand );
+			expect( command ).to.have.property( 'attributeKey', 'code' );
+		} );
+	} );
+
+	describe( 'data pipeline conversions', () => {
+		it( 'should convert <code> to code attribute', () => {
+			editor.setData( '<p><code>foo</code>bar</p>' );
+
+			expect( getModelData( doc, { withoutSelection: true } ) )
+				.to.equal( '<paragraph><$text code="true">foo</$text>bar</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p><code>foo</code>bar</p>' );
+		} );
+
+		it( 'should convert word-wrap:break-word to code attribute', () => {
+			editor.setData( '<p><span style="word-wrap: break-word">foo</span>bar</p>' );
+
+			expect( getModelData( doc, { withoutSelection: true } ) )
+				.to.equal( '<paragraph><$text code="true">foo</$text>bar</paragraph>' );
+
+			expect( editor.getData() ).to.equal( '<p><code>foo</code>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( '<code>foo</code>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 code="true">foo</$text>bar</paragraph>' );
+
+			expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( '<p><code>foo</code>bar</p>' );
+		} );
+	} );
+} );

+ 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> <u>instance</u>.</p>
+	<p><i>This</i> <code>is an</code> <strong>editor</strong> <u>instance</u>.</p>
 </div>

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

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

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

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

+ 14 - 0
packages/ckeditor5-basic-styles/theme/code.scss

@@ -0,0 +1,14 @@
+// Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
+// For licensing, see LICENSE.md or http://ckeditor.com/license
+
+@import '~@ckeditor/ckeditor5-theme-lark/theme/helpers/_colors.scss';
+@import '~@ckeditor/ckeditor5-theme-lark/theme/helpers/_rounded.scss';
+@import '~@ckeditor/ckeditor5-theme-lark/theme/helpers/_spacing.scss';
+
+@include ck-color-add( ( 'code': rgba( 200, 200, 200, 0.3 ) ) );
+
+code {
+	background-color: ck-color( 'code' );
+	padding: ck-spacing( 'tiny' );
+	border-radius: $ck-border-radius;
+}

+ 1 - 0
packages/ckeditor5-basic-styles/theme/icons/code.svg

@@ -0,0 +1 @@
+<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M11.893 5.376c.123.01.23.046.333.114l5.6 3.967.004 1.017-5.6 4.033a.633.633 0 0 1-.509.101.63.63 0 0 1-.473-.708.642.642 0 0 1 .252-.407l4.89-3.522-4.886-3.461c-.036-.027-.036-.027-.069-.057a.658.658 0 0 1-.15-.219.633.633 0 0 1 .228-.751.66.66 0 0 1 .291-.105c.044-.003.044-.003.089-.002zm-3.697.002a.66.66 0 0 1 .291.105.633.633 0 0 1 .21.791.664.664 0 0 1-.201.236L3.61 9.971l4.89 3.522c.036.027.036.027.069.057a.649.649 0 0 1 .189.394.63.63 0 0 1-.7.676.655.655 0 0 1-.288-.113l-5.6-4.033.004-1.017 5.6-3.967a.685.685 0 0 1 .333-.114c.045-.001.045-.001.089.002z" fill="#353535"/></svg>