Infinite scroller

2021. 1. 6. 오전 11:17:30

  1. // ==UserScript==
  2. // @name Infinite scroller
  3. // @namespace Violentmonkey Scripts
  4. // @match https://*.arca.live/*
  5. // @grant none
  6. // @version 1.0.3
  7. // @author awk
  8. // @description 2021. 1. 6. 오전 11:17:30
  9. // ==/UserScript==
  10.  
  11. // bounce-back element size
  12. const bounceBackBuffer = 96;
  13.  
  14. // last-checked page
  15. let scrolledPage = 1;
  16. let isLoading = false;
  17.  
  18. // from Arca Refresher@LeKAKiD v2.6.8
  19. function getTimeStr(datetime) {
  20. const date = new Date(datetime);
  21. let hh = date.getHours();
  22. let mm = date.getMinutes();
  23.  
  24. if (hh.toString().length == 1) {
  25. hh = `0${hh}`;
  26. }
  27.  
  28. if (mm.toString().length == 1) {
  29. mm = `0${mm}`;
  30. }
  31.  
  32. return `${hh}:${mm}`;
  33. }
  34.  
  35. function in24(datetime) {
  36. const target = new Date(datetime);
  37. const criteria = new Date();
  38. criteria.setHours(criteria.getHours() - 24);
  39. if (target > criteria) return true;
  40. return false;
  41. }
  42.  
  43. const bounceBack = () => {
  44. if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
  45. scrollBy({
  46. 'top': -bounceBackBuffer,
  47. 'left': 0,
  48. 'behavior': 'smooth'
  49. });
  50. }
  51. };
  52.  
  53. const getArticleId = elem => ~~elem.href.match(/\/b\/[^/]+\/(\d+)/)[1];
  54.  
  55. const loadMore = async () => {
  56. if(isLoading) return;
  57. isLoading = true;
  58. // true if scrolledPage value is reached to unread page
  59. let reached = false;
  60. const currentArticles = [...document.querySelectorAll('.vrow')];
  61. const currentLastArticleId = getArticleId(currentArticles[currentArticles.length - 1]);
  62. // determine if current page doesn't include any article
  63. if(!currentLastArticleId) {
  64. isLoading = false;
  65. return;
  66. }
  67. while(!reached) {
  68. ++scrolledPage;
  69. // get next-page url
  70. const nextPage = (() => {
  71. // determine if current url includes page information
  72. if(location.href.match(/[&?]p=(\d+)/)) {
  73. return location.href.replace(/[&?]p=\d+/, pageString => {
  74. return pageString.replace(/\d+/, scrolledPage);
  75. });
  76. }
  77. // determine if current url includes query string
  78. else if(location.href.match(/\?.+?=.+?/)){
  79. return `${location.href}&p=${scrolledPage}`;
  80. }
  81. // it might be first-page of the board
  82. else {
  83. return `${location.href}?p=${scrolledPage}`;
  84. }
  85. })();
  86. console.log(nextPage);
  87.  
  88. // fetch and parse next-page DOM
  89. const nextPageDOM = await fetch(nextPage)
  90. .then(res => res.text())
  91. .then(pageText => new DOMParser().parseFromString(pageText, 'text/html'));
  92.  
  93. const nextPageArticles = [...nextPageDOM.querySelectorAll('.vrow:last-child')];
  94. const nextPageLastArticle = nextPageArticles[nextPageArticles.length - 1];
  95. // determine if reached to end
  96. if(!nextPageLastArticle || nextPageLastArticle.classList.length != 1) {
  97. // reached to end
  98. bounceBack();
  99. break;
  100. }
  101. // determine if reached to unread page
  102. const nextPageLastArticleId = getArticleId(nextPageLastArticle);
  103. if(currentLastArticleId > nextPageLastArticleId) {
  104. // filter unread articles
  105. const unreadArticles = [...nextPageDOM.querySelectorAll('.vrow')].filter(article => {
  106. // filter out notices
  107. if(article.classList.length != 1) return false;
  108. return getArticleId(article) < currentLastArticleId;
  109. });
  110. // attach unread articles with applying <time> js
  111. const articleList = document.querySelector('.list-table');
  112. unreadArticles.forEach(article => {
  113. const time = article.querySelector('time');
  114.  
  115. if (time && in24(time.dateTime)) {
  116. time.innerText = getTimeStr(time.dateTime);
  117. }
  118. articleList.appendChild(article)
  119. });
  120. break;
  121. }
  122. }
  123. document.dispatchEvent(new Event('ar_article'));
  124. isLoading = false;
  125. return;
  126. }
  127.  
  128. window.addEventListener('load', e => {
  129. // load current page
  130. const pageMatch = location.href.match(/[&?]p=(\d+)/);
  131. console.log(location.href);
  132. scrolledPage = pageMatch ? ~~pageMatch[1] : 1;
  133. // attach bounce-back element
  134. const bounceBackElement = document.createElement('div');
  135. bounceBackElement.style.width = 'inherit';
  136. bounceBackElement.style.height = `${bounceBackBuffer}px`;
  137. // center-align text
  138. bounceBackElement.style.textAlign = 'center';
  139. bounceBackElement.style.lineHeight = `${bounceBackBuffer}px`;
  140. // element color
  141. bounceBackElement.style.backgroundColor = 'grey';
  142. bounceBackElement.style.color = 'white';
  143. // element text
  144. bounceBackElement.innerText = 'Loading next page...';
  145. bounceBackElement.fontWeight = 'bold';
  146. document.body.appendChild(bounceBackElement);
  147. // apply smooth-scroll for bounce-back
  148. document.body.style.scrollBehavior = 'smooth';
  149. });
  150.  
  151. window.addEventListener('scroll', e => {
  152. if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
  153. loadMore();
  154. }
  155. });