Skip to content

Commit 0d1d716

Browse files
committed
experimental: paste tailwind
Ref #2651 Added "Paste Html With Tailwind Classes" command Also fixed a few issues with css parser.
1 parent 8b60ff7 commit 0d1d716

22 files changed

+2212
-101
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import {
2626
deleteInstanceMutable,
2727
extractWebstudioFragment,
28+
insertWebstudioFragmentAt,
2829
insertWebstudioFragmentCopy,
2930
updateWebstudioData,
3031
} from "~/shared/instance-utils";
@@ -48,6 +49,8 @@ import {
4849
isRichTextContent,
4950
isTreeSatisfyingContentModel,
5051
} from "~/shared/content-model";
52+
import { generateFragmentFromHtml } from "~/shared/html";
53+
import { generateFragmentFromTailwind } from "~/shared/tailwind/tailwind";
5154

5255
export const $styleSourceInputElement = atom<HTMLInputElement | undefined>();
5356

@@ -519,6 +522,16 @@ export const { emitCommand, subscribeCommands } = createCommandsEmitter({
519522
handler: () => unwrap(),
520523
},
521524

525+
{
526+
name: "pasteHtmlWithTailwindClasses",
527+
handler: async () => {
528+
let html = await navigator.clipboard.readText();
529+
let fragment = generateFragmentFromHtml(html);
530+
fragment = await generateFragmentFromTailwind(fragment);
531+
return insertWebstudioFragmentAt(fragment);
532+
},
533+
},
534+
522535
// history
523536

524537
{

apps/builder/app/builder/shared/css-editor/parse-style-input.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ describe("parseStyleInput", () => {
55
test("parses custom property", () => {
66
const result = parseStyleInput("--custom-color");
77
expect(result).toEqual(
8-
new Map([["--custom-color", { type: "keyword", value: "unset" }]])
8+
new Map([["--custom-color", { type: "unparsed", value: "" }]])
99
);
1010
});
1111

@@ -43,7 +43,7 @@ describe("parseStyleInput", () => {
4343
test("converts unknown property to custom property assuming user forgot to add --", () => {
4444
const result = parseStyleInput("notaproperty");
4545
expect(result).toEqual(
46-
new Map([["--notaproperty", { type: "keyword", value: "unset" }]])
46+
new Map([["--notaproperty", { type: "unparsed", value: "" }]])
4747
);
4848
});
4949

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

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -223,15 +223,20 @@ test("generate style attribute as local styles", () => {
223223
);
224224
});
225225

226-
test("paste svg as html embed", () => {
226+
test("optionally paste svg as html embed", () => {
227227
expect(
228-
generateFragmentFromHtml(`
229-
<div>
230-
<svg viewBox="0 0 20 20">
231-
<rect x="5" y="5" width="10" height="10" />
232-
</svg>
233-
</div>
234-
`)
228+
generateFragmentFromHtml(
229+
`
230+
<div>
231+
<svg viewBox="0 0 20 20">
232+
<rect x="5" y="5" width="10" height="10" />
233+
</svg>
234+
</div>
235+
`,
236+
{
237+
unknownTags: true,
238+
}
239+
)
235240
).toEqual(
236241
renderTemplate(
237242
<ws.element ws:tag="div">

apps/builder/app/shared/html.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ const findContentTags = (element: ElementNode, tags = new Set<string>()) => {
7171
return tags;
7272
};
7373

74-
export const generateFragmentFromHtml = (html: string): WebstudioFragment => {
74+
export const generateFragmentFromHtml = (
75+
html: string,
76+
options?: { unknownTags?: boolean }
77+
): WebstudioFragment => {
7578
const attributeTypes = getAttributeTypes();
7679
const instances = new Map<Instance["id"], Instance>();
7780
const styleSourceSelections: StyleSourceSelection[] = [];
@@ -112,7 +115,11 @@ export const generateFragmentFromHtml = (html: string): WebstudioFragment => {
112115
};
113116

114117
const convertElementToInstance = (node: ElementNode) => {
115-
if (node.tagName === "svg" && node.sourceCodeLocation) {
118+
if (
119+
node.tagName === "svg" &&
120+
node.sourceCodeLocation &&
121+
options?.unknownTags
122+
) {
116123
const { startCol, startOffset, endOffset } = node.sourceCodeLocation;
117124
const indent = startCol - 1;
118125
const htmlFragment = html

apps/builder/app/shared/style-object-model.test.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,35 @@ test("support custom properties in unparsed values", () => {
517517
});
518518
});
519519

520+
test("support empty custom properties", () => {
521+
const model = createModel({
522+
css: `
523+
bodyLocal {
524+
--inset: ;
525+
box-shadow: var(--inset) red;
526+
}
527+
`,
528+
jsx: <$.Body ws:id="body" class="bodyLocal"></$.Body>,
529+
});
530+
expect(
531+
getComputedStyleDecl({
532+
model,
533+
instanceSelector: ["body"],
534+
property: "--inset",
535+
}).computedValue
536+
).toEqual({ type: "unparsed", value: "" });
537+
expect(
538+
getComputedStyleDecl({
539+
model,
540+
instanceSelector: ["body"],
541+
property: "box-shadow",
542+
}).computedValue
543+
).toEqual({
544+
type: "layers",
545+
value: [{ type: "unparsed", value: "red" }],
546+
});
547+
});
548+
520549
test("use fallback value when custom property does not exist", () => {
521550
const model = createModel({
522551
css: `

0 commit comments

Comments
 (0)