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:
parent
4cb058f1ca
commit
ecc41bebe0
|
@ -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
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue