Jump to content
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 ]

MediaWiki:ItemCategorySearch.js

From p99 Backup

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.
/**
 * For any questions or comments on this code contact Loramin via the forum Private Message system.
 *
 * NOTE: This code would be shorter/simpler/easier to read if it was in ES6 instead of ES5, but
 *       since I can't guarantee that every user will have ES6 support (and since I didn't want to
 *       bother with Babel for something so small) I chose to use the (old/ugly) ES5 syntax.
 */

// URL on the wiki where we can get all of the categories
var categoriesUrl =
  'http://wiki.project1999.com/index.php?title=Special:Categories&offset=&limit=500';

/**
 * Fetches all of the available categories in the wiki
 */
var getCategories = function() {
  return $.get(categoriesUrl)
    .then(function(html) {
      var categories = $(html).find('.mw-spcontent li a').map(function(i, link) {
        return $(link).text();
      });
      return $.makeArray(categories);
    })
};

/**
 * Function for retrieving all item names from a single page (which one is determined by "fromItem"
 * for a provided category
 * @param {string} category - an item category, with or without "Category:" (eg. "Fashion: Leather")
 * @param fromItem - the item to start the pagination from (eg. "Impskin Gloves" would skip all
 *                   items from before "Impskin Gloves" and start the page on "Impskin Gloves".
 * @returns {Promise} - when this promise is resolved it will return an object with two
 *          properties:
 *            - items - the item names from the page
 *            - nextItem - the "fromItem" to get the next page (if any)
 */
var getItemsInCategoryPage = function(category, fromItem) {
  // Add "Category:" (unless the provided category already starts with it)
  if (!category.startsWith('Category:')) category = 'Category:' + category;

  // Build the URL
  var url = 'http://wiki.project1999.com/index.php?title=' + encodeURI(category);

  // Add a "frompage" parameter if we're trying to get any page other than the first
  if (fromItem) url += '&pagefrom=' + encodeURI(fromItem);

  // Go get the page
  return $.get(url)
    .then(function(html) {
      // jQuery-ify the returned HTML
      var $html = $(html);

      // Extract all of the <a> tags that look like: <li><a>Some Item</a></li>
      var $links = $html.find('li:not(#f-list li):not(.pBody li) a');

      // Map (and return) the item titles from within all of the links
      var itemNames = $links.map(function(i, link) {
        // Extract title
        return $(link).attr('title');
      });

      // jQuery overwrites the native "map" function with its own, which returns a jQuery object
      // (even when there isn't a single jQuery object or HTML element in the return value)
      // This fixes that.
      itemNames = $.makeArray(itemNames);

      // Grab the href of the first "next 200" link
      var nextHref = $html.find('a:contains("next 200")').eq(0).attr('href');

      // Extract the next item (if any)
      var match = nextHref && nextHref.match(/pagefrom=(.*?)#/);

      // Figure out if there is a nextItem (and if not return null instead of causing an error)
      var nextItem = match ? match[1] : null;

      // Return results
      return { itemNames: itemNames, nextItem: nextItem };
    });
};

var showNormalCursor = function() {
  $('body').css({ cursor: 'default'});
}

var showWaitCursor = function() {
  $('body').css({ cursor: 'wait'});
};

var getItemsInCategoryPages = function(category, nextItem, itemNames) {
  // Change the cursor to let the user know they need to wait
  showWaitCursor();

  // Go get all the items (and the next item) for the next page (or first page on first iteration)
  return getItemsInCategoryPage(category, nextItem)
    .then(function(result) {
      // Combine any itemNames from previous iterations with this one
      itemNames = (itemNames || []).concat(result.itemNames);

      if (result.nextItem) {
        // If there is a next item, get the items from a page that starts with it as the "pagefrom"
        return getItemsInCategoryPages(category, result.nextItem, itemNames);
      } else {
        // otherwise return what we have (and restore the cursor to normal)
        showNormalCursor();
        return itemNames;
      }
    });
};

/**
 * Helper function for finding items in array "left" which are also present in array "right"
 */
var findIntersection = function(left, right) {
  return left.filter(function(item) { // return all of the items in left ...
    return right.includes(item);      // ... which are also items in right
  });
};

/**
 * This is the main function which powers the entire page.  It takes 2-3 categories (if 0-1 are
 * provided it throws an error), looks up all items in those categories, and then returns only the
 * items which are present in all of those categories.
 */
var findCategoryIntersections = function(category1, category2, category3) {
  return Promise.all([
    getItemsInCategoryPages(category1), // go get the items in category 1
    getItemsInCategoryPages(category2) // and the ones in category 2
  ])
    .then(function(results) {
      // Figure out which items are in both categories
      var intersectionOf1And2 = findIntersection(results[0], results[1]);

      // If there's a third category ...
      if (category3) {
        // Go get its items and then return ...
        return getItemsInCategoryPages(category3)
          .then(function(category3Results) {
            // ... the interesection between those items and the previous interesection of #1 and #2
            return findIntersection(intersectionOf1And2 , category3Results);
          });
      }
      // Otherwise just return the intersection of #1 and #2
      return intersectionOf1And2;
    });
};

// "onCLick"
var handleSearchClick = function(e) {
  // Determine which categories the user picked
  var category1 = $('#category1').val();
  var category2 = $('#category2').val();
  var category3 = $('#category3').val();

  // Yell at them if they didn't pick at least two
  if (!category1 || !category2) return alert('Please select at least two categories');

  // Let them know to wait while go do the search
  showWaitCursor();
  findCategoryIntersections(category1, category2, category3)
    .then(function(results) {
      // Convert the results (['itemA', 'itemB', ...]) into <li><a>item</a></li> HTML
      var resultsHtml = results.map(function(item) {
        return '<li><a href="http://wiki.project1999.com/' + item + '">' + item + '</a>';
      });
      // Add the results HTML to the page and set the cursor back to normal
      $('#results').html(resultsHtml.join(''));
      showNormalCursor();
    });
};

// "onLoad"
showWaitCursor();  // Start the page off with the loading cursor, since we need to fetch categories
getCategories()
  .then(function(categories) {
    // Convert the categories (['categoryA', 'categoryB', ...]) into <option>category</option> HTML
    var categoryOptions = '<option></option>' + categories.map(function(category) {
      return '<option value="' + category + '">' + category + '</option>';
    });

    // Fill all three category selectors with the category options
    $('#category1Cell').html('<select id="category1">' + categoryOptions + '</select>');
    $('#category2Cell').html('<select id="category2">' + categoryOptions + '</select>');
    $('#category3Cell').html('<select id="category3">' + categoryOptions + '</select>');

    // Add the search button and results UL to the page (the wiki won't let us do it without JS)
    $('#controlsPlaceholder')
      .html('<button id="searchCategories">Search</button>')
      .after('<h2>Search Results</h2><ul id="results"></ul>')

    // Bind the search button to its handler and set the cursor back to normal (the page is ready)
    $('#searchCategories').click(handleSearchClick);
    showNormalCursor();
  });