@standhigher/react-email-editor 0.3.0 → 0.4.3

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.
@@ -8,18 +8,20 @@ This guide follows “example → parameters → return values → behavior” f
8
8
  | --- | --- |
9
9
  | `EmailControls` | Declare controls, value types, defaults, validation, and editing components. |
10
10
  | `defineBlock()` | Combine controls, styles, editing UI, and email output into a block plugin. |
11
- | `createEmailEditorConfig()` | Register plugins and provide block creation, document validation, and HTML export. |
11
+ | `useEmailEditorConfig()` | Register plugins and provide block creation, document validation, and HTML export. |
12
12
 
13
13
  Built-in blocks use the same mechanism. Configuration holds no document or selection state and can be shared by multiple editors. `EmailDocument` is the source of truth for document state; the React editing session handles selection, dragging, and updates.
14
14
 
15
15
  ## Complete integration example
16
16
 
17
17
  ```tsx
18
+ import { useState } from "react";
18
19
  import {
19
20
  EmailEditor,
20
21
  EmailControls,
21
22
  createEmailDocument,
22
- createEmailEditorConfig,
23
+ useEmailEditorConfig,
24
+ useEmailEditor,
23
25
  defineBlock,
24
26
  } from "@standhigher/react-email-editor";
25
27
  import "@standhigher/react-email-editor/styles.css";
@@ -40,17 +42,12 @@ const coupon = defineBlock({
40
42
  `,
41
43
  });
42
44
 
43
- // Keep outside the component, or stabilize the reference with useMemo.
44
- const config = createEmailEditorConfig({ plugins: [coupon] });
45
- const initialDocument = createEmailDocument([config.createBlock(coupon.type)]);
46
-
47
45
  export function EmailPage() {
48
- return <EmailEditor config={config} initialDocument={initialDocument} />;
46
+ const config = useEmailEditorConfig({ plugins: [coupon] });
47
+ const [initialDocument] = useState(() => createEmailDocument([config.createBlock(coupon.type)]));
48
+ const editor = useEmailEditor({ config, initialDocument });
49
+ return <EmailEditor editor={editor} />;
49
50
  }
50
-
51
- // Standalone export uses the same configuration without mounting the editor.
52
- const issues = config.validate(initialDocument);
53
- const html = config.toHtml(initialDocument);
54
51
  ```
55
52
 
56
53
  At declaration time, `controls` contains control definitions. In nodes, Canvas/Inspector props, and render contexts, it contains their values:
@@ -66,29 +63,28 @@ controls: { code: EmailControls.text({ label: "Code", defaultValue: "SAVE20" })
66
63
  controls.code // string
67
64
  ```
68
65
 
69
- ## 1. createEmailEditorConfig(options?)
66
+ ## 1. useEmailEditorConfig(options?)
70
67
 
71
68
  ### Example
72
69
 
73
- ```ts
74
- const config = createEmailEditorConfig({
75
- plugins: [coupon],
76
- builtins: ["heading", "text", "image", "button"],
77
- pickImage: async ({ value, label, signal }) => {
78
- // Open your application's asset picker; initialize with value,
79
- // use label as its title, and cancel requests with signal.
80
- // Return an image URL, or null when canceled.
81
- return null;
82
- },
83
- });
84
-
85
- const block = config.createBlock("my-app.coupon");
86
- const document = createEmailDocument([block]);
87
- const html = config.toHtml(document);
88
- const preview = config.toHtml(document, { resolveHtmlTemplates: true });
70
+ ```tsx
71
+ function useCouponConfig() {
72
+ return useEmailEditorConfig({
73
+ plugins: [coupon],
74
+ builtins: ["heading", "text", "image", "button"],
75
+ pickImage: async ({ value, label, signal }) => {
76
+ // Open your application's asset picker; initialize with value,
77
+ // use label as its title, and cancel requests with signal.
78
+ // Return an image URL, or null when canceled.
79
+ return null;
80
+ },
81
+ });
82
+ }
89
83
  ```
90
84
 
91
- ### Parameters: EmailEditorConfigOptions
85
+ Call this Hook at the top level of a React component or custom Hook. Stable `plugins`, `builtins`, and `pickImage` references retain configuration identity. Updates preserve instance documents. Removing a plugin used by a document preserves its data and produces a missing-plugin validation issue. Configuration methods `toHtml(document)` and `validate(document)` accept any document; instance methods `getHtml()` and `validate()` use the current document.
86
+
87
+ ### Parameters: UseEmailEditorConfigOptions
92
88
 
93
89
  All options are optional.
94
90
 
@@ -482,10 +478,10 @@ Missing plugins produce a canvas placeholder and preserve the node; email export
482
478
  | `ControlComponentProps<T>` | Single-control React editor props. |
483
479
  | `BlockPlugin/BlockPluginOptions` | Block definition and declaration options. |
484
480
  | `PluginEditorProps/PluginRenderContext` | Editing component props and rendering context. |
485
- | `EmailEditorConfig/EmailEditorConfigOptions` | Registration configuration and options. |
481
+ | `EmailEditorConfig/UseEmailEditorConfigOptions` | Registration configuration and options. |
486
482
  | `ImagePickerContext/ValidationIssue` | Image picker context and validation results. |
487
483
  | `ControlOption/StyleControlConfig/BlockInspectorSection` | Options, style controls, and inspector sections. |
488
484
  | `ControlEditorProps/EditableRichTextProps` | Reusable control and rich-text component props. |
489
485
  | `EmailMarkup/EmailTools` | Email fragments and rendering helpers. |
490
486
 
491
- See [README](../README.md) for editor props, ref methods, body, and variables. Runnable examples: [coupon](../playground/plugins/coupon.ts), [product card](../playground/plugins/product.ts), and [shared configuration](../playground/plugins/config.ts).
487
+ See the [API reference](./api.md) for editor Hooks, instance methods, body, and variables, and [advanced examples](./advanced-examples.md) for business integration. Runnable examples: [coupon](../playground/plugins/coupon.ts), [product card](../playground/plugins/product.ts), and [shared configuration](../playground/plugins/config.ts).
@@ -8,18 +8,20 @@
8
8
  | --- | --- |
9
9
  | `EmailControls` | 声明控件、值类型、默认值、校验和编辑组件。 |
10
10
  | `defineBlock()` | 组合控件、样式、编辑界面和邮件输出,形成一个区块插件。 |
11
- | `createEmailEditorConfig()` | 注册插件,提供区块创建、文档校验和 HTML 导出。 |
11
+ | `useEmailEditorConfig()` | 注册插件,提供区块创建、文档校验和 HTML 导出。 |
12
12
 
13
13
  内置区块使用相同机制。配置不持有具体文档或选中状态,可以被多个编辑器复用。`EmailDocument` 是文档状态的事实源,React 编辑会话负责选择、拖拽和更新。
14
14
 
15
15
  ## 完整接入示例
16
16
 
17
17
  ```tsx
18
+ import { useState } from "react";
18
19
  import {
19
20
  EmailEditor,
20
21
  EmailControls,
21
22
  createEmailDocument,
22
- createEmailEditorConfig,
23
+ useEmailEditorConfig,
24
+ useEmailEditor,
23
25
  defineBlock,
24
26
  } from "@standhigher/react-email-editor";
25
27
  import "@standhigher/react-email-editor/styles.css";
@@ -40,17 +42,12 @@ const coupon = defineBlock({
40
42
  `,
41
43
  });
42
44
 
43
- // 放在组件外,或通过 useMemo 保持引用稳定。
44
- const config = createEmailEditorConfig({ plugins: [coupon] });
45
- const initialDocument = createEmailDocument([config.createBlock(coupon.type)]);
46
-
47
45
  export function EmailPage() {
48
- return <EmailEditor config={config} initialDocument={initialDocument} />;
46
+ const config = useEmailEditorConfig({ plugins: [coupon] });
47
+ const [initialDocument] = useState(() => createEmailDocument([config.createBlock(coupon.type)]));
48
+ const editor = useEmailEditor({ config, initialDocument });
49
+ return <EmailEditor editor={editor} />;
49
50
  }
50
-
51
- // 独立导出也使用同一配置,无需挂载编辑器。
52
- const issues = config.validate(initialDocument);
53
- const html = config.toHtml(initialDocument);
54
51
  ```
55
52
 
56
53
  `controls` 在声明阶段保存控件定义,在节点、Canvas/Inspector Props 和渲染上下文中保存对应的值:
@@ -66,28 +63,27 @@ controls: { code: EmailControls.text({ label: "优惠码", defaultValue: "SAVE20
66
63
  controls.code // string
67
64
  ```
68
65
 
69
- ## 1. createEmailEditorConfig(options?)
66
+ ## 1. useEmailEditorConfig(options?)
70
67
 
71
68
  ### 示例
72
69
 
73
- ```ts
74
- const config = createEmailEditorConfig({
75
- plugins: [coupon],
76
- builtins: ["heading", "text", "image", "button"],
77
- pickImage: async ({ value, label, signal }) => {
78
- // 调用应用自己的素材库;使用 value 初始化,label 作标题,signal 取消请求。
79
- // 返回图片 URL;取消选择时返回 null。
80
- return null;
81
- },
82
- });
83
-
84
- const block = config.createBlock("my-app.coupon");
85
- const document = createEmailDocument([block]);
86
- const html = config.toHtml(document);
87
- const preview = config.toHtml(document, { resolveHtmlTemplates: true });
70
+ ```tsx
71
+ function useCouponConfig() {
72
+ return useEmailEditorConfig({
73
+ plugins: [coupon],
74
+ builtins: ["heading", "text", "image", "button"],
75
+ pickImage: async ({ value, label, signal }) => {
76
+ // 调用应用自己的素材库;使用 value 初始化,label 作标题,signal 取消请求。
77
+ // 返回图片 URL;取消选择时返回 null。
78
+ return null;
79
+ },
80
+ });
81
+ }
88
82
  ```
89
83
 
90
- ### 参数:EmailEditorConfigOptions
84
+ 配置 Hook 必须在 React 组件或自定义 Hook 顶层调用。保持 `plugins`、`builtins` 和 `pickImage` 引用稳定会保留配置引用;更新配置不会重置已有实例文档。移除仍被文档使用的插件后,校验报告缺失插件,原始数据保留。配置的 `toHtml(document)` 和 `validate(document)` 面向任意文档;实例的 `getHtml()` 和 `validate()` 面向当前文档。
85
+
86
+ ### 参数:UseEmailEditorConfigOptions
91
87
 
92
88
  所有参数均可选。
93
89
 
@@ -481,10 +477,10 @@ renderEmail: ({ controls, email }) => email.html`
481
477
  | `ControlComponentProps<T>` | 单控件 React 编辑组件。 |
482
478
  | `BlockPlugin/BlockPluginOptions` | 区块定义及声明参数。 |
483
479
  | `PluginEditorProps/PluginRenderContext` | 编辑组件 Props / 邮件渲染上下文。 |
484
- | `EmailEditorConfig/EmailEditorConfigOptions` | 注册配置及参数。 |
480
+ | `EmailEditorConfig/UseEmailEditorConfigOptions` | 注册配置及参数。 |
485
481
  | `ImagePickerContext/ValidationIssue` | 图片资源适配器 / 校验结果。 |
486
482
  | `ControlOption/StyleControlConfig/BlockInspectorSection` | 选项 / 样式控件 / 检查器分组。 |
487
483
  | `ControlEditorProps/EditableRichTextProps` | 控件复用和富文本编辑组件 Props。 |
488
484
  | `EmailMarkup/EmailTools` | 邮件片段及工具集。 |
489
485
 
490
- 编辑器 Props、ref 方法、body 和 variables 的完整接入见 [README](../README.zh-CN.md)。可运行示例见 [优惠券](../playground/plugins/coupon.ts)、[商品卡片](../playground/plugins/product.ts)及[统一配置](../playground/plugins/config.ts)。
486
+ 编辑器 Hooks、实例方法、body 和 variables 的完整接入见 [API 参考](./api.zh-CN.md),业务集成见[复杂场景](./advanced-examples.zh-CN.md)。可运行示例见 [优惠券](../playground/plugins/coupon.ts)、[商品卡片](../playground/plugins/product.ts)及[统一配置](../playground/plugins/config.ts)。
@@ -0,0 +1,38 @@
1
+ # npm release guide
2
+
3
+ English | [简体中文](./release.zh-CN.md)
4
+
5
+ ## Toolchain and package configuration
6
+
7
+ Development uses Node 24 LTS (`.nvmrc`) and pnpm 10.16.1 (`packageManager`). The complete test suite needs Node 22.18+. These development requirements are not consumer requirements, so the package does not declare `engines`. React / React DOM 18 remain the supported peers; React 19 requires a separate compatibility validation.
8
+
9
+ The manifest declares MIT licensing, repository, homepage, issue tracker and the public npm registry. `sideEffects: ["**/*.css"]` preserves CSS imports while allowing unused JavaScript modules to be removed. Actual tree shaking depends on module contents and the consumer's bundler.
10
+
11
+ The package includes built assets, Markdown documentation and SVGs under `docs/` (including [English](./changelog.md) and [Chinese](./changelog.zh-CN.md) changelogs), plus both READMEs. npm automatically includes `package.json` and `LICENSE`. Source, playground, tests, caches and PNG originals are excluded.
12
+
13
+ ## README images
14
+
15
+ Both READMEs use absolute CDN URLs pinned to the publicly published `0.4.1` package, which contains the English and Chinese PNG and SVG assets. This avoids relative-path resolution on npm, GitHub branch synchronization and shipping PNG copies in every release.
16
+
17
+ When updating a diagram, host the new image at a stable public URL before changing either README. Do not reference an unpublished version or a `latest` URL that may no longer contain PNGs. Verify an unauthenticated `200` response and the correct image Content-Type.
18
+
19
+ README language switches, documentation, changelog and section links use absolute GitHub `blob/main` URLs. Push the corresponding commit before publishing so the linked READMEs, `docs/` and changelog exist on GitHub; local-only files still return 404. Relative links inside documentation pages are resolved by GitHub against their directories.
20
+
21
+ ## Local checks and publishing
22
+
23
+ ```bash
24
+ pnpm install --frozen-lockfile
25
+ pnpm check
26
+ npm pack --dry-run
27
+ ```
28
+
29
+ `pnpm check` runs type checking, linting, build and Node tests in order. Tests cover package contents, README image URLs and consumer CSS preservation. `pnpm test` requires an existing build. `prepack` builds assets; `prepublishOnly` runs the complete check before local publishing.
30
+
31
+ To publish locally, first update both changelogs and select an unpublished version:
32
+
33
+ ```bash
34
+ npm login --registry=https://registry.npmjs.org/
35
+ npm publish
36
+ ```
37
+
38
+ The npm account needs permission for the `@standhigher` scope and any required 2FA. Published versions cannot be overwritten. Committing or pushing code does not automatically publish a package.
@@ -0,0 +1,38 @@
1
+ # npm 发布指南
2
+
3
+ [English](./release.md) | 简体中文
4
+
5
+ ## 工具链与包配置
6
+
7
+ 开发默认使用 Node 24 LTS(`.nvmrc`)和 pnpm 10.16.1(`packageManager`)。完整测试最低需要 Node 22.18;这不是消费方安装要求,因此包没有添加 `engines` 限制。当前仍只声明 React 18 / React DOM 18 支持,React 19 需完成独立兼容性验证后再扩大 peer 范围。
8
+
9
+ `package.json` 声明 MIT 许可证、仓库、主页、Issue 地址及公开 npm registry。`sideEffects: ["**/*.css"]` 保留消费方的 CSS 导入,允许 bundler 丢弃未使用的 JavaScript 模块;实际裁剪效果取决于模块内容和消费方 bundler。
10
+
11
+ `files` 只发布构建产物、`docs/` 下的 Markdown 文档和 SVG(含[英文](./changelog.md)与[中文](./changelog.zh-CN.md)更新日志),以及中英文 README;npm 自动包含 `package.json` 和 `LICENSE`。源码、playground、测试、本地缓存和 PNG 原图不进入包。
12
+
13
+ ## README 图片
14
+
15
+ npm README 使用固定版本的绝对 CDN 地址,图片来自已公开发布的 `0.4.1` 包。中英文 PNG 和 SVG 源文件均由该版本提供,不依赖本地相对路径或 GitHub 分支同步,也不需要在每个新版包中重复分发 PNG。
16
+
17
+ 更新架构图时,先将新图片放到可公开读取的稳定地址,再更新两个 README 的图片与查看链接。不要把地址替换成尚未发布的版本,也不要使用会随 `latest` 变化且可能缺失 PNG 的路径。图片地址应无需登录,返回 `200` 和正确的图片 Content-Type。
18
+
19
+ README 的语言切换、文档、更新日志和章节导航使用 GitHub `blob/main` 绝对地址。发布前须先推送相应提交,确保 GitHub 上的 README、`docs/` 与更新日志均已同步;仅在本地存在的文档仍会返回 404。文档页内部的相对链接继续由 GitHub 按目录解析。
20
+
21
+ ## 本地检查与发布
22
+
23
+ ```bash
24
+ pnpm install --frozen-lockfile
25
+ pnpm check
26
+ npm pack --dry-run
27
+ ```
28
+
29
+ `pnpm check` 顺序执行类型检查、lint、构建和 Node 测试;测试包含发布文件范围、README 图片地址及消费方 CSS 保留验证。`pnpm test` 只执行测试,需先构建。`prepack` 负责构建;本地 `npm publish` 的 `prepublishOnly` 会执行完整校验。
30
+
31
+ 需要本地手动发布时,在确认版本未发布、中英文更新日志已同步后执行:
32
+
33
+ ```bash
34
+ npm login --registry=https://registry.npmjs.org/
35
+ npm publish
36
+ ```
37
+
38
+ 发布需要 npm 账号对 `@standhigher` scope 的权限及账号要求的 2FA。已发布的相同版本不能覆盖。代码提交或推送不会自动发布 npm 包。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@standhigher/react-email-editor",
3
- "version": "0.3.0",
3
+ "version": "0.4.3",
4
4
  "type": "module",
5
5
  "description": "React email template editor with drag-and-drop blocks, rich text, JSON storage, and HTML export.",
6
6
  "keywords": [
@@ -17,6 +17,16 @@
17
17
  "go-template"
18
18
  ],
19
19
  "author": "Nono",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/standhigher/react-email-editor.git"
24
+ },
25
+ "homepage": "https://github.com/standhigher/react-email-editor#readme",
26
+ "bugs": {
27
+ "url": "https://github.com/standhigher/react-email-editor/issues"
28
+ },
29
+ "packageManager": "pnpm@10.16.1",
20
30
  "exports": {
21
31
  ".": {
22
32
  "types": "./dist/index.d.ts",
@@ -27,21 +37,26 @@
27
37
  "main": "./dist/index.js",
28
38
  "module": "./dist/index.js",
29
39
  "types": "./dist/index.d.ts",
40
+ "sideEffects": ["**/*.css"],
30
41
  "files": [
31
42
  "dist",
32
- "docs",
43
+ "docs/**/*.md",
44
+ "docs/**/*.svg",
33
45
  "README.md",
34
- "README.zh-CN.md",
35
- "CHANGELOG.md"
46
+ "README.zh-CN.md"
36
47
  ],
37
48
  "publishConfig": {
38
- "access": "public"
49
+ "access": "public",
50
+ "registry": "https://registry.npmjs.org/"
39
51
  },
40
52
  "scripts": {
41
53
  "dev": "vite --config playground/vite.config.ts",
42
54
  "build": "tsup",
43
55
  "build:watch": "tsup --watch src",
56
+ "test": "node --test tests/*.test.mjs tests/*.test.ts",
57
+ "check": "pnpm typecheck && pnpm lint && pnpm build && pnpm test",
44
58
  "prepack": "pnpm build",
59
+ "prepublishOnly": "pnpm check",
45
60
  "typecheck": "tsc --noEmit && tsc --noEmit -p playground",
46
61
  "lint": "oxlint . --deny-warnings --report-unused-disable-directives",
47
62
  "lint:fix": "pnpm lint --fix"