Skip to content

Commit 096c222

Browse files
committed
feat: add wrap in element command
Ref #3632 Here "Wrap in Element" is replacing "Wrap in Box" as more general option.
1 parent c630054 commit 096c222

File tree

2 files changed

+65
-43
lines changed

2 files changed

+65
-43
lines changed

apps/builder/app/builder/shared/commands.test.tsx

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, expect, test } from "vitest";
22
import * as baseMetas from "@webstudio-is/sdk-components-react/metas";
33
import { createDefaultPages } from "@webstudio-is/project-build";
4-
import { $, renderData } from "@webstudio-is/template";
4+
import { $, renderData, ws } from "@webstudio-is/template";
55
import {
66
$instances,
77
$pages,
@@ -10,6 +10,7 @@ import {
1010
import { registerContainers } from "~/shared/sync";
1111
import { $awareness, selectInstance } from "~/shared/awareness";
1212
import { deleteSelectedInstance, unwrap, wrapIn } from "./commands";
13+
import { elementComponent } from "@webstudio-is/sdk";
1314

1415
registerContainers();
1516

@@ -81,71 +82,86 @@ describe("wrap in", () => {
8182
test("wrap instance in link", () => {
8283
$instances.set(
8384
renderData(
84-
<$.Body ws:id="body">
85-
<$.Box ws:id="box"></$.Box>
86-
</$.Body>
85+
<ws.element ws:tag="body" ws:id="bodyId">
86+
<ws.element ws:tag="div" ws:id="divId"></ws.element>
87+
</ws.element>
8788
).instances
8889
);
89-
selectInstance(["box", "body"]);
90-
wrapIn("Link");
90+
selectInstance(["divId", "bodyId"]);
91+
wrapIn(elementComponent, "a");
9192
expect($instances.get()).toEqual(
9293
renderData(
93-
<$.Body ws:id="body">
94-
<$.Link ws:id={expect.any(String)}>
95-
<$.Box ws:id="box"></$.Box>
96-
</$.Link>
97-
</$.Body>
94+
<ws.element ws:tag="body" ws:id="bodyId">
95+
<ws.element ws:tag="a" ws:id={expect.any(String)}>
96+
<ws.element ws:tag="div" ws:id="divId"></ws.element>
97+
</ws.element>
98+
</ws.element>
9899
).instances
99100
);
100101
});
101102

102-
test("wrap image in box", () => {
103+
test("wrap image in element", () => {
103104
$instances.set(
104105
renderData(
105-
<$.Body ws:id="body">
106-
<$.Image ws:id="image" />
107-
</$.Body>
106+
<ws.element ws:tag="body" ws:id="bodyId">
107+
<ws.element ws:tag="img" ws:id="imageId"></ws.element>
108+
</ws.element>
108109
).instances
109110
);
110-
selectInstance(["image", "body"]);
111-
wrapIn("Box");
111+
selectInstance(["imageId", "bodyId"]);
112+
wrapIn(elementComponent);
112113
expect($instances.get()).toEqual(
113114
renderData(
114-
<$.Body ws:id="body">
115-
<$.Box ws:id={expect.any(String)}>
116-
<$.Image ws:id="image" />
117-
</$.Box>
118-
</$.Body>
115+
<ws.element ws:tag="body" ws:id="bodyId">
116+
<ws.element ws:tag="div" ws:id={expect.any(String)}>
117+
<ws.element ws:tag="img" ws:id="imageId"></ws.element>
118+
</ws.element>
119+
</ws.element>
119120
).instances
120121
);
121122
});
122123

123124
test("avoid wrapping text with link in link", () => {
124125
const { instances } = renderData(
125-
<$.Body ws:id="body">
126-
<$.Text ws:id="text">
127-
<$.RichTextLink ws:id="richtextlink"></$.RichTextLink>
128-
</$.Text>
129-
</$.Body>
126+
<ws.element ws:tag="body" ws:id="bodyId">
127+
<ws.element ws:tag="p" ws:id="textId">
128+
<ws.element ws:tag="a" ws:id="linkId"></ws.element>
129+
</ws.element>
130+
</ws.element>
130131
);
131132
$instances.set(instances);
132-
selectInstance(["text", "body"]);
133-
wrapIn("Link");
133+
selectInstance(["textId", "bodyId"]);
134+
wrapIn(elementComponent, "a");
134135
// nothing is changed
135136
expect($instances.get()).toEqual(instances);
136137
});
137138

138139
test("avoid wrapping textual content", () => {
139140
const { instances } = renderData(
140-
<$.Body ws:id="body">
141-
<$.Text ws:id="text">
142-
<$.Bold ws:id="bold"></$.Bold>
143-
</$.Text>
144-
</$.Body>
141+
<ws.element ws:tag="body" ws:id="bodyId">
142+
<ws.element ws:tag="div" ws:id="textId">
143+
<ws.element ws:tag="bold" ws:id="boldId"></ws.element>
144+
</ws.element>
145+
</ws.element>
146+
);
147+
$instances.set(instances);
148+
selectInstance(["boldId", "textId", "bodyId"]);
149+
wrapIn(elementComponent);
150+
// nothing is changed
151+
expect($instances.get()).toEqual(instances);
152+
});
153+
154+
test("avoid wrapping list item", () => {
155+
const { instances } = renderData(
156+
<ws.element ws:tag="body" ws:id="bodyId">
157+
<ws.element ws:tag="ul" ws:id="listId">
158+
<ws.element ws:tag="li" ws:id="listItemId"></ws.element>
159+
</ws.element>
160+
</ws.element>
145161
);
146162
$instances.set(instances);
147-
selectInstance(["bold", "text", "body"]);
148-
wrapIn("Box");
163+
selectInstance(["listItemId", "listId", "bodyId"]);
164+
wrapIn(elementComponent);
149165
// nothing is changed
150166
expect($instances.get()).toEqual(instances);
151167
});

apps/builder/app/builder/shared/commands.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { nanoid } from "nanoid";
22
import {
33
blockTemplateComponent,
4+
elementComponent,
45
isComponentDetachable,
56
} from "@webstudio-is/sdk";
67
import type { Instance } from "@webstudio-is/sdk";
@@ -48,6 +49,7 @@ import {
4849
isRichTextContent,
4950
isTreeSatisfyingContentModel,
5051
} from "~/shared/content-model";
52+
import { getInstanceLabel } from "./instance-label";
5153

5254
export const $styleSourceInputElement = atom<HTMLInputElement | undefined>();
5355

@@ -138,7 +140,7 @@ export const deleteSelectedInstance = () => {
138140
});
139141
};
140142

141-
export const wrapIn = (component: string) => {
143+
export const wrapIn = (component: string, tag?: string) => {
142144
const instancePath = $selectedInstancePath.get();
143145
// global root or body are selected
144146
if (instancePath === undefined || instancePath.length === 1) {
@@ -167,6 +169,9 @@ export const wrapIn = (component: string) => {
167169
component,
168170
children: [{ type: "id", value: selectedInstance.id }],
169171
};
172+
if (tag || elementComponent) {
173+
newInstance.tag = tag ?? "div";
174+
}
170175
const parentInstance = data.instances.get(parentItem.instance.id);
171176
data.instances.set(newInstanceId, newInstance);
172177
if (parentInstance) {
@@ -176,14 +181,15 @@ export const wrapIn = (component: string) => {
176181
}
177182
}
178183
}
179-
const matches = isTreeSatisfyingContentModel({
184+
const isSatisfying = isTreeSatisfyingContentModel({
180185
instances: data.instances,
181186
props: data.props,
182187
metas,
183188
instanceSelector: newInstanceSelector,
184189
});
185-
if (matches === false) {
186-
toast.error(`Cannot wrap in "${component}"`);
190+
if (isSatisfying === false) {
191+
const label = getInstanceLabel({ component, tag }, {});
192+
toast.error(`Cannot wrap in ${label}`);
187193
throw Error("Abort transaction");
188194
}
189195
});
@@ -507,12 +513,12 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
507513
},
508514
},
509515
{
510-
name: "wrapInBox",
511-
handler: () => wrapIn("Box"),
516+
name: "wrapInElement",
517+
handler: () => wrapIn(elementComponent),
512518
},
513519
{
514520
name: "wrapInLink",
515-
handler: () => wrapIn("Link"),
521+
handler: () => wrapIn(elementComponent, "a"),
516522
},
517523
{
518524
name: "unwrap",

0 commit comments

Comments
 (0)