מדיה ויקי:Gadget-autocomplete.js

מתוך ויקיסוגיה
קפיצה לניווט קפיצה לחיפוש
הגרסה להדפסה אינה נתמכת עוד וייתכן שיש בה שגיאות תיצוג. נא לעדכן את הסימניות בדפדפן שלך ולהשתמש בפעולת ההדפסה הרגילה של הדפדפן במקום זה.

הערה: לאחר הפרסום, ייתכן שיהיה צורך לנקות את זיכרון המטמון (cache) של הדפדפן כדי להבחין בשינויים.

  • פיירפוקס / ספארי: להחזיק את המקש Shift בעת לחיצה על טעינה מחדש (Reload), או ללחוץ על צירוף המקשים Ctrl-F5 או Ctrl-R (במחשב מק: ⌘-R).
  • גוגל כרום: ללחוץ על צירוף המקשים Ctrl-Shift-R (במחשב מק: ⌘-Shift-R).
  • אינטרנט אקספלורר / אדג': להחזיק את המקש Ctrl בעת לחיצה על רענן (Refresh), או ללחוץ על צירוף המקשים Ctrl-F5.
  • אופרה: ללחוץ על Ctrl-F5.
/*
Autocomplete for links and templates
Written by [[משתמש:ערן]]
*/
mw.loader.using(['jquery.ui.widget', 'jquery.ui.autocomplete', 'jquery.textSelection'], function() {
    //extends jquery with autoCompleteWikiText functionality for autocomplete of links and templates
    $.fn.autoCompleteWikiText = function(options) {
        var mode = "none";
        var templateDataCache = {};
        var ctrl = $(this);
        var 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();
                var 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();
            var 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]];
                    var suggestions = [];
                    var 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;
            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.LEFT:
                        case keyCode.RIGHT:
                        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:
                            // 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.widget', 'jquery.ui.autocomplete', '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
            }
        });
    });