mirror of
https://github.com/penpot/penpot.git
synced 2026-08-06 12:58:55 +00:00
✨ Make links in comments clickable (#8894)
* ✨ 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 * 📚 Add changelog entry for clickable links in comments * 🐛 Fix URL elements dropped in comment input initialization * 🐛 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 <meobius123@gmail.com>
This commit is contained in:
parent
e7e5a19db7
commit
8dccb2a427
@ -27,6 +27,7 @@
|
|||||||
- Fix warnings for unsupported token $type (by @Dexterity104) [Github #8790](https://github.com/penpot/penpot/issues/8790)
|
- 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)
|
- 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)
|
- 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
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
|
|||||||
@ -45,20 +45,34 @@
|
|||||||
(def mentions-context (mf/create-context nil))
|
(def mentions-context (mf/create-context nil))
|
||||||
(def r-mentions-split #"@\[[^\]]*\]\([^\)]*\)")
|
(def r-mentions-split #"@\[[^\]]*\]\([^\)]*\)")
|
||||||
(def r-mentions #"@\[([^\]]*)\]\(([^\)]*)\)")
|
(def r-mentions #"@\[([^\]]*)\]\(([^\)]*)\)")
|
||||||
|
(def r-url-split #"https?://[^\s\)\]]+[^\s\)\]\.,;:!?]")
|
||||||
(def zero-width-space \u200B)
|
(def zero-width-space \u200B)
|
||||||
|
|
||||||
(defn- parse-comment
|
(defn- parse-urls
|
||||||
"Parse a comment into its elements (texts and mentions)"
|
"Split a text element into text and url sub-elements"
|
||||||
[comment]
|
[element]
|
||||||
(d/interleave-all
|
(if (= (:type element) :text)
|
||||||
(->> (str/split comment r-mentions-split)
|
(let [text (:content element)
|
||||||
(map #(hash-map :type :text :content %)))
|
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)
|
(defn- parse-comment
|
||||||
(map (fn [[_ user id]]
|
"Parse a comment into its elements (texts, mentions and urls)"
|
||||||
{:type :mention
|
[comment]
|
||||||
:content user
|
(->> (d/interleave-all
|
||||||
:data {:id id}})))))
|
(->> (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
|
(defn- parse-nodes
|
||||||
"Parse the nodes to format a comment"
|
"Parse the nodes to format a comment"
|
||||||
@ -146,7 +160,13 @@
|
|||||||
[{:keys [content]}]
|
[{:keys [content]}]
|
||||||
(let [comment-elements (mf/use-memo (mf/deps content) #(parse-comment content))]
|
(let [comment-elements (mf/use-memo (mf/deps content) #(parse-comment content))]
|
||||||
(for [[idx {:keys [type content]}] (d/enumerate comment-elements)]
|
(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
|
[:span
|
||||||
{:key idx
|
{:key idx
|
||||||
:class (stl/css-case
|
:class (stl/css-case
|
||||||
@ -177,6 +197,7 @@
|
|||||||
(doseq [{:keys [type content data]} (parse-comment value)]
|
(doseq [{:keys [type content data]} (parse-comment value)]
|
||||||
(case type
|
(case type
|
||||||
:text (dom/append-child! node (create-text-node content))
|
: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))
|
:mention (dom/append-child! node (create-mention-node (:id data) content))
|
||||||
nil)))))
|
nil)))))
|
||||||
|
|
||||||
|
|||||||
@ -418,6 +418,12 @@
|
|||||||
color: var(--color-accent-primary);
|
color: var(--color-accent-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.comment-link {
|
||||||
|
color: var(--color-accent-primary);
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.comments-mentions-empty {
|
.comments-mentions-empty {
|
||||||
font-size: deprecated.$fs-12;
|
font-size: deprecated.$fs-12;
|
||||||
color: var(--color-foreground-secondary);
|
color: var(--color-foreground-secondary);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user