react-email 4.0.0-alpha.7 → 4.0.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/CHANGELOG.md +2 -48
- package/dist/cli/index.js +24 -17
- package/dist/cli/index.mjs +32 -25
- package/dist/preview/.next/BUILD_ID +1 -1
- package/dist/preview/.next/app-build-manifest.json +10 -10
- package/dist/preview/.next/app-path-routes-manifest.json +1 -1
- package/dist/preview/.next/build-manifest.json +3 -3
- package/dist/preview/.next/cache/.rscinfo +1 -1
- package/dist/preview/.next/cache/webpack/client-production/0.pack +0 -0
- package/dist/preview/.next/cache/webpack/client-production/index.pack +0 -0
- package/dist/preview/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/dist/preview/.next/cache/webpack/server-production/0.pack +0 -0
- package/dist/preview/.next/cache/webpack/server-production/index.pack +0 -0
- package/dist/preview/.next/next-minimal-server.js.nft.json +1 -1
- package/dist/preview/.next/next-server.js.nft.json +1 -1
- package/dist/preview/.next/prerender-manifest.json +3 -3
- package/dist/preview/.next/required-server-files.json +3 -3
- package/dist/preview/.next/server/app/_not-found/page.js +1 -1
- package/dist/preview/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/preview/.next/server/app/favicon.ico/route.js +1 -1
- package/dist/preview/.next/server/app/page.js +1 -1
- package/dist/preview/.next/server/app/page.js.nft.json +1 -1
- package/dist/preview/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/preview/.next/server/app/preview/[...slug]/page.js +115 -11
- package/dist/preview/.next/server/app/preview/[...slug]/page.js.nft.json +1 -1
- package/dist/preview/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +1 -1
- package/dist/preview/.next/server/app-paths-manifest.json +1 -1
- package/dist/preview/.next/server/chunks/203.js +1 -0
- package/dist/preview/.next/server/chunks/600.js +3 -3
- package/dist/preview/.next/server/middleware-build-manifest.js +1 -1
- package/dist/preview/.next/server/next-font-manifest.js +1 -1
- package/dist/preview/.next/server/next-font-manifest.json +1 -1
- package/dist/preview/.next/server/pages/500.html +1 -1
- package/dist/preview/.next/server/server-reference-manifest.js +1 -1
- package/dist/preview/.next/server/server-reference-manifest.json +1 -1
- package/dist/preview/.next/static/chunks/683-017aee9270cb8999.js +1 -0
- package/dist/preview/.next/static/chunks/app/layout-311310b665ad8e17.js +1 -0
- package/dist/preview/.next/static/chunks/app/{page-9ea0bd45cd6294b0.js → page-9d038f3c5feb0570.js} +1 -1
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-db4014629693f2b3.js +1 -0
- package/dist/preview/.next/static/chunks/{main-app-256b213b179a95cc.js → main-app-785b93ae096c4901.js} +1 -1
- package/dist/preview/.next/static/css/dda71861895dd2e4.css +3 -0
- package/dist/preview/.next/trace +26 -26
- package/dist/preview/.next/types/app/layout.ts +1 -1
- package/dist/preview/.next/types/app/page.ts +1 -1
- package/dist/preview/.next/types/app/preview/[...slug]/page.ts +1 -1
- package/package.json +1 -1
- package/src/actions/email-validation/check-compatibility.ts +16 -4
- package/src/app/layout.tsx +1 -1
- package/src/app/page.tsx +4 -4
- package/src/app/preview/[...slug]/preview.tsx +35 -26
- package/src/components/code-container.tsx +2 -2
- package/src/components/code.tsx +16 -7
- package/src/components/resizable-wrapper.tsx +3 -3
- package/src/components/shell.tsx +13 -18
- package/src/components/sidebar/file-tree-directory-children.tsx +4 -2
- package/src/components/sidebar/file-tree-directory.tsx +18 -18
- package/src/components/sidebar/file-tree.tsx +2 -2
- package/src/components/sidebar/sidebar.tsx +16 -18
- package/src/components/toolbar/linter.tsx +35 -33
- package/src/components/toolbar/results.tsx +1 -1
- package/src/components/toolbar/spam-assassin.tsx +16 -13
- package/src/components/toolbar/use-cached-state.ts +2 -2
- package/src/components/toolbar.tsx +103 -30
- package/src/components/topbar.tsx +17 -5
- package/src/utils/__snapshots__/get-email-component.spec.ts.snap +1 -1
- package/src/utils/caniemail/ast/__snapshots__/get-object-variables.spec.ts.snap +74 -0
- package/src/utils/caniemail/ast/__snapshots__/get-used-style-properties.spec.ts.snap +24 -0
- package/src/utils/caniemail/ast/get-object-variables.spec.ts +19 -0
- package/src/utils/caniemail/ast/get-used-style-properties.spec.ts +23 -0
- package/src/utils/caniemail/get-css-property-with-value.ts +2 -2
- package/tailwind.config.ts +8 -0
- package/dist/preview/.next/server/chunks/943.js +0 -1
- package/dist/preview/.next/static/chunks/683-1fb40795502f6e63.js +0 -1
- package/dist/preview/.next/static/chunks/app/layout-ffdee5cc1be30e7b.js +0 -1
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-9e22979a25c836c0.js +0 -1
- package/dist/preview/.next/static/css/eaae8ce545b295f9.css +0 -3
- /package/dist/preview/.next/static/{Pms2orsQgT5xpttCfZfH5 → t4yeIF5ZYqVHaYIHgPxHn}/_buildManifest.js +0 -0
- /package/dist/preview/.next/static/{Pms2orsQgT5xpttCfZfH5 → t4yeIF5ZYqVHaYIHgPxHn}/_ssgManifest.js +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// File: /home/gabriel/Projects/Resend/react-email/packages/react-email/src/app/layout.tsx
|
|
1
|
+
// File: /home/gabriel/Projects/Resend/react-email.git/release/packages/react-email/src/app/layout.tsx
|
|
2
2
|
import * as entry from '../../../src/app/layout.js'
|
|
3
3
|
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// File: /home/gabriel/Projects/Resend/react-email/packages/react-email/src/app/page.tsx
|
|
1
|
+
// File: /home/gabriel/Projects/Resend/react-email.git/release/packages/react-email/src/app/page.tsx
|
|
2
2
|
import * as entry from '../../../src/app/page.js'
|
|
3
3
|
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// File: /home/gabriel/Projects/Resend/react-email/packages/react-email/src/app/preview/[...slug]/page.tsx
|
|
1
|
+
// File: /home/gabriel/Projects/Resend/react-email.git/release/packages/react-email/src/app/preview/[...slug]/page.tsx
|
|
2
2
|
import * as entry from '../../../../../src/app/preview/[...slug]/page.js'
|
|
3
3
|
import type { ResolvingMetadata, ResolvingViewport } from 'next/dist/lib/metadata/types/metadata-interface.js'
|
|
4
4
|
|
package/package.json
CHANGED
|
@@ -154,7 +154,11 @@ export const checkCompatibility = async (
|
|
|
154
154
|
);
|
|
155
155
|
if (Object.keys(compatibilityStats.perEmailClient).length === 0)
|
|
156
156
|
continue;
|
|
157
|
-
if (
|
|
157
|
+
if (
|
|
158
|
+
compatibilityStats.status === 'success' ||
|
|
159
|
+
compatibilityStats.status === 'warning'
|
|
160
|
+
)
|
|
161
|
+
continue;
|
|
158
162
|
|
|
159
163
|
if (entry.category === 'html') {
|
|
160
164
|
const entryElements = getElementNames(entry.title, entry.keywords);
|
|
@@ -274,8 +278,9 @@ export const checkCompatibility = async (
|
|
|
274
278
|
|
|
275
279
|
if (cssEntryType === 'full property') {
|
|
276
280
|
if (
|
|
277
|
-
property.name ===
|
|
278
|
-
|
|
281
|
+
snakeToCamel(property.name) ===
|
|
282
|
+
snakeToCamel(entryFullProperty!.name) &&
|
|
283
|
+
property.value === entryFullProperty!.value
|
|
279
284
|
) {
|
|
280
285
|
addToInsights(property);
|
|
281
286
|
break;
|
|
@@ -302,7 +307,8 @@ export const checkCompatibility = async (
|
|
|
302
307
|
}
|
|
303
308
|
} else if (
|
|
304
309
|
entryProperties.some(
|
|
305
|
-
(propertyName) =>
|
|
310
|
+
(propertyName) =>
|
|
311
|
+
snakeToCamel(property.name) === snakeToCamel(propertyName),
|
|
306
312
|
)
|
|
307
313
|
) {
|
|
308
314
|
addToInsights(property);
|
|
@@ -318,4 +324,10 @@ export const checkCompatibility = async (
|
|
|
318
324
|
return readableStream;
|
|
319
325
|
};
|
|
320
326
|
|
|
327
|
+
const snakeToCamel = (snakeStr: string) => {
|
|
328
|
+
return snakeStr
|
|
329
|
+
.toLowerCase()
|
|
330
|
+
.replace(/-+([a-z])/g, (_match, letter) => letter.toUpperCase());
|
|
331
|
+
};
|
|
332
|
+
|
|
321
333
|
export type AST = ReturnType<typeof parse>;
|
package/src/app/layout.tsx
CHANGED
|
@@ -27,7 +27,7 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
|
|
|
27
27
|
className={`${inter.variable} ${sfMono.variable} font-sans`}
|
|
28
28
|
lang="en"
|
|
29
29
|
>
|
|
30
|
-
<body className="relative flex h-screen flex-col
|
|
30
|
+
<body className="relative flex h-screen flex-col bg-black text-slate-11 leading-loose selection:bg-cyan-5 selection:text-cyan-12">
|
|
31
31
|
<EmailsProvider
|
|
32
32
|
initialEmailsDirectoryMetadata={emailsDirectoryMetadata}
|
|
33
33
|
>
|
package/src/app/page.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import Image from 'next/image';
|
|
|
3
3
|
import Link from 'next/link';
|
|
4
4
|
import { Button, Heading, Text } from '../components';
|
|
5
5
|
import CodeSnippet from '../components/code-snippet';
|
|
6
|
-
import { Shell
|
|
6
|
+
import { Shell } from '../components/shell';
|
|
7
7
|
import { emailsDirectoryAbsolutePath } from './env';
|
|
8
8
|
import logo from './logo.png';
|
|
9
9
|
|
|
@@ -12,8 +12,8 @@ const Home = () => {
|
|
|
12
12
|
|
|
13
13
|
return (
|
|
14
14
|
<Shell>
|
|
15
|
-
<
|
|
16
|
-
<div className="-mt-10 relative flex flex-col items-center gap-3 text-center">
|
|
15
|
+
<div className="w-full h-full flex items-center justify-center p-8">
|
|
16
|
+
<div className="-mt-10 relative max-w-lg flex flex-col items-center gap-3 text-center">
|
|
17
17
|
<Image
|
|
18
18
|
alt="React Email Icon"
|
|
19
19
|
className="mb-8"
|
|
@@ -38,7 +38,7 @@ const Home = () => {
|
|
|
38
38
|
<Link href="https://react.email/docs">Check the docs</Link>
|
|
39
39
|
</Button>
|
|
40
40
|
</div>
|
|
41
|
-
</
|
|
41
|
+
</div>
|
|
42
42
|
</Shell>
|
|
43
43
|
);
|
|
44
44
|
};
|
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
4
|
-
import { use,
|
|
4
|
+
import { use, useState } from 'react';
|
|
5
5
|
import { flushSync } from 'react-dom';
|
|
6
6
|
import { Toaster } from 'sonner';
|
|
7
7
|
import { useDebouncedCallback } from 'use-debounce';
|
|
8
8
|
import { Topbar } from '../../../components';
|
|
9
9
|
import { CodeContainer } from '../../../components/code-container';
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
ResizableWrapper,
|
|
12
12
|
makeIframeDocumentBubbleEvents,
|
|
13
13
|
} from '../../../components/resizable-wrapper';
|
|
14
14
|
import { Send } from '../../../components/send';
|
|
15
|
-
import {
|
|
15
|
+
import { useToolbarState } from '../../../components/toolbar';
|
|
16
16
|
import { Tooltip } from '../../../components/tooltip';
|
|
17
17
|
import { ActiveViewToggleGroup } from '../../../components/topbar/active-view-toggle-group';
|
|
18
18
|
import { ViewSizeControls } from '../../../components/topbar/view-size-controls';
|
|
19
19
|
import { PreviewContext } from '../../../contexts/preview';
|
|
20
20
|
import { useClampedState } from '../../../hooks/use-clamped-state';
|
|
21
|
+
import { cn } from '../../../utils';
|
|
21
22
|
import { RenderingError } from './rendering-error';
|
|
22
23
|
|
|
23
|
-
interface PreviewProps {
|
|
24
|
+
interface PreviewProps extends React.ComponentProps<'div'> {
|
|
24
25
|
emailTitle: string;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
const Preview = ({ emailTitle }: PreviewProps) => {
|
|
28
|
+
const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
|
|
28
29
|
const { renderingResult, renderedEmailMetadata } = use(PreviewContext)!;
|
|
29
30
|
|
|
30
31
|
const router = useRouter();
|
|
@@ -53,21 +54,21 @@ const Preview = ({ emailTitle }: PreviewProps) => {
|
|
|
53
54
|
const hasRenderingMetadata = typeof renderedEmailMetadata !== 'undefined';
|
|
54
55
|
const hasErrors = 'error' in renderingResult;
|
|
55
56
|
|
|
56
|
-
const
|
|
57
|
-
const
|
|
58
|
-
const minWidth =
|
|
59
|
-
const minHeight =
|
|
57
|
+
const [maxWidth, setMaxWidth] = useState(Number.POSITIVE_INFINITY);
|
|
58
|
+
const [maxHeight, setMaxHeight] = useState(Number.POSITIVE_INFINITY);
|
|
59
|
+
const minWidth = 100;
|
|
60
|
+
const minHeight = 100;
|
|
60
61
|
const storedWidth = searchParams.get('width');
|
|
61
62
|
const storedHeight = searchParams.get('height');
|
|
62
63
|
const [width, setWidth] = useClampedState(
|
|
63
64
|
storedWidth ? Number.parseInt(storedWidth) : 600,
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
minWidth,
|
|
66
|
+
maxWidth,
|
|
66
67
|
);
|
|
67
68
|
const [height, setHeight] = useClampedState(
|
|
68
69
|
storedHeight ? Number.parseInt(storedHeight) : 1024,
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
minHeight,
|
|
71
|
+
maxHeight,
|
|
71
72
|
);
|
|
72
73
|
|
|
73
74
|
const handleSaveViewSize = useDebouncedCallback(() => {
|
|
@@ -77,6 +78,8 @@ const Preview = ({ emailTitle }: PreviewProps) => {
|
|
|
77
78
|
router.push(`${pathname}?${params.toString()}${location.hash}`);
|
|
78
79
|
}, 300);
|
|
79
80
|
|
|
81
|
+
const { toggled: toolbarToggled } = useToolbarState();
|
|
82
|
+
|
|
80
83
|
return (
|
|
81
84
|
<>
|
|
82
85
|
<Topbar emailTitle={emailTitle}>
|
|
@@ -107,14 +110,20 @@ const Preview = ({ emailTitle }: PreviewProps) => {
|
|
|
107
110
|
) : null}
|
|
108
111
|
</Topbar>
|
|
109
112
|
|
|
110
|
-
<
|
|
111
|
-
|
|
113
|
+
<div
|
|
114
|
+
{...props}
|
|
115
|
+
className={cn(
|
|
116
|
+
'h-[calc(100%-3.5rem-2.375rem)] will-change-height flex p-4 transition-all duration-300',
|
|
117
|
+
activeView === 'preview' && 'bg-gray-200',
|
|
118
|
+
toolbarToggled && 'h-[calc(100%-3.5rem-13rem)]',
|
|
119
|
+
className,
|
|
120
|
+
)}
|
|
112
121
|
ref={(element) => {
|
|
113
122
|
const observer = new ResizeObserver((entry) => {
|
|
114
123
|
const [elementEntry] = entry;
|
|
115
124
|
if (elementEntry) {
|
|
116
|
-
|
|
117
|
-
|
|
125
|
+
setMaxWidth(elementEntry.contentRect.width);
|
|
126
|
+
setMaxHeight(elementEntry.contentRect.height);
|
|
118
127
|
}
|
|
119
128
|
});
|
|
120
129
|
|
|
@@ -132,11 +141,11 @@ const Preview = ({ emailTitle }: PreviewProps) => {
|
|
|
132
141
|
{hasRenderingMetadata ? (
|
|
133
142
|
<>
|
|
134
143
|
{activeView === 'preview' && (
|
|
135
|
-
<
|
|
144
|
+
<ResizableWrapper
|
|
136
145
|
minHeight={minHeight}
|
|
137
146
|
minWidth={minWidth}
|
|
138
|
-
maxHeight={
|
|
139
|
-
maxWidth={
|
|
147
|
+
maxHeight={maxHeight}
|
|
148
|
+
maxWidth={maxWidth}
|
|
140
149
|
height={height}
|
|
141
150
|
onResizeEnd={() => {
|
|
142
151
|
handleSaveViewSize();
|
|
@@ -145,9 +154,9 @@ const Preview = ({ emailTitle }: PreviewProps) => {
|
|
|
145
154
|
const isHorizontal =
|
|
146
155
|
direction === 'east' || direction === 'west';
|
|
147
156
|
if (isHorizontal) {
|
|
148
|
-
setWidth(value);
|
|
157
|
+
setWidth(Math.round(value));
|
|
149
158
|
} else {
|
|
150
|
-
setHeight(value);
|
|
159
|
+
setHeight(Math.round(value));
|
|
151
160
|
}
|
|
152
161
|
}}
|
|
153
162
|
width={width}
|
|
@@ -166,12 +175,12 @@ const Preview = ({ emailTitle }: PreviewProps) => {
|
|
|
166
175
|
}}
|
|
167
176
|
title={emailTitle}
|
|
168
177
|
/>
|
|
169
|
-
</
|
|
178
|
+
</ResizableWrapper>
|
|
170
179
|
)}
|
|
171
180
|
|
|
172
181
|
{activeView === 'source' && (
|
|
173
|
-
<div className="h-full w-full
|
|
174
|
-
<div className="m-auto flex max-w-3xl p-6">
|
|
182
|
+
<div className="h-full w-full">
|
|
183
|
+
<div className="m-auto h-full flex max-w-3xl p-6">
|
|
175
184
|
<Tooltip.Provider>
|
|
176
185
|
<CodeContainer
|
|
177
186
|
activeLang={activeLang}
|
|
@@ -199,7 +208,7 @@ const Preview = ({ emailTitle }: PreviewProps) => {
|
|
|
199
208
|
) : null}
|
|
200
209
|
|
|
201
210
|
<Toaster />
|
|
202
|
-
</
|
|
211
|
+
</div>
|
|
203
212
|
</>
|
|
204
213
|
);
|
|
205
214
|
};
|
|
@@ -39,7 +39,7 @@ export const CodeContainer: React.FC<Readonly<CodeContainerProps>> = ({
|
|
|
39
39
|
|
|
40
40
|
return (
|
|
41
41
|
<div
|
|
42
|
-
className="relative w-full
|
|
42
|
+
className="relative max-h-[650px] w-full h-full whitespace-pre rounded-md border border-slate-6 text-sm"
|
|
43
43
|
style={{
|
|
44
44
|
lineHeight: '130%',
|
|
45
45
|
background:
|
|
@@ -84,7 +84,7 @@ export const CodeContainer: React.FC<Readonly<CodeContainerProps>> = ({
|
|
|
84
84
|
filename={`email.${activeMarkup.language}`}
|
|
85
85
|
/>
|
|
86
86
|
</div>
|
|
87
|
-
<div>
|
|
87
|
+
<div className="h-[calc(100%-2.25rem)]">
|
|
88
88
|
<Code language={activeLang}>{activeMarkup.content}</Code>
|
|
89
89
|
</div>
|
|
90
90
|
</div>
|
package/src/components/code.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import Link from 'next/link';
|
|
|
3
3
|
import { useSearchParams } from 'next/navigation';
|
|
4
4
|
import type { Language } from 'prism-react-renderer';
|
|
5
5
|
import { Highlight } from 'prism-react-renderer';
|
|
6
|
-
import { Fragment, useEffect } from 'react';
|
|
6
|
+
import { Fragment, useEffect, useRef } from 'react';
|
|
7
7
|
import { useFragmentIdentifier } from '../hooks/use-fragment-identifier';
|
|
8
8
|
import { cn } from '../utils';
|
|
9
9
|
|
|
@@ -73,12 +73,18 @@ export const Code: React.FC<Readonly<CodeProps>> = ({
|
|
|
73
73
|
return highlight[0] <= line && highlight[1] >= line;
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
+
const scrollerRef = useRef<HTMLDivElement>(null);
|
|
77
|
+
|
|
76
78
|
useEffect(() => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
const scroller = scrollerRef.current;
|
|
80
|
+
if (highlight && scroller) {
|
|
81
|
+
const lineElement = scroller.querySelector(`#L${highlight[0]}`);
|
|
82
|
+
if (lineElement instanceof HTMLAnchorElement) {
|
|
83
|
+
scroller.scrollTo({
|
|
84
|
+
top: Math.max(lineElement.offsetTop - 325, 0),
|
|
85
|
+
behavior: 'smooth',
|
|
86
|
+
});
|
|
87
|
+
}
|
|
82
88
|
}
|
|
83
89
|
}, [highlight]);
|
|
84
90
|
|
|
@@ -97,7 +103,10 @@ export const Code: React.FC<Readonly<CodeProps>> = ({
|
|
|
97
103
|
'linear-gradient(90deg, rgba(56, 189, 248, 0) 0%, rgba(56, 189, 248, 0) 0%, rgba(232, 232, 232, 0.2) 33.02%, rgba(143, 143, 143, 0.6719) 64.41%, rgba(236, 72, 153, 0) 98.93%)',
|
|
98
104
|
}}
|
|
99
105
|
/>
|
|
100
|
-
<div
|
|
106
|
+
<div
|
|
107
|
+
ref={scrollerRef}
|
|
108
|
+
className="flex max-h-[650px] h-full p-4 after:w-full after:static after:block after:h-4 after:content-[''] overflow-auto"
|
|
109
|
+
>
|
|
101
110
|
<div className="text-[#49494f] text-[13px] font-light font-[MonoLisa,_Menlo,_monospace]">
|
|
102
111
|
{tokens.map((_, i) => (
|
|
103
112
|
<Link
|
|
@@ -4,7 +4,7 @@ import { cn } from '../utils';
|
|
|
4
4
|
|
|
5
5
|
type Direction = 'north' | 'south' | 'east' | 'west';
|
|
6
6
|
|
|
7
|
-
type
|
|
7
|
+
type ResizableWrapperProps = {
|
|
8
8
|
width: number;
|
|
9
9
|
height: number;
|
|
10
10
|
|
|
@@ -41,7 +41,7 @@ export const makeIframeDocumentBubbleEvents = (iframe: HTMLIFrameElement) => {
|
|
|
41
41
|
};
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
export const
|
|
44
|
+
export const ResizableWrapper = ({
|
|
45
45
|
width,
|
|
46
46
|
height,
|
|
47
47
|
onResize,
|
|
@@ -54,7 +54,7 @@ export const ResizableWarpper = ({
|
|
|
54
54
|
minWidth,
|
|
55
55
|
|
|
56
56
|
...rest
|
|
57
|
-
}:
|
|
57
|
+
}: ResizableWrapperProps) => {
|
|
58
58
|
const resizableRef = useRef<HTMLElement>(null);
|
|
59
59
|
|
|
60
60
|
const mouseMoveListener = useRef<(event: MouseEvent) => void>(null);
|
package/src/components/shell.tsx
CHANGED
|
@@ -62,36 +62,31 @@ export const Shell = ({ children, currentEmailOpenSlug }: ShellProps) => {
|
|
|
62
62
|
</svg>
|
|
63
63
|
</button>
|
|
64
64
|
</div>
|
|
65
|
-
<div className="
|
|
65
|
+
<div className="w-[100dvw] flex h-[calc(100dvh-4.375rem)] lg:h-[100dvh]">
|
|
66
66
|
<React.Suspense>
|
|
67
67
|
<Sidebar
|
|
68
|
-
className={cn(
|
|
69
|
-
'-
|
|
70
|
-
'lg:w-
|
|
71
|
-
|
|
68
|
+
className={cn(
|
|
69
|
+
'fixed top-[4.375rem] left-0 z-[9999] h-full max-h-full w-full max-w-full will-change-auto [transition:width_0.2s_ease-in-out]',
|
|
70
|
+
'lg:static lg:inline-block lg:z-auto lg:max-h-full lg:w-[16rem]',
|
|
71
|
+
{
|
|
72
|
+
'-translate-x-full lg:translate-x-0': sidebarToggled,
|
|
73
|
+
'lg:w-0': !sidebarToggled,
|
|
74
|
+
},
|
|
75
|
+
)}
|
|
72
76
|
currentEmailOpenSlug={currentEmailOpenSlug}
|
|
73
77
|
/>
|
|
74
78
|
</React.Suspense>
|
|
75
79
|
<main
|
|
76
80
|
className={cn(
|
|
77
|
-
'
|
|
78
|
-
'
|
|
81
|
+
'inline-block relative overflow-hidden will-change-width',
|
|
82
|
+
'w-full h-full',
|
|
79
83
|
'[transition:width_0.2s_ease-in-out,_transform_0.2s_ease-in-out]',
|
|
84
|
+
sidebarToggled && 'lg:w-[calc(100%-16rem)]',
|
|
80
85
|
)}
|
|
81
86
|
>
|
|
82
|
-
|
|
87
|
+
{children}
|
|
83
88
|
</main>
|
|
84
89
|
</div>
|
|
85
90
|
</ShellContext.Provider>
|
|
86
91
|
);
|
|
87
92
|
};
|
|
88
|
-
|
|
89
|
-
type ShellContentRootProps = React.ComponentProps<'div'>;
|
|
90
|
-
|
|
91
|
-
export const ShellContent = ({ children, ...rest }: ShellContentRootProps) => {
|
|
92
|
-
return (
|
|
93
|
-
<div {...rest} className={cn('relative grow', rest.className)}>
|
|
94
|
-
{children}
|
|
95
|
-
</div>
|
|
96
|
-
);
|
|
97
|
-
};
|
|
@@ -77,7 +77,7 @@ export const FileTreeDirectoryChildren = (props: {
|
|
|
77
77
|
<motion.span
|
|
78
78
|
animate={{ x: 0, opacity: 1 }}
|
|
79
79
|
className={cn(
|
|
80
|
-
'relative flex h-8
|
|
80
|
+
'relative flex h-8 w-full items-center text-start gap-2 rounded-md align-middle text-slate-11 text-sm transition-colors duration-100 ease-[cubic-bezier(.6,.12,.34,.96)]',
|
|
81
81
|
props.isRoot ? undefined : 'pl-3',
|
|
82
82
|
{
|
|
83
83
|
'text-cyan-11': isCurrentPage,
|
|
@@ -116,7 +116,9 @@ export const FileTreeDirectoryChildren = (props: {
|
|
|
116
116
|
height="20"
|
|
117
117
|
width="20"
|
|
118
118
|
/>
|
|
119
|
-
<span className="truncate">
|
|
119
|
+
<span className="truncate w-[calc(100%-1.25rem)]">
|
|
120
|
+
{emailFilename}
|
|
121
|
+
</span>
|
|
120
122
|
</motion.span>
|
|
121
123
|
</Link>
|
|
122
124
|
);
|
|
@@ -51,31 +51,31 @@ export const FileTreeDirectory = ({
|
|
|
51
51
|
>
|
|
52
52
|
<Collapsible.Trigger
|
|
53
53
|
className={cn(
|
|
54
|
-
'mt-1 mb-1.5 flex w-full items-center justify-between gap-2 font-medium text-[14px]',
|
|
54
|
+
'mt-1 mb-1.5 flex w-full items-center text-start justify-between gap-2 font-medium text-[14px]',
|
|
55
55
|
{
|
|
56
56
|
'cursor-pointer': !isEmpty,
|
|
57
57
|
},
|
|
58
58
|
)}
|
|
59
59
|
>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
</Heading>
|
|
75
|
-
</div>
|
|
60
|
+
{open ? (
|
|
61
|
+
<IconFolderOpen className="w-[20px]" height="20" width="20" />
|
|
62
|
+
) : (
|
|
63
|
+
<IconFolder height="20" width="20" />
|
|
64
|
+
)}
|
|
65
|
+
<Heading
|
|
66
|
+
as="h3"
|
|
67
|
+
className="transition grow w-[calc(100%-40px)] truncate duration-200 ease-in-out hover:text-slate-12"
|
|
68
|
+
color="gray"
|
|
69
|
+
size="2"
|
|
70
|
+
weight="medium"
|
|
71
|
+
>
|
|
72
|
+
{directoryMetadata.directoryName}
|
|
73
|
+
</Heading>
|
|
76
74
|
{!isEmpty ? (
|
|
77
75
|
<IconArrowDown
|
|
78
|
-
|
|
76
|
+
width="20"
|
|
77
|
+
height="20"
|
|
78
|
+
className="ml-auto opacity-60 transition-transform data-[open=true]:rotate-180"
|
|
79
79
|
data-open={open}
|
|
80
80
|
/>
|
|
81
81
|
) : null}
|
|
@@ -13,8 +13,8 @@ export const FileTree = ({
|
|
|
13
13
|
emailsDirectoryMetadata,
|
|
14
14
|
}: FileTreeProps) => {
|
|
15
15
|
return (
|
|
16
|
-
<div className="flex
|
|
17
|
-
<nav className="flex
|
|
16
|
+
<div className="flex w-full h-full flex-col lg:w-full lg:min-w-[14.5rem]">
|
|
17
|
+
<nav className="flex flex-grow flex-col p-4 pr-0 pl-0">
|
|
18
18
|
<Collapsible.Root open>
|
|
19
19
|
<React.Suspense>
|
|
20
20
|
<FileTreeDirectoryChildren
|
|
@@ -16,28 +16,26 @@ export const Sidebar = ({ className, currentEmailOpenSlug }: SidebarProps) => {
|
|
|
16
16
|
return (
|
|
17
17
|
<aside
|
|
18
18
|
className={cn(
|
|
19
|
-
'
|
|
19
|
+
'overflow-hidden bg-black',
|
|
20
20
|
'lg:static lg:z-auto lg:max-h-screen lg:w-[16rem]',
|
|
21
21
|
className,
|
|
22
22
|
)}
|
|
23
23
|
>
|
|
24
|
-
<div className="w-full h-full overflow-
|
|
25
|
-
<div
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
>
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
/>
|
|
40
|
-
</div>
|
|
24
|
+
<div className="flex w-full h-full overflow-hidden flex-col border-slate-6 border-r">
|
|
25
|
+
<div
|
|
26
|
+
className={clsx(
|
|
27
|
+
'hidden min-h-[3.3125rem] flex-shrink items-center p-3 px-4 lg:flex',
|
|
28
|
+
)}
|
|
29
|
+
>
|
|
30
|
+
<h2>
|
|
31
|
+
<Logo />
|
|
32
|
+
</h2>
|
|
33
|
+
</div>
|
|
34
|
+
<div className="relative grow w-full h-full overflow-y-auto overflow-x-hidden border-slate-4 border-t px-4 pb-3">
|
|
35
|
+
<FileTree
|
|
36
|
+
currentEmailOpenSlug={currentEmailOpenSlug}
|
|
37
|
+
emailsDirectoryMetadata={emailsDirectoryMetadata}
|
|
38
|
+
/>
|
|
41
39
|
</div>
|
|
42
40
|
</div>
|
|
43
41
|
</aside>
|
|
@@ -92,6 +92,22 @@ export const Linter = ({ rows }: LinterProps) => {
|
|
|
92
92
|
const failingCheck = row.result.checks.find(
|
|
93
93
|
(check) => check.passed === false,
|
|
94
94
|
)!;
|
|
95
|
+
const metadata: React.ReactNode[] = [];
|
|
96
|
+
for (const check of row.result.checks) {
|
|
97
|
+
if (
|
|
98
|
+
check.type === 'fetch_attempt' &&
|
|
99
|
+
check.metadata.fetchStatusCode
|
|
100
|
+
) {
|
|
101
|
+
metadata.push(<>HTTP {check.metadata.fetchStatusCode}</>);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
metadata.push(
|
|
105
|
+
<CodePreviewLineLink
|
|
106
|
+
line={row.result.codeLocation.line}
|
|
107
|
+
column={row.result.codeLocation.column}
|
|
108
|
+
type="html"
|
|
109
|
+
/>,
|
|
110
|
+
);
|
|
95
111
|
return (
|
|
96
112
|
<Result status={row.result.status} key={i}>
|
|
97
113
|
<Result.Name>{sanitize(failingCheck.type)}</Result.Name>
|
|
@@ -102,20 +118,14 @@ export const Linter = ({ rows }: LinterProps) => {
|
|
|
102
118
|
{failingCheck.type === 'fetch_attempt' &&
|
|
103
119
|
failingCheck.metadata.fetchStatusCode &&
|
|
104
120
|
failingCheck.metadata.fetchStatusCode >= 300 &&
|
|
105
|
-
failingCheck.metadata.fetchStatusCode < 400
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
There was a redirect, the content may have been moved
|
|
109
|
-
</>
|
|
110
|
-
) : null}
|
|
121
|
+
failingCheck.metadata.fetchStatusCode < 400
|
|
122
|
+
? 'There was a redirect, the content may have been moved'
|
|
123
|
+
: null}
|
|
111
124
|
{failingCheck.type === 'fetch_attempt' &&
|
|
112
125
|
failingCheck.metadata.fetchStatusCode &&
|
|
113
|
-
failingCheck.metadata.fetchStatusCode >= 400
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
The link is broken
|
|
117
|
-
</>
|
|
118
|
-
) : null}
|
|
126
|
+
failingCheck.metadata.fetchStatusCode >= 400
|
|
127
|
+
? 'The link is broken'
|
|
128
|
+
: null}
|
|
119
129
|
{failingCheck.type === 'syntax'
|
|
120
130
|
? 'The link is broken due to invalid syntax'
|
|
121
131
|
: null}
|
|
@@ -124,15 +134,7 @@ export const Linter = ({ rows }: LinterProps) => {
|
|
|
124
134
|
{row.result.link}
|
|
125
135
|
</span>
|
|
126
136
|
</Result.Description>
|
|
127
|
-
<Result.Metadata>
|
|
128
|
-
{[
|
|
129
|
-
<CodePreviewLineLink
|
|
130
|
-
line={row.result.codeLocation.line}
|
|
131
|
-
column={row.result.codeLocation.column}
|
|
132
|
-
type="html"
|
|
133
|
-
/>,
|
|
134
|
-
]}
|
|
135
|
-
</Result.Metadata>
|
|
137
|
+
<Result.Metadata>{metadata}</Result.Metadata>
|
|
136
138
|
</Result>
|
|
137
139
|
);
|
|
138
140
|
}
|
|
@@ -146,6 +148,12 @@ export const Linter = ({ rows }: LinterProps) => {
|
|
|
146
148
|
if (check.type === 'image_size' && check.metadata.byteCount) {
|
|
147
149
|
metadata.push(prettyBytes(check.metadata.byteCount));
|
|
148
150
|
}
|
|
151
|
+
if (
|
|
152
|
+
check.type === 'fetch_attempt' &&
|
|
153
|
+
check.metadata.fetchStatusCode
|
|
154
|
+
) {
|
|
155
|
+
metadata.push(<>HTTP {check.metadata.fetchStatusCode}</>);
|
|
156
|
+
}
|
|
149
157
|
}
|
|
150
158
|
metadata.push(
|
|
151
159
|
<CodePreviewLineLink
|
|
@@ -164,20 +172,14 @@ export const Linter = ({ rows }: LinterProps) => {
|
|
|
164
172
|
{failingCheck.type === 'fetch_attempt' &&
|
|
165
173
|
failingCheck.metadata.fetchStatusCode &&
|
|
166
174
|
failingCheck.metadata.fetchStatusCode >= 300 &&
|
|
167
|
-
failingCheck.metadata.fetchStatusCode < 400
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
There was a redirect, the image may have been moved
|
|
171
|
-
</>
|
|
172
|
-
) : null}
|
|
175
|
+
failingCheck.metadata.fetchStatusCode < 400
|
|
176
|
+
? 'There was a redirect, the image may have been moved'
|
|
177
|
+
: null}
|
|
173
178
|
{failingCheck.type === 'fetch_attempt' &&
|
|
174
179
|
failingCheck.metadata.fetchStatusCode &&
|
|
175
|
-
failingCheck.metadata.fetchStatusCode >= 400
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
The image is broken
|
|
179
|
-
</>
|
|
180
|
-
) : null}
|
|
180
|
+
failingCheck.metadata.fetchStatusCode >= 400
|
|
181
|
+
? 'The image is broken'
|
|
182
|
+
: null}
|
|
181
183
|
{failingCheck.type === 'syntax'
|
|
182
184
|
? 'The image is broken due to an invalid source'
|
|
183
185
|
: null}
|