// ==UserScript== // @name AO3: Fic's Style, Blacklist, Bookmarks // @namespace https://github.com/Schegge // @description Change font, size, width and background of a work + blacklist: hide works that contain certains tags or text, have too many tags/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 // @icon https://raw.githubusercontent.com/Schegge/Userscripts/master/images/ao3icon.png // @version 3.6 // @author Schegge // @match *://archiveofourown.org/* // @match *://*.archiveofourown.org/* // @grant GM_getValue // @grant GM_setValue // @grant GM.getValue // @grant GM.setValue // @downloadURL none // ==/UserScript== // gm4 polyfill https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js if (typeof GM == 'undefined') { this.GM = {}; Object.entries({ 'GM_getValue': 'getValue', 'GM_setValue': 'setValue' }).forEach(([oldKey, newKey]) => { let old = this[oldKey]; if (old && (typeof GM[newKey] == 'undefined')) { GM[newKey] = function(...args) { return new Promise((resolve, reject) => { try { resolve(old.apply(this, args)); } catch (e) { reject(e); } }); }; } }); } (async function() { const SN = 'stblbm'; // check which page const Check = { // script version version: async function() { if (await getStorage('version', '1') !== 36) { setStorage('version', 36); return true; } return false; }, // on search pages but not on personal user profile black: function() { let user = document.querySelector('#greeting .user a[href*="/users/"]') || false; user = user && window.location.pathname.includes(user.href.split('/users/')[1]); return document.querySelector('li.blurb.group:not(.collection):not(.tagset)') && !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 check if (await Check.version()) { // retrieve saved value from localstorage if they exist if (localStorage.getItem(`ficstyle_feature`)) await GM.setValue('feature', localStorage.getItem(`ficstyle_feature`)) if (localStorage.getItem(`ficstyle_styling`)) await GM.setValue('styling', localStorage.getItem(`ficstyle_styling`)) if (localStorage.getItem(`ficstyle_bookmarks`)) await GM.setValue('bookmarks', localStorage.getItem(`ficstyle_bookmarks`)) if (localStorage.getItem(`ficstyle_blacklist`)) await GM.setValue('blacklistTags', localStorage.getItem(`ficstyle_blacklist`)) if (localStorage.getItem(`ficstyle_blacklist_opts`)) await GM.setValue('blacklistOpts', localStorage.getItem(`ficstyle_blacklist_opts`)) // notification document.body.insertAdjacentHTML('beforeend', `
AO3: Fic's Style, Blacklist, Bookmarks UPDATES (v3.6)

You can now blacklist works that have too many tags, have a number of chapters below what you set for it, and contain certain words or phrases in the title and in the summary.
I've changed how to blacklist authors, if you were hiding someone, you have to move them in the new designated area without the @.

I've moved your saved values from the browser local storage to the userscript manager storage, there shouldn't be any loss on your part, if otherwise please contact me on greasyfork, they aren't lost.

More info.

close`); document.getElementById(`${SN}-close`).addEventListener('click', function() { this.parentElement.style.display = 'none'; }); } /** FEATURES **/ const Feature = { style: true, book: true, black: true, wpm: 250 } // Object.assign() changes only the same keys Object.assign(Feature, await getStorage('feature', '{}')); // Features' menu addCSS(`${SN}-menus`, `li[id|="${SN}"] a { cursor: pointer; } li[id|="${SN}"] .dropdown-menu li a.${SN}-save { color: #900!important; font-weight: bold; text-align: center; padding-bottom: 0.75em!important; } li[id|="${SN}"] .dropdown-menu input[type="number"], li[id |= "${SN}"] .dropdown-menu input[type="text"] { width: 3em; padding: 0 0 0 .2em; margin: 0; background: #fff; } li[id|="${SN}"] .dropdown-menu input[type="checkbox"] { margin: 0; } li[id|="${SN}"] .dropdown-menu textarea { font-size: .9em; line-height: 1.2em; min-height: 4em; padding: .3em; margin: .1em .5em; width: calc(100% - 1em); box-sizing: border-box; resize: vertical; } li[id|="${SN}"] .${SN}-opts { display: flex!important; flex-wrap: nowrap; align-items: center; } li[id|="${SN}"] .${SN}-opts span { width: 25%; flex: auto; font-size: .75em; text-transform: uppercase; padding: .3em 0; } #${SN}-black .dropdown-menu { width: 28em; } #${SN}-black .dropdown-menu .${SN}-opts { text-align: center!important; } #${SN}-black .dropdown-menu input[type="text"] { width: 8em; } #${SN}-book .${SN}-opts a:first-child { flex-grow: 1; font-size: .9em; } a.${SN}-book-delete { color: #900!important; } div[class*="${SN}-book"] a { margin: 1em .2em 0 0; font-size: .8em; cursor: pointer; } .${SN}-book-left { position: fixed; left: 0; bottom: 0; margin: 0 0 .8em .5em; z-index: 999; } .${SN}-book-top { text-align: right;} .${SN}-no-book { display: none!important; } #${SN}-style { position: fixed; bottom: 0; right: 0; margin: 0 .5em .8em 0; padding: 0; border-radius: .3em; background-color: transparent; text-align: right; font-size: .9em; z-index: 999; } #${SN}-style:not(.${SN}-style-hide) { width: 25em; } #${SN}-style.${SN}-style-hide > div { display: none; } #${SN}-style > div { background-color: #ddd; padding: 0 .5em; box-shadow: 1px 1px 3px -1px #444; margin: 0; border-radius: .2em; } #${SN}-style label { display: block; border-bottom: 1px solid #888; padding: .2em 0; margin: 0; } #${SN}-style input, #${SN}-style select { width: 50%; padding: 0; margin: 0 0 0 1em; vertical-align: middle; } #${SN}-style button { margin: .3em .2em; } #${SN}-style-button { padding: 0 .3em; } .${SN}-words { font-size: .7em; color: inherit; font-family: consolas, monospace; text-transform: uppercase; text-align: center; margin: 3em 0 .5em; }` ); let featureMenu = document.createElement('li'); featureMenu.id = `${SN}-feature`; featureMenu.className = 'dropdown'; featureMenu.innerHTML = `Features `; document.querySelector('#header > ul').appendChild(featureMenu); document.getElementById(`${SN}-feature-save`).addEventListener('click', function() { Feature.style = document.getElementById(`${SN}-feature-style`).checked; Feature.book = document.getElementById(`${SN}-feature-book`).checked; Feature.black = document.getElementById(`${SN}-feature-black`).checked; let wpm = document.getElementById(`${SN}-feature-wpm`).value.trim(); Feature.wpm = wpm ? Math.min(Math.max(parseInt(wpm, 10), 0), 1000) : 0; setStorage('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 **/ let Bookmarks = {}; if (Feature.book) { Bookmarks = { list: [], getValues: async function() { this.list = await getStorage('bookmarks', '[]'); }, setValues: function() { setStorage('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; let book = false; this.list.some((bm, i) => { // check if the same fic already exists if (bm[0].split('/chapters/')[0] === url.split('/chapters/')[0]) { // i need the index to delete the old bookmark (for change or delete) if (what === 'cancel') { book = i; return true; // check if the same chapter } else if (bm[0] === url) { // retrieve the bookmark position if (what === 'book') { book = bm[2]; // if the bookmark is in % if (book.toString().includes('%')) book = parseFloat(book.replace('%', '')) * getDocHeight(); // just check if a bookmark exist } else { book = true; } return true; } } return false; }); return book; }, cancel: function(url) { let found = this.checkIfExist('cancel', url); // !== false because it can return 0 for the index if (found !== false) this.list.splice(found, 1); }, getNew: function() { this.cancel(); this.list.push([this.getUrl, this.getTitle(), this.getPosition()]); this.setValues(); }, html: function() { let bookMenu = document.createElement('li'); bookMenu.id = `${SN}-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.setValues(); this.style.display = 'none'; this.previousSibling.style.opacity = '.4'; }; this.list.forEach(item => { let bookMenuLi = document.createElement('li'); bookMenuLi.className = `${SN}-opts`; bookMenuLi.innerHTML = `${item[1]}`; let bookMenuDelete = document.createElement('a'); bookMenuDelete.className = `${SN}-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 = '
  • No bookmark yet.
  • '; } } }; await Bookmarks.getValues(); Bookmarks.html(); } /** FIC'S STYLE + FULLSCREEN + BOOKMARKING **/ if (Check.work()) { if (Feature.style) { addCSS(`${SN}-generalstyle`, `#main div.wrapper { margin-bottom: 1em; } #workskin { margin: 0; text-align: justify; max-width: none!important; } #workskin .notes, #workskin .summary, blockquote { font-size: inherit; font-family: inherit; } .preface a, #chapters a, .preface a:link, #chapters a:link, .preface a:visited, #chapters a:visited, .preface a:visited:hover, #chapters a:visited:hover { color: inherit !important; } .actions { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'GNU Unifont', Verdana, Helvetica, sans-serif; font-size: 14px; } .chapter .preface { border-top: 0; margin-bottom: 0; padding: 0 2em; } .chapter .preface[role="complementary"] { border-width: 0; margin: 0; } .preface.group, div.preface { color: inherit; background-color: inherit; margin-left: 0; margin-right: 0; padding: 0 2em; } #workskin #chapters .preface .userstuff p, #workskin .preface .userstuff p { margin: .1em auto; line-height: 1.1em; } div.preface .byline a, #workskin #chapters a, #chapters a:link, #chapters a:visited { color: inherit; } div.preface .notes, div.preface .summary, div.preface .series, div.preface .children { min-height: 0; } div.preface .jump { margin-top: 1em; font-size: .9em; } .preface blockquote { box-shadow: 0 0 0 2px rgba(0, 0, 0, .1), 0 0 0 2px rgba(255, 255, 255, .2); padding: .6em; margin: 0; } .preface h3.title { background: repeating-linear-gradient(45deg, rgba(0, 0, 0, .05), rgba(0, 0, 0, .1) 2px, rgba(255, 255, 255, .2) 2px, rgba(255, 255, 255, .2) 4px); padding: .6em; margin: 0; } .preface h3.heading { font-size: inherit; border-width: 0; } h3.title a { border: 0; font-style: italic; } div.preface .associations, .preface .notes h3+p { margin-bottom: 0; font-style: italic; font-size: .8em; } #workskin #chapters, #workskin #chapters .userstuff { width: 100%!important; box-sizing: border-box; } #workskin #chapters .userstuff p { font-family: inherit; text-align: justify; } #workskin #chapters .userstuff { font-family: inherit; text-align: justify; } #workskin #chapters .userstuff br { display: block; margin-top: .6em; content: " "; } .userstuff hr { width: 100%; height: 2px; border: 0; margin: 1.5em 0; background-image: linear-gradient(90deg, transparent, rgba(0, 0, 0, .2), transparent), linear-gradient(90deg, transparent, rgba(255, 255, 255, .3), transparent); } #workskin #chapters .userstuff blockquote { padding-top: 1px; padding-bottom: 1px; margin: 0 .5em; font-size: inherit; } .userstuff img { max-width: 100%; height: auto; display: block; margin: auto; }` ); // CSS changes depending on the user const Styling = { opts: { fontName: 'Default', colors: 'light', fontSize: '100', margins: '7', lineSpacing: '5' }, inputs: [ // 0:css/id, 1:name, 2+:options ['fontName', 'Font', 'Default', 'Arial Black', 'Helvetica', 'Verdana', 'Segoe UI', 'Garamond', 'Georgia', 'Times New Roman', 'Consolas', 'Courier'], ['colors', 'Background', 'light', 'grey', 'sepia', 'dark', 'darkblue', 'black'], ['fontSize', 'Text Size', 100, 50, 300], ['margins', 'Page Margins', 7, 5, 40], ['lineSpacing', 'Line Spacing', 5, 3, 10] ], fonts: { 'inherit': 'inherit', // old default 'Default': 'inherit', 'Arial Black': 'Arial Black, Arial Bold, Gadget, sans-serif', 'Helvetica': 'Helvetica, Helvetica Neue, sans-serif', 'Verdana': 'Verdana, Tahoma, sans-serif', 'Segoe UI': 'Segoe UI, Trebuchet MS, sans-serif', 'Garamond': 'Garamond, Book Antiqua, Palatino, Baskerville, serif', 'Georgia': 'Georgia, serif', 'Times New Roman': 'Times New Roman, Times, serif', 'Consolas': 'Consolas, Lucida Console, monospace', 'Courier': 'Courier, Courier New, monospace' }, colors: { // background, font color light: ['#ffffff', '#000000'], grey: ['#e6e6e6', '#111111'], sepia: ['#fbf0d9', '#54331b'], dark: ['#333333', '#e1e1e1'], darkblue: ['#282a36', '#f8f8e6'], black: ['#000000', '#ffffff'] }, getValues: async function() { Object.assign(this.opts, await getStorage('styling', '{}')); }, setValues: function() { setStorage('styling', this.opts); addCSS(`${SN}-userstyle`, `#workskin { font-family: ${this.fonts[this.opts.fontName]}; font-size: ${this.opts.fontSize}%; padding: 0 ${this.opts.margins}%; color: ${this.colors[this.opts.colors][1]}; background-color: ${this.colors[this.opts.colors][0]}; } #workskin #chapters .userstuff p { line-height: ${this.opts.lineSpacing * 0.3}em; margin: ${this.opts.lineSpacing * 0.5 - 1.4}em auto; } #workskin #chapters .userstuff { line-height: ${this.opts.lineSpacing * 0.3}em; }` ); }, html: function() { this.setValues(); // the options displayed on the page let styleMenu = document.createElement('div'); styleMenu.id = `${SN}-style`; styleMenu.className = `${SN}-style-hide`; let h = ''; this.inputs.forEach(item => { h += `'; }); styleMenu.innerHTML = `
    ${h}
    `; document.body.appendChild(styleMenu); document.getElementById(`${SN}-style-save`).addEventListener('click', () => { let pos = getScroll() / getDocHeight(); this.inputs.forEach(item => { this.opts[item[0]] = styleMenu.querySelector(`#${item[0]}`).value; }); this.setValues(); setScroll(pos * getDocHeight()); }); document.getElementById(`${SN}-style-reset`).addEventListener('click', () => { let pos = getScroll() / getDocHeight(); styleMenu.parentElement.removeChild(styleMenu); this.inputs.forEach(item => { this.opts[item[0]] = item[2]; }); this.html(); setScroll(pos * getDocHeight()); }); document.getElementById(`${SN}-style-button`).addEventListener('click', function() { this.parentElement.className = this.parentElement.className ? '' : `${SN}-style-hide`; }); } }; await Styling.getValues(); Styling.html(); } // END Feature.style // # words and time for every chapter, if the fic has chapters if (Feature.wpm) { document.querySelectorAll('#chapters > .chapter > div.userstuff.module').forEach(el => { let numWords = el.textContent.replace(/['’‘-]/g, '').match(/\w+/g).length - 2; // -2 because of hidden

    Chapter Text

    el.parentElement.insertAdjacentHTML('afterbegin', `
    this chapter has ${numWords} words (time: ${countTime(numWords)})
    `); }); } // FULL SCREEN if (Feature.book) { let workskin = document.getElementById('workskin'); let ficTop = document.createElement('div'); ficTop.className = `actions ${SN}-book-top`; let toFullScreen = document.createElement('a'); toFullScreen.textContent = 'Full Screen'; ficTop.appendChild(toFullScreen); workskin.insertAdjacentElement('afterbegin', ficTop); // changes to create full screen let fullScreen = () => { if (Check.fullScreen) { window.location.replace(window.location.pathname); return; } setScroll(0); Check.fullScreen = true; addCSS(`${SN}-fullscreen`, `#outer { display: none; } #workskin .preface { margin: 0; padding-bottom: 0; } div.preface .notes, div.preface .summary, div.preface .series, div.preface .children { min-height: 0; } div.preface .module { padding-bottom: 0; text-align: center; } .preface .module h3.heading { display: inline; cursor: pointer; text-align: center; opacity: .5; font-style: italic; font-size: 100%; } .preface .module > :not(h3) { display: none; } .preface h3 + p { border: 3px solid rgba(0, 0, 0, .1); border-left: 0; border-right: 0; padding: .6em; margin: 0; } .preface .module > h3:hover ~ .userstuff, .preface .module > .userstuff:hover, .preface .module > h3:hover ~ ul, .preface .module > ul:hover, .preface .module > h3:hover + p, .preface .module > h3 + p:hover { display: block!important; position: absolute; width: 100%; max-height: 6em; font-size: .8em; transform: translateY(-100%); color: rgb(42, 42, 42); background-color: #fff; padding: 10px; box-shadow: 0 1px 2px 0 rgba(0, 0, 0, .4); margin: 0; overflow: auto; z-index: 999; cursor: pointer; } .actions:not(div[class*="${SN}-book"]) li > a:not([href*="chapters"]):not([href="#workskin"]) { display: none; } .actions:not(div[class*="${SN}-book"]) { margin-top: 2em; }` ); document.body.appendChild(workskin); toFullScreen.textContent = 'Exit'; let goToBook = document.createElement('a'); goToBook.textContent = 'Go to Bookmark'; goToBook.addEventListener('click', () => { setScroll(Bookmarks.checkIfExist('book')); }); let ficLeft = document.createElement('div'); ficLeft.className = `actions ${SN}-book-left`; let deleteBook = document.createElement('a'); deleteBook.title = 'delete bookmark'; deleteBook.textContent = 'x'; deleteBook.addEventListener('click', () => { Bookmarks.cancel(); Bookmarks.setValues(); goToBook.className = `${SN}-no-book`; deleteBook.className = `${SN}-no-book`; }); let newBook = document.createElement('a'); newBook.title = 'new bookmark'; newBook.textContent = '+'; newBook.addEventListener('click', function() { Bookmarks.getNew(); goToBook.className = ''; deleteBook.className = ''; this.textContent = 'saved'; setTimeout(() => { this.textContent = '+'; }, 1000); }); if (!Bookmarks.checkIfExist()) { goToBook.className = `${SN}-no-book`; deleteBook.className = `${SN}-no-book`; } ficTop.insertAdjacentElement('afterbegin', goToBook); ficLeft.appendChild(newBook); ficLeft.appendChild(deleteBook); document.body.appendChild(ficLeft); document.querySelector('#feedback .actions a[href="#main"]').href = '#workskin'; workskin.appendChild(document.querySelector('#feedback .actions')); }; if (Bookmarks.fromBook) fullScreen(); toFullScreen.addEventListener('click', fullScreen); } // END Feature.book // remove all the non-breaking white spaces document.getElementById('chapters').innerHTML = document.getElementById('chapters').innerHTML.replace(/ /g, ' '); } // END Check.work() /** BLACKLIST **/ if (Feature.black && Check.black()) { addCSS(`${SN}-blacklisting`, `[data-${SN}-visibility="remove"], [data-${SN}-visibility="hide"] > :not(.header), [data-${SN}-visibility="hide"] > .header > :not(h4) { display: none!important; } [data-${SN}-visibility="hide"] > .header, [data-${SN}-visibility="hide"] > .header > h4 { margin: 0!important; min-height: auto; font-size: .9em; font-style: italic; } [data-${SN}-visibility="hide"] { opacity: .6; } [data-${SN}-visibility="hide"]::before { content: "\\2573 " attr(data-${SN}-reasons); font-size: .8em; }` ); const Blacklist = { lists : { Tag: [], Text: [], Author: [] }, opts: { show: true, pause: false, maxTags: 0, maxFandoms: 0, maxRelations: 0, minIncomplete: 0, minChapters: 0, maxChapters: 0, minWords: 0, maxWords: 0, langs: '' }, blurb: 'li.blurb.group', getValues: async function() { this.lists.Tag = await getStorage('blacklistTags', '[]'); this.lists.Text = await getStorage('blacklistText', '[]'); this.lists.Author = await getStorage('blacklistAuthors', '[]'); Object.assign(this.opts, await getStorage('blacklistOpts', '{}')); }, findTags: function(w) { return this.opts.maxTags && w.querySelectorAll('.tag').length > this.opts.maxTags; }, findFandoms: function(w) { return this.opts.maxFandoms && w.querySelectorAll('.header .fandoms .tag').length > this.opts.maxFandoms; }, findRelations: function(w) { return this.opts.maxRelations && w.querySelectorAll('.tags .relationships .tag').length > this.opts.maxRelations; }, findLangs: function(w) { return this.opts.langs && w.querySelector('dd.language') && !this.opts.langs.toLowerCase().includes(w.querySelector('dd.language').textContent.toLowerCase().trim()); }, getChapters: function(w) { if ((this.opts.minChapters || this.opts.maxChapters) && w.querySelector('dd.chapters')) { let numCh = parseInt(w.querySelector('dd.chapters').textContent.split('/')[0], 10); if (this.opts.minChapters && numCh < this.opts.minChapters || this.opts.maxChapters && numCh > this.opts.maxChapters) { return `Chapters: ${numCh}`; } } return []; }, getWords: function(w) { if ((this.opts.minWords || this.opts.maxWords) && w.querySelector('dd.words')) { let numWords = parseInt(w.querySelector('dd.words').textContent.replace(/,/g, ''), 10) / 1000; if (this.opts.minWords && numWords < this.opts.minWords || this.opts.maxWords && numWords > this.opts.maxWords) { return `Words: ${Math.round(numWords*1000)}`; } } return []; }, getIncomplete: function(w) { if (this.opts.minIncomplete && w.querySelector('.required-tags .complete-no')) { // millisecs in an average month = 30.4days*24hrs*60mins*60secs*1000 let updated = (Date.now() - new Date(w.querySelector('.datetime').textContent).getTime()) / (30.4*24*60*60*1000); if (updated > this.opts.minIncomplete) return `Updated: ${Math.floor(updated)}mnth ago`; } return []; }, ifMatch: function(t, list, m) { let found = false; this.lists[list].some(v => { let b = v.trim().replace(/[.+?^${}()|[\]\\]/g, '\\$&'); if (!b) return false; // wildcard b = b.replace(/\*/g, '.*'); // match 2 words in any order b = b.replace(/(.+)&&(.+)/, '(?=.*$1)(?=.*$2).*'); // only otp if (t.parent === 'relationships') b = b.replace(/(.+)&!(.+)/, '(?=.*\\/)((?=.*$1)(?!.*$2)|(?=.*$2)(?!.*$1)).*'); let regex; if (m === 'free') regex = new RegExp(b, 'i'); // for text else regex = new RegExp(`^${b}$`, 'i'); if (regex.test(t.text)) { if (m === 'free') found = `${list}: ${v}`; // show the rule that matched (for text) else if (t.parent === 'heading') found = list; // show only list name (for author) else found = `${list}: ${t.text}`; // show the entire matched tag return true; } return false; }); return found; }, getReasons: function(w, list, where, m) { if (!this.lists[list].length) return []; return Array.prototype.reduce.call(w.querySelectorAll(where), (filtered, t) => { let found = this.ifMatch({ text: t.textContent.trim(), parent: t.parentElement.className }, list, m); if (found) filtered.push(found); return filtered; }, []); }, setVisibility: function() { if (this.opts.pause) return; document.querySelectorAll(this.blurb).forEach(w => { let reasons = [] .concat(this.getReasons(w, 'Author', 'h4.heading a[rel="author"]', '')) .concat(this.getIncomplete(w)) .concat(this.getWords(w)) .concat(this.getChapters(w)) .concat(this.getReasons(w, 'Text', 'h4.heading a:first-child, .summary', 'free')) .concat(this.getReasons(w, 'Tag', '.tags .tag, .required-tags span:not(.warnings) span.text, .header .fandoms .tag', '')); if (this.findRelations(w)) reasons.unshift('Relations'); if (this.findFandoms(w)) reasons.unshift('Fandoms'); if (this.findTags(w)) reasons.unshift('Tags'); if (this.findLangs(w)) reasons.unshift('Language'); if (!reasons.length) return; if (this.opts.show) { w.setAttribute(`data-${SN}-visibility`, 'hide'); w.setAttribute(`data-${SN}-reasons`, reasons.join(' - ')); } else { w.setAttribute(`data-${SN}-visibility`, 'remove'); } }); }, getArray: function(string) { return string.trim() ? string.split(',').filter(s => s.trim().length) : []; }, getInt: function(string, min = 0) { let number = string.trim() ? Math.max(parseInt(string, 10), 0) : 0; if (number < min) number = 0; return number; }, setValues: function() { this.lists.Tag = this.getArray(document.getElementById(`${SN}-black-tags`).value); this.lists.Text = this.getArray(document.getElementById(`${SN}-black-text`).value); this.lists.Author = this.getArray(document.getElementById(`${SN}-black-authors`).value); this.opts.maxTags = this.getInt(document.getElementById(`${SN}-black-maxTags`).value); this.opts.maxFandoms = this.getInt(document.getElementById(`${SN}-black-maxFandoms`).value); this.opts.maxRelations = this.getInt(document.getElementById(`${SN}-black-maxRelations`).value); this.opts.minIncomplete = this.getInt(document.getElementById(`${SN}-black-minIncomplete`).value); this.opts.minChapters = this.getInt(document.getElementById(`${SN}-black-minChapters`).value); this.opts.maxChapters = this.getInt(document.getElementById(`${SN}-black-maxChapters`).value, this.opts.minChapters); this.opts.minWords = this.getInt(document.getElementById(`${SN}-black-minWords`).value); this.opts.maxWords = this.getInt(document.getElementById(`${SN}-black-maxWords`).value, this.opts.minWords); this.opts.langs = document.getElementById(`${SN}-black-langs`).value; this.opts.show = document.getElementById(`${SN}-black-show`).checked; this.opts.pause = document.getElementById(`${SN}-black-pause`).checked; setStorage('blacklistTags', this.lists.Tag); setStorage('blacklistText', this.lists.Text); setStorage('blacklistAuthors', this.lists.Author); setStorage('blacklistOpts', this.opts); document.querySelectorAll(`${this.blurb}[data-${SN}-visibility]`).forEach(el => { el.removeAttribute(`data-${SN}-visibility`); el.removeAttribute(`data-${SN}-reasons`); }); this.setVisibility(); }, html: function() { let blackMenu = document.createElement('li'); blackMenu.id = `${SN}-black`; blackMenu.className = 'dropdown'; blackMenu.innerHTML = `Blacklist
    `; document.querySelector('#header > ul').appendChild(blackMenu); document.getElementById(`${SN}-black-save`).addEventListener('click', function() { Blacklist.setValues(); this.textContent = 'SAVED'; setTimeout(() => { this.textContent = 'SAVE'; }, 1000); }); } }; await Blacklist.getValues(); Blacklist.setVisibility(); Blacklist.html(); } // END Feature.black AND Check.black() /** GLOBAL FUNCTIONS **/ async function getStorage(key, def) { // def must be a string return JSON.parse(await GM.getValue(key, def)); } function setStorage(key, value) { // value can be any type GM.setValue(key, value !== 'string' ? JSON.stringify(value) : value); } function addCSS(id, css) { // unique id because of styling user changes if (!document.querySelector(`style#${id}`)) { let style = document.createElement('style'); style.id = id; style.textContent = css; document.getElementsByTagName('head')[0].appendChild(style); } else { document.querySelector(`style#${id}`).textContent = css; } } function countTime(num) { // estimate reading time if (!num) return '?'; num = Math.round(parseInt(num, 10) / Feature.wpm); let h = Math.floor(num / 60); let m = num % 60; return `${h > 0 ? `${h}hr ` : ''}${m > 0 ? `${m}min` : ''}` || '<1min'; } function getScroll() { return Math.max(document.documentElement.scrollTop, window.scrollY, 0); } function setScroll(s) { window.scroll(0, s); } function getDocHeight() { return Math.max(document.documentElement.scrollHeight, document.documentElement.offsetHeight, document.body.scrollHeight, document.body.offsetHeight); } })();