diff --git a/public/js/infiniteScroll.js b/public/js/infiniteScroll.js index be27e0c..a385edf 100644 --- a/public/js/infiniteScroll.js +++ b/public/js/infiniteScroll.js @@ -1,77 +1,82 @@ // @license http://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0 // SPDX-License-Identifier: AGPL-3.0-only function insertBeforeLast(node, elem) { - node.insertBefore(elem, node.childNodes[node.childNodes.length - 2]); + node.insertBefore(elem, node.childNodes[node.childNodes.length - 2]); } function getLoadMore(doc) { - return doc.querySelector(".show-more:not(.timeline-item)"); + return doc.querySelector(".show-more:not(.timeline-item)"); } function isDuplicate(item, itemClass) { - const tweet = item.querySelector(".tweet-link"); - if (tweet == null) return false; - const href = tweet.getAttribute("href"); - return document.querySelector(itemClass + " .tweet-link[href='" + href + "']") != null; + const tweet = item.querySelector(".tweet-link"); + if (tweet == null) return false; + const href = tweet.getAttribute("href"); + return ( + document.querySelector(itemClass + " .tweet-link[href='" + href + "']") != + null + ); } window.onload = function () { - const url = window.location.pathname; - const isTweet = url.indexOf("/status/") !== -1; - const containerClass = isTweet ? ".replies" : ".timeline"; - const itemClass = containerClass + " > div:not(.top-ref)"; + const url = window.location.pathname; + const isTweet = url.indexOf("/status/") !== -1; + const containerClass = isTweet ? ".replies" : ".timeline"; + const itemClass = containerClass + " > div:not(.top-ref)"; - var html = document.querySelector("html"); - var container = document.querySelector(containerClass); - var loading = false; + var html = document.querySelector("html"); + var container = document.querySelector(containerClass); + var loading = false; - function handleScroll(failed) { - if (loading) return; + function handleScroll(failed) { + if (loading) return; - if (html.scrollTop + html.clientHeight >= html.scrollHeight - 3000) { - loading = true; - var loadMore = getLoadMore(document); - if (loadMore == null) return; + if (html.scrollTop + html.clientHeight >= html.scrollHeight - 3000) { + loading = true; + var loadMore = getLoadMore(document); + if (loadMore == null) return; - loadMore.children[0].text = "Loading..."; + loadMore.children[0].text = "Loading..."; - var url = new URL(loadMore.children[0].href); - url.searchParams.append("scroll", "true"); + var url = new URL(loadMore.children[0].href); + url.searchParams.append("scroll", "true"); - fetch(url.toString()).then(function (response) { - if (response.status === 404) throw "error"; + fetch(url.toString()) + .then(function (response) { + if (response.status > 299) throw "error"; + return response.text(); + }) + .then(function (html) { + var parser = new DOMParser(); + var doc = parser.parseFromString(html, "text/html"); + loadMore.remove(); - return response.text(); - }).then(function (html) { - var parser = new DOMParser(); - var doc = parser.parseFromString(html, "text/html"); - loadMore.remove(); + for (var item of doc.querySelectorAll(itemClass)) { + if (item.className == "timeline-item show-more") continue; + if (isDuplicate(item, itemClass)) continue; + if (isTweet) container.appendChild(item); + else insertBeforeLast(container, item); + } - for (var item of doc.querySelectorAll(itemClass)) { - if (item.className == "timeline-item show-more") continue; - if (isDuplicate(item, itemClass)) continue; - if (isTweet) container.appendChild(item); - else insertBeforeLast(container, item); - } + loading = false; + const newLoadMore = getLoadMore(doc); + if (newLoadMore == null) return; + if (isTweet) container.appendChild(newLoadMore); + else insertBeforeLast(container, newLoadMore); + }) + .catch(function (err) { + console.warn("Something went wrong.", err); + if (failed > 3) { + loadMore.children[0].text = "Error"; + return; + } - loading = false; - const newLoadMore = getLoadMore(doc); - if (newLoadMore == null) return; - if (isTweet) container.appendChild(newLoadMore); - else insertBeforeLast(container, newLoadMore); - }).catch(function (err) { - console.warn("Something went wrong.", err); - if (failed > 3) { - loadMore.children[0].text = "Error"; - return; - } - - loading = false; - handleScroll((failed || 0) + 1); - }); - } + loading = false; + handleScroll((failed || 0) + 1); + }); } + } - window.addEventListener("scroll", () => handleScroll()); + window.addEventListener("scroll", () => handleScroll()); }; // @license-end diff --git a/src/api.nim b/src/api.nim index ef44bd0..c9a6f59 100644 --- a/src/api.nim +++ b/src/api.nim @@ -168,6 +168,11 @@ proc getGraphTweetSearch*(query: Query; after=""): Future[Timeline] {.async.} = result = parseGraphSearch[Tweets](js, after) result.query = query + # when no more items are available the API just returns the last page in + # full. this detects that and clears the page instead. + if after.len > 0 and after[0..<64] == result.bottom[0..<64]: + result.content.setLen(0) + proc getGraphUserSearch*(query: Query; after=""): Future[Result[User]] {.async.} = if query.text.len == 0: return Result[User](query: query, beginning: true) diff --git a/src/routes/status.nim b/src/routes/status.nim index 7322837..ce093cb 100644 --- a/src/routes/status.nim +++ b/src/routes/status.nim @@ -27,7 +27,7 @@ proc createStatusRouter*(cfg: Config) = if @"scroll".len > 0: let replies = await getReplies(id, getCursor()) if replies.content.len == 0: - resp Http404, "" + resp Http204 resp $renderReplies(replies, prefs, getPath()) let conv = await getTweet(id, getCursor()) diff --git a/src/routes/timeline.nim b/src/routes/timeline.nim index 379281a..39552fd 100644 --- a/src/routes/timeline.nim +++ b/src/routes/timeline.nim @@ -129,7 +129,8 @@ proc createTimelineRouter*(cfg: Config) = if @"scroll".len > 0: if query.fromUser.len != 1: var timeline = await getGraphTweetSearch(query, after) - if timeline.content.len == 0: resp Http404 + if timeline.content.len == 0: + resp Http204 timeline.beginning = true resp $renderTweetSearch(timeline, prefs, getPath()) else: