better-svelte-email 1.0.0-beta.0 → 1.0.0-beta.2
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/README.md +1 -0
- package/dist/preview/EmailPreview.svelte +539 -196
- package/dist/preview/EmailPreview.svelte.d.ts +11 -1
- package/dist/preview/EmailTreeNode.svelte +255 -0
- package/dist/preview/EmailTreeNode.svelte.d.ts +10 -0
- package/dist/preview/Favicon.svelte +106 -0
- package/dist/preview/Favicon.svelte.d.ts +5 -0
- package/dist/preview/email-tree.d.ts +13 -0
- package/dist/preview/email-tree.js +61 -0
- package/dist/preview/index.d.ts +16 -3
- package/dist/preview/index.js +29 -4
- package/dist/preview/theme.css +42 -0
- package/package.json +24 -20
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import type { PreviewData } from './index.js';
|
|
2
|
+
import './theme.css';
|
|
3
|
+
type Page = {
|
|
4
|
+
params: {
|
|
5
|
+
email?: string | null;
|
|
6
|
+
};
|
|
7
|
+
data: {
|
|
8
|
+
emails?: PreviewData;
|
|
9
|
+
};
|
|
10
|
+
url: URL;
|
|
11
|
+
};
|
|
2
12
|
type $$ComponentProps = {
|
|
3
|
-
|
|
13
|
+
page: Page;
|
|
4
14
|
};
|
|
5
15
|
declare const EmailPreview: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
16
|
type EmailPreview = ReturnType<typeof EmailPreview>;
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import EmailTreeNodeComponent from './EmailTreeNode.svelte';
|
|
3
|
+
import type { DirectoryEntry, EmailTreeEntry } from './email-tree.js';
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
node: EmailTreeEntry;
|
|
7
|
+
depth?: number;
|
|
8
|
+
selectedEmail?: string | null;
|
|
9
|
+
onSelect: (path: string) => void;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
let { node, depth = 0, selectedEmail = null, onSelect }: Props = $props();
|
|
13
|
+
|
|
14
|
+
let expanded = $state(true);
|
|
15
|
+
let isDirectory = $derived(node.type === 'directory');
|
|
16
|
+
let isActiveFile = $derived(node.type === 'file' && selectedEmail === node.path);
|
|
17
|
+
let isActiveDirectory = $derived(
|
|
18
|
+
node.type === 'directory' && !!selectedEmail && selectedEmail.startsWith(`${node.path}/`)
|
|
19
|
+
);
|
|
20
|
+
let containsSelected = $derived(
|
|
21
|
+
isDirectory &&
|
|
22
|
+
selectedEmail !== null &&
|
|
23
|
+
(selectedEmail === node.path || selectedEmail.startsWith(`${node.path}/`))
|
|
24
|
+
);
|
|
25
|
+
let directoryItems = $derived(isDirectory ? (node as DirectoryEntry).items : []);
|
|
26
|
+
|
|
27
|
+
function toggleDirectory() {
|
|
28
|
+
if (isDirectory) {
|
|
29
|
+
expanded = !expanded;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
$effect(() => {
|
|
34
|
+
if (containsSelected && !expanded) {
|
|
35
|
+
expanded = true;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
function handleSelect() {
|
|
40
|
+
if (node.type === 'file') {
|
|
41
|
+
onSelect(node.path);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<li class="tree-node" data-node-type={node.type}>
|
|
47
|
+
{#if isDirectory}
|
|
48
|
+
<button
|
|
49
|
+
class="email-directory"
|
|
50
|
+
class:active={isActiveDirectory}
|
|
51
|
+
class:collapsed={!expanded}
|
|
52
|
+
style={`--node-depth: ${depth};`}
|
|
53
|
+
type="button"
|
|
54
|
+
onclick={toggleDirectory}
|
|
55
|
+
aria-expanded={expanded}
|
|
56
|
+
>
|
|
57
|
+
<span class="email-icon caret" aria-hidden="true">
|
|
58
|
+
{#if expanded}
|
|
59
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
|
60
|
+
<path
|
|
61
|
+
fill="none"
|
|
62
|
+
stroke="currentColor"
|
|
63
|
+
stroke-width="2"
|
|
64
|
+
stroke-linecap="round"
|
|
65
|
+
stroke-linejoin="round"
|
|
66
|
+
d="m6 9 6 6 6-6"
|
|
67
|
+
/>
|
|
68
|
+
</svg>
|
|
69
|
+
{:else}
|
|
70
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24">
|
|
71
|
+
<path
|
|
72
|
+
fill="none"
|
|
73
|
+
stroke="currentColor"
|
|
74
|
+
stroke-width="2"
|
|
75
|
+
stroke-linecap="round"
|
|
76
|
+
stroke-linejoin="round"
|
|
77
|
+
d="m9 6 6 6-6 6"
|
|
78
|
+
/>
|
|
79
|
+
</svg>
|
|
80
|
+
{/if}
|
|
81
|
+
</span>
|
|
82
|
+
<span class="email-icon folder" aria-hidden="true">
|
|
83
|
+
<svg
|
|
84
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
85
|
+
width="16"
|
|
86
|
+
height="16"
|
|
87
|
+
viewBox="0 0 24 24"
|
|
88
|
+
fill="none"
|
|
89
|
+
stroke="currentColor"
|
|
90
|
+
stroke-width="2"
|
|
91
|
+
stroke-linecap="round"
|
|
92
|
+
stroke-linejoin="round"
|
|
93
|
+
>
|
|
94
|
+
<path d="M4 4h5l2 3h9a1 1 0 0 1 1 1v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2Z" />
|
|
95
|
+
</svg>
|
|
96
|
+
</span>
|
|
97
|
+
<span class="email-directory-name">{node.name}</span>
|
|
98
|
+
</button>
|
|
99
|
+
{#if expanded && directoryItems.length > 0}
|
|
100
|
+
<ul class="email-children">
|
|
101
|
+
{#each directoryItems as child (child.path)}
|
|
102
|
+
<EmailTreeNodeComponent node={child} depth={depth + 1} {selectedEmail} {onSelect} />
|
|
103
|
+
{/each}
|
|
104
|
+
</ul>
|
|
105
|
+
{/if}
|
|
106
|
+
{:else}
|
|
107
|
+
<button
|
|
108
|
+
class="email-button"
|
|
109
|
+
class:active={isActiveFile}
|
|
110
|
+
style={`--node-depth: ${depth};`}
|
|
111
|
+
type="button"
|
|
112
|
+
onclick={handleSelect}
|
|
113
|
+
>
|
|
114
|
+
<span class="email-icon" aria-hidden="true">
|
|
115
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 93.2 112">
|
|
116
|
+
<path
|
|
117
|
+
fill="currentColor"
|
|
118
|
+
d="M87.3,14.8L87.3,14.8C76.9-0.1,56.3-4.5,41.5,5L15.4,21.6c-7.1,4.5-12,11.8-13.5,20c-1.2,6.9-0.2,14,3.1,20.2
|
|
119
|
+
c-2.2,3.4-3.8,7.2-4.5,11.2C-1,81.5,1,90.2,5.9,97.2c10.4,14.9,30.9,19.3,45.8,9.8l26.1-16.6c7.1-4.5,12-11.8,13.5-20
|
|
120
|
+
c1.2-6.9,0.2-14-3.1-20.2c2.2-3.4,3.8-7.2,4.5-11.2C94.2,30.5,92.2,21.8,87.3,14.8z M79.8,36.2c-0.2,0.8-0.4,1.6-0.6,2.4l-0.5,1.5
|
|
121
|
+
l-1.3-1c-3.1-2.3-6.5-4-10.2-5.1l-1-0.3l0.1-1c0.1-1.4-0.3-2.7-1.1-3.9c-1.5-2.2-4.2-3.1-6.7-2.5c-0.6,0.2-1.1,0.4-1.6,0.7
|
|
122
|
+
L30.8,43.7c-1.3,0.8-2.2,2.1-2.4,3.6c-0.3,1.5,0.1,3.1,1,4.4c1.5,2.2,4.2,3.1,6.7,2.5c0.6-0.2,1.1-0.4,1.6-0.7l10-6.3
|
|
123
|
+
c1.6-1,3.4-1.8,5.3-2.3c8.4-2.2,17.3,1.1,22.2,8.2c3,4.2,4.2,9.4,3.3,14.5c-0.9,5-3.8,9.4-8.1,12.1L44.2,96.3
|
|
124
|
+
c-1.6,1-3.4,1.8-5.3,2.3h0c-8.4,2.2-17.3-1.1-22.2-8.2c-3-4.2-4.2-9.4-3.3-14.5c0.2-0.8,0.4-1.6,0.6-2.4l0.5-1.5l1.3,1
|
|
125
|
+
c3.1,2.3,6.5,4,10.2,5.1l1,0.3l-0.1,1c-0.1,1.4,0.3,2.8,1.1,3.9c1.5,2.2,4.2,3.1,6.7,2.5c0.6-0.2,1.1-0.4,1.6-0.7l26.1-16.6
|
|
126
|
+
c1.3-0.8,2.2-2.1,2.5-3.6c0.3-1.5-0.1-3.1-1-4.4c-1.5-2.2-4.2-3.1-6.7-2.5c-0.6,0.2-1.1,0.4-1.6,0.7l-10,6.3c-1.6,1-3.4,1.8-5.3,2.3
|
|
127
|
+
C31.9,69.4,23,66.1,18,58.9c-3-4.2-4.2-9.4-3.3-14.5c0.9-5,3.8-9.4,8.1-12.1L49,15.7c1.6-1,3.4-1.8,5.3-2.3
|
|
128
|
+
c8.4-2.2,17.3,1.1,22.2,8.2C79.5,25.9,80.7,31.1,79.8,36.2z"
|
|
129
|
+
/>
|
|
130
|
+
</svg>
|
|
131
|
+
</span>
|
|
132
|
+
<span class="email-name">{node.name}</span>
|
|
133
|
+
</button>
|
|
134
|
+
{/if}
|
|
135
|
+
</li>
|
|
136
|
+
|
|
137
|
+
<style>
|
|
138
|
+
.tree-node {
|
|
139
|
+
list-style: none;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.email-directory {
|
|
143
|
+
display: flex;
|
|
144
|
+
align-items: center;
|
|
145
|
+
gap: 0.5rem;
|
|
146
|
+
width: 100%;
|
|
147
|
+
padding: 0.375rem 0.75rem;
|
|
148
|
+
padding-left: calc(0.75rem + var(--node-depth, 0) * 1rem);
|
|
149
|
+
border-radius: 0.5rem;
|
|
150
|
+
border: 0;
|
|
151
|
+
background-color: transparent;
|
|
152
|
+
font-size: 0.75rem;
|
|
153
|
+
text-transform: uppercase;
|
|
154
|
+
letter-spacing: 0.04em;
|
|
155
|
+
color: var(--muted-foreground);
|
|
156
|
+
cursor: pointer;
|
|
157
|
+
transition: all 0.15s;
|
|
158
|
+
justify-content: flex-start;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.email-directory:hover {
|
|
162
|
+
background-color: var(--muted);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.email-button {
|
|
166
|
+
display: flex;
|
|
167
|
+
width: 100%;
|
|
168
|
+
cursor: pointer;
|
|
169
|
+
align-items: center;
|
|
170
|
+
gap: 0.75rem;
|
|
171
|
+
border-radius: 0.75rem;
|
|
172
|
+
border: 0;
|
|
173
|
+
background-color: transparent;
|
|
174
|
+
padding: 0.5rem 0.75rem;
|
|
175
|
+
padding-left: calc(0.75rem + var(--node-depth, 0) * 1rem);
|
|
176
|
+
text-align: left;
|
|
177
|
+
font-size: 0.875rem;
|
|
178
|
+
font-weight: 500;
|
|
179
|
+
color: var(--secondary-foreground);
|
|
180
|
+
transition: all 0.15s;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.email-button:hover {
|
|
184
|
+
background-color: var(--muted);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.email-button.active {
|
|
188
|
+
background-color: color-mix(in srgb, var(--svelte) 10%, transparent);
|
|
189
|
+
color: var(--svelte);
|
|
190
|
+
font-weight: 500;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.email-button.active:hover {
|
|
194
|
+
background-color: color-mix(in srgb, var(--svelte) 15%, transparent);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.email-name {
|
|
198
|
+
flex: 1;
|
|
199
|
+
overflow: hidden;
|
|
200
|
+
text-overflow: ellipsis;
|
|
201
|
+
white-space: nowrap;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.email-directory.active {
|
|
205
|
+
color: var(--foreground);
|
|
206
|
+
background-color: color-mix(in srgb, var(--muted) 80%, transparent);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.email-directory.collapsed .folder {
|
|
210
|
+
opacity: 0.7;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.email-directory.collapsed .email-directory-name {
|
|
214
|
+
color: var(--muted-foreground);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.email-children {
|
|
218
|
+
margin: 0.125rem 0 0;
|
|
219
|
+
padding: 0;
|
|
220
|
+
list-style: none;
|
|
221
|
+
display: flex;
|
|
222
|
+
flex-direction: column;
|
|
223
|
+
gap: 0.125rem;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.email-icon {
|
|
227
|
+
display: flex;
|
|
228
|
+
align-items: center;
|
|
229
|
+
justify-content: center;
|
|
230
|
+
color: var(--muted-foreground);
|
|
231
|
+
min-width: 1rem;
|
|
232
|
+
min-height: 1rem;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.email-icon.caret {
|
|
236
|
+
color: var(--muted-foreground);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.email-directory.active .email-icon {
|
|
240
|
+
color: var(--foreground);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.email-directory.active .email-icon.caret {
|
|
244
|
+
color: var(--foreground);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.email-button.active .email-icon {
|
|
248
|
+
color: var(--svelte);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.email-icon svg {
|
|
252
|
+
width: 1rem;
|
|
253
|
+
height: 1rem;
|
|
254
|
+
}
|
|
255
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { EmailTreeEntry } from './email-tree.js';
|
|
2
|
+
type Props = {
|
|
3
|
+
node: EmailTreeEntry;
|
|
4
|
+
depth?: number;
|
|
5
|
+
selectedEmail?: string | null;
|
|
6
|
+
onSelect: (path: string) => void;
|
|
7
|
+
};
|
|
8
|
+
declare const EmailTreeNode: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type EmailTreeNode = ReturnType<typeof EmailTreeNode>;
|
|
10
|
+
export default EmailTreeNode;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
const { class: className } = $props();
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<svg
|
|
6
|
+
width="64"
|
|
7
|
+
height="64"
|
|
8
|
+
viewBox="0 0 1500 1500"
|
|
9
|
+
fill="none"
|
|
10
|
+
class={className}
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
>
|
|
13
|
+
<g clip-path="url(#clip0_5_2)">
|
|
14
|
+
<path
|
|
15
|
+
d="M87 742.327C87 717.983 98.0845 694.964 117.116 679.784L700.116 214.787C729.299 191.511 770.701 191.511 799.884 214.787L1382.88 679.784C1401.92 694.964 1413 717.983 1413 742.327V1419C1413 1463.18 1377.18 1499 1333 1499H167C122.817 1499 87 1463.18 87 1419V742.327Z"
|
|
16
|
+
fill="#A6A09B"
|
|
17
|
+
/>
|
|
18
|
+
<path
|
|
19
|
+
d="M1253.72 194.097C1124.74 9.4287 870.004 -45.3077 685.819 72.084L362.345 278.322C318.646 305.823 281.165 342.142 252.296 384.958C223.428 427.774 203.807 476.145 194.689 526.978C179.258 612.604 192.821 700.932 233.229 777.979C205.54 819.995 186.655 867.192 177.716 916.716C168.5 968.573 169.729 1021.75 181.33 1073.12C192.931 1124.5 214.67 1173.04 245.272 1215.9C374.279 1400.59 629.015 1455.3 813.171 1337.91L1136.65 1131.68C1180.35 1104.18 1217.83 1067.86 1246.7 1025.04C1275.57 982.228 1295.19 933.855 1304.3 883.021C1319.73 797.396 1306.18 709.069 1265.79 632.014C1293.47 589.998 1312.35 542.803 1321.28 493.282C1330.5 441.425 1329.27 388.248 1317.67 336.872C1306.07 285.495 1284.33 236.954 1253.72 194.097Z"
|
|
20
|
+
fill="#FF3E00"
|
|
21
|
+
/>
|
|
22
|
+
<path
|
|
23
|
+
d="M654.218 1233.28C603.313 1246.52 549.572 1243.81 500.254 1225.52C450.935 1207.24 408.415 1174.25 378.436 1131.02C360.029 1105.24 346.953 1076.05 339.975 1045.16C332.997 1014.26 332.258 982.281 337.8 951.095C339.677 940.856 342.26 930.76 345.53 920.877L351.622 902.295L368.195 914.472C406.468 942.604 449.261 963.993 494.731 977.717L506.753 981.365L505.646 993.368C504.177 1010.44 508.799 1027.49 518.693 1041.48C527.715 1054.5 540.514 1064.44 555.363 1069.96C570.212 1075.47 586.395 1076.29 601.726 1072.31C608.756 1070.43 615.456 1067.48 621.595 1063.58L945.077 857.361C952.993 852.375 959.783 845.795 965.015 838.039C970.248 830.283 973.808 821.522 975.469 812.314C977.129 802.914 976.901 793.278 974.799 783.967C972.696 774.656 968.761 765.857 963.223 758.084C954.195 745.058 941.39 735.117 926.536 729.601C911.683 724.084 895.495 723.259 880.157 727.237C873.137 729.117 866.445 732.06 860.314 735.966L736.876 814.682C716.578 827.593 694.427 837.324 671.19 843.538C620.285 856.778 566.545 854.072 517.228 835.784C467.91 817.497 425.39 784.509 395.412 741.277C377.004 715.505 363.928 686.314 356.95 655.418C349.972 624.522 349.232 592.542 354.776 561.357C360.258 530.789 372.057 501.703 389.419 475.958C406.781 450.213 429.323 428.377 455.603 411.845L779.057 205.614C799.364 192.682 821.531 182.939 844.787 176.723C895.691 163.484 949.431 166.19 998.748 184.477C1048.07 202.764 1090.58 235.751 1120.56 278.983C1138.97 304.756 1152.05 333.947 1159.02 364.843C1166 395.739 1166.74 427.719 1161.2 458.905C1159.31 469.142 1156.73 479.238 1153.47 489.123L1147.38 507.705L1130.81 495.546C1092.54 467.398 1049.75 446 1004.27 432.275L992.247 428.625L993.354 416.622C994.811 399.547 990.19 382.507 980.307 368.51C971.285 355.488 958.486 345.549 943.637 340.035C928.788 334.522 912.605 333.7 897.274 337.681C890.244 339.56 883.544 342.506 877.405 346.416L553.923 552.639C546.009 557.621 539.221 564.199 533.992 571.953C528.763 579.707 525.208 588.467 523.554 597.673C521.884 607.073 522.105 616.711 524.204 626.024C526.303 635.336 530.237 644.137 535.777 651.91C544.805 664.935 557.61 674.877 572.464 680.393C587.317 685.909 603.505 686.734 618.843 682.757C625.871 680.873 632.571 677.928 638.71 674.021L762.133 595.335C782.422 582.407 804.572 572.668 827.812 566.456C878.716 553.216 932.456 555.922 981.774 574.209C1031.09 592.497 1073.61 625.484 1103.59 668.717C1122 694.489 1135.07 723.68 1142.05 754.576C1149.03 785.472 1149.77 817.451 1144.23 848.637C1138.74 879.206 1126.94 908.294 1109.58 934.042C1092.22 959.791 1069.68 981.632 1043.4 998.172L719.943 1204.39C699.636 1217.32 677.474 1227.06 654.218 1233.28Z"
|
|
24
|
+
fill="white"
|
|
25
|
+
/>
|
|
26
|
+
<mask
|
|
27
|
+
id="mask0_5_2"
|
|
28
|
+
style="mask-type:alpha"
|
|
29
|
+
maskUnits="userSpaceOnUse"
|
|
30
|
+
x="87"
|
|
31
|
+
y="197"
|
|
32
|
+
width="1326"
|
|
33
|
+
height="1303"
|
|
34
|
+
>
|
|
35
|
+
<path
|
|
36
|
+
d="M87 743.468C87 719.155 98.0565 696.161 117.047 680.98L700.047 214.932C729.256 191.582 770.744 191.582 799.953 214.932L1382.95 680.98C1401.94 696.162 1413 719.155 1413 743.468V1420C1413 1464.18 1377.18 1500 1333 1500H167C122.817 1500 87 1464.18 87 1420V743.468Z"
|
|
37
|
+
fill="white"
|
|
38
|
+
/>
|
|
39
|
+
</mask>
|
|
40
|
+
<g mask="url(#mask0_5_2)">
|
|
41
|
+
<g filter="url(#filter0_d_5_2)">
|
|
42
|
+
<path
|
|
43
|
+
d="M750 1500H132C107.147 1500 87 1479.85 87 1455V787.887C87 752.189 126.574 730.704 156.511 750.148L725.489 1119.7C740.395 1129.39 759.605 1129.39 774.511 1119.7L1343.49 750.148C1373.43 730.704 1413 752.189 1413 787.887V1455C1413 1479.85 1392.85 1500 1368 1500H750Z"
|
|
44
|
+
fill="#D6D3D1"
|
|
45
|
+
/>
|
|
46
|
+
</g>
|
|
47
|
+
<g filter="url(#filter1_d_5_2)">
|
|
48
|
+
<path
|
|
49
|
+
d="M750 1500H132C107.147 1500 87 1479.85 87 1455V1417.33C87 1402.74 94.072 1389.05 105.973 1380.62L723.96 942.463C739.557 931.404 760.439 931.409 776.031 942.475L1393.53 1380.72C1405.34 1389.1 1412.39 1402.65 1412.48 1417.13L1412.72 1454.72C1412.87 1479.68 1392.68 1500 1367.72 1500H750Z"
|
|
50
|
+
fill="#E7E5E4"
|
|
51
|
+
/>
|
|
52
|
+
</g>
|
|
53
|
+
</g>
|
|
54
|
+
</g>
|
|
55
|
+
<defs>
|
|
56
|
+
<filter
|
|
57
|
+
id="filter0_d_5_2"
|
|
58
|
+
x="-26.8"
|
|
59
|
+
y="629.013"
|
|
60
|
+
width="1553.6"
|
|
61
|
+
height="984.787"
|
|
62
|
+
filterUnits="userSpaceOnUse"
|
|
63
|
+
color-interpolation-filters="sRGB"
|
|
64
|
+
>
|
|
65
|
+
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
|
66
|
+
<feColorMatrix
|
|
67
|
+
in="SourceAlpha"
|
|
68
|
+
type="matrix"
|
|
69
|
+
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
|
70
|
+
result="hardAlpha"
|
|
71
|
+
/>
|
|
72
|
+
<feOffset />
|
|
73
|
+
<feGaussianBlur stdDeviation="56.9" />
|
|
74
|
+
<feComposite in2="hardAlpha" operator="out" />
|
|
75
|
+
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.36 0" />
|
|
76
|
+
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5_2" />
|
|
77
|
+
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5_2" result="shape" />
|
|
78
|
+
</filter>
|
|
79
|
+
<filter
|
|
80
|
+
id="filter1_d_5_2"
|
|
81
|
+
x="-4.8"
|
|
82
|
+
y="842.372"
|
|
83
|
+
width="1509.32"
|
|
84
|
+
height="749.428"
|
|
85
|
+
filterUnits="userSpaceOnUse"
|
|
86
|
+
color-interpolation-filters="sRGB"
|
|
87
|
+
>
|
|
88
|
+
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
|
89
|
+
<feColorMatrix
|
|
90
|
+
in="SourceAlpha"
|
|
91
|
+
type="matrix"
|
|
92
|
+
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
|
93
|
+
result="hardAlpha"
|
|
94
|
+
/>
|
|
95
|
+
<feOffset />
|
|
96
|
+
<feGaussianBlur stdDeviation="45.9" />
|
|
97
|
+
<feComposite in2="hardAlpha" operator="out" />
|
|
98
|
+
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" />
|
|
99
|
+
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_5_2" />
|
|
100
|
+
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_5_2" result="shape" />
|
|
101
|
+
</filter>
|
|
102
|
+
<clipPath id="clip0_5_2">
|
|
103
|
+
<rect width="1500" height="1500" fill="white" />
|
|
104
|
+
</clipPath>
|
|
105
|
+
</defs>
|
|
106
|
+
</svg>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type EmailTreeEntry = FileEntry | DirectoryEntry;
|
|
2
|
+
export type FileEntry = {
|
|
3
|
+
type: 'file';
|
|
4
|
+
name: string;
|
|
5
|
+
path: string;
|
|
6
|
+
};
|
|
7
|
+
export type DirectoryEntry = {
|
|
8
|
+
type: 'directory';
|
|
9
|
+
name: string;
|
|
10
|
+
path: string;
|
|
11
|
+
items: EmailTreeEntry[];
|
|
12
|
+
};
|
|
13
|
+
export declare function buildEmailTree(paths: readonly string[] | null | undefined): EmailTreeEntry[];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function buildEmailTree(paths) {
|
|
2
|
+
if (!paths || paths.length === 0) {
|
|
3
|
+
return [];
|
|
4
|
+
}
|
|
5
|
+
const root = [];
|
|
6
|
+
const directoryMap = new Map();
|
|
7
|
+
for (const rawPath of paths) {
|
|
8
|
+
if (!rawPath)
|
|
9
|
+
continue;
|
|
10
|
+
const normalized = normalizePath(rawPath);
|
|
11
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
12
|
+
if (segments.length === 0)
|
|
13
|
+
continue;
|
|
14
|
+
let parentChildren = root;
|
|
15
|
+
let parentPath = '';
|
|
16
|
+
segments.forEach((segment, index) => {
|
|
17
|
+
const currentPath = parentPath ? `${parentPath}/${segment}` : segment;
|
|
18
|
+
const isFile = index === segments.length - 1;
|
|
19
|
+
if (isFile) {
|
|
20
|
+
parentChildren.push({
|
|
21
|
+
type: 'file',
|
|
22
|
+
name: segment,
|
|
23
|
+
path: normalized
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
let directory = directoryMap.get(currentPath);
|
|
28
|
+
if (!directory) {
|
|
29
|
+
directory = {
|
|
30
|
+
type: 'directory',
|
|
31
|
+
name: segment,
|
|
32
|
+
path: currentPath,
|
|
33
|
+
items: []
|
|
34
|
+
};
|
|
35
|
+
directoryMap.set(currentPath, directory);
|
|
36
|
+
parentChildren.push(directory);
|
|
37
|
+
}
|
|
38
|
+
parentChildren = directory.items;
|
|
39
|
+
}
|
|
40
|
+
parentPath = currentPath;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return sortTree(root);
|
|
44
|
+
}
|
|
45
|
+
function normalizePath(path) {
|
|
46
|
+
return path.replace(/^[./]+/, '').replace(/\/+/g, '/');
|
|
47
|
+
}
|
|
48
|
+
function sortTree(nodes) {
|
|
49
|
+
nodes.sort((a, b) => {
|
|
50
|
+
if (a.type === b.type) {
|
|
51
|
+
return a.name.localeCompare(b.name);
|
|
52
|
+
}
|
|
53
|
+
return a.type === 'directory' ? -1 : 1;
|
|
54
|
+
});
|
|
55
|
+
for (const node of nodes) {
|
|
56
|
+
if (node.type === 'directory') {
|
|
57
|
+
sortTree(node.items);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return nodes;
|
|
61
|
+
}
|
package/dist/preview/index.d.ts
CHANGED
|
@@ -58,18 +58,22 @@ export declare const getEmailComponent: (emailPath: string, file: string) => Pro
|
|
|
58
58
|
* }
|
|
59
59
|
* });
|
|
60
60
|
*
|
|
61
|
-
* export const actions = createEmail(renderer);
|
|
61
|
+
* export const actions = createEmail({ renderer });
|
|
62
62
|
* ```
|
|
63
63
|
*/
|
|
64
|
-
export declare const createEmail: (
|
|
64
|
+
export declare const createEmail: (options?: {
|
|
65
|
+
renderer?: Renderer;
|
|
66
|
+
}) => {
|
|
65
67
|
'create-email': (event: RequestEvent) => Promise<{
|
|
66
68
|
status: number;
|
|
67
69
|
body: {
|
|
68
70
|
error: string;
|
|
69
71
|
};
|
|
72
|
+
source?: undefined;
|
|
70
73
|
error?: undefined;
|
|
71
74
|
} | {
|
|
72
75
|
body: string;
|
|
76
|
+
source: string | null;
|
|
73
77
|
status?: undefined;
|
|
74
78
|
error?: undefined;
|
|
75
79
|
} | {
|
|
@@ -78,6 +82,7 @@ export declare const createEmail: (renderer?: Renderer) => {
|
|
|
78
82
|
message: string;
|
|
79
83
|
};
|
|
80
84
|
body?: undefined;
|
|
85
|
+
source?: undefined;
|
|
81
86
|
}>;
|
|
82
87
|
};
|
|
83
88
|
export declare const SendEmailFunction: ({ from, to, subject, html }: {
|
|
@@ -119,7 +124,15 @@ export declare const SendEmailFunction: ({ from, to, subject, html }: {
|
|
|
119
124
|
* ```
|
|
120
125
|
*/
|
|
121
126
|
export declare const sendEmail: ({ customSendEmailFunction, resendApiKey, renderer }?: {
|
|
122
|
-
customSendEmailFunction?:
|
|
127
|
+
customSendEmailFunction?: (email: {
|
|
128
|
+
from: string;
|
|
129
|
+
to: string;
|
|
130
|
+
subject: string;
|
|
131
|
+
html: string;
|
|
132
|
+
}) => Promise<{
|
|
133
|
+
success: boolean;
|
|
134
|
+
error?: any;
|
|
135
|
+
}>;
|
|
123
136
|
renderer?: Renderer;
|
|
124
137
|
resendApiKey?: string;
|
|
125
138
|
}) => {
|
package/dist/preview/index.js
CHANGED
|
@@ -63,6 +63,28 @@ export const getEmailComponent = async (emailPath, file) => {
|
|
|
63
63
|
throw new Error(`Failed to import email component '${fileName}'. Make sure the file exists and includes the <Head /> component.\nOriginal error: ${err}`);
|
|
64
64
|
}
|
|
65
65
|
};
|
|
66
|
+
const getEmailSource = async (emailPath, file) => {
|
|
67
|
+
const normalizedEmailPath = emailPath.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
68
|
+
const normalizedFile = file.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
69
|
+
const candidates = new Set();
|
|
70
|
+
const relativeEmailPath = normalizedEmailPath.replace(/^\/+/, '');
|
|
71
|
+
if (normalizedEmailPath) {
|
|
72
|
+
candidates.add(path.resolve(process.cwd(), relativeEmailPath, `${normalizedFile}.svelte`));
|
|
73
|
+
candidates.add(path.resolve(process.cwd(), normalizedEmailPath, `${normalizedFile}.svelte`));
|
|
74
|
+
candidates.add(path.resolve(normalizedEmailPath, `${normalizedFile}.svelte`));
|
|
75
|
+
}
|
|
76
|
+
candidates.add(path.resolve(process.cwd(), `${normalizedFile}.svelte`));
|
|
77
|
+
for (const candidate of candidates) {
|
|
78
|
+
try {
|
|
79
|
+
return await fs.promises.readFile(candidate, 'utf8');
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// continue to next candidate
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
console.warn(`Source file not found for ${normalizedFile} in ${normalizedEmailPath}`);
|
|
86
|
+
return null;
|
|
87
|
+
};
|
|
66
88
|
/**
|
|
67
89
|
* SvelteKit form action to render an email component.
|
|
68
90
|
* Use this with the Preview component to render email templates on demand.
|
|
@@ -85,10 +107,11 @@ export const getEmailComponent = async (emailPath, file) => {
|
|
|
85
107
|
* }
|
|
86
108
|
* });
|
|
87
109
|
*
|
|
88
|
-
* export const actions = createEmail(renderer);
|
|
110
|
+
* export const actions = createEmail({ renderer });
|
|
89
111
|
* ```
|
|
90
112
|
*/
|
|
91
|
-
export const createEmail = (
|
|
113
|
+
export const createEmail = (options = {}) => {
|
|
114
|
+
const { renderer = new Renderer() } = options;
|
|
92
115
|
return {
|
|
93
116
|
'create-email': async (event) => {
|
|
94
117
|
try {
|
|
@@ -102,6 +125,7 @@ export const createEmail = (renderer = new Renderer()) => {
|
|
|
102
125
|
};
|
|
103
126
|
}
|
|
104
127
|
const emailComponent = await getEmailComponent(emailPath, file);
|
|
128
|
+
const source = await getEmailSource(emailPath, file);
|
|
105
129
|
// Render the component to HTML
|
|
106
130
|
const html = await renderer.render(emailComponent);
|
|
107
131
|
// Remove all HTML comments from the body before formatting
|
|
@@ -110,7 +134,8 @@ export const createEmail = (renderer = new Renderer()) => {
|
|
|
110
134
|
plugins: [parserHtml]
|
|
111
135
|
});
|
|
112
136
|
return {
|
|
113
|
-
body: formattedHtml
|
|
137
|
+
body: formattedHtml,
|
|
138
|
+
source
|
|
114
139
|
};
|
|
115
140
|
}
|
|
116
141
|
catch (error) {
|
|
@@ -190,7 +215,7 @@ export const sendEmail = ({ customSendEmailFunction, resendApiKey, renderer = ne
|
|
|
190
215
|
sent = await defaultSendEmailFunction(email, resendApiKey);
|
|
191
216
|
}
|
|
192
217
|
else if (customSendEmailFunction) {
|
|
193
|
-
sent = await customSendEmailFunction(email
|
|
218
|
+
sent = await customSendEmailFunction(email);
|
|
194
219
|
}
|
|
195
220
|
else if (!customSendEmailFunction && !resendApiKey) {
|
|
196
221
|
const error = {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--background: oklch(98.5% 0.001 106.423);
|
|
3
|
+
--foreground: oklch(21.6% 0.006 56.043);
|
|
4
|
+
--card: oklch(1 0 0);
|
|
5
|
+
--card-foreground: oklch(0.147 0.004 49.25);
|
|
6
|
+
--popover: oklch(1 0 0);
|
|
7
|
+
--popover-foreground: oklch(0.147 0.004 49.25);
|
|
8
|
+
--primary: oklch(0.216 0.006 56.043);
|
|
9
|
+
--primary-foreground: oklch(0.985 0.001 106.423);
|
|
10
|
+
--secondary: oklch(0.958 0.003 48.717);
|
|
11
|
+
--secondary-foreground: oklch(0.454 0.01 67.558);
|
|
12
|
+
--muted: oklch(0.9483 0.0061 67.75);
|
|
13
|
+
--muted-foreground: oklch(0.4761 0.021783 55.8952);
|
|
14
|
+
--accent: oklch(0.97 0.001 106.424);
|
|
15
|
+
--accent-foreground: oklch(0.216 0.006 56.043);
|
|
16
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
17
|
+
--border: oklch(0.923 0.003 48.717);
|
|
18
|
+
--input: oklch(0.923 0.003 48.717);
|
|
19
|
+
--ring: oklch(0.709 0.01 56.259);
|
|
20
|
+
--svelte: #f73b01;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.dark {
|
|
24
|
+
--background: oklch(14.7% 0.004 49.25);
|
|
25
|
+
--foreground: oklch(97% 0.001 106.424);
|
|
26
|
+
--card: oklch(0.216 0.006 56.043);
|
|
27
|
+
--card-foreground: oklch(0.985 0.001 106.423);
|
|
28
|
+
--popover: oklch(0.216 0.006 56.043);
|
|
29
|
+
--popover-foreground: oklch(0.985 0.001 106.423);
|
|
30
|
+
--primary: oklch(0.923 0.003 48.717);
|
|
31
|
+
--primary-foreground: oklch(0.216 0.006 56.043);
|
|
32
|
+
--secondary: oklch(0.216 0.006 56.043);
|
|
33
|
+
--secondary-foreground: oklch(0.709 0.01 56.259);
|
|
34
|
+
--muted: oklch(0.268 0.007 34.298);
|
|
35
|
+
--muted-foreground: oklch(0.7348 0.0326 67.28);
|
|
36
|
+
--accent: oklch(0.268 0.007 34.298);
|
|
37
|
+
--accent-foreground: oklch(0.985 0.001 106.423);
|
|
38
|
+
--destructive: oklch(0.704 0.191 22.216);
|
|
39
|
+
--border: oklch(1 0 0 / 10%);
|
|
40
|
+
--input: oklch(1 0 0 / 15%);
|
|
41
|
+
--ring: oklch(0.553 0.013 58.071);
|
|
42
|
+
}
|