@one-million-lines/email-builder 0.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/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +211 -0
- package/dist/App.d.ts +1 -0
- package/dist/core/aiActions.d.ts +52 -0
- package/dist/core/plugins.d.ts +26 -0
- package/dist/core/renderer.d.ts +2 -0
- package/dist/core/theme.d.ts +3 -0
- package/dist/core/types.d.ts +150 -0
- package/dist/core/utils.d.ts +3 -0
- package/dist/core/validation.d.ts +810 -0
- package/dist/editor/Canvas.d.ts +1 -0
- package/dist/editor/LeftSidebar.d.ts +1 -0
- package/dist/editor/RecommendationsPanel.d.ts +6 -0
- package/dist/editor/RightSidebar.d.ts +1 -0
- package/dist/editor/TemplatesModal.d.ts +6 -0
- package/dist/editor/TopBar.d.ts +1 -0
- package/dist/email-builder.cjs +80 -0
- package/dist/email-builder.cjs.map +1 -0
- package/dist/email-builder.js +8766 -0
- package/dist/email-builder.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/modules/basic.d.ts +2 -0
- package/dist/modules/content.d.ts +2 -0
- package/dist/modules/cta.d.ts +2 -0
- package/dist/modules/defaultModules.d.ts +14 -0
- package/dist/modules/ecommerce.d.ts +2 -0
- package/dist/modules/feature.d.ts +2 -0
- package/dist/modules/footer.d.ts +2 -0
- package/dist/modules/header.d.ts +2 -0
- package/dist/modules/helpers.d.ts +89 -0
- package/dist/modules/menu.d.ts +2 -0
- package/dist/modules/registry.d.ts +24 -0
- package/dist/modules/social.d.ts +2 -0
- package/dist/modules/transactional.d.ts +2 -0
- package/dist/plugins/imageUploader/index.d.ts +36 -0
- package/dist/recommendations/logic.d.ts +99 -0
- package/dist/store/emailStore.d.ts +42 -0
- package/dist/styles.css +1 -0
- package/dist/templates/abandonedCart.d.ts +3 -0
- package/dist/templates/blogDigest.d.ts +3 -0
- package/dist/templates/eventInvite.d.ts +3 -0
- package/dist/templates/flashSale.d.ts +3 -0
- package/dist/templates/index.d.ts +2 -0
- package/dist/templates/orderReceipt.d.ts +3 -0
- package/dist/templates/productLaunch.d.ts +3 -0
- package/dist/templates/registry.d.ts +25 -0
- package/dist/templates/weeklyNewsletter.d.ts +3 -0
- package/dist/templates/welcomeOnboarding.d.ts +3 -0
- package/dist/themes/defaultThemes.d.ts +7 -0
- package/package.json +91 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this package are documented here.
|
|
4
|
+
This project adheres to [Semantic Versioning](https://semver.org/) and the
|
|
5
|
+
[Keep a Changelog](https://keepachangelog.com/) format.
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- npm library build (`dist/email-builder.js` ESM, `dist/email-builder.cjs` CommonJS).
|
|
11
|
+
- Generated TypeScript declarations (`dist/index.d.ts` + per-module `.d.ts`).
|
|
12
|
+
- Compiled stylesheet at `dist/styles.css`, imported via `openpostcards-builder/styles.css`.
|
|
13
|
+
- `exports` map, `files` allowlist, and publishing metadata in `package.json`.
|
|
14
|
+
- Library-only stylesheet (`src/lib.css`) that omits app `html/body/#root` globals.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- `react` and `react-dom` are now **peer dependencies** (no longer bundled).
|
|
18
|
+
- Runtime dependencies (`@dnd-kit/*`, `lucide-react`, `zod`, `zustand`) are
|
|
19
|
+
externalized from the bundle and declared as `dependencies`.
|
|
20
|
+
|
|
21
|
+
## [0.1.0]
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- Initial visual email builder: JSON document model, modules, templates, themes,
|
|
25
|
+
table-based HTML renderer, plugin hooks, React component, and vanilla factory.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alex Rada
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# OpenPostcards AI Builder
|
|
2
|
+
|
|
3
|
+
OpenPostcards AI Builder is a visual email builder for composing modular marketing emails from a JSON document model and exporting table-based HTML. It can run as a standalone app or be embedded into other products through a React component or a vanilla JavaScript wrapper.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
It gives users a drag-and-drop editor with reusable modules, themes, HTML export, and plugin hooks for extending the builder.
|
|
8
|
+
|
|
9
|
+
## Why it exists
|
|
10
|
+
|
|
11
|
+
Marketing teams often need a reusable editor that produces email-safe HTML without tying the editor to a specific CMS or backend. This project separates the editing experience, the JSON document model, and the final HTML renderer so it can be embedded elsewhere.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- Visual editor with top bar, left sidebar, canvas, and right sidebar
|
|
16
|
+
- JSON-first email document model
|
|
17
|
+
- Built-in modules, templates, and themes
|
|
18
|
+
- Table-based HTML rendering for email output
|
|
19
|
+
- Local autosave via Zustand store
|
|
20
|
+
- React component API and vanilla JS factory
|
|
21
|
+
- Plugin registration for modules and integrations
|
|
22
|
+
- AI action pipeline that validates structured output with Zod before applying changes
|
|
23
|
+
|
|
24
|
+
## How it works
|
|
25
|
+
|
|
26
|
+
1. The app registers default modules and starter templates at load time.
|
|
27
|
+
2. The email document lives in a Zustand store with import, export, and autosave support.
|
|
28
|
+
3. The canvas and sidebars edit that shared document model.
|
|
29
|
+
4. `renderEmailHtml()` converts the JSON document into table-based email HTML.
|
|
30
|
+
5. Embedders can mount the editor with `EmailBuilder` or `createEmailBuilder()` and subscribe to document or HTML changes.
|
|
31
|
+
|
|
32
|
+
## Tech stack
|
|
33
|
+
|
|
34
|
+
- React
|
|
35
|
+
- TypeScript
|
|
36
|
+
- Vite
|
|
37
|
+
- Zustand
|
|
38
|
+
- Zod
|
|
39
|
+
- dnd-kit
|
|
40
|
+
|
|
41
|
+
## Project structure
|
|
42
|
+
|
|
43
|
+
```text
|
|
44
|
+
src/
|
|
45
|
+
core/ document types, renderer, validation, AI actions, plugins
|
|
46
|
+
editor/ top bar, sidebars, canvas
|
|
47
|
+
modules/ module registry and built-in modules
|
|
48
|
+
recommendations/ recommendation and fallback logic
|
|
49
|
+
store/ editor state and persistence
|
|
50
|
+
templates/ built-in email templates
|
|
51
|
+
themes/ theme definitions
|
|
52
|
+
plugins/ extension points, including image uploader helpers
|
|
53
|
+
index.ts public API for embedding
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Install
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install openpostcards-builder react react-dom
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`react` and `react-dom` (v18 or v19) are **peer dependencies** — install them in
|
|
63
|
+
your app; they are never bundled into the package.
|
|
64
|
+
|
|
65
|
+
> **MANUAL ACTION REQUIRED:** `openpostcards-builder` is a placeholder package
|
|
66
|
+
> name. Check availability and choose/reserve your own npm name before publishing,
|
|
67
|
+
> then update `package.json` `name`, `repository`, `homepage`, and `bugs`.
|
|
68
|
+
|
|
69
|
+
## Getting started
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git clone <repo-url>
|
|
73
|
+
cd email-builder
|
|
74
|
+
npm install
|
|
75
|
+
npm run dev
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Open `http://localhost:5315`.
|
|
79
|
+
|
|
80
|
+
## Configuration
|
|
81
|
+
|
|
82
|
+
This project does not require environment variables for local development.
|
|
83
|
+
|
|
84
|
+
The production build uses `/demo/email-builder/` as the Vite base path.
|
|
85
|
+
|
|
86
|
+
## Usage
|
|
87
|
+
|
|
88
|
+
React:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import { EmailBuilder } from "openpostcards-builder";
|
|
92
|
+
import "openpostcards-builder/styles.css";
|
|
93
|
+
|
|
94
|
+
<EmailBuilder
|
|
95
|
+
initialDocument={emailJson}
|
|
96
|
+
onChange={(doc) => console.log(doc)}
|
|
97
|
+
onExportHtml={(html) => console.log(html)}
|
|
98
|
+
/>;
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Vanilla JS:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { createEmailBuilder } from "openpostcards-builder";
|
|
105
|
+
import "openpostcards-builder/styles.css";
|
|
106
|
+
|
|
107
|
+
const instance = createEmailBuilder({
|
|
108
|
+
container: document.getElementById("builder")!,
|
|
109
|
+
initialDocument,
|
|
110
|
+
onChange(doc) {
|
|
111
|
+
console.log(doc);
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// instance.getDocument(); instance.exportHtml(); instance.exportJson(); instance.destroy();
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Always import the stylesheet once per app: `import "openpostcards-builder/styles.css";`
|
|
119
|
+
|
|
120
|
+
### Framework integration
|
|
121
|
+
|
|
122
|
+
Thin wrappers for each framework live in [`examples/`](./examples):
|
|
123
|
+
|
|
124
|
+
- **React** — `examples/react/App.jsx`
|
|
125
|
+
- **Vue 3** — `examples/vue/EmailBuilder.vue`
|
|
126
|
+
- **Angular** — `examples/angular/email-builder.component.ts`
|
|
127
|
+
- **Plain JS** — `examples/plain/index.html`
|
|
128
|
+
|
|
129
|
+
Vue and Angular mount the React-based editor through the framework-neutral
|
|
130
|
+
`createEmailBuilder()` factory (`getDocument` / `exportHtml` / `exportJson` /
|
|
131
|
+
`destroy`). React and ReactDOM remain peer dependencies in all cases.
|
|
132
|
+
|
|
133
|
+
### Styling & isolation
|
|
134
|
+
|
|
135
|
+
The stylesheet at `openpostcards-builder/styles.css` includes Tailwind
|
|
136
|
+
**Preflight**, a global CSS reset that normalizes `margin`, `box-sizing`,
|
|
137
|
+
headings, lists, etc. When embedding into an existing design system this can
|
|
138
|
+
affect host styles. Mitigations:
|
|
139
|
+
|
|
140
|
+
- Load the editor in a dedicated route/page where the reset is acceptable, or
|
|
141
|
+
- Scope/encapsulate it (e.g., render inside an iframe or a Shadow DOM host), or
|
|
142
|
+
- Build your own Tailwind stylesheet without Preflight if your app already
|
|
143
|
+
provides resets.
|
|
144
|
+
|
|
145
|
+
The editor root uses `h-screen w-screen`; place it in a full-size container.
|
|
146
|
+
|
|
147
|
+
### Server-side rendering
|
|
148
|
+
|
|
149
|
+
The package does not access `window`/`document` at import time, so it is safe to
|
|
150
|
+
import in SSR frameworks. The editor itself is client-only — render it in an
|
|
151
|
+
effect, or dynamically import it with SSR disabled in Next.js:
|
|
152
|
+
|
|
153
|
+
```tsx
|
|
154
|
+
import dynamic from "next/dynamic";
|
|
155
|
+
|
|
156
|
+
const EmailBuilder = dynamic(
|
|
157
|
+
() => import("openpostcards-builder").then((m) => m.EmailBuilder),
|
|
158
|
+
{ ssr: false }
|
|
159
|
+
);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Development
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npm run dev # standalone app
|
|
166
|
+
npm run build # build the library into dist/ (ESM + CJS + CSS + d.ts)
|
|
167
|
+
npm test # package-consumption tests (jsdom + React)
|
|
168
|
+
npm run lint # eslint
|
|
169
|
+
npm run validate:pack # build + npm pack --dry-run
|
|
170
|
+
npm run build:demo # build the standalone demo app
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Building & publishing
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
npm run build # produces dist/
|
|
177
|
+
npm pack --dry-run # inspect the tarball contents
|
|
178
|
+
npm pack # create the .tgz to test in a consumer app
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
To publish (run manually):
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
# MANUAL ACTION REQUIRED — choose a real package name first, then:
|
|
185
|
+
npm login
|
|
186
|
+
npm publish --access public
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`prepublishOnly` rebuilds the library automatically before publish.
|
|
190
|
+
|
|
191
|
+
## Roadmap
|
|
192
|
+
|
|
193
|
+
- Add persistence adapters beyond local storage
|
|
194
|
+
- Add more production-ready media upload integrations
|
|
195
|
+
- Add automated tests for document validation and HTML export
|
|
196
|
+
- Add richer framework wrappers and packaging docs
|
|
197
|
+
|
|
198
|
+
## Contributing
|
|
199
|
+
|
|
200
|
+
This project is public and open for collaboration. If you’re interested in contributing, improving the project, or discussing ideas, feel free to reach out.
|
|
201
|
+
|
|
202
|
+
LinkedIn: https://linkedin.com/in/alexrada
|
|
203
|
+
|
|
204
|
+
1. Fork the repository
|
|
205
|
+
2. Create a new branch
|
|
206
|
+
3. Make your changes
|
|
207
|
+
4. Open a pull request
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
This project is licensed under the MIT License. See [LICENSE](./LICENSE).
|
package/dist/App.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function App(): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { EmailDocument, EmailModule, Theme } from '../core/types';
|
|
2
|
+
export type AIAction = {
|
|
3
|
+
type: "update_meta";
|
|
4
|
+
patch: Partial<EmailDocument["meta"]>;
|
|
5
|
+
} | {
|
|
6
|
+
type: "update_settings";
|
|
7
|
+
patch: Partial<EmailDocument["settings"]>;
|
|
8
|
+
} | {
|
|
9
|
+
type: "apply_theme";
|
|
10
|
+
theme: Theme;
|
|
11
|
+
} | {
|
|
12
|
+
type: "insert_module";
|
|
13
|
+
index?: number;
|
|
14
|
+
module: EmailModule;
|
|
15
|
+
} | {
|
|
16
|
+
type: "delete_module";
|
|
17
|
+
moduleId: string;
|
|
18
|
+
} | {
|
|
19
|
+
type: "update_module";
|
|
20
|
+
moduleId: string;
|
|
21
|
+
patch: Partial<EmailModule>;
|
|
22
|
+
} | {
|
|
23
|
+
type: "update_element";
|
|
24
|
+
moduleId: string;
|
|
25
|
+
elementId: string;
|
|
26
|
+
patch: Record<string, unknown>;
|
|
27
|
+
};
|
|
28
|
+
export interface AIRequest {
|
|
29
|
+
task: "create_email" | "rewrite_text" | "generate_subject" | "generate_preview" | "translate" | "adapt_tone" | "apply_theme";
|
|
30
|
+
document?: EmailDocument;
|
|
31
|
+
instruction?: string;
|
|
32
|
+
context?: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
export interface AIResponse {
|
|
35
|
+
actions?: AIAction[];
|
|
36
|
+
document?: EmailDocument;
|
|
37
|
+
text?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface AIProvider {
|
|
40
|
+
generate(req: AIRequest): Promise<AIResponse>;
|
|
41
|
+
}
|
|
42
|
+
export declare function applyAIActions(doc: EmailDocument, actions: AIAction[]): EmailDocument;
|
|
43
|
+
/** Validate an AI-returned document before applying. */
|
|
44
|
+
export declare function validateAIDocument(doc: unknown): {
|
|
45
|
+
ok: true;
|
|
46
|
+
document: EmailDocument;
|
|
47
|
+
} | {
|
|
48
|
+
ok: false;
|
|
49
|
+
error: string;
|
|
50
|
+
};
|
|
51
|
+
/** Mock AI provider for demos and tests. Returns deterministic patches. */
|
|
52
|
+
export declare const mockAIProvider: AIProvider;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ModuleDefinition } from '../modules/registry';
|
|
2
|
+
import { Theme } from './types';
|
|
3
|
+
import { AIProvider } from './aiActions';
|
|
4
|
+
export interface AssetProvider {
|
|
5
|
+
upload: (file: File) => Promise<{
|
|
6
|
+
url: string;
|
|
7
|
+
alt?: string;
|
|
8
|
+
}>;
|
|
9
|
+
}
|
|
10
|
+
export interface BuilderHandle {
|
|
11
|
+
registerModule: (def: ModuleDefinition) => void;
|
|
12
|
+
registerTheme: (theme: Theme) => void;
|
|
13
|
+
registerAssetProvider: (provider: AssetProvider) => void;
|
|
14
|
+
setAIProvider: (provider: AIProvider) => void;
|
|
15
|
+
}
|
|
16
|
+
export type PluginType = "modules" | "themes" | "asset-provider" | "ai-provider";
|
|
17
|
+
export interface Plugin {
|
|
18
|
+
name: string;
|
|
19
|
+
type: PluginType;
|
|
20
|
+
setup: (builder: BuilderHandle) => void;
|
|
21
|
+
}
|
|
22
|
+
export declare const builder: BuilderHandle;
|
|
23
|
+
export declare function registerPlugin(plugin: Plugin): void;
|
|
24
|
+
export declare function getRegisteredThemes(): Theme[];
|
|
25
|
+
export declare function getAssetProvider(): AssetProvider | null;
|
|
26
|
+
export declare function getAIProvider(): AIProvider | null;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export type ElementType = "text" | "image" | "button" | "spacer" | "divider" | "productGrid";
|
|
2
|
+
export interface BaseStyle {
|
|
3
|
+
paddingTop?: number;
|
|
4
|
+
paddingBottom?: number;
|
|
5
|
+
paddingLeft?: number;
|
|
6
|
+
paddingRight?: number;
|
|
7
|
+
backgroundColor?: string;
|
|
8
|
+
align?: "left" | "center" | "right";
|
|
9
|
+
borderRadius?: number;
|
|
10
|
+
border?: string;
|
|
11
|
+
hideOn?: "mobile" | "desktop";
|
|
12
|
+
/**
|
|
13
|
+
* Per-breakpoint overrides applied via @media (max-width:600px).
|
|
14
|
+
* Any field accepted by the owning element's style can be placed here.
|
|
15
|
+
*/
|
|
16
|
+
mobile?: Record<string, unknown>;
|
|
17
|
+
}
|
|
18
|
+
export interface TextElement {
|
|
19
|
+
id: string;
|
|
20
|
+
type: "text";
|
|
21
|
+
role?: "headline" | "subheadline" | "body" | "caption";
|
|
22
|
+
content: string;
|
|
23
|
+
style?: BaseStyle & {
|
|
24
|
+
fontFamily?: string;
|
|
25
|
+
fontSize?: number;
|
|
26
|
+
lineHeight?: number;
|
|
27
|
+
letterSpacing?: number;
|
|
28
|
+
fontWeight?: number | string;
|
|
29
|
+
color?: string;
|
|
30
|
+
link?: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export interface ImageElement {
|
|
34
|
+
id: string;
|
|
35
|
+
type: "image";
|
|
36
|
+
src: string;
|
|
37
|
+
alt?: string;
|
|
38
|
+
link?: string;
|
|
39
|
+
style?: BaseStyle & {
|
|
40
|
+
width?: number;
|
|
41
|
+
height?: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export interface ButtonElement {
|
|
45
|
+
id: string;
|
|
46
|
+
type: "button";
|
|
47
|
+
label: string;
|
|
48
|
+
link: string;
|
|
49
|
+
style?: BaseStyle & {
|
|
50
|
+
backgroundColor?: string;
|
|
51
|
+
color?: string;
|
|
52
|
+
fontSize?: number;
|
|
53
|
+
fontFamily?: string;
|
|
54
|
+
fontWeight?: number | string;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export interface SpacerElement {
|
|
58
|
+
id: string;
|
|
59
|
+
type: "spacer";
|
|
60
|
+
height: number;
|
|
61
|
+
}
|
|
62
|
+
export interface DividerElement {
|
|
63
|
+
id: string;
|
|
64
|
+
type: "divider";
|
|
65
|
+
style?: {
|
|
66
|
+
color?: string;
|
|
67
|
+
thickness?: number;
|
|
68
|
+
paddingTop?: number;
|
|
69
|
+
paddingBottom?: number;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export interface Product {
|
|
73
|
+
id: string;
|
|
74
|
+
image: string;
|
|
75
|
+
imageAlt?: string;
|
|
76
|
+
name: string;
|
|
77
|
+
oldPrice?: string;
|
|
78
|
+
finalPrice: string;
|
|
79
|
+
description?: string;
|
|
80
|
+
link?: string;
|
|
81
|
+
buttonLabel?: string;
|
|
82
|
+
}
|
|
83
|
+
export interface ProductGridElement {
|
|
84
|
+
id: string;
|
|
85
|
+
type: "productGrid";
|
|
86
|
+
products: Product[];
|
|
87
|
+
columns: 1 | 2 | 3;
|
|
88
|
+
showOldPrice: boolean;
|
|
89
|
+
showButton: boolean;
|
|
90
|
+
showDescription: boolean;
|
|
91
|
+
buttonLabel?: string;
|
|
92
|
+
style?: BaseStyle & {
|
|
93
|
+
nameColor?: string;
|
|
94
|
+
finalPriceColor?: string;
|
|
95
|
+
oldPriceColor?: string;
|
|
96
|
+
buttonBackgroundColor?: string;
|
|
97
|
+
buttonColor?: string;
|
|
98
|
+
gap?: number;
|
|
99
|
+
cardBackgroundColor?: string;
|
|
100
|
+
borderRadius?: number;
|
|
101
|
+
align?: "left" | "center" | "right";
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
export type EmailElement = TextElement | ImageElement | ButtonElement | SpacerElement | DividerElement | ProductGridElement;
|
|
105
|
+
export interface EmailModule {
|
|
106
|
+
id: string;
|
|
107
|
+
type: string;
|
|
108
|
+
name: string;
|
|
109
|
+
style?: BaseStyle;
|
|
110
|
+
children: EmailElement[];
|
|
111
|
+
/** Free-form per-module data used by plugins (e.g. `recommendations` logic). */
|
|
112
|
+
data?: Record<string, unknown>;
|
|
113
|
+
}
|
|
114
|
+
export interface ThemeTokens {
|
|
115
|
+
colors: Record<string, string>;
|
|
116
|
+
fonts: Record<string, string>;
|
|
117
|
+
spacing: Record<string, number>;
|
|
118
|
+
radius: Record<string, number>;
|
|
119
|
+
}
|
|
120
|
+
export interface Theme {
|
|
121
|
+
id: string;
|
|
122
|
+
name: string;
|
|
123
|
+
tokens: ThemeTokens;
|
|
124
|
+
}
|
|
125
|
+
export interface EmailSettings {
|
|
126
|
+
width: number;
|
|
127
|
+
backgroundColor: string;
|
|
128
|
+
contentBackgroundColor: string;
|
|
129
|
+
}
|
|
130
|
+
export interface EmailMeta {
|
|
131
|
+
name: string;
|
|
132
|
+
previewText: string;
|
|
133
|
+
}
|
|
134
|
+
export interface EmailDocument {
|
|
135
|
+
version: string;
|
|
136
|
+
meta: EmailMeta;
|
|
137
|
+
theme: Theme;
|
|
138
|
+
settings: EmailSettings;
|
|
139
|
+
modules: EmailModule[];
|
|
140
|
+
}
|
|
141
|
+
export type Selection = {
|
|
142
|
+
kind: "email";
|
|
143
|
+
} | {
|
|
144
|
+
kind: "module";
|
|
145
|
+
moduleId: string;
|
|
146
|
+
} | {
|
|
147
|
+
kind: "element";
|
|
148
|
+
moduleId: string;
|
|
149
|
+
elementId: string;
|
|
150
|
+
} | null;
|