// ==UserScript==
// @name AO3: Fic's Style, Blacklist, Bookmarks
// @namespace https://github.com/Schegge
// @version 3.5.2
// @description Change font, size, width, background... of a work + blacklist: hide works that contain certains tags, have too many fandoms/relations/chapters/words and other options + fullscreen reading mode + bookmarks: save the position you stopped reading a fic + number of words for each chapter and estimated reading time
// @author Schegge
// @include http*://archiveofourown.org/*
// @grant none
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAYAAABXuSs3AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjc0REE4QjE3Qjk1ODExRTY4MEZCQTc3RERGMTNGQ0Y0IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjc0REE4QjE4Qjk1ODExRTY4MEZCQTc3RERGMTNGQ0Y0Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NzREQThCMTVCOTU4MTFFNjgwRkJBNzdEREYxM0ZDRjQiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NzREQThCMTZCOTU4MTFFNjgwRkJBNzdEREYxM0ZDRjQiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz78uZUsAAACeUlEQVR42uxZvW7bMBCWDaFLuih9A3bJrqJLVhVdigwFZGQPIHdpO9aPYC+dGz1CNXXpEiFzFq0NMtjIE0QvYNQ9Bh+BA6Mf0rKkBuABHyxQJPHxdPfdSZ7sdjvvOdrUe6bmiDvijrgj7og/mn/oDX/4T7YMCAkhxLW0nJARNnzip+32v/H4N8KaEIHsipASBMYv2WHG9TizS3haEp1r96S3C8yRT+IdobTZfNLUq1Q89iYLQEJ6Nib8RCi8NjicfBKLsUJFhsUViCwxlrasSdlaMZaqJPi9YCSKljX8fjwG8YQl2Vs2nhusVXPCMYjHB9gjGJq4gNyV3oA2PVCYyFhd1BzK5OAm+dAL8RTarHs9NAiPUYirpFSkM+3+R8Pc2FSs7ZV4BG+XNbp93uD1gOn9fMgYF/BYpukyb5z+oihF2tozwh/CS8KtasBsKnUX4jFI5jXVUO3/G+V/jUPcE37hkCeEL6zqBkMQT2pKuj72gnBMmKEneYXQ+sAOnsLrcd/EY4RKFfFSG1dzC5C80/twliPGHvc7EC8aEm9TMX+F6zcNWp6bEtinrVUvATbW1N5GiG+pLKlpa+t3SMo2CQuZ3An2FsTbYHV/ZePtfT2+RgyvDKrig5a08wY9l8k+I49nfSSnSjSTzfUkTWqSr2ROWPalKklFkTHptZV9RYgENcks+lAVFacLizXqE4Qi9Bma7mmhJmrUqLPHA1S/fYyH1bGm2zxRPRun+AbKECFEBItDVVCKmnZUYG3I3kW5fUfneEM4xfyZTYfYRnxZ0SDxF+Mc30Sqkrgp0Y4I7wnXUJqN7WOcuP+AHHFH3BF3xB1xR9zC/gkwALX7mp/233xAAAAAAElFTkSuQmCC
// @downloadURL none
// ==/UserScript==
(function() {
const NAMESPACE = 'ficstyle';
/** FEATURES **/
const Feature = {
style: true,
book: true,
black: true,
wpm: 250
}
// Object.assign changes only the same keys
Object.assign(Feature, getStorage(`${NAMESPACE}_feature`, '{}'));
// check which page
const Check = {
// script version
version: function() {
if (getStorage(`${NAMESPACE}_version`, '1') !== 352) {
setStorage(`${NAMESPACE}_version`, 352);
return true;
}
return false;
},
// on search pages but not on personal user profile
black: function() {
let user = document.querySelector('#greeting .user a[href *= "/users/"]');
user = user && window.location.pathname.includes(user.href.split('/users/')[1]);
return document.querySelector('li.blurb.group:not(.collection)') && !user;
},
// include /works/(numbers) and /works/(numbers)/chapters/(numbers) and exclude /works/(whatever)navigate
work: function() {
return /\/works\/\d+(\/chapters\/\d+)?(?!.*navigate)/.test(window.location.pathname);
},
// Full Screen
fullScreen: false
};
// new version notification + cleaning old stuff
if (Check.version()) {
localStorage.removeItem(`${NAMESPACE}_blacklist_langs`);
if (localStorage.getItem(NAMESPACE)) {
localStorage.setItem(`${NAMESPACE}_styling`, localStorage.getItem(NAMESPACE));
localStorage.removeItem(NAMESPACE);
}
if (localStorage.getItem(`${NAMESPACE}_wpm`)) {
Feature.wpm = localStorage.getItem(`${NAMESPACE}_wpm`);
localStorage.removeItem(`${NAMESPACE}_wpm`);
}
document.body.insertAdjacentHTML('beforeend', `
Every feature is now optional, you can disable them in the menu "Features". Disabling them doesn't erase the values saved previously. To disable the estimated reading time set the words per minute to 0.
I've slightly changed how to save of the preferences, none of them should be lost apart from your preferred languages in the blacklist.
`;
document.querySelector('#header > ul').appendChild(featureMenu);
document.getElementById(`${NAMESPACE}-feature-save`).addEventListener('click', function() {
Feature.style = document.getElementById(`${NAMESPACE}-feature-style`).checked;
Feature.book = document.getElementById(`${NAMESPACE}-feature-book`).checked;
Feature.black = document.getElementById(`${NAMESPACE}-feature-black`).checked;
let wpm = document.getElementById(`${NAMESPACE}-feature-wpm`).value;
Feature.wpm = wpm ? Math.min(Math.max(parseInt(wpm, 10), 0), 1000) : 0;
setStorage(`${NAMESPACE}_feature`, Feature);
this.textContent = 'SAVING...';
window.location.replace(window.location.href);
});
// add estimated reading time for every fic found
if (Feature.wpm) {
document.querySelectorAll('dl.stats dd.words').forEach(w => {
let numWords = w.textContent.replace(/,/g, '');
w.insertAdjacentHTML('afterend', `
Time:
${countTime(numWords)}
`);
});
}
/** BOOKMARKS **/
const Bookmarks = {
list: [],
get: function() {
this.list = getStorage(`${NAMESPACE}_bookmarks`, '[]');
},
set: function() {
setStorage(`${NAMESPACE}_bookmarks`, this.list);
},
fromBook: window.location.search === '?bookmark',
getUrl: window.location.pathname.split('/works/')[1],
getTitle: function() {
let title = document.querySelector('#workskin .preface.group h2.title.heading').textContent.trim().substring(0, 28);
// get the number of the chapter if chapter by chapter
if (this.getUrl.includes('/chapters/')) title += ` (${document.querySelector('#chapters > .chapter > .chapter.preface.group > h3 > a').textContent.replace('Chapter ', 'ch')})`;
return title;
},
getPosition: function() {
let position = getScroll();
// calculate % if chapter by chapter view or work completed (number/number is the same)
if (window.location.pathname.includes('/chapters/') || /(\d+)\/\1/.test(document.querySelector('dl.stats dd.chapters').textContent)) {
position = (position / getDocHeight()).toFixed(4) + '%';
}
return position;
},
checkIfExist: function(what, link) {
let url = link || this.getUrl;
for (let i = 0, len = this.list.length; i < len; i++) {
// check if the same fic already exists
if (this.list[i][0].split('/chapters/')[0] === url.split('/chapters/')[0]) {
// i need the index to delete the old bookmark (for change or cancel)
if (what === 'cancel') {
return i;
// check if the same chapter
} else if (this.list[i][0] === url) {
// retrieve the bookmark position
if (what === 'book') {
let book = this.list[i][2];
// if the bookmark is in %
if (book.toString().includes('%')) {
book = parseFloat(book.replace('%', ''));
book *= getDocHeight();
}
return book;
}
// just check if a bookmark exist
return true;
}
}
}
return false;
},
cancel: function(url) {
let found = this.checkIfExist('cancel', url);
if (found !== false) this.list.splice(found, 1);
},
getNew: function() {
this.cancel();
this.list.push([this.getUrl, this.getTitle(), this.getPosition()]);
this.set();
},
html: function() {
let bookMenu = document.createElement('li');
bookMenu.id = `${NAMESPACE}-book`;
bookMenu.className = 'dropdown';
bookMenu.innerHTML = 'Bookmarks';
let bookMenuDrop = document.createElement('ul');
bookMenuDrop.className = 'menu dropdown-menu';
bookMenu.appendChild(bookMenuDrop);
document.querySelector('#header > ul').appendChild(bookMenu);
if (this.list.length) {
let self = this;
let clickDelete = function() {
self.cancel(this.getAttribute('data-url'));
self.set();
this.style.display = 'none';
this.previousSibling.style.opacity = '.4';
};
this.list.forEach(item => {
let bookMenuLi = document.createElement('li');
bookMenuLi.innerHTML = `${item[1]}`;
let bookMenuDelete = document.createElement('a');
bookMenuDelete.className = `${NAMESPACE}-book-delete`;
bookMenuDelete.title = 'delete bookmark';
bookMenuDelete.setAttribute('data-url', item[0]);
bookMenuDelete.textContent = 'x';
bookMenuDelete.addEventListener('click', clickDelete);
bookMenuLi.appendChild(bookMenuDelete);
bookMenuDrop.appendChild(bookMenuLi);
});
} else {
bookMenuDrop.innerHTML = '