diff --git a/CHANGES.md b/CHANGES.md index a909dc22a..83cce1003 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -84,6 +84,7 @@ - Fix visual problem with stroke cap menu [Taiga #8730](https://tree.taiga.io/project/penpot/issue/8730) - Fix issue when exporting libraries when merging libraries [Taiga #8758](https://tree.taiga.io/project/penpot/issue/8758) - Fix problem with comments max length [Taiga #8778](https://tree.taiga.io/project/penpot/issue/8778) +- Fix copy/paste images in Safari [Taiga #8771](https://tree.taiga.io/project/penpot/issue/8771) ## 2.1.5 diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index dd62ff70d..6806ce89b 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -85,7 +85,8 @@ [beicon.v2.core :as rx] [cljs.spec.alpha :as s] [cuerdas.core :as str] - [potok.v2.core :as ptk])) + [potok.v2.core :as ptk] + [promesa.core :as p])) (def default-workspace-local {:zoom 1}) (log/set-level! :debug) @@ -1551,15 +1552,40 @@ shapes (->> (cfh/selected-with-children objects selected) (keep (d/getf objects)))] - (->> (rx/from shapes) - (rx/merge-map (partial prepare-object objects frame-id)) - (rx/reduce collect-data initial) - (rx/map (partial sort-selected state)) - (rx/map (partial advance-copies state selected)) - (rx/map #(t/encode-str % {:type :json-verbose})) - (rx/map wapi/write-to-clipboard) - (rx/catch on-copy-error) - (rx/ignore))))))))) + ;; The clipboard API doesn't handle well asynchronous calls because it expects to use + ;; the clipboard in an user interaction. If you do an async call the callback is outside + ;; the thread of the UI and so Safari blocks the copying event. + ;; We use the API `ClipboardItem` that allows promises to be passed and so the event + ;; will wait for the promise to resolve and everything should work as expected. + ;; This only works in the current versions of the browsers. + (if (some? (unchecked-get ug/global "ClipboardItem")) + (let [resolve-data-promise + (p/create + (fn [resolve reject] + (->> (rx/from shapes) + (rx/merge-map (partial prepare-object objects frame-id)) + (rx/reduce collect-data initial) + (rx/map (partial sort-selected state)) + (rx/map (partial advance-copies state selected)) + (rx/map #(t/encode-str % {:type :json-verbose})) + (rx/map #(wapi/create-blob % "text/plain")) + (rx/subs! resolve reject))))] + (->> (rx/from (wapi/write-to-clipboard-promise "text/plain" resolve-data-promise)) + (rx/catch on-copy-error) + (rx/ignore))) + + ;; FIXME: this is to support Firefox versions below 116 that don't support `ClipboardItem` + ;; after the version 116 is less common we could remove this. + ;; https://caniuse.com/?search=ClipboardItem + (->> (rx/from shapes) + (rx/merge-map (partial prepare-object objects frame-id)) + (rx/reduce collect-data initial) + (rx/map (partial sort-selected state)) + (rx/map (partial advance-copies state selected)) + (rx/map #(t/encode-str % {:type :json-verbose})) + (rx/map wapi/write-to-clipboard) + (rx/catch on-copy-error) + (rx/ignore)))))))))) (declare ^:private paste-transit) (declare ^:private paste-text) diff --git a/frontend/src/app/util/webapi.cljs b/frontend/src/app/util/webapi.cljs index 9d1ba5c2e..2225a96db 100644 --- a/frontend/src/app/util/webapi.cljs +++ b/frontend/src/app/util/webapi.cljs @@ -103,6 +103,14 @@ (let [cboard (unchecked-get js/navigator "clipboard")] (.writeText ^js cboard data))) +(defn write-to-clipboard-promise + [mimetype promise] + (let [cboard (unchecked-get js/navigator "clipboard") + data (js/ClipboardItem. + (-> (obj/create) + (obj/set! mimetype promise)))] + (.write ^js cboard #js [data]))) + (defn read-from-clipboard [] (try