User:MSchottlender-WMF/WhoWroteThatGadget.js

(function{function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})({1:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

/** * An activation singleton, responsible for activating and attaching the * button that activates the system when it is applicable. * * @class */ var ActivationSingleton = /*#__PURE__*/ function { /**  * Initialize if it is the first time, and cache the * initialization for the next times. */ function ActivationSingleton { _classCallCheck(this, ActivationSingleton);

if (!ActivationSingleton.instance) { this.initialized = false; this.link = null; this.namespace = null; this.mainPage = false; this.$contentWrapper = null; this.$originalContent = null; this.lang = 'en'; this.translations = {}; ActivationSingleton.instance = this; }

return ActivationSingleton.instance; } /**   * Check if the current page is valid * for the system to display. *  * @return {boolean} Page is valid */

_createClass(ActivationSingleton, [{   key: "isValidPage",    value: function isValidPage {      return !!( // Has the needed parser content this.$contentWrapper.length && // Is in the main namespace this.namespace === '' && // Is not main page !this.mainPage);   }    /**     * Set the class properties.     * This is separated from initialization for cleanliness     * and to make sure tests can run with different parameters.     *     * @param {jQuery} $content Page content     * @param {Object} config Configuration options     * @param {string} [config.lang="en"] User interface language     * @param {Object} [config.translations={}] Object with all translations     *  organized by language key with data of translation key/value pairs.     * @param {string} [config.namespace] Page namespace. Falls back to reading     *  directly from mw.config     * @param {string} [config.mainPage] Whether the current page is the main page     *  of the wiki. Falls back to reading directly from mw.config     */

}, {   key: "setProperties", value: function setProperties($content, config) { this.$contentWrapper = $content; this.$originalContent = this.$contentWrapper.clone; this.lang = config.lang || 'en'; this.namespace = config.namespace || ''; this.mainPage = !!config.mainPage; this.translations = config.translations || {}; }   /**     * Initialize the process *    * @param {jQuery} $content Page content * @param {Object} config Configuration options */

}, {   key: "initialize", value: function initialize($content, config) { this.setProperties($content, config);

if (this.initialized || !this.isValidPage) { return; } // This validation is for tests, where // mw is not defined. We don't care to test // whether messages are set properly, since that // has its own tests in the mw.messsages bundle

if (window.mw) { // Load all messages mw.messages.set(Object.assign({}, // Manually create fallback on English this.translations.en, this.translations[this.lang])); // Add a portlet link to 'tools'

this.link = mw.util.addPortletLink('p-tb', '#', mw.msg('ext-whowrotethat-activation-link'), 't-whowrotethat', mw.msg('ext-whowrotethat-activation-link-tooltip')); this.initialized = true; }   }    /**     * Set the link text and tooltip. * @param {boolean} [active] The state to toggle to. */

}, {   key: "toggleLink", value: function toggleLink(active) { var anchor = this.link.querySelector('a');

if (active) { anchor.textContent = mw.msg('ext-whowrotethat-deactivation-link'); anchor.title = ''; } else { anchor.textContent = mw.msg('ext-whowrotethat-activation-link'); anchor.title = mw.msg('ext-whowrotethat-activation-link-tooltip'); }   }    /**     * Load the required dependencies for the full script *    * @return {jQuery.Promise} Promise that is resolved when * all dependencies are ready and loaded. */

}, {   key: "loadDependencies", value: function loadDependencies { return $.when($.ready, // jQuery's document.ready     mw.loader.using([// MediaWiki dependencies 'oojs-ui', 'oojs-ui.styles.icons-user', 'oojs-ui.styles.icons-interactions', 'mediawiki.interface.helpers.styles', 'mediawiki.special.changeslist', 'mediawiki.widgets.datetime', 'moment'])); }   /**     * Get the jQuery object representing the activation button *    * @return {jQuery} Activation button */

}, {   key: "getButton", value: function getButton { return $(this.link); }   /**     * Get the content wrapper for the current page *    * @return {jQuery} Content wrapper */

}, {   key: "getContentWrapper", value: function getContentWrapper { return this.$contentWrapper; }   /**     * Get the original content of the article *    * @return {jQuery} Original content */

}, {   key: "getOriginalContent", value: function getOriginalContent { return this.$originalContent; } }]);

return ActivationSingleton; }; // Initialize the singleton

var activationInstance = new ActivationSingleton; var _default = activationInstance; exports["default"] = _default;

},{}],2:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

/** * Interface to the [WikiWho](https://www.wikiwho.net/) WhoColor API. * * @class */ var Api = /*#__PURE__*/ function { /** * @param {Object} config * @cfg config.url The WikiWho base URL. * @cfg config.mwApi The mw.Api instance. * @constructor */ function Api { var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

_classCallCheck(this, Api);

// Remove trailing slash from URL. this.url = (config.url || ).replace(/\/$/, ); this.mwApi = config.mwApi; this.results = null; } /**   * Fetch core messages needed for the revision popup, etc., making them available to mw.msg. * This is called just after the script is first loaded, and the request completes very quickly, * so we shouldn't need to bother with promises and such. */

_createClass(Api, [{   key: "fetchMessages",    value: function fetchMessages {      this.mwApi.loadMessages(['parentheses-start', 'talkpagelinktext', 'pipe-separator', 'contribslink', 'parentheses-end']);    }    /**     * Get parsed edit summary for the given revision.     * @param {number} revId     * @return {Promise} Resolving Object with keys 'comment' and 'size'.    	 */

}, {   key: "fetchEditSummary", value: function fetchEditSummary(revId) { return this.mwApi.ajax({       action: 'compare',        fromrev: revId,        torelative: 'prev',        prop: 'parsedcomment|size',        formatversion: 2      }).then(function (data) {        if (data.compare) {          return {            comment: data.compare.toparsedcomment,            size: data.compare.tosize - (data.compare.fromsize || 0)          };        }

return $.Deferred.reject; }, function (failData) { return $.Deferred.reject(failData); });   }    /**     * Get the value of a parameter from the given URL query string.     *     * @protected     * @param  {string} querystring URL query string     * @param  {string} param Parameter name     * @return {string|null} Parameter value; null if not found     */

}, {   key: "getQueryParameter", value: function getQueryParameter(querystring, param) { var urlParams, regex, results;

if (querystring === '') { return null; }

try { urlParams = new URLSearchParams(querystring); return urlParams.get(param); } catch (err) { // Fallback for IE and Edge // eslint-disable-next-line no-useless-escape param = param.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); regex = new RegExp("[?&]".concat(param, "=([^&#]*)")); results = regex.exec(querystring); return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' ')); }   }    /**     * Get a WhoColor API URL based on a given wiki URL. *    * @param  {string} wikiUrl URL of the wiki page that we want to analyze. * @return {string} Ajax URL for the data from WhoColor. */

}, {   key: "getAjaxURL", value: function getAjaxURL(wikiUrl) { var parts, oldId, title, lang, matches, queryString, linkNode = document.createElement('a'); linkNode.href = wikiUrl; queryString = linkNode.search; title = this.getQueryParameter(queryString, 'title');

if (title) { // URL is like: https://en.wikipedia.org/w/index.php?title=Foo&oldid=123 matches = linkNode.hostname.match(/([a-z]+)\.wiki.*/i); lang = matches[1]; } else { // URL is like: https://en.wikipedia.org/wiki/Foo matches = wikiUrl.match(/:\/\/([a-z]+).wikipedia.org\/wiki\/([^#?]*)/i); lang = matches[1]; title = matches[2]; }

parts = [this.url, lang, 'whocolor/v1.0.0-beta', title]; // Add oldid if it's present.

oldId = this.getQueryParameter(queryString, 'oldid');

if (oldId) { parts.push(oldId); } // Compile the full URL.

return parts.join('/') + '/'; }   /**     * Extract token and editor IDs from a WikiWho span element with `id='token-X'` and * `class='token-editor-Y'` attributes. *    * @param {HTMLElement} element * @return {Object} An object with two parameters: tokenId and editorId (string). */

}, {   key: "getIdsFromElement", value: function getIdsFromElement(element) { var out = { tokenId: false, editorId: false },         tokenMatches = element.id.match(/token-(\d+)/), editorMatches = element.className.match(/token-editor-([^\s]+)/);

if (tokenMatches && tokenMatches[1]) { out.tokenId = parseInt(tokenMatches[1]); }

if (editorMatches && editorMatches[1]) { out.editorId = editorMatches[1]; }

return out; }   /**     * Get the WikiWho replacement for `.mw-parser-output` HTML. * @return {string} */

}, {   key: "getReplacementHtml", value: function getReplacementHtml { return this.results.extended_html; }   /**     * Get user and revision information for a given token. *    * @param {number} tokenId * @return {{revisionId: *, score: *, userId: *, username: *, revisionTime: *}|boolean} Object * that represents the token info or false if a token wasn't found. */

}, {   key: "getTokenInfo", value: function getTokenInfo(tokenId) { var revId, revision, username, score; // Get the token information. results.tokens structure: // [ [ conflict_score, str, o_rev_id, in, out, editor/class_name, age ], ... ]     // e.g. Array(7) [ 0, "indicate", 769691068, [], [], "18201938", 76652371.587203 ]

var token = this.results.tokens[tokenId];

if (!token) { return false; } // Get revision information. results.revisions structure: // { rev_id: [ timestamp, parent_rev_id, user_id, editor_name ], ... }     // e.g. Array(4) [ "2017-03-11T02:12:47Z", 769315355, "18201938", "Biogeographist" ]

revId = token[2]; revision = this.results.revisions[revId]; username = revision[3]; // Get the user's edit score (percentage of content edited). // results.present_editors structure: // [ [ username, user_id, score ], ... ]

for (var i = 0; i < this.results.present_editors.length; i++) { if (this.results.present_editors[i][0] === username) { score = parseFloat(this.results.present_editors[i][2]).toFixed(1); break; }     } // Put it all together.

return { username: username, userId: token[5], revisionId: revId, revisionTime: new Date(revision[0]), score: score };   }    /**     * Get the WikiWho data for a given wiki page. *    * @param {string} wikiUrl URL of the wiki page that we want to analyze. * @return {Promise} A promise that resolves when the data is ready, * or rejects if there was an error. */

}, {   key: "getData", value: function getData(wikiUrl) { var api = this;

if (this.resultsPromise) { return this.resultsPromise; }

this.resultsPromise = $.getJSON(this.getAjaxURL(wikiUrl)).then(function (result) {       // Handle error response.        if (!result.success) {          // The API gives us an error message, but we don't use it because it's only          // in English. Some of the error messages are:          // * result.info: "Requested data is not currently available in WikiWho          //   database. It will be available soon."          // * result.error: "The article (x) you are trying to request does not exist in          //   english Wikipedia."          // We do add the full error details to the console, for easier debugging.          var errCode = result.info && result.info.match(/data is not currently available/i) ? 'refresh' : 'contact';          window.console.error('WhoWroteThat encountered a "' + errCode + '" error:', result);          return $.Deferred.reject(errCode);        } // Store all results.

api.results = result; }, function (jqXHR) { // All other errors are likely to be 4xx and 5xx, and the only one that the user // might be able to recover from is 429 Too Many Requests. var errCode = 'contact';

if (jqXHR.status === 429) { errCode = 'later'; }

return $.Deferred.reject(errCode); });     return this.resultsPromise;    }  }]);

return Api; };

var _default = Api; exports["default"] = _default;

},{}],3:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

var _config = _interopRequireDefault(require("./config"));

var _Api = _interopRequireDefault(require("./Api"));

var _InfoBarWidget = _interopRequireDefault(require("./InfoBarWidget"));

var _RevisionPopupWidget = _interopRequireDefault(require("./RevisionPopupWidget"));

var _ActivationSingleton = _interopRequireDefault(require("./ActivationSingleton"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

/** * Application class, responsible for running, activating, * and toggling the entire application. * * @class */ var App = /*#__PURE__*/ function { /**  * Only instantiate once, so the initialization doesn't run again * even if this is called on multiple clicks/calls *  * @constructor * @param {App} A class instance */ function App { _classCallCheck(this, App);

// Instantiate only once if (!App.instance) { this.initialized = false; App.instance = this; }

this.revisionPopup = new _RevisionPopupWidget["default"]; return App.instance; } /**   * Initialize the application */

_createClass(App, [{   key: "initialize",    value: function initialize {      // Only initialize once      if (this.initialized) {        return;      }

this.widget = new _InfoBarWidget["default"]({       state: 'pending'      }); this.api = new _Api["default"]({       url: _config["default"].wikiWhoUrl,        mwApi: new mw.Api      }); // Pull in necessary core messages.

this.api.fetchMessages; this.widget.setState('pending'); // Attach widget

if ($('body').hasClass('skin-timeless')) { $('#mw-content-wrapper').prepend(this.widget.$element); } else { $('#content').prepend(this.widget.$element); } // Attach events

this.widget.on('close', this.onWidgetClose.bind(this)); this.initialized = true; }   /**     * Run the application. * This performs the initialization for the first time * and then can do the toggling when and if the activation * button is clicked multiple times, toggling the state * on and off and on again. */

}, {   key: "start", value: function start { var _this = this;

this.initialize; // Close if already open.

if (this.widget.isVisible) { this.onWidgetClose; return; } // Otherwise, proceed to open and fetch data.

this.widget.toggle(true);

_ActivationSingleton["default"].toggleLink(true);

this.api.getData(window.location.href).then( // Success handler.     function  {        // The widget might have been closed since getData began.        if (!_this.widget.isVisible) {          return;        } // Insert modified HTML.

$('.mw-parser-output').html(_this.api.getReplacementHtml); $('body').append(_this.revisionPopup.$element);

_this.attachContentListeners;

_this.widget.setState('ready'); }, // Error handler. function (errorCode) { _this.widget.setState('err');

_this.widget.setErrorMessage(errorCode); });   }    /**     * Activate all the spans belonging to the given user.     * @param {number} editorId     */

}, {   key: "activateSpans", value: function activateSpans(editorId) { $('.token-editor-' + editorId).addClass('active'); }   /**     * Deactivate all spans. */

}, {   key: "deactivateSpans", value: function deactivateSpans { $('.mw-parser-output .editor-token').removeClass('active'); }   /**     * Add listener to highlight attribution and show the RevisionPopupWidget. */

}, {   key: "attachContentListeners", value: function attachContentListeners { var _this2 = this;

$('.mw-parser-output .editor-token').on('mouseenter', function (e) {       if (_this2.revisionPopup.isVisible) {          return;        }

var ids = _this2.api.getIdsFromElement(e.currentTarget);

_this2.activateSpans(ids.editorId); }).on('mouseleave', function { if (_this2.revisionPopup.isVisible) { return; }

_this2.deactivateSpans; });     $('.editor-token').on('click', function (e) { var ids = _this2.api.getIdsFromElement(e.currentTarget), tokenInfo = _this2.api.getTokenInfo(ids.tokenId);

_this2.activateSpans(ids.editorId);

_this2.revisionPopup.show(tokenInfo, $(e.currentTarget));

_this2.revisionPopup.once('toggle', _this2.deactivateSpans); // Fetch edit summary then re-render the popup.

_this2.api.fetchEditSummary(tokenInfo.revisionId).then(function (successData) {         Object.assign(tokenInfo, successData);

_this2.revisionPopup.show(tokenInfo, $(e.target)); }, function { // Silently fail. The revision info provided by WikiWho is still present, which is         // the important part, so we'll just show what we have and throw a console warning. mw.log.warn("WhoWroteThat failed to fetch the summary for revision ".concat(tokenInfo.revisionId)); });     });    }    /**     * Respond to the close event that the widget emits. * Toggle the application off, and replace the content * to the original dom of the original article. */

}, {   key: "onWidgetClose", value: function onWidgetClose { // Close button; revert back to the original content _ActivationSingleton["default"].getContentWrapper.html(_ActivationSingleton["default"].getOriginalContent.html); // Hide the widget and update the sidebar link.

this.widget.toggle(false);

_ActivationSingleton["default"].toggleLink(false); } }]);

return App; }; // This should be able to load with 'require'

module.exports = App; var _default = App; exports["default"] = _default;

},{"./ActivationSingleton":1,"./Api":2,"./InfoBarWidget":4,"./RevisionPopupWidget":5,"./config":7}],4:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

/** * @class * @param {Object} config Configuration options. * @param {string} config.state The state of the infobar; see {@link InfoBarWidget#setState}. * @constructor */ var InfoBarWidget = function InfoBarWidget { var _this = this;

var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; // Parent constructor OO.ui.ButtonWidget.parent.call(this, config); OO.ui.mixin.IconElement.call(this, config); OO.ui.mixin.LabelElement.call(this, config); OO.ui.mixin.TitledElement.call(this, config); OO.ui.mixin.FlaggedElement.call(this, config); this.closeIcon = new OO.ui.IconWidget({   icon: 'clear',    flags: ['invert'],    classes: ['ext-wwt-infoBarWidget-close']  }); this.userInfoLabel = new OO.ui.LabelWidget({   label: mw.msg('ext-whowrotethat-ready-general'),    classes: ['ext-wwt-infoBarWidget-info']  }); this.$pendingAnimation = $(' ').addClass('ext-wwt-infoBarWidget-spinner').append($(' ').addClass('ext-wwt-infoBarWidget-spinner-bounce')); // Set properties

this.setState(config.state || 'pending'); this.toggle(false); this.setLabel($(' ').append(mw.msg('ext-whowrotethat-state-pending')).contents); // Close event

this.closeIcon.$element.on('click', function {    return _this.emit('close');  }); // Initialize

this.$element.addClass('ext-wwt-infoBarWidget').append(this.$pendingAnimation, this.$icon, this.$label, this.userInfoLabel.$element, this.closeIcon.$element); }; /* Setup */

OO.inheritClass(InfoBarWidget, OO.ui.Widget); OO.mixinClass(InfoBarWidget, OO.ui.mixin.IconElement); OO.mixinClass(InfoBarWidget, OO.ui.mixin.LabelElement); OO.mixinClass(InfoBarWidget, OO.ui.mixin.TitledElement); OO.mixinClass(InfoBarWidget, OO.ui.mixin.FlaggedElement); /** * Define legal states for the widget * * @type {Array} */

InfoBarWidget["static"].legalFlags = ['pending', 'ready', 'err']; /** * Change the state of the widget * * @param {string} state Widget state; 'pending', 'ready' or 'error' */

InfoBarWidget.prototype.setState = function (state) { var flags = {};

if (this.state !== state) { this.constructor["static"].legalFlags.forEach(function (flag) {     flags[flag] = flag === state;    }); flags.invert = true; this.setFlags(flags);

if (state === 'ready') { this.setLabel($(' ').append(mw.msg('ext-whowrotethat-ready-title')).contents); this.userInfoLabel.setLabel($(' ').append(mw.msg('ext-whowrotethat-ready-general')).contents); this.setIcon('userAvatar'); } else if (state === 'pending') { this.setIcon(''); } else { this.setIcon('error'); this.setErrorMessage; }

this.$pendingAnimation.toggle(state === 'pending'); this.userInfoLabel.toggle(state === 'ready'); this.state = state; } }; /** * Set an error with a specific label * * @param {string} errCode Error code as defined by the Api class. */

InfoBarWidget.prototype.setErrorMessage = function { var errCode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'refresh'; // Messages used here: // ext-whowrotethat-error-refresh // ext-whowrotethat-error-later // ext-whowrotethat-error-contact var errorMessage = mw.msg('ext-whowrotethat-error-' + errCode);

if (errCode === 'contact') { // The contact error message is the only with with a different signature, so we handle it. var link = document.createElement('a'); link.href = 'https://meta.wikimedia.org/wiki/Talk:Community_Tech/Who_Wrote_That_tool'; link.text = mw.msg('ext-whowrotethat-error-contact-link'); errorMessage = mw.message('ext-whowrotethat-error-contact', link).parse; }

this.setLabel(new OO.ui.HtmlSnippet(mw.msg('ext-whowrotethat-state-error', errorMessage))); };

var _default = InfoBarWidget; exports["default"] = _default;

},{}],5:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

/** * @class * @constructor */ var RevisionPopupWidget = function RevisionPopupWidget { // Parent constructor RevisionPopupWidget.parent.call(this, {   padded: true,    autoClose: true,    position: 'above',    // FIXME: 'force-left' for RTL languages    align: 'force-right',    hideWhenOutOfView: false,    $content: $(' ').addClass('ext-wwt-revisionPopupWidget-content')  }); }; /* Setup */

OO.inheritClass(RevisionPopupWidget, OO.ui.PopupWidget);

function getSizeHtml(size) { var sizeClass;

if (size > 0) { sizeClass = 'mw-plusminus-pos'; } else if (size < 0) { sizeClass = 'mw-plusminus-neg'; } else { sizeClass = 'mw-plusminus-null'; }

return "\n\t\t") + "".concat(size > 0 ? '+' : '').concat(mw.language.convertNumber(size)) + ' '; } /** * Show the revision popup based on the given token data, above the given element. * Note that the English namespaces will normalize to the wiki's local namespaces. * @param {Object} data As returned by Api.getTokenInfo. * @param {jQuery} $target Element the popup should be attached to. */

RevisionPopupWidget.prototype.show = function (data, $target) { var isIP = data.username.slice(0, 2) === '0|', username = isIP ? data.username.slice(2) : data.username, contribsUrl = mw.util.getUrl("Special:Contributions/".concat(username)), // We typically link to Special:Contribs for IPs. userPageUrl = isIP ? contribsUrl : mw.util.getUrl("User:".concat(data.username)), userLinks = "\n\t\t\t").concat(username, "\n\t\t\t").concat(mw.msg('parentheses-start'), "").concat(mw.msg('talkpagelinktext'), "\n\t\t\t").concat(mw.msg('pipe-separator'), "\n\t\t\t").concat(mw.msg('contribslink'), "").concat(mw.msg('parentheses-end'), "\n\t\t"), dateStr = moment(data.revisionTime).locale(mw.config.get('wgUserLanguage')).format('LLL'), diffLink = "").concat(dateStr, ""), addedMsg = mw.message('ext-whowrotethat-revision-added', userLinks, diffLink).parse, commentMsg = data.comment ? "".concat(data.comment, " ") : '', sizeMsg = data.size ? getSizeHtml(data.size) : '', attributionMsg = "".concat(mw.message('ext-whowrotethat-revision-attribution', data.score).parse, " "), html = $.parseHTML("".concat(addedMsg.trim, " ").concat(commentMsg).concat(sizeMsg, " ").concat(attributionMsg)); $('.ext-wwt-revisionPopupWidget-content').html(html);

if ($target.find('.thumb').length) { $target = $target.find('.thumb'); }

this.setFloatableContainer($target); this.toggle(true); };

var _default = RevisionPopupWidget; exports["default"] = _default;

},{}],6:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0;

/** * A popup meant to display as a welcome message after the * extension is installed for the first time. * @class * @extends OO.ui.PopupWidget * * @constructor * @param {Object} [config={}] Configuration options */ var WelcomePopupWidget = function WelcomePopupWidget { var config = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var $content = $(' ').addClass('ext-wwt-welcomePopupWidget-content'); // Parent constructor

WelcomePopupWidget.parent.call(this, Object.assign({ padded: true, autoClose: true, position: 'after', hideWhenOutOfView: false, $content: $content }, config)); this.dismissButton = new OO.ui.ButtonWidget({   flags: ['primary', 'progressive'],    label: mw.msg('ext-whowrotethat-tour-welcome-dismiss')  }); $content.append($(' ').addClass('ext-wwt-welcomePopupWidget-title').append(mw.msg('ext-whowrotethat-tour-welcome-title')), $(' ').addClass('ext-wwt-welcomePopupWidget-description').append(mw.msg('ext-whowrotethat-tour-welcome-description')), $(' ').addClass('ext-wwt-welcomePopupWidget-button').append(this.dismissButton.$element)); // Events

this.dismissButton.connect(this, {   click: ['emit', 'dismiss']  }); // Initialize

this.$element.addClass('ext-wwt-welcomePopupWidget'); }; /* Setup */

OO.inheritClass(WelcomePopupWidget, OO.ui.PopupWidget); /* Methods */

/** * @inheritdoc */

WelcomePopupWidget.prototype.toggle = function (show) { // Parent method WelcomePopupWidget.parent.prototype.toggle.call(this, show); // HACK: Prevent clipping; we don't want (or need) the // popup to clip itself when it's outside the viewport // or close to the viewport. // Unfortunately, the toggle operation calls toggleClipping // multiple times on and off, so we have to insist on it // being actually off at the end of it.

this.toggleClipping(false); };

module.exports = WelcomePopupWidget; var _default = WelcomePopupWidget; exports["default"] = _default;

},{}],7:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var config = { wikiWhoUrl: 'https://www.wikiwho.net/' }; var _default = config; exports["default"] = _default;

},{}],8:[function(require,module,exports){ "use strict";

var _ActivationSingleton = _interopRequireDefault(require("../ActivationSingleton"));

var _languages = _interopRequireDefault(require("../../temp/languages"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

// This is generated during the build process (function {  var onActivateButtonClick = function onActivateButtonClick(e) {    _ActivationSingleton["default"].loadDependencies.then(function  { // Only load after dependencies are loaded var App = require('../App'), a = new App;

a.start; });

e.preventDefault; return false; },     loadWhoWroteThat = function loadWhoWroteThat { _ActivationSingleton["default"].initialize($('.mw-parser-output'), {     lang: $('html').attr('lang'),      translations: _languages["default"],      namespace: mw.config.get('wgCanonicalNamespace'),      mainPage: mw.config.get('wgIsMainPage')    });

_ActivationSingleton["default"].getButton.on('click', onActivateButtonClick); };

$(document).ready(function {    var welcomeTourSeen = window.localStorage.getItem('WelcomeTourSeen'),        $overlay = $(' ').addClass('ext-wwt-overlay');    loadWhoWroteThat;

if (welcomeTourSeen || !_ActivationSingleton["default"].isValidPage) { // Do not show the tour if it was previously dismissed // Or if the page is invalid return; }

$.when($.ready, mw.loader.using(['oojs-ui', 'mediawiki.jqueryMsg'])).then(function {      var WelcomePopupWidget = require('../WelcomePopupWidget'),          welcome = new WelcomePopupWidget({ $floatableContainer: $('#t-whowrotethat'), $overlay: $overlay });

welcome.on('dismiss', function {        // This is a gadget that may also work for        // non logged in users, so we can't trust        // the mw.user.options storage.        // Store the fact that the tour was        // dismissed in localStorage        window.localStorage.setItem('WelcomeTourSeen', true); // Hide the popup

welcome.toggle(false); }); // Show the popup

$('html').addClass('ext-wwt-popup'); $('body').append($overlay); $overlay.append(welcome.$element); welcome.toggle(true); }); }); // For debugging purposes, export methods to the window global

window.wwtDebug = { resetWelcomePopup: function resetWelcomePopup { window.localStorage.removeItem('WelcomeTourSeen'); window.console.log('WhoWroteThat Extension: Welcome tour reset. Please refresh.'); } }; });

},{"../../temp/languages":9,"../ActivationSingleton":1,"../App":3,"../WelcomePopupWidget":6}],9:[function(require,module,exports){ "use strict";

Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var languageBlob = { "en": { "ext-whowrotethat-activation-link": "Who Wrote That?", "ext-whowrotethat-activation-link-tooltip": "Activate Who Wrote That?", "ext-whowrotethat-deactivation-link": "Close Who Wrote That?", "ext-whowrotethat-state-pending": " Who Wrote That? is loading. This might take a while.", "ext-whowrotethat-state-error": "API Error: $1", "ext-whowrotethat-error-refresh": "Please refresh the page or try again later.", "ext-whowrotethat-error-later": "Please try again later.", "ext-whowrotethat-error-contact": "Please $1 about this issue.", "ext-whowrotethat-error-contact-link": "contact us", "ext-whowrotethat-ready-title": "Who Wrote That?", "ext-whowrotethat-ready-general": "Hover to see contributions by the same author. Click for more details.", "ext-whowrotethat-tour-welcome-title": " Who Wrote That? is enabled!", "ext-whowrotethat-tour-welcome-description": "Activate the the tool from the sidebar. Hover over the text to see the author and their other contributions to this page. Click to get more details.", "ext-whowrotethat-tour-welcome-dismiss": "Got it!", "ext-whowrotethat-revision-added": "$1 added this on $2.", "ext-whowrotethat-revision-attribution": "They have written $1% of the page." } }; var _default = languageBlob; exports["default"] = _default;

},{}]},{},[8]);