best-email-builder 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/README.md +290 -20
  3. package/dist/ai-server.js +68 -0
  4. package/dist/ai-server.js.map +1 -0
  5. package/dist/ai.js +852 -0
  6. package/dist/ai.js.map +1 -0
  7. package/dist/builder.js +16 -8
  8. package/dist/chunk-2RZHGAH5.js +64 -0
  9. package/dist/chunk-2RZHGAH5.js.map +1 -0
  10. package/dist/chunk-3LKF4QBL.js +595 -0
  11. package/dist/chunk-3LKF4QBL.js.map +1 -0
  12. package/dist/chunk-5SASKCUC.js +251 -0
  13. package/dist/chunk-5SASKCUC.js.map +1 -0
  14. package/dist/{chunk-JOC752I3.js → chunk-6D3SLT5W.js} +27 -29
  15. package/dist/chunk-6D3SLT5W.js.map +1 -0
  16. package/dist/chunk-7U6BAKM7.js +119 -0
  17. package/dist/chunk-7U6BAKM7.js.map +1 -0
  18. package/dist/chunk-DFNHICTF.js +1359 -0
  19. package/dist/chunk-DFNHICTF.js.map +1 -0
  20. package/dist/chunk-ECHGJZ2W.js +327 -0
  21. package/dist/chunk-ECHGJZ2W.js.map +1 -0
  22. package/dist/chunk-KXJJSVN5.js +98 -0
  23. package/dist/chunk-KXJJSVN5.js.map +1 -0
  24. package/dist/{chunk-ZSUHOCCE.js → chunk-LXR4QTGP.js} +123 -579
  25. package/dist/chunk-LXR4QTGP.js.map +1 -0
  26. package/dist/{chunk-BVOVAEEJ.js → chunk-LYMJ2TBX.js} +2 -2
  27. package/dist/chunk-MPX5CWTZ.js +1687 -0
  28. package/dist/chunk-MPX5CWTZ.js.map +1 -0
  29. package/dist/chunk-PB7YZ3WP.js +5635 -0
  30. package/dist/chunk-PB7YZ3WP.js.map +1 -0
  31. package/dist/chunk-UFKCLU2D.js +255 -0
  32. package/dist/chunk-UFKCLU2D.js.map +1 -0
  33. package/dist/index.js +51 -20
  34. package/dist/index.js.map +1 -1
  35. package/dist/library.js +12 -7
  36. package/dist/server.js +513 -214
  37. package/dist/server.js.map +1 -1
  38. package/dist/styles.css +1 -1
  39. package/dist/types/ai/ConnectAi.d.ts +12 -0
  40. package/dist/types/ai/GenerateButton.d.ts +4 -0
  41. package/dist/types/ai/RewriteText.d.ts +5 -0
  42. package/dist/types/ai/SubjectIdeas.d.ts +25 -0
  43. package/dist/types/ai/chip.d.ts +1 -0
  44. package/dist/types/ai/claude.d.ts +21 -0
  45. package/dist/types/ai/createAi.d.ts +66 -0
  46. package/dist/types/ai/draft.d.ts +163 -0
  47. package/dist/types/ai/generator.d.ts +123 -0
  48. package/dist/types/ai/i18n/fr.d.ts +2 -0
  49. package/dist/types/ai/i18n/index.d.ts +2 -0
  50. package/dist/types/ai/index.d.ts +9 -0
  51. package/dist/types/ai/keys.d.ts +46 -0
  52. package/dist/types/ai/prompt.d.ts +76 -0
  53. package/dist/types/ai/providers.d.ts +67 -0
  54. package/dist/types/ai/purposes.d.ts +48 -0
  55. package/dist/types/ai/rewrite.d.ts +83 -0
  56. package/dist/types/ai/server.d.ts +9 -0
  57. package/dist/types/ai/subjects.d.ts +97 -0
  58. package/dist/types/blocks/shared.d.ts +20 -0
  59. package/dist/types/core/EmailEditor.d.ts +72 -19
  60. package/dist/types/core/constants.d.ts +1 -1
  61. package/dist/types/core/mjmlDefaults.d.ts +13 -0
  62. package/dist/types/core/scrollbars.d.ts +1 -1
  63. package/dist/types/defaultEditor.d.ts +4 -1
  64. package/dist/types/engine/NativeAdapter.d.ts +28 -0
  65. package/dist/types/engine/canvas/CanvasView.d.ts +88 -0
  66. package/dist/types/engine/canvas/canvasStyles.d.ts +3 -0
  67. package/dist/types/engine/canvas/icons.d.ts +14 -0
  68. package/dist/types/engine/canvas/morph.d.ts +21 -0
  69. package/dist/types/engine/canvasMjml.d.ts +30 -0
  70. package/dist/types/engine/mjml.d.ts +31 -0
  71. package/dist/types/engine/model.d.ts +130 -0
  72. package/dist/types/engine/placement.d.ts +69 -0
  73. package/dist/types/engine/richText.d.ts +15 -0
  74. package/dist/types/engine/unwrapModule.d.ts +29 -0
  75. package/dist/types/features/brand/BrandButton.d.ts +4 -0
  76. package/dist/types/features/brand/brand.d.ts +108 -0
  77. package/dist/types/features/brand/extract.d.ts +61 -0
  78. package/dist/types/features/brand/index.d.ts +2 -0
  79. package/dist/types/features/brand/messages.d.ts +13 -0
  80. package/dist/types/features/brand/useBrand.d.ts +22 -0
  81. package/dist/types/features/preview/PreviewButton.d.ts +1 -1
  82. package/dist/types/features/preview/darkMode.d.ts +65 -0
  83. package/dist/types/features/variables/VariablePicker.d.ts +3 -2
  84. package/dist/types/i18n/index.d.ts +8 -0
  85. package/dist/types/index.d.ts +5 -2
  86. package/dist/types/server/brand.d.ts +32 -0
  87. package/dist/types/server/brandImport.d.ts +93 -0
  88. package/dist/types/server/compileMjml.d.ts +2 -0
  89. package/dist/types/server/handler.d.ts +26 -0
  90. package/dist/types/server/index.d.ts +5 -1
  91. package/dist/types/storage/client.d.ts +57 -3
  92. package/dist/types/ui/EmailBuilder.d.ts +5 -1
  93. package/dist/types/ui/EmailStudio.d.ts +6 -1
  94. package/dist/types/ui/PropertiesPanel.d.ts +2 -1
  95. package/dist/types/ui/fields.d.ts +2 -1
  96. package/dist/types/ui/primitives/button.d.ts +1 -1
  97. package/dist/types/ui/primitives/color-picker.d.ts +2 -1
  98. package/dist/types/ui/primitives/searchShortcut.d.ts +16 -0
  99. package/dist/types/ui/useLayoutWidth.d.ts +1 -1
  100. package/package.json +27 -7
  101. package/dist/chunk-3M36VVXS.js +0 -665
  102. package/dist/chunk-3M36VVXS.js.map +0 -1
  103. package/dist/chunk-57CTQNTK.js +0 -56
  104. package/dist/chunk-57CTQNTK.js.map +0 -1
  105. package/dist/chunk-ILRAQTHS.js +0 -3677
  106. package/dist/chunk-ILRAQTHS.js.map +0 -1
  107. package/dist/chunk-JOC752I3.js.map +0 -1
  108. package/dist/chunk-ZSUHOCCE.js.map +0 -1
  109. package/dist/types/adapters/grapesjs/GrapesJsAdapter.d.ts +0 -33
  110. package/dist/types/adapters/grapesjs/engineConfig.d.ts +0 -148
  111. package/dist/types/adapters/grapesjs/unwrapModule.d.ts +0 -18
  112. /package/dist/{chunk-BVOVAEEJ.js.map → chunk-LYMJ2TBX.js.map} +0 -0
@@ -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';
@@ -1,3 +1,5 @@
1
+ /** @param {string} html */
2
+ export function withClientMetas(html: string): string;
1
3
  /** @typedef {{line?: number, message: string, tagName?: string}} CompileWarning */
2
4
  /**
3
5
  * @param {string} mjml
@@ -23,8 +23,30 @@ 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("../ai/generator.js").GenerateInput, options?: {
28
+ signal?: AbortSignal;
29
+ }) => Promise<import("../ai/generator.js").GeneratedEmail>;
30
+ rewrite?: (input: import("../ai/rewrite.js").RewriteInput, options?: {
31
+ signal?: AbortSignal;
32
+ }) => Promise<{
33
+ text: string;
34
+ }>;
35
+ suggestSubjects?: (input: import("../ai/subjects.js").SubjectsInput, options?: {
36
+ signal?: AbortSignal;
37
+ }) => Promise<{
38
+ subjects: string[];
39
+ }>;
40
+ messages?: Record<string, Record<string, string>>;
41
+ } | null;
26
42
  variables?: import("../features/variables/catalog.js").EmailVariable[];
27
43
  variableStore?: import("./variables.js").VariableRepository | null;
44
+ brandStore?: import("./brand.js").BrandRepository | null;
45
+ brandImporter?: {
46
+ import: (url: string, options?: {
47
+ signal?: AbortSignal;
48
+ }) => Promise<import("./brandImport.js").BrandImport>;
49
+ } | null;
28
50
  locale?: string;
29
51
  messages?: Record<string, string>;
30
52
  lang?: string;
@@ -34,6 +56,10 @@ export type HandlerOptions = {
34
56
  rateLimits?: {
35
57
  testPerHour?: number;
36
58
  uploadsPerHour?: number;
59
+ generatePerHour?: number;
60
+ rewritePerHour?: number;
61
+ subjectsPerHour?: number;
62
+ brandImportsPerHour?: number;
37
63
  };
38
64
  onError?: (error: unknown, request: Request) => void;
39
65
  };
@@ -2,12 +2,16 @@ 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";
10
11
  export { createVariableService, createMemoryVariableRepository } from "./variables.js";
12
+ export { createBrandService, createMemoryBrandRepository } from "./brand.js";
13
+ export { createBrandImporter, isPublicAddress, BrandImportError, BRAND_IMPORT_LIMITS } from "./brandImport.js";
14
+ export { applyBrand, cleanBrand } from "../features/brand/brand.js";
11
15
  export { DEFAULT_VARIABLES, defineVariables, mergeVariables, tokenOf, keyFromLabel, validateCustomVariable } from "../features/variables/catalog.js";
12
16
  export { applyVariables, applyVariablesToText, mapVariables, sampleValues, usedVariables, unknownVariables } from "../features/variables/service.js";
13
17
  export { applyServiceLinks, absoluteAssetUrls, isAssetPath, DEFAULT_ASSETS_PATH } from "../renderer/links.js";
@@ -16,4 +20,4 @@ export { validateProject, assertValidProject } from "../validation/projectValida
16
20
  export { qualityReport, GMAIL_CLIP_BYTES } from "../validation/quality.js";
17
21
  export { readDocument, createDocument, serializeDocument, EmailDocumentError } from "../core/document.js";
18
22
  export { EMAIL_BUILDER_VERSION, EMAIL_DOCUMENT_SCHEMA_VERSION } from "../core/constants.js";
19
- export { createTranslator, translatorFor, LOCALES } from "../i18n/index.js";
23
+ export { createTranslator, translatorFor, messagesOf, LOCALES } from "../i18n/index.js";
@@ -1,34 +1,67 @@
1
+ /**
2
+ * The HTTP side of a client: JSON in and out, the host's headers, errors as
3
+ * `StorageError` (with the server's code). Shared by `createHttpStorage` and
4
+ * the AI module (`best-email-builder/ai`).
5
+ *
6
+ * @param {{baseUrl?: string, headers?: Record<string, string> | (() => Record<string, string>), fetch?: typeof fetch}} [options]
7
+ */
8
+ export function createHttpCaller({ baseUrl, headers, fetch: fetchImpl }?: {
9
+ baseUrl?: string;
10
+ headers?: Record<string, string> | (() => Record<string, string>);
11
+ fetch?: typeof fetch;
12
+ }): {
13
+ root: string;
14
+ call: (path: string, init?: RequestInit) => Promise<any>;
15
+ unwrap: (res: Response) => Promise<any>;
16
+ doFetch: typeof fetch;
17
+ extraHeaders: () => Record<string, string>;
18
+ };
1
19
  /**
2
20
  * Client for the routes of `createEmailBuilderHandler` (`best-email-builder/server`).
21
+ * The AI is a module of its own: `createAi` (`best-email-builder/ai`).
22
+ *
23
+ * `brand: true` turns on the Brand button: the server must have a
24
+ * `brandStore`, and a `brandImporter` to read a brand from a website
25
+ * (`brand: {import: false}` when it has none).
3
26
  *
4
27
  * @param {{
5
28
  * baseUrl?: string,
6
29
  * headers?: Record<string, string> | (() => Record<string, string>),
7
30
  * fetch?: typeof fetch,
31
+ * brand?: boolean|{import?: boolean},
8
32
  * }} [options]
9
33
  * @returns {TemplateStorage}
10
34
  */
11
- export function createHttpStorage({ baseUrl, headers, fetch: fetchImpl }?: {
35
+ export function createHttpStorage({ baseUrl, headers, fetch: fetchImpl, brand }?: {
12
36
  baseUrl?: string;
13
37
  headers?: Record<string, string> | (() => Record<string, string>);
14
38
  fetch?: typeof fetch;
39
+ brand?: boolean | {
40
+ import?: boolean;
41
+ };
15
42
  }): TemplateStorage;
16
43
  /**
17
44
  * In-memory storage, same contract. Documents are kept SERIALIZED.
18
45
  * `render` and `sendTest` cannot run in a browser (MJML compiles on the
19
- * server): pass implementations to enable the preview and test buttons.
46
+ * server): pass implementations to enable the preview and test buttons;
47
+ * `importBrand` likewise for reading a brand from a website. The brand itself is kept in memory (`brand`: the
48
+ * one to start with).
20
49
  *
21
50
  * @param {{
22
51
  * render?: TemplateStorage['render'],
23
52
  * sendTest?: TemplateStorage['sendTest'],
53
+ * importBrand?: TemplateStorage['importBrand'],
54
+ * brand?: Brand|null,
24
55
  * assetsPath?: string,
25
56
  * systemVariables?: EmailVariable[],
26
57
  * }} [options] `systemVariables`: keys a custom variable may not take
27
58
  * @returns {TemplateStorage & {dump: () => object}}
28
59
  */
29
- export function createMemoryStorage({ render, sendTest, assetsPath, systemVariables }?: {
60
+ export function createMemoryStorage({ render, sendTest, importBrand, brand, assetsPath, systemVariables, }?: {
30
61
  render?: TemplateStorage["render"];
31
62
  sendTest?: TemplateStorage["sendTest"];
63
+ importBrand?: TemplateStorage["importBrand"];
64
+ brand?: Brand | null;
32
65
  assetsPath?: string;
33
66
  systemVariables?: EmailVariable[];
34
67
  }): TemplateStorage & {
@@ -74,6 +107,10 @@ export function createMemoryStorage({ render, sendTest, assetsPath, systemVariab
74
107
  *
75
108
  * @typedef {{mjml: string, subject?: string|null, preheader?: string|null}} RenderInput
76
109
  * @typedef {{html: string, warnings: {message: string}[], quality: {code: string, level: string, message: string}[]}} RenderResult
110
+ * @typedef {import('../features/brand/brand.js').Brand} Brand
111
+ * @typedef {{brand: Brand, notes: string[]}} BrandImportResult
112
+ * `notes`: what could not be read (`LOGO_NOT_FOUND`, `LOGO_UNSUPPORTED`,
113
+ * `LOGO_ICON`, `COLORS_NOT_FOUND`).
77
114
  * @typedef {{email: string, ok: boolean, error: string|null}} TestResult
78
115
  * @typedef {{sent: number, failed: number, invalid: string[], results: TestResult[]}} SendTestResult
79
116
  *
@@ -92,6 +129,9 @@ export function createMemoryStorage({ render, sendTest, assetsPath, systemVariab
92
129
  * listVariables?: () => Promise<EmailVariable[]>,
93
130
  * saveVariable?: (variable: Partial<EmailVariable>, options?: {create?: boolean}) => Promise<EmailVariable>,
94
131
  * deleteVariable?: (key: string) => Promise<void>,
132
+ * loadBrand?: () => Promise<Brand>,
133
+ * saveBrand?: (brand: Brand) => Promise<Brand>,
134
+ * importBrand?: (url: string, options?: {signal?: AbortSignal}) => Promise<BrandImportResult>,
95
135
  * }} TemplateStorage
96
136
  */
97
137
  export class StorageError extends Error {
@@ -149,6 +189,15 @@ export type RenderResult = {
149
189
  message: string;
150
190
  }[];
151
191
  };
192
+ export type Brand = import("../features/brand/brand.js").Brand;
193
+ /**
194
+ * `notes`: what could not be read (`LOGO_NOT_FOUND`, `LOGO_UNSUPPORTED`,
195
+ * `LOGO_ICON`, `COLORS_NOT_FOUND`).
196
+ */
197
+ export type BrandImportResult = {
198
+ brand: Brand;
199
+ notes: string[];
200
+ };
152
201
  export type TestResult = {
153
202
  email: string;
154
203
  ok: boolean;
@@ -201,4 +250,9 @@ export type TemplateStorage = {
201
250
  create?: boolean;
202
251
  }) => Promise<EmailVariable>;
203
252
  deleteVariable?: (key: string) => Promise<void>;
253
+ loadBrand?: () => Promise<Brand>;
254
+ saveBrand?: (brand: Brand) => Promise<Brand>;
255
+ importBrand?: (url: string, options?: {
256
+ signal?: AbortSignal;
257
+ }) => Promise<BrandImportResult>;
204
258
  };
@@ -17,10 +17,13 @@
17
17
  * onSaved?: (template: object) => void,
18
18
  * onDirtyChange?: (dirty: boolean) => void,
19
19
  * dirtyCheckRef?: {current: null | (() => boolean)},
20
+ * ai?: import('../ai/createAi.js').EmailBuilderAi|null,
20
21
  * className?: string,
21
22
  * }} props
23
+ * `ai`: the AI module, `createAi()` from `best-email-builder/ai`. Without
24
+ * it, no AI button and no AI code.
22
25
  */
23
- export default function EmailBuilder({ locale, messages, onNotify, ...props }: {
26
+ export default function EmailBuilder({ locale, messages, onNotify, ai: aiProp, ...props }: {
24
27
  storage?: import("../storage/client.js").TemplateStorage;
25
28
  templateId?: string | number | null;
26
29
  editor?: import("../core/EmailEditor.js").EmailEditor;
@@ -45,5 +48,6 @@ export default function EmailBuilder({ locale, messages, onNotify, ...props }: {
45
48
  dirtyCheckRef?: {
46
49
  current: null | (() => boolean);
47
50
  };
51
+ ai?: import("../ai/createAi.js").EmailBuilderAi | null;
48
52
  className?: string;
49
53
  }): import("react").JSX.Element;
@@ -15,8 +15,12 @@
15
15
  * `libraryProps` / `builderProps` reach only one. Coming back from the editor, the library selects the template that was
16
16
  * just edited or created.
17
17
  *
18
+ * `ai` (`createAi()` from `best-email-builder/ai`) goes to the editor only:
19
+ * without it, the editor has no AI button at all.
20
+ *
18
21
  * @param {{
19
22
  * storage?: import('../storage/client.js').TemplateStorage,
23
+ * ai?: import('../ai/createAi.js').EmailBuilderAi|null,
20
24
  * locale?: string,
21
25
  * messages?: Record<string, string>,
22
26
  * onNotify?: (level: 'success'|'info'|'warning'|'error', message: string) => void,
@@ -28,8 +32,9 @@
28
32
  * className?: string,
29
33
  * }} props `onClose`: shows the library's close button.
30
34
  */
31
- export default function EmailStudio({ storage: injectedStorage, locale, messages, onNotify, sender, templates, onClose, libraryProps, builderProps, className, }: {
35
+ export default function EmailStudio({ storage: injectedStorage, ai, locale, messages, onNotify, sender, templates, onClose, libraryProps, builderProps, className, }: {
32
36
  storage?: import("../storage/client.js").TemplateStorage;
37
+ ai?: import("../ai/createAi.js").EmailBuilderAi | null;
33
38
  locale?: string;
34
39
  messages?: Record<string, string>;
35
40
  onNotify?: (level: "success" | "info" | "warning" | "error", message: string) => void;
@@ -1,4 +1,4 @@
1
- export default function PropertiesPanel({ editor, selection, meta, onMetaChange, changes, actions, showErrors, services, variables, onDismiss, }: {
1
+ export default function PropertiesPanel({ editor, selection, meta, onMetaChange, changes, actions, showErrors, services, ai, variables, onDismiss, }: {
2
2
  editor: any;
3
3
  selection: any;
4
4
  meta: any;
@@ -7,6 +7,7 @@ export default function PropertiesPanel({ editor, selection, meta, onMetaChange,
7
7
  actions?: null | undefined;
8
8
  showErrors?: boolean | undefined;
9
9
  services?: null | undefined;
10
+ ai?: null | undefined;
10
11
  variables?: null | undefined;
11
12
  onDismiss?: null | undefined;
12
13
  }): 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;
@@ -8,5 +8,5 @@ export function Button({ className, variant, size, asChild, type, ...props }: {
8
8
  }): import("react").JSX.Element;
9
9
  export const buttonVariants: (props?: ({
10
10
  variant?: "link" | "default" | "destructive" | "outline" | "secondary" | "ghost" | null | undefined;
11
- size?: "default" | "xs" | "sm-compact" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm-compact" | "icon-sm" | null | undefined;
11
+ size?: "default" | "icon" | "xs" | "sm-compact" | "sm" | "lg" | "icon-xs" | "icon-sm-compact" | "icon-sm" | null | undefined;
12
12
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
@@ -2,9 +2,10 @@
2
2
  * Colour field: a swatch opening a full-spectrum picker (with alpha), a hex
3
3
  * input and presets. `value` is `#RRGGBB` or `#RRGGBBAA`; `onChange(hex)`.
4
4
  */
5
- export function ColorPicker({ value, onChange, id, className }: {
5
+ export function ColorPicker({ value, onChange, id, className, placeholder }: {
6
6
  value: any;
7
7
  onChange: any;
8
8
  id: any;
9
9
  className: any;
10
+ placeholder?: string | undefined;
10
11
  }): import("react").JSX.Element;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Ctrl+K (⌘K) puts the cursor in this search field, its words selected, for
3
+ * as long as the field is on screen — the library's search, the one of
4
+ * "Generate", the one of the variables menu.
5
+ * @param {{current: HTMLInputElement|null}} inputRef
6
+ */
7
+ export function useSearchShortcut(inputRef: {
8
+ current: HTMLInputElement | null;
9
+ }): void;
10
+ /** The key hint at the end of a search field: Ctrl K (⌘K), or `children` instead. */
11
+ export function SearchKeyHint({ className, children }: {
12
+ className: any;
13
+ children?: null | undefined;
14
+ }): import("react").JSX.Element;
15
+ /** What a search field announces to assistive technologies. */
16
+ export const SEARCH_KEYS: "Control+K Meta+K";
@@ -3,7 +3,7 @@ export function layoutFor(width: number): "wide" | "medium" | "narrow";
3
3
  /** @param {{current: HTMLElement|null}} ref */
4
4
  export function useLayoutWidth(ref: {
5
5
  current: HTMLElement | null;
6
- }): "wide" | "medium" | "narrow";
6
+ }): "medium" | "wide" | "narrow";
7
7
  export namespace LAYOUT_BREAKPOINTS {
8
8
  let wide: number;
9
9
  let medium: number;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "best-email-builder",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Drag-and-drop email builder for React: MJML output that renders in Outlook, Gmail and Apple Mail, with server-side rendering, versions, variables and image uploads.",
5
5
  "keywords": [
6
6
  "email",
7
7
  "email-builder",
8
8
  "mjml",
9
9
  "react",
10
- "grapesjs",
10
+ "email-template",
11
11
  "newsletter",
12
12
  "drag-and-drop",
13
13
  "email-editor"
@@ -28,9 +28,21 @@
28
28
  "types": "./dist/types/index.d.ts",
29
29
  "typesVersions": {
30
30
  "*": {
31
- "builder": ["./dist/types/builder.d.ts"],
32
- "library": ["./dist/types/library.d.ts"],
33
- "server": ["./dist/types/server/index.d.ts"]
31
+ "builder": [
32
+ "./dist/types/builder.d.ts"
33
+ ],
34
+ "library": [
35
+ "./dist/types/library.d.ts"
36
+ ],
37
+ "server": [
38
+ "./dist/types/server/index.d.ts"
39
+ ],
40
+ "ai": [
41
+ "./dist/types/ai/index.d.ts"
42
+ ],
43
+ "ai/server": [
44
+ "./dist/types/ai/server.d.ts"
45
+ ]
34
46
  }
35
47
  },
36
48
  "exports": {
@@ -50,6 +62,14 @@
50
62
  "types": "./dist/types/server/index.d.ts",
51
63
  "import": "./dist/server.js"
52
64
  },
65
+ "./ai": {
66
+ "types": "./dist/types/ai/index.d.ts",
67
+ "import": "./dist/ai.js"
68
+ },
69
+ "./ai/server": {
70
+ "types": "./dist/types/ai/server.d.ts",
71
+ "import": "./dist/ai-server.js"
72
+ },
53
73
  "./styles.css": "./dist/styles.css",
54
74
  "./package.json": "./package.json"
55
75
  },
@@ -92,15 +112,15 @@
92
112
  "dependencies": {
93
113
  "class-variance-authority": "^0.7.1",
94
114
  "clsx": "^2.1.1",
95
- "grapesjs": "^0.23.6",
96
- "grapesjs-mjml": "1.0.8",
97
115
  "lucide-react": "^1.55.0",
98
116
  "mjml": "^5.4.1",
117
+ "mjml-browser": "^5.4.1",
99
118
  "radix-ui": "^1.8.0",
100
119
  "react-colorful": "^5.8.1",
101
120
  "tailwind-merge": "^3.4.1"
102
121
  },
103
122
  "devDependencies": {
123
+ "@anthropic-ai/sdk": "^0.130.0",
104
124
  "@eslint/js": "^10.0.1",
105
125
  "@ladle/react": "^5.1.1",
106
126
  "@playwright/test": "^1.64.0",