best-email-builder 0.2.0 → 0.3.0

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 (37) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/README.md +102 -0
  3. package/dist/builder.js +44 -0
  4. package/dist/builder.js.map +1 -0
  5. package/dist/{chunk-MTALS5GC.js → chunk-3M36VVXS.js} +36 -118
  6. package/dist/chunk-3M36VVXS.js.map +1 -0
  7. package/dist/chunk-57CTQNTK.js +56 -0
  8. package/dist/chunk-57CTQNTK.js.map +1 -0
  9. package/dist/chunk-BVOVAEEJ.js +73 -0
  10. package/dist/chunk-BVOVAEEJ.js.map +1 -0
  11. package/dist/chunk-ILRAQTHS.js +3677 -0
  12. package/dist/chunk-ILRAQTHS.js.map +1 -0
  13. package/dist/chunk-JOC752I3.js +529 -0
  14. package/dist/chunk-JOC752I3.js.map +1 -0
  15. package/dist/chunk-ZSUHOCCE.js +1351 -0
  16. package/dist/chunk-ZSUHOCCE.js.map +1 -0
  17. package/dist/index.js +86 -4865
  18. package/dist/index.js.map +1 -1
  19. package/dist/library.js +31 -0
  20. package/dist/library.js.map +1 -0
  21. package/dist/server.js +15 -11
  22. package/dist/server.js.map +1 -1
  23. package/dist/styles.css +1 -1
  24. package/dist/types/adapters/grapesjs/engineConfig.d.ts +0 -14
  25. package/dist/types/builder.d.ts +8 -0
  26. package/dist/types/core/constants.d.ts +1 -1
  27. package/dist/types/core/scrollbars.d.ts +13 -0
  28. package/dist/types/defaultEditor.d.ts +8 -0
  29. package/dist/types/features/templates/gallery.d.ts +7 -0
  30. package/dist/types/index.d.ts +4 -9
  31. package/dist/types/library.d.ts +5 -0
  32. package/dist/types/storage/client.d.ts +4 -0
  33. package/dist/types/ui/EmailStudio.d.ts +42 -0
  34. package/dist/types/ui/library/TemplateLibrary.d.ts +41 -0
  35. package/dist/types/ui/primitives/button.d.ts +1 -1
  36. package/package.json +18 -1
  37. package/dist/chunk-MTALS5GC.js.map +0 -1
@@ -0,0 +1,529 @@
1
+ import {
2
+ Button,
3
+ EmailPreview,
4
+ Input,
5
+ NoticeArea,
6
+ SectionLabel,
7
+ TEMPLATE_GALLERY,
8
+ UiProvider,
9
+ buildGallery,
10
+ cn,
11
+ createHttpStorage,
12
+ useLayoutWidth,
13
+ useUi,
14
+ withPlaceholderImages
15
+ } from "./chunk-ZSUHOCCE.js";
16
+
17
+ // src/ui/library/TemplateLibrary.jsx
18
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
19
+ import {
20
+ AlertCircle,
21
+ ArrowLeft,
22
+ Copy,
23
+ Download,
24
+ LayoutTemplate,
25
+ Loader2,
26
+ PencilRuler,
27
+ Plus,
28
+ RefreshCw,
29
+ Search,
30
+ Star,
31
+ Trash2,
32
+ X
33
+ } from "lucide-react";
34
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
35
+ var DATE_FORMAT = { day: "2-digit", month: "2-digit", year: "numeric" };
36
+ var DEFAULT_FAVORITES_KEY = "best-email-builder:favorites";
37
+ var matches = (query, ...fields) => {
38
+ const q = query.trim().toLowerCase();
39
+ return !q || fields.some((f) => String(f || "").toLowerCase().includes(q));
40
+ };
41
+ var codeOf = (name, fallback) => String(name || "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48) || fallback;
42
+ function useFavorites(storageKey) {
43
+ const [favorites, setFavorites] = useState(() => {
44
+ if (!storageKey) return /* @__PURE__ */ new Set();
45
+ try {
46
+ return new Set(JSON.parse(localStorage.getItem(storageKey) || "[]"));
47
+ } catch {
48
+ return /* @__PURE__ */ new Set();
49
+ }
50
+ });
51
+ const toggle = useCallback(
52
+ (key) => setFavorites((current) => {
53
+ const next = new Set(current);
54
+ if (next.has(key)) next.delete(key);
55
+ else next.add(key);
56
+ try {
57
+ localStorage.setItem(storageKey, JSON.stringify([...next]));
58
+ } catch {
59
+ }
60
+ return next;
61
+ }),
62
+ [storageKey]
63
+ );
64
+ return { enabled: Boolean(storageKey), favorites, toggle };
65
+ }
66
+ function download(name, html) {
67
+ const blob = new Blob([html], { type: "text/html;charset=utf-8" });
68
+ const url = URL.createObjectURL(blob);
69
+ const a = document.createElement("a");
70
+ a.href = url;
71
+ a.download = `${codeOf(name, "email")}.html`;
72
+ a.click();
73
+ setTimeout(() => URL.revokeObjectURL(url), 1e3);
74
+ }
75
+ function Badge({ children }) {
76
+ return /* @__PURE__ */ jsx("span", { className: "eb:inline-flex eb:w-fit eb:shrink-0 eb:items-center eb:rounded-md eb:border eb:border-border eb:px-1.5 eb:py-0 eb:text-[10px] eb:whitespace-nowrap eb:text-foreground", children });
77
+ }
78
+ function Row({ entry, selected, favorite, onSelect, testId }) {
79
+ return /* @__PURE__ */ jsxs(
80
+ "button",
81
+ {
82
+ type: "button",
83
+ onClick: onSelect,
84
+ "aria-current": selected ? "true" : void 0,
85
+ "data-testid": testId,
86
+ className: cn(
87
+ "eb:w-full eb:rounded-lg2 eb:border eb:px-2.5 eb:py-2 eb:text-left eb:transition-colors eb:outline-none",
88
+ "eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50",
89
+ selected ? "eb:border-primary/50 eb:bg-primary/5" : "eb:border-transparent eb:hover:bg-surface-75"
90
+ ),
91
+ children: [
92
+ /* @__PURE__ */ jsxs("span", { className: "eb:flex eb:items-center eb:gap-1.5", children: [
93
+ /* @__PURE__ */ jsx("span", { className: "eb:min-w-0 eb:flex-1 eb:truncate eb:text-[13px] eb:text-foreground", children: entry.name }),
94
+ favorite && /* @__PURE__ */ jsx(Star, { className: "eb:size-3.5 eb:shrink-0 eb:fill-current eb:text-warning", "aria-hidden": "true" })
95
+ ] }),
96
+ /* @__PURE__ */ jsx("span", { className: "eb:mt-0.5 eb:block eb:truncate eb:font-mono eb:text-[11px] eb:text-foreground-lighter", children: entry.code }),
97
+ /* @__PURE__ */ jsxs("span", { className: "eb:mt-1 eb:flex eb:flex-wrap eb:items-center eb:gap-1", children: [
98
+ /* @__PURE__ */ jsx(Badge, { children: entry.badge }),
99
+ entry.date && /* @__PURE__ */ jsx("span", { className: "eb:ml-auto eb:text-[10.5px] eb:text-foreground-muted", children: entry.date })
100
+ ] })
101
+ ]
102
+ }
103
+ );
104
+ }
105
+ function Section({ label, children, ...props }) {
106
+ return /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:flex-col eb:gap-1.5", ...props, children: [
107
+ /* @__PURE__ */ jsx(SectionLabel, { children: label }),
108
+ children
109
+ ] });
110
+ }
111
+ function Library({
112
+ storage: injectedStorage,
113
+ sender,
114
+ templates: galleryProp,
115
+ title,
116
+ description,
117
+ selectedId = null,
118
+ favoritesKey = DEFAULT_FAVORITES_KEY,
119
+ onEdit,
120
+ onCreate,
121
+ onClose,
122
+ className
123
+ }) {
124
+ const { t, locale, notify, confirm } = useUi();
125
+ const storage = useMemo(() => injectedStorage || createHttpStorage(), [injectedStorage]);
126
+ const bases = useMemo(
127
+ () => buildGallery({ templates: galleryProp ?? TEMPLATE_GALLERY, sender: sender || {}, t }),
128
+ [galleryProp, sender, t]
129
+ );
130
+ const dateFormat = useMemo(() => new Intl.DateTimeFormat(locale, DATE_FORMAT), [locale]);
131
+ const formatDate = useCallback((at) => at ? dateFormat.format(new Date(at)) : "", [dateFormat]);
132
+ const stars = useFavorites(favoritesKey);
133
+ const rootRef = useRef(null);
134
+ const searchRef = useRef(null);
135
+ const layout = useLayoutWidth(rootRef);
136
+ const [items, setItems] = useState({ status: "loading", list: [], error: null });
137
+ const [query, setQuery] = useState("");
138
+ const [selection, setSelection] = useState(selectedId != null ? `template:${selectedId}` : null);
139
+ const [detailsOpen, setDetailsOpen] = useState(false);
140
+ const [detail, setDetail] = useState({ status: "idle", template: null, error: null });
141
+ const [preview, setPreview] = useState({ html: "", loading: false, error: null });
142
+ const [device, setDevice] = useState("desktop");
143
+ const [busy, setBusy] = useState(null);
144
+ const lastRead = useRef(0);
145
+ const reload = useCallback(async () => {
146
+ const ticket = ++lastRead.current;
147
+ if (typeof storage.list !== "function") {
148
+ setItems({ status: "ready", list: [], error: null });
149
+ return;
150
+ }
151
+ setItems((s) => ({ ...s, status: s.list.length ? "ready" : "loading", error: null }));
152
+ try {
153
+ const list = await storage.list();
154
+ if (ticket === lastRead.current) setItems({ status: "ready", list, error: null });
155
+ } catch (error) {
156
+ if (ticket === lastRead.current) setItems({ status: "error", list: [], error: error.message });
157
+ }
158
+ }, [storage]);
159
+ useEffect(() => {
160
+ reload();
161
+ }, [reload]);
162
+ const templateEntries = useMemo(
163
+ () => items.list.map((item) => ({
164
+ key: `template:${item.id}`,
165
+ kind: "template",
166
+ id: item.id,
167
+ name: item.name,
168
+ code: codeOf(item.name, `#${item.id}`),
169
+ badge: t("Builder"),
170
+ date: formatDate(item.updatedAt || item.createdAt),
171
+ subject: item.subject,
172
+ item
173
+ })),
174
+ [items.list, t, formatDate]
175
+ );
176
+ const baseEntries = useMemo(
177
+ () => bases.map((base2) => ({
178
+ key: `base:${base2.id}`,
179
+ kind: "base",
180
+ id: base2.id,
181
+ name: base2.name,
182
+ code: `base_${base2.id}`,
183
+ badge: t("Base"),
184
+ date: "",
185
+ subject: base2.subject,
186
+ base: base2
187
+ })),
188
+ [bases, t]
189
+ );
190
+ const shown = (entries) => entries.filter((e) => matches(query, e.name, e.code, e.subject, e.base?.description));
191
+ const favoriteEntries = shown([...templateEntries, ...baseEntries]).filter((e) => stars.favorites.has(e.key));
192
+ const otherTemplates = shown(templateEntries).filter((e) => !stars.favorites.has(e.key));
193
+ const otherBases = shown(baseEntries).filter((e) => !stars.favorites.has(e.key));
194
+ useEffect(() => {
195
+ if (items.status !== "ready" || selection) return;
196
+ const base2 = bases.find((b) => b.mjml.includes("<mj-section")) || bases[0];
197
+ if (items.list.length) setSelection(`template:${items.list[0].id}`);
198
+ else if (base2) setSelection(`base:${base2.id}`);
199
+ }, [items, selection, bases]);
200
+ useEffect(() => {
201
+ const onKey = (event) => {
202
+ if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "k" && rootRef.current?.isConnected) {
203
+ event.preventDefault();
204
+ searchRef.current?.focus();
205
+ }
206
+ };
207
+ window.addEventListener("keydown", onKey);
208
+ return () => window.removeEventListener("keydown", onKey);
209
+ }, []);
210
+ const selected = templateEntries.find((e) => e.key === selection) || baseEntries.find((e) => e.key === selection) || null;
211
+ const base = selected?.kind === "base" ? selected.base : null;
212
+ const templateId = selection?.startsWith("template:") ? selection.slice("template:".length) : null;
213
+ useEffect(() => {
214
+ if (templateId == null) {
215
+ setDetail({ status: "idle", template: null, error: null });
216
+ return void 0;
217
+ }
218
+ let live = true;
219
+ setDetail((d) => ({ status: "loading", template: String(d.template?.id) === templateId ? d.template : null, error: null }));
220
+ storage.load(templateId).then(
221
+ (template2) => live && setDetail({ status: "ready", template: template2, error: null }),
222
+ (error) => live && setDetail({ status: "error", template: null, error: error.message })
223
+ );
224
+ return () => {
225
+ live = false;
226
+ };
227
+ }, [templateId, storage]);
228
+ const template = detail.template;
229
+ const source = base ? { mjml: base.mjml, subject: base.subject, preheader: base.preheader, html: "" } : template ? { mjml: template.mjml, subject: template.subject, preheader: template.preheader, html: template.html || "" } : null;
230
+ const sourceKey = base ? `base:${base.id}:${locale}` : template ? `template:${template.id}:${template.revision}` : "";
231
+ useEffect(() => {
232
+ if (!source) {
233
+ setPreview({ html: "", loading: templateId != null && detail.status === "loading", error: null });
234
+ return void 0;
235
+ }
236
+ if (typeof storage.render !== "function" || !source.mjml) {
237
+ setPreview({ html: source.html, loading: false, error: source.html ? null : t("No preview: this storage cannot render emails.") });
238
+ return void 0;
239
+ }
240
+ let live = true;
241
+ setPreview((p) => ({ ...p, loading: true, error: null }));
242
+ storage.render({ mjml: source.mjml, subject: source.subject, preheader: source.preheader }).then(
243
+ (result) => live && setPreview({ html: withPlaceholderImages(result.html), loading: false, error: null }),
244
+ (error) => live && setPreview({ html: source.html, loading: false, error: source.html ? null : error.message })
245
+ );
246
+ return () => {
247
+ live = false;
248
+ };
249
+ }, [sourceKey, storage]);
250
+ const select = (key) => {
251
+ setSelection(key);
252
+ setDetailsOpen(true);
253
+ };
254
+ const remove = async () => {
255
+ const ok = await confirm({
256
+ title: t("Delete \u201C{name}\u201D?", { name: template.name }),
257
+ description: t("The template and its versions will be deleted."),
258
+ confirmLabel: t("Delete"),
259
+ destructive: true
260
+ });
261
+ if (!ok) return;
262
+ setBusy("delete");
263
+ try {
264
+ await storage.remove(template.id);
265
+ setSelection(null);
266
+ setDetailsOpen(false);
267
+ await reload();
268
+ notify("success", t("Template deleted."));
269
+ } catch (error) {
270
+ notify("error", error.message);
271
+ } finally {
272
+ setBusy(null);
273
+ }
274
+ };
275
+ const duplicate = async () => {
276
+ setBusy("duplicate");
277
+ try {
278
+ const { template: copy } = await storage.duplicate(template.id);
279
+ await reload();
280
+ setSelection(`template:${copy.id}`);
281
+ notify("success", t("Template duplicated."));
282
+ } catch (error) {
283
+ notify("error", error.message);
284
+ } finally {
285
+ setBusy(null);
286
+ }
287
+ };
288
+ const copyHtml = async () => {
289
+ try {
290
+ await navigator.clipboard.writeText(template.html);
291
+ notify("success", t("HTML copied."));
292
+ } catch {
293
+ notify("error", t("The browser refused to copy."));
294
+ }
295
+ };
296
+ const canList = typeof storage.list === "function";
297
+ const rowProps = (entry) => ({
298
+ entry,
299
+ testId: entry.kind === "base" ? "eb-library-base" : "eb-library-template",
300
+ selected: entry.key === selection,
301
+ favorite: stars.favorites.has(entry.key),
302
+ onSelect: () => select(entry.key)
303
+ });
304
+ const listColumn = /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:min-h-0 eb:min-w-0 eb:flex-col", "data-testid": "eb-library-list", children: [
305
+ /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:shrink-0 eb:flex-col eb:gap-2 eb:border-b eb:border-border eb:p-3", children: [
306
+ onCreate && /* @__PURE__ */ jsxs(Button, { className: "eb:w-full", onClick: () => onCreate(null), children: [
307
+ /* @__PURE__ */ jsx(PencilRuler, {}),
308
+ t("New template")
309
+ ] }),
310
+ /* @__PURE__ */ jsxs("div", { className: "eb:relative", children: [
311
+ /* @__PURE__ */ jsx(Search, { className: "eb:pointer-events-none eb:absolute eb:top-1/2 eb:left-2.5 eb:size-3.5 eb:-translate-y-1/2 eb:text-foreground-muted" }),
312
+ /* @__PURE__ */ jsx(
313
+ Input,
314
+ {
315
+ ref: searchRef,
316
+ type: "search",
317
+ value: query,
318
+ onChange: (e) => setQuery(e.target.value),
319
+ onKeyDown: (e) => {
320
+ if (e.key === "Escape" && query) {
321
+ e.preventDefault();
322
+ setQuery("");
323
+ }
324
+ },
325
+ placeholder: t("Search templates"),
326
+ "aria-label": t("Search templates"),
327
+ "aria-keyshortcuts": "Control+K",
328
+ className: "eb:pr-16 eb:pl-8 eb:[&::-webkit-search-cancel-button]:hidden"
329
+ }
330
+ ),
331
+ /* @__PURE__ */ jsxs("div", { className: "eb:absolute eb:top-1/2 eb:right-2 eb:flex eb:-translate-y-1/2 eb:items-center eb:gap-1", children: [
332
+ query && /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setQuery(""), "aria-label": t("Clear the search"), className: "eb:cursor-pointer", children: /* @__PURE__ */ jsx(X, { className: "eb:size-3.5 eb:text-foreground-muted eb:hover:text-foreground" }) }),
333
+ /* @__PURE__ */ jsx(
334
+ "kbd",
335
+ {
336
+ className: "eb:inline-flex eb:h-5 eb:items-center eb:rounded eb:border eb:border-border eb:bg-surface-100 eb:px-1.5 eb:font-mono eb:text-[10px] eb:text-foreground-muted",
337
+ "aria-hidden": "true",
338
+ children: query ? "Esc" : "Ctrl K"
339
+ }
340
+ )
341
+ ] })
342
+ ] })
343
+ ] }),
344
+ /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:min-h-0 eb:flex-1 eb:flex-col eb:gap-4 eb:overflow-y-auto eb:p-3", children: [
345
+ stars.enabled && favoriteEntries.length > 0 && /* @__PURE__ */ jsx(Section, { label: t("Favorites"), role: "group", "aria-label": t("Favorites"), "data-testid": "eb-library-favorites", children: favoriteEntries.map((entry) => /* @__PURE__ */ jsx(Row, { ...rowProps(entry) }, entry.key)) }),
346
+ canList && /* @__PURE__ */ jsx(Section, { label: `${t("Templates")} (${otherTemplates.length})`, "data-testid": "eb-library-templates", children: items.status === "loading" ? /* @__PURE__ */ jsxs("p", { className: "eb:m-0 eb:flex eb:items-center eb:gap-2 eb:px-1 eb:text-[12px] eb:text-foreground-lighter", children: [
347
+ /* @__PURE__ */ jsx(Loader2, { className: "eb:size-3.5 eb:animate-spin" }),
348
+ t("Loading\u2026")
349
+ ] }) : items.status === "error" ? /* @__PURE__ */ jsxs("div", { className: "eb:px-1 eb:text-[12px] eb:text-destructive", children: [
350
+ /* @__PURE__ */ jsx("p", { className: "eb:m-0", children: t("Could not load the templates.") }),
351
+ /* @__PURE__ */ jsx("p", { className: "eb:m-0 eb:mt-0.5 eb:text-foreground-muted", children: items.error }),
352
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm-compact", className: "eb:mt-2", onClick: reload, children: [
353
+ /* @__PURE__ */ jsx(RefreshCw, {}),
354
+ t("Retry")
355
+ ] })
356
+ ] }) : otherTemplates.length === 0 ? /* @__PURE__ */ jsx("p", { className: "eb:m-0 eb:px-1 eb:text-[12px] eb:leading-relaxed eb:text-foreground-lighter", children: query ? t("Nothing matches \u201C{query}\u201D.", { query }) : templateEntries.length ? t("All your templates are in the favorites.") : t("No template yet. Start from a base, or create a new one.") }) : otherTemplates.map((entry) => /* @__PURE__ */ jsx(Row, { ...rowProps(entry) }, entry.key)) }),
357
+ otherBases.length > 0 && /* @__PURE__ */ jsx(Section, { label: `${t("Bases")} (${otherBases.length})`, "data-testid": "eb-library-bases", children: otherBases.map((entry) => /* @__PURE__ */ jsx(Row, { ...rowProps(entry) }, entry.key)) })
358
+ ] })
359
+ ] }, "list");
360
+ const meta = base ? { name: base.name, code: `base_${base.id}`, description: base.description, origin: t("Base"), subject: base.subject, preheader: base.preheader } : selected || template ? {
361
+ name: template?.name ?? selected.name,
362
+ code: selected?.code ?? codeOf(template?.name, `#${templateId}`),
363
+ description: template?.description ?? selected?.item?.description,
364
+ origin: t("Builder"),
365
+ subject: (template ?? selected?.item)?.subject,
366
+ preheader: (template ?? selected?.item)?.preheader,
367
+ createdAt: (template ?? selected?.item)?.createdAt,
368
+ createdBy: (template ?? selected?.item)?.createdBy,
369
+ updatedAt: (template ?? selected?.item)?.updatedAt,
370
+ updatedBy: (template ?? selected?.item)?.updatedBy
371
+ } : null;
372
+ const favorite = selection ? stars.favorites.has(selection) : false;
373
+ const detailsBody = templateId != null && detail.status === "error" ? /* @__PURE__ */ jsxs("p", { className: "eb:m-0 eb:flex eb:items-start eb:gap-2 eb:text-[13px] eb:text-destructive", children: [
374
+ /* @__PURE__ */ jsx(AlertCircle, { className: "eb:mt-0.5 eb:size-4 eb:shrink-0" }),
375
+ detail.error
376
+ ] }) : meta ? /* @__PURE__ */ jsxs(Fragment, { children: [
377
+ /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:flex-col eb:gap-1", children: [
378
+ /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:items-center eb:gap-2", children: [
379
+ /* @__PURE__ */ jsx("h3", { className: "eb:m-0 eb:min-w-0 eb:flex-1 eb:text-[15px] eb:font-medium eb:text-foreground", children: meta.name }),
380
+ stars.enabled && /* @__PURE__ */ jsx(
381
+ Button,
382
+ {
383
+ variant: "ghost",
384
+ size: "icon-sm-compact",
385
+ className: cn("eb:shrink-0", favorite ? "eb:text-warning" : "eb:text-foreground-muted eb:hover:text-warning"),
386
+ onClick: () => stars.toggle(selection),
387
+ "aria-pressed": favorite,
388
+ "aria-label": favorite ? t("Remove from favorites") : t("Add to favorites"),
389
+ title: favorite ? t("Remove from favorites") : t("Add to favorites"),
390
+ children: /* @__PURE__ */ jsx(Star, { className: cn("eb:size-4", favorite && "eb:fill-current") })
391
+ }
392
+ )
393
+ ] }),
394
+ /* @__PURE__ */ jsx("p", { className: "eb:m-0 eb:font-mono eb:text-[11.5px] eb:text-foreground-lighter", children: meta.code }),
395
+ meta.description && /* @__PURE__ */ jsx("p", { className: "eb:m-0 eb:text-[12.5px] eb:leading-relaxed eb:text-foreground-lighter", children: meta.description })
396
+ ] }),
397
+ /* @__PURE__ */ jsxs("dl", { className: "eb:m-0 eb:grid eb:grid-cols-[110px_minmax(0,1fr)] eb:gap-x-3 eb:gap-y-1.5 eb:text-[12px]", children: [
398
+ /* @__PURE__ */ jsx("dt", { className: "eb:text-foreground-muted", children: t("Origin") }),
399
+ /* @__PURE__ */ jsx("dd", { className: "eb:m-0 eb:text-foreground", children: meta.origin }),
400
+ /* @__PURE__ */ jsx("dt", { className: "eb:text-foreground-muted", children: t("Subject") }),
401
+ /* @__PURE__ */ jsx("dd", { className: "eb:m-0 eb:break-words eb:text-foreground", children: meta.subject || "\u2014" }),
402
+ /* @__PURE__ */ jsx("dt", { className: "eb:text-foreground-muted", children: t("Preview text") }),
403
+ /* @__PURE__ */ jsx("dd", { className: "eb:m-0 eb:break-words eb:text-foreground-lighter", children: meta.preheader || "\u2014" }),
404
+ !base && /* @__PURE__ */ jsxs(Fragment, { children: [
405
+ /* @__PURE__ */ jsx("dt", { className: "eb:text-foreground-muted", children: t("Created") }),
406
+ /* @__PURE__ */ jsxs("dd", { className: "eb:m-0 eb:text-foreground", children: [
407
+ formatDate(meta.createdAt) || "\u2014",
408
+ meta.createdBy ? ` ${t("by {name}", { name: meta.createdBy })}` : ""
409
+ ] }),
410
+ meta.updatedAt && /* @__PURE__ */ jsxs(Fragment, { children: [
411
+ /* @__PURE__ */ jsx("dt", { className: "eb:text-foreground-muted", children: t("Updated") }),
412
+ /* @__PURE__ */ jsxs("dd", { className: "eb:m-0 eb:text-foreground", children: [
413
+ formatDate(meta.updatedAt),
414
+ meta.updatedBy ? ` ${t("by {name}", { name: meta.updatedBy })}` : ""
415
+ ] })
416
+ ] })
417
+ ] })
418
+ ] }),
419
+ base && /* @__PURE__ */ jsx("p", { className: "eb:m-0 eb:rounded-lg2 eb:border eb:border-border eb:bg-surface-75 eb:p-2.5 eb:text-[12px] eb:leading-relaxed eb:text-foreground-lighter", children: t("Base \u2014 not editable: start from it to create your template.") })
420
+ ] }) : /* @__PURE__ */ jsx("div", { className: "eb:flex eb:flex-1 eb:items-center eb:justify-center", children: /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:w-full eb:flex-col eb:items-center eb:justify-center eb:gap-2 eb:rounded-lg2 eb:border eb:border-border eb:bg-surface-75 eb:px-6 eb:py-12 eb:text-center", children: [
421
+ /* @__PURE__ */ jsx("span", { className: "eb:flex eb:size-12 eb:items-center eb:justify-center eb:rounded-full eb:bg-surface-200 eb:text-foreground-muted", children: /* @__PURE__ */ jsx(LayoutTemplate, { className: "eb:size-6" }) }),
422
+ /* @__PURE__ */ jsx("p", { className: "eb:m-0 eb:text-[13px] eb:font-medium eb:text-foreground", children: t("Choose a template") }),
423
+ /* @__PURE__ */ jsx("p", { className: "eb:m-0 eb:max-w-sm eb:text-[13px] eb:leading-relaxed eb:text-foreground-muted", children: t("Select a template on the left.") })
424
+ ] }) });
425
+ const actions = base ? onCreate && /* @__PURE__ */ jsxs(
426
+ Button,
427
+ {
428
+ className: "eb:w-full",
429
+ onClick: () => onCreate({ baseId: base.id, mjml: base.mjml, meta: { subject: base.subject, preheader: base.preheader } }),
430
+ children: [
431
+ /* @__PURE__ */ jsx(Plus, {}),
432
+ t("Start from this base")
433
+ ]
434
+ }
435
+ ) : template ? /* @__PURE__ */ jsxs(Fragment, { children: [
436
+ /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:gap-2", children: [
437
+ onEdit && /* @__PURE__ */ jsxs(Button, { className: "eb:flex-1", onClick: () => onEdit(template), children: [
438
+ /* @__PURE__ */ jsx(PencilRuler, {}),
439
+ t("Edit")
440
+ ] }),
441
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", className: "eb:flex-1", disabled: !!busy, onClick: duplicate, children: [
442
+ busy === "duplicate" ? /* @__PURE__ */ jsx(Loader2, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx(Copy, {}),
443
+ t("Duplicate")
444
+ ] })
445
+ ] }),
446
+ (template.html || typeof storage.remove === "function") && /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:gap-2", children: [
447
+ template.html && /* @__PURE__ */ jsxs(Fragment, { children: [
448
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm-compact", className: "eb:flex-1", onClick: copyHtml, children: [
449
+ /* @__PURE__ */ jsx(Copy, {}),
450
+ t("Copy HTML")
451
+ ] }),
452
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm-compact", className: "eb:flex-1", onClick: () => download(template.name, template.html), children: [
453
+ /* @__PURE__ */ jsx(Download, {}),
454
+ t("Download")
455
+ ] })
456
+ ] }),
457
+ typeof storage.remove === "function" && /* @__PURE__ */ jsxs(
458
+ Button,
459
+ {
460
+ variant: "outline",
461
+ size: "sm-compact",
462
+ className: cn("eb:text-destructive", !template.html && "eb:ml-auto"),
463
+ disabled: !!busy,
464
+ onClick: remove,
465
+ "aria-label": t("Delete"),
466
+ title: t("Delete"),
467
+ children: [
468
+ busy === "delete" ? /* @__PURE__ */ jsx(Loader2, { className: "eb:animate-spin" }) : /* @__PURE__ */ jsx(Trash2, {}),
469
+ !template.html && t("Delete")
470
+ ]
471
+ }
472
+ )
473
+ ] })
474
+ ] }) : null;
475
+ const previewPane = preview.html || preview.loading || preview.error ? /* @__PURE__ */ jsx(EmailPreview, { html: preview.html, loading: preview.loading, error: preview.error, device, onDeviceChange: setDevice }) : /* @__PURE__ */ jsx("div", { className: "eb:flex eb:h-full eb:min-h-40 eb:items-center eb:justify-center eb:rounded-lg2 eb:border eb:border-dashed eb:border-border eb:p-6 eb:text-center eb:text-[13px] eb:text-foreground-lighter", children: t("The template preview appears here.") });
476
+ const detailsColumn = /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:min-h-0 eb:min-w-0 eb:flex-col", "data-testid": "eb-library-details", children: [
477
+ layout === "narrow" && /* @__PURE__ */ jsx("div", { className: "eb:shrink-0 eb:border-b eb:border-border eb:px-2 eb:py-1.5", children: /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm-compact", onClick: () => setDetailsOpen(false), children: [
478
+ /* @__PURE__ */ jsx(ArrowLeft, {}),
479
+ t("All templates")
480
+ ] }) }),
481
+ /* @__PURE__ */ jsxs("div", { className: "eb:flex eb:min-h-0 eb:flex-1 eb:flex-col eb:gap-4 eb:overflow-y-auto eb:p-4", children: [
482
+ detailsBody,
483
+ layout !== "wide" && meta && /* @__PURE__ */ jsx("div", { className: "eb:h-[560px] eb:shrink-0", children: previewPane })
484
+ ] }),
485
+ actions && /* @__PURE__ */ jsx("div", { className: "eb:flex eb:shrink-0 eb:flex-col eb:gap-2 eb:border-t eb:border-border eb:p-3", children: actions })
486
+ ] }, "details");
487
+ return /* @__PURE__ */ jsxs(
488
+ "div",
489
+ {
490
+ ref: rootRef,
491
+ className: cn("eb-root eb:relative eb:flex eb:h-full eb:min-h-0 eb:flex-col eb:bg-background eb:text-foreground", className),
492
+ "data-testid": "template-library",
493
+ "data-layout": layout,
494
+ children: [
495
+ /* @__PURE__ */ jsxs("header", { className: "eb:flex eb:shrink-0 eb:items-center eb:gap-3 eb:border-b eb:border-border eb:px-5 eb:py-3.5", children: [
496
+ /* @__PURE__ */ jsx("span", { className: "eb:flex eb:size-10 eb:shrink-0 eb:items-center eb:justify-center eb:rounded-lg2 eb:bg-primary/10 eb:text-primary", children: /* @__PURE__ */ jsx(LayoutTemplate, { className: "eb:size-5" }) }),
497
+ /* @__PURE__ */ jsxs("div", { className: "eb:min-w-0 eb:flex-1", children: [
498
+ /* @__PURE__ */ jsx("h2", { className: "eb:m-0 eb:truncate eb:text-base eb:font-normal eb:text-foreground", children: title ?? t("Email template library") }),
499
+ /* @__PURE__ */ jsx("p", { className: "eb:m-0 eb:truncate eb:text-[13px] eb:text-foreground-muted", children: description ?? t("Ready-made templates to copy into your campaigns.") })
500
+ ] }),
501
+ onClose && /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon-sm", onClick: onClose, "aria-label": t("Close"), children: /* @__PURE__ */ jsx(X, {}) })
502
+ ] }),
503
+ layout === "narrow" ? /* @__PURE__ */ jsx("div", { className: "eb:grid eb:min-h-0 eb:flex-1 eb:grid-cols-[minmax(0,1fr)]", children: detailsOpen && selection ? detailsColumn : listColumn }) : /* @__PURE__ */ jsxs(
504
+ "div",
505
+ {
506
+ className: cn(
507
+ "eb:grid eb:min-h-0 eb:flex-1 eb:grid-rows-1 eb:divide-x eb:divide-border eb:overflow-hidden",
508
+ layout === "wide" ? "eb:grid-cols-[290px_minmax(0,1fr)_minmax(0,1fr)]" : "eb:grid-cols-[260px_minmax(0,1fr)]"
509
+ ),
510
+ children: [
511
+ listColumn,
512
+ detailsColumn,
513
+ layout === "wide" && /* @__PURE__ */ jsx("div", { className: "eb:min-h-0 eb:min-w-0 eb:p-3", "data-testid": "eb-library-preview", children: previewPane })
514
+ ]
515
+ }
516
+ ),
517
+ /* @__PURE__ */ jsx(NoticeArea, {})
518
+ ]
519
+ }
520
+ );
521
+ }
522
+ function TemplateLibrary({ locale = "en", messages, onNotify, ...props }) {
523
+ return /* @__PURE__ */ jsx(UiProvider, { locale, messages, onNotify, children: /* @__PURE__ */ jsx(Library, { ...props }) });
524
+ }
525
+
526
+ export {
527
+ TemplateLibrary
528
+ };
529
+ //# sourceMappingURL=chunk-JOC752I3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ui/library/TemplateLibrary.jsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n AlertCircle,\n ArrowLeft,\n Copy,\n Download,\n LayoutTemplate,\n Loader2,\n PencilRuler,\n Plus,\n RefreshCw,\n Search,\n Star,\n Trash2,\n X,\n} from 'lucide-react';\nimport { TEMPLATE_GALLERY, buildGallery, withPlaceholderImages } from '../../features/templates/gallery.js';\nimport { EmailPreview } from '../../features/preview/PreviewButton.jsx';\nimport { createHttpStorage } from '../../storage/client.js';\nimport { NoticeArea, UiProvider, useUi } from '../context.jsx';\nimport { Button } from '../primitives/button.jsx';\nimport { cn } from '../primitives/cn.js';\nimport { Input } from '../primitives/fields.jsx';\nimport { SectionLabel } from '../primitives/misc.jsx';\nimport { useLayoutWidth } from '../useLayoutWidth.js';\n\n/**\n * TEMPLATE LIBRARY — the first screen: pick a template, see it, open it.\n *\n * ┌──────────────────────────────────────────────────────────────────┐\n * │ ▣ Email template library × │\n * ├──────────────┬──────────────────────────┬────────────────────────┤\n * │ New template │ Name ☆ │ Desktop | Mobile │\n * │ 🔍 Ctrl K │ code │ ┌────────────────────┐ │\n * │ FAVORITES │ Origin, subject, dates… │ │ rendered email │ │\n * │ TEMPLATES │ │ │ │ │\n * │ BASES │ [ Edit ] [ Duplicate ] │ └────────────────────┘ │\n * └──────────────┴──────────────────────────┴────────────────────────┘\n *\n * Standalone: it never mounts the editor. \"Edit\", \"New template\" and \"Start\n * from this base\" only call `onEdit(template)` / `onCreate(start)`, and the\n * host decides what opens — `EmailBuilder` (see `EmailStudio`, which wires\n * the two), its own screen, a route. Likewise `EmailBuilder` works without it.\n *\n * Templates `storage.list()`; preview through `storage.render()` when the\n * storage has it, else the HTML compiled at the last save.\n * Bases the gallery (`templates`, `[]` hides them): read-only, \"Start\n * from this base\" calls `onCreate({ baseId, mjml, meta })`.\n * Favorites the star of the details. A favorite leaves its section for the\n * one at the top. Kept in this browser (`favoritesKey`, `null`\n * turns favorites off).\n *\n * Layout follows the library's own width, like the builder: three columns,\n * then the preview under the details, then (phone) list and details as two\n * screens.\n */\n\nconst DATE_FORMAT = { day: '2-digit', month: '2-digit', year: 'numeric' };\nconst DEFAULT_FAVORITES_KEY = 'best-email-builder:favorites';\n\nconst matches = (query, ...fields) => {\n const q = query.trim().toLowerCase();\n return !q || fields.some((f) => String(f || '').toLowerCase().includes(q));\n};\n\n/** `October newsletter` → `october_newsletter`: the code line under a name. */\nconst codeOf = (name, fallback) =>\n String(name || '')\n .normalize('NFD')\n .replace(/[̀-ͯ]/g, '')\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .slice(0, 48) || fallback;\n\n/** Keys of the starred entries: `template:<id>` or `base:<id>`. */\nfunction useFavorites(storageKey) {\n const [favorites, setFavorites] = useState(() => {\n if (!storageKey) return new Set();\n try {\n return new Set(JSON.parse(localStorage.getItem(storageKey) || '[]'));\n } catch {\n return new Set();\n }\n });\n const toggle = useCallback(\n (key) =>\n setFavorites((current) => {\n const next = new Set(current);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n try {\n localStorage.setItem(storageKey, JSON.stringify([...next]));\n } catch {\n // Private window, blocked storage: the star holds until the page closes.\n }\n return next;\n }),\n [storageKey]\n );\n return { enabled: Boolean(storageKey), favorites, toggle };\n}\n\nfunction download(name, html) {\n const blob = new Blob([html], { type: 'text/html;charset=utf-8' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `${codeOf(name, 'email')}.html`;\n a.click();\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\nfunction Badge({ children }) {\n return (\n <span className=\"eb:inline-flex eb:w-fit eb:shrink-0 eb:items-center eb:rounded-md eb:border eb:border-border eb:px-1.5 eb:py-0 eb:text-[10px] eb:whitespace-nowrap eb:text-foreground\">\n {children}\n </span>\n );\n}\n\nfunction Row({ entry, selected, favorite, onSelect, testId }) {\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n aria-current={selected ? 'true' : undefined}\n data-testid={testId}\n className={cn(\n 'eb:w-full eb:rounded-lg2 eb:border eb:px-2.5 eb:py-2 eb:text-left eb:transition-colors eb:outline-none',\n 'eb:focus-visible:ring-[3px] eb:focus-visible:ring-ring/50',\n selected ? 'eb:border-primary/50 eb:bg-primary/5' : 'eb:border-transparent eb:hover:bg-surface-75'\n )}\n >\n <span className=\"eb:flex eb:items-center eb:gap-1.5\">\n <span className=\"eb:min-w-0 eb:flex-1 eb:truncate eb:text-[13px] eb:text-foreground\">{entry.name}</span>\n {favorite && <Star className=\"eb:size-3.5 eb:shrink-0 eb:fill-current eb:text-warning\" aria-hidden=\"true\" />}\n </span>\n <span className=\"eb:mt-0.5 eb:block eb:truncate eb:font-mono eb:text-[11px] eb:text-foreground-lighter\">{entry.code}</span>\n <span className=\"eb:mt-1 eb:flex eb:flex-wrap eb:items-center eb:gap-1\">\n <Badge>{entry.badge}</Badge>\n {entry.date && <span className=\"eb:ml-auto eb:text-[10.5px] eb:text-foreground-muted\">{entry.date}</span>}\n </span>\n </button>\n );\n}\n\nfunction Section({ label, children, ...props }) {\n return (\n <div className=\"eb:flex eb:flex-col eb:gap-1.5\" {...props}>\n <SectionLabel>{label}</SectionLabel>\n {children}\n </div>\n );\n}\n\nfunction Library({\n storage: injectedStorage,\n sender,\n templates: galleryProp,\n title,\n description,\n selectedId = null,\n favoritesKey = DEFAULT_FAVORITES_KEY,\n onEdit,\n onCreate,\n onClose,\n className,\n}) {\n const { t, locale, notify, confirm } = useUi();\n const storage = useMemo(() => injectedStorage || createHttpStorage(), [injectedStorage]);\n const bases = useMemo(\n () => buildGallery({ templates: galleryProp ?? TEMPLATE_GALLERY, sender: sender || {}, t }),\n [galleryProp, sender, t]\n );\n const dateFormat = useMemo(() => new Intl.DateTimeFormat(locale, DATE_FORMAT), [locale]);\n const formatDate = useCallback((at) => (at ? dateFormat.format(new Date(at)) : ''), [dateFormat]);\n const stars = useFavorites(favoritesKey);\n\n const rootRef = useRef(null);\n const searchRef = useRef(null);\n const layout = useLayoutWidth(rootRef);\n\n const [items, setItems] = useState({ status: 'loading', list: [], error: null });\n const [query, setQuery] = useState('');\n /** `template:<id>` or `base:<id>` */\n const [selection, setSelection] = useState(selectedId != null ? `template:${selectedId}` : null);\n /** Phone layout: the details screen is shown over the list. */\n const [detailsOpen, setDetailsOpen] = useState(false);\n const [detail, setDetail] = useState({ status: 'idle', template: null, error: null });\n const [preview, setPreview] = useState({ html: '', loading: false, error: null });\n const [device, setDevice] = useState('desktop');\n const [busy, setBusy] = useState(null);\n\n // Only the LAST read counts: a slow answer arriving after a newer one\n // (opening, then a duplicate) would show a stale list.\n const lastRead = useRef(0);\n const reload = useCallback(async () => {\n const ticket = ++lastRead.current;\n if (typeof storage.list !== 'function') {\n setItems({ status: 'ready', list: [], error: null });\n return;\n }\n setItems((s) => ({ ...s, status: s.list.length ? 'ready' : 'loading', error: null }));\n try {\n const list = await storage.list();\n if (ticket === lastRead.current) setItems({ status: 'ready', list, error: null });\n } catch (error) {\n if (ticket === lastRead.current) setItems({ status: 'error', list: [], error: error.message });\n }\n }, [storage]);\n\n useEffect(() => {\n reload();\n }, [reload]);\n\n /* ── entries: one shape for templates and bases ────────────────────────── */\n\n const templateEntries = useMemo(\n () =>\n items.list.map((item) => ({\n key: `template:${item.id}`,\n kind: 'template',\n id: item.id,\n name: item.name,\n code: codeOf(item.name, `#${item.id}`),\n badge: t('Builder'),\n date: formatDate(item.updatedAt || item.createdAt),\n subject: item.subject,\n item,\n })),\n [items.list, t, formatDate]\n );\n const baseEntries = useMemo(\n () =>\n bases.map((base) => ({\n key: `base:${base.id}`,\n kind: 'base',\n id: base.id,\n name: base.name,\n code: `base_${base.id}`,\n badge: t('Base'),\n date: '',\n subject: base.subject,\n base,\n })),\n [bases, t]\n );\n const shown = (entries) => entries.filter((e) => matches(query, e.name, e.code, e.subject, e.base?.description));\n const favoriteEntries = shown([...templateEntries, ...baseEntries]).filter((e) => stars.favorites.has(e.key));\n const otherTemplates = shown(templateEntries).filter((e) => !stars.favorites.has(e.key));\n const otherBases = shown(baseEntries).filter((e) => !stars.favorites.has(e.key));\n\n // First selection: the requested template, else the most recent one, else\n // the first base with something to show (a blank page previews as nothing).\n useEffect(() => {\n if (items.status !== 'ready' || selection) return;\n const base = bases.find((b) => b.mjml.includes('<mj-section')) || bases[0];\n if (items.list.length) setSelection(`template:${items.list[0].id}`);\n else if (base) setSelection(`base:${base.id}`);\n }, [items, selection, bases]);\n\n // Ctrl/Cmd+K focuses the search.\n useEffect(() => {\n const onKey = (event) => {\n if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'k' && rootRef.current?.isConnected) {\n event.preventDefault();\n searchRef.current?.focus();\n }\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, []);\n\n const selected =\n templateEntries.find((e) => e.key === selection) || baseEntries.find((e) => e.key === selection) || null;\n const base = selected?.kind === 'base' ? selected.base : null;\n const templateId = selection?.startsWith('template:') ? selection.slice('template:'.length) : null;\n\n // Details of a saved template (MJML and compiled HTML are not in the list).\n useEffect(() => {\n if (templateId == null) {\n setDetail({ status: 'idle', template: null, error: null });\n return undefined;\n }\n let live = true;\n setDetail((d) => ({ status: 'loading', template: String(d.template?.id) === templateId ? d.template : null, error: null }));\n storage.load(templateId).then(\n (template) => live && setDetail({ status: 'ready', template, error: null }),\n (error) => live && setDetail({ status: 'error', template: null, error: error.message })\n );\n return () => {\n live = false;\n };\n }, [templateId, storage]);\n\n // Preview: rendered by the storage (server) when it can, else the saved HTML.\n const template = detail.template;\n const source = base\n ? { mjml: base.mjml, subject: base.subject, preheader: base.preheader, html: '' }\n : template\n ? { mjml: template.mjml, subject: template.subject, preheader: template.preheader, html: template.html || '' }\n : null;\n const sourceKey = base ? `base:${base.id}:${locale}` : template ? `template:${template.id}:${template.revision}` : '';\n useEffect(() => {\n if (!source) {\n setPreview({ html: '', loading: templateId != null && detail.status === 'loading', error: null });\n return undefined;\n }\n if (typeof storage.render !== 'function' || !source.mjml) {\n setPreview({ html: source.html, loading: false, error: source.html ? null : t('No preview: this storage cannot render emails.') });\n return undefined;\n }\n let live = true;\n setPreview((p) => ({ ...p, loading: true, error: null }));\n storage.render({ mjml: source.mjml, subject: source.subject, preheader: source.preheader }).then(\n (result) => live && setPreview({ html: withPlaceholderImages(result.html), loading: false, error: null }),\n (error) => live && setPreview({ html: source.html, loading: false, error: source.html ? null : error.message })\n );\n return () => {\n live = false;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [sourceKey, storage]);\n\n /* ── actions ───────────────────────────────────────────────────────────── */\n\n const select = (key) => {\n setSelection(key);\n setDetailsOpen(true);\n };\n\n const remove = async () => {\n const ok = await confirm({\n title: t('Delete “{name}”?', { name: template.name }),\n description: t('The template and its versions will be deleted.'),\n confirmLabel: t('Delete'),\n destructive: true,\n });\n if (!ok) return;\n setBusy('delete');\n try {\n await storage.remove(template.id);\n setSelection(null);\n setDetailsOpen(false);\n await reload();\n notify('success', t('Template deleted.'));\n } catch (error) {\n notify('error', error.message);\n } finally {\n setBusy(null);\n }\n };\n\n const duplicate = async () => {\n setBusy('duplicate');\n try {\n const { template: copy } = await storage.duplicate(template.id);\n await reload();\n setSelection(`template:${copy.id}`);\n notify('success', t('Template duplicated.'));\n } catch (error) {\n notify('error', error.message);\n } finally {\n setBusy(null);\n }\n };\n\n const copyHtml = async () => {\n try {\n await navigator.clipboard.writeText(template.html);\n notify('success', t('HTML copied.'));\n } catch {\n notify('error', t('The browser refused to copy.'));\n }\n };\n\n const canList = typeof storage.list === 'function';\n const rowProps = (entry) => ({\n entry,\n testId: entry.kind === 'base' ? 'eb-library-base' : 'eb-library-template',\n selected: entry.key === selection,\n favorite: stars.favorites.has(entry.key),\n onSelect: () => select(entry.key),\n });\n\n /* ── left column ───────────────────────────────────────────────────────── */\n\n const listColumn = (\n <div key=\"list\" className=\"eb:flex eb:min-h-0 eb:min-w-0 eb:flex-col\" data-testid=\"eb-library-list\">\n <div className=\"eb:flex eb:shrink-0 eb:flex-col eb:gap-2 eb:border-b eb:border-border eb:p-3\">\n {onCreate && (\n <Button className=\"eb:w-full\" onClick={() => onCreate(null)}>\n <PencilRuler />\n {t('New template')}\n </Button>\n )}\n <div className=\"eb:relative\">\n <Search className=\"eb:pointer-events-none eb:absolute eb:top-1/2 eb:left-2.5 eb:size-3.5 eb:-translate-y-1/2 eb:text-foreground-muted\" />\n <Input\n ref={searchRef}\n type=\"search\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === 'Escape' && query) {\n e.preventDefault();\n setQuery('');\n }\n }}\n placeholder={t('Search templates')}\n aria-label={t('Search templates')}\n aria-keyshortcuts=\"Control+K\"\n className=\"eb:pr-16 eb:pl-8 eb:[&::-webkit-search-cancel-button]:hidden\"\n />\n <div className=\"eb:absolute eb:top-1/2 eb:right-2 eb:flex eb:-translate-y-1/2 eb:items-center eb:gap-1\">\n {query && (\n <button type=\"button\" onClick={() => setQuery('')} aria-label={t('Clear the search')} className=\"eb:cursor-pointer\">\n <X className=\"eb:size-3.5 eb:text-foreground-muted eb:hover:text-foreground\" />\n </button>\n )}\n <kbd\n className=\"eb:inline-flex eb:h-5 eb:items-center eb:rounded eb:border eb:border-border eb:bg-surface-100 eb:px-1.5 eb:font-mono eb:text-[10px] eb:text-foreground-muted\"\n aria-hidden=\"true\"\n >\n {query ? 'Esc' : 'Ctrl K'}\n </kbd>\n </div>\n </div>\n </div>\n\n <div className=\"eb:flex eb:min-h-0 eb:flex-1 eb:flex-col eb:gap-4 eb:overflow-y-auto eb:p-3\">\n {stars.enabled && favoriteEntries.length > 0 && (\n <Section label={t('Favorites')} role=\"group\" aria-label={t('Favorites')} data-testid=\"eb-library-favorites\">\n {favoriteEntries.map((entry) => (\n <Row key={entry.key} {...rowProps(entry)} />\n ))}\n </Section>\n )}\n {canList && (\n <Section label={`${t('Templates')} (${otherTemplates.length})`} data-testid=\"eb-library-templates\">\n {items.status === 'loading' ? (\n <p className=\"eb:m-0 eb:flex eb:items-center eb:gap-2 eb:px-1 eb:text-[12px] eb:text-foreground-lighter\">\n <Loader2 className=\"eb:size-3.5 eb:animate-spin\" />\n {t('Loading…')}\n </p>\n ) : items.status === 'error' ? (\n <div className=\"eb:px-1 eb:text-[12px] eb:text-destructive\">\n <p className=\"eb:m-0\">{t('Could not load the templates.')}</p>\n <p className=\"eb:m-0 eb:mt-0.5 eb:text-foreground-muted\">{items.error}</p>\n <Button variant=\"outline\" size=\"sm-compact\" className=\"eb:mt-2\" onClick={reload}>\n <RefreshCw />\n {t('Retry')}\n </Button>\n </div>\n ) : otherTemplates.length === 0 ? (\n <p className=\"eb:m-0 eb:px-1 eb:text-[12px] eb:leading-relaxed eb:text-foreground-lighter\">\n {query\n ? t('Nothing matches “{query}”.', { query })\n : templateEntries.length\n ? t('All your templates are in the favorites.')\n : t('No template yet. Start from a base, or create a new one.')}\n </p>\n ) : (\n otherTemplates.map((entry) => <Row key={entry.key} {...rowProps(entry)} />)\n )}\n </Section>\n )}\n {otherBases.length > 0 && (\n <Section label={`${t('Bases')} (${otherBases.length})`} data-testid=\"eb-library-bases\">\n {otherBases.map((entry) => (\n <Row key={entry.key} {...rowProps(entry)} />\n ))}\n </Section>\n )}\n </div>\n </div>\n );\n\n /* ── details ───────────────────────────────────────────────────────────── */\n\n const meta = base\n ? { name: base.name, code: `base_${base.id}`, description: base.description, origin: t('Base'), subject: base.subject, preheader: base.preheader }\n : selected || template\n ? {\n name: template?.name ?? selected.name,\n code: selected?.code ?? codeOf(template?.name, `#${templateId}`),\n description: template?.description ?? selected?.item?.description,\n origin: t('Builder'),\n subject: (template ?? selected?.item)?.subject,\n preheader: (template ?? selected?.item)?.preheader,\n createdAt: (template ?? selected?.item)?.createdAt,\n createdBy: (template ?? selected?.item)?.createdBy,\n updatedAt: (template ?? selected?.item)?.updatedAt,\n updatedBy: (template ?? selected?.item)?.updatedBy,\n }\n : null;\n const favorite = selection ? stars.favorites.has(selection) : false;\n\n const detailsBody =\n templateId != null && detail.status === 'error' ? (\n <p className=\"eb:m-0 eb:flex eb:items-start eb:gap-2 eb:text-[13px] eb:text-destructive\">\n <AlertCircle className=\"eb:mt-0.5 eb:size-4 eb:shrink-0\" />\n {detail.error}\n </p>\n ) : meta ? (\n <>\n <div className=\"eb:flex eb:flex-col eb:gap-1\">\n <div className=\"eb:flex eb:items-center eb:gap-2\">\n <h3 className=\"eb:m-0 eb:min-w-0 eb:flex-1 eb:text-[15px] eb:font-medium eb:text-foreground\">{meta.name}</h3>\n {stars.enabled && (\n <Button\n variant=\"ghost\"\n size=\"icon-sm-compact\"\n className={cn('eb:shrink-0', favorite ? 'eb:text-warning' : 'eb:text-foreground-muted eb:hover:text-warning')}\n onClick={() => stars.toggle(selection)}\n aria-pressed={favorite}\n aria-label={favorite ? t('Remove from favorites') : t('Add to favorites')}\n title={favorite ? t('Remove from favorites') : t('Add to favorites')}\n >\n <Star className={cn('eb:size-4', favorite && 'eb:fill-current')} />\n </Button>\n )}\n </div>\n <p className=\"eb:m-0 eb:font-mono eb:text-[11.5px] eb:text-foreground-lighter\">{meta.code}</p>\n {meta.description && (\n <p className=\"eb:m-0 eb:text-[12.5px] eb:leading-relaxed eb:text-foreground-lighter\">{meta.description}</p>\n )}\n </div>\n\n <dl className=\"eb:m-0 eb:grid eb:grid-cols-[110px_minmax(0,1fr)] eb:gap-x-3 eb:gap-y-1.5 eb:text-[12px]\">\n <dt className=\"eb:text-foreground-muted\">{t('Origin')}</dt>\n <dd className=\"eb:m-0 eb:text-foreground\">{meta.origin}</dd>\n <dt className=\"eb:text-foreground-muted\">{t('Subject')}</dt>\n <dd className=\"eb:m-0 eb:break-words eb:text-foreground\">{meta.subject || '—'}</dd>\n <dt className=\"eb:text-foreground-muted\">{t('Preview text')}</dt>\n <dd className=\"eb:m-0 eb:break-words eb:text-foreground-lighter\">{meta.preheader || '—'}</dd>\n {!base && (\n <>\n <dt className=\"eb:text-foreground-muted\">{t('Created')}</dt>\n <dd className=\"eb:m-0 eb:text-foreground\">\n {formatDate(meta.createdAt) || '—'}\n {meta.createdBy ? ` ${t('by {name}', { name: meta.createdBy })}` : ''}\n </dd>\n {meta.updatedAt && (\n <>\n <dt className=\"eb:text-foreground-muted\">{t('Updated')}</dt>\n <dd className=\"eb:m-0 eb:text-foreground\">\n {formatDate(meta.updatedAt)}\n {meta.updatedBy ? ` ${t('by {name}', { name: meta.updatedBy })}` : ''}\n </dd>\n </>\n )}\n </>\n )}\n </dl>\n\n {base && (\n <p className=\"eb:m-0 eb:rounded-lg2 eb:border eb:border-border eb:bg-surface-75 eb:p-2.5 eb:text-[12px] eb:leading-relaxed eb:text-foreground-lighter\">\n {t('Base — not editable: start from it to create your template.')}\n </p>\n )}\n </>\n ) : (\n <div className=\"eb:flex eb:flex-1 eb:items-center eb:justify-center\">\n <div className=\"eb:flex eb:w-full eb:flex-col eb:items-center eb:justify-center eb:gap-2 eb:rounded-lg2 eb:border eb:border-border eb:bg-surface-75 eb:px-6 eb:py-12 eb:text-center\">\n <span className=\"eb:flex eb:size-12 eb:items-center eb:justify-center eb:rounded-full eb:bg-surface-200 eb:text-foreground-muted\">\n <LayoutTemplate className=\"eb:size-6\" />\n </span>\n <p className=\"eb:m-0 eb:text-[13px] eb:font-medium eb:text-foreground\">{t('Choose a template')}</p>\n <p className=\"eb:m-0 eb:max-w-sm eb:text-[13px] eb:leading-relaxed eb:text-foreground-muted\">\n {t('Select a template on the left.')}\n </p>\n </div>\n </div>\n );\n\n const actions = base ? (\n onCreate && (\n <Button\n className=\"eb:w-full\"\n onClick={() => onCreate({ baseId: base.id, mjml: base.mjml, meta: { subject: base.subject, preheader: base.preheader } })}\n >\n <Plus />\n {t('Start from this base')}\n </Button>\n )\n ) : template ? (\n <>\n <div className=\"eb:flex eb:gap-2\">\n {onEdit && (\n <Button className=\"eb:flex-1\" onClick={() => onEdit(template)}>\n <PencilRuler />\n {t('Edit')}\n </Button>\n )}\n <Button variant=\"outline\" className=\"eb:flex-1\" disabled={!!busy} onClick={duplicate}>\n {busy === 'duplicate' ? <Loader2 className=\"eb:animate-spin\" /> : <Copy />}\n {t('Duplicate')}\n </Button>\n </div>\n {(template.html || typeof storage.remove === 'function') && (\n <div className=\"eb:flex eb:gap-2\">\n {template.html && (\n <>\n <Button variant=\"outline\" size=\"sm-compact\" className=\"eb:flex-1\" onClick={copyHtml}>\n <Copy />\n {t('Copy HTML')}\n </Button>\n <Button variant=\"outline\" size=\"sm-compact\" className=\"eb:flex-1\" onClick={() => download(template.name, template.html)}>\n <Download />\n {t('Download')}\n </Button>\n </>\n )}\n {typeof storage.remove === 'function' && (\n <Button\n variant=\"outline\"\n size=\"sm-compact\"\n className={cn('eb:text-destructive', !template.html && 'eb:ml-auto')}\n disabled={!!busy}\n onClick={remove}\n aria-label={t('Delete')}\n title={t('Delete')}\n >\n {busy === 'delete' ? <Loader2 className=\"eb:animate-spin\" /> : <Trash2 />}\n {!template.html && t('Delete')}\n </Button>\n )}\n </div>\n )}\n </>\n ) : null;\n\n const previewPane =\n preview.html || preview.loading || preview.error ? (\n <EmailPreview html={preview.html} loading={preview.loading} error={preview.error} device={device} onDeviceChange={setDevice} />\n ) : (\n <div className=\"eb:flex eb:h-full eb:min-h-40 eb:items-center eb:justify-center eb:rounded-lg2 eb:border eb:border-dashed eb:border-border eb:p-6 eb:text-center eb:text-[13px] eb:text-foreground-lighter\">\n {t('The template preview appears here.')}\n </div>\n );\n\n const detailsColumn = (\n <div key=\"details\" className=\"eb:flex eb:min-h-0 eb:min-w-0 eb:flex-col\" data-testid=\"eb-library-details\">\n {layout === 'narrow' && (\n <div className=\"eb:shrink-0 eb:border-b eb:border-border eb:px-2 eb:py-1.5\">\n <Button variant=\"ghost\" size=\"sm-compact\" onClick={() => setDetailsOpen(false)}>\n <ArrowLeft />\n {t('All templates')}\n </Button>\n </div>\n )}\n <div className=\"eb:flex eb:min-h-0 eb:flex-1 eb:flex-col eb:gap-4 eb:overflow-y-auto eb:p-4\">\n {detailsBody}\n {layout !== 'wide' && meta && <div className=\"eb:h-[560px] eb:shrink-0\">{previewPane}</div>}\n </div>\n {actions && <div className=\"eb:flex eb:shrink-0 eb:flex-col eb:gap-2 eb:border-t eb:border-border eb:p-3\">{actions}</div>}\n </div>\n );\n\n return (\n <div\n ref={rootRef}\n className={cn('eb-root eb:relative eb:flex eb:h-full eb:min-h-0 eb:flex-col eb:bg-background eb:text-foreground', className)}\n data-testid=\"template-library\"\n data-layout={layout}\n >\n <header className=\"eb:flex eb:shrink-0 eb:items-center eb:gap-3 eb:border-b eb:border-border eb:px-5 eb:py-3.5\">\n <span className=\"eb:flex eb:size-10 eb:shrink-0 eb:items-center eb:justify-center eb:rounded-lg2 eb:bg-primary/10 eb:text-primary\">\n <LayoutTemplate className=\"eb:size-5\" />\n </span>\n <div className=\"eb:min-w-0 eb:flex-1\">\n <h2 className=\"eb:m-0 eb:truncate eb:text-base eb:font-normal eb:text-foreground\">{title ?? t('Email template library')}</h2>\n <p className=\"eb:m-0 eb:truncate eb:text-[13px] eb:text-foreground-muted\">\n {description ?? t('Ready-made templates to copy into your campaigns.')}\n </p>\n </div>\n {onClose && (\n <Button variant=\"ghost\" size=\"icon-sm\" onClick={onClose} aria-label={t('Close')}>\n <X />\n </Button>\n )}\n </header>\n\n {layout === 'narrow' ? (\n <div className=\"eb:grid eb:min-h-0 eb:flex-1 eb:grid-cols-[minmax(0,1fr)]\">\n {detailsOpen && selection ? detailsColumn : listColumn}\n </div>\n ) : (\n <div\n className={cn(\n 'eb:grid eb:min-h-0 eb:flex-1 eb:grid-rows-1 eb:divide-x eb:divide-border eb:overflow-hidden',\n layout === 'wide' ? 'eb:grid-cols-[290px_minmax(0,1fr)_minmax(0,1fr)]' : 'eb:grid-cols-[260px_minmax(0,1fr)]'\n )}\n >\n {listColumn}\n {detailsColumn}\n {layout === 'wide' && (\n <div className=\"eb:min-h-0 eb:min-w-0 eb:p-3\" data-testid=\"eb-library-preview\">\n {previewPane}\n </div>\n )}\n </div>\n )}\n <NoticeArea />\n </div>\n );\n}\n\n/**\n * @param {{\n * storage?: import('../../storage/client.js').TemplateStorage,\n * locale?: string,\n * messages?: Record<string, string>,\n * onNotify?: (level: 'success'|'info'|'warning'|'error', message: string) => void,\n * sender?: import('../../features/templates/starter.js').StarterSender,\n * templates?: import('../../features/templates/gallery.js').GalleryTemplate[],\n * title?: string,\n * description?: string,\n * selectedId?: string|number|null,\n * favoritesKey?: string|null,\n * onEdit?: (template: import('../../storage/client.js').StoredTemplate) => void,\n * onCreate?: (start: null | {baseId: string, mjml: string, meta: {subject?: string, preheader?: string}}) => void,\n * onClose?: () => void,\n * className?: string,\n * }} props\n */\nexport default function TemplateLibrary({ locale = 'en', messages, onNotify, ...props }) {\n return (\n <UiProvider locale={locale} messages={messages} onNotify={onNotify}>\n <Library {...props} />\n </UiProvider>\n );\n}\n"],"mappings":"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","names":["base","template"]}