Reddit - Load 'Continue this thread' inline

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

当前为 2016-04-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Reddit - Load 'Continue this thread' inline
  3. // @description Changes 'Continue this thread' links to insert the linked comments into the current page
  4. // @author James Skinner <spiralx@gmail.com> (http://github.com/spiralx)
  5. // @namespace http://spiralx.org/
  6. // @version 1.7.0
  7. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFiAAABYgAWToQQYAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAAF3SURBVDhP1ZI/SAJhGMZN3WpKOAgsKGiIoKWg0K2LXIJWj6DAhpAgKMiW7irBqziHbhYE12sLGtWGKHBx0kHcQrBJXNIz9e35vvsQoj841g9+fHy8z/PyHZzrXzANNbgKF2ASHsOhOYGTUIb3cBSq4hyKa3Ey2GsMeMRvvzALd+AVvIPbcAb+yAq8hBehUOgxnU5TtVol27ap3W5TpVKhVCpFsiw/IHMusstwgOHxeDZM07T7/T5Rr0vUfacBvR7uHWIzwzBayK+zDm8KktFo9FXEifaXiHbnxAXEZKKtqcHSSCTygs6nBYuWZfEhR1eIzjbFBdzsYcma8xKQyWQInXmn6jCeSCQ6fDoEqqqyzxhzqgK/368Xi0UeKJfLpGka5fN5yuVyrEClUonPCoUCSZLE/oMvjGBwqut6q16vU6PRoGw2yxc0m02q1WoUj8fffD5fjGWdyvdMeL3ew2AweKsoynM4HH4KBAKW2+0+wExyIn8Hl+sDt5ENCrpr91QAAAAASUVORK5CYII=
  8. // @icon64 data:image/png;base64,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
  9. // @match *://*.reddit.com/r/*/comments/*
  10. // @grant none
  11. // @run-at document-end
  12. // @require https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.js
  13. // @require https://greasyfork.org/scripts/7602-mutation-observer/code/mutation-observer.js
  14. // ==/UserScript==
  15.  
  16. /* jshint asi: true, esnext: true */
  17. /* global jQuery, MutationSummary */
  18.  
  19. ; (function userScript($) {
  20. 'use strict'
  21.  
  22. const NORMAL = 'font-weight: normal; text-decoration: none; color: black'
  23. const ERROR = 'font-weight: bold; color: #f4f'
  24. const LINK = 'color: #05f; text-decoration: underline'
  25. const BOLD = 'font-weight: bold'
  26. const BLUE = 'color: #05f'
  27.  
  28. 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'
  29. // --------------------------------------------------------------------
  30.  
  31. const units = (v, s) => `${v}${s}`
  32. const pluralise = (w, n) => w + (n !== 1 ? 's' : '')
  33. function* flatten (arr) {
  34. for (let x of arr) {
  35. if (Array.isArray(x)) {
  36. yield* (flatten(x))
  37. }
  38. else {
  39. yield x
  40. }
  41. }
  42. }
  43.  
  44. // --------------------------------------------------------------------
  45. $.fn.extend({
  46. spinner (options) {
  47. options = Object.assign({}, $.fn.spinner.defaults, options)
  48.  
  49. const $spinner = $('<div class="pulsar-horizontal"></div>')
  50. .css({
  51. padding: units(options.size * 0.25, 'px'),
  52. height: units(options.size, 'px')
  53. })
  54.  
  55. const total_duration = (options.steps + 1) * options.step_duration
  56.  
  57. for (let i = 0; i < options.steps; i++) {
  58. const delay = i * options.step_duration
  59.  
  60. $('<div></div>')
  61. .css({
  62. width: units(options.size, 'px'),
  63. height: units(options.size, 'px'),
  64. backgroundColor: options.colour,
  65. animationDuration: units(total_duration, 's'),
  66. animationDelay: units(delay, 's')
  67. })
  68. .appendTo($spinner)
  69. }
  70.  
  71. if (options.replace) {
  72. this.empty()
  73. }
  74.  
  75. return this.append($spinner)
  76. },
  77. log (name, ...extras) {
  78. const title = [ `%c${name || '$'}%c : %c${this.length}%c ${pluralise('item', this.length)}`, BOLD, NORMAL, BLUE, NORMAL ]
  79. if (this.length > 0 || extras.length > 0) {
  80. console.group.apply(console, title)
  81.  
  82. if (this.length > 0) {
  83. console.info(this)
  84. }
  85. extras.forEach(extra => {
  86. console.log(extra)
  87. })
  88.  
  89. console.groupEnd()
  90. }
  91. else {
  92. console.info.apply(console, title)
  93. }
  94. return this
  95. }
  96. })
  97.  
  98. $.fn.spinner.defaults = {
  99. replace: false,
  100. steps: 3,
  101. size: 16,
  102. colour: 'white',
  103. step_duration: 0.4
  104. }
  105.  
  106. // --------------------------------------------------------------------
  107.  
  108. function loadComments ($span, $target, ...ids) {
  109. const urls = ids.map(id => postUrl + id)
  110.  
  111. /*
  112. console.group(`%cloadComments%c(${ids.join(', ')})`, BOLD, NORMAL)
  113. console.info($span[0].outerHTML)
  114. console.log(`%c${urls.join('\n')}%c`, LINK, NORMAL)
  115. console.groupEnd()
  116. */
  117.  
  118. $span.spinner({
  119. colour: '#28f',
  120. size: 24,
  121. step_duration: 0.25,
  122. replace: true
  123. })
  124. const pageRequests = urls.map(url => {
  125. return $.get(url)
  126. .then(
  127. data => $('.nestedlisting > .thing', data).next().andSelf().get(),
  128. console.warn.bind(console)
  129. )
  130. })
  131. $.when(...pageRequests).then((...$children) => {
  132. const $all = $([...flatten($children)])
  133. if ($all.length === 2) {
  134. $all
  135. .find('> .entry > .usertext.border')
  136. .log('.border')
  137. .removeClass('border')
  138. }
  139.  
  140. $all
  141. .replaceAll($target)
  142. .find('.usertext.border .usertext-body')
  143. .css('animation', 'fadenewpost 4s ease-out 4s both')
  144. })
  145. }
  146.  
  147. // --------------------------------------------------------------------
  148.  
  149. function processDeepThreadSpans (deepThreadSpans) {
  150. const $deepThreadSpans = $(deepThreadSpans)
  151. .filter(':not([data-comment-ids])')
  152. // console.info(`processDeepThreadSpans: processing ${$deepThreadSpans.length}/${deepThreadSpans.length} deep thread spans`)
  153. $deepThreadSpans.each(function() {
  154. const $span = $(this),
  155. $a = $span.children('a'),
  156. cid = $a[0].pathname.split('/').pop()
  157. const $target = $span.parents(`.thing[data-fullname=t1_${cid}]`)
  158. $span
  159. .attr('data-comment-ids', cid)
  160. .addClass('expand-inline')
  161. $a.one('click', event => {
  162. loadComments($span, $target, cid)
  163. return false
  164. })
  165. })
  166. }
  167.  
  168. // --------------------------------------------------------------------
  169. function processMoreCommentsSpans(moreCommentsSpans) {
  170. const $moreCommentsSpans = $(moreCommentsSpans)
  171. .filter(':not([data-comment-ids])')
  172. // console.info(`processMoreCommentsSpans: processing ${$moreCommentsSpans.length}/${moreCommentsSpans.length} more comment spans`)
  173. $moreCommentsSpans.each(function() {
  174. const $span = $(this),
  175. $target = $span.closest('.thing'),
  176. $a = $span.children('a'),
  177. cids = $a.attr('onclick').split(', ')[3].slice(1, -1).split(',')
  178. $span
  179. .attr('data-comment-ids', cids.join(','))
  180. .addClass('expand-inline')
  181.  
  182. $a
  183. .removeAttr('onclick')
  184. .one('click', event => {
  185. loadComments($span, $target, ...cids)
  186. return false
  187. })
  188. })
  189. }
  190. // --------------------------------------------------------------------
  191. const rootUrl = `https://${location.hostname}/`
  192. const postUrl = $('.thing.link > .entry a.comments').prop('href')
  193. // console.info(`%cSite:%c %c${rootUrl}%c\n%cPost:%c %c${postUrl}%c`, BOLD, NORMAL, LINK, NORMAL, BOLD, NORMAL, LINK, NORMAL)
  194. // --------------------------------------------------------------------
  195.  
  196. const observer = new MutationSummary({
  197. callback(summaries) {
  198. const deepThreadSpans = summaries.shift().added,
  199. moreCommentsSpans = summaries.shift().added
  200. // console.log(`Added ${deepThreadSpans.length} deep thread spans and ${moreCommentsSpans.length} more comment spans`)
  201. processDeepThreadSpans(deepThreadSpans)
  202. processMoreCommentsSpans(moreCommentsSpans)
  203. },
  204. rootNode: document.body,
  205. queries: [
  206. { element: 'span.deepthread' },
  207. { element: 'span.morecomments' }
  208. ]
  209. })
  210.  
  211. // To process spans in the HTML source
  212. processDeepThreadSpans($('span.deepthread'))
  213. processMoreCommentsSpans($('span.morecomments'))
  214.  
  215. // --------------------------------------------------------------------
  216. $(document.body).append(`<style type="text/css">
  217. .expand-inline {
  218. display: block;
  219. padding: 0;
  220. }
  221. .expand-inline:after {
  222. display: none !important;
  223. }
  224. .expand-inline a {
  225. display: block;
  226. background: transparent url(${EXPAND_ICON}) no-repeat center left;
  227. padding-left: 40px;
  228. height: 40px;
  229. line-height: 40px;
  230. font-size: 1.4rem !important;
  231. font-weight: normal !important;
  232. vertical-align: middle;
  233. text-align: left;
  234. }
  235. .expand-inline a:hover {
  236. background-color: rgba(0, 105, 255, 0.05);
  237. text-decoration: none;
  238. }
  239.  
  240. .pulsar-horizontal {
  241. display: inline-block;
  242. }
  243. .pulsar-horizontal > div {
  244. display: inline-block;
  245. border-radius: 100%;
  246. animation-name: pulsing;
  247. animation-timing-function: ease-in-out;
  248. animation-iteration-count: infinite;
  249. animation-fill-mode: both;
  250. }
  251.  
  252. @keyframes pulsing {
  253. 0%, 100% {
  254. transform: scale(0);
  255. opacity: 0.5;
  256. }
  257. 50% {
  258. transform: scale(1);
  259. opacity: 1;
  260. }
  261. }
  262.  
  263. @keyframes fadenewpost {
  264. 0% {
  265. background-color: #ffc;
  266. padding-left: 5px;
  267. }
  268. 100% {
  269. background-color: transparent;
  270. padding-left: 0;
  271. }
  272. }
  273. </style>`)
  274.  
  275. })(jQuery)
  276.  
  277. jQuery.noConflict(true)