Quellcode durchsuchen

Add experimental infinite scroll

Zed vor 6 Jahren
Ursprung
Commit
2c6d2897ae
6 geänderte Dateien mit 82 neuen und 11 gelöschten Zeilen
  1. 54 0
      public/js/infiniteScroll.js
  2. 1 0
      src/prefs.nim
  3. 3 0
      src/prefs_impl.nim
  4. 20 10
      src/routes/timeline.nim
  5. 1 1
      src/utils.nim
  6. 3 0
      src/views/general.nim

+ 54 - 0
public/js/infiniteScroll.js

@@ -0,0 +1,54 @@
+function insertBeforeLast(node, elem) {
+    node.insertBefore(elem, node.childNodes[node.childNodes.length - 2]);
+}
+
+function getLoadMore(doc) {
+    return doc.querySelector('.show-more:not(.timeline-item)');
+}
+
+window.onload = function() {
+    const isTweet = window.location.pathname.indexOf("/status/") !== -1;
+    const containerClass = isTweet ? ".replies" : ".timeline";
+    const itemClass = isTweet ? ".thread-line" : ".timeline-item";
+
+    var html = document.querySelector("html");
+    var container = document.querySelector(containerClass);
+    var loading = false;
+
+    window.addEventListener('scroll', function() {
+        if (loading) return;
+        if (html.scrollTop + html.clientHeight >= html.scrollHeight - 3000) {
+            loading = true;
+            var topRef = document.querySelector('.top-ref');
+            var loadMore = getLoadMore(document);
+            if (loadMore == null) return;
+
+            loadMore.children[0].text = "Loading...";
+
+            var url = new URL(loadMore.children[0].href);
+            window.history.pushState('', '', url.toString());
+            url.searchParams.append('scroll', 'true');
+
+            fetch(url.toString()).then(function (response) {
+	            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 (isTweet) container.appendChild(item);
+                    else insertBeforeLast(container, item);
+                }
+
+                if (isTweet) container.appendChild(getLoadMore(doc));
+                else insertBeforeLast(container, getLoadMore(doc));
+                loading = false;
+            }).catch(function (err) {
+	            console.warn('Something went wrong.', err);
+                loading = true;
+            });
+        }
+    });
+};

+ 1 - 0
src/prefs.nim

@@ -18,6 +18,7 @@ withDb:
   Prefs.theme.safeAddColumn
   Prefs.hidePins.safeAddColumn
   Prefs.hideReplies.safeAddColumn
+  Prefs.infiniteScroll.safeAddColumn
 
 proc getDefaultPrefs(cfg: Config): Prefs =
   result = genDefaultPrefs()

+ 3 - 0
src/prefs_impl.nim

@@ -73,6 +73,9 @@ genPrefs:
     theme(select, "Nitter"):
       "Theme"
 
+    infiniteScroll(checkbox, false):
+      "Infinite scrolling (requires JavaScript, experimental!)"
+
     stickyProfile(checkbox, true):
       "Make profile sidebar stick to top"
 

+ 20 - 10
src/routes/timeline.nim

@@ -1,10 +1,11 @@
 import asyncdispatch, strutils, sequtils, uri, options
-import jester
+import jester, karax/vdom
 
 import router_utils
 import ".."/[api, types, cache, formatters, agents, query]
 import ../views/[general, profile, timeline, status, search]
 
+export vdom
 export uri, sequtils
 export router_utils
 export api, cache, formatters, query, agents
@@ -55,13 +56,10 @@ proc fetchMultiTimeline*(names: seq[string]; after, agent: string; query: Query;
 proc get*(req: Request; key: string): string =
   params(req).getOrDefault(key)
 
-proc showTimeline*(request: Request; query: Query; cfg: Config; rss: string): Future[string] {.async.} =
-  let
-    agent = getAgent()
-    prefs = cookiePrefs()
-    name = request.get("name")
-    after = request.get("max_position")
-    names = getNames(name)
+proc showTimeline*(request: Request; query: Query; cfg: Config; prefs: Prefs;
+                   rss, after: string): Future[string] {.async.} =
+  let agent = getAgent()
+  let names = getNames(request.get("name"))
 
   if names.len != 1:
     let timeline = await fetchMultiTimeline(names, after, agent, query)
@@ -82,6 +80,10 @@ template respTimeline*(timeline: typed) =
     resp Http404, showError("User \"" & @"name" & "\" not found", cfg)
   resp timeline
 
+template respScroll*(timeline: typed) =
+  timeline.beginning = true # don't render "load newest"
+  resp $renderTimelineTweets(timeline, prefs, getPath())
+
 proc createTimelineRouter*(cfg: Config) =
   setProfileCacheTime(cfg.profileCacheTime)
 
@@ -89,10 +91,18 @@ proc createTimelineRouter*(cfg: Config) =
     get "/@name/?@tab?":
       cond '.' notin @"name"
       cond @"tab" in ["with_replies", "media", "search", ""]
-      let query = request.getQuery(@"tab", @"name")
+      let
+        prefs = cookiePrefs()
+        after = @"max_position"
+        query = request.getQuery(@"tab", @"name")
+
+      if @"scroll".len > 0:
+        respScroll(await fetchTimeline(@"name", after, getAgent(), query))
+
       var rss = "/$1/$2/rss" % [@"name", @"tab"]
       if @"tab".len == 0:
         rss = "/$1/rss" % @"name"
       elif @"tab" == "search":
         rss &= "?" & genQueryUrl(query)
-      respTimeline(await showTimeline(request, query, cfg, rss))
+
+      respTimeline(await showTimeline(request, query, cfg, prefs, rss, after))

+ 1 - 1
src/utils.nim

@@ -41,7 +41,7 @@ proc cleanFilename*(filename: string): string =
     result &= ".png"
 
 proc filterParams*(params: Table): seq[(string, string)] =
-  let filter = ["name", "id", "list", "referer"]
+  let filter = ["name", "id", "list", "referer", "scroll"]
   toSeq(params.pairs()).filterIt(it[0] notin filter and it[1].len > 0)
 
 proc isTwitterUrl*(url: string): bool =

+ 3 - 0
src/views/general.nim

@@ -56,6 +56,9 @@ proc renderHead*(prefs: Prefs; cfg: Config; titleText=""; desc=""; video="";
       script(src="/js/hls.light.min.js")
       script(src="/js/hlsPlayback.js")
 
+    if prefs.infiniteScroll:
+      script(src="/js/infiniteScroll.js")
+
     title:
       if titleText.len > 0:
         text titleText & " | " & cfg.title