better-svelte-email 0.3.0 → 0.3.3

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}
@@ -319,7 +320,8 @@
319
320
  grid-template-columns: 280px 1fr;
320
321
  height: 100vh;
321
322
  width: 100vw;
322
- background-color: #f9fafb;
323
+ max-width: none;
324
+ background-color: #fafaf9;
323
325
  font-family:
324
326
  ui-sans-serif,
325
327
  system-ui,
@@ -343,7 +345,7 @@
343
345
  display: flex;
344
346
  flex-direction: column;
345
347
  overflow: hidden;
346
- border-right: 1px solid #e5e7eb;
348
+ border-right: 1px solid #e7e5e4;
347
349
  background-color: white;
348
350
  }
349
351
 
@@ -351,7 +353,7 @@
351
353
  .sidebar {
352
354
  max-height: 40vh;
353
355
  border-right: 0;
354
- border-bottom: 1px solid #e5e7eb;
356
+ border-bottom: 1px solid #e7e5e4;
355
357
  }
356
358
  }
357
359
 
@@ -360,7 +362,7 @@
360
362
  align-items: center;
361
363
  justify-content: space-between;
362
364
  gap: 0.5rem;
363
- border-bottom: 1px solid #e5e7eb;
365
+ border-bottom: 1px solid #e7e5e4;
364
366
  padding: 1.5rem;
365
367
  padding-bottom: 1rem;
366
368
  }
@@ -369,13 +371,13 @@
369
371
  margin: 0;
370
372
  font-size: 1.125rem;
371
373
  font-weight: 600;
372
- color: #111827;
374
+ color: #1c1917;
373
375
  }
374
376
 
375
377
  .badge {
376
378
  min-width: 1.5rem;
377
379
  border-radius: 9999px;
378
- background-color: #3b82f6;
380
+ background-color: #ea580c;
379
381
  padding: 0.125rem 0.5rem;
380
382
  text-align: center;
381
383
  font-size: 0.75rem;
@@ -386,7 +388,7 @@
386
388
  .empty-state {
387
389
  padding: 2rem 1rem;
388
390
  text-align: center;
389
- color: #6b7280;
391
+ color: #78716c;
390
392
  }
391
393
 
392
394
  .empty-state p {
@@ -396,12 +398,12 @@
396
398
 
397
399
  .empty-state p:last-child {
398
400
  font-size: 0.75rem;
399
- color: #9ca3af;
401
+ color: #a8a29e;
400
402
  }
401
403
 
402
404
  .empty-state code {
403
- border-radius: 0.25rem;
404
- background-color: #f3f4f6;
405
+ border-radius: 0.375rem;
406
+ background-color: #f5f5f4;
405
407
  padding: 0.125rem 0.375rem;
406
408
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
407
409
  font-size: 0.75rem;
@@ -425,23 +427,23 @@
425
427
  cursor: pointer;
426
428
  align-items: center;
427
429
  gap: 0.75rem;
428
- border-radius: 0.5rem;
430
+ border-radius: 0.75rem;
429
431
  border: 0;
430
432
  background-color: transparent;
431
433
  padding: 0.75rem;
432
434
  text-align: left;
433
435
  font-size: 0.875rem;
434
- color: #374151;
436
+ color: #57534e;
435
437
  transition: all 0.15s;
436
438
  }
437
439
 
438
440
  .email-button:hover {
439
- background-color: #f3f4f6;
441
+ background-color: #f5f5f4;
440
442
  }
441
443
 
442
444
  .email-button.active {
443
- background-color: #eff6ff;
444
- color: #1e3a8a;
445
+ background-color: #fff7ed;
446
+ color: #c2410c;
445
447
  font-weight: 500;
446
448
  }
447
449
 
@@ -462,6 +464,8 @@
462
464
  flex-direction: column;
463
465
  overflow: hidden;
464
466
  background-color: white;
467
+ width: 100%;
468
+ min-width: 0;
465
469
  }
466
470
 
467
471
  .centered-state {
@@ -469,7 +473,7 @@
469
473
  flex: 1;
470
474
  align-items: center;
471
475
  justify-content: center;
472
- background-color: #f9fafb;
476
+ background-color: #fafaf9;
473
477
  }
474
478
 
475
479
  .centered-content {
@@ -493,8 +497,8 @@
493
497
  height: 3rem;
494
498
  width: 3rem;
495
499
  border-radius: 9999px;
496
- border: 4px solid #e5e7eb;
497
- border-top-color: #3b82f6;
500
+ border: 4px solid #e7e5e4;
501
+ border-top-color: #ea580c;
498
502
  animation: spin 1s linear infinite;
499
503
  }
500
504
 
@@ -508,20 +512,20 @@
508
512
  margin-bottom: 0.5rem;
509
513
  font-size: 1.5rem;
510
514
  font-weight: 600;
511
- color: #111827;
515
+ color: #1c1917;
512
516
  }
513
517
 
514
518
  .text-gray {
515
519
  margin: 0;
516
- color: #6b7280;
520
+ color: #78716c;
517
521
  }
518
522
 
519
523
  .btn {
520
524
  margin-top: 1rem;
521
525
  cursor: pointer;
522
- border-radius: 0.375rem;
526
+ border-radius: 0.75rem;
523
527
  border: 0;
524
- background-color: #3b82f6;
528
+ background-color: #ea580c;
525
529
  padding: 0.5rem 1rem;
526
530
  font-weight: 500;
527
531
  color: white;
@@ -529,14 +533,14 @@
529
533
  }
530
534
 
531
535
  .btn:hover {
532
- background-color: #2563eb;
536
+ background-color: #c2410c;
533
537
  }
534
538
 
535
539
  .preview-header {
536
540
  display: flex;
537
541
  align-items: center;
538
542
  justify-content: space-between;
539
- border-bottom: 1px solid #e5e7eb;
543
+ border-bottom: 1px solid #e7e5e4;
540
544
  background-color: white;
541
545
  padding: 1rem 1.5rem;
542
546
  }
@@ -545,7 +549,7 @@
545
549
  margin: 0;
546
550
  font-size: 1.125rem;
547
551
  font-weight: 600;
548
- color: #111827;
552
+ color: #1c1917;
549
553
  }
550
554
 
551
555
  .button-group {
@@ -558,19 +562,19 @@
558
562
  cursor: pointer;
559
563
  align-items: center;
560
564
  gap: 0.375rem;
561
- border-radius: 0.375rem;
562
- border: 1px solid #d1d5db;
565
+ border-radius: 0.75rem;
566
+ border: 1px solid #d6d3d1;
563
567
  background-color: white;
564
568
  padding: 0.5rem 0.875rem;
565
569
  font-size: 0.875rem;
566
570
  font-weight: 500;
567
- color: #374151;
571
+ color: #57534e;
568
572
  transition: all 0.15s;
569
573
  }
570
574
 
571
575
  .btn-secondary:hover {
572
- border-color: #9ca3af;
573
- background-color: #f9fafb;
576
+ border-color: #a8a29e;
577
+ background-color: #fafaf9;
574
578
  }
575
579
 
576
580
  .btn-primary {
@@ -578,9 +582,9 @@
578
582
  cursor: pointer;
579
583
  align-items: center;
580
584
  gap: 0.375rem;
581
- border-radius: 0.375rem;
585
+ border-radius: 0.75rem;
582
586
  border: 0;
583
- background-color: #3b82f6;
587
+ background-color: #ea580c;
584
588
  padding: 0.5rem 0.875rem;
585
589
  font-size: 0.875rem;
586
590
  font-weight: 500;
@@ -589,7 +593,7 @@
589
593
  }
590
594
 
591
595
  .btn-primary:hover {
592
- background-color: #2563eb;
596
+ background-color: #c2410c;
593
597
  }
594
598
 
595
599
  .btn-icon {
@@ -599,40 +603,42 @@
599
603
  .preview-container {
600
604
  flex: 1;
601
605
  overflow: hidden;
602
- background-color: #f9fafb;
606
+ background-color: #fafaf9;
603
607
  padding: 1rem;
604
608
  }
605
609
 
606
610
  .preview-iframe {
607
611
  height: 100%;
608
612
  width: 100%;
609
- border-radius: 0.5rem;
610
- border: 1px solid #e5e7eb;
613
+ border-radius: 0.75rem;
614
+ border: 1px solid #e7e5e4;
611
615
  background-color: white;
612
616
  }
613
617
 
614
618
  .code-section {
615
619
  overflow: auto;
616
- border-top: 1px solid #e5e7eb;
617
- background-color: #f9fafb;
620
+ border-top: 1px solid #e7e5e4;
621
+ background-color: #fafaf9;
618
622
  }
619
623
 
620
624
  .code-summary {
621
625
  cursor: pointer;
622
626
  padding: 0.75rem 1.5rem;
623
627
  font-weight: 500;
624
- color: #374151;
628
+ color: #57534e;
625
629
  user-select: none;
626
630
  }
627
631
 
628
632
  .code-summary:hover {
629
- background-color: #f3f4f6;
633
+ background-color: #f5f5f4;
630
634
  }
631
635
 
632
636
  .code-content {
633
637
  height: 100%;
634
638
  overflow-y: scroll;
635
639
  font-size: 0.75rem;
640
+ padding: 1rem;
641
+ background-color: #0d1117;
636
642
  }
637
643
 
638
644
  .modal-overlay {
@@ -648,7 +654,7 @@
648
654
  .modal {
649
655
  width: 100%;
650
656
  max-width: 28rem;
651
- border-radius: 0.5rem;
657
+ border-radius: 0.75rem;
652
658
  background-color: white;
653
659
  padding: 1.5rem;
654
660
  box-shadow:
@@ -660,12 +666,12 @@
660
666
  margin-bottom: 1rem;
661
667
  font-size: 1.25rem;
662
668
  font-weight: 600;
663
- color: #111827;
669
+ color: #1c1917;
664
670
  }
665
671
 
666
672
  .success-message {
667
673
  margin-bottom: 1rem;
668
- border-radius: 0.375rem;
674
+ border-radius: 0.75rem;
669
675
  background-color: #f0fdf4;
670
676
  padding: 1rem;
671
677
  text-align: center;
@@ -691,32 +697,32 @@
691
697
  margin-bottom: 0.25rem;
692
698
  font-size: 0.875rem;
693
699
  font-weight: 500;
694
- color: #374151;
700
+ color: #57534e;
695
701
  }
696
702
 
697
703
  .form-input {
698
704
  width: 100%;
699
- border-radius: 0.375rem;
700
- border: 1px solid #d1d5db;
705
+ border-radius: 0.75rem;
706
+ border: 1px solid #d6d3d1;
701
707
  padding: 0.5rem 0.75rem;
702
708
  font-size: 0.875rem;
703
709
  }
704
710
 
705
711
  .form-input:focus {
706
712
  outline: none;
707
- border-color: #3b82f6;
708
- 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);
709
715
  }
710
716
 
711
717
  .form-help {
712
718
  margin-top: 0.25rem;
713
719
  font-size: 0.75rem;
714
- color: #6b7280;
720
+ color: #78716c;
715
721
  }
716
722
 
717
723
  .error-message {
718
724
  margin-bottom: 1rem;
719
- border-radius: 0.375rem;
725
+ border-radius: 0.75rem;
720
726
  background-color: #fef2f2;
721
727
  padding: 0.75rem;
722
728
  font-size: 0.875rem;
@@ -731,18 +737,18 @@
731
737
 
732
738
  .btn-cancel {
733
739
  cursor: pointer;
734
- border-radius: 0.375rem;
735
- border: 1px solid #d1d5db;
740
+ border-radius: 0.75rem;
741
+ border: 1px solid #d6d3d1;
736
742
  background-color: white;
737
743
  padding: 0.5rem 1rem;
738
744
  font-size: 0.875rem;
739
745
  font-weight: 500;
740
- color: #374151;
746
+ color: #57534e;
741
747
  transition: all 0.15s;
742
748
  }
743
749
 
744
750
  .btn-cancel:hover {
745
- background-color: #f9fafb;
751
+ background-color: #fafaf9;
746
752
  }
747
753
 
748
754
  .btn-cancel:disabled {
@@ -752,9 +758,9 @@
752
758
 
753
759
  .btn-submit {
754
760
  cursor: pointer;
755
- border-radius: 0.375rem;
761
+ border-radius: 0.75rem;
756
762
  border: 0;
757
- background-color: #3b82f6;
763
+ background-color: #ea580c;
758
764
  padding: 0.5rem 1rem;
759
765
  font-size: 0.875rem;
760
766
  font-weight: 500;
@@ -763,7 +769,7 @@
763
769
  }
764
770
 
765
771
  .btn-submit:hover {
766
- background-color: #2563eb;
772
+ background-color: #c2410c;
767
773
  }
768
774
 
769
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}/${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) {
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "better-svelte-email",
3
- "version": "0.3.0",
3
+ "version": "0.3.3",
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
9
  "peerDependencies": {
10
- "svelte": "^5.14.3",
11
- "@sveltejs/kit": "^2.0.0"
10
+ "svelte": ">5.14.3",
11
+ "@sveltejs/kit": ">=2"
12
12
  },
13
13
  "peerDependenciesMeta": {
14
14
  "@sveltejs/kit": {
@@ -17,42 +17,42 @@
17
17
  },
18
18
  "dependencies": {
19
19
  "html-to-text": "^9.0.5",
20
- "magic-string": "^0.30.19",
21
- "svelte-highlight": "^7.8.4",
20
+ "magic-string": "^0.30.21",
22
21
  "tw-to-css": "^0.0.12"
23
22
  },
24
23
  "optionalDependencies": {
25
24
  "prettier": "^3.6.2",
26
- "resend": "^6.1.2"
25
+ "resend": "^6.2.2",
26
+ "shiki": "^3.14.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@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",
30
+ "@eslint/js": "^9.38.0",
31
+ "@sveltejs/adapter-vercel": "^6.0.0",
32
+ "@sveltejs/kit": "^2.47.3",
34
33
  "@sveltejs/package": "^2.5.4",
35
34
  "@sveltejs/vite-plugin-svelte": "^6.2.1",
36
- "@tailwindcss/vite": "^4.1.14",
35
+ "@tailwindcss/vite": "^4.1.16",
37
36
  "@types/html-to-text": "^9.0.4",
38
- "@types/node": "^24",
39
- "@vitest/browser": "^3.2.4",
40
- "eslint": "^9.37.0",
37
+ "@types/node": "^24.9.1",
38
+ "eslint": "^9.38.0",
41
39
  "eslint-config-prettier": "^10.1.8",
42
- "eslint-plugin-svelte": "^3.12.4",
40
+ "eslint-plugin-svelte": "^3.12.5",
43
41
  "globals": "^16.4.0",
44
- "playwright": "^1.55.1",
42
+ "gsap": "^3.13.0",
43
+ "mdsvex": "^0.12.6",
45
44
  "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",
45
+ "prettier-plugin-tailwindcss": "^0.7.1",
46
+ "publint": "^0.3.15",
47
+ "rehype-autolink-headings": "^7.1.0",
48
+ "rehype-slug": "^6.0.0",
49
+ "svelte": "5.42.2",
50
+ "svelte-check": "^4.3.3",
51
+ "tailwindcss": "^4.1.16",
51
52
  "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"
53
+ "typescript-eslint": "^8.46.2",
54
+ "vite": "^7.1.12",
55
+ "vitest": "^4.0.3"
56
56
  },
57
57
  "exports": {
58
58
  ".": {
@@ -90,7 +90,8 @@
90
90
  "files": [
91
91
  "dist",
92
92
  "!dist/**/*.test.*",
93
- "!dist/**/*.spec.*"
93
+ "!dist/**/*.spec.*",
94
+ "!dist/emails"
94
95
  ],
95
96
  "keywords": [
96
97
  "svelte",