Skip to content

Commit 2752bcc

Browse files
authored
feat: Support script and style tag when pasting HTML (#5447)
## Description 1. What is this PR about (link the issue and add a short description) ## 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: 0000) - [ ] 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
1 parent 801181c commit 2752bcc

File tree

2 files changed

+34
-42
lines changed

2 files changed

+34
-42
lines changed

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

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,8 @@ test("ignore custom elements", () => {
9292

9393
test("ignore not allowed tags", () => {
9494
expect(
95-
generateFragmentFromHtml(`
96-
<style>style</style>
97-
<script>script</script>
98-
<template>template</template>
99-
<section></section>
100-
`)
101-
).toEqual(renderTemplate(<ws.element ws:tag="section"></ws.element>));
95+
generateFragmentFromHtml(`<div><marquee>test</marquee></div>`)
96+
).toEqual(renderTemplate(<ws.element ws:tag="div"></ws.element>));
10297
});
10398

10499
test("generate props from html attributes", () => {
@@ -240,33 +235,24 @@ test("generate style attribute as local styles", () => {
240235
);
241236
});
242237

243-
test("optionally paste svg as html embed", () => {
244-
expect(
245-
generateFragmentFromHtml(
246-
`
247-
<div>
248-
<svg viewBox="0 0 20 20">
249-
<rect x="5" y="5" width="10" height="10" />
250-
</svg>
251-
</div>
252-
`,
253-
{
254-
unknownTags: true,
255-
}
256-
)
257-
).toEqual(
238+
test("script as html embed", () => {
239+
expect(generateFragmentFromHtml(`<script>a;</script>`)).toEqual(
258240
renderTemplate(
259-
<ws.element ws:tag="div">
260-
<$.HtmlEmbed
261-
code={`<svg viewBox="0 0 20 20">
262-
<rect x="5" y="5" width="10" height="10" />
263-
</svg>`}
264-
/>
265-
</ws.element>
241+
<$.HtmlEmbed
242+
ws:label="Script"
243+
clientOnly={true}
244+
code={`<script>a;</script>`}
245+
/>
266246
)
267247
);
268248
});
269249

250+
test("style as html embed", () => {
251+
expect(generateFragmentFromHtml(`<style>a;</style>`)).toEqual(
252+
renderTemplate(<$.HtmlEmbed code={`<style>a;</style>`} ws:label="Style" />)
253+
);
254+
});
255+
270256
test("generate textarea element", () => {
271257
expect(
272258
generateFragmentFromHtml(`

apps/builder/app/shared/html.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { camelCaseProperty, parseCss } from "@webstudio-is/css-data";
1919
import { richTextContentTags } from "./content-model";
2020
import { setIsSubsetOf } from "./shim";
2121
import { isAttributeNameSafe } from "@webstudio-is/react-sdk";
22+
import { capitalCase } from "change-case";
2223

2324
type ElementNode = DefaultTreeAdapterMap["element"];
2425

@@ -72,10 +73,7 @@ const findContentTags = (element: ElementNode, tags = new Set<string>()) => {
7273
return tags;
7374
};
7475

75-
export const generateFragmentFromHtml = (
76-
html: string,
77-
options?: { unknownTags?: boolean }
78-
): WebstudioFragment => {
76+
export const generateFragmentFromHtml = (html: string): WebstudioFragment => {
7977
const attributeTypes = getAttributeTypes();
8078
const instances = new Map<Instance["id"], Instance>();
8179
const styleSourceSelections: StyleSourceSelection[] = [];
@@ -98,6 +96,7 @@ export const generateFragmentFromHtml = (
9896
breakpoints.push(baseBreakpoint);
9997
return baseBreakpoint.id;
10098
};
99+
101100
const createLocalStyles = (instanceId: string, css: string) => {
102101
const localStyleSource: StyleSource = {
103102
type: "local",
@@ -117,9 +116,8 @@ export const generateFragmentFromHtml = (
117116

118117
const convertElementToInstance = (node: ElementNode) => {
119118
if (
120-
node.tagName === "svg" &&
121-
node.sourceCodeLocation &&
122-
options?.unknownTags
119+
(node.tagName === "script" || node.tagName === "style") &&
120+
node.sourceCodeLocation
123121
) {
124122
const { startCol, startOffset, endOffset } = node.sourceCodeLocation;
125123
const indent = startCol - 1;
@@ -138,18 +136,26 @@ export const generateFragmentFromHtml = (
138136
type: "instance",
139137
id: getNewId(),
140138
component: "HtmlEmbed",
139+
label: capitalCase(node.tagName),
141140
children: [],
142141
};
143142
instances.set(instance.id, instance);
144-
const name = "code";
145-
const codeProp: Prop = {
146-
id: `${instance.id}:${name}`,
143+
if (node.tagName === "script") {
144+
props.push({
145+
id: `${instance.id}:clientOnly`,
146+
instanceId: instance.id,
147+
name: "clientOnly",
148+
type: "boolean",
149+
value: true,
150+
});
151+
}
152+
props.push({
153+
id: `${instance.id}:code`,
147154
instanceId: instance.id,
148-
name,
155+
name: "code",
149156
type: "string",
150157
value: htmlFragment,
151-
};
152-
props.push(codeProp);
158+
});
153159
return { type: "id" as const, value: instance.id };
154160
}
155161
if (!tags.includes(node.tagName)) {

0 commit comments

Comments
 (0)