Guest User

8kun Baker tools v0.6.0

a guest
Jan 14th, 2020
1,255
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2. ==8Kun Baker Tools v0.6.0==
  3.  
  4. ==Features:==
  5. '''Notables'''
  6. * Highlight posts that are marked notable (I.E. someone has replied and said
  7.   notable) in Yellow
  8. * Highlight nominating posts in Pink
  9. * Highlight nominating posts in posts mentions in Green
  10. * Filter to only nominating and notable posts, Q posts, Q replies
  11. * Generate notables post
  12. * Adds "Notable Nomination" button to posts that opens the
  13.   Quick Reply box and prefills it with a BAKER NOTABLE Template
  14. * Easy access to Breads
  15.  
  16. '''Q Posts'''
  17.   * Highlights Q Posts with white BG -> DARK TO LIGHT!
  18.   * Highlights Q posts in mentions (I.E. posts that get (YOU)'ed)
  19.   * Highlights links to Q Posts
  20.   * Cycle through Q Posts
  21.  
  22. '''Comfyness'''
  23.   * Highlight PB links
  24.   * Thread stats overlay with
  25.     * color coded reply count that goes from green to red as bread ages
  26.     * UID Count
  27.     * Jump To Bottom Link
  28.     * Jump To Bottom Top link
  29.   * Option to blur images until hover
  30.   * Cycle through (You)'s
  31.   * Cycle through own posts
  32.   * Image blacklist (AKA the NOPE button)
  33.  
  34. ==To Install:==
  35. 1. Copy this source code
  36. 2. Go to 8kun
  37. 3. Click "Options" in the top right
  38. 4. Choose "User JS" tab
  39. 5. Paste Baker tools JS
  40. 6. WWG1WGA
  41.  
  42. ==Changelog:==
  43. '''0.6.0'''
  44. * Navigation bar shows scroll location of q/you/notable
  45.   posts and allows jumping to posts
  46. * Notable navigation controls in baker window and board list
  47. * Persistent Image Blacklist (AKA Nope Button)
  48. * Many bugfixes
  49.  
  50. '''0.5.2''
  51. * Fixes bread list table population bug
  52.  
  53. '''0.5.0'''
  54. * Option to show Q/(YOU)/Own Post navigation controls in the boardlist
  55. * Option to hide Notable nomination button
  56. * List of research breads
  57. * BakerTools settings are now saved in local storage
  58.  
  59. '''0.4.0'''
  60. * Option to blur images until hover
  61. * Adds a "Notable Nomination" button to posts that opens the Quick Reply
  62.   box and prefills it with a BAKER NOTABLE Template
  63. * Add Q Post navigation links to the Baker Window
  64. * Add (You) navigation links to the Baker Window
  65. * Add own post navigation links to the Baker Window
  66. * Cleaned up baker window design
  67.  
  68. * More code cleanup and linting changes
  69.  
  70. '''0.3.0'''
  71. * Highlights Q Posts with white BG -> DARK TO LIGHT!
  72. * Highlights Q posts in mentions (I.E. posts that get (YOU)'ed)
  73. * Highlights links to Q Posts
  74.  
  75. * Refactored code into classes for easier maint.
  76.  
  77. '''0.2.0'''
  78. * Highlight pb links
  79. * Thread stats overlay with
  80.     * color coded reply count that goes from green to red as bread ages
  81.     * UID Count
  82.     * Jump To Bottom Link
  83.     * Jump To Bottom Top link
  84.  
  85. '''0.1.0'''
  86. Initial release:
  87. * Highlight notables and nominators
  88. * Filter to only show notables and nominators
  89. * Create notables post
  90.  
  91. Version History:
  92. https://pastebin.com/mPVxr7Lz 0.5.2 Fixes breadlist population issue
  93. https://pastebin.com/nEhm7yyY 0.5.1
  94. https://pastebin.com/i9sF0Rd3 0.4.0
  95. https://pastebin.com/kz9LrcE9 0.3.0
  96. https://pastebin.com/4aEFsPwK 0.2.0
  97. https://pastebin.com/eNmTtzdi 0.1.0
  98. */
  99. (function($) {
  100. "use strict";
  101.  
  102. /**
  103.  * Wrapper for 8kun active_page variable to determine the type of
  104.  * page the user is on.
  105.  */
  106. class ActivePage {
  107.   /**
  108.    * Are we currently on the thread index page?
  109.    * @return {boolean} True if on index
  110.    */
  111.   static isIndex() {
  112.     return window.active_page == ActivePage.Index;
  113.   }
  114.  
  115.   /**
  116.    * Are we currently on the thread catalog page?
  117.    * @return {boolean} True if on catalog
  118.    */
  119.   static isCatalog() {
  120.     return window.active_page == ActivePage.Catalog;
  121.   }
  122.  
  123.   /**
  124.    * Are we on a thread page?
  125.    * @return {boolean} True if on thread
  126.    */
  127.   static isThread() {
  128.     console.log(window.active_page == ActivePage.Thread);
  129.     return window.active_page == ActivePage.Thread;
  130.   }
  131. }
  132. ActivePage.Index = 'index';
  133. ActivePage.Catalog = 'catalog';
  134. ActivePage.Thread = 'thread';
  135.  
  136.  
  137. /* global $ */
  138. /**
  139.  * Creates first, prev, next, last navigation controls
  140.  */
  141. class NavigationControl {
  142.   /**
  143.    * Construct navigatio control manager object
  144.    *
  145.    * @param {string} label the label for the control
  146.    * @param {Function} updateFunction Called to get latest data
  147.    * @param {string} updateEventName Called to get latest data
  148.    */
  149.   constructor(label, updateFunction, updateEventName) {
  150.     const strippedName = label.replace(/(\s|\(|\)|'|"|:)/g, '');
  151.     this.label = label;
  152.     this.updateFunction = updateFunction;
  153.     this.updateEventName = updateEventName;
  154.     this.list = this.updateFunction();
  155.  
  156.     this.currentIndex = -1;
  157.     this.navigationClass = `bakertools-navcontrol-${strippedName}`;
  158.     this.indexChangeEvent =
  159.       `bakertools-navcontrol-${strippedName}-index-changed`;
  160.     this.currentIndexClass = `${this.navigationClass}-current-index`;
  161.     this.totalClass = `${this.navigationClass}-total`;
  162.     this.goToFirstClass = `${this.navigationClass}-goto-first`;
  163.     this.goToPreviousClass = `${this.navigationClass}-goto-prev`;
  164.     this.goToNextClass = `${this.navigationClass}-goto-next`;
  165.     this.goToLastClass = `${this.navigationClass}-goto-last`;
  166.  
  167.     this._setupStyles();
  168.     this._createElement();
  169.     this._setupListeners();
  170.   }
  171.  
  172.   /**
  173.    * setup styles for nav control
  174.    */
  175.   _setupStyles() {
  176.     const sheet = window.document.styleSheets[0];
  177.  
  178.     sheet.insertRule(`.boardlist
  179.       .${NavigationControl.containerClass}:before {
  180.       content: '[';
  181.       color: #89A;
  182.     }`, sheet.cssRules.length);
  183.  
  184.     sheet.insertRule(`.boardlist .${NavigationControl.containerClass}:after {
  185.       content: ']';
  186.       color: #89A;
  187.     }`, sheet.cssRules.length);
  188.  
  189.     sheet.insertRule(`.boardlist .${NavigationControl.containerClass} {
  190.       color: rgb(52, 52, 92);
  191.     }`, sheet.cssRules.length);
  192.   }
  193.  
  194.   /**
  195.    * Create nav element
  196.    */
  197.   _createElement() {
  198.     this.element = $(`
  199.     <span title="Navigate ${this.label}"
  200.         class="${NavigationControl.containerClass}">
  201.  
  202.         <label for="${this.navigationClass}">${this.label}:</label>
  203.         <span class="${this.navigationClass}
  204.                ${NavigationControl.navigationControlClass}">
  205.  
  206.                 <i class="fa fa-fast-backward ${this.goToFirstClass}"></i>
  207.                 <i class="fa fa-backward ${this.goToPreviousClass}"></i>
  208.  
  209.                 <span class="${this.currentIndexClass}">
  210.           ${this.currentIndex+1}
  211.         </span>
  212.         of
  213.         <span class="${this.totalClass}">${this.list.length}</span>
  214.  
  215.         <i class="fa fa-forward ${this.goToNextClass}"></i>
  216.         <i class="fa fa-fast-forward ${this.goToLastClass}"></i>
  217.       </span>
  218.     </span>
  219.     `).get(0);
  220.   }
  221.  
  222.   /**
  223.    * Setup button event listeners
  224.    */
  225.   _setupListeners() {
  226.     $(this.element).find('.'+this.goToFirstClass).click(function(e) {
  227.       this.goToFirstPost();
  228.     }.bind(this));
  229.  
  230.     $(this.element).find('.'+this.goToPreviousClass).click(function(e) {
  231.       this.goToPreviousPost();
  232.     }.bind(this));
  233.  
  234.     $(this.element).find('.'+this.goToNextClass).click(function(e) {
  235.       this.goToNextPost();
  236.     }.bind(this));
  237.  
  238.     $(this.element).find('.'+this.goToLastClass).click(function(e) {
  239.       this.goToLastPost();
  240.     }.bind(this));
  241.  
  242.     $(document).on(this.indexChangeEvent, function(e, index) {
  243.       if (this.currentIndex == index) return;
  244.       this.currentIndex = index;
  245.       this._setCurrentIndexControlValue(this.currentIndex + 1);
  246.     }.bind(this));
  247.  
  248.     $(document).on(this.updateEventName, function() {
  249.       this.list = this.updateFunction();
  250.       $(this.element).find(`.${this.totalClass}`).text(this.list.length);
  251.     }.bind(this));
  252.   }
  253.  
  254.   /**
  255.    * Scroll to first post
  256.    */
  257.   goToFirstPost() {
  258.     console.info(`Go to first ${this.label} post`);
  259.     if (!this.list.length) {
  260.       return;
  261.     }
  262.     this.currentIndex = 0;
  263.     this.scrollToCurrentPost();
  264.   }
  265.  
  266.   /**
  267.    * Scroll to next navigated post
  268.    */
  269.   goToPreviousPost() {
  270.     console.info(`Go to prev ${this.label} post`);
  271.     if (!this.list.length) {
  272.       return;
  273.     }
  274.     if (this.currentIndex <= 0) {
  275.       this.currentIndex = this.list.length - 1;
  276.     } else {
  277.       this.currentIndex--;
  278.     }
  279.     this.scrollToCurrentPost();
  280.   }
  281.   /**
  282.    * Scroll to next navigated post
  283.    */
  284.   goToNextPost() {
  285.     console.info(`Go to next ${this.label} post`);
  286.     if (!this.list.length) {
  287.       return;
  288.     }
  289.     const lastPostIndex = this.list.length - 1;
  290.     if (this.currentIndex >= lastPostIndex) {
  291.       this.currentIndex = 0;
  292.     } else {
  293.       this.currentIndex++;
  294.     }
  295.     this.scrollToCurrentPost();
  296.   }
  297.  
  298.   /**
  299.    * Scroll the last  post in this bread into view
  300.    */
  301.   goToLastPost() {
  302.     console.info(`Go to last ${this.label} post`);
  303.     if (!this.list.length) {
  304.       return;
  305.     }
  306.     const numPosts = this.list.length;
  307.     this.currentIndex = numPosts - 1;
  308.     this.scrollToCurrentPost();
  309.   }
  310.  
  311.   /**
  312.    * Scrolls the current selected  post into view
  313.    */
  314.   scrollToCurrentPost() {
  315.     const post = this.list[this.currentIndex];
  316.     $(post).get(0).scrollIntoView();
  317.  
  318.     // Trigger events for other views of this data
  319.     $(document).trigger(this.navigationClass + '-index-updated',
  320.         this.currentIndex);
  321.  
  322.     // Update our local control
  323.     this._setCurrentIndexControlValue(this.currentIndex + 1);
  324.  
  325.     window.scrollBy(0, -20);
  326.   }
  327.  
  328.   /**
  329.    * Sets the value of the current index in the UI
  330.    * @param {number} val
  331.    */
  332.   _setCurrentIndexControlValue(val) {
  333.     $('.'+this.currentIndexClass).text(this.currentIndex+1);
  334.   }
  335. }
  336. NavigationControl.containerClass = `bakertools-navcontrol-container`;
  337. NavigationControl.navigationControlClass = 'bakertools-navigation-control';
  338.  
  339.  
  340.  
  341. /* global ResearchBread, $, NotableHighlighter */
  342. /**
  343. * Wrapper for a post nominated as notable
  344. */
  345. class NotablePost {
  346.   /**
  347.    * Construct an empty notable post object
  348.    */
  349.   constructor() {
  350.     this.element = null;
  351.     this.postNumber = null;
  352.     this.description = '[DESCRIPTION]';
  353.     this.nominatingPosts = [];
  354.   }
  355.  
  356.   /**
  357.    * Create a notable post from a nominating post
  358.    *
  359.    * @param {Element} nominatingPost A post that is nominating a notable
  360.    * @return {NotablePost} a Notable post or NullNotablePost if it fails
  361.    */
  362.   static fromNominatingPost(nominatingPost) {
  363.     const notables = [];
  364.     ResearchBread.getReplyLinksFromPost(nominatingPost)
  365.         .each(function(idx, link) {
  366.           const postNumber = ResearchBread.getPostNumberFromReplyLink(link);
  367.           if (!NotablePost.findNotableByPostNumber(postNumber)) {
  368.             const notable = new NotablePost();
  369.  
  370.             const notablePostElement = $(`#reply_${postNumber}`).get(0);
  371.             if (notablePostElement) {
  372.               notable.setElement(notablePostElement);
  373.             } else {
  374.               // TODO: set pb description
  375.               // get the json from the post number
  376.               notable.postNumber = postNumber;
  377.             }
  378.             NotablePost._notables.push(notable);
  379.             if (notable.element) { // Not pb will need to figure something out
  380.               $(document).trigger(NotablePost.NEW_NOTABLE_POST_EVENT,
  381.                   notable.element);
  382.             }
  383.             notable.addNominatingPost(nominatingPost);
  384.             notables.push(notable);
  385.           }
  386.         });
  387.     return notables;
  388.   }
  389.  
  390.   /**
  391.    * Is this a NullNotablePost
  392.    * @return {boolean} false
  393.    */
  394.   isNull() {
  395.     return false;
  396.   }
  397.  
  398.   /**
  399.    * @return {Array<NotablePost>} Array of the current notables
  400.    */
  401.   static getNotables() {
  402.     return NotablePost._notables;
  403.   }
  404.  
  405.   /**
  406.    * Get notable posts as regular 8kun div.post
  407.    * @return {Array} of div.post
  408.    */
  409.   static getNotablesAsPosts() {
  410.     return NotablePost._notables
  411.         .filter((n) => n.element !== null)
  412.         .map((n) => n.element);
  413.   }
  414.  
  415.   /**
  416.    * @arg {number} postNumber The post number of notable
  417.    * @return {NotablePost}
  418.    */
  419.   static findNotableByPostNumber(postNumber) {
  420.     return NotablePost._notables.find((notable) => notable.postNumber ==
  421.       postNumber);
  422.   }
  423.  
  424.   /**
  425.    * Set the element of the post
  426.    * @arg {Element} element
  427.    */
  428.   setElement(element) {
  429.     this.element = element;
  430.     this._markAsNotable(this.element);
  431.     this.description = element.querySelector('.body')
  432.         .innerText
  433.         .replace(/\n/g, ' ');
  434.     this.postNumber = $(this.element).find('.intro .post_no')
  435.         .text()
  436.         .replace('No.', '');
  437.   }
  438.  
  439.   /**
  440.    * Get the reply shortlink for the post
  441.    * @return {string}
  442.    */
  443.   shortLink() {
  444.     return '>>' + this.postNumber;
  445.   }
  446.  
  447.   /**
  448.    * Add a nominator to the notable
  449.    *
  450.    * @param {Element} nominatingPost A .div.post that nominates this post
  451.    */
  452.   addNominatingPost(nominatingPost) {
  453.     this.nominatingPosts.push(nominatingPost);
  454.     this._markAsNominator(nominatingPost);
  455.     this._markNominatorInMentions(nominatingPost);
  456.   }
  457.  
  458.   /**
  459.    * @arg {Element} nominatorPost .post
  460.    */
  461.   _markAsNominator(nominatorPost) {
  462.     nominatorPost.classList.add(NotableHighlighter.NOMINATOR_CLASS);
  463.   }
  464.  
  465.   /**
  466.    * @arg {Element} post .post
  467.    */
  468.   _markAsNotable(post) {
  469.     post.classList.add(NotableHighlighter.NOTABLE_CLASS);
  470.   }
  471.  
  472.  
  473.   /**
  474.    * Gives links to nominators a special style in notable mentions
  475.    *
  476.    * @param {Element} nominatingPost A .div.post that is nominating this
  477.    *  notable
  478.    */
  479.   _markNominatorInMentions(nominatingPost) {
  480.     if (!this.element) {
  481.       console.info(`Notable post is null - possible pb/lb`);
  482.       return;
  483.     }
  484.     const nominatingPostId = nominatingPost.id.replace('reply_', '');
  485.     $(this.element).find('.mentioned-'+nominatingPostId)
  486.         .addClass(NotableHighlighter.NOMINATOR_CLASS);
  487.   }
  488. }
  489. NotablePost._notables = [];
  490. NotablePost.NULL = null; // NullNotablePost
  491. NotablePost.NEW_NOTABLE_POST_EVENT = 'bakertools-new-notable-post-event';
  492.  
  493. /**
  494.  * A null post that is returned when the post cant be found
  495.  */
  496. class NullNotablePost extends NotablePost {
  497.   /**
  498.    * Returns true because this is a null post
  499.    * @return {boolean} true
  500.    */
  501.   isNull() {
  502.     return true;
  503.   }
  504. }
  505.  
  506.  
  507. /* globals $ */
  508. /**
  509.  * Research Bread Class
  510.  */
  511. class ResearchBread {
  512.   /**
  513.    * Get an array of post bodies with dough posts filtered out
  514.    * @return {NodeList} of .post elements
  515.    */
  516.   static getPostsWithoutDough() {
  517.     const posts = Array.from(document
  518.         .querySelectorAll(ResearchBread.POST_SELECTOR));
  519.  
  520.     const filteredPosts = posts.filter(function(post) {
  521.       return !post.querySelector('.body')
  522.           .innerText.match(ResearchBread.DOUGH_POSTS_REGEX);
  523.     });
  524.  
  525.     return filteredPosts;
  526.   }
  527.  
  528.   /**
  529.    * Determine what the bread number is
  530.    * @return {number} the number of the research bread
  531.    */
  532.   static getBreadNumber() {
  533.     const breadNumberRegex = /#(.+?) /;
  534.     return document.querySelector(ResearchBread.OP_SUBJECT_SELECTOR)
  535.         .innerText
  536.         .match(breadNumberRegex)[1] || 'COULD NOT FIND BREAD NUMBER';
  537.   }
  538.  
  539.   /**
  540.    * @arg {Element} post .post
  541.    * @return {RegexMatch} Capture 1 is the number
  542.    */
  543.   static getFirstReplyLink(post) {
  544.     const match = post.querySelector('.body')
  545.         .innerHTML
  546.         .match(ResearchBread.REPLY_REGEX);
  547.     return match && match[1] || null;
  548.   }
  549.  
  550.   /**
  551.    * Get reply links in post
  552.    * @param {Element} post div.post
  553.    * @return {JQuery}
  554.    */
  555.   static getReplyLinksFromPost(post) {
  556.     return $(post).find(ResearchBread.REPLY_SELECTOR)
  557.         .filter(function(idx, link) {
  558.           return $(link).text().match(ResearchBread.REPLY_SHORTLINK_REGEX);
  559.         });
  560.   }
  561.  
  562.   /**
  563.    * Get the post number that is being replied to
  564.    * @param {Anchor} link
  565.    * @return {string}
  566.    */
  567.   static getPostNumberFromReplyLink(link) {
  568.     return $(link).text()
  569.         .match(ResearchBread.REPLY_SHORTLINK_REGEX)[1];
  570.   }
  571. }
  572. ResearchBread.BOARD_NAME = 'qresearch';
  573. ResearchBread.NEW_POST_EVENT = 'new_post';
  574. ResearchBread.OP_SUBJECT_SELECTOR = '.post.op > p > label > span.subject';
  575. ResearchBread.REPLY_SELECTOR = 'div.body:first a:not([rel="nofollow"])';
  576. ResearchBread.REPLY_SHORTLINK_REGEX = /^>>(\d+)$/;
  577. ResearchBread.POST_BODY_SELECTOR = '.post > .body';
  578. ResearchBread.POST_SELECTOR = '.post';
  579. ResearchBread.REPLY_REGEX = /highlightReply\('(.+?)'/;
  580. ResearchBread.DOUGH_POSTS_REGEX = new RegExp(
  581.     `^(Welcome To Q Research General|` +
  582.     `Global Announcements|` +
  583.     `War Room` +
  584.     `|QPosts Archives).*`);
  585.  
  586.  
  587. /* globals $, ResearchBread */
  588. /* exported ScrollbarNavigation */
  589. /**
  590.  * Scrollbar navigation
  591.  */
  592. class ScrollbarNavigation {
  593.   /**
  594.    * Construct a scrollbar nav
  595.    * @param {Array} addPostEvents List of event names that produce posts
  596.    *        to show on scrollbar
  597.    */
  598.   constructor(addPostEvents = []) {
  599.     this.id = 'bakertools-scrollbar-navigation';
  600.     this.showScrollbarNavigationId = 'bakertools-show-scrollbar-nav';
  601.     this.width = '20px';
  602.     this.posts = [];
  603.     this.coordsToPost = [];
  604.     this.addPostEvents = addPostEvents;
  605.  
  606.     // Debounce the draw function
  607.     this.draw = debounce(this.draw, 1000 * 2);
  608.  
  609.     this._setupBakerWindowControls();
  610.     this._setupStyles();
  611.     this._createElement();
  612.     this._readSettings();
  613.     this._setupListeners();
  614.   }
  615.  
  616.   /**
  617.    * Read settings from localStorage
  618.    */
  619.   _readSettings() {
  620.     let showScrollBar = JSON.parse(localStorage
  621.         .getItem(ScrollbarNavigation.SHOW_SCROLLBAR_NAV));
  622.  
  623.     showScrollBar = showScrollBar === null ? true : showScrollBar;
  624.  
  625.     this.showScrollBar(showScrollBar);
  626.   }
  627.  
  628.   /**
  629.    * Add hide/show option to bakerwindow
  630.    */
  631.   _setupBakerWindowControls() {
  632.     window.bakerTools.mainWindow
  633.         .addOption(`
  634.     <label for="${this.showScrollbarNavigationId}"
  635.       title="Show scrollbar navigation" >
  636.       Show Scrollbar Navigation:
  637.     </label>
  638.     <input type="checkbox" id="${this.showScrollbarNavigationId}"
  639.       title="Show scrollbar navigation" /><br />
  640.     `);
  641.   }
  642.  
  643.   /**
  644.    * Setup event listeners
  645.    */
  646.   _setupListeners() {
  647.     $('#'+this.showScrollbarNavigationId).change(function(e) {
  648.       this.showScrollBar(e.target.checked);
  649.     }.bind(this));
  650.  
  651.     $(document).on(ResearchBread.NEW_POST_EVENT, this.draw.bind(this));
  652.     $(window).on('resize', this.draw.bind(this));
  653.  
  654.     $('#'+this.id).click(function(e) {
  655.       Object.keys(this.coordsToPost).forEach(function(coords) {
  656.         const [top, bottom] = coords.split(',');
  657.         if (e.clientY >= (top - 10) && e.clientY <= (bottom+10)) {
  658.           const post = this.coordsToPost[coords];
  659.           $(post).get(0).scrollIntoView();
  660.           window.scrollBy(0, -20);
  661.         }
  662.       }.bind(this));
  663.     }.bind(this));
  664.  
  665.     this.addPostEvents.forEach(function(eventName) {
  666.       $(document).on(eventName, function(event, posts) {
  667.         this.addPosts(posts);
  668.       }.bind(this));
  669.     }.bind(this));
  670.   }
  671.  
  672.   /**
  673.    * Show/hide scrollbar
  674.    * @param {boolean} shouldShow Shows if true
  675.    */
  676.   showScrollBar(shouldShow) {
  677.     $('#'+this.showScrollbarNavigationId).prop('checked',
  678.         shouldShow);
  679.  
  680.     localStorage.setItem(ScrollbarNavigation.SHOW_SCROLLBAR_NAV, shouldShow);
  681.  
  682.     if (shouldShow) {
  683.       $(`#${this.id}`).show();
  684.     } else {
  685.       $(`#${this.id}`).hide();
  686.     }
  687.   }
  688.  
  689.   /**
  690.    * Setup styles for canvas
  691.    */
  692.   _setupStyles() {
  693.     $('head').append(`
  694.     <style id='${this.id + '-style'}'>
  695.       #${this.id} {
  696.         position: fixed;
  697.         top: 0;
  698.         right: 0;
  699.         height: 100%;
  700.         width: ${this.width};
  701.         background: #000000;
  702.         background: linear-gradient(
  703.           90deg,
  704.           rgba(0,0,0,1) 0%,
  705.           rgba(92,92,92,1) 50%,
  706.           rgba(0,0,0,1) 100%
  707.         );
  708.       }
  709.     </style>
  710.     `);
  711.   }
  712.  
  713.   /**
  714.    * Create the canvas
  715.    */
  716.   _createElement() {
  717.     $(document.body).append(`
  718.       <canvas id='${this.id}' width='${this.width}' height='300'>
  719.       </canvas>
  720.     `);
  721.   }
  722.  
  723.   /**
  724.    * Draw the scrollbar
  725.    */
  726.   draw() {
  727.     const canvas = document.getElementById(this.id);
  728.     canvas.height = window.innerHeight;
  729.     const ctx = canvas.getContext('2d');
  730.     if (!ctx) {
  731.       console.info('no ctx - is the element created yet?');
  732.       return;
  733.     }
  734.     ctx.clearRect(0, 0, canvas.width, canvas.height);
  735.  
  736.     const cachedHeight = $(document).height();
  737.     const scrollHeight = canvas.height;
  738.  
  739.     this.coordsToPost = [];
  740.  
  741.     this.posts.forEach(function(post) {
  742.       const color = $(post).css('backgroundColor');
  743.       const postRect = post.getBoundingClientRect();
  744.       const scrollLocationPercentage =
  745.           (window.scrollY + postRect.top) / cachedHeight;
  746.       const drawLocation = scrollLocationPercentage * scrollHeight;
  747.       const drawHeight = Math.max(
  748.           (postRect.height / cachedHeight) * scrollHeight,
  749.           4,
  750.       );
  751.       this.coordsToPost[[drawLocation, drawLocation + drawHeight]] = post;
  752.  
  753.       ctx.fillStyle = color;
  754.       ctx.fillRect(0, drawLocation, canvas.width, drawHeight);
  755.     }.bind(this));
  756.   }
  757.  
  758.   /**
  759.    * Add posts to scrollbar
  760.    * @param {Element|Array} post div.post
  761.    */
  762.   addPosts(post) {
  763.     if (Array.isArray(post)) {
  764.       post.forEach((p) => this.posts.push(p));
  765.     } else {
  766.       this.posts.push(post);
  767.     }
  768.     this.draw();
  769.   }
  770. }
  771. ScrollbarNavigation.SHOW_SCROLLBAR_NAV = 'bakertools-show-scrollbar-nav';
  772.  
  773. /**
  774.  * Returns a function, that, as long as it continues to be invoked, will not
  775. * be triggered. The function will be called after it stops being called for
  776. * N milliseconds. If `immediate` is passed, trigger the function on the
  777. * leading edge, instead of the trailing.
  778. * https://davidwalsh.name/javascript-debounce-function
  779. *
  780. * @param {Function} func
  781. * @param {number} wait
  782. * @param {boolean} immediate
  783. * @return {Function} debounced function
  784. */
  785. function debounce(func, wait, immediate) {
  786.   let timeout;
  787.   return function(...args) {
  788.     const context = this;
  789.     const later = function() {
  790.       timeout = null;
  791.       if (!immediate) func.apply(context, args);
  792.     };
  793.     const callNow = immediate && !timeout;
  794.     clearTimeout(timeout);
  795.     timeout = setTimeout(later, wait);
  796.     if (callNow) func.apply(context, args);
  797.   };
  798. }
  799.  
  800. /* globals $ */
  801. /* exported WindowElement */
  802. /**
  803.  * Class for windows
  804.  */
  805. class WindowElement {
  806.   /**
  807.    * Construct WindowElement
  808.    * @param {string} windowName
  809.    * @param {string} linkText
  810.    */
  811.   constructor(windowName, linkText) {
  812.     this.styleId = 'bakertools-WindowElement-basestyles';
  813.     this.id = `bakertools-${windowName}-window`;
  814.     this.linkText = linkText;
  815.     this.class = 'bakertools-WindowElement';
  816.     this.headerClass = 'bakertools-WindowElement-header';
  817.     this.windowCloseId = `bakertools-${windowName}-WindowElement-close`;
  818.     this.windowCloseClass = `bakertools-WindowElement-close`;
  819.     this.element = null;
  820.  
  821.     this._createWindowStyles();
  822.     this._createElement();
  823.     this._setupWindowLink();
  824.   }
  825.  
  826.   /**
  827.    * Create the window element
  828.    */
  829.   _createElement() {
  830.     this.element = document.createElement('div');
  831.     this.element.id = this.id;
  832.     $(this.element).addClass(this.class);
  833.  
  834.     this.element.innerHTML = `
  835.     <header class="${this.headerClass}">
  836.       <h3>${this.linkText}</h3>
  837.       <a id="${this.windowCloseId}" class='${this.windowCloseClass}'
  838.         href="javascript:void(0)">
  839.         <i class="fa fa-times"></i>
  840.       </a>
  841.     </header>
  842.     `;
  843.     document.body.appendChild(this.element);
  844.  
  845.     $(this.element).draggable();
  846.     $(this.element).hide();
  847.  
  848.     $('#'+this.windowCloseId).click(function(e) {
  849.       this.hide();
  850.     }.bind(this));
  851.   }
  852.  
  853.   /**
  854.    * Create CSS styles needed by the window
  855.    */
  856.   _createWindowStyles() {
  857.     if ($('#' + this.styleId).length) {
  858.       return;
  859.     }
  860.     $('head').append(`
  861.       <style id='${this.styleId}'>
  862.       .${this.class} {
  863.         width: 300px;
  864.         background-color: rgb(214, 218, 240);
  865.         position: fixed;
  866.         z-index: 100;
  867.         float: right;
  868.         right:28.25px;
  869.         border: 1px solid;
  870.       }
  871.  
  872.       .${this.class} .${this.headerClass} {
  873.         background: #98E;
  874.         border: solid 1px;
  875.         text-align: center;
  876.         margin: 0px;
  877.       }
  878.      
  879.       .${this.class} .${this.headerClass} h3 {
  880.         margin: 0;
  881.       }
  882.  
  883.       .${this.class} .${this.windowCloseClass} {
  884.         top: 0px;
  885.         right: 0px;
  886.         position: absolute;
  887.         margin-right: 3px;
  888.         font-size: 20px;
  889.       }
  890.  
  891.       .${this.class} details {
  892.         padding: 5px;
  893.       }
  894.  
  895.       .${this.class} summary {
  896.         margin: 0 0 8px;
  897.         font-weight: bold;
  898.         border-bottom: solid 2px;
  899.       }
  900.     </style>
  901.     `);
  902.   }
  903.  
  904.   /**
  905.    * Create link for show/hiding window, placed in boardlist bar
  906.    */
  907.   _setupWindowLink() {
  908.     this.link = document.createElement('a');
  909.     this.link.textContent = `[${this.linkText}]`;
  910.     this.link.style.cssText = 'float: right;';
  911.     this.link.title = this.linkText;
  912.     this.link.href = 'javascript:void(0)';
  913.     document.querySelector('.boardlist').appendChild(this.link);
  914.  
  915.     this.link.onclick = this.toggle.bind(this);
  916.   }
  917.  
  918.   /**
  919.    * Setup timeout for updating bread list
  920.    */
  921.   _setupListeners() {
  922.     // window.setTimeout(this.updateBreadList, 1000)
  923.   }
  924.  
  925.   /**
  926.    * Show the window
  927.    */
  928.   show() {
  929.     $(this.element).css({'top': 15});
  930.     $(this.element).show();
  931.   }
  932.  
  933.   /**
  934.    * Hide the window
  935.    */
  936.   hide() {
  937.     $(this.element).hide();
  938.   }
  939.  
  940.   /**
  941.    * Is the window visible?
  942.    * @return {boolean} true if window is visible
  943.    */
  944.   isVisible() {
  945.     return $(this.element).is(':visible');
  946.   }
  947.  
  948.   /**
  949.    * Toggle visibility of window
  950.    */
  951.   toggle() {
  952.     if (this.isVisible()) {
  953.       this.hide();
  954.     } else {
  955.       this.show();
  956.     }
  957.   }
  958. }
  959.  
  960. /* exported BakerWindow */
  961. /* global NavigationControl, $, WindowElement */
  962. /**
  963. * Baker Window
  964. */
  965. class BakerWindow extends WindowElement {
  966.   /**
  967.    * Construct Baker window element, register listeners
  968.    */
  969.   constructor() {
  970.     super('baker', 'Baker Tools');
  971.     this.bakerWindowStyleId = 'bakertools-bakerwindow-style';
  972.     this.bakerWindowOptionsId = 'bakertools-window-options';
  973.     this.bakerWindowNavigationId = 'bakertools-window-navigation';
  974.     this.bakerWindowBakerId = 'bakertools-window-baker';
  975.     this.bakerWindowBodyId = 'bakertools-bakerwindow-body';
  976.  
  977.     this._createStyles();
  978.     this._createBody();
  979.   }
  980.  
  981.   /**
  982.    * Create CSS styles needed by the window
  983.    */
  984.   _createStyles() {
  985.     if ($('#' + this.bakerWindowStyleId).length) {
  986.       return;
  987.     }
  988.     $('head').append(`
  989.     <style id='${this.bakerWindowStyleId}'>
  990.       #${this.id} #${this.bakerWindowNavigationId}
  991.       .${NavigationControl.containerClass} {
  992.         display: inline-block;
  993.         width: 100%;
  994.       }
  995.  
  996.       #${this.id} #${this.bakerWindowNavigationId}
  997.       .${NavigationControl.navigationControlClass} {
  998.         float: right;
  999.       }
  1000.     </style>
  1001.     `);
  1002.   }
  1003.  
  1004.   /**
  1005.    * Create the actual window HTML element
  1006.    */
  1007.   _createBody() {
  1008.     $('#'+this.id).append(`
  1009.     <form id="${this.bakerWindowBodyId}">
  1010.       <details id='${this.bakerWindowOptionsId}' open>
  1011.         <summary>Options</summary>
  1012.       </details>
  1013.       <details id='${this.bakerWindowNavigationId}' open>
  1014.         <summary>Navigation</summary>
  1015.       </details>
  1016.       <details id='${this.bakerWindowBakerId}' open>
  1017.         <summary>Baker Tools</summary>
  1018.       </details>
  1019.     </form>
  1020.     `);
  1021.   }
  1022.  
  1023.   /**
  1024.    * Add form controls to options section of baker window
  1025.    * @arg {Element} htmlContentString form controls
  1026.    */
  1027.   addOption(htmlContentString) {
  1028.     $('#'+this.bakerWindowOptionsId).append(htmlContentString);
  1029.   }
  1030.  
  1031.   /**
  1032.    * Add html elements to the navigation section of the baker window
  1033.    * @arg {Element} htmlContentString form controls
  1034.    */
  1035.   addNavigation(htmlContentString) {
  1036.     $('#'+this.bakerWindowNavigationId).append(htmlContentString);
  1037.   }
  1038.  
  1039.   /**
  1040.    * Add html elements to the baker section of the baker window
  1041.    * @arg {Element} htmlContentString form controls
  1042.    */
  1043.   addBaker(htmlContentString) {
  1044.     $('#'+this.bakerWindowBakerId).append(htmlContentString);
  1045.   }
  1046. } // end class BakerWindow
  1047.  
  1048. /* global $ */
  1049. /**
  1050. * Blur images until highlighted
  1051. */
  1052. class BlurImages {
  1053.   /**
  1054.    * Construct blur images object and setup styles
  1055.    */
  1056.   constructor() {
  1057.     this.blurImages = 'bakertools-blur-images';
  1058.     this.blurImagesStyleId = 'bakertools-blur-images-style';
  1059.     window.bakerTools.mainWindow.addOption(`
  1060.       <label for="${this.blurImages}">Blur Images Until Hover</label>
  1061.       <input type="checkbox" id="${this.blurImages}"
  1062.         title="Blur images until mouse hover" /></br>
  1063.     `);
  1064.  
  1065.     $('#'+this.blurImages).change(function(e) {
  1066.       this.setBlurImages(e.target.checked);
  1067.     }.bind(this));
  1068.  
  1069.     this._readSettings();
  1070.   }
  1071.  
  1072.   /**
  1073.    * Read settings from localStorage
  1074.    */
  1075.   _readSettings() {
  1076.     this.setBlurImages(JSON.parse(
  1077.         localStorage.getItem(
  1078.             BlurImages.BLUR_IMAGES_SETTING),
  1079.     ));
  1080.   }
  1081.  
  1082.   /**
  1083.    * Set whether or not images are blurred
  1084.    * @param {boolean} blurImages if true, blur images
  1085.    */
  1086.   setBlurImages(blurImages) {
  1087.     $('#'+this.blurImages).prop('checked',
  1088.         blurImages);
  1089.  
  1090.     localStorage.setItem(BlurImages.BLUR_IMAGES_SETTING,
  1091.         blurImages);
  1092.  
  1093.     if (blurImages) {
  1094.       $(`<style id='${this.blurImagesStyleId}' type='text/css'>
  1095.           .post-image {
  1096.               filter: blur(5px);
  1097.               transition: all 233ms;
  1098.           }
  1099.           .post-image:hover {
  1100.               filter: blur(.5px);
  1101.               transition: all 89ms;
  1102.           }
  1103.       </style>`).appendTo('head');
  1104.     } else {
  1105.       $(`#${this.blurImagesStyleId}`).remove();
  1106.     }
  1107.   }
  1108. }
  1109. BlurImages.BLUR_IMAGES_SETTING = 'bakertools-blur-images';
  1110.  
  1111.  
  1112.  
  1113. /* globals $, WindowElement, ResearchBread */
  1114. /* exported BreadList */
  1115. /**
  1116.  * Creates a list of breads for navigation comfyness
  1117.  */
  1118. class BreadList extends WindowElement {
  1119.   /**
  1120.    * Construct breadlist object
  1121.    */
  1122.   constructor() {
  1123.     super('breadlist', 'Bread List');
  1124.     $('#'+this.id).css('height', '400px');
  1125.     this.breadListWindowHeaderId = 'bakertools-breadlist-window-header';
  1126.     this.breadListWindowCloseId = 'bakertools-breadlist-window-close';
  1127.     this.breadListWindowBody = 'bakertools-breadlist-window-body';
  1128.     this.breadListTable = 'bakertools-breadlist-table';
  1129.     this.lastUpdatedId = 'bakertools-breadlist-lastupdated';
  1130.  
  1131.     this._breads = [];
  1132.     ResearchBread.BOARD_NAME = 'qresearch';
  1133.     this.breadRegex = /(.+)\s+#(\d+):\s+(.+?$)/;
  1134.     // /\(.+\) #\(\d+\): \(.+?$\)/;
  1135.     this.indexPage = `${window.location.protocol}//${window.location.host}` +
  1136.         `/${ResearchBread.BOARD_NAME}/`;
  1137.  
  1138.     this._createBody();
  1139.     this._setupStyles();
  1140.     this.updateBreadList();
  1141.     this._setupListeners();
  1142.   }
  1143.  
  1144.   /**
  1145.    * setup table styles
  1146.    */
  1147.   _setupStyles() {
  1148.     // https://stackoverflow.com/questions/21168521/table-fixed-header-and-scrollable-body
  1149.     $('head').append(`
  1150.     <style id='baketools-breadlist-window-styles'>
  1151.       #${this.id} {
  1152.         right: 380px;
  1153.       }
  1154.  
  1155.       #${this.breadListWindowBody} {
  1156.         overflow-y: auto;
  1157.         height: 365px;
  1158.         font-size: .8em;
  1159.       }
  1160.  
  1161.       #${this.breadListTable} {
  1162.         border-collapse: collapse;
  1163.         border-spacing: 0px;
  1164.       }
  1165.  
  1166.       #${this.breadListTable} thead th {
  1167.         position: sticky;
  1168.         top: 0;
  1169.       }
  1170.  
  1171.       #${this.breadListTable} th,
  1172.       #${this.breadListTable} td {
  1173.         border: 1px solid #000;
  1174.         border-top: 0;
  1175.       }
  1176.       #${this.breadListTable} thead th {
  1177.         box-shadow: 1px 1px 0 #000;
  1178.       }
  1179.     </style>
  1180.     `);
  1181.   }
  1182.  
  1183.   /**
  1184.    * Create the actual window HTML element
  1185.    */
  1186.   _createBody() {
  1187.     $('#'+this.id).append(`
  1188.     <div id='${this.breadListWindowBody}'>
  1189.       <table id='${this.breadListTable}'>
  1190.         <thead>
  1191.           <tr>
  1192.             <th>Group</th>
  1193.             <th>No.</th>
  1194.             <th>Bread</th>
  1195.             <th>replies</th>
  1196.           </tr>
  1197.         </thead>
  1198.         <tbody>
  1199.         </tbody>
  1200.       </table>
  1201.     </div>
  1202.     <footer>
  1203.       Last Updated: <span id="${this.lastUpdatedId}"></span>
  1204.     </footer>
  1205.     `);
  1206.   }
  1207.  
  1208.   /**
  1209.    * Setup timeout for updating bread list
  1210.    */
  1211.   _setupListeners() {
  1212.     window.setInterval(function(e) {
  1213.       this.updateBreadList();
  1214.     }.bind(this), 1000 * 60 * 2.5); // 2.5min update
  1215.   }
  1216.  
  1217.   /**
  1218.    * Get the list of breads
  1219.    */
  1220.   updateBreadList() {
  1221.     this.breads = [];
  1222.  
  1223.     const promises = [];
  1224.     for (let page = 0; page < 3; page++) {
  1225.       promises.push(
  1226.           $.getJSON(this.indexPage + `${page}.json`,
  1227.               this.parseIndex.bind(this)),
  1228.       );
  1229.     }
  1230.     Promise.all(promises).then(function() {
  1231.       this.breads.sort(function(a, b) {
  1232.         if (a.lastModified < b.lastModified) return -1;
  1233.         if (a.lastModified == b.lastModified) return 0;
  1234.         if (a.lastModified > b.lastModified) return 1;
  1235.       }).reverse();
  1236.       console.info(this.breads);
  1237.       this.populateBreadTable();
  1238.     }.bind(this));
  1239.   }
  1240.  
  1241.   /**
  1242.    * Parse index json for breads
  1243.    * @param {Object} index
  1244.    */
  1245.   parseIndex(index) {
  1246.     if (index && index.threads) {
  1247.       index.threads.forEach(function(thread) {
  1248.         const op = thread.posts[0];
  1249.         const match = op.sub.match(this.breadRegex);
  1250.  
  1251.         if (match) {
  1252.           const researchGroup = match[1];
  1253.           const breadNumber = match[2];
  1254.           const breadName = match[3];
  1255.           this.breads.push(new Bread(
  1256.               ResearchBread.BOARD_NAME,
  1257.               researchGroup,
  1258.               breadNumber,
  1259.               breadName,
  1260.               op.replies,
  1261.               op.no,
  1262.               op.last_modified,
  1263.           ));
  1264.         }
  1265.       }.bind(this)); // Index foreach
  1266.     } // if index and index.threads
  1267.   }
  1268.  
  1269.   /**
  1270.    * Populate the bread list table
  1271.    */
  1272.   populateBreadTable() {
  1273.     $(`#${this.breadListTable} tbody`).empty();
  1274.     this.breads.forEach(function(bread) {
  1275.       this._addBread(bread);
  1276.     }.bind(this));
  1277.  
  1278.     const lastUpdated = new Date();
  1279.     $('#'+this.lastUpdatedId).text(lastUpdated.toLocaleString());
  1280.   }
  1281.  
  1282.   /**
  1283.    * Add bread
  1284.    * @param {Bread} bread
  1285.    */
  1286.   _addBread(bread) {
  1287.     $(`#${this.breadListTable} tbody`).append(`
  1288.       <tr>
  1289.         <td><a href='${bread.url}'>${bread.researchGroup}</a></td>
  1290.         <td><a href='${bread.url}'>${bread.researchNumber}</a></td>
  1291.         <td><a href='${bread.url}'>${bread.breadName}</a></td>
  1292.         <td><a href='${bread.url}'>${bread.replies}</a></td>
  1293.       </tr>
  1294.     `);
  1295.   }
  1296. }
  1297.  
  1298. /**
  1299.  * Represents a research bread
  1300.  */
  1301. class Bread {
  1302.   /**
  1303.    * Construct a bread
  1304.    *
  1305.    * @param {string} boardName
  1306.    * @param {string} researchGroup
  1307.    * @param {number} researchNumber
  1308.    * @param {string} breadName
  1309.    * @param {number} replies
  1310.    * @param {number} postId
  1311.    * @param {number} lastModified
  1312.    */
  1313.   constructor(boardName, researchGroup, researchNumber, breadName,
  1314.       replies, postId, lastModified) {
  1315.     this.boardName = boardName;
  1316.     this.researchGroup = researchGroup;
  1317.     this.researchNumber = researchNumber;
  1318.     this.breadName = breadName;
  1319.     this.replies = replies;
  1320.     this.postId = postId;
  1321.     this.lastModified = lastModified;
  1322.   }
  1323.  
  1324.   /**
  1325.    * Get bread url
  1326.    *
  1327.    * @return {string} url to bread
  1328.    */
  1329.   get url() {
  1330.     return `${window.location.protocol}//${window.location.host}` +
  1331.         `/${this.boardName}/res/${this.postId}.html`;
  1332.   }
  1333. }
  1334.  
  1335. /* global $, ResearchBread */
  1336. /**
  1337. * Persistent image blacklist (AKA NOPE BUTTON)
  1338. */
  1339. class ImageBlacklist {
  1340.   /**
  1341.    * Construct ImageBlacklist object
  1342.    */
  1343.   constructor() {
  1344.     this.blacklist = [];
  1345.     this.styleId = 'bakertools-blacklist-style';
  1346.     this.postBlacklistButtonClass = 'bakertools-blacklist-post';
  1347.     this.imgBlacklistButtonClass = 'bakertools-blacklist-image';
  1348.     this.hidePostBlacklistButtonCheckboxId =
  1349.         'bakertools-hide-post-blacklist-buttons';
  1350.  
  1351.     this._setupBakerWindowControls();
  1352.     this._readSettings();
  1353.     this._setupStyles();
  1354.     this._setupListeners();
  1355.     this.removeBlacklistedImages();
  1356.     this.addBlacklistButtons();
  1357.   }
  1358.  
  1359.   /**
  1360.    * Add options to baker window
  1361.    */
  1362.   _setupBakerWindowControls() {
  1363.     window.bakerTools.mainWindow.addOption(`
  1364.     <label for="${this.hidePostBlacklistButtonCheckboxId}"
  1365.       title="Hide post 'Blacklist' buttons" >
  1366.       Hide "Blacklist" buttons
  1367.     </label>
  1368.     <input type="checkbox" id="${this.hidePostBlacklistButtonCheckboxId}"
  1369.       title="Hide post 'Blacklist' buttons" /><br />
  1370.     `);
  1371.   }
  1372.  
  1373.  
  1374.   /**
  1375.    * Show or hide the post blacklist buttons
  1376.    *
  1377.    * @param {boolean} show
  1378.    */
  1379.   showPostBlacklistButton(show) {
  1380.     $('#'+this.hidePostBlacklistButtonCheckboxId).prop('checked',
  1381.         show);
  1382.  
  1383.     localStorage.setItem(ImageBlacklist.HIDE_POST_BLACKLIST_BUTTON_SETTING,
  1384.         show);
  1385.  
  1386.     const styleId = 'baker-tools-post-blacklist-button-style';
  1387.     if (show) {
  1388.       $('head').append(`
  1389.         <style id='${styleId}'>
  1390.           .${this.postBlacklistButtonClass} {
  1391.             display: none;
  1392.           }
  1393.       `);
  1394.     } else {
  1395.       $(`#${styleId}`).remove();
  1396.     }
  1397.   }
  1398.  
  1399.   /**
  1400.    * Setup styles for blacklist buttons
  1401.    */
  1402.   _setupStyles() {
  1403.     $('head').append(`
  1404.       <style id='${this.styleId}'>
  1405.         .${this.imgBlacklistButtonClass} {
  1406.           padding: 0px;
  1407.           background-color: Transparent;
  1408.           background-repeat: no-repeat;
  1409.           border: none;
  1410.           overflow: hidden;
  1411.           outline: none;
  1412.           cursor: pointer;
  1413.         }
  1414.       </style>
  1415.     `);
  1416.   }
  1417.  
  1418.   /**
  1419.    * Read settings from localstorage
  1420.    */
  1421.   _readSettings() {
  1422.     this.loadBlacklist();
  1423.   }
  1424.  
  1425.   /**
  1426.    * Setup new post event listeners
  1427.    */
  1428.   _setupListeners() {
  1429.     $(document).on(ResearchBread.NEW_POST_EVENT, function(e, post) {
  1430.       this.addBlacklistButtonToPost(post);
  1431.     }.bind(this));
  1432.  
  1433.     $('#'+this.hidePostBlacklistButtonCheckboxId).change(function(e) {
  1434.       this.showPostBlacklistButton(e.target.checked);
  1435.     }.bind(this));
  1436.   }
  1437.  
  1438.   /**
  1439.    * Load blacklist from localStorage
  1440.    */
  1441.   loadBlacklist() {
  1442.     this.blacklist = JSON.parse(localStorage.imageBlacklist || '[]');
  1443.   }
  1444.  
  1445.   /**
  1446.    * Save blacklist to localStorage
  1447.    */
  1448.   saveBlacklist() {
  1449.     localStorage.imageBlacklist = JSON.stringify(this.blacklist);
  1450.   }
  1451.  
  1452.   /**
  1453.    * Add MD5 of an image to the blacklist
  1454.    * @param {string} md5 md5 hash of image
  1455.    */
  1456.   addToBlacklist(md5) {
  1457.     if (md5 && -1 === this.blacklist.indexOf(md5)) {
  1458.       this.blacklist.push(md5);
  1459.     }
  1460.   }
  1461.  
  1462.   /**
  1463.    * Blacklist images in post
  1464.    * @param {Element} post
  1465.    */
  1466.   blacklistPostImages(post) {
  1467.     $(post).find(ImageBlacklist.POST_IMG_SELECTOR).each(function(i, postImage) {
  1468.       const md5 = postImage.getAttribute('data-md5');
  1469.       this.addToBlacklist(md5);
  1470.       this.deletePostImage(postImage);
  1471.     }.bind(this));
  1472.   }
  1473.  
  1474.   /**
  1475.    * Remove blacklist images on page load
  1476.    * @return {number} number of images removed
  1477.    */
  1478.   removeBlacklistedImages() {
  1479.     let removed = 0;
  1480.     $(ImageBlacklist.POST_IMG_SELECTOR).each(function(i, postImage) {
  1481.       if (-1 !== this.blacklist.indexOf(postImage.getAttribute('data-md5'))) {
  1482.         this.deletePostImage(postImage);
  1483.         removed += 1;
  1484.       }
  1485.     }.bind(this));
  1486.     return removed;
  1487.   }
  1488.  
  1489.   /**
  1490.    * Add blacklist buttons to post images
  1491.    */
  1492.   addBlacklistButtons() {
  1493.     $('div.post').each(function(i, post) {
  1494.       this.addBlacklistButtonToPost(post);
  1495.     }.bind(this));
  1496.   }
  1497.  
  1498.   /**
  1499.    * Add blacklist buttons to post
  1500.    * @param {Element} post div.post
  1501.    */
  1502.   addBlacklistButtonToPost(post) {
  1503.     const postImageCount = $(post)
  1504.         .find(ImageBlacklist.POST_IMG_SELECTOR).length;
  1505.  
  1506.     if (postImageCount == 0) {
  1507.       return;
  1508.     }
  1509.  
  1510.     const postBlacklistButton = document.createElement('button');
  1511.     $(postBlacklistButton).addClass(this.postBlacklistButtonClass);
  1512.     $(postBlacklistButton).append(`
  1513.     <i class="fa fa-trash" style="color: crimson;"></i>
  1514.     Blacklist all post images`);
  1515.  
  1516.     $(postBlacklistButton).click(function(e) {
  1517.       e.preventDefault();
  1518.       $(post).hide();
  1519.       this.blacklistPostImages(post);
  1520.       this.saveBlacklist();
  1521.     }.bind(this));
  1522.  
  1523.     $(post).find('.post_modified').append(postBlacklistButton);
  1524.  
  1525.     $(post).find(ImageBlacklist.POST_IMG_SELECTOR).each(function(i, img) {
  1526.       const imgBlacklistButton = document.createElement('button');
  1527.       $(imgBlacklistButton).addClass(this.imgBlacklistButtonClass);
  1528.       $(imgBlacklistButton).append(`
  1529.       <i class="fa fa-trash" style="color: crimson;"
  1530.         title="Blacklist image"
  1531.         ></i>`);
  1532.  
  1533.       $(img)
  1534.           .parents('div.file')
  1535.           .find('.fileinfo')
  1536.           .prepend(imgBlacklistButton);
  1537.  
  1538.       $(imgBlacklistButton).click(function(e) {
  1539.         e.preventDefault();
  1540.         const md5 = img.getAttribute('data-md5');
  1541.         this.addToBlacklist(md5);
  1542.         this.deletePostImage(img);
  1543.         this.saveBlacklist();
  1544.       }.bind(this));
  1545.     }.bind(this));
  1546.   }
  1547.  
  1548.   /**
  1549.    * Delete post image
  1550.    * @param {Element} image
  1551.    */
  1552.   deletePostImage(image) {
  1553.     const imageParent = $(image).parent().parent();
  1554.     $(imageParent).append(`
  1555.       Image blacklisted
  1556.       ${image.getAttribute('data-md5')}
  1557.     `);
  1558.     $(image).remove();
  1559.   }
  1560. }
  1561. ImageBlacklist.POST_IMG_SELECTOR = 'img.post-image';
  1562. ImageBlacklist.HIDE_POST_BLACKLIST_BUTTON_SETTING =
  1563.   'bakertools-hide-post-blacklist-button';
  1564.  
  1565. /* global $, ResearchBread */
  1566. /**
  1567. * Add notable button to posts that opens quick reply
  1568. * and populates with a template message
  1569. */
  1570. class NominatePostButtons {
  1571.   /**
  1572.    * Construct NPB object and setup listeners
  1573.    */
  1574.   constructor() {
  1575.     this.nominateButtonClass = 'bakertools-nominate-button';
  1576.     this.hidePostNotableButtonCheckboxId = 'bakertools-hide-notables';
  1577.     this.bakerNotableHeader = '==BAKER NOTABLE==\n';
  1578.     this.notableReasonPlaceholder = '[REASON FOR NOTABLE HERE]';
  1579.  
  1580.     $('div.post.reply').each(function(i, post) {
  1581.       this._addButtonToPost(post);
  1582.     }.bind(this));
  1583.  
  1584.  
  1585.     this._setupBakerWindowControls();
  1586.     this._setupListeners();
  1587.     this._readSettings();
  1588.   }
  1589.  
  1590.   /**
  1591.    * Read settings from localStorage
  1592.    */
  1593.   _readSettings() {
  1594.     this.showNotableNominationButton(JSON.parse(
  1595.         localStorage.getItem(
  1596.             NominatePostButtons.HIDE_NOMINATE_BUTTON_SETTING),
  1597.     ));
  1598.   }
  1599.  
  1600.   /**
  1601.    * Add options to baker window
  1602.    */
  1603.   _setupBakerWindowControls() {
  1604.     window.bakerTools.mainWindow.addOption(`
  1605.     <br />
  1606.     <label for="${this.hidePostNotableButtonCheckboxId}"
  1607.       title="Hide post 'Notable' buttons" >
  1608.       Hide "Notable" buttons
  1609.     </label>
  1610.     <input type="checkbox" id="${this.hidePostNotableButtonCheckboxId}"
  1611.       title="Hide post 'Notable' buttons" /><br />
  1612.     `);
  1613.   }
  1614.  
  1615.   /**
  1616.    * Setup event listeners
  1617.    */
  1618.   _setupListeners() {
  1619.     $(document).on(ResearchBread.NEW_POST_EVENT, function(e, post) {
  1620.       this._addButtonToPost(post);
  1621.     }.bind(this));
  1622.  
  1623.     $('#'+this.hidePostNotableButtonCheckboxId).change(function(e) {
  1624.       this.showNotableNominationButton(e.target.checked);
  1625.     }.bind(this));
  1626.   }
  1627.  
  1628.   /**
  1629.    * Show or hide the notable nomination buttons
  1630.    *
  1631.    * @param {boolean} showNotableNominationButton
  1632.    */
  1633.   showNotableNominationButton(showNotableNominationButton) {
  1634.     $('#'+this.hidePostNotableButtonCheckboxId).prop('checked',
  1635.         showNotableNominationButton);
  1636.  
  1637.     localStorage.setItem(NominatePostButtons.HIDE_NOMINATE_BUTTON_SETTING,
  1638.         showNotableNominationButton);
  1639.  
  1640.     const styleId = 'baker-tools-notable-button-style';
  1641.     if (showNotableNominationButton) {
  1642.       $('head').append(`
  1643.         <style id='${styleId}'>
  1644.           .${this.nominateButtonClass} {
  1645.             display: none;
  1646.           }
  1647.       `);
  1648.     } else {
  1649.       $(`#${styleId}`).remove();
  1650.     }
  1651.   }
  1652.  
  1653.  
  1654.   /**
  1655.    * Add button to the provided post
  1656.    * @param {Element} post
  1657.    */
  1658.   _addButtonToPost(post) {
  1659.     const button = document.createElement('button');
  1660.     $(button).addClass(this.nominateButtonClass);
  1661.     $(button).append(`
  1662.       <i class="fa fa-star" style="color: goldenrod;"></i>
  1663.       Notable`);
  1664.  
  1665.     $(button).click(function(e) {
  1666.       const postNumber = $(post)
  1667.           .find('.intro .post_no')
  1668.           .text()
  1669.           .replace('No.', '');
  1670.       const href = $(post)
  1671.           .find('.intro .post_no')
  1672.           .get(0).href;
  1673.  
  1674.       // 8kun core - adds >>postnumber to- and unhides quickreply
  1675.       window.citeReply(postNumber, href);
  1676.  
  1677.       const quickReplyBody = $('#quick-reply #body');
  1678.       const oldText = quickReplyBody.val();
  1679.  
  1680.       quickReplyBody.val(oldText + this.bakerNotableHeader +
  1681.         this.notableReasonPlaceholder);
  1682.  
  1683.       // Don't ask me why i have to do this, ask CodeMonkeyZ
  1684.       // Not sure why citeReply which calls cite needs to set a timeout to
  1685.       // replace the body of the quickreply with itself.  We need to combat
  1686.       // that here
  1687.       // setTimeout(function() {
  1688.       //     var tmp = $('#quick-reply textarea[name="body"]').val();
  1689.       //     $('#quick-reply textarea[name="body"]').val('').focus().val(tmp);
  1690.       //  }, 1);
  1691.       // $(window).on('cite', function(e, id, with_link) {
  1692.       // TODO: Figure this out
  1693.       const self = this;
  1694.       setTimeout(function() {
  1695.         quickReplyBody.select();
  1696.         quickReplyBody.prop('selectionStart',
  1697.             oldText.length + self.bakerNotableHeader.length);
  1698.       }, 1.2);
  1699.     }.bind(this));
  1700.  
  1701.     $(post).find('.post_modified').append(button);
  1702.   }
  1703. }
  1704. NominatePostButtons.HIDE_NOMINATE_BUTTON_SETTING =
  1705.     'bakertools-hide-nominate-button';
  1706.  
  1707. /* global $, ResearchBread, NotablePost, NavigationControl */
  1708. /**
  1709. * Makes notable posts easier to see by highlighting posts that anons nominate
  1710. * as notable.
  1711. *
  1712. * If someone replies to a post and their post contains the word 'notable',
  1713. * the replied to post will be considered notable.
  1714. *
  1715. * Both the notable post and the nominator posts will be highlighted, as well
  1716. * as the nominator link in the notable's mentions will be highlighted.
  1717. */
  1718. class NotableHighlighter {
  1719.   /**
  1720.    * Construct notablehighlighter object, find and highlight
  1721.    * current notable sand setup listeners
  1722.    */
  1723.   constructor() {
  1724.     this.styleId = 'bakertooks-notable-style';
  1725.     this.NOMINATING_REGEX = /notable/i;
  1726.  
  1727.     this.showOnlyNotablesCheckboxId = 'bakertools-show-only-notable';
  1728.     this.createNotablePostButtonId = 'bakertools-create-notable-post';
  1729.     this.notableEditorId = 'bakertools-notable-editor';
  1730.     this.showNotableNavgationInBoardListId =
  1731.         'bakertools-show-notable-nav-in-boardlist';
  1732.  
  1733.     this._createStyles();
  1734.     this._setupBakerWindowControls();
  1735.     this.findNominatedNotables();
  1736.     this._setupListeners();
  1737.     this._readSettings();
  1738.   }
  1739.  
  1740.   /**
  1741.    * Read settings from local storage
  1742.    */
  1743.   _readSettings() {
  1744.     this.setOnlyShowNotables(JSON.parse(
  1745.         localStorage.getItem(
  1746.             NotableHighlighter.ONLY_SHOW_NOTABLES_SETTING),
  1747.     ));
  1748.     this.showNotableNavigationInBoardList(JSON.parse(
  1749.         localStorage
  1750.             .getItem(NotableHighlighter.SHOW_NOTABLE_NAV_IN_BOARDLIST_SETTING),
  1751.     ));
  1752.   }
  1753.  
  1754.   /**
  1755.    * Create styles that determine how notables are highlighted
  1756.    */
  1757.   _createStyles() {
  1758.     $('head').append(`
  1759.     <style id='${this.styleId}'>
  1760.     .thread div.post.${NotableHighlighter.NOTABLE_CLASS} {
  1761.       background-color: #FFFFCC;
  1762.     }
  1763.     /* less specificity than notable so it has less preference */
  1764.     div.post.${NotableHighlighter.NOMINATOR_CLASS} {  
  1765.       background-color: #FFCCE5;  
  1766.     }
  1767.     div.post.reply .mentioned .${NotableHighlighter.NOMINATOR_CLASS} {
  1768.       color: #00CC00;
  1769.       font-weight: bold;
  1770.       font-size: 1.5em;
  1771.     }
  1772.     </style>
  1773.     `);
  1774.   }
  1775.  
  1776.   /**
  1777.    * Add controls to the bakerwindow
  1778.    */
  1779.   _setupBakerWindowControls() {
  1780.     const notablePostsTitle = `Only show, notables, nominators, q, q replied
  1781.       posts`;
  1782.  
  1783.     window.bakerTools.mainWindow.addOption(`
  1784.     <label for="${this.showOnlyNotablesCheckboxId}"
  1785.       title="${notablePostsTitle}" >
  1786.       Only Show Notable/Nomination Posts:
  1787.     </label>
  1788.     <input type="checkbox" id="${this.showOnlyNotablesCheckboxId}"
  1789.       title="${notablePostsTitle}" />
  1790.     `);
  1791.  
  1792.  
  1793.     window.bakerTools.mainWindow.addBaker(`
  1794.     <button type="button" id="${this.createNotablePostButtonId}"
  1795.       title="Create notables list post based on current nominated notables" >
  1796.       Create Notable Post
  1797.     </button>
  1798.     <textarea id="${this.notableEditorId}"></textarea>
  1799.     `);
  1800.  
  1801.     window.bakerTools.mainWindow
  1802.         .addOption(`
  1803.     <br /><br />
  1804.     <label for="${this.showNotableNavigationInBoardListId}"
  1805.       title="Show navigation controls in board list bar" >
  1806.       Show Notable Nav in Board List:
  1807.     </label>
  1808.     <input type="checkbox" id="${this.showNotableNavgationInBoardListId}"
  1809.       title="Show navigation controls in board list bar" /><br />
  1810.     `);
  1811.  
  1812.     this.navigation = new NavigationControl('Notables',
  1813.         () => NotablePost.getNotablesAsPosts(),
  1814.         NotablePost.NEW_NOTABLE_POST_EVENT);
  1815.     window.bakerTools.mainWindow
  1816.         .addNavigation(this.navigation.element);
  1817.  
  1818.     this.boardListNav = new NavigationControl('Notables',
  1819.         () => NotablePost.getNotablesAsPosts(),
  1820.         NotablePost.NEW_NOTABLE_POST_EVENT);
  1821.  
  1822.     $('.boardlist:first').append(this.boardListNav.element);
  1823.     $(this.boardListNav.element).hide();
  1824.   }
  1825.  
  1826.   /**
  1827.    * Setup listeners for new posts, bakerwindow controls, etc
  1828.    */
  1829.   _setupListeners() {
  1830.     $('#'+this.showOnlyNotablesCheckboxId).change(function(e) {
  1831.       this.setOnlyShowNotables(e.target.checked);
  1832.     }.bind(this));
  1833.  
  1834.     $('#'+this.createNotablePostButtonId).click(function() {
  1835.       if ($('#'+this.notableEditorId).val()) {
  1836.         if (!confirm(`If you continue, any changes you made will be
  1837.             overwritten!`)) {
  1838.           return;
  1839.         }
  1840.       }
  1841.       $('#'+this.notableEditorId).val(this.createNotablesPost());
  1842.     }.bind(this));
  1843.  
  1844.     $(document).on(ResearchBread.NEW_POST_EVENT, function(e, post) {
  1845.       this.checkNewPostsForNotables(post);
  1846.     }.bind(this));
  1847.  
  1848.     $('#'+this.showNotableNavgationInBoardListId).change(function(e) {
  1849.       this.showNotableNavigationInBoardList(e.target.checked);
  1850.     }.bind(this));
  1851.   }
  1852.  
  1853.   /**
  1854.    * Show or hide notable nav control in the boardlist
  1855.    *
  1856.    * @param {boolean} show
  1857.    */
  1858.   showNotableNavigationInBoardList(show) {
  1859.     $('#'+this.showNotableNavgationInBoardListId).prop('checked',
  1860.         show);
  1861.  
  1862.     localStorage
  1863.         .setItem(NotableHighlighter.SHOW_NOTABLE_NAV_IN_BOARDLIST_SETTING,
  1864.             show);
  1865.  
  1866.     if (show) {
  1867.       $(this.boardListNav.element).show();
  1868.     } else {
  1869.       $(this.boardListNav.element).hide();
  1870.     }
  1871.   }
  1872.  
  1873.   /**
  1874.    * Create the notables post for review
  1875.    * @return {string} Returns the notable post string
  1876.    */
  1877.   createNotablesPost() {
  1878.     const notables = NotablePost.getNotables();
  1879.     const breadNumber = ResearchBread.getBreadNumber();
  1880.     let post = `'''#${breadNumber}'''\n\n`;
  1881.  
  1882.     notables.forEach(function(notable) {
  1883.       post += `${notable.shortLink()} ${notable.description}\n\n`;
  1884.     });
  1885.  
  1886.     return post;
  1887.   }
  1888.  
  1889.   /**
  1890.    * Checks a post for notable nominations
  1891.    * @param {Element} post
  1892.    */
  1893.   checkNewPostsForNotables(post) {
  1894.     $(post).removeAttr('style'); // TODO: try removing
  1895.  
  1896.     if (this.isNominatingPost(post)) {
  1897.       NotablePost.fromNominatingPost(post);
  1898.     }
  1899.   }
  1900.  
  1901.   /**
  1902.    * Finds posts that are being tagged as notable.
  1903.    *
  1904.    * I.E. Finding any post that has been replied to by a post with the string
  1905.    * "notable" in it. Maybe at somepoint this can be smarter.  Q give me some
  1906.    * dwave snow white tech!
  1907.    *
  1908.    * Highlights notable posts in yellow
  1909.    * Highlights nominating posts in pink <3
  1910.    * Highlights nominating posts in mentions
  1911.    * Add nominee count to post
  1912.    * @return {Array<NotablePost>}
  1913.    */
  1914.   findNominatedNotables() {
  1915.     const postsWithoutDough = ResearchBread.getPostsWithoutDough();
  1916.  
  1917.     // ^s to ignore notables review posts
  1918.     const nominatingPosts = postsWithoutDough
  1919.         .filter((post) => this.isNominatingPost(post));
  1920.  
  1921.     nominatingPosts.forEach(function(nominatingPost) {
  1922.       NotablePost.fromNominatingPost(nominatingPost);
  1923.     });
  1924.     console.log(NotablePost.getNotables());
  1925.     return NotablePost.getNotables();
  1926.   }
  1927.  
  1928.   /**
  1929.    * Is the post nominating a notable
  1930.    * @arg {Element} post .post
  1931.    * @return {boolean} True if post nominates a notable
  1932.    */
  1933.   isNominatingPost(post) {
  1934.     const postContainsNotable = post.textContent
  1935.         .search(this.NOMINATING_REGEX) != -1;
  1936.     const postIsReplying = ResearchBread.getReplyLinksFromPost(post).length;
  1937.     return postContainsNotable && postIsReplying;
  1938.   }
  1939.  
  1940.   /**
  1941.    * Toggle whether only the notable/nominee posts are shown or not
  1942.    * @arg {boolean} onlyShowNotables boolean If true, only show
  1943.    *               notables/nominators, else show all
  1944.    */
  1945.   setOnlyShowNotables(onlyShowNotables) {
  1946.     $('#'+this.showOnlyNotablesCheckboxId).prop('checked', onlyShowNotables);
  1947.  
  1948.     localStorage.setItem(NotableHighlighter.ONLY_SHOW_NOTABLES_SETTING,
  1949.         onlyShowNotables);
  1950.  
  1951.     const notableOrNominationPostsSelector =
  1952.       `div.post.${NotableHighlighter.NOTABLE_CLASS},
  1953.       div.post.${NotableHighlighter.NOMINATOR_CLASS}`;
  1954.     const notableOrNominationPostBreaksSelector =
  1955.       `div.post.${NotableHighlighter.NOTABLE_CLASS}+br,
  1956.       div.post.${NotableHighlighter.NOMINATOR_CLASS}+br`;
  1957.     const onlyShowNotablesStyleId = 'bakertools-only-show-notables';
  1958.  
  1959.     if (onlyShowNotables) {
  1960.       $(`<style id='${onlyShowNotablesStyleId}' type='text/css'>
  1961.         div.reply:not(.post-hover),
  1962.         div.post+br {
  1963.           display: none !important;
  1964.           visibility: hidden !important;
  1965.         }
  1966.         ${notableOrNominationPostsSelector},
  1967.         ${notableOrNominationPostBreaksSelector} {
  1968.           display: inline-block !important;
  1969.           visibility: visible !important;
  1970.         }
  1971.         </style>`).appendTo('head');
  1972.     } else {
  1973.       $(`#${onlyShowNotablesStyleId}`).remove();
  1974.       // For whatever reason, when the non notable posts are filtered and new
  1975.       // posts come through the auto_update, the posts are created with
  1976.       // style="display:block" which messes up display.  Remove style attr
  1977.       // TODO: can we remove this now that we have !important?
  1978.       $(ResearchBread.POST_SELECTOR).removeAttr('style');
  1979.     }
  1980.   }
  1981.  
  1982.   /**
  1983.    * Retrieves only show notable ssetting from localStorage
  1984.    * @return {boolean} true if only show notables is turned on
  1985.    */
  1986.   getOnlyShowNotables() {
  1987.     return localStorage
  1988.         .getItem(NotableHighlighter.ONLY_SHOW_NOTABLES_SETTING);
  1989.   }
  1990. }
  1991. NotableHighlighter.NOMINATOR_CLASS = 'bakertools-notable-nominator';
  1992. NotableHighlighter.NOTABLE_CLASS = 'bakertools-notable';
  1993. NotableHighlighter.ONLY_SHOW_NOTABLES_SETTING =
  1994.     'bakertools-only-show-notables';
  1995. NotableHighlighter.SHOW_NOTABLE_NAV_IN_BOARDLIST_SETTING =
  1996.     'bakertools-show-notable-nav-in-boardlist';
  1997.  
  1998. /* global $ */
  1999. /**
  2000. * Highlights previous bread post links
  2001. */
  2002. class PreviousBreadHighlighter {
  2003.   /**
  2004.    * Construct pb highlighter object, setup listeners
  2005.    */
  2006.   constructor() {
  2007.     this.previousBreadClass = 'bakertools-PreviousBread';
  2008.     this._linkSelector = 'div.body > p.body-line.ltr > a';
  2009.  
  2010.     this._setupStyles();
  2011.  
  2012.     const links = $(this._linkSelector).filter('[onClick]');
  2013.     links.each(function(index, link) {
  2014.       this.markLinkIfPreviousBread(link);
  2015.     }.bind(this));
  2016.  
  2017.     this._setupListeners();
  2018.   }
  2019.  
  2020.   /**
  2021.    * Setup styles for pb links
  2022.    */
  2023.   _setupStyles() {
  2024.     const sheet = window.document.styleSheets[0];
  2025.     sheet.insertRule(`div.post.reply div.body
  2026.       a.${this.previousBreadClass} {
  2027.         color: #8B0000;
  2028.     }`, sheet.cssRules.length);
  2029.     sheet.insertRule(`a.${this.previousBreadClass}::after {
  2030.       content: " (pb)";
  2031.     }`, sheet.cssRules.length);
  2032.   }
  2033.  
  2034.   /**
  2035.    * Setup listeners for pb highlighting
  2036.    */
  2037.   _setupListeners() {
  2038.     $(document).on(ResearchBread.NEW_POST_EVENT, function(e, post) {
  2039.       $(post).find(this._linkSelector)
  2040.           .each((index, link) => this.markLinkIfPreviousBread(link));
  2041.     }.bind(this));
  2042.   }
  2043.  
  2044.   /**
  2045.    * Marks the link if it is pb
  2046.    *
  2047.    * @param {Anchor} link
  2048.    */
  2049.   markLinkIfPreviousBread(link) {
  2050.     const breadFileName = document.location.pathname.split('/').slice(-1)[0];
  2051.     const linkFileName = link.href.split('/').slice(-1)[0].split('#')[0];
  2052.  
  2053.     if ($(link).attr('onclick').search(ResearchBread.REPLY_REGEX) !=1 &&
  2054.           breadFileName != linkFileName) {
  2055.       $(link).addClass(this.previousBreadClass);
  2056.     }
  2057.   }
  2058. }
  2059.  
  2060.  
  2061.  
  2062. /* global $, ResearchBread, NavigationControl */
  2063. /**
  2064. * Highlight Q posts, replies to q, q replies.
  2065. * Adds navigation to baker window
  2066. */
  2067. class QPostHighlighter {
  2068.   /**
  2069.    * Construct qposthighlighter object and setup listeners
  2070.    */
  2071.   constructor() {
  2072.     this.qPostClass = 'bakertools-q-post';
  2073.     this.qReplyClass = 'bakertools-q-reply';
  2074.     this.qMentionClass = 'bakertools-q-mention';
  2075.     this.qLinkClass = 'bakertools-q-link';
  2076.     this._linkSelector = 'div.body > p.body-line.ltr > a';
  2077.     this.currentQTripCode = null;
  2078.  
  2079.     this.showQNavigationInBoardListId =
  2080.         'bakertools-show-q-nav-in-boardlist';
  2081.  
  2082.     this._setupStyles();
  2083.     this._getCurrentQTripFromBread();
  2084.     this._findQPosts();
  2085.     this._setupBakerWindowControls();
  2086.     this._setupListeners();
  2087.     this._readSettings();
  2088.   }
  2089.  
  2090.   /**
  2091.    * Read settings from localStorage
  2092.    */
  2093.   _readSettings() {
  2094.     this.showQNavigationInBoardList(JSON.parse(
  2095.         localStorage
  2096.             .getItem(QPostHighlighter.SHOW_Q_NAV_IN_BOARDLIST_SETTING),
  2097.     ));
  2098.   }
  2099.  
  2100.   /**
  2101.    * Setup styles for highlighting q posts
  2102.    */
  2103.   _setupStyles() {
  2104.     const sheet = window.document.styleSheets[0];
  2105.     // Dark to light
  2106.     sheet.insertRule(`div.post.reply.${this.qPostClass} {
  2107.       background: #FFFFFF !important;
  2108.       display: inline-block !important;
  2109.       visibility: visible !important;
  2110.     }`, sheet.cssRules.length);
  2111.  
  2112.     // Enlightened by the Q but still not all the way
  2113.     sheet.insertRule(`div.post.reply.${this.qReplyClass} {
  2114.       background: #DDDDDD;
  2115.       display: inline-block !important;
  2116.       visibility: visible !important;
  2117.     }`, sheet.cssRules.length);
  2118.  
  2119.     // Trippy mentions
  2120.     sheet.insertRule(`div.post.reply .intro .${this.qMentionClass},
  2121.     .${this.qLinkClass}
  2122.     {
  2123.       padding:1px 3px 1px 3px;
  2124.       background-color:black;
  2125.       border-radius:8px;
  2126.       border:1px solid #bbbbee;
  2127.       color:gold;
  2128.       background: linear-gradient(300deg, #ff0000, #ff0000, #ff0000, #bbbbbb,
  2129.                 #4444ff);
  2130.       background-size: 800% 800%;
  2131.  
  2132.       -webkit-animation: Patriot 5s ease infinite;
  2133.       -moz-animation: Patriot 5s ease infinite;
  2134.       -o-animation: Patriot 5s ease infinite;
  2135.       animation: Patriot 5s ease infinite;
  2136.       -webkit-text-fill-color: transparent;
  2137.        
  2138.       background: -o-linear-gradient(transparent, transparent);
  2139.       -webkit-background-clip: text;
  2140.     }`, sheet.cssRules.length);
  2141.   }
  2142.  
  2143.   /**
  2144.    * Get Q's current trip code from the bread
  2145.    */
  2146.   _getCurrentQTripFromBread() {
  2147.     const tripCodeMatch = $('div.post.op')
  2148.         .text()
  2149.         .match(/Q's Trip-code: Q (.+?\s)/);
  2150.  
  2151.     if (!tripCodeMatch) {
  2152.       console.error('Could not find Q\'s tripcode');
  2153.       return;
  2154.     }
  2155.     this.currentQTripCode = tripCodeMatch[1].split(' ')[0];
  2156.   }
  2157.  
  2158.   /**
  2159.    * Find current Q posts in bread
  2160.    */
  2161.   _findQPosts() {
  2162.     const posts = ResearchBread.getPostsWithoutDough();
  2163.  
  2164.     $(posts).each(function(i, post) {
  2165.       this._doItQ(post);
  2166.     }.bind(this));
  2167.   }
  2168.  
  2169.   /**
  2170.    * Check if the post is Q
  2171.    * WWG1WGA
  2172.    *
  2173.    * @param {Element} post a div.post
  2174.    */
  2175.   _doItQ(post) {
  2176.     if (this._markIfQPost(post)) { // Q Post, lets check for q replies
  2177.       const qPostNumber = $(post)
  2178.           .find('.intro .post_no')
  2179.           .text()
  2180.           .replace('No.', '');
  2181.  
  2182.       const links = $(post)
  2183.           .find(this._linkSelector)
  2184.           .filter('[onClick]');
  2185.  
  2186.       $(links).each(function(i, link) {
  2187.         const postNumber = link.href.split('#')[1];
  2188.         // Enlightened post
  2189.         $(`#reply_${postNumber}`).addClass(this.qReplyClass);
  2190.  
  2191.         const metionLinkSelector = `#reply_${postNumber} .intro .mentioned a`;
  2192.         $(metionLinkSelector).each(function(i, mentionAnchor) {
  2193.           const mentionPostNumber = $(mentionAnchor).text().replace('>>', '');
  2194.           if (mentionPostNumber == qPostNumber) {
  2195.             $(mentionAnchor).addClass(this.qMentionClass);
  2196.           }
  2197.         }.bind(this));
  2198.       }.bind(this));
  2199.     } else { // Not Q, but lets check if this post replies to Q
  2200.       const links = $(post).find(this._linkSelector).filter('[onClick]');
  2201.  
  2202.       $(links).each(function(i, link) {
  2203.         const postNumber = link.href.split('#')[1];
  2204.         const replyPost = document.querySelector(`#reply_${postNumber}`);
  2205.         if (this._isQ(replyPost)) {
  2206.           $(link).addClass(this.qLinkClass);
  2207.         }
  2208.       }.bind(this));
  2209.     }
  2210.   }
  2211.  
  2212.   /**
  2213.    * @arg {Element} post div.post.reply
  2214.    * @return {boolean} true if it is a q post
  2215.    */
  2216.   _markIfQPost(post) {
  2217.     let isQ = false;
  2218.     if (this._isQ(post)) {
  2219.       isQ = true;
  2220.       $(post).addClass(this.qPostClass);
  2221.       QPostHighlighter.qPosts.push(post);
  2222.  
  2223.       $(document).trigger(QPostHighlighter.NEW_Q_POST_EVENT, post);
  2224.     }
  2225.     return isQ;
  2226.   }
  2227.  
  2228.   /**
  2229.    * Is the post Q?
  2230.    * @param {Element} post a div.post.reply
  2231.    * @return {boolean} true if the post is Q
  2232.    */
  2233.   _isQ(post) {
  2234.     return this._getTripCodeOfPost(post) == this.currentQTripCode;
  2235.   }
  2236.  
  2237.   /**
  2238.    * Get the trip code of the provided post
  2239.    * @param {Element} post
  2240.    * @return {string} tripcode
  2241.    */
  2242.   _getTripCodeOfPost(post) {
  2243.     return $(post).find('.intro span.trip').text();
  2244.   }
  2245.  
  2246.   /**
  2247.    * Add Q post navigation to bakerwindow
  2248.    */
  2249.   _setupBakerWindowControls() {
  2250.     window.bakerTools.mainWindow
  2251.         .addOption(`
  2252.     <label for="${this.showQNavigationInBoardListId}"
  2253.       title="Show navigation controls in board list bar" >
  2254.       Show Q Nav in Board List:
  2255.     </label>
  2256.     <input type="checkbox" id="${this.showQNavigationInBoardListId}"
  2257.       title="Show navigation controls in board list bar" /><br />
  2258.     `);
  2259.  
  2260.     this.navigation = new NavigationControl('Q Posts',
  2261.         () => QPostHighlighter.qPosts, QPostHighlighter.NEW_Q_POST_EVENT);
  2262.     window.bakerTools.mainWindow
  2263.         .addNavigation(this.navigation.element);
  2264.  
  2265.     this.boardListNav = new NavigationControl('Q Posts',
  2266.         () => QPostHighlighter.qPosts, QPostHighlighter.NEW_Q_POST_EVENT);
  2267.  
  2268.     $('.boardlist:first').append(this.boardListNav.element);
  2269.     $(this.boardListNav.element).hide();
  2270.   }
  2271.  
  2272.  
  2273.   /**
  2274.    * Setup listeners for new posts
  2275.    */
  2276.   _setupListeners() {
  2277.     $(document).on(ResearchBread.NEW_POST_EVENT, function(e, post) {
  2278.       this._doItQ(post);
  2279.     }.bind(this));
  2280.  
  2281.     $('#'+this.showQNavigationInBoardListId).change(function(e) {
  2282.       this.showQNavigationInBoardList(e.target.checked);
  2283.     }.bind(this));
  2284.   }
  2285.  
  2286.   /**
  2287.    * Show or hide q nav control in the boardlist
  2288.    *
  2289.    * @param {boolean} showNavInBoardList
  2290.    */
  2291.   showQNavigationInBoardList(showNavInBoardList) {
  2292.     $('#'+this.showQNavigationInBoardListId).prop('checked',
  2293.         showNavInBoardList);
  2294.  
  2295.     localStorage.setItem(QPostHighlighter.SHOW_Q_NAV_IN_BOARDLIST_SETTING,
  2296.         showNavInBoardList);
  2297.  
  2298.     if (showNavInBoardList) {
  2299.       $(this.boardListNav.element).show();
  2300.     } else {
  2301.       $(this.boardListNav.element).hide();
  2302.     }
  2303.   }
  2304. }
  2305. QPostHighlighter.qPosts = [];
  2306. QPostHighlighter.NEW_Q_POST_EVENT = 'bakertools-new-q-post';
  2307. QPostHighlighter.SHOW_Q_NAV_IN_BOARDLIST_SETTING =
  2308.     'bakertools-show-q-nav-in-boardlist';
  2309.  
  2310. /* exported StatsOverlay */
  2311. /* global $, ResearchBread, QPostHighlighter, NotablePost */
  2312. /**
  2313. * Overlays bread stats (and some other controls) in the bottom right of the
  2314. * screen.
  2315. * TODO: create add method
  2316. */
  2317. class StatsOverlay {
  2318.   /**
  2319.    * Construct statsoverlay, html element, setup listeners
  2320.    */
  2321.   constructor() {
  2322.     this.id = 'bakertools-stats-overlay';
  2323.     this.maxPosts = 750;
  2324.     this.postCountId = 'bakertools-stats-post-count';
  2325.     this.userCountId = 'bakertools-stats-uid-count';
  2326.     this.qCountId = 'bakertools-stats-q-count';
  2327.     this.notableCountId = 'bakertools-stats-notable-count';
  2328.  
  2329.     this._createStyles();
  2330.     this._createElement();
  2331.     this._updateStats();
  2332.     $(document).on(ResearchBread.NEW_POST_EVENT, function(e, post) {
  2333.       this._updateStats();
  2334.     }.bind(this));
  2335.   }
  2336.  
  2337.   /**
  2338.    * Create styles for stats overlay
  2339.    */
  2340.   _createStyles() {
  2341.     const sheet = window.document.styleSheets[0];
  2342.     sheet.insertRule(`#${this.id} {
  2343.       padding: 5px;
  2344.       position: fixed;
  2345.       z-index: 100;
  2346.       float: right;
  2347.       right:28.25px;
  2348.       bottom: 28.25px;
  2349.     }`, sheet.cssRules.length);
  2350.   }
  2351.  
  2352.   /**
  2353.    * Create actual html element for style overlay
  2354.    */
  2355.   _createElement() {
  2356.     this.element = document.createElement('div');
  2357.     this.element.id = this.id;
  2358.  
  2359.     this.element.innerHTML = `
  2360.     Posts: <span id="${this.postCountId}" ></span>
  2361.     UIDS: <span id="${this.userCountId}"></span>
  2362.     <a href="#bottom" alt="to-bottom">
  2363.       <i class="fa fa-angle-double-down"></i>
  2364.     </a>
  2365.     <a href="#top" alt="to-top"><i class="fa fa-angle-double-up"></i></a><br/>
  2366.  
  2367.     Q's: <span id="${this.qCountId}" ></span>
  2368.    Notables: <span id="${this.notableCountId}"></span>
  2369.    `;
  2370.    document.body.appendChild(this.element);
  2371.    this._setPostCount($('div.post.reply').length);
  2372.  }
  2373.  
  2374.  /**
  2375.   * Update the stats fields
  2376.   */
  2377.  _updateStats() {
  2378.    const postCount = $('#thread_stats_posts').text();
  2379.  
  2380.    if (postCount) {
  2381.      this._setPostCount(postCount);
  2382.    }
  2383.    $('#'+this.userCountId).text($('#thread_stats_uids').text());
  2384.    $('#'+this.qCountId).text(QPostHighlighter.qPosts.length);
  2385.    $('#'+this.notableCountId).text(NotablePost.getNotables().length);
  2386.  }
  2387.  
  2388.  /**
  2389.   * Set post count in overlay
  2390.   * @param {number} count
  2391.   */
  2392.  _setPostCount(count) {
  2393.    const progress = count/this.maxPosts;
  2394.    let postColor = 'green';
  2395.    if (progress >= .87) { // ~ 650 posts (100 posts left)
  2396.      postColor = 'red';
  2397.    } else if (progress >= .5) {
  2398.      postColor = 'goldenrod';
  2399.    }
  2400.    $('#'+this.postCountId).text(count).css({'color': postColor});
  2401.  }
  2402. } // End StatsOverlay class
  2403.  
  2404. /* global $, NavigationControl, ResearchBread */
  2405. /**
  2406. * Highlight posts that (you)
  2407. * Adds (You) navigation links to baker window
  2408. */
  2409. class YouHighlighter {
  2410.  /**
  2411.   * Construct YN object
  2412.   */
  2413.  constructor() {
  2414.    this.yous = [];
  2415.    this.ownPosts = [];
  2416.  
  2417.    this.showYouNavigationInBoardListId =
  2418.        'bakertools-show-you-nav-in-boardlist';
  2419.  
  2420.    this.showOwnNavigationInBoardListId =
  2421.        'bakertools-show-own-nav-in-boardlist';
  2422.  
  2423.    this._initOwnAndYouPosts();
  2424.    this._setupBakerWindowControls();
  2425.    this._readSettings();
  2426.    this._setupListeners();
  2427.  }
  2428.  
  2429.  /**
  2430.   * Read settings from localStorage
  2431.   */
  2432.  _readSettings() {
  2433.    this.showYouNavigationInBoardList(JSON.parse(
  2434.        localStorage.getItem(
  2435.            YouHighlighter.SHOW_YOU_NAV_IN_BOARDLIST_SETTING),
  2436.    ));
  2437.  
  2438.    this.showOwnNavigationInBoardList(JSON.parse(
  2439.        localStorage.getItem(
  2440.            YouHighlighter.SHOW_OWN_NAV_IN_BOARDLIST_SETTING),
  2441.    ));
  2442.  }
  2443.  
  2444.  /**
  2445.   * Add (you) navigation to bakerwindow
  2446.   */
  2447.  _setupBakerWindowControls() {
  2448.    const youLabel = `(You)'s`;
  2449.     this.youNavigation = new NavigationControl(youLabel,
  2450.         this.getYous.bind(this), YouHighlighter.NEW_YOU_POST_EVENT);
  2451.  
  2452.     this.youBoardListNav = new NavigationControl(youLabel,
  2453.         this.getYous.bind(this), YouHighlighter.NEW_YOU_POST_EVENT);
  2454.  
  2455.     $('.boardlist:first').append(this.youBoardListNav.element);
  2456.     $(this.youBoardListNav.element).hide();
  2457.  
  2458.     window.bakerTools.mainWindow.addOption(`
  2459.     <label for="${this.showYouNavigationInBoardListId}"
  2460.       title="Show navigation controls in board list bar" >
  2461.       Show (You) Nav in Board List:
  2462.     </label>
  2463.     <input type="checkbox" id="${this.showYouNavigationInBoardListId}"
  2464.       title="Show navigation controls in board list bar" /><br />
  2465.     `);
  2466.  
  2467.     const ownLabel = `Own Posts`;
  2468.     this.ownNavigation = new NavigationControl(ownLabel,
  2469.         this.getOwnPosts.bind(this), YouHighlighter.NEW_OWN_POST_EVENT);
  2470.  
  2471.     this.ownBoardListNav = new NavigationControl(ownLabel,
  2472.         this.getOwnPosts.bind(this), YouHighlighter.NEW_OWN_POST_EVENT);
  2473.  
  2474.     $('.boardlist:first').append(this.ownBoardListNav.element);
  2475.     $(this.ownBoardListNav.element).hide();
  2476.  
  2477.     window.bakerTools.mainWindow.addOption(`
  2478.     <label for="${this.showOwnNavigationInBoardListId}"
  2479.       title="Show navigation controls in board list bar" >
  2480.       Show Own Post Nav in Board List:
  2481.     </label>
  2482.     <input type="checkbox" id="${this.showOwnNavigationInBoardListId}"
  2483.       title="Show navigation controls in board list bar" /><br />
  2484.     `);
  2485.  
  2486.     window.bakerTools.mainWindow.addNavigation(this.youNavigation.element);
  2487.     window.bakerTools.mainWindow.addNavigation(this.ownNavigation.element);
  2488.   }
  2489.  
  2490.   /**
  2491.    * Setup listeners for baker window controls
  2492.    */
  2493.   _setupListeners() {
  2494.     $('#'+this.showOwnNavigationInBoardListId).change(function(e) {
  2495.       this.showOwnNavigationInBoardList(e.target.checked);
  2496.     }.bind(this));
  2497.  
  2498.     $('#'+this.showYouNavigationInBoardListId).change(function(e) {
  2499.       this.showYouNavigationInBoardList(e.target.checked);
  2500.     }.bind(this));
  2501.  
  2502.     $(document).on(ResearchBread.NEW_POST_EVENT, function(e, post) {
  2503.       if (this.isAYou(post)) {
  2504.         this._addYouPost(post);
  2505.       }
  2506.       if (this.isOwnPost(post)) {
  2507.         this._addOwnPost(post);
  2508.       }
  2509.     }.bind(this));
  2510.   }
  2511.  
  2512.   /**
  2513.    * Show/hide you nav in boardlist
  2514.    *
  2515.    * @param {boolean} showYouNavInBoardList
  2516.    */
  2517.   showYouNavigationInBoardList(showYouNavInBoardList) {
  2518.     $('#'+this.showYouNavigationInBoardListId).prop('checked',
  2519.         showYouNavInBoardList);
  2520.  
  2521.     localStorage.setItem(YouHighlighter.SHOW_YOU_NAV_IN_BOARDLIST_SETTING,
  2522.         showYouNavInBoardList);
  2523.  
  2524.     if (showYouNavInBoardList) {
  2525.       $(this.youBoardListNav.element).show();
  2526.     } else {
  2527.       $(this.youBoardListNav.element).hide();
  2528.     }
  2529.   }
  2530.  
  2531.   /**
  2532.    * Show/hide own nav in boardlist
  2533.    *
  2534.    * @param {boolean} showOwnNavInBoardList
  2535.    */
  2536.   showOwnNavigationInBoardList(showOwnNavInBoardList) {
  2537.     $('#'+this.showOwnNavigationInBoardListId).prop('checked',
  2538.         showOwnNavInBoardList);
  2539.  
  2540.     localStorage.setItem(YouHighlighter.SHOW_OWN_NAV_IN_BOARDLIST_SETTING,
  2541.         showOwnNavInBoardList);
  2542.  
  2543.     if (showOwnNavInBoardList) {
  2544.       $(this.ownBoardListNav.element).show();
  2545.     } else {
  2546.       $(this.ownBoardListNav.element).hide();
  2547.     }
  2548.   }
  2549.  
  2550.   /**
  2551.    * Get (You)'s
  2552.    * @return {Array} of div.post
  2553.    */
  2554.   getYous() {
  2555.     return this.yous;
  2556.   }
  2557.  
  2558.   /**
  2559.    * Is the post replying to you
  2560.    * @param {Element} post div.post
  2561.    * @return {boolean} True if post is replying to you
  2562.    */
  2563.   isAYou(post) {
  2564.     return post.querySelector('.body')
  2565.         .innerHTML
  2566.         .indexOf('<small>(You)</small>') != -1;
  2567.   }
  2568.  
  2569.   /**
  2570.    * Is this your own post
  2571.    * @param {Element} post div.post
  2572.    * @return {boolean} True if post is you
  2573.    */
  2574.   isOwnPost(post) {
  2575.     return $(post).hasClass('you');
  2576.   }
  2577.  
  2578.   /**
  2579.    * Add you post and trigger event
  2580.    * @param {Element} post
  2581.    */
  2582.   _addYouPost(post) {
  2583.     this.yous.push(post);
  2584.     $(document).trigger(YouHighlighter.NEW_YOU_POST_EVENT, post);
  2585.   }
  2586.  
  2587.   /**
  2588.    * Add own post and trigger event
  2589.    * @param {Element} post
  2590.    */
  2591.   _addOwnPost(post) {
  2592.     this.ownPosts.push(post);
  2593.     $(document).trigger(YouHighlighter.NEW_OWN_POST_EVENT, post);
  2594.   }
  2595.  
  2596.   /**
  2597.    * Get own and you posts that are present at page load
  2598.    */
  2599.   _initOwnAndYouPosts() {
  2600.     const ownPosts = JSON.parse(localStorage.own_posts || '{}');
  2601.     const board = ResearchBread.BOARD_NAME;
  2602.  
  2603.     $('div.post').each(function(i, post) {
  2604.       const postId = $(post).attr('id').split('_')[1];
  2605.       if (ownPosts[board] &&
  2606.           ownPosts[board].indexOf(postId) !== -1) {
  2607.         this._addOwnPost(post);
  2608.       }
  2609.  
  2610.       ResearchBread.getReplyLinksFromPost(post).each(function(i, link) {
  2611.         const youPostId = ResearchBread.getPostNumberFromReplyLink(link);
  2612.  
  2613.         if (ownPosts[board] && ownPosts[board].indexOf(youPostId) !== -1) {
  2614.           this._addYouPost(post);
  2615.         }
  2616.       }.bind(this));
  2617.     }.bind(this));
  2618.  
  2619.     window.bakerTools.scrollBar.addPosts(this.ownPosts);
  2620.     window.bakerTools.scrollBar.addPosts(this.yous);
  2621.   }
  2622.  
  2623.   /**
  2624.    * Get own posts
  2625.    * @return {Array} of div.post
  2626.    */
  2627.   getOwnPosts() {
  2628.     return this.ownPosts;
  2629.   }
  2630. }
  2631. YouHighlighter.SHOW_YOU_NAV_IN_BOARDLIST_SETTING =
  2632.     'bakertools-show-you-nav-in-boardlist';
  2633. YouHighlighter.SHOW_OWN_NAV_IN_BOARDLIST_SETTING =
  2634.     'bakertools-show-own-nav-in-boardlist';
  2635. YouHighlighter.NEW_YOU_POST_EVENT =
  2636.     'bakertools-new-you-post-event';
  2637. YouHighlighter.NEW_OWN_POST_EVENT =
  2638.     'bakertools-new-own-post-event';
  2639.  
  2640. /* global ActivePage, $, QPostHighlighter, YouHighlighter, StatsOverlay,
  2641.  NotableHighlighter, BakerWindow, BlurImages, PreviousBreadHighlighter,
  2642.  NominatePostButtons, BreadList, ScrollbarNavigation, NotablePost,
  2643.  ImageBlacklist */
  2644. /**
  2645. * MAIN
  2646. */
  2647. if (ActivePage.isThread()) { // Only setup the tools if we are on a thread
  2648.   $(document).ready(function() {
  2649.     window.bakerTools = {};
  2650.     window.bakerTools.mainWindow = new BakerWindow();
  2651.     window.bakerTools.scrollBar = new ScrollbarNavigation([
  2652.       NotablePost.NEW_NOTABLE_POST_EVENT,
  2653.       YouHighlighter.NEW_OWN_POST_EVENT,
  2654.       YouHighlighter.NEW_YOU_POST_EVENT,
  2655.       QPostHighlighter.NEW_Q_POST_EVENT,
  2656.     ]);
  2657.     new BlurImages();
  2658.     window.bakerTools.PreviousBreadHighlighter =
  2659.       new PreviousBreadHighlighter();
  2660.     window.bakerTools.notableHighlighter = new NotableHighlighter();
  2661.     window.bakerTools.qPostHighlighter = new QPostHighlighter();
  2662.     window.bakerTools.youHighlighter = new YouHighlighter();
  2663.     window.bakerTools.statsOverlay = new StatsOverlay();
  2664.     new NominatePostButtons();
  2665.     new BreadList();
  2666.     new ImageBlacklist();
  2667.   });
  2668. }
  2669. }(window.jQuery));
Advertisement
Add Comment
Please, Sign In to add comment