User:ESanders (WMF)/veSuggestions.js

// A temporary fork of https://de.wikipedia.org/wiki/Benutzer:Schnark/js/veSuggestions.js // Dokumentation unter Benutzer:Schnark/js/veSuggestions /* global mw, OO, ve, unicodeJS */ ( function {	'use strict';

// split a text into words function getWords( text ) { var ujsString = new unicodeJS.TextString( text ), pos, prevPos, len = text.length, words = []; for ( pos = 0; pos <= len; pos++ ) { if ( unicodeJS.wordbreak.isBreak( ujsString, pos ) ) { if ( prevPos !== undefined ) { words.push( text.slice( prevPos, pos ) ); }				prevPos = pos; }		}		return words; }

function CompletionSuggestionWidget( surface, options ) { CompletionSuggestionWidget.super.call( this );

this.surface = surface; this.surfaceModel = this.surface.getModel; this.$documentElement = this.surface.getView.getDocument.getDocumentNode.$element; this.options = $.extend( {			gap: 5, // how many pixels below the cursor to show the suggestions			words: [], // words to suggest			minWordLength: 4, // minimal length of a word to be added to suggestions			minFragmentLength: 3, // minimal length before suggestions are shown			maxSuggestions: 5, // maximal number of suggestions shown			delay: 20 // how long to delay before updating suggestions (in ms)		}, options ); this.surfaceModel.on( 'select', this.onInput.bind( this ) ); this.currentSuggestions = []; this.currentSuggestionIndex = 0; this.currentFragmentLength = 0;

this.$tabIndexed = this.$element;

this.menu = new OO.ui.MenuSelectWidget( {			widget: this,			$input: this.$documentElement,			width: 'auto'		} );

this.menu.connect( this, { choose: this.onMenuChoose } );

this.$element .append( this.menu.$element ) .css( { position: 'absolute', zIndex: 100 } ); }

OO.inheritClass( CompletionSuggestionWidget, OO.ui.Widget );

CompletionSuggestionWidget.prototype.updateDisplay = function { var selection = this.surfaceModel.getFragment.adjustLinearSelection( -this.currentFragmentLength, 0 ).getSelection, boundingRect = this.surface.getView.getSelection( selection ).getSelectionBoundingRect, css = {};

css.top = boundingRect.bottom + this.options.gap; if ( this.surface.getView.getDocument.getDir === 'rtl' ) { css.right = boundingRect.right; } else { css.left = boundingRect.left; }		this.$element.css( css ); this.menu .clearItems .addItems(				this.currentSuggestions.map( function ( word ) { return new OO.ui.MenuOptionWidget( { data: word, label: word } ); } )			);		if ( this.menu.getItems.length ) { this.menu.highlightItem( this.menu.getItems[ 0 ] ); }		this.menu.toggle( true ); };

CompletionSuggestionWidget.prototype.addWordsFromContent = function { this.addWords( getWords( mw.config.get( 'wgTitle' ) ) ); this.addWords( getWords( this.surfaceModel.getDocument.data.getText( true ) ) ); };

CompletionSuggestionWidget.prototype.addWord = function ( word ) { if (			word.length >= this.options.minWordLength &&			this.options.words.indexOf( word ) === -1		) { this.options.words.push( word ); }	};

CompletionSuggestionWidget.prototype.addWords = function ( words ) { var i;		for ( i = 0; i < words.length; i++ ) { this.addWord( words[ i ] ); }	};

CompletionSuggestionWidget.prototype.getSuggestionsFor = function ( fragment ) { var suggestions; if ( fragment.length < this.options.minFragmentLength ) { return []; }		suggestions = this.options.words.filter( function ( word ) {			return word.slice( 0, fragment.length ) === fragment && word !== fragment;		} ); // TODO sort the suggestions in a sensible way if ( suggestions.length > this.options.maxSuggestions ) { suggestions.length = this.options.maxSuggestions; }		return suggestions; };

CompletionSuggestionWidget.prototype.onMenuChoose = function ( item ) { var suggestion = item.getData; suggestion = suggestion.slice( this.currentFragmentLength ); this.surfaceModel.getFragment.collapseToEnd .insertContent( suggestion.split( '' ), true ) .collapseToEnd.select; this.menu.toggle( false ); };

CompletionSuggestionWidget.prototype.showSuggestions = function ( fragment ) { this.currentSuggestions = this.getSuggestionsFor( fragment ); this.currentSuggestionIndex = 0; this.currentFragmentLength = fragment.length; if ( this.currentSuggestions.length ) { this.updateDisplay; } else { this.menu.toggle( false ); }	};

CompletionSuggestionWidget.prototype.getCursorContext = function { var data, offset, wordRange;

data = this.surfaceModel.getDocument.data; offset = this.surfaceModel.getSelection.getRange; if ( !offset.isCollapsed ) { return {}; }		offset = offset.end; wordRange = data.getWordRange( offset ); if ( wordRange.start === offset ) { return { word: offset ? data.getText( false, data.getWordRange( offset - 1 ) ) : '' };		}		if ( wordRange.end === offset ) { return { word: data.getText( false, wordRange ), atWordbreak: true };		}		return {}; };

CompletionSuggestionWidget.prototype.debouncedInput = function { var context = this.getCursorContext; if ( context.atWordbreak ) { // we are at the end of a (partial) word, show suggestions this.showSuggestions( context.word ); return; } else if ( context.word ) { // user completed a word, add it to the list to suggest it in future this.addWord( context.word ); }		this.menu.toggle( false ); };

CompletionSuggestionWidget.prototype.onInput = function { if ( this.debouncedInputId ) { this.menu.toggle( false ); // it seems to take longer, so hide the outdated suggestions clearTimeout( this.debouncedInputId ); }		this.debouncedInputId = setTimeout( function {			this.debouncedInputId = false;			this.debouncedInput;		}.bind( this ), this.options.delay ); };

mw.hook( 've.activationComplete' ).add( function {		var surface = ve.init.target.getSurface,			csWidget = new CompletionSuggestionWidget( surface );		csWidget.addWordsFromContent;		surface.getLocalOverlay.$element.append( csWidget.$element );		// TODO other surfaces as well?	} );

} ); //