From dd6b521bc72964ee492b1a2d51f2e92f85bf4dd6 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 21 Aug 2026 15:15:40 +0200 Subject: [PATCH] :bug: Fix WASM text selection copy to Windows apps (#11305) Write text/html alongside text/plain on copy/cut so Windows apps that prefer CF_HTML do not paste the empty contenteditable newline. --- .../ui/workspace/shapes/text/v3_editor.cljs | 19 ++++++++++++++----- frontend/src/app/util/clipboard.cljs | 12 ++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs index 58377e9c1c..2563463d17 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/v3_editor.cljs @@ -19,6 +19,7 @@ [app.main.ui.css-cursors :as cur] [app.render-wasm.api :as wasm.api] [app.render-wasm.text-editor :as text-editor] + [app.util.clipboard :as clipboard] [app.util.dom :as dom] [app.util.keyboard :as kbd] [cuerdas.core :as str] @@ -267,8 +268,13 @@ (when (text-editor/text-editor-has-focus?) (dom/prevent-default event) (when (text-editor/text-editor-has-selection?) - (let [text (text-editor/text-editor-export-selection)] - (.setData (.-clipboardData event) "text/plain" text)))))) + (let [text (or (text-editor/text-editor-export-selection) "") + html (clipboard/plain-text->html text) + data (.-clipboardData event)] + ;; text/html matters on Windows: many apps prefer CF_HTML, and + ;; without it they can pick up the empty contenteditable `
`. + (.setData data "text/plain" text) + (.setData data "text/html" html)))))) on-cut (mf/use-fn @@ -276,9 +282,12 @@ (when (text-editor/text-editor-has-focus?) (dom/prevent-default event) (when (text-editor/text-editor-has-selection?) - (let [text (text-editor/text-editor-export-selection)] - (.setData (.-clipboardData event) "text/plain" (or text "")) - (when (and text (seq text)) + (let [text (or (text-editor/text-editor-export-selection) "") + html (clipboard/plain-text->html text) + data (.-clipboardData event)] + (.setData data "text/plain" text) + (.setData data "text/html" html) + (when (seq text) (text-editor/text-editor-delete-backward) (sync-wasm-text-editor-content!) (wasm.api/request-render-preserving-target "text-cut")))) diff --git a/frontend/src/app/util/clipboard.cljs b/frontend/src/app/util/clipboard.cljs index 3bc1f1bba3..c2c92c9a76 100644 --- a/frontend/src/app/util/clipboard.cljs +++ b/frontend/src/app/util/clipboard.cljs @@ -22,6 +22,18 @@ #js {:decodeTransit t/decode-str :allowHTMLPaste false}) +(defn plain-text->html + "Build a minimal text/html clipboard payload from plain text. + + Windows apps often prefer CF_HTML over CF_UNICODETEXT; writing only + text/plain from a contenteditable copy handler can leave them with the + editor surface's empty/`
` fallback (a lone newline)." + [text] + (let [escaped (-> (or text "") + (dom/escape-html) + (str/replace "\n" "
"))] + (str "" escaped))) + (defn- from-data-transfer "Get clipboard stream from DataTransfer instance" ([data-transfer]