ao3 savior

Hide specified works on AO3

目前为 2017-11-10 提交的版本。查看 最新版本

  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.10
  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 _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
  214.  
  215. var getText = function getText(element) {
  216. return element.textContent.replace(/^\s*|\s*$/g, '');
  217. };
  218. var selectTextsIn = function selectTextsIn(root, selector) {
  219. return Array.from(root.querySelectorAll(selector)).map(getText);
  220. };
  221.  
  222. function selectFromWork(container) {
  223. return _extends({}, selectFromBlurb(container), {
  224. title: selectTextsIn(container, '.title')[0],
  225. summary: selectTextsIn(container, '.summary .userstuff')[0]
  226. });
  227. }
  228.  
  229. function selectFromBlurb(blurb) {
  230. return {
  231. authors: selectTextsIn(blurb, 'a[rel=author]'),
  232. tags: [].concat(selectTextsIn(blurb, 'a.tag'), selectTextsIn(blurb, '.required-tags .text')),
  233. title: selectTextsIn(blurb, '.header .heading a:first-child')[0],
  234. summary: selectTextsIn(blurb, 'blockquote.summary')[0]
  235. };
  236. }
  237.  
  238. setTimeout(function () {
  239. var debugMode = isDebug(window.location);
  240. var config = window.ao3SaviorConfig;
  241. var workContainer = document.querySelector('#main.works-show') || document.querySelector('#main.chapters-show');
  242. var blocked = 0;
  243. var total = 0;
  244.  
  245. if (debugMode) {
  246. console.groupCollapsed('AO3 SAVIOR');
  247.  
  248. if (!config) {
  249. console.warn('Exiting due to missing config.');
  250. return;
  251. }
  252. }
  253.  
  254. addStyle();
  255.  
  256. Array.from(document.querySelectorAll('li.blurb')).forEach(function (blurb) {
  257. var blockables = selectFromBlurb(blurb);
  258. var reason = getBlockReason(blockables, config);
  259.  
  260. total++;
  261.  
  262. if (reason) {
  263. blockWork(blurb, reason, config);
  264. blocked++;
  265.  
  266. if (debugMode) {
  267. console.groupCollapsed('- blocked ' + blurb.id);
  268. console.log(blurb, reason);
  269. console.groupEnd();
  270. }
  271. } else if (debugMode) {
  272. console.groupCollapsed(' skipped ' + blurb.id);
  273. console.log(blurb);
  274. console.groupEnd();
  275. }
  276. });
  277.  
  278. if (config.alertOnVisit && workContainer && document.referrer.indexOf('//archiveofourown.org') === -1) {
  279.  
  280. var blockables = selectFromWork(workContainer);
  281. var reason = getBlockReason(blockables, config);
  282.  
  283. if (reason) {
  284. blocked++;
  285. blockWork(workContainer, reason, config);
  286. }
  287. }
  288.  
  289. if (debugMode) {
  290. console.log('Blocked ' + blocked + ' out of ' + total + ' works');
  291. console.groupEnd();
  292. }
  293. }, 10);
  294. }());
  295.  
  296. //# sourceMappingURL=ao3-savior.user.js.map