Merge pull request #8684 from penpot/superalex-fix-embedded-editor-pasting-text-2

🐛 Fix embedded editor pasting text
This commit is contained in:
Aitor Moreno
2026-03-20 06:52:39 +01:00
committed by GitHub
3 changed files with 161 additions and 108 deletions

View File

@@ -41,6 +41,7 @@
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.texts :as dwtxt]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.wasm-text :as dwwt]
[app.main.errors]
[app.main.features :as features]
[app.main.refs :as refs]
@@ -970,10 +971,11 @@
text (.-textContent root)
content (tc/dom->cljs root)]
(when (types.text/valid-content? content)
(let [id (uuid/next)
width (max 8 (min (* 7 (count text)) 700))
height 16
(let [id (uuid/next)
width (max 8 (min (* 7 (count text)) 700))
height 16
{:keys [x y]} (calculate-paste-position state)
skip-edition? (features/active-feature? state "text-editor-wasm/v1")
shape {:id id
:type :text
@@ -985,9 +987,14 @@
:grow-type (if (> (count text) 100) :auto-height :auto-width)
:content content}
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dwsh/create-and-add-shape :text x y shape)
(dwu/commit-undo-transaction undo-id))))))))
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id)
(dwsh/create-and-add-shape :text x y shape
(when skip-edition? {:skip-edition? true})))
(if skip-edition?
(rx/of (dwwt/resize-wasm-text-debounce id {:undo-group id
:undo-id undo-id}))
(rx/of (dwu/commit-undo-transaction undo-id))))))))))
(defn- paste-text
[text]
@@ -995,10 +1002,11 @@
(ptk/reify ::paste-text
ptk/WatchEvent
(watch [_ state _]
(let [id (uuid/next)
width (max 8 (min (* 7 (count text)) 700))
height 16
(let [id (uuid/next)
width (max 8 (min (* 7 (count text)) 700))
height 16
{:keys [x y]} (calculate-paste-position state)
skip-edition? (features/active-feature? state "text-editor-wasm/v1")
shape {:id id
:type :text
@@ -1011,9 +1019,14 @@
:content (as-content text)}
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)
(dwsh/create-and-add-shape :text x y shape)
(dwu/commit-undo-transaction undo-id))))))
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id)
(dwsh/create-and-add-shape :text x y shape
(when skip-edition? {:skip-edition? true})))
(if skip-edition?
(rx/of (dwwt/resize-wasm-text-debounce id {:undo-group id
:undo-id undo-id}))
(rx/of (dwu/commit-undo-transaction undo-id))))))))
;; TODO: why not implement it in terms of upload-media-workspace?
(defn- paste-svg-text

View File

@@ -109,7 +109,7 @@
(defn add-shape
([shape]
(add-shape shape {}))
([shape {:keys [no-select? no-update-layout?]}]
([shape {:keys [no-select? no-update-layout? skip-edition?]}]
(cts/check-shape shape)
@@ -139,7 +139,11 @@
(js/Symbol)
parent-type
(cfh/get-shape-type objects (:parent-id shape))]
(cfh/get-shape-type objects (:parent-id shape))
;; Skip edition when using embedded editor (v3) and shape already has content (e.g. paste)
start-edition? (and (cfh/text-shape? shape)
(not (and skip-edition? (some? (:content shape)))))]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id)
@@ -149,7 +153,7 @@
(when-not no-select?
(dws/select-shapes (d/ordered-set (:id shape))))
(dwu/commit-undo-transaction undo-id))
(when (cfh/text-shape? shape)
(when start-edition?
(->> (rx/of (dwe/start-edition-mode (:id shape)))
(rx/observe-on :async)))
@@ -217,40 +221,42 @@
(dwu/commit-undo-transaction undo-id)))))))
(defn create-and-add-shape
[type frame-x frame-y {:keys [width height] :as attrs}]
(ptk/reify ::create-and-add-shape
ptk/WatchEvent
(watch [_ state _]
(let [vbc (dsh/get-viewport-center state)
x (:x attrs (- (:x vbc) (/ width 2)))
y (:y attrs (- (:y vbc) (/ height 2)))
page-id (:current-page-id state)
objects (dsh/lookup-page-objects state page-id)
frame-id (-> (dsh/lookup-page-objects state page-id)
(ctst/top-nested-frame {:x frame-x :y frame-y}))
([type frame-x frame-y attrs]
(create-and-add-shape type frame-x frame-y attrs nil))
([type frame-x frame-y {:keys [width height] :as attrs} {:keys [skip-edition?]}]
(ptk/reify ::create-and-add-shape
ptk/WatchEvent
(watch [_ state _]
(let [vbc (dsh/get-viewport-center state)
x (:x attrs (- (:x vbc) (/ width 2)))
y (:y attrs (- (:y vbc) (/ height 2)))
page-id (:current-page-id state)
objects (dsh/lookup-page-objects state page-id)
frame-id (-> (dsh/lookup-page-objects state page-id)
(ctst/top-nested-frame {:x frame-x :y frame-y}))
selected (dsh/lookup-selected state)
base (cfh/get-base-shape objects selected)
selected (dsh/lookup-selected state)
base (cfh/get-base-shape objects selected)
parent-id (if (or (and (= 1 (count selected))
(cfh/frame-shape? (get objects (first selected))))
(empty? selected))
frame-id
(:parent-id base))
parent-id (if (or (and (= 1 (count selected))
(cfh/frame-shape? (get objects (first selected))))
(empty? selected))
frame-id
(:parent-id base))
;; If the parent-id or the frame-id are component-copies, we need to get the first not copy parent
parent-id (:id (ctn/get-first-valid-parent objects parent-id)) ;; We don't want to change the structure of component copies
frame-id (:id (ctn/get-first-valid-parent objects frame-id))
;; If the parent-id or the frame-id are component-copies, we need to get the first not copy parent
parent-id (:id (ctn/get-first-valid-parent objects parent-id)) ;; We don't want to change the structure of component copies
frame-id (:id (ctn/get-first-valid-parent objects frame-id))
shape (cts/setup-shape
(-> attrs
(assoc :type type)
(assoc :x x)
(assoc :y y)
(assoc :frame-id frame-id)
(assoc :parent-id parent-id)))]
shape (cts/setup-shape
(-> attrs
(assoc :type type)
(assoc :x x)
(assoc :y y)
(assoc :frame-id frame-id)
(assoc :parent-id parent-id)))]
(rx/of (add-shape shape))))))
(rx/of (add-shape shape {:skip-edition? skip-edition?})))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Artboard

View File

@@ -17,6 +17,7 @@
[app.common.types.modifiers :as ctm]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.modifiers :as dwm]
[app.main.data.workspace.undo :as dwu]
[app.render-wasm.api :as wasm.api]
[app.render-wasm.api.fonts :as wasm.fonts]
[beicon.v2.core :as rx]
@@ -76,82 +77,115 @@
(rx/empty))))))
(defn resize-wasm-text-debounce-commit
[]
(ptk/reify ::resize-wasm-text-debounce-commit
ptk/WatchEvent
(watch [_ state _]
(let [ids (get state ::resize-wasm-text-debounce-ids)
objects (dsh/lookup-page-objects state)
([]
(resize-wasm-text-debounce-commit nil nil))
([undo-group undo-id]
(ptk/reify ::resize-wasm-text-debounce-commit
ptk/WatchEvent
(watch [_ state _]
(let [ids (get state ::resize-wasm-text-debounce-ids)
objects (dsh/lookup-page-objects state)
modifiers
(reduce
(fn [modifiers id]
(let [shape (get objects id)]
(cond-> modifiers
(and (some? shape)
(cfh/text-shape? shape)
(not= :fixed (:grow-type shape)))
(merge (resize-wasm-text-modifiers shape)))))
{}
ids)]
(if (not (empty? modifiers))
(rx/of (dwm/apply-wasm-modifiers modifiers))
(rx/empty))))))
modifiers
(reduce
(fn [modifiers id]
(let [shape (get objects id)]
(cond-> modifiers
(and (some? shape)
(cfh/text-shape? shape)
(not= :fixed (:grow-type shape)))
(merge (resize-wasm-text-modifiers shape)))))
{}
ids)
;; When undo-id is present, extend the current undo transaction instead of
;; creating a new one, and commit it after the resize (single undo action).
extend-tx? (some? undo-id)
apply-opts (cond-> {}
(some? undo-group) (assoc :undo-group undo-group)
extend-tx? (assoc :undo-transation? false))]
(cond
(not (empty? modifiers))
(if extend-tx?
(rx/concat
(rx/of (dwm/apply-wasm-modifiers modifiers apply-opts))
(rx/of (dwu/commit-undo-transaction undo-id)))
(rx/of (dwm/apply-wasm-modifiers modifiers apply-opts)))
extend-tx?
;; No resize needed (e.g. :fixed grow-type) but we must commit the add
(rx/of (dwu/commit-undo-transaction undo-id))
:else
(rx/empty)))))))
;; This event will debounce the resize events so, if there are many, they
;; are processed at the same time and not one-by-one. This will improve
;; performance because it's better to make only one layout calculation instead
;; of (potentialy) hundreds.
(defn resize-wasm-text-debounce-inner
[id]
(let [cur-event (js/Symbol)]
(ptk/reify ::resize-wasm-text-debounce-inner
ptk/UpdateEvent
(update [_ state]
(-> state
(update ::resize-wasm-text-debounce-ids (fnil conj []) id)
(cond-> (nil? (::resize-wasm-text-debounce-event state))
(assoc ::resize-wasm-text-debounce-event cur-event))))
([id]
(resize-wasm-text-debounce-inner id nil))
([id {:keys [undo-group undo-id]}]
(let [cur-event (js/Symbol)]
(ptk/reify ::resize-wasm-text-debounce-inner
ptk/UpdateEvent
(update [_ state]
(-> state
(update ::resize-wasm-text-debounce-ids (fnil conj []) id)
(cond-> (nil? (::resize-wasm-text-debounce-event state))
(assoc ::resize-wasm-text-debounce-event cur-event))))
ptk/WatchEvent
(watch [_ state stream]
(if (= (::resize-wasm-text-debounce-event state) cur-event)
(let [stopper (->> stream (rx/filter (ptk/type? :app.main.data.workspace/finalize)))]
(rx/concat
(rx/merge
(->> stream
(rx/filter (ptk/type? ::resize-wasm-text-debounce-inner))
(rx/debounce 40)
(rx/take 1)
(rx/map #(resize-wasm-text-debounce-commit))
(rx/take-until stopper))
(rx/of (resize-wasm-text-debounce-inner id)))
(rx/of #(dissoc %
::resize-wasm-text-debounce-ids
::resize-wasm-text-debounce-event))))
(rx/empty))))))
ptk/WatchEvent
(watch [_ state stream]
(if (= (::resize-wasm-text-debounce-event state) cur-event)
(let [stopper (->> stream (rx/filter (ptk/type? :app.main.data.workspace/finalize)))]
(rx/concat
(rx/merge
(->> stream
(rx/filter (ptk/type? ::resize-wasm-text-debounce-inner))
(rx/debounce 40)
(rx/take 1)
(rx/map (fn [evt]
(resize-wasm-text-debounce-commit
(some-> evt meta :undo-group)
(some-> evt meta :undo-id))))
(rx/take-until stopper))
(rx/of (with-meta
(resize-wasm-text-debounce-inner id)
{:undo-group undo-group :undo-id undo-id})))
(rx/of #(dissoc %
::resize-wasm-text-debounce-ids
::resize-wasm-text-debounce-event))))
(rx/empty)))))))
(defn resize-wasm-text-debounce
[id]
(ptk/reify ::resize-wasm-text-debounce
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (dsh/lookup-page-objects state page-id)
content (dm/get-in objects [id :content])
fonts (wasm.fonts/get-content-fonts content)
([id]
(resize-wasm-text-debounce id nil))
([id {:keys [undo-group undo-id] :as opts}]
(ptk/reify ::resize-wasm-text-debounce
ptk/WatchEvent
(watch [_ state _]
(let [page-id (:current-page-id state)
objects (dsh/lookup-page-objects state page-id)
content (dm/get-in objects [id :content])
fonts (wasm.fonts/get-content-fonts content)
fonts-loaded?
(->> fonts
(every?
(fn [font]
(let [font-data (wasm.fonts/make-font-data font)]
(wasm.fonts/font-stored? font-data (:emoji? font-data))))))]
fonts-loaded?
(->> fonts
(every?
(fn [font]
(let [font-data (wasm.fonts/make-font-data font)]
(wasm.fonts/font-stored? font-data (:emoji? font-data))))))]
(if (not fonts-loaded?)
(->> (rx/of (resize-wasm-text-debounce id))
(rx/delay 20))
(rx/of (resize-wasm-text-debounce-inner id)))))))
(if (not fonts-loaded?)
(->> (rx/of (resize-wasm-text-debounce id opts))
(rx/delay 20))
(let [pass-opts (when (or (some? undo-group) (some? undo-id))
(cond-> {}
(some? undo-group) (assoc :undo-group undo-group)
(some? undo-id) (assoc :undo-id undo-id)))]
(rx/of (resize-wasm-text-debounce-inner id pass-opts)))))))))
(defn resize-wasm-text-all
"Resize all text shapes (auto-width/auto-height) from a collection of ids."