marketplace.secondlife.com tools

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

  1. // ==UserScript==
  2. // @name marketplace.secondlife.com tools
  3. // @description Adds keyword highlight, category search, display options to Second Life Marketplace.
  4. // @namespace http://sites.google.com/site/cerisesorbet/
  5. // @include http://marketplace.secondlife.com/*
  6. // @include https://marketplace.secondlife.com/*
  7. // @include https://betasearch-marketplace.secondlife.com/*
  8. // @version 20151111
  9. // @copyright (c) 2012-2015 Cerise Sorbet
  10. // @license MIT License
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14.  
  15. function GetCookie(key) {
  16. var encKey = encodeURIComponent(key);
  17. var raw = document.cookie.split(/\s*;\s*/);
  18. for (var i = 0; i < raw.length; i++) {
  19. var pair = raw[i].split('=', 2);
  20. if (pair[0] == encKey && pair.length > 1)
  21. return decodeURIComponent(pair[1]);
  22. }
  23. return null;
  24. }
  25.  
  26. function SetCookie(key, value) {
  27. document.cookie = encodeURIComponent(key) + '=' + encodeURIComponent(value)
  28. + ';path=/;expires=' + new Date(Date.now() + 31556926000).toUTCString();
  29. }
  30.  
  31.  
  32. // attributes is an {attr: value, ...} object
  33. function BuildTag(tagName, attributes) {
  34. var rv = document.createElement(tagName);
  35. for (var i in attributes)
  36. rv.setAttribute(i, attributes[i]);
  37. return rv;
  38. }
  39.  
  40. // adapted from http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
  41. function GetParams(search) {
  42. var params = {};
  43. var segment = search.replace(/^\?/,'').split('&');
  44. var s;
  45. for (var i = 0; i < segment.length; i++) {
  46. if (!segment[i]) continue;
  47. s = segment[i].split('=');
  48. var sVal = s[1].split('+');
  49. for (var j = 0; j < sVal.length; j++)
  50. sVal[j] = decodeURIComponent(sVal[j]);
  51. params[decodeURIComponent(s[0])] = sVal.join(' ');
  52. }
  53. return params;
  54. }
  55.  
  56. // fetch the keywords from the meta tags and display them inline
  57. function ShowKeywords() {
  58. var metaTags = document.getElementsByTagName('head')[0].getElementsByTagName('meta');
  59. var keywords = '<span style="color: #333333"><small>[no keywords]</small></span>';
  60. if (metaTags) {
  61. for (var i = metaTags.length - 1; i >= 0; i--) {
  62. if (metaTags[i].name == "keywords") {
  63. if (metaTags[i].content.length)
  64. keywords = '<span style="color: #cc6633" <b>Keywords:</b> ' + metaTags[i].content + '</span>';
  65. break;
  66. }
  67. }
  68. }
  69. var tabs = document.getElementById('product-tabs');
  70. if (tabs) {
  71. var kwDiv = document.createElement('div');
  72. kwDiv.className = 'span-6';
  73. kwDiv.innerHTML = keywords;
  74. tabs.parentNode.insertBefore(kwDiv, tabs);
  75. }
  76. }
  77.  
  78. function SetupShowFeatured() {
  79. var featuredItems = document.getElementById('featured-items'); // main page
  80. if (!featuredItems)
  81. featuredItems = document.getElementById('featured-items-category'); // category page
  82. if (featuredItems) {
  83. var featHeading = featuredItems.getElementsByTagName('h2')[0];
  84. if (featHeading) {
  85. var showHideLink = document.createElement('span');
  86. showHideLink.innerHTML = '<button id="showFeatured"></button> ';
  87. featHeading.insertBefore(showHideLink, featHeading.firstChild);
  88. ShowFeatured(GetCookie('hideFeatured'), featuredItems);
  89. document.getElementById('showFeatured').addEventListener('click', function() { ShowFeatured('toggle', featuredItems); }, false);
  90. }
  91. }
  92. }
  93.  
  94. function ShowFeatured(hideFeatured, featuredItems) {
  95. if (hideFeatured == 'toggle')
  96. hideFeatured = !(GetCookie('hideFeatured') == 'true');
  97. else
  98. hideFeatured = (hideFeatured == 'true');
  99.  
  100. SetCookie('hideFeatured', hideFeatured ? 'true' : 'false');
  101. // find the featured items and show or hide them as requested
  102. var scrollable = document.getElementById('featured-items-scrollable');
  103. if (scrollable)
  104. scrollable.style.display = hideFeatured ? 'none' : '';
  105. var carousels = featuredItems.getElementsByTagName('div');
  106. for (var i = carousels.length - 1; i >= 0; i--) {
  107. if (carousels[i].className == 'carousel-controls') {
  108. var caroKids = carousels[i].childNodes;
  109. for (var j = 0; j < caroKids.length; j++) {
  110. if (caroKids[j].style)
  111. caroKids[j].style.display = hideFeatured ? 'none' : '';
  112. }
  113. }
  114. }
  115. // set the button text to match the show/hide state
  116. var showHideA = document.getElementById('showFeatured');
  117. if (showHideA) {
  118. showHideA.innerHTML = hideFeatured ? '+' : '-';
  119. showHideA.blur();
  120. }
  121. }
  122.  
  123. function ForceResultsParams() {
  124. var wantRefresh = false;
  125.  
  126. // if these values are strange there can be reload loops, so fix them.
  127. // It would be better to check these against live values in the sort header but those are not on all pages.
  128.  
  129. var sortOrder = GetCookie('sortBy');
  130. if (!~['relevance_desc', 'created_at_desc', 'created_at_asc', 'price_asc', 'price_desc', 'name_asc', 'name_desc',
  131. 'prim_count_asc', 'prim_count_desc', 'average_rating_desc', 'sales_rank_asc'].indexOf(sortOrder))
  132. sortOrder = 'relevance_desc';
  133. SetCookie('sortBy', sortOrder); // refresh
  134.  
  135. // If the link came from outside, display parameters could be the defaults.
  136. if (/^\/?products\/search$/.test(window.location.pathname) || /^\/?stores\/\d+$/.test(window.location.pathname)) {
  137.  
  138. // XXX skipping this for now, some browsers do not like it.
  139. // var parsed = GetParams(window.location.search);
  140.  
  141. // if (!parsed['search[sort]'])
  142. // parsed['search[sort]'] = 'relevance_desc';
  143. // if (parsed['search[sort]'] != sortOrder) {
  144. // parsed['search[sort]'] = sortOrder;
  145. // wantRefresh = true;
  146. // }
  147. if (wantRefresh) {
  148. var newSearch = '?';
  149. for (var key in parsed)
  150. newSearch += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(parsed[key]);
  151. window.location.search = newSearch; // reload the page
  152. }
  153. }
  154.  
  155. // fix up the search form
  156. var searchForm = document.getElementById('search');
  157. if (searchForm) {
  158. var forms = searchForm.getElementsByTagName('form');
  159. var newLink = document.createElement('a');
  160. for (var i = 0; i < forms.length; i++) {
  161. newLink.href = forms[i].action;
  162. if (/^\/?products\/search$/.test(newLink.pathname))
  163. forms[i].appendChild(BuildTag('input', {type: 'hidden', name: 'search[sort]', value: sortOrder}));
  164. break;
  165. }
  166. }
  167.  
  168. matureCookie = (GetCookie('overrideMature') == 'true');
  169.  
  170. // change all search and store links to saved items per page
  171. var tags, i;
  172. tags = document.getElementsByTagName('a');
  173. for (i = 0; i < tags.length; i++) {
  174. if (window.location.hostname != tags[i].hostname) continue;
  175. if (/^\/?products\/search$/.test(tags[i].pathname) || /^\/?stores\/\d+$/.test(tags[i].pathname)) {
  176. var parsed = GetParams(tags[i].search);
  177. parsed['search[sort]'] = sortOrder;
  178. tags[i].search = '?';
  179. for (var key in parsed)
  180. tags[i].search += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(parsed[key]);
  181. }
  182. }
  183.  
  184. // SLM bugfix: for the category sidebar links, make sure they go to page 1.
  185. var searchCat = document.getElementById('search-category');
  186. if (searchCat) {
  187. tags = searchCat.getElementsByTagName('a');
  188. for (i = 0; i < tags.length; i++) {
  189. if (window.location.hostname != tags[i].hostname) continue;
  190. if (/^\/?products\/search$/.test(tags[i].pathname)) {
  191. var parsed = GetParams(tags[i].search);
  192. parsed['search[page]'] = '1';
  193. tags[i].search = '?';
  194. for (var key in parsed)
  195. tags[i].search += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(parsed[key]);
  196. }
  197. }
  198. }
  199. }
  200.  
  201. function SaveResultsParam() {
  202. var selectVal;
  203.  
  204. if (/(^|\s)sort-by(\s|$)/.test(this.className)) {
  205. selectVal = document.getElementById('search_sort_id');
  206. if (selectVal)
  207. SetCookie('sortBy', selectVal.options[selectVal.selectedIndex].value);
  208. }
  209. else
  210. return;
  211. }
  212.  
  213. function SetupSearchEventListeners() {
  214. var i;
  215.  
  216. var container = document.getElementById('search-results-container');
  217. if (!container)
  218. return;
  219.  
  220. var tags = container.getElementsByTagName('span');
  221. if (tags) {
  222. for (i = 0; i < tags.length; i++) {
  223. if (tags[i].className && /(^|\s)sort-by(\s|$)/.test(tags[i].className)) {
  224. tags[i].addEventListener('change', SaveResultsParam, false);
  225. break;
  226. }
  227. }
  228. }
  229. }
  230.  
  231. // Add "search in this category"
  232. function AddCurrentCategory() {
  233. var thisCatText = "This category"; // no I18N, but better than hovercfrafts full of eels.
  234. var currentCat = '1'; // all categories
  235. var parsed;
  236.  
  237. var catDrop = document.getElementById('top_search_category_id');
  238. if (!catDrop)
  239. return;
  240.  
  241. parsed = GetParams(window.location.search);
  242. if (parsed['search[category_id]']) {
  243. currentCat = parsed['search[category_id]'];
  244. // try to find the cat
  245. var catTags = document.getElementsByClassName('current-category');
  246. if (catTags.length) {
  247. if (catTags[0].nodeName == 'SPAN')
  248. thisCatText = catTags[0].innerHTML;
  249. }
  250. }
  251. else {
  252. var breadcrumb = document.getElementById('breadcrumb');
  253. if (breadcrumb) {
  254. var tags = breadcrumb.getElementsByTagName('a');
  255. if (tags.length) {
  256. parsed = GetParams(tags[tags.length - 1].search);
  257. if (parsed['search[category_id]']) {
  258. currentCat = parsed['search[category_id]'];
  259. thisCatText = tags[tags.length - 1].innerHTML;
  260. }
  261. }
  262. }
  263. }
  264.  
  265. if (currentCat != '1') { // no need to add if category is All
  266. // Add it to the search form if it is not there, else activate it
  267. var newOption = document.createElement('option');
  268. newOption.value = parsed['search[category_id]'];
  269. newOption.innerHTML = thisCatText;
  270. catDrop.insertBefore(newOption, catDrop.firstChild);
  271. catDrop.selectedIndex = 0;
  272. }
  273.  
  274. // try to get a current store too
  275. var storeID = null;
  276. var mat;
  277.  
  278. mat = window.location.pathname.match(/^\/?stores\/\d+$/); // from our custom search
  279. if (mat) {
  280. storeID = mat[0].split('/').pop();
  281. }
  282. else if (parsed['search[store_id]']) { // standard store page
  283. storeID = parsed['search[store_id]'];
  284. }
  285. else { // try on product pages
  286. var merchBox = document.getElementById('merchant-box');
  287. if (merchBox) {
  288. var merchLinks = merchBox.getElementsByTagName('a');
  289. if (merchLinks.length) {
  290. mat = merchLinks[0].pathname.match(/^\/?stores\/\d+$/);
  291. if (mat)
  292. storeID = mat[0].split('/').pop();
  293. }
  294. }
  295. }
  296. }
  297.  
  298. SetupSearchEventListeners();
  299. ForceResultsParams();
  300. AddCurrentCategory();
  301. ShowKeywords();
  302. SetupShowFeatured();