diff --git a/.re-natal b/.re-natal index 98d4f13f0d..4fa97832b4 100644 --- a/.re-natal +++ b/.re-natal @@ -37,7 +37,6 @@ "web3", "eccjs", "chance", - "react-native-autolink", "instabug-reactnative", "nfc-react-native", "react-native-http-bridge", diff --git a/package-lock.json b/package-lock.json index fdd8b8fd0c..6de59d4144 100644 --- a/package-lock.json +++ b/package-lock.json @@ -401,11 +401,6 @@ "resolved": "https://registry.npmjs.org/atob/-/atob-2.0.3.tgz", "integrity": "sha1-GcenYEc3dEaPILLS0DNyrX1Mv10=" }, - "autolinker": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-1.6.0.tgz", - "integrity": "sha1-utN2t62OQV8i8QL8Dzf2QOZPHL8=" - }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -6882,15 +6877,6 @@ "prop-types": "15.6.0" } }, - "react-native-autolink": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/react-native-autolink/-/react-native-autolink-1.1.1.tgz", - "integrity": "sha512-h11oWD1x0Z1ar0W493tpXJ8hMofpcm21xg3wPFIy5r4CvsRlWNMBp9iG+4jeYqCNAQcDHHeu5yO+NJVaWffXZg==", - "requires": { - "autolinker": "1.6.0", - "prop-types": "15.6.0" - } - }, "react-native-background-timer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/react-native-background-timer/-/react-native-background-timer-2.0.0.tgz", diff --git a/package.json b/package.json index 93c9a7de83..1a0f6ef9cf 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "react-dom": "16.2.0", "react-native": "0.51.0", "react-native-action-button": "2.8.1", - "react-native-autolink": "1.1.1", "react-native-background-timer": "2.0.0", "react-native-camera": "0.10.0", "react-native-config": "0.9.0", diff --git a/react-native/src/status_im/react_native/js_dependencies.cljs b/react-native/src/status_im/react_native/js_dependencies.cljs index 4167e84a3f..45803f229f 100644 --- a/react-native/src/status_im/react_native/js_dependencies.cljs +++ b/react-native/src/status_im/react_native/js_dependencies.cljs @@ -1,7 +1,6 @@ (ns status-im.react-native.js-dependencies) (def action-button (js/require "react-native-action-button")) -(def autolink (js/require "react-native-autolink")) (def camera (js/require "react-native-camera")) (def config (js/require "react-native-config")) (def dialogs (js/require "react-native-dialogs")) diff --git a/src/status_im/chat/views/message/message.cljs b/src/status_im/chat/views/message/message.cljs index e8bfcadfed..277bff708e 100644 --- a/src/status_im/chat/views/message/message.cljs +++ b/src/status_im/chat/views/message/message.cljs @@ -14,10 +14,12 @@ [status-im.chat.views.message.request-message :as request-message] [status-im.constants :as constants] [status-im.ui.components.chat-icon.screen :as chat-icon.screen] + [status-im.utils.core :as utils] [status-im.utils.identicon :as identicon] [status-im.utils.gfycat.core :as gfycat] [status-im.utils.platform :as platform] [status-im.i18n :as i18n] + [status-im.ui.components.colors :as colors] [clojure.string :as string] [status-im.chat.events.console :as console] [taoensso.timbre :as log])) @@ -65,7 +67,7 @@ (defview message-content-command [{:keys [content params] :as message}] - (letsubs [command [:get-command (:content-command-ref content)]] + (letsubs [command [:get-command (:content-command-ref content)]] (let [preview (:preview content) {:keys [type color] icon-path :icon} command] [react/view style/content-command-view @@ -96,7 +98,49 @@ {"\\*[^*]+\\*" {:font-weight :bold} "~[^~]+~" {:font-style :italic}}) -(def regx (re-pattern (string/join "|" (map first replacements)))) +(def regx-styled (re-pattern (string/join "|" (map first replacements)))) + +(def regx-url #"(?i)(?:[a-z][\w-]+:(?:/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{1,4}/?)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'\".,<>?«»“”‘’]){0,}") + +(defn- parse-str-regx [string regx matched-fn unmatched-fn] + (if (string? string) + (let [unmatched-text (as-> (->> (string/split string regx) + (remove empty?) + vec) $ + (if (zero? (count $)) + [nil] + (unmatched-fn $))) + matched-text (as-> (->> string + (re-seq regx) + matched-fn + vec) $ + (if (> (count unmatched-text) + (count $)) + (conj $ nil) + $))] + (mapcat vector unmatched-text matched-text)) + (str string))) + +(defn parse-url [string] + (parse-str-regx string + regx-url + (fn [text-seq] + (map (fn [[text]] {:text text :url? true}) text-seq)) + (fn [text-seq] + (map (fn [text] {:text text :url? false}) text-seq)))) + +(defn- autolink [string on-press] + (->> (parse-url string) + (map-indexed (fn [idx {:keys [text url?]}] + (if url? + (let [[url _ _ _ text] (re-matches #"(?i)^((\w+://)?(www\d{0,3}[.])?)?(.*)$" text)] + [react/text + {:key idx + :style {:color colors/blue} + :on-press #(on-press url)} + (utils/truncate-str text 32 true)]) + text))) + vec)) (defn get-style [string] (->> replacements @@ -108,37 +152,29 @@ replacements)) ;; todo rewrite this, naive implementation -(defn- parse-text [string] - (if (string? string) - (let [general-text (string/split string regx) - general-text' (if (zero? (count general-text)) - [nil] - general-text) - styled-text (vec (map-indexed (fn [idx string] - (let [style (get-style string)] - [react/text - {:key (str idx "_" string) - :style style} - (subs string 1 (dec (count string)))])) - (re-seq regx string))) - styled-text' (if (> (count general-text) - (count styled-text)) - (conj styled-text nil) - styled-text)] - (mapcat vector general-text' styled-text')) - (str string))) +(defn- parse-text [string url-on-press] + (parse-str-regx string + regx-styled + (fn [text-seq] + (map-indexed (fn [idx string] + (let [style (get-style string)] + [react/text + {:key (str idx "_" string) + :style style} + (subs string 1 (dec (count string)))])) + text-seq)) + (fn [text-seq] + (map-indexed (fn [idx string] + (apply react/text + {:key (str idx "_" string)} + (autolink string url-on-press))) + text-seq)))) (defn text-message [{:keys [content] :as message}] [message-view message - (let [parsed-text (parse-text content) - simple-text? (and (= (count parsed-text) 2) - (nil? (second parsed-text)))] - (if simple-text? - [react/autolink {:style (style/text-message message) - :text (apply str parsed-text) - :onPress #(re-frame/dispatch [:browse-link-from-message %])}] - [react/text {:style (style/text-message message)} parsed-text]))]) + (let [parsed-text (parse-text content #(re-frame/dispatch [:browse-link-from-message %]))] + [react/text {:style (style/text-message message)} parsed-text])]) (defmulti message-content (fn [_ message _] (message :content-type))) diff --git a/src/status_im/ui/components/react.cljs b/src/status_im/ui/components/react.cljs index fd0027030c..cffae9dd63 100644 --- a/src/status_im/ui/components/react.cljs +++ b/src/status_im/ui/components/react.cljs @@ -160,15 +160,6 @@ (let [clipboard-contents (.getString (.-Clipboard js-dependencies/react-native))] (.then clipboard-contents #(clbk %)))) - -;; Autolink - -(def autolink-class (reagent/adapt-react-class (.-default js-dependencies/autolink))) - -(defn autolink [opts] - (reagent/as-element - [autolink-class (add-font-style :style opts)])) - ;; HTTP Bridge (def http-bridge js-dependencies/http-bridge) diff --git a/src/status_im/utils/core.cljc b/src/status_im/utils/core.cljc index 49352502e7..f13ca6065f 100644 --- a/src/status_im/utils/core.cljc +++ b/src/status_im/utils/core.cljc @@ -3,11 +3,19 @@ (defn truncate-str "Given string and max threshold, trims the string to threshold length with `...` - appended to end if length of the string exceeds max threshold, returns the same - string if threshold is not exceeded" - [s threshold] + appended to end or in the middle if length of the string exceeds max threshold, + returns the same string if threshold is not exceeded" + [s threshold & [middle?]] (if (and s (< threshold (count s))) - (str (subs s 0 (- threshold 3)) "...") + (if middle? + (let [str-len (count s) + max-len (- threshold 3) + start-len (Math/ceil (/ max-len 2)) + end-len (Math/floor (/ max-len 2)) + start (subs s 0 start-len) + end (subs s (- str-len end-len) str-len)] + (str start "..." end)) + (str (subs s 0 (- threshold 3)) "...")) s)) (defn clean-text [s] diff --git a/test/cljs/status_im/react_native/js_dependencies.cljs b/test/cljs/status_im/react_native/js_dependencies.cljs index da79a59db4..85071933b3 100644 --- a/test/cljs/status_im/react_native/js_dependencies.cljs +++ b/test/cljs/status_im/react_native/js_dependencies.cljs @@ -1,7 +1,6 @@ (ns status-im.react-native.js-dependencies) (def action-button #js {:default #js {:Item #js {}}}) -(def autolink #js {:default #js {}}) (def config #js {:default #js {}}) (def camera #js {:constants #js {}}) (def dialogs #js {}) diff --git a/test/cljs/status_im/test/chat/views/message.cljs b/test/cljs/status_im/test/chat/views/message.cljs new file mode 100644 index 0000000000..828ccb4cd2 --- /dev/null +++ b/test/cljs/status_im/test/chat/views/message.cljs @@ -0,0 +1,20 @@ +(ns status-im.test.chat.views.message + (:require [cljs.test :refer [deftest is]] + [status-im.chat.views.message.message :as message])) + +(deftest parse-url + (is (= (lazy-seq [nil {:text "www.google.com" :url? true}]) + (message/parse-url "www.google.com"))) + (is (= (lazy-seq [nil {:text "status.im" :url? true}]) + (message/parse-url "status.im"))) + (is (= (lazy-seq [{:text "$33.90" :url? false} nil]) + (message/parse-url "$33.90"))) + (is (= (lazy-seq [nil {:text "https://www.google.com/?gfe_rd=cr&dcr=0&ei=P9-CWuyBGaro8AeqkYGQDQ&gws_rd=cr&fg=1" :url? true}]) + (message/parse-url "https://www.google.com/?gfe_rd=cr&dcr=0&ei=P9-CWuyBGaro8AeqkYGQDQ&gws_rd=cr&fg=1"))) + (is (= (lazy-seq [{:text "Status - " :url? false} + {:text "https://github.com/status-im/status-react" :url? true} + {:text " a Mobile Ethereum Operating System" :url? false} + nil]) + (message/parse-url "Status - https://github.com/status-im/status-react a Mobile Ethereum Operating System"))) + (is (= (lazy-seq [{:text "Browse, chat and make payments securely on the decentralized web." :url? false} nil]) + (message/parse-url "Browse, chat and make payments securely on the decentralized web.")))) diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs index 212ecf3617..4e68bb1294 100644 --- a/test/cljs/status_im/test/runner.cljs +++ b/test/cljs/status_im/test/runner.cljs @@ -9,6 +9,7 @@ [status-im.test.profile.events] [status-im.test.bots.events] [status-im.test.chat.models.input] + [status-im.test.chat.views.message] [status-im.test.i18n] [status-im.test.protocol.web3.inbox] [status-im.test.utils.utils] @@ -43,6 +44,7 @@ 'status-im.test.wallet.transactions.subs 'status-im.test.wallet.transactions.views 'status-im.test.chat.models.input + 'status-im.test.chat.views.message 'status-im.test.i18n 'status-im.test.protocol.web3.inbox 'status-im.test.utils.utils diff --git a/test/cljs/status_im/test/utils/utils.cljs b/test/cljs/status_im/test/utils/utils.cljs index 2689ec0229..d966e45ea4 100644 --- a/test/cljs/status_im/test/utils/utils.cljs +++ b/test/cljs/status_im/test/utils/utils.cljs @@ -13,6 +13,7 @@ (deftest truncate-str-test (is (= (u/truncate-str "Long string" 7) "Long...")) ; threshold is less then string length + (is (= (u/truncate-str "Long string" 7 true) "Lo...ng")) ; threshold is less then string length (truncate middle) (is (= (u/truncate-str "Long string" 11) "Long string")) ; threshold is the same as string length (is (= (u/truncate-str "Long string" 20) "Long string"))) ; threshold is more then string length