ソースを参照

Introduce Mention#toMentionAttribute() method.

Maciej Gołaszewski 6 年 前
コミット
17140f0921

+ 1 - 1
packages/ckeditor5-mention/docs/_snippets/features/custom-mention-colors-variables.html

@@ -10,5 +10,5 @@
 </style>
 
 <div id="snippet-mention-custom-colors">
-	<p>Hello <span class="mention" data-mention="Ted">@Ted</span>.</p>
+	<p>Hello <span class="mention" data-mention="@Ted">@Ted</span>.</p>
 </div>

+ 1 - 1
packages/ckeditor5-mention/docs/_snippets/features/mention-customization.html

@@ -1,5 +1,5 @@
 <div id="snippet-mention-customization">
-	<p>Hello <a class="mention" data-mention="Ted Mosby" data-user-id="5" href="https://www.imdb.com/title/tt0460649/characters/nm1102140">@Ted Mosby</a>!</p>
+	<p>Hello <a class="mention" data-mention="@Ted Mosby" data-user-id="5" href="https://www.imdb.com/title/tt0460649/characters/nm1102140">@Ted Mosby</a>!</p>
 </div>
 
 <style>

+ 1 - 1
packages/ckeditor5-mention/docs/_snippets/features/mention-customization.js

@@ -79,7 +79,7 @@ function MentionCustomization( editor ) {
 
 			return viewWriter.createAttributeElement( 'a', {
 				class: 'mention',
-				'data-mention': modelAttributeValue.name,
+				'data-mention': modelAttributeValue._marker + modelAttributeValue.name,
 				'data-user-id': modelAttributeValue.id,
 				'href': modelAttributeValue.link
 			} );

+ 1 - 1
packages/ckeditor5-mention/docs/_snippets/features/mention.html

@@ -1,3 +1,3 @@
 <div id="snippet-mention">
-	<p>Hello <span class="mention" data-mention="Ted">@Ted</span>.</p>
+	<p>Hello <span class="mention" data-mention="@Ted">@Ted</span>.</p>
 </div>

+ 2 - 2
packages/ckeditor5-mention/docs/features/mention.md

@@ -155,13 +155,13 @@ In order to change the markup generated by the editor for mentions, you can over
 The example below defined a plugin which overrides the default output:
 
 ```html
-<span data-mention="Ted" class="mention">@Ted</span>
+<span data-mention="@Ted" class="mention">@Ted</span>
 ```
 
 To a link:
 
 ```html
-<a class="mention" data-mention="Ted Mosby" data-user-id="5" href="https://www.imdb.com/title/tt0460649/characters/nm1102140">@Ted Mosby</a>
+<a class="mention" data-mention="@Ted Mosby" data-user-id="5" href="https://www.imdb.com/title/tt0460649/characters/nm1102140">@Ted Mosby</a>
 ```
 
 The converters must be defined with a `'high'` priority to be executed before the {@link features/link link} feature's converter and before the default converter of the mention feature. A mention is stored in the model as a {@link framework/guides/architecture/editing-engine#text-attributes text attribute} which stores an object (see {@link module:mention/mention~MentionFeedItem}).

+ 17 - 1
packages/ckeditor5-mention/src/mention.js

@@ -9,7 +9,7 @@
 
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
 
-import MentionEditing from './mentionediting';
+import MentionEditing, { _toMentionAttribute } from './mentionediting';
 import MentionUI from './mentionui';
 
 import '../theme/mention.css';
@@ -22,6 +22,22 @@ import '../theme/mention.css';
  * @extends module:core/plugin~Plugin
  */
 export default class Mention extends Plugin {
+	/**
+	 * Creates mention attribute value from provided view element and optional data.
+	 *
+	 *		editor.plugins.get( 'Mention' ).toMentionAttribute( viewElement, { userId: '1234' } );
+	 *
+	 *		// for a viewElement: <span data-mention="@joe">@John Doe</span>
+	 *		// it will return:
+	 *		// { id: '@joe', userId: '1234', _uid: '7a7bc7...', _text: '@John Doe' }
+	 *
+	 * @param {module:engine/view/element~Element} viewElement
+	 * @param {String|Object} [data] Mention data to be extended.
+	 */
+	toMentionAttribute( viewElement, data ) {
+		return _toMentionAttribute( viewElement, data );
+	}
+
 	/**
 	 * @inheritDoc
 	 */

+ 4 - 2
packages/ckeditor5-mention/src/mentioncommand.js

@@ -10,6 +10,7 @@
 import Command from '@ckeditor/ckeditor5-core/src/command';
 import toMap from '@ckeditor/ckeditor5-utils/src/tomap';
 import uid from '@ckeditor/ckeditor5-utils/src/uid';
+import { _getText } from './textwatcher';
 
 /**
  * The mention command.
@@ -62,11 +63,12 @@ export default class MentionCommand extends Command {
 
 		const mention = typeof options.mention == 'string' ? { name: options.mention } : options.mention;
 
+		const range = options.range || selection.getFirstRange();
+
 		// Set internal attributes on mention object.
 		mention._id = uid();
 		mention._marker = marker;
-
-		const range = options.range || selection.getFirstRange();
+		mention._text = _getText( range );
 
 		model.change( writer => {
 			const currentAttributes = toMap( selection.getAttributes() );

+ 32 - 23
packages/ckeditor5-mention/src/mentionediting.js

@@ -17,7 +17,7 @@ import MentionCommand from './mentioncommand';
  *
  * It introduces the {@link module:mention/mentioncommand~MentionCommand command} and the `mention`
  * attribute in the {@link module:engine/model/model~Model model} which renders in the {@link module:engine/view/view view}
- * as a `<span class="mention" data-mention="name">`.
+ * as a `<span class="mention" data-mention="@mention">`.
  *
  * @extends module:core/plugin~Plugin
  */
@@ -48,7 +48,7 @@ export default class MentionEditing extends Plugin {
 			},
 			model: {
 				key: 'mention',
-				value: parseMentionViewItemAttributes
+				value: _toMentionAttribute
 			}
 		} );
 
@@ -65,33 +65,34 @@ export default class MentionEditing extends Plugin {
 	}
 }
 
-// Parses matched view element to mention attribute value.
-//
-// @param {module:engine/view/element} viewElement
-// @returns {Object} Mention attribute value
-function parseMentionViewItemAttributes( viewElement ) {
+/**
+ * Creates mention attribute value from provided view element and optional data.
+ *
+ * This function is exposed as
+ * {@link module:mention/mention~Mention#toWidgetAttribute `editor.plugins.get( 'Mention' ).toWidgetAttribute()`}.
+ *
+ * @protected
+ * @param {module:engine/view/element~Element} viewElement
+ * @param {String|Object} [data] Mention data to be extended.
+ */
+export function _toMentionAttribute( viewElement, data ) {
 	const dataMention = viewElement.getAttribute( 'data-mention' );
 
-	const textNode = viewElement.getChild( 0 );
-
-	// Do not parse empty mentions.
-	if ( !textNode || !textNode.is( 'text' ) ) {
-		return;
-	}
+	const { marker, id } = extractMarkerAndId( dataMention );
 
-	const mentionString = textNode.data;
-
-	// Assume that mention is set as marker + mention name.
-	const marker = mentionString.slice( 0, 1 );
-	const name = mentionString.slice( 1 );
+	const textNode = viewElement.getChild( 0 );
 
-	// Do not upcast partial mentions - might come from copy-paste of partially selected mention.
-	if ( name != dataMention ) {
+	// Do not convert empty mentions.
+	if ( !textNode ) {
 		return;
 	}
 
-	// Set UID for mention to not merge mentions in the same block that are next to each other.
-	return { name: dataMention, _marker: marker, _id: uid() };
+	return Object.assign( {
+		name: marker + id,
+		_text: textNode.data,
+		_marker: marker,
+		_id: uid()
+	}, data || {} );
 }
 
 // Creates mention element from mention data.
@@ -227,7 +228,7 @@ function isBrokenMentionNode( node ) {
 	const text = node.data;
 	const mention = node.getAttribute( 'mention' );
 
-	const expectedText = mention._marker + mention.name;
+	const expectedText = mention._text;
 
 	return text != expectedText;
 }
@@ -246,3 +247,11 @@ function checkAndFix( textNode, writer ) {
 
 	return false;
 }
+
+function extractMarkerAndId( data ) {
+	const string = String( data );
+	const marker = string[ 0 ];
+	const id = string.slice( 1 );
+
+	return { id, marker };
+}

+ 10 - 8
packages/ckeditor5-mention/src/textwatcher.js

@@ -129,17 +129,19 @@ export default class TextWatcher {
 
 		const block = selection.focus.parent;
 
-		return getText( block ).slice( 0, selection.focus.offset );
+		return _getText( editor.model.createRangeIn( block ) ).slice( 0, selection.focus.offset );
 	}
 }
 
-// Returns whole text from parent element by adding all data from text nodes together.
-//
-// @private
-// @param {module:engine/model/element~Element} element
-// @returns {String}
-function getText( element ) {
-	return Array.from( element.getChildren() ).reduce( ( a, b ) => a + b.data, '' );
+/**
+ * Returns whole text from given range by adding all data from text nodes together.
+ *
+ * @protected
+ * @param {module:engine/model/range~Range} range
+ * @returns {String}
+ */
+export function _getText( range ) {
+	return Array.from( range.getItems() ).reduce( ( a, b ) => a + b.data, '' );
 }
 
 mix( TextWatcher, EmitterMixin );

+ 2 - 2
packages/ckeditor5-mention/tests/manual/mention-custom-view.html

@@ -1,7 +1,7 @@
 <div id="editor">
-	<p>Have you met... <a class="mention" data-mention="Ted Mosby" href="https://www.imdb.com/title/tt0460649/characters/nm1102140">@Ted Mosby</a></p>
+	<p>Have you met... <a class="mention" data-mention="@Ted Mosby" href="https://www.imdb.com/title/tt0460649/characters/nm1102140">@Ted Mosby</a></p>
 
-	<p>Same mention twice in data: <a class="mention" data-mention="Ted Mosby" href="https://www.imdb.com/title/tt0460649/characters/nm1102140">@Ted Mosby</a><a class="mention" data-mention="Ted Mosby" href="https://www.imdb.com/title/tt0460649/characters/nm1102140">@Ted Mosby</a></p>
+	<p>Same mention twice in data: <a class="mention" data-mention="@Ted Mosby" href="https://www.imdb.com/title/tt0460649/characters/nm1102140">@Ted Mosby</a><a class="mention" data-mention="@Ted Mosby" href="https://www.imdb.com/title/tt0460649/characters/nm1102140">@Ted Mosby</a></p>
 </div>
 
 <style>

+ 3 - 11
packages/ckeditor5-mention/tests/manual/mention-custom-view.js

@@ -12,7 +12,6 @@ import Underline from '@ckeditor/ckeditor5-basic-styles/src/underline';
 import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
 import Font from '@ckeditor/ckeditor5-font/src/font';
 import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
-import uid from '@ckeditor/ckeditor5-utils/src/uid';
 
 import Mention from '../../src/mention';
 
@@ -31,16 +30,9 @@ class CustomMentionAttributeView extends Plugin {
 			},
 			model: {
 				key: 'mention',
-				value: viewItem => {
-					const mentionValue = {
-						_id: uid(),
-						_marker: '@',
-						name: viewItem.getAttribute( 'data-mention' ),
-						link: viewItem.getAttribute( 'href' )
-					};
-
-					return mentionValue;
-				}
+				value: viewItem => editor.plugins.get( 'Mention' ).toMentionAttribute( viewItem, {
+					link: viewItem.getAttribute( 'href' )
+				} )
 			},
 			converterPriority: 'high'
 		} );

+ 2 - 2
packages/ckeditor5-mention/tests/manual/mention.html

@@ -1,4 +1,4 @@
 <div id="editor">
-	<p>Hello <span class="mention" data-mention="Ted">@Ted</span>.</p>
-	<p>Hello <span class="mention" data-mention="Ted">@Ted</span><span class="mention" data-mention="Ted">@Ted</span>.</p>
+	<p>Hello <span class="mention" data-mention="@Ted">@Ted</span>.</p>
+	<p>Hello <span class="mention" data-mention="@Ted">@Ted</span><span class="mention" data-mention="@Ted">@Ted</span>.</p>
 </div>

+ 17 - 17
packages/ckeditor5-mention/tests/mention-integration.js

@@ -47,9 +47,9 @@ describe( 'Mention feature - integration', () => {
 
 		// Failing test. See ckeditor/ckeditor5#1645.
 		it( 'should restore removed mention on adding a text inside mention', () => {
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
-			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			model.change( writer => {
 				const paragraph = doc.getRoot().getChild( 0 );
@@ -64,16 +64,16 @@ describe( 'Mention feature - integration', () => {
 
 			editor.execute( 'undo' );
 
-			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 			expect( getViewData( editor.editing.view, { withoutSelection: true } ) )
-				.to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+				.to.equal( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 		} );
 
 		// Failing test. See ckeditor/ckeditor5#1645.
 		it( 'should restore removed mention on removing a text inside mention', () => {
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
-			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			model.change( writer => {
 				const paragraph = doc.getRoot().getChild( 0 );
@@ -89,15 +89,15 @@ describe( 'Mention feature - integration', () => {
 
 			editor.execute( 'undo' );
 
-			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 			expect( getViewData( editor.editing.view, { withoutSelection: true } ) )
-				.to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+				.to.equal( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 		} );
 
 		it( 'should work with attribute post-fixer (beginning formatted)', () => {
 			testAttributePostFixer(
-				'<p>foo <span class="mention" data-mention="John">@John</span> bar</p>',
-				'<p><strong>foo <span class="mention" data-mention="John">@John</span></strong> bar</p>',
+				'<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>',
+				'<p><strong>foo <span class="mention" data-mention="@John">@John</span></strong> bar</p>',
 				() => {
 					model.change( writer => {
 						const paragraph = doc.getRoot().getChild( 0 );
@@ -113,8 +113,8 @@ describe( 'Mention feature - integration', () => {
 
 		it( 'should work with attribute post-fixer (end formatted)', () => {
 			testAttributePostFixer(
-				'<p>foo <span class="mention" data-mention="John">@John</span> bar</p>',
-				'<p>foo <strong><span class="mention" data-mention="John">@John</span> ba</strong>r</p>',
+				'<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>',
+				'<p>foo <strong><span class="mention" data-mention="@John">@John</span> ba</strong>r</p>',
 				() => {
 					model.change( writer => {
 						const paragraph = doc.getRoot().getChild( 0 );
@@ -130,8 +130,8 @@ describe( 'Mention feature - integration', () => {
 
 		it( 'should work with attribute post-fixer (middle formatted)', () => {
 			testAttributePostFixer(
-				'<p>foo <span class="mention" data-mention="John">@John</span> bar</p>',
-				'<p>foo <strong><span class="mention" data-mention="John">@John</span></strong> bar</p>',
+				'<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>',
+				'<p>foo <strong><span class="mention" data-mention="@John">@John</span></strong> bar</p>',
 				() => {
 					model.change( writer => {
 						const paragraph = doc.getRoot().getChild( 0 );
@@ -187,7 +187,7 @@ describe( 'Mention feature - integration', () => {
 				} );
 		} );
 
-		it( 'should fix broken mention inside pasted content', () => {
+		it( 'should not fix broken mention inside pasted content', () => {
 			editor.setData( '<p>foobar</p>' );
 
 			model.change( writer => {
@@ -195,11 +195,11 @@ describe( 'Mention feature - integration', () => {
 			} );
 
 			clipboard.fire( 'inputTransformation', {
-				content: parseView( '<blockquote><p>xxx<span class="mention" data-mention="John">@Joh</span></p></blockquote>' )
+				content: parseView( '<blockquote><p>xxx<span class="mention" data-mention="@John">@Joh</span></p></blockquote>' )
 			} );
 
 			const expectedData = '<p>foo</p>' +
-				'<blockquote><p>xxx@Joh</p></blockquote>' +
+				'<blockquote><p>xxx<span class="mention" data-mention="@John">@Joh</span></p></blockquote>' +
 				'<p>bar</p>';
 
 			expect( editor.getData() )

+ 43 - 0
packages/ckeditor5-mention/tests/mention.js

@@ -5,6 +5,8 @@
 
 import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
 import global from '@ckeditor/ckeditor5-utils/src/dom/global';
+import Element from '@ckeditor/ckeditor5-engine/src/view/element';
+import Text from '@ckeditor/ckeditor5-engine/src/view/text';
 
 import Mention from '../src/mention';
 import MentionEditing from '../src/mentionediting';
@@ -47,4 +49,45 @@ describe( 'Mention', () => {
 	it( 'should load MentionUI plugin', () => {
 		expect( editor.plugins.get( MentionUI ) ).to.instanceOf( MentionUI );
 	} );
+
+	describe( 'toMentionAttribute()', () => {
+		it( 'should create mention attribute with default properties', () => {
+			const text = new Text( 'John Doe' );
+
+			const viewElement = new Element( 'span', {
+				'data-mention': '@John'
+			}, text );
+
+			const mentionAttribute = editor.plugins.get( 'Mention' ).toMentionAttribute( viewElement );
+
+			expect( mentionAttribute ).to.have.property( '_id' );
+			expect( mentionAttribute ).to.have.property( '_text', 'John Doe' );
+			expect( mentionAttribute ).to.have.property( 'name', '@John' );
+		} );
+
+		it( 'should create mention attribute with provided attributes', () => {
+			const text = new Text( 'John Doe' );
+
+			const viewElement = new Element( 'span', {
+				'data-mention': '@John'
+			}, text );
+
+			const mentionAttribute = editor.plugins.get( 'Mention' ).toMentionAttribute( viewElement, { foo: 'bar' } );
+
+			expect( mentionAttribute ).to.have.property( '_id' );
+			expect( mentionAttribute ).to.have.property( '_text', 'John Doe' );
+			expect( mentionAttribute ).to.have.property( 'name', '@John' );
+			expect( mentionAttribute ).to.have.property( 'foo', 'bar' );
+		} );
+
+		it( 'should return undefined if Element has no text node', () => {
+			const viewElement = new Element( 'span', {
+				'data-mention': '@John'
+			} );
+
+			const mentionAttribute = editor.plugins.get( 'Mention' ).toMentionAttribute( viewElement, { foo: 'bar' } );
+
+			expect( mentionAttribute ).to.be.undefined;
+		} );
+	} );
 } );

+ 50 - 41
packages/ckeditor5-mention/tests/mentionediting.js

@@ -67,8 +67,8 @@ describe( 'MentionEditing', () => {
 				} );
 		} );
 
-		it( 'should convert <span class="mention" data-mention="John"> to mention attribute', () => {
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+		it( 'should convert <span class="mention" data-mention="@John"> to mention attribute', () => {
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			const textNode = doc.getRoot().getChild( 0 ).getChild( 1 );
 
@@ -76,9 +76,9 @@ describe( 'MentionEditing', () => {
 			expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
 			expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_id' );
 			expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_marker', '@' );
-			expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'name', 'John' );
+			expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'name', '@John' );
 
-			const expectedView = '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>';
+			const expectedView = '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>';
 
 			expect( editor.getData() ).to.equal( expectedView );
 			expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( expectedView );
@@ -87,8 +87,8 @@ describe( 'MentionEditing', () => {
 		it( 'should convert consecutive mentions spans as two text nodes and two spans in the view', () => {
 			editor.setData(
 				'<p>' +
-					'<span class="mention" data-mention="John">@John</span>' +
-					'<span class="mention" data-mention="John">@John</span>' +
+					'<span class="mention" data-mention="@John">@John</span>' +
+					'<span class="mention" data-mention="@John">@John</span>' +
 				'</p>'
 			);
 
@@ -107,8 +107,8 @@ describe( 'MentionEditing', () => {
 
 			expect( firstMentionId ).to.not.equal( secondMentionId );
 
-			const expectedView = '<p><span class="mention" data-mention="John">@John</span>' +
-				'<span class="mention" data-mention="John">@John</span></p>';
+			const expectedView = '<p><span class="mention" data-mention="@John">@John</span>' +
+				'<span class="mention" data-mention="@John">@John</span></p>';
 
 			expect( editor.getData() ).to.equal( expectedView );
 			expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( expectedView );
@@ -118,23 +118,31 @@ describe( 'MentionEditing', () => {
 				expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
 				expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_id' );
 				expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_marker', '@' );
-				expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'name', 'John' );
+				expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'name', '@John' );
+				expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_text', '@John' );
 			}
 		} );
 
 		it( 'should not convert partial mentions', () => {
-			editor.setData( '<p><span class="mention" data-mention="John">@Jo</span></p>' );
+			editor.setData( '<p><span class="mention" data-mention="@John">@Jo</span></p>' );
 
-			expect( getModelData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>@Jo</paragraph>' );
+			const textNode = doc.getRoot().getChild( 0 ).getChild( 0 );
 
-			const expectedView = '<p>@Jo</p>';
+			expect( textNode ).to.not.be.null;
+			expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
+			expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_id' );
+			expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_marker', '@' );
+			expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_text', '@Jo' );
+			expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'name', '@John' );
+
+			const expectedView = '<p><span class="mention" data-mention="@John">@Jo</span></p>';
 
 			expect( editor.getData() ).to.equal( expectedView );
 			expect( getViewData( editor.editing.view, { withoutSelection: true } ) ).to.equal( expectedView );
 		} );
 
 		it( 'should not convert empty mentions', () => {
-			editor.setData( '<p>foo<span class="mention" data-mention="John"></span></p>' );
+			editor.setData( '<p>foo<span class="mention" data-mention="@John"></span></p>' );
 
 			expect( getModelData( model, { withoutSelection: true } ) ).to.equal( '<paragraph>foo</paragraph>' );
 
@@ -156,7 +164,7 @@ describe( 'MentionEditing', () => {
 		} );
 
 		it( 'should remove mention attribute from a selection if selection is on right side of a mention', () => {
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span>bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span>bar</p>' );
 
 			model.change( writer => {
 				const paragraph = doc.getRoot().getChild( 0 );
@@ -168,7 +176,7 @@ describe( 'MentionEditing', () => {
 		} );
 
 		it( 'should allow to type after a mention', () => {
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span>bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span>bar</p>' );
 
 			model.change( writer => {
 				const paragraph = doc.getRoot().getChild( 0 );
@@ -178,7 +186,7 @@ describe( 'MentionEditing', () => {
 				writer.insertText( ' ', paragraph, 9 );
 			} );
 
-			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 		} );
 	} );
 
@@ -193,7 +201,7 @@ describe( 'MentionEditing', () => {
 		} );
 
 		it( 'should remove mention on adding a text inside mention (in the middle)', () => {
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			const textNode = doc.getRoot().getChild( 0 ).getChild( 1 );
 
@@ -201,7 +209,8 @@ describe( 'MentionEditing', () => {
 			expect( textNode.hasAttribute( 'mention' ) ).to.be.true;
 			expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_id' );
 			expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_marker', '@' );
-			expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'name', 'John' );
+			expect( textNode.getAttribute( 'mention' ) ).to.have.property( 'name', '@John' );
+			expect( textNode.getAttribute( 'mention' ) ).to.have.property( '_text', '@John' );
 
 			model.change( writer => {
 				const paragraph = doc.getRoot().getChild( 0 );
@@ -218,7 +227,7 @@ describe( 'MentionEditing', () => {
 		} );
 
 		it( 'should remove mention on typing in mention node with selection attributes set', () => {
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			const textNode = doc.getRoot().getChild( 0 ).getChild( 1 );
 
@@ -239,7 +248,7 @@ describe( 'MentionEditing', () => {
 		} );
 
 		it( 'should remove mention on removing a text at the beginning of a mention', () => {
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			const paragraph = doc.getRoot().getChild( 0 );
 
@@ -256,7 +265,7 @@ describe( 'MentionEditing', () => {
 		} );
 
 		it( 'should remove mention on removing a text in the middle a mention', () => {
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			const paragraph = doc.getRoot().getChild( 0 );
 
@@ -273,7 +282,7 @@ describe( 'MentionEditing', () => {
 		} );
 
 		it( 'should remove mention on removing a text at the and of a mention', () => {
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			const paragraph = doc.getRoot().getChild( 0 );
 
@@ -290,7 +299,7 @@ describe( 'MentionEditing', () => {
 		} );
 
 		it( 'should not remove mention on removing a text just after a mention', () => {
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			const paragraph = doc.getRoot().getChild( 0 );
 
@@ -304,11 +313,11 @@ describe( 'MentionEditing', () => {
 				model.deleteContent( doc.selection );
 			} );
 
-			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="John">@John</span>bar</p>' );
+			expect( editor.getData() ).to.equal( '<p>foo <span class="mention" data-mention="@John">@John</span>bar</p>' );
 		} );
 
 		it( 'should remove mention on inserting text node inside a mention', () => {
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			const paragraph = doc.getRoot().getChild( 0 );
 
@@ -326,7 +335,7 @@ describe( 'MentionEditing', () => {
 			} );
 			editor.conversion.elementToElement( { model: 'inline', view: 'br' } );
 
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			const paragraph = doc.getRoot().getChild( 0 );
 
@@ -338,7 +347,7 @@ describe( 'MentionEditing', () => {
 		} );
 
 		it( 'should remove mention when splitting paragraph with a mention', () => {
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			const paragraph = doc.getRoot().getChild( 0 );
 
@@ -356,7 +365,7 @@ describe( 'MentionEditing', () => {
 			} );
 
 			editor.conversion.elementToElement( { model: 'blockQuote', view: 'blockquote' } );
-			editor.setData( '<blockquote><p>foo <span class="mention" data-mention="John">@John</span> bar</p></blockquote>' );
+			editor.setData( '<blockquote><p>foo <span class="mention" data-mention="@John">@John</span> bar</p></blockquote>' );
 
 			model.change( writer => {
 				const paragraph = doc.getRoot().getChild( 0 ).getChild( 0 );
@@ -382,7 +391,7 @@ describe( 'MentionEditing', () => {
 			model.schema.extend( '$text', { allowAttributes: [ 'bold' ] } );
 			editor.conversion.attributeToElement( { model: 'bold', view: 'strong' } );
 
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			const paragraph = doc.getRoot().getChild( 0 );
 
@@ -396,14 +405,14 @@ describe( 'MentionEditing', () => {
 			} );
 
 			expect( editor.getData() )
-				.to.equal( '<p><strong>foo <span class="mention" data-mention="John">@John</span></strong> bar</p>' );
+				.to.equal( '<p><strong>foo <span class="mention" data-mention="@John">@John</span></strong> bar</p>' );
 		} );
 
 		it( 'should set attribute on whole mention when formatting part of a mention (end formatted)', () => {
 			model.schema.extend( '$text', { allowAttributes: [ 'bold' ] } );
 			editor.conversion.attributeToElement( { model: 'bold', view: 'strong' } );
 
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			const paragraph = doc.getRoot().getChild( 0 );
 
@@ -417,14 +426,14 @@ describe( 'MentionEditing', () => {
 			} );
 
 			expect( editor.getData() )
-				.to.equal( '<p>foo <strong><span class="mention" data-mention="John">@John</span> ba</strong>r</p>' );
+				.to.equal( '<p>foo <strong><span class="mention" data-mention="@John">@John</span> ba</strong>r</p>' );
 		} );
 
 		it( 'should set attribute on whole mention when formatting part of a mention (middle of mention formatted)', () => {
 			model.schema.extend( '$text', { allowAttributes: [ 'bold' ] } );
 			editor.conversion.attributeToElement( { model: 'bold', view: 'strong' } );
 
-			editor.setData( '<p>foo <span class="mention" data-mention="John">@John</span> bar</p>' );
+			editor.setData( '<p>foo <span class="mention" data-mention="@John">@John</span> bar</p>' );
 
 			const paragraph = doc.getRoot().getChild( 0 );
 
@@ -438,7 +447,7 @@ describe( 'MentionEditing', () => {
 			} );
 
 			expect( editor.getData() )
-				.to.equal( '<p>foo <strong><span class="mention" data-mention="John">@John</span></strong> bar</p>' );
+				.to.equal( '<p>foo <strong><span class="mention" data-mention="@John">@John</span></strong> bar</p>' );
 		} );
 
 		it( 'should set attribute on whole mention when formatting part of two mentions', () => {
@@ -446,7 +455,7 @@ describe( 'MentionEditing', () => {
 			editor.conversion.attributeToElement( { model: 'bold', view: 'strong' } );
 
 			editor.setData(
-				'<p><span class="mention" data-mention="John">@John</span><span class="mention" data-mention="John">@John</span></p>'
+				'<p><span class="mention" data-mention="@John">@John</span><span class="mention" data-mention="@John">@John</span></p>'
 			);
 
 			const paragraph = doc.getRoot().getChild( 0 );
@@ -463,8 +472,8 @@ describe( 'MentionEditing', () => {
 			expect( editor.getData() ).to.equal(
 				'<p>' +
 					'<strong>' +
-						'<span class="mention" data-mention="John">@John</span>' +
-						'<span class="mention" data-mention="John">@John</span>' +
+						'<span class="mention" data-mention="@John">@John</span>' +
+						'<span class="mention" data-mention="@John">@John</span>' +
 					'</strong>' +
 				'</p>'
 			);
@@ -492,8 +501,8 @@ describe( 'MentionEditing', () => {
 
 			editor.setData(
 				'<p>' +
-					'<span class="mark-a">foo <span class="mention" data-mention="John">@John</span></span>' +
-					'<span class="mention" data-mention="John">@John</span> bar' +
+					'<span class="mark-a">foo <span class="mention" data-mention="@John">@John</span></span>' +
+					'<span class="mention" data-mention="@John">@John</span> bar' +
 				'</p>'
 			);
 
@@ -509,8 +518,8 @@ describe( 'MentionEditing', () => {
 				'<p>' +
 					'<span class="mark-a">foo </span>' +
 					'<span class="mark-b">' +
-						'<span class="mention" data-mention="John">@John</span>' +
-						'<span class="mention" data-mention="John">@John</span>' +
+						'<span class="mention" data-mention="@John">@John</span>' +
+						'<span class="mention" data-mention="@John">@John</span>' +
 					'</span> bar' +
 				'</p>'
 			);