Changes 'Continue this thread' links to insert the linked comments into the current page
// ==UserScript== // @name Reddit - Load 'Continue this thread' inline // @description Changes 'Continue this thread' links to insert the linked comments into the current page // @author James Skinner <[email protected]> (http://github.com/spiralx) // @namespace http://spiralx.org/ // @version 2.3.3 // @license MIT // @icon data:image/png;base64,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###h+Hga50phTaCvQDrwD8dginkujZ1a5NOTkOQ+FYupr22zLqMoiY9myl+v2ls6qFjOUVRpfWdGG+HRdFtdVLQp1Ftv/t4MXoXh97dFEeyNM9/QeFBshFh182aGX+D8nCy1HgatQZdd0ua6Jm+yrXMoaD7mXFRyOxgKu7DgFKl9UKMjM0bV9j/UcxWkXr3EpNE2ShqmJG6yTrsaGxgWUnFw81Q6Yoqa1QTti+d3Ywo67NtikQ9qKY2FEoW4/pY+24trrS49NsErlNXy5cmDlk/RtD9cY+7FcbmXYFSmNr9Our+rHFKiSHsFFTSM8t0Z7GjRXaQ1d0zqeXsWOuv6gQTPc1tLr4w2o+hLNtWrVqlWrVq1a/adx4/4BlQokldY0pQAAAAAASUVORK5CYII= // @match *://*.reddit.com/r/*/comments/* // @match *://*.reddit.com/user/*/comments/* // @grant GM_getValue // @grant GM_setValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_addStyle // @grant GM_addValueChangeListener // @grant GM.getValue // @grant GM.setValue // @grant GM.deleteValue // @grant GM.registerMenuCommand // @grant GM.addStyle // @grant GM.addValueChangeListener // @run-at document-end // @require https://unpkg.com/jquery@3/dist/jquery.min.js // @require https://unpkg.com/mutation-summary@1/dist/umd/mutation-summary.js // @require https://greasyfork.org/scripts/371339-gm-webextpref/code/GM_webextPref.js?version=961539 // ==/UserScript== /* jshint asi: true, esnext: true, laxbreak: true */ /* global jQuery, MutationSummary, GM_webextPref */ /* ==== 2.3.3 (2022.10.13) ==== * Actually fix bug supposedly fixed by previous version... ==== 2.3.2 (2022.08.28) ==== * Fix bug where clicking on "Continue this thread" after hover loading was triggered would open the comment's page ==== 2.3.1 (2022.06.26) ==== * Use GM_webextPref library to support Greasemonkey 4 users ==== 2.3.0 (2022.05.03) ==== * Fix centred text in expand links * Add configuration for expanding links by moving the mouse over the text "Continue this thread" or "Load more comments" ==== 2.2.1 (2022.05.02) ==== * Make expand links a block again so they stretch across whole width ==== 2.2.0 (2022.05.01) ==== * Use MonkeyConfig library to provide settings for intersection observer behaviour * CHanged styling of expandos and replaced icon with emoji ↘️ ==== 2.1.0 (2022.04.17) ==== * Use IntersectionObserver to automatically open "Load more comments" when they scroll into view * Put above behaviour behind USE_INTERSECTION_OBSERVER feature flag ==== 2.0.0 (2022.04.02) ==== * Added MIT license * Expand non-top level collapsed comments on load * Expand collapsed comments inserted from clicking "Load more comments" or "Continue this thread" * Script now also runs on posts made to a user's homepage * Remove old code handling "Load more comments" links * Tidied up old code and updated to use current JS features ==== 1.9.7 (2021.11.05) ==== * Use MutationSummary from unpkg.com instead of Greasyfork ==== 1.9.6 (2020.08.08) ==== * Reduced size of load more links compared to comment text * Fixed script icon * Removed some unnecessary code ==== 1.9.5 (2018.07.11) ==== * Updated jQuery to v3 and source from unpkg.com * Add downloadURL to update from Gist ==== 1.9.4 (2018.02.11) ==== * Added @icon field in metadata as SVG wasn't displaying on the installed userscript page ==== 1.9.3 (2017.12.03) ==== * Changed base-64 encoded PNG icons to an SVG icon ==== 1.9.2 (2017.10.11) ==== * Gets correct comment ID for links * Changed location in comment HTML to use as its root * Get children of first comment when it is already on the page ==== 1.9.1 (2017.10.11) ==== * Fix broken $target selector ==== 1.9.0 ==== * Catch failed loads, log them to the console and then restore original load link */ ; (async ($, MutationSummary) => { const config = GM_webextPref({ navbar: false, default: { autoExpandWhenVisible: false, expandOnMouseOver: false, expandOnMouseOverDelay: 500, }, body: [ { key: 'autoExpandWhenVisible', label: 'Automatically expand any links when they come into view?', type: 'checkbox', }, { key: 'expandOnMouseOver', label: 'Expand links when you move the mouse over them?', type: 'checkbox', }, { key: 'expandOnMouseOverDelay', label: 'Delay between when you move the mouse over a link and it expands (ms)', type: 'number', }, ], onSave(newSettings) { settings = newSettings createOrDestroyIntersectionObserver() addOrRemoveMouseoverHandler() }, }) await config.ready() config.on('change', changedSettings => { settings = { ...settings, ...changedSettings } createOrDestroyIntersectionObserver() addOrRemoveMouseoverHandler() }) let settings = config.getAll() // -------------------------------------------------------------------- $.fn.extend({ spinner(options) { options = { replace: true, mode: 'append', steps: 3, size: 24, colour: '#28f', step_duration: 0.25, ...options } const $spinner = $('<div class="pulsar-horizontal"></div>') .css({ padding: `${options.size * 0.25}px`, height: `${options.size}px` }) const total_duration = (options.steps + 1) * options.step_duration for (let i = 0; i < options.steps; i++) { const delay = i * options.step_duration $('<div></div>') .css({ width: `${options.size}px`, height: `${options.size}px`, backgroundColor: options.colour, animationDuration: `${total_duration}s`, animationDelay: `${delay}s` }) .appendTo($spinner) } if (options.replace) { this.empty() } return options.mode === 'prepend' ? this.prepend($spinner) : this.append($spinner) }, log(name = '$') { const title = [ `%c${name}%c : %c${this.length}%c ${this.length > 1 ? 'items' : 'item'}`, 'font-weight: bold', '', 'color: #05f', '' ] if (this.length > 0) { console.group(...title) console.info(this) console.groupEnd() } else { console.info(...title) } return this } }) // -------------------------------------------------------------------- async function loadAndInsertComments(cid, $span, $target) { $target.data('loading', 'true') $span.spinner() const data = await $.get(postUrl + cid) const $comments = $('.nestedlisting > .thing > .child > .sitetable', data) $target .empty() .append($comments) .find('.usertext.border .usertext-body') .css('animation', 'fadenewpost 4s ease-out 4s both') } // -------------------------------------------------------------------- function getCommentId(linkElem) { const m = linkElem.pathname.match(/\/([a-z0-9]+)\/?$/) if (!m) { throw new Error(`No comment ID parsed from link URL "${linkElem.href}"`) } return m[1] } // -------------------------------------------------------------------- function processDeepThreadSpans(deepThreadSpans) { const $deepThreadSpans = $(deepThreadSpans) .filter(':not([data-comment-ids])') // console.info(`processDeepThreadSpans: processing ${$deepThreadSpans.length}/${deepThreadSpans.length} deep thread spans`) $deepThreadSpans.each(function () { const $span = $(this) const $target = $span.closest('.child') const $a = $span.children('a') const cid = getCommentId($a[ 0 ]) let first = true $span .attr('data-comment-ids', cid) .addClass('expand-inline') $a .wrapInner('<span class="expand-text"></span>') .on('click', event => { const loading = $target.data('loading') if (first && !loading) { first = false loadAndInsertComments(cid, $span, $target) } return false }) }) } // -------------------------------------------------------------------- function uncollapseComments($collapsedComments) { $collapsedComments .removeClass('collapsed') .addClass('noncollapsed') .find('> .entry .tagline .expand') .text('[-]') } function uncollapseAllComments($collapsedComments, depth = 3) { // console.log($collapsedComments, depth) if ($collapsedComments.length > 0 && depth > 0) { uncollapseComments($collapsedComments) requestAnimationFrame(() => { uncollapseAllComments($collapsedComments.find('.thing.comment.collapsed'), depth - 1) }) } } // -------------------------------------------------------------------- const rootUrl = `https://${location.hostname}/` const postUrl = $('.thing.link > .entry a.comments').prop('href') // console.info(`%cSite:%c ${rootUrl}\n%cPost:%c ${postUrl}`, 'font-weight: bold', '', 'font-weight: bold', '') // -------------------------------------------------------------------- let intersectionObserver = null function createOrDestroyIntersectionObserver() { if (settings.autoExpandWhenVisible && !intersectionObserver) { intersectionObserver = new IntersectionObserver( (entries, observer) => { for (const entry of entries) { if (entry.isIntersecting) { entry.target.click() observer.unobserve(entry.target) } } }, { threshold: 0.5 } ) $('span.morecomments, span.deepthread').each(function() { intersectionObserver.observe(this.firstElementChild) }) console.log('IntersectionObserver created') } else if (!settings.autoExpandWhenVisible && intersectionObserver) { intersectionObserver.disconnect() intersectionObserver = null console.log('IntersectionObserver destroyed') } } createOrDestroyIntersectionObserver() // -------------------------------------------------------------------- function addOrRemoveMouseoverHandler() { $('.commentarea').off('mouseenter.spiralx') if (settings.expandOnMouseOver) { const hoveredElems = new WeakMap() $('.commentarea') .on('mouseenter.spiralx', '.expand-text', function() { const elem = this const timeoutId = setTimeout(() => { hoveredElems.delete(elem) elem.click() }, settings.expandOnMouseOverDelay) hoveredElems.set(elem, timeoutId) }) .on('mouseleave.spiralx', '.expand-text', function() { const timeoutId = hoveredElems.get(this) if (timeoutId) { clearTimeout(timeoutId) hoveredElems.delete(this) } }) } } addOrRemoveMouseoverHandler() // -------------------------------------------------------------------- function markAsExpand(selectorOrElements, observe = true) { const $elems = $(selectorOrElements) .addClass('expand-inline') .children('a') .wrapInner('<span class="expand-text"></span>') if (intersectionObserver) { $elems.each(function() { intersectionObserver.observe(this.firstElementChild) }) } } // -------------------------------------------------------------------- // Uncollapse non-top level comments on page load uncollapseAllComments($('.thing.comment .thing.comment.collapsed')) const observer = new MutationSummary({ callback([ deepThreadSpans, moreCommentsSpans, comments ]) { // console.log(`Added ${deepThreadSpans.added.length} deep thread spans and ${moreCommentsSpans.added.length} more comment spans`) markAsExpand(moreCommentsSpans.added) processDeepThreadSpans(deepThreadSpans.added) const $collapsedComments = $(comments.added).filter('.collapsed') uncollapseAllComments($collapsedComments) }, rootNode: document.body, queries: [ { element: 'span.deepthread' }, { element: 'span.morecomments' }, { element: '.thing.comment' }, ] }) // To process spans in the HTML source markAsExpand('span.morecomments', false) processDeepThreadSpans($('span.deepthread')) // -------------------------------------------------------------------- $(document.body).append(`<style type="text/css"> .expand-inline { display: block; padding: 0; } .expand-inline:after { display: none !important; } .expand-inline a { display: block; text-align: left; } .expand-inline a:before { content: "↘️"; padding-right: 0.4em; } .expand-inline a:hover { background-color: rgba(0, 105, 255, 0.05); text-decoration: none; } .pulsar-horizontal { display: inline-block; } .pulsar-horizontal > div { display: inline-block; border-radius: 100%; animation-name: pulsing; animation-timing-function: ease-in-out; animation-iteration-count: infinite; animation-fill-mode: both; } @keyframes pulsing { 0%, 100% { transform: scale(0); opacity: 0.5; } 50% { transform: scale(1); opacity: 1; } } @keyframes fadenewpost { 0% { background-color: #ffc; padding-left: 5px; } 100% { background-color: transparent; padding-left: 0; } } </style>`) })(jQuery, MutationSummary?.MutationSummary) jQuery.noConflict(true)