@@ -12,13 +12,13 @@ import parserHtml from 'prettier/parser-html';
1212 * Return this list to the client.
1313 */
1414export type PreviewData = {
15- files : string [ ] | null ;
16- path : string | null ;
15+ files : string [ ] | null ;
16+ path : string | null ;
1717} ;
1818
1919type EmailListProps = {
20- path ?: string ;
21- root ?: string ;
20+ path ?: string ;
21+ root ?: string ;
2222} ;
2323
2424/**
@@ -43,48 +43,54 @@ type EmailListProps = {
4343 * ```
4444 */
4545export const emailList = ( {
46- path : emailPath = '/src/lib/emails' ,
47- root
46+ path : emailPath = '/src/lib/emails' ,
47+ root
4848} : EmailListProps = { } ) : PreviewData => {
49- // If root is not provided, try to use process.cwd()
50- if ( ! root ) {
51- try {
52- root = process . cwd ( ) ;
53- } catch ( err ) {
54- throw new Error (
55- 'Could not determine the root path of your project. Please pass in the root param manually using process.cwd() or an absolute path.\nOriginal error: ' +
56- err
57- ) ;
58- }
59- }
49+ // If root is not provided, try to use process.cwd()
50+ if ( ! root ) {
51+ try {
52+ root = process . cwd ( ) ;
53+ } catch ( err ) {
54+ throw new Error (
55+ 'Could not determine the root path of your project. Please pass in the root param manually using process.cwd() or an absolute path.\nOriginal error: ' +
56+ err
57+ ) ;
58+ }
59+ }
6060
61- const fullPath = path . join ( root , emailPath ) ;
61+ const fullPath = path . join ( root , emailPath ) ;
6262
63- // Check if directory exists
64- if ( ! fs . existsSync ( fullPath ) ) {
65- console . warn ( `Email directory not found: ${ fullPath } ` ) ;
66- return { files : null , path : emailPath } ;
67- }
63+ // Check if directory exists
64+ if ( ! fs . existsSync ( fullPath ) ) {
65+ console . warn ( `Email directory not found: ${ fullPath } ` ) ;
66+ return { files : null , path : emailPath } ;
67+ }
6868
69- const files = createEmailComponentList ( emailPath , getFiles ( fullPath ) ) ;
69+ // Use the absolute folder path as the root when creating the component list so
70+ // we can compute correct relative paths on all platforms.
71+ const files = createEmailComponentList ( fullPath , getFiles ( fullPath ) ) ;
7072
71- if ( ! files . length ) {
72- return { files : null , path : emailPath } ;
73- }
73+ if ( ! files . length ) {
74+ return { files : null , path : emailPath } ;
75+ }
7476
75- return { files, path : emailPath } ;
77+ return { files, path : emailPath } ;
7678} ;
7779
7880const getEmailComponent = async ( emailPath : string , file : string ) => {
79- const fileName = `${ file } .svelte` ;
80- try {
81- // Import the email component dynamically
82- return ( await import ( /* @vite -ignore */ path . join ( emailPath , fileName ) ) ) . default ;
83- } catch ( err ) {
84- throw new Error (
85- `Failed to import email component '${ fileName } '. Make sure the file exists and includes the <Head /> component.\nOriginal error: ${ err } `
86- ) ;
87- }
81+ const fileName = `${ file } .svelte` ;
82+ try {
83+ const normalizedEmailPath = emailPath . replace ( / \\ / g, '/' ) . replace ( / \/ + $ / , '' ) ;
84+ const normalizedFile = file . replace ( / \\ / g, '/' ) . replace ( / ^ \/ + / , '' ) ;
85+ const importPath = `${ normalizedEmailPath } /${ normalizedFile } .svelte` ;
86+
87+ // Import the email component dynamically
88+ return ( await import ( /* @vite -ignore */ importPath ) ) . default ;
89+ } catch ( err ) {
90+ throw new Error (
91+ `Failed to import email component '${ fileName } '. Make sure the file exists and includes the <Head /> component.\nOriginal error: ${ err } `
92+ ) ;
93+ }
8894} ;
8995
9096/**
@@ -100,65 +106,65 @@ const getEmailComponent = async (emailPath: string, file: string) => {
100106 * ```
101107 */
102108export const createEmail = {
103- 'create-email' : async ( event : RequestEvent ) => {
104- try {
105- const data = await event . request . formData ( ) ;
106- const file = data . get ( 'file' ) ;
107- const emailPath = data . get ( 'path' ) ;
109+ 'create-email' : async ( event : RequestEvent ) => {
110+ try {
111+ const data = await event . request . formData ( ) ;
112+ const file = data . get ( 'file' ) ;
113+ const emailPath = data . get ( 'path' ) ;
108114
109- if ( ! file || ! emailPath ) {
110- return {
111- status : 400 ,
112- body : { error : 'Missing file or path parameter' }
113- } ;
114- }
115+ if ( ! file || ! emailPath ) {
116+ return {
117+ status : 400 ,
118+ body : { error : 'Missing file or path parameter' }
119+ } ;
120+ }
115121
116- const emailComponent = await getEmailComponent ( emailPath as string , file as string ) ;
122+ const emailComponent = await getEmailComponent ( emailPath as string , file as string ) ;
117123
118- // Render the component to HTML
119- const { body } = render ( emailComponent ) ;
124+ // Render the component to HTML
125+ const { body } = render ( emailComponent ) ;
120126
121- // Remove all HTML comments from the body before formatting
122- const bodyWithoutComments = body . replace ( / < ! - - [ \s \S ] * ?- - > / g, '' ) ;
123- const formattedBody = await prettier . format ( bodyWithoutComments , {
124- parser : 'html' ,
125- plugins : [ parserHtml ]
126- } ) ;
127+ // Remove all HTML comments from the body before formatting
128+ const bodyWithoutComments = body . replace ( / < ! - - [ \s \S ] * ?- - > / g, '' ) ;
129+ const formattedBody = await prettier . format ( bodyWithoutComments , {
130+ parser : 'html' ,
131+ plugins : [ parserHtml ]
132+ } ) ;
127133
128- return {
129- body : formattedBody
130- } ;
131- } catch ( error ) {
132- console . error ( 'Error rendering email:' , error ) ;
133- return {
134- status : 500 ,
135- error : {
136- message : error instanceof Error ? error . message : 'Failed to render email'
137- }
138- } ;
139- }
140- }
134+ return {
135+ body : formattedBody
136+ } ;
137+ } catch ( error ) {
138+ console . error ( 'Error rendering email:' , error ) ;
139+ return {
140+ status : 500 ,
141+ error : {
142+ message : error instanceof Error ? error . message : 'Failed to render email'
143+ }
144+ } ;
145+ }
146+ }
141147} ;
142148
143149export declare const SendEmailFunction : (
144- { from, to, subject, html } : { from : string ; to : string ; subject : string ; html : string } ,
145- resendApiKey ?: string
150+ { from, to, subject, html } : { from : string ; to : string ; subject : string ; html : string } ,
151+ resendApiKey ?: string
146152) => Promise < { success : boolean ; error ?: any } > ;
147153
148154const defaultSendEmailFunction : typeof SendEmailFunction = async (
149- { from, to, subject, html } ,
150- resendApiKey
155+ { from, to, subject, html } ,
156+ resendApiKey
151157) => {
152- // stringify api key to comment out temp
153- const resend = new Resend ( resendApiKey ) ;
154- const email = { from, to, subject, html } ;
155- const resendReq = await resend . emails . send ( email ) ;
158+ // stringify api key to comment out temp
159+ const resend = new Resend ( resendApiKey ) ;
160+ const email = { from, to, subject, html } ;
161+ const resendReq = await resend . emails . send ( email ) ;
156162
157- if ( resendReq . error ) {
158- return { success : false , error : resendReq . error } ;
159- } else {
160- return { success : true , error : null } ;
161- }
163+ if ( resendReq . error ) {
164+ return { success : false , error : resendReq . error } ;
165+ } else {
166+ return { success : true , error : null } ;
167+ }
162168} ;
163169
164170/**
@@ -179,95 +185,95 @@ const defaultSendEmailFunction: typeof SendEmailFunction = async (
179185 * ```
180186 */
181187export const sendEmail = ( {
182- customSendEmailFunction,
183- resendApiKey
188+ customSendEmailFunction,
189+ resendApiKey
184190} : {
185- customSendEmailFunction ?: typeof SendEmailFunction ;
186- resendApiKey ?: string ;
191+ customSendEmailFunction ?: typeof SendEmailFunction ;
192+ resendApiKey ?: string ;
187193} = { } ) => {
188- return {
189- 'send-email' : async ( event : RequestEvent ) : Promise < { success : boolean ; error : any } > => {
190- const data = await event . request . formData ( ) ;
191- const emailPath = data . get ( 'path' ) ;
192- const file = data . get ( 'file' ) ;
194+ return {
195+ 'send-email' : async ( event : RequestEvent ) : Promise < { success : boolean ; error : any } > => {
196+ const data = await event . request . formData ( ) ;
197+ const emailPath = data . get ( 'path' ) ;
198+ const file = data . get ( 'file' ) ;
193199
194- if ( ! file || ! emailPath ) {
195- return {
196- success : false ,
197- error : { message : 'Missing file or path parameter' }
198- } ;
199- }
200+ if ( ! file || ! emailPath ) {
201+ return {
202+ success : false ,
203+ error : { message : 'Missing file or path parameter' }
204+ } ;
205+ }
200206
201- const emailComponent = await getEmailComponent ( emailPath as string , file as string ) ;
207+ const emailComponent = await getEmailComponent ( emailPath as string , file as string ) ;
202208
203- const email = {
204- from : 'svelte-email-tailwind <onboarding@resend.dev>' ,
205- to : `${ data . get ( 'to' ) } ` ,
206- subject : `${ data . get ( 'component' ) } ${ data . get ( 'note' ) ? '| ' + data . get ( 'note' ) : '' } ` ,
207- html : ( await render ( emailComponent ) ) . body
208- } ;
209+ const email = {
210+ from : 'svelte-email-tailwind <onboarding@resend.dev>' ,
211+ to : `${ data . get ( 'to' ) } ` ,
212+ subject : `${ data . get ( 'component' ) } ${ data . get ( 'note' ) ? '| ' + data . get ( 'note' ) : '' } ` ,
213+ html : ( await render ( emailComponent ) ) . body
214+ } ;
209215
210- let sent : { success : boolean ; error ?: any } = { success : false , error : null } ;
216+ let sent : { success : boolean ; error ?: any } = { success : false , error : null } ;
211217
212- if ( ! customSendEmailFunction && resendApiKey ) {
213- sent = await defaultSendEmailFunction ( email , resendApiKey ) ;
214- } else if ( customSendEmailFunction ) {
215- sent = await customSendEmailFunction ( email , resendApiKey ) ;
216- } else if ( ! customSendEmailFunction && ! resendApiKey ) {
217- const error = {
218- message :
219- 'Resend API key not configured. Please pass your API key to the sendEmail() function in your +page.server.ts file.'
220- } ;
221- return { success : false , error } ;
222- }
218+ if ( ! customSendEmailFunction && resendApiKey ) {
219+ sent = await defaultSendEmailFunction ( email , resendApiKey ) ;
220+ } else if ( customSendEmailFunction ) {
221+ sent = await customSendEmailFunction ( email , resendApiKey ) ;
222+ } else if ( ! customSendEmailFunction && ! resendApiKey ) {
223+ const error = {
224+ message :
225+ 'Resend API key not configured. Please pass your API key to the sendEmail() function in your +page.server.ts file.'
226+ } ;
227+ return { success : false , error } ;
228+ }
223229
224- if ( sent && sent . error ) {
225- console . log ( 'Error:' , sent . error ) ;
226- return { success : false , error : sent . error } ;
227- } else {
228- console . log ( 'Email was sent successfully.' ) ;
229- return { success : true , error : null } ;
230- }
231- }
232- } ;
230+ if ( sent && sent . error ) {
231+ console . log ( 'Error:' , sent . error ) ;
232+ return { success : false , error : sent . error } ;
233+ } else {
234+ console . log ( 'Email was sent successfully.' ) ;
235+ return { success : true , error : null } ;
236+ }
237+ }
238+ } ;
233239} ;
234240
235241// Recursive function to get files
236242function getFiles ( dir : string , files : string [ ] = [ ] ) {
237- // Get an array of all files and directories in the passed directory using fs.readdirSync
238- const fileList = fs . readdirSync ( dir ) ;
239- // Create the full path of the file/directory by concatenating the passed directory and file/directory name
240- for ( const file of fileList ) {
241- const name = path . join ( dir , file ) ;
242- // Check if the current file/directory is a directory using fs.statSync
243- if ( fs . statSync ( name ) . isDirectory ( ) ) {
244- // If it is a directory, recursively call the getFiles function with the directory path and the files array
245- getFiles ( name , files ) ;
246- } else {
247- // If it is a file, push the full path to the files array
248- files . push ( name ) ;
249- }
250- }
251- return files ;
243+ // Get an array of all files and directories in the passed directory using fs.readdirSync
244+ const fileList = fs . readdirSync ( dir ) ;
245+ // Create the full path of the file/directory by concatenating the passed directory and file/directory name
246+ for ( const file of fileList ) {
247+ const name = path . join ( dir , file ) ;
248+ // Check if the current file/directory is a directory using fs.statSync
249+ if ( fs . statSync ( name ) . isDirectory ( ) ) {
250+ // If it is a directory, recursively call the getFiles function with the directory path and the files array
251+ getFiles ( name , files ) ;
252+ } else {
253+ // If it is a file, push the full path to the files array
254+ files . push ( name ) ;
255+ }
256+ }
257+ return files ;
252258}
253259
254260/**
255261 * Creates an array of names from the record of svelte email component file paths
256262 */
257263function createEmailComponentList ( root : string , paths : string [ ] ) {
258- const emailComponentList : string [ ] = [ ] ;
264+ const emailComponentList : string [ ] = [ ] ;
259265
260- paths . forEach ( ( filePath ) => {
261- if ( filePath . includes ( `.svelte` ) ) {
262- const fileName = filePath . substring (
263- filePath . indexOf ( root ) + root . length + 1 ,
264- filePath . indexOf ( '.svelte' )
265- ) ;
266- emailComponentList . push ( fileName ) ;
267- }
268- } ) ;
266+ paths . forEach ( ( filePath ) => {
267+ if ( filePath . includes ( `.svelte` ) ) {
268+ // Compute the path relative to the provided root and normalize separators
269+ // so the list displays consistently across platforms (use forward slashes)
270+ const relative = path . relative ( root , filePath ) . replace ( / \\ / g , '/' ) ;
271+ const fileName = relative . replace ( / \. s v e l t e $ / i , '' ) ;
272+ emailComponentList . push ( fileName ) ;
273+ }
274+ } ) ;
269275
270- return emailComponentList ;
276+ return emailComponentList ;
271277}
272278
273279// Export the Preview component
0 commit comments