@react-email/preview-server 4.2.11 → 4.3.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/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +9 -9
- package/.next/build-manifest.json +2 -2
- package/.next/next-minimal-server.js.nft.json +1 -1
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +3 -3
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page.js +2 -2
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/preview/[...slug]/page.js +30 -30
- package/.next/server/app/preview/[...slug]/page.js.nft.json +1 -1
- package/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/235.js +1 -1
- package/.next/server/chunks/{597.js → 385.js} +7 -7
- package/.next/server/chunks/630.js +1 -1
- package/.next/server/chunks/727.js +1 -0
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/442-9645091f2b304619.js +1 -0
- package/.next/static/chunks/{615-5d450200bdf8a0cb.js → 615-aa01e647fd9055dc.js} +1 -1
- package/.next/static/chunks/900-d73ea57316faa50d.js +1 -0
- package/.next/static/chunks/app/layout-0337303a89a72f7e.js +1 -0
- package/.next/static/chunks/app/{page-af54466b804e69f7.js → page-80a93dc65160c488.js} +1 -1
- package/.next/static/chunks/app/preview/[...slug]/page-3205284657cb4573.js +1 -0
- package/.next/static/css/7d8cf00703036864.css +3 -0
- package/.next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
- package/.next/static/media/21350d82a1f187e9-s.woff2 +0 -0
- package/.next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
- package/.next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
- package/.next/trace +29 -29
- package/CHANGELOG.md +8 -0
- package/package.json +12 -12
- package/readme.md +3 -4
- package/src/actions/email-validation/check-compatibility.ts +1 -0
- package/src/actions/get-email-path-from-slug.ts +1 -1
- package/src/actions/render-email-by-path.tsx +8 -3
- package/src/app/env.ts +0 -4
- package/src/app/layout.tsx +8 -5
- package/src/app/page.tsx +2 -4
- package/src/app/preview/[...slug]/error-overlay.tsx +1 -0
- package/src/app/preview/[...slug]/page.tsx +3 -5
- package/src/app/preview/[...slug]/preview.tsx +28 -24
- package/src/components/resizable-wrapper.tsx +170 -71
- package/src/components/toolbar.tsx +2 -3
- package/src/components/topbar/active-view-toggle-group.tsx +4 -4
- package/src/components/topbar/view-size-controls.tsx +107 -186
- package/src/contexts/emails.tsx +3 -6
- package/src/contexts/preview.tsx +13 -1
- package/src/hooks/use-hot-reload.ts +2 -2
- package/src/utils/caniemail/get-compatibility-stats-for-entry.ts +0 -1
- package/src/utils/caniemail/tailwind/get-tailwind-config.spec.ts +2 -2
- package/src/utils/contains-email-template.spec.ts +6 -6
- package/src/utils/convert-stack-with-sourcemap.ts +0 -1
- package/src/utils/get-emails-directory-metadata.ts +0 -1
- package/src/utils/js-email-detection.spec.ts +3 -3
- package/src/utils/run-bundled-code.ts +1 -3
- package/src/utils/testing/request-response-email.tsx +0 -1
- package/tailwind-internals.d.ts +0 -2
- package/.next/server/chunks/420.js +0 -1
- package/.next/static/chunks/557-287480320fe241b8.js +0 -1
- package/.next/static/chunks/926-cd84f2c04e4197e1.js +0 -1
- package/.next/static/chunks/app/layout-581001443a6ac38a.js +0 -1
- package/.next/static/chunks/app/preview/[...slug]/page-356d4a373756b232.js +0 -1
- package/.next/static/css/e2f28c91a6a919eb.css +0 -3
- package/.next/static/media/26a46d62cd723877-s.woff2 +0 -0
- package/.next/static/media/55c55f0601d81cf3-s.woff2 +0 -0
- package/.next/static/media/581909926a08bbc8-s.woff2 +0 -0
- package/.next/static/media/97e0cb1ae144a2a9-s.woff2 +0 -0
- package/src/contexts/fragment-identifier.tsx +0 -48
- package/src/hooks/use-icon-animation.ts +0 -41
- /package/.next/static/{OIjazFWdAl7sqkAJKSCpp → bw7nsTv8dL6IXcqslAAMG}/_buildManifest.js +0 -0
- /package/.next/static/{OIjazFWdAl7sqkAJKSCpp → bw7nsTv8dL6IXcqslAAMG}/_ssgManifest.js +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import { motion } from 'framer-motion';
|
|
1
|
+
import * as Popover from '@radix-ui/react-popover';
|
|
3
2
|
import * as React from 'react';
|
|
4
3
|
import { cn } from '../../utils';
|
|
5
4
|
import { IconArrowDown } from '../icons/icon-arrow-down';
|
|
@@ -11,208 +10,103 @@ interface ViewDimensions {
|
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
interface ViewSizeControlsProps {
|
|
13
|
+
minWidth: number;
|
|
14
|
+
minHeight: number;
|
|
14
15
|
viewWidth: number;
|
|
15
16
|
setViewWidth: (width: number) => void;
|
|
16
17
|
viewHeight: number;
|
|
17
18
|
setViewHeight: (height: number) => void;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
interface DimensionInputProps {
|
|
21
|
-
icon: React.ReactNode;
|
|
22
|
-
isActive: boolean;
|
|
23
|
-
label: string;
|
|
24
|
-
onBlur: () => void;
|
|
25
|
-
onChange: (value: number) => void;
|
|
26
|
-
setIsActive: (active: boolean) => void;
|
|
27
|
-
value: number;
|
|
28
|
-
hasBorder?: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
21
|
interface PresetOption {
|
|
32
22
|
name: string;
|
|
33
23
|
dimensions: ViewDimensions;
|
|
24
|
+
icon: React.ReactNode;
|
|
34
25
|
}
|
|
35
26
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
padding: '0 0 0 0.5rem',
|
|
27
|
+
export const VIEW_PRESETS: PresetOption[] = [
|
|
28
|
+
{
|
|
29
|
+
name: 'Desktop',
|
|
30
|
+
dimensions: { width: 1024, height: 600 },
|
|
31
|
+
icon: (
|
|
32
|
+
<svg width="15" height="15" viewBox="0 0 15 15" fill="none">
|
|
33
|
+
<path
|
|
34
|
+
d="M1 3.25C1 3.11193 1.11193 3 1.25 3H13.75C13.8881 3 14 3.11193 14 3.25V10.75C14 10.8881 13.8881 11 13.75 11H1.25C1.11193 11 1 10.8881 1 10.75V3.25ZM1.25 2C0.559643 2 0 2.55964 0 3.25V10.75C0 11.4404 0.559644 12 1.25 12H5.07341L4.82991 13.2986C4.76645 13.6371 5.02612 13.95 5.37049 13.95H9.62951C9.97389 13.95 10.2336 13.6371 10.1701 13.2986L9.92659 12H13.75C14.4404 12 15 11.4404 15 10.75V3.25C15 2.55964 14.4404 2 13.75 2H1.25ZM9.01091 12H5.98909L5.79222 13.05H9.20778L9.01091 12Z"
|
|
35
|
+
fill="currentColor"
|
|
36
|
+
fillRule="evenodd"
|
|
37
|
+
clipRule="evenodd"
|
|
38
|
+
/>
|
|
39
|
+
</svg>
|
|
40
|
+
),
|
|
51
41
|
},
|
|
52
|
-
|
|
53
|
-
|
|
42
|
+
{
|
|
43
|
+
name: 'Mobile',
|
|
44
|
+
dimensions: { width: 375, height: 667 },
|
|
45
|
+
icon: (
|
|
46
|
+
<svg width="15" height="15" viewBox="0 0 15 15" fill="none">
|
|
47
|
+
<path
|
|
48
|
+
d="M4 2.5C4 2.22386 4.22386 2 4.5 2H10.5C10.7761 2 11 2.22386 11 2.5V12.5C11 12.7761 10.7761 13 10.5 13H4.5C4.22386 13 4 12.7761 4 12.5V2.5ZM4.5 1C3.67157 1 3 1.67157 3 2.5V12.5C3 13.3284 3.67157 14 4.5 14H10.5C11.3284 14 12 13.3284 12 12.5V2.5C12 1.67157 11.3284 1 10.5 1H4.5ZM6 11.65C5.8067 11.65 5.65 11.8067 5.65 12C5.65 12.1933 5.8067 12.35 6 12.35H9C9.1933 12.35 9.35 12.1933 9.35 12C9.35 11.8067 9.1933 11.65 9 11.65H6Z"
|
|
49
|
+
fill="currentColor"
|
|
50
|
+
fillRule="evenodd"
|
|
51
|
+
clipRule="evenodd"
|
|
52
|
+
/>
|
|
53
|
+
</svg>
|
|
54
|
+
),
|
|
54
55
|
},
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const DimensionInput = ({
|
|
58
|
-
icon,
|
|
59
|
-
isActive,
|
|
60
|
-
label,
|
|
61
|
-
onBlur,
|
|
62
|
-
onChange,
|
|
63
|
-
setIsActive,
|
|
64
|
-
value,
|
|
65
|
-
hasBorder,
|
|
66
|
-
}: DimensionInputProps) => {
|
|
67
|
-
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
68
|
-
|
|
69
|
-
React.useEffect(() => {
|
|
70
|
-
if (isActive && inputRef.current) {
|
|
71
|
-
inputRef.current.focus();
|
|
72
|
-
}
|
|
73
|
-
}, [isActive]);
|
|
74
|
-
|
|
75
|
-
const handleButtonClick = () => {
|
|
76
|
-
if (isActive) {
|
|
77
|
-
setIsActive(false);
|
|
78
|
-
} else {
|
|
79
|
-
setIsActive(true);
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<Tooltip.Provider>
|
|
85
|
-
<Tooltip>
|
|
86
|
-
<Tooltip.Trigger asChild>
|
|
87
|
-
<motion.button
|
|
88
|
-
onClick={handleButtonClick}
|
|
89
|
-
className={cn('relative flex items-center justify-center p-2', {
|
|
90
|
-
'border-slate-6 border-r': hasBorder,
|
|
91
|
-
})}
|
|
92
|
-
>
|
|
93
|
-
{icon}
|
|
94
|
-
<motion.input
|
|
95
|
-
ref={inputRef}
|
|
96
|
-
initial={false}
|
|
97
|
-
animate={isActive ? 'active' : 'inactive'}
|
|
98
|
-
className="arrow-hide relative flex h-8 items-center justify-center bg-black text-sm outline-none"
|
|
99
|
-
onChange={(e) => onChange(Number.parseInt(e.currentTarget.value))}
|
|
100
|
-
onBlur={onBlur}
|
|
101
|
-
type="number"
|
|
102
|
-
value={value}
|
|
103
|
-
variants={inputVariant}
|
|
104
|
-
/>
|
|
105
|
-
</motion.button>
|
|
106
|
-
</Tooltip.Trigger>
|
|
107
|
-
<Tooltip.Content>
|
|
108
|
-
<span>{label}: </span>
|
|
109
|
-
<span className="font-mono">{value}px</span>
|
|
110
|
-
</Tooltip.Content>
|
|
111
|
-
</Tooltip>
|
|
112
|
-
</Tooltip.Provider>
|
|
113
|
-
);
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const PresetMenuItem = ({
|
|
117
|
-
name,
|
|
118
|
-
dimensions,
|
|
119
|
-
onSelect,
|
|
120
|
-
}: PresetMenuItemProps) => (
|
|
121
|
-
<DropdownMenu.Item
|
|
122
|
-
className="group flex w-full cursor-pointer select-none items-center justify-between rounded-md py-1.5 pr-1 pl-2 text-sm outline-none transition-colors data-[highlighted]:bg-slate-5"
|
|
123
|
-
onClick={() => onSelect(dimensions)}
|
|
124
|
-
>
|
|
125
|
-
{name}
|
|
126
|
-
<span className="flex h-fit items-center rounded-full bg-slate-6 px-2 py-1 font-medium text-slate-11 text-xs">
|
|
127
|
-
{dimensions.width}x{dimensions.height}
|
|
128
|
-
</span>
|
|
129
|
-
</DropdownMenu.Item>
|
|
130
|
-
);
|
|
56
|
+
];
|
|
131
57
|
|
|
132
58
|
export const ViewSizeControls = ({
|
|
59
|
+
minWidth,
|
|
60
|
+
minHeight,
|
|
133
61
|
viewWidth,
|
|
134
62
|
viewHeight,
|
|
135
63
|
setViewWidth,
|
|
136
64
|
setViewHeight,
|
|
137
65
|
}: ViewSizeControlsProps) => {
|
|
138
66
|
const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
|
|
139
|
-
const [
|
|
140
|
-
|
|
141
|
-
>(null);
|
|
67
|
+
const [internalWidth, setInternalWidth] = React.useState(viewWidth);
|
|
68
|
+
const [internalHeight, setInternalHeight] = React.useState(viewHeight);
|
|
142
69
|
|
|
143
70
|
const handlePresetSelect = (dimensions: ViewDimensions) => {
|
|
144
71
|
setViewWidth(dimensions.width);
|
|
145
72
|
setViewHeight(dimensions.height);
|
|
146
73
|
};
|
|
147
74
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
75
|
+
React.useEffect(() => {
|
|
76
|
+
setInternalWidth(viewWidth);
|
|
77
|
+
setInternalHeight(viewHeight);
|
|
78
|
+
}, [viewWidth, viewHeight]);
|
|
151
79
|
|
|
152
80
|
return (
|
|
153
|
-
<div className="relative flex h-9 w-fit overflow-hidden rounded-lg border border-slate-6 text-sm transition-colors duration-300 ease-in-out
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
</
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
onBlur={handleBlur}
|
|
180
|
-
label="Width"
|
|
181
|
-
hasBorder
|
|
182
|
-
/>
|
|
183
|
-
<DimensionInput
|
|
184
|
-
icon={
|
|
185
|
-
<svg
|
|
186
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
187
|
-
width="16"
|
|
188
|
-
height="16"
|
|
189
|
-
viewBox="0 0 24 24"
|
|
190
|
-
fill="none"
|
|
191
|
-
stroke="currentColor"
|
|
192
|
-
strokeWidth="2"
|
|
193
|
-
strokeLinecap="round"
|
|
194
|
-
strokeLinejoin="round"
|
|
195
|
-
>
|
|
196
|
-
<path d="M8 3H5a2 2 0 0 0-2 2v14c0 1.1.9 2 2 2h3" />
|
|
197
|
-
<path d="M16 3h3a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-3" />
|
|
198
|
-
<path d="M12 20v2" />
|
|
199
|
-
<path d="M12 14v2" />
|
|
200
|
-
<path d="M12 8v2" />
|
|
201
|
-
<path d="M12 2v2" />
|
|
202
|
-
</svg>
|
|
203
|
-
}
|
|
204
|
-
value={viewHeight}
|
|
205
|
-
onChange={setViewHeight}
|
|
206
|
-
isActive={activeInput === 'height'}
|
|
207
|
-
setIsActive={(active) => setActiveInput(active ? 'height' : null)}
|
|
208
|
-
onBlur={handleBlur}
|
|
209
|
-
label="Height"
|
|
210
|
-
/>
|
|
211
|
-
<DropdownMenu.Root open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
|
|
212
|
-
<DropdownMenu.Trigger asChild>
|
|
81
|
+
<div className="relative flex h-9 w-fit overflow-hidden rounded-lg border border-slate-6 text-sm transition-colors duration-300 ease-in-out">
|
|
82
|
+
{VIEW_PRESETS.map((preset) => (
|
|
83
|
+
<Tooltip>
|
|
84
|
+
<Tooltip.Trigger asChild>
|
|
85
|
+
<button
|
|
86
|
+
key={preset.name}
|
|
87
|
+
onClick={() => handlePresetSelect(preset.dimensions)}
|
|
88
|
+
className={cn(
|
|
89
|
+
'relative flex items-center justify-center w-9 transition-colors hover:text-slate-12',
|
|
90
|
+
{
|
|
91
|
+
'bg-slate-4':
|
|
92
|
+
viewWidth === preset.dimensions.width &&
|
|
93
|
+
viewHeight === preset.dimensions.height,
|
|
94
|
+
},
|
|
95
|
+
)}
|
|
96
|
+
type="button"
|
|
97
|
+
>
|
|
98
|
+
{preset.icon}
|
|
99
|
+
</button>
|
|
100
|
+
</Tooltip.Trigger>
|
|
101
|
+
<Tooltip.Content>{preset.name}</Tooltip.Content>
|
|
102
|
+
</Tooltip>
|
|
103
|
+
))}
|
|
104
|
+
|
|
105
|
+
<Popover.Root open={isDropdownOpen} onOpenChange={setIsDropdownOpen}>
|
|
106
|
+
<Popover.Trigger asChild>
|
|
213
107
|
<button
|
|
214
108
|
type="button"
|
|
215
|
-
className="relative flex items-center justify-center overflow-hidden
|
|
109
|
+
className="relative flex items-center justify-center overflow-hidden w-9 text-slate-11 text-sm leading-none outline-none transition-colors ease-linear focus-within:text-slate-12 hover:text-slate-12 focus:text-slate-12"
|
|
216
110
|
>
|
|
217
111
|
<span className="sr-only">View presets</span>
|
|
218
112
|
<IconArrowDown
|
|
@@ -224,24 +118,51 @@ export const ViewSizeControls = ({
|
|
|
224
118
|
)}
|
|
225
119
|
/>
|
|
226
120
|
</button>
|
|
227
|
-
</
|
|
228
|
-
<
|
|
229
|
-
<
|
|
121
|
+
</Popover.Trigger>
|
|
122
|
+
<Popover.Portal>
|
|
123
|
+
<Popover.Content
|
|
230
124
|
align="end"
|
|
231
125
|
className="flex min-w-[12rem] flex-col gap-2 rounded-md border border-slate-8 border-solid bg-black px-2 py-2 text-white"
|
|
232
126
|
sideOffset={5}
|
|
233
127
|
>
|
|
234
|
-
|
|
235
|
-
<
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
128
|
+
<div className="flex w-full items-center justify-between text-sm gap-2">
|
|
129
|
+
<span className="font-medium text-slate-11 text-xs">Width</span>
|
|
130
|
+
<input
|
|
131
|
+
type="number"
|
|
132
|
+
value={internalWidth}
|
|
133
|
+
onChange={(e) => {
|
|
134
|
+
const value = Number(e.target.value);
|
|
135
|
+
|
|
136
|
+
setInternalWidth(value);
|
|
137
|
+
|
|
138
|
+
if (value >= minWidth) {
|
|
139
|
+
setViewWidth(value);
|
|
140
|
+
}
|
|
141
|
+
}}
|
|
142
|
+
className="w-20 appearance-none rounded-lg border border-slate-6 bg-slate-5 px-1 py-1 text-sm text-slate-12 placeholder-slate-10 outline-none transition duration-300 ease-in-out focus:ring-1 focus:ring-slate-10"
|
|
143
|
+
/>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
<div className="flex w-full items-center justify-between text-sm gap-2">
|
|
147
|
+
<span className="font-medium text-slate-11 text-xs">Height</span>
|
|
148
|
+
<input
|
|
149
|
+
type="number"
|
|
150
|
+
value={internalHeight}
|
|
151
|
+
onChange={(e) => {
|
|
152
|
+
const value = Number(e.target.value);
|
|
153
|
+
|
|
154
|
+
setInternalHeight(value);
|
|
155
|
+
|
|
156
|
+
if (value >= minHeight) {
|
|
157
|
+
setViewHeight(value);
|
|
158
|
+
}
|
|
159
|
+
}}
|
|
160
|
+
className="w-20 appearance-none rounded-lg border border-slate-6 bg-slate-5 px-1 py-1 text-sm text-slate-12 placeholder-slate-10 outline-none transition duration-300 ease-in-out focus:ring-1 focus:ring-slate-10"
|
|
240
161
|
/>
|
|
241
|
-
|
|
242
|
-
</
|
|
243
|
-
</
|
|
244
|
-
</
|
|
162
|
+
</div>
|
|
163
|
+
</Popover.Content>
|
|
164
|
+
</Popover.Portal>
|
|
165
|
+
</Popover.Root>
|
|
245
166
|
</div>
|
|
246
167
|
);
|
|
247
168
|
};
|
package/src/contexts/emails.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
+
|
|
2
3
|
import { createContext, useContext, useState } from 'react';
|
|
3
4
|
import { getEmailsDirectoryMetadataAction } from '../actions/get-emails-directory-metadata-action';
|
|
4
5
|
import { isBuilding, isPreviewDevelopment } from '../app/env';
|
|
@@ -17,7 +18,7 @@ export const useEmails = () => {
|
|
|
17
18
|
|
|
18
19
|
if (typeof providerValue === 'undefined') {
|
|
19
20
|
throw new Error(
|
|
20
|
-
'Cannot call `
|
|
21
|
+
'Cannot call `useEmails` outside of an `EmailsContext` provider.',
|
|
21
22
|
);
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -48,11 +49,7 @@ export const EmailsProvider = (props: {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
return (
|
|
51
|
-
<EmailsContext.Provider
|
|
52
|
-
value={{
|
|
53
|
-
emailsDirectoryMetadata,
|
|
54
|
-
}}
|
|
55
|
-
>
|
|
52
|
+
<EmailsContext.Provider value={{ emailsDirectoryMetadata }}>
|
|
56
53
|
{props.children}
|
|
57
54
|
</EmailsContext.Provider>
|
|
58
55
|
);
|
package/src/contexts/preview.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { useRouter } from 'next/navigation';
|
|
3
|
-
import { createContext } from 'react';
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
4
|
import type {
|
|
5
5
|
EmailRenderingResult,
|
|
6
6
|
RenderedEmailMetadata,
|
|
@@ -77,3 +77,15 @@ export const PreviewProvider = ({
|
|
|
77
77
|
</PreviewContext.Provider>
|
|
78
78
|
);
|
|
79
79
|
};
|
|
80
|
+
|
|
81
|
+
export const usePreviewContext = () => {
|
|
82
|
+
const previewContext = useContext(PreviewContext);
|
|
83
|
+
|
|
84
|
+
if (typeof previewContext === 'undefined') {
|
|
85
|
+
throw new Error(
|
|
86
|
+
'Cannot call `usePreviewContext` outside of an `PreviewContext` provider.',
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return previewContext;
|
|
91
|
+
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use client';
|
|
2
|
+
|
|
2
3
|
import { useEffect, useRef } from 'react';
|
|
3
4
|
import { io, type Socket } from 'socket.io-client';
|
|
4
5
|
import type { HotReloadChange } from '../utils/types/hot-reload-change';
|
|
@@ -8,8 +9,7 @@ import type { HotReloadChange } from '../utils/types/hot-reload-change';
|
|
|
8
9
|
* and calls the received parameter callback
|
|
9
10
|
*/
|
|
10
11
|
export const useHotreload = (
|
|
11
|
-
|
|
12
|
-
onShouldReload: (changes: HotReloadChange[]) => any,
|
|
12
|
+
onShouldReload: (changes: HotReloadChange[]) => void,
|
|
13
13
|
) => {
|
|
14
14
|
const socketRef = useRef<Socket | null>(null);
|
|
15
15
|
|
|
@@ -5,7 +5,7 @@ import { pixelBasedPreset } from '@react-email/components';
|
|
|
5
5
|
import { getTailwindConfig } from './get-tailwind-config';
|
|
6
6
|
|
|
7
7
|
describe('getTailwindConfig()', () => {
|
|
8
|
-
it("
|
|
8
|
+
it("works on the demo's Vercel Invite template", async () => {
|
|
9
9
|
const sourcePath = path.resolve(
|
|
10
10
|
__dirname,
|
|
11
11
|
'../../../../../../apps/demo/emails/notifications/vercel-invite-user.tsx',
|
|
@@ -23,7 +23,7 @@ describe('getTailwindConfig()', () => {
|
|
|
23
23
|
});
|
|
24
24
|
});
|
|
25
25
|
|
|
26
|
-
it('
|
|
26
|
+
it('works with email templates that import the tailwind config', async () => {
|
|
27
27
|
const sourcePath = path.resolve(
|
|
28
28
|
__dirname,
|
|
29
29
|
'./tests/dummy-email-template.tsx',
|
|
@@ -5,19 +5,19 @@ import {
|
|
|
5
5
|
import type { EmailsDirectory } from './get-emails-directory-metadata';
|
|
6
6
|
|
|
7
7
|
describe('removeFilenameExtension()', () => {
|
|
8
|
-
it('
|
|
8
|
+
it('works with a single .', () => {
|
|
9
9
|
expect(removeFilenameExtension('email-template.tsx')).toBe(
|
|
10
10
|
'email-template',
|
|
11
11
|
);
|
|
12
12
|
});
|
|
13
13
|
|
|
14
|
-
it('
|
|
14
|
+
it('works with an example test file', () => {
|
|
15
15
|
expect(removeFilenameExtension('email-template.spec.tsx')).toBe(
|
|
16
16
|
'email-template.spec',
|
|
17
17
|
);
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
it('
|
|
20
|
+
it('does nothing when there is no extension', () => {
|
|
21
21
|
expect(removeFilenameExtension('email-template')).toBe('email-template');
|
|
22
22
|
});
|
|
23
23
|
});
|
|
@@ -112,11 +112,11 @@ describe('containsEmailTemplate()', () => {
|
|
|
112
112
|
],
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
-
it('
|
|
115
|
+
it('works with collapsed email directory', () => {
|
|
116
116
|
expect(containsEmailTemplate('first/second/email', directory)).toBe(true);
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
it('
|
|
119
|
+
it('works with email inside a single sub directory', () => {
|
|
120
120
|
expect(containsEmailTemplate('welcome/koala-welcome', directory)).toBe(
|
|
121
121
|
true,
|
|
122
122
|
);
|
|
@@ -125,7 +125,7 @@ describe('containsEmailTemplate()', () => {
|
|
|
125
125
|
);
|
|
126
126
|
});
|
|
127
127
|
|
|
128
|
-
it('
|
|
128
|
+
it('works with email inside a second sub directory', () => {
|
|
129
129
|
expect(
|
|
130
130
|
containsEmailTemplate('magic-links/resend/verify-email', directory),
|
|
131
131
|
).toBe(true);
|
|
@@ -47,7 +47,6 @@ export const convertStackWithSourceMap = (
|
|
|
47
47
|
getStackLineFromMethodNameAndSource(
|
|
48
48
|
stackFrame.methodName,
|
|
49
49
|
// This can actually be null
|
|
50
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
51
50
|
positionWithError.source ?? stackFrame.file,
|
|
52
51
|
positionWithError.line,
|
|
53
52
|
positionWithError.column,
|
|
@@ -5,19 +5,19 @@ describe('JavaScript Email Detection', async () => {
|
|
|
5
5
|
const testingDir = path.resolve(__dirname, 'testing');
|
|
6
6
|
const emailsMetadata = await getEmailsDirectoryMetadata(testingDir, true);
|
|
7
7
|
|
|
8
|
-
it('
|
|
8
|
+
it('detects JavaScript files with ES6 export default syntax', async () => {
|
|
9
9
|
expect(emailsMetadata).toBeDefined();
|
|
10
10
|
expect(emailsMetadata?.emailFilenames).toContain(
|
|
11
11
|
'js-email-export-default.js',
|
|
12
12
|
);
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
it('
|
|
15
|
+
it('detects JavaScript files with CommonJS module.exports', async () => {
|
|
16
16
|
expect(emailsMetadata).toBeDefined();
|
|
17
17
|
expect(emailsMetadata?.emailFilenames).toContain('js-email-test.js');
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
it('
|
|
20
|
+
it('detects MDX-style JavaScript files with named exports', async () => {
|
|
21
21
|
expect(emailsMetadata).toBeDefined();
|
|
22
22
|
expect(emailsMetadata?.emailFilenames).toContain('mdx-email-test.js');
|
|
23
23
|
});
|
|
@@ -35,13 +35,11 @@ export const createContext = (filename: string): vm.Context => {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
if (m in staticNodeModulesForVM) {
|
|
38
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
39
38
|
return staticNodeModulesForVM[m];
|
|
40
39
|
}
|
|
41
40
|
|
|
42
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-useless-template-literals
|
|
43
41
|
return require(`${specifiedModule}`) as unknown;
|
|
44
|
-
// this
|
|
42
|
+
// this string templating was necessary to not have
|
|
45
43
|
// webpack warnings like:
|
|
46
44
|
//
|
|
47
45
|
// Import trace for requested module:
|
package/tailwind-internals.d.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
exports.id=420,exports.ids=[420],exports.modules={8332:(a,b,c)=>{"use strict";c.r(b),c.d(b,{getEmailsDirectoryMetadata:()=>i});var d=c(73024),e=c.n(d),f=c(76760),g=c.n(f);let h=async a=>{let b;try{b=await e().promises.open(a,"r")}catch(a){return console.warn(a),!1}if((await b.stat()).isDirectory())return await b.close(),!1;let{ext:c}=g().parse(a);if(![".js",".tsx",".jsx"].includes(c))return await b.close(),!1;let d=await b.readFile("utf8");await b.close();let f=/\bexport\s+default\b/gm.test(d),h=/\bmodule\.exports\s*=/gm.test(d),i=/\bexport\s+\{[^}]*\bdefault\b[^}]*\}/gm.test(d);return f||h||i},i=async(a,b=!1,c=!1,d=a)=>{if(!e().existsSync(a))return;let f=await e().promises.readdir(a,{withFileTypes:!0}),j=await Promise.all(f.map(b=>h(g().join(a,b.name)))),k=f.filter((a,b)=>j[b]).map(a=>b?a.name:a.name.replace(g().extname(a.name),"")),l=await Promise.all(f.filter(a=>a.isDirectory()&&!a.name.startsWith("_")&&"static"!==a.name).map(c=>i(g().join(a,c.name),b,!0,d))),m={absolutePath:a,relativePath:g().relative(d,a),directoryName:a.split(g().sep).pop(),emailFilenames:k,subDirectories:l};return c?(a=>{let b=a;for(;0===b.emailFilenames.length&&1===b.subDirectories.length;){let a=b.subDirectories[0];b={...a,directoryName:g().join(b.directoryName,a.directoryName)}}return b})(m):m}},15482:(a,b,c)=>{"use strict";c.d(b,{EmailsProvider:()=>l,J:()=>k});var d=c(6362),e=c(2179),f=c(14298);let g=(0,f.createServerReference)("7f009fd45016bdb4200c108baf47a913fb024a9adb",f.callServer,void 0,f.findSourceMapURL,"getEmailsDirectoryMetadataAction");var h=c(70985),i=c(39520);let j=(0,e.createContext)(void 0),k=()=>{let a=(0,e.useContext)(j);if(void 0===a)throw Error("Cannot call `useEmail()` outside of an EmailsContext provider!");return a},l=a=>{let[b,c]=(0,e.useState)(a.initialEmailsDirectoryMetadata);return h.Hf||h.m4||(0,i.a)(async()=>{let b=await g(a.initialEmailsDirectoryMetadata.absolutePath);if(b)c(b);else throw Error("Hot reloading: unable to find the emails directory to update the sidebar")}),(0,d.jsx)(j.Provider,{value:{emailsDirectoryMetadata:b},children:a.children})}},25880:(a,b,c)=>{Promise.resolve().then(c.t.bind(c,36100,23)),Promise.resolve().then(c.t.bind(c,99455,23)),Promise.resolve().then(c.t.bind(c,26835,23)),Promise.resolve().then(c.t.bind(c,36726,23)),Promise.resolve().then(c.t.bind(c,33666,23)),Promise.resolve().then(c.t.bind(c,23670,23)),Promise.resolve().then(c.t.bind(c,30006,23)),Promise.resolve().then(c.t.bind(c,41971,23)),Promise.resolve().then(c.t.bind(c,40614,23))},27378:(a,b,c)=>{Promise.resolve().then(c.bind(c,90948))},35191:()=>{},35608:(a,b,c)=>{Promise.resolve().then(c.t.bind(c,5058,23)),Promise.resolve().then(c.t.bind(c,35621,23)),Promise.resolve().then(c.t.bind(c,44325,23)),Promise.resolve().then(c.t.bind(c,20908,23)),Promise.resolve().then(c.t.bind(c,83572,23)),Promise.resolve().then(c.t.bind(c,25928,23)),Promise.resolve().then(c.t.bind(c,57920,23)),Promise.resolve().then(c.t.bind(c,2761,23)),Promise.resolve().then(c.bind(c,98400))},39520:(a,b,c)=>{"use strict";c.d(b,{a:()=>f});var d=c(2179),e=c(80095);let f=a=>{let b=(0,d.useRef)(null);(0,d.useEffect)(()=>{b.current||(b.current=(0,e.io)());let c=b.current;return c.on("reload",b=>{console.debug("Reloading..."),a(b)}),()=>{c.off()}},[a])}},50938:(a,b,c)=>{Promise.resolve().then(c.bind(c,15482))},54208:(a,b,c)=>{"use strict";c.r(b),c.d(b,{default:()=>n,dynamic:()=>m,metadata:()=>l});var d=c(51064);c(35191);var e=c(90948),f=c(8332),g=c(88155),h=c(84858),i=c.n(h),j=c(54629),k=c.n(j);let l={title:"React Email"},m="force-dynamic",n=async({children:a})=>{let b=await (0,f.getEmailsDirectoryMetadata)(g.Z8);if(void 0===b)throw Error(`Could not find the emails directory specified under ${g.Z8}!`);return(0,d.jsx)("html",{className:`${i().variable} ${k().variable} font-sans`,lang:"en",children:(0,d.jsx)("body",{className:"relative h-screen bg-black text-slate-11 leading-loose selection:bg-cyan-5 selection:text-cyan-12",children:(0,d.jsx)("div",{className:"bg-gradient-to-t from-slate-3 flex flex-col",children:(0,d.jsx)(e.EmailsProvider,{initialEmailsDirectoryMetadata:b,children:a})})})})}},70985:(a,b,c)=>{"use strict";c.d(b,{Hf:()=>d,m4:()=>e}),process.env.EMAILS_DIR_RELATIVE_PATH,process.env.USER_PROJECT_LOCATION,process.env.PREVIEW_SERVER_LOCATION,process.env.EMAILS_DIR_ABSOLUTE_PATH;let d="true"===process.env.NEXT_PUBLIC_IS_BUILDING,e="true"===process.env.NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT},72112:()=>{},86944:(a,b,c)=>{"use strict";c.r(b),c.d(b,{default:()=>e});var d=c(89993);let e=async a=>[{type:"image/x-icon",sizes:"16x16",url:(0,d.fillMetadataSegment)(".",await a.params,"favicon.ico")+""}]},88155:(a,b,c)=>{"use strict";c.d(b,{Hf:()=>g,Z8:()=>f,m4:()=>h,n_:()=>e,w5:()=>d}),process.env.EMAILS_DIR_RELATIVE_PATH;let d=process.env.USER_PROJECT_LOCATION,e=process.env.PREVIEW_SERVER_LOCATION,f=process.env.EMAILS_DIR_ABSOLUTE_PATH,g="true"===process.env.NEXT_PUBLIC_IS_BUILDING,h="true"===process.env.NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT},88167:(a,b,c)=>{"use strict";c.r(b),c.d(b,{"7f009fd45016bdb4200c108baf47a913fb024a9adb":()=>l});var d=c(90466);c(69300);var e=c(73024),f=c.n(e),g=c(76760),h=c.n(g);let i=async a=>{let b;try{b=await f().promises.open(a,"r")}catch(a){return console.warn(a),!1}if((await b.stat()).isDirectory())return await b.close(),!1;let{ext:c}=h().parse(a);if(![".js",".tsx",".jsx"].includes(c))return await b.close(),!1;let d=await b.readFile("utf8");await b.close();let e=/\bexport\s+default\b/gm.test(d),g=/\bmodule\.exports\s*=/gm.test(d),i=/\bexport\s+\{[^}]*\bdefault\b[^}]*\}/gm.test(d);return e||g||i},j=async(a,b=!1,c=!1,d=a)=>{if(!f().existsSync(a))return;let e=await f().promises.readdir(a,{withFileTypes:!0}),g=await Promise.all(e.map(b=>i(h().join(a,b.name)))),k=e.filter((a,b)=>g[b]).map(a=>b?a.name:a.name.replace(h().extname(a.name),"")),l=await Promise.all(e.filter(a=>a.isDirectory()&&!a.name.startsWith("_")&&"static"!==a.name).map(c=>j(h().join(a,c.name),b,!0,d))),m={absolutePath:a,relativePath:h().relative(d,a),directoryName:a.split(h().sep).pop(),emailFilenames:k,subDirectories:l};return c?(a=>{let b=a;for(;0===b.emailFilenames.length&&1===b.subDirectories.length;){let a=b.subDirectories[0];b={...a,directoryName:h().join(b.directoryName,a.directoryName)}}return b})(m):m};var k=c(18012);let l=async(a,b=!1,c=!1,d=a)=>j(a,b,c,d);(0,k.D)([l]),(0,d.A)(l,"7f009fd45016bdb4200c108baf47a913fb024a9adb",null)},90948:(a,b,c)=>{"use strict";c.d(b,{EmailsProvider:()=>e});var d=c(9756);(0,d.registerClientReference)(function(){throw Error("Attempted to call useEmails() from the server but useEmails is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"/home/runner/actions-runner/_work/react-email/react-email/packages/preview-server/src/contexts/emails.tsx","useEmails");let e=(0,d.registerClientReference)(function(){throw Error("Attempted to call EmailsProvider() from the server but EmailsProvider is on the client. It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.")},"/home/runner/actions-runner/_work/react-email/react-email/packages/preview-server/src/contexts/emails.tsx","EmailsProvider")},92665:()=>{}};
|