mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-06 12:04:00 +09:00
experimental: Paste from Webflow 3 (#3466)
## Description More components: Richt Text, Supescript, Subscript, Emphasized, Strong, Section, Container, Quick Stack, VStack, HStack ## 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.example` and the `builder/env-check.js` if mandatory
This commit is contained in:
1 parent
5df7b1aa68
commit
028cd799db
2 files changed
+430
-11
No files matched your search
@@ -57,7 +57,7 @@ test("Heading", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("Link", () => {
|
||||
test("Link Block, Button, Text Link", () => {
|
||||
const fragment = toWebstudioFragment({
|
||||
type: "@webflow/XscpData",
|
||||
payload: {
|
||||
@@ -341,6 +341,360 @@ test("Blockquote", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("Strong", () => {
|
||||
const fragment = toWebstudioFragment({
|
||||
type: "@webflow/XscpData",
|
||||
payload: {
|
||||
nodes: [
|
||||
{
|
||||
_id: "25ffefdf-c015-5edd-7673-933b41a25328",
|
||||
type: "Strong",
|
||||
tag: "strong",
|
||||
classes: [],
|
||||
children: ["25ffefdf-c015-5edd-7673-933b41a25329"],
|
||||
},
|
||||
{
|
||||
_id: "25ffefdf-c015-5edd-7673-933b41a25329",
|
||||
text: true,
|
||||
v: "Bold Text",
|
||||
},
|
||||
],
|
||||
styles: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(fragment.instances).toEqual([
|
||||
{
|
||||
id: expect.not.stringMatching("instanceId"),
|
||||
type: "instance",
|
||||
component: "Bold",
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: "Bold Text",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("Emphasized", () => {
|
||||
const fragment = toWebstudioFragment({
|
||||
type: "@webflow/XscpData",
|
||||
payload: {
|
||||
nodes: [
|
||||
{
|
||||
_id: "25ffefdf-c015-5edd-7673-933b41a25328",
|
||||
type: "Emphasized",
|
||||
tag: "em",
|
||||
classes: [],
|
||||
children: ["25ffefdf-c015-5edd-7673-933b41a25329"],
|
||||
},
|
||||
{
|
||||
_id: "25ffefdf-c015-5edd-7673-933b41a25329",
|
||||
text: true,
|
||||
v: "Emphasis",
|
||||
},
|
||||
],
|
||||
styles: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(fragment.instances).toEqual([
|
||||
{
|
||||
id: expect.not.stringMatching("instanceId"),
|
||||
type: "instance",
|
||||
component: "Italic",
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: "Emphasis",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("Superscript", () => {
|
||||
const fragment = toWebstudioFragment({
|
||||
type: "@webflow/XscpData",
|
||||
payload: {
|
||||
nodes: [
|
||||
{
|
||||
_id: "25ffefdf-c015-5edd-7673-933b41a25328",
|
||||
type: "Superscript",
|
||||
tag: "sup",
|
||||
classes: [],
|
||||
children: ["25ffefdf-c015-5edd-7673-933b41a25329"],
|
||||
},
|
||||
{
|
||||
_id: "25ffefdf-c015-5edd-7673-933b41a25329",
|
||||
text: true,
|
||||
v: "Superscript",
|
||||
},
|
||||
],
|
||||
styles: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(fragment.instances).toEqual([
|
||||
{
|
||||
id: expect.not.stringMatching("instanceId"),
|
||||
type: "instance",
|
||||
component: "Superscript",
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: "Superscript",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("Subscript", () => {
|
||||
const fragment = toWebstudioFragment({
|
||||
type: "@webflow/XscpData",
|
||||
payload: {
|
||||
nodes: [
|
||||
{
|
||||
_id: "25ffefdf-c015-5edd-7673-933b41a25328",
|
||||
type: "Subscript",
|
||||
tag: "sub",
|
||||
classes: [],
|
||||
children: ["25ffefdf-c015-5edd-7673-933b41a25329"],
|
||||
},
|
||||
{
|
||||
_id: "25ffefdf-c015-5edd-7673-933b41a25329",
|
||||
text: true,
|
||||
v: "Subscript",
|
||||
},
|
||||
],
|
||||
styles: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(fragment.instances).toEqual([
|
||||
{
|
||||
id: expect.not.stringMatching("instanceId"),
|
||||
type: "instance",
|
||||
component: "Subscript",
|
||||
children: [
|
||||
{
|
||||
type: "text",
|
||||
value: "Subscript",
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("Section", () => {
|
||||
const fragment = toWebstudioFragment({
|
||||
type: "@webflow/XscpData",
|
||||
payload: {
|
||||
nodes: [
|
||||
{
|
||||
_id: "25ffefdf-c015-5edd-7673-933b41a25328",
|
||||
type: "Section",
|
||||
tag: "section",
|
||||
classes: [],
|
||||
children: ["25ffefdf-c015-5edd-7673-933b41a25329"],
|
||||
},
|
||||
],
|
||||
styles: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(fragment.instances).toEqual([
|
||||
{
|
||||
id: expect.not.stringMatching("instanceId"),
|
||||
type: "instance",
|
||||
component: "Box",
|
||||
children: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("BlockContainer", () => {
|
||||
const fragment = toWebstudioFragment({
|
||||
type: "@webflow/XscpData",
|
||||
payload: {
|
||||
nodes: [
|
||||
{
|
||||
_id: "25ffefdf-c015-5edd-7673-933b41a25328",
|
||||
type: "BlockContainer",
|
||||
tag: "div",
|
||||
classes: [],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
styles: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(fragment.instances).toEqual([
|
||||
{
|
||||
id: expect.not.stringMatching("instanceId"),
|
||||
type: "instance",
|
||||
component: "Box",
|
||||
children: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("Block", () => {
|
||||
const fragment = toWebstudioFragment({
|
||||
type: "@webflow/XscpData",
|
||||
payload: {
|
||||
nodes: [
|
||||
{
|
||||
_id: "25ffefdf-c015-5edd-7673-933b41a25328",
|
||||
type: "Block",
|
||||
tag: "div",
|
||||
classes: [],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
styles: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(fragment.instances).toEqual([
|
||||
{
|
||||
id: expect.not.stringMatching("instanceId"),
|
||||
type: "instance",
|
||||
component: "Box",
|
||||
children: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("V Flex", () => {
|
||||
const fragment = toWebstudioFragment({
|
||||
type: "@webflow/XscpData",
|
||||
payload: {
|
||||
nodes: [
|
||||
{
|
||||
_id: "25ffefdf-c015-5edd-7673-933b41a25328",
|
||||
type: "VFlex",
|
||||
tag: "div",
|
||||
classes: [],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
styles: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(fragment.instances).toEqual([
|
||||
{
|
||||
id: expect.not.stringMatching("instanceId"),
|
||||
type: "instance",
|
||||
component: "Box",
|
||||
children: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("H Flex", () => {
|
||||
const fragment = toWebstudioFragment({
|
||||
type: "@webflow/XscpData",
|
||||
payload: {
|
||||
nodes: [
|
||||
{
|
||||
_id: "25ffefdf-c015-5edd-7673-933b41a25328",
|
||||
type: "HFlex",
|
||||
tag: "div",
|
||||
classes: [],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
styles: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(fragment.instances).toEqual([
|
||||
{
|
||||
id: expect.not.stringMatching("instanceId"),
|
||||
type: "instance",
|
||||
component: "Box",
|
||||
children: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("Quick Stack", () => {
|
||||
const fragment = toWebstudioFragment({
|
||||
type: "@webflow/XscpData",
|
||||
payload: {
|
||||
nodes: [
|
||||
{
|
||||
_id: "91782272-bf55-194d-ce85-9ddc69c51dee",
|
||||
type: "Layout",
|
||||
tag: "div",
|
||||
classes: [],
|
||||
children: [
|
||||
"91782272-bf55-194d-ce85-9ddc69c51def",
|
||||
"91782272-bf55-194d-ce85-9ddc69c51df0",
|
||||
],
|
||||
data: {
|
||||
style: {
|
||||
base: {
|
||||
main: {
|
||||
noPseudo: {
|
||||
gridTemplateColumns: "1fr 1fr",
|
||||
gridTemplateRows: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
_id: "91782272-bf55-194d-ce85-9ddc69c51def",
|
||||
type: "Cell",
|
||||
tag: "div",
|
||||
classes: [],
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
_id: "91782272-bf55-194d-ce85-9ddc69c51df0",
|
||||
type: "Cell",
|
||||
tag: "div",
|
||||
classes: [],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
styles: [],
|
||||
},
|
||||
});
|
||||
|
||||
expect(fragment.instances).toEqual([
|
||||
{
|
||||
id: expect.not.stringMatching("instanceId"),
|
||||
type: "instance",
|
||||
component: "Box",
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
id: expect.not.stringMatching("instanceId"),
|
||||
type: "instance",
|
||||
component: "Box",
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
id: expect.not.stringMatching("instanceId"),
|
||||
type: "instance",
|
||||
component: "Box",
|
||||
children: [
|
||||
{ type: "id", value: expect.not.stringMatching("instanceId") },
|
||||
{ type: "id", value: expect.not.stringMatching("instanceId") },
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("Basic styles with a class", () => {
|
||||
const fragment = toWebstudioFragment({
|
||||
type: "@webflow/XscpData",
|
||||
|
||||
@@ -21,11 +21,65 @@ export const mimeType = "application/json";
|
||||
|
||||
// A list of Webflow component names that need to be mapped.
|
||||
const componentMappers = {
|
||||
Block(wfNode: WfNode) {
|
||||
if ("type" in wfNode === false || wfNode.type !== "Block") {
|
||||
return;
|
||||
Block(wfNode: WfElementNode) {
|
||||
if (wfNode.type === "Block") {
|
||||
return wfNode.data?.text ? "Text" : "Box";
|
||||
}
|
||||
return wfNode.data.text ? "Text" : "Box";
|
||||
return wfNode.type;
|
||||
},
|
||||
Section(wfNode: WfElementNode) {
|
||||
if (wfNode.type === "Section") {
|
||||
return "Box";
|
||||
}
|
||||
return wfNode.type;
|
||||
},
|
||||
RichText(wfNode: WfElementNode) {
|
||||
if (wfNode.type === "RichText") {
|
||||
return "Box";
|
||||
}
|
||||
return wfNode.type;
|
||||
},
|
||||
Strong(wfNode: WfElementNode) {
|
||||
if (wfNode.type === "Strong") {
|
||||
return "Bold";
|
||||
}
|
||||
return wfNode.type;
|
||||
},
|
||||
Emphasized(wfNode: WfElementNode) {
|
||||
if (wfNode.type === "Emphasized") {
|
||||
return "Italic";
|
||||
}
|
||||
return wfNode.type;
|
||||
},
|
||||
BlockContainer(wfNode: WfElementNode) {
|
||||
if (wfNode.type === "BlockContainer") {
|
||||
return "Box";
|
||||
}
|
||||
return wfNode.type;
|
||||
},
|
||||
Layout(wfNode: WfElementNode) {
|
||||
if (wfNode.type === "Layout") {
|
||||
return "Box";
|
||||
}
|
||||
return wfNode.type;
|
||||
},
|
||||
Cell(wfNode: WfElementNode) {
|
||||
if (wfNode.type === "Cell") {
|
||||
return "Box";
|
||||
}
|
||||
return wfNode.type;
|
||||
},
|
||||
VFlex(wfNode: WfElementNode) {
|
||||
if (wfNode.type === "VFlex") {
|
||||
return "Box";
|
||||
}
|
||||
return wfNode.type;
|
||||
},
|
||||
HFlex(wfNode: WfElementNode) {
|
||||
if (wfNode.type === "HFlex") {
|
||||
return "Box";
|
||||
}
|
||||
return wfNode.type;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -43,11 +97,11 @@ const WfTextNode = z.object({
|
||||
text: z.boolean(),
|
||||
});
|
||||
|
||||
const WfNode = z.union([
|
||||
const WfElementNode = z.union([
|
||||
WfBaseNode.extend({ type: z.enum(["Heading"]) }),
|
||||
WfBaseNode.extend({
|
||||
type: z.enum(["Block"]),
|
||||
data: z.object({ text: z.boolean().optional() }),
|
||||
data: z.object({ text: z.boolean().optional() }).optional(),
|
||||
}),
|
||||
WfBaseNode.extend({ type: z.enum(["List"]) }),
|
||||
WfBaseNode.extend({ type: z.enum(["ListItem"]) }),
|
||||
@@ -62,8 +116,21 @@ const WfNode = z.union([
|
||||
}),
|
||||
WfBaseNode.extend({ type: z.enum(["Paragraph"]) }),
|
||||
WfBaseNode.extend({ type: z.enum(["Blockquote"]) }),
|
||||
WfTextNode,
|
||||
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(["Layout"]) }),
|
||||
WfBaseNode.extend({ type: z.enum(["Cell"]) }),
|
||||
WfBaseNode.extend({ type: z.enum(["VFlex"]) }),
|
||||
WfBaseNode.extend({ type: z.enum(["HFlex"]) }),
|
||||
]);
|
||||
type WfElementNode = z.infer<typeof WfElementNode>;
|
||||
|
||||
const WfNode = z.union([WfElementNode, WfTextNode]);
|
||||
type WfNode = z.infer<typeof WfNode>;
|
||||
|
||||
const WfStyle = z.object({
|
||||
@@ -181,9 +248,7 @@ const addInstance = (
|
||||
|
||||
const component =
|
||||
wfNode.type in componentMappers
|
||||
? componentMappers[wfNode.type as keyof typeof componentMappers](
|
||||
wfNode
|
||||
) ?? wfNode.type
|
||||
? componentMappers[wfNode.type as keyof typeof componentMappers](wfNode)
|
||||
: wfNode.type;
|
||||
|
||||
fragment.instances.push({
|
||||
|
||||
Reference in new issue
Block a user