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)]
|
||||
(re-find regex lcase-text)))
|
||||
|
||||
(defn get-suggestions [users searched-text]
|
||||
(defn get-user-suggestions [users searched-text]
|
||||
(reduce
|
||||
(fn [acc [k {:keys [alias name nickname searchable-phrases] :as user}]]
|
||||
(if-let [match
|
||||
|
@ -272,26 +272,26 @@
|
|||
(match-mention text users mention-key-idx (inc mention-key-idx) []))
|
||||
([text users mention-key-idx next-word-idx words]
|
||||
(when-let [word (re-find word-regex (subs text next-word-idx))]
|
||||
(let [new-words (conj words word)
|
||||
searched-text (let [text (-> new-words
|
||||
string/join
|
||||
string/lower-case
|
||||
string/trim)
|
||||
last-char (dec (count text))]
|
||||
(if (re-matches ending-chars-regex (str (nth text last-char nil)))
|
||||
(subs text 0 last-char)
|
||||
text))
|
||||
suggestions (get-suggestions users searched-text)
|
||||
suggestions-cnt (count suggestions)]
|
||||
(cond (zero? suggestions-cnt)
|
||||
(let [new-words (conj words word)
|
||||
searched-text (let [text (-> new-words
|
||||
string/join
|
||||
string/lower-case
|
||||
string/trim)
|
||||
last-char (dec (count text))]
|
||||
(if (re-matches ending-chars-regex (str (nth text last-char nil)))
|
||||
(subs text 0 last-char)
|
||||
text))
|
||||
user-suggestions (get-user-suggestions users searched-text)
|
||||
user-suggestions-cnt (count user-suggestions)]
|
||||
(cond (zero? user-suggestions-cnt)
|
||||
nil
|
||||
|
||||
(and (= 1 suggestions-cnt)
|
||||
(mentioned? (second (first suggestions))
|
||||
(and (= 1 user-suggestions-cnt)
|
||||
(mentioned? (second (first user-suggestions))
|
||||
(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)
|
||||
text-len (count text)
|
||||
next-word-start (+ next-word-idx word-len)]
|
||||
|
@ -300,33 +300,32 @@
|
|||
next-word-start new-words))))))))
|
||||
|
||||
(defn replace-mentions
|
||||
([text users-fn]
|
||||
([text users]
|
||||
(let [idxs (get-at-signs text)]
|
||||
(replace-mentions text users-fn idxs 0)))
|
||||
([text users-fn idxs diff]
|
||||
(replace-mentions text users idxs 0)))
|
||||
([text users idxs diff]
|
||||
(if (or (string/blank? text)
|
||||
(empty? idxs))
|
||||
text
|
||||
(let [mention-key-idx (- (first idxs) diff)]
|
||||
(if-not mention-key-idx
|
||||
text
|
||||
(let [users (users-fn)]
|
||||
(if-not (seq users)
|
||||
text
|
||||
(let [{:keys [public-key match]}
|
||||
(match-mention text users mention-key-idx)]
|
||||
(if-not match
|
||||
(recur text (fn [] users) (rest idxs) diff)
|
||||
(let [new-text (string/join
|
||||
[(subs text 0 (inc mention-key-idx))
|
||||
public-key
|
||||
(subs text (+ (inc mention-key-idx)
|
||||
(count match)))])]
|
||||
(recur new-text (fn [] users) (rest idxs)
|
||||
(+ diff (- (count text) (count new-text))))))))))))))
|
||||
(if-not (seq users)
|
||||
text
|
||||
(let [{:keys [public-key match]}
|
||||
(match-mention text users mention-key-idx)]
|
||||
(if-not match
|
||||
(recur text users (rest idxs) diff)
|
||||
(let [new-text (string/join
|
||||
[(subs text 0 (inc mention-key-idx))
|
||||
public-key
|
||||
(subs text (+ (inc mention-key-idx)
|
||||
(count match)))])]
|
||||
(recur new-text users (rest idxs)
|
||||
(+ diff (- (count text) (count new-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
|
||||
([text start]
|
||||
|
@ -401,14 +400,14 @@
|
|||
new-idxs)))))))
|
||||
|
||||
(defn check-entry
|
||||
[text {:keys [from checked?] :as entry} users-fn]
|
||||
[text {:keys [from checked?] :as entry} mentionable-users]
|
||||
(if checked?
|
||||
entry
|
||||
(let [{:keys [match]}
|
||||
(match-mention (str text "@") (users-fn) from)]
|
||||
(if match
|
||||
(let [{user-match :match}
|
||||
(match-mention (str text "@") mentionable-users from)]
|
||||
(if user-match
|
||||
{:from from
|
||||
:to (+ from (count match))
|
||||
:to (+ from (count user-match))
|
||||
:checked? true
|
||||
:mention? true}
|
||||
{:from from
|
||||
|
@ -416,12 +415,13 @@
|
|||
:checked? true
|
||||
:mention false}))))
|
||||
|
||||
(defn check-idx-for-mentions [text idxs users-fn]
|
||||
(defn check-idx-for-mentions
|
||||
[text idxs mentionable-users]
|
||||
(let [idxs
|
||||
(reduce
|
||||
(fn [acc {:keys [from] :as entry}]
|
||||
(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
|
||||
(and (>= previous-entry-idx 0)
|
||||
(not (get-in acc [previous-entry-idx :mention?])))
|
||||
|
@ -497,7 +497,7 @@
|
|||
new-at-idxs (check-idx-for-mentions
|
||||
text
|
||||
(:at-idxs state)
|
||||
(fn [] mentionable-users))
|
||||
mentionable-users)
|
||||
calculated-input (calculate-input text new-at-idxs)]
|
||||
(log/debug "[mentions] new-at-idxs" new-at-idxs calculated-input)
|
||||
{:db (-> db
|
||||
|
@ -524,8 +524,8 @@
|
|||
(assoc-in [:chats/input-with-mentions chat-id] [[:text text]]))}
|
||||
(let [new-at-idxs (check-idx-for-mentions
|
||||
text
|
||||
(:at-idxs state)
|
||||
(fn [] mentionable-users))
|
||||
at-idxs
|
||||
mentionable-users)
|
||||
calculated-input (calculate-input text new-at-idxs)
|
||||
addition? (<= start end)
|
||||
end (if addition?
|
||||
|
@ -536,7 +536,7 @@
|
|||
mentions
|
||||
(when (and (not (> at-sign-idx start))
|
||||
(not (> (- end at-sign-idx) 100)))
|
||||
(get-suggestions mentionable-users searched-text))]
|
||||
(get-user-suggestions mentionable-users searched-text))]
|
||||
(log/debug "[mentions] mention check"
|
||||
"addition" addition?
|
||||
"at-sign-idx" at-sign-idx
|
||||
|
|
|
@ -4,19 +4,18 @@
|
|||
[cljs.test :as test :include-macros true]))
|
||||
|
||||
(test/deftest test-replace-mentions
|
||||
(let [users (fn []
|
||||
{"User Number One"
|
||||
{:name "User Number One"
|
||||
:alias "User Number One"
|
||||
:public-key "0xpk1"}
|
||||
"User Number Two"
|
||||
{:name "user2"
|
||||
:alias "User Number Two"
|
||||
:public-key "0xpk2"}
|
||||
"User Number Three"
|
||||
{:name "user3"
|
||||
:alias "User Number Three"
|
||||
:public-key "0xpk3"}})]
|
||||
(let [users {"User Number One"
|
||||
{:name "User Number One"
|
||||
:alias "User Number One"
|
||||
:public-key "0xpk1"}
|
||||
"User Number Two"
|
||||
{:name "user2"
|
||||
:alias "User Number Two"
|
||||
:public-key "0xpk2"}
|
||||
"User Number Three"
|
||||
{:name "user3"
|
||||
:alias "User Number Three"
|
||||
:public-key "0xpk3"}}]
|
||||
(test/testing "empty string"
|
||||
(let [text ""
|
||||
result (mentions/replace-mentions text users)]
|
||||
|
|
|
@ -79,6 +79,80 @@
|
|||
:accessibility-label :send-message-button
|
||||
: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
|
||||
[{:keys [cooldown-enabled? input-with-mentions on-text-change set-active-panel text-input-ref]}]
|
||||
(let [cursor @(re-frame/subscribe [:chat/cursor])
|
||||
|
@ -87,101 +161,29 @@
|
|||
last-text-change (atom nil)]
|
||||
[rn/view {:style (styles/text-input-wrapper)}
|
||||
[rn/text-input
|
||||
{:style (styles/text-input)
|
||||
:ref text-input-ref
|
||||
:maxFontSizeMultiplier 1
|
||||
:accessibility-label :chat-message-input
|
||||
:text-align-vertical :center
|
||||
:multiline true
|
||||
:editable (not cooldown-enabled?)
|
||||
:blur-on-submit false
|
||||
:auto-focus false
|
||||
:on-focus #(set-active-panel nil)
|
||||
:max-length chat.constants/max-text-size
|
||||
:placeholder-text-color (:text-02 @colors/theme)
|
||||
:placeholder (if cooldown-enabled?
|
||||
(i18n/label :cooldown/text-input-disabled)
|
||||
(i18n/label :t/type-a-message))
|
||||
:underlineColorAndroid :transparent
|
||||
:auto-capitalize :sentences
|
||||
:selection
|
||||
;; 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}))
|
||||
|
||||
: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]))))}
|
||||
{:style (styles/text-input)
|
||||
:ref text-input-ref
|
||||
:max-font-size-multiplier 1
|
||||
:accessibility-label :chat-message-input
|
||||
:text-align-vertical :center
|
||||
:multiline true
|
||||
:editable (not cooldown-enabled?)
|
||||
:blur-on-submit false
|
||||
:auto-focus false
|
||||
:on-focus #(set-active-panel nil)
|
||||
:max-length chat.constants/max-text-size
|
||||
:placeholder-text-color (:text-02 @colors/theme)
|
||||
:placeholder (if cooldown-enabled?
|
||||
(i18n/label :cooldown/text-input-disabled)
|
||||
(i18n/label :t/type-a-message))
|
||||
:underline-color-android :transparent
|
||||
:auto-capitalize :sentences
|
||||
:selection (selection cursor)
|
||||
:on-selection-change (partial on-selection-change
|
||||
cursor timeout-id last-text-change mentionable-users)
|
||||
:on-change (partial on-change
|
||||
on-text-change last-text-change timeout-id mentionable-users)
|
||||
:on-text-input (partial on-text-input mentionable-users)}
|
||||
;; NOTE(rasom): reduce was used instead of for here because although
|
||||
;; 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
|
||||
|
|
Loading…
Reference in New Issue