diff --git a/spec/more_speech/ui/formatters_spec.clj b/spec/more_speech/ui/formatters_spec.clj index b578aa1..e7e2ca8 100644 --- a/spec/more_speech/ui/formatters_spec.clj +++ b/spec/more_speech/ui/formatters_spec.clj @@ -230,27 +230,44 @@ (describe "Segment article content" (it "returns empty list if content is empty" (should= '() (segment-article ""))) + (it "returns a single :text element if no url in content" (should= '([:text "no url"]) (segment-article "no url"))) + (it "returns a single :url element if whole content is a url" (should= '([:url "http://nostr.com"]) (segment-article "http://nostr.com"))) - (it "returns a list of :text and :url elements when content contains multiple text and url segments" - (should= '([:text "Check this "] [:url "http://nostr.com"] [:text " It's cool"]) - (segment-article "Check this http://nostr.com It's cool"))) + + (it "returns a :namereference segment" + (should= [[:namereference "@name"] [:text " text"]] + (segment-article "@name text"))) + + (it "returns a list of :text and :url and :namereference segments" + (should= [[:text "Hey "] [:namereference "@bob"] [:text " Check this "] [:url "http://nostr.com"] [:text " It's cool"]] + (segment-article "Hey @bob Check this http://nostr.com It's cool"))) ) (describe "Format article" (it "should escape HTML entities" (should= "<b>text</b>" (reformat-article "text"))) + (it "should linkify url" (should= "nostr.com" (reformat-article "https://nostr.com"))) + + (it "should ms-link a namereference" + (should= "@name" + (reformat-article "@name"))) + (it "should escape HTML entities and linkify url" (should= "<b>Clojure</b>: clojure.org/" (reformat-article "Clojure: https://clojure.org/"))) + (it "should format replies and escape HTML entities properly" (should= ">this is
>a reply" (reformat-article ">this is >a reply"))) + (it "should replace multiple spaces with  " (should= "one two  three   ." (reformat-article "one two three ."))) + + ) (declare db) @@ -305,3 +322,17 @@ (set-mem :pubkey my-pubkey) (should= "2-deg<-trusted-pet" (format-user-id trusted-by-trusted-user)))) ) + +(describe "combine patterns" + (it "combines a single pattern and name" + (let [pattern (combine-patterns [:name1 #"pattern1"])] + (should= java.util.regex.Pattern (type pattern)) + (should= "(?pattern1)" (str pattern)))) + + (it "combines multiple patterns and names" + (let [pattern (combine-patterns [:name1 #"pattern1"] + [:name2 #"pattern2"])] + (should= java.util.regex.Pattern (type pattern)) + (should= "(?pattern1)|(?pattern2)" (str pattern)))) + + ) diff --git a/src/more_speech/ui/formatters.clj b/src/more_speech/ui/formatters.clj index 6d09a1f..922749e 100644 --- a/src/more_speech/ui/formatters.clj +++ b/src/more_speech/ui/formatters.clj @@ -6,8 +6,7 @@ [more-speech.nostr.contact-list :as contact-list] [more-speech.ui.formatter-util :refer :all] [more-speech.config :as config :refer [get-db]] - [more-speech.db.gateway :as gateway]) - ) + [more-speech.db.gateway :as gateway])) (defn format-user-id ([user-id] @@ -153,30 +152,56 @@ uri (if (= 2 (count split-url)) (second split-url) url)] (str "" uri ""))) +(defn ms-linkify [type subject] + (str "" subject "")) + +(defn combine-patterns + "patterns are a list of [:name pattern]" + [& patterns] + (let [grouped-patterns (map #(str "(?<" (name (first %)) ">" (second %) ")") patterns) + combined-patterns (interpose "|" grouped-patterns)] + (re-pattern (apply str combined-patterns)))) + (defn segment-article ([content] - (let [segment (re-find config/url-pattern content)] - (cond - (not (nil? segment)) - (let [url-start-index (string/index-of content segment) - url-end-index (+ url-start-index (.length segment)) - text-sub (subs content 0 url-start-index) - url-sub (subs content url-start-index url-end-index) - rest (subs content url-end-index)] - (concat - (if (empty? text-sub) - [[:url url-sub]] - [[:text text-sub] [:url url-sub]]) - (segment-article rest))) - (not (empty? content)) (list [:text content]) - :else '())))) + (segment-article content [])) + + ([content segments] + (let [patterns [[:url config/url-pattern] + [:namereference config/user-name-pattern]] + pattern (apply combine-patterns patterns) + group-names (map first patterns)] + (loop [content content + segments segments] + (let [matcher (re-matcher pattern content) + segment (first (re-find matcher))] + (cond + (empty? content) + segments + + (some? segment) + (let [grouped-by-name (map #(vector (keyword %) (.group matcher (name %))) group-names) + the-group (filter #(some? (second %)) grouped-by-name) + segment-type (ffirst the-group) + url-start-index (string/index-of content segment) + url-end-index (+ url-start-index (.length segment)) + text-sub (subs content 0 url-start-index) + url-sub (subs content url-start-index url-end-index) + rest (subs content url-end-index)] + (recur rest + (concat segments + (if (empty? text-sub) + [[segment-type url-sub]] + [[:text text-sub] [segment-type url-sub]])))) + :else + (concat segments (list [:text content])))))))) (defn reformat-article [article] (let [segments (segment-article article)] (reduce (fn [formatted-content [seg-type seg]] - (cond - (= seg-type :text) + (condp = seg-type + :text (str formatted-content ((comp non-breaking-spaces @@ -185,8 +210,11 @@ format-replies ) seg) ) - (= seg-type :url) - (str formatted-content (linkify seg)))) + :url + (str formatted-content (linkify seg)) + + :namereference + (str formatted-content (ms-linkify "ms-namereference" seg)))) "" segments)))