Pixiv Infinite Scroll/Download Links

Adds infinite scroll and inline expansion on the search page and artists' works pages. For manga mode a two-step expansion is used.

当前为 2014-07-07 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Pixiv Infinite Scroll/Download Links
  3. // @description Adds infinite scroll and inline expansion on the search page and artists' works pages. For manga mode a two-step expansion is used.
  4. // @namespace https://github.com/an-electric-sheep/userscripts
  5. // @match *://www.pixiv.net/search*
  6. // @match *://www.pixiv.net/member_illust*
  7. // @version 0.2
  8. // @grant none
  9. // @run-at document-start
  10. // ==/UserScript==
  11.  
  12. "use strict";
  13.  
  14. var Maybe = function (wrapped) {
  15. if (typeof this !== "object" || Object.getPrototypeOf(this) !== Maybe.prototype) {
  16. var o = Object.create(Maybe.prototype);
  17. o.constructor.apply(o, arguments);
  18. return o;
  19. }
  20. this.wrapped = wrapped;
  21. }
  22.  
  23. Maybe.prototype.isEmpty = function(){return null == this.wrapped}
  24. Maybe.prototype.orElse = function(other){return this.isEmpty() ? Maybe(other) : this}
  25. Maybe.prototype.apply = function(f){if(!this.isEmpty()){f.apply(null, [this.wrapped].concat(Array.slice(arguments, 1)))};return this;}
  26. Maybe.prototype.map = function(f){return this.isEmpty() ? this : Maybe(f.apply(null, [this.wrapped].concat(Array.slice(arguments,1))));}
  27. Maybe.prototype.get = function(){return this.wrapped;}
  28.  
  29. var paginator;
  30. var loading = false;
  31.  
  32. var imgContainerSelector = ".image-items, .display_works > ul";
  33.  
  34. document.addEventListener("DOMContentLoaded", function() {
  35. for(var e of document.querySelectorAll("iframe, .ad-printservice, .popular-introduction")){e.remove()}
  36. var sheet = document.querySelector("head").appendChild(document.createElement("style")).sheet;
  37. [
  38. // global
  39. "#wrapper {width: unset;}",
  40. // search page
  41. ".layout-body {width: 85vw;}",
  42. // member page
  43. ".layout-a {width: unset;}",
  44. ".layout-a .layout-column-2 {width: calc(100vw - 190px);}",
  45. // member works list
  46. ".display_works {width: unset;}",
  47. ".display_works .image-item {float: none; }",
  48. // search and member works list
  49. ".image-items, .display_works > ul {display: flex;flex-wrap: wrap;}",
  50. ".image-item img {padding: 0px; border: none;}",
  51. ".inline-expandable {cursor: pointer;}",
  52. ".image-item.expanded {width: 100%; height: unset;}",
  53. ".image-item.expanded img {max-width: -moz-available;}",
  54. ".manga-item {background-color: #f3f3f3 !important;}",
  55. ".image-item img.manga-medium {max-width: 156px; max-height: 230px; cursor: pointer;}"
  56. ].forEach(r => sheet.insertRule(r,0))
  57. paginator = Maybe(document.querySelectorAll(".pager-container")).map(paginators => paginators[paginators.length-1]).get();
  58. window.addEventListener("scroll", isNextNeeded)
  59. window.addEventListener("resize", isNextNeeded)
  60. for(var e of document.querySelectorAll(".image-item")){customizeImageItem(e)}
  61. isNextNeeded();
  62. })
  63.  
  64.  
  65.  
  66. function inViewport (el) {
  67.  
  68. var rect = el.getBoundingClientRect();
  69.  
  70. return (
  71. rect.top >= 0 &&
  72. rect.left >= 0 &&
  73. rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
  74. rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  75. );
  76. }
  77.  
  78.  
  79. function insertMangaSubItem(parentItem,url) {
  80. var req = new XMLHttpRequest
  81. req.open("get", url)
  82. req.onload = function() {
  83. var rsp = this.responseXML
  84. var nextItem = parentItem.nextSibling
  85. for(var e of rsp.querySelectorAll(".item-container")) {
  86. let mediumImg = e.querySelector(".image")
  87. let bigUrl = e.querySelector(".full-size-container").href
  88. let item = document.createElement("li")
  89. item.className = "image-item manga-item"
  90. let img = document.createElement("img")
  91. img.src = mediumImg.dataset.src
  92. img.className = "manga-medium"
  93. img.addEventListener("click",function(){
  94. this.parentElement.classList.add("expanded");
  95. this.className = "manga"
  96. this.src = this.src.replace(/(_p\d+)/, "_big$1")
  97. })
  98. item.appendChild(img)
  99. parentItem.parentNode.insertBefore(item, nextItem)
  100. }
  101. }
  102. req.responseType = "document"
  103. req.send()
  104. }
  105.  
  106.  
  107. function listItemExpand() {
  108. var container = this.parentNode
  109. var mediumLink = container.querySelector("a.work").href
  110. var req = new XMLHttpRequest
  111. req.open("get", mediumLink)
  112. req.onload = function() {
  113. var rsp = this.responseXML;
  114. var modeLinkUrl = rsp.querySelector(".works_display a").href
  115. var mediumSrc = rsp.querySelector(".works_display img").src
  116. var mode = modeLinkUrl.match(/mode=(\w+)/)[1]
  117. if(mode == "big") {
  118. var img = container.querySelector("img")
  119. img.src = mediumSrc.replace("_m.", ".");
  120. container.classList.add("expanded")
  121. }
  122. if(mode == "manga"){
  123. insertMangaSubItem(container, modeLinkUrl)
  124. }
  125. }
  126. req.responseType = "document"
  127. req.send()
  128. this.removeEventListener("click", listItemExpand)
  129. }
  130.  
  131. const greasedImageItems = new WeakMap;
  132.  
  133. function customizeImageItem(e) {
  134. if(greasedImageItems.has(e))
  135. return;
  136. greasedImageItems.set(e, true);
  137. var workLink = e.querySelector("a.work")
  138. // it's an animation, currently no autoexpand support for that
  139. if(workLink.classList.contains("ugoira-thumbnail"))
  140. return;
  141. var img = workLink.querySelector("img")
  142. img.classList.add("inline-expandable")
  143. img.dataset.thumbSrc = img.src
  144. e.insertBefore(img, workLink)
  145. img.addEventListener("click", listItemExpand)
  146. }
  147.  
  148. function loadNext() {
  149. if(loading)
  150. return;
  151. loading = true;
  152. var nextLink = paginator.querySelector("a[rel=next]")
  153. if(nextLink) {
  154. var req = new XMLHttpRequest();
  155. req.open("get", nextLink.href)
  156. req.onload = function() {
  157. var rsp = this.responseXML;
  158. var container = document.querySelector(imgContainerSelector)
  159. for(var e of rsp.querySelectorAll(".image-item")){
  160. var imageItem = document.importNode(e, true)
  161. container.appendChild(imageItem)
  162. customizeImageItem(imageItem)
  163. }
  164. while(paginator.hasChildNodes())
  165. paginator.firstChild.remove()
  166. for(var e of rsp.querySelector(".pager-container").childNodes){paginator.appendChild(document.importNode(e, true) )}
  167. loading = false;
  168. isNextNeeded();
  169. }
  170. req.responseType = "document"
  171. req.send()
  172. }
  173. }
  174.  
  175. function isNextNeeded() {
  176. if(loading)
  177. return;
  178.  
  179. if(paginator && inViewport(document.querySelector(".image-item:last-child"))) {
  180. loadNext();
  181. }
  182. }