From 8dccb2a427306a2283cd9b66caa9ed0dc438f742 Mon Sep 17 00:00:00 2001 From: Dream <42954461+eureka0928@users.noreply.github.com> Date: Mon, 13 Apr 2026 03:55:53 -0400 Subject: [PATCH] :sparkles: Make links in comments clickable (#8894) * :sparkles: Make links in comments clickable Detect URLs in comment text and render them as clickable links that open in a new tab. Extends the existing mention parsing to also split text elements by URL patterns, handling trailing punctuation and mixed mention+URL content. Closes #1602 * :books: Add changelog entry for clickable links in comments * :bug: Fix URL elements dropped in comment input initialization * :bug: Keep empty text elements in parse-urls to preserve cursor anchors The remove filter in parse-urls was stripping empty text elements produced by str/split at URL boundaries. These elements are needed as cursor anchor spans in the contenteditable input, without them ESC keydown and visual layout broke. Signed-off-by: eureka928 --- CHANGES.md | 1 + frontend/src/app/main/ui/comments.cljs | 45 +++++++++++++++++++------- frontend/src/app/main/ui/comments.scss | 6 ++++ 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 026fdc1b8e..b81ad93aa3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,6 +27,7 @@ - Fix warnings for unsupported token $type (by @Dexterity104) [Github #8790](https://github.com/penpot/penpot/issues/8790) - Add per-group add button for typographies (by @eureka928) [Github #5275](https://github.com/penpot/penpot/issues/5275) - Use page name for multi-export ZIP/PDF downloads (by @Dexterity104) [Github #8773](https://github.com/penpot/penpot/issues/8773) +- Make links in comments clickable (by @eureka928) [Github #1602](https://github.com/penpot/penpot/issues/1602) ### :bug: Bugs fixed diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index 8f34c41277..45660b3bb8 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -45,20 +45,34 @@ (def mentions-context (mf/create-context nil)) (def r-mentions-split #"@\[[^\]]*\]\([^\)]*\)") (def r-mentions #"@\[([^\]]*)\]\(([^\)]*)\)") +(def r-url-split #"https?://[^\s\)\]]+[^\s\)\]\.,;:!?]") (def zero-width-space \u200B) -(defn- parse-comment - "Parse a comment into its elements (texts and mentions)" - [comment] - (d/interleave-all - (->> (str/split comment r-mentions-split) - (map #(hash-map :type :text :content %))) +(defn- parse-urls + "Split a text element into text and url sub-elements" + [element] + (if (= (:type element) :text) + (let [text (:content element) + parts (str/split text r-url-split) + urls (re-seq r-url-split text)] + (d/interleave-all + (map #(hash-map :type :text :content %) parts) + (map #(hash-map :type :url :content %) urls))) + [element])) - (->> (re-seq r-mentions comment) - (map (fn [[_ user id]] - {:type :mention - :content user - :data {:id id}}))))) +(defn- parse-comment + "Parse a comment into its elements (texts, mentions and urls)" + [comment] + (->> (d/interleave-all + (->> (str/split comment r-mentions-split) + (map #(hash-map :type :text :content %))) + + (->> (re-seq r-mentions comment) + (map (fn [[_ user id]] + {:type :mention + :content user + :data {:id id}})))) + (mapcat parse-urls))) (defn- parse-nodes "Parse the nodes to format a comment" @@ -146,7 +160,13 @@ [{:keys [content]}] (let [comment-elements (mf/use-memo (mf/deps content) #(parse-comment content))] (for [[idx {:keys [type content]}] (d/enumerate comment-elements)] - (case type + (if (= type :url) + [:a {:key idx + :href content + :target "_blank" + :rel "noopener noreferrer" + :class (stl/css :comment-link)} + content] [:span {:key idx :class (stl/css-case @@ -177,6 +197,7 @@ (doseq [{:keys [type content data]} (parse-comment value)] (case type :text (dom/append-child! node (create-text-node content)) + :url (dom/append-child! node (create-text-node content)) :mention (dom/append-child! node (create-mention-node (:id data) content)) nil))))) diff --git a/frontend/src/app/main/ui/comments.scss b/frontend/src/app/main/ui/comments.scss index 79abfc420f..051a6cd613 100644 --- a/frontend/src/app/main/ui/comments.scss +++ b/frontend/src/app/main/ui/comments.scss @@ -418,6 +418,12 @@ color: var(--color-accent-primary); } +.comment-link { + color: var(--color-accent-primary); + text-decoration: underline; + cursor: pointer; +} + .comments-mentions-empty { font-size: deprecated.$fs-12; color: var(--color-foreground-secondary);