浏览代码

Extend the mention feature guide. Add `feeds` namespace to the MentionConfig.

Maciej Gołaszewski 6 年之前
父节点
当前提交
c3799d6d50

+ 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>
+	<p>Hello <span class="mention" data-mention="Ted">@Ted</span>.</p>
 </div>

+ 85 - 10
packages/ckeditor5-mention/docs/features/mention.md

@@ -16,11 +16,29 @@ You can type `'@'` character to invoke mention auto-complete UI. The below demo
 
 ## Configuration
 
-The minimal configuration of a mention requires defining a feed and optionally a marker (if not using the default `@` character).
+The minimal configuration of a mention requires defining a {@link module:mention/mention~MentionFeed#feed} and a {@link module:mention/mention~MentionFeed#marker} (if not using the default `@` character). You can define also `minimumCharacters` after which the auto-complete panel will be shown. 
 
-Below is an example of fully customize mention feature.
+```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Mention, ... ],
+		mention: {
+			feeds: [
+				{
+					marker: '@',
+					feed: [ 'Barney', 'Lily', 'Marshall', 'Robin', 'Ted' ],
+					minimumCharacters: 1
+				}
+			}
+		}
+	} )
+	.then( ... )
+	.catch( ... );
+```
 
-{@snippet features/mention-customization}
+Additionally you can configure:
+- How the item is rendered in the auto-complete panel.
+- How the item is converted during the conversion.
 
 ### Providing the feed
 
@@ -29,12 +47,12 @@ The {@link module:mention/mention~MentionFeed#feed} can be provided as:
 - static array - good for scenarios with relatively small set of auto-complete items.
 - a callback - which provides more control over the returned list of items.
 
-If using a callback you can return a `Promise` that resolves with list of {@link module:mention/mention~MentionFeedItem mention feed items}. Those can be simple stings used as mention text or plain objects with at least one `name` property. The other parameters can be used either when {@link #customizing-the-auto-complete-list customizing the auto-complete list} {@link #customizing-the-output customizing the output}.
+If using a callback you can return a `Promise` that resolves with list of {@link module:mention/mention~MentionFeedItem mention feed items}. Those can be simple stings used as mention text or plain objects with at least one `name` property. The other parameters can be used either when {@link features/mention#customizing-the-auto-complete-list customizing the auto-complete list} {@link features/mention#customizing-the-output customizing the output}.
 
-The callback receives a matched text which should be used to filter item suggestions. The callback should return a Promise and resolve it with an array of items that match to the feed text.
+The callback receives a matched text which should be used to filter item suggestions. It should return a `Promise` and resolve it with an array of items that match to the feed text.
 
 <info-box>
-The mention feature does not limit items displayed in the mention suggestion list when using the callback. You should limit the output by yourself. 
+Consider adding the `minimumCharacters` option to the feed config so the editor will call the feed callback after a minimum characters typed instead of action on marker alone. 
 </info-box>
 
 ```js
@@ -50,7 +68,13 @@ function getFeedItems( feedText ) {
 	// As an example of asynchronous action return a promise that resolves after a 100ms timeout.
 	return new Promise( resolve => {
 		setTimeout( () => {
-			resolve( items.filter( isItemMatching ) );
+			const itemsToDisplay = items
+				// Filter out the full list of all items to only those matching feedText.
+				.filter( isItemMatching )
+				// Return at most 10 items - notably for generic queries when the list may contain hundreds of elements.
+				.slice( 0, 10 );
+
+			resolve( itemsToDisplay );
 		}, 100 );
 	} );
 
@@ -69,29 +93,68 @@ function getFeedItems( feedText ) {
 }
 ```
 
+The full working demo with all customization possible is {@link features/mention#fully-customized-mention-feed  at the end of this section}.
+
+<info-box>
+The mention feature does not limit items displayed in the mention suggestion list when using the callback. You should limit the output by yourself. 
+</info-box>
+
 ### Customizing the auto-complete list
 
-The list displayed in auto-complete list can be customized by defining the {@link module:mention/mention~MentionFeed#itemRenderer} callback.
+The items displayed in auto-complete list can be customized by defining the {@link module:mention/mention~MentionFeed#itemRenderer} callback.
 
-This callback takes a plain object feed item (at least with `name` parameter - even when feed items are defined as strings). It should return a new DOM element.
+This callback takes a plain object feed item (at least with `name` parameter - even when feed items are defined as strings). The item renderer function must return a new DOM element.
 
 ```js
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Mention, ... ],
+		mention: {
+			feeds: [
+				{ 
+					feed: [ ... ],
+					// Define the custom item renderer:
+					itemRenderer: customItemRenderer
+				}
+			]
+		}
+	} )
+	.then( ... )
+	.catch( ... );
+
 function customItemRenderer( item ) {
 	const span = document.createElement( 'span' );
 
 	span.classList.add( 'custom-item' );
 	span.id = `mention-list-item-id-${ item.id }`;
 
+	// Add child nodes to the main span or just set innerHTML.
 	span.innerHTML = `${ item.name } <span class="custom-item-username">@${ item.username }</span>`;
 
 	return span;
 }
-``` 
+```
+
+The full working demo with all customization possible is {@link features/mention#fully-customized-mention-feed  at the end of this section}.
 
 ### Customizing the output
 
 In order to have full control over the markup generated by the editor you can overwrite the conversion process. To do that you must specify both {@link module:engine/conversion/upcastdispatcher~UpcastDispatcher upcast} and {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher downcast} converters.
 
+Below is an example of a plugin that overrides the default output:
+
+```html
+<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>
+```
+
+The below converters must have priority higher then link attribute converter. The mention item in the model must be stored as a plain object with `name` attribute.
+
 ```js
 import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
 
@@ -182,6 +245,18 @@ function CustomMention( editor ) {
 }
 ```
 
+The full working demo with all customization possible is {@link features/mention#fully-customized-mention-feed  at the end of this section}.
+
+# Fully customized mention feed
+
+Below is an example of a customized mention feature that:
+
+- Returns a feed of items with extended properties.
+- Renders custom DOM view in auto-complete suggestion in panel view.
+- Converts mention to an `<a>` element instead of `<span>`.
+
+{@snippet features/mention-customization}
+
 ## Installation
 
 <info-box info>

+ 29 - 3
packages/ckeditor5-mention/src/mention.js

@@ -49,13 +49,13 @@ export default class Mention extends Plugin {
  *
  * See {@link module:mention/mention~MentionConfig} to learn more.
  *
- *		{
+ *		const mentionFeed = {
  *			marker: '@',
- *			feed: [ 'Alice', 'Bob', ... ],
+ *			feed: [ 'Alice', 'Bob', ... ]
  *		}
  *
  * @typedef {Object} module:mention/mention~MentionFeed
- * @property {String} [marker='@'] The character which triggers auto-completion for mention.
+ * @property {String} [marker=''] The character which triggers auto-completion for mention.
  * @property {Array.<module:mention/mention~MentionFeedItem>|Function} feed The auto complete feed items. Provide an array for
  * static configuration or a function that returns a promise for asynchronous feeds.
  * @property {Number} [minimumCharacters=0] Specifies after how many characters show the autocomplete panel.
@@ -69,11 +69,37 @@ export default class Mention extends Plugin {
  *
  * *Note* When defining feed item as a plain object you must provide the at least the `name` property.
  *
+ * Used in {@link module:mention/mention~MentionFeed#feed} or* Used in {@link module:mention/mention~MentionFeed.feed}
+ *
  * @typedef {Object|String} module:mention/mention~MentionFeedItem
  * @property {String} name Name of the mention.
  */
 
 /**
+ * The list fo mention feeds supported by the editor.
+ *
+ *		ClassicEditor
+ *			.create( editorElement, {
+ *				plugins: [ Mention, ... ],
+ *				mention: {
+ *					feeds: [
+ *						{
+ *							marker: '@',
+ *							feed: [ 'Barney', 'Lily', 'Marshall', 'Robin', 'Ted' ]
+ *						},
+ *						...
+ * 					]
+ *				}
+ *			} )
+ *			.then( ... )
+ *			.catch( ... );
+ *
+ * You can provide as many mention feeds but they must have different `marker` defined.
+ *
+ * @member {Array.<module:mention/mention~MentionFeed>} module:mention/mention~MentionConfig#feeds
+ */
+
+/**
  * The configuration of the mention features.
  *
  * Read more about {@glink features/mention#configuration configuring the mention feature}.

+ 3 - 3
packages/ckeditor5-mention/src/mentionui.js

@@ -65,7 +65,7 @@ export default class MentionUI extends Plugin {
 		 */
 		this._mentionsConfigurations = new Map();
 
-		editor.config.define( 'mention', [] );
+		editor.config.define( 'mention', { feeds: [] } );
 	}
 
 	/**
@@ -104,9 +104,9 @@ export default class MentionUI extends Plugin {
 			callback: () => this._hidePanel()
 		} );
 
-		const config = this.editor.config.get( 'mention' );
+		const feeds = this.editor.config.get( 'mention.feeds' );
 
-		for ( const mentionDescription of config ) {
+		for ( const mentionDescription of feeds ) {
 			const feed = mentionDescription.feed;
 
 			const marker = mentionDescription.marker || '@';

+ 13 - 11
packages/ckeditor5-mention/tests/manual/mention-custom-renderer.js

@@ -27,21 +27,23 @@ ClassicEditor
 	.create( global.document.querySelector( '#editor' ), {
 		plugins: [ Enter, Typing, Paragraph, Heading, Link, Bold, Italic, Underline, Undo, Clipboard, Widget, ShiftEnter, Table, Mention ],
 		toolbar: [ 'heading', '|', 'bold', 'italic', 'underline', 'link', '|', 'insertTable', '|', 'undo', 'redo' ],
-		mention: [
-			{
-				feed: getFeed,
-				itemRenderer: item => {
-					const span = global.document.createElementNS( 'http://www.w3.org/1999/xhtml', 'span' );
+		mention: {
+			feeds: [
+				{
+					feed: getFeed,
+					itemRenderer: item => {
+						const span = global.document.createElementNS( 'http://www.w3.org/1999/xhtml', 'span' );
 
-					span.classList.add( 'custom-item' );
-					span.id = `mention-list-item-id-${ item.id }`;
+						span.classList.add( 'custom-item' );
+						span.id = `mention-list-item-id-${ item.id }`;
 
-					span.innerHTML = `${ item.name } <span class="custom-item-username">@${ item.username }</span>`;
+						span.innerHTML = `${ item.name } <span class="custom-item-username">@${ item.username }</span>`;
 
-					return span;
+						return span;
+					}
 				}
-			}
-		]
+			]
+		}
 	} )
 	.then( editor => {
 		window.editor = editor;

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

@@ -79,11 +79,11 @@ ClassicEditor
 		plugins: [ Enter, Typing, Paragraph, Link, Heading, Bold, Italic, Underline, Undo, Clipboard, Widget, ShiftEnter, Table,
 			MentionEditing, CustomMentionAttributeView, MentionUI ],
 		toolbar: [ 'heading', '|', 'bold', 'italic', 'underline', 'link', '|', 'insertTable', '|', 'undo', 'redo' ],
-		mention: [
-			{
-				feed: getFeed
-			}
-		]
+		mention: {
+			feeds: [
+				{ feed: getFeed }
+			]
+		}
 	} )
 	.then( editor => {
 		window.editor = editor;

+ 5 - 5
packages/ckeditor5-mention/tests/manual/mention.js

@@ -27,11 +27,11 @@ ClassicEditor
 	.create( global.document.querySelector( '#editor' ), {
 		plugins: [ Enter, Typing, Paragraph, Heading, Link, Bold, Italic, Underline, Undo, Clipboard, Widget, ShiftEnter, Table, Mention ],
 		toolbar: [ 'heading', '|', 'bold', 'italic', 'underline', 'link', '|', 'insertTable', '|', 'undo', 'redo' ],
-		mention: [
-			{
-				feed: [ 'Barney', 'Lily', 'Marshall', 'Robin', 'Ted' ]
-			}
-		]
+		mention: {
+			feeds: [
+				{ feed: [ 'Barney', 'Lily', 'Marshall', 'Robin', 'Ted' ] },
+			]
+		}
 	} )
 	.then( editor => {
 		window.editor = editor;

+ 36 - 30
packages/ckeditor5-mention/tests/mentionui.js

@@ -23,9 +23,11 @@ import MentionsView from '../src/ui/mentionsview';
 describe( 'MentionUI', () => {
 	let editor, model, doc, editingView, mentionUI, editorElement, mentionsView, panelView, listView;
 
-	const staticConfig = [
-		{ feed: [ 'Barney', 'Lily', 'Marshall', 'Robin', 'Ted' ] }
-	];
+	const staticConfig = {
+		feeds: [
+			{ feed: [ 'Barney', 'Lily', 'Marshall', 'Robin', 'Ted' ] }
+		]
+	};
 
 	testUtils.createSinonSandbox();
 
@@ -177,7 +179,7 @@ describe( 'MentionUI', () => {
 
 	describe( 'typing integration', () => {
 		it( 'should show panel for matched marker after typing minimum characters', () => {
-			return createClassicTestEditor( [ Object.assign( { minimumCharacters: 2 }, staticConfig[ 0 ] ) ] )
+			return createClassicTestEditor( { feeds: [ Object.assign( { minimumCharacters: 2 }, staticConfig.feeds[ 0 ] ) ] } )
 				.then( () => {
 					setData( model, '<paragraph>foo []</paragraph>' );
 
@@ -389,18 +391,20 @@ describe( 'MentionUI', () => {
 			beforeEach( () => {
 				const issuesNumbers = [ '100', '101', '102', '103' ];
 
-				return createClassicTestEditor( [
-					{
-						marker: '#',
-						feed: feedText => {
-							return new Promise( resolve => {
-								setTimeout( () => {
-									resolve( issuesNumbers.filter( number => number.includes( feedText ) ) );
-								}, 20 );
-							} );
+				return createClassicTestEditor( {
+					feeds: [
+						{
+							marker: '#',
+							feed: feedText => {
+								return new Promise( resolve => {
+									setTimeout( () => {
+										resolve( issuesNumbers.filter( number => number.includes( feedText ) ) );
+									}, 20 );
+								} );
+							}
 						}
-					}
-				] );
+					]
+				} );
 			} );
 
 			it( 'should show panel for matched marker', () => {
@@ -545,7 +549,7 @@ describe( 'MentionUI', () => {
 		} );
 
 		describe( 'default list item', () => {
-			const feedItems = staticConfig[ 0 ].feed.map( name => ( { name } ) );
+			const feedItems = staticConfig.feeds[ 0 ].feed.map( name => ( { name } ) );
 
 			beforeEach( () => {
 				return createClassicTestEditor( staticConfig );
@@ -640,21 +644,23 @@ describe( 'MentionUI', () => {
 			];
 
 			beforeEach( () => {
-				return createClassicTestEditor( [
-					{
-						marker: '@',
-						feed: feedText => {
-							return Promise.resolve( issues.filter( issue => issue.id.includes( feedText ) ) );
-						},
-						itemRenderer: item => {
-							const span = global.document.createElementNS( 'http://www.w3.org/1999/xhtml', 'span' );
-
-							span.innerHTML = `<span id="issue-${ item.id }">@${ item.title }</span>`;
-
-							return span;
+				return createClassicTestEditor( {
+					feeds: [
+						{
+							marker: '@',
+							feed: feedText => {
+								return Promise.resolve( issues.filter( issue => issue.id.includes( feedText ) ) );
+							},
+							itemRenderer: item => {
+								const span = global.document.createElementNS( 'http://www.w3.org/1999/xhtml', 'span' );
+
+								span.innerHTML = `<span id="issue-${ item.id }">@${ item.title }</span>`;
+
+								return span;
+							}
 						}
-					}
-				] );
+					]
+				} );
 			} );
 
 			it( 'should show panel for matched marker', () => {