Fandom Developers Wiki
(Add new groups)
(Improve table HTML)
Line 539: Line 539:
 
return;
 
return;
 
}
 
}
return $('<td>', {
+
return $('<th>', {
 
'html': this.i18n(msg).plain()
 
'html': this.i18n(msg).plain()
 
});
 
});

Revision as of 15:25, 19 November 2019

/**
 * @name            PortableListUsers
 * @version         v1.0
 * @author          TheGoldenPatrik1
 * @author          Ursuul
 * @description     Alternative to Special:ListUsers.
 * @protect         <nowiki>
 */
require([
    'wikia.window',
    'jquery',
    'mw',
    'wikia.browserDetect',
    'ext.wikia.design-system.loading-spinner'
], function (window, $, mw, browserDetect, Spinner) {
    'use strict';
    if (window.PortableListUsersLoaded) {
        return;
    }
    window.PortableListUsersLoaded = true;
    /**
     * @class LU
     * @classdesc Central PortableListUsers class
     */
    var LU = {};
    /**
     * @type {Number}
     * @description Number of script preloads
     */
    LU.preloads = 4;
    /**
     * @type {Object}.{Object}
     * @description User groups
     */
    LU.users = {
        'authenticated': {},
        'bot': {},
        'bot-global': {},
        'bureaucrat': {},
        'chatmoderator': {},
        'content-moderator': {},
        'content-reviewer': {},
        'content-team-member': {},
        'content-volunteer': {},
        'council': {},
        'fandom-editor': {},
        'global-discussions-moderator': {},
        'helper': {},
        'request-to-be-forgotten-admin': {},
        'restricted-login': {},
        'restricted-login-exempt': {},
        'reviewer': {},
        'staff': {},
        'sysop': {},
        'threadmoderator': {},
        'util': {},
        'vanguard': {},
        'voldev': {},
        'vstf': {},
        'wiki-manager': {}
    };
    /**
     * @type {Object}.{Object}
     * @description Object for storing user data
     */
    LU.data = {};
    /**
     * @type {Object}
     * @description Object for storing cached jQuery values
     */
    LU.$ = {};
    /**
     * @type {Object}
     * @description Script configuration options
     */
    LU.options = $.extend(
        {
            avatars: true,
            editcount: '0',
            landing: 'sysop',
            storage: false,
            time: 'timeago'
            
        },
        window.PortableListUsers
    );
    /**
     * @type {Boolean}
     * @description Shortcut for mobile or not
     */
    LU.mobile = browserDetect.isMobile();
    /**
     * @method preload
     * @description Preloads the script
     * @returns {void}
     */
    LU.preload = function () {
        if (--this.preloads === 0) {
            this.api = new mw.Api();
            window.dev.i18n.loadMessages('PortableListUsers').then(
                $.proxy(this.init, this)
            );
        }
    };
    /**
     * @method init
     * @description Adds the link
     * @param {Object} i18n - Data from I18n-js
     * @returns {void}
     */
    LU.init = function (i18n) {
        this.i18n = i18n.msg;
        window.dev.placement.loader.util({
            script: 'PortableListUsers',
            element: 'tools',
            type: 'prepend',
            content: $('<li>', {
                'id': 'lu-link'
            }).append(
                $('<a>', {
                    'text': this.i18n('title').plain(),
                    'click': $.proxy(this.click, this)
                })
            )
        });
    };
    /**
     * @method click
     * @description Displays the modal/spinner
     * @returns {void}
     */
    LU.click = function () {
        if (this.modal) {
            this.modal.show();
            this.createContent();
        } else {
            $('<div>', {
                'id': 'lu-throbber',
                'html':
                    new Spinner(38, 2).html
                    .replace('wds-block', 'wds-spinner__block')
                    .replace('wds-path', 'wds-spinner__stroke')
                    .replace('stroke-width=""', 'stroke-width="3"')
            }).appendTo(document.body);
            this.$.spinner = $('#lu-throbber');
            this.getUsers();
        }
    };
    /**
     * @method getUsers
     * @description Gets all members of a usergroup
     * @returns {Function|void}
     */
    LU.getUsers = function () {
        this.$.spinner.show();
        var val1 = $('#lu-group-select').val();
        var sto = localStorage.getItem('PortableListUsers');
        if (val1) {
            this.currentGroup = val1;
        } else {
            if (this.options.storage && sto) {
                this.currentGroup = sto.split('|')[0];
            } else {
                this.currentGroup = this.options.landing;
            }
        }
        var val2 = $('#lu-edits-select').val();
        if (val2) {
            this.currentNumber = val2;
        } else {
            if (this.options.storage && sto) {
                this.currentNumber = sto.split('|')[1];
            } else {
                this.currentNumber = this.options.editcount;
            }
        }
        this.setStorage();
        var obj = this.currentGroup;
        if (this.isObject(this.users[obj])) {
            return this.getData();
        }
        this.api.get({
            action: 'query',
            list: 'groupmembers',
            gmgroups: obj,
            gmlimit: 'max'
        }).done(
            $.proxy(this.getData, this)
        );
    };
    /**
     * @method setStorage
     * @description Resets the localStorage
     * @returns {void}
     */
    LU.setStorage = function () {
        if (this.options.storage) {
            localStorage.setItem(
                'PortableListUsers',
                this.currentGroup + '|' + this.currentNumber
            );
        }
    };
    /**
     * @method isObject
     * @description Tests to see if the object is empty
     * @param {Object} obj - The object to test
     * @returns {Boolean}
     */
    LU.isObject = function (obj) {
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                return true;
            }
        }
        return false;
    };
    /**
     * @method getData
     * @description Processes usergroup data and gets more data
     * @param {JSON} d - Data from getUsers
     * @returns {Function|void}
     */
    LU.getData = function (d) {
        var obj = this.users[this.currentGroup];
        if (d) {
            if (d.error) {
                return;
            }
            $.each(d.users, $.proxy(function (k, v) {
                obj[v.name] = v.name;
            }, this));
        }
        if (!this.isObject(obj)) {
            return this.loadModal();
        }
        var users = Object.keys(obj).join('|');
        this.api.post({
            action: 'query',
            list: 'users|usercontribs',
            ususers: users,
            usprop: 'groups|editcount|gender|registration',
            ucuser: users,
            uclimit: 'max',
            ucprop: 'timestamp|title'
        }).done(
            $.proxy(this.loadData, this)
        );
    };
    /**
     * @method loadData
     * @description Loads the data from getData
     * @param {JSON} d - The data from getData
     * @returns {void}
     */
    LU.loadData = function (d) {
        if (d.error) {
            return;
        }
        $.each(d.query.users, $.proxy(function (k, v) {
            if (!this.data[v.name]) {
                this.data[v.name] = v;
            }
        }, this));
        $.each(d.query.usercontribs, $.proxy(function (k, v) {
            if (!this.data[v.user]) {
                return;
            }
            if (!this.data[v.user].timestamp) {
                this.data[v.user].timestamp = v.timestamp;
            }
            if (!this.data[v.user].title) {
                this.data[v.user].title = v.title;
            }
        }, this));
        this.loadModal();
    };
    /**
     * @method loadModal
     * @description Updates/creates the modal
     * @returns {void}
     */
    LU.loadModal = function () {
        if (this.modal) {
            this.currentNumber = this.$.edits.val() || this.options.editcount;
            this.setStorage();
            this.$.list.html('');
            this.createRow();
            this.addContent();
        } else {
            this.createModal();
        }
    };
    /**
     * @method createModal
     * @description Creates the script modal
     * @returns {void}
     */
    LU.createModal = function () {
        this.modal = new window.dev.modal.Modal({
            content: $('<div>', {
                'class':
                    'list-users list-users-' +
                    (this.mobile ? 'mobile' : 'desktop')
            }).append(
                this.createSelect('group'),
                this.createSelect('edits'),
                $('<span>', {
                    'id': 'lu-tally'
                }),
                $(this.mobile ? '<ul>' : '<table>', {
                    'class': this.mobile ? '' : 'article-table',
                    'id': this.mobile ? 'lu-list' : 'lu-table'
                })
            ).prop('outerHTML'),
            id: 'list-users',
            size: 'large',
            title: this.i18n('title').plain(),
            buttons: [
                {
                    text: this.i18n('close').plain(),
                    event: 'close'
                }
            ]
        });
        this.modal.create().then(
            $.proxy(this.createContent, this)
        );
        this.modal.show();
    };
    /**
     * @method createContent
     * @description Creates the modal's main content
     * @returns {void}
     */
    LU.createContent = function () {
        this.$.list = $(this.mobile ? '#lu-list' : '#lu-table');
        this.$.group = $('#lu-group-select');
        this.$.group.change(
            $.proxy(this.getUsers, this)
        );
        $.each(this.users, $.proxy(function (k, v) {
            this.$.group.append(
                $('<option>', {
                    'text': k,
                    'value': k,
                    'selected': this.currentGroup === k ? true : false
                })
            );
        }, this));
        this.$.edits = $('#lu-edits-select');
        this.$.edits.change(
            $.proxy(this.loadModal, this)
        );
        $.each([
            '0',
            '1',
            '5',
            '10',
            '20',
            '50',
            '100'
        ], $.proxy(function (k, v) {
            this.$.edits.append(
                $('<option>', {
                    'text': this.i18n('number', v).plain(),
                    'value': v,
                    'selected': this.currentNumber === v ? true : false
                })
            );
        }, this));
        this.createRow();
        this.addContent();
    };
    /**
     * @method addContent
     * @description Adds the user data
     * @returns {void}
     */
    LU.addContent = function () {
        $.each(this.data, $.proxy(function (k, v) {
            if (this.users[this.currentGroup][v.name || k]) {
                var count = v.editcount || '0';
                if (Number(count) < Number(this.currentNumber)) {
                    return;
                }
                this.createRow(
                    v.name || k,
                    v.timestamp,
                    v.title,
                    v.userid,
                    count,
                    v.gender,
                    v.registration,
                    v.groups
                );
            }
        }, this));
        $('#lu-tally').html(
            this.i18n(
                'tally',
                this.mobile ?
                this.$.list.find('li').length :
                (this.$.list.find('tr').length -1)
            ).plain()
        );
        $('img#lu-avatar').each(function () {
            $(this).error(function () {
                $(this).attr(
                    'src',
                    'https://vignette.wikia.nocookie.net/messaging/images/1/19/Avatar.jpg/revision/latest'
                );
            });
        });
        this.$.spinner.hide();
    };
    /**
     * @method createRow
     * @description Creates a table row or list item
     * @param {String} user
     * @param {String} timestamp
     * @param {String} title
     * @param {String} id
     * @param {Number|String} editcount
     * @param {String} gender
     * @param {String} registration
     * @param {Array} groups
     * @returns {void}
     */
    LU.createRow = function (user, timestamp, title, id, editcount, gender, registration, groups) {
        var item = this.mobile ? '<li>' : '<tr>';
        if (user) {
            this.$.list.append(
                $(item).append(
                    this.createItem(
                        $('<span>').append(
                            this.createLink(user, 'User:' + user),
                            ' (',
                            this.createLink(
                                this.i18n('talk').plain(),
                                'User talk:' + user
                            ),
                            ' | ',
                            this.createLink(
                                this.i18n('contribs').plain(),
                                'Special:Contributions/' + user
                            ),
                            ')'
                        ),
                        'user'
                    ),
                    this.createItem(
                        timestamp ?
                        this.createLink(this.timestamp(timestamp), title)  :
                        (editcount === '0' ? 'N/A' : timestamp),
                        'last-edited'
                    ),
                    this.createItem(
                        editcount ?
                        this.createLink(
                            editcount, 'Special:EditCount/' + user
                        )  :
                        editcount,
                        'editcount'
                    ),
                    this.createItem(gender, 'gender'),
                    this.createItem(
                        registration ?
                        this.timestamp(registration) :
                        registration,
                        'registration'
                    ),
                    this.createItem(
                        groups.join(', ')
                        .replace(', *', '').replace(', user', ''),
                        'groups'
                    ),
                    this.createItem(
                        id ? $('<img>', {
                            'src':
                                'https://services.fandom.com/user-avatar/user/' +
                                id + '/avatar',
                            'id': 'lu-avatar'
                        }) : id,
                        'avatar'
                    )
                )
            );
        } else if (!this.mobile) {
            this.$.list.append(
                $(item, {
                    'id': 'lu-table-first'
                }).append(
                    this.createSpecialItem('user'),
                    this.createSpecialItem('last-edited'),
                    this.createSpecialItem('editcount'),
                    this.createSpecialItem('gender'),
                    this.createSpecialItem('registration'),
                    this.createSpecialItem('groups'),
                    this.createSpecialItem('avatar')
                )
            );
        }
    };
    /**
     * @method createItem
     * @description Creates a div or td item
     * @param {String} html - The item's HTML
     * @param {String} id - The item's id
     * @returns {String}
     */
    LU.createItem = function (html, id) {
        if (this.mobile) {
            return $('<div>', {
                'id': 'lu-list-' + id,
                'html': html || this.i18n('unknown').plain()
            }).prepend(
                /user|groups|avatar/.test(id) ?
                '' :
                ('<b>' + this.i18n(id).escape() + '</b><br/>')
            );
        } else {
            if (id === 'avatar' && !this.options.avatars) {
                return;
            }
            return $('<td>', {
                'html': html || this.i18n('unknown').plain()
            });
        }
    };
    /**
     * @method createSpecialItem
     * @description Creates a different td item
     * @param {String} msg - The text
     * @returns {String}
     */
    LU.createSpecialItem = function (msg) {
        if (msg === 'avatar' && !this.options.avatars) {
            return;
        }
        return $('<th>', {
            'html': this.i18n(msg).plain()
        });
    };
    /**
     * @method createSelect
     * @description Creates a select item
     * @param {String} id - The select's id
     * @returns {String}
     */
    LU.createSelect = function (id) {
        return $('<select>', {
            'id': 'lu-' + id + '-select'
        });
    };
    /**
     * @method createLink
     * @description Creates an a item
     * @param {String} text - The link text
     * @param {String} href - The link href
     * @returns {String}
     */
    LU.createLink = function (text, href) {
        return $('<a>', {
            'text': text,
            'href': mw.util.getUrl(href)
        });
    };
    /**
     * @method timestamp
     * @description Processes timestamps
     * @param {String} time - The timestamp to process
     * @returns {String}
     */
    LU.timestamp = function (time) {
        var $time = new Date(time).toString();
        var formattedTime;
        if (this.options.time === 'timeago') {
            formattedTime = $.timeago(
                $time.slice(0, 3) + ', ' +
                $time.slice(4, 15) + ', ' +
                $time.slice(16, 24)
            );
        } else if (this.options.time === 'utc') {
            $time = new Date(time).toUTCString();
            formattedTime = $time.slice(0, 3) + ', ' +
                $time.slice(4, 16) + ', ' +
                $time.slice(17, 25) + ' (' +
                $time.slice(26) + ')';
        } else {
            formattedTime = $time.slice(0, 3) + ', ' +
                $time.slice(4, 15) + ', ' +
                $time.slice(16, 24) + ' ' +
                $time.slice(34);
        }
        return formattedTime;
    };
    mw.loader.using([
        'mediawiki.api',
        'mediawiki.util'
    ]).then(
        $.proxy(LU.preload, LU)
    );
    mw.hook('dev.i18n').add(
        $.proxy(LU.preload, LU)
    );
    mw.hook('dev.modal').add(
        $.proxy(LU.preload, LU)
    );
    mw.hook('dev.placement').add(
        $.proxy(LU.preload, LU)
    );
    importArticles(
        {
            type: 'script',
            articles: [
                'u:dev:MediaWiki:I18n-js/code.js',
                'u:dev:MediaWiki:Modal.js',
                'u:dev:MediaWiki:Placement.js'
            ]
        },
        {
            type: 'style',
            articles: ['u:dev:MediaWiki:PortableListUsers.css']
        }
    );
});