mirror of
https://github.com/status-im/status-react.git
synced 2025-03-01 11:00:51 +00:00
Highlighted mentions in input
This commit is contained in:
parent
44e3076022
commit
0448edb509
@ -30,7 +30,7 @@
|
|||||||
{:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))})
|
{:db (assoc-in db [:chat/inputs current-chat-id :input-text] (text->emoji new-input))})
|
||||||
|
|
||||||
(fx/defn select-mention
|
(fx/defn select-mention
|
||||||
[{:keys [db] :as cofx} {:keys [name] :as user}]
|
[{:keys [db] :as cofx} {:keys [alias name searched-text match] :as user}]
|
||||||
(let [chat-id (:current-chat-id db)
|
(let [chat-id (:current-chat-id db)
|
||||||
new-text (mentions/new-input-text-with-mention cofx user)
|
new-text (mentions/new-input-text-with-mention cofx user)
|
||||||
at-sign-idx (get-in db [:chats chat-id :mentions :at-sign-idx])]
|
at-sign-idx (get-in db [:chats chat-id :mentions :at-sign-idx])]
|
||||||
@ -39,7 +39,17 @@
|
|||||||
{:db (-> db
|
{:db (-> db
|
||||||
(assoc-in [:chats/cursor chat-id] (+ at-sign-idx (count name) 2))
|
(assoc-in [:chats/cursor chat-id] (+ at-sign-idx (count name) 2))
|
||||||
(assoc-in [:chats/mention-suggestions chat-id] nil))}
|
(assoc-in [:chats/mention-suggestions chat-id] nil))}
|
||||||
(set-chat-input-text new-text))))
|
(set-chat-input-text new-text)
|
||||||
|
;; NOTE(roman): on-text-input event is not dispatched when we change input
|
||||||
|
;; programmatically, so we have to call `on-text-input` manually
|
||||||
|
(mentions/on-text-input
|
||||||
|
(let [match-len (count match)
|
||||||
|
searched-text-len (count searched-text)]
|
||||||
|
{:new-text (subs match searched-text-len)
|
||||||
|
:previous-text ""
|
||||||
|
:start (+ at-sign-idx searched-text-len 1)
|
||||||
|
:end (+ at-sign-idx match-len 1)}))
|
||||||
|
(mentions/recheck-at-idxs {alias user}))))
|
||||||
|
|
||||||
(defn- start-cooldown [{:keys [db]} cooldowns]
|
(defn- start-cooldown [{:keys [db]} cooldowns]
|
||||||
{:dispatch-later [{:dispatch [:chat/disable-cooldown]
|
{:dispatch-later [{:dispatch [:chat/disable-cooldown]
|
||||||
|
@ -85,7 +85,9 @@
|
|||||||
(string/lower-case name)
|
(string/lower-case name)
|
||||||
searched-text)
|
searched-text)
|
||||||
name)]
|
name)]
|
||||||
(assoc acc k (assoc user :match match))
|
(assoc acc k (assoc user
|
||||||
|
:match match
|
||||||
|
:searched-text searched-text))
|
||||||
acc))
|
acc))
|
||||||
{}
|
{}
|
||||||
users))
|
users))
|
||||||
@ -169,6 +171,120 @@
|
|||||||
(- (check-for-at-sign new-text)
|
(- (check-for-at-sign new-text)
|
||||||
(check-for-at-sign previous-text))))
|
(check-for-at-sign previous-text))))
|
||||||
|
|
||||||
|
(defn get-at-sign-idxs
|
||||||
|
([text start]
|
||||||
|
(get-at-sign-idxs text start 0 []))
|
||||||
|
([text start from idxs]
|
||||||
|
(if-let [idx (string/index-of text at-sign from)]
|
||||||
|
(recur text start (inc idx) (conj idxs (+ start idx)))
|
||||||
|
idxs)))
|
||||||
|
|
||||||
|
(defn calc-at-idxs
|
||||||
|
[{:keys [at-idxs new-text previous-text start]}]
|
||||||
|
(let [new-idxs (get-at-sign-idxs new-text start)
|
||||||
|
new-idx-cnt (count new-idxs)
|
||||||
|
last-new-idx (when (pos? new-idx-cnt)
|
||||||
|
(nth new-idxs (dec new-idx-cnt)))
|
||||||
|
new-text-len (count new-text)
|
||||||
|
old-text-len (count previous-text)
|
||||||
|
old-end (+ start old-text-len)]
|
||||||
|
(if-not (seq at-idxs)
|
||||||
|
(map (fn [idx]
|
||||||
|
{:from idx
|
||||||
|
:checked? false})
|
||||||
|
new-idxs)
|
||||||
|
(let [diff (- new-text-len old-text-len)
|
||||||
|
{:keys [state added?]}
|
||||||
|
(->> at-idxs
|
||||||
|
(keep (fn [{:keys [from to] :as entry}]
|
||||||
|
(let [to+1 (inc to)]
|
||||||
|
(cond
|
||||||
|
;; starts after change
|
||||||
|
(>= from old-end)
|
||||||
|
(assoc entry
|
||||||
|
:from (+ from diff)
|
||||||
|
:to (+ to diff))
|
||||||
|
|
||||||
|
;; starts and end before change
|
||||||
|
(and
|
||||||
|
(< from start)
|
||||||
|
(or
|
||||||
|
;; is not checked yet
|
||||||
|
(not to+1)
|
||||||
|
(< to+1 start)))
|
||||||
|
entry
|
||||||
|
|
||||||
|
;; starts before change intersects with it
|
||||||
|
(and (< from start)
|
||||||
|
(>= to+1 start))
|
||||||
|
{:from from
|
||||||
|
:checked? false}
|
||||||
|
|
||||||
|
;; starts in changed part of text
|
||||||
|
:else nil))))
|
||||||
|
(reduce
|
||||||
|
(fn [{:keys [state added?] :as acc} {:keys [from] :as entry}]
|
||||||
|
(if (and last-new-idx
|
||||||
|
(> from last-new-idx)
|
||||||
|
(not added?))
|
||||||
|
{:state (conj
|
||||||
|
(into state (map (fn [idx]
|
||||||
|
{:from idx
|
||||||
|
:checked? false})
|
||||||
|
new-idxs))
|
||||||
|
entry)
|
||||||
|
:added? true}
|
||||||
|
(update acc :state conj entry)))
|
||||||
|
{:state []}))]
|
||||||
|
(if added?
|
||||||
|
state
|
||||||
|
(into state (map (fn [idx]
|
||||||
|
{:from idx
|
||||||
|
:checked? false})
|
||||||
|
new-idxs)))))))
|
||||||
|
|
||||||
|
(defn check-entry
|
||||||
|
[text {:keys [from checked?] :as entry} users-fn]
|
||||||
|
(if checked?
|
||||||
|
entry
|
||||||
|
(let [{:keys [match]}
|
||||||
|
(match-mention (str text "@") (users-fn) from)]
|
||||||
|
(if match
|
||||||
|
{:from from
|
||||||
|
:to (+ from (count match))
|
||||||
|
:checked? true
|
||||||
|
:mention? true}
|
||||||
|
{:from from
|
||||||
|
:to (count text)
|
||||||
|
:checked? true
|
||||||
|
:mention false}))))
|
||||||
|
|
||||||
|
(defn check-idx-for-mentions [text idxs users-fn]
|
||||||
|
(let [idxs
|
||||||
|
(reduce
|
||||||
|
(fn [acc {:keys [from] :as entry}]
|
||||||
|
(let [previous-entry-idx (dec (count acc))
|
||||||
|
new-entry (check-entry text entry users-fn)]
|
||||||
|
(cond-> acc
|
||||||
|
(and (>= previous-entry-idx 0)
|
||||||
|
(not (get-in acc [previous-entry-idx :mention?])))
|
||||||
|
(assoc-in [previous-entry-idx :to] (dec from))
|
||||||
|
|
||||||
|
(>= previous-entry-idx 0)
|
||||||
|
(assoc-in [previous-entry-idx :next-at-idx] from)
|
||||||
|
|
||||||
|
:always
|
||||||
|
(conj (dissoc new-entry :next-at-idx)))))
|
||||||
|
[]
|
||||||
|
idxs)]
|
||||||
|
(when (seq idxs)
|
||||||
|
(let [last-idx (dec (count idxs))]
|
||||||
|
(if (get-in idxs [last-idx :mention?])
|
||||||
|
idxs
|
||||||
|
(-> idxs
|
||||||
|
(assoc-in [last-idx :to] (dec (count text)))
|
||||||
|
(assoc-in [last-idx :checked?] false)))))))
|
||||||
|
|
||||||
(fx/defn on-text-input
|
(fx/defn on-text-input
|
||||||
{:events [::on-text-input]}
|
{:events [::on-text-input]}
|
||||||
[{:keys [db] :as cofx} {:keys [new-text previous-text start end] :as args}]
|
[{:keys [db] :as cofx} {:keys [new-text previous-text start end] :as args}]
|
||||||
@ -182,13 +298,60 @@
|
|||||||
chat-id (:current-chat-id db)
|
chat-id (:current-chat-id db)
|
||||||
change (at-sign-change normalized-previous-text new-text)
|
change (at-sign-change normalized-previous-text new-text)
|
||||||
previous-state (get-in db [:chats chat-id :mentions])
|
previous-state (get-in db [:chats chat-id :mentions])
|
||||||
|
text (get-in db [:chat/inputs chat-id :input-text])
|
||||||
new-state (-> previous-state
|
new-state (-> previous-state
|
||||||
(update :at-sign-counter + change)
|
(update :at-sign-counter + change)
|
||||||
(merge args)
|
(merge args)
|
||||||
(assoc :previous-text normalized-previous-text))]
|
(assoc :previous-text normalized-previous-text))
|
||||||
|
old-at-idxs (:at-idxs new-state)
|
||||||
|
new-at-idxs (calc-at-idxs new-state)
|
||||||
|
new-state (assoc new-state :at-idxs new-at-idxs)]
|
||||||
(log/debug "[mentions] on-text-input state" new-state)
|
(log/debug "[mentions] on-text-input state" new-state)
|
||||||
{:db (assoc-in db [:chats chat-id :mentions] new-state)}))
|
{:db (assoc-in db [:chats chat-id :mentions] new-state)}))
|
||||||
|
|
||||||
|
(defn calculate-input [text [first-idx :as idxs]]
|
||||||
|
(if-not first-idx
|
||||||
|
[[:text text]]
|
||||||
|
(let [idx-cnt (count idxs)
|
||||||
|
last-from (get-in idxs [(dec idx-cnt) :from])]
|
||||||
|
(reduce
|
||||||
|
(fn [acc {:keys [from to next-at-idx mention?]}]
|
||||||
|
(cond
|
||||||
|
(and mention? next-at-idx)
|
||||||
|
(into acc [[:mention (subs text from (inc to))]
|
||||||
|
[:text (subs text (inc to) next-at-idx)]])
|
||||||
|
|
||||||
|
(and mention? (= last-from from))
|
||||||
|
(into acc [[:mention (subs text from (inc to))]
|
||||||
|
[:text (subs text (inc to))]])
|
||||||
|
|
||||||
|
:else
|
||||||
|
(conj acc [:text (subs text from (inc to))])))
|
||||||
|
(let [first-from (:from first-idx)]
|
||||||
|
(if (zero? first-from)
|
||||||
|
[]
|
||||||
|
[[:text (subs text 0 first-from)]]))
|
||||||
|
idxs))))
|
||||||
|
|
||||||
|
(fx/defn recheck-at-idxs
|
||||||
|
[{:keys [db]} mentionable-users]
|
||||||
|
(let [chat-id (:current-chat-id db)
|
||||||
|
text (get-in db [:chat/inputs chat-id :input-text])
|
||||||
|
{:keys [new-text at-sign-counter start end] :as state}
|
||||||
|
(get-in db [:chats chat-id :mentions])
|
||||||
|
new-at-idxs (check-idx-for-mentions
|
||||||
|
text
|
||||||
|
(:at-idxs state)
|
||||||
|
(fn [] mentionable-users))
|
||||||
|
calculated-input (calculate-input text new-at-idxs)]
|
||||||
|
(log/debug "[mentions] new-at-idxs" new-at-idxs calculated-input)
|
||||||
|
{:db (-> db
|
||||||
|
(update-in
|
||||||
|
[:chats chat-id :mentions]
|
||||||
|
assoc
|
||||||
|
:at-idxs new-at-idxs)
|
||||||
|
(assoc-in [:chats/input-with-mentions chat-id] calculated-input))}))
|
||||||
|
|
||||||
(fx/defn calculate-suggestion
|
(fx/defn calculate-suggestion
|
||||||
{:events [::calculate-suggestions]}
|
{:events [::calculate-suggestions]}
|
||||||
[{:keys [db] :as cofx} mentionable-users]
|
[{:keys [db] :as cofx} mentionable-users]
|
||||||
@ -200,8 +363,16 @@
|
|||||||
(log/debug "[mentions] calculate suggestions"
|
(log/debug "[mentions] calculate suggestions"
|
||||||
"state" state)
|
"state" state)
|
||||||
(if-not (pos? at-sign-counter)
|
(if-not (pos? at-sign-counter)
|
||||||
{:db (assoc-in db [:chats/mention-suggestions chat-id] nil)}
|
{:db (-> db
|
||||||
(let [addition? (<= start end)
|
(assoc-in [:chats/mention-suggestions chat-id] nil)
|
||||||
|
(assoc-in [:chats chat-id :mentions :at-idxs] nil)
|
||||||
|
(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))
|
||||||
|
calculated-input (calculate-input text new-at-idxs)
|
||||||
|
addition? (<= start end)
|
||||||
end (if addition?
|
end (if addition?
|
||||||
(+ start (count new-text))
|
(+ start (count new-text))
|
||||||
start)
|
start)
|
||||||
@ -216,21 +387,24 @@
|
|||||||
"end" end
|
"end" end
|
||||||
"searched-text" (pr-str searched-text)
|
"searched-text" (pr-str searched-text)
|
||||||
"mentions" (count mentions))
|
"mentions" (count mentions))
|
||||||
|
(log/debug "[mentions] new-at-idxs" new-at-idxs calculated-input)
|
||||||
{:db (-> db
|
{:db (-> db
|
||||||
(update-in [:chats chat-id :mentions]
|
(update-in [:chats chat-id :mentions]
|
||||||
assoc
|
assoc
|
||||||
:at-sign-idx at-sign-idx
|
:at-sign-idx at-sign-idx
|
||||||
|
:at-idxs new-at-idxs
|
||||||
:mention-end end)
|
:mention-end end)
|
||||||
|
(assoc-in [:chats/input-with-mentions chat-id] calculated-input)
|
||||||
(assoc-in [:chats/mention-suggestions chat-id] mentions))}))))
|
(assoc-in [:chats/mention-suggestions chat-id] mentions))}))))
|
||||||
|
|
||||||
(defn new-input-text-with-mention
|
(defn new-input-text-with-mention
|
||||||
[{:keys [db]} {:keys [name]}]
|
[{:keys [db]} {:keys [name]}]
|
||||||
(let [chat-id (:current-chat-id db)
|
(let [chat-id (:current-chat-id db)
|
||||||
text (get-in db [:chat/inputs chat-id :input-text])
|
text (get-in db [:chat/inputs chat-id :input-text])
|
||||||
{:keys [mention-end at-sign-idx] :as state}
|
{:keys [mention-end at-sign-idx]}
|
||||||
(get-in db [:chats chat-id :mentions])]
|
(get-in db [:chats chat-id :mentions])]
|
||||||
(log/debug "[mentions] clear suggestions"
|
(log/debug "[mentions] clear suggestions"
|
||||||
"state" state)
|
"state" new-input-text-with-mention)
|
||||||
(string/join
|
(string/join
|
||||||
[(subs text 0 (inc at-sign-idx))
|
[(subs text 0 (inc at-sign-idx))
|
||||||
name
|
name
|
||||||
@ -247,6 +421,7 @@
|
|||||||
(let [chat-id (:current-chat-id db)]
|
(let [chat-id (:current-chat-id db)]
|
||||||
{:db (-> db
|
{:db (-> db
|
||||||
(update-in [:chats chat-id] dissoc :mentions)
|
(update-in [:chats chat-id] dissoc :mentions)
|
||||||
|
(update :chats/input-with-mentions dissoc chat-id)
|
||||||
(update :chats/mention-suggestions dissoc chat-id))}))
|
(update :chats/mention-suggestions dissoc chat-id))}))
|
||||||
|
|
||||||
(fx/defn clear-cursor
|
(fx/defn clear-cursor
|
||||||
|
@ -121,7 +121,7 @@
|
|||||||
(reg-root-key-sub :group-chat/invitations :group-chat/invitations)
|
(reg-root-key-sub :group-chat/invitations :group-chat/invitations)
|
||||||
(reg-root-key-sub :chats/mention-suggestions :chats/mention-suggestions)
|
(reg-root-key-sub :chats/mention-suggestions :chats/mention-suggestions)
|
||||||
(reg-root-key-sub :chats/cursor :chats/cursor)
|
(reg-root-key-sub :chats/cursor :chats/cursor)
|
||||||
|
(reg-root-key-sub :chats/input-with-mentions :chats/input-with-mentions)
|
||||||
;;browser
|
;;browser
|
||||||
(reg-root-key-sub :browsers :browser/browsers)
|
(reg-root-key-sub :browsers :browser/browsers)
|
||||||
(reg-root-key-sub :browser/options :browser/options)
|
(reg-root-key-sub :browser/options :browser/options)
|
||||||
@ -904,6 +904,13 @@
|
|||||||
(fn [[chat-id cursor]]
|
(fn [[chat-id cursor]]
|
||||||
(get cursor chat-id)))
|
(get cursor chat-id)))
|
||||||
|
|
||||||
|
(re-frame/reg-sub
|
||||||
|
:chat/input-with-mentions
|
||||||
|
:<- [:chats/current-chat-id]
|
||||||
|
:<- [:chats/input-with-mentions]
|
||||||
|
(fn [[chat-id cursor]]
|
||||||
|
(get cursor chat-id)))
|
||||||
|
|
||||||
;;BOOTNODES ============================================================================================================
|
;;BOOTNODES ============================================================================================================
|
||||||
|
|
||||||
(re-frame/reg-sub
|
(re-frame/reg-sub
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
:color (styles/send-icon-color)}]]])
|
:color (styles/send-icon-color)}]]])
|
||||||
|
|
||||||
(defn text-input
|
(defn text-input
|
||||||
[{:keys [cooldown-enabled? text-value 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])
|
||||||
mentionable-users @(re-frame/subscribe [:chats/mentionable-users])]
|
mentionable-users @(re-frame/subscribe [:chats/mentionable-users])]
|
||||||
[rn/view {:style (styles/text-input-wrapper)}
|
[rn/view {:style (styles/text-input-wrapper)}
|
||||||
@ -90,7 +90,6 @@
|
|||||||
:accessibility-label :chat-message-input
|
:accessibility-label :chat-message-input
|
||||||
:text-align-vertical :center
|
:text-align-vertical :center
|
||||||
:multiline true
|
:multiline true
|
||||||
:default-value text-value
|
|
||||||
:editable (not cooldown-enabled?)
|
:editable (not cooldown-enabled?)
|
||||||
:blur-on-submit false
|
:blur-on-submit false
|
||||||
:auto-focus false
|
:auto-focus false
|
||||||
@ -146,7 +145,20 @@
|
|||||||
;; `on-change`, that's why mention suggestions are calculated
|
;; `on-change`, that's why mention suggestions are calculated
|
||||||
;; on `on-change`
|
;; on `on-change`
|
||||||
(when platform/android?
|
(when platform/android?
|
||||||
(re-frame/dispatch [::mentions/calculate-suggestions mentionable-users]))))}]]))
|
(re-frame/dispatch [::mentions/calculate-suggestions 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
|
||||||
|
;; inside it works just fine on both platforms.
|
||||||
|
(reduce
|
||||||
|
(fn [acc [type text]]
|
||||||
|
(conj
|
||||||
|
acc
|
||||||
|
[rn/text (when (= type :mention)
|
||||||
|
{:style {:color "#0DA4C9"}})
|
||||||
|
text]))
|
||||||
|
[:<>]
|
||||||
|
input-with-mentions)]]))
|
||||||
|
|
||||||
(defn mention-item
|
(defn mention-item
|
||||||
[[_ {:keys [identicon alias name] :as user}]]
|
[[_ {:keys [identicon alias name] :as user}]]
|
||||||
@ -248,7 +260,10 @@
|
|||||||
(let [disconnected? @(re-frame/subscribe [:disconnected?])
|
(let [disconnected? @(re-frame/subscribe [:disconnected?])
|
||||||
{:keys [processing]} @(re-frame/subscribe [:multiaccounts/login])
|
{:keys [processing]} @(re-frame/subscribe [:multiaccounts/login])
|
||||||
mainnet? @(re-frame/subscribe [:mainnet?])
|
mainnet? @(re-frame/subscribe [:mainnet?])
|
||||||
input-text @(re-frame/subscribe [:chats/current-chat-input-text])
|
input-text
|
||||||
|
@(re-frame/subscribe [:chats/current-chat-input-text])
|
||||||
|
input-with-mentions
|
||||||
|
@(re-frame/subscribe [:chat/input-with-mentions])
|
||||||
cooldown-enabled? @(re-frame/subscribe [:chats/cooldown-enabled?])
|
cooldown-enabled? @(re-frame/subscribe [:chats/cooldown-enabled?])
|
||||||
one-to-one-chat? @(re-frame/subscribe [:current-chat/one-to-one-chat?])
|
one-to-one-chat? @(re-frame/subscribe [:current-chat/one-to-one-chat?])
|
||||||
{:keys [public?
|
{:keys [public?
|
||||||
@ -295,6 +310,7 @@
|
|||||||
:on-send-press #(do (re-frame/dispatch [:chat.ui/send-current-message])
|
:on-send-press #(do (re-frame/dispatch [:chat.ui/send-current-message])
|
||||||
(clear-input))
|
(clear-input))
|
||||||
:text-value input-text
|
:text-value input-text
|
||||||
|
:input-with-mentions input-with-mentions
|
||||||
:on-text-change on-text-change
|
:on-text-change on-text-change
|
||||||
:cooldown-enabled? cooldown-enabled?
|
:cooldown-enabled? cooldown-enabled?
|
||||||
:show-send show-send
|
:show-send show-send
|
||||||
|
Loading…
x
Reference in New Issue
Block a user