|
|
שורה 1: |
שורה 1: |
| /*
| | mw.loader.load('//he.wikipedia.org/w/load.php?modules=ext.gadget.autocomplete'); |
| Autocomplete for links and templates
| |
| Written by [[משתמש:ערן]]
| |
| */
| |
| mw.loader.using(['jquery.ui', 'jquery.textSelection'], function() { | |
| //extends jquery with autoCompleteWikiText functionality for autocomplete of links and templates
| |
| $.fn.autoCompleteWikiText = function(options) {
| |
| var mode = "none",
| |
| templateDataCache = {},
| |
| ctrl = $(this),
| |
| settings = $.extend(true, {
| |
| positionMy: $('body').is('.rtl') ? "left top" : "right top", // be default, open below the control
| |
| positionAt: $('body').is('.rtl') ? "left bottom" : "right bottom",
| |
| positionOf: ctrl,
| |
| positionOffset: "0",
| |
| filterResponse: function(a) {
| |
| return a;
| |
| }, // function that expects array of string and returns array of strings
| |
| menuCSS: {
| |
| width: 'auto',
| |
| maxHeight: '30em',
| |
| 'overflow-y': 'auto'
| |
| },
| |
| itemCSS: {
| |
| right: 'inherit'
| |
| },
| |
| onselected: function(item) {
| |
| var pos = ctrl.textSelection('getCaretPosition') - 1,
| |
| txt = ctrl.val(),
| |
| open, close, caretBackwards;
| |
| | |
| switch (mode) {
| |
| case "none":
| |
| return;
| |
| case "templateValue":
| |
| open = "|";
| |
| close = "";
| |
| caretBackwards = 0;
| |
| break;
| |
| case "templateParams":
| |
| open = "|";
| |
| close = "=";
| |
| caretBackwards = 0;
| |
| break;
| |
| case "template":
| |
| item = item.substr(mw.config.get('wgFormattedNamespaces')[10].length + 1);
| |
| caretBackwards = 2;
| |
| open = "{{";
| |
| close = "|}}";
| |
| break;
| |
| case "link":
| |
| open = "[[";
| |
| close = "]]";
| |
| caretBackwards = 0;
| |
| if (item[item.length - 1] == ')') item += '|';
| |
| break;
| |
| }
| |
| var lastbegin = txt.lastIndexOf(open, pos);
| |
| if (txt[lastbegin + 2] == ':')
| |
| item = ':' + item;
| |
| | |
| var newTxt = txt.substr(0, lastbegin) + open + item + close + txt.substr(pos + 1);
| |
| var orgScroll = ctrl.scrollTop();
| |
| ctrl.val(newTxt);
| |
| ctrl.textSelection('setSelection', {
| |
| start: lastbegin + (open + item + close).length - caretBackwards
| |
| });
| |
| ctrl.scrollTop(orgScroll);
| |
| }
| |
| }, options);
| |
| | |
| function findLinks(res) {
| |
| var pos = ctrl.textSelection('getCaretPosition') - 1;
| |
| var txt = ctrl.val();
| |
| | |
| var lastbegin = txt.lastIndexOf("[[", pos);
| |
| var lastend = txt.lastIndexOf("]]", pos);
| |
| var isLink = lastbegin > lastend;
| |
| if (isLink) {
| |
| mode = 'link';
| |
| fillLinksList(res, txt.substr(lastbegin + 2, pos - lastbegin));
| |
| } else {
| |
| lastbegin = txt.lastIndexOf("{{", pos);
| |
| lastend = txt.lastIndexOf("}}", pos);
| |
| var isTemplate = lastbegin > lastend;
| |
| if (isTemplate) {
| |
| var prefixName = mw.config.get('wgFormattedNamespaces')[10] + ':' + txt.substr(lastbegin + 2, pos - lastbegin - 1);
| |
| mode = (prefixName.indexOf('|') > -1) ? 'templateParams' : 'template';
| |
| fillLinksList(res, prefixName);
| |
| } else {
| |
| mode = "none";
| |
| res([]);
| |
| }
| |
| }
| |
| }
| |
| | |
| | |
| function resolveTempalte(templateName) {
| |
| var dfd = new jQuery.Deferred();
| |
| if (!templateName) return dfd.reject().promise();
| |
| if (templateDataCache[templateName]) return dfd.resolve(templateDataCache[templateName]).promise();
| |
| var api = new mw.Api();
| |
| api.get({
| |
| action: 'templatedata',
| |
| titles: templateName,
| |
| redirects: 1
| |
| }).done(function(data) {
| |
| if (!data.pages) return dfd.reject();
| |
| for (var pageid in data.pages) {
| |
| templateDataCache[templateName] = data.pages[pageid];
| |
| dfd.resolve(templateDataCache[templateName]);
| |
| }
| |
| if (!templateDataCache[templateName]) dfd.reject();
| |
| });
| |
| return dfd.promise();
| |
| }
| |
| | |
| function resolveApi(queryType, queryValue) {
| |
| var dfd = new jQuery.Deferred(),
| |
| api = new mw.Api();
| |
| | |
| switch (queryType) {
| |
| case 'users':
| |
| api.get({
| |
| action: 'query',
| |
| list: 'allusers',
| |
| auactiveusers: 1,
| |
| auprefix: queryValue
| |
| }).done(function(data) {
| |
| if (data && data.query && data.query.allusers) dfd.resolve($.map(data.query.allusers, function(e) {
| |
| return e.name;
| |
| }));
| |
| else dfd.reject();
| |
| });
| |
| break;
| |
| case 'pages':
| |
| api.get({
| |
| action: 'opensearch',
| |
| search: queryValue
| |
| }).done(function(data) {
| |
| if (data[1]) dfd.resolve(settings.filterResponse(data[1]));
| |
| else dfd.reject();
| |
| });
| |
| break;
| |
| default:
| |
| throw 'unexpected queryType';
| |
| }
| |
| | |
| return dfd.promise();
| |
| }
| |
| | |
| function fillLinksList(res, txt) {
| |
| txt = $.trim(txt);
| |
| if (txt.length <= 1 || (mode != 'templateParams' && txt.indexOf('|') > -1) || (txt.indexOf('#') > -1 && mw.config.get('wgNamespaceNumber') === 0)) res([]);
| |
| else if (mode === 'templateParams') {
| |
| var templateMatch = /(.+?)\|(?:.*\|)?([^=]+$)/.exec(txt);
| |
| var templateParamMatch = /(.+?)\|(?:.*\|)?([^=]+=[^=]+$)/.exec(txt);
| |
| | |
| $.when(resolveTempalte((templateMatch && templateMatch[1]) || (templateParamMatch && templateParamMatch[1]))).done(function(td) {
| |
| | |
| var curTemplateData = td,
| |
| suggestions = [],
| |
| curParamIndex = txt.split('|').length - 1;
| |
| if (templateParamMatch && !templateMatch) {
| |
| var paramNameVal = templateParamMatch[2].split('=');
| |
| if (paramNameVal[0] in curTemplateData.params && 'suggestedvalues' in curTemplateData.params[paramNameVal[0]]) {
| |
| mode = 'templateValue';
| |
| for (const suggestedVal of curTemplateData.params[paramNameVal[0]].suggestedvalues) {
| |
| if (suggestedVal.indexOf(paramNameVal[1])==0) suggestions.push(paramNameVal[0] + '=' + suggestedVal);
| |
| }
| |
| }
| |
| } else {
| |
| for (var paramName in curTemplateData.params) {
| |
| if (paramName == curParamIndex) {
| |
| var paramValue = templateMatch[2];
| |
| var dfd;
| |
| switch (curTemplateData.params[paramName].type) {
| |
| case 'wiki-page-name':
| |
| dfd = $.when(resolveApi('pages', paramValue));
| |
| mode = 'templateValue';
| |
| break;
| |
| case 'wiki-file-name':
| |
| dfd = $.when(resolveApi('pages', 'File:' + paramValue));
| |
| mode = 'templateValue';
| |
| break;
| |
| case 'wiki-template-name':
| |
| dfd = $.when(resolveApi('pages', 'Template:' + paramValue));
| |
| mode = 'templateValue';
| |
| break;
| |
| case 'wiki-user-name':
| |
| dfd = $.when(resolveApi('users', paramValue));
| |
| mode = 'templateValue';
| |
| break;
| |
| case 'string':
| |
| | |
| if ('suggestedvalues' in curTemplateData.params[paramName]){
| |
| mode = 'templateValue';
| |
| for (const suggestedVal of curTemplateData.params[paramName].suggestedvalues) {
| |
| if (suggestedVal.indexOf(paramValue)==0) suggestions.push(suggestedVal);
| |
| }
| |
| return res(suggestions);
| |
| } else
| |
| return res([]);
| |
| default:
| |
| return res([]); // dont suggest for this indexed param
| |
| }
| |
| return dfd.done(res).fail(function() {
| |
| res([]);
| |
| });
| |
| }
| |
| if (paramName === '1' || txt.indexOf(paramName) > -1) continue; //dont suggest used params
| |
| suggestions.push(paramName);
| |
| }
| |
| }
| |
| res(suggestions);
| |
| }).fail(res);
| |
| } else if (txt.indexOf('#') > -1) {
| |
| var pageTitle = txt.substr(0, txt.indexOf('#'));
| |
| var sectionPrefix = txt.substr(txt.indexOf('#') + 1);
| |
| var api = new mw.Api();
| |
| api.get({
| |
| action: 'parse',
| |
| page: pageTitle,
| |
| prop: 'sections'
| |
| }).done(function(data) {
| |
| if (data && data.parse && data.parse.sections) res($(data.parse.sections).map(function() {
| |
| return this.line.indexOf(sectionPrefix) == 0 ? (pageTitle + '#' + this.line.replace(/[|\[\]\{\}]/g, escape)) : null;
| |
| }));
| |
| });
| |
| } else {
| |
| $.when(resolveApi('pages', txt)).done(res).fail(function() {
| |
| res([])
| |
| });
| |
| }
| |
| }
| |
| | |
| ctrl.autocomplete({
| |
| source: function(request, response) {
| |
| if (fixArrowsBug(this))
| |
| response([]);
| |
| else
| |
| findLinks(response);
| |
| },
| |
| focus: function() {
| |
| return false;
| |
| },
| |
| select: function(e, ui) {
| |
| settings.onselected(ui.item.value);
| |
| return false;
| |
| },
| |
| open: function() {
| |
| $(".ui-autocomplete")
| |
| .css(settings.menuCSS)
| |
| .position({
| |
| my: settings.positionMy,
| |
| at: settings.positionAt,
| |
| of: settings.positionOf,
| |
| offset: settings.positionOffset,
| |
| collision: 'none fit'
| |
| })
| |
| .find('li').css(settings.itemCSS);
| |
| }
| |
| });
| |
| var fixed, stfu, escapes = 0;
| |
| //this is hack to prevent known serious bug in autocomplete.js that prevent default of the up and down key which may drive you crazy....
| |
| function fixArrowsBug(self) {
| |
| if (fixed) return false;
| |
| fixed = true;
| |
| | |
| // on click selection may change. close the menu
| |
| ctrl.on("click.autocomplete", function(e){
| |
| clearTimeout(self.searching);
| |
| self.close();
| |
| });
| |
| ctrl.off("keydown.autocomplete");
| |
| ctrl.off("keydown.autocomplete0");
| |
| ctrl.on("keydown.autocomplete",
| |
| function(event) {
| |
| var keyCode = $.ui.keyCode;
| |
| // hack to allow cancelling the gadget: mostly useful when editing templates.
| |
| escapes = event.keyCode == keyCode.ESCAPE ? escapes + 1 : 0;
| |
| if ( stfu || ( stfu = escapes >= 3 ) ) {
| |
| self.close(event);
| |
| return;
| |
| }
| |
| switch (event.keyCode) {
| |
| case keyCode.PAGE_UP:
| |
| self._move("previousPage", event);
| |
| break;
| |
| case keyCode.PAGE_DOWN:
| |
| self._move("nextPage", event);
| |
| break;
| |
| case keyCode.UP:
| |
| if (!self.menu.element.is(":visible")) return;
| |
| self._move("previous", event);
| |
| // prevent moving cursor to beginning of text field in some browsers
| |
| event.preventDefault();
| |
| break;
| |
| case keyCode.DOWN:
| |
| if (!self.menu.element.is(":visible")) return;
| |
| self._move("next", event);
| |
| // prevent moving cursor to end of text field in some browsers
| |
| event.preventDefault();
| |
| break;
| |
| case keyCode.ENTER:
| |
| case keyCode.NUMPAD_ENTER:
| |
| // when menu is open or has focus
| |
| if (self.menu.active) {
| |
| event.preventDefault();
| |
| }
| |
| //passthrough - ENTER and TAB both select the current element
| |
| case keyCode.TAB:
| |
| if (!self.menu.active) {
| |
| return;
| |
| }
| |
| self.menu.select(event);
| |
| break;
| |
| case keyCode.ESCAPE:
| |
| self.element.val(self.term);
| |
| self.close(event);
| |
| break;
| |
| | |
| case keyCode.SHIFT:
| |
| case keyCode.CONTROL:
| |
| case keyCode.ALT:
| |
| case keyCode.COMMAND:
| |
| case keyCode.COMMAND_RIGHT:
| |
| case keyCode.INSERT:
| |
| case keyCode.CAPS_LOCK:
| |
| case keyCode.END:
| |
| case keyCode.HOME:
| |
| case keyCode.LEFT:
| |
| case keyCode.RIGHT:
| |
| // ignore metakeys (shift, ctrl, alt)
| |
| break;
| |
| default:
| |
| // keypress is triggered before the input value is changed
| |
| clearTimeout(self.searching);
| |
| self.searching = setTimeout(function() {
| |
| self.search(null, event);
| |
| }, self.options.delay);
| |
| break;
| |
| }
| |
| });
| |
| return true;
| |
| }
| |
| }
| |
| });
| |
| | |
| if ($.inArray(mw.config.get('wgAction'), ['edit', 'submit']) + 1)
| |
| mw.loader.using(['jquery.ui', 'jquery.textSelection'], function() {
| |
| //enable autocomplete for editbox, relative to editform in an offset of -80 vertical
| |
| $("#wpTextbox1").autoCompleteWikiText({
| |
| positionAt: $('#wpTextbox1').prop('dir') == 'rtl' ? "left top" : "right top",
| |
| positionOf: '#editform',
| |
| positionOffset: "0 0",
| |
| menuCSS: {
| |
| background: '#E0EEF7',
| |
| opacity: 0.8
| |
| },
| |
| itemCSS: {
| |
| padding: 0,
| |
| margin: 0
| |
| }
| |
| });
| |
| });
| |