@standhigher/react-email-editor 0.2.0 → 0.4.1

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 CHANGED
@@ -1,5 +1,65 @@
1
1
  # 更新日志
2
2
 
3
+ ## 0.4.1 — 2026-10-28
4
+
5
+ ### 文档与类型提示
6
+
7
+ - 精简中英文 README,增加模块架构图,并将完整接入说明整理为独立的 API 参考、基础场景和复杂场景文档。
8
+ - 补充保存恢复、主体与变量、多实例订阅、自定义区块、多区域富文本、图片资源选择器及单区块模板预览示例,同步更新自定义区块指南的文档导航。
9
+ - 为编辑器实例、文档模型、插件与控件、邮件渲染工具、富文本及基础 UI 组件补充英文 JSDoc,随 TypeScript 声明发布,便于 IDE 悬停查看参数、默认值及行为边界。
10
+ - 本次仅更新文档、类型注释和包版本,不改变运行时行为、公开 API 或持久化文档版本;文档仍为 `version: 4`。
11
+
12
+ ### 验证
13
+
14
+ - 类型检查、Oxlint、构建和全部 96 项 Node 测试通过。
15
+ - 中英文 README、API 参考及场景文档的 34 个 TypeScript/TSX 示例文件通过类型检查,文档本地链接目标均存在。
16
+
17
+ ## 0.4.0 — 2026-10-28
18
+
19
+ ### 新增与改进
20
+
21
+ - React 接入统一使用 `useEmailEditorConfig()`、`useEmailEditor()` 和 `<EmailEditor editor={editor}>`;新增 `useEmailEditorState(editor)` 按需订阅缓存的只读文档快照。
22
+ - 编辑器实例引用稳定且互相隔离,无需挂载界面即可读取、校验及导出;提供 `setDocument()` 和 `reset()`,替换或重置时清空旧文档的选择、焦点与控件状态。
23
+ - 配置与变量更新保留当前文档;外部主体输入按值变化合并,初始化不触发回调,文档读取及回调参数保持独立快照。
24
+ - playground 增加导出、新建、重置与订阅示例,中英文接入文档同步更新。
25
+
26
+ ### 修复与验证
27
+
28
+ - 配置、变量、主体及回调在提交阶段同步,子组件的 layout effect 可读取新值;订阅通知及主体回调延后到 layout 阶段,被中断的并发 render 不改变现有实例。
29
+ - 主题输入框使用实例级 ID,多个编辑器的标签、输入框和字数提示独立关联。
30
+ - 补齐配置更新时序、并发中断、多实例主题关联、旧 API 负向类型契约和源码兼容入口检查。
31
+
32
+ ### 不兼容变更
33
+
34
+ - 移除公开 `createEmailEditorConfig`、`EmailEditorConfigOptions`、`EmailEditorHandle`、组件 ref 和旧状态 Props,不提供别名或兼容层。配置与状态改为传给 `useEmailEditor()`,组件只接收 `editor` 和 `height`。
35
+ - `blockToHtml()` 和 `renderBlockContent()` 的第四个参数仅接受 `BlockRenderOptions` 对象;移除 boolean 参数适配,JavaScript 传入 boolean 时明确报错。模板预览改用 `{ resolveHtmlTemplate: true }`。
36
+ - 持久化文档继续使用 `version: 4`,不迁移旧版本;本次版本号变化仅涉及包 API 与行为。
37
+
38
+ ## 0.3.0 — 2026-10-28
39
+
40
+ ### 新增与改进
41
+
42
+ - 新增 `defineBlock()`、`EmailControls` 和 `createEmailEditorConfig()`,编辑器与独立邮件导出共用插件配置;内置区块库支持筛选、排序和显式覆盖。
43
+ - 区块插件支持自定义控件组件、React Canvas/Inspector、多区域 `EditableRichText`、`ControlEditor`、声明式嵌套列表、图片资源选择器与统一邮件 HTML 工具。
44
+ - 新增 `config.validate()`,汇总数字范围、选项、URL/图片协议、列表数量及业务校验问题;`setControls()` 支持一次更新多个关联控件。
45
+ - 补充中英文自定义区块 API 文档、优惠券及商品卡片示例,以及组件级回归测试。
46
+
47
+ ### 修复
48
+
49
+ - 修改链接 URL 或打开方式时保留已有的显式颜色和下划线,重置后恢复继承 BODY。
50
+ - 画布主体使用与预览及邮件导出一致的基础排版,修复 `em` 内边距、边框等尺寸的计算差异。
51
+ - 修复列表异步回写覆盖后续编辑或排序的问题;按稳定行标识合并最新值,删除或卸载后忽略过期结果,新行不复用已删除行的标识。
52
+ - 自定义控件接收独立的值、主体配置和变量快照,避免直接修改参数污染宿主;自定义 Canvas/Inspector 的变量同步隔离。
53
+ - 可选下拉框显示空选项并支持清空;控件标签正确关联下拉框。
54
+ - 检查器富文本继承主体及已启用的区块排版、段落、列表和链接样式,主体配置变化后同步更新。
55
+ - 缺失插件或邮件导出失败时明确报错;画布和检查器异常按区块隔离,并保留原始节点数据。
56
+
57
+ ### 不兼容变更
58
+
59
+ - 自定义区块改为 `defineBlock` + `EmailControls` + `createEmailEditorConfig`,组件接收 `config`;移除旧 `blocks` Prop、公开 `BlockDefinition/FieldConfig/FieldType` 和 `resolveBlockStyleFields`,不保留适配层。控件工厂使用 `EmailControls.text(...)`,区块及列表通过 `controls` 声明控件(不保留 `field` / `fields` 别名);运行时、持久化节点统一使用 `controls`,样式声明使用 `styleControls`,更新方法使用 `setControl/setControls`,配套类型、`ControlEditor` 和 `controlPath` 同步更名。内容默认值来自控件声明,渲染函数改为 `renderEmail`,文档格式升为 v4,不兼容旧数据、不提供自动迁移。
60
+ - 邮件主体设置统一改为 `body`:文档使用 `EmailDocument.body`,组件使用 `body` / `onBodyChange`,类型及工具改为 `EmailBodySettings`、`DEFAULT_EMAIL_BODY`、`resolveEmailBody`;区块渲染上下文和 `BlockRenderOptions` 同步使用 `body`。移除旧 Global API,不保留别名或迁移代码。
61
+ - 文档版本升为 `4`,不支持旧版本。BODY 的 General 提供文字颜色、链接颜色、字体、字重和链接下划线开关;Content 提供唯一主体 Background、内容宽度、Padding 和区块间距,边框颜色、宽度及样式收进默认折叠的 Advanced: Border。配置同步用于画布、预览和导出,内容显式格式保持优先。仅保留 `contentPadding` 内边距,移除 `outerPadding`、主体对齐、外侧背景和圆角配置;主体固定居中,外侧固定白色。
62
+
3
63
  ## 0.2.0 — 2026-10-27
4
64
 
5
65
  ### 不兼容变更
package/README.md CHANGED
@@ -2,131 +2,51 @@
2
2
 
3
3
  English | [简体中文](./README.zh-CN.md)
4
4
 
5
- A React email template editor with drag-and-drop blocks, inline rich text, desktop/mobile previews, JSON storage, and HTML export.
5
+ Drag content onto a canvas, style your email, preview it, then save editable JSON or export email HTML.
6
6
 
7
- ## Installation
7
+ ## Modules and features
8
8
 
9
- Requires React 18 and React DOM 18. Includes ESM and TypeScript declarations.
9
+ ![Project modules and features](./docs/assets/architecture.png)
10
+
11
+ [Open full-size architecture diagram](./docs/assets/architecture.png) · [Editable SVG](./docs/assets/architecture.svg)
12
+
13
+ ## Install and integrate
14
+
15
+ Requires React 18 and React DOM 18. Ships ESM and TypeScript declarations.
10
16
 
11
17
  ```bash
12
18
  pnpm add @standhigher/react-email-editor
13
19
  ```
14
20
 
15
- ## Quick start
16
-
17
- Import the stylesheet once in your application and use a ref to read the document and HTML. Your application handles persistence.
18
-
19
21
  ```tsx
20
- import { useRef } from "react";
21
- import {
22
- BLOCK_TYPES,
23
- EmailEditor,
24
- createBlockNode,
25
- createEmailDocument,
26
- type EmailEditorHandle,
27
- type Variable,
28
- } from "@standhigher/react-email-editor";
22
+ import { EmailEditor, useEmailEditor } from "@standhigher/react-email-editor";
29
23
  import "@standhigher/react-email-editor/styles.css";
30
24
 
31
- const text = createBlockNode(BLOCK_TYPES.text);
32
- if (text) text.fields.html = "<p>Hello, {{ store_name }}</p>";
33
- const initialDocument = createEmailDocument(text ? [text] : []);
34
- const variables: Variable[] = [
35
- { name: "{{ store_name }}", description: "Store name", value: "Example Store" },
36
- ];
37
-
38
25
  export function EmailPage() {
39
- const editorRef = useRef<EmailEditorHandle>(null);
40
-
41
- function save() {
42
- const data = editorRef.current?.getData();
43
- if (!data) return;
44
- // Persist data.document in your app; use data.html to export the template.
45
- console.log(data);
46
- }
47
-
48
- return (
49
- <>
50
- <EmailEditor
51
- ref={editorRef}
52
- initialDocument={initialDocument}
53
- variables={variables}
54
- height={720}
55
- />
56
- <button type="button" onClick={save}>Save</button>
57
- </>
58
- );
26
+ const editor = useEmailEditor();
27
+ return <EmailEditor editor={editor} height={720} />;
59
28
  }
60
29
  ```
61
30
 
62
- Omit `initialDocument` to start empty. In Next.js App Router, add `"use client"` to the integration component and import CSS from an allowed global stylesheet entry. When using `height="100%"`, give the parent a definite height.
63
-
64
- ## Props
65
-
66
- All props are optional.
67
-
68
- | Prop | Type | Behavior |
69
- | --- | --- | --- |
70
- | `initialDocument` | `EmailDocument` | Read on first mount only. Remount with a new React `key` to switch documents. |
71
- | `height` | `number \| string` | Defaults to `"100%"`; numbers are pixels. |
72
- | `global` | `Partial<EmailGlobalSettings>` | Overrides global styles; later changes merge into the current document. Omitting a field does not restore its default. |
73
- | `variables` | `Variable[]` | Variables for insertion and preview. Defaults to `[]`; Personalization controls are hidden when empty. |
74
- | `blocks` | `BlockDefinition[]` | Adds custom blocks; matching types replace built-in definitions. |
75
- | `onChange` | `(document: EmailDocument) => void` | Called on document changes, including external `global` updates that change document values. |
76
- | `onGlobalChange` | `(global: EmailGlobalSettings) => void` | Called for global style edits inside the editor, excluding external `global` updates. |
77
-
78
- Global style example: `global={{ contentWidth: 640, outerPadding: "24px" }}`. Defaults are exported as `DEFAULT_EMAIL_GLOBAL`.
79
-
80
- Built-in blocks: `heading`, `text`, `button`, `image`, `divider`, `spacer`, and `html`, available through `BLOCK_TYPES`.
81
-
82
- ## Save and export
83
-
84
- | Ref method | Returns |
85
- | --- | --- |
86
- | `getData()` | `{ document, json, html, renderedHtml }` |
87
- | `getDocument()` | An independent `EmailDocument` snapshot to persist and pass back as `initialDocument`. |
88
- | `getJson()` | A formatted document JSON string. |
89
- | `getHtml()` | A complete email HTML document preserving template expressions. |
90
- | `getRenderedHtml()` | A complete email HTML document with templates resolved using preview data. |
31
+ With Next.js App Router, add `"use client"` to the integration component and import CSS from an allowed global stylesheet entry. Your app implements persistence, resource uploads and email delivery.
91
32
 
92
- Render a document without mounting the editor:
33
+ ## Run locally
93
34
 
94
- ```ts
95
- import { documentToHtml } from "@standhigher/react-email-editor";
96
-
97
- const html = documentToHtml(initialDocument, undefined, { variables });
98
- const previewHtml = documentToHtml(initialDocument, undefined, {
99
- variables,
100
- resolveHtmlTemplates: true,
101
- });
102
- ```
103
-
104
- A variable's `name` is inserted verbatim; `description` and preview `value` are optional. Export adds a missing dot only to tokens matching `variables[].name` exactly, preserving whitespace: `{{ store_name }}` → `{{ .store_name }}`. Other Go template expressions remain unchanged. Preview supports a subset of Go template syntax; data precedence is block `defaultData` > `templateData` (`documentToHtml` option) > `variables`.
105
-
106
- The document format is under development. Only `version: 2` is accepted, with no automatic migration of older documents. Create data with `createEmailDocument()` and `createBlockNode()`. See [CHANGELOG](./CHANGELOG.md) for upgrade notes.
107
-
108
- ## Custom blocks
109
-
110
- Pass `BlockDefinition[]` through `blocks`. Required fields are `type`, `title`, and `render(ctx)`, which returns HTML. Use `fields` / `styleFields` for inspector controls and `defaultFields` / `defaultStyle` / `defaultData` for initial values. The render context includes `mode: "canvas" | "email"`.
111
-
112
- Built-in text formatting lives in `fields.html`, container styles in `style.dimension` / `style.decorations`, and button, image, and divider element styles in `style.element`. Rendering uses only enabled style fields. See [BlockDefinition](https://github.com/standhigher/react-email-editor/blob/main/src/types/block.ts) and [EmailDocument](https://github.com/standhigher/react-email-editor/blob/main/src/types/document.ts) for full types. When calling `documentToHtml` directly, pass the complete definition list including custom blocks as its second argument.
113
-
114
- ## Local development
115
-
116
- Node.js 22.18+ and pnpm are recommended:
35
+ Use a supported Node.js LTS release (at least 22.18 for the complete test suite) and pnpm:
117
36
 
118
37
  ```bash
119
38
  pnpm install --frozen-lockfile
120
39
  pnpm dev
121
40
  ```
122
41
 
123
- Open [http://localhost:5173](http://localhost:5173). The playground loads `src/` directly; reuse an existing server. Validation:
42
+ Open [http://localhost:5173](http://localhost:5173); reuse an existing server when available. See [development and local linking](./docs/api.md#development-and-local-linking) for build and validation commands.
124
43
 
125
- ```bash
126
- pnpm typecheck
127
- pnpm lint
128
- pnpm build
129
- node --test tests/*.test.mjs tests/*.test.ts
130
- ```
44
+ ## Documentation
45
+
46
+ - [API reference](./docs/api.md): every public export, parameter, default and type.
47
+ - [Basic examples](./docs/basic-examples.md): initial content, saving/loading, body styles, variables and preview.
48
+ - [Advanced examples](./docs/advanced-examples.md): validated saving, multiple instances, custom blocks, multiple editing regions, resource adapters and standalone export.
49
+ - [Custom block guide](./docs/custom-blocks.md): detailed controls, plugins and rendering helpers.
50
+ - [Changelog](./CHANGELOG.md).
131
51
 
132
- For local consumers, use `link:../react-email-editor`, run `pnpm build` first, and keep `pnpm build:watch` running to update `dist/`. Both projects must share one React instance. Vite can use `resolve.dedupe: ["react", "react-dom"]`; Next.js can use `transpilePackages: ["@standhigher/react-email-editor"]`, with Turbopack's `root` covering the linked directory.
52
+ The document format is under development. Only `version: 4` is supported, without automatic migration of older documents.
package/README.zh-CN.md CHANGED
@@ -2,131 +2,51 @@
2
2
 
3
3
  [English](./README.md) | 简体中文
4
4
 
5
- React 邮件模板编辑器,支持拖拽区块、画布富文本编辑、桌面/移动预览、JSON 保存和 HTML 导出。
5
+ 把内容拖进画布,调整邮件外观,预览后保存为可继续编辑的 JSON 或导出邮件 HTML。
6
6
 
7
- ## 安装
7
+ ## 模块架构图
8
8
 
9
- 需要 React 18 和 React DOM 18。提供 ESM 和 TypeScript 类型。
9
+ ![项目模块架构图:8 个模块及对应功能](./docs/assets/architecture.zh-CN.png)
10
+
11
+ [查看高清架构图](./docs/assets/architecture.zh-CN.png) · [可编辑 SVG](./docs/assets/architecture.zh-CN.svg)
12
+
13
+ ## 安装与最小接入
14
+
15
+ 需要 React 18 和 React DOM 18;包提供 ESM 和 TypeScript 类型。
10
16
 
11
17
  ```bash
12
18
  pnpm add @standhigher/react-email-editor
13
19
  ```
14
20
 
15
- ## 基本接入
16
-
17
- 在应用入口引入一次样式,通过 `ref` 读取文档和 HTML;保存逻辑由应用实现。
18
-
19
21
  ```tsx
20
- import { useRef } from "react";
21
- import {
22
- BLOCK_TYPES,
23
- EmailEditor,
24
- createBlockNode,
25
- createEmailDocument,
26
- type EmailEditorHandle,
27
- type Variable,
28
- } from "@standhigher/react-email-editor";
22
+ import { EmailEditor, useEmailEditor } from "@standhigher/react-email-editor";
29
23
  import "@standhigher/react-email-editor/styles.css";
30
24
 
31
- const text = createBlockNode(BLOCK_TYPES.text);
32
- if (text) text.fields.html = "<p>你好,{{ store_name }}</p>";
33
- const initialDocument = createEmailDocument(text ? [text] : []);
34
- const variables: Variable[] = [
35
- { name: "{{ store_name }}", description: "店铺名称", value: "示例店铺" },
36
- ];
37
-
38
25
  export function EmailPage() {
39
- const editorRef = useRef<EmailEditorHandle>(null);
40
-
41
- function save() {
42
- const data = editorRef.current?.getData();
43
- if (!data) return;
44
- // 将 data.document 保存到应用,data.html 用于邮件模板导出。
45
- console.log(data);
46
- }
47
-
48
- return (
49
- <>
50
- <EmailEditor
51
- ref={editorRef}
52
- initialDocument={initialDocument}
53
- variables={variables}
54
- height={720}
55
- />
56
- <button type="button" onClick={save}>保存</button>
57
- </>
58
- );
26
+ const editor = useEmailEditor();
27
+ return <EmailEditor editor={editor} height={720} />;
59
28
  }
60
29
  ```
61
30
 
62
- 省略 `initialDocument` 即为空编辑器。Next.js App Router 中,接入组件需声明 `"use client"`,CSS 在应用允许的全局样式入口引入。使用 `height="100%"` 时,父容器需要有明确高度。
63
-
64
- ## 常用参数
65
-
66
- 所有 Props 均可选。
67
-
68
- | 参数 | 类型 | 行为 |
69
- | --- | --- | --- |
70
- | `initialDocument` | `EmailDocument` | 仅首次挂载读取;切换文档时通过新的 React `key` 重新挂载。 |
71
- | `height` | `number \| string` | 默认 `"100%"`;数字为 px。 |
72
- | `global` | `Partial<EmailGlobalSettings>` | 覆盖全局样式,后续更新合并到当前文档;省略字段不会恢复默认值。 |
73
- | `variables` | `Variable[]` | 插入和预览变量;默认 `[]`,为空时隐藏 Personalization 入口。 |
74
- | `blocks` | `BlockDefinition[]` | 添加自定义区块,同名 `type` 覆盖内置定义。 |
75
- | `onChange` | `(document: EmailDocument) => void` | 文档变化时触发,包括实际改变文档值的外部 `global` 更新。 |
76
- | `onGlobalChange` | `(global: EmailGlobalSettings) => void` | 编辑器内修改全局样式时触发,外部 `global` 更新不触发。 |
77
-
78
- 全局样式示例:`global={{ contentWidth: 640, outerPadding: "24px" }}`。默认值通过 `DEFAULT_EMAIL_GLOBAL` 导出。
79
-
80
- 内置区块:`heading`、`text`、`button`、`image`、`divider`、`spacer`、`html`,均可通过 `BLOCK_TYPES` 引用。
81
-
82
- ## 保存与导出
83
-
84
- | Ref 方法 | 返回值 |
85
- | --- | --- |
86
- | `getData()` | `{ document, json, html, renderedHtml }` |
87
- | `getDocument()` | 独立的 `EmailDocument` 快照,可存储后传回 `initialDocument`。 |
88
- | `getJson()` | 格式化的文档 JSON 字符串。 |
89
- | `getHtml()` | 保留模板表达式的完整邮件 HTML。 |
90
- | `getRenderedHtml()` | 使用预览数据解析模板后的完整邮件 HTML。 |
31
+ Next.js App Router 的接入组件需声明 `"use client"`;样式在应用允许的全局样式入口引入。保存、上传资源与发送邮件由你的应用实现。
91
32
 
92
- 不挂载编辑器时,也可直接渲染文档:
33
+ ## 本地启动
93
34
 
94
- ```ts
95
- import { documentToHtml } from "@standhigher/react-email-editor";
96
-
97
- const html = documentToHtml(initialDocument, undefined, { variables });
98
- const previewHtml = documentToHtml(initialDocument, undefined, {
99
- variables,
100
- resolveHtmlTemplates: true,
101
- });
102
- ```
103
-
104
- 变量的 `name` 原样插入,`description` 和预览值 `value` 可选。导出时仅对与 `variables[].name` 原文精确匹配的 token 补点并保留空格,如 `{{ store_name }}` → `{{ .store_name }}`;其他 Go template 表达式保持原样。预览支持部分 Go template 语法,数据优先级为区块 `defaultData` > `templateData`(`documentToHtml` 选项)> `variables`。
105
-
106
- 当前文档格式仍处于开发阶段,仅接受 `version: 2`,不自动迁移旧文档。使用 `createEmailDocument()` 和 `createBlockNode()` 创建数据。升级差异见 [CHANGELOG](./CHANGELOG.md)。
107
-
108
- ## 自定义区块
109
-
110
- 通过 `blocks` 传入 `BlockDefinition[]`,必填项为 `type`、`title` 和 `render(ctx)`,返回 HTML 字符串。`fields` / `styleFields` 配置检查器,`defaultFields` / `defaultStyle` / `defaultData` 设置初始值。`render` 接收 `mode: "canvas" | "email"`。
111
-
112
- 内置文字格式存于 `fields.html`,容器样式存于 `style.dimension` / `style.decorations`,按钮、图片和分割线自身样式存于 `style.element`。渲染仅使用区块启用的样式字段。完整定义见 [BlockDefinition](https://github.com/standhigher/react-email-editor/blob/main/src/types/block.ts) 和 [EmailDocument](https://github.com/standhigher/react-email-editor/blob/main/src/types/document.ts)。独立调用 `documentToHtml` 时,第二个参数需传入包含自定义区块的完整定义列表。
113
-
114
- ## 本地开发
115
-
116
- 建议 Node.js 22.18+,使用 pnpm:
35
+ 使用依赖支持的 Node.js LTS(运行全部测试至少需要 22.18)和 pnpm:
117
36
 
118
37
  ```bash
119
38
  pnpm install --frozen-lockfile
120
39
  pnpm dev
121
40
  ```
122
41
 
123
- 打开 [http://localhost:5173](http://localhost:5173)。playground 直接加载 `src/`,已有服务时复用。验证命令:
42
+ 打开 [http://localhost:5173](http://localhost:5173),已有服务时优先复用。构建与校验命令见 [API 文档的开发说明](./docs/api.zh-CN.md#开发与本地联调)。
124
43
 
125
- ```bash
126
- pnpm typecheck
127
- pnpm lint
128
- pnpm build
129
- node --test tests/*.test.mjs tests/*.test.ts
130
- ```
44
+ ## 文档
45
+
46
+ - [API 参考](./docs/api.zh-CN.md):所有公共导出、参数、默认值与类型。
47
+ - [基础场景](./docs/basic-examples.zh-CN.md):初始内容、保存恢复、主体样式、变量及预览。
48
+ - [复杂场景](./docs/advanced-examples.zh-CN.md):校验后保存、多实例、自定义区块、多区域编辑、资源适配与独立导出。
49
+ - [自定义区块指南](./docs/custom-blocks.zh-CN.md):控件、插件和渲染工具的完整说明。
50
+ - [更新记录](./CHANGELOG.md)。
131
51
 
132
- 本地消费方可使用 `link:../react-email-editor`;先运行 `pnpm build`,再运行 `pnpm build:watch` 持续生成 `dist/`。两个项目需共用一个 React 实例;Vite 可按需设置 `resolve.dedupe: ["react", "react-dom"]`,Next.js 可设置 `transpilePackages: ["@standhigher/react-email-editor"]`,Turbopack 的 `root` 需覆盖链接目录。
52
+ 文档格式仍处于开发阶段,当前只支持 `version: 4`,不自动迁移旧版文档。