experimental: Allow json in template literals (#3886)

## Description
closes #3872


- [x] - Builder support
- [x] - Publish support

## Steps for reproduction

Example

https://add-json.staging.webstudio.is/builder/8160e16d-5ab4-4249-86de-0c98a64526a3

https://test-json-ki9yj.wstd.work/

## 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
This commit is contained in:
Ivan Starkov authored and GitHub committed 2024-08-11 11:50:18 +03:00
1 parent 403d7b2328
commit 13da170ef4
38 files changed
+888 -173

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import {
Body as Body,
Heading as Heading,
+1 -1
View File
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import {
Body as Body,
Heading as Heading,
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import { Body as Body } from "@webstudio-is/sdk-components-react-remix";
import { Heading as Heading } from "@webstudio-is/sdk-components-react";
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import {
Body as Body,
Link as Link,
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import {
Body as Body,
Link as Link,
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import { XmlNode } from "@webstudio-is/sdk-components-react";
export const siteName = "Fixture Site";
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import { Body as Body } from "@webstudio-is/sdk-components-react-remix";
import { Heading as Heading } from "@webstudio-is/sdk-components-react";
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import {
Body as Body,
Link as Link,
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import { Body as Body } from "@webstudio-is/sdk-components-react-remix";
import { Heading as Heading } from "@webstudio-is/sdk-components-react";
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import {
Body as Body,
Link as Link,
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import { Body as Body } from "@webstudio-is/sdk-components-react-remix";
import { Heading as Heading } from "@webstudio-is/sdk-components-react";
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import {
Body as Body,
Link as Link,
@@ -1,23 +1,22 @@
{
"build": {
"id": "3dce3f4f-3c20-46e5-a885-d1b543db6237",
"id": "0f80ffe8-7059-4577-8ef3-232f3aded028",
"projectId": "cddc1d44-af37-4cb6-a430-d300cf6f932d",
"version": 257,
"createdAt": "2024-05-30T16:07:53.141",
"updatedAt": "2024-05-30T16:07:53.141+00:00",
"version": 300,
"createdAt": "2024-08-07T15:25:00.822",
"updatedAt": "2024-08-07T15:25:00.822+00:00",
"pages": {
"meta": {
"siteName": "KittyGuardedZone",
"faviconAssetId": "88d5e2ff-b8f2-4899-aaf8-dde4ade6da10",
"code": "<script>console.log('KittyGuardedZone')</script>\n"
},
"settings": {
"atomicStyles": true
},
"homePage": {
"id": "7Db64ZXgYiRqKSQNR-qTQ",
"name": "Home",
"title": "\"The Ultimate Cat Protection Zone\"",
"rootInstanceId": "On9cvWCxr5rdZtY9O1Bv0",
"systemDataSourceId": "u9wtUkt2-E0RDaljl_I6E",
"meta": {
"description": "\"Dive into the world of felines and discover why some whiskers are best left untouched. From intriguing cat behaviors to protective measures, \\nKittyGuardedZone is your go-to hub for all things 'hands-off' in the cat realm.\"",
"socialImageAssetId": "cd939c56-bcdd-4e64-bd9c-567a9bccd3da",
@@ -28,61 +27,61 @@
}
]
},
"rootInstanceId": "On9cvWCxr5rdZtY9O1Bv0",
"path": "",
"systemDataSourceId": "u9wtUkt2-E0RDaljl_I6E"
"path": ""
},
"pages": [
{
"id": "xfvB4UThQXmQ_OubPYrkg",
"name": "radix excluded from the search",
"title": "\"Radix Revelations: Unraveling the Feline Mystique\"",
"rootInstanceId": "uKWGyE9JY3cPwY-xI9vk6",
"systemDataSourceId": "GqDH_PSDF_8QXqPW2A0un",
"meta": {
"description": "\"Delve deep into the radix roots of feline behaviors. At KittyNoTouchy, we dissect the core essence, or 'radix', of what makes cats the enigmatic creatures they are. Join us as we explore the radix of their instincts, habits, and quirks.\"",
"excludePageFromSearch": "true",
"socialImageAssetId": "88d5e2ff-b8f2-4899-aaf8-dde4ade6da10"
},
"rootInstanceId": "uKWGyE9JY3cPwY-xI9vk6",
"path": "/radix",
"systemDataSourceId": "GqDH_PSDF_8QXqPW2A0un"
"path": "/radix"
},
{
"id": "szYLvBduHPmbtqQKCDY0b",
"name": "RouteWithSymbols",
"title": "\"RouteWithSymbols\"",
"rootInstanceId": "EDEfpMPRqDejthtwkH7ws",
"systemDataSourceId": "UiZtSC5BK8Ub-Ow7-XOdW",
"meta": {
"description": "\"\""
},
"rootInstanceId": "EDEfpMPRqDejthtwkH7ws",
"path": "/_route_with_symbols_",
"systemDataSourceId": "UiZtSC5BK8Ub-Ow7-XOdW"
"path": "/_route_with_symbols_"
},
{
"id": "U1tRJl2ERr8_OFe0g9cN_",
"name": "form",
"title": "\"form\"",
"rootInstanceId": "a-4nDFkaWy4px1fn38XWJ",
"systemDataSourceId": "-CX9SNsPSue42RkfxjL8_",
"meta": {
"description": "\"\""
},
"rootInstanceId": "a-4nDFkaWy4px1fn38XWJ",
"path": "/form",
"systemDataSourceId": "-CX9SNsPSue42RkfxjL8_"
"path": "/form"
},
{
"id": "-J9I4Oo6mONfQlf_3-OqG",
"name": "heading-with-id",
"title": "\"heading-with-id\"",
"rootInstanceId": "O-ljaGZQ0iRNTlEshMkgE",
"systemDataSourceId": "Q6XrK4-ZZUdXsocdiWqVk",
"meta": {
"description": "\"\""
},
"rootInstanceId": "O-ljaGZQ0iRNTlEshMkgE",
"path": "/heading-with-id",
"systemDataSourceId": "Q6XrK4-ZZUdXsocdiWqVk"
"path": "/heading-with-id"
},
{
"id": "TS39WeBd0Qr3QgbSlzdf2",
"name": "resources",
"title": "\"resources\"",
"rootInstanceId": "AWY2qZfpbykoiWELeJhse",
"systemDataSourceId": "keP0A66msOpEL6HbrC9OJ",
"meta": {
"description": "\"\"",
"excludePageFromSearch": "false",
@@ -94,14 +93,14 @@
}
]
},
"rootInstanceId": "AWY2qZfpbykoiWELeJhse",
"path": "/resources",
"systemDataSourceId": "keP0A66msOpEL6HbrC9OJ"
"path": "/resources"
},
{
"id": "Pnz-BxUm6XmvFygk_XdEy",
"name": "Nested Page",
"title": "\"Nested Page\"",
"rootInstanceId": "L0ZXd5F9xk9Rsl9ORzIkJ",
"systemDataSourceId": "8qqAbq6TEA3ccoCagjlZL",
"meta": {
"description": "\"\"",
"excludePageFromSearch": "false",
@@ -113,9 +112,28 @@
}
]
},
"rootInstanceId": "L0ZXd5F9xk9Rsl9ORzIkJ",
"path": "/nested-page",
"systemDataSourceId": "8qqAbq6TEA3ccoCagjlZL"
"path": "/nested-page"
},
{
"id": "9xlwLSHxuk8HmS3-EEGcf",
"name": "expressions",
"title": "\"Untitled\"",
"rootInstanceId": "MTUwXTcUJcWmr9iGvdcoH",
"systemDataSourceId": "8tTIagkgzmdy4T4qzKXqd",
"meta": {
"description": "\"\"",
"excludePageFromSearch": "true",
"language": "\"\"",
"socialImageUrl": "\"\"",
"status": "200",
"redirect": "\"\"",
"documentType": "html",
"custom": []
},
"marketplace": {
"include": false
},
"path": "/expressions"
}
],
"folders": [
@@ -130,7 +148,8 @@
"U1tRJl2ERr8_OFe0g9cN_",
"-J9I4Oo6mONfQlf_3-OqG",
"TS39WeBd0Qr3QgbSlzdf2",
"42cWhASQ3tTtKDnsvzhUF"
"42cWhASQ3tTtKDnsvzhUF",
"9xlwLSHxuk8HmS3-EEGcf"
]
},
{
@@ -677,18 +696,6 @@
}
}
],
[
"ZLyayr_Puk3HM35c44F2J:UoTkWyaFuTYJihS3MFYK5:overflow:",
{
"breakpointId": "UoTkWyaFuTYJihS3MFYK5",
"styleSourceId": "ZLyayr_Puk3HM35c44F2J",
"property": "overflow",
"value": {
"type": "keyword",
"value": "hidden"
}
}
],
[
"ZLyayr_Puk3HM35c44F2J:UoTkWyaFuTYJihS3MFYK5:fontSize:",
{
@@ -1020,18 +1027,6 @@
}
}
],
[
"4pgxDxhhX5AvMmL4olZw0:UoTkWyaFuTYJihS3MFYK5:overflow:",
{
"breakpointId": "UoTkWyaFuTYJihS3MFYK5",
"styleSourceId": "4pgxDxhhX5AvMmL4olZw0",
"property": "overflow",
"value": {
"type": "keyword",
"value": "hidden"
}
}
],
[
"4pgxDxhhX5AvMmL4olZw0:UoTkWyaFuTYJihS3MFYK5:fontSize:",
{
@@ -1363,18 +1358,6 @@
}
}
],
[
"EG1HUMyU4Rw4xD2pnGi28:UoTkWyaFuTYJihS3MFYK5:overflow:",
{
"breakpointId": "UoTkWyaFuTYJihS3MFYK5",
"styleSourceId": "EG1HUMyU4Rw4xD2pnGi28",
"property": "overflow",
"value": {
"type": "keyword",
"value": "hidden"
}
}
],
[
"EG1HUMyU4Rw4xD2pnGi28:UoTkWyaFuTYJihS3MFYK5:fontSize:",
{
@@ -1450,6 +1433,78 @@
"value": "block"
}
}
],
[
"ZLyayr_Puk3HM35c44F2J:UoTkWyaFuTYJihS3MFYK5:overflowX:",
{
"breakpointId": "UoTkWyaFuTYJihS3MFYK5",
"styleSourceId": "ZLyayr_Puk3HM35c44F2J",
"property": "overflowX",
"value": {
"type": "keyword",
"value": "hidden"
}
}
],
[
"ZLyayr_Puk3HM35c44F2J:UoTkWyaFuTYJihS3MFYK5:overflowY:",
{
"breakpointId": "UoTkWyaFuTYJihS3MFYK5",
"styleSourceId": "ZLyayr_Puk3HM35c44F2J",
"property": "overflowY",
"value": {
"type": "keyword",
"value": "hidden"
}
}
],
[
"4pgxDxhhX5AvMmL4olZw0:UoTkWyaFuTYJihS3MFYK5:overflowX:",
{
"breakpointId": "UoTkWyaFuTYJihS3MFYK5",
"styleSourceId": "4pgxDxhhX5AvMmL4olZw0",
"property": "overflowX",
"value": {
"type": "keyword",
"value": "hidden"
}
}
],
[
"4pgxDxhhX5AvMmL4olZw0:UoTkWyaFuTYJihS3MFYK5:overflowY:",
{
"breakpointId": "UoTkWyaFuTYJihS3MFYK5",
"styleSourceId": "4pgxDxhhX5AvMmL4olZw0",
"property": "overflowY",
"value": {
"type": "keyword",
"value": "hidden"
}
}
],
[
"EG1HUMyU4Rw4xD2pnGi28:UoTkWyaFuTYJihS3MFYK5:overflowX:",
{
"breakpointId": "UoTkWyaFuTYJihS3MFYK5",
"styleSourceId": "EG1HUMyU4Rw4xD2pnGi28",
"property": "overflowX",
"value": {
"type": "keyword",
"value": "hidden"
}
}
],
[
"EG1HUMyU4Rw4xD2pnGi28:UoTkWyaFuTYJihS3MFYK5:overflowY:",
{
"breakpointId": "UoTkWyaFuTYJihS3MFYK5",
"styleSourceId": "EG1HUMyU4Rw4xD2pnGi28",
"property": "overflowY",
"value": {
"type": "keyword",
"value": "hidden"
}
}
]
],
"styleSources": [
@@ -2163,6 +2218,16 @@
"type": "expression",
"value": "$ws$dataSource$MShox7Ngja3P8_t6XGMBw.name"
}
],
[
"te2oQYASF3qsGMGekIcsF",
{
"id": "te2oQYASF3qsGMGekIcsF",
"instanceId": "sHn4Mh7DJ5X0RiETB-zVc",
"name": "code",
"type": "expression",
"value": "`<script>\nconst a = ${$ws$dataSource$LYqm_7uq8W4T7BKky9zcj.data.args}\n\nconst b = ${$ws$dataSource$KqMdUAQ4YmObkN1ofVzeO}\n\nconsole.log(a, b);\n</script>`"
}
]
],
"dataSources": [
@@ -2227,64 +2292,98 @@
[
"u9wtUkt2-E0RDaljl_I6E",
{
"type": "parameter",
"id": "u9wtUkt2-E0RDaljl_I6E",
"scopeInstanceId": "On9cvWCxr5rdZtY9O1Bv0",
"name": "system",
"type": "parameter"
"name": "system"
}
],
[
"GqDH_PSDF_8QXqPW2A0un",
{
"type": "parameter",
"id": "GqDH_PSDF_8QXqPW2A0un",
"scopeInstanceId": "uKWGyE9JY3cPwY-xI9vk6",
"name": "system",
"type": "parameter"
"name": "system"
}
],
[
"UiZtSC5BK8Ub-Ow7-XOdW",
{
"type": "parameter",
"id": "UiZtSC5BK8Ub-Ow7-XOdW",
"scopeInstanceId": "EDEfpMPRqDejthtwkH7ws",
"name": "system",
"type": "parameter"
"name": "system"
}
],
[
"-CX9SNsPSue42RkfxjL8_",
{
"type": "parameter",
"id": "-CX9SNsPSue42RkfxjL8_",
"scopeInstanceId": "a-4nDFkaWy4px1fn38XWJ",
"name": "system",
"type": "parameter"
"name": "system"
}
],
[
"Q6XrK4-ZZUdXsocdiWqVk",
{
"type": "parameter",
"id": "Q6XrK4-ZZUdXsocdiWqVk",
"scopeInstanceId": "O-ljaGZQ0iRNTlEshMkgE",
"name": "system",
"type": "parameter"
"name": "system"
}
],
[
"keP0A66msOpEL6HbrC9OJ",
{
"type": "parameter",
"id": "keP0A66msOpEL6HbrC9OJ",
"scopeInstanceId": "AWY2qZfpbykoiWELeJhse",
"name": "system",
"type": "parameter"
"name": "system"
}
],
[
"8qqAbq6TEA3ccoCagjlZL",
{
"type": "parameter",
"id": "8qqAbq6TEA3ccoCagjlZL",
"scopeInstanceId": "L0ZXd5F9xk9Rsl9ORzIkJ",
"name": "system",
"type": "parameter"
"name": "system"
}
],
[
"8tTIagkgzmdy4T4qzKXqd",
{
"type": "parameter",
"id": "8tTIagkgzmdy4T4qzKXqd",
"scopeInstanceId": "MTUwXTcUJcWmr9iGvdcoH",
"name": "system"
}
],
[
"LYqm_7uq8W4T7BKky9zcj",
{
"type": "resource",
"id": "LYqm_7uq8W4T7BKky9zcj",
"scopeInstanceId": "MTUwXTcUJcWmr9iGvdcoH",
"name": "jsonResourceVariable",
"resourceId": "fjMzCru8O2U31xY2P1Ovr"
}
],
[
"KqMdUAQ4YmObkN1ofVzeO",
{
"type": "variable",
"id": "KqMdUAQ4YmObkN1ofVzeO",
"scopeInstanceId": "MTUwXTcUJcWmr9iGvdcoH",
"name": "jsonVar",
"value": {
"type": "json",
"value": {
"hello": "world"
}
}
}
]
],
@@ -2299,6 +2398,16 @@
"headers": [],
"body": ""
}
],
[
"fjMzCru8O2U31xY2P1Ovr",
{
"id": "fjMzCru8O2U31xY2P1Ovr",
"name": "jsonResourceVariable",
"method": "get",
"url": "\"https://httpbin.org/get?hello=world\"",
"headers": []
}
]
],
"instances": [
@@ -3341,9 +3450,52 @@
}
]
}
],
[
"MTUwXTcUJcWmr9iGvdcoH",
{
"type": "instance",
"id": "MTUwXTcUJcWmr9iGvdcoH",
"component": "Body",
"children": [
{
"type": "id",
"value": "DiiJ6c5jxLGydyTZTCdg0"
},
{
"type": "id",
"value": "sHn4Mh7DJ5X0RiETB-zVc"
}
]
}
],
[
"DiiJ6c5jxLGydyTZTCdg0",
{
"type": "instance",
"id": "DiiJ6c5jxLGydyTZTCdg0",
"component": "Heading",
"label": "worl",
"children": [
{
"type": "expression",
"value": "`${$ws$dataSource$LYqm_7uq8W4T7BKky9zcj.data.args}`"
}
]
}
],
[
"sHn4Mh7DJ5X0RiETB-zVc",
{
"type": "instance",
"id": "sHn4Mh7DJ5X0RiETB-zVc",
"component": "HtmlEmbed",
"children": []
}
]
],
"deployment": {
"destination": "saas",
"domains": [],
"projectDomain": "webstudio-fixture-project-a-0su3o"
},
@@ -3353,6 +3505,8 @@
"id": "7Db64ZXgYiRqKSQNR-qTQ",
"name": "Home",
"title": "\"The Ultimate Cat Protection Zone\"",
"rootInstanceId": "On9cvWCxr5rdZtY9O1Bv0",
"systemDataSourceId": "u9wtUkt2-E0RDaljl_I6E",
"meta": {
"description": "\"Dive into the world of felines and discover why some whiskers are best left untouched. From intriguing cat behaviors to protective measures, \\nKittyGuardedZone is your go-to hub for all things 'hands-off' in the cat realm.\"",
"socialImageAssetId": "cd939c56-bcdd-4e64-bd9c-567a9bccd3da",
@@ -3363,15 +3517,15 @@
}
]
},
"rootInstanceId": "On9cvWCxr5rdZtY9O1Bv0",
"path": "",
"systemDataSourceId": "u9wtUkt2-E0RDaljl_I6E"
"path": ""
},
"pages": [
{
"id": "7Db64ZXgYiRqKSQNR-qTQ",
"name": "Home",
"title": "\"The Ultimate Cat Protection Zone\"",
"rootInstanceId": "On9cvWCxr5rdZtY9O1Bv0",
"systemDataSourceId": "u9wtUkt2-E0RDaljl_I6E",
"meta": {
"description": "\"Dive into the world of felines and discover why some whiskers are best left untouched. From intriguing cat behaviors to protective measures, \\nKittyGuardedZone is your go-to hub for all things 'hands-off' in the cat realm.\"",
"socialImageAssetId": "cd939c56-bcdd-4e64-bd9c-567a9bccd3da",
@@ -3382,60 +3536,60 @@
}
]
},
"rootInstanceId": "On9cvWCxr5rdZtY9O1Bv0",
"path": "",
"systemDataSourceId": "u9wtUkt2-E0RDaljl_I6E"
"path": ""
},
{
"id": "xfvB4UThQXmQ_OubPYrkg",
"name": "radix excluded from the search",
"title": "\"Radix Revelations: Unraveling the Feline Mystique\"",
"rootInstanceId": "uKWGyE9JY3cPwY-xI9vk6",
"systemDataSourceId": "GqDH_PSDF_8QXqPW2A0un",
"meta": {
"description": "\"Delve deep into the radix roots of feline behaviors. At KittyNoTouchy, we dissect the core essence, or 'radix', of what makes cats the enigmatic creatures they are. Join us as we explore the radix of their instincts, habits, and quirks.\"",
"excludePageFromSearch": "true",
"socialImageAssetId": "88d5e2ff-b8f2-4899-aaf8-dde4ade6da10"
},
"rootInstanceId": "uKWGyE9JY3cPwY-xI9vk6",
"path": "/radix",
"systemDataSourceId": "GqDH_PSDF_8QXqPW2A0un"
"path": "/radix"
},
{
"id": "szYLvBduHPmbtqQKCDY0b",
"name": "RouteWithSymbols",
"title": "\"RouteWithSymbols\"",
"rootInstanceId": "EDEfpMPRqDejthtwkH7ws",
"systemDataSourceId": "UiZtSC5BK8Ub-Ow7-XOdW",
"meta": {
"description": "\"\""
},
"rootInstanceId": "EDEfpMPRqDejthtwkH7ws",
"path": "/_route_with_symbols_",
"systemDataSourceId": "UiZtSC5BK8Ub-Ow7-XOdW"
"path": "/_route_with_symbols_"
},
{
"id": "U1tRJl2ERr8_OFe0g9cN_",
"name": "form",
"title": "\"form\"",
"rootInstanceId": "a-4nDFkaWy4px1fn38XWJ",
"systemDataSourceId": "-CX9SNsPSue42RkfxjL8_",
"meta": {
"description": "\"\""
},
"rootInstanceId": "a-4nDFkaWy4px1fn38XWJ",
"path": "/form",
"systemDataSourceId": "-CX9SNsPSue42RkfxjL8_"
"path": "/form"
},
{
"id": "-J9I4Oo6mONfQlf_3-OqG",
"name": "heading-with-id",
"title": "\"heading-with-id\"",
"rootInstanceId": "O-ljaGZQ0iRNTlEshMkgE",
"systemDataSourceId": "Q6XrK4-ZZUdXsocdiWqVk",
"meta": {
"description": "\"\""
},
"rootInstanceId": "O-ljaGZQ0iRNTlEshMkgE",
"path": "/heading-with-id",
"systemDataSourceId": "Q6XrK4-ZZUdXsocdiWqVk"
"path": "/heading-with-id"
},
{
"id": "TS39WeBd0Qr3QgbSlzdf2",
"name": "resources",
"title": "\"resources\"",
"rootInstanceId": "AWY2qZfpbykoiWELeJhse",
"systemDataSourceId": "keP0A66msOpEL6HbrC9OJ",
"meta": {
"description": "\"\"",
"excludePageFromSearch": "false",
@@ -3447,14 +3601,14 @@
}
]
},
"rootInstanceId": "AWY2qZfpbykoiWELeJhse",
"path": "/resources",
"systemDataSourceId": "keP0A66msOpEL6HbrC9OJ"
"path": "/resources"
},
{
"id": "Pnz-BxUm6XmvFygk_XdEy",
"name": "Nested Page",
"title": "\"Nested Page\"",
"rootInstanceId": "L0ZXd5F9xk9Rsl9ORzIkJ",
"systemDataSourceId": "8qqAbq6TEA3ccoCagjlZL",
"meta": {
"description": "\"\"",
"excludePageFromSearch": "false",
@@ -3466,9 +3620,28 @@
}
]
},
"rootInstanceId": "L0ZXd5F9xk9Rsl9ORzIkJ",
"path": "/nested-page",
"systemDataSourceId": "8qqAbq6TEA3ccoCagjlZL"
"path": "/nested-page"
},
{
"id": "9xlwLSHxuk8HmS3-EEGcf",
"name": "expressions",
"title": "\"Untitled\"",
"rootInstanceId": "MTUwXTcUJcWmr9iGvdcoH",
"systemDataSourceId": "8tTIagkgzmdy4T4qzKXqd",
"meta": {
"description": "\"\"",
"excludePageFromSearch": "true",
"language": "\"\"",
"socialImageUrl": "\"\"",
"status": "200",
"redirect": "\"\"",
"documentType": "html",
"custom": []
},
"marketplace": {
"include": false
},
"path": "/expressions"
}
],
"assets": [
@@ -1,26 +1,26 @@
export const sitemap = [
{
path: "/",
lastModified: "2024-05-30",
lastModified: "2024-08-07",
},
{
path: "/_route_with_symbols_",
lastModified: "2024-05-30",
lastModified: "2024-08-07",
},
{
path: "/form",
lastModified: "2024-05-30",
lastModified: "2024-08-07",
},
{
path: "/heading-with-id",
lastModified: "2024-05-30",
lastModified: "2024-08-07",
},
{
path: "/resources",
lastModified: "2024-05-30",
lastModified: "2024-08-07",
},
{
path: "/nested/nested-page",
lastModified: "2024-05-30",
lastModified: "2024-08-07",
},
];
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import { Body as Body } from "@webstudio-is/sdk-components-react-remix";
import { Image as Image } from "@webstudio-is/sdk-components-react";
@@ -0,0 +1,48 @@
/* eslint-disable */
/* This is a auto generated file for building the project */
import type { PageMeta } from "@webstudio-is/sdk";
import type { System, ResourceRequest } from "@webstudio-is/sdk";
export const getResources = (_props: { system: System }) => {
const jsonResourceVariable_1: ResourceRequest = {
id: "fjMzCru8O2U31xY2P1Ovr",
name: "jsonResourceVariable",
url: "https://httpbin.org/get?hello=world",
method: "get",
headers: [],
};
const _data = new Map<string, ResourceRequest>([
["jsonResourceVariable_1", jsonResourceVariable_1],
]);
const _action = new Map<string, ResourceRequest>([]);
return { data: _data, action: _action };
};
export const getPageMeta = ({
system,
resources,
}: {
system: System;
resources: Record<string, any>;
}): PageMeta => {
return {
title: "Untitled",
description: "",
excludePageFromSearch: true,
language: "",
socialImageAssetName: undefined,
socialImageUrl: "",
status: 200,
redirect: "",
custom: [],
};
};
type Params = Record<string, string | undefined>;
export const getRemixParams = ({ ...params }: Params): Params => {
return params;
};
export const projectId = "cddc1d44-af37-4cb6-a430-d300cf6f932d";
export const contactEmail = "hello@webstudio.is";
@@ -0,0 +1,54 @@
/* eslint-disable */
/* This is a auto generated file for building the project */
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import { Body as Body } from "@webstudio-is/sdk-components-react-remix";
import {
Heading as Heading,
HtmlEmbed as HtmlEmbed,
} from "@webstudio-is/sdk-components-react";
export const siteName = "KittyGuardedZone";
export const favIconAsset: ImageAsset | undefined = {
id: "88d5e2ff-b8f2-4899-aaf8-dde4ade6da10",
name: "DALL_E_2023-10-30_12.39.46_-_Photo_logo_with_a_bold_cat_silhouette_centered_on_a_contrasting_background_designed_for_clarity_at_small_32x32_favicon_resolution_00h6cEA8u2pJRvVJv7hRe.png",
description: null,
projectId: "cddc1d44-af37-4cb6-a430-d300cf6f932d",
size: 268326,
type: "image",
format: "png",
createdAt: "2023-10-30T13:51:08.416",
meta: { width: 790, height: 786 },
};
// Font assets on current page (can be preloaded)
export const pageFontAssets: FontAsset[] = [];
export const pageBackgroundImageAssets: ImageAsset[] = [];
const Page = ({}: { system: any }) => {
let jsonResourceVariable = useResource("jsonResourceVariable_1");
let [jsonVar, set$jsonVar] = useVariableState<any>({ hello: "world" });
return (
<Body className="w-body">
<Heading className="w-heading">
{`${jsonResourceVariable?.data?.args}`}
</Heading>
<HtmlEmbed
code={`<script>
const a = ${jsonResourceVariable?.data?.args}
const b = ${jsonVar}
console.log(a, b);
</script>`}
className="w-html-embed"
/>
</Body>
);
};
export { Page };
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import {
Body as Body,
Form as Form,
@@ -36,8 +36,8 @@ export const pageFontAssets: FontAsset[] = [];
export const pageBackgroundImageAssets: ImageAsset[] = [];
const Page = ({}: { system: any }) => {
let [formState, set$formState] = useState<any>("initial");
let [formState_1, set$formState_1] = useState<any>("initial");
let [formState, set$formState] = useVariableState<any>("initial");
let [formState_1, set$formState_1] = useVariableState<any>("initial");
return (
<Body className="w-body">
<Form
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import { Body as Body } from "@webstudio-is/sdk-components-react-remix";
import { Heading as Heading } from "@webstudio-is/sdk-components-react";
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import { Body as Body } from "@webstudio-is/sdk-components-react-remix";
import { Heading as Heading } from "@webstudio-is/sdk-components-react";
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import { Body as Body } from "@webstudio-is/sdk-components-react-remix";
import {
Accordion as Accordion,
@@ -38,7 +38,7 @@ export const pageFontAssets: FontAsset[] = [];
export const pageBackgroundImageAssets: ImageAsset[] = [];
const Page = ({}: { system: any }) => {
let [accordionValue, set$accordionValue] = useState<any>("0");
let [accordionValue, set$accordionValue] = useVariableState<any>("0");
return (
<Body className="w-body">
<Accordion
@@ -64,7 +64,7 @@ const Page = ({}: { system: any }) => {
</Box>
</AccordionTrigger>
</AccordionHeader>
<AccordionContent className="w-item-content c1mxwmum c1j0j9ep c1gl2i2 c44srea">
<AccordionContent className="w-item-content c1j0j9ep c1gl2i2 c44srea c1xcxvc0 c12ct49k">
{"Yes. It adheres to the WAI-ARIA design pattern."}
</AccordionContent>
</AccordionItem>
@@ -82,7 +82,7 @@ const Page = ({}: { system: any }) => {
</Box>
</AccordionTrigger>
</AccordionHeader>
<AccordionContent className="w-item-content c1mxwmum c1j0j9ep c1gl2i2 c44srea">
<AccordionContent className="w-item-content c1j0j9ep c1gl2i2 c44srea c1xcxvc0 c12ct49k">
{
"Yes. It comes with default styles that matches the other components' aesthetic."
}
@@ -102,7 +102,7 @@ const Page = ({}: { system: any }) => {
</Box>
</AccordionTrigger>
</AccordionHeader>
<AccordionContent className="w-item-content c1mxwmum c1j0j9ep c1gl2i2 c44srea">
<AccordionContent className="w-item-content c1j0j9ep c1gl2i2 c44srea c1xcxvc0 c12ct49k">
{
"Yes. It's animated by default, but you can disable it if you prefer."
}
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import { Body as Body } from "@webstudio-is/sdk-components-react-remix";
import {
Box as Box,
@@ -3,7 +3,7 @@
import { Fragment, useState } from "react";
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
import { useResource } from "@webstudio-is/react-sdk/runtime";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import {
Body as Body,
Link as Link,
+6 -3
View File
@@ -368,15 +368,18 @@ html {
.c1vri55v {
transition-duration: 200ms;
}
.c1mxwmum {
overflow: hidden;
}
.c1j0j9ep {
font-size: 0.875rem;
}
.c1gl2i2 {
line-height: 1.25rem;
}
.c1xcxvc0 {
overflow-x: hidden;
}
.c12ct49k {
overflow-y: hidden;
}
.ch2exr5 {
display: block;
}
@@ -0,0 +1,342 @@
import {
type ServerRuntimeMetaFunction as MetaFunction,
type LinksFunction,
type LinkDescriptor,
type ActionFunctionArgs,
type LoaderFunctionArgs,
type HeadersFunction,
json,
redirect,
} from "@remix-run/server-runtime";
import { useLoaderData } from "@remix-run/react";
import {
isLocalResource,
loadResource,
loadResources,
formIdFieldName,
formBotFieldName,
} from "@webstudio-is/sdk";
import { ReactSdkContext } from "@webstudio-is/react-sdk/runtime";
import {
Page,
siteName,
favIconAsset,
pageFontAssets,
pageBackgroundImageAssets,
} from "../__generated__/[expressions]._index";
import {
getResources,
getPageMeta,
getRemixParams,
projectId,
contactEmail,
} from "../__generated__/[expressions]._index.server";
import { assetBaseUrl, imageBaseUrl, imageLoader } from "../constants.mjs";
import css from "../__generated__/index.css?url";
import { sitemap } from "../__generated__/$resources.sitemap.xml";
const customFetch: typeof fetch = (input, init) => {
if (typeof input !== "string") {
return fetch(input, init);
}
if (isLocalResource(input, "sitemap.xml")) {
// @todo: dynamic import sitemap ???
const response = new Response(JSON.stringify(sitemap));
response.headers.set("content-type", "application/json; charset=utf-8");
return Promise.resolve(response);
}
return fetch(input, init);
};
export const loader = async (arg: LoaderFunctionArgs) => {
const url = new URL(arg.request.url);
const host =
arg.request.headers.get("x-forwarded-host") ||
arg.request.headers.get("host") ||
"";
url.host = host;
url.protocol = "https";
const params = getRemixParams(arg.params);
const system = {
params,
search: Object.fromEntries(url.searchParams),
origin: url.origin,
};
const resources = await loadResources(
customFetch,
getResources({ system }).data
);
const pageMeta = getPageMeta({ system, resources });
if (pageMeta.redirect) {
const status =
pageMeta.status === 301 || pageMeta.status === 302
? pageMeta.status
: 302;
return redirect(pageMeta.redirect, status);
}
// typecheck
arg.context.EXCLUDE_FROM_SEARCH satisfies boolean;
if (arg.context.EXCLUDE_FROM_SEARCH) {
pageMeta.excludePageFromSearch = arg.context.EXCLUDE_FROM_SEARCH;
}
return json(
{
host,
url: url.href,
system,
resources,
pageMeta,
},
// No way for current information to change, so add cache for 10 minutes
// In case of CRM Data, this should be set to 0
{
status: pageMeta.status,
headers: {
"Cache-Control": "public, max-age=600",
},
}
);
};
export const headers: HeadersFunction = () => {
return {
"Cache-Control": "public, max-age=0, must-revalidate",
};
};
export const meta: MetaFunction<typeof loader> = ({ data }) => {
const metas: ReturnType<MetaFunction> = [];
if (data === undefined) {
return metas;
}
const { pageMeta } = data;
if (data.url) {
metas.push({
property: "og:url",
content: data.url,
});
}
if (pageMeta.title) {
metas.push({ title: pageMeta.title });
metas.push({
property: "og:title",
content: pageMeta.title,
});
}
metas.push({ property: "og:type", content: "website" });
const origin = `https://${data.host}`;
if (siteName) {
metas.push({
property: "og:site_name",
content: siteName,
});
metas.push({
"script:ld+json": {
"@context": "https://schema.org",
"@type": "WebSite",
name: siteName,
url: origin,
},
});
}
if (pageMeta.excludePageFromSearch) {
metas.push({
name: "robots",
content: "noindex, nofollow",
});
}
if (pageMeta.description) {
metas.push({
name: "description",
content: pageMeta.description,
});
metas.push({
property: "og:description",
content: pageMeta.description,
});
}
if (pageMeta.socialImageAssetName) {
metas.push({
property: "og:image",
content: `https://${data.host}${imageLoader({
src: pageMeta.socialImageAssetName,
// Do not transform social image (not enough information do we need to do this)
format: "raw",
})}`,
});
} else if (pageMeta.socialImageUrl) {
metas.push({
property: "og:image",
content: pageMeta.socialImageUrl,
});
}
metas.push(...pageMeta.custom);
return metas;
};
export const links: LinksFunction = () => {
const result: LinkDescriptor[] = [];
result.push({
rel: "stylesheet",
href: css,
});
if (favIconAsset) {
result.push({
rel: "icon",
href: imageLoader({
src: favIconAsset.name,
// width,height must be multiple of 48 https://developers.google.com/search/docs/appearance/favicon-in-search
width: 144,
height: 144,
fit: "pad",
quality: 100,
format: "auto",
}),
type: undefined,
});
}
for (const asset of pageFontAssets) {
result.push({
rel: "preload",
href: `${assetBaseUrl}${asset.name}`,
as: "font",
crossOrigin: "anonymous",
});
}
for (const backgroundImageAsset of pageBackgroundImageAssets) {
result.push({
rel: "preload",
href: `${assetBaseUrl}${backgroundImageAsset.name}`,
as: "image",
});
}
return result;
};
const getRequestHost = (request: Request): string =>
request.headers.get("x-forwarded-host") || request.headers.get("host") || "";
export const action = async ({
request,
context,
}: ActionFunctionArgs): Promise<
{ success: true } | { success: false; errors: string[] }
> => {
try {
const url = new URL(request.url);
url.host = getRequestHost(request);
const formData = await request.formData();
const system = {
params: {},
search: {},
origin: url.origin,
};
const resourceName = formData.get(formIdFieldName);
let resource =
typeof resourceName === "string"
? getResources({ system }).action.get(resourceName)
: undefined;
const formBotValue = formData.get(formBotFieldName);
if (formBotValue == null || typeof formBotValue !== "string") {
throw new Error("Form bot field not found");
}
const submitTime = parseInt(formBotValue, 16);
// Assumes that the difference between the server time and the form submission time,
// including any client-server time drift, is within a 5-minute range.
// Note: submitTime might be NaN because formBotValue can be any string used for logging purposes.
// Example: `formBotValue: jsdom`, or `formBotValue: headless-env`
if (
Number.isNaN(submitTime) ||
Math.abs(Date.now() - submitTime) > 1000 * 60 * 5
) {
throw new Error(`Form bot value invalid ${formBotValue}`);
}
formData.delete(formIdFieldName);
formData.delete(formBotFieldName);
if (resource) {
resource.headers.push({
name: "Content-Type",
value: "application/json",
});
resource.body = Object.fromEntries(formData);
} else {
if (contactEmail === undefined) {
throw new Error("Contact email not found");
}
resource = context.getDefaultActionResource?.({
url,
projectId,
contactEmail,
formData,
});
}
if (resource === undefined) {
throw Error("Resource not found");
}
const { ok, statusText } = await loadResource(fetch, resource);
if (ok) {
return { success: true };
}
return { success: false, errors: [statusText] };
} catch (error) {
console.error(error);
return {
success: false,
errors: [error instanceof Error ? error.message : "Unknown error"],
};
}
};
const Outlet = () => {
const { system, resources, url } = useLoaderData<typeof loader>();
return (
<ReactSdkContext.Provider
value={{
imageLoader,
assetBaseUrl,
imageBaseUrl,
resources,
}}
>
{/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
<Page key={url} system={system} />
</ReactSdkContext.Provider>
);
};
export default Outlet;
+1 -1
View File
@@ -6,7 +6,7 @@
"typecheck": "tsc",
"cli": "NODE_OPTIONS='--conditions=webstudio --import=tsx' webstudio",
"fixtures:link": "pnpm cli link --link ${BUILDER_URL:-https://main.development.webstudio.is}'/builder/cddc1d44-af37-4cb6-a430-d300cf6f932d?authToken=1cdc6026-dd5b-4624-b89b-9bd45e9bcc3d'",
"fixtures:sync": "pnpm cli sync --buildId 3dce3f4f-3c20-46e5-a885-d1b543db6237 && pnpm prettier --write ./.webstudio/",
"fixtures:sync": "pnpm cli sync --buildId 0f80ffe8-7059-4577-8ef3-232f3aded028 && pnpm prettier --write ./.webstudio/",
"fixtures:build": "pnpm cli build --template vercel --template internal --preview && pnpm prettier --write ./app/ ./package.json ./tsconfig.json"
},
"private": true,