// ==UserScript== // @name AO3: Tracking // @description Track any filterable listing. // @namespace https://greasyfork.org/en/scripts/8382-ao3-tracking // @author Min // @version 1.5 // @grant none // @require https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js // @include http://*archiveofourown.org/* // @include https://*archiveofourown.org/* // @downloadURL https://update.greasyfork.icu/scripts/8382/AO3%3A%20Tracking.user.js // @updateURL https://update.greasyfork.icu/scripts/8382/AO3%3A%20Tracking.meta.js // ==/UserScript== (function($) { if (typeof(Storage) !== 'undefined') { var tracked_list = ''; var tracked_array = []; loadList(); addCss(); addTrackedMenu(); var main = $('#main'); // if it's a listing of works or bookmarks if (main.hasClass('works-index') || main.hasClass('bookmarks-index')) { var is_tracked = false; var is_first_page = true; // get page url var location_url = location.href; // check if page is already tracked var array_index = tracked_array.indexOf(location_url); if (array_index > -1) { is_tracked = true; checkOpen(); } // make sure it's the first page of the listing var current_page = main.find('ol.pagination:first span.current'); if (current_page.length && current_page.text() !== '1') { is_first_page = false; } if (is_first_page) { addTrackButton(); } } // add "Mark all as vieved" button when all listing get checked $(document).ajaxStop(function() { if ($('#tracked-box').length && !$('#button-check').length) { $('#button-mark').css('visibility', 'visible'); } }); } // load the saved list function loadList() { tracked_list = localStorage.getItem('ao3tracking_list'); if (!tracked_list) { tracked_list = ''; } // make an array of the list if (tracked_list.length) { tracked_array = tracked_list.split(',,,'); } } // add current page to tracked listings function addToTracked() { var added = false; loadList(); // if there's less than 25 tracked if (tracked_array.length < 75) { // ask for name var heading = main.find('h2.heading:first'); var heading_link = heading.find('a'); if (heading_link.length) { var suggest = heading_link.text(); } else { var suggest = heading.text().replace(/\n/g, '').replace(/^\s+/, '').replace(/(.+of )?\d+ /, ''); } var listing_name = prompt('Name for the tracked listing:', suggest); if (listing_name !== '' && listing_name !== null) { // remove characters we don't want in the names listing_name = listing_name.replace(/,,,/g, ' '); var listing_count = heading.text().replace(/\n/g, '').replace(/^\s+/, '').replace(/.*\d+ - \d+ of /, '').replace(/(\d+)(.+)/, '$1'); // add name, url, count tracked_array.push(listing_name, location_url, listing_count); tracked_list = tracked_array.join(',,,'); // save the updated list localStorage.setItem('ao3tracking_list', tracked_list); added = true; } } else { alert("You're already tracking 25 listings. Remove some first."); } return added; } // remove a given url from tracked listings function removeFromTracked(url) { var removed = false; loadList(); var index = tracked_array.indexOf(url); // if the url is on the saved list if (index > -1) { // ask for confirmation var confirmed = confirm('Sure you want to remove "' + tracked_array[index-1] + '"?'); if (confirmed) { // remove name, url, count tracked_array.splice(index-1, 3); tracked_list = tracked_array.join(',,,'); // save the updated list localStorage.setItem('ao3tracking_list', tracked_list); removed = true; } } return removed; } // check open page for new works function checkOpen() { var heading = main.find('h2.heading:first'); // get a count of new works var current_count = getCountFromHeading(heading.text()); var saved_count = parseInt(tracked_array[array_index+1]); var new_count = current_count - saved_count; if (new_count !== 0) { heading.append(' (' + new_count + ' new) [mark viewed]'); $('#mark-viewed').click(function() { loadList(); var array_index = tracked_array.indexOf(location_url); if (array_index > -1) { // update the count tracked_array[array_index+1] = current_count; tracked_list = tracked_array.join(',,,'); // save the updated list localStorage.setItem('ao3tracking_list', tracked_list); } $('#new-works').detach(); $(this).detach(); }); } } // check the tracked listings for new works function checkForNew() { // check if it's more than 8 hours since last check var last_check = localStorage.getItem('ao3tracking_lastcheck'); if (!last_check) { var last_check = 0; } else { last_check = parseInt(last_check); } var now = new Date(); now = now.getTime(); var wait = 28800000 - (now - last_check); if (wait < 0) { localStorage.setItem('ao3tracking_lastcheck', now); // for each tracked listing $('#tracked-box li.tracked-listing').each(function() { var tracked_url = $(this).find('a').attr('href'); var listing_id = $(this).attr('id'); tracked_url += ' #main h2.heading:first'; // load heading of the tracked page $(this).find('span.tracked-current').load(tracked_url, function() { var listing = $('#' + listing_id); // get a count of new works var current_count = getCountFromHeading(listing.find('span.tracked-current').text()); listing.find('span.tracked-current').html(current_count); var saved_count = parseInt(listing.find('span.tracked-saved').text()); var new_count = current_count - saved_count; listing.find('span.tracked-new').text('(' + new_count + ' new)'); if (new_count !== 0) { listing.find('span.tracked-new').addClass('new-stuff'); listing.parent().prepend(listing); } else { listing.find('span.tracked-new').addClass('no-new-stuff'); } }); }); } else { var hours = Math.floor(wait/3600000); var minutes = Math.ceil((wait%3600000)/60000); var warning = $('

'); if (hours > 0) { warning.html('Please be kind to the AO3 servers! Wait ' + hours + ' hour(s) and ' + minutes + ' minute(s) more before another check.'); } else { warning.html('Please be kind to the AO3 servers! Wait ' + minutes + ' more minute(s) before another check.'); } $('#tracked-box p.actions').after(warning); } } // add the 'Track This' button function addTrackButton() { var work_filters = $('form.filters, form.old-filters').find('dd.submit.actions:first'); var track_this_button = $(''); track_this_button.click(function() { var added = addToTracked(); if (added) { track_this_button.detach(); work_filters.prepend(untrack_this_button); } }); var untrack_this_button = $(''); untrack_this_button.click(function() { var removed = removeFromTracked(location_url); if (removed) { untrack_this_button.detach(); work_filters.prepend(track_this_button); } }); // if the page is already tracked if (is_tracked) { work_filters.prepend(untrack_this_button); } // if it's not tracked else { work_filters.prepend(track_this_button); } } // rearrange things on the list function editList() { var box_list = $('#box-list'); box_list.find('li.tracked-listing').each(function() { $(this).prepend(' '); }); box_list.on('click', 'span.up-arrow', function() { $(this).parent().prev().before($(this).parent()); }); box_list.on('click', 'span.down-arrow', function() { $(this).parent().next().after($(this).parent()); }); box_list.on('click', 'span.cross', function() { $(this).parent().detach(); }); } // save list after edits function saveList() { tracked_array = []; // get name, url, count for all listings $('#tracked-box li.tracked-listing').each(function() { var name = $(this).find('a').text(); var url = $(this).find('a').attr('href'); var count = $(this).find('span.tracked-saved').text(); tracked_array.push(name, url, count); }); // update and save the new list tracked_list = tracked_array.join(',,,'); localStorage.setItem('ao3tracking_list', tracked_list); // reload the box $('#tracked-box').detach(); $('#tracked-bg').detach(); showBox(); } // update the listings counts function markAllViewed() { loadList(); // get the current count for all listings $('#tracked-box li.tracked-listing').each(function() { var url = $(this).find('a').attr('href'); var current_count = $(this).find('span.tracked-current').text(); var index = tracked_array.indexOf(url); if (index > -1) { tracked_array[index+1] = current_count; } }); // update and save the new list tracked_list = tracked_array.join(',,,'); localStorage.setItem('ao3tracking_list', tracked_list); // reload the box $('#tracked-box').detach(); $('#tracked-bg').detach(); showBox(); } // show the box with tracked listings function showBox() { var tracked_bg = $('
'); var tracked_box = $('
'); var box_buttons = $('

'); var box_button_check = $(''); box_button_check.click(function() { box_button_edit.after(box_button_mark); checkForNew(); box_button_edit.detach(); box_button_check.detach(); }); var box_button_edit = $(''); box_button_edit.click(function() { editList(); box_button_edit.after(box_button_save, box_button_cancel); box_button_check.detach(); box_button_edit.detach(); }); var box_button_save = $(''); box_button_save.click(function() { saveList(); }); var box_button_cancel = $(''); box_button_cancel.click(function() { tracked_box.detach(); tracked_bg.detach(); showBox(); }); var box_button_mark = $(''); box_button_mark.click(function() { markAllViewed(); }); var box_button_close = $(''); box_button_close.click(function() { tracked_box.detach(); tracked_bg.detach(); }); var box_header = $('

').text('Tracked listings [' + tracked_array.length/3 + '/25]:'); var box_list = $(''); tracked_box.append(box_buttons, box_header, box_list); // if there are saved listings if (tracked_array.length > 2) { for (var i = 0; i < tracked_array.length; i += 3) { var listing = $('
  • ').html('' + tracked_array[i] + ' ' + tracked_array[i+2] + ' '); box_list.append(listing); } } else { var no_listings = $('
  • ').html("you're not tracking anything yet!"); box_list.append(no_listings); box_button_check.css('visibility', 'hidden'); box_button_edit.css('visibility', 'hidden'); } box_buttons.append(box_button_check, box_button_edit, box_button_close); $('body').append(tracked_bg, tracked_box); } // attach the menu function addTrackedMenu() { // get the header menu var header_menu = $('ul.primary.navigation.actions'); // create and insert menu button var tracked_button = $(''); header_menu.find('#search').prepend(tracked_button); tracked_button.click(function() { if ($('#tracked-box').length == 0) { loadList(); showBox(); } }); } // parse heading for works count function getCountFromHeading(heading_text) { try { return parseInt(heading_text.replace(/\n/g, '').replace(/^\s+/, '').replace(/,/g, '').replace(/.*\d+ - \d+ of /, '').replace(/(\d+)(.+)/, '$1')); } catch (e) { return 0; } } // add css rules to page head function addCss() { var style = $('').appendTo($('head')); 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;}\ #tracked-bg {position: fixed; width: 100%; height: 100%; background-color: #000000; opacity: 0.7; z-index: 998;}\ input[type="button"] {height: auto;}\ .filters input.track-this {margin-bottom: 0; width: 100%;}\ .old-filters input.track-this {margin-bottom: 10px;}\ #tracked-box p.actions {float: none; text-align: left;}\ #button-save {font-weight: bold;}\ #button-close {float: right;}\ #tracked-box li span.tracked-new.new-stuff {font-weight: bold;}\ #tracked-box li span.tracked-new.no-new-stuff {opacity: 0.5;}\ #tracked-box li span.tracked-current, #tracked-box li span.tracked-saved {display: none;}\ #tracked-box li .clickable {cursor: pointer; margin-right: 7px;}\ #new-works {font-weight: bold;}'; style.append(css); } })(jQuery);