@ckeditor/ckeditor5-mention
Advanced tools
Comparing version 10.0.0 to 11.0.0
Changelog | ||
========= | ||
## [11.0.0](https://github.com/ckeditor/ckeditor5-mention/compare/v10.0.0...v11.0.0) (2019-06-05) | ||
### Bug fixes | ||
* A mention can now be preceded by characters such as brackets, quotes, soft break, etc. Closes [#44](https://github.com/ckeditor/ckeditor5-mention/issues/44). ([86262d1](https://github.com/ckeditor/ckeditor5-mention/commit/86262d1)) | ||
* The mention plugin should not throw errors when another `ContextualBalloon` is already visible. Closes [#67](https://github.com/ckeditor/ckeditor5-mention/issues/67). ([de9ee71](https://github.com/ckeditor/ckeditor5-mention/commit/de9ee71)) | ||
* The mention panel should have precedence over all other panels. Closes [#74](https://github.com/ckeditor/ckeditor5-mention/issues/74). ([3e8a84c](https://github.com/ckeditor/ckeditor5-mention/commit/3e8a84c)) | ||
* The Mention UI will use `ContextualBalloon` plugin to display to prevent balloon collisions with other features. Closes [#27](https://github.com/ckeditor/ckeditor5-mention/issues/27). ([9ae7f30](https://github.com/ckeditor/ckeditor5-mention/commit/9ae7f30)) | ||
### Other changes | ||
* Remove unknown stack option from `ContextualBalloon#add()` method call. ([b6a50cf](https://github.com/ckeditor/ckeditor5-mention/commit/b6a50cf)) | ||
* Use `Model#insertContent()` instead of `model.Writer#insertText()`. Closes [#69](https://github.com/ckeditor/ckeditor5-mention/issues/69). ([ee973bb](https://github.com/ckeditor/ckeditor5-mention/commit/ee973bb)) | ||
### BREAKING CHANGES | ||
* The `MentionUI#panelView` property is removed. The mention feature now uses the `ContextualBalloon` plugin. | ||
## [10.0.0](https://github.com/ckeditor/ckeditor5-mention/tree/v10.0.0) (2019-04-10) | ||
The initial release. |
{ | ||
"name": "@ckeditor/ckeditor5-mention", | ||
"version": "10.0.0", | ||
"version": "11.0.0", | ||
"description": "Mention feature for CKEditor 5.", | ||
@@ -13,15 +13,19 @@ "keywords": [ | ||
"dependencies": { | ||
"@ckeditor/ckeditor5-core": "^12.1.0", | ||
"@ckeditor/ckeditor5-ui": "^12.1.0", | ||
"@ckeditor/ckeditor5-utils": "^12.1.0" | ||
"@ckeditor/ckeditor5-core": "^12.1.1", | ||
"@ckeditor/ckeditor5-ui": "^13.0.0", | ||
"@ckeditor/ckeditor5-utils": "^12.1.1" | ||
}, | ||
"devDependencies": { | ||
"@ckeditor/ckeditor5-basic-styles": "^11.1.0", | ||
"@ckeditor/ckeditor5-block-quote": "^11.0.1", | ||
"@ckeditor/ckeditor5-clipboard": "^11.0.1", | ||
"@ckeditor/ckeditor5-editor-classic": "^12.1.0", | ||
"@ckeditor/ckeditor5-engine": "^13.1.0", | ||
"@ckeditor/ckeditor5-font": "^11.1.0", | ||
"@ckeditor/ckeditor5-paragraph": "^11.0.1", | ||
"@ckeditor/ckeditor5-undo": "^11.0.1", | ||
"@ckeditor/ckeditor5-basic-styles": "^11.1.1", | ||
"@ckeditor/ckeditor5-block-quote": "^11.1.0", | ||
"@ckeditor/ckeditor5-clipboard": "^11.0.2", | ||
"@ckeditor/ckeditor5-editor-classic": "^12.1.1", | ||
"@ckeditor/ckeditor5-engine": "^13.1.1", | ||
"@ckeditor/ckeditor5-font": "^11.1.1", | ||
"@ckeditor/ckeditor5-link": "^11.0.2", | ||
"@ckeditor/ckeditor5-paragraph": "^11.0.2", | ||
"@ckeditor/ckeditor5-table": "^13.0.0", | ||
"@ckeditor/ckeditor5-typing": "^12.0.2", | ||
"@ckeditor/ckeditor5-undo": "^11.0.2", | ||
"@ckeditor/ckeditor5-widget": "^11.0.2", | ||
"eslint": "^5.5.0", | ||
@@ -28,0 +32,0 @@ "eslint-config-ckeditor5": "^1.0.11", |
@@ -13,4 +13,8 @@ CKEditor 5 mention feature | ||
This package implements mention support for CKEditor 5. | ||
This package implements mention support for CKEditor 5 and brings smart autocompletion based on user input. | ||
## Demo | ||
Check out the demo in the [Mentions (autocomplete) feature](https://ckeditor.com/docs/ckeditor5/latest/features/mentions.html) guide. | ||
## Documentation | ||
@@ -22,2 +26,2 @@ | ||
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). For full details about the license, please check the `LICENSE.md` file. | ||
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). For full details about the license, please check the `LICENSE.md` file or [https://ckeditor.com/legal/ckeditor-oss-license](https://ckeditor.com/legal/ckeditor-oss-license). |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
@@ -30,3 +30,3 @@ | ||
* | ||
* // for a viewElement: <span data-mention="@joe">@John Doe</span> | ||
* // For a view element: <span data-mention="@joe">@John Doe</span> | ||
* // it will return: | ||
@@ -74,3 +74,3 @@ * // { id: '@joe', userId: '1234', _uid: '7a7bc7...', _text: '@John Doe' } | ||
* .create( editorElement, { | ||
* mention: ... // Media embed feature options. | ||
* mention: ... // Mention feature options. | ||
* } ) | ||
@@ -104,3 +104,3 @@ * .then( ... ) | ||
* | ||
* You can provide as many mention feeds but they must use different `marker`s. | ||
* You can provide many mention feeds but they must use different `marker`s. | ||
* For example, you can use `'@'` to autocomplete people and `'#'` to autocomplete tags. | ||
@@ -123,3 +123,3 @@ * | ||
* | ||
* // Simple, synchronous callback. | ||
* // Simple synchronous callback. | ||
* const mentionFeedTags = { | ||
@@ -161,7 +161,7 @@ * marker: '#', | ||
* @property {String} [marker] The character which triggers autocompletion for mention. It must be a single character. | ||
* @property {Array.<module:mention/mention~MentionFeedItem>|Function} feed The autocomplete items. Provide an array for | ||
* @property {Array.<module:mention/mention~MentionFeedItem>|Function} feed Autocomplete items. Provide an array for | ||
* a static configuration (the mention feature will show matching items automatically) or a function which returns an array of | ||
* matching items (directly, or via a promise). | ||
* @property {Number} [minimumCharacters=0] Specifies after how many characters the autocomplete panel should be shown. | ||
* @property {Function} [itemRenderer] Function that renders a {@link module:mention/mention~MentionFeedItem} | ||
* @property {Function} [itemRenderer] A function that renders a {@link module:mention/mention~MentionFeedItem} | ||
* to the autocomplete panel. | ||
@@ -173,3 +173,3 @@ */ | ||
* | ||
* When defining a feed item as a plain object, the `id` property is obligatory. The additional properties | ||
* When defining a feed item as a plain object, the `id` property is obligatory. Additional properties | ||
* can be used when customizing the mention feature bahavior | ||
@@ -224,3 +224,3 @@ * (see {@glink features/mentions#customizing-the-autocomplete-list "Customizing the autocomplete list"} | ||
* @typedef {Object|String} module:mention/mention~MentionFeedItem | ||
* @property {String} id Unique id of the mention. It must start with the marker character. | ||
* @property {String} id A unique ID of the mention. It must start with the marker character. | ||
* @property {String} [text] Text inserted into the editor when creating a mention. | ||
@@ -230,3 +230,3 @@ */ | ||
/** | ||
* Represents mention in the model. | ||
* Represents a mention in the model. | ||
* | ||
@@ -236,6 +236,7 @@ * See {@link module:mention/mention~Mention#toMentionAttribute `Mention#toMentionAttribute()`}. | ||
* @interface module:mention/mention~MentionAttribute | ||
* @property {String} id Id of a mention – identifies the mention item in mention feed. | ||
* @property {String} _uid Internal mention view item id. Should be passed as an `option.id` when using | ||
* @property {String} id The ID of a mention. It identifies the mention item in the mention feed. | ||
* @property {String} _uid An internal mention view item ID. Should be passed as an `option.id` when using | ||
* {@link module:engine/view/downcastwriter~DowncastWriter#createAttributeElement writer.createAttributeElement()}. | ||
* @property {String} _text Helper property that holds text of inserted mention. Used for detecting broken mention in the editing area. | ||
* @property {String} _text Helper property that stores the text of the inserted mention. Used for detecting a broken mention | ||
* in the editing area. | ||
*/ |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
@@ -18,10 +18,10 @@ | ||
* | ||
* The command is registered by the {@link module:mention/mentionediting~MentionEditing} as `'mention'`. | ||
* The command is registered by {@link module:mention/mentionediting~MentionEditing} as `'mention'`. | ||
* | ||
* To insert a mention on a range, execute the command and specify a mention object and a range to replace: | ||
* To insert a mention onto a range, execute the command and specify a mention object with a range to replace: | ||
* | ||
* const focus = editor.model.document.selection.focus; | ||
* | ||
* // It will replace one character before selection focus with '#1234' text | ||
* // with mention attribute filled with passed attributes. | ||
* // It will replace one character before the selection focus with the '#1234' text | ||
* // with the mention attribute filled with passed attributes. | ||
* editor.execute( 'mention', { | ||
@@ -37,4 +37,4 @@ * marker: '#', | ||
* | ||
* // It will replace one character before selection focus with 'Teh "Big Foo"' text | ||
* // with attribute filled with passed attributes. | ||
* // It will replace one character before the selection focus with the 'The "Big Foo"' text | ||
* // with the mention attribute filled with passed attributes. | ||
* editor.execute( 'mention', { | ||
@@ -68,8 +68,9 @@ * marker: '#', | ||
* @param {Object} [options] Options for the executed command. | ||
* @param {Object|String} options.mention Mention object to insert. If passed a string it will be used to create a plain object with | ||
* name attribute equal to passed string. | ||
* @param {Object|String} options.mention The mention object to insert. When a string is passed, it will be used to create a plain | ||
* object with the name attribute that equals the passed string. | ||
* @param {String} options.marker The marker character (e.g. `'@'`). | ||
* @param {String} [options.text] The text of inserted mention. Defaults to full mention string composed from `marker` and | ||
* `mention` string or `mention.id` if object is passed. | ||
* @param {String} [options.range] Range to replace. Note that replace range might be shorter then inserted text with mention attribute. | ||
* @param {String} [options.text] The text of the inserted mention. Defaults to the full mention string composed from `marker` and | ||
* `mention` string or `mention.id` if an object is passed. | ||
* @param {String} [options.range] The range to replace. Note that the replaced range might be shorter than the inserted text with the | ||
* mention attribute. | ||
* @fires execute | ||
@@ -108,3 +109,3 @@ */ | ||
/** | ||
* The feed item id must start with the marker character. | ||
* The feed item ID must start with the marker character. | ||
* | ||
@@ -142,10 +143,7 @@ * Correct mention feed setting: | ||
// Replace range with a text with mention. | ||
writer.remove( range ); | ||
writer.insertText( mentionText, attributesWithMention, range.start ); | ||
// Insert space after a mention. | ||
writer.insertText( ' ', currentAttributes, model.document.selection.focus ); | ||
// Replace a range with the text with a mention. | ||
model.insertContent( writer.createText( mentionText, attributesWithMention ), range ); | ||
model.insertContent( writer.createText( ' ', currentAttributes ), range.start.getShiftedBy( mentionText.length ) ); | ||
} ); | ||
} | ||
} |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
@@ -40,3 +40,3 @@ | ||
// Allow mention attribute on all text nodes. | ||
// Allow the mention attribute on all text nodes. | ||
model.schema.extend( '$text', { allowAttributes: 'mention' } ); | ||
@@ -74,3 +74,3 @@ | ||
/** | ||
* Creates mention attribute value from provided view element and optional data. | ||
* Creates a mention attribute value from the provided view element and optional data. | ||
* | ||
@@ -103,3 +103,3 @@ * This function is exposed as | ||
// Creates mention element from mention data. | ||
// Creates a mention element from the mention data. | ||
// | ||
@@ -127,7 +127,7 @@ // @param {Object} mention | ||
// Model post-fixer that disallows typing with selection when selection is placed after the text node with mention attribute. | ||
// Model post-fixer that disallows typing with selection when the selection is placed after the text node with the mention attribute. | ||
// | ||
// @param {module:engine/model/writer~Writer} writer | ||
// @param {module:engine/model/document~Document} doc | ||
// @returns {Boolean} Returns true if selection was fixed. | ||
// @returns {Boolean} Returns `true` if the selection was fixed. | ||
function selectionMentionAttributePostFixer( writer, doc ) { | ||
@@ -148,7 +148,7 @@ const selection = doc.selection; | ||
// Model post-fixer that removes mention attribute from modified text node. | ||
// Model post-fixer that removes the mention attribute from the modified text node. | ||
// | ||
// @param {module:engine/model/writer~Writer} writer | ||
// @param {module:engine/model/document~Document} doc | ||
// @returns {Boolean} Returns true if selection was fixed. | ||
// @returns {Boolean} Returns `true` if the selection was fixed. | ||
function removePartialMentionPostFixer( writer, doc, schema ) { | ||
@@ -160,3 +160,3 @@ const changes = doc.differ.getChanges(); | ||
for ( const change of changes ) { | ||
// Check text node on current position; | ||
// Checks the text node on the current position. | ||
const position = change.position; | ||
@@ -167,6 +167,6 @@ | ||
// Check textNode where the change occurred. | ||
// Checks the text node where the change occurred. | ||
wasChanged = checkAndFix( position.textNode, writer ) || wasChanged; | ||
// Occurs on paste occurs inside a text node with mention. | ||
// Occurs on paste inside a text node with mention. | ||
wasChanged = checkAndFix( nodeAfterInsertedTextNode, writer ) || wasChanged; | ||
@@ -177,3 +177,3 @@ wasChanged = checkAndFix( position.nodeBefore, writer ) || wasChanged; | ||
// Check text nodes in inserted elements (might occur when splitting paragraph or pasting content inside text with mention). | ||
// Checks text nodes in inserted elements (might occur when splitting a paragraph or pasting content inside text with mention). | ||
if ( change.name != '$text' && change.type == 'insert' ) { | ||
@@ -199,7 +199,8 @@ const insertedNode = position.nodeAfter; | ||
// This post-fixer will extend attribute applied on part of a mention so a whole text node of a mention will have added attribute. | ||
// This post-fixer will extend the attribute applied on the part of the mention so the whole text node of the mention will have | ||
// the added attribute. | ||
// | ||
// @param {module:engine/model/writer~Writer} writer | ||
// @param {module:engine/model/document~Document} doc | ||
// @returns {Boolean} Returns true if selection was fixed. | ||
// @returns {Boolean} Returns `true` if the selection was fixed. | ||
function extendAttributeOnMentionPostFixer( writer, doc ) { | ||
@@ -212,5 +213,5 @@ const changes = doc.differ.getChanges(); | ||
if ( change.type === 'attribute' && change.attributeKey != 'mention' ) { | ||
// Check node at the left side of a range... | ||
// Checks the node on the left side of the range... | ||
const nodeBefore = change.range.start.nodeBefore; | ||
// ... and on right side of range. | ||
// ... and on the right side of the range. | ||
const nodeAfter = change.range.end.nodeAfter; | ||
@@ -231,6 +232,6 @@ | ||
// Checks if node has correct mention attribute if present. | ||
// Returns true if node is text and has a mention attribute which text does not match expected mention text. | ||
// Checks if a node has a correct mention attribute if present. | ||
// Returns `true` if the node is text and has a mention attribute whose text does not match the expected mention text. | ||
// | ||
// @param {module:engine/model/node~Node} node a node to check | ||
// @param {module:engine/model/node~Node} node The node to check. | ||
// @returns {Boolean} | ||
@@ -250,3 +251,3 @@ function isBrokenMentionNode( node ) { | ||
// Fixes mention on text node it needs a fix. | ||
// Fixes a mention on a text node if it needs a fix. | ||
// | ||
@@ -253,0 +254,0 @@ // @param {module:engine/model/text~Text} textNode |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
@@ -13,7 +13,8 @@ | ||
import Collection from '@ckeditor/ckeditor5-utils/src/collection'; | ||
import BalloonPanelView from '@ckeditor/ckeditor5-ui/src/panel/balloon/balloonpanelview'; | ||
import clickOutsideHandler from '@ckeditor/ckeditor5-ui/src/bindings/clickoutsidehandler'; | ||
import { keyCodes } from '@ckeditor/ckeditor5-utils/src/keyboard'; | ||
import featureDetection from './featuredetection'; | ||
import Rect from '@ckeditor/ckeditor5-utils/src/dom/rect'; | ||
import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror'; | ||
import ContextualBalloon from '@ckeditor/ckeditor5-ui/src/panel/balloon/contextualballoon'; | ||
@@ -48,11 +49,4 @@ import TextWatcher from './textwatcher'; | ||
/** | ||
* The balloon panel view, containing the mention view. | ||
* The mention view. | ||
* | ||
* @type {module:ui/panel/balloon/balloonpanelview~BalloonPanelView} | ||
*/ | ||
this.panelView = this._creatPanelView(); | ||
/** | ||
* The mentions view. | ||
* | ||
* @type {module:mention/ui/mentionsview~MentionsView} | ||
@@ -64,3 +58,3 @@ * @private | ||
/** | ||
* Stores mentions feeds configurations. | ||
* Stores mention feeds configurations. | ||
* | ||
@@ -79,7 +73,17 @@ * @type {Map<String, Object>} | ||
init() { | ||
const editor = this.editor; | ||
/** | ||
* The contextual balloon plugin instance. | ||
* | ||
* @private | ||
* @member {module:ui/panel/balloon/contextualballoon~ContextualBalloon} | ||
*/ | ||
this._balloon = editor.plugins.get( ContextualBalloon ); | ||
// Key listener that handles navigation in mention view. | ||
this.editor.editing.view.document.on( 'keydown', ( evt, data ) => { | ||
if ( isHandledKey( data.keyCode ) && this.panelView.isVisible ) { | ||
editor.editing.view.document.on( 'keydown', ( evt, data ) => { | ||
if ( isHandledKey( data.keyCode ) && this._isUIVisible ) { | ||
data.preventDefault(); | ||
evt.stop(); // Required for enter overriding. | ||
evt.stop(); // Required for Enter key overriding. | ||
@@ -99,16 +103,16 @@ if ( data.keyCode == keyCodes.arrowdown ) { | ||
if ( data.keyCode == keyCodes.esc ) { | ||
this._hidePanelAndRemoveMarker(); | ||
this._hideUIAndRemoveMarker(); | ||
} | ||
} | ||
}, { priority: 'highest' } ); // Required to override enter. | ||
}, { priority: 'highest' } ); // Required to override the Enter key. | ||
// Close the #panelView upon clicking outside of the plugin UI. | ||
// Close the dropdown upon clicking outside of the plugin UI. | ||
clickOutsideHandler( { | ||
emitter: this.panelView, | ||
contextElements: [ this.panelView.element ], | ||
activator: () => this.panelView.isVisible, | ||
callback: () => this._hidePanelAndRemoveMarker() | ||
emitter: this._mentionsView, | ||
activator: () => this._isUIVisible, | ||
contextElements: [ this._balloon.view.element ], | ||
callback: () => this._hideUIAndRemoveMarker() | ||
} ); | ||
const feeds = this.editor.config.get( 'mention.feeds' ); | ||
const feeds = editor.config.get( 'mention.feeds' ); | ||
@@ -132,3 +136,3 @@ for ( const mentionDescription of feeds ) { | ||
*/ | ||
throw new CKEditorError( 'mentionconfig-incorrect-marker: The marker must be provided and be a single character.' ); | ||
throw new CKEditorError( 'mentionconfig-incorrect-marker: The marker must be provided and it must be a single character.' ); | ||
} | ||
@@ -154,20 +158,22 @@ | ||
// Destroy created UI components as they are not automatically destroyed (see ckeditor5#1341). | ||
this.panelView.destroy(); | ||
this._mentionsView.destroy(); | ||
} | ||
/** | ||
* Creates the {@link #panelView}. | ||
* @inheritDoc | ||
*/ | ||
static get requires() { | ||
return [ ContextualBalloon ]; | ||
} | ||
/** | ||
* Returns true when {@link #_mentionsView} is in the {@link module:ui/panel/balloon/contextualballoon~ContextualBalloon} and it is | ||
* currently visible. | ||
* | ||
* @private | ||
* @returns {module:ui/panel/balloon/balloonpanelview~BalloonPanelView} | ||
* @readonly | ||
* @protected | ||
* @type {Boolean} | ||
*/ | ||
_creatPanelView() { | ||
const panelView = new BalloonPanelView( this.editor.locale ); | ||
panelView.withArrow = false; | ||
panelView.render(); | ||
this.editor.ui.view.body.add( panelView ); | ||
return panelView; | ||
get _isUIVisible() { | ||
return this._balloon.visibleView === this._mentionsView; | ||
} | ||
@@ -188,4 +194,2 @@ | ||
this.panelView.content.add( mentionsView ); | ||
mentionsView.items.bindTo( this._items ).using( data => { | ||
@@ -233,3 +237,3 @@ const { item, marker } = data; | ||
this._hidePanelAndRemoveMarker(); | ||
this._hideUIAndRemoveMarker(); | ||
@@ -250,3 +254,3 @@ editor.execute( 'mention', { | ||
/** | ||
* Returns item renderer for marker. | ||
* Returns item renderer for the marker. | ||
* | ||
@@ -264,3 +268,3 @@ * @private | ||
/** | ||
* Returns a promise that resolves with autocomplete items for given text. | ||
* Returns a promise that resolves with autocomplete items for a given text. | ||
* | ||
@@ -279,3 +283,3 @@ * @param {String} marker | ||
/** | ||
* Registers a text watcher for marker. | ||
* Registers a text watcher for the marker. | ||
* | ||
@@ -297,6 +301,14 @@ * @private | ||
const hasMention = selection.hasAttribute( 'mention' ); | ||
const nodeBefore = selection.focus.nodeBefore; | ||
const focus = selection.focus; | ||
// 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' ) ) { | ||
this._hideUIAndRemoveMarker(); | ||
return; | ||
@@ -309,5 +321,5 @@ } | ||
// create marker range | ||
const start = selection.focus.getShiftedBy( -matchedTextLength ); | ||
const end = selection.focus.getShiftedBy( -feedText.length ); | ||
// Create a marker range. | ||
const start = focus.getShiftedBy( -matchedTextLength ); | ||
const end = focus.getShiftedBy( -feedText.length ); | ||
@@ -339,5 +351,5 @@ const markerRange = editor.model.createRange( start, end ); | ||
if ( this._items.length ) { | ||
this._showPanel( mentionMarker ); | ||
this._showUI( mentionMarker ); | ||
} else { | ||
this._hidePanelAndRemoveMarker(); | ||
this._hideUIAndRemoveMarker(); | ||
} | ||
@@ -348,3 +360,3 @@ } ); | ||
watcher.on( 'unmatched', () => { | ||
this._hidePanelAndRemoveMarker(); | ||
this._hideUIAndRemoveMarker(); | ||
} ); | ||
@@ -356,3 +368,3 @@ | ||
/** | ||
* Returns registered text watcher for marker. | ||
* Returns the registered text watcher for the marker. | ||
* | ||
@@ -370,9 +382,21 @@ * @private | ||
/** | ||
* Shows the {@link #panelView}. If panel is already visible it will reposition it. | ||
* Shows the mentions balloon. If the panel is already visible, it will reposition it. | ||
* | ||
* @private | ||
*/ | ||
_showPanel( markerMarker ) { | ||
this.panelView.pin( this._getBalloonPanelPositionData( markerMarker, this.panelView.position ) ); | ||
this.panelView.show(); | ||
_showUI( markerMarker ) { | ||
if ( this._isUIVisible ) { | ||
// Update balloon position as the mention list view may change its size. | ||
this._balloon.updatePosition( this._getBalloonPanelPositionData( markerMarker, this._mentionsView.position ) ); | ||
} else { | ||
this._balloon.add( { | ||
view: this._mentionsView, | ||
position: this._getBalloonPanelPositionData( markerMarker, this._mentionsView.position ), | ||
withArrow: false, | ||
singleViewMode: true | ||
} ); | ||
} | ||
this._mentionsView.position = this._balloon.view.position; | ||
this._mentionsView.selectFirst(); | ||
@@ -382,7 +406,12 @@ } | ||
/** | ||
* Hides the {@link #panelView} and remove 'mention' marker from markers collection. | ||
* Hides the mentions balloon and removes the 'mention' marker from the markers collection. | ||
* | ||
* @private | ||
*/ | ||
_hidePanelAndRemoveMarker() { | ||
_hideUIAndRemoveMarker() { | ||
// Remove the mention view from balloon before removing marker - it is used by balloon position target(). | ||
if ( this._balloon.hasView( this._mentionsView ) ) { | ||
this._balloon.remove( this._mentionsView ); | ||
} | ||
if ( this.editor.model.markers.has( 'mention' ) ) { | ||
@@ -392,6 +421,5 @@ this.editor.model.change( writer => writer.removeMarker( 'mention' ) ); | ||
this.panelView.unpin(); | ||
// Make last matched position on panel view undefined so the #_getBalloonPanelPositionData() will return all positions on next call. | ||
this.panelView.position = undefined; | ||
this.panelView.hide(); | ||
// Make the last matched position on panel view undefined so the #_getBalloonPanelPositionData() method will return all positions | ||
// on the next call. | ||
this._mentionsView.position = undefined; | ||
} | ||
@@ -438,10 +466,11 @@ | ||
/** | ||
* Creates position options object used to position the balloon panel. | ||
* Creates a position options object used to position the balloon panel. | ||
* | ||
* @param {module:engine/model/markercollection~Marker} mentionMarker | ||
* @param {String|undefined} positionName Name of last matched position name. | ||
* @param {String|undefined} preferredPosition The name of the last matched position name. | ||
* @returns {module:utils/dom/position~Options} | ||
* @private | ||
*/ | ||
_getBalloonPanelPositionData( mentionMarker, positionName ) { | ||
_getBalloonPanelPositionData( mentionMarker, preferredPosition ) { | ||
const editor = this.editor; | ||
const editing = this.editor.editing; | ||
@@ -453,4 +482,11 @@ const domConverter = editing.view.domConverter; | ||
target: () => { | ||
const viewRange = mapper.toViewRange( mentionMarker.getRange() ); | ||
let modelRange = mentionMarker.getRange(); | ||
// Target the UI to the model selection range - the marker has been removed so probably the UI will not be shown anyway. | ||
// The logic is used by ContextualBalloon to display another panel in the same place. | ||
if ( modelRange.start.root.rootName == '$graveyard' ) { | ||
modelRange = editor.model.document.selection.getFirstRange(); | ||
} | ||
const viewRange = mapper.toViewRange( modelRange ); | ||
const rangeRects = Rect.getDomRangeRects( domConverter.viewRangeToDom( viewRange ) ); | ||
@@ -471,4 +507,3 @@ | ||
}, | ||
positions: getBalloonPanelPositions( positionName ), | ||
fitInViewport: true | ||
positions: getBalloonPanelPositions( preferredPosition ) | ||
}; | ||
@@ -478,8 +513,9 @@ } | ||
// Returns balloon positions data callbacks. | ||
// Returns the balloon positions data callbacks. | ||
// | ||
// @param {String} preferredPosition | ||
// @returns {Array.<module:utils/dom/position~Position>} | ||
function getBalloonPanelPositions( positionName ) { | ||
function getBalloonPanelPositions( preferredPosition ) { | ||
const positions = { | ||
// Positions panel to the south of caret rect. | ||
// Positions the panel to the southeast of the caret rectangle. | ||
'caret_se': targetRect => { | ||
@@ -493,3 +529,3 @@ return { | ||
// Positions panel to the north of caret rect. | ||
// Positions the panel to the northeast of the caret rectangle. | ||
'caret_ne': ( targetRect, balloonRect ) => { | ||
@@ -503,3 +539,3 @@ return { | ||
// Positions panel to the south of caret rect. | ||
// Positions the panel to the southwest of the caret rectangle. | ||
'caret_sw': ( targetRect, balloonRect ) => { | ||
@@ -513,3 +549,3 @@ return { | ||
// Positions panel to the north of caret rect. | ||
// Positions the panel to the northwest of the caret rect. | ||
'caret_nw': ( targetRect, balloonRect ) => { | ||
@@ -524,14 +560,14 @@ return { | ||
// Return only last position if it was matched to prevent panel from jumping after first match. | ||
if ( positions.hasOwnProperty( positionName ) ) { | ||
// Returns only the last position if it was matched to prevent the panel from jumping after the first match. | ||
if ( positions.hasOwnProperty( preferredPosition ) ) { | ||
return [ | ||
positions[ positionName ] | ||
positions[ preferredPosition ] | ||
]; | ||
} | ||
// As default return all positions callbacks. | ||
// By default return all position callbacks. | ||
return [ | ||
positions.caret_se, | ||
positions.caret_sw, | ||
positions.caret_ne, | ||
positions.caret_sw, | ||
positions.caret_nw | ||
@@ -541,20 +577,33 @@ ]; | ||
// Creates a regex pattern for marker. | ||
// Creates a RegExp pattern for the marker. | ||
// | ||
// Function has to be exported to achieve 100% code coverage. | ||
// | ||
// @param {String} marker | ||
// @param {Number} minimumCharacters | ||
// @returns {String} | ||
function createPattern( marker, minimumCharacters ) { | ||
// @returns {RegExp} | ||
export function createRegExp( marker, minimumCharacters ) { | ||
const numberOfCharacters = minimumCharacters == 0 ? '*' : `{${ minimumCharacters },}`; | ||
const patternBase = featureDetection.isPunctuationGroupSupported ? '\\p{Ps}\\p{Pi}"\'' : '\\(\\[{"\''; | ||
return `(^| )(\\${ marker })([_a-zA-Z0-9À-ž]${ numberOfCharacters }?)$`; | ||
return new RegExp( buildPattern( patternBase, marker, numberOfCharacters ), 'u' ); | ||
} | ||
// Creates a test callback for marker to be used in text watcher instance. | ||
// Helper to build a RegExp pattern string for the marker. | ||
// | ||
// @param {String} whitelistedCharacters | ||
// @param {String} marker | ||
// @param {Number} minimumCharacters | ||
// @returns {String} | ||
function buildPattern( whitelistedCharacters, marker, numberOfCharacters ) { | ||
return `(^|[ ${ whitelistedCharacters }])([${ marker }])([_a-zA-Z0-9À-ž]${ numberOfCharacters }?)$`; | ||
} | ||
// Creates a test callback for the marker to be used in the text watcher instance. | ||
// | ||
// @param {String} marker | ||
// @param {Number} minimumCharacters | ||
// @returns {Function} | ||
function createTestCallback( marker, minimumCharacters ) { | ||
const regExp = new RegExp( createPattern( marker, minimumCharacters ) ); | ||
const regExp = createRegExp( marker, minimumCharacters ); | ||
@@ -564,3 +613,3 @@ return text => regExp.test( text ); | ||
// Creates a text watcher matcher for marker. | ||
// Creates a text matcher from the marker. | ||
// | ||
@@ -570,3 +619,3 @@ // @param {String} marker | ||
function createTextMatcher( marker ) { | ||
const regExp = new RegExp( createPattern( marker, 0 ) ); | ||
const regExp = createRegExp( marker, 0 ); | ||
@@ -583,7 +632,7 @@ return text => { | ||
// Default feed callback | ||
// The default feed callback. | ||
function createFeedCallback( feedItems ) { | ||
return feedText => { | ||
const filteredItems = feedItems | ||
// Make default mention feed case-insensitive. | ||
// Make the default mention feed case-insensitive. | ||
.filter( item => { | ||
@@ -603,3 +652,3 @@ // Item might be defined as object. | ||
// Checks if given key code is handled by the mention ui. | ||
// Checks if a given key code is handled by the mention UI. | ||
// | ||
@@ -606,0 +655,0 @@ // @param {Number} |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
@@ -14,6 +14,6 @@ | ||
/** | ||
* Text watcher feature. | ||
* The text watcher feature. | ||
* | ||
* Fires {@link module:mention/textwatcher~TextWatcher#event:matched matched} and | ||
* {@link module:mention/textwatcher~TextWatcher#event:unmatched unmatched} events on typing or selection changes. | ||
* Fires {@link module:mention/textwatcher~TextWatcher#event:matched `matched`} and | ||
* {@link module:mention/textwatcher~TextWatcher#event:unmatched `unmatched`} events on typing or selection changes. | ||
* | ||
@@ -26,4 +26,4 @@ * @private | ||
* @param {module:core/editor/editor~Editor} editor | ||
* @param {Function} testCallback Function used to match the text. | ||
* @param {Function} textMatcherCallback Function used to process matched text. | ||
* @param {Function} testCallback The function used to match the text. | ||
* @param {Function} textMatcherCallback The function used to process matched text. | ||
*/ | ||
@@ -41,3 +41,3 @@ constructor( editor, testCallback, textMatcherCallback ) { | ||
/** | ||
* Last matched text. | ||
* The last matched text. | ||
* | ||
@@ -51,3 +51,3 @@ * @property {String} | ||
/** | ||
* Starts listening the editor for typing & selection events. | ||
* Starts listening to the editor for typing and selection events. | ||
* | ||
@@ -59,4 +59,4 @@ * @private | ||
editor.model.document.selection.on( 'change', ( evt, { directChange } ) => { | ||
// The indirect changes (ie on typing) are handled in document's change event. | ||
editor.model.document.selection.on( 'change:range', ( evt, { directChange } ) => { | ||
// Indirect changes (i.e. when the user types or external changes are applied) are handled in the document's change event. | ||
if ( !directChange ) { | ||
@@ -93,3 +93,3 @@ return; | ||
/** | ||
* Fired whenever text doesn't match anymore. Fired only when text matcher was matched. | ||
* Fired whenever the text does not match anymore. Fired only when the text watcher found a match. | ||
* | ||
@@ -107,3 +107,3 @@ * @event unmatched | ||
/** | ||
* Fired whenever text matcher was matched. | ||
* Fired whenever the text watcher found a match. | ||
* | ||
@@ -119,3 +119,3 @@ * @event matched | ||
* | ||
* @returns {String|undefined} Text from block or undefined if selection is not collapsed. | ||
* @returns {String|undefined} The text from the block or undefined if the selection is not collapsed. | ||
* @private | ||
@@ -125,5 +125,6 @@ */ | ||
const editor = this.editor; | ||
const selection = editor.model.document.selection; | ||
const model = editor.model; | ||
const selection = model.document.selection; | ||
// Do nothing if selection is not collapsed. | ||
// Do nothing if the selection is not collapsed. | ||
if ( !selection.isCollapsed ) { | ||
@@ -133,5 +134,5 @@ return; | ||
const block = selection.focus.parent; | ||
const rangeBeforeSelection = model.createRange( model.createPositionAt( selection.focus.parent, 0 ), selection.focus ); | ||
return _getText( editor.model.createRangeIn( block ) ).slice( 0, selection.focus.offset ); | ||
return _getText( rangeBeforeSelection ); | ||
} | ||
@@ -141,3 +142,3 @@ } | ||
/** | ||
* Returns whole text from given range by adding all data from text nodes together. | ||
* Returns the whole text from a given range by adding all data from the text nodes together. | ||
* | ||
@@ -149,3 +150,10 @@ * @protected | ||
export function _getText( range ) { | ||
return Array.from( range.getItems() ).reduce( ( a, b ) => a + b.data, '' ); | ||
return Array.from( range.getItems() ).reduce( ( rangeText, node ) => { | ||
if ( node.is( 'softBreak' ) ) { | ||
// Trim text to softBreak | ||
return ''; | ||
} | ||
return rangeText + node.data; | ||
}, '' ); | ||
} | ||
@@ -152,0 +160,0 @@ |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
@@ -5,0 +5,0 @@ |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
@@ -5,0 +5,0 @@ |
/** | ||
* @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved. | ||
* For licensing, see LICENSE.md. | ||
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license | ||
*/ | ||
@@ -88,9 +88,9 @@ | ||
const item = this.items.get( indexToGet ); | ||
item.highlight(); | ||
// Scroll the mentions view to the selected element. | ||
if ( !this._isItemVisibleInScrolledArea( item ) ) { | ||
this.element.scrollTop = item.element.offsetTop; | ||
// Return early if item is already selected. | ||
if ( this.selected === item ) { | ||
return; | ||
} | ||
// Remove highlight of previously selected item. | ||
if ( this.selected ) { | ||
@@ -100,3 +100,9 @@ this.selected.removeHighlight(); | ||
item.highlight(); | ||
this.selected = item; | ||
// Scroll the mentions view to the selected element. | ||
if ( !this._isItemVisibleInScrolledArea( item ) ) { | ||
this.element.scrollTop = item.element.offsetTop; | ||
} | ||
} | ||
@@ -103,0 +109,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
54245
15
1440
26
16
+ Added@ckeditor/ckeditor5-ui@13.0.2(transitive)
+ Added@ckeditor/ckeditor5-utils@13.0.1(transitive)
- Removed@ckeditor/ckeditor5-ui@12.1.0(transitive)