Files
Ivan Starkov 19c95fe9f0 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
2024-07-16 21:08:36 +03:00

405 lines
9.5 KiB
TypeScript

import { z } from "zod";
const Attr = z
.object({ id: z.string(), role: z.string(), href: z.string() })
.partial();
const styleBase = z.string();
const styleBreakpoint = z.string();
const stylePseudo = z.string();
const styleProperty = z.string();
const styleValue = z.unknown();
const WfNodeData = z.object({
attr: Attr.optional(),
xattr: z.array(z.object({ name: z.string(), value: z.string() })).optional(),
visibility: z
.object({
conditions: z.array(z.boolean()),
})
.optional(),
style: z
.record(
styleBase,
z.record(
styleBreakpoint,
z.record(stylePseudo, z.record(styleProperty, styleValue))
)
)
.optional(),
});
const WfBaseNode = z.object({
_id: z.string(),
tag: z.string(),
children: z.array(z.string()),
classes: z.array(z.string()),
data: WfNodeData.optional(),
attr: Attr.optional(),
});
const WfTextNode = z.object({
_id: z.string(),
v: z.string(),
text: z.boolean(),
});
const WfLinkData = WfNodeData.extend({
attr: Attr.optional(),
block: z.enum(["inline", "block", ""]).optional(),
button: z.boolean().optional(),
link: z.union([
// External link
z.object({
url: z.string(),
target: z.string().optional(),
}),
// Page link and section link
z.object({
href: z.string(),
}),
// Email link
z.object({
email: z.string(),
subject: z.string().optional(),
}),
// Phone link
z.object({
tel: z.string(),
}),
z.object({
mode: z.string(),
}),
]),
});
export const wfNodeTypes = [
"Heading",
"Block",
"List",
"ListItem",
"Link",
"Paragraph",
"Blockquote",
"RichText",
"Strong",
"Emphasized",
"Superscript",
"Subscript",
"Section",
"BlockContainer",
"Container",
"Layout",
"Cell",
"VFlex",
"HFlex",
"Grid",
"Row",
"Column",
"CodeBlock",
"HtmlEmbed",
"Image",
"FormWrapper",
"FormForm",
"FormSuccessMessage",
"FormErrorMessage",
"FormButton",
"FormTextInput",
"FormTextarea",
"FormBlockLabel",
"FormCheckboxWrapper",
"FormCheckboxInput",
"FormInlineLabel",
"FormRadioWrapper",
"FormRadioInput",
"FormSelect",
"LineBreak",
"Span",
"NavbarMenu",
"NavbarWrapper",
"NavbarBrand",
"NavbarLink",
"NavbarButton",
"NavbarContainer",
"Icon",
"LightboxWrapper",
] as const;
const WfElementNode = z.union([
WfBaseNode.extend({
type: z.enum(["Icon"]),
data: WfNodeData.extend({
widget: z
.object({
type: z.string(),
icon: z.string(),
})
.optional(),
}),
}),
WfBaseNode.extend({ type: z.enum(["LightboxWrapper"]) }),
WfBaseNode.extend({ type: z.enum(["NavbarMenu"]) }),
WfBaseNode.extend({ type: z.enum(["NavbarContainer"]) }),
WfBaseNode.extend({ type: z.enum(["NavbarWrapper"]) }),
WfBaseNode.extend({
type: z.enum(["NavbarBrand"]),
data: WfLinkData,
}),
WfBaseNode.extend({
type: z.enum(["NavbarLink"]),
data: WfLinkData,
}),
WfBaseNode.extend({ type: z.enum(["NavbarButton"]) }),
WfBaseNode.extend({ type: z.enum(["Heading"]) }),
WfBaseNode.extend({
type: z.enum(["Block"]),
data: WfNodeData.extend({
attr: Attr.optional(),
text: z.boolean().optional(),
}).optional(),
}),
WfBaseNode.extend({ type: z.enum(["List"]) }),
WfBaseNode.extend({ type: z.enum(["ListItem"]) }),
WfBaseNode.extend({
type: z.enum(["Link"]),
data: WfLinkData,
}),
WfBaseNode.extend({ type: z.enum(["Paragraph"]) }),
WfBaseNode.extend({ type: z.enum(["Blockquote"]) }),
WfBaseNode.extend({ type: z.enum(["RichText"]) }),
WfBaseNode.extend({ type: z.enum(["Strong"]) }),
WfBaseNode.extend({ type: z.enum(["Emphasized"]) }),
WfBaseNode.extend({ type: z.enum(["Superscript"]) }),
WfBaseNode.extend({ type: z.enum(["Subscript"]) }),
WfBaseNode.extend({ type: z.enum(["Section"]) }),
WfBaseNode.extend({ type: z.enum(["BlockContainer"]) }),
WfBaseNode.extend({ type: z.enum(["Container"]) }),
WfBaseNode.extend({ type: z.enum(["Layout"]) }),
WfBaseNode.extend({ type: z.enum(["Cell"]) }),
WfBaseNode.extend({ type: z.enum(["VFlex"]) }),
WfBaseNode.extend({ type: z.enum(["HFlex"]) }),
WfBaseNode.extend({ type: z.enum(["Grid"]) }),
WfBaseNode.extend({ type: z.enum(["Row"]) }),
WfBaseNode.extend({ type: z.enum(["Column"]) }),
WfBaseNode.extend({
type: z.enum(["CodeBlock"]),
data: WfNodeData.extend({
attr: Attr.optional(),
language: z.string().optional(),
code: z.string(),
}),
}),
WfBaseNode.extend({
type: z.enum(["HtmlEmbed"]),
v: z.string(),
}),
WfBaseNode.extend({
type: z.enum(["Image"]),
data: WfNodeData.extend({
attr: Attr.extend({
alt: z.string().optional(),
loading: z.enum(["lazy", "eager", "auto"]),
src: z.string(),
width: z.string(),
height: z.string(),
}),
}),
}),
WfBaseNode.extend({ type: z.enum(["FormWrapper"]) }),
WfBaseNode.extend({
type: z.enum(["FormForm"]),
data: WfNodeData.extend({
attr: Attr.extend({
action: z.string(),
method: z.string(),
name: z.string(),
}),
}),
}),
WfBaseNode.extend({ type: z.enum(["FormSuccessMessage"]) }),
WfBaseNode.extend({ type: z.enum(["FormErrorMessage"]) }),
WfBaseNode.extend({
type: z.enum(["FormButton"]),
data: WfNodeData.extend({
attr: Attr.extend({
value: z.string(),
}),
}),
}),
WfBaseNode.extend({
type: z.enum(["FormTextInput"]),
data: WfNodeData.extend({
attr: Attr.extend({
name: z.string(),
maxlength: z.number(),
placeholder: z.string(),
disabled: z.boolean(),
type: z.string(),
required: z.boolean(),
autofocus: z.boolean(),
}),
}),
}),
WfBaseNode.extend({
type: z.enum(["FormTextarea"]),
data: WfNodeData.extend({
attr: Attr.extend({
name: z.string(),
maxlength: z.number(),
placeholder: z.string(),
required: z.boolean(),
autofocus: z.boolean(),
}),
}),
}),
WfBaseNode.extend({
type: z.enum(["FormBlockLabel"]),
data: WfNodeData.extend({
attr: Attr.extend({
for: z.string().optional(),
}),
}),
}),
WfBaseNode.extend({
type: z.enum(["FormCheckboxWrapper"]),
}),
WfBaseNode.extend({
type: z.enum(["FormCheckboxInput"]),
data: WfNodeData.extend({
attr: Attr.extend({
type: z.enum(["checkbox"]),
name: z.string(),
required: z.boolean(),
checked: z.boolean(),
}),
}),
}),
WfBaseNode.extend({
type: z.enum(["FormInlineLabel"]),
}),
WfBaseNode.extend({
type: z.enum(["FormRadioWrapper"]),
}),
WfBaseNode.extend({
type: z.enum(["FormRadioInput"]),
data: WfNodeData.extend({
attr: Attr.extend({
type: z.enum(["radio"]),
name: z.string(),
required: z.boolean(),
value: z.string(),
}),
}),
}),
WfBaseNode.extend({
type: z.enum(["FormSelect"]),
data: WfNodeData.extend({
attr: Attr.extend({
name: z.string(),
required: z.boolean(),
multiple: z.boolean(),
}),
form: z.object({
opts: z.array(
z.object({
t: z.string(),
v: z.string(),
})
),
}),
}),
}),
WfBaseNode.extend({ type: z.enum(["LineBreak"]) }),
WfBaseNode.extend({ type: z.enum(["Span"]) }),
]);
export type WfElementNode = z.infer<typeof WfElementNode>;
[...wfNodeTypes] as const satisfies WfElementNode["type"][];
//@todo verify the other way around too
//(typeof WfElementNode)["type"] satisfies typeof wfNodeTypes[number]
const WfNode = z.union([WfElementNode, WfTextNode]);
export type WfNode = z.infer<typeof WfNode>;
const WfStyle = z.object({
_id: z.string(),
type: z.enum(["class"]),
name: z.string(),
styleLess: z.string(),
fake: z.boolean().optional(),
comb: z.string().optional(),
namespace: z.string().optional(),
variants: z
.record(z.string(), z.object({ styleLess: z.string() }))
.optional(),
children: z.array(z.string()).optional(),
});
export type WfStyle = z.infer<typeof WfStyle>;
const WfErrorAssetVariant = z.object({
origFileName: z.string(),
fileName: z.string(),
format: z.string(),
size: z.number().optional(),
width: z.number().optional(),
height: z.number().optional(),
quality: z.number().optional(),
error: z.string(),
_id: z.string(),
});
const WfAssetVariant = z.object({
origFileName: z.string(),
fileName: z.string(),
format: z.string(),
size: z.number().optional(),
width: z.number().optional(),
height: z.number().optional(),
quality: z.number().optional(),
cdnUrl: z.string().url(),
s3Url: z.string().url(),
});
const WfAsset = z.object({
cdnUrl: z.string().url(),
siteId: z.string(),
width: z.number().optional(),
height: z.number().optional(),
fileName: z.string(),
createdOn: z.string(),
origFileName: z.string(),
fileHash: z.string(),
variants: z.array(z.union([WfAssetVariant, WfErrorAssetVariant])).optional(),
mimeType: z.string(),
s3Url: z.string().url(),
thumbUrl: z.string().optional(),
_id: z.string(),
markedAsDeleted: z.boolean().optional(),
fileSize: z.number(),
});
export type WfAsset = z.infer<typeof WfAsset>;
export const WfData = z.object({
type: z.literal("@webflow/XscpData"),
payload: z.object({
// Using WfBaseNode here just so we can skip a node with unknown node.type.
nodes: z.array(z.union([WfNode, WfBaseNode])),
styles: z.array(WfStyle),
assets: z.array(WfAsset),
}),
});
export type WfData = z.infer<typeof WfData>;