Benutzer:Schnark/js/veSuggestions.js
Hinweis: Leere nach dem Veröffentlichen den Browser-Cache, um die Änderungen sehen zu können.
- Firefox/Safari: Umschalttaste drücken und gleichzeitig Aktualisieren anklicken oder entweder Strg+F5 oder Strg+R (⌘+R auf dem Mac) drücken
- Google Chrome: Umschalttaste+Strg+R (⌘+Umschalttaste+R auf dem Mac) drücken
- Edge: Strg+F5 drücken oder Strg drücken und gleichzeitig Aktualisieren anklicken
//Dokumentation unter [[Benutzer:Schnark/js/veSuggestions]] <nowiki>
/*global mediaWiki, OO, ve, unicodeJS*/
(function ($, mw) {
"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) {
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.$documentElement.on({
keydown: this.onDocumentKeyDown.bind(this),
keypress: this.onDocumentKeyPress.bind(this),
keyup: this.onDocumentKeyUp.bind(this),
click: this.onDocumentClick.bind(this),
blur: this.onDocumentBlur.bind(this)
});
this.isVisible = false;
this.currentSuggestions = [];
this.currentSuggestionIndex = 0;
this.currentFragmentLength = 0;
this.$element = $('<div>').on('click', this.onClick.bind(this))
.css({position: 'absolute', zIndex: 100}).appendTo('body');
}
CompletionSuggestionWidget.prototype.hide = function () {
if (this.isVisible) {
this.isVisible = false;
this.$element.hide();
}
};
CompletionSuggestionWidget.prototype.updateDisplay = function () {
var boundingRect = this.surface.getView().getSelection().getSelectionBoundingRect(),
offset = this.surface.$element.offset(),
selectedIndex = this.currentSuggestionIndex,
css = {};
css.top = boundingRect.bottom + this.options.gap + offset.top;
if (this.$documentElement.css('direction') === 'rtl') {
css.right = $(document).width() - boundingRect.left - offset.left;
} else {
css.left = boundingRect.left + offset.left;
}
this.$element.css(css);
//size=1 would create a dropdown, so use Math.max(2, length)
this.$element.html(mw.html.element('select', {size: Math.max(2, this.currentSuggestions.length)}, new mw.html.Raw(
this.currentSuggestions.map(function (word, i) {
return mw.html.element('option', {selected: i === selectedIndex}, word);
}).join('')
)));
if (!this.isVisible) {
this.isVisible = true;
this.$element.show();
}
};
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) {
if (fragment.length < this.options.minFragmentLength) {
return [];
}
var 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.insertSuggestion = function () {
var suggestion = this.currentSuggestions[this.currentSuggestionIndex];
suggestion = suggestion.slice(this.currentFragmentLength);
this.surfaceModel.getFragment().collapseToEnd()
.insertContent(suggestion.split(''), true)
.collapseToEnd().select();
this.hide();
};
CompletionSuggestionWidget.prototype.changeSelectedSuggestion = function (d) {
this.currentSuggestionIndex = (
this.currentSuggestionIndex + d +
this.currentSuggestions.length) % this.currentSuggestions.length;
this.updateDisplay();
};
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.hide();
}
};
CompletionSuggestionWidget.prototype.getCursorContext = function () {
var data, offset, wordRange, selection;
data = this.surfaceModel.getDocument().data;
selection = this.surfaceModel.getSelection();
if (!selection || !selection.getRange) {
return;
}
offset = selection.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) {
return;
}
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.hide();
};
CompletionSuggestionWidget.prototype.onInput = function () {
if (this.debouncedInputId) {
this.hide(); //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);
};
CompletionSuggestionWidget.prototype.onDocumentKeyDown = function (e) {
var handled = false;
if (this.isVisible) {
switch (e.keyCode) {
case OO.ui.Keys.UP:
this.changeSelectedSuggestion(-1);
handled = true;
break;
case OO.ui.Keys.DOWN:
this.changeSelectedSuggestion(1);
handled = true;
break;
case OO.ui.Keys.ENTER:
this.insertSuggestion();
handled = true;
break;
case OO.ui.Keys.ESCAPE:
this.hide();
handled = true;
break;
}
}
if (handled) {
e.preventDefault();
e.stopPropagation();
this.preventCurrentKey = true;
} else {
this.onInput();
}
};
CompletionSuggestionWidget.prototype.onDocumentKeyPress = function (e) {
if (this.preventCurrentKey) {
e.preventDefault();
e.stopPropagation();
}
};
CompletionSuggestionWidget.prototype.onDocumentKeyUp = function (e) {
if (this.preventCurrentKey) {
e.preventDefault();
e.stopPropagation();
this.preventCurrentKey = false;
}
};
CompletionSuggestionWidget.prototype.onDocumentClick = function () {
this.onInput();
};
CompletionSuggestionWidget.prototype.onDocumentBlur = function () {
window.setTimeout(function () {
this.hide();
}.bind(this), 200); //delay to have it still visible when the user clicks on a suggestion
};
CompletionSuggestionWidget.prototype.onClick = function () {
this.insertSuggestion();
};
mw.hook('ve.activationComplete').add(function () {
var csWidget = new CompletionSuggestionWidget(ve.init.target.getSurface());
csWidget.addWordsFromContent();
//TODO other surfaces as well?
});
})(jQuery, mediaWiki);
//</nowiki>