MediaWiki:DisambiguationManagement/code.js

/* DisambiguationManagement * * Creates a button in Special:Disambiguations that displays a specialized editor for redirecting disambiguation links. * This was a pain in the ass to make so please bear with me if you see any bugs, try to report them on the script talk page and I'll take a look * * @author Dorumin */

mw.loader.using('mediawiki.api').then(function {   if (wgCanonicalSpecialPageName != 'Disambiguations' || !$('.special').length || $('#dm-button').length) return;    if (typeof dev == 'undefined' || typeof dev.i18n == 'undefined') {        importArticle({ type: 'script', article: 'u:dev:MediaWiki:I18n-js/code.js' });   }    var Api = new mw.Api,    config = window.Disambiguations || {},    cache = {},    edit_index = 0,    load_index = 4,    load_icon = null, // set it later 'cause i18n hasn't loaded yet    summary = '',    init = false,    i18n;

function get_all_results(index, callback, merge) { if (cache._disambigs) { callback(cache._disambigs.pages, cache._disambigs.results); return; }       update_state(i18n.msg('state-loading-disambig-results').escape); Api.get({           action: 'query',            list: 'querypage',            qppage:'Disambiguations',            qplimit: 500,            qpoffset: index        }).done(function(data) {            if (data['query-continue']) {                get_all_results(index + 500, callback, data.query.querypage.results);                return;            }            var r = data.query.querypage.results.concat(merge ? merge : []),           ids = _.uniq(r.map(function(el) {                return el.value;            })),            disambigs = _.uniq(r.map(function(el) {                return el.title;            }));            Api.get({ action: 'query', prop: 'revisions', rvprop: 'ids', pageids: ids.join('|') }).done(function(p) { var pages = Object.keys(p.query.pages).map(function(key) {                   return p.query.pages[key].title;                }); cache._disambigs = { pages: pages, results: disambigs };               callback(pages, disambigs); });       });    }

function get_possible_alternatives(disambig, callback) { if (cache[disambig]) { callback(cache[disambig]); return; }       Api.get({            action: 'query',            prop: 'links',            titles: disambig        }).done(function(d) {            var r = d.query.pages,            p = r[Object.keys(r)[0]],            links = p.links.map(function(page) { return page.title; });           cache[disambig] = links;            callback(links);        }); }

function get_page_content(title, callback) { update_state(i18n.msg('state-getting-page-content').escape); if (cache[title]) { setTimeout(function { // wait for the call stack to clear               callback(cache[title]);            }, 0); return; }       Api.get({            action: 'query',            prop: 'revisions',            rvprop: 'content',            titles: title        }).done(function(d) {            var r = d.query.pages,            p = r[Object.keys(r)[0]],            content = p.revisions[0]['*'];            cache[title] = content;            callback(content);        }); }

function edit_page(title, content, callback) { Api.post({           action: 'edit',            title: title,            text: content,            summary: $('#dm-summary-input').val || $('#dm-summary-input').attr('placeholder') || '',            token: mw.user.tokens.get('editToken')        }).done(function(d) {            if (d.error) {                alert('Could not edit ' + title + '. (' + d.error.code + ': ' + d.error.info + ')');               return;            } else {                $('#dm-editor').removeClass('dm-loading');                $('#dm-editor-skip').click;            }        }); }

function get_disambig_links(content, disambigs) { disambigs = disambigs.concat(disambigs.map(function(disambig) { // I know it looks confusing, but trust me, it makes sense. return disambig.charAt(0).toLowerCase + disambig.slice(1); }));       var regex = new RegExp('\\[{2}(' + disambigs.join('|') + ')(?:\\|.*?)?\\]{2}', 'gi'), matches = [], m;       while ((m = regex.exec(content))) { if (m.index === regex.lastIndex) { regex.lastIndex++; }           matches.push(m); get_possible_alternatives(m[1], $.noop); // cache that thing }       return matches; }

function display_editor(title, content, matches) { var $editor_wrap = $(' ', {           id: 'dm-editor',            'data-content': content,            'data-title': title,            append: [                $(' ', { css: { paddingBottom: '1em' }               }).append( i18n.msg('editing-header', title).escape, $('', {                       class: 'button',                        id: 'dm-editor-skip',                        text: i18n.msg('skip-button-text').plain,                        click: function(e) {                            if ($(e.target).attr('disabled') || $(e.target).closest($editor_wrap).hasClass('dm-loading')) return;                            load_index++;                            edit_index++;                            $editor_wrap.addClass('dm-loading');                            get_all_results(0, function(titles, disambigs) { var title = titles[edit_index], nextTitleToLoad = titles[load_index]; if (nextTitleToLoad) { get_page_content(nextTitleToLoad, $.noop); }                               get_page_content(title, function(content) {                                    var links = get_disambig_links(content, disambigs);                                    display_editor(title, content, links);                                    if (!titles[edit_index + 1]) {                                        $(e.target).attr('disabled', 'disabled');                                    }                                }); });                       }                    })                ),                $(' ', {                    id: 'dm-editor-editor' })           ]        }),        $editor = $editor_wrap.find('#dm-editor-editor'), lines = get_lines(content, matches); lines[0].forEach(function(line) {           $editor.append( $(' ', {                   class: 'dm-line dm-context',                    'data-index': line[0],                    append: $(' ', { text: line[1] })               })            );        });        lines[1].forEach(function(mainLine) {            var $el = $editor.find('.dm-context[data-index="' + mainLine[0] + '"]'),            html = $el.html,            escaped = escape_deep(mainLine[2][0]),            newHTML = html                .split(escaped)                .join( $(' ').append(                       $(' ', { class: 'dm-disambig-link', 'data-disambig': mainLine[2][1], 'data-match': mainLine[2][0], html: escaped })                   ).html );           $el                .attr('class', 'dm-line dm-main-line')                .empty                .html(newHTML);        }); $editor.find('.dm-disambig-link').mouseenter(function {           $('.alternative-picker').not('.not-hovered').remove;            var $this = $(this),            pos = $this.position,            $picker = $(' ', { class: 'alternative-picker not-hovered', append: $(' ', {                   class: 'custom-disambig-wrapper',                    append: $(' ', { class: 'custom-disambig-input', keydown: function(e) { if (e.which != 13) return; e.preventDefault; var val = this.value, title = val.split('|')[0], display = val.split('|')[1]; $this .attr('data-disambig', title) .html(                                    + escape_deep(display) : ) +                                    ''                                ); }                   }).attr('placeholder', i18n.msg('custom-input-placeholder').plain)                }), css: { top: pos.top + $this.height, left: pos.left - 20, backgroundColor: $('#dm-modal').css('background-color'), border: '1px solid ' + $('#dm-cancel-butt').css('border-color') },               mouseenter: function { $(this).toggleClass('not-hovered'); },               mouseleave: function { $(this).remove; }           });            $editor_wrap.append($picker);            get_possible_alternatives($this.data('disambig'), function(alts) { alts.forEach(function(title) {                   $picker.prepend( $(' ', {                           class: 'dm-picker-option',                            text: title,                            click: function {                                var match = $this.attr('data-match'),                                disambig = $this.data('disambig');                                $this                                    .attr('class', 'dm-solved-disambig-link')                                    .html( match .replace(                                           new RegExp('(\\[{2})(?:' +                                                 [                                                    disambig,                                                    disambig.charAt(0).toLowerCase + disambig.slice(1)                                                ].join('|') + ')(\\||\\]{2})'),                                            function(s, c1, c2) {                                                if (c2 == ']]') {                                                    return c1 + escape_deep(title) + '|' + disambig + c2;                                                }                                                return c1 + escape_deep(title) + c2;                                            }                                        ) )                                   .attr('data-disambig', title);                            }                        }) );               });            });        }).mouseleave($.debounce(500, function { $('.alternative-picker.not-hovered').remove; }));       $('#dm-modal-ajax, #dm-editor').replaceWith($editor_wrap); if (!lines[0].length) { update_state(i18n.msg('state-no-links-found').escape); setTimeout(function {               $('#dm-editor-skip').click;            }, 1000); return; }       i18n.useContentLang; // Switch to content lang for summary update_state($(' ', { id: 'dm-summary-input', val: summary || '', keyup: function { summary = this.value; }       }).attr('placeholder', i18n.msg('default-summary').plain)); i18n.useUserLang; }

function get_lines(content, matches) { var lines = [], mainLines = [], split = content.split('\n'); matches.forEach(function(match) {           var line = content.slice(0, match.index),            index = line.split('\n').length;            mainLines.push([index, split[index - 1], match]);            lines = lines.concat([index - 2, index - 1, index, index + 1, index + 2]); // yeah it's ugly but it gets the job done        }); lines = _.uniq(lines.filter(function(index) { return index > 0; })).map(function(idx) {           return [idx, split[idx - 1]];        }); return [lines, mainLines]; }

function escape_deep(input) { var text = document.createTextNode(input), span = document.createElement('span'); span.appendChild(text); return span.innerHTML; }

function update_links { var $editor = $('#dm-editor'), content = $editor.data('content'), title = $editor.data('title'), lines = $editor.find('.dm-main-line'), split = content.split('\n'); lines.each(function(i, line) {           var $line = $(this);            var index = $line.data('index') - 1,            cur = 0,            links = $line.find('.dm-solved-disambig-link, .dm-disambig-link');            links.each(function { var $link = $(this); split[index] = split[index].replace($link.data('match'), $link.text); cur++; });       });        $editor.addClass('dm-loading'); edit_page(title, split.join('\n')); }

function update_state(msg) { $('.state-indicator').html(msg); }

function show_modal { if ($('#dm-modal').length) return; $.showCustomModal(i18n.msg('modal-header').escape, load_icon, {           id: 'dm-modal',            buttons: [{                id: 'dm-cancel-butt',                message: i18n.msg('cancel-button-text').escape,                handler: function {                    $('#dm-modal').closeModal;                }            }, {                id: 'dm-update-butt',                message: i18n.msg('update-button-text').escape,                handler: update_links            }],            callback: function($modal) {                $modal.find('.modalToolbar').prepend(' ');                get_all_results(0, function(titles, disambigs) { if (init) { get_page_content(titles[edit_index], function(content) {                           update_state('');                            var links = get_disambig_links(content, disambigs);                            display_editor(titles[edit_index], content, links);                        }); } else { for (var i = 0; i < 5; i++) { var title = titles[i], start_title = titles[0]; init = true; get_page_content(title, i === 0 ? function(content) {                               var links = get_disambig_links(content, disambigs);                                display_editor(start_title, content, links);                            } : $.noop); // shut up editor }                   }                });            }        });    }    mw.hook('dev.i18n').add(function(lib) {        lib.loadMessages('DisambiguationManagement').done(function(_i18n) { i18n = _i18n; i18n.useUserLang; load_icon = $(' ', {               id: 'dm-modal-ajax',                src: window.stylepath + '/common/images/ajax.gif',                alt: i18n.msg('loading').plain,                title: i18n.msg('loading').plain            }); $('.mw-spcontent p').first.append(               $(' ').append( $('', {                       class: 'button',                        id: 'dm-button',                        text: i18n.msg('resolve-button-text').plain,                        click: show_modal                    }) )           );        });    });    // Adding some CSS to make it look extra sexy importArticles({       type: 'style',        articles: [            'u:dev:MediaWiki:DisambiguationManagement/code.css'        ]    });

// And some dynamic css for good measure mw.util.addCSS('.dm-picker-option:hover {' +       'background-color: ' + $(' ', { class: 'accent', appendTo: 'body' }).css('background-color') +   '}'); $('.accent').last.remove; });