Merge pull request #5895 from penpot/niwinz-bugfix-comments

🐛 Fix unexpected exception on clicking empty area on creating comment
This commit is contained in:
luisδμ 2025-02-19 11:29:21 +01:00 committed by GitHub
commit 4ac52c138c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 121 additions and 114 deletions

View file

@ -90,22 +90,29 @@
(dom/set-data! "fullname" fullname) (dom/set-data! "fullname" fullname)
(obj/set! "textContent" fullname))) (obj/set! "textContent" fullname)))
(defn- current-text-node*
"Retrieves the text node and the offset that the cursor is positioned on"
[node anchor-node]
(when (.contains node anchor-node)
(let [span-node (if (instance? js/Text anchor-node)
(dom/get-parent anchor-node)
anchor-node)
container (dom/get-parent span-node)]
(when (= node container)
span-node))))
(defn- current-text-node (defn- current-text-node
"Retrieves the text node and the offset that the cursor is positioned on" "Retrieves the text node and the offset that the cursor is positioned on"
[node] [node]
(assert (some? node) "expected valid node")
(let [selection (wapi/get-selection) (when-let [selection (wapi/get-selection)]
range (wapi/get-range selection 0) (let [range (wapi/get-range selection 0)
anchor-node (wapi/range-start-container range) anchor-node (wapi/range-start-container range)
anchor-offset (wapi/range-start-offset range)] offset (wapi/range-start-offset range)
(when (and node (.contains node anchor-node)) span-node (current-text-node* node anchor-node)]
(let [span-node (when span-node
(if (instance? js/Text anchor-node) [span-node offset]))))
(dom/get-parent anchor-node)
anchor-node)
container (dom/get-parent span-node)]
(when (= node container)
[span-node anchor-offset])))))
(defn- absolute-offset (defn- absolute-offset
[node child offset] [node child offset]
@ -156,7 +163,8 @@
mentions-s (mf/use-ctx mentions-context) mentions-s (mf/use-ctx mentions-context)
cur-mention (mf/use-var nil) cur-mention (mf/use-var nil)
prev-selection (mf/use-var nil) prev-selection-ref
(mf/use-ref)
init-input init-input
(mf/use-fn (mf/use-fn
@ -203,58 +211,59 @@
handle-select handle-select
(mf/use-fn (mf/use-fn
(fn [] (fn []
(let [node (mf/ref-val local-ref) (when-let [node (mf/ref-val local-ref)]
selection (wapi/get-selection) (when-let [selection (wapi/get-selection)]
range (wapi/get-range selection 0) (let [range (wapi/get-range selection 0)
anchor-node (wapi/range-start-container range)] anchor-node (wapi/range-start-container range)
(when (and (= node anchor-node) (.-collapsed range)) offset (wapi/range-start-offset range)]
(wapi/set-cursor-after! anchor-node)))
(let [node (mf/ref-val local-ref) (when (and (= node anchor-node) (.-collapsed ^js range))
[span-node offset] (current-text-node node) (wapi/set-cursor-after! anchor-node))
[prev-span prev-offset] @prev-selection]
(reset! prev-selection #js [span-node offset]) (when-let [span-node (current-text-node* node anchor-node)]
(let [[prev-span prev-offset]
(mf/ref-val prev-selection-ref)
(when (= (dom/get-data span-node "type") "mention") node-text
(let [from-offset (absolute-offset node prev-span prev-offset) (subs (dom/get-text span-node) 0 offset)
to-offset (absolute-offset node span-node offset)
[_ prev next] current-at-symbol
(->> node (str/last-index-of (subs node-text 0 offset) "@")
(dom/seq-nodes)
(d/with-prev-next)
(filter (fn [[elem _ _]] (= elem span-node)))
(first))]
(if (> from-offset to-offset) mention-text
(wapi/set-cursor-after! prev) (subs node-text current-at-symbol)
(wapi/set-cursor-before! next))))
(when span-node at-symbol-inside-word?
(let [node-text (subs (dom/get-text span-node) 0 offset) (and (> current-at-symbol 0)
(str/word? (str/slice node-text (- current-at-symbol 1) current-at-symbol)))]
current-at-symbol (mf/set-ref-val! prev-selection-ref #js [span-node offset])
(str/last-index-of (subs node-text 0 offset) "@")
mention-text (when (= (dom/get-data span-node "type") "mention")
(subs node-text current-at-symbol) (let [from-offset (absolute-offset node prev-span prev-offset)
to-offset (absolute-offset node span-node offset)
at-symbol-inside-word? [_ prev next]
(and (> current-at-symbol 0) (->> node
(str/word? (str/slice node-text (- current-at-symbol 1) current-at-symbol)))] (dom/seq-nodes)
(d/with-prev-next)
(filter (fn [[elem _ _]] (= elem span-node)))
(first))]
(if (> from-offset to-offset)
(wapi/set-cursor-after! prev)
(wapi/set-cursor-before! next))))
(if (and (not at-symbol-inside-word?) (if (and (not at-symbol-inside-word?)
(re-matches #"@\w*" mention-text)) (re-matches #"@\w*" mention-text))
(do (do
(reset! cur-mention mention-text) (reset! cur-mention mention-text)
(rx/push! mentions-s {:type :display-mentions}) (rx/push! mentions-s {:type :display-mentions})
(let [mention (subs mention-text 1)] (let [mention (subs mention-text 1)]
(when (d/not-empty? mention) (when (d/not-empty? mention)
(rx/push! mentions-s {:type :filter-mentions :data mention})))) (rx/push! mentions-s {:type :filter-mentions :data mention}))))
(do (do
(reset! cur-mention nil) (reset! cur-mention nil)
(rx/push! mentions-s {:type :hide-mentions})))))))) (rx/push! mentions-s {:type :hide-mentions}))))))))))
handle-focus handle-focus
(mf/use-fn (mf/use-fn
@ -279,9 +288,8 @@
(mf/use-fn (mf/use-fn
(mf/deps on-change) (mf/deps on-change)
(fn [data] (fn [data]
(let [node (mf/ref-val local-ref) (when-let [node (mf/ref-val local-ref)]
[span-node offset] (current-text-node node)] (when-let [[span-node offset] (current-text-node node)]
(when span-node
(let [node-text (let [node-text
(dom/get-text span-node) (dom/get-text span-node)
@ -314,8 +322,8 @@
handle-insert-at-symbol handle-insert-at-symbol
(mf/use-fn (mf/use-fn
(fn [] (fn []
(let [node (mf/ref-val local-ref) [span-node] (current-text-node node)] (when-let [node (mf/ref-val local-ref)]
(when span-node (when-let [[span-node] (current-text-node node)]
(let [node-text (dom/get-text span-node) (let [node-text (dom/get-text span-node)
at-symbol (if (blank-content? node-text) "@" " @")] at-symbol (if (blank-content? node-text) "@" " @")]
@ -327,66 +335,62 @@
(mf/deps on-esc on-ctrl-enter handle-select handle-input) (mf/deps on-esc on-ctrl-enter handle-select handle-input)
(fn [event] (fn [event]
(handle-select event) (handle-select event)
(when-let [node (mf/ref-val local-ref)]
(when-let [[span-node offset] (current-text-node node)]
(cond
(and @cur-mention (kbd/enter? event))
(do (dom/prevent-default event)
(dom/stop-propagation event)
(rx/push! mentions-s {:type :insert-selected-mention}))
(let [node (mf/ref-val local-ref) (and @cur-mention (kbd/down-arrow? event))
[span-node offset] (current-text-node node)] (do (dom/prevent-default event)
(dom/stop-propagation event)
(rx/push! mentions-s {:type :insert-next-mention}))
(cond (and @cur-mention (kbd/up-arrow? event))
(and @cur-mention (kbd/enter? event)) (do (dom/prevent-default event)
(do (dom/prevent-default event) (dom/stop-propagation event)
(dom/stop-propagation event) (rx/push! mentions-s {:type :insert-prev-mention}))
(rx/push! mentions-s {:type :insert-selected-mention}))
(and @cur-mention (kbd/down-arrow? event)) (and @cur-mention (kbd/esc? event))
(do (dom/prevent-default event) (do (dom/prevent-default event)
(dom/stop-propagation event) (dom/stop-propagation event)
(rx/push! mentions-s {:type :insert-next-mention})) (rx/push! mentions-s {:type :hide-mentions}))
(and @cur-mention (kbd/up-arrow? event)) (and (kbd/esc? event) (fn? on-esc))
(do (dom/prevent-default event) (on-esc event)
(dom/stop-propagation event)
(rx/push! mentions-s {:type :insert-prev-mention}))
(and @cur-mention (kbd/esc? event)) (and (kbd/mod? event) (kbd/enter? event) (fn? on-ctrl-enter))
(do (dom/prevent-default event) (on-ctrl-enter event)
(dom/stop-propagation event)
(rx/push! mentions-s {:type :hide-mentions}))
(and (kbd/esc? event) (fn? on-esc)) (kbd/enter? event)
(on-esc event) (let [sel (wapi/get-selection)
range (.getRangeAt sel 0)]
(and (kbd/mod? event) (kbd/enter? event) (fn? on-ctrl-enter))
(on-ctrl-enter event)
(kbd/enter? event)
(let [sel (wapi/get-selection)
range (.getRangeAt sel 0)]
(dom/prevent-default event)
(dom/stop-propagation event)
(let [[span-node offset] (current-text-node node)]
(.deleteContents range)
(handle-input)
(when span-node
(let [txt (.-textContent span-node)]
(dom/set-html! span-node (dm/str (subs txt 0 offset) "\n" zero-width-space (subs txt offset)))
(wapi/set-cursor! span-node (inc offset))
(handle-input)))))
(kbd/backspace? event)
(let [prev-node (get-prev-node node span-node)]
(when (and (some? prev-node)
(= "mention" (dom/get-data prev-node "type"))
(= offset 1))
(dom/prevent-default event) (dom/prevent-default event)
(dom/stop-propagation event) (dom/stop-propagation event)
(.remove prev-node)))))))] (let [[span-node offset] (current-text-node node)]
(.deleteContents range)
(handle-input)
(mf/use-layout-effect (when span-node
(mf/deps autofocus) (let [txt (.-textContent span-node)]
(fn [] (dom/set-html! span-node (dm/str (subs txt 0 offset) "\n" zero-width-space (subs txt offset)))
(when autofocus (wapi/set-cursor! span-node (inc offset))
(dom/focus! (mf/ref-val local-ref))))) (handle-input)))))
(kbd/backspace? event)
(let [prev-node (get-prev-node node span-node)]
(when (and (some? prev-node)
(= "mention" (dom/get-data prev-node "type"))
(= offset 1))
(dom/prevent-default event)
(dom/stop-propagation event)
(.remove prev-node))))))))]
(mf/with-layout-effect [autofocus]
(when ^boolean autofocus
(dom/focus! (mf/ref-val local-ref))))
;; Creates the handlers for selection ;; Creates the handlers for selection
(mf/with-effect [handle-select] (mf/with-effect [handle-select]
@ -410,12 +414,12 @@
;; Auto resize input to display the comment ;; Auto resize input to display the comment
(mf/with-layout-effect nil (mf/with-layout-effect nil
(let [^js node (mf/ref-val local-ref)] (when-let [^js node (mf/ref-val local-ref)]
(set! (.-height (.-style node)) "0") (set! (.-height (.-style node)) "0")
(set! (.-height (.-style node)) (str (+ 2 (.-scrollHeight node)) "px")))) (set! (.-height (.-style node)) (str (+ 2 (.-scrollHeight node)) "px"))))
(mf/with-effect [value prev-value] (mf/with-effect [value prev-value]
(let [node (mf/ref-val local-ref)] (when-let [node (mf/ref-val local-ref)]
(cond (cond
(and (d/not-empty? prev-value) (empty? value)) (and (d/not-empty? prev-value) (empty? value))
(do (dom/set-html! node "") (do (dom/set-html! node "")

View file

@ -282,9 +282,12 @@
(.selectAllChildren selection node)) (.selectAllChildren selection node))
(defn get-selection (defn get-selection
"Only returns valid selection"
[] []
(when-let [document globals/document] (when-let [document globals/document]
(.getSelection document))) (let [selection (.getSelection document)]
(when (not= (.-type selection) "None")
selection))))
(defn get-anchor-node (defn get-anchor-node
[^js selection] [^js selection]