Guest User

Untitled

a guest
Jan 7th, 2021
172
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 44.11 KB | None | 0 0
  1. // My custom colors
  2. window.qjsSettings = {
  3. youcolor: '#fcc',
  4. qcolor: '#ffc',
  5. boards: {
  6. abcu: {
  7. youcolor: '#fcc !important',
  8. youscrollcolor: '#fcc',
  9. },
  10. },
  11. };
  12. // end my custom colors
  13.  
  14. // BEGIN Q.JS CODE -- Version 2020-09-29
  15. // MODIFICATIONS BELOW THIS LINE NOT RECOMMENDED!
  16. // SEE https://github.com/learnedtocode/qjs
  17. // for instructions on modifying this script's behavior without modifying its code!
  18. jQuery(function($) {
  19. var defaultSettings = {
  20. qflair: '', // Examples: REAL, →
  21. qcolor: '#99d6ff',
  22. youcolor: '#F3D74D',
  23. scrollcolor: 'rgba(255, 255, 255, 0.9)',
  24. scrollcolor2: 'rgba(255, 255, 255, 0.6)', // may enlarge scroll bar for visibility
  25. scrollminheight: 9,
  26. scrollbackcolor: '#333',
  27. offbreadcolor: '#ffb', // background for posts in other breads
  28. // background for the current post link when showing a quoted post via hover
  29. quotehovercolor: '#ebf',
  30. scrolltime: 0, // ms
  31. // youscrollcolor: (same as 'youcolor' by default),
  32. updateDelay: 30, // ms
  33. sidenavWidth: 30, // px
  34.  
  35. // Improve reply citing when you select text and click to quote it.
  36. // NOTE: This setting also DISABLES the posting box at the top of each thread!
  37. improveReplyCiting: false,
  38.  
  39. floodEnabled: false,
  40. floodThreshold: 15, // min # posts before beginning fade
  41. floodVanish: 25, // max # posts before completed fade/hide
  42. floodBehavior: 'fade', // hide, fade
  43. fadenametripregex: /^(Anon(ymous)?-.*|.*-!!Hs1Jq13jV6)$/i,
  44. fadenametripfloodvalue: -1, // Effective post count for fading, or -1 for auto of floodThreshold+post count
  45. strikeThroughNameFags: false, // doesn't work well with other boards!
  46.  
  47. rateHistoryLen: 50, // Data points on chart
  48. rateAvgLen: 10, // Number of data points to average for instantaneous rate
  49.  
  50. extraStyles: '',
  51.  
  52. boards: {
  53. qrb: {
  54. youcolor: '#2a3a4a',
  55. youscrollcolor: '#4a6376',
  56. offbreadcolor: '#480000',
  57. quotehovercolor: '#705',
  58. extraStyles: `
  59. .post > div.qjs-controls a {
  60. color: #ddaadd;
  61. }
  62. .body small {
  63. color: #339955;
  64. }
  65. span.qjs-pastebin {
  66. color: #c5c8c6;
  67. border-color: rgba(197, 200, 198, 0.6);
  68. }
  69. span.qjs-pastebin a {
  70. color: #f33 !important;
  71. }
  72. `,
  73. },
  74. abcu: { // overuses !important in board styles
  75. youcolor: '#f3e77d !important',
  76. youscrollcolor: '#f3e77d',
  77. scrollbackcolor: 'rgba(51, 51, 51, 0.75)',
  78. extraStyles: `
  79. .desktop-style div.boardlist:not(.bottom), .boardlist:hover {
  80. opacity: 1;
  81. background: rgb(195,225,255) !important;
  82. color: #cc0000 !important;
  83. }
  84. .qjs-overlay {
  85. color: #cc3333;
  86. }
  87. `,
  88. },
  89. },
  90. };
  91.  
  92. // If not on an individual thread page (ex. index or catalog) then just exit
  93. if (!/^\/[^\/]+\/res\/\d+\.html$/.test(window.location.pathname)) {
  94. return;
  95. }
  96.  
  97. var qjs_main = window.qjs_main = window.qjs_main || {};
  98. qjs_main.defaultSettings = defaultSettings;
  99.  
  100. // House keeping variables
  101. var boardName = window.location.pathname.match(/\/([^\/]+)\//)[1];
  102. var opPostId = window.location.href.match(/\/(\d+)\.html/)[1];
  103. var qposts = [];
  104. var currq = -1;
  105. var youposts = [];
  106. var curryou = -1;
  107. var qnavposts = [];
  108. var younavposts = [];
  109. var postIdsByIndex = {};
  110. var ctx;
  111. var borderSz;
  112. var minheight;
  113. var ratehistory = [];
  114. var floodEnabled = getSetting('floodEnabled');
  115. var postCount = 0;
  116. var navScaleFactor = 3;
  117. var isCyclical = $('.post.op .fa.fa-refresh').length > 0;
  118. var isArtificialClick = false;
  119.  
  120. function getSetting(name) {
  121. var s = window.qjsSettings;
  122. if (s && s.boards && boardName && s.boards[boardName] && name in s.boards[boardName]) {
  123. return s.boards[boardName][name];
  124. }
  125. if (boardName && defaultSettings.boards[boardName] && name in defaultSettings.boards[boardName]) {
  126. return defaultSettings.boards[boardName][name];
  127. }
  128. if (s && name in s) {
  129. return s[name];
  130. }
  131. return defaultSettings[name];
  132. }
  133. qjs_main.getSetting = getSetting;
  134.  
  135. // Suggestions to consider for future development
  136. // from 589388.html#590283 ...shill detection features, such as
  137. // easily knowing the proportion of posts from a user that don't link.
  138. // I'd want to know any ID that was posting > 1/3 posts targetting noone.
  139.  
  140. // TODO: Behavior for post hover should be to show original post visual before all q.js mods
  141. // TODO: Add flags to turn on/off features
  142. // TODO: Custom-regex -> post color/fade (auto-filter by selecting text and choosing new menu item?)
  143. // Examples: daily reminder, guys, check this out, shill, get out, filtered, tell us more, archive everything
  144. // TODO: Manual shade
  145. // TODO: remove Q trip codes from post content?
  146. // TODO: remove Q from end of post content if not a Q post?
  147. // TODO: recognize all of known Q trip codes? (make to sure to exclude known comps)
  148. // TODO: Links to reset on current Q/(you) post
  149. // TODO: Link to go to latest post (end key doesn't always work, but try capturing that as well?)
  150. // TODO: Keyboard shortcuts for navigation
  151. // TODO: Current/Total overall post navigation
  152. // TODO: Remap end key to always go to end of page
  153. // TODO: Check box for each post to mark as "read", "spam", ?
  154. // TODO: Autocorrect all-caps posts (50% threshold)?
  155. // TODO: Correct broken links but remove referral when clicked?
  156. // TODO: Make flood post fading non-linear to give leniency to posters just passing flood threshold
  157. // TODO: Penalize reposts in flood detection (if id's different, merge?) ?
  158. // TODO: Scorecard of posters ordered by post count (post rate, reply count, ...)?
  159. // TODO: Color/shade posts where there are no references and no question marks
  160. // TODO: If Q or trip used in name field, strike them out or replace with Anonymous?
  161. // TODO: embedded posts in Q posts don't have background-color and inherit Q color, fix?
  162.  
  163. var $allPosts = null;
  164. var autoUpdateInterval;
  165. var autoUpdateFinishedCount = 0;
  166.  
  167. // Image blacklist and Nope links
  168. var imageBlacklist = {};
  169.  
  170. function loadImageBlacklist() {
  171. Object.keys(JSON.parse(localStorage.imageBlacklistHash || "{}"))
  172. .forEach(addToImageBlacklist);
  173. }
  174. loadImageBlacklist();
  175.  
  176. function saveImageBlacklist() {
  177. localStorage.imageBlacklistHash = JSON.stringify(imageBlacklist);
  178. }
  179.  
  180. function addToImageBlacklist(md5) {
  181. if (md5) {
  182. imageBlacklist[md5] = true;
  183. }
  184. }
  185.  
  186. function findFileParent(img) {
  187. // For videos the DOM structure is .files > .file > { .fileinfo, .file }
  188. // For images it's .files > .file > { .fileinfo, a }
  189. // WTF...
  190. var $file = $(img).closest('.file');
  191. var $file2 = $file.parent().closest('.file');
  192. return $file2.length ? $file2 : $file;
  193. }
  194.  
  195. function blacklistPostImages(post, undo) {
  196. $(post).find('img.post-image').each(function() {
  197. var md5 = this.getAttribute('data-md5');
  198. if (undo) {
  199. delete imageBlacklist[md5];
  200. findFileParent(this).removeClass('blacklisted');
  201. } else {
  202. addToImageBlacklist(md5);
  203. findFileParent(this).addClass('blacklisted');
  204. }
  205. });
  206. updateBlacklistedCount(post);
  207. }
  208.  
  209. function updateBlacklistedCount(post) {
  210. var $post = $(post);
  211. var blacklistedCount = $post.find('.file.blacklisted').length;
  212. // note: may be more than 1 '.file' element
  213. var totalCount = $post.find('.fileinfo').length;
  214. $post.find('.qjs-blacklist-count').text(
  215. blacklistedCount
  216. ? ' | ' + blacklistedCount + '/' + totalCount + ' :blacklisted'
  217. : ''
  218. );
  219. }
  220.  
  221. function addPostControls() {
  222. // Must iterate over all posts here, to get the correct index
  223. $allPosts.each(function(i, el) {
  224. if (!el.classList.contains('qjs-processed-controls')) {
  225. $(el).prepend(
  226. '<div class="qjs-controls">'
  227. + '[#' + i + '] '
  228. + '<a class="qjs-hide" href="#">:hide</a>'
  229. + '<span class="qjs-blacklist-count"></span>'
  230. + '</div>'
  231. );
  232. // see: "Don't scroll to my own new posts" section below
  233. postIdsByIndex[i] = el.id.replace(/^(disabled_)/, '');
  234. el.classList.add('qjs-processed-controls');
  235. }
  236. });
  237. }
  238.  
  239. function removeBlacklistedImages() {
  240. $allPosts.filter(':not(.qjs-processed-images)').each(function() {
  241. var blacklistedAny = false;
  242. $('img.post-image', this).each(function() {
  243. if (imageBlacklist[this.getAttribute('data-md5')]) {
  244. blacklistedAny = true;
  245. findFileParent(this).addClass('blacklisted');
  246. }
  247. });
  248. if (blacklistedAny) {
  249. updateBlacklistedCount(this);
  250. }
  251. this.classList.add('qjs-processed-images');
  252. });
  253. }
  254.  
  255. function addPostNumberSpacesForElement(el, isMention) {
  256. var $el = $(el);
  257. var postNumber = $el.text();
  258. var pieces = [];
  259. var prefix = postNumber.replace(/\d+$/, '');
  260. var numbersOnly = postNumber.substring(prefix.length);
  261. if (!numbersOnly) {
  262. // may be a link directly to a board like >>>/abcu/ - ignore
  263. return;
  264. }
  265. while (numbersOnly.length % 3 !== 0) numbersOnly = ' ' + numbersOnly;
  266. for (var i = 0; i < numbersOnly.length / 3; i++) {
  267. pieces.push(numbersOnly.substring(i * 3, i * 3 + 3).trim());
  268. }
  269. if (prefix) {
  270. pieces[0] = prefix + pieces[0];
  271. }
  272. var tagOpen = '<span class="qjs-postnum-part">';
  273. var tagClose = '</span>';
  274. $el.html(tagOpen + pieces.join(tagClose + tagOpen) + tagClose);
  275. if (isMention) {
  276. $el.addClass('qjs-processed-postnums');
  277. }
  278. }
  279.  
  280. function addPostNumberSpaces() {
  281. $allPosts.filter(':not(.qjs-processed-postnums)').each(function() {
  282. var sels = [
  283. '.post_no:not([id^="post_no"])',
  284. '.body-line a[onclick^="highlightReply("]',
  285. '.body-line a[href^="/"]',
  286. ];
  287. $(this).find(sels.join(', ')).each(function() {
  288. addPostNumberSpacesForElement(this);
  289. });
  290. this.classList.add('qjs-processed-postnums');
  291. });
  292. // Process quoted posts that appear during an update
  293. $allPosts.find('.mentioned > a:not(.qjs-processed-postnums)').each(function() {
  294. addPostNumberSpacesForElement(this, true);
  295. });
  296. }
  297.  
  298. function fixPostFlags() {
  299. $allPosts.filter(':not(.qjs-processed-flags)').each(function() {
  300. $('img.flag[src$="/undefined.png"]', this).remove();
  301. this.classList.add('qjs-processed-flags');
  302. });
  303. }
  304.  
  305. function fixAutoUpdates() {
  306. if ($allPosts.length < 700) {
  307. return;
  308. }
  309. if (autoUpdateInterval) {
  310. return;
  311. }
  312. if (!$('#auto_update_status').prop('checked')) {
  313. return;
  314. }
  315.  
  316. var reenableAutoUpdateLimit = 600;
  317.  
  318. function enableAutoUpdateCheck() {
  319. autoUpdateInterval = setInterval(function() {
  320. if (!$('#auto_update_status').prop('checked')) {
  321. var reenablePercent = Math.min(
  322. 100,
  323. Math.round(100 * autoUpdateFinishedCount / reenableAutoUpdateLimit)
  324. );
  325. console.log('re-enabling auto update (' + reenablePercent + '%)');
  326. isArtificialClick = true;
  327. $('#auto_update_status').click();
  328. isArtificialClick = false;
  329. }
  330. if ($allPosts.length >= 751) {
  331. autoUpdateFinishedCount++;
  332. if (autoUpdateFinishedCount >= reenableAutoUpdateLimit) {
  333. clearInterval(autoUpdateInterval);
  334. autoUpdateInterval = null;
  335. console.log('auto-update check disabled (expired)');
  336. }
  337. }
  338. }, 600);
  339. }
  340. enableAutoUpdateCheck();
  341.  
  342. $('#auto_update_status').on('click', function() {
  343. if (isArtificialClick) {
  344. return;
  345. }
  346. if ($(this).prop('checked')) {
  347. enableAutoUpdateCheck();
  348. } else {
  349. console.log('auto-update check disabled (manual)');
  350. clearInterval(autoUpdateInterval);
  351. autoUpdateInterval = null;
  352. }
  353. });
  354. }
  355.  
  356. $(document).on('click', 'div.qjs-controls a.qjs-hide', function() {
  357. var $post = $(this).closest('.post');
  358.  
  359. if (this.textContent === ':hide') {
  360. $post.addClass('qjs-hidden');
  361. blacklistPostImages($post);
  362. this.textContent = ($post.find('.file').length ? ':show-text' : ':show');
  363.  
  364. } else if (this.textContent === ':show-text') {
  365. $post.removeClass('qjs-hidden');
  366. this.textContent = ':show';
  367.  
  368. } else { // ':show'
  369. $post.removeClass('qjs-hidden');
  370. blacklistPostImages($post, true);
  371. this.textContent = ':hide';
  372. }
  373.  
  374. saveImageBlacklist();
  375. return false;
  376. });
  377.  
  378. $(document).on('mouseover', 'a.hash_unix.hash_u', function() {
  379. $(this).closest('.file.blacklisted').addClass('show-temp');
  380. }).on('mouseout', 'a.hash_unix.hash_u', function() {
  381. $(this).closest('.file.blacklisted').removeClass('show-temp');
  382. });
  383.  
  384. // Don't scroll to my own new posts
  385. // Relevant 8kun code:
  386. // highlightReply(post_response.id);
  387. // window.location.hash = post_response.id;
  388. // var scroll_reply = $('div.post#reply_' + post_response.id);
  389. // if (scroll_reply.length) {
  390. // $(window).scrollTop(scroll_reply.offset().top);
  391. // }
  392. var enableHighlightReply = true;
  393. window.highlightReplyOrig = window.highlightReply;
  394. window.highlightReply = function(id, event) {
  395. if (enableHighlightReply) {
  396. return window.highlightReplyOrig.call(this, id, event);
  397. } else {
  398. enableHighlightReply = true;
  399. var $post = $('div.post#reply_' + id);
  400. var $anchor = $('#' + id);
  401. // Disable scroll_reply in 8kun code
  402. $post.attr('id', 'disabled_reply_' + id);
  403. // Disable window.location.hash in 8kun code
  404. $anchor.attr('id', 'disabled_' + id);
  405. setTimeout(function() {
  406. // Restore normal function
  407. $post.attr('id', 'reply_' + id);
  408. $anchor.attr('id', id);
  409. }, 0);
  410. // Show a notice
  411. var $myPost = $('<div class="qjs_my_post">')
  412. .data('created', Date.now())
  413. .data('expires', Date.now() + 60000);
  414. var $newPostIndicator = $('<a>')
  415. .text('>>' + id)
  416. .attr('href', '#' + id)
  417. .attr('onclick', 'highlightReply(' + JSON.stringify(id) + ', event)')
  418. $myPost.append($newPostIndicator);
  419. addPostNumberSpacesForElement($newPostIndicator);
  420. $myPost.append(
  421. $('<span>')
  422. .text(' ' + $post.find('time').text().trim().split(' ')[2])
  423. );
  424. $('#qjs_my_posts').append($myPost);
  425. fadeMyPosts();
  426. // Don't highlight multiple posts that were just loaded
  427. // TODO why does this happen?
  428. $('.post.highlighted').removeClass('highlighted');
  429. }
  430. };
  431. $(document).ajaxSuccess(function(event, xhr, settings) {
  432. if (settings.url === 'https://sys.8kun.top/post.php') {
  433. enableHighlightReply = false;
  434. }
  435. });
  436. function fadeMyPosts() {
  437. var $myPosts = $('.qjs_my_post');
  438. if (!$myPosts.length) {
  439. return;
  440. }
  441. $myPosts.each(function() {
  442. var $this = $(this);
  443. if (Date.now() >= $this.data('expires')) {
  444. $this.remove();
  445. } else {
  446. var ratio = (
  447. ($this.data('expires') - Date.now())
  448. / ($this.data('expires') - $this.data('created'))
  449. );
  450. $this.css('opacity', 0.3 + 0.6 * ratio);
  451. }
  452. });
  453. setTimeout(fadeMyPosts, 600);
  454. }
  455.  
  456. if (getSetting('improveReplyCiting')) {
  457. // Fix reply citing: don't reset textarea scroll position or cursor position
  458. // To make this simpler, and also fix the quick reply box not appearing:
  459. // use the quick reply box only
  460. $(window).trigger('cite');
  461. // Disable hide_at_top - relevant 8kun code:
  462. // if ($(this).scrollTop() < $origPostForm.offset().top + $origPostForm.height() - 100) {
  463. // $postForm.fadeOut(100);
  464. // }
  465. $('form[name="post"]:first').css({
  466. display: 'block',
  467. position: 'absolute',
  468. top: '-900px',
  469. });
  470. // Fix close-btn behavior
  471. $('#quick-reply .close-btn').off('click');
  472. $('#quick-reply .close-btn').on('click', function() {
  473. $('#quick-reply #post-form-inner').hide();
  474. });
  475. // Fix [Post a Reply] button behavior
  476. $('#link-quick-reply').off('click');
  477. $('#link-quick-reply').on('click', function() {
  478. $('#quick-reply #post-form-inner').show();
  479. $('#quick-reply textarea[name=body]').focus();
  480. return false;
  481. });
  482. // Hide reply box until it's needed
  483. // but only if there are already some replies (i.e. not baking)
  484. if ($('.post.reply').length) {
  485. $('#quick-reply #post-form-inner').hide();
  486. }
  487. window.citeReply = function(id, with_link) {
  488. $('#quick-reply #post-form-inner').show();
  489. // TODO what triggers this to break?
  490. $('#post-form-outer textarea[name=body]').attr('id', '');
  491. $('#quick-reply textarea[name=body]').attr('id', 'body');
  492. var textarea = document.getElementById('body');
  493. var toInsert = '>>' + id + '\n';
  494. // 8kun code sets this to window.getSelection().toString()
  495. var selectedText = sessionStorage.quoteClipboard;
  496. if (selectedText) {
  497. toInsert += selectedText
  498. .split('\n')
  499. .map(function(line) {
  500. if (line !== '') {
  501. return '>' + line + '\n';
  502. } else {
  503. // No way to tell the difference between real empty
  504. // lines and end of paragraph, so skip them
  505. return '';
  506. }
  507. })
  508. .join('');
  509. delete sessionStorage.quoteClipboard;
  510. }
  511. var scrollTop = textarea.scrollTop;
  512. var insertPoint = Math.max(textarea.selectionStart, textarea.selectionEnd);
  513. if (insertPoint > 0) {
  514. insertPoint = textarea.value.indexOf('\n', insertPoint - 1);
  515. if (insertPoint === -1) {
  516. // Insertion point on last line, which doesn't have a trailing newline
  517. toInsert = '\n' + toInsert;
  518. insertPoint = textarea.value.length;
  519. } else {
  520. insertPoint++;
  521. }
  522. }
  523. textarea.value = (
  524. textarea.value.substring(0, insertPoint)
  525. + toInsert
  526. + textarea.value.substring(insertPoint)
  527. );
  528. textarea.selectionStart = textarea.selectionEnd = insertPoint + toInsert.length;
  529. textarea.scrollTop = scrollTop;
  530. textarea.focus();
  531. };
  532. }
  533.  
  534. // Misc styles
  535. $('head').append(
  536. `<style>
  537. div.boardlist > span.sub > a {
  538. display: inline-block;
  539. margin: 0 -3px;
  540. }
  541. span.post-num {
  542. margin-left: 0.3em;
  543. opacity: 0.6;
  544. font-size: 81%;
  545. }
  546. a[href^="/${boardName}/res/"]:not([href^="/${boardName}/res/${opPostId}.html"]):not([href*="+50.html"]) {
  547. background: ${getSetting('offbreadcolor')};
  548. }
  549. a.dashed-underline {
  550. background: ${getSetting('quotehovercolor')};
  551. }
  552. span.qjs-pastebin {
  553. font-size: 81%;
  554. opacity: 0.75;
  555. margin-left: 0.3em;
  556. color: #000;
  557. border: 1px solid rgb(0, 0, 0, 0.6);
  558. border-radius: 3px;
  559. padding: 1px 2px;
  560. }
  561. span.qjs-pastebin a {
  562. color: #900 !important;
  563. }
  564. .post > div.qjs-controls {
  565. opacity: 0.75;
  566. font-size: 90%;
  567. }
  568. .post > div.qjs-controls a {
  569. color: #306;
  570. text-decoration: none;
  571. }
  572. .post > div.qjs-controls a:hover {
  573. color: #c33;
  574. }
  575. .post.qjs-hidden > .files, .post.qjs-hidden > .body {
  576. display: none;
  577. }
  578. .file.blacklisted {
  579. background: #999;
  580. padding: 0.3em 0;
  581. margin: 0.15em;
  582. opacity: 0.6;
  583. }
  584. .file.blacklisted:not(.show-temp) img {
  585. display: none;
  586. }
  587. .disclaimer-8kun {
  588. display: none !important;
  589. }
  590. .qjs-overlay {
  591. position: fixed;
  592. top: 36px;
  593. right: ${getSetting('sidenavWidth') + 6}px;
  594. opacity: 0.45;
  595. color: #f60;
  596. font-family: sans-serif;
  597. text-align: right;
  598. font-size: 12px;
  599. }
  600. #qjs_post_count {
  601. font-size: 36px;
  602. }
  603. #sidenav {
  604. cursor: pointer;
  605. }
  606. .qjs-postnum-part {
  607. padding-right: 0.15em;
  608. }
  609. .qjs-postnum-part:last-child {
  610. padding-right: 0;
  611. }
  612. #qjs-boards-parent {
  613. position: relative;
  614. }
  615. #qjs-boards {
  616. position: absolute;
  617. top: 1em;
  618. left: 0;
  619. right: auto;
  620. padding: 0.3em;
  621. }
  622. #qjs-boards > a {
  623. display: block;
  624. }
  625.  
  626. /* Extra styles */
  627. ${getSetting('extraStyles')}
  628. </style>`
  629. );
  630.  
  631. /* Display a replies counter overlay in the top right corner */
  632. $('body').append(
  633. '<div id="qjs_overlay_main" class="qjs-overlay">'
  634. + '<div id="qjs_post_count" />'
  635. + '<div id="qjs_my_posts" />'
  636. + '</div>'
  637. );
  638. var updatePostCountDelay = 30;
  639. function updatePostCount() {
  640. postCount = $('#thread_stats_posts').text();
  641. if (postCount) {
  642. postCount = +postCount;
  643. $('#qjs_post_count').text(postCount);
  644. } else {
  645. // waiting for 8kun code?
  646. setTimeout(updatePostCount, updatePostCountDelay);
  647. updatePostCountDelay *= 1.5;
  648. }
  649. }
  650. $(document).on('new_post', function() {
  651. setTimeout(updatePostCount, 30);
  652. });
  653. updatePostCount();
  654.  
  655. // On scroll stop. SO #9144560
  656. (function ($) {
  657. var on = $.fn.on, timer;
  658. $.fn.on = function () {
  659. var args = Array.apply(null, arguments);
  660. var last = args[args.length - 1];
  661.  
  662. if (isNaN(last) || (last === 1 && args.pop())) return on.apply(this, args);
  663.  
  664. var delay = args.pop();
  665. var fn = args.pop();
  666.  
  667. args.push(function () {
  668. var self = this, params = arguments;
  669. clearTimeout(timer);
  670. timer = setTimeout(function () {
  671. fn.apply(self, params);
  672. }, delay);
  673. });
  674.  
  675. return on.apply(this, args);
  676. };
  677. }(window.jQuery));
  678.  
  679. // Case insensitive contains selector for finding yous. SO #8746882
  680. $.expr[":"].icontains = jQuery.expr.createPseudo(function (arg) {
  681. return function (elem) {
  682. return jQuery(elem).text().toUpperCase().indexOf(arg.toUpperCase()) >= 0;
  683. };
  684. });
  685.  
  686. // Get non-child text. SO #3442394
  687. function immediateText(el) {
  688. return el.contents().not(el.children()).text();
  689. }
  690.  
  691. // Scroll to element
  692. function myScrollTo(el) {
  693. $('html, body').animate({
  694. scrollTop: $(el).offset().top - $('div.boardlist').height()
  695. }, getSetting('scrolltime'));
  696. }
  697.  
  698. // Scroll to next Q
  699. qjs_main.nextq = function() {
  700. if(qposts.length > 0) {
  701. if(currq < qposts.length-1) {
  702. currq++;
  703. }
  704. myScrollTo($(qposts).get(currq));
  705. }
  706. };
  707.  
  708. // Scroll to last Q
  709. qjs_main.lastq = function() {
  710. if(qposts.length > 0) {
  711. currq = qposts.length - 1;
  712. myScrollTo($(qposts).get(currq));
  713. }
  714. };
  715.  
  716. // Scroll to previous Q
  717. qjs_main.prevq = function() {
  718. if(qposts.length > 0) {
  719. if(currq > 0) {
  720. currq--;
  721. }
  722. myScrollTo($(qposts).get(currq));
  723. }
  724. };
  725.  
  726. // Scroll to first Q
  727. qjs_main.firstq = function() {
  728. if(qposts.length > 0) {
  729. currq = 0;
  730. myScrollTo($(qposts).get(currq));
  731. }
  732. };
  733.  
  734. // Scroll to next (You)
  735. qjs_main.nextyou = function() {
  736. if(youposts.length > 0) {
  737. if(curryou < youposts.length-1) {
  738. curryou++;
  739. }
  740. myScrollTo($(youposts).get(curryou));
  741. }
  742. };
  743.  
  744. // Scroll to last (You)
  745. qjs_main.lastyou = function() {
  746. if(youposts.length > 0) {
  747. curryou = youposts.length - 1;
  748. myScrollTo($(youposts).get(curryou));
  749. }
  750. };
  751.  
  752. // Scroll to previous (You)
  753. qjs_main.prevyou = function() {
  754. if(youposts.length > 0) {
  755. if(curryou > 0) {
  756. curryou--;
  757. }
  758. myScrollTo($(youposts).get(curryou));
  759. }
  760. };
  761.  
  762. // Scroll to first (You)
  763. qjs_main.firstyou = function() {
  764. if(youposts.length > 0) {
  765. curryou = 0;
  766. myScrollTo($(youposts).get(curryou));
  767. }
  768. };
  769.  
  770. // Inserts Q navigation links
  771. function qnav() {
  772. $('div.boardlist').append('<span>[ <a href="javascript:qjs_main.firstq();"><i class="fa fa-step-backward"></i></a> <a href="javascript:qjs_main.prevq();"><i class="fa fa-backward"></i></a> <span style="filter:brightness(70%);">Q</span> <span class="qcount">(?:?)</span> <a href="javascript:qjs_main.nextq();"><i class="fa fa-forward"></i></a> <a href="javascript:qjs_main.lastq();"><i class="fa fa-step-forward"></i></a> ]</span>');
  773. }
  774.  
  775. // Inserts (You) navigation links
  776. function younav() {
  777. $('div.boardlist').append('<span>[ <a href="javascript:qjs_main.firstyou();"><i class="fa fa-step-backward"></i></a> <a href="javascript:qjs_main.prevyou();"><i class="fa fa-backward"></i></a> <span style="filter:brightness(70%);">(You)</span> <span class="youcount">(?:?)</span> </span><a href="javascript:qjs_main.nextyou();"><i class="fa fa-forward"></i></a> <a href="javascript:qjs_main.lastyou();"><i class="fa fa-step-forward"></i></a> ]</span>');
  778. }
  779.  
  780. // Inserts feature toggle links
  781. function togglenav() {
  782. $('div.boardlist').append('<span>[ <a href="javascript:qjs_main.toggleFlood();">Post Fading <span class="toggleFloodState">' + (floodEnabled ? 'Off' : 'On') + '</span></a> ]</span>')
  783. }
  784.  
  785. // Inserts post rate count/chart
  786. function postratenav() {
  787. var height = $('div.boardlist').height() - 1;
  788. $('div.boardlist').append('<span>[ Post Rate: <span class="postRate">0</span> posts/min <canvas class="postRateChart"></canvas>]</span>')
  789. $('.postRate').css('color', $('div.boardlist a').css('color'));
  790. var charts = $('.postRateChart');
  791. $(charts).each(function() {
  792. $(this).css('width', '75px');
  793. $(this).css('height', height);
  794. $(this).css('vertical-align', 'middle');
  795. //$(this).css('border', '1px solid');
  796. //$(this).css('border-color', $('div.boardlist').css('color'));
  797. var gctx = $(this).get(0).getContext('2d');
  798. gctx.canvas.height = 20;
  799. gctx.canvas.width = 100;
  800. });
  801. }
  802.  
  803. // Inserts side navigation (bird's eye)
  804. var sideNavKey = null;
  805. function sidenav() {
  806. var winHeight = $(window).height();
  807. var boardlistHeight = $('div.boardlist').height();
  808. var sideNavKeyNew = [winHeight, boardlistHeight].join(',');
  809. if (sideNavKey === sideNavKeyNew) {
  810. return; // No size change, so need to do anything now
  811. }
  812. sideNavKey = sideNavKeyNew;
  813.  
  814. var $nav = $('#sidenav');
  815. if ($nav.length) $nav.remove();
  816. $('body').append('<canvas id="sidenav"></canvas>');
  817. $nav = $('#sidenav');
  818. $nav.css('position', 'fixed');
  819. $nav.css('top', $('div.boardlist').height());
  820. $nav.css('right', 0);
  821. $nav.css('width', getSetting('sidenavWidth'));
  822. $nav.css('height', $(window).height() - $('div.boardlist').height());
  823. $nav.css('background-color', getSetting('scrollbackcolor'));
  824. $('body').css('margin-right', getSetting('sidenavWidth'));
  825. ctx = $('#sidenav').get(0).getContext('2d');
  826. var canvasHeight = $(window).height() - $('div.boardlist').height();
  827. ctx.canvas.height = canvasHeight * navScaleFactor;
  828. ctx.canvas.width = getSetting('sidenavWidth') * navScaleFactor;
  829. borderSz = 1;
  830.  
  831. var $navTooltip = null;
  832. var postOffsets = [];
  833. // https://stackoverflow.com/a/41956372
  834. function binarySearch(array, pred) {
  835. let lo = -1, hi = array.length;
  836. while (1 + lo < hi) {
  837. const mi = lo + ((hi - lo) >> 1);
  838. if (pred(array[mi])) {
  839. hi = mi;
  840. } else {
  841. lo = mi;
  842. }
  843. }
  844. return hi;
  845. }
  846. function updateTooltip(e) {
  847. hoverPostNumber = Math.max(binarySearch(postOffsets, function(o) {
  848. return o > e.offsetY;
  849. }) - 1, 0);
  850. $navTooltip
  851. .css('top', Math.min($(window).height() - 18, Math.max(e.clientY - 6, 78)) + 'px')
  852. .html('&rarr; #' + hoverPostNumber);
  853. }
  854. $nav.on('mouseenter', function(e) {
  855. var docHeight = $(document).height();
  856. postOffsets = $allPosts.map(function() {
  857. return $(this).offset().top / docHeight * canvasHeight;
  858. });
  859. $navTooltip = $('<div class="qjs-overlay">')
  860. .css('font-weight', 'bold')
  861. .css('opacity', '1')
  862. .appendTo('body');
  863. updateTooltip(e);
  864. }).on('mousemove', function(e) {
  865. updateTooltip(e);
  866. }).on('mouseleave', function() {
  867. $navTooltip.remove();
  868. $navTooltip = null;
  869. }).on('click', function(e) {
  870. $(window).scrollTop($('#' + postIdsByIndex[hoverPostNumber]).offset().top - 36);
  871. });
  872. updateNav();
  873. }
  874.  
  875. // Update nav when scrolling stops
  876. $(window).on('scroll', function(e) {
  877. updateNavCounts();
  878. updateNavGraphics();
  879. }, getSetting('updateDelay'));
  880.  
  881. // Update nav when resize stops
  882. $(window).on('resize', function(e) {
  883. setTimeout(sidenav, 300); // why long timeout needed?
  884. }, getSetting('updateDelay'));
  885. setInterval(sidenav, 3000); // and why is this needed?
  886. // but it fixes problems with the post #/offset calculation
  887.  
  888. // Toggle post flooding
  889. qjs_main.toggleFlood = function() {
  890. if (floodEnabled) {
  891. floodEnabled = false;
  892. $('.toggleFloodState').text('On');
  893. if(getSetting('floodBehavior') === 'fade') {
  894. $('span.poster_id').each(function () {
  895. $(this).closest('div.post').css('opacity', 1);
  896. $(this).closest('div.post').off('mouseenter mouseleave');
  897. });
  898. } else if(getSetting('floodBehavior') === 'hide') {
  899. $(this).closest('div.post').show();
  900. }
  901. } else {
  902. floodEnabled = true;
  903. $('.toggleFloodState').text('Off');
  904. runq();
  905. }
  906. };
  907.  
  908. // Helper to run snippets in the right order
  909. function runq() {
  910. $allPosts = $('div.post:not(.post-hover):not(.hidden)');
  911.  
  912. processPastebins(); // Added later (learnedtocode)
  913. processQanonbins(); // Added later (learnedtocode)
  914. setQPosts();
  915. removeInvalidYous();
  916. setYouPosts();
  917. setFloodPosts();
  918. updateNav();
  919.  
  920. // Added here later (learnedtocode)
  921. addPostControls();
  922. removeBlacklistedImages();
  923. addPostNumberSpaces();
  924. fixAutoUpdates();
  925. fixPostFlags();
  926. }
  927.  
  928. // Try to look up pastebin author(s)
  929. function processPastebins() {
  930. if (!getSetting('pastebin_key')) return;
  931. $allPosts.filter(':not(.qjs-processed-pastebin)').each(function() {
  932. if (/pastebin\.com/.test(this.textContent)) {
  933. $('p.body-line', this).each(function() {
  934. // Pastebin URL length is 29, allow a few more chars for "dough:" etc
  935. if (this.textContent.length > 45) return;
  936. var binUrl = (this.textContent || '')
  937. .match(/(https?:\/\/pastebin\.com\/[a-zA-Z0-9]{8})\b/);
  938. if (!binUrl) return;
  939. binUrl = binUrl[1];
  940. var binId = binUrl.substring(binUrl.length - 8);
  941. var bodyLine = this;
  942. function pbDone(data) {
  943. var msg;
  944. if (data.error) {
  945. msg = 'ERROR: ' + data.error;
  946. } else if (data.anonymous) {
  947. msg = 'anonymous';
  948. } else {
  949. msg = (
  950. '<a href="https://pastebin.com/u/' + data.username
  951. + '" target="_blank" rel="noopener noreferer">'
  952. + '/u/' + data.username
  953. + '</a>'
  954. );
  955. }
  956. $(bodyLine).append(
  957. '<span class="qjs-pastebin">'
  958. + '<a href="' + binUrl + '" target="_blank" rel="noopener noreferer">'
  959. + ':pastebin'
  960. + '</a> ' + msg
  961. + '</span>'
  962. );
  963. }
  964. $.ajax({
  965. url: 'https://wearethene.ws/api/dough?v=3&paste_id=' + binId + '&key=' + encodeURIComponent(getSetting('pastebin_key')),
  966. success: function(data) {
  967. pbDone(data);
  968. },
  969. error: function(xhr, textStatus, errorThrown) {
  970. pbDone({
  971. error: errorThrown || textStatus || 'unknown',
  972. });
  973. },
  974. dataType: 'json',
  975. });
  976. });
  977. }
  978. this.classList.add('qjs-processed-pastebin');
  979. });
  980. }
  981.  
  982. // Try to look up qanonbin author(s)
  983. function processQanonbins() {
  984. $allPosts.filter(':not(.qjs-processed-qanonbin)').each(function() {
  985. if (/qanonbin\.com/.test(this.textContent)) {
  986. $('p.body-line', this).each(function() {
  987. // qanonbin URL length is 41, allow a few more chars for "dough:" etc
  988. if (!this.textContent || this.textContent.length > 60) return;
  989. var binUrl = (this.textContent || '')
  990. .match(/(https?:\/\/[a-z0-9\.]*qanonbin\.com\/(paste|clone)\/[a-zA-Z0-9]{9})\b/);
  991. if (!binUrl) return;
  992. binUrl = binUrl[1];
  993. var binId = binUrl.substring(binUrl.length - 9);
  994. var bodyLine = this;
  995. function pbDone(data) {
  996. var msg;
  997. if (data.error) {
  998. msg = 'ERROR: ' + data.error;
  999. } else {
  1000. msg = data.info;
  1001. }
  1002. $(bodyLine).append(
  1003. '<span class="qjs-pastebin">'
  1004. + '<a href="' + binUrl + '" target="_blank" rel="noopener noreferer">'
  1005. + ':qanonbin'
  1006. + '</a> '
  1007. + msg
  1008. + '</span>'
  1009. );
  1010. }
  1011. $.ajax({
  1012. url: 'https://www2.qanonbin.com/api/info/' + binId,
  1013. success: function(data) {
  1014. pbDone(data);
  1015. },
  1016. error: function(xhr, textStatus, errorThrown) {
  1017. pbDone({
  1018. error: errorThrown || textStatus || 'unknown',
  1019. });
  1020. },
  1021. dataType: 'json',
  1022. });
  1023. });
  1024. }
  1025. this.classList.add('qjs-processed-qanonbin');
  1026. });
  1027. }
  1028.  
  1029. // Set which posts are Q posts
  1030. function setQPosts() {
  1031. $allPosts.filter(':not(.qjs-processed-q)').each(function() {
  1032. var $this = $(this);
  1033. if ($this.find('span.trip:contains("!!Hs1Jq13jV6")').length) {
  1034. qposts.push(this);
  1035. $this.css('background-color', getSetting('qcolor'));
  1036. if (getSetting('qflair') !== '') {
  1037. $this
  1038. .find('p.intro > label > span.trip')
  1039. .first()
  1040. .prepend(getSetting('qflair') + ' ');
  1041. }
  1042. }
  1043. this.classList.add('qjs-processed-q');
  1044. });
  1045. }
  1046.  
  1047. // Remove invalid (you)'s
  1048. function removeInvalidYous() {
  1049. $allPosts.filter(':not(.qjs-processed-invalidyou)').each(function() {
  1050. if (/\(You\)/i.test(this.textContent)) {
  1051. $(this).find('.body :not(small)').contents().filter(function() {
  1052. return this.nodeType === 3;
  1053. }).each(function() {
  1054. var text = this.textContent.replace(/\(You\)/ig, '(.You.)');
  1055. if (text !== this.textContent) {
  1056. this.textContent = text;
  1057. }
  1058. });
  1059. }
  1060. this.classList.add('qjs-processed-invalidyou');
  1061. });
  1062. }
  1063.  
  1064. // Set which posts are (you) posts
  1065. function setYouPosts() {
  1066. $allPosts.filter(':not(.qjs-processed-you)').each(function() {
  1067. var $this = $(this);
  1068. if ($this.find('span.own_post, small:icontains("(You)")').length) {
  1069. youposts.push(this);
  1070. var youcolor = getSetting('youcolor');
  1071. if (/!important/.test(youcolor)) {
  1072. // Workaround for boards that have badly written styles
  1073. // https://bugs.jquery.com/ticket/2066
  1074. $this.css('cssText', 'background-color: ' + youcolor);
  1075. } else {
  1076. $this.css('background-color', youcolor);
  1077. }
  1078. }
  1079. this.classList.add('qjs-processed-you');
  1080. });
  1081. }
  1082.  
  1083. // Set flood posts
  1084. function setFloodPosts() {
  1085. if (floodEnabled) {
  1086. var stats = {};
  1087. var firstId = null;
  1088. //console.log("==[ Name Fags ]=================================================");
  1089. var posts = $('div.post:not(.post-hover)').not('.you');
  1090. $(posts).each(function () {
  1091. var id = $(this).find('p > span.poster_id').first().text();
  1092. if(firstId != null) {
  1093. if (!(id in stats)) {
  1094. stats[id] = {count: 0, namefag: false, floodcount: 0};
  1095. }
  1096. stats[id].count++;
  1097. if(!stats[id].namefag) {
  1098. var name = immediateText($(this).find('p > label span.name').first());
  1099. var trip = $(this).find('p > label > span.trip').first().text();
  1100. stats[id].namefag = !getSetting('fadenametripregex').test(name+'-'+trip);
  1101. //if(stats[id].namefag) {
  1102. // console.log(id + '=' + stats[id].namefag + ', ' + name + ', ' + trip);
  1103. //}
  1104. }
  1105. if(stats[id].namefag) {
  1106. if(getSetting('fadenametripfloodvalue') < 0) {
  1107. stats[id].floodcount = getSetting('floodThreshold') + stats[id].count;
  1108. } else {
  1109. stats[id].floodcount = getSetting('fadenametripfloodvalue');
  1110. }
  1111. } else {
  1112. stats[id].floodcount = stats[id].count;
  1113. }
  1114. }
  1115. if (firstId == null) {
  1116. firstId = id;
  1117. }
  1118. });
  1119. $.each(stats, function (key, value) {
  1120. if (key !== firstId) {
  1121. var ids = $('span.poster_id:contains("' + key + '")');
  1122. if (value.floodcount > getSetting('floodThreshold') || value.namefag) {
  1123. if(getSetting('strikeThroughNameFags') && value.namefag) {
  1124. $(ids).each(function() {
  1125. $(this).closest('div.post').find('p > label span.name').first().css('text-decoration','line-through');
  1126. });
  1127. }
  1128. if (getSetting('floodBehavior') === 'fade') {
  1129. var intensity = value.floodcount;
  1130. if (intensity > getSetting('floodVanish')) {
  1131. intensity = getSetting('floodVanish');
  1132. }
  1133. intensity = ((getSetting('floodVanish') - getSetting('floodThreshold')) - (intensity - getSetting('floodThreshold'))) / (getSetting('floodVanish') - getSetting('floodThreshold'));
  1134. if (intensity < 0.1) {
  1135. intensity = 0.1;
  1136. }
  1137. $(ids).each(function () {
  1138. $(this).closest('div.post').css('opacity', intensity);
  1139. $(this).closest('div.post').hover(function () {
  1140. $(this).animate({opacity: 1.0}, getSetting('updateDelay'));
  1141. }, function () {
  1142. $(this).animate({opacity: intensity}, getSetting('updateDelay'));
  1143. });
  1144. });
  1145. } else if (getSetting('floodBehavior') === 'hide') {
  1146. if (value.count >= getSetting('floodVanish')) {
  1147. $(ids).each(function () {
  1148. $(this).closest('div.post').hide();
  1149. });
  1150. }
  1151. }
  1152. } else {
  1153. $(ids).each(function () {
  1154. $(this).closest('div.post').css('opacity', 1.0);
  1155. });
  1156. }
  1157. }
  1158. });
  1159. }
  1160. return $.Deferred().resolve();
  1161. }
  1162.  
  1163. // Update navigation
  1164. function updateNav() {
  1165. updateNavCounts();
  1166. updateNavGraphics();
  1167. updateNavPostRate();
  1168. }
  1169.  
  1170. // Update navigation counts
  1171. function updateNavCounts() {
  1172. var fontSize = -1;
  1173. var lineHeight;
  1174.  
  1175. if(currq > qposts.length) { currq = qposts.length; }
  1176. if(curryou > youposts.length) { curryou = youposts.length; }
  1177.  
  1178. for(i=0; i<qposts.length; i++) {
  1179. var el = $(qposts).get(i);
  1180. if(fontSize == -1) {
  1181. fontSize = $(el).css('font-size');
  1182. lineHeight = Math.floor(parseInt(fontSize.replace('px', '')) * 1.5);
  1183. }
  1184. if(($(el).offset().top + $(el).height() - 2.25*lineHeight) > $(window).scrollTop()) {
  1185. currq = i;
  1186. break;
  1187. }
  1188. }
  1189.  
  1190. for(i=0; i<youposts.length; i++) {
  1191. var el = $(youposts).get(i);
  1192. if(fontSize == -1) {
  1193. fontSize = $(el).css('font-size');
  1194. lineHeight = Math.floor(parseInt(fontSize.replace('px', '')) * 1.5);
  1195. }
  1196. if(($(el).offset().top + $(el).height() - 2.25*lineHeight) > $(window).scrollTop()) {
  1197. curryou = i;
  1198. break;
  1199. }
  1200. }
  1201.  
  1202. // TODO: check for duplicates and remove from counts
  1203. $('.qcount').text("(" + (currq+1) + ":" + qposts.length + ")");
  1204. $('.youcount').text("(" + (curryou+1) + ":" + youposts.length + ")");
  1205. }
  1206.  
  1207. // Update navigation graphics
  1208. function updateNavGraphics() {
  1209. var sidenav = $('#sidenav');
  1210. if(sidenav.length) {
  1211. $(sidenav).css('height', $(window).height() - $('div.boardlist').height());
  1212. minheight = ctx.canvas.height / ($(window).height() - $('div.boardlist').height()); // 1px
  1213. ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  1214.  
  1215. // Draw nav q posts
  1216. qnavposts = [];
  1217. ctx.fillStyle = getSetting('qcolor');
  1218. for (i = 0; i < qposts.length; i++) {
  1219. // TODO: check if we have already added post, don't draw it again
  1220. var el = $(qposts).get(i);
  1221. var height = $(el).height() / $(document).height() * ctx.canvas.height;
  1222. if(height < minheight) height = minheight;
  1223. qnavposts[i] = {
  1224. x : borderSz,
  1225. y : $(el).offset().top / $(document).height() * ctx.canvas.height,
  1226. width : ctx.canvas.width - borderSz*2,
  1227. height : height
  1228. };
  1229. ctx.fillRect(qnavposts[i].x, qnavposts[i].y, qnavposts[i].width, qnavposts[i].height);
  1230. }
  1231.  
  1232. // Draw nav you posts
  1233. younavposts = [];
  1234. ctx.fillStyle = getSetting('youscrollcolor') || getSetting('youcolor');
  1235. for (i = 0; i < youposts.length; i++) {
  1236. // TODO: check if we have already added post, don't add it again
  1237. var el = $(youposts).get(i);
  1238. var height = $(el).height() / $(document).height() * ctx.canvas.height;
  1239. if(height < minheight) height = minheight;
  1240. younavposts[i] = {
  1241. x : borderSz,
  1242. y : $(el).offset().top / $(document).height() * ctx.canvas.height,
  1243. width : ctx.canvas.width - borderSz*2,
  1244. height : height
  1245. };
  1246. ctx.fillRect(younavposts[i].x, younavposts[i].y, younavposts[i].width, younavposts[i].height);
  1247. }
  1248.  
  1249. // Update nav window
  1250. var heightScale = ctx.canvas.height / $(document).height(),
  1251. scrollStart = $(window).scrollTop() * heightScale,
  1252. scrollHeight = $(window).height() * heightScale,
  1253. scrollMinHeight = getSetting('scrollminheight') * ctx.canvas.height / $(window).height();
  1254. if (scrollHeight < scrollMinHeight) {
  1255. var scrollStart2 = scrollStart - (scrollMinHeight - scrollHeight) / 2;
  1256. if (scrollStart2 < 0) {
  1257. scrollStart2 = 0;
  1258. } else if (scrollStart2 + scrollMinHeight > ctx.canvas.height) {
  1259. scrollStart2 = ctx.canvas.height - scrollMinHeight;
  1260. }
  1261. ctx.fillStyle = getSetting('scrollcolor2');
  1262. ctx.fillRect(
  1263. ctx.canvas.width / 4,
  1264. scrollStart2,
  1265. ctx.canvas.width / 2,
  1266. scrollMinHeight
  1267. );
  1268. }
  1269. ctx.fillStyle = getSetting('scrollcolor');
  1270. ctx.fillRect(
  1271. ctx.canvas.width / 4,
  1272. scrollStart,
  1273. ctx.canvas.width / 2,
  1274. scrollHeight
  1275. );
  1276.  
  1277. // Add red marker at bottom of >750 posts
  1278. if($('#thread_stats_posts').text() > 750 && !isCyclical) {
  1279. var barHeight = (4 * ctx.canvas.height) / ($(window).height() - $('div.boardlist').height());
  1280. ctx.fillStyle = '#f66';
  1281. ctx.fillRect(
  1282. 0,
  1283. ctx.canvas.height - barHeight,
  1284. ctx.canvas.width,
  1285. barHeight
  1286. );
  1287. }
  1288. }
  1289. }
  1290.  
  1291. // Updates post rate count/chart
  1292. function updateNavPostRate() {
  1293. var posts = $('div.post').not('.post-hover');
  1294. var startPost = posts.length - (getSetting('rateHistoryLen') + getSetting('rateAvgLen')) + 1;
  1295. if(startPost < 1) startPost = 1;
  1296. var start = $($($(posts).get(0)).find('.intro time').get(0)).attr('unixtime'); //$('div.post:first .intro time').attr('unixtime');
  1297. ratehistory = [];
  1298. timehistory = [];
  1299. for(var i=startPost; i<posts.length; i++) {
  1300. // TODO: check if we have already added post, don't add it again
  1301. var step = $($($(posts).get(i)).find('.intro time').get(0)).attr('unixtime'); //$($('div.post .intro time').get(i)).attr('unixtime');
  1302. timehistory[timehistory.length] = step;
  1303. if(timehistory.length - getSetting('rateAvgLen') - 1 >= 0) {
  1304. var avgend = timehistory[timehistory.length - 1];
  1305. var avgstart = timehistory[timehistory.length - getSetting('rateAvgLen') - 1];
  1306. ratehistory[ratehistory.length] = getSetting('rateAvgLen') / ((avgend - avgstart) / 60);
  1307. } else {
  1308. ratehistory[ratehistory.length] = 0;
  1309. }
  1310. }
  1311. //console.log(ratehistory);
  1312.  
  1313. if (ratehistory.length) {
  1314. $('.postRate').text(ratehistory[ratehistory.length-1].toFixed(1));
  1315. }
  1316.  
  1317. if(ratehistory.length > getSetting('rateAvgLen')) {
  1318. var maxRate = Math.max.apply(null, ratehistory);
  1319. var minRate = Math.min.apply(null, ratehistory);
  1320. //console.log("Max: " + maxRate);
  1321. //console.log("Min: " + minRate);
  1322. if(minRate > (maxRate - 0.5)) {
  1323. minRate = maxRate - 0.5;
  1324. maxRate = maxRate + 0.5;
  1325. }
  1326. if(minRate < 0) {
  1327. minRate = 0;
  1328. }
  1329. var maxTime = timehistory[timehistory.length-1];
  1330. var minTime = timehistory[getSetting('rateAvgLen')];
  1331. $('.postRateChart').each(function() {
  1332. var gctx = $(this).get(0).getContext('2d');
  1333. gctx.clearRect(0, 0, gctx.canvas.width, gctx.canvas.height);
  1334. gctx.strokeStyle = $('div.boardlist a').css('color');
  1335. gctx.beginPath();
  1336. var x = 0;
  1337. var y = gctx.canvas.height - (ratehistory[getSetting('rateAvgLen')] - minRate)/(maxRate - minRate) * gctx.canvas.height;
  1338. gctx.moveTo(x, y);
  1339. for(var i=getSetting('rateAvgLen')+1; i<ratehistory.length; i++) {
  1340. x = (timehistory[i] - minTime)/(maxTime - minTime) * gctx.canvas.width;
  1341. y = gctx.canvas.height - (ratehistory[i] - minRate)/(maxRate - minRate) * gctx.canvas.height;
  1342. gctx.lineTo(x, y);
  1343. }
  1344. gctx.stroke();
  1345. gctx.closePath();
  1346. });
  1347. }
  1348. }
  1349.  
  1350.  
  1351. qjs_main.Q = function() {
  1352. qnav();
  1353. younav();
  1354. togglenav();
  1355. postratenav();
  1356. sidenav();
  1357. runq();
  1358.  
  1359. // Collapse the boards list in the top bar
  1360. (function() {
  1361. var $boards = $('.boardlist:first > .sub[data-description="1"]');
  1362. $boards.attr('id', 'qjs-boards-parent');
  1363. var links = $boards.html().replace(/^\[\s*|\s*\]$/g, '').split(' / ');
  1364. $boards.html('[ <a id="qjs-expand-boards" href="#">Boards</a> ]');
  1365. $boards.append(
  1366. $('<div id="qjs-boards" class="boardlist">')
  1367. .html(links.join('\n'))
  1368. .hide()
  1369. );
  1370. var $qjsBoards = $('#qjs-boards');
  1371. $('#qjs-expand-boards').on('click', function() {
  1372. if ($qjsBoards.css('display') === 'none') {
  1373. $qjsBoards.show();
  1374. function hideQjsBoardsAgain() {
  1375. if (isArtificialClick) {
  1376. return;
  1377. }
  1378. $qjsBoards.hide();
  1379. $(document).off('click', hideQjsBoardsAgain);
  1380. }
  1381. $(document).on('click', hideQjsBoardsAgain);
  1382. } else {
  1383. $qjsBoards.hide();
  1384. }
  1385. return false;
  1386. });
  1387. })();
  1388.  
  1389. // Scroll back to target post after qjs controls added
  1390. setTimeout(function() {
  1391. var hashBackup = document.location.hash;
  1392. if (hashBackup) {
  1393. document.location.hash = '';
  1394. document.location.hash = hashBackup;
  1395. $(window).scrollTop($(window).scrollTop() - 60);
  1396. }
  1397. }, 0);
  1398.  
  1399. // Select the node that will be observed for mutations
  1400. var targetNode = $('div.thread')[0];
  1401.  
  1402. // Options for the observer (which mutations to observe)
  1403. var config = { childList: true };
  1404.  
  1405. // Callback function to execute when mutations are observed
  1406. var callback = function(mutationsList) {
  1407. for(var mutation in mutationsList) {
  1408. if (mutationsList[mutation].type == 'childList') {
  1409. runq();
  1410. break;
  1411. }
  1412. }
  1413. };
  1414.  
  1415. // Create an observer instance linked to the callback function
  1416. var observer = new MutationObserver(callback);
  1417.  
  1418. // Start observing the target node for configured mutations
  1419. observer.observe(targetNode, config);
  1420. };
  1421.  
  1422. // Attach snippets to ready/change events
  1423. // Wait for 8kun code that adds (you)s to run
  1424. setTimeout(qjs_main.Q, 30);
  1425. });
  1426. // END Q.JS CODE
  1427.  
Advertisement
Add Comment
Please, Sign In to add comment