mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
🐛 Fix pasting application/transit+json (#7812)
This commit is contained in:
@@ -71,6 +71,7 @@ example. It's still usable as before, we just removed the example.
|
|||||||
- Add new shape validation mechanism for shapes [Github #7696](https://github.com/penpot/penpot/pull/7696)
|
- Add new shape validation mechanism for shapes [Github #7696](https://github.com/penpot/penpot/pull/7696)
|
||||||
- Apply color tokens from sidebar [Taiga #11353](https://tree.taiga.io/project/penpot/us/11353)
|
- Apply color tokens from sidebar [Taiga #11353](https://tree.taiga.io/project/penpot/us/11353)
|
||||||
- Display tokens in the inspect tab [Taiga #9313](https://tree.taiga.io/project/penpot/us/9313)
|
- Display tokens in the inspect tab [Taiga #9313](https://tree.taiga.io/project/penpot/us/9313)
|
||||||
|
- Refactor clipboard behavior to assess some minor inconsistencies and make pasting binary data faster. [Taiga #12571](https://tree.taiga.io/project/penpot/task/12571)
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
@@ -85,6 +86,7 @@ example. It's still usable as before, we just removed the example.
|
|||||||
- Fix shortcut conflict in text editor (increase/decrease font size vs word selection)
|
- Fix shortcut conflict in text editor (increase/decrease font size vs word selection)
|
||||||
- Fix problem with plugins generating code for pages different than current one [Taiga #12312](https://tree.taiga.io/project/penpot/issue/12312)
|
- Fix problem with plugins generating code for pages different than current one [Taiga #12312](https://tree.taiga.io/project/penpot/issue/12312)
|
||||||
- Fix input confirmation behavior is not uniform [Taiga #12294](https://tree.taiga.io/project/penpot/issue/12294)
|
- Fix input confirmation behavior is not uniform [Taiga #12294](https://tree.taiga.io/project/penpot/issue/12294)
|
||||||
|
- Fix copy/pasting application/transit+json [Taiga #12721](https://tree.taiga.io/project/penpot/issue/12721)
|
||||||
|
|
||||||
## 2.11.1
|
## 2.11.1
|
||||||
|
|
||||||
|
|||||||
@@ -254,6 +254,10 @@
|
|||||||
(declare ^:private paste-svg-text)
|
(declare ^:private paste-svg-text)
|
||||||
(declare ^:private paste-shapes)
|
(declare ^:private paste-shapes)
|
||||||
|
|
||||||
|
(def ^:private default-options
|
||||||
|
#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?]
|
[in-viewport?]
|
||||||
(fn [blob]
|
(fn [blob]
|
||||||
@@ -290,7 +294,7 @@
|
|||||||
(ptk/reify ::paste-from-clipboard
|
(ptk/reify ::paste-from-clipboard
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ _ _]
|
||||||
(->> (clipboard/from-navigator)
|
(->> (clipboard/from-navigator default-options)
|
||||||
(rx/mapcat default-paste-from-blob)
|
(rx/mapcat default-paste-from-blob)
|
||||||
(rx/take 1)))))
|
(rx/take 1)))))
|
||||||
|
|
||||||
@@ -308,7 +312,7 @@
|
|||||||
;; we forbid that scenario so the default behaviour is executed
|
;; we forbid that scenario so the default behaviour is executed
|
||||||
(if is-editing?
|
(if is-editing?
|
||||||
(rx/empty)
|
(rx/empty)
|
||||||
(->> (clipboard/from-synthetic-clipboard-event event)
|
(->> (clipboard/from-synthetic-clipboard-event event default-options)
|
||||||
(rx/mapcat (create-paste-from-blob in-viewport?))))))))
|
(rx/mapcat (create-paste-from-blob in-viewport?))))))))
|
||||||
|
|
||||||
(defn copy-selected-svg
|
(defn copy-selected-svg
|
||||||
@@ -478,7 +482,7 @@
|
|||||||
(js/console.error "Clipboard error:" cause))
|
(js/console.error "Clipboard error:" cause))
|
||||||
(rx/empty)))]
|
(rx/empty)))]
|
||||||
|
|
||||||
(->> (clipboard/from-navigator)
|
(->> (clipboard/from-navigator default-options)
|
||||||
(rx/mapcat #(.text %))
|
(rx/mapcat #(.text %))
|
||||||
(rx/map decode-entry)
|
(rx/map decode-entry)
|
||||||
(rx/take 1)
|
(rx/take 1)
|
||||||
|
|||||||
@@ -18,28 +18,35 @@
|
|||||||
"image/svg+xml"])
|
"image/svg+xml"])
|
||||||
|
|
||||||
(def ^:private default-options
|
(def ^:private default-options
|
||||||
#js {:decodeTransit t/decode-str})
|
#js {:decodeTransit t/decode-str
|
||||||
|
:allowHTMLPaste false})
|
||||||
|
|
||||||
(defn- from-data-transfer
|
(defn- from-data-transfer
|
||||||
"Get clipboard stream from DataTransfer instance"
|
"Get clipboard stream from DataTransfer instance"
|
||||||
[data-transfer]
|
([data-transfer]
|
||||||
(->> (rx/from (impl/fromDataTransfer data-transfer default-options))
|
(from-data-transfer data-transfer default-options))
|
||||||
(rx/mapcat #(rx/from %))))
|
([data-transfer options]
|
||||||
|
(->> (rx/from (impl/fromDataTransfer data-transfer options))
|
||||||
|
(rx/mapcat #(rx/from %)))))
|
||||||
|
|
||||||
(defn from-navigator
|
(defn from-navigator
|
||||||
[]
|
([]
|
||||||
(->> (rx/from (impl/fromNavigator default-options))
|
(from-navigator default-options))
|
||||||
(rx/mapcat #(rx/from %))))
|
([options]
|
||||||
|
(->> (rx/from (impl/fromNavigator options))
|
||||||
|
(rx/mapcat #(rx/from %)))))
|
||||||
|
|
||||||
(defn from-clipboard-event
|
(defn from-clipboard-event
|
||||||
"Get clipboard stream from clipboard event"
|
"Get clipboard stream from clipboard event"
|
||||||
[event]
|
([event]
|
||||||
|
(from-clipboard-event event default-options))
|
||||||
|
([event options]
|
||||||
(let [cdata (.-clipboardData ^js event)]
|
(let [cdata (.-clipboardData ^js event)]
|
||||||
(from-data-transfer cdata)))
|
(from-data-transfer cdata options))))
|
||||||
|
|
||||||
(defn from-synthetic-clipboard-event
|
(defn from-synthetic-clipboard-event
|
||||||
"Get clipboard stream from syntetic clipboard event"
|
"Get clipboard stream from syntetic clipboard event"
|
||||||
[event]
|
([event options]
|
||||||
(let [target
|
(let [target
|
||||||
(dom/get-target event)
|
(dom/get-target event)
|
||||||
|
|
||||||
@@ -53,12 +60,14 @@
|
|||||||
(when-not (or content-editable? is-input?)
|
(when-not (or content-editable? is-input?)
|
||||||
(-> event
|
(-> event
|
||||||
(dom/event->browser-event)
|
(dom/event->browser-event)
|
||||||
(from-clipboard-event)))))
|
(from-clipboard-event options))))))
|
||||||
|
|
||||||
(defn from-drop-event
|
(defn from-drop-event
|
||||||
"Get clipboard stream from drop event"
|
"Get clipboard stream from drop event"
|
||||||
[event]
|
([event]
|
||||||
(from-data-transfer (.-dataTransfer ^js event)))
|
(from-drop-event event default-options))
|
||||||
|
([event options]
|
||||||
|
(from-data-transfer (.-dataTransfer ^js event) options)))
|
||||||
|
|
||||||
;; FIXME: rename to `write-text`
|
;; FIXME: rename to `write-text`
|
||||||
(defn to-clipboard
|
(defn to-clipboard
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const exclusiveTypes = [
|
|||||||
/**
|
/**
|
||||||
* @typedef {Object} ClipboardSettings
|
* @typedef {Object} ClipboardSettings
|
||||||
* @property {Function} [decodeTransit]
|
* @property {Function} [decodeTransit]
|
||||||
|
* @property {boolean} [allowHTMLPaste]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,9 +39,7 @@ const exclusiveTypes = [
|
|||||||
*/
|
*/
|
||||||
function parseText(text, options) {
|
function parseText(text, options) {
|
||||||
options = options || {};
|
options = options || {};
|
||||||
|
|
||||||
const decodeTransit = options["decodeTransit"];
|
const decodeTransit = options["decodeTransit"];
|
||||||
|
|
||||||
if (decodeTransit) {
|
if (decodeTransit) {
|
||||||
try {
|
try {
|
||||||
decodeTransit(text);
|
decodeTransit(text);
|
||||||
@@ -57,18 +56,85 @@ function parseText(text, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters ClipboardItem types
|
||||||
|
*
|
||||||
|
* @param {ClipboardSettings} options
|
||||||
|
* @returns {Function<AllowedTypesFilterFunction>}
|
||||||
|
*/
|
||||||
|
function filterAllowedTypes(options) {
|
||||||
|
/**
|
||||||
|
* @param {string} type
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
return function filter(type) {
|
||||||
|
if (
|
||||||
|
(!("allowHTMLPaste" in options) || !options["allowHTMLPaste"]) &&
|
||||||
|
type === "text/html"
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return allowedTypes.includes(type);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters DataTransferItems
|
||||||
|
*
|
||||||
|
* @param {ClipboardSettings} options
|
||||||
|
* @returns {Function<AllowedTypesFilterFunction>}
|
||||||
|
*/
|
||||||
|
function filterAllowedItems(options) {
|
||||||
|
/**
|
||||||
|
* @param {DataTransferItem}
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
return function filter(item) {
|
||||||
|
if (
|
||||||
|
(!("allowHTMLPaste" in options) || !options["allowHTMLPaste"]) &&
|
||||||
|
item.type === "text/html"
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return allowedTypes.includes(item.type);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts ClipboardItem types
|
||||||
|
*
|
||||||
|
* @param {string} a
|
||||||
|
* @param {string} b
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function sortTypes(a, b) {
|
||||||
|
return allowedTypes.indexOf(a) - allowedTypes.indexOf(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts DataTransferItems
|
||||||
|
*
|
||||||
|
* @param {DataTransferItem} a
|
||||||
|
* @param {DataTransferItem} b
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
function sortItems(a, b) {
|
||||||
|
return allowedTypes.indexOf(a.type) - allowedTypes.indexOf(b.type);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {ClipboardSettings} [options]
|
* @param {ClipboardSettings} [options]
|
||||||
* @returns {Promise<Array<Blob>>}
|
* @returns {Promise<Array<Blob>>}
|
||||||
*/
|
*/
|
||||||
export async function fromNavigator(options) {
|
export async function fromNavigator(options) {
|
||||||
|
options = options || {};
|
||||||
const items = await navigator.clipboard.read();
|
const items = await navigator.clipboard.read();
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
Array.from(items).map(async (item) => {
|
Array.from(items).map(async (item) => {
|
||||||
const itemAllowedTypes = Array.from(item.types)
|
const itemAllowedTypes = Array.from(item.types)
|
||||||
.filter((type) => allowedTypes.includes(type))
|
.filter(filterAllowedTypes(options))
|
||||||
.sort((a, b) => allowedTypes.indexOf(a) - allowedTypes.indexOf(b));
|
.sort(sortTypes);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
itemAllowedTypes.length === 1 &&
|
itemAllowedTypes.length === 1 &&
|
||||||
@@ -96,12 +162,11 @@ export async function fromNavigator(options) {
|
|||||||
* @returns {Promise<Array<Blob>>}
|
* @returns {Promise<Array<Blob>>}
|
||||||
*/
|
*/
|
||||||
export async function fromDataTransfer(dataTransfer, options) {
|
export async function fromDataTransfer(dataTransfer, options) {
|
||||||
|
options = options || {};
|
||||||
const items = await Promise.all(
|
const items = await Promise.all(
|
||||||
Array.from(dataTransfer.items)
|
Array.from(dataTransfer.items)
|
||||||
.filter((item) => allowedTypes.includes(item.type))
|
.filter(filterAllowedItems(options))
|
||||||
.sort(
|
.sort(sortItems)
|
||||||
(a, b) => allowedTypes.indexOf(a.type) - allowedTypes.indexOf(b.type),
|
|
||||||
)
|
|
||||||
.map(async (item) => {
|
.map(async (item) => {
|
||||||
if (item.kind === "file") {
|
if (item.kind === "file") {
|
||||||
return Promise.resolve(item.getAsFile());
|
return Promise.resolve(item.getAsFile());
|
||||||
|
|||||||
Reference in New Issue
Block a user