better-svelte-email 0.3.1 → 0.3.4

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.
@@ -1,14 +1,14 @@
1
1
  <script lang="ts">
2
- import { HighlightAuto } from 'svelte-highlight';
3
- import oneDark from 'svelte-highlight/styles/onedark';
4
2
  import type { PreviewData } from './index.js';
5
3
  import type { ActionResult } from '@sveltejs/kit';
6
- import { applyAction, deserialize } from '$app/forms';
4
+ import { deserialize } from '$app/forms';
5
+ import { codeToHtml } from 'shiki';
7
6
 
8
7
  let { emailList }: { emailList: PreviewData } = $props();
9
8
 
10
9
  let selectedEmail = $state<string | null>(null);
11
10
  let renderedHtml = $state<string>('');
11
+ let highlightedHtml = $state<string>('');
12
12
  let iframeContent = $state<string>('');
13
13
  let loading = $state(false);
14
14
  let error = $state<string | null>(null);
@@ -84,6 +84,10 @@
84
84
  if (result.type === 'success' && result.data?.body) {
85
85
  renderedHtml = result.data.body;
86
86
  iframeContent = withFontSans(result.data.body);
87
+ highlightedHtml = await codeToHtml(result.data.body, {
88
+ lang: 'html',
89
+ theme: 'github-dark-default'
90
+ });
87
91
  } else if (result.type === 'error') {
88
92
  error = result.error?.message || 'Failed to render email';
89
93
  }
@@ -130,7 +134,8 @@
130
134
  body: new URLSearchParams({
131
135
  to: recipientEmail,
132
136
  component: selectedEmail || 'Email Template',
133
- html: renderedHtml
137
+ path: emailList.path || '/src/lib/emails',
138
+ file: selectedEmail as string
134
139
  })
135
140
  });
136
141
 
@@ -155,10 +160,6 @@
155
160
  }
156
161
  </script>
157
162
 
158
- <svelte:head>
159
- {@html oneDark}
160
- </svelte:head>
161
-
162
163
  <div class="container">
163
164
  <div class="sidebar">
164
165
  <div class="sidebar-header">
@@ -247,7 +248,7 @@
247
248
  <details class="code-section">
248
249
  <summary class="code-summary"> View HTML Source </summary>
249
250
  <div class="code-content">
250
- <HighlightAuto code={renderedHtml} />
251
+ {@html highlightedHtml}
251
252
  </div>
252
253
  </details>
253
254
  {/if}
@@ -320,7 +321,7 @@
320
321
  height: 100vh;
321
322
  width: 100vw;
322
323
  max-width: none;
323
- background-color: #f9fafb;
324
+ background-color: #fafaf9;
324
325
  font-family:
325
326
  ui-sans-serif,
326
327
  system-ui,
@@ -344,7 +345,7 @@
344
345
  display: flex;
345
346
  flex-direction: column;
346
347
  overflow: hidden;
347
- border-right: 1px solid #e5e7eb;
348
+ border-right: 1px solid #e7e5e4;
348
349
  background-color: white;
349
350
  }
350
351
 
@@ -352,7 +353,7 @@
352
353
  .sidebar {
353
354
  max-height: 40vh;
354
355
  border-right: 0;
355
- border-bottom: 1px solid #e5e7eb;
356
+ border-bottom: 1px solid #e7e5e4;
356
357
  }
357
358
  }
358
359
 
@@ -361,7 +362,7 @@
361
362
  align-items: center;
362
363
  justify-content: space-between;
363
364
  gap: 0.5rem;
364
- border-bottom: 1px solid #e5e7eb;
365
+ border-bottom: 1px solid #e7e5e4;
365
366
  padding: 1.5rem;
366
367
  padding-bottom: 1rem;
367
368
  }
@@ -370,13 +371,13 @@
370
371
  margin: 0;
371
372
  font-size: 1.125rem;
372
373
  font-weight: 600;
373
- color: #111827;
374
+ color: #1c1917;
374
375
  }
375
376
 
376
377
  .badge {
377
378
  min-width: 1.5rem;
378
379
  border-radius: 9999px;
379
- background-color: #3b82f6;
380
+ background-color: #ea580c;
380
381
  padding: 0.125rem 0.5rem;
381
382
  text-align: center;
382
383
  font-size: 0.75rem;
@@ -387,7 +388,7 @@
387
388
  .empty-state {
388
389
  padding: 2rem 1rem;
389
390
  text-align: center;
390
- color: #6b7280;
391
+ color: #78716c;
391
392
  }
392
393
 
393
394
  .empty-state p {
@@ -397,12 +398,12 @@
397
398
 
398
399
  .empty-state p:last-child {
399
400
  font-size: 0.75rem;
400
- color: #9ca3af;
401
+ color: #a8a29e;
401
402
  }
402
403
 
403
404
  .empty-state code {
404
- border-radius: 0.25rem;
405
- background-color: #f3f4f6;
405
+ border-radius: 0.375rem;
406
+ background-color: #f5f5f4;
406
407
  padding: 0.125rem 0.375rem;
407
408
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
408
409
  font-size: 0.75rem;
@@ -426,23 +427,23 @@
426
427
  cursor: pointer;
427
428
  align-items: center;
428
429
  gap: 0.75rem;
429
- border-radius: 0.5rem;
430
+ border-radius: 0.75rem;
430
431
  border: 0;
431
432
  background-color: transparent;
432
433
  padding: 0.75rem;
433
434
  text-align: left;
434
435
  font-size: 0.875rem;
435
- color: #374151;
436
+ color: #57534e;
436
437
  transition: all 0.15s;
437
438
  }
438
439
 
439
440
  .email-button:hover {
440
- background-color: #f3f4f6;
441
+ background-color: #f5f5f4;
441
442
  }
442
443
 
443
444
  .email-button.active {
444
- background-color: #eff6ff;
445
- color: #1e3a8a;
445
+ background-color: #fff7ed;
446
+ color: #c2410c;
446
447
  font-weight: 500;
447
448
  }
448
449
 
@@ -472,7 +473,7 @@
472
473
  flex: 1;
473
474
  align-items: center;
474
475
  justify-content: center;
475
- background-color: #f9fafb;
476
+ background-color: #fafaf9;
476
477
  }
477
478
 
478
479
  .centered-content {
@@ -496,8 +497,8 @@
496
497
  height: 3rem;
497
498
  width: 3rem;
498
499
  border-radius: 9999px;
499
- border: 4px solid #e5e7eb;
500
- border-top-color: #3b82f6;
500
+ border: 4px solid #e7e5e4;
501
+ border-top-color: #ea580c;
501
502
  animation: spin 1s linear infinite;
502
503
  }
503
504
 
@@ -511,20 +512,20 @@
511
512
  margin-bottom: 0.5rem;
512
513
  font-size: 1.5rem;
513
514
  font-weight: 600;
514
- color: #111827;
515
+ color: #1c1917;
515
516
  }
516
517
 
517
518
  .text-gray {
518
519
  margin: 0;
519
- color: #6b7280;
520
+ color: #78716c;
520
521
  }
521
522
 
522
523
  .btn {
523
524
  margin-top: 1rem;
524
525
  cursor: pointer;
525
- border-radius: 0.375rem;
526
+ border-radius: 0.75rem;
526
527
  border: 0;
527
- background-color: #3b82f6;
528
+ background-color: #ea580c;
528
529
  padding: 0.5rem 1rem;
529
530
  font-weight: 500;
530
531
  color: white;
@@ -532,14 +533,14 @@
532
533
  }
533
534
 
534
535
  .btn:hover {
535
- background-color: #2563eb;
536
+ background-color: #c2410c;
536
537
  }
537
538
 
538
539
  .preview-header {
539
540
  display: flex;
540
541
  align-items: center;
541
542
  justify-content: space-between;
542
- border-bottom: 1px solid #e5e7eb;
543
+ border-bottom: 1px solid #e7e5e4;
543
544
  background-color: white;
544
545
  padding: 1rem 1.5rem;
545
546
  }
@@ -548,7 +549,7 @@
548
549
  margin: 0;
549
550
  font-size: 1.125rem;
550
551
  font-weight: 600;
551
- color: #111827;
552
+ color: #1c1917;
552
553
  }
553
554
 
554
555
  .button-group {
@@ -561,19 +562,19 @@
561
562
  cursor: pointer;
562
563
  align-items: center;
563
564
  gap: 0.375rem;
564
- border-radius: 0.375rem;
565
- border: 1px solid #d1d5db;
565
+ border-radius: 0.75rem;
566
+ border: 1px solid #d6d3d1;
566
567
  background-color: white;
567
568
  padding: 0.5rem 0.875rem;
568
569
  font-size: 0.875rem;
569
570
  font-weight: 500;
570
- color: #374151;
571
+ color: #57534e;
571
572
  transition: all 0.15s;
572
573
  }
573
574
 
574
575
  .btn-secondary:hover {
575
- border-color: #9ca3af;
576
- background-color: #f9fafb;
576
+ border-color: #a8a29e;
577
+ background-color: #fafaf9;
577
578
  }
578
579
 
579
580
  .btn-primary {
@@ -581,9 +582,9 @@
581
582
  cursor: pointer;
582
583
  align-items: center;
583
584
  gap: 0.375rem;
584
- border-radius: 0.375rem;
585
+ border-radius: 0.75rem;
585
586
  border: 0;
586
- background-color: #3b82f6;
587
+ background-color: #ea580c;
587
588
  padding: 0.5rem 0.875rem;
588
589
  font-size: 0.875rem;
589
590
  font-weight: 500;
@@ -592,7 +593,7 @@
592
593
  }
593
594
 
594
595
  .btn-primary:hover {
595
- background-color: #2563eb;
596
+ background-color: #c2410c;
596
597
  }
597
598
 
598
599
  .btn-icon {
@@ -602,40 +603,42 @@
602
603
  .preview-container {
603
604
  flex: 1;
604
605
  overflow: hidden;
605
- background-color: #f9fafb;
606
+ background-color: #fafaf9;
606
607
  padding: 1rem;
607
608
  }
608
609
 
609
610
  .preview-iframe {
610
611
  height: 100%;
611
612
  width: 100%;
612
- border-radius: 0.5rem;
613
- border: 1px solid #e5e7eb;
613
+ border-radius: 0.75rem;
614
+ border: 1px solid #e7e5e4;
614
615
  background-color: white;
615
616
  }
616
617
 
617
618
  .code-section {
618
619
  overflow: auto;
619
- border-top: 1px solid #e5e7eb;
620
- background-color: #f9fafb;
620
+ border-top: 1px solid #e7e5e4;
621
+ background-color: #fafaf9;
621
622
  }
622
623
 
623
624
  .code-summary {
624
625
  cursor: pointer;
625
626
  padding: 0.75rem 1.5rem;
626
627
  font-weight: 500;
627
- color: #374151;
628
+ color: #57534e;
628
629
  user-select: none;
629
630
  }
630
631
 
631
632
  .code-summary:hover {
632
- background-color: #f3f4f6;
633
+ background-color: #f5f5f4;
633
634
  }
634
635
 
635
636
  .code-content {
636
637
  height: 100%;
637
638
  overflow-y: scroll;
638
639
  font-size: 0.75rem;
640
+ padding: 1rem;
641
+ background-color: #0d1117;
639
642
  }
640
643
 
641
644
  .modal-overlay {
@@ -651,7 +654,7 @@
651
654
  .modal {
652
655
  width: 100%;
653
656
  max-width: 28rem;
654
- border-radius: 0.5rem;
657
+ border-radius: 0.75rem;
655
658
  background-color: white;
656
659
  padding: 1.5rem;
657
660
  box-shadow:
@@ -663,12 +666,12 @@
663
666
  margin-bottom: 1rem;
664
667
  font-size: 1.25rem;
665
668
  font-weight: 600;
666
- color: #111827;
669
+ color: #1c1917;
667
670
  }
668
671
 
669
672
  .success-message {
670
673
  margin-bottom: 1rem;
671
- border-radius: 0.375rem;
674
+ border-radius: 0.75rem;
672
675
  background-color: #f0fdf4;
673
676
  padding: 1rem;
674
677
  text-align: center;
@@ -694,32 +697,32 @@
694
697
  margin-bottom: 0.25rem;
695
698
  font-size: 0.875rem;
696
699
  font-weight: 500;
697
- color: #374151;
700
+ color: #57534e;
698
701
  }
699
702
 
700
703
  .form-input {
701
704
  width: 100%;
702
- border-radius: 0.375rem;
703
- border: 1px solid #d1d5db;
705
+ border-radius: 0.75rem;
706
+ border: 1px solid #d6d3d1;
704
707
  padding: 0.5rem 0.75rem;
705
708
  font-size: 0.875rem;
706
709
  }
707
710
 
708
711
  .form-input:focus {
709
712
  outline: none;
710
- border-color: #3b82f6;
711
- box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.5);
713
+ border-color: #ea580c;
714
+ box-shadow: 0 0 0 2px rgba(234, 88, 12, 0.2);
712
715
  }
713
716
 
714
717
  .form-help {
715
718
  margin-top: 0.25rem;
716
719
  font-size: 0.75rem;
717
- color: #6b7280;
720
+ color: #78716c;
718
721
  }
719
722
 
720
723
  .error-message {
721
724
  margin-bottom: 1rem;
722
- border-radius: 0.375rem;
725
+ border-radius: 0.75rem;
723
726
  background-color: #fef2f2;
724
727
  padding: 0.75rem;
725
728
  font-size: 0.875rem;
@@ -734,18 +737,18 @@
734
737
 
735
738
  .btn-cancel {
736
739
  cursor: pointer;
737
- border-radius: 0.375rem;
738
- border: 1px solid #d1d5db;
740
+ border-radius: 0.75rem;
741
+ border: 1px solid #d6d3d1;
739
742
  background-color: white;
740
743
  padding: 0.5rem 1rem;
741
744
  font-size: 0.875rem;
742
745
  font-weight: 500;
743
- color: #374151;
746
+ color: #57534e;
744
747
  transition: all 0.15s;
745
748
  }
746
749
 
747
750
  .btn-cancel:hover {
748
- background-color: #f9fafb;
751
+ background-color: #fafaf9;
749
752
  }
750
753
 
751
754
  .btn-cancel:disabled {
@@ -755,9 +758,9 @@
755
758
 
756
759
  .btn-submit {
757
760
  cursor: pointer;
758
- border-radius: 0.375rem;
761
+ border-radius: 0.75rem;
759
762
  border: 0;
760
- background-color: #3b82f6;
763
+ background-color: #ea580c;
761
764
  padding: 0.5rem 1rem;
762
765
  font-size: 0.875rem;
763
766
  font-weight: 500;
@@ -766,7 +769,7 @@
766
769
  }
767
770
 
768
771
  .btn-submit:hover {
769
- background-color: #2563eb;
772
+ background-color: #c2410c;
770
773
  }
771
774
 
772
775
  .btn-submit:disabled {
@@ -47,6 +47,15 @@ export const emailList = ({ path: emailPath = '/src/lib/emails', root } = {}) =>
47
47
  }
48
48
  return { files, path: emailPath };
49
49
  };
50
+ const getEmailComponent = async (emailPath, file) => {
51
+ try {
52
+ // Import the email component dynamically
53
+ return (await import(/* @vite-ignore */ `${emailPath}${path.sep}${file}.svelte`)).default;
54
+ }
55
+ catch {
56
+ throw new Error(`Failed to import email component '${file}'. Make sure the file exists and includes the <Head /> component.`);
57
+ }
58
+ };
50
59
  /**
51
60
  * SvelteKit form action to render an email component.
52
61
  * Use this with the Preview component to render email templates on demand.
@@ -71,16 +80,7 @@ export const createEmail = {
71
80
  body: { error: 'Missing file or path parameter' }
72
81
  };
73
82
  }
74
- const getEmailComponent = async () => {
75
- try {
76
- // Import the email component dynamically
77
- return (await import(/* @vite-ignore */ `${emailPath}/${file}.svelte`)).default;
78
- }
79
- catch {
80
- throw new Error(`Failed to import email component '${file}'. Make sure the file exists and includes the <Head /> component.`);
81
- }
82
- };
83
- const emailComponent = await getEmailComponent();
83
+ const emailComponent = await getEmailComponent(emailPath, file);
84
84
  // Render the component to HTML
85
85
  const { body } = render(emailComponent);
86
86
  // Remove all HTML comments from the body before formatting
@@ -137,11 +137,20 @@ export const sendEmail = ({ customSendEmailFunction, resendApiKey } = {}) => {
137
137
  return {
138
138
  'send-email': async (event) => {
139
139
  const data = await event.request.formData();
140
+ const emailPath = data.get('path');
141
+ const file = data.get('file');
142
+ if (!file || !emailPath) {
143
+ return {
144
+ success: false,
145
+ error: { message: 'Missing file or path parameter' }
146
+ };
147
+ }
148
+ const emailComponent = await getEmailComponent(emailPath, file);
140
149
  const email = {
141
150
  from: 'svelte-email-tailwind <onboarding@resend.dev>',
142
151
  to: `${data.get('to')}`,
143
152
  subject: `${data.get('component')} ${data.get('note') ? '| ' + data.get('note') : ''}`,
144
- html: `${data.get('html')}`
153
+ html: (await render(emailComponent)).body
145
154
  };
146
155
  let sent = { success: false, error: null };
147
156
  if (!customSendEmailFunction && resendApiKey) {
@@ -173,7 +182,7 @@ function getFiles(dir, files = []) {
173
182
  const fileList = fs.readdirSync(dir);
174
183
  // Create the full path of the file/directory by concatenating the passed directory and file/directory name
175
184
  for (const file of fileList) {
176
- const name = `${dir}/${file}`;
185
+ const name = `${dir}${path.sep}${file}`;
177
186
  // Check if the current file/directory is a directory using fs.statSync
178
187
  if (fs.statSync(name).isDirectory()) {
179
188
  // If it is a directory, recursively call the getFiles function with the directory path and the files array
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "better-svelte-email",
3
- "version": "0.3.1",
3
+ "version": "0.3.4",
4
4
  "author": "Konixy",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/Konixy/better-svelte-email.git"
8
8
  },
9
+ "homepage": "https://better-svelte-email.konixy.fr",
9
10
  "peerDependencies": {
10
- "svelte": "^5.14.3",
11
- "@sveltejs/kit": "^2.0.0"
11
+ "svelte": ">5.14.3",
12
+ "@sveltejs/kit": ">=2"
12
13
  },
13
14
  "peerDependenciesMeta": {
14
15
  "@sveltejs/kit": {
@@ -17,42 +18,42 @@
17
18
  },
18
19
  "dependencies": {
19
20
  "html-to-text": "^9.0.5",
20
- "magic-string": "^0.30.19",
21
- "svelte-highlight": "^7.8.4",
21
+ "magic-string": "^0.30.21",
22
22
  "tw-to-css": "^0.0.12"
23
23
  },
24
24
  "optionalDependencies": {
25
25
  "prettier": "^3.6.2",
26
- "resend": "^6.1.2"
26
+ "resend": "^6.2.2",
27
+ "shiki": "^3.14.0"
27
28
  },
28
29
  "devDependencies": {
29
30
  "@eslint/compat": "^1.4.0",
30
- "@eslint/js": "^9.37.0",
31
- "@sveltejs/adapter-auto": "^6.1.1",
32
- "@sveltejs/adapter-vercel": "^5.10.3",
33
- "@sveltejs/kit": "^2.43.8",
31
+ "@eslint/js": "^9.38.0",
32
+ "@sveltejs/adapter-vercel": "^6.0.0",
33
+ "@sveltejs/kit": "^2.47.3",
34
34
  "@sveltejs/package": "^2.5.4",
35
35
  "@sveltejs/vite-plugin-svelte": "^6.2.1",
36
- "@tailwindcss/vite": "^4.1.14",
36
+ "@tailwindcss/vite": "^4.1.16",
37
37
  "@types/html-to-text": "^9.0.4",
38
- "@types/node": "^24",
39
- "@vitest/browser": "^3.2.4",
40
- "eslint": "^9.37.0",
38
+ "@types/node": "^24.9.1",
39
+ "eslint": "^9.38.0",
41
40
  "eslint-config-prettier": "^10.1.8",
42
- "eslint-plugin-svelte": "^3.12.4",
41
+ "eslint-plugin-svelte": "^3.12.5",
43
42
  "globals": "^16.4.0",
44
- "playwright": "^1.55.1",
43
+ "gsap": "^3.13.0",
44
+ "mdsvex": "^0.12.6",
45
45
  "prettier-plugin-svelte": "^3.4.0",
46
- "prettier-plugin-tailwindcss": "^0.6.14",
47
- "publint": "^0.3.13",
48
- "svelte": "^5.39.8",
49
- "svelte-check": "^4.3.2",
50
- "tailwindcss": "^4.1.14",
46
+ "prettier-plugin-tailwindcss": "^0.7.1",
47
+ "publint": "^0.3.15",
48
+ "rehype-autolink-headings": "^7.1.0",
49
+ "rehype-slug": "^6.0.0",
50
+ "svelte": "5.43.2",
51
+ "svelte-check": "^4.3.3",
52
+ "tailwindcss": "^4.1.16",
51
53
  "typescript": "^5.9.3",
52
- "typescript-eslint": "^8.45.0",
53
- "vite": "^7.1.9",
54
- "vitest": "^3.2.4",
55
- "vitest-browser-svelte": "^1.1.0"
54
+ "typescript-eslint": "^8.46.2",
55
+ "vite": "^7.1.12",
56
+ "vitest": "^4.0.3"
56
57
  },
57
58
  "exports": {
58
59
  ".": {
@@ -90,7 +91,8 @@
90
91
  "files": [
91
92
  "dist",
92
93
  "!dist/**/*.test.*",
93
- "!dist/**/*.spec.*"
94
+ "!dist/**/*.spec.*",
95
+ "!dist/emails"
94
96
  ],
95
97
  "keywords": [
96
98
  "svelte",