Greasy Fork is available in English.

ao3 saved filters

Adds fields for persistent global & fandom filters to works index pages on AO3

  1. // ==UserScript==
  2. // @name ao3 saved filters
  3. // @description Adds fields for persistent global & fandom filters to works index pages on AO3
  4. // @namespace ao3
  5. // @include http*://www.archiveofourown.org/*works*
  6. // @include http*://archiveofourown.org/*works*
  7. // @grant none
  8. // @version 1.5
  9. // ==/UserScript==
  10.  
  11. (function ($) {
  12. // config
  13. var TAG_OWNERSHIP_PERCENT = 70; // the top fandom which owns works in the current tag must own at least this percent in order to be considered the search's active fandom
  14.  
  15. var works = $('#main.works-index'), form = $('form#work-filters');
  16. if (!works[0] || !form[0]) return;
  17.  
  18. var fandomName = (function () {
  19. var fandom = $('#include_fandom_tags label').first().text(),
  20. fandomCount = parseInt(
  21. fandom.substring(fandom.lastIndexOf('(') + 1, fandom.lastIndexOf(')'))
  22. ),
  23. tagCount = works.find('.heading').first().text();
  24.  
  25. tagCount = tagCount.substring(0, tagCount.indexOf(' Works'));
  26. tagCount = parseInt(tagCount.substring(tagCount.lastIndexOf(' ') + 1));
  27.  
  28. fandom = fandom.substring(0, fandom.lastIndexOf('(')).trim();
  29.  
  30. if (!fandom || !fandomCount || !tagCount) { return; }
  31.  
  32. return (fandomCount / tagCount * 100 > TAG_OWNERSHIP_PERCENT) ? fandom : null;
  33. })(),
  34. tempKey = 'temp-filter', tempFilter = localStorage[tempKey],
  35. tempGlobalKey = 'temp-global-filter',
  36. tempGlobalFilter = localStorage[tempGlobalKey],
  37. tempFandomKey = 'temp-fandom-filter',
  38. tempFandomFilter = localStorage[tempFandomKey],
  39. globalKey = 'global-filter', fandomKey = fandomName ? 'filter-' + fandomName : '',
  40. globalBox = $('<textarea>').val(localStorage[globalKey] ?
  41. localStorage[globalKey] : ''),
  42. fandomBox = fandomKey ?
  43. globalBox.clone().val(localStorage[fandomKey] ? localStorage[fandomKey] : '') : $(),
  44. search = $('#work_search_query'),
  45. dt = search.parents('dd').first().prev(dt),
  46. realSearch = $('<textarea>')
  47. .attr('name', search.attr('name'))
  48. .css('display', 'none')
  49. .insertAfter(search.removeAttr('name')),
  50. collapser = $('<dt>').addClass('saved-filters-collapser'),
  51. rightArrow = $('<img>').attr('src', '/images/arrow-right.gif?1352358192'),
  52. downArrow = $('<img>').attr('src', '/images/arrow-down.gif?1352358192'),
  53. container = $('<div>').addClass('saved-filters'),
  54. prevSearch = (function () {
  55. var ps, key = realSearch.attr('name') + '=';
  56. if (decodeURIComponent(window.location).indexOf(key) > 0) {
  57. ps = decodeURIComponent(window.location);
  58. ps = ps.substring(ps.indexOf(key) + key.length);
  59. ps = ps.indexOf('&') != -1 ? ps.substring(0, ps.indexOf('&')) : ps;
  60. }
  61. return ps;
  62. })();
  63.  
  64. $('<style>').text(
  65. '.saved-filters-collapser { cursor: pointer; } .saved-filters, saved-filters > div { margin-bottom: 0.6em; } .saved-filters { border-style: solid; border-width: 1px; padding: 0.6em; } .saved-filters textarea { min-height: 8em; } .saved-filters div label { padding-left: 3px; } .prev-search span { color: #000; } .prev-search .temp { background: #ACEA72; } .prev-search .global { background: #93D2ED; } .prev-search .fandom { background: #B9AAED; }'
  66. ).appendTo($('head'));
  67.  
  68. globalBox.addClass('global-filter')
  69. .add(fandomBox.addClass('fandom-filter'))
  70. .each(function () {
  71. var ta = $(this),
  72. cls = ta.attr('class'),
  73. title = cls.charAt(0).toUpperCase() + cls.substring(1, cls.indexOf('-')) + ':';
  74.  
  75. $('<div>').addClass(`${cls} ao3-saved-filters-section`)
  76. .prepend(title)
  77. .append(ta.removeClass(),
  78. $('<label>').text('Enabled')
  79. .prepend(
  80. $('<input>').attr({ 'type': 'checkbox' })
  81. .addClass('js-enabled-checkbox')
  82. .css({
  83. clip: 'auto',
  84. 'margin-right': '0.3em',
  85. position: 'relative',
  86. width: 'auto',
  87. })
  88. ),
  89. $('<button>')
  90. .attr('type', 'button')
  91. .addClass('action js-save-filter')
  92. .text('Save')
  93. ).appendTo(container);
  94. });
  95.  
  96. container.find('.ao3-saved-filters-section').each(function () {
  97. var $section = $(this),
  98. $checkbox = $section.find('.js-enabled-checkbox'),
  99. $saveButton = $section.find('.js-save-filter'),
  100. key = $saveButton.parents('.global-filter')[0] ? globalKey : fandomKey,
  101. checked = localStorage[key + '-on'] !== 'false';
  102.  
  103. $checkbox.attr('checked', checked);
  104. $saveButton.click(function () {
  105. localStorage[key] = $saveButton.siblings('textarea').val();
  106. localStorage[key + '-on'] = $checkbox.is(':checked') + '';
  107. });
  108. });
  109.  
  110. if (tempFilter && search.val().indexOf(tempFilter) != -1) {
  111. search.val(tempFilter);
  112. } else {
  113. localStorage[tempFilter] = '';
  114. search.val('');
  115. }
  116.  
  117. container = $('<dd>').append(container);
  118.  
  119. collapser.prepend(rightArrow,
  120. ' Saved filters'
  121. ).click(function () {
  122. container.toggle();
  123. collapser.children('img').replaceWith(
  124. container.is(':visible') ? downArrow : rightArrow);
  125. }).add(container.hide()).insertBefore(dt);
  126.  
  127. form.submit(function () {
  128. var val = search.val() || '';
  129.  
  130. container.find('.ao3-saved-filters-section').each(function () {
  131. var $section = $(this);
  132. var $textarea = $section.find('textarea');
  133. var enabled = $section.find('.js-enabled-checkbox').is(':checked');
  134. var key = $textarea.parents('.global-filter')[0] ? tempGlobalKey : tempFandomKey;
  135.  
  136. if ($textarea.val() && enabled) {
  137. localStorage[key] = $textarea.val();
  138.  
  139. if ((' ' + val + ' ').indexOf(' ' + $textarea.val() + ' ') < 0) {
  140. val += ' ' + $textarea.val();
  141. }
  142. } else if (localStorage[key]) {
  143. localStorage[key] = '';
  144. }
  145. });
  146.  
  147. localStorage[tempKey] = search.val();
  148. realSearch.val(val);
  149. });
  150.  
  151. if (prevSearch) {
  152. prevSearch = 'Your filter was: ' + decodeURIComponent(prevSearch).split('+').join(' ') + '.';
  153. if (tempFilter) {
  154. prevSearch = prevSearch.replace(tempFilter, '<span class="temp">' + tempFilter + '</span>');
  155. }
  156. if (tempGlobalFilter) {
  157. prevSearch = prevSearch.replace(tempGlobalFilter, '<span class="global">' + tempGlobalFilter + '</span>');
  158. }
  159. if (tempFandomFilter) {
  160. prevSearch = prevSearch.replace(tempFandomFilter, '<span class="fandom">' + tempFandomFilter + '</span>');
  161. }
  162.  
  163. works.find('.heading').first().after(
  164. $('<div>').addClass('prev-search').append(prevSearch));
  165. } else if ((localStorage[globalKey] && localStorage[globalKey + '-on'] !== 'false') ||
  166. (localStorage[fandomKey] && localStorage[fandomKey + '-on'] !== 'false')) {
  167. form.submit();
  168. }
  169.  
  170. })(window.jQuery);