webstudio 0.151.0 → 0.163.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cli.js +96 -59
- package/package.json +9 -9
- package/templates/cloudflare/functions/[[path]].ts +0 -1
- package/templates/defaults/app/{routes/[sitemap.xml].tsx → route-templates/default-sitemap.tsx} +1 -1
- package/templates/defaults/app/{routes/template.tsx → route-templates/html.tsx} +75 -51
- package/templates/defaults/app/route-templates/xml.tsx +61 -0
- package/templates/defaults/package.json +7 -7
- package/templates/defaults/app/__generated__/[sitemap.xml].ts +0 -9
package/lib/cli.js
CHANGED
|
@@ -68,16 +68,16 @@ var isFileExists = async (filePath) => {
|
|
|
68
68
|
return false;
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
|
-
var
|
|
71
|
+
var createFileIfNotExists = async (filePath, content) => {
|
|
72
72
|
const dir = dirname(filePath);
|
|
73
|
-
await
|
|
73
|
+
await createFolderIfNotExists(dir);
|
|
74
74
|
try {
|
|
75
75
|
await access(filePath, constants.F_OK);
|
|
76
76
|
} catch {
|
|
77
77
|
await writeFile(filePath, content || "", "utf8");
|
|
78
78
|
}
|
|
79
79
|
};
|
|
80
|
-
var
|
|
80
|
+
var createFolderIfNotExists = async (folderPath) => {
|
|
81
81
|
try {
|
|
82
82
|
await access(folderPath, constants.F_OK);
|
|
83
83
|
} catch {
|
|
@@ -142,7 +142,7 @@ var link = async (options) => {
|
|
|
142
142
|
const localConfig = {
|
|
143
143
|
projectId
|
|
144
144
|
};
|
|
145
|
-
await
|
|
145
|
+
await createFileIfNotExists(
|
|
146
146
|
join2(cwd(), LOCAL_CONFIG_FILE),
|
|
147
147
|
JSON.stringify(localConfig, null, 2)
|
|
148
148
|
);
|
|
@@ -231,7 +231,7 @@ var sync = async (options) => {
|
|
|
231
231
|
project;
|
|
232
232
|
spinner.text = "Saving project data to config file";
|
|
233
233
|
const localBuildFilePath = join3(cwd2(), LOCAL_DATA_FILE);
|
|
234
|
-
await
|
|
234
|
+
await createFileIfNotExists(localBuildFilePath);
|
|
235
235
|
await writeFile3(localBuildFilePath, JSON.stringify(project, null, 2), "utf8");
|
|
236
236
|
spinner.succeed("Project data synced successfully");
|
|
237
237
|
};
|
|
@@ -265,7 +265,7 @@ import {
|
|
|
265
265
|
normalizeProps,
|
|
266
266
|
generateRemixRoute,
|
|
267
267
|
generateRemixParams,
|
|
268
|
-
|
|
268
|
+
isCoreComponent
|
|
269
269
|
} from "@webstudio-is/react-sdk";
|
|
270
270
|
import {
|
|
271
271
|
createScope,
|
|
@@ -288,7 +288,7 @@ var downloadAsset = async (url, name, assetBaseUrl) => {
|
|
|
288
288
|
try {
|
|
289
289
|
await access2(assetPath);
|
|
290
290
|
} catch {
|
|
291
|
-
await
|
|
291
|
+
await createFolderIfNotExists(dirname2(assetPath));
|
|
292
292
|
try {
|
|
293
293
|
const response = await fetch(url);
|
|
294
294
|
if (!response.ok) {
|
|
@@ -480,7 +480,7 @@ var prebuild = async (options) => {
|
|
|
480
480
|
};
|
|
481
481
|
componentsByPage[page.id] = /* @__PURE__ */ new Set();
|
|
482
482
|
for (const [_instanceId, instance] of instances) {
|
|
483
|
-
if (instance.component
|
|
483
|
+
if (isCoreComponent(instance.component)) {
|
|
484
484
|
continue;
|
|
485
485
|
}
|
|
486
486
|
componentsByPage[page.id].add(instance.component);
|
|
@@ -546,6 +546,8 @@ var prebuild = async (options) => {
|
|
|
546
546
|
const assets = new Map(siteData.assets.map((asset) => [asset.id, asset]));
|
|
547
547
|
spinner.text = "Generating css file";
|
|
548
548
|
const { cssText, classesMap } = generateCss({
|
|
549
|
+
instances: new Map(siteData.build.instances),
|
|
550
|
+
props: new Map(siteData.build.props),
|
|
549
551
|
assets,
|
|
550
552
|
breakpoints: new Map(siteData.build?.breakpoints),
|
|
551
553
|
styles: new Map(siteData.build?.styles),
|
|
@@ -555,11 +557,18 @@ var prebuild = async (options) => {
|
|
|
555
557
|
assetBaseUrl,
|
|
556
558
|
atomic: siteData.build.pages.compiler?.atomicStyles ?? true
|
|
557
559
|
});
|
|
558
|
-
await
|
|
560
|
+
await createFileIfNotExists(join4(generatedDir, "index.css"), cssText);
|
|
559
561
|
spinner.text = "Generating routes and pages";
|
|
560
|
-
const
|
|
562
|
+
const routeTemplatesDir = join4(cwd3(), "app/route-templates");
|
|
563
|
+
const routeTemplatePath = normalize(join4(routeTemplatesDir, "html.tsx"));
|
|
564
|
+
const routeXmlTemplatePath = normalize(join4(routeTemplatesDir, "xml.tsx"));
|
|
565
|
+
const defaultSiteMapXmlPath = normalize(
|
|
566
|
+
join4(routeTemplatesDir, "default-sitemap.tsx")
|
|
567
|
+
);
|
|
561
568
|
const routeFileTemplate = await readFile4(routeTemplatePath, "utf8");
|
|
562
|
-
await
|
|
569
|
+
const routeXmlFileTemplate = await readFile4(routeXmlTemplatePath, "utf8");
|
|
570
|
+
const defaultSiteMapTemplate = await readFile4(defaultSiteMapXmlPath, "utf8");
|
|
571
|
+
await rm(routeTemplatesDir, { recursive: true, force: true });
|
|
563
572
|
for (const [pageId, pageComponents] of Object.entries(componentsByPage)) {
|
|
564
573
|
const scope = createScope([
|
|
565
574
|
// manually maintained list of occupied identifiers
|
|
@@ -592,14 +601,36 @@ var prebuild = async (options) => {
|
|
|
592
601
|
namespaces.get(namespace)?.add([shortName, component]);
|
|
593
602
|
}
|
|
594
603
|
let componentImports = "";
|
|
604
|
+
let xmlPresentationComponents = "";
|
|
605
|
+
const pageData = siteDataByPage[pageId];
|
|
606
|
+
const documentType = pageData.page.meta.documentType ?? "html";
|
|
595
607
|
for (const [namespace, componentsSet] of namespaces.entries()) {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
608
|
+
switch (documentType) {
|
|
609
|
+
case "html":
|
|
610
|
+
{
|
|
611
|
+
const specifiers = Array.from(componentsSet).map(
|
|
612
|
+
([shortName, component]) => `${shortName} as ${scope.getName(component, shortName)}`
|
|
613
|
+
).join(", ");
|
|
614
|
+
componentImports += `import { ${specifiers} } from "${namespace}";
|
|
600
615
|
`;
|
|
616
|
+
}
|
|
617
|
+
break;
|
|
618
|
+
case "xml":
|
|
619
|
+
{
|
|
620
|
+
componentImports = `import { XmlNode } from "@webstudio-is/sdk-components-react";
|
|
621
|
+
`;
|
|
622
|
+
xmlPresentationComponents += Array.from(componentsSet).map(
|
|
623
|
+
([shortName, component]) => scope.getName(component, shortName)
|
|
624
|
+
).filter((scopedName) => scopedName !== "XmlNode").map(
|
|
625
|
+
(scopedName) => scopedName === "Body" ? `const ${scopedName} = (props: any) => props.children;` : `const ${scopedName} = () => null;`
|
|
626
|
+
).join("\n");
|
|
627
|
+
}
|
|
628
|
+
break;
|
|
629
|
+
default: {
|
|
630
|
+
documentType;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
601
633
|
}
|
|
602
|
-
const pageData = siteDataByPage[pageId];
|
|
603
634
|
const pageFontAssets = fontAssetsByPage[pageId];
|
|
604
635
|
const pageBackgroundImageAssets = backgroundImageAssetsByPage[pageId];
|
|
605
636
|
const rootInstanceId = pageData.page.rootInstanceId;
|
|
@@ -639,82 +670,88 @@ var prebuild = async (options) => {
|
|
|
639
670
|
const favIconAsset = assets.get(projectMeta?.faviconAssetId ?? "");
|
|
640
671
|
const socialImageAsset = assets.get(pageMeta.socialImageAssetId ?? "");
|
|
641
672
|
const pageExports = `/* eslint-disable */
|
|
642
|
-
/* This is a auto generated file for building the project */
|
|
643
|
-
|
|
673
|
+
/* This is a auto generated file for building the project */
|
|
644
674
|
|
|
645
|
-
import { Fragment, useState } from "react";
|
|
646
|
-
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
|
|
647
|
-
import { useResource } from "@webstudio-is/react-sdk";
|
|
648
|
-
${componentImports}
|
|
649
675
|
|
|
650
|
-
|
|
676
|
+
import { Fragment, useState } from "react";
|
|
677
|
+
import type { FontAsset, ImageAsset } from "@webstudio-is/sdk";
|
|
678
|
+
import { useResource } from "@webstudio-is/react-sdk";
|
|
679
|
+
${componentImports}
|
|
651
680
|
|
|
652
|
-
export const
|
|
653
|
-
${JSON.stringify(favIconAsset)};
|
|
681
|
+
export const siteName = ${JSON.stringify(projectMeta?.siteName)};
|
|
654
682
|
|
|
655
|
-
export const
|
|
656
|
-
|
|
683
|
+
export const favIconAsset: ImageAsset | undefined =
|
|
684
|
+
${JSON.stringify(favIconAsset)};
|
|
657
685
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
${JSON.stringify(pageFontAssets)}
|
|
686
|
+
export const socialImageAsset: ImageAsset | undefined =
|
|
687
|
+
${JSON.stringify(socialImageAsset)};
|
|
661
688
|
|
|
662
|
-
|
|
663
|
-
|
|
689
|
+
// Font assets on current page (can be preloaded)
|
|
690
|
+
export const pageFontAssets: FontAsset[] =
|
|
691
|
+
${JSON.stringify(pageFontAssets)}
|
|
664
692
|
|
|
693
|
+
export const pageBackgroundImageAssets: ImageAsset[] =
|
|
694
|
+
${JSON.stringify(pageBackgroundImageAssets)}
|
|
665
695
|
|
|
696
|
+
${xmlPresentationComponents}
|
|
666
697
|
|
|
667
|
-
${pageComponent}
|
|
698
|
+
${pageComponent}
|
|
668
699
|
|
|
669
|
-
export { Page }
|
|
670
|
-
`;
|
|
700
|
+
export { Page }
|
|
701
|
+
`;
|
|
671
702
|
const serverExports = `/* eslint-disable */
|
|
672
|
-
/* This is a auto generated file for building the project */
|
|
703
|
+
/* This is a auto generated file for building the project */
|
|
673
704
|
|
|
674
705
|
|
|
675
|
-
import type { PageMeta } from "@webstudio-is/sdk";
|
|
676
|
-
${generateResourcesLoader({
|
|
706
|
+
import type { PageMeta } from "@webstudio-is/sdk";
|
|
707
|
+
${generateResourcesLoader({
|
|
677
708
|
scope,
|
|
678
709
|
page: pageData.page,
|
|
679
710
|
dataSources,
|
|
680
711
|
resources
|
|
681
712
|
})}
|
|
682
713
|
|
|
683
|
-
${generatePageMeta({
|
|
714
|
+
${generatePageMeta({
|
|
684
715
|
globalScope: scope,
|
|
685
716
|
page: pageData.page,
|
|
686
717
|
dataSources
|
|
687
718
|
})}
|
|
688
719
|
|
|
689
|
-
${generateFormsProperties(props)}
|
|
720
|
+
${generateFormsProperties(props)}
|
|
690
721
|
|
|
691
|
-
${generateRemixParams(pageData.page.path)}
|
|
722
|
+
${generateRemixParams(pageData.page.path)}
|
|
692
723
|
|
|
693
|
-
export const projectId = "${siteData.build.projectId}";
|
|
724
|
+
export const projectId = "${siteData.build.projectId}";
|
|
694
725
|
|
|
695
|
-
export const contactEmail = ${JSON.stringify(contactEmail)};
|
|
726
|
+
export const contactEmail = ${JSON.stringify(contactEmail)};
|
|
696
727
|
|
|
697
|
-
export const customCode = ${JSON.stringify(
|
|
698
|
-
|
|
728
|
+
export const customCode = ${JSON.stringify(
|
|
729
|
+
projectMeta?.code?.trim() ?? ""
|
|
730
|
+
)};
|
|
731
|
+
`;
|
|
699
732
|
const pagePath = getPagePath(pageData.page.id, siteData.build.pages);
|
|
700
733
|
const remixRoute = generateRemixRoute(pagePath);
|
|
701
734
|
const fileName = `${remixRoute}.tsx`;
|
|
702
|
-
const routeFileContent = routeFileTemplate.replace(
|
|
735
|
+
const routeFileContent = (documentType === "html" ? routeFileTemplate : routeXmlFileTemplate).replace(
|
|
703
736
|
/".*\/__generated__\/_index"/,
|
|
704
737
|
`"../__generated__/${remixRoute}"`
|
|
705
738
|
).replace(
|
|
706
739
|
/".*\/__generated__\/_index.server"/,
|
|
707
740
|
`"../__generated__/${remixRoute}.server"`
|
|
708
741
|
);
|
|
709
|
-
await
|
|
710
|
-
await
|
|
711
|
-
await
|
|
742
|
+
await createFileIfNotExists(join4(routesDir, fileName), routeFileContent);
|
|
743
|
+
await createFileIfNotExists(join4(generatedDir, fileName), pageExports);
|
|
744
|
+
await createFileIfNotExists(
|
|
712
745
|
join4(generatedDir, `${remixRoute}.server.tsx`),
|
|
713
746
|
serverExports
|
|
714
747
|
);
|
|
715
748
|
}
|
|
716
|
-
await
|
|
717
|
-
join4(
|
|
749
|
+
await createFileIfNotExists(
|
|
750
|
+
join4(routesDir, "[sitemap.xml]._index.tsx"),
|
|
751
|
+
defaultSiteMapTemplate.replace(/".*\/__generated__\//, `"../__generated__/`)
|
|
752
|
+
);
|
|
753
|
+
await createFileIfNotExists(
|
|
754
|
+
join4(generatedDir, "$resources.sitemap.xml.ts"),
|
|
718
755
|
`
|
|
719
756
|
export const sitemap = ${JSON.stringify(
|
|
720
757
|
getStaticSiteMapXml(siteData.build.pages, siteData.build.updatedAt),
|
|
@@ -731,11 +768,11 @@ export const customCode = ${JSON.stringify(projectMeta?.code?.trim() ?? "")};
|
|
|
731
768
|
const redirectFileName = `${redirectPagePath}.ts`;
|
|
732
769
|
const content = `import { type LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
|
|
733
770
|
|
|
734
|
-
export const loader = (arg: LoaderFunctionArgs) => {
|
|
735
|
-
|
|
736
|
-
};
|
|
737
|
-
`;
|
|
738
|
-
await
|
|
771
|
+
export const loader = (arg: LoaderFunctionArgs) => {
|
|
772
|
+
return redirect("${redirect.new}", ${redirect.status ?? 301});
|
|
773
|
+
};
|
|
774
|
+
`;
|
|
775
|
+
await createFileIfNotExists(join4(routesDir, redirectFileName), content);
|
|
739
776
|
}
|
|
740
777
|
}
|
|
741
778
|
spinner.text = "Downloading fonts and images";
|
|
@@ -820,7 +857,7 @@ var initFlow = async (options) => {
|
|
|
820
857
|
if (folderName === void 0) {
|
|
821
858
|
throw new Error("Folder name is required");
|
|
822
859
|
}
|
|
823
|
-
await
|
|
860
|
+
await createFolderIfNotExists(join5(cwd4(), folderName));
|
|
824
861
|
chdir(join5(cwd4(), folderName));
|
|
825
862
|
}
|
|
826
863
|
const { projectLink } = await prompt({
|
|
@@ -912,7 +949,7 @@ import makeCLI from "yargs";
|
|
|
912
949
|
// package.json
|
|
913
950
|
var package_default = {
|
|
914
951
|
name: "webstudio",
|
|
915
|
-
version: "0.
|
|
952
|
+
version: "0.163.0",
|
|
916
953
|
description: "Webstudio CLI",
|
|
917
954
|
author: "Webstudio <github@webstudio.is>",
|
|
918
955
|
homepage: "https://webstudio.is",
|
|
@@ -983,7 +1020,7 @@ var package_default = {
|
|
|
983
1020
|
// src/cli.ts
|
|
984
1021
|
var main = async () => {
|
|
985
1022
|
try {
|
|
986
|
-
await
|
|
1023
|
+
await createFileIfNotExists(GLOBAL_CONFIG_FILE, "{}");
|
|
987
1024
|
const cmd = makeCLI(hideBin(argv)).strict().fail(function(msg, err, yargs) {
|
|
988
1025
|
if (err) {
|
|
989
1026
|
throw err;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webstudio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.163.0",
|
|
4
4
|
"description": "Webstudio CLI",
|
|
5
5
|
"author": "Webstudio <github@webstudio.is>",
|
|
6
6
|
"homepage": "https://webstudio.is",
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"title-case": "^4.1.0",
|
|
29
29
|
"yargs": "^17.7.2",
|
|
30
30
|
"zod": "^3.22.4",
|
|
31
|
-
"@webstudio-is/http-client": "0.
|
|
32
|
-
"@webstudio-is/react-sdk": "0.
|
|
33
|
-
"@webstudio-is/image": "0.
|
|
34
|
-
"@webstudio-is/sdk": "0.
|
|
35
|
-
"@webstudio-is/sdk-components-react-radix": "0.
|
|
36
|
-
"@webstudio-is/sdk-components-react
|
|
37
|
-
"@webstudio-is/sdk-components-react": "0.
|
|
31
|
+
"@webstudio-is/http-client": "0.163.0",
|
|
32
|
+
"@webstudio-is/react-sdk": "0.163.0",
|
|
33
|
+
"@webstudio-is/image": "0.163.0",
|
|
34
|
+
"@webstudio-is/sdk": "0.163.0",
|
|
35
|
+
"@webstudio-is/sdk-components-react-radix": "0.163.0",
|
|
36
|
+
"@webstudio-is/sdk-components-react": "0.163.0",
|
|
37
|
+
"@webstudio-is/sdk-components-react-remix": "0.163.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@netlify/remix-adapter": "^2.3.1",
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
"typescript": "5.4.5",
|
|
57
57
|
"vite": "^5.2.11",
|
|
58
58
|
"wrangler": "^3.48.0",
|
|
59
|
-
"@webstudio-is/form-handlers": "0.
|
|
59
|
+
"@webstudio-is/form-handlers": "0.163.0",
|
|
60
60
|
"@webstudio-is/tsconfig": "1.0.7"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
@@ -2,7 +2,6 @@ import { createPagesFunctionHandler } from "@remix-run/cloudflare-pages";
|
|
|
2
2
|
|
|
3
3
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
4
4
|
// @ts-ignore - the server build file is generated by `remix vite:build`
|
|
5
|
-
// eslint-disable-next-line import/no-unresolved
|
|
6
5
|
import * as build from "../build/server";
|
|
7
6
|
|
|
8
7
|
export const onRequest = createPagesFunctionHandler({ build });
|
package/templates/defaults/app/{routes/[sitemap.xml].tsx → route-templates/default-sitemap.tsx}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
|
2
|
-
import { sitemap } from "
|
|
2
|
+
import { sitemap } from "../../../../__generated__/$resources.sitemap.xml";
|
|
3
3
|
|
|
4
4
|
export const loader = (arg: LoaderFunctionArgs) => {
|
|
5
5
|
const host =
|
|
@@ -11,7 +11,11 @@ import {
|
|
|
11
11
|
} from "@remix-run/server-runtime";
|
|
12
12
|
import { useLoaderData } from "@remix-run/react";
|
|
13
13
|
import { ReactSdkContext } from "@webstudio-is/react-sdk";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
n8nHandler,
|
|
16
|
+
formIdFieldName,
|
|
17
|
+
formBotFieldName,
|
|
18
|
+
} from "@webstudio-is/form-handlers";
|
|
15
19
|
import {
|
|
16
20
|
Page,
|
|
17
21
|
siteName,
|
|
@@ -240,66 +244,86 @@ const getMethod = (value: string | undefined) => {
|
|
|
240
244
|
}
|
|
241
245
|
};
|
|
242
246
|
|
|
243
|
-
export const action = async ({
|
|
244
|
-
|
|
247
|
+
export const action = async ({
|
|
248
|
+
request,
|
|
249
|
+
context,
|
|
250
|
+
}: ActionFunctionArgs): Promise<
|
|
251
|
+
{ success: true } | { success: false; errors: string[] }
|
|
252
|
+
> => {
|
|
253
|
+
try {
|
|
254
|
+
const formData = await request.formData();
|
|
245
255
|
|
|
246
|
-
|
|
247
|
-
if (formId === undefined) {
|
|
248
|
-
// We're throwing rather than returning { success: false }
|
|
249
|
-
// because this isn't supposed to happen normally: bug or malicious user
|
|
250
|
-
throw json("Form not found", { status: 404 });
|
|
251
|
-
}
|
|
256
|
+
const formId = formData.get(formIdFieldName);
|
|
252
257
|
|
|
253
|
-
|
|
258
|
+
if (formId == null || typeof formId !== "string") {
|
|
259
|
+
throw new Error("No form id in FormData");
|
|
260
|
+
}
|
|
254
261
|
|
|
255
|
-
|
|
256
|
-
const { action, method } = formProperties ?? {};
|
|
262
|
+
const formBotValue = formData.get(formBotFieldName);
|
|
257
263
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
264
|
+
if (formBotValue == null || typeof formBotValue !== "string") {
|
|
265
|
+
throw new Error("Form bot field not found");
|
|
266
|
+
}
|
|
261
267
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
268
|
+
const submitTime = parseInt(formBotValue, 16);
|
|
269
|
+
// Assumes that the difference between the server time and the form submission time,
|
|
270
|
+
// including any client-server time drift, is within a 5-minute range.
|
|
271
|
+
// Note: submitTime might be NaN because formBotValue can be any string used for logging purposes.
|
|
272
|
+
// Example: `formBotValue: jsdom`, or `formBotValue: headless-env`
|
|
273
|
+
if (
|
|
274
|
+
Number.isNaN(submitTime) ||
|
|
275
|
+
Math.abs(Date.now() - submitTime) > 1000 * 60 * 5
|
|
276
|
+
) {
|
|
277
|
+
throw new Error(`Form bot value invalid ${formBotValue}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const formProperties = formsProperties.get(formId);
|
|
281
|
+
|
|
282
|
+
// form properties are not defined when defaults are used
|
|
283
|
+
const { action, method } = formProperties ?? {};
|
|
284
|
+
|
|
285
|
+
if (contactEmail === undefined) {
|
|
286
|
+
throw new Error("Contact email not found");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const pageUrl = new URL(request.url);
|
|
267
290
|
pageUrl.host = getRequestHost(request);
|
|
268
|
-
} catch {
|
|
269
|
-
return { success: false };
|
|
270
|
-
}
|
|
271
291
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
},
|
|
282
|
-
{ status: 200 }
|
|
283
|
-
);
|
|
292
|
+
if (action !== undefined) {
|
|
293
|
+
try {
|
|
294
|
+
// Test that action is full URL
|
|
295
|
+
new URL(action);
|
|
296
|
+
} catch {
|
|
297
|
+
throw new Error(
|
|
298
|
+
"Invalid action URL, must be valid http/https protocol"
|
|
299
|
+
);
|
|
300
|
+
}
|
|
284
301
|
}
|
|
285
|
-
}
|
|
286
302
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
303
|
+
const formInfo = {
|
|
304
|
+
formData,
|
|
305
|
+
projectId,
|
|
306
|
+
action: action ?? null,
|
|
307
|
+
method: getMethod(method),
|
|
308
|
+
pageUrl: pageUrl.toString(),
|
|
309
|
+
toEmail: contactEmail,
|
|
310
|
+
fromEmail: pageUrl.hostname + "@webstudio.email",
|
|
311
|
+
} as const;
|
|
312
|
+
|
|
313
|
+
const result = await n8nHandler({
|
|
314
|
+
formInfo,
|
|
315
|
+
hookUrl: context.N8N_FORM_EMAIL_HOOK,
|
|
316
|
+
});
|
|
301
317
|
|
|
302
|
-
|
|
318
|
+
return result;
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.error(error);
|
|
321
|
+
|
|
322
|
+
return {
|
|
323
|
+
success: false,
|
|
324
|
+
errors: [error instanceof Error ? error.message : "Unknown error"],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
303
327
|
};
|
|
304
328
|
|
|
305
329
|
const Outlet = () => {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import { type LoaderFunctionArgs, redirect } from "@remix-run/server-runtime";
|
|
3
|
+
import { ReactSdkContext } from "@webstudio-is/react-sdk";
|
|
4
|
+
import { Page } from "../../../../__generated__/_index";
|
|
5
|
+
import {
|
|
6
|
+
loadResources,
|
|
7
|
+
getPageMeta,
|
|
8
|
+
getRemixParams,
|
|
9
|
+
} from "../../../../__generated__/_index.server";
|
|
10
|
+
|
|
11
|
+
import { assetBaseUrl, imageBaseUrl, imageLoader } from "../constants.mjs";
|
|
12
|
+
import { renderToString } from "react-dom/server";
|
|
13
|
+
|
|
14
|
+
export const loader = async (arg: LoaderFunctionArgs) => {
|
|
15
|
+
const url = new URL(arg.request.url);
|
|
16
|
+
const host =
|
|
17
|
+
arg.request.headers.get("x-forwarded-host") ||
|
|
18
|
+
arg.request.headers.get("host") ||
|
|
19
|
+
"";
|
|
20
|
+
url.host = host;
|
|
21
|
+
url.protocol = "https";
|
|
22
|
+
|
|
23
|
+
const params = getRemixParams(arg.params);
|
|
24
|
+
|
|
25
|
+
const system = {
|
|
26
|
+
params,
|
|
27
|
+
search: Object.fromEntries(url.searchParams),
|
|
28
|
+
origin: url.origin,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const resources = await loadResources({ system });
|
|
32
|
+
const pageMeta = getPageMeta({ system, resources });
|
|
33
|
+
|
|
34
|
+
if (pageMeta.redirect) {
|
|
35
|
+
const status =
|
|
36
|
+
pageMeta.status === 301 || pageMeta.status === 302
|
|
37
|
+
? pageMeta.status
|
|
38
|
+
: 302;
|
|
39
|
+
return redirect(pageMeta.redirect, status);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// typecheck
|
|
43
|
+
arg.context.EXCLUDE_FROM_SEARCH satisfies boolean;
|
|
44
|
+
|
|
45
|
+
const text = renderToString(
|
|
46
|
+
<ReactSdkContext.Provider
|
|
47
|
+
value={{
|
|
48
|
+
imageLoader,
|
|
49
|
+
assetBaseUrl,
|
|
50
|
+
imageBaseUrl,
|
|
51
|
+
resources,
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
<Page system={system} />
|
|
55
|
+
</ReactSdkContext.Provider>
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return new Response(`<?xml version="1.0" encoding="UTF-8"?>\n${text}`, {
|
|
59
|
+
headers: { "Content-Type": "application/xml" },
|
|
60
|
+
});
|
|
61
|
+
};
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
"@remix-run/node": "2.9.1",
|
|
12
12
|
"@remix-run/react": "2.9.1",
|
|
13
13
|
"@remix-run/server-runtime": "2.9.1",
|
|
14
|
-
"@webstudio-is/react-sdk": "0.
|
|
15
|
-
"@webstudio-is/sdk-components-react-radix": "0.
|
|
16
|
-
"@webstudio-is/sdk-components-react-remix": "0.
|
|
17
|
-
"@webstudio-is/sdk-components-react": "0.
|
|
18
|
-
"@webstudio-is/form-handlers": "0.
|
|
19
|
-
"@webstudio-is/image": "0.
|
|
20
|
-
"@webstudio-is/sdk": "0.
|
|
14
|
+
"@webstudio-is/react-sdk": "0.163.0",
|
|
15
|
+
"@webstudio-is/sdk-components-react-radix": "0.163.0",
|
|
16
|
+
"@webstudio-is/sdk-components-react-remix": "0.163.0",
|
|
17
|
+
"@webstudio-is/sdk-components-react": "0.163.0",
|
|
18
|
+
"@webstudio-is/form-handlers": "0.163.0",
|
|
19
|
+
"@webstudio-is/image": "0.163.0",
|
|
20
|
+
"@webstudio-is/sdk": "0.163.0",
|
|
21
21
|
"isbot": "^3.6.8",
|
|
22
22
|
"react": "18.3.0-canary-14898b6a9-20240318",
|
|
23
23
|
"react-dom": "18.3.0-canary-14898b6a9-20240318"
|