🐛 Fix dropping caret styles when pasting html text (#12108)

This commit is contained in:
Belén Albeza authored and GitHub committed 2026-10-06 10:29:53 +02:00
1 parent d3ca9a0b83
commit 8d8dff8de4
4 files changed
+45 -8

No files matched your search

@@ -1067,7 +1067,8 @@
(watch [_ state _]
(let [[text content]
(if (v3-html-paste? state)
(when-let [fragment (text-clipboard/html->fragment html)]
(when-let [fragment (some-> (text-clipboard/html->fragment html)
(text-clipboard/without-overrides))]
[(text-paste/fragment->text fragment)
(text-paste/fragment->content fragment (txt/get-default-text-attrs))])
(let [style (deref refs/workspace-clipboard-style)
@@ -107,11 +107,13 @@
(def ^:private max-paste-html-length 1000000)
(defn- clipboard->fragment
"The paste fragment for `data`: its HTML when allowed and it has text, else its plain text."
"The paste fragment for `data`: its HTML when allowed and it has text, else its
plain text. Either way it is unstyled, so it adopts the caret style."
[^js data html-paste?]
(let [html (when html-paste? (.getData data "text/html"))]
(or (when (< 0 (count html) max-paste-html-length)
(text-clipboard/html->fragment html))
(some-> (text-clipboard/html->fragment html)
(text-clipboard/without-overrides)))
(text-clipboard/text->fragment (.getData data "text/plain")))))
(defn- selection-start
@@ -125,18 +127,32 @@
{:para focus-para :offset focus-offset})))
(defn- paste-fragment
"Insert the text of `fragment`, then restyle it with the fragment overrides."
"Insert the text of `fragment`, then restyle it with the fragment overrides or,
when it has none, with the pending caret style."
[fragment]
(let [shape-id (text-editor/text-editor-get-active-shape-id)
start (selection-start)]
start (selection-start)
styled? (text-paste/styled? fragment)
pending? (some? (text-editor/get-pending-caret-styles shape-id))]
(when styled?
(text-editor/clear-pending-caret-styles!))
(text-editor/text-editor-insert-text (text-paste/fragment->text fragment))
(if (and (some? start) (text-paste/styled? fragment))
(cond
(nil? start)
(sync-wasm-text-editor-content!)
styled?
(do
;; Sync first so the cached content stays index-aligned with WASM.
(text-editor/text-editor-sync-content)
(if-let [{:keys [content]} (wasm.api/apply-paste-styles shape-id fragment start)]
(commit-restyled-content shape-id content)
(sync-wasm-text-editor-content!)))
pending?
(sync-with-pending-caret-styles! shape-id start)
:else
(sync-wasm-text-editor-content!))))
(defn- reset-input-node
@@ -315,8 +331,6 @@
(mf/use-fn
(fn [^js event]
(dom/prevent-default event)
;; Pasted text keeps the surrounding style; drop any pending caret style.
(text-editor/clear-pending-caret-styles!)
(when-let [fragment (some-> (.-clipboardData event)
(clipboard->fragment
(features/active-feature? @st/state "text-editor-wasm/v1-html-paste")))]
+11
View File
@@ -244,6 +244,17 @@
(.parseFromString html "text/html")
(document->fragment)))
(defn without-overrides
"`fragment` as one plain run per paragraph, so it takes the style it is pasted
into. External sources keep only their text and paragraphs."
[fragment]
(mapv (fn [{:keys [children] :as paragraph}]
(assoc paragraph :children
(if (seq children)
[{:text (str/join (map :text children)) :attrs {}}]
[])))
fragment))
(defn text->fragment
"The paste fragment for plain text, one paragraph per line, or nil when empty."
[text]
@@ -163,3 +163,14 @@
(t/testing "empty text gives no fragment"
(t/is (nil? (clipboard/text->fragment "")))))
(t/deftest without-overrides
(t/testing "external HTML keeps its text and paragraphs but no emphasis"
(t/is (= [(paragraph (plain-run "Hello bold and italic"))
(paragraph)
(paragraph (plain-run "Second"))]
(clipboard/without-overrides (html->fragment google-docs-html)))))
(t/testing "empty paragraphs stay empty"
(t/is (= [(paragraph)]
(clipboard/without-overrides [(paragraph)])))))