Reddit - Load 'Continue this thread' inline

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

От 25.01.2021. Виж последната версия.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

// ==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        1.9.6
// @icon           data:image/png;base64,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
// @match          *://*.reddit.com/r/*/comments/*
// @grant          none
// @run-at         document-end
// @require        https://unpkg.com/jquery@3/dist/jquery.min.js
// @require        https://greasyfork.org/scripts/7602-mutation-observer/code/mutation-observer.js
// ==/UserScript==

/* jshint asi: true, es6: true, laxbreak: true */
/* global jQuery, MutationSummary */

/*

==== 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

*/

; ($ => {
  'use strict'

  const EXPAND_ICON = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAADZklEQVR42tVXv08TURyvIdbYjg0DCQuaLjA4Y5rQ61lo0jASHIQFEnUQ/BGDkqJiAj00YkuPa0Fw4S8gYYDgxEYTdHaDgAMDDQUGBunz+7m79t6112srFPEln6Tpfd/3+3nfX+/7HI7/bQmCcDsQCNwnjIqBwEcAv/EfvtXFaCgUahQFYSwgCD/bu/tZa7/EWh4vseahZRUtj5bU//ANMpAN+XyNF2H4BpR1dIZPvINJ5n6/wxzTjDk+F2FaxyfG3OM7zDuQZNijEiEdf2VcFMVb5NYfbX2TzBnNaIZiOuIWiJkJOSczDHuhA7pqjfMdQQztNz1fMwzDyIyOhAXy33gy5JGmZ2vML3btQ2fVJxdog+d12jCeNyprcE3tMc/YpgkuaddMJmZ4wzOSVklU9ERPT4+Tkuh74eRxzvgsQQFy7OHwS/Ztfd2E4RcjrGHmVCOZ4LzBeQK6YaP86SlpEDeTcZkznmTsWirHJElixUuWZXY9ntXkZvV9M+ZwtD2YYLBhnfFUNshcp5Qx3J43ToYdKcIcEZi3IZDIanJJfR8fDj0xO+6FTyxLFMxQaoXTJ4qMzxO+EIEFGwJyVpNLMcMTCXMovAOKtRfQQNQ650+vmI07FonAog0BJavJzXEkZLMX3O921GZV0l7bu/ssTp+jmJ+R2wkLhK/A7/IEkoeaHOSxTzkjPbkSL8CWqSLQx9FK+dO7Puyp2T5FxoqxsrJSQgCVYCULHS5pz0SgtS8KAr08gVH0dp6A502arZPS8y7o8IyljTAQAdwdsGkkIN1ozcPLZgJv60SA8qD5yTKDzatD4J+HwDIJp3bV9ionEiWwIraxsWEpO0Q6XNFd+yRESViVYYN8St3tiBoMQSGkgMOyZXgz/kuTgzz2xY+0+6FSGV5uI9oubUR6K454B5XLasURy8vI3xk+VqefOl5G/mD42FduXtSu44l6X8eRKgaS1YoDCSqBR8WB5Olq5YEEKxgMtmB88rzatB/JqE8UQHVuP5JtqiMZdNcwlHbtFzxxrqF0tbahlO8NFK8t5IQzelDdWB7jx/KDfMy3qj65VU4gaZC5KFH3+Lb9wwR1TjIoNezB3ooxr2ahbKAMDeQudTG00uKnWRv9h2/60yziu4inWdnQUB8vfpyKfn9vzS+gq7D+AAlDQCI1XwNKAAAAAElFTkSuQmCC'

  // --------------------------------------------------------------------

  const units = (v, s) => `${v}${s}`

  const pluralise = (w, n) => w + (n !== 1 ? 's' : '')

  const capitalise = s => typeof s === 'string' && s && s.split(/\s+/g).map(w => w[0].toUpperCase() + w.substr(1).toLowerCase()).join(' ')

  function* flatten (arr) {
    for (let x of arr) {
      if (Array.isArray(x)) {
        yield* (flatten(x))
      }
      else {
        yield x
      }
    }
  }

  // --------------------------------------------------------------------

  $.fn.extend({
    spinner (options) {
      options = Object.assign({}, $.fn.spinner.defaults, options)

      const $spinner = $('<div class="pulsar-horizontal"></div>')
        .css({
          padding: units(options.size * 0.25, 'px'),
          height: units(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: units(options.size, 'px'),
            height: units(options.size, 'px'),
            backgroundColor: options.colour,
            animationDuration: units(total_duration, 's'),
            animationDelay: units(delay, 's')
          })
          .appendTo($spinner)
      }

      if (options.replace) {
        this.empty()
      }

      return options.mode === 'prepend'
        ? this.prepend($spinner)
        : this.append($spinner)
    },

    log (name, ...extras) {
      const title = [ `%c${name || '$'}%c : %c${this.length}%c ${pluralise('item', this.length)}`, 'font-weight: bold', '', 'color: #05f', '' ]

      if (this.length > 0 || extras.length > 0) {
        console.group.apply(console, title)

        if (this.length > 0) {
          console.info(this)
        }
        extras.forEach(extra => {
          console.log(extra)
        })

        console.groupEnd()
      }
      else {
        console.info.apply(console, title)
      }

      return this
    }
  })

  $.fn.spinner.defaults = {
    replace: true,
    mode: 'append',
    steps: 3,
    size: 24,
    colour: '#28f',
    step_duration: 0.25
  }

  // --------------------------------------------------------------------

  async function getCommentPage (id) {
    const data = await $.get(postUrl + id)

    return $('.nestedlisting', data)
  }

  // --------------------------------------------------------------------

  function addComments ($target, $comments) {
    $target
      .empty()
      .append($comments)
      .find('.usertext.border .usertext-body')
        .css('animation', 'fadenewpost 4s ease-out 4s both')
  }

  // --------------------------------------------------------------------

  function loadComments ($span, $target, ids) {
    let insertChildren = false

    if (!Array.isArray(ids)) {
      ids = [ ids ]
      insertChildren = true
    }

    const urls = ids.map(id => postUrl + id)

    const original = $span.parent().html()

    $span.spinner()

    const pageRequests = urls.map(url => {
      return $.get(url)
        .then(
          data => $('.nestedlisting', data).get(),
          (xhr, textStatus, errorThrown) => {
            console.warn(`%c${capitalise(textStatus)}: ${xhr.status} ${xhr.statusText}%c ${url}`, 'font-weight: bold; color: #f4f', '')
          }
        )
    })

    $.when(...pageRequests)
      .then((...children) => {
        let $children = $([...flatten(children)])

        if (insertChildren) {
          $children = $children.find('> .thing > .child > .sitetable')

          $children
            .find('> .entry > .usertext.border')
            .removeClass('border')
        }

        $target
          .empty()
          .append($children)
          .find('.usertext.border .usertext-body')
            .css('animation', 'fadenewpost 4s ease-out 4s both')
      })
      .fail((xhr, textStatus, errorThrown) => {
        $span.parent().html(original)
      })
  }

  // --------------------------------------------------------------------

  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 ])

      $span
        .attr('data-comment-ids', cid)
        .addClass('expand-inline')

      async function load () {
        $span.spinner()

        const $listing = await getCommentPage(cid)
        const $children = $listing.find('> .thing > .child > .sitetable')

        addComments($target, $children)
      }

      $a.one('click', event => {
        load()

        return false
      })
    })
  }

  // --------------------------------------------------------------------

  function processMoreCommentsSpans (moreCommentsSpans) {
    const $moreCommentsSpans = $(moreCommentsSpans)
      .filter(':not([data-comment-ids])')

    // console.info(`processMoreCommentsSpans: processing ${$moreCommentsSpans.length}/${moreCommentsSpans.length} more comment spans`)

    $moreCommentsSpans.each(function() {
      const $span = $(this)
      const $target = $span.closest('.child')

      const $a = $span.children('a')
      const onclick = $a.attr('onclick')
      const cids = onclick.split(', ')[3].slice(1, -1).split(',')

      $span
        .attr('data-comment-ids', cids.join(','))
        .addClass('expand-inline')

      async function load () {
        $span.spinner()

        const $listings = $(await Promise.all(cids.map(getCommentPage)))

        addComments($target, $listings)
      }

      $a
        .removeAttr('onclick')
        .attr('data-onclick', onclick)
        .one('click', event => {
          load()

          return false
        })
    })
  }

  function processMoreCommentsSpans2 (moreCommentsSpans) {
    $(moreCommentsSpans).addClass('expand-inline')
  }

  // --------------------------------------------------------------------

  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', '')

  // --------------------------------------------------------------------

  const observer = new MutationSummary({
    callback([ deepThreadSpans, moreCommentsSpans ]) {
      // console.log(`Added ${deepThreadSpans.added.length} deep thread spans and ${moreCommentsSpans.added.length} more comment spans`)

      processDeepThreadSpans(deepThreadSpans.added)
      processMoreCommentsSpans2(moreCommentsSpans.added)
    },
    rootNode: document.body,
    queries: [
      { element: 'span.deepthread' },
      { element: 'span.morecomments' }
    ]
  })

  // To process spans in the HTML source
  processDeepThreadSpans($('span.deepthread'))
  processMoreCommentsSpans2($('span.morecomments'))

  // --------------------------------------------------------------------

  $(document.body).append(`<style type="text/css">
    .expand-inline {
      display: block;
      padding: 0;
    }
    .expand-inline:after {
      display: none !important;
    }
    .expand-inline a {
      display: block;
      background: transparent url(${EXPAND_ICON}) no-repeat center left;
      padding-left: 40px;
      height: 32px;
      line-height: 32px;
      font-size: 1.2rem !important;
      font-weight: normal !important;
      vertical-align: middle;
      text-align: left;
    }
    .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)

jQuery.noConflict(true)