浏览代码

feat: imp move cursor up down by position

Weihua Lu 4 年之前
父节点
当前提交
237857aafe

+ 22 - 0
src/main/frontend/components/editor.cljs

@@ -1,6 +1,7 @@
 (ns frontend.components.editor
 (ns frontend.components.editor
   (:require [rum.core :as rum]
   (:require [rum.core :as rum]
             [frontend.components.svg :as svg]
             [frontend.components.svg :as svg]
+            [cljs-bean.core :as bean]
             [frontend.config :as config]
             [frontend.config :as config]
             [frontend.handler.editor :as editor-handler :refer [get-state]]
             [frontend.handler.editor :as editor-handler :refer [get-state]]
             [frontend.handler.editor.lifecycle :as lifecycle]
             [frontend.handler.editor.lifecycle :as lifecycle]
@@ -375,6 +376,25 @@
     6 {:font-size "0.75em" :font-weight "bold" :margin "1.67em 0"}
     6 {:font-size "0.75em" :font-weight "bold" :margin "1.67em 0"}
     nil))
     nil))
 
 
+
+(rum/defc mock-textarea
+  [content]
+  [:div#mock-text
+   {:style {:width "100%"
+            :height "100%"
+            :position "absolute"
+            :visibility "hidden"
+            :top 0
+            :left 0}}
+   (map-indexed (fn [idx c]
+                  (if (= c "\n")
+                    [:span {:id (str "mock-text_" idx)
+                          :key idx} "0" [:br]]
+                    [:span {:id (str "mock-text_" idx)
+                            :key idx} c]))
+                (string/split (str content "0") ""))])
+
+
 (rum/defcs box < rum/reactive
 (rum/defcs box < rum/reactive
   {:init (fn [state]
   {:init (fn [state]
            (assoc state ::heading-level (:heading-level (first (:rum/args state)))))
            (assoc state ::heading-level (:heading-level (first (:rum/args state)))))
@@ -406,6 +426,8 @@
        :auto-focus        false
        :auto-focus        false
        :style             (get-editor-style heading-level)})
        :style             (get-editor-style heading-level)})
 
 
+     (mock-textarea content)
+
      ;; TODO: how to render the transitions asynchronously?
      ;; TODO: how to render the transitions asynchronously?
      (transition-cp
      (transition-cp
       (commands id format)
       (commands id format)

+ 3 - 2
src/main/frontend/handler/editor.cljs

@@ -23,6 +23,7 @@
             [clojure.set :as set]
             [clojure.set :as set]
             [clojure.zip :as zip]
             [clojure.zip :as zip]
             [frontend.util :as util :refer-macros [profile]]
             [frontend.util :as util :refer-macros [profile]]
+            [frontend.util.cursor :as cursor]
             [frontend.config :as config]
             [frontend.config :as config]
             [dommy.core :as dom]
             [dommy.core :as dom]
             [frontend.utf8 :as utf8]
             [frontend.utf8 :as utf8]
@@ -2265,8 +2266,8 @@
 
 
       :else
       :else
       (if up?
       (if up?
-        (util/move-cursor-up input)
-        (util/move-cursor-down input)))))
+        (cursor/move-cursor-up input)
+        (cursor/move-cursor-down input)))))
 
 
 (defn- move-to-block-when-cross-boundrary
 (defn- move-to-block-when-cross-boundrary
   [direction]
   [direction]

+ 0 - 31
src/main/frontend/util.cljc

@@ -662,37 +662,6 @@
                      (count val))]
                      (count val))]
        (.setRangeText input "" start end))))
        (.setRangeText input "" start end))))
 
 
-#?(:cljs
-   (defn move-cursor-up
-     "Move cursor up. If EOL, always move cursor to previous EOL."
-     [input]
-     (let [val (gobj/get input "value")
-           pos (.-selectionStart input)
-           prev-idx (string/last-index-of val \newline pos)
-           pprev-idx (or (string/last-index-of val \newline (dec prev-idx)) -1)
-           cal-idx (+ pprev-idx pos (- prev-idx))]
-       (if (or (== pos (count val))
-               (> (- pos prev-idx) (- prev-idx pprev-idx)))
-         (move-cursor-to input prev-idx)
-         (move-cursor-to input cal-idx)))))
-
-#?(:cljs
-   (defn move-cursor-down
-     "Move cursor down by calculating current cursor line pos.
-  If EOL, always move cursor to next EOL."
-     [input]
-     (let [val (gobj/get input "value")
-           pos (.-selectionStart input)
-           prev-idx (or (string/last-index-of val \newline pos) -1)
-           next-idx (or (string/index-of val \newline (inc pos))
-                        (count val))
-           nnext-idx (or (string/index-of val \newline (inc next-idx))
-                        (count val))
-           cal-idx (+ next-idx pos (- prev-idx))]
-       (if (> (- pos prev-idx) (- nnext-idx next-idx))
-         (move-cursor-to input nnext-idx)
-         (move-cursor-to input cal-idx)))))
-
 ;; copied from re_com
 ;; copied from re_com
 #?(:cljs
 #?(:cljs
    (defn deref-or-value
    (defn deref-or-value

+ 116 - 0
src/main/frontend/util/cursor.cljs

@@ -0,0 +1,116 @@
+(ns frontend.util.cursor
+  (:require ["/frontend/caret_pos" :as caret-pos]
+            [cljs-bean.core :as bean]
+            [clojure.string :as string]
+            [frontend.util :as util]
+            [goog.dom :as gdom]
+            [goog.object :as gobj]))
+
+(defn- closer [a b c]
+  (let [a-left (or (:left a) 0)
+        b-left (:left b)
+        c-left (or (:left c) js/Number.MAX_SAFE_INTEGER)]
+    (if (< (- b-left a-left) (- c-left b-left))
+      a
+      c)))
+
+(defn mock-char-pos [e]
+  {:left (.-offsetLeft e)
+   :top  (.-offsetTop e)
+   :pos  (-> (.-id e)
+             (string/split "_")
+             second
+             int)})
+
+(defn get-caret-pos
+  [input]
+  (when input
+    (try
+      (let [pos ((gobj/get caret-pos "position") input)]
+        (bean/->clj pos))
+      (catch js/Error e
+        (js/console.error e)))))
+
+(defn move-cursor-to [input n]
+  (.setSelectionRange input n n))
+
+(defn move-cursor-up [input]
+  (let [elms  (-> (gdom/getElement "mock-text")
+                  gdom/getChildren
+                  array-seq)
+        cusor (-> input
+                  (util/get-caret-pos)
+                  (select-keys [:left :top :pos]))
+        chars (->> elms
+                   (map mock-char-pos)
+                   (group-by :top))
+        tops  (sort (keys chars))
+        tops-p (partition-by #(== (:top cusor) %) tops)
+        prev-t (-> tops-p first last)
+        lefts
+        (->> (get chars prev-t)
+             (partition-by (fn [char-pos]
+                             (<= (:left char-pos) (:left cusor)))))
+        left-a (-> lefts first last)
+        left-c (-> lefts last first)
+        closer
+        (if (> 2 (count lefts))
+          left-a
+          (closer left-a cusor left-c))]
+    (move-cursor-to input (:pos closer))))
+
+(defn move-cursor-down [input]
+  (let [elms  (-> (gdom/getElement "mock-text")
+                  gdom/getChildren
+                  array-seq)
+        cusor (-> input
+                  (util/get-caret-pos)
+                  (select-keys [:left :top :pos]))
+        chars (->> elms
+                   (map mock-char-pos)
+                   (group-by :top))
+        tops  (sort (keys chars))
+        tops-p (partition-by #(== (:top cusor) %) tops)
+        next-t (-> tops-p last first)
+        lefts
+        (->> (get chars next-t)
+             (partition-by (fn [char-pos]
+                             (<= (:left char-pos) (:left cusor)))))
+        left-a (-> lefts first last)
+        left-c (-> lefts last first)
+        closer
+        (if (< (count lefts) 2)
+          left-a
+          (closer left-a cusor left-c))]
+    (move-cursor-to input (:pos closer))))
+
+(comment
+  ;; previous implementation of up/down
+  (defn move-cursor-up
+    "Move cursor up. If EOL, always move cursor to previous EOL."
+    [input]
+    (let [val (gobj/get input "value")
+          pos (.-selectionStart input)
+          prev-idx (string/last-index-of val \newline pos)
+          pprev-idx (or (string/last-index-of val \newline (dec prev-idx)) -1)
+          cal-idx (+ pprev-idx pos (- prev-idx))]
+      (if (or (== pos (count val))
+              (> (- pos prev-idx) (- prev-idx pprev-idx)))
+        (move-cursor-to input prev-idx)
+        (move-cursor-to input cal-idx))))
+
+  (defn move-cursor-down
+    "Move cursor down by calculating current cursor line pos.
+  If EOL, always move cursor to next EOL."
+    [input]
+    (let [val (gobj/get input "value")
+          pos (.-selectionStart input)
+          prev-idx (or (string/last-index-of val \newline pos) -1)
+          next-idx (or (string/index-of val \newline (inc pos))
+                       (count val))
+          nnext-idx (or (string/index-of val \newline (inc next-idx))
+                        (count val))
+          cal-idx (+ next-idx pos (- prev-idx))]
+      (if (> (- pos prev-idx) (- nnext-idx next-idx))
+        (move-cursor-to input nnext-idx)
+        (move-cursor-to input cal-idx)))))