MediaWiki:Gadget-lastedited.js

From Galaxy Life Wiki
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
(function() {
    'use strict';
	mw.loader.load( 'https://hollowknight.wiki/mw/index.php?title=MediaWiki:Modal.js&action=raw&ctype=text/javascript' );

	function loadTimeaAgo() {
		const js = document.createElement('script'),
			head = document.head;

		js.setAttribute('src', 'https://timeago.yarp.com/jquery.timeago.js');
		js.setAttribute('type', 'text/javascript');

		head.append(js);
	};

	loadTimeaAgo();

    /**
     * Main object
     * @class lastEdited
     */
    var lastEdited = {
        // Cached mw.config values
        config: mw.config.get([
            'stylepath',
            'wgAction',
            'wgArticleId',
            'wgFormattedNamespaces',
            'wgIsMainPage',
            'wgMainPageTitle',
            'wgNamespaceNumber',
            'wgPageName',
            'wgUserGroups',
            'wgUserName'
        ]),
        // Configuration options
        options: $.extend({
            size: true,
            diff: true,
            diffModal: true,
            comment: true,
            newpage: true,
            mainpage: true,
            time: 'timestamp',
			timezone: 'UTC',
            position: {
                element: '',
                method: ''
            },
            namespaces: {
                exclude: [-1, 1201, 2001]
            },
            pages: []
        }, window.lastEdited),
        // The amount of dependencies to load
        loaded: 2,
        // If the user can rollback edits
        canRollback: /(sysop|soap|staff|content-moderator|rollback|wiki-representative|wiki-specialist|content-volunteer)/.test(mw.config.get('wgUserGroups').join(' ')),
        /**
         * Initializes everything
         */
        init: function() {
            window.lastEditedLoaded = true;
            this.api = new mw.Api();
            this.pageName = this.config.wgPageName.replace(/_/g, ' ');
            this.insert();
            var preload = this.preload.bind(this);
            mw.hook('dev.modal').add(preload);

			// this.preload doesn't seem to execute so I just copied it's contents without the if statement here.
			$.when(
				this.fetch(),
				mw.loader.using([
					'mediawiki.diff.styles'
				])
			).then(this.render.bind(this));
        },
        preload: function() {
            if (--this.loaded === 0) {
                $.when(
                    this.fetch(),
                    mw.loader.using([
                        'mediawiki.diff.styles'
                    ])
                ).then(this.render.bind(this));
            }
        },
        /**
         * Checks whether the script should run further or not
         * @return {Boolean} If the script should run further
         */
        shouldRun: function() {
            var allowed = Object.keys(this.config.wgFormattedNamespaces).map(Number),
                ns = this.options.namespaces;
            if (ns && ns.exclude instanceof Array) {
                allowed = allowed.filter(function(elem) {
                    return ns.exclude.indexOf(elem) < 0;
                });
            }
            return !mw.util.getParamValue('diff') &&
                   !mw.util.getParamValue('oldid') &&
                   allowed.indexOf(this.config.wgNamespaceNumber) !== -1 &&
                   this.options.pages.indexOf(this.config.wgPageName) === -1 &&
                   (
                       // The script is allowed to run on the main page.
                       this.options.mainpage ||
                       // The current page is not the main page
                       this.pageName !== this.config.wgMainPageTitle
                    ) &&
                   this.config.wgAction === 'view' &&
                   !window.lastEditedLoaded &&
                   this.config.wgArticleId !== 0;
        },
        /**
         * Inserts the placeholder for last edit information
         */
        insert: function() {
            var $loader = $('<div>', {
                id: 'lastEdited',
                'class': 'lastEdited'
            }).append(
                $('<span>', {
                    'class': 'mw-ajax-loader',
                    'id': 'lastEdited-loading'
                })
            );
            var pos = this.options.position;
            if (pos.element && pos.method) {
                var $el = $(pos.element),
                    m = pos.method;
                if ($el.length && (m === 'append' || m === 'prepend')) {
                    $el[m]($loader);
                }
            } else {
                $loader.insertAfter('.firstHeading');
            }
            this.$content = $loader;
            mw.hook('LastEdited.inserted').fire($loader);
        },
        /**
         * Fetches last edit information from the API
         * @returns {jQuery.Deferred} A Promise-like object
         */
        fetch: function() {
            var tokentype = this.canRollback ? 'rollback' : undefined;

            return this.api.get({
                action: 'query',
                titles: this.config.wgPageName,
                meta: 'tokens',
                prop: 'revisions',
                rvprop: 'ids|timestamp|user|userid|size|parsedcomment|flags',
                rvlimit: 2,
                rvtoken: tokentype,
                type: tokentype,
                formatversion: 2
            }).then(this.fetchPreviousDiff.bind(this));
        },
        /**
         * Fetches the previous diff for each revision.
         * @returns {jQuery.Deferred} A Promise-like object
         */
        fetchPreviousDiff: function(data) {
            var revisions = data.query.pages[0].revisions;
            var promises = revisions.map(function(revision) {
                return this.api.get({
                    action: 'compare',
                    fromrev: revision.revid,
                    torelative: 'prev',
                    prop: 'diff|ids',
                    formatversion: 2
                }).then(function(data1) {
                    var compare = data1.compare;
                    revision.diff = {
                        from: compare.fromrevid,
                        to: compare.torevid,
                        body: compare.body
                    };
                });
            }, this);
            return $.when.apply($, promises).then(function() {
                return data;
            });
        },
        /**
         * Renders last edited information
         * @param {Object} data Edit information obtained from the API
         * @param {Object} modal Modal generator obtained from UI factory
         */
        render: function(data) {
            var diffData = data.query.pages[0].revisions;
            if (!diffData[1] && !this.options.newpage) {
                this.$content.remove();
                return;
            }
            var prev = diffData[1],
                curr = diffData[0];
            if (data.query.tokens) {
                curr.rollbacktoken = data.query.tokens.rollbacktoken;
            }
            if (prev) {
                this.createModal(curr);
            }
            this.$content.html('');
            ['UserTime', 'Diff', 'Minor', 'Comment', 'Size'].forEach(function(el) {
                this.$content.append(this['render' + el](curr, prev));
            }, this);
            mw.hook('LastEdited.render').fire(this.$content);
        },
        /**
         * Returns HTML for a link to a page
         * containing a user's username
         * Utility function for renderUserTime
         * @returns {String} HTML for an <a> tag
         */
        userLink: function(prefix, user, text) {
            return mw.html.element('a', { href: mw.util.getUrl(prefix + user) }, text);
        },
        /**
         * Renders user and time links
         * @param {Object} data Edit information obtained from the API
         * @returns {Array} Parts to append to last edited information
         */
        renderUserTime: function(data) {
            // Build user links
            var user = data.user,
            links = this.userLink('User:', user, user) +
                    '<span class="mw-usertoollinks"> (' +
                    this.userLink('User talk:', user, 'talk') +
                    ' | ' +
                    this.userLink('Special:Contributions/', user, 'contribs');
            if (/(bureaucrat|sysop|wiki-representative|wiki-specialist|soap|staff)/.test(this.config.wgUserGroups.join(' '))) {
                links += ' | ' + this.userLink('Special:Block/', user, 'block');
            }
            links += ')</span>';
            // Build time
            var $time = $('<span>', {
                'class': 'lastEdited-timeago',
                title: data.timestamp
            });
            if (this.options.time === 'timestamp') {
                var date = new Date(data.timestamp).toString();
                if (this.options.timezone && this.options.timezone === 'UTC') {
                    date = new Date(data.timestamp).toUTCString();
                }
                if (this.options.timezone && this.options.timezone === 'locale') {
                    date = new Date(data.timestamp).toLocaleString();
                    $time.text(date);
                }
                else {
                    $time.text(date.slice(0, 3) + ', ' + date.slice(4, 16) + ', ' + date.slice(17, 26));
                }
            } else {
                $time.timeago();

                if($time.html() == "") {
                	// Custom fix since mediawiki's timeago (specifically $.timeago.inWords) doesn't seem to like values greater than 30 days?
                	// this is the same exact function, but without that time check
                	var customInWords = function(t) {
						// if (t <= 2592e6) {
							var e = false;

							$.timeago.settings.allowFuture && (t < 0 && (e = !0), t = Math.abs(t));

							var r = t / 1e3, a = r / 60, i = a / 60, n = i / 24, o = n / 365;

							return r < 45 && u("second", Math.round(r)) || r < 90 && u("minute", 1) || a < 45 && u("minute", Math.round(a)) || a < 90 && u("hour", 1) || i < 24 && u("hour", Math.round(i)) || i < 48 && u("day", 1) || n < 30 && u("day", Math.floor(n)) || n < 60 && u("month", 1) || n < 365 && u("month", Math.floor(n / 30)) || o < 2 && u("year", 1) || u("year", Math.floor(o));
						// }
						function u(t, r) {
							return mw.message(e ? "timeago-" + t + "-from-now" : "timeago-" + t, r).parse();
						}
					};

					var myDate = $.timeago.parse(data.timestamp);
					var timeDistance = new Date().getTime() - myDate.getTime();
					// This doesn't seem to work se we'll remove it atm.
					$time.html( customInWords(timeDistance) );
                }
            }
            return [
                'Last edited by $1 $2'
                    .replace('$1', links)
                    .replace('$2', $time.prop('outerHTML'))
            ];
        },
        /**
         * Renders the diff link
         * @param {Object} data Edit information obtained from the API
         * @returns {Array} Parts to append to last edited information
         */
        renderDiff: function(data) {
            if (this.options.diff && data.diff.from) {
                var link = $('<a>', {
                    id: 'lastEdited-diff-link',
                    href: '?diff=' + data.diff.to,
                    text: 'diff',
                    title: 'Special:Diff/' + data.diff.to
                });
                if (this.options.diffModal) {
                    link.attr('data-disable-quickdiff', '');
                    link.click((function(e) {
                        e.preventDefault();
                        this.modal.show();
                    }).bind(this));
                }
                return [
                    ' (',
                    link,
                    ')'
                ];
            }
            return [];
        },
        /**
         * Renders the "m" sign next to minor edits
         * @param {Object} data Edit information obtained from the API
         * @returns {Array} Parts to append to last edited information
         */
        renderMinor: function(data) {
            if (data.minor === '') {
                return [
                    ' ',
                    $('<span>', {
                        id: 'lastEdited-minor',
                        text: '[m]'
                    })
                ];
            }
            return [];
        },
        /**
         * Renders the last edit summary used
         * @param {Object} data Edit information obtained from the API
         * @returns {Array} Parts to append to last edited information
         */
        renderComment: function(data) {
            var comment = data.parsedcomment;
            if (this.options.comment && comment) {
                return [
                    '<br />',
                    'Edit summary',
                    ': ',
                    comment.indexOf('Created page with') === -1 ? comment : 'Created page.'
                ];
            }
            return [];
        },
        /**
         * Renders the size of the last diff
         * @param {Object} data Edit information obtained from the API
         * @param {Object} prev Information about the previous edit from the API
         * @returns {Array} Parts to append to last edited information
         */
        renderSize: function(data, prev) {
            if (!this.options.size) {
                return [];
            }
            var arr = [
                '<br />',
                'Current size',
                ': ',
                data.size,
                ' ',
                'bytes'
            ];
            if (prev) {
                var bytes = data.size - prev.size,
                    classes = 'mw-plusminus-' + (bytes > 0 ?
                        'pos' :
                        bytes < 0 ?
                            'neg' :
                            'null');
                if (Math.abs(bytes) > 500) {
                    classes += ' lastEdited-diff-major';
                }
                arr.push(
                    ' ',
                    $('<span>', {
                        text: '(' + (bytes > 0 ? '+' : '') + bytes.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ')',
                        'class': classes
                    })
                );
            }
            return arr;
        },
        /**
         * (Re)generates the diff modal
         * @param {Object} data Edit information obtained from the API
         * @param {Object} modal Modal generator obtained from UI factory
         */
        createModal: function(data) {
            var buttons = [
                {
                    event: 'link',
                    text: 'Link'
                },
                {
                    event: 'undo',
                    text: 'Undo'
                }
            ];
            if (this.canRollback && this.config.wgUserName !== data.user) {
                buttons.push({
                    event: 'rollback',
                    text: 'Rollback'
                });
            }
            this.modal = new window.dev.modal.Modal({
                buttons: buttons,
                content: '<div id="lastEdited-diff-changes" class="page-content">' +
                             '<table class="diff">' +
                                 data.diff.body +
                             '</table>' +
                         '</div>',
                context: this,
                events: {
                    link: function() {
                        this.modal.close();
                        window.open(mw.util.getUrl('', {
                            diff: data.diff.to
                        }), '_blank');
                    },
                    rollback: function() {
                        this.api.post({
                            action: 'rollback',
                            title: this.config.wgPageName,
                            user: data.user,
                            token: data.rollbacktoken,
                            format: 'json'
                        }).done(function(d) {
                            if (!d.error) {
                                window.location.reload();
                            }
                        });
                    },
                    undo: function() {
                        this.modal.close();
                        window.open(mw.util.getUrl(this.config.wgPageName, {
                            action: 'edit',
                            undoafter: data.diff.from,
                            undo: data.diff.to
                        }), '_blank');
                    }
                },
                id: 'lastEdited-diff',
                size: 'full',
                title: 'Changes: ' + this.pageName
            });
            this.modal.create();
        }
    };
    mw.loader.using([
        'mediawiki.api',
        'mediawiki.util',
        (mw.config.get('isGamepedia') ? 'jquery.timeago' : 'jquery')
    ]).then(function() {
        if (lastEdited.shouldRun()) {
            $(lastEdited.init.bind(lastEdited));
        }
    });
})();