瀏覽代碼

Expand the feature guide and fix examples.

Maciej Gołaszewski 6 年之前
父節點
當前提交
5920573825

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

@@ -0,0 +1,22 @@
+<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>
+</div>
+
+<style>
+	.custom-item {
+		display: block;
+		padding: 1em;
+	}
+
+	.custom-item.ck-on {
+		color: white;
+	}
+
+	.custom-item .custom-item-username {
+		color: #666;
+	}
+
+	.custom-item.ck-on .custom-item-username{
+		color: #ddd;
+	}
+</style>

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

@@ -0,0 +1,153 @@
+/**
+ * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md.
+ */
+
+/* globals ClassicEditor, console, window, document, setTimeout */
+
+import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
+
+import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
+
+// The link plugin using highest priority in conversion pipeline.
+const HIGHER_THEN_HIGHEST = priorities.highest + 50;
+
+ClassicEditor
+	.create( document.querySelector( '#snippet-mention-customization' ), {
+		cloudServices: CS_CONFIG,
+		extraPlugins: [ CustomMention ],
+		toolbar: {
+			items: [
+				'heading', '|', 'bold', 'italic', '|', 'undo', 'redo'
+			],
+			viewportTopOffset: window.getViewportTopOffsetConfig(),
+		},
+		mention: [
+			{
+				marker: '@',
+				feed: getFeedItems,
+				itemRenderer: customItemRenderer,
+				minimumCharacters: 1
+			}
+		]
+	} )
+	.then( editor => {
+		window.editor = editor;
+	} )
+	.catch( err => {
+		console.error( err.stack );
+	} );
+
+function CustomMention( editor ) {
+	// The upcast converter will convert <a class="mention"> elements to the model 'mention' attribute.
+	editor.conversion.for( 'upcast' ).elementToAttribute( {
+		view: {
+			name: 'a',
+			key: 'data-mention',
+			classes: 'mention',
+			attributes: {
+				href: true,
+				'data-user-id': true
+			}
+		},
+		model: {
+			key: 'mention',
+			value: viewItem => {
+				// Optionally: do not convert partial mentions.
+				if ( !isFullMention( viewItem ) ) {
+					return;
+				}
+
+				// The mention feature expects that mention attribute value in the model is a plain object:
+				const mentionValue = {
+					// The name attribute is required by mention editing.
+					name: viewItem.getAttribute( 'data-mention' ),
+					// Add any other properties as required.
+					link: viewItem.getAttribute( 'href' ),
+					id: viewItem.getAttribute( 'data-user-id' )
+				};
+
+				return mentionValue;
+			}
+		},
+		converterPriority: HIGHER_THEN_HIGHEST
+	} );
+
+	function isFullMention( viewElement ) {
+		const textNode = viewElement.getChild( 0 );
+		const dataMention = viewElement.getAttribute( 'data-mention' );
+
+		// Do not parse empty mentions.
+		if ( !textNode || !textNode.is( 'text' ) ) {
+			return false;
+		}
+
+		const mentionString = textNode.data;
+
+		// Assume that mention is set as marker + mention name.
+		const name = mentionString.slice( 1 );
+
+		// Do not upcast partial mentions - might come from copy-paste of partially selected mention.
+		return name == dataMention;
+	}
+
+	// Don't forget to define a downcast converter as well:
+	editor.conversion.for( 'downcast' ).attributeToElement( {
+		model: 'mention',
+		view: ( modelAttributeValue, viewWriter ) => {
+			if ( !modelAttributeValue ) {
+				// Do not convert empty attributes.
+				return;
+			}
+
+			return viewWriter.createAttributeElement( 'a', {
+				class: 'mention',
+				'data-mention': modelAttributeValue.name,
+				'data-user-id': modelAttributeValue.id,
+				'href': modelAttributeValue.link
+			} );
+		},
+		converterPriority: HIGHER_THEN_HIGHEST
+	} );
+}
+
+const items = [
+	{ id: '1', name: 'Barney Stinson', username: 'swarley', link: 'https://www.imdb.com/title/tt0460649/characters/nm0000439' },
+	{ id: '2', name: 'Lily Aldrin', username: 'lilypad', link: 'https://www.imdb.com/title/tt0460649/characters/nm0004989' },
+	{ id: '3', name: 'Marshall Eriksen', username: 'marshmallow', link: 'https://www.imdb.com/title/tt0460649/characters/nm0781981' },
+	{ id: '4', name: 'Robin Scherbatsky', username: 'rsparkles', link: 'https://www.imdb.com/title/tt0460649/characters/nm1130627' },
+	{ id: '5', name: 'Ted Mosby', username: 'tdog', link: 'https://www.imdb.com/title/tt0460649/characters/nm1102140' }
+];
+
+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 ) );
+		}, 100 );
+	} );
+
+	// Filtering function - it uses `name` and `username` properties of an item to find a match.
+	function isItemMatching( item ) {
+		// Make search case-insensitive.
+		const searchString = feedText.toLowerCase();
+
+		// Include an item in the search results if name or username includes the current user input.
+		return textIncludesSearchSting( item.name, searchString ) || textIncludesSearchSting( item.username, searchString );
+	}
+
+	function textIncludesSearchSting( text, searchString ) {
+		return text.toLowerCase().includes( searchString );
+	}
+}
+
+function customItemRenderer( item ) {
+	const span = document.createElement( 'span' );
+
+	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>`;
+
+	return span;
+}

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

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

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

@@ -8,7 +8,7 @@
 import { CS_CONFIG } from '@ckeditor/ckeditor5-cloud-services/tests/_utils/cloud-services-config';
 
 ClassicEditor
-	.create( document.querySelector( '#snippet-mention-embed' ), {
+	.create( document.querySelector( '#snippet-mention' ), {
 		cloudServices: CS_CONFIG,
 		toolbar: {
 			items: [

+ 86 - 74
packages/ckeditor5-mention/docs/features/mention.md

@@ -46,6 +46,10 @@ ClassicEditor
 
 The minimal configuration of a mention requires defining a feed and optionally a marker (if not using the default `@` character).
 
+Below is an example of fully customize mention feature.
+
+{@snippet features/mention-customization}
+
 ### Providing the feed
 
 The {@link module:mention/mention~MentionFeed#feed} can be provided as:
@@ -63,18 +67,18 @@ The mention feature does not limit items displayed in the mention suggestion lis
 
 ```js
 const items = [
-		{ id: '1', name: 'Barney Stinson', username: 'swarley' },
-		{ id: '2', name: 'Lily Aldrin', username: 'lilypad' },
-		{ id: '3', name: 'Marshall Eriksen', username: 'marshmallow' },
-		{ id: '4', name: 'Robin Scherbatsky', username: 'rsparkles' },
-		{ id: '5', name: 'Ted Mosby', username: 'tdog' }
-	];
+	{ id: '1', name: 'Barney Stinson', username: 'swarley', link: 'https://www.imdb.com/title/tt0460649/characters/nm0000439' },
+	{ id: '2', name: 'Lily Aldrin', username: 'lilypad', link: 'https://www.imdb.com/title/tt0460649/characters/nm0004989' },
+	{ id: '3', name: 'Marshall Eriksen', username: 'marshmallow', link: 'https://www.imdb.com/title/tt0460649/characters/nm0781981' },
+	{ id: '4', name: 'Robin Scherbatsky', username: 'rsparkles', link: 'https://www.imdb.com/title/tt0460649/characters/nm1130627' },
+	{ id: '5', name: 'Ted Mosby', username: 'tdog', link: 'https://www.imdb.com/title/tt0460649/characters/nm1102140' }
+];
 
 function getFeedItems( feedText ) {
-	// As an example of asynchronous action return a promise that resolves after a 100ms timeout.  
+	// As an example of asynchronous action return a promise that resolves after a 100ms timeout.
 	return new Promise( resolve => {
 		setTimeout( () => {
-			resolve( items.filter( isItemMathing ) );
+			resolve( items.filter( isItemMatching ) );
 		}, 100 );
 	} );
 
@@ -85,13 +89,12 @@ function getFeedItems( feedText ) {
 
 		// Include an item in the search results if name or username includes the current user input.
 		return textIncludesSearchSting( item.name, searchString ) || textIncludesSearchSting( item.username, searchString );
- 	}
+	}
 
 	function textIncludesSearchSting( text, searchString ) {
 		return text.toLowerCase().includes( searchString );
 	}
 }
-
 ```
 
 ### Customizing the auto-complete list
@@ -115,7 +118,7 @@ function customItemRenderer( item ) {
 
 ### 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/upcastdisatcher upcast} and {@link module:engine/conversion/downcastdisatcher downcast} converters.
+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/upcastdisatcher~UpcastDispatcher upcast} and {@link module:engine/conversion/downcastdisatcher~DowncastDispatcher downcast} converters.
 
 ```js
 import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
@@ -123,79 +126,88 @@ import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
 // The link plugin using highest priority in conversion pipeline.
 const HIGHER_THEN_HIGHEST = priorities.highest + 50;
 
-// The upcast converter will convert <a class="mention"> elements to the model 'mention' attribute. 
-editor.conversion.for( 'upcast' ).elementToAttribute( {
-	view: {
-		name: 'a',
-		key: 'data-mention',
-		classes: 'mention',
-		attributes: {
-			href: true
+ClassicEditor
+	.create( document.querySelector( '#editor' ), {
+		plugins: [ Mention, CustomMention, ... ],    // Add custom mention plugin function.
+		mention: {
+			// configuration...
 		}
-	},
-	model: {
-		key: 'mention',
-		value: viewItem => {
-			// Optionally: do not convert partial mentions.
-			if( !isFullMention(viewItem)){
-				return;
+	} )
+	.then( ... )
+	.catch( ... );
+
+function CustomMention( editor ) {
+	// The upcast converter will convert <a class="mention"> elements to the model 'mention' attribute.
+	editor.conversion.for( 'upcast' ).elementToAttribute( {
+		view: {
+			name: 'a',
+			key: 'data-mention',
+			classes: 'mention',
+			attributes: {
+				href: true,
+				'data-user-id': true
 			}
-			  
-			// The mention feature expects that mention attribute value in the model is a plain object:
-			const mentionValue = {
-				// The name attribute is required by mention editing.
-				name: viewItem.getAttribute( 'data-mention' ),
-				// Add any other properties as required.
-				link: viewItem.getAttribute( 'href' ),
-				id: viewItem.getAttribute( 'data-user-id' )
-			};
-
-			return mentionValue;
-		}
-	},
-	converterPriority: HIGHER_THEN_HIGHEST
-} );
+		},
+		model: {
+			key: 'mention',
+			value: viewItem => {
+				// Optionally: do not convert partial mentions.
+				if ( !isFullMention( viewItem ) ) {
+					return;
+				}
+
+				// The mention feature expects that mention attribute value in the model is a plain object:
+				const mentionValue = {
+					// The name attribute is required by mention editing.
+					name: viewItem.getAttribute( 'data-mention' ),
+					// Add any other properties as required.
+					link: viewItem.getAttribute( 'href' ),
+					id: viewItem.getAttribute( 'data-user-id' )
+				};
+
+				return mentionValue;
+			}
+		},
+		converterPriority: HIGHER_THEN_HIGHEST
+	} );
 
-function isFullMention( viewElement ) {
-	const textNode = viewElement.getChild( 0 );
+	function isFullMention( viewElement ) {
+		const textNode = viewElement.getChild( 0 );
+		const dataMention = viewElement.getAttribute( 'data-mention' );
 
-	// Do not parse empty mentions.
-	if ( !textNode || !textNode.is( 'text' ) ) {
-		return;
-	}
+		// Do not parse empty mentions.
+		if ( !textNode || !textNode.is( 'text' ) ) {
+			return false;
+		}
 
-	const mentionString = textNode.data;
+		const mentionString = textNode.data;
 
-	// Assume that mention is set as marker + mention name.
-	const marker = mentionString.slice( 0, 1 );
-	const name = mentionString.slice( 1 );
+		// Assume that mention is set as marker + mention name.
+		const name = mentionString.slice( 1 );
 
-	// Do not upcast partial mentions - might come from copy-paste of partially selected mention.
-	if ( name != dataMention ) {
-		return;
+		// Do not upcast partial mentions - might come from copy-paste of partially selected mention.
+		return name == dataMention;
 	}
 
-}
-
-// Don't forget to define a downcast converter as well:
-
-editor.conversion.for( 'downcast' ).attributeToElement( {
-	model: 'mention',
-	view: ( modelAttributeValue, viewWriter ) => {
-		if ( !modelAttributeValue ) {
-			// Do not convert empty attributes.
-			return;
-		}
+	// Don't forget to define a downcast converter as well:
+	editor.conversion.for( 'downcast' ).attributeToElement( {
+		model: 'mention',
+		view: ( modelAttributeValue, viewWriter ) => {
+			if ( !modelAttributeValue ) {
+				// Do not convert empty attributes.
+				return;
+			}
 
-		return viewWriter.createAttributeElement( 'a', {
-			class: 'mention',
-			'data-mention': modelAttributeValue.name,
-			'data-user-id': modelAttributeValue.id,
-			'href': modelAttributeValue.link
-		} );
-	},
-	converterPriority: HIGHER_THEN_HIGHEST
-} );
+			return viewWriter.createAttributeElement( 'a', {
+				class: 'mention',
+				'data-mention': modelAttributeValue.name,
+				'data-user-id': modelAttributeValue.id,
+				'href': modelAttributeValue.link
+			} );
+		},
+		converterPriority: HIGHER_THEN_HIGHEST
+	} );
+}
 ```
 
 ## Common API