|
|
@@ -10,6 +10,8 @@ The {@link module:mention/mention~Mention} feature brings support for smart comp
|
|
|
|
|
|
## Demo
|
|
|
|
|
|
+You can type `'@'` character to invoke mention auto-complete UI. The below demo is configured as static list of names.
|
|
|
+
|
|
|
{@snippet features/mention}
|
|
|
|
|
|
## Installation
|
|
|
@@ -42,6 +44,160 @@ ClassicEditor
|
|
|
|
|
|
## Configuration
|
|
|
|
|
|
+The minimal configuration of a mention requires defining a feed and optionally a marker (if not using the default `@` character).
|
|
|
+
|
|
|
+### Providing the feed
|
|
|
+
|
|
|
+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}.
|
|
|
+
|
|
|
+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.
|
|
|
+
|
|
|
+<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>
|
|
|
+
|
|
|
+```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' }
|
|
|
+ ];
|
|
|
+
|
|
|
+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 );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+```
|
|
|
+
|
|
|
+### 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.
|
|
|
+
|
|
|
+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.
|
|
|
+
|
|
|
+```js
|
|
|
+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;
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 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.
|
|
|
+
|
|
|
+```js
|
|
|
+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
|
|
|
+} );
|
|
|
+```
|
|
|
+
|
|
|
## Common API
|
|
|
|
|
|
The {@link module:mention/mention~Mention} plugin registers:
|