MediaWiki:QuickLogs/whatever.js

/* QuickLogs * * Displays user logs on Special:Contributions * Awaiting merge. * * Original script by Slyst */

(function {   // Scoping and double runs    if ( mw.config.get('wgCanonicalSpecialPageName') != 'Contributions' || window.QuickLogs && window.QuickLogs.loaded ) return;

var QuickLogs = { /// The name of the user whose contributions you're looking at       user: mw.config.get('wgTitle').split('/')[1] || decodeURIComponent($.getUrlVar('target')), /// The wikia user ID of the user you're looking at       /// Only is defined when the current user is a chat moderator or above, /// but that's fine because you only need it in those cases userId: $('.chat-change-ban').data('user-id'), /// Plain object holding mediawiki config values cfg: mw.config.get([           'wgWikiaChatWindowFeatures',            'wgUserGroups',            'wgUserName',            'stylepath'        ]), /// Reference to #quicklogs-ul ul: null, /// Double-run safety net loaded: true, /// Whether render was called or not built: false, /// Whether message walls are enabled in the current wiki walls: false, /// Whether Abuse Filter is enabled in the current wiki abuseFilter: false, /// Internal variable for handling preloading _loads: 0, /// Called each time a resource is fetched preload: function { if (++this._loads === 5) { dev.i18n.loadMessages('QuickLogs').done(this.init.bind(this)); }       },        /// Returns whether you're looking at your own contributions ownPage: function { return this.user == this.cfg.wgUserName; },       /// Returns whether you're not looking at your contribs and another function of passed notOwnPageAnd: function(check) { return !this.ownPage && check.bind(this); },       /// Returns whether the current user has any of the rights provided hasRights: function(rights) { var len = rights.length; while (len--) { if (this.cfg.wgUserGroups.indexOf(rights[len]) != -1) return true; }           return false; },       /// Returns whether the user has classic admin rights isAdmin: function { return this.hasRights(['bureaucrat', 'sysop', 'helper', 'vstf', 'staff', 'global-discussions-moderator']); },       /// Returns whether the user has classic chat mod rights isChatMod: function { return this.hasRights(['chatmoderator', 'threadmoderator', 'sysop', 'helper', 'staff', 'vstf']); },       /// Returns whether the user has any roles that could be self-removed, or a helper/staff/vstf hasNotableRoles: function { return this.hasRights([               'bureaucrat',                'sysop',                'content-moderator',                'threadmoderator',                'chatmoderator',                'rollback',                'bot',                'global-discussions-moderator',                'helper',                'staff',                'vstf'            ]); },       /// Returns an anchor element with the supplied href and text, optionally striked-through makeLink: function(href, text, striked) { return dev.ui({               type: 'a',                classes: striked === false ? [] : ['link'],                attr: {                    href: href                },                text: text            }); },       /// Returns an anchor element that calls this.load on click makeLogLink: function(logType) { var log = this.logTypes[logType]; return dev.ui({               type: 'a',                classes: ['quicklogs'].concat(log.classes || []),                attr: {                    'data-log': logType                },                events: {                    click: this.load.bind(this, logType),                },                text: log.message || this.i18n.msg(log.key || logType + '-log').plain            }); },       /// Toggles the selected class and calls the custom click handler or the default this.loadLogs load: function(logType) { $('.quicklogs.selected').removeClass('selected'); $('.quicklogs[data-log="' + logType + '"]').addClass('selected'); var log = this.logTypes[logType]; if (log.click) { return log.click(logType, log); }           return this.loadLogs(logType, log); },       /// Clears the content of this.ul, begins a loading animation and sets the header text to loading startLoading: function { this.ul.innerHTML = ''; this.header.textContent = this.i18n.msg('loading').plain; dev.ui({               type: 'img',                style: {                    display: 'block',                    margin: '0 auto'                },                attr: {                    src: this.cfg.stylepath + '/common/images/ajax.gif'                },                parent: this.ul            }); },       /// Loads classic log events by type loadLogs: function(logType, log) { this.startLoading; var obj = { action: 'query', list: 'logevents', leprop: 'title|user|timestamp|type|parsedcomment|details', lelimit: 'max' };           if (logType != 'logs') { obj.letype = logType; }           if (log.by) { obj.leuser = log.by; }           if (log.title) { obj.letitle = 'User:' + log.title; }           return this.api.get(obj) .then(this.displayLogs.bind(this, logType, log)); },       /// Loads abuse log entries loadAbuseLog: function(logType, log) { this.startLoading; this.api.get({               action: 'query',                list: 'abuselog',                afluser: this.user,                afllimit: 'max',                aflprop: 'ids|filter|user|title|action|result|timestamp|hidden'            }) .then(this.displayLogs.bind(this, logType, log)); },       /// Loads deletedrevs entries loadDeletedContribs: function(logType, log) { this.startLoading; this.api.get({               action: 'query',                list: 'deletedrevs',                druser: this.user,                drlimit: 'max',                drprop: 'parsedcomment'            }) .then(this.displayLogs.bind(this, logType, log)); },       /// Displays any kind of log event displayLogs: function(logType, log, data) { var le = data.query && data.query.logevents || data.query.abuselog || data.query.deletedrevs, children = [];

if (data.error) { children.push(dev.ui({ type: 'li', text: this.i18n.msg('error', data.error.info, data.error.code).plain }));               this.header.textContent = this.i18n.msg('error-header', logType).plain; } else if ($.isEmptyObject(le)) { children.push(dev.ui({ type: 'li', text: this.i18n.msg('no-logs').plain }));               this.header.textContent = this.i18n.msg('logs-header-selected', logType).plain; } else { for (var i in le) { children.push(dev.ui({ type: 'li', html: this.format(le[i]) }));               }                if (data['query-continue']) { children.push(dev.ui({ type: 'li', children: [ {                               type: 'a', attr: { href: { logs: '/wiki/Special:Log/' + mw.util.wikiUrlencode(this.user), upload: '/wiki/Special:ListFiles/' + mw.util.wikiUrlencode(this.user), abuse: '/wiki/Special:AbuseLog?wpSearchUser=' + mw.util.wikiUrlencode(this.user) }[logType] || '/wiki/Special:Logs/' + logType + '?' + $.param({                                           user: log.by,                                            title: log.title                                        }), },                               text: this.i18n.msg('see-more').plain }                       ]                    }));                }                this.header.textContent = this.i18n.msg('logs-header-selected', logType).plain; }

this.ul.innerHTML = ''; dev.ui({               type: '#document-fragment',                children: children,                parent: this.ul            }); },       /// Formats a log event into an HTML string format: function(ev) { var date = this.date(ev.timestamp || ev.revisions[0].timestamp), userLinks = this.userLinks(ev.user, true), target = dev.ui({               type: 'a',                attr: { href: '/wiki/User:' + this.user },                text: this.user            }).outerHTML, page = dev.ui({               type: 'a',                attr: { href: '/wiki/' + mw.util.wikiUrlencode(ev.title) },                text: ev.title            }).outerHTML, newTitle = !ev.move ? '' : dev.ui({               type: 'a',                attr: { href: '/wiki/' + mw.util.wikiUrlencode(ev.move.new_title) },                text: ev.move.new_title            }).outerHTML, comment = !ev.parsedcomment ? '' : dev.ui({               type: 'span',                classes: ['comment'],                html: '(' + ev.parsedcomment + ')'            }).outerHTML, list = !ev.filter ? '' : dev.ui({               type: 'ul',                children: [                    {                        type: 'li',                        text: this.i18n.msg('action', ev.action).plain                    },                    {                        type: 'li',                        text: this.i18n.msg('actions-taken', ev.result).plain                    },                    {                        type: 'li',                        text: this.i18n.msg('description', ev.filter).plain                    }                ]            }).outerHTML, oldrights = ev.rights && ev.rights.old || this.i18n.msg('none').escape, newrights = ev.rights && ev.rights.new || this.i18n.msg('none').escape, before = ev[0], after = ev[1], expiry = ev.action.indexOf('chatban') === 0 && ev[2] ? ev[2] : '', ends = ev.action.indexOf('chatban') === 0 && ev[3] ? this.date(ev[3] * 1000) : '', duration = ev.block && ev.block.duration ? ev.block.duration : '', flags = ev.block && ev.block.flags ? ' (' + ev.block.flags.replace(/,/g, ', ') + ') ' : '';

if (ev.move && ev.move.suppressedredirect) { ev.action += '-without-redirect'; }

if (ev.action == 'move_redir') { ev.action = 'move'; }

if (ev.action == 'patrol' && ev.patrol.auto) { ev.action += '-auto'; }

if (ev.filter) { ev.action = 'abuse'; }

return this.parse('format-' + ev.action, {               date: date,                userLinks: userLinks,                target: target,                page: page,                newTitle: newTitle,                comment: comment,                oldrights: oldrights,                newrights: newrights,                expiry: expiry,                duration: duration,                flags: flags,                list: list,                ends: ends,                before: before,                after: after            }); },       /// Parses named parameters that aren't currently supported in i18n-js parse: function(msg, obj) { return this.i18n.msg(msg).escape.replace(/\$([a-z]+)/gi, function(fullMatch, identifier) {               return obj[identifier] || '';            }).trim.replace(/(\s)+/g, function(_, first) {                return first;            }); },       /// Returns a date string defined by the specification in format-date date: function(timestamp) { var date = new Date(timestamp), pad = function(n) { return ('0' + n).slice(-2); };           return this.parse('format-date', {                hours: pad(date.getUTCHours),                minutes: pad(date.getUTCMinutes),                seconds: pad(date.getUTCSeconds),                date: date.getUTCDate,                year: date.getUTCFullYear,                month: wgMonthNames[date.getUTCMonth + 1]            }); },       /// Generates a link to {user}'s userpage /// If subpages is true then there will be additional talk, contribs, and block (if admin) links. userLinks: function(user, subpages) { var encodedName = mw.util.wikiUrlencode(user), nodes = [ dev.ui({                   type: 'a',                    attr: { href: '/wiki/User:' + encodedName },                    text: user                }) ];           if (subpages) { nodes.push(                   ' (', this.walls ? {                           type: 'a', attr: { href: '/wiki/Message_Wall:' + encodedName }, text: this.i18n.msg('wall').plain }                     : {                            type: 'a', attr: { href: '/wiki/Message_Wall:' + encodedName }, text: this.i18n.msg('talk').plain },                   ' | ',                    {                        type: 'a', attr: { href: '/wiki/Special:Contributions/' + encodedName }, text: this.i18n.msg('contribs').plain }               );                if (this.isAdmin) {                    nodes.push( ' | ',                       {                            type: 'a', attr: { href: '/wiki/Special:Block/' + encodedName }, text: this.i18n.msg('block').plain }                   );                }                nodes.push(')'); }           return dev.ui({                type: 'div', // Document fragments don't implement innerHTML, much sad                children: nodes            }).innerHTML; },       /// Copied straight over from ext.Chat2.ChatBanModal, I'm not touching thas shit showBanModal: function { var data = {}, okCallback = function(expires, reason) { $.post(wgScript + '?action=ajax&rs=ChatAjax&method=blockOrBanChat', {                   userToBanId: this.userId,                    time: expires,                    reason: reason,                    mode: 'global',                    token: mw.user.tokens.get('editToken')                }, function {                    window.location.reload;                }); }.bind(this); $.get(window.wgScript + '?action=ajax&rs=ChatAjax&method=BanModal', data, function(data) {               require(['wikia.ui.factory'], function(uiFactory) { uiFactory.init(['modal']).then(function(uiModal) {                       var banModalConfig = {                            type: 'default',                            vars: {                                id: 'ChatBanModal',                                size: 'small',                                content: data.template,                                title: mw.message('chat-ban-modal-change-ban-heading').plain,                                buttons: [{                                    vars: {                                        value: data.isChangeBan ? mw.message('chat-ban-modal-button-change-ban').escaped : mw.message('chat-ban-modal-button-ok').escaped,                                        classes: ['normal', 'primary'],                                        data: [{                                            key: 'event',                                            value: 'ok' }]                                   }                                }, {                                    'vars': { 'value': mw.message('chat-ban-modal-button-cancel').escaped, data: [{ key: 'event', value: 'close' }]                                   }                                }]                            }                        };                        uiModal.createComponent(banModalConfig, function(banModal) {                            var reasonInput = banModal.$element.find('input[name=reason]');                            function banUser {                                var reason = reasonInput.val                                 , expires = banModal.$element.find('select[name=expires]').val;                                okCallback(expires, reason);                                banModal.trigger('close');                            }                            reasonInput.placeholder.keydown(function(e) { if (e.which === 13) { e.preventDefault; banUser; }                           });                            banModal.bind('ok', function(event) { event.preventDefault; banUser; });                           banModal.show;                        }); });               });            });        },        /// Renders the QuickLogs UI inside #contentSub        /// IMPORTANT: This function has the potential of being called multiple times, in case new quick links are registered,        /// or called directly by another script to refresh the UI.        render: function {            var i = 0,            children = [                this.i18n.msg('for').plain + ' ',                this.makeLink('/wiki/User:' + this.user, this.user),                ' (' ],           contentSub = document.getElementById('contentSub'), logType, log;

if (!contentSub) return; // wot

while (logType = this.logOrder[i++]) { log = this.logTypes[logType];

if (!log.check || log.check) { if (log.href) { children.push(this.makeLink(log.href, log.message || this.i18n.msg(log.key).plain, log.striked)); } else { children.push(this.makeLogLink(logType)); }                   children.push(' | '); }           }

contentSub.innerHTML = ''; contentSub.className += ' quicklogs__content-sub'; children[children.length - 1] = ')';           dev.ui({ type: '#document-fragment', children: children, parent: contentSub });

if (!this.built) { this.ul = dev.ui({                   type: 'ul',                    children: [                        {                            type: 'li',                            text: this.i18n.msg('select-logs').plain                        }                    ]                }); this.header = dev.ui({                   type: 'div',                    attr: {                        id: 'quicklogs-head'                    },                    text: this.i18n.msg('logs-header').plain                }); contentSub.parentElement.insertBefore(                   dev.ui({ type: 'div', attr: { // This is dumb, this class is already used in contentSub, but I'm keeping it for legacy reasons // Just make sure you use the ID to get this element 'class': 'quicklogs__content-sub', id: 'quicklogs-container' },                       children: [ this.header, {                               type: 'div', attr: { // SYKE IT WASNT AN UL, IT WAS JUST A CONTAINER id: 'quicklogs-ul' },                               children: [this.ul] }                       ]                    }),                    contentSub.nextElementSibling                ); }

this.built = true; mw.hook('QuickLogs.render').fire(QuickLogs); },       /// Adds hooks that will be run immediately, since we asserted the load order with this.preload addHooks: function { mw.hook('dev.wgMessageWallEnabled').add(function(result) {               this.walls = result;                if (this.logTypes.wall) {                    this.logTypes.wall.key = result ? 'wall' : 'talk';                    this.logTypes.wall.href = result                        ? '/wiki/Message_Wall:' + this.user                        : '/wiki/User_talk:' + this.user;                }            }.bind(this)); mw.hook('dev.abuseFilterEnabled').add(function(result) {               this.abuseFilter = result;            }.bind(this)); },       /// Function to be called after all necessary resources have been fetched init: function(i18n) { this.i18n = i18n; this.api = new mw.Api; i18n.useUserLang; this.addHooks; this.render; }   };

QuickLogs.logTypes = $.extend({       wall: {            href: '/wiki/User_talk:' + QuickLogs.user,            key: 'talk'        },        blockLink: {            check: QuickLogs.notOwnPageAnd.bind(QuickLogs, QuickLogs.isAdmin),            href: '/wiki/Special:Block/' + QuickLogs.user,            key: 'block'        },        block: {            title: QuickLogs.user        },        upload: {            by: QuickLogs.user        },        move: {            by: QuickLogs.user        },        logs: {            by: QuickLogs.user,            key: 'logs'        },        chatban: {            title: QuickLogs.user        },        abuse: {            check: function { return QuickLogs.abuseFilter },            click: QuickLogs.loadAbuseLog.bind(QuickLogs)        },        rights: {            title: QuickLogs.user        },        deletedcontribs: {            check: QuickLogs.isAdmin.bind(QuickLogs), click: QuickLogs.loadDeletedContribs.bind(QuickLogs) },       rightsLink: { check: function { return QuickLogs.isAdmin || QuickLogs.ownPage && QuickLogs.hasNotableRoles; },           href: '/wiki/Special:UserRights/' + QuickLogs.user, key: 'rights-management' },       chatbanLink: { check: QuickLogs.notOwnPageAnd.bind(QuickLogs, QuickLogs.isChatMod), click: QuickLogs.showBanModal.bind(QuickLogs), striked: false, key: 'ban-from-chat' }   }, (window.QuickLogs || {}).logTypes);    QuickLogs.logOrder = ['wall', 'blockLink', 'block', 'upload', 'move', 'logs', 'abuse', 'rights', 'deletedcontribs', 'rightsLink', 'chatban', 'chatbanLink'];

// Expose resources window.QuickLogs = QuickLogs; mw.hook('QuickLogs.loaded').fire(QuickLogs); // Loading necessary resources importArticles(       {            type: 'script',            articles: [                'u:dev:MediaWiki:I18n-js/code.js',                'u:dev:MediaWiki:UI-js/code.js'            ]        },        {            type: 'style',            articles: [                'u:dev:MediaWiki:QuickLogs/code.css'            ]        }    );

// Fork from WgMessageWallsExist because such script does not provide suitable bindings var wallHook = mw.hook('dev.wgMessageWallEnabled'); $.nirvana .getJson('WikiFeaturesSpecialController', 'index') .done(function(d) {           var disabled =                d.features.filter(function (t) { return t.name === 'wgEnableWallExt' && t.enabled; }).length === 0;

if (disabled) { wallHook.fire(false); } else { wallHook.fire(true); }       })        .error(function { var wall = '.wds-global-navigation__dropdown-link[data-tracking-label="account.message-wall"]';

if (document.querySelector(wall) === null) { wallHook.fire(false); } else { wallHook.fire(true); }       });    // Check if AbuseFilter is enabled    $.get('/api.php?action=query&meta=siteinfo&siprop=extensions&format=json')        .done(function(d) { mw.hook('dev.abuseFilterEnabled').fire(               d.query.extensions.filter(function(ext) { return ext.name == 'Abuse Filter'; }).length > 0           ); });   mw.hook('dev.ui').add(QuickLogs.preload.bind(QuickLogs));    mw.hook('dev.i18n').add(QuickLogs.preload.bind(QuickLogs));    mw.hook('dev.abuseFilterEnabled').add(QuickLogs.preload.bind(QuickLogs));    mw.hook('dev.wgMessageWallEnabled').add(QuickLogs.preload.bind(QuickLogs));    mw.loader.using(['mediawiki.api']).then(QuickLogs.preload.bind(QuickLogs)); });