best-email-builder 0.3.0 → 0.4.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.
- package/CHANGELOG.md +133 -0
- package/README.md +214 -19
- package/dist/builder.js +4 -4
- package/dist/{chunk-ZSUHOCCE.js → chunk-AT73Y7JA.js} +159 -83
- package/dist/chunk-AT73Y7JA.js.map +1 -0
- package/dist/chunk-HHLOE6OZ.js +1682 -0
- package/dist/chunk-HHLOE6OZ.js.map +1 -0
- package/dist/chunk-JNHFZXDL.js +1776 -0
- package/dist/chunk-JNHFZXDL.js.map +1 -0
- package/dist/chunk-MY6WPDT4.js +6110 -0
- package/dist/chunk-MY6WPDT4.js.map +1 -0
- package/dist/{chunk-JOC752I3.js → chunk-NAEGU6PR.js} +13 -4
- package/dist/{chunk-JOC752I3.js.map → chunk-NAEGU6PR.js.map} +1 -1
- package/dist/{chunk-BVOVAEEJ.js → chunk-TGDZ4NFI.js} +44 -3
- package/dist/chunk-TGDZ4NFI.js.map +1 -0
- package/dist/index.js +45 -8
- package/dist/index.js.map +1 -1
- package/dist/library.js +3 -3
- package/dist/server.js +544 -204
- package/dist/server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/blocks/shared.d.ts +20 -0
- package/dist/types/core/EmailEditor.d.ts +72 -19
- package/dist/types/core/constants.d.ts +1 -1
- package/dist/types/core/mjmlDefaults.d.ts +13 -0
- package/dist/types/core/scrollbars.d.ts +1 -1
- package/dist/types/defaultEditor.d.ts +4 -1
- package/dist/types/engine/NativeAdapter.d.ts +28 -0
- package/dist/types/engine/canvas/CanvasView.d.ts +88 -0
- package/dist/types/engine/canvas/canvasStyles.d.ts +3 -0
- package/dist/types/engine/canvas/icons.d.ts +14 -0
- package/dist/types/engine/canvas/morph.d.ts +21 -0
- package/dist/types/engine/canvasMjml.d.ts +30 -0
- package/dist/types/engine/mjml.d.ts +31 -0
- package/dist/types/engine/model.d.ts +130 -0
- package/dist/types/engine/placement.d.ts +69 -0
- package/dist/types/engine/richText.d.ts +15 -0
- package/dist/types/engine/unwrapModule.d.ts +29 -0
- package/dist/types/features/brand/BrandButton.d.ts +4 -0
- package/dist/types/features/brand/brand.d.ts +108 -0
- package/dist/types/features/brand/extract.d.ts +61 -0
- package/dist/types/features/brand/index.d.ts +2 -0
- package/dist/types/features/brand/messages.d.ts +13 -0
- package/dist/types/features/brand/useBrand.d.ts +22 -0
- package/dist/types/features/generate/GenerateButton.d.ts +3 -0
- package/dist/types/features/generate/RewriteText.d.ts +5 -0
- package/dist/types/features/generate/SubjectIdeas.d.ts +16 -0
- package/dist/types/features/generate/chip.d.ts +1 -0
- package/dist/types/features/generate/claude.d.ts +21 -0
- package/dist/types/features/generate/draft.d.ts +122 -0
- package/dist/types/features/generate/generator.d.ts +118 -0
- package/dist/types/features/generate/index.d.ts +2 -0
- package/dist/types/features/generate/prompt.d.ts +76 -0
- package/dist/types/features/generate/purposes.d.ts +48 -0
- package/dist/types/features/generate/rewrite.d.ts +83 -0
- package/dist/types/features/generate/subjects.d.ts +83 -0
- package/dist/types/features/preview/PreviewButton.d.ts +1 -1
- package/dist/types/features/preview/darkMode.d.ts +48 -0
- package/dist/types/index.d.ts +10 -1
- package/dist/types/server/brand.d.ts +32 -0
- package/dist/types/server/brandImport.d.ts +93 -0
- package/dist/types/server/compileMjml.d.ts +2 -0
- package/dist/types/server/handler.d.ts +25 -0
- package/dist/types/server/index.d.ts +11 -0
- package/dist/types/storage/client.d.ts +75 -3
- package/dist/types/ui/fields.d.ts +2 -1
- package/dist/types/ui/primitives/button.d.ts +1 -1
- package/dist/types/ui/primitives/color-picker.d.ts +2 -1
- package/dist/types/ui/useLayoutWidth.d.ts +1 -1
- package/package.json +13 -7
- package/dist/chunk-3M36VVXS.js +0 -665
- package/dist/chunk-3M36VVXS.js.map +0 -1
- package/dist/chunk-57CTQNTK.js +0 -56
- package/dist/chunk-57CTQNTK.js.map +0 -1
- package/dist/chunk-BVOVAEEJ.js.map +0 -1
- package/dist/chunk-ILRAQTHS.js +0 -3677
- package/dist/chunk-ILRAQTHS.js.map +0 -1
- package/dist/chunk-ZSUHOCCE.js.map +0 -1
- package/dist/types/adapters/grapesjs/GrapesJsAdapter.d.ts +0 -33
- package/dist/types/adapters/grapesjs/engineConfig.d.ts +0 -148
- package/dist/types/adapters/grapesjs/unwrapModule.d.ts +0 -18
|
@@ -9,10 +9,15 @@ import {
|
|
|
9
9
|
buildGallery,
|
|
10
10
|
cn,
|
|
11
11
|
createHttpStorage,
|
|
12
|
+
useBrand,
|
|
12
13
|
useLayoutWidth,
|
|
13
14
|
useUi,
|
|
14
15
|
withPlaceholderImages
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-AT73Y7JA.js";
|
|
17
|
+
import {
|
|
18
|
+
brandTemplate,
|
|
19
|
+
brandedSender
|
|
20
|
+
} from "./chunk-HHLOE6OZ.js";
|
|
16
21
|
|
|
17
22
|
// src/ui/library/TemplateLibrary.jsx
|
|
18
23
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
@@ -123,9 +128,13 @@ function Library({
|
|
|
123
128
|
}) {
|
|
124
129
|
const { t, locale, notify, confirm } = useUi();
|
|
125
130
|
const storage = useMemo(() => injectedStorage || createHttpStorage(), [injectedStorage]);
|
|
131
|
+
const { brand } = useBrand(storage);
|
|
126
132
|
const bases = useMemo(
|
|
127
|
-
() => buildGallery({ templates: galleryProp ?? TEMPLATE_GALLERY, sender: sender
|
|
128
|
-
|
|
133
|
+
() => buildGallery({ templates: galleryProp ?? TEMPLATE_GALLERY, sender: brandedSender(sender, brand), t }).map((base2) => ({
|
|
134
|
+
...base2,
|
|
135
|
+
mjml: brandTemplate(base2.mjml, brand, { sender, t })
|
|
136
|
+
})),
|
|
137
|
+
[galleryProp, sender, brand, t]
|
|
129
138
|
);
|
|
130
139
|
const dateFormat = useMemo(() => new Intl.DateTimeFormat(locale, DATE_FORMAT), [locale]);
|
|
131
140
|
const formatDate = useCallback((at) => at ? dateFormat.format(new Date(at)) : "", [dateFormat]);
|
|
@@ -526,4 +535,4 @@ function TemplateLibrary({ locale = "en", messages, onNotify, ...props }) {
|
|
|
526
535
|
export {
|
|
527
536
|
TemplateLibrary
|
|
528
537
|
};
|
|
529
|
-
//# sourceMappingURL=chunk-
|
|
538
|
+
//# sourceMappingURL=chunk-NAEGU6PR.js.map
|
|
@@ -1 +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"]}
|
|
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 { brandTemplate, brandedSender } from '../../features/brand/brand.js';\nimport { useBrand } from '../../features/brand/useBrand.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 { brand } = useBrand(storage);\n const bases = useMemo(\n () =>\n buildGallery({ templates: galleryProp ?? TEMPLATE_GALLERY, sender: brandedSender(sender, brand), t }).map((base) => ({\n ...base,\n mjml: brandTemplate(base.mjml, brand, { sender, t }),\n })),\n [galleryProp, sender, brand, 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"]}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EmailGenerationError
|
|
3
|
+
} from "./chunk-JNHFZXDL.js";
|
|
1
4
|
import {
|
|
2
5
|
DEFAULT_VARIABLES
|
|
3
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-HHLOE6OZ.js";
|
|
4
7
|
|
|
5
8
|
// src/features/variables/service.js
|
|
6
9
|
function escapeHtml(text) {
|
|
@@ -62,12 +65,50 @@ function sampleValues(variables = DEFAULT_VARIABLES, overrides = {}) {
|
|
|
62
65
|
return values;
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
// src/features/generate/claude.js
|
|
69
|
+
var DEFAULT_CLAUDE_MODEL = "claude-opus-5-5";
|
|
70
|
+
function claudeEmailModel({
|
|
71
|
+
client,
|
|
72
|
+
model = DEFAULT_CLAUDE_MODEL,
|
|
73
|
+
effort = "medium",
|
|
74
|
+
rewriteEffort = "low",
|
|
75
|
+
maxTokens = 32e3
|
|
76
|
+
} = (
|
|
77
|
+
/** @type {any} */
|
|
78
|
+
{}
|
|
79
|
+
)) {
|
|
80
|
+
if (!client?.messages?.stream) {
|
|
81
|
+
throw new Error("claudeEmailModel: pass an Anthropic client \u2014 `new Anthropic()` from @anthropic-ai/sdk");
|
|
82
|
+
}
|
|
83
|
+
return async ({ system, messages, schema, signal, task }) => {
|
|
84
|
+
const message = await client.messages.stream(
|
|
85
|
+
{
|
|
86
|
+
model,
|
|
87
|
+
max_tokens: maxTokens,
|
|
88
|
+
system,
|
|
89
|
+
messages,
|
|
90
|
+
output_config: { effort: task === "rewrite" || task === "subjects" ? rewriteEffort : effort, format: { type: "json_schema", schema } }
|
|
91
|
+
},
|
|
92
|
+
{ signal }
|
|
93
|
+
).finalMessage();
|
|
94
|
+
if (message.stop_reason === "refusal") {
|
|
95
|
+
throw new EmailGenerationError("REFUSED");
|
|
96
|
+
}
|
|
97
|
+
if (message.stop_reason === "max_tokens") {
|
|
98
|
+
throw new EmailGenerationError("TRUNCATED");
|
|
99
|
+
}
|
|
100
|
+
return message.content.filter((block) => block.type === "text").map((block) => block.text).join("");
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
65
104
|
export {
|
|
66
105
|
applyVariables,
|
|
67
106
|
applyVariablesToText,
|
|
68
107
|
mapVariables,
|
|
69
108
|
usedVariables,
|
|
70
109
|
unknownVariables,
|
|
71
|
-
sampleValues
|
|
110
|
+
sampleValues,
|
|
111
|
+
DEFAULT_CLAUDE_MODEL,
|
|
112
|
+
claudeEmailModel
|
|
72
113
|
};
|
|
73
|
-
//# sourceMappingURL=chunk-
|
|
114
|
+
//# sourceMappingURL=chunk-TGDZ4NFI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/features/variables/service.js","../src/features/generate/claude.js"],"sourcesContent":["// @ts-check\n/**\n * Variables service — replaces merge tags in compiled HTML (or in a subject).\n *\n * Only the tags of the catalog are touched, in their `{{key}}` or\n * `{{key|fallback}}` form (spaces allowed): other tags (an email provider's own\n * merge tags, `{{ mirror }}`) are left intact.\n *\n * Values are escaped in HTML: a first name \"<b>Jo</b>\" is displayed as typed,\n * it makes nothing bold — and cannot inject anything.\n *\n * Missing value → the tag's inline fallback → the variable's `fallback` → ''.\n * A raw tag never reaches a recipient.\n *\n * The unsubscribe link is NOT replaced here: only the sending pipeline knows\n * the right link (see `applyServiceLinks`).\n */\n\nimport { DEFAULT_VARIABLES } from './catalog.js';\n\n/** @typedef {import('./catalog.js').EmailVariable} EmailVariable */\n/** @typedef {Record<string, string|number|null|undefined>} VariableValues */\n\nfunction escapeHtml(text) {\n return String(text ?? '')\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n\n/** Undoes the escaping the editor applied to an inline fallback. */\nfunction decodeHtml(text) {\n return String(text ?? '')\n .replace(/ /g, ' ')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/&/g, '&');\n}\n\nconst escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n/** `{{ key }}` or `{{ key | fallback }}`. @param {string} key */\nfunction patternOf(key) {\n return new RegExp(`\\\\{\\\\{\\\\s*${escapeRegExp(key)}\\\\s*(?:\\\\|([^{}]*))?\\\\}\\\\}`, 'g');\n}\n\n/** Any `{{ … }}` tag that looks like a variable. */\nconst ANY_TAG = /\\{\\{\\s*([A-Za-z][\\w.]*)\\s*(?:\\|[^{}]*)?\\}\\}/g;\n\n/** @param {EmailVariable[]} variables */\nconst textVariables = (variables) => variables.filter((v) => v.kind !== 'link');\n\nconst present = (value) => value !== null && value !== undefined && String(value) !== '';\n\n/**\n * @param {string} text\n * @param {VariableValues} values\n * @param {EmailVariable[]} variables\n * @param {boolean} html\n */\nfunction replace(text, values, variables, html) {\n let out = String(text || '');\n for (const variable of textVariables(variables)) {\n out = out.replace(patternOf(variable.key), (_, inline) => {\n const value = values?.[variable.key];\n if (present(value)) return html ? escapeHtml(value) : String(value);\n const fallback = inline !== undefined ? (html ? decodeHtml(inline) : inline).trim() : variable.fallback || '';\n return html ? escapeHtml(fallback) : fallback;\n });\n }\n return out;\n}\n\n/**\n * Replaces the known tags with values (escaped) in HTML.\n *\n * @param {string} html\n * @param {VariableValues} values\n * @param {EmailVariable[]} [variables]\n */\nexport function applyVariables(html, values, variables = DEFAULT_VARIABLES) {\n return replace(html, values, variables, true);\n}\n\n/**\n * Same as `applyVariables` for PLAIN TEXT (subject line, preview text): no\n * HTML escaping.\n *\n * @param {string} text\n * @param {VariableValues} values\n * @param {EmailVariable[]} [variables]\n */\nexport function applyVariablesToText(text, values, variables = DEFAULT_VARIABLES) {\n return replace(text, values, variables, false);\n}\n\n/**\n * Replaces the known tags with RAW replacements — typically the merge tags of\n * an email provider (`{ 'contact.firstname': '{{ contact.FIRSTNAME }}' }`).\n * A tag without replacement is left untouched.\n *\n * @param {string} html\n * @param {Record<string, string>} mapping\n * @param {EmailVariable[]} [variables]\n */\nexport function mapVariables(html, mapping, variables = DEFAULT_VARIABLES) {\n let out = String(html || '');\n for (const variable of textVariables(variables)) {\n if (!Object.prototype.hasOwnProperty.call(mapping, variable.key)) continue;\n out = out.replace(patternOf(variable.key), () => String(mapping[variable.key]));\n }\n return out;\n}\n\n/**\n * Catalog keys present in a text.\n * @param {string} text\n * @param {EmailVariable[]} [variables]\n */\nexport function usedVariables(text, variables = DEFAULT_VARIABLES) {\n const source = String(text || '');\n return variables.filter((v) => patternOf(v.key).test(source)).map((v) => v.key);\n}\n\n/**\n * Tags that look like variables but are not in the catalog (typos such as\n * `{{contact.fistname}}`). `ignore` lists tags handled elsewhere.\n *\n * @param {string} text\n * @param {EmailVariable[]} [variables]\n * @param {string[]} [ignore]\n */\nexport function unknownVariables(text, variables = DEFAULT_VARIABLES, ignore = ['unsubscribe', 'mirror']) {\n const known = new Set([...variables.map((v) => v.key), ...ignore]);\n const found = new Set();\n for (const match of String(text || '').matchAll(ANY_TAG)) {\n if (!known.has(match[1])) found.add(match[1]);\n }\n return [...found];\n}\n\n/**\n * Sample values (preview, test), with optional overrides\n * (e.g. `{ 'contact.email': 'reviewer@example.com' }`).\n *\n * @param {EmailVariable[]} [variables]\n * @param {VariableValues} [overrides]\n */\nexport function sampleValues(variables = DEFAULT_VARIABLES, overrides = {}) {\n /** @type {Record<string, string>} */\n const values = Object.fromEntries(textVariables(variables).map((v) => [v.key, v.sample]));\n for (const [key, value] of Object.entries(overrides)) {\n if (present(value)) values[key] = String(value);\n }\n return values;\n}\n","// @ts-check\n/**\n * CLAUDE as the email model of `createEmailGenerator`.\n *\n * import Anthropic from '@anthropic-ai/sdk';\n * const model = claudeEmailModel({ client: new Anthropic() });\n *\n * The client is the host's own (`@anthropic-ai/sdk`, installed by the\n * application): its key, its proxy, its retries. Nothing here imports it.\n *\n * The answer comes as structured output (`output_config.format`): the JSON\n * schema of a draft, so the model cannot answer anything else. Streaming, read\n * to the end with `finalMessage()`: an email with the model's reasoning can\n * take a minute or more, longer than a plain request may stay open.\n */\n\nimport { EmailGenerationError } from './draft.js';\n\nexport const DEFAULT_CLAUDE_MODEL = 'claude-opus-5-5';\n\n/**\n * @param {{\n * client: any,\n * model?: string,\n * effort?: 'low'|'medium'|'high'|'xhigh'|'max',\n * rewriteEffort?: 'low'|'medium'|'high'|'xhigh'|'max',\n * maxTokens?: number,\n * }} options\n * `effort`: how much the model thinks before writing an email — `low`\n * answers faster, `high` takes more care. `rewriteEffort`: the same for\n * the light jobs the user waits on — one text rewritten, subject ideas.\n * @returns {import('./generator.js').EmailModel}\n */\nexport function claudeEmailModel({\n client,\n model = DEFAULT_CLAUDE_MODEL,\n effort = 'medium',\n rewriteEffort = 'low',\n maxTokens = 32000,\n} = /** @type {any} */ ({})) {\n if (!client?.messages?.stream) {\n throw new Error('claudeEmailModel: pass an Anthropic client — `new Anthropic()` from @anthropic-ai/sdk');\n }\n return async ({ system, messages, schema, signal, task }) => {\n const message = await client.messages\n .stream(\n {\n model,\n max_tokens: maxTokens,\n system,\n messages,\n output_config: { effort: task === 'rewrite' || task === 'subjects' ? rewriteEffort : effort, format: { type: 'json_schema', schema } },\n },\n { signal }\n )\n .finalMessage();\n if (message.stop_reason === 'refusal') {\n throw new EmailGenerationError('REFUSED');\n }\n if (message.stop_reason === 'max_tokens') {\n throw new EmailGenerationError('TRUNCATED');\n }\n return message.content\n .filter((/** @type {any} */ block) => block.type === 'text')\n .map((/** @type {any} */ block) => block.text)\n .join('');\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
|
+
EMPTY_MJML,
|
|
3
|
+
ENGINE_NAME,
|
|
2
4
|
EmailBuilder,
|
|
3
5
|
EmailBuilderBoundary,
|
|
4
6
|
EmailCanvas,
|
|
5
7
|
createDefaultEmailEditor,
|
|
6
8
|
createEmailEditor,
|
|
7
|
-
|
|
9
|
+
createNativeAdapter,
|
|
8
10
|
createRegistry,
|
|
9
11
|
emailBlocks,
|
|
10
12
|
emailBuilderFeatures,
|
|
11
13
|
isFeatureEnabled
|
|
12
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-MY6WPDT4.js";
|
|
13
15
|
import {
|
|
14
16
|
TemplateLibrary
|
|
15
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-NAEGU6PR.js";
|
|
16
18
|
import {
|
|
17
19
|
STARTER_MJML,
|
|
18
20
|
StorageError,
|
|
@@ -25,14 +27,24 @@ import {
|
|
|
25
27
|
useT,
|
|
26
28
|
useUi,
|
|
27
29
|
withPlaceholderImages
|
|
28
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-AT73Y7JA.js";
|
|
29
31
|
import {
|
|
32
|
+
DEFAULT_CLAUDE_MODEL,
|
|
30
33
|
applyVariables,
|
|
31
34
|
applyVariablesToText,
|
|
35
|
+
claudeEmailModel,
|
|
32
36
|
unknownVariables,
|
|
33
37
|
usedVariables
|
|
34
|
-
} from "./chunk-
|
|
35
|
-
import
|
|
38
|
+
} from "./chunk-TGDZ4NFI.js";
|
|
39
|
+
import {
|
|
40
|
+
EMAIL_PURPOSES,
|
|
41
|
+
EmailGenerationError,
|
|
42
|
+
PURPOSE_CATEGORIES,
|
|
43
|
+
REWRITE_ACTIONS,
|
|
44
|
+
REWRITE_LANGUAGES,
|
|
45
|
+
createEmailGenerator,
|
|
46
|
+
emailWords
|
|
47
|
+
} from "./chunk-JNHFZXDL.js";
|
|
36
48
|
import {
|
|
37
49
|
CUSTOM_GROUP,
|
|
38
50
|
DEFAULT_ASSETS_PATH,
|
|
@@ -43,18 +55,25 @@ import {
|
|
|
43
55
|
EmailDocumentError,
|
|
44
56
|
LOCALES,
|
|
45
57
|
absoluteAssetUrls,
|
|
58
|
+
applyBrand,
|
|
59
|
+
brandedSender,
|
|
60
|
+
cleanBrand,
|
|
46
61
|
createDocument,
|
|
47
62
|
createTranslator,
|
|
48
63
|
defineVariables,
|
|
49
64
|
isAssetPath,
|
|
50
65
|
keyFromLabel,
|
|
51
66
|
mergeVariables,
|
|
67
|
+
parseMjml,
|
|
68
|
+
parseMjmlFragment,
|
|
52
69
|
readDocument,
|
|
53
70
|
serializeDocument,
|
|
71
|
+
serializeMjml,
|
|
72
|
+
simulateDarkMode,
|
|
54
73
|
tokenOf,
|
|
55
74
|
translatorFor,
|
|
56
75
|
validateCustomVariable
|
|
57
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-HHLOE6OZ.js";
|
|
58
77
|
|
|
59
78
|
// src/ui/EmailStudio.jsx
|
|
60
79
|
import { useMemo, useState } from "react";
|
|
@@ -115,43 +134,61 @@ function EmailStudio({
|
|
|
115
134
|
export {
|
|
116
135
|
CUSTOM_GROUP,
|
|
117
136
|
DEFAULT_ASSETS_PATH,
|
|
137
|
+
DEFAULT_CLAUDE_MODEL,
|
|
118
138
|
DEFAULT_VARIABLES,
|
|
119
139
|
EMAIL_BUILDER_VERSION,
|
|
120
140
|
EMAIL_DEVICES,
|
|
121
141
|
EMAIL_DOCUMENT_SCHEMA_VERSION,
|
|
142
|
+
EMAIL_PURPOSES,
|
|
143
|
+
EMPTY_MJML,
|
|
144
|
+
ENGINE_NAME,
|
|
122
145
|
EmailBuilder,
|
|
123
146
|
EmailBuilderBoundary,
|
|
124
147
|
EmailCanvas,
|
|
125
148
|
EmailDocumentError,
|
|
149
|
+
EmailGenerationError,
|
|
126
150
|
EmailStudio,
|
|
127
151
|
LOCALES,
|
|
152
|
+
PURPOSE_CATEGORIES,
|
|
153
|
+
REWRITE_ACTIONS,
|
|
154
|
+
REWRITE_LANGUAGES,
|
|
128
155
|
STARTER_MJML,
|
|
129
156
|
StorageError,
|
|
130
157
|
TEMPLATE_GALLERY,
|
|
131
158
|
TemplateLibrary,
|
|
132
159
|
UiProvider,
|
|
133
160
|
absoluteAssetUrls,
|
|
161
|
+
applyBrand,
|
|
134
162
|
applyVariables,
|
|
135
163
|
applyVariablesToText,
|
|
164
|
+
brandedSender,
|
|
136
165
|
buildGallery,
|
|
166
|
+
claudeEmailModel,
|
|
167
|
+
cleanBrand,
|
|
137
168
|
createDefaultEmailEditor,
|
|
138
169
|
createDocument,
|
|
139
170
|
createEmailEditor,
|
|
140
|
-
|
|
171
|
+
createEmailGenerator,
|
|
141
172
|
createHttpStorage,
|
|
142
173
|
createMemoryStorage,
|
|
174
|
+
createNativeAdapter,
|
|
143
175
|
createRegistry,
|
|
144
176
|
createStarterMjml,
|
|
145
177
|
createTranslator,
|
|
146
178
|
defineVariables,
|
|
147
179
|
emailBlocks,
|
|
148
180
|
emailBuilderFeatures,
|
|
181
|
+
emailWords,
|
|
149
182
|
isAssetPath,
|
|
150
183
|
isFeatureEnabled,
|
|
151
184
|
keyFromLabel,
|
|
152
185
|
mergeVariables,
|
|
186
|
+
parseMjml,
|
|
187
|
+
parseMjmlFragment,
|
|
153
188
|
readDocument,
|
|
154
189
|
serializeDocument,
|
|
190
|
+
serializeMjml,
|
|
191
|
+
simulateDarkMode,
|
|
155
192
|
tokenOf,
|
|
156
193
|
translatorFor,
|
|
157
194
|
unknownVariables,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/ui/EmailStudio.jsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { createHttpStorage } from '../storage/client.js';\nimport EmailBuilder from './EmailBuilder.jsx';\nimport TemplateLibrary from './library/TemplateLibrary.jsx';\n\n/**\n * EMAIL STUDIO — the two screens wired together.\n *\n * TemplateLibrary ── Edit / New template / Start from this base ──▶ EmailBuilder\n * ▲ │\n * └──────────────────────────── Back ───────────────────────────┘\n *\n * Nothing here that a host could not write itself: both screens are exported\n * on their own, and an application that only wants the editor mounts\n * `EmailBuilder` alone (or the library alone, opening its own editor).\n *\n * Shared props go to both screens — except `templates` (the bases), which\n * only the library shows: the editor gets `[]`, so no second gallery replaces\n * the email being edited (`builderProps.templates` brings it back).\n * `libraryProps` / `builderProps` reach only one. Coming back from the editor, the library selects the template that was\n * just edited or created.\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 * onClose?: () => void,\n * libraryProps?: object,\n * builderProps?: object,\n * className?: string,\n * }} props `onClose`: shows the library's close button.\n */\nexport default function EmailStudio({\n storage: injectedStorage,\n locale = 'en',\n messages,\n onNotify,\n sender,\n templates,\n onClose,\n libraryProps,\n builderProps,\n className,\n}) {\n const storage = useMemo(() => injectedStorage || createHttpStorage(), [injectedStorage]);\n /** null = library; else the editor session: {key, templateId, start}. */\n const [session, setSession] = useState(null);\n const [lastId, setLastId] = useState(null);\n const shared = { storage, locale, messages, onNotify, sender, templates, className };\n\n if (session) {\n return (\n <EmailBuilder\n key={session.key}\n {...shared}\n // The bases are picked in the bank: no second gallery in the editor,\n // where picking one would replace the email being edited.\n templates={[]}\n {...builderProps}\n templateId={session.templateId}\n initialMjml={session.start?.mjml}\n initialMeta={session.start?.meta}\n onSaved={(template) => {\n if (template?.id != null) setLastId(template.id);\n builderProps?.onSaved?.(template);\n }}\n onClose={() => {\n setSession(null);\n builderProps?.onClose?.();\n }}\n />\n );\n }\n\n return (\n <TemplateLibrary\n {...shared}\n {...libraryProps}\n selectedId={lastId}\n onClose={onClose}\n onEdit={(template) => {\n setLastId(template.id);\n setSession({ key: Date.now(), templateId: template.id, start: null });\n }}\n onCreate={(start) => setSession({ key: Date.now(), templateId: null, start })}\n />\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/ui/EmailStudio.jsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { createHttpStorage } from '../storage/client.js';\nimport EmailBuilder from './EmailBuilder.jsx';\nimport TemplateLibrary from './library/TemplateLibrary.jsx';\n\n/**\n * EMAIL STUDIO — the two screens wired together.\n *\n * TemplateLibrary ── Edit / New template / Start from this base ──▶ EmailBuilder\n * ▲ │\n * └──────────────────────────── Back ───────────────────────────┘\n *\n * Nothing here that a host could not write itself: both screens are exported\n * on their own, and an application that only wants the editor mounts\n * `EmailBuilder` alone (or the library alone, opening its own editor).\n *\n * Shared props go to both screens — except `templates` (the bases), which\n * only the library shows: the editor gets `[]`, so no second gallery replaces\n * the email being edited (`builderProps.templates` brings it back).\n * `libraryProps` / `builderProps` reach only one. Coming back from the editor, the library selects the template that was\n * just edited or created.\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 * onClose?: () => void,\n * libraryProps?: object,\n * builderProps?: object,\n * className?: string,\n * }} props `onClose`: shows the library's close button.\n */\nexport default function EmailStudio({\n storage: injectedStorage,\n locale = 'en',\n messages,\n onNotify,\n sender,\n templates,\n onClose,\n libraryProps,\n builderProps,\n className,\n}) {\n const storage = useMemo(() => injectedStorage || createHttpStorage(), [injectedStorage]);\n /** null = library; else the editor session: {key, templateId, start}. */\n const [session, setSession] = useState(null);\n const [lastId, setLastId] = useState(null);\n const shared = { storage, locale, messages, onNotify, sender, templates, className };\n\n if (session) {\n return (\n <EmailBuilder\n key={session.key}\n {...shared}\n // The bases are picked in the bank: no second gallery in the editor,\n // where picking one would replace the email being edited.\n templates={[]}\n {...builderProps}\n templateId={session.templateId}\n initialMjml={session.start?.mjml}\n initialMeta={session.start?.meta}\n onSaved={(template) => {\n if (template?.id != null) setLastId(template.id);\n builderProps?.onSaved?.(template);\n }}\n onClose={() => {\n setSession(null);\n builderProps?.onClose?.();\n }}\n />\n );\n }\n\n return (\n <TemplateLibrary\n {...shared}\n {...libraryProps}\n selectedId={lastId}\n onClose={onClose}\n onEdit={(template) => {\n setLastId(template.id);\n setSession({ key: Date.now(), templateId: template.id, start: null });\n }}\n onCreate={(start) => setSession({ key: Date.now(), templateId: null, start })}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,gBAAgB;AAuD5B;AApBS,SAAR,YAA6B;AAAA,EAClC,SAAS;AAAA,EACT,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAG;AACD,QAAM,UAAU,QAAQ,MAAM,mBAAmB,kBAAkB,GAAG,CAAC,eAAe,CAAC;AAEvF,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,IAAI;AACzC,QAAM,SAAS,EAAE,SAAS,QAAQ,UAAU,UAAU,QAAQ,WAAW,UAAU;AAEnF,MAAI,SAAS;AACX,WACE;AAAA,MAAC;AAAA;AAAA,QAEE,GAAG;AAAA,QAGJ,WAAW,CAAC;AAAA,QACX,GAAG;AAAA,QACJ,YAAY,QAAQ;AAAA,QACpB,aAAa,QAAQ,OAAO;AAAA,QAC5B,aAAa,QAAQ,OAAO;AAAA,QAC5B,SAAS,CAAC,aAAa;AACrB,cAAI,UAAU,MAAM,KAAM,WAAU,SAAS,EAAE;AAC/C,wBAAc,UAAU,QAAQ;AAAA,QAClC;AAAA,QACA,SAAS,MAAM;AACb,qBAAW,IAAI;AACf,wBAAc,UAAU;AAAA,QAC1B;AAAA;AAAA,MAhBK,QAAQ;AAAA,IAiBf;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACH,GAAG;AAAA,MACJ,YAAY;AAAA,MACZ;AAAA,MACA,QAAQ,CAAC,aAAa;AACpB,kBAAU,SAAS,EAAE;AACrB,mBAAW,EAAE,KAAK,KAAK,IAAI,GAAG,YAAY,SAAS,IAAI,OAAO,KAAK,CAAC;AAAA,MACtE;AAAA,MACA,UAAU,CAAC,UAAU,WAAW,EAAE,KAAK,KAAK,IAAI,GAAG,YAAY,MAAM,MAAM,CAAC;AAAA;AAAA,EAC9E;AAEJ;","names":[]}
|
package/dist/library.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TemplateLibrary
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NAEGU6PR.js";
|
|
4
4
|
import {
|
|
5
5
|
StorageError,
|
|
6
6
|
TEMPLATE_GALLERY,
|
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
createHttpStorage,
|
|
9
9
|
createMemoryStorage,
|
|
10
10
|
withPlaceholderImages
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-AT73Y7JA.js";
|
|
12
12
|
import {
|
|
13
13
|
EMAIL_BUILDER_VERSION,
|
|
14
14
|
LOCALES,
|
|
15
15
|
createTranslator,
|
|
16
16
|
translatorFor
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-HHLOE6OZ.js";
|
|
18
18
|
export {
|
|
19
19
|
EMAIL_BUILDER_VERSION,
|
|
20
20
|
LOCALES,
|