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)
|
||||
- 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)
|
||||
- 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
|
||||
|
||||
@@ -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 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 copy/pasting application/transit+json [Taiga #12721](https://tree.taiga.io/project/penpot/issue/12721)
|
||||
|
||||
## 2.11.1
|
||||
|
||||
|
||||
@@ -254,6 +254,10 @@
|
||||
(declare ^:private paste-svg-text)
|
||||
(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
|
||||
[in-viewport?]
|
||||
(fn [blob]
|
||||
@@ -290,7 +294,7 @@
|
||||
(ptk/reify ::paste-from-clipboard
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (clipboard/from-navigator)
|
||||
(->> (clipboard/from-navigator default-options)
|
||||
(rx/mapcat default-paste-from-blob)
|
||||
(rx/take 1)))))
|
||||
|
||||
@@ -308,7 +312,7 @@
|
||||
;; we forbid that scenario so the default behaviour is executed
|
||||
(if is-editing?
|
||||
(rx/empty)
|
||||
(->> (clipboard/from-synthetic-clipboard-event event)
|
||||
(->> (clipboard/from-synthetic-clipboard-event event default-options)
|
||||
(rx/mapcat (create-paste-from-blob in-viewport?))))))))
|
||||
|
||||
(defn copy-selected-svg
|
||||
@@ -478,7 +482,7 @@
|
||||
(js/console.error "Clipboard error:" cause))
|
||||
(rx/empty)))]
|
||||
|
||||
(->> (clipboard/from-navigator)
|
||||
(->> (clipboard/from-navigator default-options)
|
||||
(rx/mapcat #(.text %))
|
||||
(rx/map decode-entry)
|
||||
(rx/take 1)
|
||||
|
||||
@@ -18,47 +18,56 @@
|
||||
"image/svg+xml"])
|
||||
|
||||
(def ^:private default-options
|
||||
#js {:decodeTransit t/decode-str})
|
||||
#js {:decodeTransit t/decode-str
|
||||
:allowHTMLPaste false})
|
||||
|
||||
(defn- from-data-transfer
|
||||
"Get clipboard stream from DataTransfer instance"
|
||||
[data-transfer]
|
||||
(->> (rx/from (impl/fromDataTransfer data-transfer default-options))
|
||||
(rx/mapcat #(rx/from %))))
|
||||
([data-transfer]
|
||||
(from-data-transfer data-transfer default-options))
|
||||
([data-transfer options]
|
||||
(->> (rx/from (impl/fromDataTransfer data-transfer options))
|
||||
(rx/mapcat #(rx/from %)))))
|
||||
|
||||
(defn from-navigator
|
||||
[]
|
||||
(->> (rx/from (impl/fromNavigator default-options))
|
||||
(rx/mapcat #(rx/from %))))
|
||||
([]
|
||||
(from-navigator default-options))
|
||||
([options]
|
||||
(->> (rx/from (impl/fromNavigator options))
|
||||
(rx/mapcat #(rx/from %)))))
|
||||
|
||||
(defn from-clipboard-event
|
||||
"Get clipboard stream from clipboard event"
|
||||
[event]
|
||||
(let [cdata (.-clipboardData ^js event)]
|
||||
(from-data-transfer cdata)))
|
||||
([event]
|
||||
(from-clipboard-event event default-options))
|
||||
([event options]
|
||||
(let [cdata (.-clipboardData ^js event)]
|
||||
(from-data-transfer cdata options))))
|
||||
|
||||
(defn from-synthetic-clipboard-event
|
||||
"Get clipboard stream from syntetic clipboard event"
|
||||
[event]
|
||||
(let [target
|
||||
(dom/get-target event)
|
||||
([event options]
|
||||
(let [target
|
||||
(dom/get-target event)
|
||||
|
||||
content-editable?
|
||||
(dom/is-content-editable? target)
|
||||
content-editable?
|
||||
(dom/is-content-editable? target)
|
||||
|
||||
is-input?
|
||||
(= (dom/get-tag-name target) "INPUT")]
|
||||
is-input?
|
||||
(= (dom/get-tag-name target) "INPUT")]
|
||||
|
||||
;; ignore when pasting into an editable control
|
||||
(when-not (or content-editable? is-input?)
|
||||
(-> event
|
||||
(dom/event->browser-event)
|
||||
(from-clipboard-event)))))
|
||||
(when-not (or content-editable? is-input?)
|
||||
(-> event
|
||||
(dom/event->browser-event)
|
||||
(from-clipboard-event options))))))
|
||||
|
||||
(defn from-drop-event
|
||||
"Get clipboard stream from drop event"
|
||||
[event]
|
||||
(from-data-transfer (.-dataTransfer ^js event)))
|
||||
([event]
|
||||
(from-drop-event event default-options))
|
||||
([event options]
|
||||
(from-data-transfer (.-dataTransfer ^js event) options)))
|
||||
|
||||
;; FIXME: rename to `write-text`
|
||||
(defn to-clipboard
|
||||
|
||||
@@ -27,6 +27,7 @@ const exclusiveTypes = [
|
||||
/**
|
||||
* @typedef {Object} ClipboardSettings
|
||||
* @property {Function} [decodeTransit]
|
||||
* @property {boolean} [allowHTMLPaste]
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -38,9 +39,7 @@ const exclusiveTypes = [
|
||||
*/
|
||||
function parseText(text, options) {
|
||||
options = options || {};
|
||||
|
||||
const decodeTransit = options["decodeTransit"];
|
||||
|
||||
if (decodeTransit) {
|
||||
try {
|
||||
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]
|
||||
* @returns {Promise<Array<Blob>>}
|
||||
*/
|
||||
export async function fromNavigator(options) {
|
||||
options = options || {};
|
||||
const items = await navigator.clipboard.read();
|
||||
return Promise.all(
|
||||
Array.from(items).map(async (item) => {
|
||||
const itemAllowedTypes = Array.from(item.types)
|
||||
.filter((type) => allowedTypes.includes(type))
|
||||
.sort((a, b) => allowedTypes.indexOf(a) - allowedTypes.indexOf(b));
|
||||
.filter(filterAllowedTypes(options))
|
||||
.sort(sortTypes);
|
||||
|
||||
if (
|
||||
itemAllowedTypes.length === 1 &&
|
||||
@@ -96,12 +162,11 @@ export async function fromNavigator(options) {
|
||||
* @returns {Promise<Array<Blob>>}
|
||||
*/
|
||||
export async function fromDataTransfer(dataTransfer, options) {
|
||||
options = options || {};
|
||||
const items = await Promise.all(
|
||||
Array.from(dataTransfer.items)
|
||||
.filter((item) => allowedTypes.includes(item.type))
|
||||
.sort(
|
||||
(a, b) => allowedTypes.indexOf(a.type) - allowedTypes.indexOf(b.type),
|
||||
)
|
||||
.filter(filterAllowedItems(options))
|
||||
.sort(sortItems)
|
||||
.map(async (item) => {
|
||||
if (item.kind === "file") {
|
||||
return Promise.resolve(item.getAsFile());
|
||||
|
||||
Reference in New Issue
Block a user