diff --git a/CHANGES.md b/CHANGES.md index e8fdc0b05d..e5b9dd1bb0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -44,6 +44,8 @@ - Add a search bar to filter colors in the color palette toolbar (by @eureka0928) [Github #7653](https://github.com/penpot/penpot/issues/7653) - Allow customising the OIDC login button label (by @wdeveloper16) [Github #7027](https://github.com/penpot/penpot/issues/7027) - Add page separators in Workspace [Taiga #13611](https://tree.taiga.io/project/penpot/us/13611?milestone=262806) +- Preserve vector content when pasting from external tools such as Inkscape: recognise SVG sent as text/plain (with optional XML declaration and HTML comments), skip the raster preview when an SVG sibling is on the clipboard, and ignore empty SVG blobs that some tools advertise alongside the real payload, so pasted graphics arrive editable without spurious "SVG is invalid" warnings [Github #546](https://github.com/penpot/penpot/issues/546) + - Add Shift+Numpad0/1/2 as aliases to Shift+0/1/2 for zoom shortcuts [Github #2457](https://github.com/penpot/penpot/issues/2457) ### :bug: Bugs fixed diff --git a/frontend/src/app/util/clipboard.js b/frontend/src/app/util/clipboard.js index 294652666a..43bd636b88 100644 --- a/frontend/src/app/util/clipboard.js +++ b/frontend/src/app/util/clipboard.js @@ -24,6 +24,13 @@ const exclusiveTypes = [ "text/plain" ]; +const svgTextPattern = + /^(\s*<\?xml[^?]*\?>\s*)?(\s*\s*)*]/i; + +function hasSvgItem(items) { + return items.some((item) => item?.type === "image/svg+xml"); +} + /** * @typedef {Object} ClipboardSettings * @property {Function} [decodeTransit] @@ -59,7 +66,7 @@ function parseText(text, options) { } } - if (/^]/i.test(text)) { + if (svgTextPattern.test(text)) { return new Blob([text], { type: "image/svg+xml" }); } else { return new Blob([text], { type: "text/plain" }); @@ -207,14 +214,20 @@ export async function fromDataTransfer(dataTransfer, options) { }), ); return items - .filter((item) => !!item) - .reduce((filtered, item) => { + .filter((item) => !!item && item.size > 0) + .reduce((filtered, item, _index, all) => { if ( exclusiveTypes.includes(item.type) && filtered.find((filteredItem) => exclusiveTypes.includes(filteredItem.type)) ) { return filtered; } + if ( + item.type !== "image/svg+xml" && item.type.startsWith("image/") && + hasSvgItem(all) + ) { + return filtered; + } filtered.push(item); return filtered; }, []);