Reddit - Load 'Continue this thread' inline

Changes 'Continue this thread' links to insert the linked comments into the current page

Instalează acest script?
Script sugerat de autor

Poate îți va plăcea șiReddit - Quick RES user tagging.

Instalează acest script
// ==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 <spiralx@gmail.com> (http://github.com/spiralx)
// @namespace      http://spiralx.org/
// @version        2.3.3
// @license        MIT
// @icon           data:image/png;base64,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
// @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)