Browse Source

keep-alive journals to keep scroll position

Tienson Qin 3 months ago
parent
commit
d5a9b1e43a

+ 2 - 0
src/main/frontend/components/journal.cljs

@@ -31,6 +31,8 @@
 (rum/defc all-journals < rum/reactive db-mixins/query
   []
   (let [data (sub-journals)]
+    (prn :debug :data (count data))
+    (js/console.dir (util/app-scroll-container-node))
     (when (seq data)
       [:div#journals
        (ui/virtualized-list

+ 10 - 17
src/main/frontend/util.cljc

@@ -93,27 +93,20 @@
 #?(:cljs (defonce ^js node-path utils/nodePath))
 #?(:cljs (defonce ^js sem-ver semver))
 #?(:cljs (defonce ^js full-path-extname pathCompleteExtname))
-#?(:cljs
-   (defn mobile-page-scroll
-     ([] (some-> (js/document.querySelector ".app-silk-index-scroll-content") (.-parentNode)))
-     ([el] (if el
-             (some-> (or (.closest el ".app-silk-scroll-content")
-                         (.closest el ".app-silk-index-scroll-content")) (.-parentNode))
-             (mobile-page-scroll)))))
 
 #?(:cljs (defn app-scroll-container-node
            ([]
-            (if (capacitor?)
-              (mobile-page-scroll)
-              (gdom/getElement "main-content-container")))
+            (or
+             (gdom/getElement "main-content-container")
+             (gdom/getElement "app-main-home")))
            ([el]
-            (if (capacitor?)
-              (mobile-page-scroll el)
-              (if (some-> el (.closest "#main-content-container"))
-                (app-scroll-container-node)
-                (or
-                 (gdom/getElementByClass "sidebar-item-list")
-                 (app-scroll-container-node)))))))
+            (if (or
+                 (some-> el (.closest "#main-content-container"))
+                 (some-> el (.closest "#app-main-home")))
+              (app-scroll-container-node)
+              (or
+               (gdom/getElementByClass "sidebar-item-list")
+               (app-scroll-container-node))))))
 #?(:cljs (defonce el-visible-in-viewport? utils/elementIsVisibleInViewport))
 #?(:cljs (defonce convert-to-roman utils/convertToRoman))
 #?(:cljs (defonce convert-to-letters utils/convertToLetters))

+ 88 - 66
src/main/mobile/components/app.cljs

@@ -116,28 +116,22 @@
    [:div.pt-3
     (journal/all-journals)]))
 
-(rum/defc home-inner
-  [*page db-restoring? current-tab]
-  [:div {:id "app-main-content"
-         :ref *page}
-
-   ;; main content
-   (if db-restoring?
-     [:div.space-y-2.mt-8.mx-0.opacity-75
-      (shui/skeleton {:class "h-10 w-full mb-6 bg-gray-200"})
-      (shui/skeleton {:class "h-6 w-full bg-gray-200"})
-      (shui/skeleton {:class "h-6 w-full bg-gray-200"})]
-     (if (= current-tab "search")
-       [:div]
-       (journals)))])
-
-(rum/defc home < rum/reactive
+(rum/defc home-inner < rum/static
+  [db-restoring?]
+  (if db-restoring?
+    [:div.space-y-2.mt-8.mx-0.opacity-75
+     (shui/skeleton {:class "h-10 w-full mb-6 bg-gray-200"})
+     (shui/skeleton {:class "h-6 w-full bg-gray-200"})
+     (shui/skeleton {:class "h-6 w-full bg-gray-200"})]
+    (journals)))
+
+(rum/defc home < rum/reactive rum/static
   {:did-mount (fn [state]
                 (ui/inject-document-devices-envs!)
                 state)}
-  [*page current-tab]
+  []
   (let [db-restoring? (state/sub :db/restoring?)]
-    (home-inner *page db-restoring? current-tab)))
+    (home-inner db-restoring?)))
 
 (defn use-theme-effects!
   [current-repo]
@@ -175,65 +169,93 @@
        #(.removeEventListener js/window "orientationchange" handle-size!)))
    []))
 
+(comment
+  (rum/defc main-content-inner < rum/static
+    [tab route-match]
+    (let [view (get-in route-match [:data :view])
+          home? (and (= tab "home") (nil? view))]
+      [:<>
+       [:div#home-container {:class (when-not home? "hidden")}
+        (home)]
+       (case (keyword tab)
+         :home
+         (when view
+           (view route-match))
+         :settings
+         (settings/page)
+         :search
+         (if view
+           (view route-match)
+           (search/search))
+         "Not Found")])))
+
+(rum/defc main-content-inner < rum/static
+  [tab route-match]
+  (let [view (get-in route-match [:data :view])
+        ;; We are on the journals home screen if the tab is :home
+        ;; AND there is no view (e.g. not viewing a specific journal page)
+        home? (and (= tab "home") (nil? view))]
+    ;; Two-layer structure:
+    ;; - Journals layer keeps its own scroll container and is always in the DOM.
+    ;; - Page layer keeps its own independent scroll container.
+    ;; This ensures switching tabs does not reset scrollTop.
+    [:div#main-container
+     ;; Journals scroll container (keep-alive)
+     ;; This element stays mounted permanently and only toggles visibility.
+     [:div#app-main-home {:class (when-not home? "hidden")}
+      [:div.px-5
+       (home)]]
+
+     ;; Other pages: page, search, settings, etc.
+     ;; These views scroll independently from the journals layer.
+     (when-not home?
+       [:div#main-content-container.px-5
+        (case (keyword tab)
+          :home
+          (when view
+            (view route-match))
+
+          :settings
+          (settings/page)
+
+          :search
+          (if view
+            (view route-match)
+            (search/search))
+
+          nil)])]))
+
 (rum/defc main-content < rum/reactive
-  [tab *home]
-  (let [route-match (state/sub :route-match)
-        view (get-in route-match [:data :view])]
-    (prn :debug :route-match route-match
-         :view view)
-    (case (keyword tab)
-      :home
-      (if view
-        (view route-match)
-        (home *home tab))
-      :settings
-      (settings/page)
-      :search
-      (if view
-        (view route-match)
-        (search/search))
-      "Not Found")))
+  [tab]
+  (let [route-match (state/sub :route-match)]
+    (main-content-inner tab route-match)))
 
 (rum/defc app
   [current-repo {:keys [login?]}]
-  (let [[tab] (mobile-state/use-tab)
-        *home (rum/use-ref nil)]
+  (let [[tab] (mobile-state/use-tab)]
     (use-screen-size-effects!)
     (use-theme-effects! current-repo)
     (hooks/use-effect!
      (fn []
        (when (mobile-util/native-ios?)
          (bottom-tabs/configure))
-       (when-let [element (util/mobile-page-scroll)]
+       (when-let [element (util/app-scroll-container-node)]
          (common-handler/listen-to-scroll! element))) [])
-    (silkhq/depth-sheet-stack
-     {:as-child true}
-     (silkhq/depth-sheet-scenery-outlets
-      (silkhq/scroll {:as-child true}
-                     (silkhq/scroll-view
-                      {:class "app-silk-index-scroll-view"
-                       :pageScroll true
-                       :nativePageScrollReplacement false}
-                      (silkhq/scroll-content
-                       {:class "app-silk-index-scroll-content"}
-                       [:div.app-silk-index-container
-                        {:data-tab (str tab)}
-                        (main-content tab *home)])))
-
-      (mobile-header/header tab login?)
-
-      ;; bottom tabs
-      (when-not (mobile-util/native-ios?)
-        (ui-silk/app-silk-tabs))
-
-      (ui-component/keep-keyboard-virtual-input)
-      (ui-component/install-notifications)
-      (ui-component/install-modals)
-
-      (shui-toaster/install-toaster)
-      (shui-dialog/install-modals)
-      (shui-popup/install-popups)
-      (popup/popup)))))
+    [:div.mt-24.h-full
+     (mobile-header/header tab login?)
+     (main-content tab)
+     ;; bottom tabs
+     (when-not (mobile-util/native-ios?)
+       (ui-silk/app-silk-tabs))
+
+     (ui-component/keep-keyboard-virtual-input)
+     (ui-component/install-notifications)
+     (ui-component/install-modals)
+
+     (shui-toaster/install-toaster)
+     (shui-dialog/install-modals)
+     (shui-popup/install-popups)
+     (popup/popup)]))
 
 (rum/defc main < rum/reactive
   []

+ 13 - 8
src/main/mobile/components/app.css

@@ -38,10 +38,6 @@ html.is-native-android {
   }
 }
 
-#main-container {
-  overflow-y: visible;
-}
-
 html.has-mobile-keyboard {
   body {
     @apply overflow-hidden
@@ -529,10 +525,6 @@ html[data-silk-native-page-scroll-replaced=false] .app-silk-index-scroll-view {
   }
 }
 
-#app-main-content {
-  padding-bottom: 200px;
-}
-
 .menu-link, .cp__repos-quick-actions .ui__button {
   @apply text-base;
 }
@@ -559,3 +551,16 @@ html[data-silk-native-page-scroll-replaced=false] .app-silk-index-scroll-view {
     margin-bottom: -6px;
   }
 }
+
+body, #root {
+  @apply h-full;
+}
+
+/* Both layers use their own independent scroll containers. */
+#app-main-home, #main-content-container
+{
+    height: 100%;
+    overflow-y: auto;
+    -webkit-overflow-scrolling: touch;
+    padding-bottom: 48px;
+}

+ 1 - 1
src/main/mobile/components/search.cljs

@@ -65,7 +65,7 @@
      (fn []
        (if focused?
          (let [input (rum/deref *ref)
-               scroll-cnt (some-> input (.closest ".app-silk-index-scroll-content") (.-parentNode))
+               scroll-cnt (util/app-scroll-container-node)
                handle! (fn [] (some-> input (.blur)))]
            (.addEventListener scroll-cnt "scroll" handle!)
            #(.removeEventListener scroll-cnt "scroll" handle!))

+ 3 - 1
src/main/mobile/core.cljs

@@ -2,6 +2,7 @@
   "Mobile core"
   (:require ["react-dom/client" :as rdc]
             [clojure.string :as string]
+            [dommy.core :as dom]
             [frontend.background-tasks]
             [frontend.components.imports :as imports]
             [frontend.db.async :as db-async]
@@ -35,14 +36,15 @@
     (rfe/start!
      router
      (fn [route]
-       (route-handler/set-route-match! route)
        (let [route-name (get-in route [:data :name])
              path (-> js/location .-hash (string/replace-first #"^#" ""))]
+         (route-handler/set-route-match! route)
          (mobile-nav/notify-route-change!
           {:route {:to route-name
                    :path-params (:path-params route)
                    :query-params (:query-params route)}
            :path path})
+
          (when (not= route-name :left-sidebar)
            (reset! mobile-state/*left-sidebar-open? false)
            (bottom-tabs/show!))