webstudio 0.105.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/LICENSE +661 -0
- package/README.md +145 -0
- package/bin.js +5 -0
- package/lib/cli.js +875 -0
- package/package.json +54 -0
- package/templates/defaults/app/constants.mjs +13 -0
- package/templates/defaults/app/root.tsx +1 -0
- package/templates/defaults/package.json +34 -0
- package/templates/defaults/public/favicon.ico +0 -0
- package/templates/defaults/remix.config.js +18 -0
- package/templates/defaults/remix.env.d.ts +2 -0
- package/templates/defaults/tsconfig.json +26 -0
- package/templates/netlify-edge-functions/app/entry.server.tsx +21 -0
- package/templates/netlify-edge-functions/netlify.toml +7 -0
- package/templates/netlify-edge-functions/package.json +11 -0
- package/templates/netlify-edge-functions/remix.config.js +27 -0
- package/templates/netlify-edge-functions/server.js +19 -0
- package/templates/netlify-functions/_app_redirects +7 -0
- package/templates/netlify-functions/app/entry.server.tsx +21 -0
- package/templates/netlify-functions/netlify.toml +14 -0
- package/templates/netlify-functions/package.json +10 -0
- package/templates/netlify-functions/remix.config.js +29 -0
- package/templates/netlify-functions/server.js +7 -0
- package/templates/route-template.tsx +178 -0
- package/templates/vercel/.vercelignore +8 -0
- package/templates/vercel/app/constants.mjs +25 -0
- package/templates/vercel/package.json +5 -0
- package/templates/vercel/vercel.json +11 -0
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "webstudio",
|
|
3
|
+
"version": "0.105.0",
|
|
4
|
+
"description": "Webstudio CLI",
|
|
5
|
+
"author": "Webstudio <github@webstudio.is>",
|
|
6
|
+
"homepage": "https://webstudio.is",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"webstudio-cli": "./bin.js",
|
|
10
|
+
"webstudio": "./bin.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"lib/*",
|
|
14
|
+
"templates/*",
|
|
15
|
+
"bin.js",
|
|
16
|
+
"!*.{test,stories}.*"
|
|
17
|
+
],
|
|
18
|
+
"license": "AGPL-3.0-or-later",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"deepmerge": "^4.3.1",
|
|
21
|
+
"env-paths": "^3.0.0",
|
|
22
|
+
"execa": "^7.2.0",
|
|
23
|
+
"ora": "^7.0.1",
|
|
24
|
+
"p-limit": "^4.0.0",
|
|
25
|
+
"picocolors": "^1.0.0",
|
|
26
|
+
"prompts": "^2.4.2",
|
|
27
|
+
"strip-indent": "^4.0.0",
|
|
28
|
+
"title-case": "^4.1.0",
|
|
29
|
+
"yargs": "^17.7.2",
|
|
30
|
+
"zod": "^3.21.4",
|
|
31
|
+
"@webstudio-is/http-client": "0.105.0",
|
|
32
|
+
"@webstudio-is/image": "0.105.0",
|
|
33
|
+
"@webstudio-is/react-sdk": "0.105.0",
|
|
34
|
+
"@webstudio-is/sdk": "0.105.0",
|
|
35
|
+
"@webstudio-is/sdk-components-react": "0.105.0",
|
|
36
|
+
"@webstudio-is/sdk-components-react-radix": "0.105.0",
|
|
37
|
+
"@webstudio-is/sdk-components-react-remix": "0.105.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^18.17.1",
|
|
41
|
+
"@types/prompts": "^2.4.5",
|
|
42
|
+
"tsx": "^3.12.8",
|
|
43
|
+
"typescript": "5.2.2",
|
|
44
|
+
"@webstudio-is/form-handlers": "0.105.0",
|
|
45
|
+
"@webstudio-is/tsconfig": "1.0.7"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"typecheck": "tsc",
|
|
49
|
+
"checks": "pnpm typecheck",
|
|
50
|
+
"build": "rm -rf lib && esbuild src/cli.ts --outdir=lib --bundle --format=esm --packages=external",
|
|
51
|
+
"local-run": "tsx --no-warnings ./src/bin.ts",
|
|
52
|
+
"dev": "esbuild src/cli.ts --watch --bundle --format=esm --packages=external --outdir=./lib"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* We use mjs extension as constants in this file is shared with the build script
|
|
3
|
+
* and we use `node --eval` to extract the constants.
|
|
4
|
+
*/
|
|
5
|
+
export const assetBaseUrl = "/assets/";
|
|
6
|
+
export const imageBaseUrl = "/assets/";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @type {import("@webstudio-is/image").ImageLoader}
|
|
10
|
+
*/
|
|
11
|
+
export const imageLoader = ({ src }) => {
|
|
12
|
+
return imageBaseUrl + src;
|
|
13
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Root as default } from "@webstudio-is/react-sdk";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"private": true,
|
|
3
|
+
"sideEffects": false,
|
|
4
|
+
"scripts": {
|
|
5
|
+
"build": "remix build",
|
|
6
|
+
"dev": "remix dev",
|
|
7
|
+
"typecheck": "tsc"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"@remix-run/react": "^1.19.2",
|
|
11
|
+
"@remix-run/server-runtime": "^1.19.2",
|
|
12
|
+
"@remix-run/node": "^1.19.2",
|
|
13
|
+
"@webstudio-is/react-sdk": "^0.105.0",
|
|
14
|
+
"@webstudio-is/sdk-components-react-radix": "^0.105.0",
|
|
15
|
+
"@webstudio-is/sdk-components-react-remix": "^0.105.0",
|
|
16
|
+
"@webstudio-is/sdk-components-react": "^0.105.0",
|
|
17
|
+
"@webstudio-is/form-handlers": "^0.105.0",
|
|
18
|
+
"@webstudio-is/image": "^0.105.0",
|
|
19
|
+
"@webstudio-is/sdk": "^0.105.0",
|
|
20
|
+
"isbot": "^3.6.8",
|
|
21
|
+
"react": "^18.2.0",
|
|
22
|
+
"react-dom": "^18.2.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@remix-run/serve": "^1.19.2",
|
|
26
|
+
"@remix-run/dev": "^1.19.2",
|
|
27
|
+
"@types/react": "^18.2.20",
|
|
28
|
+
"@types/react-dom": "^18.2.7",
|
|
29
|
+
"typescript": "5.2.2"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18.0.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** @type {import('@remix-run/dev').AppConfig} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
ignoredRouteFiles: ["**/.*"],
|
|
4
|
+
serverModuleFormat: "cjs",
|
|
5
|
+
serverDependenciesToBundle: [
|
|
6
|
+
/@webstudio-is\//,
|
|
7
|
+
"nanoid",
|
|
8
|
+
"@jsep-plugin/assignment",
|
|
9
|
+
],
|
|
10
|
+
future: {
|
|
11
|
+
v2_errorBoundary: true,
|
|
12
|
+
v2_headers: true,
|
|
13
|
+
v2_meta: true,
|
|
14
|
+
v2_normalizeFormMethod: true,
|
|
15
|
+
v2_routeConvention: true,
|
|
16
|
+
v2_dev: true,
|
|
17
|
+
},
|
|
18
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx", "**/*.mjs"],
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
|
5
|
+
"isolatedModules": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"target": "ES2022",
|
|
11
|
+
"strict": true,
|
|
12
|
+
"allowJs": true,
|
|
13
|
+
"checkJs": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"allowImportingTsExtensions": true,
|
|
16
|
+
"baseUrl": ".",
|
|
17
|
+
"paths": {
|
|
18
|
+
"~/*": ["./app/*"]
|
|
19
|
+
},
|
|
20
|
+
"customConditions": ["webstudio"],
|
|
21
|
+
|
|
22
|
+
// Remix takes care of building everything in `remix build`.
|
|
23
|
+
"noEmit": true,
|
|
24
|
+
"skipLibCheck": true
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { EntryContext } from "@remix-run/node";
|
|
2
|
+
import { RemixServer } from "@remix-run/react";
|
|
3
|
+
import { renderToString } from "react-dom/server";
|
|
4
|
+
|
|
5
|
+
export default function handleRequest(
|
|
6
|
+
request: Request,
|
|
7
|
+
responseStatusCode: number,
|
|
8
|
+
responseHeaders: Headers,
|
|
9
|
+
remixContext: EntryContext
|
|
10
|
+
) {
|
|
11
|
+
const markup = renderToString(
|
|
12
|
+
<RemixServer context={remixContext} url={request.url} />
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
responseHeaders.set("Content-Type", "text/html");
|
|
16
|
+
|
|
17
|
+
return new Response("<!DOCTYPE html>" + markup, {
|
|
18
|
+
headers: responseHeaders,
|
|
19
|
+
status: responseStatusCode,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scripts": {
|
|
3
|
+
"predev": "node -e \"fs.rmSync('./public/_redirects', { recursive: true, force: true })\"",
|
|
4
|
+
"start": "netlify serve"
|
|
5
|
+
},
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@netlify/functions": "^1.3.0",
|
|
8
|
+
"@netlify/edge-functions": "^2.0.0",
|
|
9
|
+
"@netlify/remix-edge-adapter": "1.2.0"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const { config } = require("@netlify/remix-edge-adapter");
|
|
2
|
+
const baseConfig =
|
|
3
|
+
process.env.NODE_ENV === "production"
|
|
4
|
+
? config
|
|
5
|
+
: {
|
|
6
|
+
ignoredRouteFiles: ["**/.*"],
|
|
7
|
+
serverModuleFormat: "cjs",
|
|
8
|
+
serverDependenciesToBundle: [
|
|
9
|
+
/@webstudio-is\//,
|
|
10
|
+
"nanoid",
|
|
11
|
+
"@jsep-plugin/assignment",
|
|
12
|
+
],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** @type {import('@remix-run/dev').AppConfig} */
|
|
16
|
+
module.exports = {
|
|
17
|
+
ignoredRouteFiles: ["**/.*"],
|
|
18
|
+
...baseConfig,
|
|
19
|
+
future: {
|
|
20
|
+
v2_errorBoundary: true,
|
|
21
|
+
v2_headers: true,
|
|
22
|
+
v2_meta: true,
|
|
23
|
+
v2_normalizeFormMethod: true,
|
|
24
|
+
v2_routeConvention: true,
|
|
25
|
+
v2_dev: true,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Import path interpreted by the Remix compiler
|
|
2
|
+
import * as build from "@remix-run/dev/server-build";
|
|
3
|
+
import { createRequestHandler } from "@netlify/remix-edge-adapter";
|
|
4
|
+
|
|
5
|
+
export default createRequestHandler({
|
|
6
|
+
build,
|
|
7
|
+
// process.env.NODE_ENV is provided by Remix at compile time
|
|
8
|
+
mode: process.env.NODE_ENV,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export const config = {
|
|
12
|
+
cache: "manual",
|
|
13
|
+
path: "/*",
|
|
14
|
+
// Let the CDN handle requests for static assets, i.e. ^/_assets/*$
|
|
15
|
+
//
|
|
16
|
+
// Add other exclusions here, e.g. "^/api/*$" for custom Netlify functions or
|
|
17
|
+
// custom Netlify Edge Functions
|
|
18
|
+
excluded_patterns: ["^/_assets/*$"],
|
|
19
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# This template uses this file instead of the typicial Netlify _redirects file.
|
|
2
|
+
# For more information about redirects and rewrites, see https://docs.netlify.com/routing/redirects/.
|
|
3
|
+
|
|
4
|
+
# Do not remove the line below. This is required to serve the site when deployed.
|
|
5
|
+
/* /.netlify/functions/server 200
|
|
6
|
+
|
|
7
|
+
# Add other redirects and rewrites here and/or in your netlify.toml
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { EntryContext } from "@remix-run/node";
|
|
2
|
+
import { RemixServer } from "@remix-run/react";
|
|
3
|
+
import { renderToString } from "react-dom/server";
|
|
4
|
+
|
|
5
|
+
export default function handleRequest(
|
|
6
|
+
request: Request,
|
|
7
|
+
responseStatusCode: number,
|
|
8
|
+
responseHeaders: Headers,
|
|
9
|
+
remixContext: EntryContext
|
|
10
|
+
) {
|
|
11
|
+
const markup = renderToString(
|
|
12
|
+
<RemixServer context={remixContext} url={request.url} />
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
responseHeaders.set("Content-Type", "text/html");
|
|
16
|
+
|
|
17
|
+
return new Response("<!DOCTYPE html>" + markup, {
|
|
18
|
+
headers: responseHeaders,
|
|
19
|
+
status: responseStatusCode,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[build]
|
|
2
|
+
command = "remix build && cp _app_redirects public/_redirects"
|
|
3
|
+
publish = "public"
|
|
4
|
+
|
|
5
|
+
[dev]
|
|
6
|
+
command = "npm run dev"
|
|
7
|
+
targetPort = 3000
|
|
8
|
+
|
|
9
|
+
[[headers]]
|
|
10
|
+
for = "/build/*"
|
|
11
|
+
|
|
12
|
+
[headers.values]
|
|
13
|
+
# Set to 60 seconds as an example. You can also add cache headers via Remix. See the documentation on [headers](https://remix.run/docs/en/v1/route/headers) in Remix.
|
|
14
|
+
"Cache-Control" = "public, max-age=60, s-maxage=60"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const baseConfig =
|
|
2
|
+
process.env.NODE_ENV === "production"
|
|
3
|
+
? // when running the Netify CLI or building on Netlify, we want to use
|
|
4
|
+
{
|
|
5
|
+
server: "./server.js",
|
|
6
|
+
serverBuildPath: ".netlify/functions-internal/server.js",
|
|
7
|
+
}
|
|
8
|
+
: // otherwise support running remix dev, i.e. no custom server
|
|
9
|
+
undefined;
|
|
10
|
+
|
|
11
|
+
/** @type {import('@remix-run/dev').AppConfig} */
|
|
12
|
+
module.exports = {
|
|
13
|
+
...baseConfig,
|
|
14
|
+
ignoredRouteFiles: ["**/.*"],
|
|
15
|
+
serverModuleFormat: "cjs",
|
|
16
|
+
serverDependenciesToBundle: [
|
|
17
|
+
/@webstudio-is\//,
|
|
18
|
+
"nanoid",
|
|
19
|
+
"@jsep-plugin/assignment",
|
|
20
|
+
],
|
|
21
|
+
future: {
|
|
22
|
+
v2_errorBoundary: true,
|
|
23
|
+
v2_headers: true,
|
|
24
|
+
v2_meta: true,
|
|
25
|
+
v2_normalizeFormMethod: true,
|
|
26
|
+
v2_routeConvention: true,
|
|
27
|
+
v2_dev: true,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import {
|
|
3
|
+
type V2_ServerRuntimeMetaFunction,
|
|
4
|
+
type LinksFunction,
|
|
5
|
+
type LinkDescriptor,
|
|
6
|
+
type ActionArgs,
|
|
7
|
+
json,
|
|
8
|
+
} from "@remix-run/server-runtime";
|
|
9
|
+
import type { Page as PageType } from "@webstudio-is/sdk";
|
|
10
|
+
import { ReactSdkContext } from "@webstudio-is/react-sdk";
|
|
11
|
+
import { n8nHandler, getFormId } from "@webstudio-is/form-handlers";
|
|
12
|
+
import { Scripts, ScrollRestoration } from "@remix-run/react";
|
|
13
|
+
import {
|
|
14
|
+
fontAssets,
|
|
15
|
+
pageData,
|
|
16
|
+
user,
|
|
17
|
+
projectId,
|
|
18
|
+
pagesPaths,
|
|
19
|
+
formsProperties,
|
|
20
|
+
Page,
|
|
21
|
+
} from "../__generated__/index";
|
|
22
|
+
import css from "../__generated__/index.css";
|
|
23
|
+
import { assetBaseUrl, imageBaseUrl, imageLoader } from "~/constants.mjs";
|
|
24
|
+
|
|
25
|
+
export type PageData = {
|
|
26
|
+
page: PageType;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const meta: V2_ServerRuntimeMetaFunction = () => {
|
|
30
|
+
const { page } = pageData;
|
|
31
|
+
const metas: ReturnType<V2_ServerRuntimeMetaFunction> = [
|
|
32
|
+
{ title: page?.title || "Webstudio" },
|
|
33
|
+
];
|
|
34
|
+
for (const [name, value] of Object.entries(page?.meta ?? {})) {
|
|
35
|
+
if (name.startsWith("og:")) {
|
|
36
|
+
metas.push({
|
|
37
|
+
property: name,
|
|
38
|
+
content: value,
|
|
39
|
+
});
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
metas.push({
|
|
44
|
+
name,
|
|
45
|
+
content: value,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return metas;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const links: LinksFunction = () => {
|
|
53
|
+
const result: LinkDescriptor[] = [];
|
|
54
|
+
|
|
55
|
+
result.push({
|
|
56
|
+
rel: "stylesheet",
|
|
57
|
+
href: css,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
for (const asset of fontAssets) {
|
|
61
|
+
if (asset.type === "font") {
|
|
62
|
+
result.push({
|
|
63
|
+
rel: "preload",
|
|
64
|
+
href: assetBaseUrl + asset.name,
|
|
65
|
+
as: "font",
|
|
66
|
+
crossOrigin: "anonymous",
|
|
67
|
+
// @todo add mimeType
|
|
68
|
+
// type: asset.mimeType,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return result;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const getRequestHost = (request: Request): string =>
|
|
77
|
+
request.headers.get("x-forwarded-host") || request.headers.get("host") || "";
|
|
78
|
+
|
|
79
|
+
const getMethod = (value: string | undefined) => {
|
|
80
|
+
if (value === undefined) {
|
|
81
|
+
return "post";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
switch (value.toLowerCase()) {
|
|
85
|
+
case "get":
|
|
86
|
+
return "get";
|
|
87
|
+
default:
|
|
88
|
+
return "post";
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const action = async ({ request, context }: ActionArgs) => {
|
|
93
|
+
const formData = await request.formData();
|
|
94
|
+
|
|
95
|
+
const formId = getFormId(formData);
|
|
96
|
+
if (formId === undefined) {
|
|
97
|
+
// We're throwing rather than returning { success: false }
|
|
98
|
+
// because this isn't supposed to happen normally: bug or malicious user
|
|
99
|
+
throw json("Form not found", { status: 404 });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const formProperties = formsProperties.get(formId);
|
|
103
|
+
|
|
104
|
+
// form properties are not defined when defaults are used
|
|
105
|
+
const { action, method } = formProperties ?? {};
|
|
106
|
+
|
|
107
|
+
const email = user?.email;
|
|
108
|
+
|
|
109
|
+
if (email == null) {
|
|
110
|
+
return { success: false };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// wrapped in try/catch just in cases new URL() throws
|
|
114
|
+
// (should not happen)
|
|
115
|
+
let pageUrl: URL;
|
|
116
|
+
try {
|
|
117
|
+
pageUrl = new URL(request.url);
|
|
118
|
+
pageUrl.host = getRequestHost(request);
|
|
119
|
+
} catch {
|
|
120
|
+
return { success: false };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (action !== undefined) {
|
|
124
|
+
try {
|
|
125
|
+
// Test that action is full URL
|
|
126
|
+
new URL(action);
|
|
127
|
+
} catch {
|
|
128
|
+
return json(
|
|
129
|
+
{
|
|
130
|
+
success: false,
|
|
131
|
+
error: "Invalid action URL, must be valid http/https protocol",
|
|
132
|
+
},
|
|
133
|
+
{ status: 200 }
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const formInfo = {
|
|
139
|
+
formData,
|
|
140
|
+
projectId,
|
|
141
|
+
action: action ?? null,
|
|
142
|
+
method: getMethod(method),
|
|
143
|
+
pageUrl: pageUrl.toString(),
|
|
144
|
+
toEmail: email,
|
|
145
|
+
fromEmail: pageUrl.hostname + "@webstudio.email",
|
|
146
|
+
} as const;
|
|
147
|
+
|
|
148
|
+
const result = await n8nHandler({
|
|
149
|
+
formInfo,
|
|
150
|
+
hookUrl: context.N8N_FORM_EMAIL_HOOK as string,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return result;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const Outlet = () => {
|
|
157
|
+
return (
|
|
158
|
+
<ReactSdkContext.Provider
|
|
159
|
+
value={{
|
|
160
|
+
imageLoader,
|
|
161
|
+
assetBaseUrl,
|
|
162
|
+
imageBaseUrl,
|
|
163
|
+
pagesPaths,
|
|
164
|
+
}}
|
|
165
|
+
>
|
|
166
|
+
<Page
|
|
167
|
+
scripts={
|
|
168
|
+
<>
|
|
169
|
+
<Scripts />
|
|
170
|
+
<ScrollRestoration />
|
|
171
|
+
</>
|
|
172
|
+
}
|
|
173
|
+
/>
|
|
174
|
+
</ReactSdkContext.Provider>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
export default Outlet;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* We use mjs extension as constants in this file is shared with the build script
|
|
3
|
+
* and we use `node --eval` to extract the constants.
|
|
4
|
+
*/
|
|
5
|
+
export const assetBaseUrl = "/assets/";
|
|
6
|
+
export const imageBaseUrl = "/assets/";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @type {import("@webstudio-is/image").ImageLoader}
|
|
10
|
+
*/
|
|
11
|
+
export const imageLoader = ({ quality, src, width }) => {
|
|
12
|
+
if (process.env.NODE_ENV !== "production") {
|
|
13
|
+
return imageBaseUrl + src;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// https://vercel.com/blog/build-your-own-web-framework#automatic-image-optimization
|
|
17
|
+
return (
|
|
18
|
+
"/_vercel/image?url=" +
|
|
19
|
+
encodeURIComponent(imageBaseUrl + src) +
|
|
20
|
+
"&w=" +
|
|
21
|
+
width +
|
|
22
|
+
"&q=" +
|
|
23
|
+
quality
|
|
24
|
+
);
|
|
25
|
+
};
|