מדיה ויקי:Gadget-autocomplete.js: הבדלים בין גרסאות בדף
מ (השלמה אוטומטית) |
מ (46 גרסאות של הדף wikipedia:he:מדיה_ויקי:Gadget-autocomplete.js יובאו) |
||
(45 גרסאות ביניים של 2 משתמשים אינן מוצגות) | |||
שורה 3: | שורה 3: | ||
Written by [[משתמש:ערן]] | Written by [[משתמש:ערן]] | ||
*/ | */ | ||
mw.loader.using('jquery.ui. | 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([]); | |||
} | |||
var fixed= | } | ||
} | |||
function resolveTempalte(templateName) { | |||
var dfd = new jQuery.Deferred(); | |||
if (!templateName) return dfd.reject().promise(); | |||
if (templateDataCache[templateName]) return dfd.resolve().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(); | |||
} | |||
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); | |||
$.when(resolveTempalte(templateMatch && templateMatch[1])).done(function() { | |||
var curTemplateData = templateDataCache[templateMatch[1]], | |||
suggestions = [], | |||
curParamIndex = txt.split('|').length - 1; | |||
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; | |||
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.... | //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 | |||
} | |||
}); | |||
}); |
גרסה אחרונה מ־23:34, 4 בדצמבר 2022
/*
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().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();
}
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);
$.when(resolveTempalte(templateMatch && templateMatch[1])).done(function() {
var curTemplateData = templateDataCache[templateMatch[1]],
suggestions = [],
curParamIndex = txt.split('|').length - 1;
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;
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
}
});
});