浏览代码

enhance(mobile): fixed position of head-bar & mobile-bar (#3360)

enhance(mobile): fixed position of head-bar & mobile-bar
Co-authored-by: charlie <[email protected]>
Tienson Qin 3 年之前
父节点
当前提交
d9452bd739

+ 2 - 0
resources/css/common.css

@@ -9,6 +9,8 @@
   --ls-scrollbar-width: 6px;
   --ls-border-radius-low: 4px;
   --ls-border-radius-medium: 8px;
+  --ls-headbar-height: 3rem;
+  --ls-headbar-inner-top-padding: 0px;
   --ls-left-sidebar-width: 240px;
   --ls-left-sidebar-nav-btn-size: 38px;
 }

+ 94 - 85
src/main/frontend/components/editor.cljs

@@ -8,6 +8,7 @@
             [frontend.components.search :as search]
             [frontend.components.svg :as svg]
             [frontend.config :as config]
+            [frontend.handler.notification :as notification]
             [frontend.db :as db]
             [frontend.extensions.zotero :as zotero]
             [frontend.handler.editor :as editor-handler :refer [get-state]]
@@ -217,91 +218,99 @@
 
 (rum/defc mobile-bar < rum/reactive
   [parent-state parent-id]
-  [:div#mobile-editor-toolbar.bg-base-2.fix-ios-fixed-bottom
-   [:div.flex.justify-around.w-full
-    [:div
-     [:button.bottom-action
-      {:on-mouse-down (fn [e]
-                        (util/stop e)
-                        (editor-handler/indent-outdent true))}
-      (ui/icon "arrow-bar-right"
-               {:style {:fontSize ui/icon-size}})]]
-    [:div
-     [:button.bottom-action
-      {:on-mouse-down (fn [e]
-                        (util/stop e)
-                        (editor-handler/indent-outdent false))}
-      (ui/icon "arrow-bar-left"
-               {:style {:fontSize ui/icon-size}})]]
-    [:div
-     [:button.bottom-action
-      {:on-mouse-down (fn [e]
-                        (util/stop e)
-                        ((editor-handler/move-up-down true)))}
-      (ui/icon "arrow-bar-to-up"
-               {:style {:fontSize ui/icon-size}})]]
-    [:div
-     [:button.bottom-action
-      {:on-mouse-down (fn [e]
-                        (util/stop e)
-                        ((editor-handler/move-up-down false)))}
-      (ui/icon "arrow-bar-to-down"
-               {:style {:fontSize ui/icon-size}})]]
-    [:div
-     [:button.bottom-action
-      {:on-mouse-down (fn [e]
-                        (util/stop e)
-                        (commands/simple-insert! parent-id "\n"
-                                                 {:forward-pos 1})
-                        ;; TODO: should we add this focus step to `simple-insert!`?
-                        (when-let [input (gdom/getElement parent-id)]
-                          (.focus input)))}
-      (ui/icon "arrow-back"
-               {:style {:fontSize ui/icon-size}})]]
-    [:div
-     [:button.bottom-action
-      {:on-mouse-down (fn [e]
-                        (util/stop e)
-                        (editor-handler/cycle-todo!))}
-      (ui/icon "checkbox"
-               {:style {:fontSize ui/icon-size}})]]
-    [:div
-     [:button.bottom-action
-      {:on-mouse-down (fn [e]
-                        (util/stop e)
-                        (commands/simple-insert!
-                         parent-id "[[]]"
-                         {:backward-pos 2
-                          :check-fn     (fn [_ _ new-pos]
-                                          (reset! commands/*slash-caret-pos new-pos)
-                                          (commands/handle-step [:editor/search-page]))})
-                        (when-let [input (gdom/getElement parent-id)]
-                          (.focus input)))}
-      (ui/icon "brackets"
-               {:style {:fontSize ui/icon-size}})]]
-    [:div
-     [:button.bottom-action
-      {:on-mouse-down (fn [e]
-                        (util/stop e)
-                        (commands/simple-insert!
-                         parent-id "(())"
-                         {:backward-pos 2
-                          :check-fn     (fn [_ _ new-pos]
-                                          (reset! commands/*slash-caret-pos new-pos)
-                                          (commands/handle-step [:editor/search-block]))})
-                        (when-let [input (gdom/getElement parent-id)]
-                          (.focus input)))}
-      (ui/icon "parentheses"
-               {:style {:fontSize ui/icon-size}})]]
-    [:div
-     [:button.bottom-action
-      {:on-mouse-down (fn [e]
-                        (util/stop e)
-                        (commands/simple-insert! parent-id "/" {})
-                        (when-let [input (gdom/getElement parent-id)]
-                          (.focus input)))}
-      (ui/icon "command"
-               {:style {:fontSize ui/icon-size}})]]]])
+  (let [vw-state (state/sub :ui/visual-viewport-state)
+        vw-pending? (state/sub :ui/visual-viewport-pending?)]
+    [:div#mobile-editor-toolbar.bg-base-2
+     {:style {:bottom (if (and vw-state)
+                        (- (.-clientHeight js/document.documentElement)
+                           (:height vw-state)
+                           (:offset-top vw-state))
+                        0)}
+      :class (util/classnames [{:is-vw-pending (boolean vw-pending?)}])}
+     [:div.flex.justify-around.w-full
+      [:div
+       [:button.bottom-action
+        {:on-mouse-down (fn [e]
+                          (util/stop e)
+                          (editor-handler/indent-outdent true))}
+        (ui/icon "arrow-bar-right"
+                 {:style {:fontSize ui/icon-size}})]]
+      [:div
+       [:button.bottom-action
+        {:on-mouse-down (fn [e]
+                          (util/stop e)
+                          (editor-handler/indent-outdent false))}
+        (ui/icon "arrow-bar-left"
+                 {:style {:fontSize ui/icon-size}})]]
+      [:div
+       [:button.bottom-action
+        {:on-mouse-down (fn [e]
+                          (util/stop e)
+                          ((editor-handler/move-up-down true)))}
+        (ui/icon "arrow-bar-to-up"
+                 {:style {:fontSize ui/icon-size}})]]
+      [:div
+       [:button.bottom-action
+        {:on-mouse-down (fn [e]
+                          (util/stop e)
+                          ((editor-handler/move-up-down false)))}
+        (ui/icon "arrow-bar-to-down"
+                 {:style {:fontSize ui/icon-size}})]]
+      [:div
+       [:button.bottom-action
+        {:on-mouse-down (fn [e]
+                          (util/stop e)
+                          (commands/simple-insert! parent-id "\n"
+                                                   {:forward-pos 1})
+                          ;; TODO: should we add this focus step to `simple-insert!`?
+                          (when-let [input (gdom/getElement parent-id)]
+                            (.focus input)))}
+        (ui/icon "arrow-back"
+                 {:style {:fontSize ui/icon-size}})]]
+      [:div
+       [:button.bottom-action
+        {:on-mouse-down (fn [e]
+                          (util/stop e)
+                          (editor-handler/cycle-todo!))}
+        (ui/icon "checkbox"
+                 {:style {:fontSize ui/icon-size}})]]
+      [:div
+       [:button.bottom-action
+        {:on-mouse-down (fn [e]
+                          (util/stop e)
+                          (commands/simple-insert!
+                            parent-id "[[]]"
+                            {:backward-pos 2
+                             :check-fn     (fn [_ _ new-pos]
+                                             (reset! commands/*slash-caret-pos new-pos)
+                                             (commands/handle-step [:editor/search-page]))})
+                          (when-let [input (gdom/getElement parent-id)]
+                            (.focus input)))}
+        (ui/icon "brackets"
+                 {:style {:fontSize ui/icon-size}})]]
+      [:div
+       [:button.bottom-action
+        {:on-mouse-down (fn [e]
+                          (util/stop e)
+                          (commands/simple-insert!
+                            parent-id "(())"
+                            {:backward-pos 2
+                             :check-fn     (fn [_ _ new-pos]
+                                             (reset! commands/*slash-caret-pos new-pos)
+                                             (commands/handle-step [:editor/search-block]))})
+                          (when-let [input (gdom/getElement parent-id)]
+                            (.focus input)))}
+        (ui/icon "parentheses"
+                 {:style {:fontSize ui/icon-size}})]]
+      [:div
+       [:button.bottom-action
+        {:on-mouse-down (fn [e]
+                          (util/stop e)
+                          (commands/simple-insert! parent-id "/" {})
+                          (when-let [input (gdom/getElement parent-id)]
+                            (.focus input)))}
+        (ui/icon "command"
+                 {:style {:fontSize ui/icon-size}})]]]]))
 
 (rum/defcs input < rum/reactive
   (rum/local {} ::input-value)

+ 2 - 2
src/main/frontend/components/editor.css

@@ -1,15 +1,15 @@
 #mobile-editor-toolbar {
   position: fixed;
   bottom: 0;
-  width: 100%;
   left: 0;
+  width: 100%;
   justify-content: center;
   height: 2.5rem;
   display: flex;
   align-items: center;
   z-index: 9999;
 
-  transition: top 0.3s;
+  transition: none;
 
   button {
     padding: 10px;

+ 8 - 4
src/main/frontend/components/header.cljs

@@ -161,6 +161,8 @@
   (let [repos (->> (state/sub [:me :repos])
                    (remove #(= (:url %) config/local-repo)))
         electron-mac? (and util/mac? (util/electron?))
+        vw-state (state/sub :ui/visual-viewport-state)
+        vw-pending? (state/sub :ui/visual-viewport-pending?)
         show-open-folder? (and (or (nfs/supported?)
                                    (mobile-util/is-native-platform?))
                                (empty? repos)
@@ -168,15 +170,17 @@
         refreshing? (state/sub :nfs/refreshing?)]
     (rum/with-context [[t] i18n/*tongue-context*]
       [:div.cp__header#head
-       {:class (cond electron-mac? "electron-mac"
-                     (mobile-util/native-ios?) "native-ios"
-                     (mobile-util/native-android?) "native-android")
+       {:class           (util/classnames [{:electron-mac   electron-mac?
+                                            :native-ios     (mobile-util/native-ios?)
+                                            :native-android (mobile-util/native-android?)
+                                            :is-vw-pending  (boolean vw-pending?)}])
         :on-double-click (fn [^js e]
                            (when-let [target (.-target e)]
                              (when (and (util/electron?)
                                         (or (.. target -classList (contains "cp__header"))))
                                (js/window.apis.toggleMaxOrMinActiveWindow))))
-        :style {:fontSize 50}}
+        :style           {:fontSize  50
+                          :transform (str "translateY(" (or (:offset-top vw-state) 0) "px)")}}
        [:div.l.flex
         (left-menu-button {:on-click (fn []
                                        (open-fn)

+ 23 - 1
src/main/frontend/components/header.css

@@ -1,8 +1,10 @@
 .cp__header {
-  @apply shadow z-10 h-12;
+  @apply shadow z-10;
   -webkit-app-region: drag;
 
   padding-right: 0.5rem;
+  padding-top: var(--ls-headbar-inner-top-padding);
+  height: calc(var(--ls-headbar-height) + var(--ls-headbar-inner-top-padding));
   display: flex;
   align-items: center;
   justify-content: space-between;
@@ -170,3 +172,23 @@ a.button {
 .is-mac.is-electron :is(.cp__header, .cp__right-sidebar-topbar) :is(button, .button, a) {
   cursor: default !important;
 }
+
+html.is-native-ios,
+html.is-ios.is-safari {
+  #main-container {
+    padding-top: 20px;
+  }
+
+  .cp__header {
+    position: fixed !important;
+    background-color: var(--ls-primary-background-color);
+  }
+
+  .is-vw-pending {
+    display: none !important;
+  }
+}
+
+html.is-native-ios {
+  --ls-headbar-inner-top-padding: 36px;
+}

+ 3 - 2
src/main/frontend/components/sidebar.cljs

@@ -568,8 +568,9 @@
           :close-fn    close-fn
           :route-match route-match})
 
-        [:div.#app-container.h-screen.flex {:style {:padding-top (ui/main-content-top-padding)}}
-         [:div.flex-1.h-full.flex.flex-col#left-container.relative
+        [:div.#app-container
+         {:style {:padding-top (ui/main-content-top-padding)}}
+         [:div#left-container
           {:class (if (state/sub :ui/sidebar-open?) "overflow-hidden" "w-full")}
           (header/header {:open-fn        open-fn
                           :white?         white?

+ 23 - 11
src/main/frontend/components/sidebar.css

@@ -1,8 +1,8 @@
-@supports(padding: max(0px)) {
-    .post {
-        padding-left: max(12px, env(safe-area-inset-left));
-        padding-right: max(12px, env(safe-area-inset-right));
-    }
+@supports (padding: max(0px)) {
+  .post {
+    padding-left: max(12px, env(safe-area-inset-left));
+    padding-right: max(12px, env(safe-area-inset-right));
+  }
 }
 
 #app-container {
@@ -21,9 +21,14 @@
 }
 
 #app-container {
+  display: flex;
   flex: 0 0 100%;
 }
 
+#left-container {
+  @apply flex flex-1 flex-col relative h-screen;
+}
+
 #main-container {
   position: relative;
   height: 100%;
@@ -44,6 +49,12 @@ html.is-mobile {
   #main-content.is-left-sidebar-open {
     padding-left: 0;
   }
+
+  .left-sidebar-inner {
+    > .wrap {
+      padding-top: 64px;
+    }
+  }
 }
 
 #left-sidebar {
@@ -64,8 +75,8 @@ html.is-mobile {
 
 #left-bar {
   background-color: var(--ls-primary-background-color);
-  width:             70vw;
-  max-width:         300px;
+  width: 70vw;
+  max-width: 300px;
 
   > .head-wrap {
     background-color: var(--ls-search-background-color);
@@ -100,10 +111,11 @@ html.is-mobile {
   overflow: auto;
 
   > .wrap {
-      padding-top: 24px;
-      @screen md {
-          padding-top: 60px;
-      }
+    padding-top: 24px;
+
+    @screen md {
+      padding-top: 55px;
+    }
   }
 
   .dropdown-wrapper {

+ 0 - 6
src/main/frontend/components/theme.cljs

@@ -62,12 +62,6 @@
      #(when system-theme?
         (ui/setup-system-theme-effect!))
      [system-theme?])
-
-    (rum/use-effect!
-     #(when (mobile-util/native-ios?)
-        (ui/setup-patch-ios-fixed-bottom-position!))
-     [edit?])
-
     [:div
      {:class    (str theme "-theme")
       :on-click on-click}

+ 3 - 1
src/main/frontend/page.cljs

@@ -17,7 +17,9 @@
                    (ui/inject-document-devices-envs!)
                    (ui/inject-dynamic-style-node!)
                    (plugin-handler/host-mounted!)
-                   (let [teardown-fn (ui/setup-active-keystroke!)]
+                   (let [teardown-fn (comp
+                                      (ui/setup-active-keystroke!)
+                                      (ui/setup-patch-ios-visual-viewport-state!))]
                      (assoc state ::teardown teardown-fn)))
    :will-unmount (fn [state]
                    (let [teardown (::teardown state)]

+ 10 - 0
src/main/frontend/state.cljs

@@ -82,6 +82,8 @@
       :ui/shortcut-tooltip? (if (false? (storage/get :ui/shortcut-tooltip?))
                               false
                               true)
+      :ui/visual-viewport-pending? false
+      :ui/visual-viewport-state nil
 
       :document/mode? document-mode?
 
@@ -1578,3 +1580,11 @@
 (defn get-last-key-code
   []
   (:editor/last-key-code @state))
+
+(defn set-visual-viewport-state
+  [input]
+  (set-state! :ui/visual-viewport-state input))
+
+(defn get-visual-viewport-state
+  []
+  (:ui/visual-viewport-state @state))

+ 33 - 32
src/main/frontend/ui.cljs

@@ -50,7 +50,6 @@
     :else
     0))
 
-
 (defonce icon-size (if (mobile-util/is-native-platform?) 23 20))
 
 (rum/defc ls-textarea
@@ -305,35 +304,37 @@
         (.appendChild js/document.head node))
       style)))
 
-(defn setup-patch-ios-fixed-bottom-position!
-  "fix a common issue about ios webpage viewport
-   when soft keyboard setup"
+(defn setup-patch-ios-visual-viewport-state!
   []
-  (when (and
-         (util/ios?)
-         (not (nil? js/window.visualViewport)))
-    (let [viewport js/visualViewport
-          style (get-dynamic-style-node)
-          sheet (.-sheet style)
-          raf-pending? (atom false)
+  (when-let [^js vp (and (or (and (util/mobile?) (util/safari?))
+                             (mobile-util/native-ios?))
+                         js/window.visualViewport)]
+    (let [raf-pending? (atom false)
           set-raf-pending! #(reset! raf-pending? %)
-          handler
+          on-viewport-changed
           (fn []
-            (when-not @raf-pending?
-              (let [f (fn []
-                        (set-raf-pending! false)
-                        (let [vh (+ (.-offsetTop viewport) (.-height viewport))
-                              rule (.. sheet -rules (item 0))
-                              set-top #(set! (.. rule -style -top) (str % "px"))]
-                          (set-top vh)))]
-                (set-raf-pending! true)
-                (js/window.requestAnimationFrame f))))]
-      (.insertRule sheet ".fix-ios-fixed-bottom {bottom:unset !important; transform: translateY(-100%); top: 100vh;}")
-      (.addEventListener viewport "resize" handler)
-      (.addEventListener viewport "scroll" handler)
+            (let [update-vw-state
+                  (util/debounce 20
+                                 (fn []
+                                   (state/set-visual-viewport-state {:height     (.-height vp)
+                                                                     :page-top   (.-pageTop vp)
+                                                                     :offset-top (.-offsetTop vp)})
+                                   (state/set-state! :ui/visual-viewport-pending? false)))]
+              (when-not @raf-pending?
+                (let [f (fn []
+                          (set-raf-pending! false)
+                          (update-vw-state))]
+                  (set-raf-pending! true)
+                  (state/set-state! :ui/visual-viewport-pending? true)
+                  (js/window.requestAnimationFrame f)))))]
+
+      (.addEventListener vp "resize" on-viewport-changed)
+      (.addEventListener vp "scroll" on-viewport-changed)
+
       (fn []
-        (.removeEventListener viewport "resize" handler)
-        (.removeEventListener viewport "scroll" handler)))))
+        (.removeEventListener vp "resize" on-viewport-changed)
+        (.removeEventListener vp "scroll" on-viewport-changed)
+        (state/set-visual-viewport-state nil)))))
 
 (defn setup-system-theme-effect!
   []
@@ -471,14 +472,14 @@
                        (str/split  #" |\+"))
                    sequence)]
     [:span.keyboard-shortcut
-   (map-indexed (fn [i key]
-                  [:code {:key i}
+     (map-indexed (fn [i key]
+                    [:code {:key i}
                    ;; Display "cmd" rather than "meta" to the user to describe the Mac
                    ;; mod key, because that's what the Mac keyboards actually say.
-                   (if (or (= :meta key) (= "meta" key))
-                     (util/meta-key-name)
-                     (name key))])
-                sequence)]))
+                     (if (or (= :meta key) (= "meta" key))
+                       (util/meta-key-name)
+                       (name key))])
+                  sequence)]))
 
 (defn keyboard-shortcut-from-config [shortcut-name]
   (let [default-binding (:binding (get shortcut-config/all-default-keyboard-shortcuts shortcut-name))