best-email-builder 0.2.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 +186 -0
- package/README.md +309 -12
- package/dist/builder.js +44 -0
- package/dist/builder.js.map +1 -0
- package/dist/chunk-AT73Y7JA.js +1427 -0
- 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-NAEGU6PR.js +538 -0
- package/dist/chunk-NAEGU6PR.js.map +1 -0
- package/dist/chunk-TGDZ4NFI.js +114 -0
- package/dist/chunk-TGDZ4NFI.js.map +1 -0
- package/dist/index.js +124 -4866
- package/dist/index.js.map +1 -1
- package/dist/library.js +31 -0
- package/dist/library.js.map +1 -0
- package/dist/server.js +554 -210
- 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/builder.d.ts +8 -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 +13 -0
- package/dist/types/defaultEditor.d.ts +11 -0
- 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/features/templates/gallery.d.ts +7 -0
- package/dist/types/index.d.ts +14 -10
- package/dist/types/library.d.ts +5 -0
- 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 +79 -3
- package/dist/types/ui/EmailStudio.d.ts +42 -0
- package/dist/types/ui/fields.d.ts +2 -1
- package/dist/types/ui/library/TemplateLibrary.d.ts +41 -0
- package/dist/types/ui/primitives/button.d.ts +2 -2
- package/dist/types/ui/primitives/color-picker.d.ts +2 -1
- package/dist/types/ui/useLayoutWidth.d.ts +1 -1
- package/package.json +27 -4
- package/dist/chunk-MTALS5GC.js +0 -747
- package/dist/chunk-MTALS5GC.js.map +0 -1
- package/dist/types/adapters/grapesjs/GrapesJsAdapter.d.ts +0 -33
- package/dist/types/adapters/grapesjs/engineConfig.d.ts +0 -162
- package/dist/types/adapters/grapesjs/unwrapModule.d.ts +0 -18
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The words of an email, as a reader sees them: the head, the styles and the
|
|
3
|
+
* markup left out.
|
|
4
|
+
* @param {string} mjml
|
|
5
|
+
*/
|
|
6
|
+
export function emailWords(mjml: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* The request as the model sees it, or an error when there is nothing to
|
|
9
|
+
* draw subjects from.
|
|
10
|
+
* @param {SubjectsInput} input
|
|
11
|
+
*/
|
|
12
|
+
export function cleanSubjectsInput(input: SubjectsInput): {
|
|
13
|
+
subject: string;
|
|
14
|
+
preheader: string;
|
|
15
|
+
text: string;
|
|
16
|
+
language: string;
|
|
17
|
+
variables: {
|
|
18
|
+
key: string;
|
|
19
|
+
label: string;
|
|
20
|
+
}[];
|
|
21
|
+
avoid: string[];
|
|
22
|
+
};
|
|
23
|
+
/** @param {ReturnType<typeof cleanSubjectsInput>} request */
|
|
24
|
+
export function buildSubjectsPrompt({ subject, preheader, text, language, variables, avoid }: ReturnType<typeof cleanSubjectsInput>): string;
|
|
25
|
+
/** The subjects in a model's answer: the JSON field, else one per line. @param {string} answer */
|
|
26
|
+
export function parseSubjects(answer: string): any;
|
|
27
|
+
/**
|
|
28
|
+
* The usable subjects, and what is wrong with the others (for the model).
|
|
29
|
+
* @param {string[]} subjects @param {ReturnType<typeof cleanSubjectsInput>} request
|
|
30
|
+
*/
|
|
31
|
+
export function reviewSubjects(subjects: string[], request: ReturnType<typeof cleanSubjectsInput>): {
|
|
32
|
+
usable: string[];
|
|
33
|
+
problems: string[];
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* @param {{model: import('./generator.js').EmailModel, maxAttempts?: number}} options
|
|
37
|
+
* @returns {(input: SubjectsInput, options?: {signal?: AbortSignal}) => Promise<{subjects: string[], attempts: number}>}
|
|
38
|
+
*/
|
|
39
|
+
export function createSubjectSuggester({ model, maxAttempts }: {
|
|
40
|
+
model: import("./generator.js").EmailModel;
|
|
41
|
+
maxAttempts?: number;
|
|
42
|
+
}): (input: SubjectsInput, options?: {
|
|
43
|
+
signal?: AbortSignal;
|
|
44
|
+
}) => Promise<{
|
|
45
|
+
subjects: string[];
|
|
46
|
+
attempts: number;
|
|
47
|
+
}>;
|
|
48
|
+
export const SUBJECT_COUNT: 3;
|
|
49
|
+
export const MAX_SUBJECT_LENGTH: 150;
|
|
50
|
+
export const MAX_EMAIL_WORDS: 4000;
|
|
51
|
+
export namespace SUBJECTS_SCHEMA {
|
|
52
|
+
let type: string;
|
|
53
|
+
namespace properties {
|
|
54
|
+
namespace subjects {
|
|
55
|
+
let type_1: string;
|
|
56
|
+
export { type_1 as type };
|
|
57
|
+
export let description: string;
|
|
58
|
+
export namespace items {
|
|
59
|
+
let type_2: string;
|
|
60
|
+
export { type_2 as type };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
let required: string[];
|
|
65
|
+
let additionalProperties: boolean;
|
|
66
|
+
}
|
|
67
|
+
export const SUBJECTS_SYSTEM_PROMPT: "You write email subject lines. You get an email (its current subject, its preview text and its words) and answer three NEW subject lines for it, in the JSON field \"subjects\".\n\nEach subject:\n- tells what the email brings, specific to its content: under 50 characters when possible, 70 at most;\n- takes its own angle: one clear and direct, one that makes the reader curious, one personal or timely;\n- is written in the language of the email;\n- invents no facts: no price, discount, date, number, name or promise the email does not contain;\n- is plain text: no HTML, no quotation marks around it, no line break;\n- has no word in capitals, at most one \"!\", no spam wording (\"free!!!\", \"act now\", \"100% guaranteed\");\n- has no emoji, unless the current subject has one;\n- differs from the current subject and from the two others, and does not repeat the preview text.\n\nMerge tags: you may personalise ONE of the three with a merge tag from the list you are given, written exactly as listed, e.g. {{contact.firstname}}. No other merge tag.";
|
|
68
|
+
/**
|
|
69
|
+
* `text`: the words of the email (`emailWords`); `language`: the one of the
|
|
70
|
+
* interface, used when the email has no words yet; `avoid`: ideas already
|
|
71
|
+
* shown ("More ideas" asks for new ones).
|
|
72
|
+
*/
|
|
73
|
+
export type SubjectsInput = {
|
|
74
|
+
subject?: string;
|
|
75
|
+
preheader?: string;
|
|
76
|
+
text?: string;
|
|
77
|
+
language?: string;
|
|
78
|
+
variables?: {
|
|
79
|
+
key: string;
|
|
80
|
+
label?: string;
|
|
81
|
+
}[];
|
|
82
|
+
avoid?: string[];
|
|
83
|
+
};
|
|
@@ -2,7 +2,7 @@ export function QualityList({ quality, warnings }: {
|
|
|
2
2
|
quality?: never[] | undefined;
|
|
3
3
|
warnings?: never[] | undefined;
|
|
4
4
|
}): import("react").JSX.Element;
|
|
5
|
-
/** Sandboxed preview frame, desktop or mobile width. */
|
|
5
|
+
/** Sandboxed preview frame, desktop or mobile width, light or dark. */
|
|
6
6
|
export function EmailPreview({ html, loading, error, device, onDeviceChange, onRefresh, hint }: {
|
|
7
7
|
html: any;
|
|
8
8
|
loading?: boolean | undefined;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** @typedef {{r: number, g: number, b: number, a: number}} Rgba channels 0–255, alpha 0–1 */
|
|
2
|
+
/** @param {string} token @returns {Rgba|null} */
|
|
3
|
+
export function parseCssColor(token: string): Rgba | null;
|
|
4
|
+
/** WCAG relative luminance, 0 (black) to 1 (white). @param {Rgba} color */
|
|
5
|
+
export function luminance({ r, g, b }: Rgba): number;
|
|
6
|
+
/**
|
|
7
|
+
* One color in dark mode. `surface`: a background or a border; otherwise a
|
|
8
|
+
* text color. Unknown values (`transparent`, `inherit`…) come back as given.
|
|
9
|
+
* @param {string} token @param {'surface'|'text'} role
|
|
10
|
+
*/
|
|
11
|
+
export function darkColor(token: string, role: "surface" | "text"): string;
|
|
12
|
+
/** The colors of a CSS text (a sheet, or a `style` attribute) in dark mode. @param {string} css */
|
|
13
|
+
export function darkCss(css: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* A compiled email as a dark-mode app would show it.
|
|
16
|
+
* @param {string} html
|
|
17
|
+
*/
|
|
18
|
+
export function simulateDarkMode(html: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* DARK MODE, simulated — an email as the mail apps that repaint it in dark
|
|
21
|
+
* mode show it (Gmail and Outlook apps, Outlook.com): light backgrounds
|
|
22
|
+
* turned dark, dark text turned light, everything else left as it is.
|
|
23
|
+
*
|
|
24
|
+
* surfaces background and border colors (`background`, `bgcolor`,
|
|
25
|
+
* `border-color`…): a LIGHT one becomes dark, same hue; a dark
|
|
26
|
+
* or saturated one (a brand button) stays.
|
|
27
|
+
* text `color`: a DARK one becomes light, same hue; a light one
|
|
28
|
+
* (white on a button) stays.
|
|
29
|
+
* images untouched: a dark logo on a transparent background disappears
|
|
30
|
+
* here as it does in those apps — what the preview is for.
|
|
31
|
+
*
|
|
32
|
+
* Works on the compiled HTML as a string: the colors of `style` attributes,
|
|
33
|
+
* of `<style>` sheets and of `bgcolor`. No script, no DOM: the preview frame
|
|
34
|
+
* stays sandboxed.
|
|
35
|
+
*
|
|
36
|
+
* Dependency-free: runs in the browser, on the server and in unit tests.
|
|
37
|
+
*/
|
|
38
|
+
/** The page behind the email, and its default text, in dark apps. */
|
|
39
|
+
export const DARK_PAGE: "#121212";
|
|
40
|
+
/**
|
|
41
|
+
* channels 0–255, alpha 0–1
|
|
42
|
+
*/
|
|
43
|
+
export type Rgba = {
|
|
44
|
+
r: number;
|
|
45
|
+
g: number;
|
|
46
|
+
b: number;
|
|
47
|
+
a: number;
|
|
48
|
+
};
|
|
@@ -22,6 +22,13 @@ export function buildGallery({ templates, sender, t }?: {
|
|
|
22
22
|
* @param {string} mjml
|
|
23
23
|
*/
|
|
24
24
|
export function withThumbnailImages(mjml: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Compiled HTML for a thumbnail or a preview: an image not chosen yet (an
|
|
27
|
+
* `<img>` with no `src`) gets the same grey frame. For HTML that went through
|
|
28
|
+
* the server's validator, which rightly refuses `data:` images in MJML.
|
|
29
|
+
* @param {string} html
|
|
30
|
+
*/
|
|
31
|
+
export function withPlaceholderImages(html: string): string;
|
|
25
32
|
/** @type {GalleryTemplate[]} */
|
|
26
33
|
export const TEMPLATE_GALLERY: GalleryTemplate[];
|
|
27
34
|
export type StarterSender = import("./starter.js").StarterSender;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,26 +1,30 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* An editor wired to the default engine and the built-in registries.
|
|
3
|
-
*
|
|
4
|
-
* @param {{locale?: string}} [options] `locale`: engine UI locale (`'en'` by default)
|
|
5
|
-
*/
|
|
6
|
-
export function createDefaultEmailEditor(options?: {
|
|
7
|
-
locale?: string;
|
|
8
|
-
}): import("./core/EmailEditor.js").EmailEditor;
|
|
9
1
|
export { createEmailEditor } from "./core/EmailEditor.js";
|
|
10
2
|
export { emailBlocks } from "./blocks/index.js";
|
|
3
|
+
export { createDefaultEmailEditor } from "./defaultEditor.js";
|
|
11
4
|
export { default as EmailBuilder } from "./ui/EmailBuilder.jsx";
|
|
5
|
+
export { default as TemplateLibrary } from "./ui/library/TemplateLibrary.jsx";
|
|
6
|
+
export { default as EmailStudio } from "./ui/EmailStudio.jsx";
|
|
12
7
|
export { default as EmailBuilderBoundary } from "./ui/EmailBuilderBoundary.jsx";
|
|
13
8
|
export { default as EmailCanvas } from "./ui/EmailCanvas.jsx";
|
|
14
|
-
export { createGrapesJsAdapter } from "./adapters/grapesjs/GrapesJsAdapter.js";
|
|
15
9
|
export { createRegistry } from "./core/registry.js";
|
|
10
|
+
export { createEmailGenerator } from "./features/generate/generator.js";
|
|
11
|
+
export { EmailGenerationError } from "./features/generate/draft.js";
|
|
12
|
+
export { emailWords } from "./features/generate/subjects.js";
|
|
13
|
+
export { simulateDarkMode } from "./features/preview/darkMode.js";
|
|
16
14
|
export { emailBuilderFeatures, isFeatureEnabled } from "./features/index.js";
|
|
17
15
|
export { UiProvider, useUi, useT } from "./ui/context.jsx";
|
|
18
16
|
export { createHttpStorage, createMemoryStorage, StorageError } from "./storage/client.js";
|
|
17
|
+
export { createNativeAdapter, ENGINE_NAME, EMPTY_MJML } from "./engine/NativeAdapter.js";
|
|
18
|
+
export { parseMjml, parseMjmlFragment, serializeMjml } from "./engine/mjml.js";
|
|
19
19
|
export { readDocument, createDocument, serializeDocument, EmailDocumentError } from "./core/document.js";
|
|
20
20
|
export { EMAIL_BUILDER_VERSION, EMAIL_DOCUMENT_SCHEMA_VERSION, EMAIL_DEVICES } from "./core/constants.js";
|
|
21
21
|
export { DEFAULT_VARIABLES, CUSTOM_GROUP, defineVariables, mergeVariables, keyFromLabel, validateCustomVariable, tokenOf } from "./features/variables/catalog.js";
|
|
22
22
|
export { applyVariables, applyVariablesToText, usedVariables, unknownVariables } from "./features/variables/service.js";
|
|
23
23
|
export { createStarterMjml, STARTER_MJML } from "./features/templates/starter.js";
|
|
24
|
-
export { TEMPLATE_GALLERY, buildGallery } from "./features/templates/gallery.js";
|
|
24
|
+
export { TEMPLATE_GALLERY, buildGallery, withPlaceholderImages } from "./features/templates/gallery.js";
|
|
25
25
|
export { isAssetPath, absoluteAssetUrls, DEFAULT_ASSETS_PATH } from "./renderer/links.js";
|
|
26
|
+
export { claudeEmailModel, DEFAULT_CLAUDE_MODEL } from "./features/generate/claude.js";
|
|
27
|
+
export { EMAIL_PURPOSES, PURPOSE_CATEGORIES } from "./features/generate/purposes.js";
|
|
28
|
+
export { REWRITE_ACTIONS, REWRITE_LANGUAGES } from "./features/generate/rewrite.js";
|
|
29
|
+
export { applyBrand, cleanBrand, brandedSender } from "./features/brand/brand.js";
|
|
26
30
|
export { createTranslator, translatorFor, LOCALES } from "./i18n/index.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as TemplateLibrary } from "./ui/library/TemplateLibrary.jsx";
|
|
2
|
+
export { EMAIL_BUILDER_VERSION } from "./core/constants.js";
|
|
3
|
+
export { createHttpStorage, createMemoryStorage, StorageError } from "./storage/client.js";
|
|
4
|
+
export { TEMPLATE_GALLERY, buildGallery, withPlaceholderImages } from "./features/templates/gallery.js";
|
|
5
|
+
export { createTranslator, translatorFor, LOCALES } from "./i18n/index.js";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('../features/brand/brand.js').Brand} Brand
|
|
3
|
+
*
|
|
4
|
+
* @typedef {{
|
|
5
|
+
* get: () => Promise<Brand|null>,
|
|
6
|
+
* save: (brand: Brand & {updatedBy: string|null}) => Promise<void>,
|
|
7
|
+
* }} BrandRepository
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @param {{repository?: BrandRepository|null, t?: import('../i18n/index.js').Translate}} options
|
|
11
|
+
*/
|
|
12
|
+
export function createBrandService({ repository, t }: {
|
|
13
|
+
repository?: BrandRepository | null;
|
|
14
|
+
t?: import("../i18n/index.js").Translate;
|
|
15
|
+
}): {
|
|
16
|
+
/** The brand, `{}` when none was set (or no repository). */
|
|
17
|
+
get(): Promise<import("../features/brand/brand.js").Brand>;
|
|
18
|
+
/** @param {unknown} input @param {{id?: string|number, name?: string}|null} [user] */
|
|
19
|
+
save(input: unknown, user?: {
|
|
20
|
+
id?: string | number;
|
|
21
|
+
name?: string;
|
|
22
|
+
} | null): Promise<import("../features/brand/brand.js").Brand>;
|
|
23
|
+
};
|
|
24
|
+
/** @returns {BrandRepository} */
|
|
25
|
+
export function createMemoryBrandRepository(): BrandRepository;
|
|
26
|
+
export type Brand = import("../features/brand/brand.js").Brand;
|
|
27
|
+
export type BrandRepository = {
|
|
28
|
+
get: () => Promise<Brand | null>;
|
|
29
|
+
save: (brand: Brand & {
|
|
30
|
+
updatedBy: string | null;
|
|
31
|
+
}) => Promise<void>;
|
|
32
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* May the server connect to this IP address? Public unicast only.
|
|
3
|
+
* @param {string} address
|
|
4
|
+
*/
|
|
5
|
+
export function isPublicAddress(address: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* What the user typed, as the address of a home page: "acme.com" →
|
|
8
|
+
* "https://acme.com/".
|
|
9
|
+
* @param {unknown} input
|
|
10
|
+
*/
|
|
11
|
+
export function siteUrl(input: unknown): string;
|
|
12
|
+
/**
|
|
13
|
+
* GET one address and its redirects, under the rules above.
|
|
14
|
+
* @param {string} address
|
|
15
|
+
* @param {{
|
|
16
|
+
* allow: (address: string) => boolean,
|
|
17
|
+
* allowPort?: (port: number) => boolean,
|
|
18
|
+
* signal: AbortSignal,
|
|
19
|
+
* maxBytes: number,
|
|
20
|
+
* truncate?: boolean,
|
|
21
|
+
* accept?: string,
|
|
22
|
+
* redirects?: number,
|
|
23
|
+
* }} options
|
|
24
|
+
* `truncate`: past `maxBytes`, keep what came (a page's head, the start of
|
|
25
|
+
* a stylesheet) instead of failing.
|
|
26
|
+
* @returns {Promise<{bytes: Buffer, url: string, contentType: string}>}
|
|
27
|
+
*/
|
|
28
|
+
export function safeGet(address: string, { allow, allowPort, signal, maxBytes, truncate, accept, redirects }: {
|
|
29
|
+
allow: (address: string) => boolean;
|
|
30
|
+
allowPort?: (port: number) => boolean;
|
|
31
|
+
signal: AbortSignal;
|
|
32
|
+
maxBytes: number;
|
|
33
|
+
truncate?: boolean;
|
|
34
|
+
accept?: string;
|
|
35
|
+
redirects?: number;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
bytes: Buffer;
|
|
38
|
+
url: string;
|
|
39
|
+
contentType: string;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* @param {{
|
|
43
|
+
* timeoutMs?: number,
|
|
44
|
+
* allowAddress?: (address: string) => boolean,
|
|
45
|
+
* allowPort?: (port: number) => boolean,
|
|
46
|
+
* }} [options]
|
|
47
|
+
* `allowAddress`, `allowPort`: which IP addresses and ports may be reached
|
|
48
|
+
* (default: public addresses, ports 80 and 443). Tests reading a local
|
|
49
|
+
* server loosen them; nothing else should.
|
|
50
|
+
*/
|
|
51
|
+
export function createBrandImporter({ timeoutMs, allowAddress, allowPort, }?: {
|
|
52
|
+
timeoutMs?: number;
|
|
53
|
+
allowAddress?: (address: string) => boolean;
|
|
54
|
+
allowPort?: (port: number) => boolean;
|
|
55
|
+
}): {
|
|
56
|
+
import: (input: unknown, { signal }?: {
|
|
57
|
+
signal?: AbortSignal;
|
|
58
|
+
}) => Promise<BrandImport>;
|
|
59
|
+
};
|
|
60
|
+
export namespace BRAND_IMPORT_LIMITS {
|
|
61
|
+
export let pageBytes: number;
|
|
62
|
+
export let cssBytes: number;
|
|
63
|
+
export { MAX_IMAGE_BYTES as logoBytes };
|
|
64
|
+
export let stylesheets: number;
|
|
65
|
+
export let logos: number;
|
|
66
|
+
export let redirects: number;
|
|
67
|
+
export let timeoutMs: number;
|
|
68
|
+
}
|
|
69
|
+
/** Why an import failed; the handler turns `code` into a message. */
|
|
70
|
+
export class BrandImportError extends Error {
|
|
71
|
+
/** @param {BrandImportCode} code @param {string} [detail] */
|
|
72
|
+
constructor(code: BrandImportCode, detail?: string);
|
|
73
|
+
code: BrandImportCode;
|
|
74
|
+
}
|
|
75
|
+
export type BrandImportCode = "BAD_URL" | "PRIVATE_ADDRESS" | "UNREACHABLE" | "TIMEOUT" | "HTTP_ERROR" | "NOT_HTML" | "TOO_MANY_REDIRECTS" | "TOO_LARGE";
|
|
76
|
+
/**
|
|
77
|
+
* `LOGO_UNSUPPORTED`: the site's logo is an SVG or a WebP, which most email
|
|
78
|
+
* apps do not show; `LOGO_ICON`: the site's icon was used instead.
|
|
79
|
+
*/
|
|
80
|
+
export type BrandNote = "LOGO_NOT_FOUND" | "LOGO_UNSUPPORTED" | "LOGO_ICON" | "COLORS_NOT_FOUND";
|
|
81
|
+
export type BrandImport = {
|
|
82
|
+
brand: import("../features/brand/brand.js").Brand;
|
|
83
|
+
logo: {
|
|
84
|
+
bytes: Uint8Array;
|
|
85
|
+
width: number;
|
|
86
|
+
height: number;
|
|
87
|
+
type: string;
|
|
88
|
+
ext: string;
|
|
89
|
+
shownWidth: number;
|
|
90
|
+
} | null;
|
|
91
|
+
notes: BrandNote[];
|
|
92
|
+
};
|
|
93
|
+
import { MAX_IMAGE_BYTES } from '../validation/images.js';
|
|
@@ -23,8 +23,29 @@ export type HandlerOptions = {
|
|
|
23
23
|
assetsPath?: string;
|
|
24
24
|
publicUrl?: string | null;
|
|
25
25
|
transport?: import("./sendTest.js").EmailTransport | null;
|
|
26
|
+
generator?: {
|
|
27
|
+
generate: (input: import("../features/generate/generator.js").GenerateInput, options?: {
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}) => Promise<import("../features/generate/generator.js").GeneratedEmail>;
|
|
30
|
+
rewrite?: (input: import("../features/generate/rewrite.js").RewriteInput, options?: {
|
|
31
|
+
signal?: AbortSignal;
|
|
32
|
+
}) => Promise<{
|
|
33
|
+
text: string;
|
|
34
|
+
}>;
|
|
35
|
+
suggestSubjects?: (input: import("../features/generate/subjects.js").SubjectsInput, options?: {
|
|
36
|
+
signal?: AbortSignal;
|
|
37
|
+
}) => Promise<{
|
|
38
|
+
subjects: string[];
|
|
39
|
+
}>;
|
|
40
|
+
} | null;
|
|
26
41
|
variables?: import("../features/variables/catalog.js").EmailVariable[];
|
|
27
42
|
variableStore?: import("./variables.js").VariableRepository | null;
|
|
43
|
+
brandStore?: import("./brand.js").BrandRepository | null;
|
|
44
|
+
brandImporter?: {
|
|
45
|
+
import: (url: string, options?: {
|
|
46
|
+
signal?: AbortSignal;
|
|
47
|
+
}) => Promise<import("./brandImport.js").BrandImport>;
|
|
48
|
+
} | null;
|
|
28
49
|
locale?: string;
|
|
29
50
|
messages?: Record<string, string>;
|
|
30
51
|
lang?: string;
|
|
@@ -34,6 +55,10 @@ export type HandlerOptions = {
|
|
|
34
55
|
rateLimits?: {
|
|
35
56
|
testPerHour?: number;
|
|
36
57
|
uploadsPerHour?: number;
|
|
58
|
+
generatePerHour?: number;
|
|
59
|
+
rewritePerHour?: number;
|
|
60
|
+
subjectsPerHour?: number;
|
|
61
|
+
brandImportsPerHour?: number;
|
|
37
62
|
};
|
|
38
63
|
onError?: (error: unknown, request: Request) => void;
|
|
39
64
|
};
|
|
@@ -2,12 +2,23 @@ export { createEmailBuilderHandler } from "./handler.js";
|
|
|
2
2
|
export { toNodeListener } from "./node.js";
|
|
3
3
|
export { HttpError } from "./errors.js";
|
|
4
4
|
export { compileMjml } from "./compileMjml.js";
|
|
5
|
+
export { simulateDarkMode } from "../features/preview/darkMode.js";
|
|
5
6
|
export { prepareMjml } from "../renderer/prepareMjml.js";
|
|
6
7
|
export { createTemplateService, createMemoryTemplateRepository, MAX_ACTIVE_VERSIONS, LIMITS } from "./templates.js";
|
|
7
8
|
export { createAssetService, createMemoryAssetRepository, inspectAsset } from "./assets.js";
|
|
8
9
|
export { compileTemplate, personalize, renderEmail, htmlToText } from "./render.js";
|
|
9
10
|
export { sendTestEmail, MAX_TEST_RECIPIENTS } from "./sendTest.js";
|
|
11
|
+
export { createEmailGenerator, cleanGenerateInput, MAX_DETAILS_LENGTH } from "../features/generate/generator.js";
|
|
12
|
+
export { claudeEmailModel, DEFAULT_CLAUDE_MODEL } from "../features/generate/claude.js";
|
|
13
|
+
export { EmailGenerationError, GENERATION_ERRORS, normalizeGeneratedMjml, parseDraft, reviewDraft } from "../features/generate/draft.js";
|
|
14
|
+
export { EMAIL_PURPOSES, PURPOSE_CATEGORIES, purposeById, purposesByCategory } from "../features/generate/purposes.js";
|
|
15
|
+
export { SYSTEM_PROMPT, EMAIL_DRAFT_SCHEMA, buildUserPrompt } from "../features/generate/prompt.js";
|
|
16
|
+
export { REWRITE_ACTIONS, REWRITE_LANGUAGES, REWRITE_SYSTEM_PROMPT, createRewriter, reviewRewrite } from "../features/generate/rewrite.js";
|
|
17
|
+
export { SUBJECTS_SYSTEM_PROMPT, createSubjectSuggester, emailWords, reviewSubjects } from "../features/generate/subjects.js";
|
|
10
18
|
export { createVariableService, createMemoryVariableRepository } from "./variables.js";
|
|
19
|
+
export { createBrandService, createMemoryBrandRepository } from "./brand.js";
|
|
20
|
+
export { createBrandImporter, isPublicAddress, BrandImportError, BRAND_IMPORT_LIMITS } from "./brandImport.js";
|
|
21
|
+
export { applyBrand, cleanBrand } from "../features/brand/brand.js";
|
|
11
22
|
export { DEFAULT_VARIABLES, defineVariables, mergeVariables, tokenOf, keyFromLabel, validateCustomVariable } from "../features/variables/catalog.js";
|
|
12
23
|
export { applyVariables, applyVariablesToText, mapVariables, sampleValues, usedVariables, unknownVariables } from "../features/variables/service.js";
|
|
13
24
|
export { applyServiceLinks, absoluteAssetUrls, isAssetPath, DEFAULT_ASSETS_PATH } from "../renderer/links.js";
|
|
@@ -1,34 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client for the routes of `createEmailBuilderHandler` (`best-email-builder/server`).
|
|
3
3
|
*
|
|
4
|
+
* `generate: true` turns on "Generate", the AI rewriting of a text and the
|
|
5
|
+
* subject ideas: the server must have a `generator` (otherwise it answers 501). Off by default,
|
|
6
|
+
* as the buttons would only fail.
|
|
7
|
+
*
|
|
8
|
+
* `brand: true` turns on the Brand button: the server must have a
|
|
9
|
+
* `brandStore`, and a `brandImporter` to read a brand from a website
|
|
10
|
+
* (`brand: {import: false}` when it has none).
|
|
11
|
+
*
|
|
4
12
|
* @param {{
|
|
5
13
|
* baseUrl?: string,
|
|
6
14
|
* headers?: Record<string, string> | (() => Record<string, string>),
|
|
7
15
|
* fetch?: typeof fetch,
|
|
16
|
+
* generate?: boolean,
|
|
17
|
+
* brand?: boolean|{import?: boolean},
|
|
8
18
|
* }} [options]
|
|
9
19
|
* @returns {TemplateStorage}
|
|
10
20
|
*/
|
|
11
|
-
export function createHttpStorage({ baseUrl, headers, fetch: fetchImpl }?: {
|
|
21
|
+
export function createHttpStorage({ baseUrl, headers, fetch: fetchImpl, generate, brand }?: {
|
|
12
22
|
baseUrl?: string;
|
|
13
23
|
headers?: Record<string, string> | (() => Record<string, string>);
|
|
14
24
|
fetch?: typeof fetch;
|
|
25
|
+
generate?: boolean;
|
|
26
|
+
brand?: boolean | {
|
|
27
|
+
import?: boolean;
|
|
28
|
+
};
|
|
15
29
|
}): TemplateStorage;
|
|
16
30
|
/**
|
|
17
31
|
* In-memory storage, same contract. Documents are kept SERIALIZED.
|
|
18
32
|
* `render` and `sendTest` cannot run in a browser (MJML compiles on the
|
|
19
|
-
* server): pass implementations to enable the preview and test buttons
|
|
33
|
+
* server): pass implementations to enable the preview and test buttons;
|
|
34
|
+
* `generate`, `rewrite` and `suggestSubjects` likewise for the AI, `importBrand` for reading a
|
|
35
|
+
* brand from a website. The brand itself is kept in memory (`brand`: the
|
|
36
|
+
* one to start with).
|
|
20
37
|
*
|
|
21
38
|
* @param {{
|
|
22
39
|
* render?: TemplateStorage['render'],
|
|
23
40
|
* sendTest?: TemplateStorage['sendTest'],
|
|
41
|
+
* generate?: TemplateStorage['generate'],
|
|
42
|
+
* rewrite?: TemplateStorage['rewrite'],
|
|
43
|
+
* suggestSubjects?: TemplateStorage['suggestSubjects'],
|
|
44
|
+
* importBrand?: TemplateStorage['importBrand'],
|
|
45
|
+
* brand?: Brand|null,
|
|
24
46
|
* assetsPath?: string,
|
|
25
47
|
* systemVariables?: EmailVariable[],
|
|
26
48
|
* }} [options] `systemVariables`: keys a custom variable may not take
|
|
27
49
|
* @returns {TemplateStorage & {dump: () => object}}
|
|
28
50
|
*/
|
|
29
|
-
export function createMemoryStorage({ render, sendTest, assetsPath, systemVariables }?: {
|
|
51
|
+
export function createMemoryStorage({ render, sendTest, generate, rewrite, suggestSubjects, importBrand, brand, assetsPath, systemVariables, }?: {
|
|
30
52
|
render?: TemplateStorage["render"];
|
|
31
53
|
sendTest?: TemplateStorage["sendTest"];
|
|
54
|
+
generate?: TemplateStorage["generate"];
|
|
55
|
+
rewrite?: TemplateStorage["rewrite"];
|
|
56
|
+
suggestSubjects?: TemplateStorage["suggestSubjects"];
|
|
57
|
+
importBrand?: TemplateStorage["importBrand"];
|
|
58
|
+
brand?: Brand | null;
|
|
32
59
|
assetsPath?: string;
|
|
33
60
|
systemVariables?: EmailVariable[];
|
|
34
61
|
}): TemplateStorage & {
|
|
@@ -74,11 +101,21 @@ export function createMemoryStorage({ render, sendTest, assetsPath, systemVariab
|
|
|
74
101
|
*
|
|
75
102
|
* @typedef {{mjml: string, subject?: string|null, preheader?: string|null}} RenderInput
|
|
76
103
|
* @typedef {{html: string, warnings: {message: string}[], quality: {code: string, level: string, message: string}[]}} RenderResult
|
|
104
|
+
* @typedef {import('../features/generate/generator.js').GenerateInput} GenerateInput
|
|
105
|
+
* @typedef {import('../features/generate/generator.js').GeneratedEmail} GeneratedEmail
|
|
106
|
+
* @typedef {import('../features/generate/rewrite.js').RewriteInput} RewriteInput
|
|
107
|
+
* @typedef {import('../features/generate/subjects.js').SubjectsInput} SubjectsInput
|
|
108
|
+
* @typedef {import('../features/brand/brand.js').Brand} Brand
|
|
109
|
+
* @typedef {{brand: Brand, notes: string[]}} BrandImportResult
|
|
110
|
+
* `notes`: what could not be read (`LOGO_NOT_FOUND`, `LOGO_UNSUPPORTED`,
|
|
111
|
+
* `LOGO_ICON`, `COLORS_NOT_FOUND`).
|
|
77
112
|
* @typedef {{email: string, ok: boolean, error: string|null}} TestResult
|
|
78
113
|
* @typedef {{sent: number, failed: number, invalid: string[], results: TestResult[]}} SendTestResult
|
|
79
114
|
*
|
|
80
115
|
* @typedef {{
|
|
116
|
+
* list?: () => Promise<StoredTemplate[]>,
|
|
81
117
|
* load: (id: number|string) => Promise<StoredTemplate>,
|
|
118
|
+
* remove?: (id: number|string) => Promise<void>,
|
|
82
119
|
* create: (payload: SavePayload & {name: string}) => Promise<{template: StoredTemplate, warnings: object[]}>,
|
|
83
120
|
* duplicate: (id: number|string) => Promise<{template: StoredTemplate, warnings: object[]}>,
|
|
84
121
|
* save: (id: number|string, payload: SavePayload) => Promise<{template: StoredTemplate, warnings: object[], version: {id: number|string, versionNumber: number}|null}>,
|
|
@@ -90,6 +127,12 @@ export function createMemoryStorage({ render, sendTest, assetsPath, systemVariab
|
|
|
90
127
|
* listVariables?: () => Promise<EmailVariable[]>,
|
|
91
128
|
* saveVariable?: (variable: Partial<EmailVariable>, options?: {create?: boolean}) => Promise<EmailVariable>,
|
|
92
129
|
* deleteVariable?: (key: string) => Promise<void>,
|
|
130
|
+
* generate?: (input: GenerateInput, options?: {signal?: AbortSignal}) => Promise<GeneratedEmail>,
|
|
131
|
+
* rewrite?: (input: RewriteInput, options?: {signal?: AbortSignal}) => Promise<{text: string}>,
|
|
132
|
+
* suggestSubjects?: (input: SubjectsInput, options?: {signal?: AbortSignal}) => Promise<{subjects: string[]}>,
|
|
133
|
+
* loadBrand?: () => Promise<Brand>,
|
|
134
|
+
* saveBrand?: (brand: Brand) => Promise<Brand>,
|
|
135
|
+
* importBrand?: (url: string, options?: {signal?: AbortSignal}) => Promise<BrandImportResult>,
|
|
93
136
|
* }} TemplateStorage
|
|
94
137
|
*/
|
|
95
138
|
export class StorageError extends Error {
|
|
@@ -147,6 +190,19 @@ export type RenderResult = {
|
|
|
147
190
|
message: string;
|
|
148
191
|
}[];
|
|
149
192
|
};
|
|
193
|
+
export type GenerateInput = import("../features/generate/generator.js").GenerateInput;
|
|
194
|
+
export type GeneratedEmail = import("../features/generate/generator.js").GeneratedEmail;
|
|
195
|
+
export type RewriteInput = import("../features/generate/rewrite.js").RewriteInput;
|
|
196
|
+
export type SubjectsInput = import("../features/generate/subjects.js").SubjectsInput;
|
|
197
|
+
export type Brand = import("../features/brand/brand.js").Brand;
|
|
198
|
+
/**
|
|
199
|
+
* `notes`: what could not be read (`LOGO_NOT_FOUND`, `LOGO_UNSUPPORTED`,
|
|
200
|
+
* `LOGO_ICON`, `COLORS_NOT_FOUND`).
|
|
201
|
+
*/
|
|
202
|
+
export type BrandImportResult = {
|
|
203
|
+
brand: Brand;
|
|
204
|
+
notes: string[];
|
|
205
|
+
};
|
|
150
206
|
export type TestResult = {
|
|
151
207
|
email: string;
|
|
152
208
|
ok: boolean;
|
|
@@ -159,7 +215,9 @@ export type SendTestResult = {
|
|
|
159
215
|
results: TestResult[];
|
|
160
216
|
};
|
|
161
217
|
export type TemplateStorage = {
|
|
218
|
+
list?: () => Promise<StoredTemplate[]>;
|
|
162
219
|
load: (id: number | string) => Promise<StoredTemplate>;
|
|
220
|
+
remove?: (id: number | string) => Promise<void>;
|
|
163
221
|
create: (payload: SavePayload & {
|
|
164
222
|
name: string;
|
|
165
223
|
}) => Promise<{
|
|
@@ -197,4 +255,22 @@ export type TemplateStorage = {
|
|
|
197
255
|
create?: boolean;
|
|
198
256
|
}) => Promise<EmailVariable>;
|
|
199
257
|
deleteVariable?: (key: string) => Promise<void>;
|
|
258
|
+
generate?: (input: GenerateInput, options?: {
|
|
259
|
+
signal?: AbortSignal;
|
|
260
|
+
}) => Promise<GeneratedEmail>;
|
|
261
|
+
rewrite?: (input: RewriteInput, options?: {
|
|
262
|
+
signal?: AbortSignal;
|
|
263
|
+
}) => Promise<{
|
|
264
|
+
text: string;
|
|
265
|
+
}>;
|
|
266
|
+
suggestSubjects?: (input: SubjectsInput, options?: {
|
|
267
|
+
signal?: AbortSignal;
|
|
268
|
+
}) => Promise<{
|
|
269
|
+
subjects: string[];
|
|
270
|
+
}>;
|
|
271
|
+
loadBrand?: () => Promise<Brand>;
|
|
272
|
+
saveBrand?: (brand: Brand) => Promise<Brand>;
|
|
273
|
+
importBrand?: (url: string, options?: {
|
|
274
|
+
signal?: AbortSignal;
|
|
275
|
+
}) => Promise<BrandImportResult>;
|
|
200
276
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EMAIL STUDIO — the two screens wired together.
|
|
3
|
+
*
|
|
4
|
+
* TemplateLibrary ── Edit / New template / Start from this base ──▶ EmailBuilder
|
|
5
|
+
* ▲ │
|
|
6
|
+
* └──────────────────────────── Back ───────────────────────────┘
|
|
7
|
+
*
|
|
8
|
+
* Nothing here that a host could not write itself: both screens are exported
|
|
9
|
+
* on their own, and an application that only wants the editor mounts
|
|
10
|
+
* `EmailBuilder` alone (or the library alone, opening its own editor).
|
|
11
|
+
*
|
|
12
|
+
* Shared props go to both screens — except `templates` (the bases), which
|
|
13
|
+
* only the library shows: the editor gets `[]`, so no second gallery replaces
|
|
14
|
+
* the email being edited (`builderProps.templates` brings it back).
|
|
15
|
+
* `libraryProps` / `builderProps` reach only one. Coming back from the editor, the library selects the template that was
|
|
16
|
+
* just edited or created.
|
|
17
|
+
*
|
|
18
|
+
* @param {{
|
|
19
|
+
* storage?: import('../storage/client.js').TemplateStorage,
|
|
20
|
+
* locale?: string,
|
|
21
|
+
* messages?: Record<string, string>,
|
|
22
|
+
* onNotify?: (level: 'success'|'info'|'warning'|'error', message: string) => void,
|
|
23
|
+
* sender?: import('../features/templates/starter.js').StarterSender,
|
|
24
|
+
* templates?: import('../features/templates/gallery.js').GalleryTemplate[],
|
|
25
|
+
* onClose?: () => void,
|
|
26
|
+
* libraryProps?: object,
|
|
27
|
+
* builderProps?: object,
|
|
28
|
+
* className?: string,
|
|
29
|
+
* }} props `onClose`: shows the library's close button.
|
|
30
|
+
*/
|
|
31
|
+
export default function EmailStudio({ storage: injectedStorage, locale, messages, onNotify, sender, templates, onClose, libraryProps, builderProps, className, }: {
|
|
32
|
+
storage?: import("../storage/client.js").TemplateStorage;
|
|
33
|
+
locale?: string;
|
|
34
|
+
messages?: Record<string, string>;
|
|
35
|
+
onNotify?: (level: "success" | "info" | "warning" | "error", message: string) => void;
|
|
36
|
+
sender?: import("../features/templates/starter.js").StarterSender;
|
|
37
|
+
templates?: import("../features/templates/gallery.js").GalleryTemplate[];
|
|
38
|
+
onClose?: () => void;
|
|
39
|
+
libraryProps?: object;
|
|
40
|
+
builderProps?: object;
|
|
41
|
+
className?: string;
|
|
42
|
+
}): import("react").JSX.Element;
|
|
@@ -4,9 +4,10 @@ export function emailColor(value: any): string | null;
|
|
|
4
4
|
* Field of a property. The caller's `key` (element + property) remounts it on
|
|
5
5
|
* every selection change: a draft never carries over from one element to another.
|
|
6
6
|
*/
|
|
7
|
-
export function PropertyField({ property, value, onCommit, idPrefix, services }: {
|
|
7
|
+
export function PropertyField({ property, value, fallback, onCommit, idPrefix, services }: {
|
|
8
8
|
property: any;
|
|
9
9
|
value: any;
|
|
10
|
+
fallback?: string | undefined;
|
|
10
11
|
onCommit: any;
|
|
11
12
|
idPrefix?: string | undefined;
|
|
12
13
|
services?: null | undefined;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {{
|
|
3
|
+
* storage?: import('../../storage/client.js').TemplateStorage,
|
|
4
|
+
* locale?: string,
|
|
5
|
+
* messages?: Record<string, string>,
|
|
6
|
+
* onNotify?: (level: 'success'|'info'|'warning'|'error', message: string) => void,
|
|
7
|
+
* sender?: import('../../features/templates/starter.js').StarterSender,
|
|
8
|
+
* templates?: import('../../features/templates/gallery.js').GalleryTemplate[],
|
|
9
|
+
* title?: string,
|
|
10
|
+
* description?: string,
|
|
11
|
+
* selectedId?: string|number|null,
|
|
12
|
+
* favoritesKey?: string|null,
|
|
13
|
+
* onEdit?: (template: import('../../storage/client.js').StoredTemplate) => void,
|
|
14
|
+
* onCreate?: (start: null | {baseId: string, mjml: string, meta: {subject?: string, preheader?: string}}) => void,
|
|
15
|
+
* onClose?: () => void,
|
|
16
|
+
* className?: string,
|
|
17
|
+
* }} props
|
|
18
|
+
*/
|
|
19
|
+
export default function TemplateLibrary({ locale, messages, onNotify, ...props }: {
|
|
20
|
+
storage?: import("../../storage/client.js").TemplateStorage;
|
|
21
|
+
locale?: string;
|
|
22
|
+
messages?: Record<string, string>;
|
|
23
|
+
onNotify?: (level: "success" | "info" | "warning" | "error", message: string) => void;
|
|
24
|
+
sender?: import("../../features/templates/starter.js").StarterSender;
|
|
25
|
+
templates?: import("../../features/templates/gallery.js").GalleryTemplate[];
|
|
26
|
+
title?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
selectedId?: string | number | null;
|
|
29
|
+
favoritesKey?: string | null;
|
|
30
|
+
onEdit?: (template: import("../../storage/client.js").StoredTemplate) => void;
|
|
31
|
+
onCreate?: (start: null | {
|
|
32
|
+
baseId: string;
|
|
33
|
+
mjml: string;
|
|
34
|
+
meta: {
|
|
35
|
+
subject?: string;
|
|
36
|
+
preheader?: string;
|
|
37
|
+
};
|
|
38
|
+
}) => void;
|
|
39
|
+
onClose?: () => void;
|
|
40
|
+
className?: string;
|
|
41
|
+
}): import("react").JSX.Element;
|