react-email 4.0.0-alpha.7 → 4.0.0-alpha.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/CHANGELOG.md +7 -0
- package/dist/cli/index.js +17 -16
- package/dist/cli/index.mjs +25 -24
- package/dist/preview/.next/BUILD_ID +1 -1
- package/dist/preview/.next/app-build-manifest.json +5 -5
- package/dist/preview/.next/app-path-routes-manifest.json +1 -1
- package/dist/preview/.next/build-manifest.json +2 -2
- 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/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/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 +113 -9
- 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/42.js +1 -0
- 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-b769e5d91bdf9a82.js +1 -0
- package/dist/preview/.next/static/chunks/app/layout-7dee682873546401.js +1 -0
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-a610d641c64448cc.js +1 -0
- package/dist/preview/.next/static/css/e68ebc9bb8f7b3f4.css +3 -0
- package/dist/preview/.next/trace +26 -26
- package/package.json +1 -1
- package/src/actions/email-validation/check-compatibility.ts +16 -4
- package/src/components/sidebar/file-tree-directory-children.tsx +12 -2
- package/src/components/sidebar/file-tree-directory.tsx +26 -18
- package/src/components/sidebar/file-tree.tsx +2 -2
- package/src/components/sidebar/sidebar.tsx +16 -18
- package/src/components/toolbar/spam-assassin.tsx +16 -13
- package/src/components/toolbar/use-cached-state.ts +2 -2
- package/src/components/toolbar.tsx +60 -16
- 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/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 → SoPVDfPAp9R983pBBriVn}/_buildManifest.js +0 -0
- /package/dist/preview/.next/static/{Pms2orsQgT5xpttCfZfH5 → SoPVDfPAp9R983pBBriVn}/_ssgManifest.js +0 -0
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>;
|
|
@@ -5,6 +5,7 @@ import { useSearchParams } from 'next/navigation';
|
|
|
5
5
|
import { cn } from '../../utils';
|
|
6
6
|
import type { EmailsDirectory } from '../../utils/get-emails-directory-metadata';
|
|
7
7
|
import { IconFile } from '../icons/icon-file';
|
|
8
|
+
import { Tooltip } from '../tooltip';
|
|
8
9
|
import { FileTreeDirectory } from './file-tree-directory';
|
|
9
10
|
|
|
10
11
|
export const FileTreeDirectoryChildren = (props: {
|
|
@@ -77,7 +78,7 @@ export const FileTreeDirectoryChildren = (props: {
|
|
|
77
78
|
<motion.span
|
|
78
79
|
animate={{ x: 0, opacity: 1 }}
|
|
79
80
|
className={cn(
|
|
80
|
-
'relative flex h-8
|
|
81
|
+
'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
82
|
props.isRoot ? undefined : 'pl-3',
|
|
82
83
|
{
|
|
83
84
|
'text-cyan-11': isCurrentPage,
|
|
@@ -116,7 +117,16 @@ export const FileTreeDirectoryChildren = (props: {
|
|
|
116
117
|
height="20"
|
|
117
118
|
width="20"
|
|
118
119
|
/>
|
|
119
|
-
<
|
|
120
|
+
<Tooltip.Provider>
|
|
121
|
+
<Tooltip>
|
|
122
|
+
<Tooltip.Trigger asChild>
|
|
123
|
+
<span className="truncate w-[calc(100%-1.25rem)]">
|
|
124
|
+
{emailFilename}
|
|
125
|
+
</span>
|
|
126
|
+
</Tooltip.Trigger>
|
|
127
|
+
<Tooltip.Content>{emailFilename}</Tooltip.Content>
|
|
128
|
+
</Tooltip>
|
|
129
|
+
</Tooltip.Provider>
|
|
120
130
|
</motion.span>
|
|
121
131
|
</Link>
|
|
122
132
|
);
|
|
@@ -7,6 +7,7 @@ import { Heading } from '../heading';
|
|
|
7
7
|
import { IconArrowDown } from '../icons/icon-arrow-down';
|
|
8
8
|
import { IconFolder } from '../icons/icon-folder';
|
|
9
9
|
import { IconFolderOpen } from '../icons/icon-folder-open';
|
|
10
|
+
import { Tooltip } from '../tooltip';
|
|
10
11
|
import { FileTreeDirectoryChildren } from './file-tree-directory-children';
|
|
11
12
|
|
|
12
13
|
interface SidebarDirectoryProps {
|
|
@@ -51,31 +52,38 @@ export const FileTreeDirectory = ({
|
|
|
51
52
|
>
|
|
52
53
|
<Collapsible.Trigger
|
|
53
54
|
className={cn(
|
|
54
|
-
'mt-1 mb-1.5 flex w-full items-center justify-between gap-2 font-medium text-[14px]',
|
|
55
|
+
'mt-1 mb-1.5 flex w-full items-center text-start justify-between gap-2 font-medium text-[14px]',
|
|
55
56
|
{
|
|
56
57
|
'cursor-pointer': !isEmpty,
|
|
57
58
|
},
|
|
58
59
|
)}
|
|
59
60
|
>
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
61
|
+
{open ? (
|
|
62
|
+
<IconFolderOpen className="w-[20px]" height="20" width="20" />
|
|
63
|
+
) : (
|
|
64
|
+
<IconFolder height="20" width="20" />
|
|
65
|
+
)}
|
|
66
|
+
<Tooltip.Provider>
|
|
67
|
+
<Tooltip>
|
|
68
|
+
<Tooltip.Trigger asChild>
|
|
69
|
+
<Heading
|
|
70
|
+
as="h3"
|
|
71
|
+
className="transition grow w-[calc(100%-40px)] truncate duration-200 ease-in-out hover:text-slate-12"
|
|
72
|
+
color="gray"
|
|
73
|
+
size="2"
|
|
74
|
+
weight="medium"
|
|
75
|
+
>
|
|
76
|
+
{directoryMetadata.directoryName}
|
|
77
|
+
</Heading>
|
|
78
|
+
</Tooltip.Trigger>
|
|
79
|
+
<Tooltip.Content>{directoryMetadata.directoryName}</Tooltip.Content>
|
|
80
|
+
</Tooltip>
|
|
81
|
+
</Tooltip.Provider>
|
|
76
82
|
{!isEmpty ? (
|
|
77
83
|
<IconArrowDown
|
|
78
|
-
|
|
84
|
+
width="20"
|
|
85
|
+
height="20"
|
|
86
|
+
className="ml-auto opacity-60 transition-transform data-[open=true]:rotate-180"
|
|
79
87
|
data-open={open}
|
|
80
88
|
/>
|
|
81
89
|
) : 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
|
-
'fixed top-[4.375rem] left-0 z-[9999] h-full max-h-full w-screen max-w-
|
|
19
|
+
'fixed top-[4.375rem] left-0 z-[9999] h-full max-h-full w-screen max-w-full bg-black will-change-auto',
|
|
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>
|
|
@@ -101,19 +101,22 @@ export const SpamAssassin = ({ result }: SpamAssassinProps) => {
|
|
|
101
101
|
<Results.Column>
|
|
102
102
|
{result.points === 0
|
|
103
103
|
? 'Congratulations! Your email is clean of abuse indicators.'
|
|
104
|
-
: '
|
|
104
|
+
: 'Higher scores are better'}
|
|
105
105
|
</Results.Column>
|
|
106
|
-
<Results.Column
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
<Results.Column className="text-right tracking-tighter font-bold">
|
|
107
|
+
<span
|
|
108
|
+
className={cn(
|
|
109
|
+
'text-3xl',
|
|
110
|
+
result.points === 0 ? 'text-green-400' : null,
|
|
111
|
+
result.points > 0 && result.points <= 1.5 ? null : null,
|
|
112
|
+
result.points > 1.5 ? 'text-yellow-200' : null,
|
|
113
|
+
result.points > 3 ? 'text-orange-400' : null,
|
|
114
|
+
result.points >= 5 ? 'text-red-400' : null,
|
|
115
|
+
)}
|
|
116
|
+
>
|
|
117
|
+
{(10 - result.points).toFixed(1)}
|
|
118
|
+
</span>{' '}
|
|
119
|
+
<span className="text-lg">/ 10</span>
|
|
117
120
|
</Results.Column>
|
|
118
121
|
</Results.Row>
|
|
119
122
|
{toSorted(result.checks, (a, b) => b.points - a.points).map(
|
|
@@ -140,7 +143,7 @@ export const SpamAssassin = ({ result }: SpamAssassinProps) => {
|
|
|
140
143
|
check.points > 3 ? 'text-red-400' : null,
|
|
141
144
|
)}
|
|
142
145
|
>
|
|
143
|
-
{check.points.toFixed(1)}
|
|
146
|
+
-{check.points.toFixed(1)}
|
|
144
147
|
</Results.Column>
|
|
145
148
|
</Results.Row>
|
|
146
149
|
),
|
|
@@ -4,7 +4,7 @@ export const useCachedState = <T>(key: string) => {
|
|
|
4
4
|
let value: T | undefined = undefined;
|
|
5
5
|
if ('localStorage' in global) {
|
|
6
6
|
const storedValue = global.localStorage.getItem(key);
|
|
7
|
-
if (storedValue !== null) {
|
|
7
|
+
if (storedValue !== null && storedValue !== 'undefined') {
|
|
8
8
|
try {
|
|
9
9
|
value = JSON.parse(storedValue) as T;
|
|
10
10
|
} catch (exception) {
|
|
@@ -12,7 +12,7 @@ export const useCachedState = <T>(key: string) => {
|
|
|
12
12
|
'Failed to load stored value for',
|
|
13
13
|
key,
|
|
14
14
|
'with value',
|
|
15
|
-
|
|
15
|
+
storedValue,
|
|
16
16
|
);
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -8,6 +8,7 @@ import { isBuilding } from '../app/env';
|
|
|
8
8
|
import { PreviewContext } from '../contexts/preview';
|
|
9
9
|
import { cn } from '../utils';
|
|
10
10
|
import { IconArrowDown } from './icons/icon-arrow-down';
|
|
11
|
+
import { IconCheck } from './icons/icon-check';
|
|
11
12
|
import { IconInfo } from './icons/icon-info';
|
|
12
13
|
import { IconReload } from './icons/icon-reload';
|
|
13
14
|
import { Compatibility, useCompatibility } from './toolbar/compatibility';
|
|
@@ -155,13 +156,6 @@ const ToolbarInner = ({
|
|
|
155
156
|
'The Compatibility tab shows how well the HTML/CSS is supported across mail clients like Outlook, Gmail, etc. Powered by Can I Email.') ||
|
|
156
157
|
'Info'
|
|
157
158
|
}
|
|
158
|
-
onClick={() => {
|
|
159
|
-
if (activeTab === undefined) {
|
|
160
|
-
setActivePanelValue('linter');
|
|
161
|
-
} else {
|
|
162
|
-
setActivePanelValue(undefined);
|
|
163
|
-
}
|
|
164
|
-
}}
|
|
165
159
|
>
|
|
166
160
|
<IconInfo size={24} />
|
|
167
161
|
</ToolbarButton>
|
|
@@ -215,26 +209,50 @@ const ToolbarInner = ({
|
|
|
215
209
|
<div className="animate-pulse text-slate-11 text-sm pt-1">
|
|
216
210
|
Running linting...
|
|
217
211
|
</div>
|
|
212
|
+
) : lintingRows?.length === 0 ? (
|
|
213
|
+
<div className="flex flex-col items-center justify-center pt-8">
|
|
214
|
+
<SuccessIcon />
|
|
215
|
+
<SuccessTitle>All good</SuccessTitle>
|
|
216
|
+
<SuccessDescription>
|
|
217
|
+
No linting issues found.
|
|
218
|
+
</SuccessDescription>
|
|
219
|
+
</div>
|
|
218
220
|
) : (
|
|
219
|
-
<Linter rows={lintingRows} />
|
|
221
|
+
<Linter rows={lintingRows ?? []} />
|
|
220
222
|
)}
|
|
221
223
|
</Tabs.Content>
|
|
222
|
-
<Tabs.Content value="
|
|
223
|
-
{
|
|
224
|
+
<Tabs.Content value="compatibility">
|
|
225
|
+
{compatibilityLoading ? (
|
|
224
226
|
<div className="animate-pulse text-slate-11 text-sm pt-1">
|
|
225
|
-
Running
|
|
227
|
+
Running compatibility check...
|
|
228
|
+
</div>
|
|
229
|
+
) : compatibilityCheckingResults?.length === 0 ? (
|
|
230
|
+
<div className="flex flex-col items-center justify-center py-8 px-4 my-4">
|
|
231
|
+
<SuccessIcon />
|
|
232
|
+
<SuccessTitle>Great compatibility</SuccessTitle>
|
|
233
|
+
<SuccessDescription>
|
|
234
|
+
It should render properly everywhere.
|
|
235
|
+
</SuccessDescription>
|
|
226
236
|
</div>
|
|
227
237
|
) : (
|
|
228
|
-
<
|
|
238
|
+
<Compatibility results={compatibilityCheckingResults ?? []} />
|
|
229
239
|
)}
|
|
230
240
|
</Tabs.Content>
|
|
231
|
-
<Tabs.Content value="
|
|
232
|
-
{
|
|
241
|
+
<Tabs.Content value="spam-assassin">
|
|
242
|
+
{spamLoading ? (
|
|
233
243
|
<div className="animate-pulse text-slate-11 text-sm pt-1">
|
|
234
|
-
Running
|
|
244
|
+
Running spam check...
|
|
245
|
+
</div>
|
|
246
|
+
) : spamCheckingResult?.isSpam === false ? (
|
|
247
|
+
<div className="flex flex-col items-center justify-center py-4 px-4 my-4">
|
|
248
|
+
<SuccessIcon />
|
|
249
|
+
<SuccessTitle>10/10</SuccessTitle>
|
|
250
|
+
<SuccessDescription>
|
|
251
|
+
Your email is clean of abuse indicators.
|
|
252
|
+
</SuccessDescription>
|
|
235
253
|
</div>
|
|
236
254
|
) : (
|
|
237
|
-
<
|
|
255
|
+
<SpamAssassin result={spamCheckingResult} />
|
|
238
256
|
)}
|
|
239
257
|
</Tabs.Content>
|
|
240
258
|
</div>
|
|
@@ -244,6 +262,32 @@ const ToolbarInner = ({
|
|
|
244
262
|
);
|
|
245
263
|
};
|
|
246
264
|
|
|
265
|
+
const SuccessIcon = () => {
|
|
266
|
+
return (
|
|
267
|
+
<div className="relative mb-8 flex items-center justify-center">
|
|
268
|
+
<div className="h-16 w-16 rounded-full bg-gradient-to-br from-green-300/20 opacity-80 to-emerald-500/30 blur-md absolute m-auto left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
|
|
269
|
+
<div className="h-12 w-12 rounded-full bg-gradient-to-br from-green-400/80 opacity-10 to-emerald-600/80 absolute m-auto left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 shadow-lg" />
|
|
270
|
+
<div className="h-10 w-10 rounded-full bg-gradient-to-br from-green-400 to-emerald-600 flex items-center justify-center absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 shadow-[inset_0_1px_1px_rgba(255,255,255,0.4)]">
|
|
271
|
+
<IconCheck size={24} className="text-white drop-shadow-sm" />
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const SuccessTitle = ({ children }) => {
|
|
278
|
+
return (
|
|
279
|
+
<h3 className="text-slate-12 font-medium text-base mb-1">{children}</h3>
|
|
280
|
+
);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const SuccessDescription = ({ children }) => {
|
|
284
|
+
return (
|
|
285
|
+
<p className="text-slate-11 text-sm text-center max-w-[300px]">
|
|
286
|
+
{children}
|
|
287
|
+
</p>
|
|
288
|
+
);
|
|
289
|
+
};
|
|
290
|
+
|
|
247
291
|
interface ToolbarProps {
|
|
248
292
|
serverSpamCheckingResult: SpamCheckingResult | undefined;
|
|
249
293
|
serverLintingRows: LintingRow[] | undefined;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
2
|
|
|
3
|
-
exports[`getEmailComponent() > with a demo email template 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head><link rel="preload" as="image" href="/static/vercel-logo.png"/><link rel="preload" as="image" href="/static/vercel-user.png"/><link rel="preload" as="image" href="/static/vercel-arrow.png"/><link rel="preload" as="image" href="/static/vercel-team.png"/><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><!--$--></head><body style="background-color:rgb(255,255,255);margin-top:auto;margin-bottom:auto;margin-left:auto;margin-right:auto;font-family:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";padding-left:0.5rem;padding-right:0.5rem"><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0">Join Alan on Vercel<div> </div></div><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="border-width:1px;border-style:solid;border-color:rgb(234,234,234);border-radius:0.25rem;margin-top:40px;margin-bottom:40px;margin-left:auto;margin-right:auto;padding:20px;max-width:465px"><tbody><tr style="width:100%"><td><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="margin-top:32px"><tbody><tr><td><img alt="Vercel" height="37" src="/static/vercel-logo.png" style="margin-top:0px;margin-bottom:0px;margin-left:auto;margin-right:auto;display:block;outline:none;border:none;text-decoration:none" width="40"/></td></tr></tbody></table><h1 style="color:rgb(0,0,0);font-size:24px;font-weight:400;text-align:center;padding:0px;margin-top:30px;margin-bottom:30px;margin-left:0px;margin-right:0px">Join <strong>Enigma</strong> on <strong>Vercel</strong></h1><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin-bottom:16px;margin-top:16px">Hello <!-- -->alanturing<!-- -->,</p><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin-bottom:16px;margin-top:16px"><strong>Alan</strong> (<a href="mailto:alan.turing@example.com" style="color:rgb(37,99,235);text-decoration-line:none" target="_blank">alan.turing@example.com</a>) has invited you to the <strong>Enigma</strong> team on<!-- --> <strong>Vercel</strong>.</p><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation"><tbody><tr><td><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation"><tbody style="width:100%"><tr style="width:100%"><td align="right" data-id="__react-email-column"><img height="64" src="/static/vercel-user.png" style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none" width="64"/></td><td align="center" data-id="__react-email-column"><img alt="
|
|
3
|
+
exports[`getEmailComponent() > with a demo email template 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head><link rel="preload" as="image" href="/static/vercel-logo.png"/><link rel="preload" as="image" href="/static/vercel-user.png"/><link rel="preload" as="image" href="/static/vercel-arrow.png"/><link rel="preload" as="image" href="/static/vercel-team.png"/><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><!--$--></head><body style="background-color:rgb(255,255,255);margin-top:auto;margin-bottom:auto;margin-left:auto;margin-right:auto;font-family:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";padding-left:0.5rem;padding-right:0.5rem"><div style="display:none;overflow:hidden;line-height:1px;opacity:0;max-height:0;max-width:0">Join Alan on Vercel<div> </div></div><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="border-width:1px;border-style:solid;border-color:rgb(234,234,234);border-radius:0.25rem;margin-top:40px;margin-bottom:40px;margin-left:auto;margin-right:auto;padding:20px;max-width:465px"><tbody><tr style="width:100%"><td><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="margin-top:32px"><tbody><tr><td><img alt="Vercel Logo" height="37" src="/static/vercel-logo.png" style="margin-top:0px;margin-bottom:0px;margin-left:auto;margin-right:auto;display:block;outline:none;border:none;text-decoration:none" width="40"/></td></tr></tbody></table><h1 style="color:rgb(0,0,0);font-size:24px;font-weight:400;text-align:center;padding:0px;margin-top:30px;margin-bottom:30px;margin-left:0px;margin-right:0px">Join <strong>Enigma</strong> on <strong>Vercel</strong></h1><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin-bottom:16px;margin-top:16px">Hello <!-- -->alanturing<!-- -->,</p><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin-bottom:16px;margin-top:16px"><strong>Alan</strong> (<a href="mailto:alan.turing@example.com" style="color:rgb(37,99,235);text-decoration-line:none" target="_blank">alan.turing@example.com</a>) has invited you to the <strong>Enigma</strong> team on<!-- --> <strong>Vercel</strong>.</p><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation"><tbody><tr><td><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation"><tbody style="width:100%"><tr style="width:100%"><td align="right" data-id="__react-email-column"><img alt="alanturing's profile picture" height="64" src="/static/vercel-user.png" style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none" width="64"/></td><td align="center" data-id="__react-email-column"><img alt="Arrow indicating invitation" height="9" src="/static/vercel-arrow.png" style="display:block;outline:none;border:none;text-decoration:none" width="12"/></td><td align="left" data-id="__react-email-column"><img alt="Enigma team logo" height="64" src="/static/vercel-team.png" style="border-radius:9999px;display:block;outline:none;border:none;text-decoration:none" width="64"/></td></tr></tbody></table></td></tr></tbody></table><table align="center" width="100%" border="0" cellPadding="0" cellSpacing="0" role="presentation" style="text-align:center;margin-top:32px;margin-bottom:32px"><tbody><tr><td><a href="https://vercel.com" style="background-color:rgb(0,0,0);border-radius:0.25rem;color:rgb(255,255,255);font-size:12px;font-weight:600;text-decoration-line:none;text-align:center;padding-left:1.25rem;padding-right:1.25rem;padding-top:0.75rem;padding-bottom:0.75rem;line-height:100%;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;padding:12px 20px 12px 20px" target="_blank"><span><!--[if mso]><i style="mso-font-width:500%;mso-text-raise:18" hidden>  </i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:9px">Join the team</span><span><!--[if mso]><i style="mso-font-width:500%" hidden>  ​</i><![endif]--></span></a></td></tr></tbody></table><p style="color:rgb(0,0,0);font-size:14px;line-height:24px;margin-bottom:16px;margin-top:16px">or copy and paste this URL into your browser:<!-- --> <a href="https://vercel.com" style="color:rgb(37,99,235);text-decoration-line:none" target="_blank">https://vercel.com</a></p><hr style="border-width:1px;border-style:solid;border-color:rgb(234,234,234);margin-top:26px;margin-bottom:26px;margin-left:0px;margin-right:0px;width:100%;border:none;border-top:1px solid #eaeaea"/><p style="color:rgb(102,102,102);font-size:12px;line-height:24px;margin-bottom:16px;margin-top:16px">This invitation was intended for<!-- --> <span style="color:rgb(0,0,0)">alanturing</span>. This invite was sent from <span style="color:rgb(0,0,0)">204.13.186.218</span> <!-- -->located in<!-- --> <span style="color:rgb(0,0,0)">São Paulo, Brazil</span>. If you were not expecting this invitation, you can ignore this email. If you are concerned about your account's safety, please reply to this email to get in touch with us.</p></td></tr></tbody></table><!--/$--></body></html>"`;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`getObjectVariables() 1`] = `
|
|
4
|
+
{
|
|
5
|
+
"buttonStyle": [
|
|
6
|
+
Node {
|
|
7
|
+
"computed": false,
|
|
8
|
+
"end": 91,
|
|
9
|
+
"key": Node {
|
|
10
|
+
"end": 84,
|
|
11
|
+
"loc": SourceLocation {
|
|
12
|
+
"end": Position {
|
|
13
|
+
"column": 14,
|
|
14
|
+
"index": 84,
|
|
15
|
+
"line": 5,
|
|
16
|
+
},
|
|
17
|
+
"filename": undefined,
|
|
18
|
+
"identifierName": "borderRadius",
|
|
19
|
+
"start": Position {
|
|
20
|
+
"column": 2,
|
|
21
|
+
"index": 72,
|
|
22
|
+
"line": 5,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
"name": "borderRadius",
|
|
26
|
+
"start": 72,
|
|
27
|
+
"type": "Identifier",
|
|
28
|
+
},
|
|
29
|
+
"loc": SourceLocation {
|
|
30
|
+
"end": Position {
|
|
31
|
+
"column": 21,
|
|
32
|
+
"index": 91,
|
|
33
|
+
"line": 5,
|
|
34
|
+
},
|
|
35
|
+
"filename": undefined,
|
|
36
|
+
"identifierName": undefined,
|
|
37
|
+
"start": Position {
|
|
38
|
+
"column": 2,
|
|
39
|
+
"index": 72,
|
|
40
|
+
"line": 5,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
"method": false,
|
|
44
|
+
"shorthand": false,
|
|
45
|
+
"start": 72,
|
|
46
|
+
"type": "ObjectProperty",
|
|
47
|
+
"value": Node {
|
|
48
|
+
"end": 91,
|
|
49
|
+
"extra": {
|
|
50
|
+
"raw": "'5px'",
|
|
51
|
+
"rawValue": "5px",
|
|
52
|
+
},
|
|
53
|
+
"loc": SourceLocation {
|
|
54
|
+
"end": Position {
|
|
55
|
+
"column": 21,
|
|
56
|
+
"index": 91,
|
|
57
|
+
"line": 5,
|
|
58
|
+
},
|
|
59
|
+
"filename": undefined,
|
|
60
|
+
"identifierName": undefined,
|
|
61
|
+
"start": Position {
|
|
62
|
+
"column": 16,
|
|
63
|
+
"index": 86,
|
|
64
|
+
"line": 5,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
"start": 86,
|
|
68
|
+
"type": "StringLiteral",
|
|
69
|
+
"value": "5px",
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`getUsedStyleProperties() 1`] = `
|
|
4
|
+
[
|
|
5
|
+
{
|
|
6
|
+
"location": SourceLocation {
|
|
7
|
+
"end": Position {
|
|
8
|
+
"column": 21,
|
|
9
|
+
"index": 91,
|
|
10
|
+
"line": 5,
|
|
11
|
+
},
|
|
12
|
+
"filename": undefined,
|
|
13
|
+
"identifierName": undefined,
|
|
14
|
+
"start": Position {
|
|
15
|
+
"column": 2,
|
|
16
|
+
"index": 72,
|
|
17
|
+
"line": 5,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
"name": "borderRadius",
|
|
21
|
+
"value": "5px",
|
|
22
|
+
},
|
|
23
|
+
]
|
|
24
|
+
`;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import { getObjectVariables } from './get-object-variables';
|
|
3
|
+
|
|
4
|
+
test('getObjectVariables()', () => {
|
|
5
|
+
const reactCode = `
|
|
6
|
+
<Button style={buttonStyle}>Click me</Button>
|
|
7
|
+
|
|
8
|
+
const buttonStyle = {
|
|
9
|
+
borderRadius: '5px',
|
|
10
|
+
};
|
|
11
|
+
`;
|
|
12
|
+
const ast = parse(reactCode, {
|
|
13
|
+
strictMode: false,
|
|
14
|
+
errorRecovery: true,
|
|
15
|
+
sourceType: 'unambiguous',
|
|
16
|
+
plugins: ['jsx', 'typescript', 'decorators'],
|
|
17
|
+
});
|
|
18
|
+
expect(getObjectVariables(ast)).toMatchSnapshot();
|
|
19
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import { getObjectVariables } from './get-object-variables';
|
|
3
|
+
import { getUsedStyleProperties } from './get-used-style-properties';
|
|
4
|
+
|
|
5
|
+
test('getUsedStyleProperties()', async () => {
|
|
6
|
+
const reactCode = `
|
|
7
|
+
<Button style={buttonStyle}>Click me</Button>
|
|
8
|
+
|
|
9
|
+
const buttonStyle = {
|
|
10
|
+
borderRadius: '5px',
|
|
11
|
+
};
|
|
12
|
+
`;
|
|
13
|
+
const ast = parse(reactCode, {
|
|
14
|
+
strictMode: false,
|
|
15
|
+
errorRecovery: true,
|
|
16
|
+
sourceType: 'unambiguous',
|
|
17
|
+
plugins: ['jsx', 'typescript', 'decorators'],
|
|
18
|
+
});
|
|
19
|
+
const objectVariables = getObjectVariables(ast);
|
|
20
|
+
expect(
|
|
21
|
+
await getUsedStyleProperties(ast, reactCode, '', objectVariables),
|
|
22
|
+
).toMatchSnapshot();
|
|
23
|
+
});
|
|
@@ -6,8 +6,8 @@ export const getCssPropertyWithValue = (title: string) => {
|
|
|
6
6
|
if (match) {
|
|
7
7
|
const [_full, propertyName, propertyValue] = match;
|
|
8
8
|
return {
|
|
9
|
-
name: propertyName
|
|
10
|
-
value: propertyValue
|
|
9
|
+
name: propertyName!,
|
|
10
|
+
value: propertyValue!,
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
13
|
return undefined;
|