// ==UserScript== // @name AO3: Kudosed and seen history // @description Highlight or hide works you kudosed/marked as seen. // @namespace https://greasyfork.org/scripts/5835-ao3-kudosed-and-seen-history // @author Min // @version 2.0 // @history 2.0 - rewrite, click actions for blurbs, new highlighting // @history 1.5 - import/export seen list // @history 1.4 - thinner stripes, remembers bookmarks you left // @history 1.3 - option to collapse blurbs of seen works // @history 1.2.1 - double click on date marks work as seen // @history 1.2 - check for bookmarks you left, changes to the menu // @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 none // ==/UserScript== (function ($) { var DEBUG = false; // newest more-or-less major version, for the update notice var current_version = '2.0'; var kudos_history = {}, seen_buttons = {}, saved_settings = {}; var main = $('#main'); if ($('head').find('link[href*="reversi"]').length) { main.addClass('kh-reversi'); } // uncomment the next five lines if you need to clear your local lists (then comment them again after refreshing the page once) // localStorage.removeItem('kudoshistory_kudosed'); // localStorage.removeItem('kudoshistory_checked'); // localStorage.removeItem('kudoshistory_seen'); // localStorage.removeItem('kudoshistory_bookmarked'); // localStorage.removeItem('kudoshistory_skipped'); var KHList = { init: function init(name, max_length) { this.name = name; this.max_length = max_length || 200000; this.list = localStorage.getItem('kudoshistory_' + this.name) || ','; return this; }, reload: function reload() { this.list = localStorage.getItem('kudoshistory_' + this.name) || this.list; return this; }, save: function save() { try { localStorage.setItem('kudoshistory_' + this.name, this.list.slice(0, this.max_length)); } catch (e) { localStorage.setItem('kudoshistory_' + this.name, this.list.slice(0, this.list.length * 0.9)); } return this; }, hasId: function hasId(work_id) { if (this.list.indexOf(',' + work_id + ',') > -1) { this.list = ',' + work_id + this.list.replace(',' + work_id + ',', ','); return true; } else { return false; } }, add: function add(work_id) { this.list = ',' + work_id + this.list.replace(',' + work_id + ',', ','); return this; }, remove: function remove(work_id) { this.list = this.list.replace(',' + work_id + ',', ','); return this; }, }; var KHSetting = { init: function init(name, label, description, options, default_value) { this.name = name; this.label = label; this.description = description; this.options = options; this.current = saved_settings[this.name] || default_value; return this; }, next: function next() { this.current = this.options[this.options.indexOf(this.current) + 1] || this.options[0]; this.updateButton(); this.save(); addMainClass(true); }, getButton: function getButton() { this.button_link = $('').text(this.label + ': ' + this.current.toUpperCase()).prop('title', this.options.join(' / ').toUpperCase()); this.button = $('
  • ').append(this.button_link); var this_setting = this; this.button.click(function () { this_setting.next(); }); return this.button; }, updateButton: function updateButton() { this.button_link.text(this.label + ': ' + this.current.toUpperCase()); }, changeValue: function changeValue(value) { this.current = value; this.updateButton(); this.updateSettingRow(); this.save(); addMainClass(true); }, getSettingRow: function getSettingRow() { var setting_row_info = $('

    ' + this.description + '

    '); var setting_row_info_button = $('?'); var setting_row_label = $('' + this.label + ': ').append(setting_row_info_button); this.setting_row_options = $(''); this.setting_row = $('

    ').append(setting_row_label, this.setting_row_options, setting_row_info); this.updateSettingRow(); setting_row_info_button.click(function () { setting_row_info.toggleClass('kh-hide-element'); }); return this.setting_row; }, updateSettingRow: function updateSettingRow() { var this_setting = this; this_setting.setting_row_options.empty(); this.options.forEach(function (option) { var option_link = $(''); option_link.click(function () { this_setting.changeValue(option); }); if (this_setting.current == option) { option_link.html('' + option.toUpperCase() + '').addClass('kh-setting-option-selected'); } else { option_link.text(option.toUpperCase()); } this_setting.setting_row_options.append(' ', option_link, ' '); }); }, save: function save() { saved_settings[this.name] = this.current; localStorage.setItem('kudoshistory_settings', JSON.stringify(saved_settings)); }, check: function check(compare) { return (this.current === (compare || 'yes')); }, }; var settings_list = [ { name: 'seen_display', label: 'Kudosed/seen works', description: 'Hide the works on lists, show the whole blurb, or show just the blurb header.', options: ['hide', 'show', 'collapse'], default_value: 'collapse', }, { name: 'skipped_display', label: 'Skipped works', description: 'Hide the works on lists, replace the blurb content with "Skipped", or show just the blurb header.', options: ['hide', 'placeholder', 'collapse'], default_value: 'placeholder', }, { name: 'toggles_display', label: 'Blurb options', description: 'The controls on top right of the blurb that let you mark/unmark the work as seen or skipped from the list.', options: ['hide', 'show', 'on hover'], default_value: 'on hover', }, { name: 'highlight_bookmarked', label: 'Highlight bookmarked', description: 'Show a striped stripe (yeah) on the right for works you\'ve bookmarked.', options: ['yes', 'no'], default_value: 'yes', }, { name: 'highlight_new', label: 'Highlight new', description: 'Show a thin coloured stripe on the left of the blurb for works you\'re seeing for the first time. Only shows on the lists.', options: ['yes', 'no'], default_value: 'yes', }, { name: 'autoseen', label: 'Mark as seen on open', description: 'Automatically mark as seen when you open the work page.', options: ['yes', 'no'], default_value: 'no', }, ]; if (typeof (Storage) !== 'undefined') { saved_settings = JSON.parse(localStorage.getItem('kudoshistory_settings')) || {}; kudos_history = { kudosed: Object.create(KHList).init('kudosed'), checked: Object.create(KHList).init('checked'), seen: Object.create(KHList).init('seen', 2000000), bookmarked: Object.create(KHList).init('bookmarked'), skipped: Object.create(KHList).init('skipped'), username: localStorage.getItem('kudoshistory_username'), saveLists: function () { DEBUG && console.log('saving lists'); this.kudosed.save(); this.checked.save(); this.seen.save(); this.bookmarked.save(); this.skipped.save(); }, }; settings_list.forEach(function (setting) { kudos_history[setting.name] = Object.create(KHSetting).init(setting.name, setting.label, setting.description, setting.options, setting.default_value); }); var userlink = $('#greeting li.dropdown > a[href^="/users/"]'); if (!kudos_history.username) { localStorage.setItem('kudoshistory_lastver', current_version); } // if logged in if (userlink.length) { var found_username = userlink.attr('href').split('/')[2]; DEBUG && console.log('found username: ' + found_username); if (kudos_history.username !== found_username) { kudos_history.username = found_username; localStorage.setItem('kudoshistory_username', kudos_history.username); } } // if not logged in, but remembers username else if (!!kudos_history.username) { DEBUG && console.log("didn't find username on page, saved username: " + kudos_history.username); } else { kudos_history.username = prompt('AO3: Kudosed and seen history\n\nYour AO3 username:'); localStorage.setItem('kudoshistory_username', kudos_history.username); } $(document).ajaxStop(function () { kudos_history.saveLists(); }); // add css rules for kudosed works addCss(); var works_and_bookmarks = $('li.work.blurb, li.bookmark.blurb'); // if there's a list of works or bookmarks if (works_and_bookmarks.length) { addSeenMenu(); var blurb_index = $('.index'); // click on heading to collapse/expand blurb_index.on('click', '.header .heading', function (e) { if (!$(e.target).is('a')) { $(this).closest('.blurb').toggleClass('collapsed-blurb'); } }); // toggle seen blurb_index.on('click', '.kh-toggle-seen', function () { changeBlurbStatus($(this).closest('.blurb'), 'seen', true); }); // toggle skipped blurb_index.on('click', '.kh-toggle-skipped', function () { changeBlurbStatus($(this).closest('.blurb'), 'skipped', true); }); // click on delete bookmark blurb_index.on('click', '.own.user a[data-method="delete"]', function () { var work_id = $(this).closest('.blurb').data('work_id'); if (work_id) { kudos_history.bookmarked.reload().remove(work_id).save(); } }); // for each work and bookmark blurb works_and_bookmarks.not('.deleted').each(function () { blurbCheck($(this)); }); } // if it's the first time after an update addNotice(); // if it's a work page if ($('#workskin').length) { var work_meta = $('dl.work.meta.group'); // get work id var work_id = $('#kudo_commentable_id').val(); DEBUG && console.log('work_id ' + work_id); if (kudos_history.autoseen.check()) { kudos_history.seen.add(work_id); } // check if work id is on the seen list var is_seen = kudos_history.seen.hasId(work_id); if (is_seen) { work_meta.addClass('marked-seen'); } addSeenButtons(); // if work id is on the kudosed list if (kudos_history.kudosed.hasId(work_id)) { work_meta.addClass('has-kudos'); DEBUG && console.log('- on kudosed list'); } else { // check if there are kudos from the user var user_kudos = $('#kudos').find('[href="/users/' + kudos_history.username + '"]'); if (user_kudos.length) { // highlight blurb and add work id to kudosed list kudos_history.kudosed.add(work_id); kudos_history.checked.remove(work_id); work_meta.addClass('has-kudos'); } else { // add work id to checked list kudos_history.checked.add(work_id); $('#new_kudo').one('click', function () { kudos_history.kudosed.reload().add(work_id).save(); kudos_history.checked.reload().remove(work_id).save(); work_meta.addClass('has-kudos'); }); } } // check if it's bookmarked var bookmark_button_text = $('a.bookmark_form_placement_open').filter(':first').text(); if (bookmark_button_text.indexOf('Edit') > -1) { // highlight blurb kudos_history.bookmarked.add(work_id); work_meta.addClass('is-bookmarked'); } else { kudos_history.bookmarked.remove(work_id); } } // save all lists kudos_history.saveLists(); } // check if work is on lists function blurbCheck(blurb) { var work_id; var blurb_id = blurb.attr('id'); if (blurb.hasClass('work')) { work_id = blurb_id.replace('work_', ''); } else if (blurb.hasClass('bookmark')) { var work_link = blurb.find('h4 a:first').attr('href'); // if it's not a deleted work and not a series or external bookmark if (!!work_link && work_link.indexOf('series') === -1 && work_link.indexOf('external_work') === -1) { work_id = work_link.split('/').pop(); // if it's your own bookmark var own_bookmark = blurb.find('div.own.user.module.group'); if (own_bookmark.length) { kudos_history.bookmarked.add(work_id); } } } blurb.data('work_id', work_id); DEBUG && console.log('blurb check ' + blurb_id + ', work_id: ' + work_id); if (!work_id) { return false; } blurb.addClass('blurb-with-toggles').prepend('
    mark/unmark as: seenskipped'); // if work id is on the kudosed list if (kudos_history.kudosed.hasId(work_id)) { DEBUG && console.log('- is kudosed'); blurb.addClass('has-kudos collapsed-blurb'); } // if work id is on the seen list else if (kudos_history.seen.hasId(work_id)) { DEBUG && console.log('- is seen'); blurb.addClass('marked-seen collapsed-blurb'); } // if work id is on the checked list else if (kudos_history.checked.hasId(work_id)) { DEBUG && console.log('- is checked'); } else { blurb.addClass('new-blurb'); loadKudos(blurb); } // if work id is on the bookmarked list if (kudos_history.bookmarked.hasId(work_id)) { DEBUG && console.log('- is bookmarked'); blurb.addClass('is-bookmarked'); } // if work id is on the skipped list if (kudos_history.skipped.hasId(work_id)) { DEBUG && console.log('- is skipped'); blurb.addClass('skipped-work collapsed-blurb'); } } // load kudos for blurb function loadKudos(blurb) { var work_id = blurb.data('work_id'); if (!work_id) { return false; } DEBUG && console.log('- loading kudos for ' + work_id); // add a div to the blurb that will house the kudos var kudos_container = $('
    '); blurb.append(kudos_container); // retrieve a list of kudos from the work var work_url = window.location.protocol + '//' + window.location.hostname + '/works/' + work_id + '/kudos #kudos'; kudos_container.load(work_url, function () { // check if there are kudos from the user var user_kudos = kudos_container.find('[href="/users/' + kudos_history.username + '"]'); if (user_kudos.length) { // highlight blurb and add work id to kudosed list blurb.addClass('has-kudos collapsed-blurb'); kudos_history.kudosed.add(work_id); } else { // add work id to checked list kudos_history.checked.add(work_id); } }); } // mark all works on the page as seen function markPageSeen() { kudos_history.seen.reload(); // for each blurb works_and_bookmarks.not('.marked-seen').not('.has-kudos').not('.deleted').each(function () { changeBlurbStatus($(this), 'seen', false, true); }); kudos_history.seen.save(); } // mark all works on the page as unseen function markPageUnseen() { kudos_history.seen.reload(); // for each blurb works_and_bookmarks.not('.deleted').each(function () { changeBlurbStatus($(this), 'seen', false, false); }); kudos_history.seen.save(); } // mark/unmark blurb as X function changeBlurbStatus(blurb, list, save_list, add_to_list) { var work_id = blurb.data('work_id'); if (work_id) { save_list && kudos_history[list].reload(); var blurb_class = { seen: 'marked-seen', skipped: 'skipped-work' }; if (add_to_list == undefined) { add_to_list = !kudos_history[list].hasId(work_id); } if (add_to_list) { DEBUG && console.log('marking as ' + list + ' ' + work_id); kudos_history[list].add(work_id); blurb.addClass(blurb_class[list] + ' collapsed-blurb'); } else { DEBUG && console.log('unmarking as ' + list + ' ' + work_id); kudos_history[list].remove(work_id); blurb.removeClass(blurb_class[list]); } save_list && kudos_history[list].save(); } } // re-check the page for kudos function recheckKudos() { kudos_history.kudosed.reload(); kudos_history.checked.reload(); // for each non-kudosed blurb works_and_bookmarks.not('.has-kudos').not('.deleted').each(function () { loadKudos($(this)); }); } // check the page for bookmarks function checkForBookmarks() { kudos_history.bookmarked.reload(); // for each work and bookmark blurb works_and_bookmarks.not('.deleted').each(function () { var blurb = $(this); var work_id = blurb.data('work_id'); if (!work_id) { return false; } DEBUG && console.log('- loading bookmark buttons for ' + work_id); // add a div to the blurb that will house the kudos var bookmark_container = $('
    '); blurb.append(bookmark_container); // retrieve the bookmark button from the work var work_url = window.location.protocol + '//' + window.location.hostname + '/works/' + work_id + ' a.bookmark_form_placement_open:first'; bookmark_container.load(work_url, function () { // check if there is a bookmark from the user var bookmark_button_text = bookmark_container.find('a').text(); if (bookmark_button_text.indexOf('Edit') > -1) { // highlight blurb blurb.addClass('is-bookmarked'); kudos_history.bookmarked.add(work_id); } else { blurb.removeClass('is-bookmarked'); kudos_history.bookmarked.remove(work_id); } }); }); } // show the box with import/export options function importExport() { var importexport_bg = $('
    '); var importexport_box = $('
    '); var box_button_save = $(''); box_button_save.click(function () { var confirmed = confirm('Sure you want to replace your saved lists?'); if (confirmed) { var import_lists = JSON.parse($('#import-seen-list').val()); for (var list_name in import_lists) { if (kudos_history[list_name]) { kudos_history[list_name].list = import_lists[list_name]; kudos_history[list_name].save(); } } $('#importexport-save').prepend('Lists imported! '); } }); var box_button_close = $(''); var export_lists = { kudosed: kudos_history.kudosed.reload().list, seen: kudos_history.seen.reload().list, bookmarked: kudos_history.bookmarked.reload().list, skipped: kudos_history.skipped.reload().list, checked: kudos_history.checked.reload().list, }; importexport_box.append( $('

    ').append(box_button_close), $('

    ').text('Settings'), ); settings_list.forEach(function (setting) { importexport_box.append(kudos_history[setting.name].getSettingRow()); }); importexport_box.append( $('

    ').text('Export your saved lists'), $('

    ').text('Copy your current lists (kudosed, seen, bookmarked, skipped) from the field below and save wherever you want as a backup.'), $('').val(JSON.stringify(export_lists)), $('

    ').text('Import your lists'), $('

    ').html('Put your saved lists in the field below and select the "Import lists" button. Warning: it will replace your current lists.'), $(''), $('

    ').append(box_button_save), ); $('body').append(importexport_bg); main.append(importexport_box); box_button_close.add(importexport_bg).click(function () { importexport_box.detach(); importexport_bg.detach(); }); } // add the seen/unseen buttons function addSeenButtons() { seen_buttons = { is_seen: is_seen, buttons_links: [], change: function () { DEBUG && console.log(this); this.is_seen = !this.is_seen; kudos_history.seen.reload(); if (this.is_seen) { kudos_history.seen.add(work_id); work_meta.addClass('marked-seen'); } else { kudos_history.seen.remove(work_id); work_meta.removeClass('marked-seen'); } kudos_history.seen.save(); this.updateButtons(); DEBUG && console.log(this); }, getButton: function () { var button_link = $('').html(this.is_seen ? 'Unseen ✗' : 'Seen ✓'); var button = $('
  • ').append(button_link); var this_setting = this; button.click(function () { this_setting.change(); }); this.buttons_links.push(button_link); return button; }, updateButtons: function () { for (var i = 0; i < this.buttons_links.length; i++) { this.buttons_links[i].html(this.is_seen ? 'Unseen ✗' : 'Seen ✓'); } }, }; $('li.bookmark').after(seen_buttons.getButton()); $('#new_kudo').parent().after(seen_buttons.getButton()); } // attach the menu function addSeenMenu() { // create a button for the menu function getMenuButton(button_text, on_click) { var button = $('
  • ' + button_text + '
  • '); if (on_click) { button.click(on_click); } else { button.addClass('kh-menu-header'); } return button; } // get the header menu var header_menu = $('ul.primary.navigation.actions'); // create menu button var seen_menu = $('').html('Seen works'); // create dropdown menu var drop_menu = $('