react-email 4.1.0-canary.6 → 4.1.0-canary.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.
Files changed (282) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/{cli/index.mjs → index.js} +437 -429
  3. package/package.json +10 -45
  4. package/src/commands/build.ts +306 -0
  5. package/src/commands/dev.ts +27 -0
  6. package/src/commands/export.ts +204 -0
  7. package/src/commands/start.ts +38 -0
  8. package/src/index.ts +55 -0
  9. package/src/utils/__snapshots__/tree.spec.ts.snap +27 -0
  10. package/src/utils/esbuild/renderring-utilities-exporter.ts +1 -1
  11. package/src/utils/get-emails-directory-metadata.spec.ts +1 -1
  12. package/src/utils/get-preview-server-location.ts +32 -0
  13. package/src/utils/index.ts +2 -6
  14. package/src/utils/packageJson.ts +4 -0
  15. package/src/utils/preview/get-env-variables-for-preview-app.ts +14 -0
  16. package/src/utils/preview/hot-reloading/create-dependency-graph.spec.ts +281 -0
  17. package/src/utils/preview/hot-reloading/create-dependency-graph.ts +321 -0
  18. package/src/utils/preview/hot-reloading/get-imported-modules.spec.ts +151 -0
  19. package/src/utils/preview/hot-reloading/get-imported-modules.ts +49 -0
  20. package/src/utils/preview/hot-reloading/resolve-path-aliases.spec.ts +11 -0
  21. package/src/utils/preview/hot-reloading/resolve-path-aliases.ts +32 -0
  22. package/src/utils/preview/hot-reloading/setup-hot-reloading.ts +121 -0
  23. package/src/utils/preview/hot-reloading/test/tsconfig.json +8 -0
  24. package/src/utils/preview/index.ts +2 -0
  25. package/src/utils/preview/serve-static-file.ts +51 -0
  26. package/src/utils/preview/start-dev-server.ts +234 -0
  27. package/src/utils/tree.spec.ts +5 -0
  28. package/src/utils/tree.ts +76 -0
  29. package/src/utils/types/hot-reload-change.ts +1 -1
  30. package/src/utils/types/hot-reload-event.ts +1 -1
  31. package/tsconfig.json +4 -10
  32. package/dist/preview/.next/BUILD_ID +0 -1
  33. package/dist/preview/.next/app-build-manifest.json +0 -44
  34. package/dist/preview/.next/app-path-routes-manifest.json +0 -6
  35. package/dist/preview/.next/build-manifest.json +0 -33
  36. package/dist/preview/.next/diagnostics/build-diagnostics.json +0 -6
  37. package/dist/preview/.next/diagnostics/framework.json +0 -1
  38. package/dist/preview/.next/export-marker.json +0 -6
  39. package/dist/preview/.next/images-manifest.json +0 -57
  40. package/dist/preview/.next/next-minimal-server.js.nft.json +0 -1
  41. package/dist/preview/.next/next-server.js.nft.json +0 -1
  42. package/dist/preview/.next/package.json +0 -1
  43. package/dist/preview/.next/prerender-manifest.json +0 -41
  44. package/dist/preview/.next/react-loadable-manifest.json +0 -1
  45. package/dist/preview/.next/required-server-files.json +0 -311
  46. package/dist/preview/.next/routes-manifest.json +0 -64
  47. package/dist/preview/.next/server/app/_not-found/page.js +0 -1
  48. package/dist/preview/.next/server/app/_not-found/page.js.nft.json +0 -1
  49. package/dist/preview/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
  50. package/dist/preview/.next/server/app/favicon.ico/route.js +0 -1
  51. package/dist/preview/.next/server/app/favicon.ico/route.js.nft.json +0 -1
  52. package/dist/preview/.next/server/app/favicon.ico.body +0 -0
  53. package/dist/preview/.next/server/app/favicon.ico.meta +0 -1
  54. package/dist/preview/.next/server/app/page.js +0 -1
  55. package/dist/preview/.next/server/app/page.js.nft.json +0 -1
  56. package/dist/preview/.next/server/app/page_client-reference-manifest.js +0 -1
  57. package/dist/preview/.next/server/app/preview/[...slug]/page.js +0 -321
  58. package/dist/preview/.next/server/app/preview/[...slug]/page.js.nft.json +0 -1
  59. package/dist/preview/.next/server/app/preview/[...slug]/page_client-reference-manifest.js +0 -1
  60. package/dist/preview/.next/server/app-paths-manifest.json +0 -6
  61. package/dist/preview/.next/server/chunks/134.js +0 -6
  62. package/dist/preview/.next/server/chunks/235.js +0 -15
  63. package/dist/preview/.next/server/chunks/315.js +0 -1
  64. package/dist/preview/.next/server/chunks/343.js +0 -20
  65. package/dist/preview/.next/server/chunks/428.js +0 -14
  66. package/dist/preview/.next/server/chunks/963.js +0 -1
  67. package/dist/preview/.next/server/functions-config-manifest.json +0 -4
  68. package/dist/preview/.next/server/interception-route-rewrite-manifest.js +0 -1
  69. package/dist/preview/.next/server/middleware-build-manifest.js +0 -1
  70. package/dist/preview/.next/server/middleware-manifest.json +0 -6
  71. package/dist/preview/.next/server/middleware-react-loadable-manifest.js +0 -1
  72. package/dist/preview/.next/server/next-font-manifest.js +0 -1
  73. package/dist/preview/.next/server/next-font-manifest.json +0 -1
  74. package/dist/preview/.next/server/pages/500.html +0 -1
  75. package/dist/preview/.next/server/pages/_app.js +0 -1
  76. package/dist/preview/.next/server/pages/_app.js.nft.json +0 -1
  77. package/dist/preview/.next/server/pages/_document.js +0 -1
  78. package/dist/preview/.next/server/pages/_document.js.nft.json +0 -1
  79. package/dist/preview/.next/server/pages/_error.js +0 -1
  80. package/dist/preview/.next/server/pages/_error.js.nft.json +0 -1
  81. package/dist/preview/.next/server/pages-manifest.json +0 -5
  82. package/dist/preview/.next/server/server-reference-manifest.js +0 -1
  83. package/dist/preview/.next/server/server-reference-manifest.json +0 -1
  84. package/dist/preview/.next/server/webpack-runtime.js +0 -1
  85. package/dist/preview/.next/static/EYH0WN4--LLC3GZrZIVN8/_buildManifest.js +0 -1
  86. package/dist/preview/.next/static/EYH0WN4--LLC3GZrZIVN8/_ssgManifest.js +0 -1
  87. package/dist/preview/.next/static/chunks/107-3043079e7cb8bcae.js +0 -1
  88. package/dist/preview/.next/static/chunks/293-297b1eb2241f9a70.js +0 -1
  89. package/dist/preview/.next/static/chunks/3bd82e28-cda2c00a924937c5.js +0 -1
  90. package/dist/preview/.next/static/chunks/45-1021fac82f766268.js +0 -1
  91. package/dist/preview/.next/static/chunks/484-a7b30a88a7939680.js +0 -1
  92. package/dist/preview/.next/static/chunks/589-817d8691661d370e.js +0 -1
  93. package/dist/preview/.next/static/chunks/902-c34acb56733e0ce1.js +0 -1
  94. package/dist/preview/.next/static/chunks/app/_not-found/page-4cbc7dce3ad33336.js +0 -1
  95. package/dist/preview/.next/static/chunks/app/layout-46a09d953364e102.js +0 -1
  96. package/dist/preview/.next/static/chunks/app/page-65fd67d48528e2ba.js +0 -1
  97. package/dist/preview/.next/static/chunks/app/preview/[...slug]/page-5e69ffe7506383a0.js +0 -1
  98. package/dist/preview/.next/static/chunks/f33a14d2-ec7c5f0b91818561.js +0 -6
  99. package/dist/preview/.next/static/chunks/framework-b887e9fc751a9906.js +0 -1
  100. package/dist/preview/.next/static/chunks/main-9a03e7ba8acb1900.js +0 -1
  101. package/dist/preview/.next/static/chunks/main-app-dbd8e1ec12eabb66.js +0 -1
  102. package/dist/preview/.next/static/chunks/pages/_app-542a93a5a214e1c0.js +0 -1
  103. package/dist/preview/.next/static/chunks/pages/_error-d5fe1b1612642f76.js +0 -1
  104. package/dist/preview/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  105. package/dist/preview/.next/static/chunks/webpack-31c45daa2bd82a7b.js +0 -1
  106. package/dist/preview/.next/static/css/6f42d128f111d7fa.css +0 -3
  107. package/dist/preview/.next/static/media/05613964ce6c782e-s.p.otf +0 -0
  108. package/dist/preview/.next/static/media/11c6126b9369e85e-s.p.otf +0 -0
  109. package/dist/preview/.next/static/media/26a46d62cd723877-s.woff2 +0 -0
  110. package/dist/preview/.next/static/media/26cb97734d8cb717-s.p.otf +0 -0
  111. package/dist/preview/.next/static/media/55c55f0601d81cf3-s.woff2 +0 -0
  112. package/dist/preview/.next/static/media/581909926a08bbc8-s.woff2 +0 -0
  113. package/dist/preview/.next/static/media/6d93bde91c0c2823-s.woff2 +0 -0
  114. package/dist/preview/.next/static/media/97e0cb1ae144a2a9-s.woff2 +0 -0
  115. package/dist/preview/.next/static/media/a34f9d1faa5f3315-s.p.woff2 +0 -0
  116. package/dist/preview/.next/static/media/bb6462617151f6b7-s.p.otf +0 -0
  117. package/dist/preview/.next/static/media/cf6daef822ab0142-s.p.otf +0 -0
  118. package/dist/preview/.next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
  119. package/dist/preview/.next/static/media/e4051546b3043204-s.p.otf +0 -0
  120. package/dist/preview/.next/static/media/logo.2ce2a759.png +0 -0
  121. package/dist/preview/.next/trace +0 -28
  122. package/dist/preview/.next/types/app/layout.ts +0 -84
  123. package/dist/preview/.next/types/app/page.ts +0 -84
  124. package/dist/preview/.next/types/app/preview/[...slug]/page.ts +0 -84
  125. package/dist/preview/.next/types/cache-life.d.ts +0 -141
  126. package/dist/preview/.next/types/package.json +0 -1
  127. package/module-punycode.d.ts +0 -3
  128. package/next-env.d.ts +0 -5
  129. package/next.config.js +0 -22
  130. package/postcss.config.js +0 -8
  131. package/scripts/build-preview-server.mjs +0 -33
  132. package/scripts/fill-caniemail-data.mjs +0 -36
  133. package/src/actions/email-validation/caniemail-data.ts +0 -85993
  134. package/src/actions/email-validation/check-compatibility.ts +0 -333
  135. package/src/actions/email-validation/check-images.spec.tsx +0 -100
  136. package/src/actions/email-validation/check-images.ts +0 -160
  137. package/src/actions/email-validation/check-links.spec.tsx +0 -113
  138. package/src/actions/email-validation/check-links.ts +0 -113
  139. package/src/actions/email-validation/get-code-location-from-ast-element.ts +0 -18
  140. package/src/actions/email-validation/quick-fetch.ts +0 -14
  141. package/src/actions/get-email-path-from-slug.ts +0 -32
  142. package/src/actions/get-emails-directory-metadata-action.ts +0 -19
  143. package/src/actions/render-email-by-path.tsx +0 -121
  144. package/src/animated-icons-data/help.json +0 -1082
  145. package/src/animated-icons-data/link.json +0 -1309
  146. package/src/animated-icons-data/load.json +0 -443
  147. package/src/animated-icons-data/mail.json +0 -1320
  148. package/src/app/env.ts +0 -15
  149. package/src/app/favicon.ico +0 -0
  150. package/src/app/fonts/SFMono/SFMonoBold.otf +0 -0
  151. package/src/app/fonts/SFMono/SFMonoBoldItalic.otf +0 -0
  152. package/src/app/fonts/SFMono/SFMonoHeavy.otf +0 -0
  153. package/src/app/fonts/SFMono/SFMonoHeavyItalic.otf +0 -0
  154. package/src/app/fonts/SFMono/SFMonoLight.otf +0 -0
  155. package/src/app/fonts/SFMono/SFMonoLightItalic.otf +0 -0
  156. package/src/app/fonts/SFMono/SFMonoMedium.otf +0 -0
  157. package/src/app/fonts/SFMono/SFMonoMediumItalic.otf +0 -0
  158. package/src/app/fonts/SFMono/SFMonoRegular.otf +0 -0
  159. package/src/app/fonts/SFMono/SFMonoRegularItalic.otf +0 -0
  160. package/src/app/fonts/SFMono/SFMonoSemibold.otf +0 -0
  161. package/src/app/fonts/SFMono/SFMonoSemiboldItalic.otf +0 -0
  162. package/src/app/fonts.ts +0 -39
  163. package/src/app/globals.css +0 -15
  164. package/src/app/layout.tsx +0 -45
  165. package/src/app/logo.png +0 -0
  166. package/src/app/page.tsx +0 -46
  167. package/src/app/preview/[...slug]/page.tsx +0 -157
  168. package/src/app/preview/[...slug]/preview.tsx +0 -234
  169. package/src/app/preview/[...slug]/rendering-error.tsx +0 -40
  170. package/src/components/button.tsx +0 -101
  171. package/src/components/code-container.tsx +0 -164
  172. package/src/components/code-snippet.tsx +0 -9
  173. package/src/components/code.tsx +0 -184
  174. package/src/components/heading.tsx +0 -113
  175. package/src/components/icons/icon-arrow-down.tsx +0 -16
  176. package/src/components/icons/icon-base.tsx +0 -26
  177. package/src/components/icons/icon-bug.tsx +0 -19
  178. package/src/components/icons/icon-button.tsx +0 -23
  179. package/src/components/icons/icon-check.tsx +0 -19
  180. package/src/components/icons/icon-clipboard.tsx +0 -40
  181. package/src/components/icons/icon-download.tsx +0 -19
  182. package/src/components/icons/icon-email.tsx +0 -18
  183. package/src/components/icons/icon-file.tsx +0 -19
  184. package/src/components/icons/icon-folder-open.tsx +0 -19
  185. package/src/components/icons/icon-folder.tsx +0 -18
  186. package/src/components/icons/icon-hide-sidebar.tsx +0 -23
  187. package/src/components/icons/icon-image.tsx +0 -19
  188. package/src/components/icons/icon-info.tsx +0 -18
  189. package/src/components/icons/icon-link.tsx +0 -14
  190. package/src/components/icons/icon-monitor.tsx +0 -19
  191. package/src/components/icons/icon-moon.tsx +0 -16
  192. package/src/components/icons/icon-phone.tsx +0 -26
  193. package/src/components/icons/icon-reload.tsx +0 -18
  194. package/src/components/icons/icon-source.tsx +0 -19
  195. package/src/components/icons/icon-stamp.tsx +0 -14
  196. package/src/components/icons/icon-sun.tsx +0 -16
  197. package/src/components/icons/icon-warning.tsx +0 -31
  198. package/src/components/index.ts +0 -7
  199. package/src/components/logo.tsx +0 -63
  200. package/src/components/resizable-wrapper.tsx +0 -173
  201. package/src/components/send.tsx +0 -134
  202. package/src/components/shell.tsx +0 -92
  203. package/src/components/sidebar/file-tree-directory-children.tsx +0 -139
  204. package/src/components/sidebar/file-tree-directory.tsx +0 -92
  205. package/src/components/sidebar/file-tree.tsx +0 -31
  206. package/src/components/sidebar/index.ts +0 -1
  207. package/src/components/sidebar/sidebar.tsx +0 -43
  208. package/src/components/text.tsx +0 -99
  209. package/src/components/toolbar/checking-results.tsx +0 -150
  210. package/src/components/toolbar/code-preview-line-link.tsx +0 -40
  211. package/src/components/toolbar/compatibility.tsx +0 -113
  212. package/src/components/toolbar/linter.tsx +0 -278
  213. package/src/components/toolbar/results.tsx +0 -51
  214. package/src/components/toolbar/spam-assassin.tsx +0 -155
  215. package/src/components/toolbar/toolbar-button.tsx +0 -52
  216. package/src/components/toolbar/use-cached-state.ts +0 -33
  217. package/src/components/toolbar.tsx +0 -349
  218. package/src/components/tooltip-content.tsx +0 -31
  219. package/src/components/tooltip.tsx +0 -19
  220. package/src/components/topbar/active-view-toggle-group.tsx +0 -86
  221. package/src/components/topbar/theme-toggle-group.tsx +0 -87
  222. package/src/components/topbar/view-size-controls.tsx +0 -247
  223. package/src/components/topbar.tsx +0 -59
  224. package/src/contexts/emails.tsx +0 -59
  225. package/src/contexts/fragment-identifier.tsx +0 -48
  226. package/src/contexts/preview.tsx +0 -79
  227. package/src/hooks/use-clamped-state.ts +0 -24
  228. package/src/hooks/use-email-rendering-result.ts +0 -58
  229. package/src/hooks/use-fragment-identifier.ts +0 -14
  230. package/src/hooks/use-hot-reload.ts +0 -31
  231. package/src/hooks/use-icon-animation.ts +0 -41
  232. package/src/hooks/use-iframe-color-scheme.ts +0 -35
  233. package/src/hooks/use-rendering-metadata.ts +0 -36
  234. package/src/utils/__snapshots__/get-email-component.spec.ts.snap +0 -3
  235. package/src/utils/caniemail/all-css-properties.ts +0 -358
  236. package/src/utils/caniemail/ast/__snapshots__/get-object-variables.spec.ts.snap +0 -74
  237. package/src/utils/caniemail/ast/__snapshots__/get-used-style-properties.spec.ts.snap +0 -24
  238. package/src/utils/caniemail/ast/get-object-variables.spec.ts +0 -19
  239. package/src/utils/caniemail/ast/get-object-variables.ts +0 -61
  240. package/src/utils/caniemail/ast/get-used-style-properties.spec.ts +0 -23
  241. package/src/utils/caniemail/ast/get-used-style-properties.ts +0 -91
  242. package/src/utils/caniemail/get-compatibility-stats-for-entry.ts +0 -118
  243. package/src/utils/caniemail/get-css-functions.ts +0 -25
  244. package/src/utils/caniemail/get-css-property-names.ts +0 -32
  245. package/src/utils/caniemail/get-css-property-with-value.ts +0 -14
  246. package/src/utils/caniemail/get-css-unit.ts +0 -3
  247. package/src/utils/caniemail/get-element-attributes.ts +0 -7
  248. package/src/utils/caniemail/get-element-names.ts +0 -20
  249. package/src/utils/caniemail/tailwind/generate-tailwind-rules.ts +0 -30
  250. package/src/utils/caniemail/tailwind/get-tailwind-config.ts +0 -187
  251. package/src/utils/caniemail/tailwind/get-tailwind-metadata.spec.ts +0 -25
  252. package/src/utils/caniemail/tailwind/get-tailwind-metadata.ts +0 -45
  253. package/src/utils/caniemail/tailwind/setup-tailwind-context.ts +0 -15
  254. package/src/utils/cn.ts +0 -6
  255. package/src/utils/constants.ts +0 -6
  256. package/src/utils/contains-email-template.spec.ts +0 -107
  257. package/src/utils/contains-email-template.ts +0 -33
  258. package/src/utils/copy-text-to-clipboard.ts +0 -7
  259. package/src/utils/get-email-component.spec.ts +0 -41
  260. package/src/utils/get-email-component.ts +0 -134
  261. package/src/utils/get-line-and-column-from-offset.spec.ts +0 -11
  262. package/src/utils/get-line-and-column-from-offset.ts +0 -11
  263. package/src/utils/improve-error-with-sourcemap.ts +0 -86
  264. package/src/utils/js-email-detection.spec.ts +0 -24
  265. package/src/utils/language-map.ts +0 -7
  266. package/src/utils/linting.ts +0 -60
  267. package/src/utils/load-stream.ts +0 -15
  268. package/src/utils/result.ts +0 -49
  269. package/src/utils/run-bundled-code.ts +0 -64
  270. package/src/utils/sanitize.ts +0 -6
  271. package/src/utils/static-node-modules-for-vm.ts +0 -93
  272. package/src/utils/testing/js-email-export-default.js +0 -17
  273. package/src/utils/testing/js-email-test.js +0 -18
  274. package/src/utils/testing/mdx-email-test.js +0 -128
  275. package/src/utils/testing/request-response-email.tsx +0 -9
  276. package/src/utils/types/as.ts +0 -26
  277. package/src/utils/types/email-template.ts +0 -8
  278. package/src/utils/types/error-object.ts +0 -11
  279. package/src/utils/unreachable.ts +0 -8
  280. package/tailwind-internals.d.ts +0 -133
  281. package/tailwind.config.ts +0 -99
  282. /package/src/{components/toolbar/results-table.tsx → utils/preview/hot-reloading/test/some-file.ts} +0 -0
package/src/app/env.ts DELETED
@@ -1,15 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
- /** ONLY ACCESSIBLE ON THE SERVER */
3
- export const emailsDirRelativePath = process.env.EMAILS_DIR_RELATIVE_PATH!;
4
-
5
- /** ONLY ACCESSIBLE ON THE SERVER */
6
- export const userProjectLocation = process.env.USER_PROJECT_LOCATION!;
7
-
8
- /** ONLY ACCESSIBLE ON THE SERVER */
9
- export const emailsDirectoryAbsolutePath =
10
- process.env.EMAILS_DIR_ABSOLUTE_PATH!;
11
-
12
- export const isBuilding = process.env.NEXT_PUBLIC_IS_BUILDING === 'true';
13
-
14
- export const isPreviewDevelopment =
15
- process.env.NEXT_PUBLIC_IS_PREVIEW_DEVELOPMENT === 'true';
Binary file
Binary file
package/src/app/fonts.ts DELETED
@@ -1,39 +0,0 @@
1
- import { Inter } from 'next/font/google';
2
- import Local from 'next/font/local';
3
-
4
- export const inter = Inter({
5
- subsets: ['latin'],
6
- variable: '--font-inter',
7
- display: 'swap',
8
- });
9
-
10
- export const sfMono = Local({
11
- src: [
12
- {
13
- path: './fonts/SFMono/SFMonoLight.otf',
14
- weight: '300',
15
- },
16
- {
17
- path: './fonts/SFMono/SFMonoRegular.otf',
18
- weight: '400',
19
- },
20
- {
21
- path: './fonts/SFMono/SFMonoMedium.otf',
22
- weight: '500',
23
- },
24
- {
25
- path: './fonts/SFMono/SFMonoSemibold.otf',
26
- weight: '600',
27
- },
28
- {
29
- path: './fonts/SFMono/SFMonoBold.otf',
30
- weight: '700',
31
- },
32
- {
33
- path: './fonts/SFMono/SFMonoHeavy.otf',
34
- weight: '800',
35
- },
36
- ],
37
- variable: '--font-sf-mono',
38
- display: 'swap',
39
- });
@@ -1,15 +0,0 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
4
-
5
- html {
6
- color-scheme: dark;
7
- }
8
-
9
- .popup-open iframe {
10
- pointer-events: none;
11
- }
12
-
13
- nav > div > div > .line {
14
- display: none;
15
- }
@@ -1,45 +0,0 @@
1
- import type { Metadata } from 'next';
2
- import './globals.css';
3
- import { EmailsProvider } from '../contexts/emails';
4
- import { getEmailsDirectoryMetadata } from '../utils/get-emails-directory-metadata';
5
- import { emailsDirectoryAbsolutePath } from './env';
6
- import { inter, sfMono } from './fonts';
7
-
8
- export const metadata: Metadata = {
9
- title: 'React Email',
10
- };
11
-
12
- export const dynamic = 'force-dynamic';
13
-
14
- const RootLayout = async ({ children }: { children: React.ReactNode }) => {
15
- const emailsDirectoryMetadata = await getEmailsDirectoryMetadata(
16
- emailsDirectoryAbsolutePath,
17
- );
18
-
19
- if (typeof emailsDirectoryMetadata === 'undefined') {
20
- throw new Error(
21
- `Could not find the emails directory specified under ${emailsDirectoryAbsolutePath}!`,
22
- );
23
- }
24
-
25
- return (
26
- <html
27
- className={`${inter.variable} ${sfMono.variable} font-sans`}
28
- lang="en"
29
- >
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
- <EmailsProvider
32
- initialEmailsDirectoryMetadata={emailsDirectoryMetadata}
33
- >
34
- {children}
35
- </EmailsProvider>
36
- <div
37
- aria-hidden
38
- className="pointer-events-none absolute inset-0 bg-gradient-to-t from-slate-3"
39
- />
40
- </body>
41
- </html>
42
- );
43
- };
44
-
45
- export default RootLayout;
package/src/app/logo.png DELETED
Binary file
package/src/app/page.tsx DELETED
@@ -1,46 +0,0 @@
1
- import path from 'node:path';
2
- import Image from 'next/image';
3
- import Link from 'next/link';
4
- import { Button, Heading, Text } from '../components';
5
- import CodeSnippet from '../components/code-snippet';
6
- import { Shell } from '../components/shell';
7
- import { emailsDirectoryAbsolutePath } from './env';
8
- import logo from './logo.png';
9
-
10
- const Home = () => {
11
- const baseEmailsDirectoryName = path.basename(emailsDirectoryAbsolutePath);
12
-
13
- return (
14
- <Shell>
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
- <Image
18
- alt="React Email Icon"
19
- className="mb-8"
20
- height={144}
21
- src={logo}
22
- style={{
23
- borderRadius: 34,
24
- boxShadow: '0 .625rem 12.5rem 1.25rem #2B7CA080',
25
- }}
26
- width={141}
27
- />
28
- <Heading as="h2" size="6" weight="medium">
29
- Welcome to React Email
30
- </Heading>
31
- <Text as="p">
32
- To start developing your emails, you can create a<br />
33
- <CodeSnippet>.jsx</CodeSnippet> or <CodeSnippet>.tsx</CodeSnippet>{' '}
34
- file under your <CodeSnippet>{baseEmailsDirectoryName}</CodeSnippet>{' '}
35
- folder.
36
- </Text>
37
- <Button asChild className="mt-3" size="3">
38
- <Link href="https://react.email/docs">Check the docs</Link>
39
- </Button>
40
- </div>
41
- </div>
42
- </Shell>
43
- );
44
- };
45
-
46
- export default Home;
@@ -1,157 +0,0 @@
1
- import path from 'node:path';
2
- import { redirect } from 'next/navigation';
3
- import { Suspense } from 'react';
4
- import {
5
- type CompatibilityCheckingResult,
6
- checkCompatibility,
7
- } from '../../../actions/email-validation/check-compatibility';
8
- import { getEmailPathFromSlug } from '../../../actions/get-email-path-from-slug';
9
- import { renderEmailByPath } from '../../../actions/render-email-by-path';
10
- import { Shell } from '../../../components/shell';
11
- import { Toolbar } from '../../../components/toolbar';
12
- import type { LintingRow } from '../../../components/toolbar/linter';
13
- import type { SpamCheckingResult } from '../../../components/toolbar/spam-assassin';
14
- import { PreviewProvider } from '../../../contexts/preview';
15
- import { getEmailsDirectoryMetadata } from '../../../utils/get-emails-directory-metadata';
16
- import { getLintingSources, loadLintingRowsFrom } from '../../../utils/linting';
17
- import { loadStream } from '../../../utils/load-stream';
18
- import { emailsDirectoryAbsolutePath, isBuilding } from '../../env';
19
- import Preview from './preview';
20
-
21
- export const dynamicParams = true;
22
-
23
- export const dynamic = 'force-dynamic';
24
-
25
- export interface PreviewParams {
26
- slug: string[];
27
- }
28
-
29
- const Page = async ({
30
- params: paramsPromise,
31
- }: {
32
- params: Promise<PreviewParams>;
33
- }) => {
34
- const params = await paramsPromise;
35
- // will come in here as segments of a relative path to the email
36
- // ex: ['authentication', 'verify-password.tsx']
37
- const slug = decodeURIComponent(params.slug.join('/'));
38
- const emailsDirMetadata = await getEmailsDirectoryMetadata(
39
- emailsDirectoryAbsolutePath,
40
- );
41
-
42
- if (typeof emailsDirMetadata === 'undefined') {
43
- throw new Error(
44
- `Could not find the emails directory specified under ${emailsDirectoryAbsolutePath}!
45
-
46
- This is most likely not an issue with the preview server. Maybe there was a typo on the "--dir" flag?`,
47
- );
48
- }
49
-
50
- let emailPath: string;
51
- try {
52
- emailPath = await getEmailPathFromSlug(slug);
53
- } catch (exception) {
54
- if (exception instanceof Error) {
55
- console.warn(exception.message);
56
- redirect('/');
57
- }
58
- throw exception;
59
- }
60
-
61
- const serverEmailRenderingResult = await renderEmailByPath(emailPath);
62
-
63
- let spamCheckingResult: SpamCheckingResult | undefined = undefined;
64
- let lintingRows: LintingRow[] | undefined = undefined;
65
- let compatibilityCheckingResults: CompatibilityCheckingResult[] | undefined =
66
- undefined;
67
-
68
- if (isBuilding) {
69
- if ('error' in serverEmailRenderingResult) {
70
- throw new Error(serverEmailRenderingResult.error.message, {
71
- cause: serverEmailRenderingResult.error,
72
- });
73
- }
74
- const lintingSources = getLintingSources(
75
- serverEmailRenderingResult.markup,
76
- '',
77
- );
78
- lintingRows = [];
79
- for await (const row of loadLintingRowsFrom(lintingSources)) {
80
- lintingRows.push(row);
81
- }
82
- lintingRows.sort((a, b) => {
83
- if (a.result.status === 'error' && b.result.status === 'warning') {
84
- return -1;
85
- }
86
-
87
- if (a.result.status === 'warning' && b.result.status === 'error') {
88
- return 1;
89
- }
90
-
91
- return 0;
92
- });
93
- compatibilityCheckingResults = [];
94
- for await (const result of loadStream(
95
- await checkCompatibility(
96
- serverEmailRenderingResult.reactMarkup,
97
- emailPath,
98
- ),
99
- )) {
100
- compatibilityCheckingResults.push(result);
101
- }
102
-
103
- const response = await fetch('https://react.email/api/check-spam', {
104
- method: 'POST',
105
- headers: { 'Content-Type': 'application/json' },
106
- body: JSON.stringify({
107
- html: serverEmailRenderingResult.markup,
108
- plainText: serverEmailRenderingResult.plainText,
109
- }),
110
- });
111
- const responseBody = (await response.json()) as
112
- | { error: string }
113
- | SpamCheckingResult;
114
- if ('error' in responseBody) {
115
- throw new Error(`Failed doing Spam Check. ${responseBody.error}`, {
116
- cause: responseBody,
117
- });
118
- }
119
-
120
- spamCheckingResult = responseBody;
121
- }
122
-
123
- return (
124
- <PreviewProvider
125
- emailSlug={slug}
126
- emailPath={emailPath}
127
- serverRenderingResult={serverEmailRenderingResult}
128
- >
129
- <Shell currentEmailOpenSlug={slug}>
130
- {/* This suspense is so that this page doesn't throw warnings */}
131
- {/* on the build of the preview server de-opting into */}
132
- {/* client-side rendering on build */}
133
- <Suspense>
134
- <Preview emailTitle={path.basename(emailPath)} />
135
-
136
- <Toolbar
137
- serverLintingRows={lintingRows}
138
- serverSpamCheckingResult={spamCheckingResult}
139
- serverCompatibilityResults={compatibilityCheckingResults}
140
- />
141
- </Suspense>
142
- </Shell>
143
- </PreviewProvider>
144
- );
145
- };
146
-
147
- export async function generateMetadata({
148
- params,
149
- }: {
150
- params: Promise<PreviewParams>;
151
- }) {
152
- const { slug } = await params;
153
-
154
- return { title: `${path.basename(slug.join('/'))} — React Email` };
155
- }
156
-
157
- export default Page;
@@ -1,234 +0,0 @@
1
- 'use client';
2
-
3
- import { usePathname, useRouter, useSearchParams } from 'next/navigation';
4
- import { use, useRef, useState } from 'react';
5
- import { flushSync } from 'react-dom';
6
- import { Toaster } from 'sonner';
7
- import { useDebouncedCallback } from 'use-debounce';
8
- import { Topbar } from '../../../components';
9
- import { CodeContainer } from '../../../components/code-container';
10
- import {
11
- makeIframeDocumentBubbleEvents,
12
- ResizableWrapper,
13
- } from '../../../components/resizable-wrapper';
14
- import { Send } from '../../../components/send';
15
- import { useToolbarState } from '../../../components/toolbar';
16
- import { Tooltip } from '../../../components/tooltip';
17
- import { ActiveViewToggleGroup } from '../../../components/topbar/active-view-toggle-group';
18
- import { ThemeToggleGroup } from '../../../components/topbar/theme-toggle-group';
19
- import { ViewSizeControls } from '../../../components/topbar/view-size-controls';
20
- import { PreviewContext } from '../../../contexts/preview';
21
- import { useClampedState } from '../../../hooks/use-clamped-state';
22
- import { useIframeColorScheme } from '../../../hooks/use-iframe-color-scheme';
23
- import { cn } from '../../../utils';
24
- import { RenderingError } from './rendering-error';
25
-
26
- interface PreviewProps extends React.ComponentProps<'div'> {
27
- emailTitle: string;
28
- }
29
-
30
- const Preview = ({ emailTitle, className, ...props }: PreviewProps) => {
31
- const { renderingResult, renderedEmailMetadata } = use(PreviewContext)!;
32
-
33
- const router = useRouter();
34
- const pathname = usePathname();
35
- const searchParams = useSearchParams();
36
-
37
- const activeTheme: 'dark' | 'light' =
38
- searchParams.get('theme') === 'dark' ? 'dark' : 'light';
39
- const activeView = searchParams.get('view') ?? 'preview';
40
- const activeLang = searchParams.get('lang') ?? 'jsx';
41
-
42
- const handleThemeChange = (theme: 'dark' | 'light') => {
43
- const params = new URLSearchParams(searchParams);
44
- params.set('theme', theme);
45
- router.push(`${pathname}?${params.toString()}`);
46
- };
47
-
48
- const handleViewChange = (view: string) => {
49
- const params = new URLSearchParams(searchParams);
50
- params.set('view', view);
51
- router.push(`${pathname}?${params.toString()}${location.hash}`);
52
- };
53
-
54
- const handleLangChange = (lang: string) => {
55
- const params = new URLSearchParams(searchParams);
56
- params.set('view', 'source');
57
- params.set('lang', lang);
58
- const isSameLang = searchParams.get('lang') === lang;
59
- router.push(
60
- `${pathname}?${params.toString()}${isSameLang ? location.hash : ''}`,
61
- );
62
- };
63
-
64
- const iframeRef = useRef<HTMLIFrameElement>(null);
65
- useIframeColorScheme(iframeRef, activeTheme);
66
-
67
- const hasRenderingMetadata = typeof renderedEmailMetadata !== 'undefined';
68
- const hasErrors = 'error' in renderingResult;
69
-
70
- const [maxWidth, setMaxWidth] = useState(Number.POSITIVE_INFINITY);
71
- const [maxHeight, setMaxHeight] = useState(Number.POSITIVE_INFINITY);
72
- const minWidth = 100;
73
- const minHeight = 100;
74
- const storedWidth = searchParams.get('width');
75
- const storedHeight = searchParams.get('height');
76
- const [width, setWidth] = useClampedState(
77
- storedWidth ? Number.parseInt(storedWidth) : 600,
78
- minWidth,
79
- maxWidth,
80
- );
81
- const [height, setHeight] = useClampedState(
82
- storedHeight ? Number.parseInt(storedHeight) : 1024,
83
- minHeight,
84
- maxHeight,
85
- );
86
-
87
- const handleSaveViewSize = useDebouncedCallback(() => {
88
- const params = new URLSearchParams(searchParams);
89
- params.set('width', width.toString());
90
- params.set('height', height.toString());
91
- router.push(`${pathname}?${params.toString()}${location.hash}`);
92
- }, 300);
93
-
94
- const { toggled: toolbarToggled } = useToolbarState();
95
-
96
- return (
97
- <>
98
- <Topbar emailTitle={emailTitle}>
99
- <ViewSizeControls
100
- setViewHeight={(height) => {
101
- setHeight(height);
102
- flushSync(() => {
103
- handleSaveViewSize();
104
- });
105
- }}
106
- setViewWidth={(width) => {
107
- setWidth(width);
108
- flushSync(() => {
109
- handleSaveViewSize();
110
- });
111
- }}
112
- viewHeight={height}
113
- viewWidth={width}
114
- />
115
- <ThemeToggleGroup
116
- active={activeTheme}
117
- onChange={(theme) => handleThemeChange(theme)}
118
- />
119
- <ActiveViewToggleGroup
120
- activeView={activeView}
121
- setActiveView={handleViewChange}
122
- />
123
- {hasRenderingMetadata ? (
124
- <div className="flex justify-end">
125
- <Send markup={renderedEmailMetadata.markup} />
126
- </div>
127
- ) : null}
128
- </Topbar>
129
-
130
- <div
131
- {...props}
132
- className={cn(
133
- 'h-[calc(100%-3.5rem-2.375rem)] will-change-[height] flex p-4 transition-[height] duration-300',
134
- activeView === 'preview' && 'bg-gray-200',
135
- toolbarToggled && 'h-[calc(100%-3.5rem-13rem)]',
136
- className,
137
- )}
138
- ref={(element) => {
139
- const observer = new ResizeObserver((entry) => {
140
- const [elementEntry] = entry;
141
- if (elementEntry) {
142
- setMaxWidth(elementEntry.contentRect.width);
143
- setMaxHeight(elementEntry.contentRect.height);
144
- }
145
- });
146
-
147
- if (element) {
148
- observer.observe(element);
149
- }
150
-
151
- return () => {
152
- observer.disconnect();
153
- };
154
- }}
155
- >
156
- {hasErrors ? <RenderingError error={renderingResult.error} /> : null}
157
-
158
- {hasRenderingMetadata ? (
159
- <>
160
- {activeView === 'preview' && (
161
- <ResizableWrapper
162
- minHeight={minHeight}
163
- minWidth={minWidth}
164
- maxHeight={maxHeight}
165
- maxWidth={maxWidth}
166
- height={height}
167
- onResizeEnd={() => {
168
- handleSaveViewSize();
169
- }}
170
- onResize={(value, direction) => {
171
- const isHorizontal =
172
- direction === 'east' || direction === 'west';
173
- if (isHorizontal) {
174
- setWidth(Math.round(value));
175
- } else {
176
- setHeight(Math.round(value));
177
- }
178
- }}
179
- width={width}
180
- >
181
- <iframe
182
- className="solid max-h-full rounded-lg bg-white"
183
- ref={(iframe) => {
184
- iframeRef.current = iframe;
185
- if (iframe) {
186
- return makeIframeDocumentBubbleEvents(iframe);
187
- }
188
- }}
189
- srcDoc={renderedEmailMetadata.markup}
190
- style={{
191
- width: `${width}px`,
192
- height: `${height}px`,
193
- }}
194
- title={emailTitle}
195
- />
196
- </ResizableWrapper>
197
- )}
198
-
199
- {activeView === 'source' && (
200
- <div className="h-full w-full">
201
- <div className="m-auto h-full flex max-w-3xl p-6">
202
- <Tooltip.Provider>
203
- <CodeContainer
204
- activeLang={activeLang}
205
- markups={[
206
- {
207
- language: 'jsx',
208
- content: renderedEmailMetadata.reactMarkup,
209
- },
210
- {
211
- language: 'markup',
212
- content: renderedEmailMetadata.markup,
213
- },
214
- {
215
- language: 'markdown',
216
- content: renderedEmailMetadata.plainText,
217
- },
218
- ]}
219
- setActiveLang={handleLangChange}
220
- />
221
- </Tooltip.Provider>
222
- </div>
223
- </div>
224
- )}
225
- </>
226
- ) : null}
227
-
228
- <Toaster />
229
- </div>
230
- </>
231
- );
232
- };
233
-
234
- export default Preview;
@@ -1,40 +0,0 @@
1
- 'use client';
2
- import type { ErrorObject } from '../../../utils/types/error-object';
3
-
4
- export const RenderingError = (props: { error: ErrorObject }) => {
5
- return (
6
- <>
7
- <div className="absolute inset-0 z-50 bg-black/80" />
8
- <div className="absolute left-[50%] top-[50%] z-50 grid min-h-[50vh] w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 rounded-t-sm border border-t-4 bg-white p-6 text-black shadow-lg duration-200 sm:rounded-lg md:max-w-[568px] lg:max-w-[968px]">
9
- <div className="flex min-w-0 max-w-full flex-col space-y-1.5">
10
- <h2 className="flex flex-shrink items-center gap-4 pb-2 text-lg font-semibold leading-none tracking-tight">
11
- <svg
12
- className="h-6 w-6 font-extrabold text-red-600"
13
- fill="none"
14
- height="24"
15
- stroke="currentColor"
16
- strokeLinecap="round"
17
- strokeLinejoin="round"
18
- strokeWidth="2"
19
- viewBox="0 0 24 24"
20
- width="24"
21
- xmlns="http://www.w3.org/2000/svg"
22
- >
23
- <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" />
24
- <path d="M12 9v4" />
25
- <path d="M12 17h.01" />
26
- </svg>
27
- {props.error.name}: {props.error.message}
28
- </h2>
29
- {props.error.stack ? (
30
- <div className="flex-grow scroll-px-4 overflow-x-auto rounded-lg bg-red-500 p-2 text-sm text-gray-100">
31
- <pre className="w-full min-w-0 font-mono leading-7">
32
- {props.error.stack}
33
- </pre>
34
- </div>
35
- ) : undefined}
36
- </div>
37
- </div>
38
- </>
39
- );
40
- };