| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- /**
- * @license Copyright (c) 2003-2019, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
- */
- /**
- * @module mention/textwatcher
- */
- import mix from '@ckeditor/ckeditor5-utils/src/mix';
- import EmitterMixin from '@ckeditor/ckeditor5-utils/src/emittermixin';
- /**
- * 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.
- *
- * @private
- */
- export default class TextWatcher {
- /**
- * Creates a text watcher instance.
- * @param {module:core/editor/editor~Editor} editor
- * @param {Function} testCallback The function used to match the text.
- * @param {Function} textMatcherCallback The function used to process matched text.
- */
- constructor( editor, testCallback, textMatcherCallback ) {
- this.editor = editor;
- this.testCallback = testCallback;
- this.textMatcher = textMatcherCallback;
- this.hasMatch = false;
- this._startListening();
- }
- /**
- * The last matched text.
- *
- * @property {String}
- */
- get last() {
- return this._getText();
- }
- /**
- * Starts listening to the editor for typing and selection events.
- *
- * @private
- */
- _startListening() {
- const editor = this.editor;
- 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 ) {
- return;
- }
- this._evaluateTextBeforeSelection();
- } );
- editor.model.document.on( 'change:data', ( evt, batch ) => {
- if ( batch.type == 'transparent' ) {
- return false;
- }
- this._evaluateTextBeforeSelection();
- } );
- }
- /**
- * Checks the editor content for matched text.
- *
- * @fires matched
- * @fires unmatched
- *
- * @private
- */
- _evaluateTextBeforeSelection() {
- const text = this._getText();
- const textHasMatch = this.testCallback( text );
- if ( !textHasMatch && this.hasMatch ) {
- /**
- * Fired whenever the text does not match anymore. Fired only when the text watcher found a match.
- *
- * @event unmatched
- */
- this.fire( 'unmatched' );
- }
- this.hasMatch = textHasMatch;
- if ( textHasMatch ) {
- const matched = this.textMatcher( text );
- /**
- * Fired whenever the text watcher found a match.
- *
- * @event matched
- */
- this.fire( 'matched', { text, matched } );
- }
- }
- /**
- * Returns the text before the caret from the current selection block.
- *
- * @returns {String|undefined} The text from the block or undefined if the selection is not collapsed.
- * @private
- */
- _getText() {
- const editor = this.editor;
- const model = editor.model;
- const selection = model.document.selection;
- // Do nothing if the selection is not collapsed.
- if ( !selection.isCollapsed ) {
- return;
- }
- const rangeBeforeSelection = model.createRange( model.createPositionAt( selection.focus.parent, 0 ), selection.focus );
- return _getText( rangeBeforeSelection );
- }
- }
- /**
- * Returns the whole text from a given range by adding all data from the text nodes together.
- *
- * @protected
- * @param {module:engine/model/range~Range} range
- * @returns {String}
- */
- export function _getText( range ) {
- return Array.from( range.getItems() ).reduce( ( rangeText, node ) => {
- if ( node.is( 'softBreak' ) ) {
- // Trim text to softBreak
- return '';
- }
- return rangeText + node.data;
- }, '' );
- }
- mix( TextWatcher, EmitterMixin );
|