react-email 4.0.0-alpha.4 → 4.0.0-alpha.6
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 +12 -0
- package/dist/cli/index.js +1175 -2658
- package/dist/cli/index.mjs +18 -12
- package/dist/preview/.next/BUILD_ID +1 -1
- package/dist/preview/.next/app-build-manifest.json +31 -34
- package/dist/preview/.next/app-path-routes-manifest.json +6 -1
- package/dist/preview/.next/build-manifest.json +14 -14
- 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/diagnostics/framework.json +1 -1
- package/dist/preview/.next/export-marker.json +6 -1
- package/dist/preview/.next/images-manifest.json +57 -1
- 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 +41 -1
- package/dist/preview/.next/required-server-files.json +310 -1
- package/dist/preview/.next/routes-manifest.json +64 -1
- package/dist/preview/.next/server/app/_not-found/page.js +1 -1
- package/dist/preview/.next/server/app/_not-found/page.js.nft.json +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/favicon.ico/route.js.nft.json +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 +47 -10
- 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/171.js +14 -0
- package/dist/preview/.next/server/chunks/446.js +6 -0
- package/dist/preview/.next/server/chunks/600.js +8 -0
- package/dist/preview/.next/server/chunks/811.js +13 -0
- package/dist/preview/.next/server/chunks/833.js +1 -0
- package/dist/preview/.next/server/functions-config-manifest.json +4 -1
- 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/_app.js +1 -1
- package/dist/preview/.next/server/pages/_app.js.nft.json +1 -1
- package/dist/preview/.next/server/pages/_document.js +1 -1
- package/dist/preview/.next/server/pages/_document.js.nft.json +1 -1
- package/dist/preview/.next/server/pages/_error.js +1 -1
- package/dist/preview/.next/server/pages/_error.js.nft.json +1 -1
- package/dist/preview/.next/server/pages-manifest.json +5 -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/server/webpack-runtime.js +1 -1
- package/dist/preview/.next/static/chunks/416-56f79fc7e689f06f.js +1 -0
- package/dist/preview/.next/static/chunks/683-8bbfd191e5105f01.js +1 -0
- package/dist/preview/.next/static/chunks/744-79730358b37b2212.js +1 -0
- package/dist/preview/.next/static/chunks/781-5f16c6bc9d9d4cc1.js +1 -0
- package/dist/preview/.next/static/chunks/832ad4be-cb988facfb8f955f.js +1 -0
- package/dist/preview/.next/static/chunks/87-38e35f08507de015.js +1 -0
- package/dist/preview/.next/static/chunks/{afa401a5-9ebf2515b1397993.js → afa401a5-3e949a1cfd317dd3.js} +3 -3
- package/dist/preview/.next/static/chunks/app/_not-found/page-09d694081cc9d4dc.js +1 -0
- package/dist/preview/.next/static/chunks/app/layout-a6640e62690d8fd6.js +1 -0
- package/dist/preview/.next/static/chunks/app/page-ba68f50b287e7478.js +1 -0
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-4a5b026ab543e27f.js +1 -0
- package/dist/preview/.next/static/chunks/framework-c2bd6d936e3077bc.js +1 -0
- package/dist/preview/.next/static/chunks/main-44463a8301435b64.js +1 -0
- package/dist/preview/.next/static/chunks/main-app-c2e686acf8d370d7.js +1 -0
- package/dist/preview/.next/static/chunks/pages/_app-f3011d3f00bb8dba.js +1 -0
- package/dist/preview/.next/static/chunks/pages/_error-39a87dee2e97a2a3.js +1 -0
- package/dist/preview/.next/static/chunks/{webpack-9255716c9496e606.js → webpack-41e2667c9f086a4f.js} +1 -1
- package/dist/preview/.next/static/css/d7df9cfc3e182163.css +3 -0
- package/dist/preview/.next/static/gFk9UfWL8joM4iD7-wlKF/_buildManifest.js +1 -0
- package/dist/preview/.next/static/media/05613964ce6c782e-s.p.otf +0 -0
- package/dist/preview/.next/static/media/11c6126b9369e85e-s.p.otf +0 -0
- package/dist/preview/.next/static/media/26cb97734d8cb717-s.p.otf +0 -0
- package/dist/preview/.next/static/media/bb6462617151f6b7-s.p.otf +0 -0
- package/dist/preview/.next/static/media/cf6daef822ab0142-s.p.otf +0 -0
- package/dist/preview/.next/static/media/e4051546b3043204-s.p.otf +0 -0
- package/dist/preview/.next/trace +26 -22
- package/dist/preview/.next/types/cache-life.d.ts +3 -3
- package/package.json +17 -11
- package/scripts/build-preview-server.mjs +32 -0
- package/scripts/fill-caniemail-data.mjs +36 -0
- package/src/actions/email-validation/caniemail-data.ts +85993 -0
- package/src/actions/email-validation/check-compatibility.ts +322 -0
- package/src/actions/email-validation/check-images.spec.tsx +21 -12
- package/src/actions/email-validation/check-images.ts +88 -86
- package/src/actions/email-validation/check-links.spec.tsx +24 -14
- package/src/actions/email-validation/check-links.ts +59 -56
- package/src/actions/get-email-path-from-slug.ts +1 -1
- package/src/actions/render-email-by-path.tsx +2 -1
- package/src/{utils/emails-directory-absolute-path.ts → app/env.ts} +2 -0
- package/src/app/fonts/SFMono/SFMonoBold.otf +0 -0
- package/src/app/fonts/SFMono/SFMonoBoldItalic.otf +0 -0
- package/src/app/fonts/SFMono/SFMonoHeavy.otf +0 -0
- package/src/app/fonts/SFMono/SFMonoHeavyItalic.otf +0 -0
- package/src/app/fonts/SFMono/SFMonoLight.otf +0 -0
- package/src/app/fonts/SFMono/SFMonoLightItalic.otf +0 -0
- package/src/app/fonts/SFMono/SFMonoMedium.otf +0 -0
- package/src/app/fonts/SFMono/SFMonoMediumItalic.otf +0 -0
- package/src/app/fonts/SFMono/SFMonoRegular.otf +0 -0
- package/src/app/fonts/SFMono/SFMonoRegularItalic.otf +0 -0
- package/src/app/fonts/SFMono/SFMonoSemibold.otf +0 -0
- package/src/app/fonts/SFMono/SFMonoSemiboldItalic.otf +0 -0
- package/src/app/fonts.ts +39 -0
- package/src/app/layout.tsx +6 -3
- package/src/app/page.tsx +4 -4
- package/src/app/preview/[...slug]/page.tsx +73 -16
- package/src/app/preview/[...slug]/preview.tsx +49 -77
- package/src/components/code.tsx +0 -1
- package/src/components/icons/icon-base.tsx +4 -2
- package/src/components/icons/icon-reload.tsx +19 -0
- package/src/components/icons/icon-scanner.tsx +19 -0
- package/src/components/icons/icon-scissors.tsx +19 -0
- package/src/components/icons/icon-warning.tsx +31 -0
- package/src/components/send.tsx +1 -2
- package/src/components/shell.tsx +52 -88
- package/src/components/sidebar/file-tree-directory-children.tsx +1 -1
- package/src/components/sidebar/file-tree.tsx +1 -1
- package/src/components/sidebar/sidebar.tsx +23 -378
- package/src/components/toolbar/linter.tsx +310 -0
- package/src/components/toolbar/results-table.tsx +0 -0
- package/src/components/toolbar/results.tsx +48 -0
- package/src/components/toolbar/spam-assassin.tsx +144 -0
- package/src/components/toolbar/toolbar-button.tsx +50 -0
- package/src/components/toolbar/use-cached-state.ts +33 -0
- package/src/components/toolbar.tsx +197 -0
- package/src/components/tooltip-content.tsx +1 -2
- package/src/components/topbar/view-size-controls.tsx +1 -0
- package/src/components/topbar.tsx +29 -48
- package/src/contexts/emails.tsx +2 -1
- package/src/contexts/preview.tsx +81 -0
- package/src/hooks/use-email-rendering-result.ts +2 -1
- package/src/utils/__snapshots__/get-email-component.spec.ts.snap +1 -1
- package/src/utils/caniemail/all-css-properties.ts +358 -0
- package/src/utils/caniemail/ast/get-object-variables.ts +61 -0
- package/src/utils/caniemail/ast/get-used-style-properties.ts +91 -0
- package/src/utils/caniemail/get-compatibility-stats-for-entry.ts +118 -0
- package/src/utils/caniemail/get-css-functions.ts +25 -0
- package/src/utils/caniemail/get-css-property-names.ts +32 -0
- package/src/utils/caniemail/get-css-property-with-value.ts +14 -0
- package/src/utils/caniemail/get-css-unit.ts +3 -0
- package/src/utils/caniemail/get-element-attributes.ts +7 -0
- package/src/utils/caniemail/get-element-names.ts +20 -0
- package/src/utils/caniemail/tailwind/generate-tailwind-rules.ts +30 -0
- package/src/utils/caniemail/tailwind/get-tailwind-config.ts +205 -0
- package/src/utils/caniemail/tailwind/get-tailwind-metadata.spec.ts +25 -0
- package/src/utils/caniemail/tailwind/get-tailwind-metadata.ts +45 -0
- package/src/utils/caniemail/tailwind/setup-tailwind-context.ts +15 -0
- package/src/utils/get-email-component.ts +34 -67
- package/src/utils/linting.ts +85 -0
- package/src/utils/result.ts +49 -0
- package/src/utils/run-bundled-code.ts +64 -0
- package/tailwind-internals.d.ts +133 -0
- package/tailwind.config.ts +1 -0
- package/tsconfig.json +9 -3
- package/build-preview-server.mjs +0 -25
- package/dist/preview/.next/server/chunks/196.js +0 -5
- package/dist/preview/.next/server/chunks/300.js +0 -13
- package/dist/preview/.next/server/chunks/631.js +0 -6
- package/dist/preview/.next/server/chunks/644.js +0 -1
- package/dist/preview/.next/server/chunks/734.js +0 -15
- package/dist/preview/.next/static/Pt6wqIrWnQxbiyqaKNFOx/_buildManifest.js +0 -1
- package/dist/preview/.next/static/chunks/285-dbf6306a0d45c33d.js +0 -1
- package/dist/preview/.next/static/chunks/447-886131c35ca42b91.js +0 -1
- package/dist/preview/.next/static/chunks/490-d5745684930d49e0.js +0 -1
- package/dist/preview/.next/static/chunks/5fec7a0a-5179023f3f5a9421.js +0 -1
- package/dist/preview/.next/static/chunks/603-36207c8905355e23.js +0 -1
- package/dist/preview/.next/static/chunks/797-46f6c20952f0a280.js +0 -2
- package/dist/preview/.next/static/chunks/app/_not-found/page-96d3eac723be3ee2.js +0 -1
- package/dist/preview/.next/static/chunks/app/layout-d06046b8a368df3b.js +0 -1
- package/dist/preview/.next/static/chunks/app/page-ef1c23b954fbd0b5.js +0 -1
- package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-ea8e1ae2b5a4a0ec.js +0 -1
- package/dist/preview/.next/static/chunks/framework-e7cae9cecd5c9ba2.js +0 -1
- package/dist/preview/.next/static/chunks/main-app-9f2fb5ea26e2765b.js +0 -1
- package/dist/preview/.next/static/chunks/main-df761fde212f9cda.js +0 -1
- package/dist/preview/.next/static/chunks/pages/_app-203a61b355820ccf.js +0 -1
- package/dist/preview/.next/static/chunks/pages/_error-1764ca54938748c8.js +0 -1
- package/dist/preview/.next/static/css/e4822d5ba3082a95.css +0 -3
- package/dist/preview/.next/static/css/ec5d7e66bd3b6cb8.css +0 -1
- package/src/app/inter.ts +0 -7
- package/src/components/icons/icon-circle-check.tsx +0 -21
- package/src/components/icons/icon-circle-close.tsx +0 -17
- package/src/components/icons/icon-circle-warning.tsx +0 -17
- package/src/components/sidebar/image-checker.tsx +0 -162
- package/src/components/sidebar/link-checker.tsx +0 -151
- package/src/components/sidebar/spam-assassin.tsx +0 -158
- /package/dist/preview/.next/static/{Pt6wqIrWnQxbiyqaKNFOx → gFk9UfWL8joM4iD7-wlKF}/_ssgManifest.js +0 -0
- /package/src/components/{sidebar → toolbar}/checking-results.tsx +0 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as Tabs from '@radix-ui/react-tabs';
|
|
3
|
+
import { LayoutGroup } from 'framer-motion';
|
|
4
|
+
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
|
5
|
+
import { use, useEffect } from 'react';
|
|
6
|
+
import { isBuilding } from '../app/env';
|
|
7
|
+
import { PreviewContext } from '../contexts/preview';
|
|
8
|
+
import { cn } from '../utils';
|
|
9
|
+
import { IconArrowDown } from './icons/icon-arrow-down';
|
|
10
|
+
import { IconReload } from './icons/icon-reload';
|
|
11
|
+
import { IconScanner } from './icons/icon-scanner';
|
|
12
|
+
import { IconScissors } from './icons/icon-scissors';
|
|
13
|
+
import { Linter, type LintingRow, useLinter } from './toolbar/linter';
|
|
14
|
+
import {
|
|
15
|
+
SpamAssassin,
|
|
16
|
+
type SpamCheckingResult,
|
|
17
|
+
useSpamAssassin,
|
|
18
|
+
} from './toolbar/spam-assassin';
|
|
19
|
+
import { ToolbarButton } from './toolbar/toolbar-button';
|
|
20
|
+
import { useCachedState } from './toolbar/use-cached-state';
|
|
21
|
+
|
|
22
|
+
export type ToolbarTabValue = 'linter' | 'spam-assassin';
|
|
23
|
+
|
|
24
|
+
const ToolbarInner = ({
|
|
25
|
+
serverLintingRows,
|
|
26
|
+
serverSpamCheckingResult,
|
|
27
|
+
|
|
28
|
+
markup,
|
|
29
|
+
reactMarkup,
|
|
30
|
+
plainText,
|
|
31
|
+
emailPath,
|
|
32
|
+
emailSlug,
|
|
33
|
+
}: ToolbarProps & {
|
|
34
|
+
markup: string;
|
|
35
|
+
reactMarkup: string;
|
|
36
|
+
plainText: string;
|
|
37
|
+
emailSlug: string;
|
|
38
|
+
emailPath: string;
|
|
39
|
+
}) => {
|
|
40
|
+
const pathname = usePathname();
|
|
41
|
+
const searchParams = useSearchParams();
|
|
42
|
+
const router = useRouter();
|
|
43
|
+
|
|
44
|
+
const activeTab = (searchParams.get('toolbar-panel') ?? undefined) as
|
|
45
|
+
| ToolbarTabValue
|
|
46
|
+
| undefined;
|
|
47
|
+
|
|
48
|
+
const toggled = activeTab !== undefined;
|
|
49
|
+
|
|
50
|
+
const setActivePanelValue = (newValue: ToolbarTabValue | undefined) => {
|
|
51
|
+
const params = new URLSearchParams(searchParams);
|
|
52
|
+
if (newValue === undefined) {
|
|
53
|
+
params.delete('toolbar-panel');
|
|
54
|
+
} else {
|
|
55
|
+
params.set('toolbar-panel', newValue);
|
|
56
|
+
}
|
|
57
|
+
router.push(`${pathname}?${params.toString()}`);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const [cachedSpamCheckingResult, setCachedSpamCheckingResult] =
|
|
61
|
+
useCachedState<SpamCheckingResult>(
|
|
62
|
+
`spam-assassin-${emailSlug.replaceAll('/', '-')}`,
|
|
63
|
+
);
|
|
64
|
+
const [spamCheckingResult, { load: loadSpamChecking }] = useSpamAssassin({
|
|
65
|
+
markup,
|
|
66
|
+
plainText,
|
|
67
|
+
|
|
68
|
+
initialResult: serverSpamCheckingResult ?? cachedSpamCheckingResult,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const [cachedLintingRows, setCachedLintingRows] = useCachedState<
|
|
72
|
+
LintingRow[]
|
|
73
|
+
>(`linter-${emailSlug.replaceAll('/', '-')}`);
|
|
74
|
+
const [lintingRows, { load: loadLinting }] = useLinter({
|
|
75
|
+
reactMarkup,
|
|
76
|
+
emailPath,
|
|
77
|
+
markup,
|
|
78
|
+
|
|
79
|
+
initialRows: serverLintingRows ?? cachedLintingRows,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!isBuilding) {
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
(async () => {
|
|
85
|
+
const lintingRows = await loadLinting();
|
|
86
|
+
setCachedLintingRows(lintingRows);
|
|
87
|
+
|
|
88
|
+
const spamCheckingResult = await loadSpamChecking();
|
|
89
|
+
setCachedSpamCheckingResult(spamCheckingResult);
|
|
90
|
+
})();
|
|
91
|
+
}, []);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div
|
|
96
|
+
data-toggled={toggled}
|
|
97
|
+
className={cn(
|
|
98
|
+
'bg-black group/toolbar text-xs text-slate-11 h-48 transition-all',
|
|
99
|
+
'data-[toggled=false]:h-8',
|
|
100
|
+
)}
|
|
101
|
+
>
|
|
102
|
+
<Tabs.Root
|
|
103
|
+
value={activeTab}
|
|
104
|
+
onValueChange={(newValue) => {
|
|
105
|
+
setActivePanelValue(newValue as ToolbarTabValue);
|
|
106
|
+
}}
|
|
107
|
+
asChild
|
|
108
|
+
>
|
|
109
|
+
<div className="flex flex-col h-full">
|
|
110
|
+
<Tabs.List className="flex gap-4 px-2 border-b border-solid border-slate-6 h-7 w-full">
|
|
111
|
+
<LayoutGroup id="toolbar">
|
|
112
|
+
<Tabs.Trigger asChild value="spam-assassin">
|
|
113
|
+
<ToolbarButton active={activeTab === 'spam-assassin'}>
|
|
114
|
+
<IconScissors />
|
|
115
|
+
Spam Assassin
|
|
116
|
+
</ToolbarButton>
|
|
117
|
+
</Tabs.Trigger>
|
|
118
|
+
<Tabs.Trigger asChild value="linter">
|
|
119
|
+
<ToolbarButton active={activeTab === 'linter'}>
|
|
120
|
+
<IconScanner />
|
|
121
|
+
Linter
|
|
122
|
+
</ToolbarButton>
|
|
123
|
+
</Tabs.Trigger>
|
|
124
|
+
</LayoutGroup>
|
|
125
|
+
<div className="flex gap-1 ml-auto">
|
|
126
|
+
{isBuilding ? null : (
|
|
127
|
+
<ToolbarButton
|
|
128
|
+
tooltip="Reload"
|
|
129
|
+
onClick={async () => {
|
|
130
|
+
if (activeTab === undefined) {
|
|
131
|
+
setActivePanelValue('linter');
|
|
132
|
+
}
|
|
133
|
+
if (activeTab === 'spam-assassin') {
|
|
134
|
+
await loadSpamChecking();
|
|
135
|
+
} else {
|
|
136
|
+
await loadLinting();
|
|
137
|
+
}
|
|
138
|
+
}}
|
|
139
|
+
>
|
|
140
|
+
<IconReload />
|
|
141
|
+
</ToolbarButton>
|
|
142
|
+
)}
|
|
143
|
+
<ToolbarButton
|
|
144
|
+
tooltip="Toggle toolbar"
|
|
145
|
+
onClick={() => {
|
|
146
|
+
if (activeTab === undefined) {
|
|
147
|
+
setActivePanelValue('linter');
|
|
148
|
+
} else {
|
|
149
|
+
setActivePanelValue(undefined);
|
|
150
|
+
}
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
<IconArrowDown className="transition-transform group-data-[toggled=false]/toolbar:rotate-180" />
|
|
154
|
+
</ToolbarButton>
|
|
155
|
+
</div>
|
|
156
|
+
</Tabs.List>
|
|
157
|
+
|
|
158
|
+
<div className="flex-grow transition-opacity opacity-100 group-data-[toggled=false]/toolbar:opacity-0 overflow-y-auto px-2">
|
|
159
|
+
<Tabs.Content value="linter">
|
|
160
|
+
<Linter rows={lintingRows} />
|
|
161
|
+
</Tabs.Content>
|
|
162
|
+
<Tabs.Content value="spam-assassin">
|
|
163
|
+
<SpamAssassin result={spamCheckingResult} />
|
|
164
|
+
</Tabs.Content>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</Tabs.Root>
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
interface ToolbarProps {
|
|
173
|
+
serverSpamCheckingResult: SpamCheckingResult | undefined;
|
|
174
|
+
serverLintingRows: LintingRow[] | undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export const Toolbar = ({
|
|
178
|
+
serverLintingRows,
|
|
179
|
+
serverSpamCheckingResult,
|
|
180
|
+
}: ToolbarProps) => {
|
|
181
|
+
const { emailPath, emailSlug, renderedEmailMetadata } = use(PreviewContext)!;
|
|
182
|
+
|
|
183
|
+
if (renderedEmailMetadata === undefined) return null;
|
|
184
|
+
const { markup, plainText, reactMarkup } = renderedEmailMetadata;
|
|
185
|
+
|
|
186
|
+
return (
|
|
187
|
+
<ToolbarInner
|
|
188
|
+
emailPath={emailPath}
|
|
189
|
+
emailSlug={emailSlug}
|
|
190
|
+
markup={markup}
|
|
191
|
+
reactMarkup={reactMarkup}
|
|
192
|
+
plainText={plainText}
|
|
193
|
+
serverLintingRows={serverLintingRows}
|
|
194
|
+
serverSpamCheckingResult={serverSpamCheckingResult}
|
|
195
|
+
/>
|
|
196
|
+
);
|
|
197
|
+
};
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { inter } from '../app/inter';
|
|
4
3
|
import { cn } from '../utils';
|
|
5
4
|
|
|
6
5
|
type ContentElement = React.ComponentRef<typeof TooltipPrimitive.Content>;
|
|
@@ -19,7 +18,7 @@ export const TooltipContent = React.forwardRef<
|
|
|
19
18
|
{...props}
|
|
20
19
|
className={cn(
|
|
21
20
|
'z-20 rounded-md border border-slate-6 bg-black px-3 py-2 text-white text-xs',
|
|
22
|
-
|
|
21
|
+
'font-sans',
|
|
23
22
|
)}
|
|
24
23
|
ref={forwardedRef}
|
|
25
24
|
sideOffset={sideOffset}
|
|
@@ -1,37 +1,19 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { use } from 'react';
|
|
3
4
|
import { Heading } from './heading';
|
|
4
5
|
import { IconHideSidebar } from './icons/icon-hide-sidebar';
|
|
5
|
-
import {
|
|
6
|
+
import { ShellContext } from './shell';
|
|
6
7
|
import { Tooltip } from './tooltip';
|
|
7
|
-
import { ActiveViewToggleGroup } from './topbar/active-view-toggle-group';
|
|
8
|
-
import { ViewSizeControls } from './topbar/view-size-controls';
|
|
9
8
|
|
|
10
9
|
interface TopbarProps {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
markup?: string;
|
|
14
|
-
onToggleSidebar?: () => void;
|
|
15
|
-
activeView?: string;
|
|
16
|
-
setActiveView?: (view: string) => void;
|
|
17
|
-
viewWidth?: number;
|
|
18
|
-
setViewWidth?: (width: number) => void;
|
|
19
|
-
viewHeight?: number;
|
|
20
|
-
setViewHeight?: (height: number) => void;
|
|
10
|
+
emailTitle: string;
|
|
11
|
+
children: React.ReactNode;
|
|
21
12
|
}
|
|
22
13
|
|
|
23
|
-
export const Topbar = ({
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
markup,
|
|
27
|
-
activeView,
|
|
28
|
-
setActiveView,
|
|
29
|
-
viewWidth,
|
|
30
|
-
setViewWidth,
|
|
31
|
-
viewHeight,
|
|
32
|
-
setViewHeight,
|
|
33
|
-
onToggleSidebar,
|
|
34
|
-
}: TopbarProps) => {
|
|
14
|
+
export const Topbar = ({ emailTitle, children }: TopbarProps) => {
|
|
15
|
+
const { toggleSidebar } = use(ShellContext)!;
|
|
16
|
+
|
|
35
17
|
return (
|
|
36
18
|
<Tooltip.Provider>
|
|
37
19
|
<header className="relative flex h-[3.3125rem] items-center justify-between gap-3 border-slate-6 border-b px-3">
|
|
@@ -41,9 +23,7 @@ export const Topbar = ({
|
|
|
41
23
|
<button
|
|
42
24
|
className="relative hidden rounded-lg px-2 py-2 text-slate-11 transition duration-200 ease-in-out hover:bg-slate-5 hover:text-slate-12 lg:flex"
|
|
43
25
|
onClick={() => {
|
|
44
|
-
|
|
45
|
-
onToggleSidebar();
|
|
46
|
-
}
|
|
26
|
+
toggleSidebar();
|
|
47
27
|
}}
|
|
48
28
|
type="button"
|
|
49
29
|
>
|
|
@@ -54,30 +34,31 @@ export const Topbar = ({
|
|
|
54
34
|
</Tooltip>
|
|
55
35
|
<div className="hidden items-center overflow-hidden text-center lg:flex">
|
|
56
36
|
<Heading as="h2" className="truncate" size="2" weight="medium">
|
|
57
|
-
{
|
|
37
|
+
{emailTitle}
|
|
58
38
|
</Heading>
|
|
59
39
|
</div>
|
|
60
40
|
</div>
|
|
61
41
|
<div className="flex w-full items-center justify-between gap-3 lg:w-fit lg:justify-start">
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
{
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
42
|
+
{children}
|
|
43
|
+
{/* {setViewWidth && setViewHeight && viewWidth && viewHeight ? ( */}
|
|
44
|
+
{/* <ViewSizeControls */}
|
|
45
|
+
{/* setViewHeight={setViewHeight} */}
|
|
46
|
+
{/* setViewWidth={setViewWidth} */}
|
|
47
|
+
{/* viewHeight={viewHeight} */}
|
|
48
|
+
{/* viewWidth={viewWidth} */}
|
|
49
|
+
{/* /> */}
|
|
50
|
+
{/* ) : null} */}
|
|
51
|
+
{/* {activeView && setActiveView ? ( */}
|
|
52
|
+
{/* <ActiveViewToggleGroup */}
|
|
53
|
+
{/* activeView={activeView} */}
|
|
54
|
+
{/* setActiveView={setActiveView} */}
|
|
55
|
+
{/* /> */}
|
|
56
|
+
{/* ) : null} */}
|
|
57
|
+
{/* {markup ? ( */}
|
|
58
|
+
{/* <div className="flex justify-end"> */}
|
|
59
|
+
{/* <Send markup={markup} /> */}
|
|
60
|
+
{/* </div> */}
|
|
61
|
+
{/* ) : null} */}
|
|
81
62
|
</div>
|
|
82
63
|
</header>
|
|
83
64
|
</Tooltip.Provider>
|
package/src/contexts/emails.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { createContext, useContext, useState } from 'react';
|
|
3
3
|
import { getEmailsDirectoryMetadataAction } from '../actions/get-emails-directory-metadata-action';
|
|
4
|
+
import { isBuilding } from '../app/env';
|
|
4
5
|
import { useHotreload } from '../hooks/use-hot-reload';
|
|
5
6
|
import type { EmailsDirectory } from '../utils/get-emails-directory-metadata';
|
|
6
7
|
|
|
@@ -30,7 +31,7 @@ export const EmailsProvider = (props: {
|
|
|
30
31
|
const [emailsDirectoryMetadata, setEmailsDirectoryMetadata] =
|
|
31
32
|
useState<EmailsDirectory>(props.initialEmailsDirectoryMetadata);
|
|
32
33
|
|
|
33
|
-
if (
|
|
34
|
+
if (!isBuilding) {
|
|
34
35
|
// this will not change on runtime so it doesn't violate
|
|
35
36
|
// the rules of hooks
|
|
36
37
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRouter } from 'next/navigation';
|
|
3
|
+
import { createContext } from 'react';
|
|
4
|
+
import type {
|
|
5
|
+
EmailRenderingResult,
|
|
6
|
+
RenderedEmailMetadata,
|
|
7
|
+
} from '../actions/render-email-by-path';
|
|
8
|
+
import { isBuilding } from '../app/env';
|
|
9
|
+
import { useEmailRenderingResult } from '../hooks/use-email-rendering-result';
|
|
10
|
+
import { useHotreload } from '../hooks/use-hot-reload';
|
|
11
|
+
import { useRenderingMetadata } from '../hooks/use-rendering-metadata';
|
|
12
|
+
|
|
13
|
+
export const PreviewContext = createContext<
|
|
14
|
+
| {
|
|
15
|
+
renderedEmailMetadata: RenderedEmailMetadata | undefined;
|
|
16
|
+
renderingResult: EmailRenderingResult;
|
|
17
|
+
|
|
18
|
+
emailSlug: string;
|
|
19
|
+
emailPath: string;
|
|
20
|
+
}
|
|
21
|
+
| undefined
|
|
22
|
+
>(undefined);
|
|
23
|
+
|
|
24
|
+
interface PreviewProvider {
|
|
25
|
+
emailSlug: string;
|
|
26
|
+
emailPath: string;
|
|
27
|
+
|
|
28
|
+
serverRenderingResult: EmailRenderingResult;
|
|
29
|
+
|
|
30
|
+
children: React.ReactNode;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const PreviewProvider = ({
|
|
34
|
+
emailSlug,
|
|
35
|
+
emailPath,
|
|
36
|
+
serverRenderingResult,
|
|
37
|
+
children,
|
|
38
|
+
}: PreviewProvider) => {
|
|
39
|
+
const router = useRouter();
|
|
40
|
+
|
|
41
|
+
const renderingResult = useEmailRenderingResult(
|
|
42
|
+
emailPath,
|
|
43
|
+
serverRenderingResult,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const renderedEmailMetadata = useRenderingMetadata(
|
|
47
|
+
emailPath,
|
|
48
|
+
renderingResult,
|
|
49
|
+
serverRenderingResult,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (!isBuilding) {
|
|
53
|
+
// this will not change on runtime so it doesn't violate
|
|
54
|
+
// the rules of hooks
|
|
55
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
56
|
+
useHotreload((changes) => {
|
|
57
|
+
const changeForThisEmail = changes.find((change) =>
|
|
58
|
+
change.filename.includes(emailSlug),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (typeof changeForThisEmail !== 'undefined') {
|
|
62
|
+
if (changeForThisEmail.event === 'unlink') {
|
|
63
|
+
router.push('/');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<PreviewContext.Provider
|
|
71
|
+
value={{
|
|
72
|
+
emailPath,
|
|
73
|
+
emailSlug,
|
|
74
|
+
renderedEmailMetadata,
|
|
75
|
+
renderingResult,
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
{children}
|
|
79
|
+
</PreviewContext.Provider>
|
|
80
|
+
);
|
|
81
|
+
};
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type EmailRenderingResult,
|
|
5
5
|
renderEmailByPath,
|
|
6
6
|
} from '../actions/render-email-by-path';
|
|
7
|
+
import { isBuilding } from '../app/env';
|
|
7
8
|
import { useHotreload } from './use-hot-reload';
|
|
8
9
|
|
|
9
10
|
export const useEmailRenderingResult = (
|
|
@@ -14,7 +15,7 @@ export const useEmailRenderingResult = (
|
|
|
14
15
|
serverEmailRenderedResult,
|
|
15
16
|
);
|
|
16
17
|
|
|
17
|
-
if (
|
|
18
|
+
if (!isBuilding) {
|
|
18
19
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
19
20
|
useHotreload(async (changes) => {
|
|
20
21
|
for await (const change of changes) {
|
|
@@ -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:16px
|
|
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="invited you to" 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 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/teams/invite/foo" 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/teams/invite/foo" style="color:rgb(37,99,235);text-decoration-line:none" target="_blank">https://vercel.com/teams/invite/foo</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>"`;
|