@ckeditor/ckeditor5-mention
Advanced tools
Comparing version 16.0.0 to 17.0.0
Changelog | ||
========= | ||
## [17.0.0](https://github.com/ckeditor/ckeditor5-mention/compare/v16.0.0...v17.0.0) (2020-02-19) | ||
### Features | ||
* Implemented debounced mechanism for requesting a mention feed. Closes [ckeditor/ckeditor5#4619](https://github.com/ckeditor/ckeditor5/issues/4619). ([f50db9c](https://github.com/ckeditor/ckeditor5-mention/commit/f50db9c)) | ||
## [16.0.0](https://github.com/ckeditor/ckeditor5-mention/compare/v15.0.0...v16.0.0) (2019-12-04) | ||
@@ -5,0 +12,0 @@ |
@@ -5,3 +5,3 @@ Software License Agreement | ||
**CKEditor 5 Mention Feature** – https://github.com/ckeditor/ckeditor5-mention <br> | ||
Copyright (c) 2003-2019, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved. | ||
Copyright (c) 2003-2020, [CKSource](http://cksource.com) Frederico Knabben. All rights reserved. | ||
@@ -8,0 +8,0 @@ Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). |
{ | ||
"name": "@ckeditor/ckeditor5-mention", | ||
"version": "16.0.0", | ||
"version": "17.0.0", | ||
"description": "Mention feature for CKEditor 5.", | ||
@@ -13,19 +13,20 @@ "keywords": [ | ||
"dependencies": { | ||
"@ckeditor/ckeditor5-core": "^16.0.0", | ||
"@ckeditor/ckeditor5-ui": "^16.0.0", | ||
"@ckeditor/ckeditor5-typing": "^16.0.0", | ||
"@ckeditor/ckeditor5-utils": "^16.0.0" | ||
"@ckeditor/ckeditor5-core": "^17.0.0", | ||
"@ckeditor/ckeditor5-ui": "^17.0.0", | ||
"@ckeditor/ckeditor5-typing": "^17.0.0", | ||
"@ckeditor/ckeditor5-utils": "^17.0.0", | ||
"lodash-es": "^4.17.10" | ||
}, | ||
"devDependencies": { | ||
"@ckeditor/ckeditor5-basic-styles": "^16.0.0", | ||
"@ckeditor/ckeditor5-block-quote": "^16.0.0", | ||
"@ckeditor/ckeditor5-clipboard": "^16.0.0", | ||
"@ckeditor/ckeditor5-editor-classic": "^16.0.0", | ||
"@ckeditor/ckeditor5-engine": "^16.0.0", | ||
"@ckeditor/ckeditor5-font": "^16.0.0", | ||
"@ckeditor/ckeditor5-link": "^16.0.0", | ||
"@ckeditor/ckeditor5-paragraph": "^16.0.0", | ||
"@ckeditor/ckeditor5-table": "^16.0.0", | ||
"@ckeditor/ckeditor5-undo": "^16.0.0", | ||
"@ckeditor/ckeditor5-widget": "^16.0.0", | ||
"@ckeditor/ckeditor5-basic-styles": "^17.0.0", | ||
"@ckeditor/ckeditor5-block-quote": "^17.0.0", | ||
"@ckeditor/ckeditor5-clipboard": "^17.0.0", | ||
"@ckeditor/ckeditor5-editor-classic": "^17.0.0", | ||
"@ckeditor/ckeditor5-engine": "^17.0.0", | ||
"@ckeditor/ckeditor5-font": "^17.0.0", | ||
"@ckeditor/ckeditor5-link": "^17.0.0", | ||
"@ckeditor/ckeditor5-paragraph": "^17.0.0", | ||
"@ckeditor/ckeditor5-table": "^17.0.0", | ||
"@ckeditor/ckeditor5-undo": "^17.0.0", | ||
"@ckeditor/ckeditor5-widget": "^17.0.0", | ||
"eslint": "^5.5.0", | ||
@@ -35,2 +36,3 @@ "eslint-config-ckeditor5": "^2.0.0", | ||
"lint-staged": "^7.0.0", | ||
"lodash": "^4.17.11", | ||
"stylelint": "^11.1.1", | ||
@@ -37,0 +39,0 @@ "stylelint-config-ckeditor5": "^1.0.0" |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
@@ -4,0 +4,0 @@ */ |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
@@ -4,0 +4,0 @@ */ |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
@@ -84,3 +84,3 @@ */ | ||
* @param {String|Object} [data] Mention data to be extended. | ||
* @return {module:mention/mention~MentionAttribute} | ||
* @returns {module:mention/mention~MentionAttribute} | ||
*/ | ||
@@ -87,0 +87,0 @@ export function _toMentionAttribute( viewElementOrMention, data ) { |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
@@ -10,2 +10,4 @@ */ | ||
/* global console */ | ||
import Plugin from '@ckeditor/ckeditor5-core/src/plugin'; | ||
@@ -18,4 +20,5 @@ import ButtonView from '@ckeditor/ckeditor5-ui/src/button/buttonview'; | ||
import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect'; | ||
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; | ||
import CKEditorError, { attachLinkToDocumentation } from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; | ||
import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon'; | ||
import { debounce } from 'lodash-es'; | ||
@@ -30,2 +33,12 @@ import TextWatcher from '@ckeditor/ckeditor5-typing/src/textwatcher'; | ||
// The key codes that mention UI handles when it is open. | ||
const handledKeyCodes = [ | ||
keyCodes.arrowup, | ||
keyCodes.arrowdown, | ||
keyCodes.enter, | ||
keyCodes.tab, | ||
keyCodes.space, | ||
keyCodes.esc | ||
]; | ||
/** | ||
@@ -47,2 +60,9 @@ * The mention UI feature. | ||
*/ | ||
static get requires() { | ||
return [ ContextualBalloon ]; | ||
} | ||
/** | ||
* @inheritDoc | ||
*/ | ||
constructor( editor ) { | ||
@@ -67,2 +87,12 @@ super( editor ); | ||
/** | ||
* Debounced feed requester. It uses `lodash#debounce` method to delay function call. | ||
* | ||
* @private | ||
* @param {String} marker | ||
* @param {String} feedText | ||
* @method | ||
*/ | ||
this._requestFeedDebounced = debounce( this._requestFeed, 100 ); | ||
editor.config.define( 'mention', { feeds: [] } ); | ||
@@ -124,3 +154,3 @@ } | ||
if ( !marker || marker.length != 1 ) { | ||
if ( !isValidMentionMarker( marker ) ) { | ||
/** | ||
@@ -152,2 +182,5 @@ * The marker must be a single character. | ||
} | ||
this.on( 'requestFeed:response', ( evt, data ) => this._handleFeedResponse( data ) ); | ||
this.on( 'requestFeed:error', () => this._hideUIAndRemoveMarker() ); | ||
} | ||
@@ -166,9 +199,2 @@ | ||
/** | ||
* @inheritDoc | ||
*/ | ||
static get requires() { | ||
return [ ContextualBalloon ]; | ||
} | ||
/** | ||
* Returns true when {@link #_mentionsView} is in the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon} and it is | ||
@@ -263,13 +289,76 @@ * currently visible. | ||
/** | ||
* Returns a promise that resolves with autocomplete items for a given text. | ||
* Requests a feed from a configured callbacks. | ||
* | ||
* @private | ||
* @fires module:mention/mentionui~MentionUI#event:requestFeed:response | ||
* @fires module:mention/mentionui~MentionUI#event:requestFeed:discarded | ||
* @fires module:mention/mentionui~MentionUI#event:requestFeed:error | ||
* @param {String} marker | ||
* @param {String} feedText | ||
* @return {Promise<module:mention/mention~MentionFeedItem>} | ||
* @private | ||
*/ | ||
_getFeed( marker, feedText ) { | ||
_requestFeed( marker, feedText ) { | ||
// Store the last requested feed - it is used to discard any out-of order requests. | ||
this._lastRequested = feedText; | ||
const { feedCallback } = this._mentionsConfigurations.get( marker ); | ||
const feedResponse = feedCallback( feedText ); | ||
return Promise.resolve().then( () => feedCallback( feedText ) ); | ||
const isAsynchronous = feedResponse instanceof Promise; | ||
// For synchronous feeds (e.g. callbacks, arrays) fire the response event immediately. | ||
if ( !isAsynchronous ) { | ||
/** | ||
* Fired whenever requested feed has a response. | ||
* | ||
* @event requestFeed:response | ||
* @param {Object} data Event data. | ||
* @param {Array.<module:mention/mention~MentionFeedItem>} data.feed Autocomplete items. | ||
* @param {String} data.marker The character which triggers autocompletion for mention. | ||
* @param {String} data.feedText The text for which feed items were requested. | ||
*/ | ||
this.fire( 'requestFeed:response', { feed: feedResponse, marker, feedText } ); | ||
return; | ||
} | ||
// Handle the asynchronous responses. | ||
feedResponse | ||
.then( response => { | ||
// Check the feed text of this response with the last requested one so either: | ||
if ( this._lastRequested == feedText ) { | ||
// It is the same and fire the response event. | ||
this.fire( 'requestFeed:response', { feed: response, marker, feedText } ); | ||
} else { | ||
// It is different - most probably out-of-order one, so fire the discarded event. | ||
/** | ||
* Fired whenever the requested feed was discarded. This happens when the response was delayed and | ||
* other feed was already requested. | ||
* | ||
* @event requestFeed:discarded | ||
* @param {Object} data Event data. | ||
* @param {Array.<module:mention/mention~MentionFeedItem>} data.feed Autocomplete items. | ||
* @param {String} data.marker The character which triggers autocompletion for mention. | ||
* @param {String} data.feedText The text for which feed items were requested. | ||
*/ | ||
this.fire( 'requestFeed:discarded', { feed: response, marker, feedText } ); | ||
} | ||
} ) | ||
.catch( error => { | ||
/** | ||
* Fired whenever the requested {@link module:mention/mention~MentionFeed#feed} promise fails with error. | ||
* | ||
* @event requestFeed:error | ||
* @param {Object} data Event data. | ||
* @param {Error} data.error The error that was caught. | ||
*/ | ||
this.fire( 'requestFeed:error', { error } ); | ||
/** | ||
* The callback used for obtaining mention autocomplete feed thrown and error and the mention UI was hidden or | ||
* not displayed at all. | ||
* | ||
* @error mention-feed-callback-error | ||
*/ | ||
console.warn( attachLinkToDocumentation( 'mention-feed-callback-error: Could not obtain mention autocomplete feed.' ) ); | ||
} ); | ||
} | ||
@@ -294,10 +383,3 @@ | ||
// The text watcher listens only to changed range in selection - so the selection attributes are not yet available | ||
// and you cannot use selection.hasAttribute( 'mention' ) just yet. | ||
// See https://github.com/ckeditor/ckeditor5-engine/issues/1723. | ||
const hasMention = focus.textNode && focus.textNode.hasAttribute( 'mention' ); | ||
const nodeBefore = focus.nodeBefore; | ||
if ( hasMention || nodeBefore && nodeBefore.is( 'text' ) && nodeBefore.hasAttribute( 'mention' ) ) { | ||
if ( hasExistingMention( focus ) ) { | ||
this._hideUIAndRemoveMarker(); | ||
@@ -308,3 +390,3 @@ | ||
const feedText = getFeedText( marker, data.text ); | ||
const feedText = requestFeedText( marker, data.text ); | ||
const matchedTextLength = marker.length + feedText.length; | ||
@@ -318,30 +400,16 @@ | ||
let mentionMarker; | ||
if ( checkIfStillInCompletionMode( editor ) ) { | ||
const mentionMarker = editor.model.markers.get( 'mention' ); | ||
if ( editor.model.markers.has( 'mention' ) ) { | ||
mentionMarker = editor.model.markers.get( 'mention' ); | ||
// Update the marker - user might've moved the selection to other mention trigger. | ||
editor.model.change( writer => { | ||
writer.updateMarker( mentionMarker, { range: markerRange } ); | ||
} ); | ||
} else { | ||
mentionMarker = editor.model.change( writer => writer.addMarker( 'mention', { | ||
range: markerRange, | ||
usingOperation: false, | ||
affectsData: false | ||
} ) ); | ||
editor.model.change( writer => { | ||
writer.addMarker( 'mention', { range: markerRange, usingOperation: false, affectsData: false } ); | ||
} ); | ||
} | ||
this._getFeed( marker, feedText ) | ||
.then( feed => { | ||
this._items.clear(); | ||
for ( const feedItem of feed ) { | ||
const item = typeof feedItem != 'object' ? { id: feedItem, text: feedItem } : feedItem; | ||
this._items.add( { item, marker } ); | ||
} | ||
if ( this._items.length ) { | ||
this._showUI( mentionMarker ); | ||
} else { | ||
this._hideUIAndRemoveMarker(); | ||
} | ||
} ); | ||
this._requestFeedDebounced( marker, feedText ); | ||
} ); | ||
@@ -357,2 +425,35 @@ | ||
/** | ||
* Handles the feed response event data. | ||
* | ||
* @param data | ||
* @private | ||
*/ | ||
_handleFeedResponse( data ) { | ||
const { feed, marker } = data; | ||
// If the marker is not in the document happens when the selection had changed and the 'mention' marker was removed. | ||
if ( !checkIfStillInCompletionMode( this.editor ) ) { | ||
return; | ||
} | ||
// Reset the view. | ||
this._items.clear(); | ||
for ( const feedItem of feed ) { | ||
const item = typeof feedItem != 'object' ? { id: feedItem, text: feedItem } : feedItem; | ||
this._items.add( { item, marker } ); | ||
} | ||
const mentionMarker = this.editor.model.markers.get( 'mention' ); | ||
if ( this._items.length ) { | ||
this._showOrUpdateUI( mentionMarker ); | ||
} else { | ||
// Do not show empty mention UI. | ||
this._hideUIAndRemoveMarker(); | ||
} | ||
} | ||
/** | ||
* Shows the mentions balloon. If the panel is already visible, it will reposition it. | ||
@@ -362,3 +463,3 @@ * | ||
*/ | ||
_showUI( markerMarker ) { | ||
_showOrUpdateUI( markerMarker ) { | ||
if ( this._isUIVisible ) { | ||
@@ -377,3 +478,2 @@ // Update balloon position as the mention list view may change its size. | ||
this._mentionsView.position = this._balloon.view.position; | ||
this._mentionsView.selectFirst(); | ||
@@ -393,3 +493,3 @@ } | ||
if ( this.editor.model.markers.has( 'mention' ) ) { | ||
if ( checkIfStillInCompletionMode( this.editor ) ) { | ||
this.editor.model.change( writer => writer.removeMarker( 'mention' ) ); | ||
@@ -451,3 +551,3 @@ } | ||
const editor = this.editor; | ||
const editing = this.editor.editing; | ||
const editing = editor.editing; | ||
const domConverter = editing.view.domConverter; | ||
@@ -586,3 +686,3 @@ const mapper = editing.mapper; | ||
// @returns {Function} | ||
function getFeedText( marker, text ) { | ||
function requestFeedText( marker, text ) { | ||
const regExp = createRegExp( marker, 0 ); | ||
@@ -610,3 +710,3 @@ | ||
return Promise.resolve( filteredItems ); | ||
return filteredItems; | ||
}; | ||
@@ -620,12 +720,33 @@ } | ||
function isHandledKey( keyCode ) { | ||
const handledKeyCodes = [ | ||
keyCodes.arrowup, | ||
keyCodes.arrowdown, | ||
keyCodes.enter, | ||
keyCodes.tab, | ||
keyCodes.space, | ||
keyCodes.esc | ||
]; | ||
return handledKeyCodes.includes( keyCode ); | ||
} | ||
// Checks if position in inside or right after a text with a mention. | ||
// | ||
// @param {module:engine/model/position~Position} position. | ||
// @returns {Boolean} | ||
function hasExistingMention( position ) { | ||
// The text watcher listens only to changed range in selection - so the selection attributes are not yet available | ||
// and you cannot use selection.hasAttribute( 'mention' ) just yet. | ||
// See https://github.com/ckeditor/ckeditor5-engine/issues/1723. | ||
const hasMention = position.textNode && position.textNode.hasAttribute( 'mention' ); | ||
const nodeBefore = position.nodeBefore; | ||
return hasMention || nodeBefore && nodeBefore.is( 'text' ) && nodeBefore.hasAttribute( 'mention' ); | ||
} | ||
// Checks if string is a valid mention marker. | ||
// | ||
// @param {String} marker | ||
// @returns {Boolean} | ||
function isValidMentionMarker( marker ) { | ||
return marker && marker.length == 1; | ||
} | ||
// Checks the mention plugins is in completion mode (e.g. when typing is after a valid mention string like @foo). | ||
// | ||
// @returns {Boolean} | ||
function checkIfStillInCompletionMode( editor ) { | ||
return editor.model.markers.has( 'mention' ); | ||
} |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
@@ -4,0 +4,0 @@ */ |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
@@ -4,0 +4,0 @@ */ |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* @license Copyright (c) 2003-2020, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
@@ -4,0 +4,0 @@ */ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
57987
1413
5
18
+ Addedlodash-es@^4.17.10
+ Added@ckeditor/ckeditor5-core@17.0.0(transitive)
+ Added@ckeditor/ckeditor5-engine@17.0.0(transitive)
+ Added@ckeditor/ckeditor5-typing@17.0.0(transitive)
+ Added@ckeditor/ckeditor5-ui@17.0.0(transitive)
+ Added@ckeditor/ckeditor5-utils@17.0.0(transitive)
- Removed@ckeditor/ckeditor5-core@16.0.0(transitive)
- Removed@ckeditor/ckeditor5-engine@16.0.0(transitive)
- Removed@ckeditor/ckeditor5-typing@16.0.0(transitive)
- Removed@ckeditor/ckeditor5-ui@16.0.0(transitive)
- Removed@ckeditor/ckeditor5-utils@16.0.0(transitive)