ao3 savior

Hide specified works on AO3

目前为 2017-05-02 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name ao3 savior
  3. // @description Hide specified works on AO3
  4. // @namespace ao3
  5. // @include http*://archiveofourown.org/*
  6. // @grant none
  7. // @version 1.9
  8. // ==/UserScript==
  9.  
  10.  
  11. ;(function() {
  12. var STYLE = '\n html body .ao3-savior-hidden {\n display: none;\n }\n \n .ao3-savior-cut {\n display: none;\n }\n \n .ao3-savior-cut::after {\n clear: both;\n content: \'\';\n display: block;\n }\n \n .ao3-savior-reason {\n margin-left: 5px;\n }\n \n .ao3-savior-hide-reasons .ao3-savior-reason {\n display: none;\n }\n \n .ao3-savior-unhide .ao3-savior-cut {\n display: block;\n }\n \n .ao3-savior-fold {\n align-items: center;\n display: flex;\n justify-content: flex-start;\n }\n \n .ao3-savior-unhide .ao3-savior-fold {\n background: #ffc68e;\n }\n \n button.ao3-savior-toggle {\n margin-left: auto;\n }\n';
  13.  
  14. function addStyle() {
  15. var style = document.createElement('style');
  16. style.innerHTML = STYLE;
  17. style.className = 'ao3-savior';
  18.  
  19. document.head.appendChild(style);
  20. }
  21.  
  22. var CSS_NAMESPACE = 'ao3-savior';
  23.  
  24. var getCut = function getCut(work) {
  25. var cut = document.createElement('div');
  26.  
  27. cut.className = CSS_NAMESPACE + '-cut';
  28. cut.innerHTML = work.innerHTML;
  29.  
  30. return cut;
  31. };
  32.  
  33. var getFold = function getFold(reason) {
  34. var fold = document.createElement('div');
  35. var note = document.createElement('span');
  36.  
  37. fold.className = CSS_NAMESPACE + '-fold';
  38. note.className = CSS_NAMESPACE + '-note';
  39.  
  40. note.innerHTML = 'This work is hidden!';
  41.  
  42. fold.appendChild(note);
  43. fold.append(' ');
  44. fold.appendChild(getReasonSpan(reason));
  45. fold.appendChild(getToggleButton());
  46.  
  47. return fold;
  48. };
  49.  
  50. var getToggleButton = function getToggleButton() {
  51. var button = document.createElement('button');
  52. var unhideClassFragment = ' ' + CSS_NAMESPACE + '-unhide';
  53.  
  54. button.innerHTML = 'Unhide';
  55. button.className = CSS_NAMESPACE + '-toggle';
  56.  
  57. button.addEventListener('click', function (event) {
  58. var work = event.target.closest('.' + CSS_NAMESPACE + '-work');
  59.  
  60. if (work.className.indexOf(unhideClassFragment) !== -1) {
  61. work.className = work.className.replace(unhideClassFragment, '');
  62. work.querySelector('.' + CSS_NAMESPACE + '-note').innerHTML = 'This work is hidden.';
  63. event.target.innerHTML = 'Unhide';
  64. } else {
  65. work.className += unhideClassFragment;
  66. work.querySelector('.' + CSS_NAMESPACE + '-note').innerHTML = 'This work was hidden.';
  67. event.target.innerHTML = 'Hide';
  68. }
  69. });
  70.  
  71. return button;
  72. };
  73.  
  74. var getReasonSpan = function getReasonSpan(reason) {
  75. var span = document.createElement('span');
  76. var tag = reason.tag,
  77. author = reason.author,
  78. title = reason.title,
  79. summary = reason.summary;
  80.  
  81. var text = void 0;
  82.  
  83. if (tag) {
  84. text = 'tags include <strong>' + tag + '</strong>';
  85. } else if (author) {
  86. text = 'authors include <strong>' + author + '</strong>';
  87. } else if (title) {
  88. text = 'title is <strong>' + title + '</strong>';
  89. } else if (summary) {
  90. text = 'summary includes <strong>' + summary + '</strong>';
  91. }
  92.  
  93. if (text) {
  94. span.innerHTML = '(Reason: ' + text + '.)';
  95. }
  96.  
  97. span.className = CSS_NAMESPACE + '-reason';
  98.  
  99. return span;
  100. };
  101.  
  102. function blockWork(work, reason, config) {
  103. if (!reason) return;
  104.  
  105. var showReasons = config.showReasons,
  106. showPlaceholders = config.showPlaceholders;
  107.  
  108.  
  109. if (showPlaceholders) {
  110. var fold = getFold(reason);
  111. var cut = getCut(work);
  112.  
  113. work.className += ' ' + CSS_NAMESPACE + '-work';
  114. work.innerHTML = '';
  115. work.appendChild(fold);
  116. work.appendChild(cut);
  117.  
  118. if (!showReasons) {
  119. work.className += ' ' + CSS_NAMESPACE + '-hide-reasons';
  120. }
  121. } else {
  122. work.className += ' ' + CSS_NAMESPACE + '-hidden';
  123. }
  124. }
  125.  
  126. function matchTermsWithWildCard(term, pattern) {
  127. if (term.toLowerCase() === pattern.toLowerCase()) return true;
  128. if (pattern.indexOf('*') === -1) return false;
  129.  
  130. var regexified = pattern.replace(/\*/g, '.*?');
  131. var regex = new RegExp('^' + regexified + '$', 'i');
  132.  
  133. return regex.test(term);
  134. }
  135.  
  136. var isTagWhitelisted = function isTagWhitelisted(tags, whitelist) {
  137. var whitelistLookup = whitelist.reduce(function (lookup, tag) {
  138. lookup[tag] = true;
  139. return lookup;
  140. }, {});
  141.  
  142. return tags.some(function (tag) {
  143. return !!whitelistLookup[tag];
  144. });
  145. };
  146.  
  147. var findBlacklistedItem = function findBlacklistedItem(list, blacklist, comparator) {
  148. var matchingEntry = void 0;
  149.  
  150. list.some(function (item) {
  151. blacklist.some(function (entry) {
  152. var matched = comparator(item, entry);
  153.  
  154. if (matched) matchingEntry = entry;
  155.  
  156. return matched;
  157. });
  158. });
  159.  
  160. return matchingEntry;
  161. };
  162.  
  163. var equals = function equals(a, b) {
  164. return a === b;
  165. };
  166. var contains = function contains(a, b) {
  167. return a.indexOf(b) !== -1;
  168. };
  169.  
  170. function getBlockReason(_ref, _ref2) {
  171. var _ref$authors = _ref.authors,
  172. authors = _ref$authors === undefined ? [] : _ref$authors,
  173. _ref$title = _ref.title,
  174. title = _ref$title === undefined ? '' : _ref$title,
  175. _ref$tags = _ref.tags,
  176. tags = _ref$tags === undefined ? [] : _ref$tags,
  177. _ref$summary = _ref.summary,
  178. summary = _ref$summary === undefined ? '' : _ref$summary;
  179. var _ref2$authorBlacklist = _ref2.authorBlacklist,
  180. authorBlacklist = _ref2$authorBlacklist === undefined ? [] : _ref2$authorBlacklist,
  181. _ref2$titleBlacklist = _ref2.titleBlacklist,
  182. titleBlacklist = _ref2$titleBlacklist === undefined ? [] : _ref2$titleBlacklist,
  183. _ref2$tagBlacklist = _ref2.tagBlacklist,
  184. tagBlacklist = _ref2$tagBlacklist === undefined ? [] : _ref2$tagBlacklist,
  185. _ref2$tagWhitelist = _ref2.tagWhitelist,
  186. tagWhitelist = _ref2$tagWhitelist === undefined ? [] : _ref2$tagWhitelist,
  187. _ref2$summaryBlacklis = _ref2.summaryBlacklist,
  188. summaryBlacklist = _ref2$summaryBlacklis === undefined ? [] : _ref2$summaryBlacklis;
  189.  
  190.  
  191. if (isTagWhitelisted(tags, tagWhitelist)) return null;
  192.  
  193. var tag = findBlacklistedItem(tags, tagBlacklist, matchTermsWithWildCard);
  194. if (tag) return { tag: tag };
  195.  
  196. var author = findBlacklistedItem(authors, authorBlacklist, equals);
  197. if (author) return { author: author };
  198.  
  199. if (titleBlacklist.some(function (entry) {
  200. return title === entry;
  201. })) return { title: title };
  202.  
  203. var summaryTerm = findBlacklistedItem([summary], summaryBlacklist, contains);
  204. if (summaryTerm) return { summary: summaryTerm };
  205.  
  206. return null;
  207. }
  208.  
  209. function isDebug(location) {
  210. return location.hostname === 'localhost' || /\ba3sv-debug\b/.test(location.search);
  211. }
  212.  
  213. var getText = function getText(element) {
  214. return element.textContent.replace(/^\s*|\s*$/g, '');
  215. };
  216. var selectTextsIn = function selectTextsIn(root, selector) {
  217. return Array.from(root.querySelectorAll(selector)).map(getText);
  218. };
  219.  
  220. function selectWorkBlockables(workContainerElement) {
  221. return {
  222. authors: selectTextsIn(workContainerElement, 'a[rel=author]'),
  223. tags: [].concat(selectTextsIn(workContainerElement, 'a.tag'), selectTextsIn(workContainerElement, '.required-tags .text')),
  224. title: selectTextsIn(workContainerElement, '.header .heading a:first-child')[0],
  225. summary: selectTextsIn(workContainerElement, 'blockquote.summary')[0]
  226. };
  227. }
  228.  
  229. setTimeout(function () {
  230. var debugMode = isDebug(window.location);
  231. var config = window.ao3SaviorConfig;
  232. var blocked = 0;
  233. var total = 0;
  234.  
  235. if (debugMode) {
  236. console.groupCollapsed('AO3 SAVIOR');
  237.  
  238. if (!config) {
  239. console.warn('Exiting due to missing config.');
  240. return;
  241. }
  242. }
  243.  
  244. addStyle();
  245.  
  246. Array.from(document.querySelectorAll('li.blurb')).forEach(function (work) {
  247. var blockables = selectWorkBlockables(work);
  248. var reason = getBlockReason(blockables, config);
  249.  
  250. total++;
  251.  
  252. if (reason) {
  253. blockWork(work, reason, config);
  254. blocked++;
  255.  
  256. if (debugMode) {
  257. console.groupCollapsed('- blocked ' + work.id);
  258. console.log(work, reason);
  259. console.groupEnd();
  260. }
  261. } else if (debugMode) {
  262. console.groupCollapsed(' skipped ' + work.id);
  263. console.log(work);
  264. console.groupEnd();
  265. }
  266. });
  267.  
  268. if (debugMode) {
  269. console.log('Blocked ' + blocked + ' out of ' + total + ' works');
  270. console.groupEnd();
  271. }
  272. }, 10);
  273. }());
  274.  
  275. //# sourceMappingURL=ao3-savior.user.js.map