This is a wiki for a reason. Anyone can contribute. If you see something that is inaccurate or can be improved, don't ask that it be fixed--just improve it.
[ Disclaimer, Create new user --- Wiki markup help, Install P99 ]

Difference between revisions of "MediaWiki:Common.js"

From Project 1999 Wiki
Jump to: navigation, search
Line 415: Line 415:
  
 
   $mobsTable.after(
 
   $mobsTable.after(
     `<div style="font-size:0.85em; text-align:right; max-width: ${$mobsTable.width()}px"><a class="broken-dynamic-table-help" href="#">Is this table not working?</a></div>`
+
     '<div style="font-size:0.85em; text-align:right; max-width: '+ $mobsTable.width()+'px"><a class="broken-dynamic-table-help" href="#">Is this table not working?</a></div>`
 
   );
 
   );
  
 
   const $itemsTable = $mobsTable.next("div").next("p").next();
 
   const $itemsTable = $mobsTable.next("div").next("p").next();
 
   $itemsTable.after(
 
   $itemsTable.after(
     `<div style="font-size:0.85em; text-align:right; max-width: ${$itemsTable.width()}px"><a class="broken-dynamic-table-help" href="#">Is this table not working?</a></div>`
+
     '<div style="font-size:0.85em; text-align:right; max-width: '+ $mobsTable.width()+'px"><a class="broken-dynamic-table-help" href="#">Is this table not working?</a></div>`
 
   );
 
   );
  

Revision as of 19:35, 17 July 2025

/* Any JavaScript here will be loaded for all users on every page load. */

importScript('MediaWiki:Polyfills.js');

// HTTP prevents people from logging in now in (some?) Chrome browsers; redirect to HTTPS
try {
  var isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
  var onHttp = location.protocol === 'http:';
  var haveNotTriedAlready = !location.search.includes('redirected_from_http');

  if (isChrome && onHttp && haveNotTriedAlready) {
    location =
      location.origin.replace('http:', 'https:') +
      location.pathname +
      location.search +
//      (location.search[0] === '?' ? '&' : '?') +
//      'redirected_from_http=1' +
      location.hash;
  }
} catch (err) {
  // Give up on trying to redirect
  // maybe alert user?
}

importScript('MediaWiki:Simple-lightbox.min.js');
importScript('MediaWiki:Zones.js');

// Get all magelos of current user
// (currently un-used, but intended for future achievement code, so that
//  we can have an "add this achievement to my magelo" drop-down with all
//  their magelos listed)
var getMagelosForCurrentUser = function () {
  var userName = $('#pt-userpage a').text();
  var url =
    'https://wiki.project1999.com/index.php?limit=50&tagfilter=&' +
    'title=Special%3AContributions&contribs=user&target=' +
    userName +
    '&namespace=500&topOnly=1&year=&month=-1';
  fetch(url)
    .then(function (response) {
      return response.text();
    })
    .then(function (html) {
      var mageloLinks = Array.from($(html).find('li a:contains("Magelo")'));
      var names = mageloLinks
        .filter(function (i, a) {
          return !$(a).text().includes('(');
        })
        .map(function (i, el) {
          return $(el).text();
        });
      console.log(names);
    });
};

// **********************************
// Add localStorage helper functions
// **********************************

/**
 * Basic "state getter": checks localStorage for *stateKey*, then return the
 * value for *pageName* (if any) found on that object.
 */
var getPageState = function (pageName, stateKey) {
  // Get the state for all pages for this key
  var allPageStates = JSON.parse(localStorage.getItem(stateKey) || '{}');

  // Extract just the state for this page (if any)
  return allPageStates[pageName];
};

/**
 * Returns the state data stored for the provided stateKey and the current page.
 * For instance, getStateByPage('checkbox-status') would return the checkbox\
 * status state for the current page.
 */
window.getStateByPage = function (stateKey) {
  var pageName = location.pathname.substr(1); // ditch the leading "/"
  return getPageState(pageName, stateKey);
};

/**
 * Return the global state for a provided state key
 * (works just like getStateByPage, but for states that don't care
 * about which page you are on)
 */
window.getGlobalState = function (stateKey) {
  return getPageState('__global', stateKey);
};

/**
 * Basic "state setter" function
 */
var setPageState = function (pageName, stateKey, state) {
  // Get the state for this key (for all pages) out of local storage
  var allPageStates = JSON.parse(localStorage.getItem(stateKey) || '{}');

  // Set the state only for this page
  allPageStates[pageName] = state;

  // Put the state for the stateKey (for all pages) back into local storage
  localStorage.setItem(stateKey, JSON.stringify(allPageStates));
};

/**
 * Sets the state data stored for the provided stateKey and the current page.
 * For instance, setStateByPage('checkbox-status', {a: 'b'}) would set the
 * checkbox status state for the current page to an object with a key of "a" and
 * value of "b".
 */
window.setStateByPage = function (stateKey, state) {
  var pageName = location.pathname.substr(1); // ditch the leading "/"
  setPageState(pageName, stateKey, state);
};

window.setGlobalState = function (stateKey, state) {
  return setPageState('__global', stateKey, state);
};

// *******************************

/* p1999wiki.js
 * written by http://wiki.project1999.com/User:Ravhin
 * last update: 7 January, 2018 by Loramin
 */
$(function () {
  var hideDelay = 0;
  var trigDelay = 250;
  var hideTimer = null;
  var ajax = null;

  var currentPosition = { left: '0px', top: '0px' };

  // One instance that's reused to show info for the current person
  var container = $(
    '<div id="itemHoverContainer">' + '<div id="HoverContent"></div>' + '</div>'
  );

  $('body').append(container);

  /* --- hoverbox for item/mob, currently only used in magelo --- */

  // Determine which "a" elements should trigger the item stats mouseover
  var $mouseoverTargets = $('span.ih a');
  var isItemCategory =
    document.title.startsWith('Category:') &&
    document.title.includes('Equipment - Project 1999 Wiki') &&
    !document.title.includes('Worshiper Equipment');
  if (isItemCategory) {
    // Include the category's item list along with ".ih" links
    $mouseoverTargets = $mouseoverTargets.add('.mw-content-ltr a');
  }

  $mouseoverTargets = $mouseoverTargets.filter(function (i, a) {
    // Don't add hover to links like "next 200"
    return (
      !$(a).attr('href').startsWith('/Special:') &&
      !$(a).attr('href').includes('title=Category:')
    );
  });

  $mouseoverTargets.on('mouseover', function () {
    var $this = $(this);
    var itemname = $this.attr('title');

    if (itemname == '' || itemname == 'undefined') return;

    if (hideTimer) clearTimeout(hideTimer);

    if ($this.parents('div.mw-content-ltr').length) {
      var pos = $this.offset();
      var width = $this.width();

      container.css({
        left: pos.left + 5 + 'px',
        top: pos.top + 5 + 'px',
      });
    }

    $(this).trigger('mousemove');

    $('#itemHoverContent').html('&nbsp;');

    //$('#itemHoverContent').html('<div class="itemtopbg"><div class="itemtitle">Loading...</div></div>'
    //                          + '<div class="itembg" style="min-height:50px;"><div class="itemdata">'
    //                          + '<div class="itemicon" style="float:right;"><img alt="" src="/images/Ajax_loader.gif" border="0"></div>'
    //                          + '<p></p></div></div><div class="itembotbg"></div>');

    if (ajax) {
      ajax.abort();
      ajax = null;
    }
    ajax = $.ajax({
      url:
        window.location.protocol +
        '//wiki.project1999.com/index.php/Special:AjaxHoverHelper/' +
        itemname,
      cacheResponse: true,
      success: function (html) {
        var $html = $(html);
        $('#itemHoverContent')
          .html($html[2])
          .prepend($html[0])
          .prepend($html[1]);
      },
    });

    container.css('display', 'block');
    //container.fadeIn('fast');
  }); //on mouseover

  $('span.ih a').on('mouseout', function () {
    if (hideTimer) clearTimeout(hideTimer);
    hideTimer = setTimeout(function () {
      container.css('display', 'none');
      //container.fadeOut('fast');
    }, hideDelay);
  });

  $('span.ih a').mousemove(function (e) {
    var mousex = e.pageX + 20; //Get X coodrinates
    var mousey = e.pageY + 20; //Get Y coordinates
    var tipWidth = container.width(); //Find width of tooltip
    var tipHeight = container.height(); //Find height of tooltip

    //Distance of element from the right edge of viewport
    var tipVisX = $(window).width() - (mousex + tipWidth);
    //Distance of element from the bottom of viewport
    var tipVisY = $(window).height() - (mousey + tipHeight);

    if (tipVisX < 20) {
      //If tooltip exceeds the X coordinate of viewport

      if (tipWidth > e.pageX - 20) {
        mousex = 0;
      } else {
        mousex = e.pageX - tipWidth - 20;
      }
    }
    if (tipVisY < 20) {
      //If tooltip exceeds the Y coordinate of viewport
      mousey = e.pageY - tipHeight - 20;
    }

    container.css({ top: mousey, left: mousex });
  });

  // Allow mouse over of details without hiding details
  $('#itemHoverContainer').mouseover(function () {
    if (hideTimer) clearTimeout(hideTimer);
  });

  // Hide after mouseout
  $('#itemHoverContainer').mouseout(function () {
    if (hideTimer) clearTimeout(hideTimer);
    hideTimer = setTimeout(function () {
      container.css('display', 'none');
      //container.fadeOut('fast');
    }, hideDelay);
  });

  // magelo non-ajax item hover, but move box with mouse
  $('.magelohb').mousemove(function (e) {
    var childContainer = $(this).children('span.hb');

    var tipWidth = childContainer.width(); //Find width of tooltip
    var tipHeight = childContainer.height(); //Find height of tooltip

    var mousex = e.pageX + 20; //Get X coodrinates
    var mousey = e.pageY + 20; //Get Y coordinates

    //Distance of element from the right edge of viewport
    var tipVisX = $(window).width() - (mousex + tipWidth - 20);
    //Distance of element from the bottom of viewport
    var tipVisY = $(window).height() - (mousey + tipHeight - 20);

    if (tipVisX < 20) {
      //If tooltip exceeds the X coordinate of viewport

      if (tipWidth > e.pageX - 20) {
        mousex = 0;
      } else {
        mousex = e.pageX - tipWidth - 20;
      }
    }
    if (tipVisY < 20) {
      //If tooltip exceeds the Y coordinate of viewport
      mousey = e.pageY - tipHeight - 20;
    }

    childContainer.css({ top: mousey, left: mousex, 'z-index': '999' });
  });

  // change to position:fixed on all hover divs if we have JS active
  // otherwise leave as position:absolute so the stationary hovers are near their items
  $('.magelohb span.hb').each(function (i) {
    $(this).css({ position: 'fixed' });
  });

  // Chrome no longer displays alt text when an image is hovered over.  However the wiki only has
  // alt attributes for images, not titles.  This fixes that by converting alt => title
  $('img[alt]').each(function (i, img) {
    $(img).attr('title', $(img).attr('alt'));
  });

  // Fashion for item pages
  var extractFashionHtml = function (html) {
    return $(html)
      .find('.fashion_show, .primary_secondary_show')
      .map(function (i, el) {
        var $el = $(el);
        var data = $el.data();
        data.race = $el.find('.fashion_race').html();
        if ($el.is('.fashion_show')) {
          data.armor = $el.find('.fashion_armor').html();
        }
        return data;
      })
      .toArray();
  };

  var getFashionShows = function (fashionCategory) {
    var url = '/Category:Fashion: ' + fashionCategory;
    return $.get(url).then(extractFashionHtml);
  };

  var getItemPageShows = function () {
    var fashionCategories = $('#catlinks li a')
      .map(function (i, a) {
        return $(a).text();
      })
      .filter(function (i, text) {
        return text.startsWith('Fashion:');
      })
      .map(function (i, text) {
        return text.substr('Fashion: '.length);
      });
    var fashion = fashionCategories[0];
    if (!fashion) return $.when();

    var tint = fashionCategories[1];
    return getFashionShows(fashion).then(function (shows) {
      var sameTint = [];
      var otherTint = [];
      shows.forEach(function (show) {
        var isHeld = show.primaryFashion || show.secondaryFashion;
        var bothHaveSameTint = show.tint === tint;
        var bothHaveNoTint = !(show.tint || tint);
        if (isHeld || bothHaveSameTint || bothHaveNoTint) sameTint.push(show);
        else otherTint.push(show);
      });
      return {
        matches: sameTint,
        partialMatches: otherTint,
        fashion: fashion,
        tint: tint,
      };
    });
  };
  var addFashionSection = function (shows, isFullMatch) {
    var preface = isFullMatch
      ? '<h2>Fashion/Appearance</h2>'
      : '<div>Fashion images with the wrong "tint" (i.e. coloring) which ' +
        "can only be used to get a general sense of the item's " +
        'appearance:</div>';

    // Don't show the explanatory text if there are no shows to explain
    if (!shows.length && !isFullMatch) preface = '';

    var $ul = $('<ul>').append(
      shows.map(function (show) {
        var img = isFullMatch
          ? '<br/><img style="max-height: 100px; max-width: 100px;" src="/images/' +
            show.file.replace(/ /g, '_') +
            '"/>'
          : '';
        return (
          '<li class="fashion-link" data-file="' +
          show.file +
          '">' +
          '<a href="#">' +
          show.gender +
          ' ' +
          show.race +
          img +
          '</a></li>'
        );
      })
    );

    $('#itemfashion').before($('<div>' + preface + '</div>').append($ul));
  };

  var addFashionSections = function () {
    getItemPageShows().then(function (pageShows) {
      if (!pageShows) return;
      var matches = pageShows.matches || [];
      var partialMatches = pageShows.partialMatches || [];
      if (pageShows.matches.length || pageShows.partialMatches.length)
        addFashionSection(pageShows.matches, true);
      if (pageShows.partialMatches.length)
        addFashionSection(pageShows.partialMatches, false);
    });
  };
  addFashionSections();


// Add a help link to dynamic zone lists, to explain how to fix them if they break
try {
  const $mobsTable = $("h2 .mw-headline")
    .filter(function(i, el) {$(el).text().includes("What's in this zone"); })
    .parent()
    .next("p")
    .next("table");

  $mobsTable.after(
    '<div style="font-size:0.85em; text-align:right; max-width: '+ $mobsTable.width()+'px"><a class="broken-dynamic-table-help" href="#">Is this table not working?</a></div>`
  );

  const $itemsTable = $mobsTable.next("div").next("p").next();
  $itemsTable.after(
    '<div style="font-size:0.85em; text-align:right; max-width: '+ $mobsTable.width()+'px"><a class="broken-dynamic-table-help" href="#">Is this table not working?</a></div>`
  );

  $(".broken-dynamic-table-help").click(function(e){
    e.preventDefault();
    alert(
      "This table updates itself nightly with data from other mob/item pages.  If one of the pages contains even a single invalid character (eg. a smiley face or other Unicode character), it can break that update.\n\nUnfortunately, at this time the only fix is to click the zone's category link (at the bottom) and check the history of every page in it for recent edits, and then remove any special characters added recently ... and then wait 24 hours to see if it worked."
    );
  });
} catch (err) {
  1 + 1; /* Wed don't care; it probably was a page with no dynamic zone list */
}


  //*****************************************
  // Add class-based filtering to item tables
  //*****************************************w

  // Determine whether or not a provided row should be shown for the provided
  // class abbreviation (eg. "BRD")
  var isRowShown = function ($tr, classAbbrev) {
    // Extract classes (and races)
    var classText = $tr.find('td').eq(3).text().split('Class:')[1];
    if (!classText) return true; // Ignore (don't filter) rows without class text

    // remove "Race: " part (if any) and upper-case text
    classText = classText.split('Race:')[0] || classText;
    classText = classText.toUpperCase();

    // determine matching text
    var isMatch = classText.includes(classAbbrev);
    var isAllMatch = classText.includes('ALL');
    var isExcept = classText.includes('ALL EXCEPT');

    // determine matches
    if (isMatch && !isExcept) return true; // (eg. BRD in "BRD")
    if (!isMatch && isExcept) return true; // (eg. BRD in "ALL EXCEPT WIZ")
    if (!isExcept && isAllMatch) return true; // (eg. BRD in "ALL")
    return false;
  };

  var scrollToTable = function ($table) {
    window.setTimeout(function () {
      $('html, body').animate({ scrollTop: $table.offset().top }, 200);
    }, 100);
  };

  // Filter the provided table to only display rows for the provided class
  // abbreviation
  var filterTable = function ($table, classAbbrev) {
    $table.find('tr').each(function (i, tr) {
      var $tr = $(tr);
      var rowIsShown = isRowShown($tr, classAbbrev);
      $tr.toggle(rowIsShown);
    });
    scrollToTable($table);

    addFilterLink($table, false);
    $('.itemsUnfilterLink').click(function () {
      unfilterTable($table);
    });

    alert('Filtered to only show rows containing ' + classAbbrev + ' gear.');
  };

  // Restore a table to its previous, un-filtered state
  var unfilterTable = function ($table) {
    $table.find('tr').show();
    addFilterLink($table, true);
    scrollToTable($table);
  };

  // When a user clicks on a table class filter link, handle it by asking them
  // which class (and then filtering the table for that class)
  var handleItemFilterLinkClick = function (e) {
    var promptMessage =
      'Please enter the three-letter abbreviation for the ' +
      'class you want to filter by (eg. "brd" for "Bard").';

    var $table = $(e.target).closest('table');
    var classAbbrev = prompt(promptMessage).toUpperCase();
    if (!classAbbrev || classAbbrev.length !== 3 || classAbbrev === 'ALL') {
      alert(
        'A 3-digit abbreviation wasn\'t entered (or "All" was entered); ' +
          'filtering disabled'
      );
      unfilterTable($table);
      return false;
    }
    filterTable($table, classAbbrev);
  };

  // Adds either a filter or unfilter link (as determined by showFilter) to the
  // provided table
  var addFilterLink = function ($table, showFilter) {
    var $statsHeaderCells = $table.find('th:contains("Stats")');
    var filterType = showFilter ? 'Filter' : 'Unfilter';
    $statsHeaderCells.html(
      'Stats <span style="float:right">' +
        '<a style="text-decoration: underline" class="items' +
        filterType +
        'Link" href="#">' +
        filterType +
        (showFilter ? ' by Class' : '') +
        '</a></span>'
    );

    var $link = $statsHeaderCells.find('.items' + filterType + 'Link');
    if (showFilter) $link.click(handleItemFilterLinkClick);
    else
      $link.click(function () {
        unfilterTable($table);
      });
  };

  // Add class filtering links to all item tables
  var addItemFilteringLinks = function () {
    var $tables = $('table th:contains("Item Name")').closest('table');
    $tables.each(function (i, table) {
      var $table = $(table);
      // Make sure it has both "Item Name" and "Stats" columns
      if (!$table.has('th:contains("Stats")')) return;

      addFilterLink($table, true);
    });
  };
  addItemFilteringLinks();

  //*****************************************
  // End class-based filtering to item tables
  //*****************************************

  // Add No-Drop-Based filtering to the class equipment pages
  if (window.location.pathname.includes('Special:ClassSlotEquip')) {
    // Add checkboxes UI to the page
    $('table').before(
      '<label><input id="showNoDrop" type="checkbox" checked/> Show No Drop</label> ' +
        '<label><input id="showDroppable" type="checkbox" checked/> Show Droppable</label>' /*+
              '| <label><input id="showEffect" type="checkbox" checked/> Show With Effects</label>' +
              '<label><input id="showNoEffect" type="checkbox" checked/> Show Without Effects</label>'*/
    );

    // Handle when drop/no drop box is checked
    $('#showNoDrop, #showDroppable').change(function () {
      var showNoDrop = $('#showNoDrop').is(':checked');
      var showDroppable = $('#showDroppable').is(':checked');
      // NOTE: Some tables are weird and don't keep their TRs in THEAD
      //       .... but those rows do have an old bgcolor="#cccccc" attribute
      //       that we can use to identify (and not hide) them
      $('tbody tr[bgcolor!=#cccccc]').each(function (i, el) {
        var text = $(el).find('td:eq(0) .itemdata').text();
        var isNoDrop = text.includes('NO DROP');
        $(el).toggle((showNoDrop && isNoDrop) || (showDroppable && !isNoDrop));
      });
    });
    /*
Problem: the event handler needs to be merged so unchecking one box (eg. effect) doesn't undo the other (eg. no drop)

    // Handle when effect/no effect box is checked
    $('#showNoEfefct, #showEffect').change(function() {
      var showNoEffect = $('#showNoEffect').is(':checked');
      var showEffect = $('#showEffect').is(':checked');
      // NOTE: Some tables are weird and don't keep their TRs in THEAD
      //       .... but those rows do have an old bgcolor="#cccccc" attribute
      //       that we can use to identify (and not hide) them
      $('tbody tr[bgcolor!=#cccccc]').each(function(i, el) {
        var text = $(el).find('td:eq(0) .itemdata').text();
        var hasEffect = text.includes('NO DROP');
        $(el).toggle((showEffect && hasEffect) || (showNoEffect && !hasEffect));
      });
    });
*/
  }

  // Generic Table Filtering (for Template:TableFilterCheckbox)
  $('.table-filter-checkbox-container').each(function (i, el) {
    var $container = $(el);
    var matchSelector = $container.data('match');
    var text = $container.text();
    $container.html(
      '<label><input class="table-filter-checkbox" type="checkbox" ' +
        'value="' +
        matchSelector +
        '" checked />' +
        text +
        '</label>'
    );
  });

  var shouldRowBeShown = function (tr) {
    var $tr = $(tr);
    // Check all the checkboxes to see if any given row should appear
    // TODO: Instead of checking *every* checkbox, check ones in the same .filter-group
    return $('.table-filter-checkbox')
      .toArray()
      .reduce(function (isShown, checkbox) {
        var matchSelector = $(checkbox).val();
        var matches = $tr.is(matchSelector) || !!$tr.has(matchSelector).length;
        var isChecked = $(checkbox).attr('checked');
        return isShown && !!(isChecked || (!isChecked && !matches));
      }, true);
  };
  $('body').on('change', '.table-filter-checkbox', function (e) {
    $('table').show();
    $('table:not(.toc) tbody tr:not([bgcolor="#cccccc"])').each(function (
      i,
      tr
    ) {
      $(tr).toggle(shouldRowBeShown(tr));
    });
    // If all of a table's non-header rows are hidden, hide it
    $('table')
      .filter(function (i, table) {
        return !$(table).has('tr:visible:not([bgcolor="#cccccc"])').length;
      })
      .hide();
  });

  // Convert youtube template divs into actual iframes
  $('.youtube-placeholder').each(function (i, placeholder) {
    var $placeholder = $(placeholder);
    var data = $placeholder.data();
    var url = data.url;
    // Youtube gets mad if you try to embed watch URLs
    // @see https://stackoverflow.com/questions/25661182/embed-youtube-video-refused-to-display-in-a-frame-because-it-set-x-frame-opti
    //if (url.includes('/watch?') url = url.replace('/watch?', '/embed?');

    // TODO: get height/width from template?
    $placeholder.replaceWith(
      '<iframe ' +
        'width="' +
        (data.width || '') +
        '" ' +
        'height="' +
        (data.height || '') +
        '" ' +
        'src="' +
        url +
        '" ' +
        'frameborder="0" ' +
        'allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" ' +
        'allowfullscreen' +
        '></iframe>'
    );
  });


  importScript('MediaWiki:CheckboxLists.js');
  importScript('MediaWiki:LocMaps.js');

  // Fix searches with url-encoded text
  if (window.location.search.includes('title=Special:Search')) {
    var searchText = $('#searchText').val();
    var decoded = decodeURIComponent(searchText)
    if (decoded !== searchText) {
      // If we have URL-encoded text, which won't give results,
      // replace it with the decoded text and retry
      $('#searchText').val(decoded).closest('form').submit();
    }
  }

  var fullTitle = $('title').text();
  var title = fullTitle.substr(
    0,
    fullTitle.length - ' - Project 1999 Wiki'.length
  );
  switch (title) {
 case 'Admin:Spam Removal':
      importScript('MediaWiki:SpamRemoval.js');
       break;
    case 'Damage Calculator':
      importScript('MediaWiki:DamageCalculator.js');
      break;
    case 'Magelo Blue':
    case 'Magelo Green':
    case 'Magelo Red':
      $('.createboxInput').on('focus', function () {
        this.value = '';
      });
      break;
    // Give the Per-Level Hunting Guide Page its own JS
    case 'Per-Level Hunting Guide':
      importScript('MediaWiki:HuntingGuide.js');
      break;
    // The item category search also needs special JS
    case 'Item Category Search':
      importScript('MediaWiki:ItemCategorySearch.js');
      break;
    // So does the Solo Artist Challenge
    case 'Solo Artist Challenge':
      importScript('MediaWiki:SoloArtistChallenge.js');
      break;
    case 'Treasure Hunting Guide':
      importScript('MediaWiki:TreasureHuntingGuide.js');
      break;
    case 'Buff Lines':
      importScript('MediaWiki:BuffLines.js');
      break;
    case 'FashionQuest Finder':
      importScript('MediaWiki:FashionQuest.js');
      break;
    case 'Magelo Import':
    case 'Item ID Generator':
      importScript('MediaWiki:MageloImport.js');
      break;
    case 'Qeynos':
      var now = new Date();
      if (now.getHours() < 9 || now.getHours() > 21) {
        var $art = $('[src="/images/thumb/Qeynos_Day_Art.jpg/300px-Qeynos_Day_Art.jpg"]');
        $art.attr('src', '/images/thumb/Qeynos_Night_Art.jpg/300px-Qeynos_Night_Art.jpg');
        $art.parent().attr('href', 'https://wiki.project1999.com/File:Qeynos_Night_Art.jpg');
      }
      break;
    case 'Mobs By Level':
      // Basic DOM manipulation (since the wiki won't let us add <input> tags in the wiki text
      $('#placeholder').replaceWith(
        '<form id="form">' +
          'Class: <input id="class" style="width: 8em" value="Warrior" /> ' +
          'Level: <input id="level" style="width: 4em" /> ' +
          '<input type="submit"/>' +
          '</form>'
      );
      $('#form').submit(function () {
        var clazz = $('#class').val();
        var level = $('#level').val();
        window.location =
          'https://wiki.project1999.com/index.php?title=Special:Search&limit=500&offset=0&redirs=0&profile=default&search=%22Level%5C%3A%5C+' +
          level +
          '%22+-%22Shopkeeper%22+-%22Merchant%22+%22Class%5C%3A%5C+' +
          clazz +
          '%22+-"startMageloProfile"';
        return false;
      });
      break;
    case 'Patch Notes':
      // Let users control patch note width and how
      // the table of contents is shown

      var setNotesColumns = function (cols) {
        $('#notesWrapper').css('width', cols ? cols + 'em' : '');
      };
      var toggleNotRelevant = function (isChecked) {
        $('#notesWrapper').find(':not(:visible)').show();
        if (isChecked) return;

        $('#notesWrapper')
          .children()
          .filter(function (i, el) {
            var isRelevant = $(el).is('.relevant');
            $(el).toggle(isRelevant);
            // Show the patch header also
            if (isRelevant) $(el).prevUntil('h3').last().prev().show();
          });
      };
      var toggleTableOfContents = function (isChecked) {
        $('#toc').toggle(isChecked);
      };
      var togglePatchHeaders = function (isChecked) {
        $('.toclevel-4').toggle(isChecked);
      };

      var notesWidth = getStateByPage('notes-width') || '';
      var showTableOfContents =
        getStateByPage('show-table-of-contents') !== false;
      var showPatchHeaders = getStateByPage('show-patch-headers') !== false;
      var showNotRelevant = getStateByPage('show-not-relevant') !== false;

      setNotesColumns(notesWidth);
      toggleTableOfContents(showTableOfContents);
      togglePatchHeaders(showPatchHeaders);
      toggleNotRelevant(showNotRelevant);

      $('#patchNoteOptions').html(
        '<div><label><input ' +
          (showTableOfContents ? 'checked ' : '') +
          'id="show-table-of-contents" type="checkbox" /> Show Table of Contents?<label><br/>' +
          '<label><input ' +
          (showPatchHeaders ? 'checked ' : '') +
          'id="show-patch-headers"  type="checkbox"/> Show patch headers in Table of Contents?<label><br/>' +
          '<label><input ' +
          (showNotRelevant ? 'checked ' : '') +
          'id="show-not-relevant"  type="checkbox"/> Show content not marked as relevant to P99?<label></div>' +
          '<div><label>Limit notes to a width of <input id="notes-width" placeholder="40" style="width: 4em;" value="' +
          notesWidth +
          '" /> columns (for readability)</label></div>'
      );
      $('#notes-width').change(function (e) {
        var cols = parseFloat($(e.target).val());
        setNotesColumns(cols);
        setStateByPage('notes-width', cols);
      });
      $('#show-table-of-contents').change(function (e) {
        var isChecked = $(e.target).is(':checked');
        toggleTableOfContents(isChecked);
        setStateByPage('show-table-of-contents', isChecked);
      });
      $('#show-patch-headers').change(function (e) {
        var isChecked = $(e.target).is(':checked');
        togglePatchHeaders(isChecked);
        setStateByPage('show-patch-headers', isChecked);
      });
      $('#show-not-relevant').change(function (e) {
        var isChecked = $(e.target).is(':checked');
        toggleNotRelevant(isChecked);
        setStateByPage('show-not-relevant', isChecked);
      });

      break;
  }

  // Warn users who accidentally try to edit a templated section
  if (
    (window.location + '').includes('title=Template:Namedmobpage&action=edit')
  ) {
    alert(
      "Warning: You are attempting you edit the template for all named mobs.  You probably didn't mean to do that: you probably clicked on an edit link somewhere in the page and wound up here.  To avoid this simply go back and use the edit *tab* at the top of the page instead."
    );
  }

  // Warn uses who try to edit a mostly-transcluded section (and offer to take them to the transcluded page)
  var text = $('#wpTextbox1').val();
  var match = text && text.match(/\{\{\#lsth\:(.*?)\|\[\[(.*?)\]\](.*)\}\}/);
  if (match) {
    var pageName = match[1];
    var section = match[2];
    var extra = match[3] || '';
    var isEdit = (window.location + '').includes('action=edit');
    var isUnderTenLines = $('#wpTextbox1').val().split('\n').length < 10;
    var url = '/' + pageName + '#' + (section + extra).replace(/ /g, '_');
    var message =
      'This page uses "transclusion" to show part of another page, specifically the code:\n\n    {{#lsth:' +
      pageName +
      '|[[' +
      section +
      ']]' +
      extra +
      '}}\n\nIf you want to edit the transcluded section, click "Ok": you will be taken to that page.' +
      '  If you want to stay on this page, click "Cancel".';
    if (isEdit && isUnderTenLines && confirm(message)) location = url;
  }

  // *** AUCTION TRACKER RELATED ***

  var selectedServer = getGlobalState('selectedServer') || 'Blue';
  var buildTabHtml = function (serverNames) {
    var tabDivs = serverNames.map(function (name, i) {
      return (
        '<div id="' +
        name +
        'Tab" class="tab' +
        (name === selectedServer ? ' selected' : '') +
        '">' +
        name +
        '</div>'
      );
    });
    return (
      '<div class="tabs">' +
      tabDivs.join('') +
      '<div class="clear"></div></div>'
    );
  };

  var selectTab = function (name) {
    // show the server's box (and hide the others)
    $trackers.hide();
    var $tracker = $('#auc_' + name).show();

    // If the selected tab doesn't exist on this item, show the first tab instead
    if (!$tracker.length) $tracker = $('.auctrackerbox:first').show();

    // Select the tab for that server (and unselect the others)
    $('.auctrackerbox .tab').removeClass('selected');
    var $tab = $('.auctrackerbox .tab:contains("' + name + '")');
    if (!$tab.length) $tab = $('.auctrackerbox .tab:first');
    $tab.addClass('selected');
    setGlobalState('selectedServer', name);
  };

  var $trackers = $('.auctrackerbox');
  // if we're on an item page with an auction tracker
  if ($trackers.length) {
    // Convert IDs of "auc_Blue" into an array of ["Blue", "Green", ...]
    var servers = $trackers.toArray().map(function (tracker) {
      return tracker.id.substr(4);
    });
    $trackers.prepend(buildTabHtml(servers));
    selectTab(selectedServer);
    $trackers.on('click', '.tab', function (e) {
      selectTab($(e.target).text());
    });

    // Add links to search the forum for items to their auction tracker

    // Forum IDs come from forum URLs, eg. red's auction forum is:
    //   https://www.project1999.com/forums/forumdisplay.php?f=59
    // so  we have "red: 59 " below
    var forumIds = { blue: 27, green: 77, red: 59, teal: 78 };

    $('.auctrackerbox span span:contains("Project 1999 Auction Tracker")').each(
      function (i, el) {
        var $el = $(el);
        var serverName = $el
          .closest('.auctrackerbox')
          .attr('id')
          .substr(4)
          .toLowerCase();
        const forumSearchUrl =
          'https://www.project1999.com/forums/search.php?do=process&forumchoice[]=' +
          forumIds[serverName] +
          '&query=%22' +
          $('#firstHeading').text() +
          '%22';
        $el.append(
          '<a ' +
            'style="font-size: 0.5em; float: right" ' +
            'target="_new" ' +
            'href="' +
            forumSearchUrl +
            '"' +
            '>Search Forum</a>'
        );
      }
    );
  }
});