MediaWiki:Common.js

From Granblue Fantasy 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.
/* Any JavaScript here will be loaded for all users on every page load. */

// Add a dynamic fixed header to tables with:
// 1) table with "header-fixed" class
// 2) a table row with "header-row" class
(function (mw, $) {
  'use strict';

  mw.loader.using('jquery.tablesorter', function () {
    var ts = $.fn.tablesorter;
    $.fn.tablesorter = function () {
      ts.apply(this, arguments);
      initializeStickyHeader();
    };

    function initializeStickyHeader() {
      var tables = [];
      var $tables = $('.header-fixed');

      if ($tables.length == 0) return;

      $tables.each(function () {
        // If this table is under tabber AND is hidden,
        // temporarily make element visible so we can pull its width data later
        var tabberTabParent = $(this).parent('.tabbertab')[0];
        var originalStyleDisplay;
        if (tabberTabParent) {
          originalStyleDisplay = tabberTabParent.style.display;
          tabberTabParent.style.visibility = 'hidden';
          tabberTabParent.style.display = 'block';
        }

        // Build fixed-header-table content
        var $headerRow = $(this).find('.header-row');
        var $newHeaderContent = $('<tbody>');
        $newHeaderContent.append($headerRow.clone());

        // Add fixed-header-table colgroup to fix width
        var $columnsOld = $headerRow.find(
          'td:not([colspan]),th:not([colspan])'
        );
        var $newHeaderColgroup = $('<colgroup>');

        for (var i = 0; i < $columnsOld.length; i++) {
          var width = $columnsOld[i].getBoundingClientRect().width;
          $newHeaderColgroup.append($('<col>').css('width', width));
        }

        $(this).before(
          $('<table>')
            .addClass('header-fixed-helper')
            .addClass('wikitable')
            .css('position', 'sticky')
            .css('top', '0')
            .css('z-index', '9')
            .css('display', 'none')
            .css('background-color', '#fff')
            .css('margin-top', '0')
            .css('margin-bottom', '0')
            .css('table-layout', 'fixed')
            .append($newHeaderColgroup)
            .append($newHeaderContent)
        );
        tables.push({
          table: this,
          helper: this.previousSibling,
        });
        $(this.previousSibling).css('width', $(this).width() + 1);

        // Restore original tab display status
        // if this table was under tabber and hidden
        if (tabberTabParent) {
          tabberTabParent.style.visibility = '';
          tabberTabParent.style.display = originalStyleDisplay;
        }
      });

      $(window).on('scroll', function () {
        var scrollOffset = $(this).scrollTop();
        for (var i = 0; i < tables.length; i++) {
          var tableOffset = $(tables[i].table).offset().top;
          var tableHeight = $(tables[i].table).height();
          var helperHeight = $(tables[i].helper).height();
          if (
            scrollOffset > tableOffset &&
            scrollOffset < tableOffset + tableHeight - helperHeight
          ) {
            if ($(tables[i].helper).is(':hidden')) $(tables[i].helper).show();
          } else {
            $(tables[i].helper).hide();
          }
        }
      });
    }
  });
})(mediaWiki, jQuery);

/* also in MediaWiki:AudioPlayer.js */
(function (mw, $) {
  var audioplayerz = document.getElementsByClassName('audio-button');
  var audioPlayer = function () {
    var audioplayerx = this;
    var audioplayery = audioplayerx.getElementsByTagName('audio')[0];
    if (audioplayery.paused) {
      var audio = document.getElementsByTagName('audio'),
        audioplayeri = audio.length;
      while (audioplayeri--) {
        audio[audioplayeri].pause();
        audio[audioplayeri].currentTime = 0;
      }
      var audioplayerj = document.getElementsByClassName(
        'audio-button playing'
      );
      while (audioplayerj.length > 0) {
        audioplayerj[0].className = 'audio-button';
      }
      audioplayery.play();
      audioplayerx.className = 'audio-button playing';
      audioplayery.addEventListener('timeupdate', audioUpdate, false);
      audioplayery.addEventListener(
        'canplaythrough',
        function () {
          audioplayerd = audioplayery.duration;
        },
        false
      );
    } else {
      audioplayery.pause();
      audioplayery.currentTime = 0;
      audioplayerx.className = 'audio-button';
    }
  };
  function audioUpdate() {
    if (this.currentTime == audioplayerd) {
      this.parentElement.className = 'audio-button';
      this.removeEventListener('timeupdate', audioUpdate, false);
    }
  }
  for (
    var audioplayeri = 0;
    audioplayeri < audioplayerz.length;
    audioplayeri++
  ) {
    audioplayerz[audioplayeri].addEventListener('click', audioPlayer, false);
  }
})(mediaWiki, jQuery);
(function (mw, $) {
  function checkExist(selector, cb) {
    var i = 0;
    var handler = setInterval(function () {
      i += 1;
      if (i > 50) clearInterval(handler);
      if (document.querySelector(selector)) {
        cb.call();
        clearInterval(handler);
      }
    }, 200);
  }

  checkExist('.gallery-swap-images', function () {
    var swap = function (el) {
      $el = $(el);
      $active = $el.find('.active');
      $next = $active.next();
      if ($next.length === 0) {
        $next = $el.children().eq(0);
      }
      $active.removeClass('active');
      $next.addClass('active');
    };

    $('.gallery-swap-images').each(function () {
      var self = this;
      var delay = $(this).hasClass('gallery-swap-images--speed-fast')
        ? 2000
        : 7000;

      // Shuffle
      if ($(this).hasClass('gallery-swap-images--shuffle')) {
        $(this).html(
          $(this)
            .children()
            .sort(function () {
              return Math.random() - 0.5;
            })
        );
      }

      // Put everything inside wrapper with layout info
      var width = $(this).find('img')[0].getAttribute('width');
      var height = $(this).find('img')[0].getAttribute('height');
      var div = document.createElement('div');
      div.classList.add('wrapper');
      div.style['width'] = width + 'px';
      div.style['max-width'] = width + 'px';
      div.style['aspect-ratio'] = (width / height).toFixed(3);
      $(div).append($(this).children());
      $(this).append(div);

      // Activate
      $(this).addClass('enabled');
      $(div).children().first().addClass('active');
      setInterval(function () {
        swap(div);
      }, delay);
    });
  });
})(mediaWiki, jQuery);

/*
  This was previously known as Widget:LocalTimeHelper
  Used in Template:EventCountdown, Template:EventTime
*/
(function () {
  'use strict';

  var verifyTime = function (value) {
    var time = parseInt(value, 10);
    if (isNaN(time)) time = 0;
    return time * 1000;
  };

  var verifyText = function (text, defaultText, hasTime) {
    if (text === undefined) return defaultText;
    if (hasTime && !text.includes('%s')) text = text + ' %s.';
    return text;
  };

  var formatTime = function (days, hours, minutes, seconds) {
    var result = '';
    result += days ? ' ' + days.toString() + 'd' : '';
    result += hours ? ' ' + hours.toString() + 'h' : '';
    result += minutes ? ' ' + minutes.toString() + 'm' : '';
    result += seconds ? ' ' + Math.max(seconds - 1, 0).toString() + 's' : '';
    return result;
  };

  var updateCountdownText = function (el) {
    var now = new Date();
    var startDate = new Date(parseInt(el.dataset.startTime, 10));
    var endDate = new Date(parseInt(el.dataset.endTime, 10));

    var startDiff = startDate.getTime() - now.getTime();
    var endDiff = endDate.getTime() - now.getTime();

    var text = el.dataset.textEnd;
    var diff = startDiff;
    if (startDiff > 1000) {
      if (startDate < endDate) text = el.dataset.textStart;
    } else if (endDiff > 1000) {
      diff = endDiff;
    } else {
      text = el.dataset.textAfter;
    }

    if (diff < 1000) {
      el.innerHTML = text; // + ' (start: ' + startDate.toUTCString() + ', end: ' + endDate.toUTCString() + ')';
      return;
    }

    diff = diff / 1000;
    var days = Math.floor(diff / 86400);
    var hours = Math.floor((diff % 86400) / 3600);
    var minutes = Math.floor((diff % 3600) / 60);
    var seconds = ((diff % 3600) % 60).toFixed(0);

    if (days === 1) {
      hours += 24;
      days = 0;
    }
    if (hours > 1 || days > 0) seconds = 0;
    if (days > 0) minutes = 0;

    var result = formatTime(days, hours, minutes, seconds);

    var interval = 1000;
    if (days > 0) {
      interval = 60 * 60 * 1000;
    } else if (hours > 1) {
      interval = 60 * 1000;
    }

    el.innerHTML = text.replace('%s', result); // + ' (start: ' + startDate.toUTCString() + ', end: ' + endDate.toUTCString() + ', diff: ' + diff +', interval: ' + interval + ')';

    window.setTimeout(function () {
      updateCountdownText(el);
    }, interval);
  };

  const nth = function (d) {
    if (d > 3 && d < 21) return 'th';
    switch (d % 10) {
      case 1:
        return 'st';
      case 2:
        return 'nd';
      case 3:
        return 'rd';
      default:
        return 'th';
    }
  };

  var updateTimeText = function (el, time) {
    var months = [
      'January',
      'February',
      'March',
      'April',
      'May',
      'June',
      'July',
      'August',
      'September',
      'October',
      'November',
      'December',
    ];
    var date = new Date(time);
    var output = '';
    if (el.dataset.format == 'hm' || el.dataset.format == 'H:i') {
      output =
        ('000' + date.getHours()).slice(-2) +
        ':' +
        ('000' + date.getMinutes()).slice(-2);
    } else if (el.dataset.format == 'md') {
      output =
        months[date.getMonth()] + ' ' + date.getDate() + nth(date.getDate());
    } else if (el.dataset.format == 'mdy') {
      output =
        months[date.getMonth()] +
        ' ' +
        date.getDate() +
        nth(date.getDate()) +
        ', ' +
        date.getFullYear();
    } else {
      output =
        ('000' + date.getHours()).slice(-2) +
        ':' +
        ('000' + date.getMinutes()).slice(-2) +
        ', ' +
        months[date.getMonth()] +
        ' ' +
        date.getDate() +
        nth(date.getDate()) +
        ', ' +
        date.getFullYear();
    }
    el.innerHTML = output;
  };

  var ready = function () {
    var elements = document.querySelectorAll('.localtime');
    Array.from(elements).forEach(function (el) {
      var startTime = verifyTime(el.dataset.start);
      var endTime = verifyTime(el.dataset.end);
      var time = verifyTime(el.dataset.time);
      el.dataset.textStart = verifyText(
        el.dataset.textStart,
        'Starts in %s.',
        true
      );
      el.dataset.textEnd = verifyText(el.dataset.textEnd, 'Ends in %s.', true);
      el.dataset.textAfter = verifyText(
        el.dataset.textAfter,
        'Event has ended.',
        false
      );
      if (startTime + endTime > 0) {
        el.dataset.startTime = startTime;
        el.dataset.endTime = endTime;
        updateCountdownText(el);
      } else if (time > 0) {
        updateTimeText(el, time);
      }
    });
  };

  if (document.readyState == 'loading') {
    document.addEventListener('DOMContentLoaded', ready);
  } else {
    ready();
  }
})();

/*
  Mobile Tooltip Hover/Touch Fix
  Prevent link from opening when you first tap a link with tooltip.
  Tapping the link again opens the link.
*/
(function () {
  document.addEventListener('touchstart', function () {
    $('.tooltip.hover').removeClass('hover');
  });

  $('.tooltip').on('touchstart', function (e) {
    if (!$(this).hasClass('hover')) {
      e.preventDefault();
      e.stopPropagation();
      $('.tooltip.hover').removeClass('hover');
      $(this).addClass('hover');
    }
  });
})();

/* Add [edit] link to newsposts */
(function () {
  if (!mw.config.get('wgUserId')) return;
  document
    .querySelectorAll("span.mw-headline[id^='20']")
    .forEach(function (el) {
      // Stop if it already has edit link
      if (el.nextSibling && el.nextSibling.classList.contains('mw-editsection'))
        return;

      const date = el.textContent;
      const link =
        '' +
        '<span class="mw-editsection">' +
        '<span class="mw-editsection-bracket">[</span>' +
        '<a href="/News:WhatsNew_' +
        date +
        '/edit">edit</a>' +
        '<span class="mw-editsection-bracket">]</span>' +
        '</span>';
      el.insertAdjacentHTML('afterend', link);
    });
})();

/**
 * Tabber - Make links with matching a Tabber hash work when clicked
 */
(function (mw, $) {
  /** @type {Map<string, HTMLElement>} */
  var shorthandMap = new Map();

  /**
   * @param {HashChangeEvent=} ev
   */
  function handleFuzzyHash(ev) {
    var hash = location.hash;
    var id = hash.slice(1);

    if (!document.getElementById(id)) {
      var tab = shorthandMap.get(hash.replace(/-\d+$/, ''));
      if (tab) {
        tab.click();

        // if direct call from hook instead of hashchange event
        if (!ev) {
          tab.scrollIntoView({ behavior: 'instant' });
        }
      }
    }
  }

  $(function () {
    addEventListener('hashchange', handleFuzzyHash);
  });

  /**
   * @param {HTMLElement} container
   */
  function handleNewTabs(container) {
    /** @type {HTMLAnchorElement[]} */
    var tabberTabs = Array.from(
      container.querySelectorAll('a.tabber__tab')
    ).filter(function (el) {
      return el.getAttribute('href') === el.hash;
    });

    /**
     * Intercept this link click and turn it into a tab activation instead
     *
     * @param {HTMLAnchorElement} link
     * @param {HTMLAnchorElement} tab
     */
    function handleLink(link, tab) {
      link.classList.add('tabber__link');
      link.addEventListener('click', function (e) {
        e.preventDefault();
        tab.click();
      });
    }

    /**
     * @param {string} hash
     * @param {boolean} exact
     */
    function makeSelector(hash, exact) {
      if (exact) {
        return 'a:not(.tabber__link):not(.tabber__tab)[href="'.concat(
          hash,
          '"]'
        );
      }

      var prefix = hash.replace(/-\d+$/, '');
      return 'a:not(.tabber__link):not(.tabber__tab)[href|="'.concat(
        prefix,
        '"]'
      );
    }

    tabberTabs.forEach(function (tab) {
      var prefix = tab.hash.replace(/-\d+$/, '');

      if (prefix !== '#' && !shorthandMap.has(prefix)) {
        shorthandMap.set(prefix, tab);
      }

      const tabContainer = tab.closest('.tabber.tabber--live');

      // handle links inside this tabber's contents first, to handle transclusions more safely
      // there's still a risk of conflict when there's nested tabbers,
      // but addressing that is significantly more difficult
      tabContainer
        .querySelectorAll(makeSelector(tab.hash, true))
        .forEach(handleThisLink);
      // assuming this is a transclusion, also handle fuzzy indices that got mismatched
      tabContainer
        .querySelectorAll(makeSelector(tab.hash, false))
        .forEach(handleThisLink);

      /**
       * @param {HTMLAnchorElement} link
       */
      function handleThisLink(link) {
        handleLink(link, tab);
      }
    });

    tabberTabs.forEach(function (tab) {
      // handle all document links that are exact id matches next
      document
        .querySelectorAll(makeSelector(tab.hash, true))
        .forEach(handleThisLink);

      /**
       * @param {HTMLAnchorElement} link
       */
      function handleThisLink(link) {
        handleLink(link, tab);
      }
    });

    tabberTabs.forEach(function (tab) {
      // if there are still any leftover links after processing all tabs, finally deal with them
      document
        .querySelectorAll(makeSelector(tab.hash, false))
        .forEach(handleThisLink);

      /**
       * @param {HTMLAnchorElement} link
       */
      function handleThisLink(link) {
        handleLink(link, tab);
      }
    });

    handleFuzzyHash();
  }

  /** @type {Set<HTMLElement>} */
  var waiting = new Set();
  /** @type {MutationObserver=} */
  var observer;

  mw.hook('wikipage.content').add(function (containersArg) {
    /** @type {HTMLElement[]} */
    var containers = Array.from(containersArg);

    var tabberStyle = document.getElementById('tabber-style');
    if (tabberStyle && tabberStyle.parentNode) {
      // tabberNeuer has not yet gotten to run -- wait for it
      containers.forEach(function (container) {
        waiting.add(container);
      });

      if (!observer) {
        observer = new MutationObserver(function (records) {
          if (
            records.some(function (record) {
              return Array.from(record.removedNodes).includes(tabberStyle);
            })
          ) {
            observer.disconnect();
            observer = undefined;
            waiting.forEach(function (container) {
              handleNewTabs(container);
            });
            waiting.clear();
          }
        });
        observer.observe(tabberStyle.parentNode, { childList: true });
      }
    } else {
      containers.forEach(function (container) {
        handleNewTabs(container);
      });
    }
  });
})(mediaWiki, $);