Comments for input model; redundant functions has been removed; refactoring
This commit is contained in:
parent
2adc02849f
commit
6669f79c44
|
@ -13,25 +13,19 @@
|
|||
[status-im.i18n :as i18n]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(handlers/register-handler
|
||||
:update-input-data
|
||||
(fn [db]
|
||||
(input-model/modified-db-after-change db)))
|
||||
|
||||
(handlers/register-handler :set-chat-input-text
|
||||
(fn [{:keys [current-chat-id chats chat-ui-props] :as db} [_ text chat-id]]
|
||||
(let [chat-id (or chat-id current-chat-id)
|
||||
ends-with-space? (input-model/text-ends-with-space? text)]
|
||||
(dispatch [:update-suggestions chat-id text])
|
||||
|
||||
(->> text
|
||||
(input-model/text->emoji)
|
||||
(assoc-in db [:chats chat-id :input-text]))
|
||||
|
||||
;; TODO(alwx): need to understand the need in this
|
||||
#_(if-let [{command :command} (input-model/selected-chat-command db chat-id text)]
|
||||
(let [{old-args :args} (input-model/selected-chat-command db chat-id)
|
||||
text-splitted (input-model/split-command-args text)
|
||||
new-input-text (input-model/make-input-text text-splitted old-args)]
|
||||
(assoc-in db [:chats chat-id :input-text] new-input-text))
|
||||
(->> text
|
||||
(input-model/text->emoji)
|
||||
(assoc-in db [:chats chat-id :input-text]))))))
|
||||
(assoc-in db [:chats chat-id :input-text])))))
|
||||
|
||||
(handlers/register-handler :add-to-chat-input-text
|
||||
(handlers/side-effect!
|
||||
|
@ -157,7 +151,7 @@
|
|||
:context (merge {:data data
|
||||
:from current-account-id
|
||||
:to to}
|
||||
(input-model/command-dependent-context-params command))}]
|
||||
(input-model/command-dependent-context-params current-chat-id command))}]
|
||||
(status/call-jail
|
||||
{:jail-id (or bot owner-id current-chat-id)
|
||||
:path path
|
||||
|
@ -341,7 +335,7 @@
|
|||
(dispatch [::request-command-data
|
||||
{:content command
|
||||
:chat-id chat-id
|
||||
;;TODO(alwx): jail-id ?
|
||||
:jail-id (or (get-in command [:command :bot]) chat-id)
|
||||
:data-type :validator
|
||||
:after #(dispatch [::proceed-validation %2 after-validation])}])))))
|
||||
|
||||
|
|
|
@ -1,32 +1,52 @@
|
|||
(ns status-im.chat.models.input
|
||||
(:require [clojure.string :as str]
|
||||
[status-im.components.react :as rc]
|
||||
[status-im.components.status :as status]
|
||||
[status-im.chat.constants :as const]
|
||||
[status-im.chat.views.input.validation-messages :refer [validation-message]]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.phone-number :as phone-number]
|
||||
[taoensso.timbre :as log]
|
||||
[status-im.chat.utils :as chat-utils]
|
||||
[status-im.bots.constants :as bots-constants]))
|
||||
[status-im.bots.constants :as bots-constants]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(def emojis (js/require "emojilib"))
|
||||
|
||||
(defn text->emoji [text]
|
||||
(defn text->emoji
|
||||
"Replaces emojis in a specified `text`"
|
||||
[text]
|
||||
(when text
|
||||
(str/replace text
|
||||
#":([a-z_\-+0-9]*):"
|
||||
(fn [[original emoji-id]]
|
||||
(if-let [emoji-map (aget emojis "lib" emoji-id)]
|
||||
(if-let [emoji-map (aget rc/emojilib "lib" emoji-id)]
|
||||
(aget emoji-map "char")
|
||||
original)))))
|
||||
|
||||
(defn text-ends-with-space? [text]
|
||||
(defn text-ends-with-space?
|
||||
"Returns true if the last symbol of `text` is space"
|
||||
[text]
|
||||
(when text
|
||||
(= (str/last-index-of text const/spacing-char)
|
||||
(dec (count text)))))
|
||||
|
||||
(defn possible-chat-actions [{:keys [global-commands] :as db} chat-id]
|
||||
(let [{:keys [contacts requests]} (get-in db [:chats chat-id])]
|
||||
(defn starts-as-command?
|
||||
"Returns true if `text` may be treated as a command.
|
||||
To make sure that text is command we need to use `possible-chat-actions` function."
|
||||
[text]
|
||||
(and (not (nil? text))
|
||||
(or (str/starts-with? text const/bot-char)
|
||||
(str/starts-with? text const/command-char))))
|
||||
|
||||
(defn possible-chat-actions
|
||||
"Returns a map of possible chat actions (commands and response) for a specified `chat-id`.
|
||||
Every map's key is a command's name, value is a pair of [`command` `message-id`]. In the case
|
||||
of commands `message-id` is `:any`, for responses value contains the actual id.
|
||||
|
||||
Example of output:
|
||||
{:browse [{:description \"Launch the browser\" :name \"browse\" ...} :any]
|
||||
:request [{:description \"Request a payment\" :name \"request\" ...} \"message-id\"]}"
|
||||
[{:keys [global-commands current-chat-id] :as db} chat-id]
|
||||
(let [chat-id (or chat-id current-chat-id)
|
||||
{:keys [contacts requests]} (get-in db [:chats chat-id])]
|
||||
(->> contacts
|
||||
(map (fn [{:keys [identity]}]
|
||||
(let [{:keys [commands responses]} (get-in db [:contacts identity])]
|
||||
|
@ -36,10 +56,20 @@
|
|||
requests)]
|
||||
(into commands' responses')))))
|
||||
(reduce (fn [m cur] (into (or m {}) cur)))
|
||||
(into {})
|
||||
(vals))))
|
||||
(into {}))))
|
||||
|
||||
(defn split-command-args [command-text]
|
||||
(defn split-command-args
|
||||
"Returns a list of command's arguments including the command's name.
|
||||
|
||||
Examples:
|
||||
Input: '/send Jarrad 1.0'
|
||||
Output: ['/send' 'Jarrad' '1.0']
|
||||
|
||||
Input: '/send \"Complex name with space in between\" 1.0'
|
||||
Output: ['/send' 'Complex name with space in between' '1.0']
|
||||
|
||||
All the complex logic inside this function aims to support wrapped arguments."
|
||||
[command-text]
|
||||
(let [space? (text-ends-with-space? command-text)
|
||||
command-text (if space?
|
||||
(str command-text ".")
|
||||
|
@ -66,6 +96,14 @@
|
|||
(first))))
|
||||
|
||||
(defn join-command-args [args]
|
||||
"Transforms a list of args to a string. The opposite of `split-command-args`.
|
||||
|
||||
Examples:
|
||||
Input: ['/send' 'Jarrad' '1.0']
|
||||
Output: '/send Jarrad 1.0'
|
||||
|
||||
Input: ['/send' 'Complex name with space in between' '1.0']
|
||||
Output: '/send \"Complex name with space in between\" 1.0'"
|
||||
(->> args
|
||||
(map (fn [arg]
|
||||
(if (not (str/index-of arg const/spacing-char))
|
||||
|
@ -74,6 +112,14 @@
|
|||
(str/join const/spacing-char)))
|
||||
|
||||
(defn selected-chat-command
|
||||
"Returns a map containing `:command`, `:metadata` and `:args` keys.
|
||||
Can also return `nil` if there is no selected command.
|
||||
|
||||
* `:command` key contains a map with all information about command.
|
||||
* `:metadata` is also a map which contains some additional information, usually not visible by user.
|
||||
For instance, we can add a `:to-message-id` key to this map, and this key will allow us to identity
|
||||
the request we're responding to.
|
||||
* `:args` contains all arguments provided by user."
|
||||
([{:keys [current-chat-id] :as db} chat-id input-text]
|
||||
(let [chat-id (or chat-id current-chat-id)
|
||||
input-metadata (get-in db [:chats chat-id :input-metadata])
|
||||
|
@ -81,12 +127,12 @@
|
|||
possible-actions (possible-chat-actions db chat-id)
|
||||
command-args (split-command-args input-text)
|
||||
command-name (first command-args)]
|
||||
(when (chat-utils/starts-as-command? (or command-name ""))
|
||||
(when (starts-as-command? (or command-name ""))
|
||||
(when-let [[command to-message-id]
|
||||
(-> (filter (fn [[{:keys [name bot]} message-id]]
|
||||
(= (or (when-not (bots-constants/mailman-bot? bot) bot) name)
|
||||
(subs command-name 1)))
|
||||
possible-actions)
|
||||
(vals possible-actions))
|
||||
(first))]
|
||||
{:command command
|
||||
:metadata (if (and (nil? (:to-message-id input-metadata)) (not= :any to-message-id))
|
||||
|
@ -101,6 +147,8 @@
|
|||
(def *no-argument-error* -1)
|
||||
|
||||
(defn current-chat-argument-position
|
||||
"Returns the position of current argument. It's just an integer number from -1 to infinity.
|
||||
-1 (`*no-argument-error*`) means error. It can happen if there is no selected command or selection."
|
||||
[{:keys [args] :as command} input-text selection seq-arguments]
|
||||
(if command
|
||||
(if (get-in command [:command :sequential-params])
|
||||
|
@ -120,7 +168,12 @@
|
|||
*no-argument-error*)))
|
||||
*no-argument-error*))
|
||||
|
||||
(defn argument-position [{:keys [current-chat-id] :as db} chat-id]
|
||||
(defn argument-position
|
||||
"Returns the position of current argument. It's just an integer from -1 to infinity.
|
||||
-1 (`*no-argument-error*`) means error. It can happen if there is no command or selection.
|
||||
|
||||
This method is basically just another way of calling `current-chat-argument-position`."
|
||||
[{:keys [current-chat-id] :as db} chat-id]
|
||||
(let [chat-id (or chat-id current-chat-id)
|
||||
input-text (get-in db [:chats chat-id :input-text])
|
||||
seq-arguments (get-in db [:chats chat-id :seq-arguments])
|
||||
|
@ -129,6 +182,11 @@
|
|||
(current-chat-argument-position chat-command input-text selection seq-arguments)))
|
||||
|
||||
(defn command-completion
|
||||
"Returns one of the following values indicating a command's completion status:
|
||||
* `:complete` means that the command is complete and can be sent;
|
||||
* `:less-than-needed` means that the command is not complete and additional arguments should be provided;
|
||||
* `:more-than-needed` means that the command is more than complete and contains redundant arguments;
|
||||
* `:no-command` means that there is no selected command."
|
||||
([{:keys [current-chat-id] :as db} chat-id]
|
||||
(let [chat-id (or chat-id current-chat-id)
|
||||
input-text (get-in db [:chats chat-id :input-text])
|
||||
|
@ -154,7 +212,11 @@
|
|||
:no-command)
|
||||
:no-command))))
|
||||
|
||||
(defn args->params [{:keys [command args]}]
|
||||
(defn args->params
|
||||
"Uses `args` (which is a list or vector like ['Jarrad' '1.0']) and command's `params`
|
||||
and returns a map that looks the following way:
|
||||
{:recipient \"Jarrad\" :amount \"1.0\"}"
|
||||
[{:keys [command args]}]
|
||||
(let [params (:params command)]
|
||||
(->> args
|
||||
(map-indexed (fn [i value]
|
||||
|
@ -162,11 +224,39 @@
|
|||
(into {}))))
|
||||
|
||||
(defn command-dependent-context-params
|
||||
[{:keys [name] :as command}]
|
||||
(case name
|
||||
"phone" {:suggestions (phone-number/get-examples)}
|
||||
"Returns additional `:context` data that will be added to specific commands.
|
||||
The following data shouldn't be hardcoded here."
|
||||
[chat-id {:keys [name] :as command}]
|
||||
(case chat-id
|
||||
"console" (case name
|
||||
"phone" {:suggestions (phone-number/get-examples)}
|
||||
{})
|
||||
{}))
|
||||
|
||||
(defn modified-db-after-change
|
||||
"Returns the new db object that should be used after any input change."
|
||||
[{:keys [current-chat-id] :as db}]
|
||||
(let [input-text (get-in db [:chats current-chat-id :input-text])
|
||||
command (selected-chat-command db current-chat-id input-text)
|
||||
prev-command (get-in db [:chat-ui-props current-chat-id :prev-command])]
|
||||
(if command
|
||||
(cond-> db
|
||||
;; clear the bot db
|
||||
(not= prev-command (-> command :command :name))
|
||||
(assoc-in [:bot-db (or (:bot command) current-chat-id)] nil)
|
||||
;; clear the chat's validation messages
|
||||
true
|
||||
(assoc-in [:chat-ui-props current-chat-id :validation-messages] nil))
|
||||
(-> db
|
||||
;; clear input metadata
|
||||
(assoc-in [:chats current-chat-id :input-metadata] nil)
|
||||
;; clear
|
||||
(update-in [:chat-ui-props current-chat-id]
|
||||
merge
|
||||
{:result-box nil
|
||||
:validation-messages nil
|
||||
:prev-command (-> command :command :name)})))))
|
||||
|
||||
(defmulti validation-handler (fn [name] (keyword name)))
|
||||
|
||||
(defmethod validation-handler :phone
|
||||
|
@ -176,31 +266,4 @@
|
|||
(proceed)
|
||||
(set-errors [validation-message
|
||||
{:title (i18n/label :t/phone-number)
|
||||
:description (i18n/label :t/invalid-phone)}]))))
|
||||
|
||||
(defn- changed-arg-position [xs ys]
|
||||
(let [longest (into [] (max-key count xs ys))
|
||||
shortest (into [] (if (= longest xs) ys xs))]
|
||||
(->> longest
|
||||
(map-indexed (fn [index x]
|
||||
(if (and (> (count shortest) index)
|
||||
(= (count x) (count (get shortest index))))
|
||||
nil
|
||||
index)))
|
||||
(remove nil?)
|
||||
(first))))
|
||||
|
||||
(defn make-input-text [[command & args] old-args]
|
||||
(let [args (into [] args)
|
||||
old-args (into [] old-args)
|
||||
|
||||
arg-pos (changed-arg-position args old-args)
|
||||
new-arg (get args arg-pos)
|
||||
new-args (if arg-pos
|
||||
(assoc old-args arg-pos (when new-arg
|
||||
(str/trim new-arg)))
|
||||
old-args)]
|
||||
(str
|
||||
command
|
||||
const/spacing-char
|
||||
(join-command-args new-args))))
|
||||
:description (i18n/label :t/invalid-phone)}]))))
|
|
@ -73,13 +73,6 @@
|
|||
(merge responses commands))))
|
||||
(apply merge)))))
|
||||
|
||||
(reg-sub :possible-chat-actions
|
||||
(fn [db [_ chat-id]]
|
||||
"Returns a vector of [command message-id] values. `message-id` can be `:any`.
|
||||
Example: [[browse-command :any] [debug-command :any] [phone-command '1489161286111-58a2cd...']]"
|
||||
(let [chat-id (or chat-id (db :current-chat-id))]
|
||||
(input-model/possible-chat-actions db chat-id))))
|
||||
|
||||
(reg-sub
|
||||
:selected-chat-command
|
||||
(fn [db [_ chat-id]]
|
||||
|
@ -138,7 +131,7 @@
|
|||
selected-command (subscribe [:selected-chat-command chat-id])
|
||||
requests (subscribe [:chat :request-suggestions chat-id])
|
||||
commands (subscribe [:chat :command-suggestions chat-id])]
|
||||
(and (or @show-suggestions? (chat-utils/starts-as-command? (str/trim (or @input-text ""))))
|
||||
(and (or @show-suggestions? (input-model/starts-as-command? (str/trim (or @input-text ""))))
|
||||
(not (:command @selected-command))
|
||||
(or (not-empty @requests)
|
||||
(not-empty @commands))))))
|
||||
|
|
|
@ -56,8 +56,3 @@
|
|||
bot (str const/bot-char bot)
|
||||
|
||||
:else (str const/command-char name)))
|
||||
|
||||
(defn starts-as-command? [text]
|
||||
(and (not (nil? text))
|
||||
(or (str/starts-with? text const/bot-char)
|
||||
(str/starts-with? text const/command-char))))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
(ns status-im.chat.views.choosers.choose-contact
|
||||
(ns status-im.chat.views.api.choose-contact
|
||||
(:require-macros [status-im.utils.views :refer [defview]])
|
||||
(:require [reagent.core :as r]
|
||||
[re-frame.core :refer [dispatch subscribe]]
|
|
@ -1,4 +1,4 @@
|
|||
(ns status-im.chat.views.geolocation.styles
|
||||
(ns status-im.chat.views.api.geolocation.styles
|
||||
(:require [status-im.components.styles :as common]))
|
||||
|
||||
(defn place-item-container [address]
|
|
@ -1,4 +1,4 @@
|
|||
(ns status-im.chat.views.geolocation.views
|
||||
(ns status-im.chat.views.api.geolocation.views
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]]
|
||||
[reagent.ratom :refer [reaction]])
|
||||
(:require [status-im.components.react :refer [view image text touchable-highlight]]
|
||||
|
@ -6,7 +6,7 @@
|
|||
[goog.string :as gstr]
|
||||
[status-im.utils.utils :refer [http-get]]
|
||||
[status-im.utils.types :refer [json->clj]]
|
||||
[status-im.chat.views.geolocation.styles :as st]
|
||||
[status-im.chat.views.api.geolocation.styles :as st]
|
||||
[status-im.components.mapbox :refer [mapview]]
|
||||
[re-frame.core :refer [dispatch subscribe]]
|
||||
[status-im.i18n :refer [label]]
|
|
@ -64,9 +64,8 @@
|
|||
command (subscribe [:selected-chat-command])
|
||||
sending-in-progress? (subscribe [:chat-ui-props :sending-in-progress?])
|
||||
input-focused? (subscribe [:chat-ui-props :input-focused?])
|
||||
prev-command (subscribe [:chat-ui-props :prev-command])
|
||||
input-ref (atom nil)]
|
||||
(fn [{:keys [set-layout-height set-container-width height single-line-input?]}]
|
||||
(fn [{:keys [set-layout-height-fn set-container-width-fn height single-line-input?]}]
|
||||
[text-input
|
||||
{:ref #(when %
|
||||
(dispatch [:set-chat-ui-props {:input-ref %}])
|
||||
|
@ -78,39 +77,30 @@
|
|||
:blur-on-submit false
|
||||
:on-focus #(dispatch [:set-chat-ui-props {:input-focused? true
|
||||
:show-emoji? false}])
|
||||
:on-blur #(do (dispatch [:set-chat-ui-props {:input-focused? false}]))
|
||||
:on-blur #(dispatch [:set-chat-ui-props {:input-focused? false}])
|
||||
:on-submit-editing (fn [e]
|
||||
(if single-line-input?
|
||||
(dispatch [:send-current-message])
|
||||
(.setNativeProps @input-ref (clj->js {:text (str @input-text "\n")}))))
|
||||
:on-layout (fn [e]
|
||||
(set-container-width (.-width (.-layout (.-nativeEvent e)))))
|
||||
(set-container-width-fn (.-width (.-layout (.-nativeEvent e)))))
|
||||
:on-change (fn [e]
|
||||
(let [native-event (.-nativeEvent e)
|
||||
text (.-text native-event)]
|
||||
text (.-text native-event)
|
||||
height (.. native-event -contentSize -height)]
|
||||
(when-not single-line-input?
|
||||
(let [height (.. native-event -contentSize -height)]
|
||||
(set-layout-height height)))
|
||||
(set-layout-height-fn height))
|
||||
(when (not= text @input-text)
|
||||
(dispatch [:set-chat-input-text text])
|
||||
(if @command
|
||||
(do
|
||||
(when (not= @prev-command (-> @command :command :name))
|
||||
(dispatch [:clear-bot-db @command]))
|
||||
(dispatch [:load-chat-parameter-box (:command @command)])
|
||||
(dispatch [:set-chat-ui-props {:validation-messages nil}]))
|
||||
(do
|
||||
(dispatch [:set-chat-input-metadata nil])
|
||||
(dispatch [:set-chat-ui-props
|
||||
{:result-box nil
|
||||
:validation-messages nil
|
||||
:prev-command (-> @command :command :name)}]))))))
|
||||
(when @command
|
||||
(dispatch [:load-chat-parameter-box (:command @command)]))
|
||||
(dispatch [:update-input-data]))))
|
||||
:on-content-size-change (when (and (not @input-focused?)
|
||||
(not single-line-input?))
|
||||
#(let [h (-> (.-nativeEvent %)
|
||||
(.-contentSize)
|
||||
(.-height))]
|
||||
(set-layout-height h)))
|
||||
(set-layout-height-fn h)))
|
||||
:on-selection-change #(let [s (-> (.-nativeEvent %)
|
||||
(.-selection))
|
||||
end (.-end s)]
|
||||
|
@ -119,13 +109,13 @@
|
|||
:placeholder-text-color style/color-input-helper-placeholder
|
||||
:auto-capitalize :sentences}])))
|
||||
|
||||
(defn- invisible-input [{:keys [set-layout-width value]}]
|
||||
(defn- invisible-input [{:keys [set-layout-width-fn value]}]
|
||||
(let [input-text (subscribe [:chat :input-text])]
|
||||
[text {:style style/invisible-input-text
|
||||
:on-layout #(let [w (-> (.-nativeEvent %)
|
||||
(.-layout)
|
||||
(.-width))]
|
||||
(set-layout-width w))}
|
||||
(set-layout-width-fn w))}
|
||||
(or @input-text "")]))
|
||||
|
||||
(defn- input-helper [_]
|
||||
|
@ -183,25 +173,25 @@
|
|||
(get-options type))])))))
|
||||
|
||||
(defn input-view [_]
|
||||
(let [component (r/current-component)
|
||||
set-layout-width #(r/set-state component {:width %})
|
||||
set-layout-height #(r/set-state component {:height %})
|
||||
set-container-width #(r/set-state component {:container-width %})
|
||||
command (subscribe [:selected-chat-command])]
|
||||
(let [component (r/current-component)
|
||||
set-layout-width-fn #(r/set-state component {:width %})
|
||||
set-layout-height-fn #(r/set-state component {:height %})
|
||||
set-container-width-fn #(r/set-state component {:container-width %})
|
||||
command (subscribe [:selected-chat-command])]
|
||||
(r/create-class
|
||||
{:reagent-render
|
||||
(fn [{:keys [anim-margin single-line-input?]}]
|
||||
(let [{:keys [width height container-width]} (r/state component)
|
||||
command @command]
|
||||
[animated-view {:style (style/input-root height anim-margin)}
|
||||
[invisible-input {:set-layout-width set-layout-width}]
|
||||
[basic-text-input {:set-layout-height set-layout-height
|
||||
:set-container-width set-container-width
|
||||
:height height
|
||||
:single-line-input? single-line-input?}]
|
||||
[invisible-input {:set-layout-width-fn set-layout-width-fn}]
|
||||
[basic-text-input {:set-layout-height-fn set-layout-height-fn
|
||||
:set-container-width-fn set-container-width-fn
|
||||
:height height
|
||||
:single-line-input? single-line-input?}]
|
||||
[input-helper {:command command
|
||||
:width width}]
|
||||
[seq-input {:command-width width
|
||||
[seq-input {:command-width width
|
||||
:container-width container-width}]
|
||||
(if-not command
|
||||
[touchable-highlight
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
[status-im.components.react :as components]
|
||||
[status-im.chat.views.input.web-view :as chat-web-view]
|
||||
[status-im.chat.views.input.validation-messages :as chat-validation-messages]
|
||||
[status-im.chat.views.choosers.choose-contact :as choose-contact]
|
||||
[status-im.chat.views.api.choose-contact :as choose-contact]
|
||||
[status-im.components.qr-code :as qr]
|
||||
[status-im.chat.views.geolocation.views :as geolocation]
|
||||
[status-im.chat.views.api.geolocation.views :as geolocation]
|
||||
[status-im.utils.handlers :refer [register-handler]]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
|
|
|
@ -170,3 +170,7 @@
|
|||
[keyboard-avoiding-view-class (merge {:behavior :padding} props)]
|
||||
[view props])]
|
||||
(vec (concat view-element children))))
|
||||
|
||||
;; Emoji
|
||||
|
||||
(def emojilib (js/require "emojilib"))
|
Loading…
Reference in New Issue