promote-email-templates 0.1.6 → 0.1.8
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/dist/index.d.mts +8 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +27 -3
- package/dist/index.mjs +20 -0
- package/package.json +17 -14
- package/.react-email/.eslintrc.js +0 -52
- package/.react-email/.prettierignore +0 -3
- package/.react-email/.prettierrc.js +0 -8
- package/.react-email/license.md +0 -7
- package/.react-email/next.config.js +0 -36
- package/.react-email/package.json +0 -1
- package/.react-email/postcss.config.js +0 -8
- package/.react-email/readme.md +0 -44
- package/.react-email/src/actions/get-email-path-from-slug.ts +0 -26
- package/.react-email/src/actions/get-emails-directory-metadata.spec.ts +0 -73
- package/.react-email/src/actions/get-emails-directory-metadata.ts +0 -91
- package/.react-email/src/actions/render-email-by-path.tsx +0 -59
- package/.react-email/src/app/favicon.ico +0 -0
- package/.react-email/src/app/globals.css +0 -35
- package/.react-email/src/app/inter.ts +0 -7
- package/.react-email/src/app/layout.tsx +0 -36
- package/.react-email/src/app/logo.png +0 -0
- package/.react-email/src/app/page.tsx +0 -47
- package/.react-email/src/app/preview/[...slug]/page.tsx +0 -65
- package/.react-email/src/app/preview/[...slug]/preview.tsx +0 -141
- package/.react-email/src/app/preview/[...slug]/rendering-error.tsx +0 -40
- package/.react-email/src/components/button.tsx +0 -90
- package/.react-email/src/components/code-container.tsx +0 -145
- package/.react-email/src/components/code.tsx +0 -112
- package/.react-email/src/components/heading.tsx +0 -113
- package/.react-email/src/components/icons/icon-arrow-down.tsx +0 -16
- package/.react-email/src/components/icons/icon-base.tsx +0 -24
- package/.react-email/src/components/icons/icon-button.tsx +0 -23
- package/.react-email/src/components/icons/icon-check.tsx +0 -19
- package/.react-email/src/components/icons/icon-clipboard.tsx +0 -40
- package/.react-email/src/components/icons/icon-download.tsx +0 -19
- package/.react-email/src/components/icons/icon-file.tsx +0 -19
- package/.react-email/src/components/icons/icon-folder-open.tsx +0 -19
- package/.react-email/src/components/icons/icon-folder.tsx +0 -18
- package/.react-email/src/components/icons/icon-hide-sidebar.tsx +0 -23
- package/.react-email/src/components/icons/icon-monitor.tsx +0 -19
- package/.react-email/src/components/icons/icon-phone.tsx +0 -26
- package/.react-email/src/components/icons/icon-source.tsx +0 -19
- package/.react-email/src/components/index.ts +0 -7
- package/.react-email/src/components/logo.tsx +0 -64
- package/.react-email/src/components/send.tsx +0 -135
- package/.react-email/src/components/shell.tsx +0 -115
- package/.react-email/src/components/sidebar/index.ts +0 -1
- package/.react-email/src/components/sidebar/sidebar-directory-children.tsx +0 -134
- package/.react-email/src/components/sidebar/sidebar-directory.tsx +0 -106
- package/.react-email/src/components/sidebar/sidebar.tsx +0 -45
- package/.react-email/src/components/text.tsx +0 -99
- package/.react-email/src/components/tooltip-content.tsx +0 -32
- package/.react-email/src/components/tooltip.tsx +0 -19
- package/.react-email/src/components/topbar.tsx +0 -161
- package/.react-email/src/contexts/emails.tsx +0 -127
- package/.react-email/src/hooks/use-hot-reload.ts +0 -35
- package/.react-email/src/hooks/use-rendering-metadata.ts +0 -36
- package/.react-email/src/utils/cn.ts +0 -6
- package/.react-email/src/utils/constants.ts +0 -6
- package/.react-email/src/utils/copy-text-to-clipboard.ts +0 -7
- package/.react-email/src/utils/emails-directory-absolute-path.ts +0 -34
- package/.react-email/src/utils/get-email-component.ts +0 -108
- package/.react-email/src/utils/improve-error-with-sourcemap.ts +0 -55
- package/.react-email/src/utils/index.ts +0 -5
- package/.react-email/src/utils/language-map.ts +0 -7
- package/.react-email/src/utils/static-node-modules-for-vm.ts +0 -92
- package/.react-email/src/utils/types/as.ts +0 -26
- package/.react-email/src/utils/types/email-template.ts +0 -8
- package/.react-email/src/utils/types/error-object.ts +0 -11
- package/.react-email/src/utils/types/hot-reload-change.ts +0 -6
- package/.react-email/src/utils/types/hot-reload-event.ts +0 -6
- package/.react-email/src/utils/unreachable.ts +0 -8
- package/.react-email/tailwind.config.ts +0 -94
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import * as SlotPrimitive from '@radix-ui/react-slot';
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { type As, unreachable, cn } from '../utils';
|
|
4
|
-
|
|
5
|
-
export type TextSize = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
|
|
6
|
-
export type TextColor = 'gray' | 'white';
|
|
7
|
-
export type TextTransform = 'uppercase' | 'lowercase' | 'capitalize';
|
|
8
|
-
export type TextWeight = 'normal' | 'medium';
|
|
9
|
-
|
|
10
|
-
interface TextOwnProps {
|
|
11
|
-
size?: TextSize;
|
|
12
|
-
color?: TextColor;
|
|
13
|
-
transform?: TextTransform;
|
|
14
|
-
weight?: TextWeight;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type TextProps = As<'span', 'div', 'p'> & TextOwnProps;
|
|
18
|
-
|
|
19
|
-
export const Text = React.forwardRef<HTMLSpanElement, Readonly<TextProps>>(
|
|
20
|
-
(
|
|
21
|
-
{
|
|
22
|
-
as: Tag = 'span',
|
|
23
|
-
size = '2',
|
|
24
|
-
color = 'gray',
|
|
25
|
-
transform,
|
|
26
|
-
weight = 'normal',
|
|
27
|
-
className,
|
|
28
|
-
children,
|
|
29
|
-
...props
|
|
30
|
-
},
|
|
31
|
-
forwardedRef,
|
|
32
|
-
) => (
|
|
33
|
-
<SlotPrimitive.Slot
|
|
34
|
-
className={cn(
|
|
35
|
-
className,
|
|
36
|
-
transform,
|
|
37
|
-
getSizesClassNames(size),
|
|
38
|
-
getColorClassNames(color),
|
|
39
|
-
getWeightClassNames(weight),
|
|
40
|
-
)}
|
|
41
|
-
ref={forwardedRef}
|
|
42
|
-
{...props}
|
|
43
|
-
>
|
|
44
|
-
<Tag>{children}</Tag>
|
|
45
|
-
</SlotPrimitive.Slot>
|
|
46
|
-
),
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
const getSizesClassNames = (size: TextSize | undefined) => {
|
|
50
|
-
switch (size) {
|
|
51
|
-
case '1':
|
|
52
|
-
return 'text-xs';
|
|
53
|
-
case undefined:
|
|
54
|
-
case '2':
|
|
55
|
-
return 'text-sm';
|
|
56
|
-
case '3':
|
|
57
|
-
return 'text-base';
|
|
58
|
-
case '4':
|
|
59
|
-
return 'text-lg';
|
|
60
|
-
case '5':
|
|
61
|
-
return ['text-17px', 'md:text-xl tracking-[-0.16px]'];
|
|
62
|
-
case '6':
|
|
63
|
-
return 'text-2xl tracking-[-0.288px]';
|
|
64
|
-
case '7':
|
|
65
|
-
return 'text-[28px] leading-[34px] tracking-[-0.416px]';
|
|
66
|
-
case '8':
|
|
67
|
-
return 'text-[35px] leading-[42px] tracking-[-0.64px]';
|
|
68
|
-
case '9':
|
|
69
|
-
return 'text-6xl leading-[73px] tracking-[-0.896px]';
|
|
70
|
-
default:
|
|
71
|
-
return unreachable(size);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const getColorClassNames = (color: TextColor | undefined) => {
|
|
76
|
-
switch (color) {
|
|
77
|
-
case 'white':
|
|
78
|
-
return 'text-slate-12';
|
|
79
|
-
case undefined:
|
|
80
|
-
case 'gray':
|
|
81
|
-
return 'text-slate-11';
|
|
82
|
-
default:
|
|
83
|
-
return unreachable(color);
|
|
84
|
-
}
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const getWeightClassNames = (weight: TextWeight | undefined) => {
|
|
88
|
-
switch (weight) {
|
|
89
|
-
case undefined:
|
|
90
|
-
case 'normal':
|
|
91
|
-
return 'font-normal';
|
|
92
|
-
case 'medium':
|
|
93
|
-
return 'font-medium';
|
|
94
|
-
default:
|
|
95
|
-
return unreachable(weight);
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
Text.displayName = 'Text';
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { cn } from '../utils';
|
|
4
|
-
import { inter } from '../app/inter';
|
|
5
|
-
|
|
6
|
-
type ContentElement = React.ElementRef<typeof TooltipPrimitive.Content>;
|
|
7
|
-
type ContentProps = React.ComponentPropsWithoutRef<
|
|
8
|
-
typeof TooltipPrimitive.Content
|
|
9
|
-
>;
|
|
10
|
-
|
|
11
|
-
export type TooltipProps = ContentProps;
|
|
12
|
-
|
|
13
|
-
export const TooltipContent = React.forwardRef<
|
|
14
|
-
ContentElement,
|
|
15
|
-
Readonly<TooltipProps>
|
|
16
|
-
>(({ sideOffset = 6, children, ...props }, forwardedRef) => (
|
|
17
|
-
<TooltipPrimitive.Portal>
|
|
18
|
-
<TooltipPrimitive.Content
|
|
19
|
-
{...props}
|
|
20
|
-
className={cn(
|
|
21
|
-
'bg-black border border-slate-6 z-20 px-3 py-2 rounded-md text-xs',
|
|
22
|
-
`${inter.variable} font-sans`,
|
|
23
|
-
)}
|
|
24
|
-
ref={forwardedRef}
|
|
25
|
-
sideOffset={sideOffset}
|
|
26
|
-
>
|
|
27
|
-
{children}
|
|
28
|
-
</TooltipPrimitive.Content>
|
|
29
|
-
</TooltipPrimitive.Portal>
|
|
30
|
-
));
|
|
31
|
-
|
|
32
|
-
TooltipContent.displayName = 'TooltipContent';
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { TooltipContent } from './tooltip-content';
|
|
4
|
-
|
|
5
|
-
type RootProps = React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Root>;
|
|
6
|
-
|
|
7
|
-
export type TooltipProps = RootProps;
|
|
8
|
-
|
|
9
|
-
export const TooltipRoot: React.FC<Readonly<TooltipProps>> = ({
|
|
10
|
-
children,
|
|
11
|
-
...props
|
|
12
|
-
}) => <TooltipPrimitive.Root {...props}>{children}</TooltipPrimitive.Root>;
|
|
13
|
-
|
|
14
|
-
export const Tooltip = Object.assign(TooltipRoot, {
|
|
15
|
-
Arrow: TooltipPrimitive.TooltipArrow,
|
|
16
|
-
Provider: TooltipPrimitive.TooltipProvider,
|
|
17
|
-
Content: TooltipContent,
|
|
18
|
-
Trigger: TooltipPrimitive.TooltipTrigger,
|
|
19
|
-
});
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import * as ToggleGroup from '@radix-ui/react-toggle-group';
|
|
3
|
-
import { motion } from 'framer-motion';
|
|
4
|
-
import * as React from 'react';
|
|
5
|
-
import { cn } from '../utils';
|
|
6
|
-
import { tabTransition } from '../utils/constants';
|
|
7
|
-
import { Heading } from './heading';
|
|
8
|
-
import { IconHideSidebar } from './icons/icon-hide-sidebar';
|
|
9
|
-
import { IconMonitor } from './icons/icon-monitor';
|
|
10
|
-
import { IconPhone } from './icons/icon-phone';
|
|
11
|
-
import { IconSource } from './icons/icon-source';
|
|
12
|
-
import { Send } from './send';
|
|
13
|
-
import { Tooltip } from './tooltip';
|
|
14
|
-
|
|
15
|
-
interface TopbarProps {
|
|
16
|
-
currentEmailOpenSlug: string;
|
|
17
|
-
activeView?: string;
|
|
18
|
-
markup?: string;
|
|
19
|
-
onToggleSidebar?: () => void;
|
|
20
|
-
setActiveView?: (view: string) => void;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const Topbar: React.FC<Readonly<TopbarProps>> = ({
|
|
24
|
-
currentEmailOpenSlug,
|
|
25
|
-
markup,
|
|
26
|
-
activeView,
|
|
27
|
-
setActiveView,
|
|
28
|
-
onToggleSidebar,
|
|
29
|
-
}) => {
|
|
30
|
-
return (
|
|
31
|
-
<Tooltip.Provider>
|
|
32
|
-
<header className="flex relative items-center px-4 justify-between h-[70px] border-b border-slate-6">
|
|
33
|
-
<Tooltip>
|
|
34
|
-
<Tooltip.Trigger asChild>
|
|
35
|
-
<button
|
|
36
|
-
className="hidden lg:flex rounded-lg px-2 py-2 transition ease-in-out duration-200 relative hover:bg-slate-5 text-slate-11 hover:text-slate-12"
|
|
37
|
-
onClick={() => {
|
|
38
|
-
if (onToggleSidebar) {
|
|
39
|
-
onToggleSidebar();
|
|
40
|
-
}
|
|
41
|
-
}}
|
|
42
|
-
type="button"
|
|
43
|
-
>
|
|
44
|
-
<IconHideSidebar height={20} width={20} />
|
|
45
|
-
</button>
|
|
46
|
-
</Tooltip.Trigger>
|
|
47
|
-
<Tooltip.Content>Show/hide sidebar</Tooltip.Content>
|
|
48
|
-
</Tooltip>
|
|
49
|
-
|
|
50
|
-
<div className="items-center overflow-hidden hidden lg:flex text-center absolute left-1/2 transform -translate-x-1/2 top-1/2 -translate-y-1/2">
|
|
51
|
-
<Heading as="h2" className="truncate" size="2" weight="medium">
|
|
52
|
-
{currentEmailOpenSlug}
|
|
53
|
-
</Heading>
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<div className="flex gap-3 justify-between lg:justify-start w-full lg:w-fit">
|
|
57
|
-
<ToggleGroup.Root
|
|
58
|
-
aria-label="View mode"
|
|
59
|
-
className="inline-block items-center bg-slate-2 border border-slate-6 rounded-md overflow-hidden h-[36px]"
|
|
60
|
-
onValueChange={(value) => {
|
|
61
|
-
if (value) setActiveView?.(value);
|
|
62
|
-
}}
|
|
63
|
-
type="single"
|
|
64
|
-
value={activeView}
|
|
65
|
-
>
|
|
66
|
-
<ToggleGroup.Item value="desktop">
|
|
67
|
-
<Tooltip>
|
|
68
|
-
<Tooltip.Trigger asChild>
|
|
69
|
-
<div
|
|
70
|
-
className={cn(
|
|
71
|
-
'px-3 py-2 transition ease-in-out duration-200 relative hover:text-slate-12',
|
|
72
|
-
{
|
|
73
|
-
'text-slate-11': activeView !== 'desktop',
|
|
74
|
-
'text-slate-12': activeView === 'desktop',
|
|
75
|
-
},
|
|
76
|
-
)}
|
|
77
|
-
>
|
|
78
|
-
{activeView === 'desktop' && (
|
|
79
|
-
<motion.span
|
|
80
|
-
animate={{ opacity: 1 }}
|
|
81
|
-
className="absolute left-0 right-0 top-0 bottom-0 bg-slate-4"
|
|
82
|
-
exit={{ opacity: 0 }}
|
|
83
|
-
initial={{ opacity: 0 }}
|
|
84
|
-
layoutId="topbar-tabs"
|
|
85
|
-
transition={tabTransition}
|
|
86
|
-
/>
|
|
87
|
-
)}
|
|
88
|
-
<IconMonitor />
|
|
89
|
-
</div>
|
|
90
|
-
</Tooltip.Trigger>
|
|
91
|
-
<Tooltip.Content>Desktop</Tooltip.Content>
|
|
92
|
-
</Tooltip>
|
|
93
|
-
</ToggleGroup.Item>
|
|
94
|
-
<ToggleGroup.Item value="mobile">
|
|
95
|
-
<Tooltip>
|
|
96
|
-
<Tooltip.Trigger asChild>
|
|
97
|
-
<div
|
|
98
|
-
className={cn(
|
|
99
|
-
'px-3 py-2 transition ease-in-out duration-200 relative hover:text-slate-12',
|
|
100
|
-
{
|
|
101
|
-
'text-slate-11': activeView !== 'mobile',
|
|
102
|
-
'text-slate-12': activeView === 'mobile',
|
|
103
|
-
},
|
|
104
|
-
)}
|
|
105
|
-
>
|
|
106
|
-
{activeView === 'mobile' && (
|
|
107
|
-
<motion.span
|
|
108
|
-
animate={{ opacity: 1 }}
|
|
109
|
-
className="absolute left-0 right-0 top-0 bottom-0 bg-slate-4"
|
|
110
|
-
exit={{ opacity: 0 }}
|
|
111
|
-
initial={{ opacity: 0 }}
|
|
112
|
-
layoutId="topbar-tabs"
|
|
113
|
-
transition={tabTransition}
|
|
114
|
-
/>
|
|
115
|
-
)}
|
|
116
|
-
<IconPhone />
|
|
117
|
-
</div>
|
|
118
|
-
</Tooltip.Trigger>
|
|
119
|
-
<Tooltip.Content>Mobile</Tooltip.Content>
|
|
120
|
-
</Tooltip>
|
|
121
|
-
</ToggleGroup.Item>
|
|
122
|
-
<ToggleGroup.Item value="source">
|
|
123
|
-
<Tooltip>
|
|
124
|
-
<Tooltip.Trigger asChild>
|
|
125
|
-
<div
|
|
126
|
-
className={cn(
|
|
127
|
-
'px-3 py-2 transition ease-in-out duration-200 relative hover:text-slate-12',
|
|
128
|
-
{
|
|
129
|
-
'text-slate-11': activeView !== 'source',
|
|
130
|
-
'text-slate-12': activeView === 'source',
|
|
131
|
-
},
|
|
132
|
-
)}
|
|
133
|
-
>
|
|
134
|
-
{activeView === 'source' && (
|
|
135
|
-
<motion.span
|
|
136
|
-
animate={{ opacity: 1 }}
|
|
137
|
-
className="absolute left-0 right-0 top-0 bottom-0 bg-slate-4"
|
|
138
|
-
exit={{ opacity: 0 }}
|
|
139
|
-
initial={{ opacity: 0 }}
|
|
140
|
-
layoutId="topbar-tabs"
|
|
141
|
-
transition={tabTransition}
|
|
142
|
-
/>
|
|
143
|
-
)}
|
|
144
|
-
<IconSource />
|
|
145
|
-
</div>
|
|
146
|
-
</Tooltip.Trigger>
|
|
147
|
-
<Tooltip.Content>Code</Tooltip.Content>
|
|
148
|
-
</Tooltip>
|
|
149
|
-
</ToggleGroup.Item>
|
|
150
|
-
</ToggleGroup.Root>
|
|
151
|
-
|
|
152
|
-
{markup ? (
|
|
153
|
-
<div className="flex justify-end">
|
|
154
|
-
<Send markup={markup} />
|
|
155
|
-
</div>
|
|
156
|
-
) : null}
|
|
157
|
-
</div>
|
|
158
|
-
</header>
|
|
159
|
-
</Tooltip.Provider>
|
|
160
|
-
);
|
|
161
|
-
};
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { createContext, useContext, useEffect, useState } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
getEmailsDirectoryMetadata,
|
|
5
|
-
type EmailsDirectory,
|
|
6
|
-
} from '../actions/get-emails-directory-metadata';
|
|
7
|
-
import { useHotreload } from '../hooks/use-hot-reload';
|
|
8
|
-
import {
|
|
9
|
-
emailsDirRelativePath,
|
|
10
|
-
emailsDirectoryAbsolutePath,
|
|
11
|
-
normalizePath,
|
|
12
|
-
pathSeparator,
|
|
13
|
-
} from '../utils/emails-directory-absolute-path';
|
|
14
|
-
import {
|
|
15
|
-
renderEmailByPath,
|
|
16
|
-
type EmailRenderingResult,
|
|
17
|
-
} from '../actions/render-email-by-path';
|
|
18
|
-
import { getEmailPathFromSlug } from '../actions/get-email-path-from-slug';
|
|
19
|
-
|
|
20
|
-
const EmailsContext = createContext<
|
|
21
|
-
| {
|
|
22
|
-
emailsDirectoryMetadata: EmailsDirectory;
|
|
23
|
-
/**
|
|
24
|
-
* Uses the hot reloaded bundled build and rendering email result
|
|
25
|
-
*/
|
|
26
|
-
useEmailRenderingResult: (
|
|
27
|
-
emailPath: string,
|
|
28
|
-
serverEmailRenderedResult: EmailRenderingResult,
|
|
29
|
-
) => EmailRenderingResult;
|
|
30
|
-
}
|
|
31
|
-
| undefined
|
|
32
|
-
>(undefined);
|
|
33
|
-
|
|
34
|
-
export const useEmails = () => {
|
|
35
|
-
const providerValue = useContext(EmailsContext);
|
|
36
|
-
|
|
37
|
-
if (typeof providerValue === 'undefined') {
|
|
38
|
-
throw new Error(
|
|
39
|
-
'Cannot call `useEmail()` outside of an EmailsContext provider!',
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return providerValue;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
export const EmailsProvider = (props: {
|
|
47
|
-
initialEmailsDirectoryMetadata: EmailsDirectory;
|
|
48
|
-
children: React.ReactNode;
|
|
49
|
-
}) => {
|
|
50
|
-
const [emailsDirectoryMetadata, setEmailsDirectoryMetadata] =
|
|
51
|
-
useState<EmailsDirectory>(props.initialEmailsDirectoryMetadata);
|
|
52
|
-
|
|
53
|
-
const [renderingResultPerEmailPath, setRenderingResultPerEmailPath] =
|
|
54
|
-
useState<Record<string, EmailRenderingResult>>({});
|
|
55
|
-
|
|
56
|
-
if (process.env.NEXT_PUBLIC_IS_BUILDING !== 'true') {
|
|
57
|
-
// this will not change on runtime so it doesn't violate
|
|
58
|
-
// the rules of hooks
|
|
59
|
-
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
60
|
-
useHotreload(async (changes) => {
|
|
61
|
-
const metadata = await getEmailsDirectoryMetadata(
|
|
62
|
-
emailsDirectoryAbsolutePath,
|
|
63
|
-
);
|
|
64
|
-
if (metadata) {
|
|
65
|
-
setEmailsDirectoryMetadata(metadata);
|
|
66
|
-
} else {
|
|
67
|
-
throw new Error(
|
|
68
|
-
'Hot reloading: unable to find the emails directory to update the sidebar',
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
for await (const change of changes) {
|
|
73
|
-
const normalizedEmailsDirRelativePath = normalizePath(
|
|
74
|
-
emailsDirRelativePath,
|
|
75
|
-
);
|
|
76
|
-
const slugForChangedEmail =
|
|
77
|
-
// filename ex: emails/apple-receipt.tsx
|
|
78
|
-
// so we need to remove the "emails/" because it isn't used
|
|
79
|
-
// on the slug parameter for the preview page
|
|
80
|
-
change.filename
|
|
81
|
-
.replace(`${normalizedEmailsDirRelativePath}${pathSeparator}`, '')
|
|
82
|
-
.replace(normalizedEmailsDirRelativePath, '');
|
|
83
|
-
|
|
84
|
-
const pathForChangedEmail =
|
|
85
|
-
await getEmailPathFromSlug(slugForChangedEmail);
|
|
86
|
-
|
|
87
|
-
const lastResult = renderingResultPerEmailPath[pathForChangedEmail];
|
|
88
|
-
|
|
89
|
-
if (typeof lastResult !== 'undefined') {
|
|
90
|
-
const renderingResult = await renderEmailByPath(pathForChangedEmail);
|
|
91
|
-
|
|
92
|
-
setRenderingResultPerEmailPath((map) => ({
|
|
93
|
-
...map,
|
|
94
|
-
[pathForChangedEmail]: renderingResult,
|
|
95
|
-
}));
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return (
|
|
102
|
-
<EmailsContext.Provider
|
|
103
|
-
value={{
|
|
104
|
-
emailsDirectoryMetadata,
|
|
105
|
-
useEmailRenderingResult: (emailPath, serverEmailRenderedResult) => {
|
|
106
|
-
useEffect(() => {
|
|
107
|
-
if (typeof renderingResultPerEmailPath[emailPath] === 'undefined') {
|
|
108
|
-
setRenderingResultPerEmailPath((map) => ({
|
|
109
|
-
...map,
|
|
110
|
-
[emailPath]: serverEmailRenderedResult,
|
|
111
|
-
}));
|
|
112
|
-
}
|
|
113
|
-
}, [serverEmailRenderedResult, emailPath]);
|
|
114
|
-
|
|
115
|
-
if (typeof renderingResultPerEmailPath[emailPath] !== 'undefined') {
|
|
116
|
-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
117
|
-
return renderingResultPerEmailPath[emailPath]!;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return serverEmailRenderedResult;
|
|
121
|
-
},
|
|
122
|
-
}}
|
|
123
|
-
>
|
|
124
|
-
{props.children}
|
|
125
|
-
</EmailsContext.Provider>
|
|
126
|
-
);
|
|
127
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { useEffect, useRef } from 'react';
|
|
3
|
-
import { type Socket, io } from 'socket.io-client';
|
|
4
|
-
import type { HotReloadChange } from '../utils/types/hot-reload-change';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Hook that detects any "reload" event sent from the CLI's web socket
|
|
8
|
-
* and calls the received parameter callback
|
|
9
|
-
*/
|
|
10
|
-
export const useHotreload = (
|
|
11
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
-
onShouldReload: (changes: HotReloadChange[]) => any,
|
|
13
|
-
) => {
|
|
14
|
-
const socketRef = useRef<Socket | null>(null);
|
|
15
|
-
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
if (process.env.NODE_ENV === 'production') {
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (!socketRef.current) {
|
|
22
|
-
socketRef.current = io();
|
|
23
|
-
}
|
|
24
|
-
const socket = socketRef.current;
|
|
25
|
-
|
|
26
|
-
socket.on('reload', (changes: HotReloadChange[]) => {
|
|
27
|
-
console.debug('Reloading...');
|
|
28
|
-
void onShouldReload(changes);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
return () => {
|
|
32
|
-
socket.off();
|
|
33
|
-
};
|
|
34
|
-
}, [onShouldReload]);
|
|
35
|
-
};
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import type {
|
|
3
|
-
EmailRenderingResult,
|
|
4
|
-
RenderedEmailMetadata,
|
|
5
|
-
} from '../actions/render-email-by-path';
|
|
6
|
-
|
|
7
|
-
const lastRenderingMetadataPerEmailPath = {} as Record<
|
|
8
|
-
string,
|
|
9
|
-
RenderedEmailMetadata
|
|
10
|
-
>;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Returns the rendering metadata if the given `renderingResult`
|
|
14
|
-
* does not error. If it does error it returns the last value it had for the hook.
|
|
15
|
-
*/
|
|
16
|
-
export const useRenderingMetadata = (
|
|
17
|
-
emailPath: string,
|
|
18
|
-
renderingResult: EmailRenderingResult,
|
|
19
|
-
initialRenderingMetadata?: EmailRenderingResult,
|
|
20
|
-
): RenderedEmailMetadata | undefined => {
|
|
21
|
-
useEffect(() => {
|
|
22
|
-
if ('markup' in renderingResult) {
|
|
23
|
-
lastRenderingMetadataPerEmailPath[emailPath] = renderingResult;
|
|
24
|
-
} else if (
|
|
25
|
-
typeof initialRenderingMetadata !== 'undefined' &&
|
|
26
|
-
'markup' in initialRenderingMetadata &&
|
|
27
|
-
typeof lastRenderingMetadataPerEmailPath[emailPath] === 'undefined'
|
|
28
|
-
) {
|
|
29
|
-
lastRenderingMetadataPerEmailPath[emailPath] = initialRenderingMetadata;
|
|
30
|
-
}
|
|
31
|
-
}, [renderingResult, emailPath, initialRenderingMetadata]);
|
|
32
|
-
|
|
33
|
-
return 'error' in renderingResult
|
|
34
|
-
? lastRenderingMetadataPerEmailPath[emailPath]
|
|
35
|
-
: renderingResult;
|
|
36
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
-
export const emailsDirRelativePath =
|
|
3
|
-
process.env.NEXT_PUBLIC_EMAILS_DIR_RELATIVE_PATH ?? 'emails';
|
|
4
|
-
|
|
5
|
-
export const userProjectLocation =
|
|
6
|
-
process.env.NEXT_PUBLIC_USER_PROJECT_LOCATION!;
|
|
7
|
-
|
|
8
|
-
// this trickery to find the path separator for the OS is for this to work both on the client
|
|
9
|
-
// and on the server properly
|
|
10
|
-
export const pathSeparator = process.env.NEXT_PUBLIC_OS_PATH_SEPARATOR! as
|
|
11
|
-
| '/'
|
|
12
|
-
| '\\';
|
|
13
|
-
|
|
14
|
-
export const normalizePath = (path: string) => {
|
|
15
|
-
let newPath = path;
|
|
16
|
-
|
|
17
|
-
while (newPath.startsWith(`.${pathSeparator}`)) {
|
|
18
|
-
newPath = newPath.slice(2);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
while (newPath.startsWith(pathSeparator)) {
|
|
22
|
-
newPath = newPath.slice(1);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
while (newPath.endsWith(pathSeparator)) {
|
|
26
|
-
newPath = newPath.slice(0, -1);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return newPath;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const emailsDirectoryAbsolutePath = `${
|
|
33
|
-
process.env.NEXT_PUBLIC_USER_PROJECT_LOCATION
|
|
34
|
-
}${pathSeparator}${normalizePath(emailsDirRelativePath)}`;
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import vm from 'node:vm';
|
|
4
|
-
import { type RawSourceMap } from 'source-map-js';
|
|
5
|
-
import { type OutputFile, build, type BuildFailure } from 'esbuild';
|
|
6
|
-
import type { EmailTemplate as EmailComponent } from './types/email-template';
|
|
7
|
-
import type { ErrorObject } from './types/error-object';
|
|
8
|
-
import { improveErrorWithSourceMap } from './improve-error-with-sourcemap';
|
|
9
|
-
import { staticNodeModulesForVM } from './static-node-modules-for-vm';
|
|
10
|
-
|
|
11
|
-
export const getEmailComponent = async (
|
|
12
|
-
emailPath: string,
|
|
13
|
-
): Promise<
|
|
14
|
-
| {
|
|
15
|
-
emailComponent: EmailComponent;
|
|
16
|
-
|
|
17
|
-
sourceMapToOriginalFile: RawSourceMap;
|
|
18
|
-
}
|
|
19
|
-
| { error: ErrorObject }
|
|
20
|
-
> => {
|
|
21
|
-
let outputFiles: OutputFile[];
|
|
22
|
-
try {
|
|
23
|
-
const buildData = await build({
|
|
24
|
-
bundle: true,
|
|
25
|
-
entryPoints: [emailPath],
|
|
26
|
-
platform: 'node',
|
|
27
|
-
write: false,
|
|
28
|
-
format: 'cjs',
|
|
29
|
-
jsx: 'automatic',
|
|
30
|
-
logLevel: 'silent',
|
|
31
|
-
// allows for using jsx on a .js file
|
|
32
|
-
loader: {
|
|
33
|
-
'.js': 'jsx',
|
|
34
|
-
},
|
|
35
|
-
outdir: 'stdout', // just a stub for esbuild, it won't actually write to this folder
|
|
36
|
-
sourcemap: 'external',
|
|
37
|
-
});
|
|
38
|
-
outputFiles = buildData.outputFiles;
|
|
39
|
-
} catch (exception) {
|
|
40
|
-
const buildFailure = exception as BuildFailure;
|
|
41
|
-
return {
|
|
42
|
-
error: {
|
|
43
|
-
message: buildFailure.message,
|
|
44
|
-
stack: buildFailure.stack,
|
|
45
|
-
name: buildFailure.name,
|
|
46
|
-
cause: buildFailure.cause,
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const sourceMapFile = outputFiles[0]!;
|
|
52
|
-
const bundledEmailFile = outputFiles[1]!;
|
|
53
|
-
const builtEmailCode = bundledEmailFile.text;
|
|
54
|
-
|
|
55
|
-
const fakeContext = {
|
|
56
|
-
...global,
|
|
57
|
-
console,
|
|
58
|
-
Buffer,
|
|
59
|
-
module: { exports: { default: undefined as unknown } },
|
|
60
|
-
__filanem: emailPath,
|
|
61
|
-
__dirname: path.dirname(emailPath),
|
|
62
|
-
require: (module: string) => {
|
|
63
|
-
if (module in staticNodeModulesForVM) {
|
|
64
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
65
|
-
return staticNodeModulesForVM[module];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
69
|
-
return require(`${module}`) as unknown;
|
|
70
|
-
// this stupid string templating was necessary to not have
|
|
71
|
-
// webpack warnings like:
|
|
72
|
-
//
|
|
73
|
-
// Import trace for requested module:
|
|
74
|
-
// ./src/utils/get-email-component.tsx
|
|
75
|
-
// ./src/app/page.tsx
|
|
76
|
-
// ⚠ ./src/utils/get-email-component.tsx
|
|
77
|
-
// Critical dependency: the request of a dependency is an expression
|
|
78
|
-
},
|
|
79
|
-
process,
|
|
80
|
-
};
|
|
81
|
-
const sourceMapToEmail = JSON.parse(sourceMapFile.text) as RawSourceMap;
|
|
82
|
-
try {
|
|
83
|
-
vm.runInNewContext(builtEmailCode, fakeContext, { filename: emailPath });
|
|
84
|
-
} catch (exception) {
|
|
85
|
-
const error = exception as Error;
|
|
86
|
-
|
|
87
|
-
return {
|
|
88
|
-
error: improveErrorWithSourceMap(error, emailPath, sourceMapToEmail),
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (fakeContext.module.exports.default === undefined) {
|
|
93
|
-
return {
|
|
94
|
-
error: improveErrorWithSourceMap(
|
|
95
|
-
new Error(
|
|
96
|
-
`The email component at ${emailPath} does not contain a default export`,
|
|
97
|
-
),
|
|
98
|
-
emailPath,
|
|
99
|
-
sourceMapToEmail,
|
|
100
|
-
),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
emailComponent: fakeContext.module.exports.default as EmailComponent,
|
|
106
|
-
sourceMapToOriginalFile: sourceMapToEmail,
|
|
107
|
-
};
|
|
108
|
-
};
|