esender-email-editor 1.0.3 → 1.1.2

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 CHANGED
@@ -11,24 +11,47 @@ A drag-and-drop **MJML email editor** for React 18 + Ant Design 5. The editor re
11
11
  The package is published on the public **npm registry**.
12
12
 
13
13
  ```bash
14
- npm install esender-email-editor
14
+ npm install esender-email-editor --legacy-peer-deps
15
15
  ```
16
16
 
17
+ > `--legacy-peer-deps` is required because `react-giphy-searchbox` (a transitive runtime dependency used by the Gif block) still declares an ancient `react@16.x` peer. The runtime works fine on React 18 — only the metadata is stale.
18
+
17
19
  ## Peer dependencies
18
20
 
19
- The package externalizes its heavy runtime deps so the host app provides them. Install the tested versions:
21
+ 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
22
 
21
23
  ```bash
22
24
  npm install \
23
- react@^18.3.1 react-dom@^18.3.1 \
24
- antd@^5.25.1 @ant-design/icons@^5.6.1 @ant-design/pro-components@^2.8.7 \
25
- @reduxjs/toolkit@^2.1.0 react-redux@^9.2.0 redux-persist@^6.0.0 \
26
- mjml-browser@^4.15.3 cheerio@1.0.0-rc.12 \
27
- lodash@^4.17.21 axios@^1.9.0 swr@^2.3.3 \
28
- framer-motion@^12.12.1 html2canvas@^1.4.1 \
29
- styled-components@^6.1.18 use-sync-external-store@^1.5.0
25
+ react@^18 react-dom@^18 \
26
+ antd@^5 @ant-design/icons@^5 @ant-design/pro-components@^2 \
27
+ @reduxjs/toolkit@^2 react-redux@^9 redux-persist@^6 \
28
+ mjml-browser@^4 cheerio@1.0.0-rc.12 \
29
+ lodash@^4 axios@^1 swr@^2 \
30
+ framer-motion@^12 html2canvas@^1 \
31
+ styled-components@^6 use-sync-external-store@^1
30
32
  ```
31
33
 
34
+ ### Supported version ranges
35
+
36
+ | Package | Declared range | Notes |
37
+ |---|---|---|
38
+ | `react`, `react-dom` | `>=18.0.0 <19.0.0` | React 19 is **not** supported (see below). |
39
+ | `antd` | `>=5.0.0 <6.0.0` | |
40
+ | `@ant-design/icons` | `>=5.0.0 <6.0.0` | v6 conflicts with `pro-components`. |
41
+ | `@ant-design/pro-components` | `>=2.0.0 <3.0.0` | |
42
+ | `@reduxjs/toolkit` | `>=2.0.0 <3.0.0` | |
43
+ | `react-redux` | `>=8.0.0 <10.0.0` | v8 and v9 both work under React 18. |
44
+ | `redux-persist` | `>=6.0.0 <7.0.0` | |
45
+ | `mjml-browser` | `>=4.15.0 <6.0.0` | v5 still requires `cheerio`. |
46
+ | `lodash` | `>=4.17.0 <5.0.0` | |
47
+ | `axios` | `>=1.0.0 <2.0.0` | |
48
+ | `swr` | `>=2.0.0 <3.0.0` | |
49
+ | `framer-motion` | `>=10.0.0 <13.0.0` | v10+; older majors removed `motion` imports we use. |
50
+ | `html2canvas` | `>=1.0.0 <2.0.0` | |
51
+ | `styled-components` | `>=5.0.0 <7.0.0` | v5 and v6 both work. |
52
+ | `use-sync-external-store` | `>=1.0.0 <2.0.0` | |
53
+ | `cheerio` | `1.0.0-rc.12` | Transitive of `mjml-browser`; host must install. Not declared as a peer. |
54
+
32
55
  ### The non-negotiables
33
56
 
34
57
  | Constraint | Why |
@@ -49,26 +72,36 @@ export const Composer = () => {
49
72
  const ref = useRef<EditorHandle>(null);
50
73
  const projectId = 'proj_123';
51
74
 
52
- const save = async () => {
75
+ const create = async () => {
76
+ // Omit templateId → server creates a new template.
53
77
  const res = await ref.current?.saveTemplate({ projectId });
54
78
  if (!res?.status) console.error(res?.message);
55
79
  };
56
80
 
57
- const load = async () => {
58
- await ref.current?.loadTemplate({ projectId }); // omit templateId → latest for project
81
+ const update = async (templateId: string) => {
82
+ // Pass templateId → server updates the existing template.
83
+ await ref.current?.saveTemplate({ projectId, templateId });
84
+ };
85
+
86
+ const load = async (templateId?: string) => {
87
+ // Omit templateId → loads the project's current template.
88
+ await ref.current?.loadTemplate({ projectId, templateId });
59
89
  };
60
90
 
61
91
  return (
62
92
  <>
63
93
  <button onClick={() => console.log(ref.current?.getHtml())}>Export HTML</button>
64
94
  <button onClick={() => console.log(ref.current?.getJson())}>Export JSON</button>
65
- <button onClick={save}>Save to API</button>
66
- <button onClick={load}>Load from API</button>
95
+ <button onClick={create}>Create</button>
96
+ <button onClick={() => load()}>Load latest</button>
67
97
 
68
98
  <Package
69
99
  ref={ref}
70
100
  apiKey={process.env.NEXT_PUBLIC_ESENDER_KEY!}
71
101
  onLicenseError={(err: LicenseError) => console.error(err)}
102
+ onSave={(e) => console.log('saved', e.templateId, e.response)}
103
+ onLoad={(e) => console.log('loaded', e.response)}
104
+ onTemplateChange={(json) => console.log('changed', json)}
72
105
  showUndoRedo
73
106
  />
74
107
  </>
@@ -84,10 +117,15 @@ export const Composer = () => {
84
117
 
85
118
  | Prop | Type | Required | Description |
86
119
  |---|---|---|---|
87
- | `apiKey` | `string` | yes | eSender license key. Exchanged for a JWT on mount. |
120
+ | `apiKey` | `string` | yes | License key. Exchanged for a JWT on mount. |
88
121
  | `customButtons` | `ReactNode` | no | Slot rendered in the editor's top-right toolbar. |
89
122
  | `showUndoRedo` | `boolean` | no | Show the undo/redo toolbar (default `true`). |
90
123
  | `onLicenseError` | `(error: LicenseError) => void` | no | Fires when the license verify call fails. |
124
+ | `onSave` | `(event: SaveEvent) => void` | no | Fires after a successful `saveTemplate()` call. |
125
+ | `onLoad` | `(event: LoadEvent) => void` | no | Fires after a successful `loadTemplate()` call. |
126
+ | `onTemplateChange` | `(json: unknown) => void` | no | Fires on every edit (the exported JSON object — not stringified). |
127
+ | `onHtmlExport` | `(html: string) => void` | no | Fires when `ref.current.getHtml()` is invoked. |
128
+ | `onMediaUpload` | `(event: MediaUploadEvent) => void` | no | Fires after a successful media upload. |
91
129
  | `projectId` | `string` | no | **Deprecated.** Ignored at runtime. Kept only for migration. |
92
130
 
93
131
  ### `EditorHandle` (`ref.current`)
@@ -95,10 +133,38 @@ export const Composer = () => {
95
133
  | Method | Returns | Description |
96
134
  |---|---|---|
97
135
  | `getJson()` | `string` | Stringified MJML JSON tree (export-ready). |
98
- | `getHtml()` | `string` | Compiled email HTML via `mjml-browser`. |
136
+ | `getHtml()` | `string` | Compiled email HTML via `mjml-browser`. Triggers `onHtmlExport`. |
99
137
  | `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>` | POSTs the current HTML + JSON to `/license/template/create`. |
101
- | `loadTemplate({ projectId, templateId? })` | `Promise<TemplateApiResponse>` | GETs `/license/template/:projectId[/:templateId]` and pipes the JSON back into `loadJson(...)`. |
138
+ | `saveTemplate({ projectId, templateId? })` | `Promise<TemplateApiResponse>` | Create or update a template. Omit `templateId` to create; include it to update. POSTs to `https://esender.in/api/license/template/create`. |
139
+ | `loadTemplate({ projectId, templateId? })` | `Promise<TemplateApiResponse>` | GET from `https://esender.in/api/license/template/:projectId[/:templateId]` and pipe the JSON back into `loadJson(...)`. |
140
+
141
+ ## Backend
142
+
143
+ The editor talks to the hosted eSender API at `https://esender.in/api`. Endpoints are fixed and not configurable; consumers only supply `apiKey`.
144
+
145
+ ## Events
146
+
147
+ All callbacks are optional. Pass any subset.
148
+
149
+ | Callback | Argument | Fires when |
150
+ |---|---|---|
151
+ | `onSave` | `{ projectId, templateId?, response }` | `saveTemplate()` returns `status: true`. |
152
+ | `onLoad` | `{ projectId, templateId?, response }` | `loadTemplate()` returns `status: true` and the JSON is applied. |
153
+ | `onTemplateChange` | The exported JSON object | The editor's MJML tree changes (every edit, drag/drop, undo/redo, `loadJson`). |
154
+ | `onHtmlExport` | `string` (the HTML) | A consumer calls `ref.current.getHtml()`. |
155
+ | `onMediaUpload` | `{ url, file?, response? }` | The uploader receives a hosted URL from `uploadMediaUrl`. |
156
+
157
+ Callbacks are stored internally in a ref, so inline arrow functions are safe — they will not cause spurious re-fires.
158
+
159
+ ## Theme isolation
160
+
161
+ 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:
162
+
163
+ - The editor will **not** restyle your host app's antd components.
164
+ - Your host app's `<ConfigProvider theme={…}>` will **not** restyle the editor.
165
+ - Antd's `message` / `notification` / `Modal.useApp` are scoped via `<App />` inside the editor.
166
+
167
+ You can safely nest `<Package />` inside a host app that already uses antd with a different theme.
102
168
 
103
169
  ## License handshake
104
170
 
@@ -108,7 +174,7 @@ The editor will not render until the eSender license is verified:
108
174
  2. On mount, the package checks `localStorage` for a cached access token.
109
175
  3. If absent, it `POST`s to `https://esender.in/api/license/verify` with `x-api-key: <apiKey>`.
110
176
  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 `/license/refresh` once and retries. If refresh fails, tokens are cleared and the next mount re-runs verify.
177
+ 5. While running, the internal axios client attaches the access token to protected calls; on `401` / `token expired` it calls `https://esender.in/api/license/refresh` once and retries. If refresh fails, tokens are cleared and the next mount re-runs verify.
112
178
 
113
179
  No further token management is required from the host.
114
180
 
@@ -196,6 +262,10 @@ import Package, {
196
262
  type RefreshTokenResponse,
197
263
  type TemplateApiResponse,
198
264
  type TemplateDocument,
265
+ type EditorEventCallbacks,
266
+ type SaveEvent,
267
+ type LoadEvent,
268
+ type MediaUploadEvent,
199
269
  } from 'esender-email-editor';
200
270
  ```
201
271