diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index ce8fde3b53..053b85300b 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -41,36 +41,19 @@ (rf/defn select-mention {:events [:chat.ui/select-mention]} - [{:keys [db] :as cofx} text-input-ref {:keys [primary-name searched-text match] :as user}] - (let [chat-id (:current-chat-id db) - new-text (mentions/new-input-text-with-mention cofx user) - at-sign-idx (get-in db [:chats/mentions chat-id :mentions :at-sign-idx]) - cursor (+ at-sign-idx (count primary-name) 2)] - (rf/merge - cofx - {:db (-> db - (assoc-in [:chats/cursor chat-id] cursor) - (assoc-in [:chats/mention-suggestions chat-id] nil)) - :set-text-input-value [chat-id new-text text-input-ref]} - (set-chat-input-text new-text chat-id) - ;; NOTE(rasom): Some keyboards do not react on selection property passed to - ;; text input (specifically Samsung keyboard with predictive text set on). - ;; In this case, if the user continues typing after the programmatic change, - ;; the new text is added to the last known cursor position before - ;; programmatic change. By calling `reset-text-input-cursor` we force the - ;; keyboard's cursor position to be changed before the next input. - (mentions/reset-text-input-cursor text-input-ref cursor) - ;; 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) - start (inc at-sign-idx) - end (+ start match-len)] - {:new-text match - :previous-text searched-text - :start start - :end end})) - (mentions/recheck-at-idxs {primary-name user})))) + [{:keys [db] :as cofx} text-input-ref {:keys [primary-name searched-text match public-key] :as user}] + (let [chat-id (:current-chat-id db) + text (get-in db [:chat/inputs chat-id :input-text]) + method "wakuext_chatMentionNewInputTextWithMention" + params [chat-id text primary-name]] + {:json-rpc/call [{:method method + :params params + :on-success #(rf/dispatch [:mention/on-new-input-text-with-mentions-success % + primary-name text-input-ref match searched-text + public-key]) + :on-error #(rf/dispatch [:mention/on-error + {:method method + :params params} %])}]})) (rf/defn disable-chat-cooldown "Turns off chat cooldown (protection against message spamming)" @@ -182,8 +165,7 @@ (rf/merge cofx {:set-text-input-value [current-chat-id ""]} (clean-input current-chat-id) - (mentions/clear-mentions) - (mentions/clear-cursor)))) + (mentions/clear-mentions)))) (rf/defn send-messages [{:keys [db] :as cofx} input-text current-chat-id] @@ -251,13 +233,29 @@ [{{:keys [current-chat-id] :as db} :db :as cofx}] (let [{:keys [input-text metadata]} (get-in db [:chat/inputs current-chat-id]) editing-message (:editing-message metadata) - input-text-with-mentions (mentions/check-mentions cofx input-text)] - (rf/merge cofx - (if editing-message - (send-edited-message input-text-with-mentions editing-message) - (send-messages input-text-with-mentions current-chat-id)) - (mentions/clear-mentions) - (mentions/clear-cursor)))) + method "wakuext_chatMentionCheckMentions" + params [current-chat-id input-text]] + {:json-rpc/call [{:method method + :params params + :on-error #(rf/dispatch [:mention/on-error {:method method :params params} %]) + :on-success #(rf/dispatch [:mention/on-check-mentions-success + current-chat-id + editing-message + input-text + %])}]})) + +(rf/defn on-check-mentions-success + {:events [:mention/on-check-mentions-success]} + [{:keys [db] :as cofx} current-chat-id editing-message input-text new-text] + (log/debug "[mentions] on-check-mentions-success" + {:chat-id current-chat-id + :editing-message editing-message + :input-text input-text + :new-text new-text}) + (rf/merge cofx + (if editing-message + (send-edited-message new-text editing-message) + (send-messages new-text current-chat-id)))) (rf/defn send-contact-request {:events [:contacts/send-contact-request]} @@ -271,7 +269,6 @@ :on-error #(log/warn "failed to send a contact request" %) :on-success #(re-frame/dispatch [:transport/message-sent %])}]} (mentions/clear-mentions) - (mentions/clear-cursor) (clean-input (:current-chat-id db)))) (rf/defn cancel-contact-request @@ -282,7 +279,6 @@ (rf/merge cofx {:db (assoc-in db [:chat/inputs current-chat-id :metadata :sending-contact-request] nil)} (mentions/clear-mentions) - (mentions/clear-cursor) (clean-input (:current-chat-id db))))) (rf/defn chat-send-sticker diff --git a/src/status_im/chat/models/mentions.cljs b/src/status_im/chat/models/mentions.cljs index ae2ed9f077..8a44dcf696 100644 --- a/src/status_im/chat/models/mentions.cljs +++ b/src/status_im/chat/models/mentions.cljs @@ -1,631 +1,191 @@ (ns status-im.chat.models.mentions - (:require [clojure.string :as string] + (:require [clojure.set :as set] [quo.react :as react] [quo.react-native :as rn] [re-frame.core :as re-frame] - [status-im2.constants :as constants] - [status-im.contact.db :as contact.db] - [status-im.multiaccounts.core :as multiaccounts] - [status-im.native-module.core :as status] [utils.re-frame :as rf] [status-im.utils.platform :as platform] - [taoensso.timbre :as log])) + [taoensso.timbre :as log] + [status-im.native-module.core :as status])) -(def at-sign "@") +(defn- transfer-input-segments + [segments] + (map (fn [segment] + (let [{:keys [type value]} segment + type (case type + 0 :text + 1 :mention + (log/warn "unknown segment type" {:type type}))] + [type value])) + segments)) -(defn re-pos - [re s] - (loop [res [] - s s - last-idx 0] - (if-let [m (.exec re s)] - (let [new-idx (.-index m) - idx (+ last-idx new-idx) - c (get m 0)] - (recur (conj res [idx c]) (subs s (inc new-idx)) (inc idx))) - res))) +(defn- rename-at-idxs + [at-idxs] + (map #(set/rename-keys % + {:From :from + :To :to + :Checked :checked? + :Mentioned :mention? + :Mention :mention + :NextAtIdx :next-at-idx}) + at-idxs)) -(defn check-style-tag - [text idxs idx] - (let [[pos c] (get idxs idx) - [pos2 c2] (get idxs (inc idx)) - [pos3 c3] (get idxs (+ 2 idx)) - prev-c (get text (dec pos)) - len (cond - (and (= c c2 c3) - (= pos (dec pos2) (- pos3 2))) - 3 - (and (= c c2) - (= pos (dec pos2))) - 2 - :else 1) - next-idx (inc (first (get idxs (+ idx (dec len))))) - next-c (get text next-idx) - can-be-end? (if (= 1 len) - (and prev-c - (not (string/blank? prev-c)) - (or (nil? next-c) - (string/blank? next-c))) - (and prev-c - (not (string/blank? prev-c)))) - can-be-start? (and next-c - (not (string/blank? next-c)))] - [len can-be-start? can-be-end?])) +(defn- rename-state + [state] + (-> state + (set/rename-keys {:AtSignIdx :at-sign-idx + :AtIdxs :at-idxs + :MentionEnd :mention-end + :PreviousText :previous-text + :NewText :new-text + :Start :start + :End :end}) + (update :at-idxs rename-at-idxs))) -(defn clear-pending-at-signs - [data from] - (let [{:keys [pending]} (get data at-sign) - new-idxs (filter (partial > from) pending)] - (-> data - (update at-sign dissoc :pending) - (update-in [at-sign :checked] - (fn [checked] - ;; NOTE(rasom): there might be stack overflow without doall - (doall - ((fnil concat []) checked new-idxs))))))) - -(defn apply-style-tag - [data idx pos c len start? end?] - (let [was-started? (get data c) - tripple-tilde? (and (= "~" c) (= 3 len))] - (cond - (and was-started? end?) - (let [old-len (:len was-started?) - tag-len (cond - (and tripple-tilde? - (= 3 old-len)) - 2 - - (>= old-len len) - len - - :else - old-len) - old-idx (:idx was-started?)] - {:data (-> data - (dissoc c) - (clear-pending-at-signs old-idx)) - :next-idx (+ idx tag-len)}) - - start? - {:data (-> data - (assoc c - {:len len - :idx pos}) - (clear-pending-at-signs pos)) - :next-idx (+ idx len)} - - :else - {:data data - :next-idx (+ idx len)}))) - -(defn code-tag-len - [idxs idx] - (let [[pos c] (get idxs idx) - [pos2 c2] (get idxs (inc idx)) - [pos3 c3] (get idxs (+ 2 idx))] - (cond - (and (= c c2 c3) - (= pos (dec pos2) (- pos3 2))) - 3 - (and (= c c2) - (= pos (dec pos2))) - 2 - :else 1))) - -(defn get-at-signs - ([text] - (let [idxs (re-pos #"[@~\\*_\n>`]{1}" text)] - (loop [data nil - idx 0] - (let [quote-started? (get data ">") - [pos c] (get idxs idx) - styling-tag? (get #{"*" "_" "~"} c) - code-tag? (= "`" c) - quote? (= ">" c) - at-sign? (= at-sign c) - newline? (= "\n" c)] - (if (nil? c) - (let [{:keys [checked pending]} (get data at-sign)] - (concat checked pending)) - (cond - newline? - (let [prev-newline (first (get data :newline))] - (recur - (cond-> (update data :newline (fnil conj '()) pos) - (and quote-started? - prev-newline - (string/blank? (subs text prev-newline (dec pos)))) - (dissoc ">")) - (inc idx))) - - quote-started? - (recur data (inc idx)) - - quote? - (let [prev-newlines (take 2 (get data :newline))] - (if (or (zero? pos) - (and (= 1 (count prev-newlines)) - (string/blank? (subs text 0 (dec pos)))) - (and (= 2 (count prev-newlines)) - (string/blank? (subs text (first prev-newlines) (dec pos))))) - (recur (-> data - (dissoc :newline "*" "_" "~" "`") - (assoc ">" {:idx pos})) - (inc idx)) - (recur data (inc idx)))) - - at-sign? - (recur (update-in data [at-sign :pending] (fnil conj []) pos) - (inc idx)) - - code-tag? - (let [len (code-tag-len idxs idx) - {:keys [data next-idx]} - (apply-style-tag data idx pos c len true true)] - (recur data next-idx)) - - styling-tag? - (let [[len can-be-start? can-be-end?] - (check-style-tag text idxs idx) - {:keys [data next-idx]} - (apply-style-tag data idx pos c len can-be-start? can-be-end?)] - (recur data next-idx)) - - :else (recur data (inc idx))))))))) - -(defn add-searchable-phrases-to-contact - [{:keys [primary-name secondary-name blocked?] :as contact}] - (when (not blocked?) - (reduce - (fn [user s] - (if (nil? s) - user - (let [new-words (concat - [s] - (rest (string/split s " ")))] - (update user :searchable-phrases (fnil concat []) new-words)))) - contact - [primary-name secondary-name]))) - -(defn mentionable-contacts - [contacts] - (reduce - (fn [acc [key contact]] - (let [mentionable-contact (add-searchable-phrases-to-contact contact)] - (if (nil? mentionable-contact) - acc - (assoc acc key mentionable-contact)))) - {} - contacts)) - -(defn mentionable-contacts-from-identites - [contacts my-public-key identities] - (reduce (fn [acc identity] - (let [contact (multiaccounts/contact-by-identity contacts identity) - mentionable-contact (add-searchable-phrases-to-contact contact)] - (if (nil? mentionable-contact) - acc - (assoc acc identity mentionable-contact)))) +; referenced function: contact-list-item +(defn- rename-mentionable-users + [mentionable-users] + (reduce (fn [acc [id val]] + (assoc acc + id + (set/rename-keys val + {:id :public-key + :primaryName :primary-name + :secondaryName :secondary-name + :compressedKey :compressed-key + :ensVerified :ens-verified + :added :added? + :displayName :display-name + :searchedText :searched-text + }))) {} - (remove #(= my-public-key %) identities))) + mentionable-users)) +(defn- transfer-mention-result + [result] + (let [{:keys [input-segments mentionable-users state chat-id new-text]} + (set/rename-keys result + {:InputSegments :input-segments + :MentionSuggestions :mentionable-users + :MentionState :state + :ChatID :chat-id + :NewText :new-text})] + {:chat-id chat-id + :input-segments (transfer-input-segments input-segments) + :mentionable-users (rename-mentionable-users mentionable-users) + :state (rename-state state) + :new-text new-text})) -(defn get-mentionable-users - [chat all-contacts current-multiaccount community-members] - (let [{:keys [name preferred-name public-key]} current-multiaccount - {:keys [chat-id users contacts chat-type]} chat - mentionable-contacts (mentionable-contacts all-contacts) - mentionable-users (assoc users - public-key - {:secondary-name name - :primary-name (or preferred-name name) - :public-key public-key})] - (cond - (= chat-type constants/private-group-chat-type) - (merge mentionable-users - (mentionable-contacts-from-identites all-contacts public-key contacts)) - - (= chat-type constants/one-to-one-chat-type) - (assoc mentionable-users - chat-id - (get mentionable-contacts - chat-id - (-> chat-id - contact.db/public-key->new-contact - contact.db/enrich-contact))) - - (= chat-type constants/community-chat-type) - (mentionable-contacts-from-identites - all-contacts - public-key - (distinct (concat (keys community-members) (keys mentionable-users)))) - - (= chat-type constants/public-chat-type) - (merge mentionable-users (select-keys mentionable-contacts (keys mentionable-users))) - - :else - mentionable-users))) - -(def ending-chars "[\\s\\.,;:]") -(def ending-chars-regex (re-pattern ending-chars)) -(def word-regex (re-pattern (str "^[\\w\\d]*" ending-chars "|^[\\S]*$"))) - -(defn mentioned? - [{:keys [primary-name secondary-name]} text] - (let [lcase-fname (string/lower-case primary-name) - lcase-sname (when secondary-name (string/lower-case secondary-name)) - regex (re-pattern - (string/join - "|" - [(str "^" lcase-fname ending-chars) - (str "^" lcase-fname "$") - (str "^" lcase-sname ending-chars) - (str "^" lcase-sname "$")])) - lcase-text (string/lower-case text)] - (re-find regex lcase-text))) - -(defn get-user-suggestions - [users searched-text] - (reduce - (fn [acc [k {:keys [primary-name secondary-name searchable-phrases] :as user}]] - (if-let [match - (if (seq searchable-phrases) - (when (some - (fn [s] - (string/starts-with? - (string/lower-case s) - searched-text)) - searchable-phrases) - (or primary-name secondary-name)) - (cond - (and primary-name - (string/starts-with? - (string/lower-case primary-name) - searched-text)) - (or primary-name secondary-name) - - (and secondary-name - (string/starts-with? - (string/lower-case secondary-name) - searched-text)) - secondary-name))] - (assoc acc - k - (assoc user - :key k - :match match - :searched-text searched-text)) - acc)) - {} - users)) - -(defn match-mention - ([text users mention-key-idx] - (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)) - user-suggestions (get-user-suggestions users searched-text) - user-suggestions-cnt (count user-suggestions)] - (cond - (zero? user-suggestions-cnt) - nil - - (and (= 1 user-suggestions-cnt) - (mentioned? (second (first user-suggestions)) - (subs text (inc mention-key-idx)))) - (second (first user-suggestions)) - - (> user-suggestions-cnt 1) - (let [word-len (count word) - text-len (count text) - next-word-start (+ next-word-idx word-len)] - (when (> text-len next-word-start) - (match-mention text - users - mention-key-idx - next-word-start - new-words)))))))) - -(defn replace-mentions - ([text users] - (let [idxs (get-at-signs text)] - (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 - (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 - [{:keys [db]} text] - (let [current-chat-id (:current-chat-id db) - chat (get-in db [:chats current-chat-id]) - all-contacts (:contacts/contacts db) - current-multiaccount (:multiaccount db) - community-members (get-in db [:communities (:community-id chat) :members]) - mentionable-users (get-mentionable-users chat - all-contacts - current-multiaccount - community-members)] - (replace-mentions text mentionable-users))) - -(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} mentionable-users] - (if checked? - entry - (let [{user-match :match} - (match-mention (str text "@") mentionable-users from)] - (if user-match - {:from from - :to (+ from (count user-match)) - :checked? true - :mention? true} - {:from from - :to (count text) - :checked? true - :mention false})))) - -(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 mentionable-users)] - (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))))))) +(rf/defn on-error + {:events [:mention/on-error]} + [_ context error] + (log/error "[mentions] on-error" + {:context context + :error error})) +(rf/defn on-to-input-field-success + {:events [:mention/on-to-input-field-success]} + [{:keys [db]} result] + (log/debug "[mentions] on-to-input-field-success" {:result result}) + (let [{:keys [input-segments state chat-id new-text]} (transfer-mention-result result)] + {:set-text-input-value [chat-id new-text] + :db (-> db + (assoc-in [:chats/mentions chat-id :mentions] state) + (assoc-in [:chat/inputs-with-mentions chat-id] input-segments))})) (rf/defn on-text-input - {:events [::on-text-input]} + {:events [:mention/on-text-input]} [{:keys [db]} {:keys [previous-text start end] :as args}] - (log/debug "[mentions] on-text-input args" args) - (let [normalized-previous-text + (let [previous-text ;; NOTE(rasom): on iOS `previous-text` contains entire input's text. To ;; get only removed part of text we have cut it. (if platform/android? previous-text (subs previous-text start end)) - chat-id (:current-chat-id db) - previous-state (get-in db [:chats/mentions chat-id :mentions]) - new-state (-> previous-state - (merge args) - (assoc :previous-text normalized-previous-text)) - 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) - {:db (assoc-in db [:chats/mentions chat-id :mentions] new-state)})) + chat-id (:current-chat-id db) + state (merge args {:previous-text previous-text}) + state (set/rename-keys state + {:previous-text :PreviousText + :new-text :NewText + :start :Start + :end :End}) + params [chat-id state] + method "wakuext_chatMentionOnTextInput"] + (log/debug "[mentions] on-text-input" {:params params}) + {:json-rpc/call [{:method method + :params [chat-id state] + :on-success #(rf/dispatch [:mention/on-text-input-success %]) + :on-error #(rf/dispatch [:mention/on-error + {:method method + :params params} %])}]})) -(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)))) +(rf/defn on-text-input-success + {:events [:mention/on-text-input-success]} + [{:keys [db]} result] + (log/debug "[mentions] on-text-input-success" {:result result}) + (let [{:keys [state chat-id]} (transfer-mention-result result)] + {:db (assoc-in db [:chats/mentions chat-id :mentions] state)})) (rf/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]) - state (get-in db [:chats/mentions chat-id :mentions]) - new-at-idxs (check-idx-for-mentions - text - (:at-idxs state) - mentionable-users) - calculated-input (calculate-input text new-at-idxs)] - (log/debug "[mentions] new-at-idxs" new-at-idxs calculated-input) + [{:keys [db]} public-key] + (let [chat-id (:current-chat-id db) + text (get-in db [:chat/inputs chat-id :input-text]) + params [chat-id text public-key] + method "wakuext_chatMentionRecheckAtIdxs"] + {:json-rpc/call [{:method method + :params params + :on-success #(rf/dispatch [:mention/on-recheck-at-idxs-success %]) + :on-error #(rf/dispatch [:mention/on-error + {:method method + :params params} %])}]})) + +(rf/defn on-recheck-at-idxs-success + {:events [:mention/on-recheck-at-idxs-success]} + [{:keys [db]} result] + (log/debug "[mentions] on-recheck-at-idxs-success" {:result result}) + (let [{:keys [input-segments state chat-id]} (transfer-mention-result result)] {:db (-> db - (update-in - [:chats/mentions chat-id :mentions] - assoc - :at-idxs - new-at-idxs) - (assoc-in [:chat/inputs-with-mentions chat-id] calculated-input))})) + (assoc-in [:chats/mentions chat-id :mentions] state) + (assoc-in [:chat/inputs-with-mentions chat-id] input-segments))})) -(rf/defn calculate-suggestions - {:events [::calculate-suggestions]} - [{: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-idxs start end] :as state} - (get-in db [:chats/mentions chat-id :mentions]) - new-text (or new-text text)] - (log/debug "[mentions] calculate suggestions" - "state" - state) - (if-not (seq at-idxs) - {:db (-> db - (assoc-in [:chats/mention-suggestions chat-id] nil) - (assoc-in [:chats/mentions chat-id :mentions :at-idxs] nil) - (assoc-in [:chat/inputs-with-mentions chat-id] [[:text text]]))} - (let [new-at-idxs (check-idx-for-mentions - text - at-idxs - mentionable-users) - calculated-input (calculate-input text new-at-idxs) - addition? (<= start end) - end (if addition? - (+ start (count new-text)) - start) - at-sign-idx (string/last-index-of text at-sign start) - searched-text (string/lower-case (subs text (inc at-sign-idx) end)) - mentions - (when (and (not (> at-sign-idx start)) - (not (> (- end at-sign-idx) 100))) - (get-user-suggestions mentionable-users searched-text))] - (log/debug "[mentions] mention check" - "addition" addition? - "at-sign-idx" at-sign-idx - "start" start - "end" end - "searched-text" (pr-str searched-text) - "mentions" (count mentions)) - (log/debug "[mentions] new-at-idxs" new-at-idxs calculated-input) - {:db (-> db - (update-in [:chats/mentions chat-id :mentions] - assoc - :at-sign-idx at-sign-idx - :at-idxs new-at-idxs - :mention-end end) - (assoc-in [:chat/inputs-with-mentions chat-id] calculated-input) - (assoc-in [:chats/mention-suggestions chat-id] mentions))})))) +(rf/defn reset-text-input-cursor + [_ ref cursor] + {::reset-text-input-cursor [ref cursor]}) -(defn new-input-text-with-mention - [{:keys [db]} {:keys [primary-name]}] - (let [chat-id (:current-chat-id db) - text (get-in db [:chat/inputs chat-id :input-text]) - {:keys [mention-end at-sign-idx]} - (get-in db [:chats/mentions chat-id :mentions])] - (log/debug "[mentions] clear suggestions" - "state" - new-input-text-with-mention) - (string/join - [(subs text 0 (inc at-sign-idx)) - primary-name - (let [next-char (get text mention-end)] - (when (or (not next-char) - (and next-char - (not (re-matches #"\s" next-char)))) - " ")) - (subs text mention-end)]))) +(rf/defn on-new-input-text-with-mentions-success + {:events [:mention/on-new-input-text-with-mentions-success]} + [{:keys [db] :as cofx} result primary-name text-input-ref match searched-text public-key] + (log/debug "[mentions] on-new-input-text-with-mentions-success" + {:result result + :primary-name primary-name + :match match + :searched-text searched-text + :public-key public-key}) + (let [{:keys [new-text state chat-id]} (transfer-mention-result result) + {:keys [at-sign-idx]} state + cursor (+ at-sign-idx (count primary-name) 2)] + (rf/merge + cofx + {:db (-> db + (assoc-in [:chats/mention-suggestions chat-id] nil)) + :set-text-input-value [chat-id new-text text-input-ref] + :dispatch [:chat.ui/set-chat-input-text new-text chat-id]} + ;; NOTE(rasom): Some keyboards do not react on selection property passed to + ;; text input (specifically Samsung keyboard with predictive text set on). + ;; In this case, if the user continues typing after the programmatic change, + ;; the new text is added to the last known cursor position before + ;; programmatic change. By calling `reset-text-input-cursor` we force the + ;; keyboard's cursor position to be changed before the next input. + (reset-text-input-cursor text-input-ref cursor) + ;; NOTE(roman): on-text-input event is not dispatched when we change input + ;; programmatically, so we have to call `on-text-input` manually + (on-text-input + (let [match-len (count match) + start (inc at-sign-idx) + end (+ start match-len)] + {:new-text match + :previous-text searched-text + :start start + :end end})) + (recheck-at-idxs public-key)))) (rf/defn clear-suggestions [{:keys [db]}] @@ -639,43 +199,39 @@ (let [chat-id (:current-chat-id db)] (rf/merge cofx - {:db (-> db - (update-in [:chats/mentions chat-id] dissoc :mentions) - (update :chat/inputs-with-mentions dissoc chat-id))} + {:db (-> db + (update-in [:chats/mentions chat-id] dissoc :mentions) + (update :chat/inputs-with-mentions dissoc chat-id)) + :json-rpc/call [{:method "wakuext_chatMentionClearMentions" + :params [chat-id] + :on-success #() + :on-error #(log/error "Error while calling wakuext_chatMentionClearMentions" + {:error %})}]} (clear-suggestions)))) -(rf/defn clear-cursor - {:events [::clear-cursor]} - [{:keys [db]}] - (log/debug "[mentions] clear cursor") - {:db - (update db :chats/cursor dissoc (:current-chat-id db))}) - (rf/defn check-selection - {:events [::on-selection-change]} - [{:keys [db] :as cofx} - {:keys [start end] :as selection} - mentionable-users] + {:events [:mention/on-selection-change]} + [{:keys [db]} + {:keys [start end]}] (let [chat-id (:current-chat-id db) - {:keys [mention-end at-idxs]} - (get-in db [:chats/mentions chat-id :mentions])] - (when (seq at-idxs) - (if (some - (fn [{:keys [from to] :as idx}] - (when (and (not (< start from)) - (<= (dec end) to)) - idx)) - at-idxs) - (rf/merge - cofx - {:db (update-in db - [:chats/mentions chat-id :mentions] - assoc - :start end - :end end - :new-text "")} - (calculate-suggestions mentionable-users)) - (clear-suggestions cofx))))) + text (get-in db [:chat/inputs chat-id :input-text]) + params [chat-id text start end] + method "wakuext_chatMentionHandleSelectionChange"] + (when text + (log/debug "[mentions] check-selection" {:params params}) + {:json-rpc/call [{:method method + :params [chat-id text start end] + :on-success #(rf/dispatch [:mention/on-handle-selection-change-success %]) + :on-error #(rf/dispatch [:mention/on-error + {:method method + :params params} %])}]}))) + +(rf/defn on-check-selection-success + {:events [:mention/on-handle-selection-change-success]} + [{:keys [db]} result] + (log/debug "[mentions] on-check-selection-success" {:result result}) + (let [{:keys [state chat-id]} (transfer-mention-result result)] + {:db (assoc-in db [:chats/mentions chat-id :mentions] (rename-state state))})) (re-frame/reg-fx ::reset-text-input-cursor @@ -685,149 +241,27 @@ (rn/find-node-handle (react/current-ref ref)) cursor)))) -(rf/defn reset-text-input-cursor - [_ ref cursor] - {::reset-text-input-cursor [ref cursor]}) +(rf/defn calculate-suggestions + {:events [:mention/calculate-suggestions]} + [{:keys [db]}] + (let [chat-id (:current-chat-id db) + text (get-in db [:chat/inputs chat-id :input-text]) + params [chat-id text] + method "wakuext_chatMentionCalculateSuggestions"] + (log/debug "[mentions] calculate-suggestions" {:params params}) + {:json-rpc/call [{:method method + :params [chat-id text] + :on-success #(rf/dispatch [:mention/on-calculate-suggestions-success %]) + :on-error #(rf/dispatch [:mention/on-error + {:method method + :params params} %])}]})) -(defn is-valid-terminating-character? - [c] - (case c - "\t" true ; tab - "\n" true ; newline - "\f" true ; new page - "\r" true ; carriage return - " " true ; whitespace - "," true - "." true - ":" true - ";" true - false)) - -(def hex-reg #"[0-9a-f]") - -(defn is-public-key-character? - [c] - (.test hex-reg c)) - -(def mention-length 133) - -(defn ->input-field - "->input-field takes a string with mentions in the @0xpk format - and retuns a list in the format - [{:type :text :text text} {:type :mention :text 0xpk}...]" - [text] - (let [{:keys [text - current-mention-length - current-text - current-mention]} - (reduce (fn [{:keys [text - current-text - current-mention - current-mention-length]} character] - (let [is-pk-character (is-public-key-character? character) - is-termination-character (is-valid-terminating-character? character)] - (cond - ;; It's a valid mention. - ;; Add any text that is before if present - ;; and add the mention. - ;; Set the text to the new termination character - (and (= current-mention-length mention-length) - is-termination-character) - {:current-mention-length 0 - :current-mention "" - :current-text character - :text (cond-> text - (seq current-text) - (conj [:text current-text]) - :always - (conj [:mention current-mention]))} - - - ;; It's either a pk character, or the `x` in the pk - ;; in this case add the text to the mention and continue - - - (or - (and is-pk-character - (pos? current-mention-length)) - (and (= 2 current-mention-length) - (= "x" character))) - {:current-mention-length (inc current-mention-length) - :current-text current-text - :current-mention (str current-mention character) - :text text} - - - ;; The beginning of a mention, discard the @ sign - ;; and start following a mention - - - (= "@" character) - {:current-mention-length 1 - :current-mention "" - :current-text current-text - :text text} - - ;; Not a mention character, but we were following a mention - ;; discard everything up to know an count as text - (and (not is-pk-character) - (pos? current-mention-length)) - {:current-mention-length 0 - :current-text (str current-text "@" current-mention character) - :current-mention "" - :text text} - - ;; Just a normal text character - :else - {:current-mention-length 0 - :current-mention "" - :current-text (str current-text character) - :text text}))) - {:current-mention-length 0 - :current-text "" - :current-mention "" - :text []} - text)] - ;; Process any remaining mention/text - (cond-> text - (seq current-text) - (conj [:text current-text]) - (= current-mention-length mention-length) - (conj [:mention current-mention])))) - -(defn ->info - "->info convert a input-field representation of mentions to - a db based representation used to indicate where mentions are placed in the - input string" - [m] - (reduce (fn [{:keys [start end at-idxs at-sign-idx mention-end]} [t text]] - (if (= :mention t) - (let [new-mention {:checked? true - :mention? true - :from mention-end - :to (+ start (count text))} - has-previous? (seq at-idxs)] - {:new-text (last text) - :previous-text "" - :start (+ start (count text)) - :end (+ end (count text)) - :at-idxs (cond-> at-idxs - has-previous? - (-> pop - (conj (assoc (peek at-idxs) :next-at-idx mention-end))) - :always - (conj new-mention)) - :at-sign-idx mention-end - :mention-end (+ mention-end (count text))}) - {:new-text (last text) - :previous-text "" - :start (+ start (count text)) - :end (+ end (count text)) - :at-idxs at-idxs - :at-sign-idx at-sign-idx - :mention-end (+ mention-end (count text))})) - {:start -1 - :end -1 - :at-idxs [] - :mention-end 0} - m)) +(rf/defn on-calculate-suggestions-success + {:events [:mention/on-calculate-suggestions-success]} + [{:keys [db]} result] + (log/debug "[mentions] on-calculate-suggestions-success" {:result result}) + (let [{:keys [state chat-id mentionable-users input-segments]} (transfer-mention-result result)] + {:db (-> db + (assoc-in [:chats/mention-suggestions chat-id] mentionable-users) + (assoc-in [:chats/mentions chat-id :mentions] state) + (assoc-in [:chat/inputs-with-mentions chat-id] input-segments))})) diff --git a/src/status_im/chat/models/mentions_test.cljs b/src/status_im/chat/models/mentions_test.cljs deleted file mode 100644 index 4595511a29..0000000000 --- a/src/status_im/chat/models/mentions_test.cljs +++ /dev/null @@ -1,323 +0,0 @@ -(ns status-im.chat.models.mentions-test - (:require [cljs.test :as test] - [clojure.string :as string] - [status-im.chat.models.mentions :as mentions])) - -(def ->info-input - [[:text "H."] - [:mention - "@helpinghand.eth"] - [:text - " "]]) - -(def ->info-expected - {:at-sign-idx 2 - :mention-end 19 - :new-text " " - :previous-text "" - :start 18 - :end 18 - :at-idxs [{:mention? true - :from 2 - :to 17 - :checked? true}]}) - -(test/deftest test->info - (test/testing "->info base case" - (test/is (= ->info-expected (mentions/->info ->info-input))))) - -;; No mention -(def mention-text-1 "parse-text") -(def mention-text-result-1 [[:text "parse-text"]]) - -;; Mention in the middle -(def mention-text-2 - "hey @0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073 he") -(def mention-text-result-2 - [[:text "hey "] - [:mention - "0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073"] - [:text " he"]]) - -;; Mention at the beginning -(def mention-text-3 - "@0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073 he") -(def mention-text-result-3 - [[:mention - "0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073"] - [:text " he"]]) - -;; Mention at the end -(def mention-text-4 - "hey @0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073") -(def mention-text-result-4 - [[:text "hey "] - [:mention - "0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073"]]) - -;; Invalid mention -(def mention-text-5 - "invalid @0x04fBce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073") -(def mention-text-result-5 - [[:text - "invalid @0x04fBce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073"]]) - -(test/deftest test-to-input - (test/testing "only text" - (test/is (= mention-text-result-1 (mentions/->input-field mention-text-1)))) - (test/testing "in the middle" - (test/is (= mention-text-result-2 (mentions/->input-field mention-text-2)))) - (test/testing "at the beginning" - (test/is (= mention-text-result-3 (mentions/->input-field mention-text-3)))) - (test/testing "at the end" - (test/is (= mention-text-result-4 (mentions/->input-field mention-text-4)))) - (test/testing "invalid" - (test/is (= mention-text-result-5 (mentions/->input-field mention-text-5))))) - -(test/deftest test-replace-mentions - (let [users {"User Number One" - {:primary-name "User Number One" - :public-key "0xpk1"} - "User Number Two" - {:primary-name "user2" - :secondary-name "User Number Two" - :public-key "0xpk2"} - "User Number Three" - {:primary-name "user3" - :secondary-name "User Number Three" - :public-key "0xpk3"}}] - (test/testing "empty string" - (let [text "" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "no text" - (let [text nil - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "incomlepte mention 1" - (let [text "@" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "incomplete mention 2" - (let [text "@r" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "no mentions" - (let [text "foo bar @buzz kek @foo" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "starts with mention" - (let [text "@User Number One" - result (mentions/replace-mentions text users)] - (test/is (= result "@0xpk1") (pr-str text)))) - - (test/testing "starts with mention, comma after mention" - (let [text "@User Number One," - result (mentions/replace-mentions text users)] - (test/is (= result "@0xpk1,") (pr-str text)))) - - (test/testing "starts with mention but no space after" - (let [text "@User Number Onefoo" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "starts with mention, some text after mention" - (let [text "@User Number One foo" - result (mentions/replace-mentions text users)] - (test/is (= result "@0xpk1 foo") (pr-str text)))) - - (test/testing "starts with some text, then mention" - (let [text "text @User Number One" - result (mentions/replace-mentions text users)] - (test/is (= result "text @0xpk1") (pr-str text)))) - - (test/testing "starts with some text, then mention, then more text" - (let [text "text @User Number One foo" - result (mentions/replace-mentions text users)] - (test/is (= result "text @0xpk1 foo") (pr-str text)))) - - (test/testing "no space before mention" - (let [text "text@User Number One" - result (mentions/replace-mentions text users)] - (test/is (= result "text@0xpk1") (pr-str text)))) - - (test/testing "two different mentions" - (let [text "@User Number One @User Number two" - result (mentions/replace-mentions text users)] - (test/is (= result "@0xpk1 @0xpk2") (pr-str text)))) - - (test/testing "two different mentions, separated with comma" - (let [text "@User Number One,@User Number two" - result (mentions/replace-mentions text users)] - (test/is (= result "@0xpk1,@0xpk2") (pr-str text)))) - - (test/testing "two different mentions inside text" - (let [text "foo@User Number One bar @User Number two baz" - result (mentions/replace-mentions text users)] - (test/is (= result "foo@0xpk1 bar @0xpk2 baz") (pr-str text)))) - - (test/testing "ens mention" - (let [text "@user2" - result (mentions/replace-mentions text users)] - (test/is (= result "@0xpk2") (pr-str text)))) - - (test/testing "multiple mentions" - (let [text (string/join - " " - (repeat 1000 "@User Number One @User Number two")) - result (mentions/replace-mentions text users) - exprected-result (string/join - " " - (repeat 1000 "@0xpk1 @0xpk2"))] - (test/is (= exprected-result result)))) - (test/testing "markdown" - (test/testing "single * case 1" - (let [text "*@user2*" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "single * case 2" - (let [text "*@user2 *" - result (mentions/replace-mentions text users)] - (test/is (= result "*@0xpk2 *") (pr-str text)))) - - (test/testing "single * case 3" - (let [text "a*@user2*" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "single * case 4" - (let [text "*@user2 foo*foo" - result (mentions/replace-mentions text users)] - (test/is (= result "*@0xpk2 foo*foo") (pr-str text)))) - - (test/testing "single * case 5" - (let [text "a *@user2*" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "single * case 6" - (let [text "*@user2 foo*" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "single * case 7" - (let [text "@user2 *@user2 foo* @user2" - result (mentions/replace-mentions text users)] - (test/is (= result "@0xpk2 *@user2 foo* @0xpk2") (pr-str text)))) - - (test/testing "single * case 8" - (let [text "*@user2 foo**@user2 foo*" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "single * case 9" - (let [text "*@user2 foo***@user2 foo* @user2" - result (mentions/replace-mentions text users)] - (test/is (= result "*@user2 foo***@user2 foo* @0xpk2") (pr-str text)))) - - (test/testing "double * case 1" - (let [text "**@user2**" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "double * case 2" - (let [text "**@user2 **" - result (mentions/replace-mentions text users)] - (test/is (= result "**@0xpk2 **") (pr-str text)))) - - (test/testing "double * case 3" - (let [text "a**@user2**" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "double * case 4" - (let [text "**@user2 foo**foo" - result (mentions/replace-mentions text users)] - (test/is (= result "**@user2 foo**foo") (pr-str text)))) - - (test/testing "double * case 5" - (let [text "a **@user2**" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "double * case 6" - (let [text "**@user2 foo**" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "double * case 7" - (let [text "@user2 **@user2 foo** @user2" - result (mentions/replace-mentions text users)] - (test/is (= result "@0xpk2 **@user2 foo** @0xpk2") (pr-str text)))) - - (test/testing "double * case 8" - (let [text "**@user2 foo****@user2 foo**" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "double * case 9" - (let [text "**@user2 foo*****@user2 foo** @user2" - result (mentions/replace-mentions text users)] - (test/is (= result "**@user2 foo*****@user2 foo** @0xpk2") (pr-str text)))) - - (test/testing "tripple * case 1" - (let [text "***@user2 foo***@user2 foo*" - result (mentions/replace-mentions text users)] - (test/is (= result "***@user2 foo***@0xpk2 foo*") (pr-str text)))) - - (test/testing "tripple ~ case 1" - (let [text "~~~@user2 foo~~~@user2 foo~" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "quote case 1" - (let [text ">@user2" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "quote case 2" - (let [text "\n>@user2" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "quote case 3" - (let [text "\n> @user2 \n \n @user2" - result (mentions/replace-mentions text users)] - (test/is (= result "\n> @user2 \n \n @0xpk2") (pr-str text)))) - - (test/testing "quote case 4" - (let [text ">@user2\n\n>@user2" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "quote case 5" - (let [text "***hey\n\n>@user2\n\n@user2 foo***" - result (mentions/replace-mentions text users)] - (test/is (= result "***hey\n\n>@user2\n\n@0xpk2 foo***") - (pr-str text)))) - - (test/testing "code case 1" - (let [text "` @user2 `" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "code case 2" - (let [text "` @user2 `" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "code case 3" - (let [text "``` @user2 ```" - result (mentions/replace-mentions text users)] - (test/is (= result text) (pr-str text)))) - - (test/testing "code case 4" - (let [text "` ` @user2 ``" - result (mentions/replace-mentions text users)] - (test/is (= result "` ` @0xpk2 ``") (pr-str text))))))) diff --git a/src/status_im/ui/screens/chat/components/input.cljs b/src/status_im/ui/screens/chat/components/input.cljs index 6f8e564170..ffac82fce6 100644 --- a/src/status_im/ui/screens/chat/components/input.cljs +++ b/src/status_im/ui/screens/chat/components/input.cljs @@ -10,9 +10,7 @@ [re-frame.core :as re-frame] [reagent.core :as reagent] [status-im2.constants :as chat.constants] - [status-im.chat.models.mentions :as mentions] [utils.i18n :as i18n] - [status-im.multiaccounts.core :as multiaccounts] [status-im.ui.components.icons.icons :as icons] [status-im.ui.components.list.views :as list] [status-im.ui.screens.chat.components.reply :as reply] @@ -95,7 +93,7 @@ :color (styles/send-icon-color)}])]]) (defn on-selection-change - [timeout-id last-text-change mentionable-users args] + [timeout-id last-text-change args] (let [selection (.-selection ^js (.-nativeEvent ^js args)) start (.-start selection) end (.-end selection)] @@ -106,10 +104,9 @@ (reset! timeout-id (utils.utils/set-timeout - #(re-frame/dispatch [::mentions/on-selection-change + #(re-frame/dispatch [:mention/on-selection-change {:start start - :end end} - mentionable-users]) + :end end}]) 50))) ;; NOTE(rasom): on Android we dispatch event only in case if there ;; was no text changes during last 50ms. `on-selection-change` is @@ -118,10 +115,9 @@ (when (and platform/android? (or (not @last-text-change) (< 50 (- (js/Date.now) @last-text-change)))) - (re-frame/dispatch [::mentions/on-selection-change + (re-frame/dispatch [:mention/on-selection-change {:start start - :end end} - mentionable-users])))) + :end end}])))) (defonce input-texts (atom {})) (defonce mentions-enabled (reagent/atom {})) @@ -183,7 +179,7 @@ (re-frame/dispatch [:chat.ui/set-chat-input-text val])) (defn on-change - [last-text-change timeout-id mentionable-users refs chat-id sending-image args] + [last-text-change timeout-id refs chat-id sending-image args] (let [text (.-text ^js (.-nativeEvent ^js args)) prev-text (get @input-texts chat-id)] (when (and (seq prev-text) (empty? text) (not sending-image)) @@ -206,46 +202,24 @@ ;; 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])))) + (re-frame/dispatch [:mention/calculate-suggestions])))) (rf/defn set-input-text "Set input text for current-chat. Takes db and input text and cofx as arguments and returns new fx. Always clear all validation messages." {:events [:chat.ui.input/set-chat-input-text]} [{:keys [db]} text chat-id] - (let [text-with-mentions (mentions/->input-field text) - all-contacts (:contacts/contacts db) - chat (get-in db [:chats chat-id]) - current-multiaccount (:multiaccount db) - community-members (when (= (:chat-type chat) chat.constants/community-chat-type) - (get-in db [:communities (:community-id chat) :members])) - mentionable-users (mentions/get-mentionable-users - chat - all-contacts - current-multiaccount - community-members) - hydrated-mentions (map - (fn [[t mention :as e]] - (if (= t :mention) - (let [mention (multiaccounts/displayed-name - (get mentionable-users mention))] - [:mention - (if (string/starts-with? mention "@") - mention - (str "@" mention))]) - e)) - text-with-mentions) - info (mentions/->info hydrated-mentions) - new-text (string/join (map second hydrated-mentions))] - {:set-text-input-value [chat-id new-text] - :db - (-> db - (assoc-in [:chats/cursor chat-id] (:mention-end info)) - (assoc-in [:chat/inputs-with-mentions chat-id] hydrated-mentions) - (assoc-in [:chats/mentions chat-id :mentions] info))})) + (let [params [chat-id text] + method "wakuext_chatMentionToInputField"] + {:json-rpc/call [{:method method + :params params + :on-success #(rf/dispatch [:mention/on-to-input-field-success %]) + :on-error #(rf/dispatch [:mention/on-error + {:method method + :params params} %])}]})) (defn on-text-input - [mentionable-users chat-id args] + [chat-id args] (let [native-event (.-nativeEvent ^js args) text (.-text ^js native-event) previous-text (.-previousText ^js native-event) @@ -256,7 +230,7 @@ (swap! mentions-enabled assoc chat-id true)) (re-frame/dispatch - [::mentions/on-text-input + [:mention/on-text-input {:new-text text :previous-text previous-text :start start @@ -265,12 +239,11 @@ ;; `on-change`, that's why mention suggestions are calculated ;; on `on-change` (when platform/android? - (re-frame/dispatch [::mentions/calculate-suggestions mentionable-users])))) + (re-frame/dispatch [:mention/calculate-suggestions])))) (defn text-input [{:keys [set-active-panel refs chat-id sending-image]}] (let [cooldown-enabled? @(re-frame/subscribe [:chats/current-chat-cooldown-enabled?]) - mentionable-users @(re-frame/subscribe [:chats/mentionable-users]) timeout-id (atom nil) last-text-change (atom nil) mentions-enabled (get @mentions-enabled chat-id) @@ -296,11 +269,10 @@ :auto-capitalize :sentences :on-selection-change (partial on-selection-change timeout-id - last-text-change - mentionable-users) + last-text-change) :on-change - (partial on-change last-text-change timeout-id mentionable-users refs chat-id sending-image) - :on-text-input (partial on-text-input mentionable-users chat-id)} + (partial on-change last-text-change timeout-id refs chat-id sending-image) + :on-text-input (partial on-text-input chat-id)} (if mentions-enabled (for [[idx [type text]] (map-indexed (fn [idx item] diff --git a/src/status_im/ui2/screens/chat/composer/input.cljs b/src/status_im/ui2/screens/chat/composer/input.cljs index cd9998b50e..f94605691d 100644 --- a/src/status_im/ui2/screens/chat/composer/input.cljs +++ b/src/status_im/ui2/screens/chat/composer/input.cljs @@ -5,7 +5,6 @@ [re-frame.core :as re-frame] [reagent.core :as reagent] [status-im2.constants :as chat.constants] - [status-im.chat.models.mentions :as mentions] [utils.i18n :as i18n] [utils.re-frame :as rf] [utils.transforms :as transforms] @@ -93,7 +92,7 @@ (rf/dispatch [:chat.ui/set-chat-input-text val])) (defn on-selection-change - [timeout-id last-text-change mentionable-users args] + [timeout-id last-text-change args] (let [selection (.-selection ^js (.-nativeEvent ^js args)) start (.-start selection) end (.-end selection)] @@ -104,10 +103,9 @@ (reset! timeout-id (background-timer/set-timeout - #(rf/dispatch [::mentions/on-selection-change + #(rf/dispatch [:mention/on-selection-change {:start start - :end end} - mentionable-users]) + :end end}]) 50))) ;; NOTE(rasom): on Android we dispatch event only in case if there ;; was no text changes during last 50ms. `on-selection-change` is @@ -116,13 +114,12 @@ (when (and platform/android? (or (not @last-text-change) (< 50 (- (js/Date.now) @last-text-change)))) - (rf/dispatch [::mentions/on-selection-change + (rf/dispatch [:mention/on-selection-change {:start start - :end end} - mentionable-users])))) + :end end}])))) (defn on-change - [last-text-change timeout-id mentionable-users refs chat-id sending-image args] + [last-text-change timeout-id refs chat-id sending-image args] (let [text (.-text ^js (.-nativeEvent ^js args)) prev-text (get @input-texts chat-id)] (when (and (seq prev-text) (empty? text) (not sending-image)) @@ -147,10 +144,10 @@ ;; 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? - (rf/dispatch [::mentions/calculate-suggestions mentionable-users])))) + (rf/dispatch [:mention/calculate-suggestions])))) (defn on-text-input - [mentionable-users chat-id args] + [chat-id args] (let [native-event (.-nativeEvent ^js args) text (.-text ^js native-event) previous-text (.-previousText ^js native-event) @@ -161,7 +158,7 @@ (swap! mentions-enabled? assoc chat-id true)) (rf/dispatch - [::mentions/on-text-input + [:mention/on-text-input {:new-text text :previous-text previous-text :start start @@ -170,7 +167,7 @@ ;; `on-change`, that's why mention suggestions are calculated ;; on `on-change` (when platform/android? - (rf/dispatch [::mentions/calculate-suggestions mentionable-users])))) + (rf/dispatch [:mention/calculate-suggestions])))) (defn text-input-style [chat-id] @@ -193,7 +190,6 @@ (defn text-input [{:keys [refs chat-id sending-image on-content-size-change]}] (let [cooldown-enabled? (rf/sub [:chats/current-chat-cooldown-enabled?]) - mentionable-users (rf/sub [:chats/mentionable-users]) timeout-id (reagent/atom nil) last-text-change (reagent/atom nil) mentions-enabled? (get @mentions-enabled? chat-id) @@ -220,18 +216,17 @@ :on-content-size-change on-content-size-change :on-selection-change (partial on-selection-change timeout-id - last-text-change - mentionable-users) + last-text-change) :on-change - (partial on-change last-text-change timeout-id mentionable-users refs chat-id sending-image) - :on-text-input (partial on-text-input mentionable-users chat-id)} + (partial on-change last-text-change timeout-id refs chat-id sending-image) + :on-text-input (partial on-text-input chat-id)} input-with-mentions (rf/sub [:chat/input-with-mentions]) children (fn [] (if mentions-enabled? (map-indexed - (fn [index [_ text]] - ^{:key (str index "_" type "_" text)} - [rn/text (when (= type :mention) {:style {:color colors/primary-50}}) + (fn [index [mention-type text]] + ^{:key (str index "_" mention-type "_" text)} + [rn/text (when (= mention-type :mention) {:style {:color colors/primary-50}}) text]) input-with-mentions) (get @input-texts chat-id)))] diff --git a/src/status_im2/subs/chat/chats.cljs b/src/status_im2/subs/chat/chats.cljs index bb9879e514..04088c3496 100644 --- a/src/status_im2/subs/chat/chats.cljs +++ b/src/status_im2/subs/chat/chats.cljs @@ -3,7 +3,6 @@ [quo.design-system.colors :as colors] [re-frame.core :as re-frame] [status-im.add-new.db :as db] - [status-im.chat.models.mentions :as mentions] [status-im.communities.core :as communities] [status-im.group-chats.core :as group-chat] [status-im.group-chats.db :as group-chats.db] @@ -438,26 +437,6 @@ (fn [[current-chat pk]] (group-chat/member-removed? current-chat pk))) -(re-frame/reg-sub - :chats/mentionable-users - :<- [:chats/current-chat] - :<- [:contacts/blocked-set] - :<- [:contacts/contacts] - :<- [:multiaccount] - :<- [:communities/current-community-members] - (fn - [[{:keys [users] :as chat} - blocked - all-contacts - {:keys [public-key] :as current-multiaccount} - community-members]] - (let [mentionable-users (mentions/get-mentionable-users chat - all-contacts - current-multiaccount - community-members) - members-left (into #{} (filter #(group-chat/member-removed? chat %) (keys users)))] - (apply dissoc mentionable-users (conj (concat blocked members-left) public-key))))) - (re-frame/reg-sub :chat/mention-suggestions :<- [:chats/current-chat-id] diff --git a/status-go-version.json b/status-go-version.json index 7c913ad656..d71957cbe9 100644 --- a/status-go-version.json +++ b/status-go-version.json @@ -3,7 +3,7 @@ "_comment": "Instead use: scripts/update-status-go.sh ", "owner": "status-im", "repo": "status-go", - "version": "v0.143.0", - "commit-sha1": "39be3c081ce80178f0daa1f38c00aebb87c1a574", - "src-sha256": "0hdz740p0n6dk384cdqgpc7pv3a8fz558q20k4iji98bk9m5qjs1" + "version": "v0.143.1", + "commit-sha1": "ee01fe4e0c14f03fb32dda15365da3c73470cde0", + "src-sha256": "0l5rld1p5nsvfahn8iymyn87a8h6wrllcx2jngcy7pxffbgk3619" }