// ==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.3 // @history 2.3 - checking for kudos in the bg disabled by default, export options // @history 2.2.1 - fix for bookmarked blurbs // @history 2.2 - separate kudosed and seen settings, smaller icons // @history 2.1 - fix for reversi // @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.03'; var kudos_history = {}, seen_buttons = {}, saved_settings = {}; var main = $('#main'); // check if reversi var body_bg_color = window.getComputedStyle(document.body).backgroundColor; if (body_bg_color == 'rgb(51, 51, 51)') { 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(setting) { this.name = setting.name; this.label = setting.label; this.description = setting.description; this.options = setting.options; this.current = saved_settings[this.name] || setting.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: 'kudosed_display', label: 'Kudosed 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: 'seen_display', label: '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', }, { name: 'background_check', label: 'Check for kudos while browsing works lists', description: 'The script checks kudos on the works in the background. Warning: This may cause too many requests to AO3 if you browse too quickly, and it\'s not very reliable since AO3 started paginating kudos (so the script may not find yours if it\'s further down the list).', 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); }); 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 header to collapse/expand blurb_index.on('click', '.header', function (e) { if (!$(e.target).is('a') && !$(e.target).is('span')) { $(this).closest('.blurb').toggleClass('collapsed-blurb'); e.stopPropagation(); } }); // toggle seen/skipped blurb_index.on('click', '.kh-toggle', function (e) { changeBlurbStatus($(this).closest('.blurb'), $(this).data('list'), true); e.stopPropagation(); }); // 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; } var found_on_list = false; var blurb_classes = 'blurb-with-toggles'; blurb.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_classes += ' has-kudos collapsed-blurb'; found_on_list = true; } // if work id is on the seen list if (kudos_history.seen.hasId(work_id)) { DEBUG && console.log('- is seen'); blurb_classes += ' marked-seen collapsed-blurb'; found_on_list = true; } // not on the kudosed/seen list if (!found_on_list) { // if work id is on the checked list if (kudos_history.checked.hasId(work_id)) { DEBUG && console.log('- is checked'); } else { blurb_classes += ' new-blurb'; if (kudos_history.background_check.check()) { loadKudos(blurb); } else { DEBUG && console.log('- marking as checked'); kudos_history.checked.add(work_id); } } } // if work id is on the bookmarked list if (kudos_history.bookmarked.hasId(work_id)) { DEBUG && console.log('- is bookmarked'); blurb_classes += ' is-bookmarked'; } // if work id is on the skipped list if (kudos_history.skipped.hasId(work_id)) { DEBUG && console.log('- is skipped'); blurb_classes += ' skipped-work collapsed-blurb'; } blurb.addClass(blurb_classes); } // 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('.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 list_types = ['kudosed', 'seen', 'bookmarked', 'skipped', 'checked']; var list_types_checkboxes = []; list_types.forEach(function (list_type) { list_types_checkboxes.push(''); }); var box_button_generate = $(''); box_button_generate.click(function () { var export_lists = {}; list_types.forEach(function (list_type) { if ($('#kh-export-checkbox-' + list_type).prop('checked')) { export_lists[list_type] = kudos_history[list_type].reload().list; } }); $('#export-seen-list').val(JSON.stringify(export_lists)); }); 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('Select which lists to export (leave them all selected if you\'re not sure) and generate your export. Then copy your current lists from the field below and save wherever you want as a backup.'), $('

    ').append(list_types_checkboxes), $('

    ').append(box_button_generate), $(''), $('

    ').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); button.addClass('kh-menu-setting'); } 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 = $('