Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- ==8Kun Baker Tools v0.7.3==
- We have entered the STORM. Be STRONG!
- GOD WINS! Pray MEME LOVE!
- For God and Country! WWG1WGA
- ==Features:==
- '''Post Highlighting'''
- * Highlight posts that are marked notable (I.E. someone has replied and said
- notable) in light green
- * Highlight nominating posts in dark green
- * Highlight nominating posts in posts mentions in light green
- * Highlight Q Posts in yellow
- * Highlight Q posts in mentions (I.E. on posts that get (YOU)'ed)
- * Highlight links to Q Posts in sparkle (Like q's trip)
- * Highlight previous bread links in blue
- '''Navigation'''
- * Cycle through Q Posts
- * Cycle through (You)'s
- * Cycle through own posts
- * Jump To Bottom Link
- * Jump To Bottom Top link
- * NEW IN v0.7.0 Jump to last reading location (like when you post and it
- sends you to bottom, you can jump right back)
- * Easy access to Breads via Bread List window
- * Scrollbar navigation shows location of Q/You/Notable/etc posts
- * NEW IN v0.7.0: Hover over post marker to preview post
- * Click on a post marker in scrollbar to jump to post
- '''Filtering'''
- * Filter to only nominating and notable posts, Q posts, Q replies
- * Option to blur images until hover
- * Image blacklist (AKA the NOPE button)
- * NEW IN v0.7.0: SpamFader with multiple spam detection strategies:
- * NameFag strategy: Marks namefags as spam
- * Breadshitter strategy: Marks bread shitters as spam
- * High post count strategy: Marks those with high post count as spam
- * Flood fag strategy: Marks those who post in short intervals as spam
- * Mark user as not spam button
- * Spam badges tell WHY the algorithm marked as post as spam. TRANSPARENCY!
- '''Customizable'''
- * NEW IN v0.7.0: Customizable post highlighting colors
- * Hide/Show features
- * Settings saved in localStorage
- '''Notables'''
- * Generate notables post
- * Adds "Notable Nomination" button to posts that opens the
- Quick Reply box and prefills it with a BAKER NOTABLE Template
- '''Stats'''
- * Thread stats overlay with
- * color coded reply count that goes from green to red as bread ages
- * UID Count
- * Post rate chart shows how many posts per min
- ==To Install:==
- 1. Copy this source code
- 2. Go to 8kun
- 3. Click "Options" in the top right
- 4. Choose "User JS" tab
- 5. Paste Baker tools JS
- 6. WWG1WGA
- ==Changelog:==
- '''0.7.3'''
- * Fix previous bread vs next bread link highlighting logic
- * Update navigation controls colo(u)r to play nicer with dark theme
- '''0.7.2'''
- * Use flex layout in bakerwindow controls
- * Reorder navcontrols
- * Reduce space of controls in boardlist
- * Disable spamfader new post event listener on disable
- * Don't mark q posts as notable
- * Make windows resizable width, fix table formatting in breadlist
- * Use boardlist height in current index calcs
- '''0.7.1'''
- * Fix notable navigation in boardlist checkbox not working
- * Differentiate between previous and newer breads when highlighting
- '''0.7.0'''
- * Switched color scheme to match other tools
- * Post Per Minute Graph
- * Spam Fading with multiple strategies and spam badges to tell why post is spam
- * Allow customization of post highligting colors
- * Add go back to last reading location button
- * Improve Q post detection (all past trip codes)
- * Add post preview on hover to Scrollbar Navigation
- * Navigation controls are now aware of current page location
- * Bugfixes
- '''0.6.0'''
- * Navigation bar shows scroll location of q/you/notable
- posts and allows jumping to posts
- * Notable navigation controls in baker window and board list
- * Persistent Image Blacklist (AKA Nope Button)
- * Many bugfixes
- '''0.5.2'''
- * Fixes bread list table population bug
- '''0.5.0'''
- * Option to show Q/(YOU)/Own Post navigation controls in the boardlist
- * Option to hide Notable nomination button
- * List of research breads
- * BakerTools settings are now saved in local storage
- '''0.4.0'''
- * Option to blur images until hover
- * Adds a "Notable Nomination" button to posts that opens the Quick Reply
- box and prefills it with a BAKER NOTABLE Template
- * Add Q Post navigation links to the Baker Window
- * Add (You) navigation links to the Baker Window
- * Add own post navigation links to the Baker Window
- * Cleaned up baker window design
- * More code cleanup and linting changes
- '''0.3.0'''
- * Highlights Q Posts with white BG -> DARK TO LIGHT!
- * Highlights Q posts in mentions (I.E. posts that get (YOU)'ed)
- * Highlights links to Q Posts
- * Refactored code into classes for easier maint.
- '''0.2.0'''
- * Highlight pb links
- * Thread stats overlay with
- * color coded reply count that goes from green to red as bread ages
- * UID Count
- * Jump To Bottom Link
- * Jump To Bottom Top link
- '''0.1.0'''
- Initial release:
- * Highlight notables and nominators
- * Filter to only show notables and nominators
- * Create notables post
- Version History:
- https://pastebin.com/L1p6iRzZ 0.7.2
- https://pastebin.com/dN5FhHCv 0.7.1
- https://pastebin.com/6XuDuHYu 0.7.0
- https://pastebin.com/YTSSmH7t 0.6.0
- https://pastebin.com/mPVxr7Lz 0.5.2
- https://pastebin.com/nEhm7yyY 0.5.1
- https://pastebin.com/i9sF0Rd3 0.4.0
- https://pastebin.com/kz9LrcE9 0.3.0
- https://pastebin.com/4aEFsPwK 0.2.0
- https://pastebin.com/eNmTtzdi 0.1.0
- */
- (function($) {
- "use strict";
- /* globals $, board_name */
- /* exported 8kun */
- /**
- * Functions and vars related to EightKun functionality
- */
- class EightKun {
- /**
- * Get reply links in post
- * @param {Element} post div.post
- * @return {JQuery}
- */
- static getReplyLinksFromPost(post) {
- return $(post).find(EightKun.REPLY_SELECTOR)
- .filter(function(idx, link) {
- return $(link).text().match(EightKun.REPLY_SHORTLINK_REGEX);
- });
- }
- /**
- * Get the post number that is being replied to
- * @param {Anchor} link
- * @return {string}
- */
- static getPostNumberFromReplyLink(link) {
- return $(link).text()
- .match(EightKun.REPLY_SHORTLINK_REGEX)[1];
- }
- /**
- * Get time of post
- * @param {Element} post div.post
- * @return {number} post time in unixtime
- */
- static getPostTime(post) {
- return $(post).find('.intro time').attr('unixtime');
- }
- /**
- * Get date of post
- * @param {Element} post div.post
- * @return {number} post time in unixtime
- */
- static getPostDateTime(post) {
- return $(post).find('.intro time').attr('datetime');
- }
- /**
- * Get poster id of provided post
- * @param {Element} post div.post
- * @return {string} id of poster
- */
- static getPosterId(post) {
- return $(post).find('p > span.poster_id').first().text();
- }
- /**
- * Get name from post
- * @param {Element} post div.post
- * @return {string} name of post
- */
- static getPostName(post) {
- return $(post).find('.intro > label > .name').text();
- }
- /**
- * Get trip from post
- * @param {Element} post div.post
- * @return {string} trip of post
- */
- static getPostTrip(post) {
- return $(post).find('.intro .trip').text();
- }
- /**
- * Get the opening post of the thread
- * @return {Element} div.post
- */
- static getOpPost() {
- return $(EightKun.OP_POST_SELECTOR);
- }
- /**
- * Get poster id of OP
- * @return {number} poster id
- */
- static getOpPosterId() {
- return EightKun.getPosterId(EightKun.getOpPost());
- }
- /**
- * Is the post made by op?
- * @param {Element} post div.post
- * @return {boolean} true if op's post
- */
- static isPostFromOp(post) {
- return EightKun.getPosterId(post) === EightKun.getOpPosterId();
- }
- /**
- * Get the thread id
- * @return {number} id of thread
- */
- static getThreadId() {
- return $('.thread').get(0).id.split('_')[1];
- }
- /**
- * Use 8kun hide function on post
- * @param {Element} post div.post
- */
- static hidePost(post) {
- // TODO: implement it and use in spam and blacklist
- }
- /**
- * Get current board
- * @return {string}
- */
- static getCurrentBoard() {
- /* eslint-disable camelcase */
- return board_name;
- }
- /**
- * Get post number of post
- * @param {Element} post div.post
- * @return {number} Number of the post
- */
- static getPostNumber(post) {
- return post.id.split('_')[1];
- }
- /**
- * Get the top boardlist element
- * @return {Element} div.boardlist
- */
- static getTopBoardlist() {
- return $(EightKun.TOP_BOARDLIST_SELECTOR).get(0);
- }
- }
- EightKun.POST_SELECTOR = 'div.post';
- EightKun.POST_REPLY_SELECTOR = 'div.post.reply';
- EightKun.OP_POST_SELECTOR = 'div.post.op';
- EightKun.POST_BODY_SELECTOR = '.body';
- EightKun.POST_MODIFIED_SELECTOR = '.post_modified';
- EightKun.NEW_POST_EVENT = 'new_post';
- EightKun.OP_SUBJECT_SELECTOR = '.post.op > p > label > span.subject';
- EightKun.REPLY_SELECTOR = 'div.body:first a:not([rel="nofollow"])';
- EightKun.REPLY_SHORTLINK_REGEX = /^>>(\d+)$/;
- EightKun.REPLY_REGEX = /highlightReply\('(.+?)'/;
- EightKun.BOARDLIST_SELECTOR = `.boardlist`;
- EightKun.TOP_BOARDLIST_SELECTOR = `${EightKun.BOARDLIST_SELECTOR}:first`;
- /**
- * Wrapper for 8kun active_page variable to determine the type of
- * page the user is on.
- */
- class ActivePage {
- /**
- * Are we currently on the thread index page?
- * @return {boolean} True if on index
- */
- static isIndex() {
- return window.active_page == ActivePage.Index;
- }
- /**
- * Are we currently on the thread catalog page?
- * @return {boolean} True if on catalog
- */
- static isCatalog() {
- return window.active_page == ActivePage.Catalog;
- }
- /**
- * Are we on a thread page?
- * @return {boolean} True if on thread
- */
- static isThread() {
- return window.active_page == ActivePage.Thread;
- }
- }
- ActivePage.Index = 'index';
- ActivePage.Catalog = 'catalog';
- ActivePage.Thread = 'thread';
- /* globals $ */
- /* exported ColorPicker */
- /**
- * A color picker control that saves to localStorage
- */
- class ColorPicker {
- /**
- * Construct color picker
- *
- * @param {string} label The label for the control
- * @param {string} title Mouseover title
- * @param {string} setting localStorage setting name
- * @param {string} defaultValue the default color when setting is missing
- * @param {Function} changeHandler handler for value changes. Passes color
- */
- constructor(label, title, setting, defaultValue, changeHandler) {
- this.styleId = 'bakertools-colorpickers-styles';
- this.class = 'bakertools-colorpicker';
- this.labelClass = 'bakertools-colorpicker-label';
- this.inputClass = 'bakertools-colorpicker-input';
- this.resetButtonClass = 'bakertools-colorpicker-reset';
- this.changeHandler = changeHandler;
- this.label = label;
- this.title = title;
- this.setting = setting;
- this.defaultValue = defaultValue;
- this.strippedName = label.replace(/(\s|\(|\)|'|"|:)/g, '');
- this.defaultValue = defaultValue;
- this._createStyles();
- this._createElement();
- }
- /**
- * Create the HTML Element
- */
- _createElement() {
- this.element = $(`
- <div class='${this.class}'>
- <label class='${this.labelClass}'
- for="${this.strippedName}" title="${this.title}" >
- ${this.label}:
- </label>
- </div>
- `).get(0);
- this.input = $(`
- <input type="color" class='${this.inputClass}'
- id="${this.strippedName}" title="${this.title}" />
- `).get(0);
- $(this.element).append(this.input);
- $(this.input).change(function(e) {
- this.setColor(this.input.value);
- }.bind(this));
- this.reset = $(`
- <button class='${this.resetButtonClass}' title="Reset to Default">
- <i class="fa fa-undo"></i>
- </button>
- `).get(0);
- $(this.element).append(this.reset);
- $(this.reset).click(function(e) {
- e.preventDefault();
- this.setColor(this.defaultValue);
- }.bind(this));
- this.setColor(localStorage.getItem(this.setting) || this.defaultValue);
- }
- /**
- * Set the color
- * @param {string} color valid css color string
- */
- setColor(color) {
- localStorage.setItem(this.setting, color);
- this.input.value = color;
- this.changeHandler(color);
- }
- /**
- * Get the color
- * @return {string} color
- */
- getColor() {
- return this.input.value;
- }
- /**
- * Create styles for the control
- */
- _createStyles() {
- if ($(`#${this.styleId}`).length) {
- return;
- }
- $('head').append(`
- <style id='${this.styleId}'>
- .${this.class} {
- display: flex;
- align-items: center;
- }
- .${this.class} .${this.labelClass} {
- flex-grow: 1;
- }
- .${this.class} .${this.inputClass} {
- margin-right: .5em;
- }
- .${this.resetButtonClass} {
- padding: 0;
- background-color: Transparent;
- background-repeat: no-repeat;
- border: none;
- cursor: pointer;
- overflow: hidden;
- outline: none;
- }
- </style>
- `);
- }
- }
- /* global $, debounce, EightKun */
- /**
- * Creates first, prev, next, last navigation controls
- */
- class NavigationControl {
- /**
- * Construct navigatio control manager object
- *
- * @param {string} label the label for the control
- * @param {Function} updateFunction Called to get latest data
- * @param {string} updateEventName Called to get latest data
- */
- constructor(label, updateFunction, updateEventName) {
- const strippedName = label.replace(/(\s|\(|\)|'|"|:)/g, '');
- this.styleId = 'bakertools-navigationcontrol-styles';
- this.label = label;
- this.updateFunction = updateFunction;
- this.updateEventName = updateEventName;
- this.list = this.updateFunction();
- this.currentIndex = -1;
- const instanceId = $(NavigationControl.containerClass).length;
- this.navigationClass = `bakertools-navcontrol-${strippedName}`;
- this.indexChangeEvent =
- `bakertools-navcontrol-${strippedName}-index-changed`;
- this.currentIndexId = `${this.navigationClass}-current-index-${instanceId}`;
- this.currentIndexClass = `bakertools-navcontrol-current-index`;
- this.totalClass = `${this.navigationClass}-total`;
- this.goToFirstClass = `${this.navigationClass}-goto-first`;
- this.goToPreviousClass = `${this.navigationClass}-goto-prev`;
- this.goToNextClass = `${this.navigationClass}-goto-next`;
- this.goToLastClass = `${this.navigationClass}-goto-last`;
- this._setupStyles();
- this._createElement();
- this.updateIndexFromCurrentScrollPosition();
- this.updateIndexFromCurrentScrollPosition =
- debounce(this.updateIndexFromCurrentScrollPosition, 500);
- this._setupListeners();
- }
- // TODO: switch to flexbox layout
- /**
- * setup styles for nav control
- */
- _setupStyles() {
- if ($(`#${this.styleId}`).length) {
- return;
- }
- const boardListNavSelector =
- `${EightKun.BOARDLIST_SELECTOR} .${NavigationControl.containerClass}`;
- $('head').append(`
- <style id='${this.styleId}'>
- ${boardListNavSelector}:before {
- content: '[';
- color: #89A;
- }
- ${boardListNavSelector}:after {
- content: ']';
- color: #89A;
- }
- ${boardListNavSelector} {
- color: rgb(20, 137, 183);
- }
- </style>
- `);
- }
- /**
- * Create nav element
- */
- _createElement() {
- this.element = $(`
- <span title="Navigate ${this.label}"
- class="${NavigationControl.containerClass}">
- <label for="${this.navigationClass}">${this.label}:</label>
- <span class="${this.navigationClass}
- ${NavigationControl.navigationControlClass}">
- <i class="fa fa-angle-double-left ${this.goToFirstClass}"></i>
- <i class="fa fa-angle-left ${this.goToPreviousClass}"></i>
- <span class="${this.currentIndexClass}" id='${this.currentIndexId}'>
- ${this.currentIndex+1}
- </span>
- :
- <span class="${this.totalClass}">${this.list.length}</span>
- <i class="fa fa-angle-right ${this.goToNextClass}"></i>
- <i class="fa fa-angle-double-right ${this.goToLastClass}"></i>
- </span>
- </span>
- `).get(0);
- }
- /**
- * Setup button event listeners
- */
- _setupListeners() {
- $(this.element).find('.'+this.goToFirstClass).click(function(e) {
- this.goToFirstPost();
- }.bind(this));
- $(this.element).find('.'+this.goToPreviousClass).click(function(e) {
- this.goToPreviousPost();
- }.bind(this));
- $(this.element).find('.'+this.goToNextClass).click(function(e) {
- this.goToNextPost();
- }.bind(this));
- $(this.element).find('.'+this.goToLastClass).click(function(e) {
- this.goToLastPost();
- }.bind(this));
- $(document).on(this.indexChangeEvent, function(e, index) {
- if (this.currentIndex == index) return;
- this._setCurrentIndex(index);
- }.bind(this));
- $(document).on(this.updateEventName, function() {
- this.list = this.updateFunction();
- $(this.element).find(`.${this.totalClass}`).text(this.list.length);
- }.bind(this));
- $(document).scroll(this.updateIndexFromCurrentScrollPosition.bind(this));
- }
- /**
- * Determine the current index based on scroll position
- */
- updateIndexFromCurrentScrollPosition() {
- const boardListHeight = $(EightKun.getTopBoardlist()).height();
- for (let i = 0; i < this.list.length; ++i) {
- const post = this.list[i];
- const boundingRect = post.getBoundingClientRect();
- const postTopAboveBottomOfScreen = boundingRect.top < window.innerHeight;
- const postBottomBelowTopOfScreen = boundingRect.bottom > boardListHeight;
- const currentPostIsInViewport = postTopAboveBottomOfScreen &&
- postBottomBelowTopOfScreen;
- if (currentPostIsInViewport) {
- this._setCurrentIndex(i);
- break;
- }
- const isFirstPost = i === 0;
- const isBeforeFirstNotable = isFirstPost && !postTopAboveBottomOfScreen;
- if (isBeforeFirstNotable) {
- this._setCurrentIndex(-1);
- break;
- }
- const isLastPost = i === (this.list.length - 1);
- const isPastLastNotable = isLastPost && !postBottomBelowTopOfScreen;
- if (isPastLastNotable) {
- this._setCurrentIndex(i + .5);
- break;
- }
- const nextPost = this.list[i+1];
- const nextPostBounds = nextPost.getBoundingClientRect();
- const nextPostIsBelowBottomOfScreen =
- nextPostBounds.top >= window.innerHeight;
- const inBetweenPosts = !postBottomBelowTopOfScreen &&
- nextPostIsBelowBottomOfScreen;
- if (inBetweenPosts) {
- this._setCurrentIndex(i + .5);
- break;
- }
- }
- }
- /**
- * Scroll to first post
- */
- goToFirstPost() {
- if (!this.list.length) {
- return;
- }
- this._setCurrentIndex(0);
- this.scrollToCurrentPost();
- }
- /**
- * Scroll to next navigated post
- */
- goToPreviousPost() {
- if (!this.list.length) {
- return;
- }
- if (this.currentIndex <= 0) {
- this._setCurrentIndex(this.list.length - 1);
- } else {
- this._setCurrentIndex(Math.ceil(this.currentIndex) - 1);
- }
- this.scrollToCurrentPost();
- }
- /**
- * Scroll to next navigated post
- */
- goToNextPost() {
- if (!this.list.length) {
- return;
- }
- const lastPostIndex = this.list.length - 1;
- if (this.currentIndex >= lastPostIndex) {
- this._setCurrentIndex(0);
- } else {
- this._setCurrentIndex(Math.floor(this.currentIndex) + 1);
- }
- this.scrollToCurrentPost();
- }
- /**
- * Scroll the last post in this bread into view
- */
- goToLastPost() {
- if (!this.list.length) {
- return;
- }
- const numPosts = this.list.length;
- this._setCurrentIndex(numPosts - 1);
- this.scrollToCurrentPost();
- }
- /**
- * Scrolls the current selected post into view
- */
- scrollToCurrentPost() {
- const post = this.list[this.currentIndex];
- $(post).get(0).scrollIntoView();
- // Trigger events for other views of this data
- $(document).trigger(this.indexChangeEvent,
- this.currentIndex);
- const boardListHeight = $(EightKun.getTopBoardlist()).height();
- window.scrollBy(0, -boardListHeight);
- }
- /**
- * Set internal index var and UI
- * @param {number} index
- */
- _setCurrentIndex(index) {
- this.currentIndex = index;
- this._setCurrentIndexControlValue(index + 1);
- }
- /**
- * Sets the value of the current index in the UI
- * @param {number} val
- */
- _setCurrentIndexControlValue(val) {
- $('#'+this.currentIndexId).text(val);
- }
- }
- NavigationControl.containerClass = `bakertools-navcontrol-container`;
- NavigationControl.navigationControlClass = 'bakertools-navigation-control';
- /* global EightKun, $, NotableHighlighter */
- /**
- * Wrapper for a post nominated as notable
- */
- class NotablePost {
- /**
- * Construct an empty notable post object
- */
- constructor() {
- this.element = null;
- this.postNumber = null;
- this.description = '[DESCRIPTION]';
- this.nominatingPosts = [];
- }
- /**
- * Create a notable post from a nominating post
- *
- * @param {Element} nominatingPost A post that is nominating a notable
- * @return {NotablePost} a Notable post or NullNotablePost if it fails
- */
- static fromNominatingPost(nominatingPost) {
- const notables = [];
- EightKun.getReplyLinksFromPost(nominatingPost)
- .each(function(idx, link) {
- const postNumber = EightKun.getPostNumberFromReplyLink(link);
- const notablePostElement = $(`#reply_${postNumber}`).get(0);
- if (window.bakerTools.qPostHighlighter.isQ(notablePostElement)) {
- return false;
- }
- if (!NotablePost.findNotableByPostNumber(postNumber)) {
- const notable = new NotablePost();
- if (notablePostElement) {
- notable.setElement(notablePostElement);
- } else {
- // TODO: set pb description
- // get the json from the post number
- notable.postNumber = postNumber;
- }
- notable.addNominatingPost(nominatingPost);
- NotablePost.addToListOfNotables(notable);
- notables.push(notable);
- if (notable.element) { // Not pb will need to figure something out
- $(document).trigger(NotablePost.NEW_NOTABLE_POST_EVENT,
- notable.element);
- }
- }
- });
- return notables;
- }
- /**
- * Add notable to list, and sort list
- * @param {NotablePost} notable
- */
- static addToListOfNotables(notable) {
- NotablePost._notables.push(notable);
- NotablePost._notables.sort(function(n1, n2) {
- if (n1.postNumber < n2.postNumber) {
- return -1;
- } else if ( n1.postNumber > n2.postNumber) {
- return 1;
- }
- return 0;
- });
- }
- /**
- * Is this a NullNotablePost
- * @return {boolean} false
- */
- isNull() {
- return false;
- }
- /**
- * @return {Array<NotablePost>} Array of the current notables
- */
- static getNotables() {
- return NotablePost._notables;
- }
- /**
- * Get notable posts as regular 8kun div.post
- * @return {Array} of div.post
- */
- static getNotablesAsPosts() {
- return NotablePost._notables
- .filter((n) => n.element !== null)
- .map((n) => n.element);
- }
- /**
- * @arg {number} postNumber The post number of notable
- * @return {NotablePost}
- */
- static findNotableByPostNumber(postNumber) {
- return NotablePost._notables.find((notable) => notable.postNumber ==
- postNumber);
- }
- /**
- * Set the element of the post
- * @arg {Element} element
- */
- setElement(element) {
- this.element = element;
- this._markAsNotable(this.element);
- this.description = element.querySelector('.body')
- .innerText
- .replace(/\n/g, ' ');
- this.postNumber = $(this.element).find('.intro .post_no')
- .text()
- .replace('No.', '');
- }
- /**
- * Get the reply shortlink for the post
- * @return {string}
- */
- shortLink() {
- return '>>' + this.postNumber;
- }
- /**
- * Add a nominator to the notable
- *
- * @param {Element} nominatingPost A .div.post that nominates this post
- */
- addNominatingPost(nominatingPost) {
- this.nominatingPosts.push(nominatingPost);
- this._markAsNominator(nominatingPost);
- this._markNominatorInMentions(nominatingPost);
- }
- /**
- * @arg {Element} nominatorPost .post
- */
- _markAsNominator(nominatorPost) {
- nominatorPost.classList.add(NotableHighlighter.NOMINATOR_CLASS);
- }
- /**
- * @arg {Element} post .post
- */
- _markAsNotable(post) {
- post.classList.add(NotableHighlighter.NOTABLE_CLASS);
- }
- /**
- * Gives links to nominators a special style in notable mentions
- *
- * @param {Element} nominatingPost A .div.post that is nominating this
- * notable
- */
- _markNominatorInMentions(nominatingPost) {
- if (!this.element) {
- console.info(`Notable post is null - possible pb/lb`);
- return;
- }
- const nominatingPostId = nominatingPost.id.replace('reply_', '');
- $(this.element).find('.mentioned-'+nominatingPostId)
- .addClass(NotableHighlighter.NOMINATOR_CLASS);
- }
- }
- NotablePost._notables = [];
- NotablePost.NULL = null; // NullNotablePost
- NotablePost.NEW_NOTABLE_POST_EVENT = 'bakertools-new-notable-post-event';
- /* globals EightKun */
- /**
- * Research Bread Class
- */
- class ResearchBread {
- /**
- * Get an array of post bodies with dough posts filtered out
- * @return {NodeList} of .post elements
- */
- static getPostsWithoutDough() {
- const posts = Array.from(document
- .querySelectorAll(EightKun.POST_SELECTOR));
- const filteredPosts = posts.filter(function(post) {
- return !post.querySelector(EightKun.POST_BODY_SELECTOR)
- .innerText.match(ResearchBread.DOUGH_POSTS_REGEX);
- });
- return filteredPosts;
- }
- /**
- * Determine what the bread number is
- * @return {number} the number of the research bread
- */
- static getBreadNumber() {
- const breadNumberRegex = /#(.+?) /;
- return document.querySelector(EightKun.OP_SUBJECT_SELECTOR)
- .innerText
- .match(breadNumberRegex)[1] || 'COULD NOT FIND BREAD NUMBER';
- }
- /**
- * Find the post with the dough
- * @return {Element} div.post
- */
- static getDoughPost() {
- const posts = Array.from(document
- .querySelectorAll(EightKun.POST_SELECTOR));
- const dough = posts.find(function(post) {
- return post.querySelector(EightKun.POST_BODY_SELECTOR)
- .innerText.toUpperCase().match(ResearchBread.DOUGH_POST_TITLE);
- });
- return dough;
- }
- }
- ResearchBread.BOARD_NAME = 'qresearch';
- ResearchBread.WELCOME_POST_TITLE = 'Welcome To Q Research General';
- ResearchBread.ANNOUNCEMENTS_POST_TITLE = 'Global Announcements';
- ResearchBread.WAR_ROOM_POST_TITLE = 'War Room';
- ResearchBread.ARCHIVES_POST_TITLE = 'QPosts Archives';
- ResearchBread.DOUGH_POST_TITLE = 'DOUGH';
- ResearchBread.DOUGH_POSTS_REGEX = new RegExp(
- `^(${ResearchBread.WELCOME_POST_TITLE}|` +
- `${ResearchBread.ANNOUNCEMENTS_POST_TITLE}|` +
- `${ResearchBread.WAR_ROOM_POST_TITLE}|` +
- `${ResearchBread.ARCHIVES_POST_TITLE}|` +
- `${ResearchBread.DOUGH_POST_TITLE}).*`);
- /* globals $, EightKun, debounce, POST_BACKGROUND_CHANGE_EVENT,
- BakerWindow */
- /* exported ScrollbarNavigation */
- /**
- * Scrollbar navigation
- */
- class ScrollbarNavigation {
- /**
- * Construct a scrollbar nav
- * @param {Array} addPostEvents List of event names that produce posts
- * to show on scrollbar
- */
- constructor(addPostEvents = []) {
- this.id = 'bakertools-scrollbar-navigation';
- this.showScrollbarNavigationId = 'bakertools-show-scrollbar-nav';
- this.width = '20px';
- this.posts = [];
- this.coordsToPost = new Map();
- this.addPostEvents = addPostEvents;
- this.draw = debounce(this.draw, 80);
- this._setupBakerWindowControls();
- this._setupStyles();
- this._createElement();
- this._readSettings();
- this._setupListeners();
- }
- /**
- * Read settings from localStorage
- */
- _readSettings() {
- let showScrollBar = JSON.parse(localStorage
- .getItem(ScrollbarNavigation.SHOW_SCROLLBAR_NAV));
- showScrollBar = showScrollBar === null ? true : showScrollBar;
- this.showScrollBar(showScrollBar);
- }
- /**
- * Add hide/show option to bakerwindow
- */
- _setupBakerWindowControls() {
- window.bakerTools.mainWindow
- .addNavigationOption(`
- <div class='${BakerWindow.CONTROL_GROUP_CLASS}'>
- <label for="${this.showScrollbarNavigationId}"
- title="Show scrollbar navigation" >
- Show Scrollbar Navigation:
- </label>
- <input type="checkbox" id="${this.showScrollbarNavigationId}"
- title="Show scrollbar navigation" /><br />
- </div>
- `);
- }
- /**
- * Setup event listeners
- */
- _setupListeners() {
- $('#'+this.showScrollbarNavigationId).change(function(e) {
- this.showScrollBar(e.target.checked);
- }.bind(this));
- $(document).on(EightKun.NEW_POST_EVENT, this.draw.bind(this));
- $(window).on('resize', this.draw.bind(this));
- $(window).on(POST_BACKGROUND_CHANGE_EVENT, this.draw.bind(this));
- $('#'+this.id).click(function(e) {
- const post = this.findFirstPostUnderMouse(e.clientX, e.clientY);
- if (post) {
- $(post).get(0).scrollIntoView();
- window.scrollBy(0, -20);
- }
- }.bind(this));
- $(this.element).mousemove(function(e) {
- const [post, coords] = this.findFirstPostUnderMouse(e.clientX, e.clientY);
- const notOverAPost = !post;
- const hoveringOverADifferentPost = this.hoveringPost &&
- this.hoveringPost != post;
- if (notOverAPost || hoveringOverADifferentPost) {
- this.endHover();
- }
- if (this.hovering) {
- return;
- }
- const top = coords.top;
- if (post) {
- this.postHover(post, top);
- }
- }.bind(this));
- $(this.element).mouseout(this.endHover.bind(this));
- this.addPostEvents.forEach(function(eventName) {
- $(document).on(eventName, function(event, posts) {
- this.addPosts(posts);
- }.bind(this));
- }.bind(this));
- }
- /**
- * Find the first post that is under the mouse
- * @param {number} clientX x location of mouse
- * @param {number} clientY y location of mouse
- * @return {Array} div.post, [top,bottom] or null if not found
- */
- findFirstPostUnderMouse(clientX, clientY) {
- let post = null;
- let coords = null;
- for (const keyValue of this.coordsToPost) {
- coords = keyValue[0];
- // if (clientY >= (top - 10) && clientY <= (bottom+10)) {
- if (clientY >= (coords.top) && clientY <= (coords.bottom)) {
- post = keyValue[1];
- break;
- }
- }
- return [post, coords];
- }
- /**
- * Perform post hover functionality for provided post
- * @param {Element} post div.post
- * @param {number} hoverY y location to hover at
- */
- postHover(post, hoverY) {
- this.hovering = true;
- this.hoveringPost = post;
- const $post = $(post);
- if ($post.is(':visible') &&
- $post.offset().top >= $(window).scrollTop() &&
- $post.offset().top + $post.height() <=
- $(window).scrollTop() + $(window).height()) {
- // post is in view
- $post.addClass('highlighted');
- } else {
- const newPost = $post.clone();
- newPost.find('>.reply, >br').remove();
- newPost.find('a.post_anchor').remove();
- const postNumber = EightKun.getPostNumber(post);
- newPost.attr('id', 'post-hover-' + postNumber)
- .attr('data-board', EightKun.getCurrentBoard())
- .addClass('post-hover')
- .css('border-style', 'solid')
- .css('box-shadow', '1px 1px 1px #999')
- .css('display', 'block')
- .css('position', 'absolute')
- .css('font-style', 'normal')
- .css('z-index', '100')
- .css('left', '0')
- .css('margin-left', '')
- .addClass('reply')
- .addClass('post')
- .appendTo('.thread');
- // shrink expanded images
- newPost.find('div.file img.post-image').css({
- 'display': '',
- 'opacity': '',
- });
- newPost.find('div.file img.full-image').remove();
- let previewWidth = newPost.outerWidth(true);
- const widthDiff = previewWidth - newPost.width();
- const scrollNavLeft = $(this.element).offset().left;
- let left;
- if (scrollNavLeft < $(document).width() * 0.7) {
- left = scrollNavLeft + $(this.element).width();
- if (left + previewWidth > $(window).width()) {
- newPost.css('width', $(window).width() - left - widthDiff);
- }
- } else {
- if (previewWidth > scrollNavLeft) {
- newPost.css('width', scrollNavLeft - widthDiff);
- previewWidth = scrollNavLeft;
- }
- left = scrollNavLeft - previewWidth;
- }
- newPost.css('left', left);
- const scrollTop = $(window).scrollTop();
- let top = scrollTop + hoverY;
- if (top < scrollTop + 15) {
- top = scrollTop;
- } else if (top > scrollTop + $(window).height() - newPost.height() - 15) {
- top = scrollTop + $(window).height() - newPost.height() - 15;
- }
- if (newPost.height() > $(window).height()) {
- top = scrollTop;
- }
- newPost.css('top', top);
- }
- }
- /**
- * End hovering
- */
- endHover() {
- this.hovering = false;
- if (!this.hoveringPost) {
- return;
- }
- $(this.hoveringPost).removeClass('highlighted');
- if ($(this.hoveringPost).hasClass('hidden')) {
- $(this.hoveringPost).css('display', 'none');
- }
- $('.post-hover').remove();
- }
- /**
- * Show/hide scrollbar
- * @param {boolean} shouldShow Shows if true
- */
- showScrollBar(shouldShow) {
- $('#'+this.showScrollbarNavigationId).prop('checked',
- shouldShow);
- localStorage.setItem(ScrollbarNavigation.SHOW_SCROLLBAR_NAV, shouldShow);
- if (shouldShow) {
- $(`#${this.id}`).show();
- } else {
- $(`#${this.id}`).hide();
- }
- }
- /**
- * Setup styles for canvas
- */
- _setupStyles() {
- $('head').append(`
- <style id='${this.id + '-style'}'>
- #${this.id} {
- position: fixed;
- top: 0;
- right: 0;
- height: 100%;
- width: ${this.width};
- background: #000000;
- background: linear-gradient(
- 90deg,
- rgba(0,0,0,1) 0%,
- rgba(92,92,92,1) 50%,
- rgba(0,0,0,1) 100%
- );
- }
- </style>
- `);
- }
- /**
- * Create the canvas
- */
- _createElement() {
- $(document.body).append(`
- <canvas id='${this.id}' width='${this.width}' height='300'>
- </canvas>
- `);
- this.element = $(`#${this.id}`).get(0);
- }
- /**
- * Draw the scrollbar
- */
- draw() {
- const canvas = this.element;
- canvas.height = window.innerHeight;
- const ctx = canvas.getContext('2d');
- if (!ctx) {
- console.info('no ctx - is the element created yet?');
- return;
- }
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- const cachedHeight = $(document).height();
- const scrollHeight = canvas.height;
- this.coordsToPost = new Map();
- let lastCoords = null;
- this.posts.forEach(function(post, index) {
- const color = $(post).css('backgroundColor');
- const postRect = post.getBoundingClientRect();
- const scrollLocationPercentage =
- (window.scrollY + postRect.top) / cachedHeight;
- let drawLocation = scrollLocationPercentage * scrollHeight;
- const overlappingPrevious = lastCoords &&
- drawLocation <= (lastCoords.bottom + 2);
- if (overlappingPrevious) {
- drawLocation = lastCoords.bottom + 4;
- }
- const drawHeight = Math.max(
- (postRect.height / cachedHeight) * scrollHeight,
- 5,
- );
- const coords = new ScrollbarCoordinates(drawLocation,
- drawLocation + drawHeight);
- this.coordsToPost.set(coords, post);
- ctx.fillStyle = color;
- ctx.fillRect(0, drawLocation, canvas.width, drawHeight);
- lastCoords = coords;
- }.bind(this));
- }
- /**
- * Add posts to scrollbar
- * @param {Element|Array} post div.post
- */
- addPosts(post) {
- if (Array.isArray(post)) {
- post.forEach((p) => this._addPost(p));
- } else {
- this._addPost(post);
- }
- this._sortPosts();
- this.draw();
- }
- /**
- * Add post to post array if not already included
- * @param {Element} post div.post
- */
- _addPost(post) {
- if (this.posts.includes(post)) {
- return;
- }
- this.posts.push(post);
- }
- /**
- * Sort posts by time
- */
- _sortPosts() {
- this.posts.sort(function(p1, p2) {
- const p1PostTime = EightKun.getPostTime(p1);
- const p2PostTime = EightKun.getPostTime(p2);
- if (p1PostTime < p2PostTime) {
- return -1;
- }
- if (p1PostTime > p2PostTime) {
- return 1;
- }
- return 0;
- });
- }
- }
- ScrollbarNavigation.SHOW_SCROLLBAR_NAV = 'bakertools-show-scrollbar-nav';
- /**
- * Coordinates on the scrollbar
- */
- class ScrollbarCoordinates {
- /**
- * Construct coords
- * @param {number} top top of rect
- * @param {number} bottom top of rect
- */
- constructor(top, bottom) {
- this.top = top;
- this.bottom = bottom;
- }
- }
- /* exported debounce, POST_BACKGROUND_CHANGE_EVENT */
- /**
- * Returns a function, that, as long as it continues to be invoked, will not
- * be triggered. The function will be called after it stops being called for
- * N milliseconds. If `immediate` is passed, trigger the function on the
- * leading edge, instead of the trailing.
- * https://davidwalsh.name/javascript-debounce-function
- *
- * @param {Function} func
- * @param {number} wait
- * @param {boolean} immediate
- * @return {Function} debounced function
- */
- function debounce(func, wait, immediate) {
- let timeout;
- return function(...args) {
- const context = this;
- const later = function() {
- timeout = null;
- if (!immediate) func.apply(context, args);
- };
- const callNow = immediate && !timeout;
- clearTimeout(timeout);
- timeout = setTimeout(later, wait);
- if (callNow) func.apply(context, args);
- };
- }
- const POST_BACKGROUND_CHANGE_EVENT = 'bakertools-post-background-change';
- /* globals $, EightKun */
- /* exported WindowElement */
- /**
- * Class for windows
- */
- class WindowElement {
- /**
- * Construct WindowElement
- * @param {string} windowName
- * @param {string} linkText
- */
- constructor(windowName, linkText) {
- this.styleId = 'bakertools-WindowElement-basestyles';
- this.id = `bakertools-${windowName}-window`;
- this.linkText = linkText;
- this.class = 'bakertools-WindowElement';
- this.headerClass = 'bakertools-WindowElement-header';
- this.windowCloseId = `bakertools-${windowName}-WindowElement-close`;
- this.windowCloseClass = `bakertools-WindowElement-close`;
- this.element = null;
- this._createWindowStyles();
- this._createElement();
- this._setupWindowLink();
- }
- /**
- * Create the window element
- */
- _createElement() {
- this.element = document.createElement('div');
- this.element.id = this.id;
- $(this.element).addClass(this.class);
- this.element.innerHTML = `
- <header class="${this.headerClass}">
- <h3>${this.linkText}</h3>
- <a id="${this.windowCloseId}" class='${this.windowCloseClass}'
- href="javascript:void(0)">
- <i class="fa fa-times"></i>
- </a>
- </header>
- `;
- document.body.appendChild(this.element);
- $(this.element).resizable({
- 'handles': 'e, w',
- }).draggable();
- $(this.element).hide();
- $('#'+this.windowCloseId).click(function(e) {
- this.hide();
- }.bind(this));
- }
- /*
- * Create CSS styles needed by the window
- */
- _createWindowStyles() {
- if ($('#' + this.styleId).length) {
- return;
- }
- $('head').append(`
- <style id='${this.styleId}'>
- /*
- * ui-resizable styles: https://stackoverflow.com/a/11339280
- */
- .ui-resizable { position: relative;}
- .ui-resizable-handle {
- position: absolute;
- font-size: 0.1px;
- display: block;
- }
- .ui-resizable-disabled .ui-resizable-handle,
- .ui-resizable-autohide .ui-resizable-handle {
- display: none;
- }
- .ui-resizable-n {
- cursor: n-resize;
- height: 7px;
- width: 100%;
- top: -5px;
- left: 0;
- }
- .ui-resizable-s {
- cursor: s-resize;
- height: 7px;
- width: 100%;
- bottom: -5px;
- left: 0;
- }
- .ui-resizable-e {
- cursor: e-resize;
- width: 7px;
- right: -5px;
- top: 0;
- height: 100%;
- }
- .ui-resizable-w {
- cursor: w-resize;
- width: 7px;
- left: -5px;
- top: 0;
- height: 100%;
- }
- .ui-resizable-se {
- cursor: se-resize;
- width: 12px;
- height: 12px;
- right: 1px;
- bottom: 1px;
- }
- .ui-resizable-sw {
- cursor: sw-resize;
- width: 9px;
- height: 9px;
- left: -5px;
- bottom: -5px;
- }
- .ui-resizable-nw {
- cursor: nw-resize;
- width: 9px;
- height: 9px;
- left: -5px;
- top: -5px;
- }
- .ui-resizable-ne {
- cursor: ne-resize;
- width: 9px;
- height: 9px;
- right: -5px;
- top: -5px;
- }
- .${this.class} {
- width: 300px;
- background-color: rgb(214, 218, 240);
- position: fixed;
- z-index: 100;
- float: right;
- right:28.25px;
- border: 1px solid;
- }
- .${this.class} .${this.headerClass} {
- background: #98E;
- border: solid 1px;
- text-align: center;
- margin: 0px;
- }
- .${this.class} .${this.headerClass} h3 {
- margin: 0;
- }
- .${this.class} .${this.windowCloseClass} {
- top: 0px;
- right: 0px;
- position: absolute;
- margin-right: 3px;
- font-size: 20px;
- }
- .${this.class} details {
- padding: 5px;
- }
- .${this.class} summary {
- margin: 0 0 8px;
- font-weight: bold;
- border-bottom: solid 2px;
- }
- </style>
- `);
- }
- /**
- * Create link for show/hiding window, placed in boardlist bar
- */
- _setupWindowLink() {
- this.link = document.createElement('a');
- this.link.textContent = `[${this.linkText}]`;
- this.link.style.cssText = 'float: right;';
- this.link.title = this.linkText;
- this.link.href = 'javascript:void(0)';
- $(EightKun.getTopBoardlist()).append(this.link);
- this.link.onclick = this.toggle.bind(this);
- }
- /**
- * Setup timeout for updating bread list
- */
- _setupListeners() {
- // window.setTimeout(this.updateBreadList, 1000)
- }
- /**
- * Show the window
- */
- show() {
- $(this.element).css({'top': 15});
- $(this.element).show();
- }
- /**
- * Hide the window
- */
- hide() {
- $(this.element).hide();
- }
- /**
- * Is the window visible?
- * @return {boolean} true if window is visible
- */
- isVisible() {
- return $(this.element).is(':visible');
- }
- /**
- * Toggle visibility of window
- */
- toggle() {
- if (this.isVisible()) {
- this.hide();
- } else {
- this.show();
- }
- }
- }
- /* exported BakerWindow */
- /* global NavigationControl, $, WindowElement */
- /**
- * Baker Window
- */
- class BakerWindow extends WindowElement {
- /**
- * Construct Baker window element, register listeners
- */
- constructor() {
- super('baker', 'Baker Tools');
- this.bakerWindowStyleId = 'bakertools-bakerwindow-style';
- this.bakerWindowOptionsId = 'bakertools-window-options';
- this.bakerWindowColorOptionsId = 'bakertools-window-color-options';
- this.bakerWindowNavigationOptionsId =
- 'bakertools-window-navigation-options';
- this.bakerWindowNotableOptionsId =
- 'bakertools-window-notable-options';
- this.bakerWindowSpamOptionsId =
- 'bakertools-window-spam-options';
- this.bakerWindowNavigationId = 'bakertools-window-navigation';
- this.bakerWindowBakerId = 'bakertools-window-baker';
- this.bakerWindowBodyId = 'bakertools-bakerwindow-body';
- this._createStyles();
- this._createBody();
- }
- /**
- * Create CSS styles needed by the window
- */
- _createStyles() {
- if ($('#' + this.bakerWindowStyleId).length) {
- return;
- }
- $('head').append(`
- <style id='${this.bakerWindowStyleId}'>
- #${this.id} #${this.bakerWindowNavigationId}
- .${NavigationControl.containerClass} {
- display: inline-block;
- width: 100%;
- }
- #${this.id} #${this.bakerWindowNavigationId}
- .${NavigationControl.navigationControlClass} {
- float: right;
- }
- ${BakerWindow.CONTROL_GROUP_SELECTOR} {
- display: flex;
- }
- ${BakerWindow.CONTROL_GROUP_SELECTOR} label {
- flex-grow: 1;
- }
- </style>
- `);
- }
- /**
- * Create the actual window HTML element
- */
- _createBody() {
- $('#'+this.id).append(`
- <form id="${this.bakerWindowBodyId}">
- <details id='${this.bakerWindowOptionsId}' open>
- <summary>Options</summary>
- <details id='${this.bakerWindowColorOptionsId}' open>
- <summary>Colors</summary>
- </details>
- <details id='${this.bakerWindowNavigationOptionsId}' open>
- <summary>Navigation</summary>
- </details>
- <details id='${this.bakerWindowNotableOptionsId}' open>
- <summary>Notables</summary>
- </details>
- <details id='${this.bakerWindowSpamOptionsId}' open>
- <summary>Spam</summary>
- </details>
- </details>
- <details id='${this.bakerWindowNavigationId}' open>
- <summary>Navigation</summary>
- </details>
- <details id='${this.bakerWindowBakerId}' open>
- <summary>Baker Tools</summary>
- </details>
- </form>
- `);
- }
- /**
- * Add form controls to options section of baker window
- * @arg {Element} htmlContentString form controls
- */
- addOption(htmlContentString) {
- $('#'+this.bakerWindowOptionsId).append(htmlContentString);
- }
- /**
- * Add form controls to notable options section of baker window
- * @arg {Element} htmlContentString form controls
- */
- addNotableOption(htmlContentString) {
- $('#'+this.bakerWindowNotableOptionsId)
- .append(htmlContentString);
- }
- /**
- * Add form controls to spam options section of baker window
- * @arg {Element} htmlContentString form controls
- */
- addSpamOption(htmlContentString) {
- $('#'+this.bakerWindowSpamOptionsId)
- .append(htmlContentString);
- }
- /**
- * Add form controls to navigation options section of baker window
- * @arg {Element} htmlContentString form controls
- */
- addNavigationOption(htmlContentString) {
- $('#'+this.bakerWindowNavigationOptionsId)
- .append(htmlContentString);
- }
- /**
- * Add form controls to color options section of baker window
- * @arg {Element} htmlContentString form controls
- */
- addColorOption(htmlContentString) {
- $('#'+this.bakerWindowColorOptionsId).append(htmlContentString);
- }
- /**
- * Add html elements to the navigation section of the baker window
- * @arg {Element} htmlContentString form controls
- */
- addNavigation(htmlContentString) {
- $('#'+this.bakerWindowNavigationId).append(htmlContentString);
- }
- /**
- * Add html elements to the baker section of the baker window
- * @arg {Element} htmlContentString form controls
- */
- addBaker(htmlContentString) {
- $('#'+this.bakerWindowBakerId).append(htmlContentString);
- }
- } // end class BakerWindow
- BakerWindow.CONTROL_GROUP_CLASS = 'bakertools-bakerwindow-control-group';
- BakerWindow.CONTROL_GROUP_SELECTOR =
- `.${BakerWindow.CONTROL_GROUP_CLASS}`;
- /* global $, BakerWindow */
- /**
- * Blur images until highlighted
- */
- class BlurImages {
- /**
- * Construct blur images object and setup styles
- */
- constructor() {
- this.blurImages = 'bakertools-blur-images';
- this.blurImagesStyleId = 'bakertools-blur-images-style';
- window.bakerTools.mainWindow.addOption(`
- <div class='${BakerWindow.CONTROL_GROUP_CLASS}'>
- <label for="${this.blurImages}">Blur Images Until Hover</label>
- <input type="checkbox" id="${this.blurImages}"
- title="Blur images until mouse hover" /></br>
- </div>
- `);
- $('#'+this.blurImages).change(function(e) {
- this.setBlurImages(e.target.checked);
- }.bind(this));
- this._readSettings();
- }
- /**
- * Read settings from localStorage
- */
- _readSettings() {
- this.setBlurImages(JSON.parse(
- localStorage.getItem(
- BlurImages.BLUR_IMAGES_SETTING),
- ));
- }
- /**
- * Set whether or not images are blurred
- * @param {boolean} blurImages if true, blur images
- */
- setBlurImages(blurImages) {
- $('#'+this.blurImages).prop('checked',
- blurImages);
- localStorage.setItem(BlurImages.BLUR_IMAGES_SETTING,
- blurImages);
- if (blurImages) {
- $(`<style id='${this.blurImagesStyleId}' type='text/css'>
- .post-image {
- filter: blur(5px);
- transition: all 233ms;
- }
- .post-image:hover {
- filter: blur(.5px);
- transition: all 89ms;
- }
- </style>`).appendTo('head');
- } else {
- $(`#${this.blurImagesStyleId}`).remove();
- }
- }
- }
- BlurImages.BLUR_IMAGES_SETTING = 'bakertools-blur-images';
- /* globals $, WindowElement, ResearchBread */
- /* exported BreadList */
- /**
- * Creates a list of breads for navigation comfyness
- */
- class BreadList extends WindowElement {
- /**
- * Construct breadlist object
- */
- constructor() {
- super('breadlist', 'Bread List');
- $('#'+this.id).css('height', '400px');
- this.breadListWindowHeaderId = 'bakertools-breadlist-window-header';
- this.breadListWindowCloseId = 'bakertools-breadlist-window-close';
- this.breadListWindowBody = 'bakertools-breadlist-window-body';
- this.breadListTable = 'bakertools-breadlist-table';
- this.lastUpdatedId = 'bakertools-breadlist-lastupdated';
- this._breads = [];
- ResearchBread.BOARD_NAME = 'qresearch';
- this.breadRegex = /(.+)\s+#(\d+):\s+(.+?$)/;
- // /\(.+\) #\(\d+\): \(.+?$\)/;
- this.indexPage = `${window.location.protocol}//${window.location.host}` +
- `/${ResearchBread.BOARD_NAME}/`;
- this._createBody();
- this._setupStyles();
- this.updateBreadList();
- this._setupListeners();
- }
- /**
- * setup table styles
- */
- _setupStyles() {
- // https://stackoverflow.com/questions/21168521/table-fixed-header-and-scrollable-body
- $('head').append(`
- <style id='baketools-breadlist-window-styles'>
- #${this.id} {
- right: 380px;
- width: 380px;
- }
- #${this.breadListWindowBody} {
- overflow-y: auto;
- height: 365px;
- font-size: .8em;
- }
- #${this.breadListTable} {
- border-collapse: collapse;
- border-spacing: 0px;
- margin: 0;
- width: 100%;
- }
- #${this.breadListTable} thead th {
- position: sticky;
- top: 0;
- }
- #${this.breadListTable} th,
- #${this.breadListTable} td {
- border: 1px solid #000;
- border-top: 0;
- padding: 1px 2px 1px 2px;
- margin: 0;
- }
- #${this.breadListTable} thead th {
- box-shadow: 1px 1px 0 #000;
- }
- </style>
- `);
- }
- /**
- * Create the actual window HTML element
- */
- _createBody() {
- $('#'+this.id).append(`
- <div id='${this.breadListWindowBody}'>
- <table id='${this.breadListTable}'>
- <thead>
- <tr>
- <th>Group</th>
- <th>No.</th>
- <th>Bread</th>
- <th>replies</th>
- </tr>
- </thead>
- <tbody>
- </tbody>
- </table>
- </div>
- <footer>
- Last Updated: <span id="${this.lastUpdatedId}"></span>
- </footer>
- `);
- }
- /**
- * Setup timeout for updating bread list
- */
- _setupListeners() {
- window.setInterval(function(e) {
- this.updateBreadList();
- }.bind(this), 1000 * 60 * 2.5); // 2.5min update
- }
- /**
- * Get the list of breads
- */
- updateBreadList() {
- this.breads = [];
- const promises = [];
- for (let page = 0; page < 3; page++) {
- promises.push(
- $.getJSON(this.indexPage + `${page}.json`,
- this.parseIndex.bind(this)),
- );
- }
- Promise.all(promises).then(function() {
- this.breads.sort(function(a, b) {
- if (a.lastModified < b.lastModified) return -1;
- if (a.lastModified == b.lastModified) return 0;
- if (a.lastModified > b.lastModified) return 1;
- }).reverse();
- this.populateBreadTable();
- }.bind(this));
- }
- /**
- * Parse index json for breads
- * @param {Object} index
- */
- parseIndex(index) {
- if (index && index.threads) {
- index.threads.forEach(function(thread) {
- const op = thread.posts[0];
- const match = op.sub.match(this.breadRegex);
- if (match) {
- const researchGroup = match[1];
- const breadNumber = match[2];
- const breadName = match[3];
- this.breads.push(new Bread(
- ResearchBread.BOARD_NAME,
- researchGroup,
- breadNumber,
- breadName,
- op.replies,
- op.no,
- op.last_modified,
- ));
- }
- }.bind(this)); // Index foreach
- } // if index and index.threads
- }
- /**
- * Populate the bread list table
- */
- populateBreadTable() {
- $(`#${this.breadListTable} tbody`).empty();
- this.breads.forEach(function(bread) {
- this._addBread(bread);
- }.bind(this));
- const lastUpdated = new Date();
- $('#'+this.lastUpdatedId).text(lastUpdated.toLocaleString());
- }
- /**
- * Add bread
- * @param {Bread} bread
- */
- _addBread(bread) {
- $(`#${this.breadListTable} tbody`).append(`
- <tr>
- <td><a href='${bread.url}'>${bread.researchGroup}</a></td>
- <td><a href='${bread.url}'>${bread.researchNumber}</a></td>
- <td><a href='${bread.url}'>${bread.breadName}</a></td>
- <td><a href='${bread.url}'>${bread.replies}</a></td>
- </tr>
- `);
- }
- }
- /**
- * Represents a research bread
- */
- class Bread {
- /**
- * Construct a bread
- *
- * @param {string} boardName
- * @param {string} researchGroup
- * @param {number} researchNumber
- * @param {string} breadName
- * @param {number} replies
- * @param {number} postId
- * @param {number} lastModified
- */
- constructor(boardName, researchGroup, researchNumber, breadName,
- replies, postId, lastModified) {
- this.boardName = boardName;
- this.researchGroup = researchGroup;
- this.researchNumber = researchNumber;
- this.breadName = breadName;
- this.replies = replies;
- this.postId = postId;
- this.lastModified = lastModified;
- }
- /**
- * Get bread url
- *
- * @return {string} url to bread
- */
- get url() {
- return `${window.location.protocol}//${window.location.host}` +
- `/${this.boardName}/res/${this.postId}.html`;
- }
- }
- /* global $, EightKun, BakerWindow */
- /**
- * Persistent image blacklist (AKA NOPE BUTTON)
- */
- class ImageBlacklist {
- /**
- * Construct ImageBlacklist object
- */
- constructor() {
- this.blacklist = [];
- this.styleId = 'bakertools-blacklist-style';
- this.postBlacklistButtonClass = 'bakertools-blacklist-post';
- this.imgBlacklistButtonClass = 'bakertools-blacklist-image';
- this.hidePostBlacklistButtonCheckboxId =
- 'bakertools-hide-post-blacklist-buttons';
- this._setupBakerWindowControls();
- this._readSettings();
- this._setupStyles();
- this._setupListeners();
- this.removeBlacklistedImages();
- this.addBlacklistButtons();
- }
- /**
- * Add options to baker window
- */
- _setupBakerWindowControls() {
- window.bakerTools.mainWindow.addSpamOption(`
- <div class='${BakerWindow.CONTROL_GROUP_CLASS}'>
- <label for="${this.hidePostBlacklistButtonCheckboxId}"
- title="Hide post 'Blacklist' buttons" >
- Hide "Blacklist" buttons
- </label>
- <input type="checkbox" id="${this.hidePostBlacklistButtonCheckboxId}"
- title="Hide post 'Blacklist' buttons" /><br />
- </div>
- `);
- }
- /**
- * Show or hide the post blacklist buttons
- *
- * @param {boolean} hide
- */
- hidePostBlacklistButton(hide) {
- $('#'+this.hidePostBlacklistButtonCheckboxId).prop('checked',
- hide);
- localStorage.setItem(ImageBlacklist.HIDE_POST_BLACKLIST_BUTTON_SETTING,
- hide);
- const styleId = 'baker-tools-post-blacklist-button-style';
- if (hide) {
- $('head').append(`
- <style id='${styleId}'>
- .${this.postBlacklistButtonClass} {
- display: none;
- }
- `);
- } else {
- $(`#${styleId}`).remove();
- }
- }
- /**
- * Setup styles for blacklist buttons
- */
- _setupStyles() {
- $('head').append(`
- <style id='${this.styleId}'>
- .${this.imgBlacklistButtonClass} {
- padding: 0px;
- background-color: Transparent;
- background-repeat: no-repeat;
- border: none;
- overflow: hidden;
- outline: none;
- cursor: pointer;
- }
- </style>
- `);
- }
- /**
- * Read settings from localstorage
- */
- _readSettings() {
- this.loadBlacklist();
- const hideBlacklistButton = JSON.parse(
- localStorage.getItem(ImageBlacklist.HIDE_POST_BLACKLIST_BUTTON_SETTING),
- );
- this.hidePostBlacklistButton(hideBlacklistButton);
- }
- /**
- * Setup new post event listeners
- */
- _setupListeners() {
- $(document).on(EightKun.NEW_POST_EVENT, function(e, post) {
- this.addBlacklistButtonToPost(post);
- }.bind(this));
- $('#'+this.hidePostBlacklistButtonCheckboxId).change(function(e) {
- this.hidePostBlacklistButton(e.target.checked);
- }.bind(this));
- }
- /**
- * Load blacklist from localStorage
- */
- loadBlacklist() {
- this.blacklist = JSON.parse(localStorage.imageBlacklist || '[]');
- }
- /**
- * Save blacklist to localStorage
- */
- saveBlacklist() {
- localStorage.imageBlacklist = JSON.stringify(this.blacklist);
- }
- /**
- * Add MD5 of an image to the blacklist
- * @param {string} md5 md5 hash of image
- */
- addToBlacklist(md5) {
- if (md5 && -1 === this.blacklist.indexOf(md5)) {
- this.blacklist.push(md5);
- }
- }
- /**
- * Blacklist images in post
- * @param {Element} post
- */
- blacklistPostImages(post) {
- $(post).find(ImageBlacklist.POST_IMG_SELECTOR).each(function(i, postImage) {
- const md5 = postImage.getAttribute('data-md5');
- this.addToBlacklist(md5);
- this.deletePostImage(postImage);
- }.bind(this));
- }
- /**
- * Remove blacklist images on page load
- * @return {number} number of images removed
- */
- removeBlacklistedImages() {
- let removed = 0;
- $(ImageBlacklist.POST_IMG_SELECTOR).each(function(i, postImage) {
- if (-1 !== this.blacklist.indexOf(postImage.getAttribute('data-md5'))) {
- this.deletePostImage(postImage);
- removed += 1;
- }
- }.bind(this));
- return removed;
- }
- /**
- * Add blacklist buttons to post images
- */
- addBlacklistButtons() {
- $('div.post').each(function(i, post) {
- this.addBlacklistButtonToPost(post);
- }.bind(this));
- }
- /**
- * Add blacklist buttons to post
- * @param {Element} post div.post
- */
- addBlacklistButtonToPost(post) {
- const postImageCount = $(post)
- .find(ImageBlacklist.POST_IMG_SELECTOR).length;
- if (postImageCount == 0) {
- return;
- }
- const postBlacklistButton = document.createElement('button');
- $(postBlacklistButton).addClass(this.postBlacklistButtonClass);
- $(postBlacklistButton).append(`
- <i class="fa fa-trash" style="color: crimson;"></i>
- Blacklist all post images`);
- $(postBlacklistButton).click(function(e) {
- e.preventDefault();
- $(post).hide();
- this.blacklistPostImages(post);
- this.saveBlacklist();
- }.bind(this));
- $(post).find(EightKun.POST_MODIFIED_SELECTOR).append(postBlacklistButton);
- $(post).find(ImageBlacklist.POST_IMG_SELECTOR).each(function(i, img) {
- const imgBlacklistButton = document.createElement('button');
- $(imgBlacklistButton).addClass(this.imgBlacklistButtonClass);
- $(imgBlacklistButton).append(`
- <i class="fa fa-trash" style="color: crimson;"
- title="Blacklist image"
- ></i>`);
- $(img)
- .parents('div.file')
- .find('.fileinfo')
- .prepend(imgBlacklistButton);
- $(imgBlacklistButton).click(function(e) {
- e.preventDefault();
- const md5 = img.getAttribute('data-md5');
- this.addToBlacklist(md5);
- this.deletePostImage(img);
- this.saveBlacklist();
- }.bind(this));
- }.bind(this));
- }
- /**
- * Delete post image
- * @param {Element} image
- */
- deletePostImage(image) {
- const imageParent = $(image).parent().parent();
- $(imageParent).append(`
- Image blacklisted
- ${image.getAttribute('data-md5')}
- `);
- $(image).remove();
- }
- }
- ImageBlacklist.POST_IMG_SELECTOR = 'img.post-image';
- ImageBlacklist.HIDE_POST_BLACKLIST_BUTTON_SETTING =
- 'bakertools-hide-post-blacklist-button';
- /* global $, EightKun */
- /**
- * Add notable button to posts that opens quick reply
- * and populates with a template message
- */
- class NominatePostButtons {
- /**
- * Construct NPB object and setup listeners
- */
- constructor() {
- this.nominateButtonClass = 'bakertools-nominate-button';
- this.hidePostNotableButtonCheckboxId = 'bakertools-hide-notables';
- this.bakerNotableHeader = '==BAKER NOTABLE==\n';
- this.notableReasonPlaceholder = '[REASON FOR NOTABLE HERE]';
- $('div.post.reply').each(function(i, post) {
- this._addButtonToPost(post);
- }.bind(this));
- this._setupBakerWindowControls();
- this._setupListeners();
- this._readSettings();
- }
- /**
- * Read settings from localStorage
- */
- _readSettings() {
- this.showNotableNominationButton(JSON.parse(
- localStorage.getItem(
- NominatePostButtons.HIDE_NOMINATE_BUTTON_SETTING),
- ));
- }
- /**
- * Add options to baker window
- */
- _setupBakerWindowControls() {
- window.bakerTools.mainWindow.addNotableOption(`
- <div class='${BakerWindow.CONTROL_GROUP_CLASS}'>
- <label for="${this.hidePostNotableButtonCheckboxId}"
- title="Hide post 'Notable' buttons" >
- Hide "Notable" buttons
- </label>
- <input type="checkbox" id="${this.hidePostNotableButtonCheckboxId}"
- title="Hide post 'Notable' buttons" /><br />
- </div>
- `);
- }
- /**
- * Setup event listeners
- */
- _setupListeners() {
- $(document).on(EightKun.NEW_POST_EVENT, function(e, post) {
- this._addButtonToPost(post);
- }.bind(this));
- $('#'+this.hidePostNotableButtonCheckboxId).change(function(e) {
- this.showNotableNominationButton(e.target.checked);
- }.bind(this));
- }
- /**
- * Show or hide the notable nomination buttons
- *
- * @param {boolean} showNotableNominationButton
- */
- showNotableNominationButton(showNotableNominationButton) {
- $('#'+this.hidePostNotableButtonCheckboxId).prop('checked',
- showNotableNominationButton);
- localStorage.setItem(NominatePostButtons.HIDE_NOMINATE_BUTTON_SETTING,
- showNotableNominationButton);
- const styleId = 'baker-tools-notable-button-style';
- if (showNotableNominationButton) {
- $('head').append(`
- <style id='${styleId}'>
- .${this.nominateButtonClass} {
- display: none;
- }
- `);
- } else {
- $(`#${styleId}`).remove();
- }
- }
- /**
- * Add button to the provided post
- * @param {Element} post
- */
- _addButtonToPost(post) {
- const button = document.createElement('button');
- $(button).addClass(this.nominateButtonClass);
- $(button).append(`
- <i class="fa fa-star" style="color: goldenrod;"></i>
- Notable`);
- $(button).click(function(e) {
- const postNumber = $(post)
- .find('.intro .post_no')
- .text()
- .replace('No.', '');
- const href = $(post)
- .find('.intro .post_no')
- .get(0).href;
- // 8kun core - adds >>postnumber to- and unhides quickreply
- window.citeReply(postNumber, href);
- const quickReplyBody = $('#quick-reply #body');
- const oldText = quickReplyBody.val();
- quickReplyBody.val(oldText + this.bakerNotableHeader +
- this.notableReasonPlaceholder);
- // Don't ask me why i have to do this, ask CodeMonkeyZ
- // Not sure why citeReply which calls cite needs to set a timeout to
- // replace the body of the quickreply with itself. We need to combat
- // that here
- // setTimeout(function() {
- // var tmp = $('#quick-reply textarea[name="body"]').val();
- // $('#quick-reply textarea[name="body"]').val('').focus().val(tmp);
- // }, 1);
- // $(window).on('cite', function(e, id, with_link) {
- // TODO: Figure this out
- const self = this;
- setTimeout(function() {
- quickReplyBody.select();
- quickReplyBody.prop('selectionStart',
- oldText.length + self.bakerNotableHeader.length);
- }, 1.2);
- }.bind(this));
- $(post).find(EightKun.POST_MODIFIED_SELECTOR).append(button);
- }
- }
- NominatePostButtons.HIDE_NOMINATE_BUTTON_SETTING =
- 'bakertools-hide-nominate-button';
- /* global $, EightKun, ResearchBread, NotablePost, NavigationControl,
- ColorPicker, POST_BACKGROUND_CHANGE_EVENT */
- /**
- * Makes notable posts easier to see by highlighting posts that anons nominate
- * as notable.
- *
- * If someone replies to a post and their post contains the word 'notable',
- * the replied to post will be considered notable.
- *
- * Both the notable post and the nominator posts will be highlighted, as well
- * as the nominator link in the notable's mentions will be highlighted.
- */
- class NotableHighlighter {
- /**
- * Construct notablehighlighter object, find and highlight
- * current notable sand setup listeners
- */
- constructor() {
- this.styleId = 'bakertools-notable-style';
- this.NOMINATING_REGEX = /notable/i;
- this.showOnlyNotablesCheckboxId = 'bakertools-show-only-notable';
- this.createNotablePostButtonId = 'bakertools-create-notable-post';
- this.notableEditorId = 'bakertools-notable-editor';
- this.showNotableNavigationInBoardListId =
- 'bakertools-show-notable-nav-in-boardlist';
- this._createStyles();
- this._setupBakerWindowControls();
- this._readSettings();
- this.findNominatedNotables();
- this._setupListeners();
- }
- /**
- * Read settings from local storage
- */
- _readSettings() {
- this.setOnlyShowNotables(JSON.parse(
- localStorage.getItem(
- NotableHighlighter.ONLY_SHOW_NOTABLES_SETTING),
- ));
- this.showNotableNavigationInBoardList(JSON.parse(
- localStorage
- .getItem(NotableHighlighter.SHOW_NOTABLE_NAV_IN_BOARDLIST_SETTING),
- ));
- }
- /**
- * Create styles that determine how notables are highlighted
- */
- _createStyles() {
- const nominatorSelector =
- `${EightKun.POST_REPLY_SELECTOR}.${NotableHighlighter.NOMINATOR_CLASS}`;
- const notableSelector =
- `.thread ${EightKun.POST_REPLY_SELECTOR}` +
- `.${NotableHighlighter.NOTABLE_CLASS}`;
- $('head').append(`
- <style id='${this.styleId}'>
- ${notableSelector} {
- background-color: ${this.notableColor};
- }
- /* less specificity than notable so it has less preference */
- ${nominatorSelector} {
- background-color: ${this.nominatorColor};
- }
- div.post.reply .mentioned .${NotableHighlighter.NOMINATOR_CLASS} {
- color: ${this.nominatorMentionLinkColor};
- font-weight: bold;
- font-size: 1.5em;
- }
- ${notableSelector}.highlighted {
- background: #d6bad0;
- }
- ${nominatorSelector}.highlighted {
- background: #d6bad0;
- }
- </style>
- `);
- }
- /**
- * Add controls to the bakerwindow
- */
- _setupBakerWindowControls() {
- const notablePostsTitle = `Only show, notables, nominators, q, q replied
- posts`;
- const notableColorPicker = new ColorPicker(
- 'Notable Post Color',
- 'Set background color of notable Posts',
- NotableHighlighter.NOTABLE_COLOR_SETTTING,
- NotableHighlighter.DEFAULT_NOTABLE_COLOR,
- (color) => this.notableColor = color,
- );
- const nominatorColorPicker = new ColorPicker(
- 'Nominator Color',
- 'Set background color of nominator posts',
- NotableHighlighter.NOMINATOR_COLOR_SETTTING,
- NotableHighlighter.DEFAULT_NOMINATOR_COLOR,
- (color) => this.nominatorColor = color,
- );
- const nominatorMentionLinkColorPicker = new ColorPicker(
- 'Nominator Mention Link Color',
- 'Set color of nominator mention links',
- NotableHighlighter.NOMINATOR_MENTION_LINK_COLOR_SETTTING,
- NotableHighlighter.DEFAULT_NOMINATOR_MENTION_LINK_COLOR,
- (color) => this.nominatorMentionLinkColor = color,
- );
- window.bakerTools.mainWindow.addColorOption(notableColorPicker.element);
- window.bakerTools.mainWindow.addColorOption(nominatorColorPicker.element);
- window.bakerTools.mainWindow
- .addColorOption(nominatorMentionLinkColorPicker.element);
- window.bakerTools.mainWindow.addNotableOption(`
- <div class='${BakerWindow.CONTROL_GROUP_CLASS}'>
- <label for="${this.showOnlyNotablesCheckboxId}"
- title="${notablePostsTitle}" >
- Only Show Notable/Nomination Posts:
- </label>
- <input type="checkbox" id="${this.showOnlyNotablesCheckboxId}"
- title="${notablePostsTitle}" />
- </div>
- `);
- window.bakerTools.mainWindow.addBaker(`
- <button type="button" id="${this.createNotablePostButtonId}"
- title="Create notables list post based on current nominated notables" >
- Create Notable Post
- </button>
- <textarea id="${this.notableEditorId}"></textarea>
- `);
- window.bakerTools.mainWindow
- .addNavigationOption(`
- <div class='${BakerWindow.CONTROL_GROUP_CLASS}'>
- <label for="${this.showNotableNavigationInBoardListId}"
- title="Show navigation controls in board list bar" >
- Show Notable Nav in Board List:
- </label>
- <input type="checkbox" id="${this.showNotableNavigationInBoardListId}"
- title="Show navigation controls in board list bar" /><br />
- </div>
- `);
- this.navigation = new NavigationControl('Notables',
- () => NotablePost.getNotablesAsPosts(),
- NotablePost.NEW_NOTABLE_POST_EVENT);
- window.bakerTools.mainWindow
- .addNavigation(this.navigation.element);
- this.boardListNav = new NavigationControl('Notables',
- () => NotablePost.getNotablesAsPosts(),
- NotablePost.NEW_NOTABLE_POST_EVENT);
- $(EightKun.getTopBoardlist()).append(this.boardListNav.element);
- $(this.boardListNav.element).hide();
- }
- /**
- * Set the background color of notable posts
- * @param {string} color A valid css color value.
- * E.G. ('#ff00ee', 'rgba()' or 'blue')
- */
- set notableColor(color) {
- this._notableColor = color;
- document.getElementById(this.styleId)
- .sheet.cssRules[0].style.background = color;
- $(document).trigger(POST_BACKGROUND_CHANGE_EVENT, color);
- }
- /**
- * Get color for notable post backgrounds
- */
- get notableColor() {
- return this._notableColor;
- }
- /**
- * Set the background color of nominator posts
- * @param {string} color A valid css color value.
- * E.G. ('#ff00ee', 'rgba()' or 'blue')
- */
- set nominatorColor(color) {
- this._nominatorColor = color;
- document.getElementById(this.styleId)
- .sheet.cssRules[1].style.background = color;
- $(document).trigger(POST_BACKGROUND_CHANGE_EVENT, color);
- }
- /**
- * Get color for notable post backgrounds
- */
- get nominatorColor() {
- return this._nominatorColor;
- }
- /**
- * Set the color of nominator mention links posts
- * @param {string} color A valid css color value.
- * E.G. ('#ff00ee', 'rgba()' or 'blue')
- */
- set nominatorMentionLinkColor(color) {
- this._nominatorMentionLinkColor = color;
- document.getElementById(this.styleId)
- .sheet.cssRules[2].style.color = color;
- }
- /**
- * Get color for notable post backgrounds
- */
- get nominatorMentionLinkColor() {
- return this._nominatorMentionLinkColor;
- }
- /**
- * Setup listeners for new posts, bakerwindow controls, etc
- */
- _setupListeners() {
- $('#'+this.showOnlyNotablesCheckboxId).change(function(e) {
- this.setOnlyShowNotables(e.target.checked);
- }.bind(this));
- $('#'+this.createNotablePostButtonId).click(function() {
- if ($('#'+this.notableEditorId).val()) {
- if (!confirm(`If you continue, any changes you made will be
- overwritten!`)) {
- return;
- }
- }
- $('#'+this.notableEditorId).val(this.createNotablesPost());
- }.bind(this));
- $(document).on(EightKun.NEW_POST_EVENT, function(e, post) {
- this.checkNewPostsForNotables(post);
- }.bind(this));
- $('#'+this.showNotableNavigationInBoardListId).change(function(e) {
- this.showNotableNavigationInBoardList(e.target.checked);
- }.bind(this));
- }
- /**
- * Show or hide notable nav control in the boardlist
- *
- * @param {boolean} show
- */
- showNotableNavigationInBoardList(show) {
- $('#'+this.showNotableNavigationInBoardListId).prop('checked',
- show);
- localStorage
- .setItem(NotableHighlighter.SHOW_NOTABLE_NAV_IN_BOARDLIST_SETTING,
- show);
- if (show) {
- $(this.boardListNav.element).show();
- } else {
- $(this.boardListNav.element).hide();
- }
- }
- /**
- * Create the notables post for review
- * @return {string} Returns the notable post string
- */
- createNotablesPost() {
- const notables = NotablePost.getNotables();
- const breadNumber = ResearchBread.getBreadNumber();
- let post = `'''#${breadNumber}'''\n\n`;
- notables.forEach(function(notable) {
- post += `${notable.shortLink()} ${notable.description}\n\n`;
- });
- return post;
- }
- /**
- * Checks a post for notable nominations
- * @param {Element} post
- */
- checkNewPostsForNotables(post) {
- $(post).removeAttr('style'); // TODO: try removing
- if (this.isNominatingPost(post)) {
- NotablePost.fromNominatingPost(post);
- }
- }
- /**
- * Finds posts that are being tagged as notable.
- *
- * I.E. Finding any post that has been replied to by a post with the string
- * "notable" in it. Maybe at somepoint this can be smarter. Q give me some
- * dwave snow white tech!
- *
- * Highlights notable posts in yellow
- * Highlights nominating posts in pink <3
- * Highlights nominating posts in mentions
- * Add nominee count to post
- * @return {Array<NotablePost>}
- */
- findNominatedNotables() {
- const postsWithoutDough = ResearchBread.getPostsWithoutDough();
- // ^s to ignore notables review posts
- const nominatingPosts = postsWithoutDough
- .filter((post) => this.isNominatingPost(post));
- nominatingPosts.forEach(function(nominatingPost) {
- NotablePost.fromNominatingPost(nominatingPost);
- });
- console.log(NotablePost.getNotables());
- return NotablePost.getNotables();
- }
- /**
- * Is the post nominating a notable
- * @arg {Element} post .post
- * @return {boolean} True if post nominates a notable
- */
- isNominatingPost(post) {
- const postContainsNotable = post.textContent
- .search(this.NOMINATING_REGEX) != -1;
- const postIsReplying = EightKun.getReplyLinksFromPost(post).length;
- return postContainsNotable && postIsReplying;
- }
- /**
- * Toggle whether only the notable/nominee posts are shown or not
- * @arg {boolean} onlyShowNotables boolean If true, only show
- * notables/nominators, else show all
- */
- setOnlyShowNotables(onlyShowNotables) {
- $('#'+this.showOnlyNotablesCheckboxId).prop('checked', onlyShowNotables);
- localStorage.setItem(NotableHighlighter.ONLY_SHOW_NOTABLES_SETTING,
- onlyShowNotables);
- const notableOrNominationPostsSelector =
- `div.post.${NotableHighlighter.NOTABLE_CLASS},
- div.post.${NotableHighlighter.NOMINATOR_CLASS}`;
- const notableOrNominationPostBreaksSelector =
- `div.post.${NotableHighlighter.NOTABLE_CLASS}+br,
- div.post.${NotableHighlighter.NOMINATOR_CLASS}+br`;
- const onlyShowNotablesStyleId = 'bakertools-only-show-notables';
- if (onlyShowNotables) {
- $(`<style id='${onlyShowNotablesStyleId}' type='text/css'>
- div.reply:not(.post-hover),
- div.post+br {
- display: none !important;
- visibility: hidden !important;
- }
- ${notableOrNominationPostsSelector},
- ${notableOrNominationPostBreaksSelector} {
- display: inline-block !important;
- visibility: visible !important;
- }
- </style>`).appendTo('head');
- } else {
- $(`#${onlyShowNotablesStyleId}`).remove();
- // For whatever reason, when the non notable posts are filtered and new
- // posts come through the auto_update, the posts are created with
- // style="display:block" which messes up display. Remove style attr
- // TODO: can we remove this now that we have !important?
- $(EightKun.POST_SELECTOR).removeAttr('style');
- }
- }
- /**
- * Retrieves only show notable ssetting from localStorage
- * @return {boolean} true if only show notables is turned on
- */
- getOnlyShowNotables() {
- return localStorage
- .getItem(NotableHighlighter.ONLY_SHOW_NOTABLES_SETTING);
- }
- }
- NotableHighlighter.NOMINATOR_CLASS = 'bakertools-notable-nominator';
- NotableHighlighter.NOTABLE_CLASS = 'bakertools-notable';
- NotableHighlighter.ONLY_SHOW_NOTABLES_SETTING =
- 'bakertools-only-show-notables';
- NotableHighlighter.SHOW_NOTABLE_NAV_IN_BOARDLIST_SETTING =
- 'bakertools-show-notable-nav-in-boardlist';
- NotableHighlighter.NOMINATOR_COLOR_SETTTING =
- 'bakertools-nominator-color';
- NotableHighlighter.NOTABLE_COLOR_SETTTING =
- 'bakertools-notable-color';
- NotableHighlighter.NOMINATOR_MENTION_LINK_COLOR_SETTTING =
- 'bakertools-nominator-metion-link-color';
- NotableHighlighter.DEFAULT_NOTABLE_COLOR = '#E5FFCC';
- NotableHighlighter.DEFAULT_NOMINATOR_COLOR = '#ACC395';
- NotableHighlighter.DEFAULT_NOMINATOR_MENTION_LINK_COLOR = '#00CC00';
- /* globals $, EightKun, debounce, BakerWindow */
- /* exported PostRateChart */
- /**
- * Displays chart of post/min
- */
- class PostRateChart {
- /**
- * Construct a postrate chart
- */
- constructor() {
- this.containerClass = 'bakertools-postrate-container';
- this.chartClass = 'bakertools-postrate-chart';
- this.rateClass = 'bakertools-postrate-rate';
- this.styleId = 'bakertools-postrate-style';
- this.hidePostRateChartId = 'bakertools-postrate-hide-postrate';
- this.numberOfPostsForAverage = 10;
- this.numberOfDataPointsShownOnChart = 10;
- this.postTimes = [];
- this.postsPerMinuteHistory = [];
- this._setupStyles();
- this._setupBakerWindowControls();
- this._createElement();
- this._getExistingPostRates();
- this._setupListeners();
- this.draw();
- this.draw = debounce(this.draw, 1000 *2);
- this._readSettings();
- }
- /**
- * Read settings from local storage
- */
- _readSettings() {
- const hidePostRate = JSON.parse(localStorage
- .getItem(PostRateChart.HIDE_POSTRATE_SETTING));
- this.showPostRateChart(!hidePostRate);
- }
- /**
- * Setup chart styles
- */
- _setupStyles() {
- $('head').append(`
- <style id='${this.styleId}'>
- .${this.containerClass} {
- height: 20px;
- padding: 0;
- color: rgb(20, 137, 183);
- }
- .${this.chartClass} {
- border: 1px solid;
- vertical-align: middle;
- padding: 1px;
- }
- ${EightKun.BOARDLIST_SELECTOR} .${this.containerClass}:before {
- content: '[';
- color: #89A;
- }
- ${EightKun.BOARDLIST_SELECTOR} .${this.containerClass}:after {
- content: ']';
- color: #89A;
- }
- `);
- }
- /**
- * Add controls to the bakerwindow
- */
- _setupBakerWindowControls() {
- window.bakerTools.mainWindow.addOption(`
- <div class='${BakerWindow.CONTROL_GROUP_CLASS}'>
- <label for="${this.hidePostRateChartId}"
- title="Hide the postrate chart in boardlist" >
- Hide Post/Min Chart:
- </label>
- <input type="checkbox" id="${this.hidePostRateChartId}"
- title="Hide the postrate chart in boardlist" />
- </div>
- `);
- }
- /**
- * Setup listener to record post times
- */
- _setupListeners() {
- $(document).on(EightKun.NEW_POST_EVENT, function(idx, post) {
- this._addDataPointFromPost(post);
- this.draw();
- }.bind(this));
- $('#'+this.hidePostRateChartId).change(function(e) {
- this.showPostRateChart(!e.target.checked);
- }.bind(this));
- }
- /**
- * Show or hide post rate chart in the boardlist
- *
- * @param {boolean} show
- */
- showPostRateChart(show) {
- $('#'+this.hidePostRateChartId).prop('checked',
- !show);
- localStorage
- .setItem(PostRateChart.HIDE_POSTRATE_SETTING,
- !show);
- if (show) {
- $(this.element).show();
- } else {
- $(this.element).hide();
- }
- }
- /**
- * Create the canvas element
- */
- _createElement() {
- this.element = $(`
- <span class='${this.containerClass}' title = 'Posts Per Minute'>
- <span class='${this.rateClass}'>0</span> ppm
- <canvas class='${this.chartClass}'></canvas>
- </span>`,
- ).get(0);
- $(EightKun.getTopBoardlist()).append(this.element);
- this.canvas = $(this.element).find('canvas').get(0);
- this.canvas.height = 10;
- this.canvas.width = 100;
- }
- /**
- * Collect post rate data on posts at page load
- */
- _getExistingPostRates() {
- $('div.post').each(function(idx, post) {
- this._addDataPointFromPost(post);
- }.bind(this));
- }
- /**
- * Add a data point (aka the time a post was made)
- * @param {Element} post div.post
- */
- _addDataPointFromPost(post) {
- this.postTimes.push(EightKun.getPostTime(post));
- if (this._isEnoughDataToAverage()) {
- this._recordPostPerMinute();
- }
- }
- /**
- * Return true if theres enough data to perform averaging
- * @return {boolean} true if enough data
- */
- _isEnoughDataToAverage() {
- return this.postTimes.length > (this.numberOfPostsForAverage + 1);
- }
- /**
- * Record post per minute with the current set of post times
- * Calc is done with the last ${this.numberOfPostsForAverage} post times
- */
- _recordPostPerMinute() {
- const startPostIndex =
- this.postTimes.length - this.numberOfPostsForAverage - 1;
- const endPostIndex = this.postTimes.length - 1;
- const startPostTime = this.postTimes[startPostIndex];
- const endPostTime = this.postTimes[endPostIndex];
- const postsPerMinute =
- this.numberOfPostsForAverage / ((endPostTime - startPostTime) / 60);
- this.postsPerMinuteHistory.push(postsPerMinute);
- }
- /**
- * Draw the post rate chart
- */
- draw() {
- if (!this.postsPerMinuteHistory.length) {
- return;
- }
- const canvas = this.canvas;
- const ctx = canvas.getContext('2d');
- this._setPostRateText();
- const normalizedPostPerMinutes = this._normalizePostPerMinutes();
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- ctx.strokeStyle = $(`${EightKun.BOARDLIST_SELECTOR} a`).css('color');
- ctx.beginPath();
- let x = 0;
- let y = canvas.height * normalizedPostPerMinutes[0];
- ctx.moveTo(x, y);
- normalizedPostPerMinutes.slice(1).forEach(function(ppm, i) {
- x = (i+1) * (canvas.width / this.numberOfDataPointsShownOnChart);
- y = canvas.height * ppm;
- ctx.lineTo(x, y);
- }.bind(this));
- ctx.stroke();
- ctx.closePath();
- }
- /**
- * Set the text label of current PPM
- */
- _setPostRateText() {
- const lastIndex = this.postsPerMinuteHistory.length-1;
- const currentPPM =
- this.postsPerMinuteHistory[lastIndex].toFixed(2);
- $(`.${this.rateClass}`).text(currentPPM);
- }
- /**
- * Normalize the data points to be within 0-1 range.
- * Slice the array to only contain the currently drawn slice
- * @return {Array}
- */
- _normalizePostPerMinutes() {
- const slicedArray =
- this.postsPerMinuteHistory.slice(-this.numberOfDataPointsShownOnChart);
- const maxPPM = Math.max(...slicedArray);
- const minPPM = Math.min(...slicedArray);
- const range = maxPPM - minPPM;
- return slicedArray.map(function(ppm) {
- return (ppm - minPPM) / range;
- });
- }
- }
- PostRateChart.HIDE_POSTRATE_SETTING =
- 'bakertools-hide-postrate-chart';
- /* global $, EightKun, ColorPicker */
- /* exported PreviousBreadHighlighter */
- /**
- * Highlights previous bread post links
- */
- class PreviousBreadHighlighter {
- /**
- * Construct pb highlighter object, setup listeners
- */
- constructor() {
- this.styleId = 'bakertools-previous-bread-styles';
- this.previousBreadClass = 'bakertools-PreviousBread';
- this.newerBreadClass = 'bakertools-NewBread';
- this._linkSelector = 'div.body > p.body-line.ltr > a';
- this._setupStyles();
- this._setupBakerWindowControls();
- const links = $(this._linkSelector).filter('[onClick]');
- links.each(function(index, link) {
- this.markLinkIfPreviousBread(link);
- }.bind(this));
- this._setupListeners();
- }
- /**
- * Setup color picker controls
- */
- _setupBakerWindowControls() {
- const colorPicker = new ColorPicker(
- 'Previous Bread Link Color',
- 'Set the color of links to previous breads',
- PreviousBreadHighlighter.PREVIOUS_BREAD_LINK_COLOR_SETTING,
- PreviousBreadHighlighter.DEFAULT_PREVIOUS_BREAD_LINK_COLOR,
- (color) => this.previousBreadLinkColor = color,
- );
- window.bakerTools.mainWindow.addColorOption(colorPicker.element);
- const newerBreadColorPicker = new ColorPicker(
- 'Newer Bread Link Color',
- 'Set the color of links to newer breads',
- PreviousBreadHighlighter.NEWER_BREAD_LINK_COLOR_SETTING,
- PreviousBreadHighlighter.DEFAULT_NEWER_BREAD_LINK_COLOR,
- (color) => this.newerBreadLinkColor = color,
- );
- window.bakerTools.mainWindow.addColorOption(newerBreadColorPicker.element);
- }
- /**
- * Set the color of pb links
- * @param {string} color A valid css color value.
- * E.G. ('#ff00ee', 'rgba()' or 'blue')
- */
- set previousBreadLinkColor(color) {
- this._previousBreadLinkColor = color;
- document.getElementById(this.styleId)
- .sheet.cssRules[0].style.color = color;
- }
- /**
- * Get color of pb links
- */
- get previousBreadLinkColor() {
- return this._previousBreadLinkColor;
- }
- /**
- * Set the color of nb links
- * @param {string} color A valid css color value.
- * E.G. ('#ff00ee', 'rgba()' or 'blue')
- */
- set newerBreadLinkColor(color) {
- this._newerBreadLinkColor = color;
- document.getElementById(this.styleId)
- .sheet.cssRules[2].style.color = color;
- }
- /**
- * Get color of nb links
- */
- get newerBreadLinkColor() {
- return this._newerBreadLinkColor;
- }
- /**
- * Setup styles for pb links
- */
- _setupStyles() {
- $('head').append(`
- <style id='${this.styleId}'>
- ${EightKun.POST_REPLY_SELECTOR} div.body a.${this.previousBreadClass} {
- color: ${this.previousBreadLinkColor};
- }
- a.${this.previousBreadClass}::after {
- content: " (pb)";
- }
- ${EightKun.POST_REPLY_SELECTOR} div.body a.${this.newerBreadClass} {
- color: ${this.newerBreadLinkColor};
- }
- a.${this.newerBreadClass}::after {
- content: " (nb)";
- }
- </style>
- `);
- }
- /**
- * Setup listeners for pb highlighting
- */
- _setupListeners() {
- $(document).on(EightKun.NEW_POST_EVENT, function(e, post) {
- $(post).find(this._linkSelector)
- .each((index, link) => this.markLinkIfPreviousBread(link));
- }.bind(this));
- }
- /**
- * Marks the link if it is pb
- *
- * @param {Anchor} link
- */
- markLinkIfPreviousBread(link) {
- const currentBreadNumber = document.location.pathname
- .split('/')
- .slice(-1)[0]
- .split('.')[0];
- const linkBreadNumber = link.href.split('/')
- .slice(-1)[0]
- .split('#')[0]
- .split('.')[0];
- const isAReplyLink = $(link)
- .attr('onclick')
- .search(EightKun.REPLY_REGEX) != 1;
- if (isAReplyLink &&
- parseInt(currentBreadNumber, 10) > parseInt(linkBreadNumber, 10)) {
- $(link).addClass(this.previousBreadClass);
- } else if (isAReplyLink &&
- parseInt(currentBreadNumber, 10) < parseInt(linkBreadNumber, 10)) {
- $(link).addClass(this.newerBreadClass);
- }
- }
- }
- PreviousBreadHighlighter.PREVIOUS_BREAD_LINK_COLOR_SETTING =
- 'bakertools-previous-bread-link-color';
- PreviousBreadHighlighter.DEFAULT_PREVIOUS_BREAD_LINK_COLOR =
- '#0000CC';
- PreviousBreadHighlighter.NEWER_BREAD_LINK_COLOR_SETTING =
- 'bakertools-newer-bread-link-color';
- PreviousBreadHighlighter.DEFAULT_NEWER_BREAD_LINK_COLOR =
- '#00CC00';
- /* global $, EightKun, ResearchBread, NavigationControl,
- ColorPicker, POST_BACKGROUND_CHANGE_EVENT, BakerWindow */
- /**
- * Highlight Q posts, replies to q, q replies.
- * Adds navigation to baker window
- */
- class QPostHighlighter {
- /**
- * Construct qposthighlighter object and setup listeners
- */
- constructor() {
- this.styleId = 'bakertools-q-style';
- this.qPostClass = 'bakertools-q-post';
- this.qReplyClass = 'bakertools-q-reply';
- this.qMentionClass = 'bakertools-q-mention';
- this.qLinkClass = 'bakertools-q-link';
- this.styleId = 'bakertools-q-styles';
- this._linkSelector = 'div.body > p.body-line.ltr > a';
- this.showQNavigationInBoardListId =
- 'bakertools-show-q-nav-in-boardlist';
- this.currentQTripCode = null;
- this._setupStyles();
- this._setupBakerWindowControls();
- this._readSettings();
- this._findQPosts();
- this._setupListeners();
- }
- /**
- * Read settings from localStorage
- */
- _readSettings() {
- this.showQNavigationInBoardList(JSON.parse(
- localStorage
- .getItem(QPostHighlighter.SHOW_Q_NAV_IN_BOARDLIST_SETTING),
- ));
- }
- /**
- * Setup styles for highlighting q posts
- */
- _setupStyles() {
- $('head').append(`
- <style id='${this.styleId}'>
- ${EightKun.POST_REPLY_SELECTOR}.${this.qPostClass} {
- background: ${this.qPostColor};
- display: inline-block !important;
- visibility: visible !important;
- }
- ${EightKun.POST_REPLY_SELECTOR}.${this.qReplyClass} {
- background: ${this.qYouColor};
- display: inline-block !important;
- visibility: visible !important;
- }
- ${EightKun.POST_REPLY_SELECTOR}.${this.qPostClass}.highlighted {
- background: #d6bad0;
- }
- ${EightKun.POST_REPLY_SELECTOR} .intro .${this.qMentionClass},
- .${this.qLinkClass} {
- padding:1px 3px 1px 3px;
- background-color:black;
- border-radius:8px;
- border:1px solid #bbbbee;
- color:gold;
- background: linear-gradient(300deg, #ff0000, #ff0000, #ff0000, #bbbbbb,
- #4444ff);
- background-size: 800% 800%;
- -webkit-animation: Patriot 5s ease infinite;
- -moz-animation: Patriot 5s ease infinite;
- -o-animation: Patriot 5s ease infinite;
- animation: Patriot 5s ease infinite;
- -webkit-text-fill-color: transparent;
- background: -o-linear-gradient(transparent, transparent);
- -webkit-background-clip: text;
- }
- </style>
- `);
- }
- /**
- * Set the background color of q posts
- * @param {string} color A valid css color value.
- * E.G. ('#ff00ee', 'rgba()' or 'blue')
- */
- set qPostColor(color) {
- this._qPostColor = color;
- document.getElementById(this.styleId)
- .sheet.cssRules[0].style.background = color;
- $(document).trigger(POST_BACKGROUND_CHANGE_EVENT, color);
- }
- /**
- * Get color for q post backgrounds
- */
- get qPostColor() {
- return this._qPostColor;
- }
- /**
- * Set the background color of q posts
- * @param {string} color A valid css color value.
- * E.G. ('#ff00ee', 'rgba()' or 'blue')
- */
- set qYouColor(color) {
- this._qYouColor = color;
- document.getElementById(this.styleId)
- .sheet.cssRules[1].style.background = color;
- $(document).trigger(POST_BACKGROUND_CHANGE_EVENT, color);
- }
- /**
- * Get bg color for posts q replies to
- */
- get qYouColor() {
- return this._qYouColor;
- }
- /**
- * Get Q's current trip code from the bread
- */
- _getCurrentQTripFromBread() {
- const tripCodeMatch = $(EightKun.getOpPost())
- .text()
- .match(/Q's Trip-code: Q (.+?\s)/);
- if (!tripCodeMatch) {
- console.error('Could not find Q\'s tripcode');
- return;
- }
- this.currentQTripCode = tripCodeMatch[1].split(' ')[0];
- }
- /**
- * Find current Q posts in bread
- */
- _findQPosts() {
- const posts = ResearchBread.getPostsWithoutDough();
- $(posts).each(function(i, post) {
- this._doItQ(post);
- }.bind(this));
- }
- /**
- * Check if the post is Q
- * WWG1WGA
- *
- * @param {Element} post a div.post
- */
- _doItQ(post) {
- if (this._markIfQPost(post)) { // Q Post, lets check for q replies
- const qPostNumber = $(post)
- .find('.intro .post_no')
- .text()
- .replace('No.', '');
- const links = $(post)
- .find(this._linkSelector)
- .filter('[onClick]');
- $(links).each(function(i, link) {
- const postNumber = link.href.split('#')[1];
- // Enlightened post
- $(`#reply_${postNumber}`).addClass(this.qReplyClass);
- const metionLinkSelector = `#reply_${postNumber} .intro .mentioned a`;
- $(metionLinkSelector).each(function(i, mentionAnchor) {
- const mentionPostNumber = $(mentionAnchor).text().replace('>>', '');
- if (mentionPostNumber == qPostNumber) {
- $(mentionAnchor).addClass(this.qMentionClass);
- }
- }.bind(this));
- }.bind(this));
- } else { // Not Q, but lets check if this post replies to Q
- const links = $(post).find(this._linkSelector).filter('[onClick]');
- $(links).each(function(i, link) {
- const postNumber = link.href.split('#')[1];
- const replyPost = document.querySelector(`#reply_${postNumber}`);
- // TODO: need to handle pb posts
- if (this.isQ(replyPost)) {
- $(link).addClass(this.qLinkClass);
- }
- }.bind(this));
- }
- }
- /**
- * @arg {Element} post div.post.reply
- * @return {boolean} true if it is a q post
- */
- _markIfQPost(post) {
- let isQ = false;
- if (this.isQ(post)) {
- isQ = true;
- $(post).addClass(this.qPostClass);
- QPostHighlighter.qPosts.push(post);
- $(document).trigger(QPostHighlighter.NEW_Q_POST_EVENT, post);
- }
- return isQ;
- }
- /**
- * Is the post Q?
- * @param {Element} post a div.post.reply
- * @return {boolean} true if the post is Q
- */
- isQ(post) {
- const qTripHistory = QTripCodeHistory.INSTANCE;
- const dateOfPost = new Date(EightKun.getPostDateTime(post));
- const expectedQTripBasedOnDate = qTripHistory.getTripCodeByDate(dateOfPost);
- if (!expectedQTripBasedOnDate) {
- console.info(`Could not find Q trip code for date: ${dateOfPost}`);
- return false;
- }
- return EightKun.getPostTrip(post) == expectedQTripBasedOnDate.tripCode;
- }
- /**
- * Add Q post navigation to bakerwindow
- */
- _setupBakerWindowControls() {
- window.bakerTools.mainWindow
- .addNavigationOption(`
- <div class='${BakerWindow.CONTROL_GROUP_CLASS}'>
- <label for="${this.showQNavigationInBoardListId}"
- title="Show navigation controls in board list bar" >
- Show Q Nav in Board List:
- </label>
- <input type="checkbox" id="${this.showQNavigationInBoardListId}"
- title="Show navigation controls in board list bar" /><br />
- </div>
- `);
- this.navigation = new NavigationControl('Q Posts',
- () => QPostHighlighter.qPosts, QPostHighlighter.NEW_Q_POST_EVENT);
- window.bakerTools.mainWindow
- .addNavigation(this.navigation.element);
- this.boardListNav = new NavigationControl('Q',
- () => QPostHighlighter.qPosts, QPostHighlighter.NEW_Q_POST_EVENT);
- $(EightKun.getTopBoardlist()).append(this.boardListNav.element);
- $(this.boardListNav.element).hide();
- const qColorPicker = new ColorPicker(
- 'Q Post Color',
- 'Set background color of Q Posts',
- QPostHighlighter.Q_POST_COLOR_SETTING,
- QPostHighlighter.DEFAULT_Q_POST_COLOR,
- (color) => this.qPostColor = color,
- );
- const qYouColorPicker = new ColorPicker(
- 'Q (You) Color',
- 'Set background color of posts Q Replies to',
- QPostHighlighter.Q_YOU_POST_COLOR_SETTING,
- QPostHighlighter.DEFAULT_Q_YOU_POST_COLOR,
- (color) => this.qYouColor = color,
- );
- window.bakerTools.mainWindow.addColorOption(qColorPicker.element);
- window.bakerTools.mainWindow.addColorOption(qYouColorPicker.element);
- }
- /**
- * Setup listeners for new posts
- */
- _setupListeners() {
- $(document).on(EightKun.NEW_POST_EVENT, function(e, post) {
- this._doItQ(post);
- }.bind(this));
- $('#'+this.showQNavigationInBoardListId).change(function(e) {
- this.showQNavigationInBoardList(e.target.checked);
- }.bind(this));
- }
- /**
- * Show or hide q nav control in the boardlist
- *
- * @param {boolean} showNavInBoardList
- */
- showQNavigationInBoardList(showNavInBoardList) {
- $('#'+this.showQNavigationInBoardListId).prop('checked',
- showNavInBoardList);
- localStorage.setItem(QPostHighlighter.SHOW_Q_NAV_IN_BOARDLIST_SETTING,
- showNavInBoardList);
- if (showNavInBoardList) {
- $(this.boardListNav.element).show();
- } else {
- $(this.boardListNav.element).hide();
- }
- }
- }
- QPostHighlighter.qPosts = [];
- QPostHighlighter.NEW_Q_POST_EVENT = 'bakertools-new-q-post';
- QPostHighlighter.SHOW_Q_NAV_IN_BOARDLIST_SETTING =
- 'bakertools-show-q-nav-in-boardlist';
- QPostHighlighter.Q_YOU_COLOR_SETTING =
- 'bakertools-q-you-color';
- QPostHighlighter.Q_POST_COLOR_SETTING =
- 'bakertools-q-post-color';
- QPostHighlighter.DEFAULT_Q_POST_COLOR = '#FFFFCC';
- QPostHighlighter.DEFAULT_Q_YOU_POST_COLOR = '#DDDDDD';
- /**
- * History of Q's tripcodes and their date ranges
- */
- class QTripCodeHistory {
- /**
- * Construct the q trip history
- */
- constructor() {
- // Hat tip to https://8kun.top/qresearch/res/7762733.html#7832643 for Q trip history
- this.history = [
- new QTripCode('!ITPb.qbhqo',
- new Date('2017-11-10 04:07:15Z'), new Date('2017-12-15 06:04:43Z')),
- new QTripCode('!UW.yye1fxo',
- new Date('2017-12-15 06:04:06Z'), new Date('2018-03-24 13:09:02Z')),
- new QTripCode('!xowAT4Z3VQ',
- new Date('2018-03-24 13:09:37Z'), new Date('2018-05-04 20:02:22Z')),
- new QTripCode('!2jsTvXXmXs',
- new Date('2018-05-04 20:01:19Z'), new Date('2018-05-08 23:46:39Z')),
- new QTripCode('!4pRcUA0lBE',
- new Date('2018-05-08 23:47:17Z'), new Date('2018-05-19 22:06:20Z')),
- new QTripCode('!CbboFOtcZs',
- new Date('2018-05-19 22:07:06Z'), new Date('2018-08-05 20:12:52Z')),
- new QTripCode('!A6yxsPKia.',
- new Date('2018-08-05 20:14:24Z'), new Date('2018-08-10 18:24:24Z')),
- new QTripCode('!!mG7VJxZNCI',
- new Date('2018-08-10 18:26:08Z'), new Date('2019-11-25 22:35:45Z')),
- new QTripCode('!!Hs1Jq13jV6',
- new Date('2019-12-02 17:55:59Z'), null),
- ];
- }
- /**
- * Get Q Tripcode by the provided date
- * @param {Date} date
- * @return {QTripCode}
- */
- getTripCodeByDate(date) {
- let returnTripCode = null;
- for (const tripCode of this.history) {
- if (tripCode.isValidForDate(date)) {
- returnTripCode = tripCode;
- break;
- }
- }
- return returnTripCode;
- }
- /**
- * Get Q Tripcode by the current
- * @return {QTripCode}
- */
- getCurrentTripCode() {
- return this.getTripCodeByDate(new Date());
- }
- }
- /**
- * Represents a Tripcode used by Q and the timeframe
- */
- class QTripCode {
- /**
- * Create a new QTripCode
- * @param {string} tripCode
- * @param {DateTime} startDate
- * @param {DateTime} endDate
- */
- constructor(tripCode, startDate, endDate) {
- this.tripCode = tripCode;
- this.startDate = startDate;
- this.isCurrentTrip = false;
- if (!endDate) {
- this.isCurrentTrip = true;
- }
- this.endDate = endDate;
- }
- /**
- * Is this tripcode valid for the provided date?
- * @param {Date} date
- * @return {boolean} true if this trip code is valid for the date
- */
- isValidForDate(date) {
- const dateIsOnOrAfterTripStart = date >= this.startDate;
- const dateIsOnOrBeforeTripEnd = date <= this.endDate;
- return dateIsOnOrAfterTripStart &&
- (this.isCurrentTrip || dateIsOnOrBeforeTripEnd);
- }
- }
- QTripCodeHistory.INSTANCE = new QTripCodeHistory();
- /* globals $, EightKun, ResearchBread, BakerWindow */
- /* exported SpamFader, NameFagStrategy, HighPostCountFagStrategy,
- * FloodFagStrategy, BreadShitterFagStrategy */
- /**
- * Fade posts that post too fast
- */
- class SpamFader {
- /**
- * Construct spamfader
- * @param {Array} spamDetectionStrategies An array of SpamDetectionStrategy's
- */
- constructor(spamDetectionStrategies) {
- this.spamDetectionStrategies = spamDetectionStrategies;
- this.styleId = 'bakertools-spamfader-style';
- this.spamClass = 'bakertools-spamfader-spam';
- this.disableSpamFaderId = 'bakertools-spamfader-disable';
- this.hideSpamBadgesId = 'bakertools-spamfader-hide-spam-badges';
- this._createStyles();
- this._setupBakerWindowControls();
- this._readSettings();
- this._spamFadeExistingPosts();
- this._setupListeners();
- }
- /**
- * Create stylesheets
- */
- _createStyles() {
- $('head').append(`
- <style id='${this.styleId}'>
- div.post.post-hover {
- opacity: 1 !important;
- }
- </style>
- `);
- }
- /**
- * Setup settings UI for spamfading
- */
- _setupBakerWindowControls() {
- window.bakerTools.mainWindow.addSpamOption(`
- <div class='${BakerWindow.CONTROL_GROUP_CLASS}'>
- <label for='${this.disableSpamFaderId}'>Disable SpamFader</label>
- <input type='checkbox' id='${this.disableSpamFaderId}' />
- </div>
- <div class='${BakerWindow.CONTROL_GROUP_CLASS}'>
- <label for='${this.hideSpamBadgesId}'>Hide spam badges</label>
- <input type='checkbox' id='${this.hideSpamBadgesId}'/>
- </div>
- `);
- }
- /**
- * Loop through posts for spam
- */
- _spamFadeExistingPosts() {
- $(EightKun.POST_REPLY_SELECTOR).each(function(i, post) {
- this._detectSpam(post);
- }.bind(this));
- }
- /**
- * Determine if provided post is spam, if so, add spam class
- *
- * @param {Element} post div.post
- */
- _detectSpam(post) {
- const posterStats = SpamFader.getPosterStats(post);
- posterStats.addPost(post);
- if (SpamFader.isMarkedAsNotSpam(posterStats)) {
- return;
- }
- this.spamDetectionStrategies.forEach((sds) => sds.isSpam(post));
- this._takeSpamAction(posterStats);
- }
- /**
- * Performs the spam action against the poster's posts.
- * @param {PosterStats} posterStats
- */
- _takeSpamAction(posterStats) {
- if (!posterStats.isSpam) {
- return;
- }
- if (this.spamAction === SpamFader.FADE) {
- const opacity =
- Math.max(SpamFader.MIN_OPACITY, (1 - posterStats.fadeProgress));
- posterStats.posts.forEach(function(p) {
- $(p).css('opacity', opacity);
- $(p).off('mouseenter mouseleave');
- $(p).hover(function() {
- $(p).animate({opacity: 1.0}, SpamFader.ANIMATION_DURATION);
- }, function() {
- $(p).animate({opacity: opacity}, SpamFader.ANIMATION_DURATION);
- });
- });
- } else if (this.spamAction === SpamFader.HIDE) {
- posterStats.posts.forEach(function(p) {
- EightKun.hidePost(p);
- });
- }
- }
- /**
- * Setup new post listener
- */
- _setupListeners() {
- this._setupNewPostListener();
- $(`#${this.disableSpamFaderId}`).change(function(e) {
- this.disableSpamFader(e.target.checked);
- }.bind(this));
- $(`#${this.hideSpamBadgesId}`).change(function(e) {
- this.hideSpamBadges(e.target.checked);
- }.bind(this));
- }
- /**
- * Setup listener to check new posts for spam
- */
- _setupNewPostListener() {
- $(document).on(EightKun.NEW_POST_EVENT, function(e, post) {
- this._detectSpam(post);
- }.bind(this));
- }
- /**
- * Hide the actions of the spamfader.
- * @param {boolean} disable
- */
- disableSpamFader(disable) {
- $('#'+this.disableSpamFaderId).prop('checked', disable);
- localStorage.setItem(SpamFader.DISABLE_SPAM_FADER_SETTING, disable);
- if (disable) {
- $(SpamFader.SPAM_BADGES_SELECTOR).hide();
- $(SpamFader.NOT_SPAM_BUTTON_SELECTOR).hide();
- $(EightKun.POST_REPLY_SELECTOR)
- .css({'opacity': ''})
- .off('mouseenter mouseleave');
- $(document).off(EightKun.NEW_POST_EVENT);
- } else {
- $(SpamFader.SPAM_BADGES_SELECTOR).show();
- $(SpamFader.NOT_SPAM_BUTTON_SELECTOR).show();
- SpamFader.posterStats.forEach(this._takeSpamAction.bind(this));
- this._setupNewPostListener();
- }
- }
- /**
- * Hide spam badges on posts
- * @param {boolean} hide
- */
- hideSpamBadges(hide) {
- $('#'+this.hideSpamBadgesId).prop('checked', hide);
- localStorage.setItem(SpamFader.HIDE_SPAM_BADGES_SETTING, hide);
- if (hide) {
- $(SpamFader.SPAM_BADGES_SELECTOR).hide();
- } else {
- $(SpamFader.SPAM_BADGES_SELECTOR).show();
- }
- }
- /**
- * Read spamfader settings
- */
- _readSettings() {
- this.spamAction = localStorage[SpamFader.SPAM_ACTION_SETTING] ||
- SpamFader.FADE;
- this.hideSpamBadges(JSON.parse(
- localStorage.getItem(
- SpamFader.HIDE_SPAM_BADGES_SETTING),
- ));
- this.disableSpamFader(JSON.parse(
- localStorage.getItem(
- SpamFader.DISABLE_SPAM_FADER_SETTING),
- ));
- }
- /**
- * Get post stats for post
- * @param {Element} post div.post
- * @return {PosterStats}
- */
- static getPosterStats(post) {
- const posterId = EightKun.getPosterId(post);
- if (!SpamFader.posterStats.has(posterId)) {
- SpamFader.posterStats.set(posterId, new PosterStats(posterId));
- }
- return SpamFader.posterStats.get(posterId);
- }
- /**
- * Adds spam badge to the posts by the poster.
- * Wear them proudly fag!
- *
- * @param {PosterStats} posterStats The posterStats object representing
- * spam fag
- * @param {string} badge Font-Awesome glyph for badge
- * @param {string} badgeTitle The title describing the badge
- */
- static addSpamBadge(posterStats, badge, badgeTitle) {
- posterStats.posts.forEach(function(post) {
- if (!$(post).find(SpamFader.SPAM_BADGES_SELECTOR).length) {
- SpamFader.createSpamBadgeSection(post);
- }
- const alreadyHasBadge = $(post)
- .find(SpamFader.SPAM_BADGES_SELECTOR)
- .find(`.fa-${badge}`).length;
- if (!alreadyHasBadge) {
- $(post).find(SpamFader.SPAM_BADGES_SELECTOR).append(
- `<i class="fa fa-${badge}" title='${badgeTitle}'></i>`,
- );
- }
- });
- }
- /**
- * Create section for spam badges
- * @param {Element} post div.post
- */
- static createSpamBadgeSection(post) {
- const $postModifiedSection = $(post).find(EightKun.POST_MODIFIED_SELECTOR);
- const button = $(`<button class='${SpamFader.NOT_SPAM_BUTTON_CLASS}'>
- <i class="fa fa-undo" title='Not spam'></i>Not Spam
- </button>
- `);
- button.click(function(e) {
- e.preventDefault();
- SpamFader.markNotSpam(post);
- });
- button.appendTo($postModifiedSection);
- $postModifiedSection.append(`
- <span class='${SpamFader.SPAM_BADGES_CLASS}'>Spam Badges:</span>`);
- }
- /**
- * Mark poster as not spam.
- *
- * @param {Element} post div.post
- */
- static markNotSpam(post) {
- const stats = SpamFader.getPosterStats(post);
- stats.markNotSpam();
- stats.posts.forEach(function(p) {
- $(p).css('opacity', 1);
- $(p).off('mouseenter mouseleave');
- $(p).find(SpamFader.SPAM_BADGES_SELECTOR).remove();
- $(p).find(`.${SpamFader.NOT_SPAM_BUTTON_CLASS}`).remove();
- });
- SpamFader.addToNotSpamList(stats);
- }
- /**
- * Save not spam in localstorage
- * @param {PosterStats} posterStats
- */
- static addToNotSpamList(posterStats) {
- const threadId = EightKun.getThreadId();
- const notSpamList = SpamFader.getNotSpamList();
- if (!(threadId in notSpamList)) {
- notSpamList[threadId] = [];
- }
- if (!SpamFader.isMarkedAsNotSpam(posterStats)) {
- notSpamList[threadId].push(posterStats.posterId);
- localStorage.setItem(SpamFader.NOT_SPAM_SETTING,
- JSON.stringify(notSpamList));
- }
- }
- /**
- * Has this poster been marked as not spam?
- * @param {PosterStats} posterStats
- * @return {boolean} true if not spam
- */
- static isMarkedAsNotSpam(posterStats) {
- const threadId = EightKun.getThreadId();
- const notSpamList = SpamFader.getNotSpamList();
- return threadId in notSpamList &&
- notSpamList[threadId].includes(posterStats.posterId);
- }
- /**
- * Get not spam list from localStorage
- * @return {Array} map of thread to not spam poster ids
- */
- static getNotSpamList() {
- return JSON.parse(
- localStorage.getItem(SpamFader.NOT_SPAM_SETTING) || '{}',
- );
- }
- }
- SpamFader.posterStats = new Map();
- SpamFader.FADE = 'fade';
- SpamFader.HIDE = 'hide';
- SpamFader.SPAM_BADGES_CLASS = 'bakertools-spam-badges';
- SpamFader.SPAM_BADGES_SELECTOR = `.${SpamFader.SPAM_BADGES_CLASS}`;
- SpamFader.MIN_OPACITY = .2;
- SpamFader.ANIMATION_DURATION = 200; // milliseconds
- SpamFader.SPAM_ACTION_SETTING = 'bakertools-spamfader-action';
- SpamFader.HIDE_SPAM_BADGES_SETTING = 'bakertools-spamfader-hide-badges';
- SpamFader.DISABLE_SPAM_FADER_SETTING = 'bakertools-spamfader-disable';
- SpamFader.NOT_SPAM_SETTING = 'bakertools-spamfader-notspam';
- SpamFader.NOT_SPAM_BUTTON_CLASS = 'bakertools-spamfader-notspam';
- SpamFader.NOT_SPAM_BUTTON_SELECTOR = `.${SpamFader.NOT_SPAM_BUTTON_CLASS}`;
- /**
- * Holds spam stats
- */
- class PosterStats {
- /**
- * Construct poststats for post
- * @param {number} posterId id of poster
- */
- constructor(posterId) {
- this.posts = [];
- this.posterId = posterId;
- this.markNotSpam();
- }
- /**
- * Reset spam indicators
- */
- markNotSpam() {
- this._spamCertainty = 0;
- this._fadeProgress = 0;
- this.floodCount = 0;
- this.breadShitCount = 0;
- this.isBreadShitter = false;
- }
- /**
- * Add post to poster's list of post
- * @param {Element} post div.post
- */
- addPost(post) {
- if (!this.posts.includes(post)) {
- this.posts.push(post);
- }
- }
- /**
- * Set spam certainty property
- * @param {number} certainty
- */
- set spamCertainty(certainty) {
- if (certainty > this._spamCertainty) {
- this._spamCertainty = certainty;
- }
- }
- /**
- * Get spam spamCertainty
- * @return {number} 1 represents 100% certainty.
- */
- get spamCertainty() {
- return this._spamCertainty;
- }
- /**
- * Set fade progress property
- * @param {number} progress
- */
- set fadeProgress(progress) {
- if (progress > this._fadeProgress) {
- this._fadeProgress = progress;
- }
- }
- /**
- * Get spam fade progress
- * @return {number} 1 represents 100% progress.
- */
- get fadeProgress() {
- return this._fadeProgress;
- }
- /**
- * Number of posts by id
- * @return {number}
- */
- get postCount() {
- return this.posts.length;
- }
- /**
- * Is this post spam?
- * @return {boolean} true if spam
- */
- get isSpam() {
- return this._spamCertainty >= 1;
- }
- }
- /**
- * Base class for spamDetectionStrategies
- */
- class SpamDetectionStrategy {
- /**
- * Determine if the provided post is spam
- * @param {Element} post div.post
- * @return {boolean} true if is spam
- */
- isSpam(post) {
- return false;
- }
- }
- /**
- * Marks namefags as spam
- */
- class NameFagStrategy extends SpamDetectionStrategy {
- /**
- * Construct NameFagStrategy
- */
- constructor() {
- super();
- this.nameRegex = /^Anonymous( \(You\))?$/;
- this.badge = 'tag';
- this.badgeTitle = 'Namefag';
- }
- /**
- * Returns true if a namefag, sets spamCertainty to 100% for post
- * to begin fading
- * @param {Element} post div.post
- * @return {boolean} true if is namefag spam
- */
- isSpam(post) {
- const isNameFag = !window.bakerTools.qPostHighlighter.isQ(post) &&
- (
- !this.nameRegex.test(EightKun.getPostName(post)) ||
- EightKun.getPostTrip(post) != ''
- );
- if (isNameFag) {
- const stats = SpamFader.getPosterStats(post);
- stats.spamCertainty = 1;
- stats.fadeProgress = .2;
- stats.isNameFag = true;
- SpamFader.addSpamBadge(stats, this.badge, this.badgeTitle);
- }
- return isNameFag;
- }
- }
- /**
- * Marks floodfags with high post count as spam
- */
- class HighPostCountFagStrategy extends SpamDetectionStrategy {
- /**
- * Construct HighPostCountFagStrategy
- */
- constructor() {
- super();
- this.postCountSpamThreshold = 15;
- this.postCountHideThreshold = 25;
- this.badge = 'bullhorn';
- this.badgeTitle = 'High Post Count Fag';
- }
- /**
- * Returns true if the poster has posted more than the threshold
- * @param {Element} post div.post
- * @return {boolean} true if spam
- */
- isSpam(post) {
- if (EightKun.isPostFromOp(post)) {
- return;
- }
- const posterStats = SpamFader.getPosterStats(post);
- const highCountSpamCertainty =
- Math.min(1, posterStats.postCount / this.postCountSpamThreshold);
- posterStats.spamCertainty = highCountSpamCertainty;
- if (highCountSpamCertainty === 1) {
- posterStats.isHighPostCountFag = true;
- SpamFader.addSpamBadge(posterStats, this.badge, this.badgeTitle);
- }
- // We already hit spam threshold
- // Either we have hit threshold count or some other strategy says its spam
- if (posterStats.isSpam) {
- // Number of posts needed past threshold to hide
- const hideCount =
- this.postCountHideThreshold - this.postCountSpamThreshold;
- const progressIncrement = 1/hideCount;
- posterStats.fadeProgress += progressIncrement;
- }
- return posterStats.isSpam;
- }
- }
- /**
- * Marks floodfags with quick succession posts as spam
- */
- class FloodFagStrategy extends SpamDetectionStrategy {
- /**
- * Construct flood fag strategy
- */
- constructor() {
- super();
- this.postIntervalConsideredFlooding = 60; // seconds
- this.floodCountSpamThreshold = 5;
- this.floodCountHideThreshold = 10;
- this.badge = 'tint';
- this.badgeTitle = 'Floodfag';
- }
- /**
- * Returns true if a spam
- * @param {Element} post div.post
- * @return {boolean} true if is spam
- */
- isSpam(post) {
- const posterStats = SpamFader.getPosterStats(post);
- if (EightKun.isPostFromOp(post) || !this.isPostFlooded(posterStats)) {
- return;
- }
- posterStats.floodCount++;
- const floodSpamCertainty =
- Math.min(1, posterStats.floodCount / this.floodCountSpamThreshold);
- posterStats.spamCertainty = floodSpamCertainty;
- if (floodSpamCertainty === 1) {
- posterStats.isFloodFag = true;
- SpamFader.addSpamBadge(posterStats, this.badge, this.badgeTitle);
- }
- // We already hit spam threshold
- // Either we have hit threshold count or some other strategy says its spam
- if (posterStats.isSpam) {
- // Number of posts needed past threshold to hide
- const hideCount =
- this.floodCountHideThreshold - this.floodCountSpamThreshold;
- const progressIncrement = 1/hideCount;
- posterStats.fadeProgress += progressIncrement;
- }
- return posterStats.isSpam;
- }
- /**
- * Is this a flooded post?
- * @param {PosterStats} posterStats
- * @return {boolean} true if flooded
- */
- isPostFlooded(posterStats) {
- if (posterStats.posts.length <= 1) {
- return false;
- }
- const currentPost = posterStats.posts.slice(-1)[0];
- const previousPost = posterStats.posts.slice(-2)[0];
- const previousPostTime = EightKun.getPostTime(previousPost);
- const currentPostTime = EightKun.getPostTime(currentPost);
- return (currentPostTime - previousPostTime) <=
- this.postIntervalConsideredFlooding;
- }
- }
- /**
- * Marks breadshitters as spam
- */
- class BreadShitterFagStrategy extends SpamDetectionStrategy {
- // TODO: dont check for bread shitting on non research thread?
- /**
- * Construct flood fag strategy
- */
- constructor() {
- super();
- // Let's go easy, maybe its a newfag?
- this.breadShittingIncrement = .1;
- this.badge = 'clock-o';
- this.badgeTitle = 'Bread shitter';
- }
- /**
- * Returns true if a spam
- * @param {Element} post div.post
- * @return {boolean} true if is spam
- */
- isSpam(post) {
- const posterStats = SpamFader.getPosterStats(post);
- if (EightKun.isPostFromOp(post) || !this.isBreadShitter(post)) {
- return;
- }
- posterStats.breadShitCount++;
- posterStats.isBreadShitter = true;
- SpamFader.addSpamBadge(posterStats, this.badge, this.badgeTitle);
- posterStats.spamCertainty = 1;
- posterStats.fadeProgress += this.breadShittingIncrement;
- return posterStats.isSpam;
- }
- /**
- * Is this a bread shitting post?
- * @param {Element} post div.post
- * @return {boolean} true if bread shitter
- */
- isBreadShitter(post) {
- const dough = ResearchBread.getDoughPost();
- const doughTime = EightKun.getPostTime(dough);
- const postTime = EightKun.getPostTime(post);
- return postTime <= doughTime;
- }
- }
- /* exported StatsOverlay */
- /* global $, EightKun, QPostHighlighter, NotablePost, debounce */
- /**
- * Overlays bread stats (and some other controls) in the bottom right of the
- * screen.
- */
- class StatsOverlay {
- /**
- * Construct statsoverlay, html element, setup listeners
- */
- constructor() {
- this.id = 'bakertools-stats-overlay';
- this.maxPosts = 750;
- this.postCountId = 'bakertools-stats-post-count';
- this.userCountId = 'bakertools-stats-uid-count';
- this.qCountId = 'bakertools-stats-q-count';
- this.notableCountId = 'bakertools-stats-notable-count';
- this._createStyles();
- this._createElement();
- this._updateStats();
- $(document).on(EightKun.NEW_POST_EVENT, function(e, post) {
- this._updateStats();
- }.bind(this));
- }
- /**
- * Create styles for stats overlay
- */
- _createStyles() {
- const sheet = window.document.styleSheets[0];
- sheet.insertRule(`#${this.id} {
- padding: 5px;
- position: fixed;
- z-index: 100;
- float: right;
- right:28.25px;
- bottom: 28.25px;
- }`, sheet.cssRules.length);
- }
- /**
- * Create actual html element for style overlay
- */
- _createElement() {
- this.element = document.createElement('div');
- this.element.id = this.id;
- this.$goToLast = $(`
- <a href="javascript:void(0)" alt="last" title="Go to last reading location">
- <i class="fa fa-step-backward"></i>
- </a>`);
- this.saveLastReadingLocation = debounce(this.saveLastReadingLocation, 450);
- this.currentReadingLocation = $(window).scrollTop();
- $(window).scroll(function() {
- this.saveLastReadingLocation();
- }.bind(this));
- this.$goToLast.click(function() {
- $(window).scrollTop(this.lastReadingLocation);
- }.bind(this));
- $(this.element).append( `
- Posts: <span id="${this.postCountId}" ></span>
- UIDS: <span id="${this.userCountId}"></span>
- `);
- $(this.element).append(this.$goToLast);
- $(this.element).append(`
- <a href="#bottom" alt="to-bottom" title="Go to bottom">
- <i class="fa fa-angle-double-down"></i>
- </a>
- <a href="#top" alt="to-top" title="Go to top">
- <i class="fa fa-angle-double-up"></i>
- </a>
- <br/>
- Q's: <span id="${this.qCountId}" ></span>
- Notables: <span id="${this.notableCountId}"></span>
- `);
- document.body.appendChild(this.element);
- this._setPostCount($('div.post.reply').length);
- }
- /**
- * Save the last spot before scrolling or navigation
- */
- saveLastReadingLocation() {
- const scrollDistance = Math.abs(
- this.currentReadingLocation - $(window).scrollTop());
- const scrolledMoreThanThirdScreenHeight =
- scrollDistance > (window.innerHeight / 3);
- if (!scrolledMoreThanThirdScreenHeight) {
- return;
- }
- this.lastReadingLocation = this.currentReadingLocation;
- this.currentReadingLocation = $(window).scrollTop();
- }
- /**
- * Update the stats fields
- */
- _updateStats() {
- const postCount = $('#thread_stats_posts').text();
- if (postCount) {
- this._setPostCount(postCount);
- }
- // TODO: uids dont load at first load.
- $('#'+this.userCountId).text($('#thread_stats_uids').text() || '0');
- $('#'+this.qCountId).text(QPostHighlighter.qPosts.length);
- $('#'+this.notableCountId).text(NotablePost.getNotables().length);
- }
- /**
- * Set post count in overlay
- * @param {number} count
- */
- _setPostCount(count) {
- const progress = count/this.maxPosts;
- let postColor = 'green';
- if (progress >= .87) { // ~ 650 posts (100 posts left)
- postColor = 'red';
- } else if (progress >= .5) {
- postColor = 'goldenrod';
- }
- $('#'+this.postCountId).text(count).css({'color': postColor});
- }
- } // End StatsOverlay class
- /* global $, NavigationControl, EightKun, ResearchBread,
- ColorPicker, POST_BACKGROUND_CHANGE_EVENT, BakerWindow */
- /**
- * Highlight posts that (you)
- * Adds (You) navigation links to baker window
- */
- class YouHighlighter {
- /**
- * Construct YN object
- */
- constructor() {
- this.styleId = 'bakertools-you-styles';
- this.yous = [];
- this.ownPosts = [];
- this.showYouNavigationInBoardListId =
- 'bakertools-show-you-nav-in-boardlist';
- this.showOwnNavigationInBoardListId =
- 'bakertools-show-own-nav-in-boardlist';
- this._createStyles();
- this._setupBakerWindowControls();
- this._readSettings();
- this._initOwnAndYouPosts();
- this._setupListeners();
- }
- /**
- * Create styles
- */
- _createStyles() {
- $('head').append(`
- <style id='${this.styleId}'>
- ${EightKun.POST_SELECTOR}.${YouHighlighter.YOU_CLASS} {
- background: ${this.youColor};
- }
- ${EightKun.POST_SELECTOR}.${YouHighlighter.OWN_CLASS} {
- background: ${this.ownPostColor};
- }
- </style>
- `);
- }
- /**
- * Read settings from localStorage
- */
- _readSettings() {
- this.showYouNavigationInBoardList(JSON.parse(
- localStorage.getItem(
- YouHighlighter.SHOW_YOU_NAV_IN_BOARDLIST_SETTING),
- ));
- this.showOwnNavigationInBoardList(JSON.parse(
- localStorage.getItem(
- YouHighlighter.SHOW_OWN_NAV_IN_BOARDLIST_SETTING),
- ));
- }
- /**
- * Add (you) navigation to bakerwindow
- */
- _setupBakerWindowControls() {
- const youColorPicker = new ColorPicker(
- '(You) Post Color',
- 'Set background color of posts replying to (you)',
- YouHighlighter.YOU_COLOR_SETTING,
- YouHighlighter.DEFAULT_YOU_COLOR,
- (color) => this.youColor = color,
- );
- const ownPostColorPicker = new ColorPicker(
- 'Own Post Color',
- 'Set background color your own posts',
- YouHighlighter.OWN_COLOR_SETTING,
- YouHighlighter.DEFAULT_OWN_COLOR,
- (color) => this.ownPostColor = color,
- );
- window.bakerTools.mainWindow.addColorOption(ownPostColorPicker.element);
- window.bakerTools.mainWindow.addColorOption(youColorPicker.element);
- this.ownNavigation = new NavigationControl('Own Posts',
- this.getOwnPosts.bind(this), YouHighlighter.NEW_OWN_POST_EVENT);
- this.ownBoardListNav = new NavigationControl('Own',
- this.getOwnPosts.bind(this), YouHighlighter.NEW_OWN_POST_EVENT);
- $(EightKun.getTopBoardlist()).append(this.ownBoardListNav.element);
- $(this.ownBoardListNav.element).hide();
- window.bakerTools.mainWindow.addNavigationOption(`
- <div class='${BakerWindow.CONTROL_GROUP_CLASS}'>
- <label for="${this.showOwnNavigationInBoardListId}"
- title="Show navigation controls in board list bar" >
- Show Own Post Nav in Board List:
- </label>
- <input type="checkbox" id="${this.showOwnNavigationInBoardListId}"
- title="Show navigation controls in board list bar" /><br />
- </div>
- `);
- window.bakerTools.mainWindow.addNavigation(this.ownNavigation.element);
- this.youNavigation = new NavigationControl(`(You)'s`,
- this.getYous.bind(this), YouHighlighter.NEW_YOU_POST_EVENT);
- this.youBoardListNav = new NavigationControl('You',
- this.getYous.bind(this), YouHighlighter.NEW_YOU_POST_EVENT);
- $(EightKun.getTopBoardlist()).append(this.youBoardListNav.element);
- $(this.youBoardListNav.element).hide();
- window.bakerTools.mainWindow.addNavigationOption(`
- <div class='${BakerWindow.CONTROL_GROUP_CLASS}'>
- <label for="${this.showYouNavigationInBoardListId}"
- title="Show navigation controls in board list bar" >
- Show (You) Nav in Board List:
- </label>
- <input type="checkbox" id="${this.showYouNavigationInBoardListId}"
- title="Show navigation controls in board list bar" /><br />
- </div>
- `);
- window.bakerTools.mainWindow.addNavigation(this.youNavigation.element);
- }
- /**
- * Set the background color of posts replying to (you)
- * @param {string} color A valid css color value.
- * E.G. ('#ff00ee', 'rgba()' or 'blue')
- */
- set youColor(color) {
- this._youColor = color;
- document.getElementById(this.styleId)
- .sheet.cssRules[0].style.background = color;
- $(document).trigger(POST_BACKGROUND_CHANGE_EVENT, color);
- }
- /**
- * Get background color for posts replying to (you)
- */
- get youColor() {
- return this._youColor;
- }
- /**
- * Set the background color of your own posts
- * @param {string} color A valid css color value.
- * E.G. ('#ff00ee', 'rgba()' or 'blue')
- */
- set ownPostColor(color) {
- this._ownPostColor = color;
- document.getElementById(this.styleId)
- .sheet.cssRules[1].style.background = color;
- $(document).trigger(POST_BACKGROUND_CHANGE_EVENT, color);
- }
- /**
- * Get background color for your own posts
- */
- get ownPostColor() {
- return this._ownPostColor;
- }
- /**
- * Setup listeners for baker window controls
- */
- _setupListeners() {
- $('#'+this.showOwnNavigationInBoardListId).change(function(e) {
- this.showOwnNavigationInBoardList(e.target.checked);
- }.bind(this));
- $('#'+this.showYouNavigationInBoardListId).change(function(e) {
- this.showYouNavigationInBoardList(e.target.checked);
- }.bind(this));
- $(document).on(EightKun.NEW_POST_EVENT, function(e, post) {
- if (this.isAYou(post)) {
- this._addYouPost(post);
- }
- if (this.isOwnPost(post)) {
- this._addOwnPost(post);
- }
- }.bind(this));
- }
- /**
- * Show/hide you nav in boardlist
- *
- * @param {boolean} showYouNavInBoardList
- */
- showYouNavigationInBoardList(showYouNavInBoardList) {
- $('#'+this.showYouNavigationInBoardListId).prop('checked',
- showYouNavInBoardList);
- localStorage.setItem(YouHighlighter.SHOW_YOU_NAV_IN_BOARDLIST_SETTING,
- showYouNavInBoardList);
- if (showYouNavInBoardList) {
- $(this.youBoardListNav.element).show();
- } else {
- $(this.youBoardListNav.element).hide();
- }
- }
- /**
- * Show/hide own nav in boardlist
- *
- * @param {boolean} showOwnNavInBoardList
- */
- showOwnNavigationInBoardList(showOwnNavInBoardList) {
- $('#'+this.showOwnNavigationInBoardListId).prop('checked',
- showOwnNavInBoardList);
- localStorage.setItem(YouHighlighter.SHOW_OWN_NAV_IN_BOARDLIST_SETTING,
- showOwnNavInBoardList);
- if (showOwnNavInBoardList) {
- $(this.ownBoardListNav.element).show();
- } else {
- $(this.ownBoardListNav.element).hide();
- }
- }
- /**
- * Get (You)'s
- * @return {Array} of div.post
- */
- getYous() {
- return this.yous;
- }
- /**
- * Is the post replying to you
- * @param {Element} post div.post
- * @return {boolean} True if post is replying to you
- */
- isAYou(post) {
- return post.querySelector('.body')
- .innerHTML
- .indexOf('<small>(You)</small>') != -1;
- }
- /**
- * Is this your own post
- * @param {Element} post div.post
- * @return {boolean} True if post is you
- */
- isOwnPost(post) {
- return $(post).hasClass('you');
- }
- /**
- * Add you post and trigger event
- * @param {Element} post
- */
- _addYouPost(post) {
- this.yous.push(post);
- $(post).addClass(YouHighlighter.YOU_CLASS);
- $(document).trigger(YouHighlighter.NEW_YOU_POST_EVENT, post);
- }
- /**
- * Add own post and trigger event
- * @param {Element} post
- */
- _addOwnPost(post) {
- this.ownPosts.push(post);
- $(post).addClass(YouHighlighter.OWN_CLASS);
- $(document).trigger(YouHighlighter.NEW_OWN_POST_EVENT, post);
- }
- /**
- * Get own and you posts that are present at page load
- */
- _initOwnAndYouPosts() {
- const ownPosts = JSON.parse(localStorage.own_posts || '{}');
- const board = ResearchBread.BOARD_NAME;
- $('div.post').each(function(i, post) {
- const postId = $(post).attr('id').split('_')[1];
- if (ownPosts[board] &&
- ownPosts[board].indexOf(postId) !== -1) {
- this._addOwnPost(post);
- }
- EightKun.getReplyLinksFromPost(post).each(function(i, link) {
- const youPostId = EightKun.getPostNumberFromReplyLink(link);
- if (ownPosts[board] && ownPosts[board].indexOf(youPostId) !== -1) {
- this._addYouPost(post);
- }
- }.bind(this));
- }.bind(this));
- window.bakerTools.scrollBar.addPosts(this.ownPosts);
- window.bakerTools.scrollBar.addPosts(this.yous);
- }
- /**
- * Get own posts
- * @return {Array} of div.post
- */
- getOwnPosts() {
- return this.ownPosts;
- }
- }
- YouHighlighter.SHOW_YOU_NAV_IN_BOARDLIST_SETTING =
- 'bakertools-show-you-nav-in-boardlist';
- YouHighlighter.SHOW_OWN_NAV_IN_BOARDLIST_SETTING =
- 'bakertools-show-own-nav-in-boardlist';
- YouHighlighter.NEW_YOU_POST_EVENT =
- 'bakertools-new-you-post-event';
- YouHighlighter.NEW_OWN_POST_EVENT =
- 'bakertools-new-own-post-event';
- YouHighlighter.YOU_CLASS = 'bakertools-you-post';
- YouHighlighter.OWN_CLASS = 'bakertools-own-post';
- YouHighlighter.OWN_COLOR_SETTING =
- 'bakertools-own-post-color';
- YouHighlighter.YOU_COLOR_SETTING =
- 'bakertools-you-post-color';
- YouHighlighter.DEFAULT_OWN_COLOR = '#F8D2D2';
- YouHighlighter.DEFAULT_YOU_COLOR = '#E1B3DA';
- /* global ActivePage, $, QPostHighlighter, YouHighlighter, StatsOverlay,
- NotableHighlighter, BakerWindow, BlurImages, PreviousBreadHighlighter,
- NominatePostButtons, BreadList, ScrollbarNavigation, NotablePost,
- ImageBlacklist, PostRateChart, SpamFader */
- /**
- * MAIN
- */
- if (ActivePage.isThread()) { // Only setup the tools if we are on a thread
- $(document).ready(function() {
- console.info('Thanks for using bakertools! For God and Country! WWG1WGA');
- window.bakerTools = {};
- window.bakerTools.mainWindow = new BakerWindow();
- window.bakerTools.scrollBar = new ScrollbarNavigation([
- NotablePost.NEW_NOTABLE_POST_EVENT,
- YouHighlighter.NEW_OWN_POST_EVENT,
- YouHighlighter.NEW_YOU_POST_EVENT,
- QPostHighlighter.NEW_Q_POST_EVENT,
- ]);
- new BlurImages();
- window.bakerTools.PreviousBreadHighlighter =
- new PreviousBreadHighlighter();
- window.bakerTools.qPostHighlighter = new QPostHighlighter();
- window.bakerTools.notableHighlighter = new NotableHighlighter();
- window.bakerTools.youHighlighter = new YouHighlighter();
- window.bakerTools.statsOverlay = new StatsOverlay();
- new NominatePostButtons();
- new BreadList();
- new ImageBlacklist();
- new PostRateChart();
- new SpamFader([new NameFagStrategy(), new HighPostCountFagStrategy(),
- new FloodFagStrategy(), new BreadShitterFagStrategy()]);
- });
- }
- }(window.jQuery));
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement