diff --git a/CHANGES.md b/CHANGES.md index 90ac9c8962..a55bcfed02 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -40,6 +40,7 @@ - Fix 'not ISeqable' error when entering float values in layout item and opacity inputs [Github #8569](https://github.com/penpot/penpot/pull/8569) - Fix crash in select component when options vector is empty [Github #8578](https://github.com/penpot/penpot/pull/8578) - Fix scroll on colorpicker [Taiga #13623](https://tree.taiga.io/project/penpot/issue/13623) +- Fix crash when pasting non-map transit clipboard data [Github #8580](https://github.com/penpot/penpot/pull/8580) ## 2.13.3 diff --git a/frontend/src/app/main/data/workspace/clipboard.cljs b/frontend/src/app/main/data/workspace/clipboard.cljs index 2692eef103..4bd8190895 100644 --- a/frontend/src/app/main/data/workspace/clipboard.cljs +++ b/frontend/src/app/main/data/workspace/clipboard.cljs @@ -258,33 +258,39 @@ #js {:decodeTransit t/decode-str :allowHTMLPaste (features/active-feature? @st/state "text-editor/v2-html-paste")}) -(defn create-paste-from-blob +(defn- create-paste-from-blob [in-viewport?] (fn [blob] - (let [type (.-type blob) - result (cond - (= type "image/svg+xml") - (->> (rx/from (.text blob)) - (rx/map paste-svg-text)) + (let [type (.-type blob)] + (cond + (= type "image/svg+xml") + (->> (rx/from (.text blob)) + (rx/map paste-svg-text)) - (some #(= type %) clipboard/image-types) - (rx/of (paste-image blob)) + (some #(= type %) clipboard/image-types) + (rx/of (paste-image blob)) - (= type "text/html") - (->> (rx/from (.text blob)) - (rx/map paste-html-text)) + (= type "text/html") + (->> (rx/from (.text blob)) + (rx/map paste-html-text)) - (= type "application/transit+json") - (->> (rx/from (.text blob)) - (rx/map (fn [text] - (let [transit-data (t/decode-str text)] - (assoc transit-data :in-viewport in-viewport?)))) - (rx/map paste-transit-shapes)) + (= type "application/transit+json") + (->> (rx/from (.text blob)) + (rx/map t/decode-str) + (rx/filter map?) + (rx/map + (fn [pdata] + (assoc pdata :in-viewport in-viewport?))) + (rx/mapcat + (fn [pdata] + (case (:type pdata) + :copied-props (rx/of (paste-transit-props pdata)) + :copied-shapes (rx/of (paste-transit-shapes pdata)) + (rx/empty))))) - :else - (->> (rx/from (.text blob)) - (rx/map paste-text)))] - result))) + :else + (->> (rx/from (.text blob)) + (rx/map paste-text)))))) (def default-paste-from-blob (create-paste-from-blob false)) diff --git a/frontend/src/app/util/clipboard.js b/frontend/src/app/util/clipboard.js index 742183d0cb..294652666a 100644 --- a/frontend/src/app/util/clipboard.js +++ b/frontend/src/app/util/clipboard.js @@ -30,6 +30,15 @@ const exclusiveTypes = [ * @property {boolean} [allowHTMLPaste] */ +const looksLikeJSON = (str) => { + if (typeof str !== 'string') return false; + const trimmed = str.trim(); + return ( + (trimmed.startsWith('{') && trimmed.endsWith('}')) || + (trimmed.startsWith('[') && trimmed.endsWith(']')) + ); +}; + /** * * @param {string} text @@ -39,13 +48,14 @@ const exclusiveTypes = [ */ function parseText(text, options) { options = options || {}; + const decodeTransit = options["decodeTransit"]; - if (decodeTransit) { + if (decodeTransit && looksLikeJSON(text)) { try { decodeTransit(text); return new Blob([text], { type: "application/transit+json" }); } catch (_error) { - // NOOP + return new Blob([text], { type: "text/plain" }); } }