From 308acbbaf72f86531ff44f888accd6640a534ce7 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Tue, 16 Jul 2024 17:44:11 +0200 Subject: [PATCH 1/5] experimental: add transparent color when paste bg clip from webflow (#3739) Here fixed the issue with pasting gradients from webflow. Their css engine output -webkit-fill-color: transparent along with background-clip property. This is very implicit behavior from css point of view. Here I instead added logic to add color: transparent from paste from webflow overriding previously set color. It will not work in 100% but should cover relume templates. --- .../plugin-webflow/plugin-webflow.test.tsx | 83 +++++++++++++++++-- .../copy-paste/plugin-webflow/styles.ts | 28 ++++++- 2 files changed, 103 insertions(+), 8 deletions(-) diff --git a/apps/builder/app/shared/copy-paste/plugin-webflow/plugin-webflow.test.tsx b/apps/builder/app/shared/copy-paste/plugin-webflow/plugin-webflow.test.tsx index c8b19eed5cdd..ab48a5bccc8a 100644 --- a/apps/builder/app/shared/copy-paste/plugin-webflow/plugin-webflow.test.tsx +++ b/apps/builder/app/shared/copy-paste/plugin-webflow/plugin-webflow.test.tsx @@ -3075,12 +3075,81 @@ describe("Styles", () => { const fragment = await toWebstudioFragment(input); expect(toCss(fragment)).toMatchInlineSnapshot(` -"@media all { - Div Block { - color: black; - background-color: red - } -}" -`); + "@media all { + Div Block { + color: black; + background-color: red + } + }" + `); + }); + + test("append transparent color when background-clip is used", async () => { + const input = WfData.parse({ + type: "@webflow/XscpData", + payload: { + nodes: [ + { + _id: "15c3fb65-b871-abe4-f9a4-3747c8a882e0", + type: "Heading", + tag: "h2", + classes: ["069649be-a33a-1a9a-3763-c0bd9d1f3a3d"], + children: ["b69a5869-f046-5a0c-151e-9b134a6852aa"], + data: { + tag: "h2", + devlink: { runtimeProps: {}, slot: "" }, + displayName: "", + attr: { id: "" }, + xattr: [], + search: { exclude: false }, + visibility: { conditions: [] }, + }, + }, + { + _id: "b69a5869-f046-5a0c-151e-9b134a6852aa", + text: true, + v: "Protect your systems securely with Prism", + }, + ], + styles: [ + { + _id: "069649be-a33a-1a9a-3763-c0bd9d1f3a3d", + fake: false, + type: "class", + name: "H2 Heading 2", + namespace: "", + comb: "", + styleLess: + "background-image: linear-gradient(350deg, hsla(256.3636363636363, 72.13%, 23.92%, 0.00), hsla(256.2162162162162, 72.55%, 80.00%, 1.00) 49%, #bba7f1); color: hsla(0, 0.00%, 100.00%, 1.00); background-clip: text;", + variants: {}, + children: [], + createdBy: "58b4b8186ceb395341fcf640", + origin: null, + selector: null, + }, + ], + assets: [], + }, + }); + + const fragment = await toWebstudioFragment(input); + + expect(toCss(fragment)).toMatchInlineSnapshot(` + "@media all { + h2 { + margin-bottom: 10px; + font-weight: bold; + margin-top: 20px; + font-size: 32px; + line-height: 36px + } + H2 Heading 2 { + background-image: linear-gradient(350deg,hsla(256.3636363636363,72.13%,23.92%,0.00),hsla(256.2162162162162,72.55%,80.00%,1.00) 49%,#bba7f1); + -webkit-background-clip: text; + background-clip: text; + color: transparent + } + }" + `); }); }); diff --git a/apps/builder/app/shared/copy-paste/plugin-webflow/styles.ts b/apps/builder/app/shared/copy-paste/plugin-webflow/styles.ts index 7028f88e7281..e6fc623df8bf 100644 --- a/apps/builder/app/shared/copy-paste/plugin-webflow/styles.ts +++ b/apps/builder/app/shared/copy-paste/plugin-webflow/styles.ts @@ -102,6 +102,30 @@ const replaceAtImages = ( }); }; +const processStyles = (parsedStyles: ParsedStyleDecl[]) => { + const styles = new Map(); + for (const parsedStyleDecl of parsedStyles) { + const { breakpoint, selector, state, property } = parsedStyleDecl; + const key = `${breakpoint}:${selector}:${state}:${property}`; + styles.set(key, parsedStyleDecl); + } + for (const parsedStyleDecl of styles.values()) { + const { breakpoint, selector, state, property } = parsedStyleDecl; + const key = `${breakpoint}:${selector}:${state}:${property}`; + styles.set(key, parsedStyleDecl); + if (property === "backgroundClip") { + const colorKey = `${breakpoint}:${selector}:${state}:color`; + styles.delete(colorKey); + styles.set(colorKey, { + ...parsedStyleDecl, + property: "color", + value: { type: "keyword", value: "transparent" }, + }); + } + } + return Array.from(styles.values()); +}; + type UnparsedVariants = Map>; // Variants value can be wf styleLess string which is a styles block @@ -114,7 +138,9 @@ const toParsedVariants = (variants: UnparsedVariants) => { if (typeof styles === "string") { try { const sanitizedStyles = styles.replaceAll(/@raw<\|([^@]+)\|>/g, "$1"); - const parsed = parseCss(`.styles${state} {${sanitizedStyles}}`) ?? []; + const parsed = processStyles( + parseCss(`.styles${state} {${sanitizedStyles}}`) ?? [] + ); const allBreakpointStyles = parsedVariants.get(breakpointName) ?? []; allBreakpointStyles.push(...parsed); parsedVariants.set(breakpointName, allBreakpointStyles); From cfde2f33b3aecac0ee548d4452b65d599bfd65c4 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Tue, 16 Jul 2024 17:49:09 +0200 Subject: [PATCH 2/5] refactor: parse complex aspect ratio as unparsed value (#3740) Fixes the issue with pasting images from webflow. --- packages/css-data/src/parse-css-value.test.ts | 29 ++++++++++++++----- packages/css-data/src/parse-css-value.ts | 6 +++- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/packages/css-data/src/parse-css-value.test.ts b/packages/css-data/src/parse-css-value.test.ts index 8beac987c6fd..3342da198403 100644 --- a/packages/css-data/src/parse-css-value.test.ts +++ b/packages/css-data/src/parse-css-value.test.ts @@ -58,13 +58,6 @@ describe("Parse CSS value", () => { type: "invalid", value: "10", }); - - // This will return number unit, as number is valid for aspectRatio. - expect(parseCssValue("aspectRatio", "10")).toEqual({ - type: "unit", - unit: "number", - value: 10, - }); }); }); @@ -870,3 +863,25 @@ describe("parse filters", () => { }); }); }); + +describe("aspect-ratio", () => { + test("support single numeric value", () => { + expect(parseCssValue("aspectRatio", "10")).toEqual({ + type: "unit", + unit: "number", + value: 10, + }); + }); + test("support keyword", () => { + expect(parseCssValue("aspectRatio", "auto")).toEqual({ + type: "keyword", + value: "auto", + }); + }); + test("support two values", () => { + expect(parseCssValue("aspectRatio", "16 / 9")).toEqual({ + type: "unparsed", + value: "16 / 9", + }); + }); +}); diff --git a/packages/css-data/src/parse-css-value.ts b/packages/css-data/src/parse-css-value.ts index a137aeb7e6e1..125f34eb6b87 100644 --- a/packages/css-data/src/parse-css-value.ts +++ b/packages/css-data/src/parse-css-value.ts @@ -402,13 +402,17 @@ export const parseCssValue = ( // Probably a tuple like background-size or box-shadow if ( ast.type === "Value" && - (ast.children.size > 1 || tupleProps.has(property)) + (ast.children.size === 2 || tupleProps.has(property)) ) { const tuple: TupleValue = { type: "tuple", value: [], }; for (const node of ast.children) { + // output any values with unhandled operators like slash or comma as unparsed + if (node.type === "Operator") { + return { type: "unparsed", value: input }; + } const matchedValue = parseLiteral(node, keywordValues[property as never]); if (matchedValue) { tuple.value.push(matchedValue as never); From d7a46f3c4f918aebc0911ebb450a35fde9460f40 Mon Sep 17 00:00:00 2001 From: Bogdan Chadkin Date: Tue, 16 Jul 2024 18:24:21 +0200 Subject: [PATCH 3/5] experimental: fix webflow validation error (#3742) This error happened because we assumed local styles to be strings but for example order can be number. Relaxed validation to unknown. Value is used only in template literal. --- .../shared/copy-paste/plugin-webflow/plugin-webflow.test.tsx | 4 +++- apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/builder/app/shared/copy-paste/plugin-webflow/plugin-webflow.test.tsx b/apps/builder/app/shared/copy-paste/plugin-webflow/plugin-webflow.test.tsx index ab48a5bccc8a..8e81dcd00437 100644 --- a/apps/builder/app/shared/copy-paste/plugin-webflow/plugin-webflow.test.tsx +++ b/apps/builder/app/shared/copy-paste/plugin-webflow/plugin-webflow.test.tsx @@ -685,6 +685,7 @@ test("QuickStack with instance styles", async () => { noPseudo: { gridTemplateColumns: "1fr 1fr", gridTemplateRows: "auto", + order: 1, }, }, }, @@ -731,7 +732,8 @@ test("QuickStack with instance styles", async () => { } Local { grid-template-columns: 1fr 1fr; - grid-template-rows: auto + grid-template-rows: auto; + order: 1 } w-layout-cell { flex-direction: column; diff --git a/apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts b/apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts index 49fc5a57a208..3cdcdb5a8237 100644 --- a/apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts +++ b/apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts @@ -10,7 +10,7 @@ const stylePseudo = z.string(); const styleProperty = z.string(); -const styleValue = z.string(); +const styleValue = z.unknown(); const WfNodeData = z.object({ attr: Attr.optional(), From f3a625f96f20d8dbf81e376376cb2498150d4e64 Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Tue, 16 Jul 2024 19:30:02 +0300 Subject: [PATCH 4/5] fix: Image paste from webflow (#3743) ## Description https://discord.com/channels/955905230107738152/1262723546669645865/1262723682032287838 ## Steps for reproduction 1. click button 2. expect xyz ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 5de6) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file --- apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts b/apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts index 3cdcdb5a8237..2797aacb9e1f 100644 --- a/apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts +++ b/apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts @@ -202,7 +202,7 @@ const WfElementNode = z.union([ type: z.enum(["Image"]), data: WfNodeData.extend({ attr: Attr.extend({ - alt: z.string(), + alt: z.string().optional(), loading: z.enum(["lazy", "eager", "auto"]), src: z.string(), width: z.string(), From 19c95fe9f0895e0e16cd73393de582fc35e72043 Mon Sep 17 00:00:00 2001 From: Ivan Starkov Date: Tue, 16 Jul 2024 21:08:36 +0300 Subject: [PATCH 5/5] fix: Lightbox paste partial support (#3744) ## Description Now can be pasted https://www.relume.io/components/gallery-7 ## Steps for reproduction 1. click button 2. expect xyz ## Code Review - [ ] hi @kof, I need you to do - conceptual review (architecture, feature-correctness) - detailed review (read every line) - test it on preview ## Before requesting a review - [ ] made a self-review - [ ] added inline comments where things may be not obvious (the "why", not "what") ## Before merging - [ ] tested locally and on preview environment (preview dev login: 5de6) - [ ] updated [test cases](https://github.com/webstudio-is/webstudio/blob/main/apps/builder/docs/test-cases.md) document - [ ] added tests - [ ] if any new env variables are added, added them to `.env` file --- .../copy-paste/plugin-webflow/instances-properties.ts | 7 ++++++- .../builder/app/shared/copy-paste/plugin-webflow/schema.ts | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/builder/app/shared/copy-paste/plugin-webflow/instances-properties.ts b/apps/builder/app/shared/copy-paste/plugin-webflow/instances-properties.ts index 6138b4afcb24..7b3e1489ff0a 100644 --- a/apps/builder/app/shared/copy-paste/plugin-webflow/instances-properties.ts +++ b/apps/builder/app/shared/copy-paste/plugin-webflow/instances-properties.ts @@ -345,7 +345,12 @@ const toFragment = ( addInstance(component); return fragment; } - + case "LightboxWrapper": { + addProp("tag", wfNode.tag); + addProp("href", wfNode.data?.attr?.href); + addInstance("Box", [], component); + return fragment; + } case "NavbarMenu": { addProp("tag", wfNode.tag); addProp("role", wfNode.data?.attr?.role); diff --git a/apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts b/apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts index 2797aacb9e1f..b6b00f8f7e61 100644 --- a/apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts +++ b/apps/builder/app/shared/copy-paste/plugin-webflow/schema.ts @@ -1,6 +1,8 @@ import { z } from "zod"; -const Attr = z.object({ id: z.string(), role: z.string() }).partial(); +const Attr = z + .object({ id: z.string(), role: z.string(), href: z.string() }) + .partial(); const styleBase = z.string(); @@ -124,6 +126,7 @@ export const wfNodeTypes = [ "NavbarButton", "NavbarContainer", "Icon", + "LightboxWrapper", ] as const; const WfElementNode = z.union([ @@ -139,6 +142,7 @@ const WfElementNode = z.union([ }), }), + WfBaseNode.extend({ type: z.enum(["LightboxWrapper"]) }), WfBaseNode.extend({ type: z.enum(["NavbarMenu"]) }), WfBaseNode.extend({ type: z.enum(["NavbarContainer"]) }),