AO3: Tracking

Track any filterable listing.

当前为 2017-11-19 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name AO3: Tracking
  3. // @description Track any filterable listing.
  4. // @namespace https://greasyfork.org/en/scripts/8382-ao3-tracking
  5. // @author Min
  6. // @version 1.3.2
  7. // @grant none
  8. // @require https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js
  9. // @include http://archiveofourown.org/*
  10. // @include https://archiveofourown.org/*
  11. // ==/UserScript==
  12.  
  13.  
  14. (function($) {
  15. if (typeof(Storage) !== 'undefined') {
  16. var tracked_list = '';
  17. var tracked_array = [];
  18. loadList();
  19. addCss();
  20. addTrackedMenu();
  21. var main = $('#main');
  22. // if it's a listing of works or bookmarks
  23. if (main.hasClass('works-index') || main.hasClass('bookmarks-index')) {
  24. var is_tracked = false;
  25. var is_first_page = true;
  26. // get page url
  27. var location_url = location.href;
  28. // check if page is already tracked
  29. var array_index = tracked_array.indexOf(location_url);
  30. if (array_index > -1) {
  31. is_tracked = true;
  32. checkOpen();
  33. }
  34. // make sure it's the first page of the listing
  35. var current_page = main.find('ol.pagination:first span.current');
  36. if (current_page.length && current_page.text() !== '1') {
  37. is_first_page = false;
  38. }
  39. if (is_first_page) { addTrackButton(); }
  40. }
  41. // add "Mark all as vieved" button when all listing get checked
  42. $(document).ajaxStop(function() {
  43. if ($('#tracked-box').length && !$('#button-check').length) {
  44. $('#button-mark').css('visibility', 'visible');
  45. }
  46. });
  47. }
  48. // load the saved list
  49. function loadList() {
  50. tracked_list = localStorage.getItem('ao3tracking_list');
  51. if (!tracked_list) { tracked_list = ''; }
  52. // make an array of the list
  53. if (tracked_list.length) {
  54. tracked_array = tracked_list.split(',,,');
  55. }
  56. }
  57. // add current page to tracked listings
  58. function addToTracked() {
  59. var added = false;
  60. loadList();
  61. // if there's less than 25 tracked
  62. if (tracked_array.length < 75) {
  63. // ask for name
  64. var heading = main.find('h2.heading:first');
  65. var heading_link = heading.find('a');
  66. if (heading_link.length) {
  67. var suggest = heading_link.text();
  68. }
  69. else {
  70. var suggest = heading.text().replace(/\n/g, '').replace(/^\s+/, '').replace(/(.+of )?\d+ /, '');
  71. }
  72. var listing_name = prompt('Name for the tracked listing:', suggest);
  73. if (listing_name !== '' && listing_name !== null) {
  74. // remove characters we don't want in the names
  75. listing_name = listing_name.replace(/,,,/g, ' ');
  76. var listing_count = heading.text().replace(/\n/g, '').replace(/^\s+/, '').replace(/.*\d+ - \d+ of /, '').replace(/(\d+)(.+)/, '$1');
  77. // add name, url, count
  78. tracked_array.push(listing_name, location_url, listing_count);
  79. tracked_list = tracked_array.join(',,,');
  80. // save the updated list
  81. localStorage.setItem('ao3tracking_list', tracked_list);
  82. added = true;
  83. }
  84. }
  85. else {
  86. alert("You're already tracking 25 listings. Remove some first.");
  87. }
  88. return added;
  89. }
  90. // remove a given url from tracked listings
  91. function removeFromTracked(url) {
  92. var removed = false;
  93. loadList();
  94. var index = tracked_array.indexOf(url);
  95. // if the url is on the saved list
  96. if (index > -1) {
  97. // ask for confirmation
  98. var confirmed = confirm('Sure you want to remove "' + tracked_array[index-1] + '"?');
  99. if (confirmed) {
  100. // remove name, url, count
  101. tracked_array.splice(index-1, 3);
  102. tracked_list = tracked_array.join(',,,');
  103. // save the updated list
  104. localStorage.setItem('ao3tracking_list', tracked_list);
  105. removed = true;
  106. }
  107. }
  108. return removed;
  109. }
  110. // check open page for new works
  111. function checkOpen() {
  112. var heading = main.find('h2.heading:first');
  113. // get a count of new works
  114. var current_count = parseInt(heading.text().replace(/\n/g, '').replace(/^\s+/, '').replace(/.*\d+ - \d+ of /, '').replace(/(\d+)(.+)/, '$1'));
  115. var saved_count = parseInt(tracked_array[array_index+1]);
  116. var new_count = current_count - saved_count;
  117. if (new_count !== 0) {
  118. heading.append(' <span id="new-works">(' + new_count + ' new)</span> <a id="mark-viewed">[mark viewed]</a>');
  119. $('#mark-viewed').click(function() {
  120. loadList();
  121. var array_index = tracked_array.indexOf(location_url);
  122. if (array_index > -1) {
  123. // update the count
  124. tracked_array[array_index+1] = current_count;
  125. tracked_list = tracked_array.join(',,,');
  126. // save the updated list
  127. localStorage.setItem('ao3tracking_list', tracked_list);
  128. }
  129. $('#new-works').detach();
  130. $(this).detach();
  131. });
  132. }
  133. }
  134. // check the tracked listings for new works
  135. function checkForNew() {
  136. // check if it's more than 8 hours since last check
  137. var last_check = localStorage.getItem('ao3tracking_lastcheck');
  138. if (!last_check) { var last_check = 0; }
  139. else { last_check = parseInt(last_check); }
  140. var now = new Date();
  141. now = now.getTime();
  142. var wait = 28800000 - (now - last_check);
  143. if (wait < 0) {
  144. localStorage.setItem('ao3tracking_lastcheck', now);
  145. // for each tracked listing
  146. $('#tracked-box li.tracked-listing').each(function() {
  147. var tracked_url = $(this).find('a').attr('href');
  148. var listing_id = $(this).attr('id');
  149. tracked_url += ' #main h2.heading:first';
  150. // load heading of the tracked page
  151. $(this).find('span.tracked-current').load(tracked_url, function() {
  152. var listing = $('#' + listing_id);
  153. // get a count of new works
  154. var current_count = parseInt(listing.find('span.tracked-current').text().replace(/\n/g, '').replace(/^\s+/, '').replace(/.*\d+ - \d+ of /, '').replace(/(\d+)(.+)/, '$1'));
  155. listing.find('span.tracked-current').html(current_count);
  156. var saved_count = parseInt(listing.find('span.tracked-saved').text());
  157. var new_count = current_count - saved_count;
  158. listing.find('span.tracked-new').text('(' + new_count + ' new)');
  159. if (new_count !== 0) {
  160. listing.find('span.tracked-new').addClass('new-stuff');
  161. listing.parent().prepend(listing);
  162. }
  163. else {
  164. listing.find('span.tracked-new').addClass('no-new-stuff');
  165. }
  166. });
  167. });
  168. }
  169. else {
  170. var hours = Math.floor(wait/3600000);
  171. var minutes = Math.ceil((wait%3600000)/60000);
  172. var warning = $('<p style="color: #990000;"></p>');
  173. if (hours > 0) {
  174. warning.html('<strong>Please be kind to the AO3 servers!</strong> Wait ' + hours + ' hour(s) and ' + minutes + ' minute(s) more before another check.');
  175. }
  176. else {
  177. warning.html('<strong>Please be kind to the AO3 servers!</strong> Wait ' + minutes + ' more minute(s) before another check.');
  178. }
  179. $('#tracked-box p.actions').after(warning);
  180. }
  181. }
  182. // add the 'Track This' button
  183. function addTrackButton() {
  184. var work_filters = $('form.filters dd.submit.actions:first');
  185. var track_this_button = $('<input type="button" value="Track This" class="track-this"></input>');
  186. track_this_button.click(function() {
  187. var added = addToTracked();
  188. if (added) {
  189. track_this_button.detach();
  190. work_filters.prepend(untrack_this_button);
  191. }
  192. });
  193. var untrack_this_button = $('<input type="button" value="Untrack This" class="track-this"></input>');
  194. untrack_this_button.click(function() {
  195. var removed = removeFromTracked(location_url);
  196. if (removed) {
  197. untrack_this_button.detach();
  198. work_filters.prepend(track_this_button);
  199. }
  200. });
  201. // if the page is already tracked
  202. if (is_tracked) {
  203. work_filters.prepend(untrack_this_button);
  204. }
  205. // if it's not tracked
  206. else {
  207. work_filters.prepend(track_this_button);
  208. }
  209. }
  210. // rearrange things on the list
  211. function editList() {
  212. var box_list = $('#box-list');
  213. box_list.find('li.tracked-listing').each(function() {
  214. $(this).prepend('<span class="up-arrow">&uarr;</span> <span class="down-arrow">&darr;</span> <span class="cross">&cross;</span> ');
  215. });
  216. box_list.on('click', 'span.up-arrow', function() {
  217. $(this).parent().prev().before($(this).parent());
  218. });
  219. box_list.on('click', 'span.down-arrow', function() {
  220. $(this).parent().next().after($(this).parent());
  221. });
  222. box_list.on('click', 'span.cross', function() {
  223. $(this).parent().detach();
  224. });
  225. }
  226. // save list after edits
  227. function saveList() {
  228. tracked_array = [];
  229. // get name, url, count for all listings
  230. $('#tracked-box li.tracked-listing').each(function() {
  231. var name = $(this).find('a').text();
  232. var url = $(this).find('a').attr('href');
  233. var count = $(this).find('span.tracked-saved').text();
  234. tracked_array.push(name, url, count);
  235. });
  236. // update and save the new list
  237. tracked_list = tracked_array.join(',,,');
  238. localStorage.setItem('ao3tracking_list', tracked_list);
  239. // reload the box
  240. $('#tracked-box').detach();
  241. $('#tracked-bg').detach();
  242. showBox();
  243. }
  244. // update the listings counts
  245. function markAllViewed() {
  246. loadList();
  247. // get the current count for all listings
  248. $('#tracked-box li.tracked-listing').each(function() {
  249. var url = $(this).find('a').attr('href');
  250. var current_count = $(this).find('span.tracked-current').text();
  251. var index = tracked_array.indexOf(url);
  252. if (index > -1) {
  253. tracked_array[index+1] = current_count;
  254. }
  255. });
  256. // update and save the new list
  257. tracked_list = tracked_array.join(',,,');
  258. localStorage.setItem('ao3tracking_list', tracked_list);
  259. // reload the box
  260. $('#tracked-box').detach();
  261. $('#tracked-bg').detach();
  262. showBox();
  263. }
  264. // show the box with tracked listings
  265. function showBox() {
  266. var tracked_bg = $('<div id="tracked-bg"></div>');
  267. var tracked_box = $('<div id="tracked-box"></div>');
  268. var box_buttons = $('<p class="actions"></p>');
  269. var box_button_check = $('<input type="button" id="button-check" value="Check for new"></input>');
  270. box_button_check.click(function() {
  271. box_button_edit.after(box_button_mark);
  272. checkForNew();
  273. box_button_edit.detach();
  274. box_button_check.detach();
  275. });
  276. var box_button_edit = $('<input type="button" id="button-edit" value="Edit list"></input>');
  277. box_button_edit.click(function() {
  278. editList();
  279. box_button_edit.after(box_button_save, box_button_cancel);
  280. box_button_check.detach();
  281. box_button_edit.detach();
  282. });
  283. var box_button_save = $('<input type="button" id="button-save" value="Save list"></input>');
  284. box_button_save.click(function() { saveList(); });
  285. var box_button_cancel = $('<input type="button" id="button-cancel" value="Cancel edits"></input>');
  286. box_button_cancel.click(function() {
  287. tracked_box.detach();
  288. tracked_bg.detach();
  289. showBox();
  290. });
  291. var box_button_mark = $('<input type="button" id="button-mark" style="visibility: hidden;" value="Mark all as viewed"></input>');
  292. box_button_mark.click(function() { markAllViewed(); });
  293. var box_button_close = $('<input type="button" id="button-close" value="Close"></input>');
  294. box_button_close.click(function() {
  295. tracked_box.detach();
  296. tracked_bg.detach();
  297. });
  298. var box_header = $('<h3></h3>').text('Tracked listings [' + tracked_array.length/3 + '/25]:');
  299. var box_list = $('<ul id="box-list"></ul>');
  300. tracked_box.append(box_buttons, box_header, box_list);
  301. // if there are saved listings
  302. if (tracked_array.length > 2) {
  303. for (var i = 0; i < tracked_array.length; i += 3) {
  304. var listing = $('<li id="tracked-listing-' + i/3 + '" class="tracked-listing"></li>').html('<a href="' + tracked_array[i+1] + '">' + tracked_array[i] + '</a> <span class="tracked-new"></span> <span class="tracked-saved">' + tracked_array[i+2] + '</span> <span class="tracked-current"></span>');
  305. box_list.append(listing);
  306. }
  307. }
  308. else {
  309. var no_listings = $('<li style="opacity: 0.5; font-style: oblique;"></li>').html("you're not tracking anything yet!");
  310. box_list.append(no_listings);
  311. box_button_check.css('visibility', 'hidden');
  312. box_button_edit.css('visibility', 'hidden');
  313. }
  314. box_buttons.append(box_button_check, box_button_edit, box_button_close);
  315. $('body').append(tracked_bg, tracked_box);
  316. }
  317. // attach the menu
  318. function addTrackedMenu() {
  319. // get the header menu
  320. var header_menu = $('ul.primary.navigation.actions');
  321. // create and insert menu button
  322. var tracked_button = $('<input class="button" type="button" value="Tracked"></input>');
  323. header_menu.find('#search').prepend(tracked_button);
  324. tracked_button.click(function() {
  325. if ($('#tracked-box').length == 0) {
  326. loadList();
  327. showBox();
  328. }
  329. });
  330. }
  331. // add css rules to page head
  332. function addCss() {
  333. var style = $('<style type="text/css"></style>').appendTo($('head'));
  334. var css = '#tracked-box {position: fixed; top: 0px; bottom: 0px; left: 0px; right: 0px; width: 60%; height: 80%; max-width: 800px; margin: auto; overflow-y: auto; border: 10px solid #eee; box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.2); padding: 0 20px; background-color: #ffffff; z-index: 999;}\
  335. #tracked-bg {position: fixed; width: 100%; height: 100%; background-color: #000000; opacity: 0.7; z-index: 998;}\
  336. input[type="button"] {height: auto;}\
  337. input.track-this {margin-bottom: 10px;}\
  338. #tracked-box p.actions {float: none; text-align: left;}\
  339. #button-save {font-weight: bold;}\
  340. #button-close {float: right;}\
  341. #tracked-box li span.tracked-new.new-stuff {font-weight: bold;}\
  342. #tracked-box li span.tracked-new.no-new-stuff {opacity: 0.5;}\
  343. #tracked-box li span.tracked-current, #tracked-box li span.tracked-saved {display: none;}\
  344. #new-works {font-weight: bold;}';
  345. style.append(css);
  346. }
  347. })(jQuery);