esender-email-editor 1.0.3 → 1.1.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/README.md +119 -19
- package/dist/index.cjs +6 -6
- package/dist/index.d.ts +79 -4
- package/dist/index.js +6 -6
- package/package.json +17 -17
package/README.md
CHANGED
|
@@ -16,19 +16,40 @@ npm install esender-email-editor
|
|
|
16
16
|
|
|
17
17
|
## Peer dependencies
|
|
18
18
|
|
|
19
|
-
The package externalizes its heavy runtime deps so the host app provides them.
|
|
19
|
+
The package externalizes its heavy runtime deps so the host app provides them. Peer ranges are intentionally permissive — any version within the declared range works, and hosts that already satisfy a peer can skip it.
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
npm install \
|
|
23
|
-
react@^18
|
|
24
|
-
antd@^5
|
|
25
|
-
@reduxjs/toolkit@^2
|
|
26
|
-
mjml-browser@^4
|
|
27
|
-
lodash@^4
|
|
28
|
-
framer-motion@^12
|
|
29
|
-
styled-components@^6
|
|
23
|
+
react@^18 react-dom@^18 \
|
|
24
|
+
antd@^5 @ant-design/icons@^5 @ant-design/pro-components@^2 \
|
|
25
|
+
@reduxjs/toolkit@^2 react-redux@^9 redux-persist@^6 \
|
|
26
|
+
mjml-browser@^4 cheerio@1.0.0-rc.12 \
|
|
27
|
+
lodash@^4 axios@^1 swr@^2 \
|
|
28
|
+
framer-motion@^12 html2canvas@^1 \
|
|
29
|
+
styled-components@^6 use-sync-external-store@^1
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
### Supported version ranges
|
|
33
|
+
|
|
34
|
+
| Package | Declared range | Notes |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `react`, `react-dom` | `>=18.0.0 <19.0.0` | React 19 is **not** supported (see below). |
|
|
37
|
+
| `antd` | `>=5.0.0 <6.0.0` | |
|
|
38
|
+
| `@ant-design/icons` | `>=5.0.0 <6.0.0` | v6 conflicts with `pro-components`. |
|
|
39
|
+
| `@ant-design/pro-components` | `>=2.0.0 <3.0.0` | |
|
|
40
|
+
| `@reduxjs/toolkit` | `>=2.0.0 <3.0.0` | |
|
|
41
|
+
| `react-redux` | `>=8.0.0 <10.0.0` | v8 and v9 both work under React 18. |
|
|
42
|
+
| `redux-persist` | `>=6.0.0 <7.0.0` | |
|
|
43
|
+
| `mjml-browser` | `>=4.15.0 <6.0.0` | v5 still requires `cheerio`. |
|
|
44
|
+
| `lodash` | `>=4.17.0 <5.0.0` | |
|
|
45
|
+
| `axios` | `>=1.0.0 <2.0.0` | |
|
|
46
|
+
| `swr` | `>=2.0.0 <3.0.0` | |
|
|
47
|
+
| `framer-motion` | `>=10.0.0 <13.0.0` | v10+; older majors removed `motion` imports we use. |
|
|
48
|
+
| `html2canvas` | `>=1.0.0 <2.0.0` | |
|
|
49
|
+
| `styled-components` | `>=5.0.0 <7.0.0` | v5 and v6 both work. |
|
|
50
|
+
| `use-sync-external-store` | `>=1.0.0 <2.0.0` | |
|
|
51
|
+
| `cheerio` | `1.0.0-rc.12` | Transitive of `mjml-browser`; host must install. Not declared as a peer. |
|
|
52
|
+
|
|
32
53
|
### The non-negotiables
|
|
33
54
|
|
|
34
55
|
| Constraint | Why |
|
|
@@ -49,26 +70,36 @@ export const Composer = () => {
|
|
|
49
70
|
const ref = useRef<EditorHandle>(null);
|
|
50
71
|
const projectId = 'proj_123';
|
|
51
72
|
|
|
52
|
-
const
|
|
73
|
+
const create = async () => {
|
|
74
|
+
// Omit templateId → server creates a new template.
|
|
53
75
|
const res = await ref.current?.saveTemplate({ projectId });
|
|
54
76
|
if (!res?.status) console.error(res?.message);
|
|
55
77
|
};
|
|
56
78
|
|
|
57
|
-
const
|
|
58
|
-
|
|
79
|
+
const update = async (templateId: string) => {
|
|
80
|
+
// Pass templateId → server updates the existing template.
|
|
81
|
+
await ref.current?.saveTemplate({ projectId, templateId });
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const load = async (templateId?: string) => {
|
|
85
|
+
// Omit templateId → loads the project's current template.
|
|
86
|
+
await ref.current?.loadTemplate({ projectId, templateId });
|
|
59
87
|
};
|
|
60
88
|
|
|
61
89
|
return (
|
|
62
90
|
<>
|
|
63
91
|
<button onClick={() => console.log(ref.current?.getHtml())}>Export HTML</button>
|
|
64
92
|
<button onClick={() => console.log(ref.current?.getJson())}>Export JSON</button>
|
|
65
|
-
<button onClick={
|
|
66
|
-
<button onClick={load}>Load
|
|
93
|
+
<button onClick={create}>Create</button>
|
|
94
|
+
<button onClick={() => load()}>Load latest</button>
|
|
67
95
|
|
|
68
96
|
<Package
|
|
69
97
|
ref={ref}
|
|
70
98
|
apiKey={process.env.NEXT_PUBLIC_ESENDER_KEY!}
|
|
71
99
|
onLicenseError={(err: LicenseError) => console.error(err)}
|
|
100
|
+
onSave={(e) => console.log('saved', e.templateId, e.response)}
|
|
101
|
+
onLoad={(e) => console.log('loaded', e.response)}
|
|
102
|
+
onTemplateChange={(json) => console.log('changed', json)}
|
|
72
103
|
showUndoRedo
|
|
73
104
|
/>
|
|
74
105
|
</>
|
|
@@ -84,10 +115,16 @@ export const Composer = () => {
|
|
|
84
115
|
|
|
85
116
|
| Prop | Type | Required | Description |
|
|
86
117
|
|---|---|---|---|
|
|
87
|
-
| `apiKey` | `string` | yes |
|
|
118
|
+
| `apiKey` | `string` | yes | License key. Exchanged for a JWT on mount. |
|
|
119
|
+
| `apiConfig` | `ApiConfig` | no | Override base URL and any endpoint path. See [API configuration](#api-configuration). |
|
|
88
120
|
| `customButtons` | `ReactNode` | no | Slot rendered in the editor's top-right toolbar. |
|
|
89
121
|
| `showUndoRedo` | `boolean` | no | Show the undo/redo toolbar (default `true`). |
|
|
90
122
|
| `onLicenseError` | `(error: LicenseError) => void` | no | Fires when the license verify call fails. |
|
|
123
|
+
| `onSave` | `(event: SaveEvent) => void` | no | Fires after a successful `saveTemplate()` call. |
|
|
124
|
+
| `onLoad` | `(event: LoadEvent) => void` | no | Fires after a successful `loadTemplate()` call. |
|
|
125
|
+
| `onTemplateChange` | `(json: unknown) => void` | no | Fires on every edit (the exported JSON object — not stringified). |
|
|
126
|
+
| `onHtmlExport` | `(html: string) => void` | no | Fires when `ref.current.getHtml()` is invoked. |
|
|
127
|
+
| `onMediaUpload` | `(event: MediaUploadEvent) => void` | no | Fires after a successful media upload. |
|
|
91
128
|
| `projectId` | `string` | no | **Deprecated.** Ignored at runtime. Kept only for migration. |
|
|
92
129
|
|
|
93
130
|
### `EditorHandle` (`ref.current`)
|
|
@@ -95,10 +132,68 @@ export const Composer = () => {
|
|
|
95
132
|
| Method | Returns | Description |
|
|
96
133
|
|---|---|---|
|
|
97
134
|
| `getJson()` | `string` | Stringified MJML JSON tree (export-ready). |
|
|
98
|
-
| `getHtml()` | `string` | Compiled email HTML via `mjml-browser`. |
|
|
135
|
+
| `getHtml()` | `string` | Compiled email HTML via `mjml-browser`. Triggers `onHtmlExport`. |
|
|
99
136
|
| `loadJson(json, raw?)` | `void` | Load a previously saved template. `json` is the same string `getJson()` produced. Pass `null` to clear. |
|
|
100
|
-
| `saveTemplate({ projectId })` | `Promise<TemplateApiResponse>` |
|
|
101
|
-
| `loadTemplate({ projectId, templateId? })` | `Promise<TemplateApiResponse>` |
|
|
137
|
+
| `saveTemplate({ projectId, templateId? })` | `Promise<TemplateApiResponse>` | Create or update a template. Omit `templateId` to create; include it to update. POSTs to the configured `saveTemplateUrl` (default `/template/create`). |
|
|
138
|
+
| `loadTemplate({ projectId, templateId? })` | `Promise<TemplateApiResponse>` | GET the configured `getTemplatesUrl` or `getTemplateByIdUrl` and pipe the JSON back into `loadJson(...)`. |
|
|
139
|
+
|
|
140
|
+
## API configuration
|
|
141
|
+
|
|
142
|
+
The editor talks to a small set of backend endpoints. Defaults point at the hosted eSender API, but every URL is overridable via the `apiConfig` prop so consumers can self-host the backend.
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
<Package
|
|
146
|
+
ref={ref}
|
|
147
|
+
apiKey="…"
|
|
148
|
+
apiConfig={{
|
|
149
|
+
baseUrl: 'https://api.example.com',
|
|
150
|
+
verifyUrl: '/license/verify',
|
|
151
|
+
refreshUrl: '/license/refresh',
|
|
152
|
+
saveTemplateUrl: '/templates',
|
|
153
|
+
getTemplatesUrl: '/templates/:projectId',
|
|
154
|
+
getTemplateByIdUrl: '/templates/:projectId/:templateId',
|
|
155
|
+
uploadMediaUrl: '/media',
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Defaults
|
|
161
|
+
|
|
162
|
+
| Field | Default | Method | Purpose |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| `baseUrl` | `https://esender.in/api` | — | Prepended to every relative URL below. |
|
|
165
|
+
| `verifyUrl` | `/verify` | POST | Exchange `apiKey` for access + refresh tokens. |
|
|
166
|
+
| `refreshUrl` | `/refresh` | POST | Exchange a refresh token for a new access token. |
|
|
167
|
+
| `saveTemplateUrl` | `/template/create` | POST | Create (or update, if `templateId` is in the body) a template. |
|
|
168
|
+
| `getTemplatesUrl` | `/template/:projectId` | GET | Load the project's current template. |
|
|
169
|
+
| `getTemplateByIdUrl` | `/template/:projectId/:templateId` | GET | Load a specific template. |
|
|
170
|
+
| `uploadMediaUrl` | `/media/add` | POST (multipart) | Upload an image; returns a hosted URL. |
|
|
171
|
+
|
|
172
|
+
Template URLs may contain `:projectId` and `:templateId` placeholders. They're substituted at call time and URL-encoded.
|
|
173
|
+
|
|
174
|
+
## Events
|
|
175
|
+
|
|
176
|
+
All callbacks are optional. Pass any subset.
|
|
177
|
+
|
|
178
|
+
| Callback | Argument | Fires when |
|
|
179
|
+
|---|---|---|
|
|
180
|
+
| `onSave` | `{ projectId, templateId?, response }` | `saveTemplate()` returns `status: true`. |
|
|
181
|
+
| `onLoad` | `{ projectId, templateId?, response }` | `loadTemplate()` returns `status: true` and the JSON is applied. |
|
|
182
|
+
| `onTemplateChange` | The exported JSON object | The editor's MJML tree changes (every edit, drag/drop, undo/redo, `loadJson`). |
|
|
183
|
+
| `onHtmlExport` | `string` (the HTML) | A consumer calls `ref.current.getHtml()`. |
|
|
184
|
+
| `onMediaUpload` | `{ url, file?, response? }` | The uploader receives a hosted URL from `uploadMediaUrl`. |
|
|
185
|
+
|
|
186
|
+
Callbacks are stored internally in a ref, so inline arrow functions are safe — they will not cause spurious re-fires.
|
|
187
|
+
|
|
188
|
+
## Theme isolation
|
|
189
|
+
|
|
190
|
+
The editor mounts its own `<ConfigProvider>` with the eSender primary color and uses an internal `getPopupContainer` so antd portals (Tooltip, Dropdown, Select, Drawer, Popover) render inside the editor's root rather than escaping to `document.body`. As a result:
|
|
191
|
+
|
|
192
|
+
- The editor will **not** restyle your host app's antd components.
|
|
193
|
+
- Your host app's `<ConfigProvider theme={…}>` will **not** restyle the editor.
|
|
194
|
+
- Antd's `message` / `notification` / `Modal.useApp` are scoped via `<App />` inside the editor.
|
|
195
|
+
|
|
196
|
+
You can safely nest `<Package />` inside a host app that already uses antd with a different theme.
|
|
102
197
|
|
|
103
198
|
## License handshake
|
|
104
199
|
|
|
@@ -106,9 +201,9 @@ The editor will not render until the eSender license is verified:
|
|
|
106
201
|
|
|
107
202
|
1. Host passes `apiKey` to `<Package />`.
|
|
108
203
|
2. On mount, the package checks `localStorage` for a cached access token.
|
|
109
|
-
3. If absent, it `POST`s to `https://esender.in/api/
|
|
204
|
+
3. If absent, it `POST`s to `{baseUrl}{verifyUrl}` (default `https://esender.in/api/verify`) with `x-api-key: <apiKey>`.
|
|
110
205
|
4. On success the returned `accessToken` and `refreshToken` are stored in `localStorage` and the editor mounts.
|
|
111
|
-
5. While running, the internal axios client attaches the access token to protected calls; on `401` / `token expired` it calls
|
|
206
|
+
5. While running, the internal axios client attaches the access token to protected calls; on `401` / `token expired` it calls `{baseUrl}{refreshUrl}` once and retries. If refresh fails, tokens are cleared and the next mount re-runs verify.
|
|
112
207
|
|
|
113
208
|
No further token management is required from the host.
|
|
114
209
|
|
|
@@ -196,6 +291,11 @@ import Package, {
|
|
|
196
291
|
type RefreshTokenResponse,
|
|
197
292
|
type TemplateApiResponse,
|
|
198
293
|
type TemplateDocument,
|
|
294
|
+
type ApiConfig,
|
|
295
|
+
type EditorEventCallbacks,
|
|
296
|
+
type SaveEvent,
|
|
297
|
+
type LoadEvent,
|
|
298
|
+
type MediaUploadEvent,
|
|
199
299
|
} from 'esender-email-editor';
|
|
200
300
|
```
|
|
201
301
|
|