Moved text-input props to their own functions, renamed get-suggestions to get-user-suggestions

Signed-off-by: shivekkhurana <shivek@status.im>
This commit is contained in:
shivekkhurana 2020-11-19 21:41:53 +05:30
parent 4cb058f1ca
commit ecc41bebe0
No known key found for this signature in database
GPG Key ID: 9BEB56E6E62968C7
3 changed files with 155 additions and 154 deletions

View File

@ -231,7 +231,7 @@
lcase-text (string/lower-case text)] lcase-text (string/lower-case text)]
(re-find regex lcase-text))) (re-find regex lcase-text)))
(defn get-suggestions [users searched-text] (defn get-user-suggestions [users searched-text]
(reduce (reduce
(fn [acc [k {:keys [alias name nickname searchable-phrases] :as user}]] (fn [acc [k {:keys [alias name nickname searchable-phrases] :as user}]]
(if-let [match (if-let [match
@ -272,26 +272,26 @@
(match-mention text users mention-key-idx (inc mention-key-idx) [])) (match-mention text users mention-key-idx (inc mention-key-idx) []))
([text users mention-key-idx next-word-idx words] ([text users mention-key-idx next-word-idx words]
(when-let [word (re-find word-regex (subs text next-word-idx))] (when-let [word (re-find word-regex (subs text next-word-idx))]
(let [new-words (conj words word) (let [new-words (conj words word)
searched-text (let [text (-> new-words searched-text (let [text (-> new-words
string/join string/join
string/lower-case string/lower-case
string/trim) string/trim)
last-char (dec (count text))] last-char (dec (count text))]
(if (re-matches ending-chars-regex (str (nth text last-char nil))) (if (re-matches ending-chars-regex (str (nth text last-char nil)))
(subs text 0 last-char) (subs text 0 last-char)
text)) text))
suggestions (get-suggestions users searched-text) user-suggestions (get-user-suggestions users searched-text)
suggestions-cnt (count suggestions)] user-suggestions-cnt (count user-suggestions)]
(cond (zero? suggestions-cnt) (cond (zero? user-suggestions-cnt)
nil nil
(and (= 1 suggestions-cnt) (and (= 1 user-suggestions-cnt)
(mentioned? (second (first suggestions)) (mentioned? (second (first user-suggestions))
(subs text (inc mention-key-idx)))) (subs text (inc mention-key-idx))))
(second (first suggestions)) (second (first user-suggestions))
(> suggestions-cnt 1) (> user-suggestions-cnt 1)
(let [word-len (count word) (let [word-len (count word)
text-len (count text) text-len (count text)
next-word-start (+ next-word-idx word-len)] next-word-start (+ next-word-idx word-len)]
@ -300,33 +300,32 @@
next-word-start new-words)))))))) next-word-start new-words))))))))
(defn replace-mentions (defn replace-mentions
([text users-fn] ([text users]
(let [idxs (get-at-signs text)] (let [idxs (get-at-signs text)]
(replace-mentions text users-fn idxs 0))) (replace-mentions text users idxs 0)))
([text users-fn idxs diff] ([text users idxs diff]
(if (or (string/blank? text) (if (or (string/blank? text)
(empty? idxs)) (empty? idxs))
text text
(let [mention-key-idx (- (first idxs) diff)] (let [mention-key-idx (- (first idxs) diff)]
(if-not mention-key-idx (if-not mention-key-idx
text text
(let [users (users-fn)] (if-not (seq users)
(if-not (seq users) text
text (let [{:keys [public-key match]}
(let [{:keys [public-key match]} (match-mention text users mention-key-idx)]
(match-mention text users mention-key-idx)] (if-not match
(if-not match (recur text users (rest idxs) diff)
(recur text (fn [] users) (rest idxs) diff) (let [new-text (string/join
(let [new-text (string/join [(subs text 0 (inc mention-key-idx))
[(subs text 0 (inc mention-key-idx)) public-key
public-key (subs text (+ (inc mention-key-idx)
(subs text (+ (inc mention-key-idx) (count match)))])]
(count match)))])] (recur new-text users (rest idxs)
(recur new-text (fn [] users) (rest idxs) (+ diff (- (count text) (count new-text)))))))))))))
(+ diff (- (count text) (count new-text))))))))))))))
(defn check-mentions [cofx text] (defn check-mentions [cofx text]
(replace-mentions text #(get-mentionable-users cofx))) (replace-mentions text (get-mentionable-users cofx)))
(defn get-at-sign-idxs (defn get-at-sign-idxs
([text start] ([text start]
@ -401,14 +400,14 @@
new-idxs))))))) new-idxs)))))))
(defn check-entry (defn check-entry
[text {:keys [from checked?] :as entry} users-fn] [text {:keys [from checked?] :as entry} mentionable-users]
(if checked? (if checked?
entry entry
(let [{:keys [match]} (let [{user-match :match}
(match-mention (str text "@") (users-fn) from)] (match-mention (str text "@") mentionable-users from)]
(if match (if user-match
{:from from {:from from
:to (+ from (count match)) :to (+ from (count user-match))
:checked? true :checked? true
:mention? true} :mention? true}
{:from from {:from from
@ -416,12 +415,13 @@
:checked? true :checked? true
:mention false})))) :mention false}))))
(defn check-idx-for-mentions [text idxs users-fn] (defn check-idx-for-mentions
[text idxs mentionable-users]
(let [idxs (let [idxs
(reduce (reduce
(fn [acc {:keys [from] :as entry}] (fn [acc {:keys [from] :as entry}]
(let [previous-entry-idx (dec (count acc)) (let [previous-entry-idx (dec (count acc))
new-entry (check-entry text entry users-fn)] new-entry (check-entry text entry mentionable-users)]
(cond-> acc (cond-> acc
(and (>= previous-entry-idx 0) (and (>= previous-entry-idx 0)
(not (get-in acc [previous-entry-idx :mention?]))) (not (get-in acc [previous-entry-idx :mention?])))
@ -497,7 +497,7 @@
new-at-idxs (check-idx-for-mentions new-at-idxs (check-idx-for-mentions
text text
(:at-idxs state) (:at-idxs state)
(fn [] mentionable-users)) mentionable-users)
calculated-input (calculate-input text new-at-idxs)] calculated-input (calculate-input text new-at-idxs)]
(log/debug "[mentions] new-at-idxs" new-at-idxs calculated-input) (log/debug "[mentions] new-at-idxs" new-at-idxs calculated-input)
{:db (-> db {:db (-> db
@ -524,8 +524,8 @@
(assoc-in [:chats/input-with-mentions chat-id] [[:text text]]))} (assoc-in [:chats/input-with-mentions chat-id] [[:text text]]))}
(let [new-at-idxs (check-idx-for-mentions (let [new-at-idxs (check-idx-for-mentions
text text
(:at-idxs state) at-idxs
(fn [] mentionable-users)) mentionable-users)
calculated-input (calculate-input text new-at-idxs) calculated-input (calculate-input text new-at-idxs)
addition? (<= start end) addition? (<= start end)
end (if addition? end (if addition?
@ -536,7 +536,7 @@
mentions mentions
(when (and (not (> at-sign-idx start)) (when (and (not (> at-sign-idx start))
(not (> (- end at-sign-idx) 100))) (not (> (- end at-sign-idx) 100)))
(get-suggestions mentionable-users searched-text))] (get-user-suggestions mentionable-users searched-text))]
(log/debug "[mentions] mention check" (log/debug "[mentions] mention check"
"addition" addition? "addition" addition?
"at-sign-idx" at-sign-idx "at-sign-idx" at-sign-idx

View File

@ -4,19 +4,18 @@
[cljs.test :as test :include-macros true])) [cljs.test :as test :include-macros true]))
(test/deftest test-replace-mentions (test/deftest test-replace-mentions
(let [users (fn [] (let [users {"User Number One"
{"User Number One" {:name "User Number One"
{:name "User Number One" :alias "User Number One"
:alias "User Number One" :public-key "0xpk1"}
:public-key "0xpk1"} "User Number Two"
"User Number Two" {:name "user2"
{:name "user2" :alias "User Number Two"
:alias "User Number Two" :public-key "0xpk2"}
:public-key "0xpk2"} "User Number Three"
"User Number Three" {:name "user3"
{:name "user3" :alias "User Number Three"
:alias "User Number Three" :public-key "0xpk3"}}]
:public-key "0xpk3"}})]
(test/testing "empty string" (test/testing "empty string"
(let [text "" (let [text ""
result (mentions/replace-mentions text users)] result (mentions/replace-mentions text users)]

View File

@ -79,6 +79,80 @@
:accessibility-label :send-message-button :accessibility-label :send-message-button
:color (styles/send-icon-color)}]]]) :color (styles/send-icon-color)}]]])
(defn selection [cursor]
;; NOTE(rasom): In case if mention is added on pressing suggestion and
;; it is placed inside some text we have to specify `:selection` on
;; Android to ensure that cursor is added after the mention, not after
;; the last char in input. On iOS it works that way without this code
(when (and cursor platform/android?)
(clj->js {:start cursor :end cursor})))
(defn on-selection-change [cursor timeout-id last-text-change mentionable-users args]
(let [selection (.-selection ^js (.-nativeEvent ^js args))
start (.-start selection)
end (.-end selection)]
;; NOTE(rasom): on iOS we do not dispatch this event immediately
;; because it is needed only in case if selection is changed without
;; typing. Timeout might be canceled on `on-change`.
(when platform/ios?
(reset!
timeout-id
(utils.utils/set-timeout
#(re-frame/dispatch [::mentions/on-selection-change
{:start start
:end end}
mentionable-users])
50)))
;; NOTE(rasom): on Android we dispatch event only in case if there
;; was no text changes during last 50ms. `on-selection-change` is
;; dispatched after `on-change`, that's why there is no another way
;; to know whether selection was changed without typing.
(when (and platform/android?
(or (not @last-text-change)
(< 50 (- (js/Date.now) @last-text-change))))
(re-frame/dispatch [::mentions/on-selection-change
{:start start
:end end}
mentionable-users]))
;; NOTE(rasom): we have to reset `cursor` value when user starts using
;; text-input because otherwise cursor will stay in the same position
(when (and cursor platform/android?)
(re-frame/dispatch [::mentions/clear-cursor]))))
(defn on-change [on-text-change last-text-change timeout-id mentionable-users args]
(let [text (.-text ^js (.-nativeEvent ^js args))]
;; NOTE(rasom): on iOS `on-selection-change` is canceled in case if it
;; happens during typing because it is not needed for mention
;; suggestions calculation
(when (and platform/ios? @timeout-id)
(utils.utils/clear-timeout @timeout-id))
(when platform/android?
(reset! last-text-change (js/Date.now)))
(on-text-change text)
;; NOTE(rasom): on iOS `on-change` is dispatched after `on-text-input`,
;; that's why mention suggestions are calculated on `on-change`
(when platform/ios?
(re-frame/dispatch [::mentions/calculate-suggestions mentionable-users]))))
(defn on-text-input [mentionable-users args]
(let [native-event (.-nativeEvent ^js args)
text (.-text ^js native-event)
previous-text (.-previousText ^js native-event)
range (.-range ^js native-event)
start (.-start ^js range)
end (.-end ^js range)]
(re-frame/dispatch
[::mentions/on-text-input
{:new-text text
:previous-text previous-text
:start start
:end end}])
;; NOTE(rasom): on Android `on-text-input` is dispatched after
;; `on-change`, that's why mention suggestions are calculated
;; on `on-change`
(when platform/android?
(re-frame/dispatch [::mentions/calculate-suggestions mentionable-users]))))
(defn text-input (defn text-input
[{:keys [cooldown-enabled? input-with-mentions on-text-change set-active-panel text-input-ref]}] [{:keys [cooldown-enabled? input-with-mentions on-text-change set-active-panel text-input-ref]}]
(let [cursor @(re-frame/subscribe [:chat/cursor]) (let [cursor @(re-frame/subscribe [:chat/cursor])
@ -87,101 +161,29 @@
last-text-change (atom nil)] last-text-change (atom nil)]
[rn/view {:style (styles/text-input-wrapper)} [rn/view {:style (styles/text-input-wrapper)}
[rn/text-input [rn/text-input
{:style (styles/text-input) {:style (styles/text-input)
:ref text-input-ref :ref text-input-ref
:maxFontSizeMultiplier 1 :max-font-size-multiplier 1
:accessibility-label :chat-message-input :accessibility-label :chat-message-input
:text-align-vertical :center :text-align-vertical :center
:multiline true :multiline true
:editable (not cooldown-enabled?) :editable (not cooldown-enabled?)
:blur-on-submit false :blur-on-submit false
:auto-focus false :auto-focus false
:on-focus #(set-active-panel nil) :on-focus #(set-active-panel nil)
:max-length chat.constants/max-text-size :max-length chat.constants/max-text-size
:placeholder-text-color (:text-02 @colors/theme) :placeholder-text-color (:text-02 @colors/theme)
:placeholder (if cooldown-enabled? :placeholder (if cooldown-enabled?
(i18n/label :cooldown/text-input-disabled) (i18n/label :cooldown/text-input-disabled)
(i18n/label :t/type-a-message)) (i18n/label :t/type-a-message))
:underlineColorAndroid :transparent :underline-color-android :transparent
:auto-capitalize :sentences :auto-capitalize :sentences
:selection :selection (selection cursor)
;; NOTE(rasom): In case if mention is added on pressing suggestion and :on-selection-change (partial on-selection-change
;; it is placed inside some text we have to specify `:selection` on cursor timeout-id last-text-change mentionable-users)
;; Android to ensure that cursor is added after the mention, not after :on-change (partial on-change
;; the last char in input. On iOS it works that way without this code on-text-change last-text-change timeout-id mentionable-users)
(when (and cursor platform/android?) :on-text-input (partial on-text-input mentionable-users)}
(clj->js
{:start cursor
:end cursor}))
:on-selection-change
(fn [args]
(let [selection (.-selection ^js (.-nativeEvent ^js args))
start (.-start selection)
end (.-end selection)]
;; NOTE(rasom): on iOS we do not dispatch this event immediately
;; because it is needed only in case if selection is changed without
;; typing. Timeout might be canceled on `on-change`.
(when platform/ios?
(reset!
timeout-id
(utils.utils/set-timeout
#(re-frame/dispatch [::mentions/on-selection-change
{:start start
:end end}
mentionable-users])
50)))
;; NOTE(rasom): on Android we dispatch event only in case if there
;; was no text changes during last 50ms. `on-selection-change` is
;; dispatched after `on-change`, that's why there is no another way
;; to know whether selection was changed without typing.
(when (and platform/android?
(or (not @last-text-change)
(< 50 (- (js/Date.now) @last-text-change))))
(re-frame/dispatch [::mentions/on-selection-change
{:start start
:end end}
mentionable-users]))
;; NOTE(rasom): we have to reset `cursor` value when user starts using
;; text-input because otherwise cursor will stay in the same position
(when (and cursor platform/android?)
(re-frame/dispatch [::mentions/clear-cursor]))))
:on-change
(fn [args]
(let [text (.-text ^js (.-nativeEvent ^js args))]
;; NOTE(rasom): on iOS `on-selection-change` is canceled in case if it
;; happens during typing because it is not needed for mention
;; suggestions calculation
(when (and platform/ios? @timeout-id)
(utils.utils/clear-timeout @timeout-id))
(when platform/android?
(reset! last-text-change (js/Date.now)))
(on-text-change text)
;; NOTE(rasom): on iOS `on-change` is dispatched after `on-text-input`,
;; that's why mention suggestions are calculated on `on-change`
(when platform/ios?
(re-frame/dispatch [::mentions/calculate-suggestions mentionable-users]))))
:on-text-input
(fn [args]
(let [native-event (.-nativeEvent ^js args)
text (.-text ^js native-event)
previous-text (.-previousText ^js native-event)
range (.-range ^js native-event)
start (.-start ^js range)
end (.-end ^js range)]
(re-frame/dispatch
[::mentions/on-text-input
{:new-text text
:previous-text previous-text
:start start
:end end}])
;; NOTE(rasom): on Android `on-text-input` is dispatched after
;; `on-change`, that's why mention suggestions are calculated
;; on `on-change`
(when platform/android?
(re-frame/dispatch [::mentions/calculate-suggestions mentionable-users]))))}
;; NOTE(rasom): reduce was used instead of for here because although ;; NOTE(rasom): reduce was used instead of for here because although
;; each text component was given a unique id it still would mess with ;; each text component was given a unique id it still would mess with
;; colors on Android. In case if entire component is built without lists ;; colors on Android. In case if entire component is built without lists