react-email 4.0.0-alpha.6 → 4.0.0-alpha.7
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 +6 -0
- package/dist/cli/index.js +7 -3
- package/dist/cli/index.mjs +7 -3
- package/dist/preview/.next/BUILD_ID +1 -1
- package/dist/preview/.next/app-build-manifest.json +14 -13
- 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 +29 -25
- 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/600.js +3 -3
- package/dist/preview/.next/server/chunks/{171.js → 816.js} +6 -6
- package/dist/preview/.next/server/chunks/943.js +1 -0
- 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/pages-manifest.json +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/287-7864b805e6bdc854.js +1 -0
- package/dist/preview/.next/static/chunks/412-31817e53b50a3e73.js +1 -0
- package/dist/preview/.next/static/chunks/683-1fb40795502f6e63.js +1 -0
- package/dist/preview/.next/static/chunks/880-9c0b721328117b8b.js +1 -0
- package/dist/preview/.next/static/chunks/app/layout-ffdee5cc1be30e7b.js +1 -0
- package/dist/preview/.next/static/chunks/app/page-9ea0bd45cd6294b0.js +1 -0
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-9e22979a25c836c0.js +1 -0
- package/dist/preview/.next/static/chunks/{main-app-c2e686acf8d370d7.js → main-app-256b213b179a95cc.js} +1 -1
- package/dist/preview/.next/static/css/eaae8ce545b295f9.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 +84 -0
- 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 +0 -1
- package/src/actions/email-validation/check-images.spec.tsx +13 -11
- package/src/actions/email-validation/check-images.ts +6 -0
- package/src/actions/email-validation/check-links.spec.tsx +23 -11
- package/src/actions/email-validation/check-links.ts +6 -0
- package/src/actions/email-validation/get-code-location-from-ast-element.ts +18 -0
- package/src/actions/render-email-by-path.tsx +2 -2
- package/src/app/env.ts +3 -0
- package/src/app/preview/[...slug]/page.tsx +24 -11
- package/src/app/preview/[...slug]/preview.tsx +15 -12
- package/src/components/code-container.tsx +90 -71
- package/src/components/code.tsx +106 -42
- package/src/components/icons/icon-info.tsx +18 -0
- package/src/components/icons/icon-reload.tsx +13 -14
- package/src/components/logo.tsx +3 -2
- package/src/components/resizable-wrapper.tsx +1 -4
- package/src/components/sidebar/file-tree-directory-children.tsx +1 -0
- package/src/components/sidebar/sidebar.tsx +2 -3
- package/src/components/toolbar/code-preview-line-link.tsx +40 -0
- package/src/components/toolbar/compatibility.tsx +113 -0
- package/src/components/toolbar/linter.tsx +69 -111
- package/src/components/toolbar/results.tsx +5 -2
- package/src/components/toolbar/spam-assassin.tsx +20 -12
- package/src/components/toolbar/toolbar-button.tsx +4 -2
- package/src/components/toolbar.tsx +108 -30
- package/src/components/tooltip-content.tsx +1 -1
- package/src/components/topbar/view-size-controls.tsx +1 -2
- package/src/components/topbar.tsx +1 -20
- package/src/contexts/fragment-identifier.tsx +46 -0
- package/src/hooks/use-fragment-identifier.ts +14 -0
- package/src/utils/caniemail/tailwind/get-tailwind-config.ts +0 -2
- package/src/utils/get-email-component.ts +1 -1
- package/src/utils/get-line-and-column-from-offset.spec.ts +11 -0
- package/src/utils/get-line-and-column-from-offset.ts +11 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/linting.ts +5 -30
- package/src/utils/load-stream.ts +15 -0
- package/src/utils/sanitize.ts +6 -0
- package/dist/preview/.next/server/chunks/833.js +0 -1
- package/dist/preview/.next/static/chunks/416-56f79fc7e689f06f.js +0 -1
- package/dist/preview/.next/static/chunks/683-8bbfd191e5105f01.js +0 -1
- package/dist/preview/.next/static/chunks/87-38e35f08507de015.js +0 -1
- package/dist/preview/.next/static/chunks/app/layout-a6640e62690d8fd6.js +0 -1
- package/dist/preview/.next/static/chunks/app/page-ba68f50b287e7478.js +0 -1
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-4a5b026ab543e27f.js +0 -1
- package/dist/preview/.next/static/css/d7df9cfc3e182163.css +0 -3
- package/src/actions/email-validation/get-line-and-column-from-index.spec.ts +0 -22
- package/src/actions/email-validation/get-line-and-column-from-index.ts +0 -43
- package/src/components/icons/icon-scanner.tsx +0 -19
- package/src/components/icons/icon-scissors.tsx +0 -19
- /package/dist/preview/.next/static/{gFk9UfWL8joM4iD7-wlKF → Pms2orsQgT5xpttCfZfH5}/_buildManifest.js +0 -0
- /package/dist/preview/.next/static/{gFk9UfWL8joM4iD7-wlKF → Pms2orsQgT5xpttCfZfH5}/_ssgManifest.js +0 -0
package/src/components/code.tsx
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import Link from 'next/link';
|
|
3
|
+
import { useSearchParams } from 'next/navigation';
|
|
1
4
|
import type { Language } from 'prism-react-renderer';
|
|
2
5
|
import { Highlight } from 'prism-react-renderer';
|
|
3
|
-
import
|
|
6
|
+
import { Fragment, useEffect } from 'react';
|
|
7
|
+
import { useFragmentIdentifier } from '../hooks/use-fragment-identifier';
|
|
4
8
|
import { cn } from '../utils';
|
|
5
9
|
|
|
6
10
|
interface CodeProps {
|
|
@@ -43,10 +47,43 @@ const theme = {
|
|
|
43
47
|
],
|
|
44
48
|
};
|
|
45
49
|
|
|
50
|
+
const lineHashRegex = /#L(?<start>\d+)(?:,(?<end>\d+))?/;
|
|
51
|
+
|
|
46
52
|
export const Code: React.FC<Readonly<CodeProps>> = ({
|
|
47
53
|
children,
|
|
48
54
|
language = 'html',
|
|
49
55
|
}) => {
|
|
56
|
+
const locationHash = useFragmentIdentifier();
|
|
57
|
+
const highlight = (() => {
|
|
58
|
+
if (locationHash) {
|
|
59
|
+
const match = locationHash.match(lineHashRegex);
|
|
60
|
+
if (match?.groups?.start) {
|
|
61
|
+
const start = Number.parseInt(match.groups.start);
|
|
62
|
+
const end = match.groups.end
|
|
63
|
+
? Number.parseInt(match.groups.end)
|
|
64
|
+
: start;
|
|
65
|
+
return [start, end] as const;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
})();
|
|
69
|
+
|
|
70
|
+
const isHighlighting = (line: number) => {
|
|
71
|
+
if (!highlight) return false;
|
|
72
|
+
|
|
73
|
+
return highlight[0] <= line && highlight[1] >= line;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
if (highlight) {
|
|
78
|
+
document.getElementById(`L${highlight[0]}`)?.scrollIntoView({
|
|
79
|
+
block: 'start',
|
|
80
|
+
behavior: 'smooth',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}, [highlight]);
|
|
84
|
+
|
|
85
|
+
const searchParams = useSearchParams();
|
|
86
|
+
|
|
50
87
|
const value = children.trim();
|
|
51
88
|
|
|
52
89
|
return (
|
|
@@ -60,50 +97,77 @@ export const Code: React.FC<Readonly<CodeProps>> = ({
|
|
|
60
97
|
'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%)',
|
|
61
98
|
}}
|
|
62
99
|
/>
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
});
|
|
69
|
-
return (
|
|
70
|
-
<div
|
|
71
|
-
{...lineProps}
|
|
72
|
-
className={cn('whitespace-pre', {
|
|
73
|
-
"before:mr-2 before:text-slate-11 before:content-['$']":
|
|
74
|
-
language === 'bash' && tokens.length === 1,
|
|
75
|
-
})}
|
|
100
|
+
<div className="flex h-[650px] p-4 max-h-[calc(100vh-10rem)] after:w-full after:static after:block after:h-4 after:content-[''] overflow-auto">
|
|
101
|
+
<div className="text-[#49494f] text-[13px] font-light font-[MonoLisa,_Menlo,_monospace]">
|
|
102
|
+
{tokens.map((_, i) => (
|
|
103
|
+
<Link
|
|
104
|
+
id={`L${i + 1}`}
|
|
76
105
|
key={i}
|
|
106
|
+
href={{
|
|
107
|
+
hash: `#L${i + 1}`,
|
|
108
|
+
search: searchParams.toString(),
|
|
109
|
+
}}
|
|
110
|
+
scroll={false}
|
|
111
|
+
className={cn(
|
|
112
|
+
'align-middle block scroll-mt-[325px] rounded-l-sm select-none pr-3 cursor-pointer hover:text-slate-12',
|
|
113
|
+
isHighlighting(i + 1) &&
|
|
114
|
+
'text-cyan-11 hover:text-cyan-11 bg-cyan-5',
|
|
115
|
+
)}
|
|
116
|
+
type="button"
|
|
77
117
|
>
|
|
78
|
-
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
118
|
+
{i + 1}
|
|
119
|
+
</Link>
|
|
120
|
+
))}
|
|
121
|
+
</div>
|
|
122
|
+
<pre>
|
|
123
|
+
{tokens.map((line, i) => {
|
|
124
|
+
const lineProps = getLineProps({
|
|
125
|
+
line,
|
|
126
|
+
key: i,
|
|
127
|
+
});
|
|
128
|
+
return (
|
|
129
|
+
<div
|
|
130
|
+
{...lineProps}
|
|
131
|
+
className={cn(
|
|
132
|
+
'whitespace-pre flex transition-colors rounded-r-sm',
|
|
133
|
+
isHighlighting(i + 1) && 'bg-cyan-5',
|
|
134
|
+
{
|
|
135
|
+
"before:mr-2 before:text-slate-11 before:content-['$']":
|
|
136
|
+
language === 'bash' && tokens.length === 1,
|
|
137
|
+
},
|
|
138
|
+
)}
|
|
139
|
+
key={i}
|
|
140
|
+
>
|
|
141
|
+
{line.map((token, key) => {
|
|
142
|
+
const tokenProps = getTokenProps({
|
|
143
|
+
token,
|
|
144
|
+
});
|
|
145
|
+
const isException =
|
|
146
|
+
token.content === 'from' &&
|
|
147
|
+
line[key + 1]?.content === ':';
|
|
148
|
+
const newTypes = isException
|
|
149
|
+
? [...token.types, 'key-white']
|
|
150
|
+
: token.types;
|
|
151
|
+
token.types = newTypes;
|
|
89
152
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
153
|
+
return (
|
|
154
|
+
<Fragment key={key}>
|
|
155
|
+
<span {...tokenProps} />
|
|
156
|
+
</Fragment>
|
|
157
|
+
);
|
|
158
|
+
})}
|
|
159
|
+
</div>
|
|
160
|
+
);
|
|
161
|
+
})}
|
|
162
|
+
</pre>
|
|
163
|
+
<div
|
|
164
|
+
className="absolute bottom-0 left-0 h-px w-[200px]"
|
|
165
|
+
style={{
|
|
166
|
+
background:
|
|
167
|
+
'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%)',
|
|
168
|
+
}}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
107
171
|
</>
|
|
108
172
|
)}
|
|
109
173
|
</Highlight>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { IconElement, IconProps } from './icon-base';
|
|
3
|
+
import { IconBase } from './icon-base';
|
|
4
|
+
|
|
5
|
+
export const IconInfo = React.forwardRef<IconElement, Readonly<IconProps>>(
|
|
6
|
+
({ ...props }, forwardedRef) => (
|
|
7
|
+
<IconBase ref={forwardedRef} {...props}>
|
|
8
|
+
<path
|
|
9
|
+
d="M12 4C7.58173 4 4 7.58172 4 12C4 16.4182 7.58173 20 12 20C16.4183 20 20 16.4182 20 12C20 7.58172 16.4183 4 12 4ZM5.14754 12C5.14754 8.21549 8.21551 5.14754 12 5.14754C15.7845 5.14754 18.8525 8.21549 18.8525 12C18.8525 15.7844 15.7845 18.8525 12 18.8525C8.21551 18.8525 5.14754 15.7844 5.14754 12ZM12.906 8.37648C12.906 8.87682 12.5004 9.28243 12 9.28243C11.4997 9.28243 11.0941 8.87682 11.0941 8.37648C11.0941 7.87613 11.4997 7.47053 12 7.47053C12.5004 7.47053 12.906 7.87613 12.906 8.37648ZM10.1883 10.1884H10.7922H12.0002C12.3337 10.1884 12.6041 10.4588 12.6041 10.7924V15.0201H13.2081H13.8121V16.2281H13.2081H12.0002H10.7922H10.1883V15.0201H10.7922H11.3962V11.3963H10.7922H10.1883V10.1884Z"
|
|
10
|
+
fill="currentColor"
|
|
11
|
+
fillRule="evenodd"
|
|
12
|
+
clipRule="evenodd"
|
|
13
|
+
/>
|
|
14
|
+
</IconBase>
|
|
15
|
+
),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
IconInfo.displayName = 'IconInfo';
|
|
@@ -1,19 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
-
{...props}
|
|
10
|
-
>
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { IconElement, IconProps } from './icon-base';
|
|
3
|
+
import { IconBase } from './icon-base';
|
|
4
|
+
|
|
5
|
+
export const IconReload = React.forwardRef<IconElement, Readonly<IconProps>>(
|
|
6
|
+
({ ...props }, forwardedRef) => (
|
|
7
|
+
<IconBase ref={forwardedRef} {...props}>
|
|
11
8
|
<path
|
|
12
9
|
fillRule="evenodd"
|
|
13
10
|
clipRule="evenodd"
|
|
14
|
-
d="
|
|
11
|
+
d="M17.9354 12C17.9354 9.01537 15.5828 6.05264 11.9202 6.05264C8.96229 6.05264 7.50033 8.21724 6.87735 9.36841H8.72625C9.02024 9.36841 9.25858 9.60406 9.25858 9.89473C9.25858 10.1854 9.02024 10.421 8.72625 10.421H5.53232C5.23833 10.421 5 10.1854 5 9.89473V6.73684C5 6.44617 5.23833 6.21052 5.53232 6.21052C5.82631 6.21052 6.06465 6.44617 6.06465 6.73684V8.64548C6.81471 7.33819 8.54959 5 11.9202 5C16.2456 5 19 8.51094 19 12C19 15.4891 16.2456 19 11.9202 19C9.8507 19 8.12769 18.1904 6.9009 16.9562C6.24405 16.2954 5.73107 15.5148 5.38132 14.6744C5.26942 14.4057 5.39911 14.0981 5.67098 13.9875C5.94285 13.8768 6.25395 14.0051 6.36583 14.2738C6.66482 14.9921 7.10262 15.6574 7.66023 16.2183C8.69486 17.2593 10.1475 17.9474 11.9202 17.9474C15.5828 17.9474 17.9354 14.9846 17.9354 12Z"
|
|
15
12
|
fill="currentColor"
|
|
16
13
|
/>
|
|
17
|
-
</
|
|
18
|
-
)
|
|
19
|
-
|
|
14
|
+
</IconBase>
|
|
15
|
+
),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
IconReload.displayName = 'IconReload';
|
package/src/components/logo.tsx
CHANGED
|
@@ -100,10 +100,7 @@ export const ResizableWarpper = ({
|
|
|
100
100
|
return (
|
|
101
101
|
<div
|
|
102
102
|
{...rest}
|
|
103
|
-
className={cn(
|
|
104
|
-
'relative mx-auto my-auto box-content px-4 py-2',
|
|
105
|
-
rest.className,
|
|
106
|
-
)}
|
|
103
|
+
className={cn('relative mx-auto my-auto box-content', rest.className)}
|
|
107
104
|
>
|
|
108
105
|
<div
|
|
109
106
|
aria-label="resize-west"
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { clsx } from 'clsx';
|
|
3
3
|
import { useEmails } from '../../contexts/emails';
|
|
4
4
|
import { cn } from '../../utils';
|
|
5
|
-
import { Heading } from '../heading';
|
|
6
5
|
import { Logo } from '../logo';
|
|
7
6
|
import { FileTree } from './file-tree';
|
|
8
7
|
|
|
@@ -29,9 +28,9 @@ export const Sidebar = ({ className, currentEmailOpenSlug }: SidebarProps) => {
|
|
|
29
28
|
'hidden min-h-[3.3125rem] flex-shrink items-center p-3 px-4 lg:flex',
|
|
30
29
|
)}
|
|
31
30
|
>
|
|
32
|
-
<
|
|
31
|
+
<h2>
|
|
33
32
|
<Logo />
|
|
34
|
-
</
|
|
33
|
+
</h2>
|
|
35
34
|
</div>
|
|
36
35
|
<div className="relative h-full w-full border-slate-4 border-t px-4 pb-3">
|
|
37
36
|
<FileTree
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
2
|
+
import { useSearchParams } from 'next/navigation';
|
|
3
|
+
|
|
4
|
+
interface CodePreviewLineLinkProps {
|
|
5
|
+
line: number;
|
|
6
|
+
column: number;
|
|
7
|
+
|
|
8
|
+
type: 'react' | 'html';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const CodePreviewLineLink = ({
|
|
12
|
+
line,
|
|
13
|
+
column,
|
|
14
|
+
type,
|
|
15
|
+
}: CodePreviewLineLinkProps) => {
|
|
16
|
+
const searchParams = useSearchParams();
|
|
17
|
+
|
|
18
|
+
const newSearchParams = new URLSearchParams(searchParams);
|
|
19
|
+
newSearchParams.set('view', 'source');
|
|
20
|
+
if (type === 'html') {
|
|
21
|
+
newSearchParams.set('lang', 'markup');
|
|
22
|
+
} else if (type === 'react') {
|
|
23
|
+
newSearchParams.set('lang', 'jsx');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const fragmentIdentifier = `#L${line}`;
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<Link
|
|
30
|
+
href={{
|
|
31
|
+
search: newSearchParams.toString(),
|
|
32
|
+
hash: fragmentIdentifier,
|
|
33
|
+
}}
|
|
34
|
+
scroll={false}
|
|
35
|
+
className="appearance-none underline mx-2"
|
|
36
|
+
>
|
|
37
|
+
L{line.toString().padStart(2, '0')}
|
|
38
|
+
</Link>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useRef, useState } from 'react';
|
|
2
|
+
import { toast } from 'sonner';
|
|
3
|
+
import { nicenames } from '../../actions/email-validation/caniemail-data';
|
|
4
|
+
import {
|
|
5
|
+
type CompatibilityCheckingResult,
|
|
6
|
+
checkCompatibility,
|
|
7
|
+
} from '../../actions/email-validation/check-compatibility';
|
|
8
|
+
import { sanitize } from '../../utils';
|
|
9
|
+
import { loadStream } from '../../utils/load-stream';
|
|
10
|
+
import { IconWarning } from '../icons/icon-warning';
|
|
11
|
+
import { CodePreviewLineLink } from './code-preview-line-link';
|
|
12
|
+
import { Results } from './results';
|
|
13
|
+
|
|
14
|
+
export const useCompatibility = ({
|
|
15
|
+
reactMarkup,
|
|
16
|
+
emailPath,
|
|
17
|
+
|
|
18
|
+
initialResults,
|
|
19
|
+
}: {
|
|
20
|
+
reactMarkup: string;
|
|
21
|
+
emailPath: string;
|
|
22
|
+
|
|
23
|
+
initialResults?: CompatibilityCheckingResult[];
|
|
24
|
+
}) => {
|
|
25
|
+
const [results, setResults] = useState(initialResults);
|
|
26
|
+
|
|
27
|
+
const [loading, setLoading] = useState(false);
|
|
28
|
+
const isLoadingRef = useRef(false);
|
|
29
|
+
|
|
30
|
+
const load = async () => {
|
|
31
|
+
if (isLoadingRef.current) return;
|
|
32
|
+
isLoadingRef.current = true;
|
|
33
|
+
setLoading(true);
|
|
34
|
+
|
|
35
|
+
setResults([]);
|
|
36
|
+
let rawResults: CompatibilityCheckingResult[] = [];
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const stream = await checkCompatibility(reactMarkup, emailPath);
|
|
40
|
+
for await (const result of loadStream(stream)) {
|
|
41
|
+
if (result.status !== 'error') continue;
|
|
42
|
+
setResults((current) => {
|
|
43
|
+
if (!current) {
|
|
44
|
+
return [result];
|
|
45
|
+
}
|
|
46
|
+
rawResults = [...current, result];
|
|
47
|
+
return rawResults;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
} catch (exception) {
|
|
51
|
+
console.error(exception);
|
|
52
|
+
toast.error(JSON.stringify(exception));
|
|
53
|
+
} finally {
|
|
54
|
+
setLoading(false);
|
|
55
|
+
isLoadingRef.current = false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return rawResults;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return [results, { loading, load }] as const;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
interface CompatibilityProps {
|
|
65
|
+
results: CompatibilityCheckingResult[] | undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const Compatibility = ({ results }: CompatibilityProps) => {
|
|
69
|
+
return (
|
|
70
|
+
<Results>
|
|
71
|
+
{results?.map((result, i) => {
|
|
72
|
+
const statsReportedNotWorking = Object.entries(
|
|
73
|
+
result.statsPerEmailClient,
|
|
74
|
+
).filter(([, stats]) => stats.status === 'error');
|
|
75
|
+
const unsupportedClientsString = statsReportedNotWorking
|
|
76
|
+
.map(([emailClient]) => nicenames.family[emailClient])
|
|
77
|
+
.join(', ');
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Results.Row key={i}>
|
|
81
|
+
<Results.Column>
|
|
82
|
+
<span className="flex text-red-400 uppercase gap-2 items-center">
|
|
83
|
+
<IconWarning />
|
|
84
|
+
{sanitize(result.entry.title)}
|
|
85
|
+
</span>
|
|
86
|
+
</Results.Column>
|
|
87
|
+
<Results.Column>
|
|
88
|
+
{statsReportedNotWorking.length > 0
|
|
89
|
+
? `Not supported in ${unsupportedClientsString}`
|
|
90
|
+
: null}
|
|
91
|
+
|
|
92
|
+
<a
|
|
93
|
+
href={result.entry.url}
|
|
94
|
+
className="underline ml-2 decoration-slate-9 decoration-1 hover:decoration-slate-11 transition-colors hover:text-slate-12"
|
|
95
|
+
rel="noreferrer"
|
|
96
|
+
target="_blank"
|
|
97
|
+
>
|
|
98
|
+
More ↗
|
|
99
|
+
</a>
|
|
100
|
+
</Results.Column>
|
|
101
|
+
<Results.Column className="font-mono text-slate-11 text-right">
|
|
102
|
+
<CodePreviewLineLink
|
|
103
|
+
line={result.location.start.line}
|
|
104
|
+
column={result.location.start.column}
|
|
105
|
+
type="react"
|
|
106
|
+
/>
|
|
107
|
+
</Results.Column>
|
|
108
|
+
</Results.Row>
|
|
109
|
+
);
|
|
110
|
+
})}
|
|
111
|
+
</Results>
|
|
112
|
+
);
|
|
113
|
+
};
|