Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

MediaWiki:Gadget-staff.js

MediaWiki interface page

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)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
(function () {
  var api;

  var HIDDEN_TAG_GROUPS = {
    '*': true,
    user: true,
    autoconfirmed: true,
    emailconfirmed: true,
    bot: true,
  };
  var joinedDateFormatter = new Intl.DateTimeFormat('en', {
    month: 'short',
    year: 'numeric',
  });

  function getApi() {
    if (!api) {
      api = new mw.Api();
    }
    return api;
  }

  function normalizeUserName(name) {
    return String(name || '')
      .replace(/_/g, ' ')
      .replace(/^\s+|\s+$/g, '');
  }

  function getField(card, fieldName) {
    return card.querySelector('[data-staff-field="' + fieldName + '"]');
  }

  function formatRegistrationDate(registration) {
    var date;
    if (!registration) {
      return 'Joined —';
    }
    date = new Date(registration);
    if (isNaN(date.getTime())) {
      return 'Joined —';
    }
    return 'Joined ' + joinedDateFormatter.format(date);
  }

  function getGroupMessageKey(group) {
    return 'group-' + group + '-member';
  }

  function formatGroupFallback(group) {
    return String(group || '')
      .replace(/[-_]+/g, ' ')
      .replace(/\b\w/g, function (char) {
        return char.toUpperCase();
      });
  }

  function getGroupLabel(group) {
    var key = getGroupMessageKey(group);
    if (mw.message(key).exists()) {
      return mw.message(key).text();
    }
    return formatGroupFallback(group);
  }

  function getVisibleGroups(groups) {
    var visible = [];
    var i;
    var group;
    groups = Array.isArray(groups) ? groups : [];
    for (i = 0; i < groups.length; i++) {
      group = groups[i];
      if (!HIDDEN_TAG_GROUPS[group]) {
        visible.push(group);
      }
    }
    return visible;
  }

  function mergeUserGroups(userData) {
    var merged = [];
    var seen = {};
    var groups = (userData && userData.groups) || [];
    var memberships = (userData && userData.groupmemberships) || [];
    var i;
    var name;
    for (i = 0; i < groups.length; i++) {
      name = groups[i];
      if (!seen[name]) {
        seen[name] = true;
        merged.push(name);
      }
    }
    for (i = 0; i < memberships.length; i++) {
      name = memberships[i] && memberships[i].group;
      if (name && !seen[name]) {
        seen[name] = true;
        merged.push(name);
      }
    }
    return merged;
  }

  function loadMissingGroupLabels(groups) {
    var messageKeys = [];
    var seen = {};
    var i;
    var key;
    for (i = 0; i < groups.length; i++) {
      key = getGroupMessageKey(groups[i]);
      if (seen[key] || mw.message(key).exists()) continue;
      seen[key] = true;
      messageKeys.push(key);
    }
    if (!messageKeys.length) return $.Deferred().resolve().promise();
    return getApi()
      .get({
        action: 'query',
        meta: 'allmessages',
        format: 'json',
        amincludelocal: 1,
        amlang: mw.config.get('wgUserLanguage') || 'en',
        ammessages: messageKeys.join('|'),
      })
      .then(function (data) {
        var messages = (data && data.query && data.query.allmessages) || [];
        var loadedMessages = {};
        var i;
        var content;
        for (i = 0; i < messages.length; i++) {
          content = messages[i].content;
          if (typeof content !== 'string') {
            content = messages[i]['*'];
          }
          if (typeof content === 'string' && content !== '') {
            loadedMessages[messages[i].name] = content;
          }
        }
        if (Object.keys(loadedMessages).length) {
          mw.messages.set(loadedMessages);
        }
      });
  }

  function fetchUserData(userName) {
    return getApi()
      .get({
        action: 'query',
        list: 'users',
        format: 'json',
        formatversion: 2,
        ususers: userName,
        usprop: 'registration|groups|groupmemberships',
      })
      .then(function (data) {
        var users = (data && data.query && data.query.users) || [];
        return users[0] || null;
      });
  }

  function fetchUserActivity(userName) {
    return getApi()
      .get({
        action: 'query',
        list: 'recentchanges',
        format: 'json',
        formatversion: 2,
        rcprop: 'user|timestamp',
        rctype: 'edit|new|log',
        rcuser: userName,
        rclimit: 1,
      })
      .then(function (data) {
        var changes = (data && data.query && data.query.recentchanges) || [];
        return changes.length > 0;
      });
  }

  function renderGroups(card, groups) {
    var container = getField(card, 'tags');
    var visibleGroups = getVisibleGroups(groups);
    var i;
    var chip;
    if (!container) return;
    container.innerHTML = '';
    if (!visibleGroups.length) {
      chip = document.createElement('span');
      chip.className = 'staff-role-tag staff-role-tag--placeholder';
      chip.appendChild(document.createTextNode('No tags'));
      container.appendChild(chip);
      return;
    }
    for (i = 0; i < visibleGroups.length; i++) {
      chip = document.createElement('span');
      chip.className = 'staff-role-tag';
      chip.appendChild(document.createTextNode(getGroupLabel(visibleGroups[i])));
      container.appendChild(chip);
    }
  }

  function renderJoined(card, registration) {
    var joined = getField(card, 'joined');
    if (joined) {
      joined.textContent = formatRegistrationDate(registration);
    }
  }

  function renderStatus(card, isActive) {
    var status = getField(card, 'status');
    if (!status) return;
    status.className = 'staff-status ' + (isActive ? 'active' : 'inactive');
    status.textContent = isActive ? 'Active' : 'Inactive';
  }

  function hydrateCard(card) {
    var userName = normalizeUserName(card.getAttribute('data-staff-user'));
    if (!userName || card.getAttribute('data-staff-ready') === '1') return;
    $.when(fetchUserData(userName), fetchUserActivity(userName)).then(
      function (userData, isActive) {
        var groups = mergeUserGroups(userData);
        var registration = userData && userData.registration ? userData.registration : null;
        loadMissingGroupLabels(getVisibleGroups(groups)).then(function () {
          renderGroups(card, groups);
          renderJoined(card, registration);
          renderStatus(card, !!isActive);
          card.setAttribute('data-staff-ready', '1');
        });
      },
      function (error) {
        console.error('staff card hydration failed for', userName, error);
      }
    );
  }

  function init() {
    var cards = document.querySelectorAll('.staff-card[data-staff-user]');
    var i;
    if (!cards.length) return;
    for (i = 0; i < cards.length; i++) {
      hydrateCard(cards[i]);
    }
  }
  mw.loader.using(['mediawiki.api']).then(init);
})();