{@snippet features/build-mention-source}
The {@link module:mention/mention~Mention} feature brings support for smart completion based on user input. When user types a pre-configured marker, such as @ or #, they get an autocomplete suggestions in a balloon panel displayed next to the caret. The selected suggestion is then inserted into the content.
You can type '@' character to invoke mention auto-complete UI. The below demo is configured as static list of names.
{@snippet features/mention}
This feature is enabled by default in all builds. The installation instructions are for developers interested in building their own, custom editor.
To add this feature to your editor, install the @ckeditor/ckeditor5-mention package:
npm install --save @ckeditor/ckeditor5-mention
Then add Mention to your plugin list and {@link module:mention/mention~MentionConfig configure} the feature (if needed):
import Mention from '@ckeditor/ckeditor5-mention/src/mention';
ClassicEditor
.create( document.querySelector( '#editor' ), {
plugins: [ Mention, ... ],
mention: {
// configuration...
}
} )
.then( ... )
.catch( ... );
The minimal configuration of a mention requires defining a feed and optionally a marker (if not using the default @ character).
The {@link module:mention/mention~MentionFeed#feed} can be provided as:
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}.
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 mention feature does not limit items displayed in the mention suggestion list when using the callback. You should limit the output by yourself.
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' }
];
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( isItemMathing ) );
}, 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 );
}
}
The list 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.
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;
}
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.
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
}
},
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 );
// Do not parse empty mentions.
if ( !textNode || !textNode.is( 'text' ) ) {
return;
}
const mentionString = textNode.data;
// Assume that mention is set as marker + mention name.
const marker = mentionString.slice( 0, 1 );
const name = mentionString.slice( 1 );
// Do not upcast partial mentions - might come from copy-paste of partially selected mention.
if ( name != dataMention ) {
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
} );
The {@link module:mention/mention~Mention} plugin registers:
the 'mention' command implemented by {@link module:mention/mentioncommand~MentionCommand}.
You can insert a mention element by executing the following code:
editor.execute( 'mention', { marker: '@', mention: 'John' } );
The source code of the feature is available on GitHub in https://github.com/ckeditor/ckeditor5-mention.