marketplace.secondlife.com tools

Adds keyword highlight, category search, display options to Second Life Marketplace.

// ==UserScript==
// @name           marketplace.secondlife.com tools
// @description    Adds keyword highlight, category search, display options to Second Life Marketplace.
// @namespace      http://sites.google.com/site/cerisesorbet/
// @include        http://marketplace.secondlife.com/*
// @include        https://marketplace.secondlife.com/*
// @include	   https://betasearch-marketplace.secondlife.com/*
// @version        20151111
// @copyright      (c) 2012-2015 Cerise Sorbet
// @license        MIT License
// @grant          none
// ==/UserScript==


function GetCookie(key) {
  var encKey = encodeURIComponent(key);
  var raw = document.cookie.split(/\s*;\s*/);
  for (var i = 0; i < raw.length; i++) {
    var pair = raw[i].split('=', 2);
    if (pair[0] == encKey && pair.length > 1)
      return decodeURIComponent(pair[1]);
  }
  return null;
}

function SetCookie(key, value) {
  document.cookie = encodeURIComponent(key) + '=' + encodeURIComponent(value) 
    + ';path=/;expires=' + new Date(Date.now() + 31556926000).toUTCString();
}


//  attributes is an {attr: value, ...} object
function BuildTag(tagName, attributes) {
  var rv = document.createElement(tagName);
  for (var i in attributes)
    rv.setAttribute(i, attributes[i]);
  return rv;
}

// adapted from http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
function GetParams(search) {
  var params = {};
  var segment = search.replace(/^\?/,'').split('&');
  var s;
  for (var i = 0; i < segment.length; i++) {
    if (!segment[i]) continue;
    s = segment[i].split('=');
    var sVal = s[1].split('+');
    for (var j = 0; j < sVal.length; j++)
      sVal[j] = decodeURIComponent(sVal[j]);
    params[decodeURIComponent(s[0])] = sVal.join(' ');
  }
  return params;
}

// fetch the keywords from the meta tags and display them inline
function ShowKeywords() {
  var metaTags = document.getElementsByTagName('head')[0].getElementsByTagName('meta');
  var keywords = '<span style="color: #333333"><small>[no keywords]</small></span>';
  if (metaTags) {
    for (var i =  metaTags.length - 1; i >= 0; i--) {
      if (metaTags[i].name == "keywords") {
        if  (metaTags[i].content.length)
          keywords = '<span style="color: #cc6633" <b>Keywords:</b> ' +  metaTags[i].content + '</span>';
        break;
      }
    }
  }
  var tabs = document.getElementById('product-tabs');
  if (tabs) {
    var kwDiv = document.createElement('div');
    kwDiv.className = 'span-6';
    kwDiv.innerHTML = keywords;
    tabs.parentNode.insertBefore(kwDiv, tabs);
  }
}

function SetupShowFeatured() {
  var featuredItems = document.getElementById('featured-items'); // main page
  if (!featuredItems)
    featuredItems = document.getElementById('featured-items-category'); // category page
  if (featuredItems) {
    var featHeading = featuredItems.getElementsByTagName('h2')[0];
    if (featHeading) {
      var showHideLink = document.createElement('span');
      showHideLink.innerHTML = '<button id="showFeatured"></button> ';
      featHeading.insertBefore(showHideLink, featHeading.firstChild);
      ShowFeatured(GetCookie('hideFeatured'), featuredItems);
      document.getElementById('showFeatured').addEventListener('click', function() { ShowFeatured('toggle', featuredItems); }, false);
    }
  }
}

function ShowFeatured(hideFeatured, featuredItems) {
  if (hideFeatured == 'toggle')
    hideFeatured = !(GetCookie('hideFeatured') == 'true');
  else
    hideFeatured = (hideFeatured == 'true');

  SetCookie('hideFeatured', hideFeatured ? 'true' : 'false');
    
  // find the featured items and show or hide them as requested
  var scrollable = document.getElementById('featured-items-scrollable');
  if (scrollable)
    scrollable.style.display = hideFeatured ? 'none' : '';
  var carousels = featuredItems.getElementsByTagName('div');
  for (var i = carousels.length - 1; i >= 0; i--) {
    if (carousels[i].className == 'carousel-controls') {
      var caroKids = carousels[i].childNodes;
      for (var j = 0; j <  caroKids.length; j++) {
        if (caroKids[j].style)
          caroKids[j].style.display = hideFeatured ? 'none' : '';
      }
    }
  }
  // set the button text to match the show/hide state
  var showHideA = document.getElementById('showFeatured');
  if (showHideA) {
    showHideA.innerHTML = hideFeatured ? '+' : '-';
    showHideA.blur();
  }
}

function ForceResultsParams() {
  var wantRefresh = false;

  // if these values are strange there can be reload loops, so fix them.
  // It would be better to check these against live values in the sort header but those are not on all pages.

  var sortOrder = GetCookie('sortBy');
  if (!~['relevance_desc', 'created_at_desc', 'created_at_asc', 'price_asc', 'price_desc', 'name_asc', 'name_desc',
         'prim_count_asc', 'prim_count_desc', 'average_rating_desc', 'sales_rank_asc'].indexOf(sortOrder))
    sortOrder = 'relevance_desc';
  SetCookie('sortBy', sortOrder); // refresh

  // If the link came from outside, display parameters could be the defaults.
  if (/^\/?products\/search$/.test(window.location.pathname) || /^\/?stores\/\d+$/.test(window.location.pathname)) {

// XXX skipping this for now, some browsers do not like it.
//     var parsed = GetParams(window.location.search);

//     if (!parsed['search[sort]'])
//       parsed['search[sort]'] = 'relevance_desc';
//     if (parsed['search[sort]'] != sortOrder) {
//       parsed['search[sort]'] = sortOrder;
//       wantRefresh = true;
//     }
    
    if (wantRefresh) {
      var newSearch = '?';
      for (var key in parsed)
        newSearch += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(parsed[key]);
      window.location.search = newSearch; // reload the page
    }
  }

  // fix up the search form
  var searchForm = document.getElementById('search');
  if (searchForm) {
    var forms = searchForm.getElementsByTagName('form');
    var newLink = document.createElement('a');
    for (var i = 0; i < forms.length; i++) {
      newLink.href = forms[i].action;
      if (/^\/?products\/search$/.test(newLink.pathname))
      forms[i].appendChild(BuildTag('input', {type: 'hidden', name: 'search[sort]', value: sortOrder}));
      break;
    }
  }

  matureCookie = (GetCookie('overrideMature') == 'true');

  // change all search and store links to saved items per page
  var tags, i;
  tags = document.getElementsByTagName('a');
  for (i = 0; i < tags.length; i++) {
    if (window.location.hostname != tags[i].hostname) continue;
    if (/^\/?products\/search$/.test(tags[i].pathname) ||  /^\/?stores\/\d+$/.test(tags[i].pathname)) {
      var parsed = GetParams(tags[i].search);
      parsed['search[sort]'] = sortOrder;        
      tags[i].search = '?';
      for (var key in parsed)
        tags[i].search += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(parsed[key]);
    }
  }

  // SLM bugfix: for the category sidebar links, make sure they go to page 1.
  var searchCat = document.getElementById('search-category');
  if (searchCat) {
    tags = searchCat.getElementsByTagName('a');
    for (i = 0; i < tags.length; i++) {
      if (window.location.hostname != tags[i].hostname) continue;
      if (/^\/?products\/search$/.test(tags[i].pathname)) {
        var parsed = GetParams(tags[i].search);
        parsed['search[page]'] = '1';
        tags[i].search = '?';
        for (var key in parsed)
          tags[i].search += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(parsed[key]);
      }
    }
  }
}

function SaveResultsParam() {
  var selectVal;

  if (/(^|\s)sort-by(\s|$)/.test(this.className)) {
    selectVal = document.getElementById('search_sort_id');
    if (selectVal)
      SetCookie('sortBy', selectVal.options[selectVal.selectedIndex].value);
  }
  else
    return;
}

function SetupSearchEventListeners() {
  var i;

  var container = document.getElementById('search-results-container');
  if (!container)
    return;

  var tags = container.getElementsByTagName('span');
  if (tags) {
    for (i = 0; i < tags.length; i++) {
      if (tags[i].className && /(^|\s)sort-by(\s|$)/.test(tags[i].className)) {
        tags[i].addEventListener('change', SaveResultsParam, false);
        break;
      }
    }
  }
}

// Add "search in this category"
function AddCurrentCategory() {
  var thisCatText = "This category"; // no I18N, but better than hovercfrafts full of eels.
  var currentCat = '1'; // all categories
  var parsed;

  var catDrop = document.getElementById('top_search_category_id');
  if (!catDrop)
    return;

  parsed = GetParams(window.location.search);
  if (parsed['search[category_id]']) {
    currentCat = parsed['search[category_id]'];
    // try to find the cat
    var catTags = document.getElementsByClassName('current-category');
    if (catTags.length) {
      if (catTags[0].nodeName == 'SPAN')
        thisCatText = catTags[0].innerHTML;
    }
  }
  else {
    var breadcrumb = document.getElementById('breadcrumb');
    if (breadcrumb) {
      var tags = breadcrumb.getElementsByTagName('a');
      if (tags.length) {
        parsed = GetParams(tags[tags.length - 1].search);
        if (parsed['search[category_id]']) {
          currentCat = parsed['search[category_id]'];
          thisCatText = tags[tags.length - 1].innerHTML;
        }
      }
    }
  }

  if (currentCat != '1') { // no need to add if category is All
    // Add it to the search form if it is not there, else activate it
    var newOption = document.createElement('option');
    newOption.value = parsed['search[category_id]'];
    newOption.innerHTML = thisCatText;
    catDrop.insertBefore(newOption, catDrop.firstChild);
    catDrop.selectedIndex = 0;
  }

  // try to get a current store too
  var storeID = null;
  var mat;

  mat = window.location.pathname.match(/^\/?stores\/\d+$/); // from our custom search
  if (mat) {
      storeID = mat[0].split('/').pop();
  }
  else if (parsed['search[store_id]']) { // standard store page
      storeID = parsed['search[store_id]'];
  }
  else { // try on product pages
    var merchBox = document.getElementById('merchant-box');
    if (merchBox) {
      var merchLinks = merchBox.getElementsByTagName('a');
      if (merchLinks.length) {
        mat = merchLinks[0].pathname.match(/^\/?stores\/\d+$/);
        if (mat)
          storeID = mat[0].split('/').pop();
      }
    }
  }
}

SetupSearchEventListeners();
ForceResultsParams();
AddCurrentCategory();
ShowKeywords();
SetupShowFeatured();