mail-editor-pancake 0.2.0 → 0.2.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 +177 -17
- package/package.json +1 -1
- package/packages/blocks/dist/index.js +168 -154
- package/packages/blocks/dist/index.js.map +1 -1
- package/packages/blocks/src/i18n.ts +31 -1
- package/packages/core/dist/index.d.ts +59 -4
- package/packages/core/dist/index.js +1517 -1470
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/style.css +1 -1
- package/packages/core/src/editor/Editor.ts +1 -1
- package/packages/core/src/editor/ImageGalleryModal.ts +135 -83
- package/packages/core/src/editor/RightPanel.ts +30 -15
- package/packages/core/src/editor/imageAssets.ts +65 -3
- package/packages/core/src/editor/styles.css +15 -0
- package/packages/core/src/i18n/index.ts +10 -2
- package/packages/core/src/index.ts +4 -0
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Doc → Section → Column → Block // 仅四层,Block 不可再嵌套子
|
|
|
18
18
|
- [MailEditor API 速览](#maileditor-api-速览)
|
|
19
19
|
- [画布清空与重置](#画布清空与重置)
|
|
20
20
|
- [界面主题与品牌色](#界面主题与品牌色)
|
|
21
|
+
- [国际化 i18n](#国际化-i18n)
|
|
21
22
|
- [UI 选项 `ui`](#ui-选项-ui)
|
|
22
23
|
- [构造选项(画布行为)](#构造选项画布行为)
|
|
23
24
|
- [变量系统](#变量系统)
|
|
@@ -43,7 +44,7 @@ Doc → Section → Column → Block // 仅四层,Block 不可再嵌套子
|
|
|
43
44
|
| 包名 | 内容 |
|
|
44
45
|
|------|------|
|
|
45
46
|
| `@simple-mail/core` | 编辑器引擎、`MailEditor`、类型与工具函数;样式入口 `@simple-mail/core/style.css` |
|
|
46
|
-
| `@simple-mail/blocks` | 内置组件与示例自定义块(`builtinBlocks`、`allBlocks`) |
|
|
47
|
+
| `@simple-mail/blocks` | 内置组件与示例自定义块(`builtinBlocks`、`allBlocks`、`createBuiltinBlocks`、`createAllBlocks`) |
|
|
47
48
|
|
|
48
49
|
本仓库在 Git 中目录名为 `simple-mail`;npm 上 monorepo 根包名为 `mail-editor-pancake`(因无 scope 的 `simple-mail` 已被占用)。**集成时请只安装并 import `@simple-mail/*`**,不要依赖 `mail-editor-pancake/packages/...` 这类深路径。
|
|
49
50
|
|
|
@@ -52,7 +53,7 @@ Doc → Section → Column → Block // 仅四层,Block 不可再嵌套子
|
|
|
52
53
|
```ts
|
|
53
54
|
import { MailEditor } from '@simple-mail/core';
|
|
54
55
|
import '@simple-mail/core/style.css';
|
|
55
|
-
import {
|
|
56
|
+
import { createAllBlocks } from '@simple-mail/blocks';
|
|
56
57
|
```
|
|
57
58
|
|
|
58
59
|
仓库目录结构:
|
|
@@ -75,10 +76,11 @@ simple-mail/
|
|
|
75
76
|
- **MJML 管线**:内部 `EmailDoc` JSON → MJML → 邮件客户端可用 HTML
|
|
76
77
|
- 双模式:设计态 + 源码态(文档级只读 MJML/HTML;组件级可锁定 `lockedMjml`)
|
|
77
78
|
- 内置块:文本、HTML(raw)、图片、按钮、分隔线、间距、Hero、社交组,以及 1/2/3 列布局;示例自定义:Logo、单链社交、页脚
|
|
78
|
-
- **图片字段**:手输 URL;可选上传(`uploadImage`)、内置图库(`imageGallery
|
|
79
|
+
- **图片字段**:手输 URL;可选上传(`uploadImage`)、内置图库(`imageGallery`)、自管图库(`pickImageFromGallery`)
|
|
79
80
|
- 撤销/重做、键盘删除、复制 Section/Block;**复制/导入设计稿**(JSON 信封)
|
|
80
81
|
- **界面主题**:浅色 / 深色 / 跟随系统;画布仍为白纸贴近成品
|
|
81
82
|
- **品牌色**:`accentColor` / `setAccentColor`;可选顶栏拾色器
|
|
83
|
+
- **国际化**:内置 `zh-CN` / `en-US`;支持 `locale`、`messages` 覆盖;blocks 可按语言生成
|
|
82
84
|
- **仅搭正文**:`ui.hideMailMeta` 隐藏主题、Preheader 与顶栏「邮件设置」
|
|
83
85
|
- **清空 / 重置**:`presetDoc` 与 `initialDoc` 分离
|
|
84
86
|
- **变量系统**:`setVariables`;`kind: link | image`;顶栏 `{{ }}` 弹层
|
|
@@ -119,6 +121,13 @@ pnpm --filter @simple-mail/playground-vanilla build
|
|
|
119
121
|
|
|
120
122
|
若你正在本地修改本仓库源码,请在 monorepo 根执行 `pnpm build` 后再刷新引用方项目,以保证 `dist` 与类型声明一致。
|
|
121
123
|
|
|
124
|
+
Playground 支持通过 URL 验证语言:
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
http://localhost:5173/?locale=zh-CN
|
|
128
|
+
http://localhost:5173/?locale=en-US
|
|
129
|
+
```
|
|
130
|
+
|
|
122
131
|
---
|
|
123
132
|
|
|
124
133
|
## 安装与集成
|
|
@@ -135,10 +144,16 @@ pnpm add codemirror mjml-browser sortablejs
|
|
|
135
144
|
```ts
|
|
136
145
|
import { MailEditor } from '@simple-mail/core';
|
|
137
146
|
import '@simple-mail/core/style.css';
|
|
138
|
-
import {
|
|
147
|
+
import {
|
|
148
|
+
builtinBlocks,
|
|
149
|
+
allBlocks,
|
|
150
|
+
createBuiltinBlocks,
|
|
151
|
+
createAllBlocks,
|
|
152
|
+
} from '@simple-mail/blocks';
|
|
139
153
|
```
|
|
140
154
|
|
|
141
155
|
- 仅需内置块时用 `builtinBlocks`;需要 Logo/页脚等示例时用 `allBlocks`,或与业务 `defineBlock` 合并。
|
|
156
|
+
- 需要英文或动态语言时,用 `createBuiltinBlocks({ locale })` / `createAllBlocks({ locale })` 生成块定义。
|
|
142
157
|
- `@simple-mail/blocks` 依赖 `@simple-mail/core`,版本宜对齐。
|
|
143
158
|
|
|
144
159
|
### 本地 link(参与本仓库开发时)
|
|
@@ -203,11 +218,14 @@ npm 上仅发布 `@simple-mail/core` 与 `@simple-mail/blocks`。根包 `mail-ed
|
|
|
203
218
|
```ts
|
|
204
219
|
import { MailEditor } from '@simple-mail/core';
|
|
205
220
|
import '@simple-mail/core/style.css';
|
|
206
|
-
import {
|
|
221
|
+
import { createAllBlocks } from '@simple-mail/blocks';
|
|
222
|
+
|
|
223
|
+
const locale = 'zh-CN';
|
|
207
224
|
|
|
208
225
|
const editor = new MailEditor({
|
|
209
226
|
container: document.getElementById('app')!,
|
|
210
|
-
|
|
227
|
+
locale,
|
|
228
|
+
blocks: createAllBlocks({ locale }),
|
|
211
229
|
initialDoc: {
|
|
212
230
|
meta: { subject: '欢迎', width: 600 },
|
|
213
231
|
variables: [{ key: 'user.name', label: '用户名', sample: '张三' }],
|
|
@@ -268,7 +286,8 @@ editor.setVariables([
|
|
|
268
286
|
```ts
|
|
269
287
|
const editor = new MailEditor({
|
|
270
288
|
container: el,
|
|
271
|
-
|
|
289
|
+
locale: 'zh-CN',
|
|
290
|
+
blocks: createAllBlocks({ locale: 'zh-CN' }),
|
|
272
291
|
initialDoc: loadedFromApi,
|
|
273
292
|
presetDoc: businessDefaultTemplate,
|
|
274
293
|
onChange: (doc) => save(doc),
|
|
@@ -297,6 +316,128 @@ editor.getTheme();
|
|
|
297
316
|
|
|
298
317
|
---
|
|
299
318
|
|
|
319
|
+
## 国际化 i18n
|
|
320
|
+
|
|
321
|
+
编辑器内置 `zh-CN` 与 `en-US`,默认 `zh-CN`。国际化覆盖范围包括编辑器 UI、弹窗、图库、富文本工具条、内置 blocks 与示例自定义 blocks 的名称、属性、默认文案。
|
|
322
|
+
|
|
323
|
+
### 基础用法
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
import { MailEditor, type SimpleMailLocale } from '@simple-mail/core';
|
|
327
|
+
import { createAllBlocks } from '@simple-mail/blocks';
|
|
328
|
+
|
|
329
|
+
const locale: SimpleMailLocale = appLanguage === 'en' ? 'en-US' : 'zh-CN';
|
|
330
|
+
|
|
331
|
+
const editor = new MailEditor({
|
|
332
|
+
container,
|
|
333
|
+
locale,
|
|
334
|
+
blocks: createAllBlocks({ locale }),
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
仅使用内置块:
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
import { createBuiltinBlocks } from '@simple-mail/blocks';
|
|
342
|
+
|
|
343
|
+
new MailEditor({
|
|
344
|
+
container,
|
|
345
|
+
locale: 'en-US',
|
|
346
|
+
blocks: createBuiltinBlocks({ locale: 'en-US' }),
|
|
347
|
+
});
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`builtinBlocks` / `allBlocks` 仍保留,默认中文,主要用于兼容旧集成;新项目建议使用 `createBuiltinBlocks` / `createAllBlocks`。
|
|
351
|
+
|
|
352
|
+
### 覆盖文案
|
|
353
|
+
|
|
354
|
+
可用 `messages` 覆盖内置文案。只需传入要覆盖的 key,未传的 key 会回退到对应语言,再回退到中文。
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
new MailEditor({
|
|
358
|
+
container,
|
|
359
|
+
locale: 'en-US',
|
|
360
|
+
blocks: createAllBlocks({ locale: 'en-US' }),
|
|
361
|
+
messages: {
|
|
362
|
+
'topbar.exportHtml': 'Export final HTML',
|
|
363
|
+
'leftPanel.components': 'Content blocks',
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
可导入类型与内置字典:
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
import {
|
|
372
|
+
createI18nContext,
|
|
373
|
+
enUSMessages,
|
|
374
|
+
zhCNMessages,
|
|
375
|
+
type SimpleMailLocale,
|
|
376
|
+
type SimpleMailMessagesInput,
|
|
377
|
+
type SimpleMailT,
|
|
378
|
+
} from '@simple-mail/core';
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
### 宿主语言切换
|
|
382
|
+
|
|
383
|
+
`MailEditor` 构造后不会热更新 `locale` / `blocks`。宿主应用切换语言时,建议保存当前文档、销毁旧实例、用新语言重建。
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
let editor: MailEditor | null = null;
|
|
387
|
+
|
|
388
|
+
function mount(locale: SimpleMailLocale, initialDoc?: Partial<EmailDoc>) {
|
|
389
|
+
editor = new MailEditor({
|
|
390
|
+
container,
|
|
391
|
+
locale,
|
|
392
|
+
blocks: createAllBlocks({ locale }),
|
|
393
|
+
initialDoc,
|
|
394
|
+
onChange: save,
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function changeLocale(nextLocale: SimpleMailLocale) {
|
|
399
|
+
const currentDoc = editor?.getValue();
|
|
400
|
+
editor?.destroy();
|
|
401
|
+
mount(nextLocale, currentDoc);
|
|
402
|
+
}
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### 自定义组件国际化
|
|
406
|
+
|
|
407
|
+
自定义组件的 `name`、`paletteTooltip`、`schema.label`、`schema.placeholder`、`schema.help`、`inlineEditable.placeholder` 由业务侧维护。推荐暴露一个工厂函数,按语言返回组件定义:
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
import { defineBlock, type BlockDefinition, type SimpleMailLocale } from '@simple-mail/core';
|
|
411
|
+
import { createBuiltinBlocks } from '@simple-mail/blocks';
|
|
412
|
+
|
|
413
|
+
function couponBlock(locale: SimpleMailLocale): BlockDefinition<any> {
|
|
414
|
+
const en = locale === 'en-US';
|
|
415
|
+
return defineBlock({
|
|
416
|
+
type: 'custom:coupon',
|
|
417
|
+
name: en ? 'Coupon' : '优惠券',
|
|
418
|
+
category: 'custom',
|
|
419
|
+
defaultProps: { title: en ? 'Exclusive offer' : '专属优惠' },
|
|
420
|
+
schema: [
|
|
421
|
+
{ key: 'title', label: en ? 'Title' : '标题', type: 'text' },
|
|
422
|
+
{ key: 'code', label: en ? 'Code' : '券码', type: 'text' },
|
|
423
|
+
],
|
|
424
|
+
toMjml: (p) => `<mj-text>${p.title}</mj-text>`,
|
|
425
|
+
renderPreview: (p) => `<div>${p.title}</div>`,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function createBlocks(locale: SimpleMailLocale) {
|
|
430
|
+
return [
|
|
431
|
+
...createBuiltinBlocks({ locale }),
|
|
432
|
+
couponBlock(locale),
|
|
433
|
+
];
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
如果需要在保持同一份 block 实现的同时只替换显示文案,也可以在宿主侧对 `BlockDefinition` 做浅拷贝并替换 `name`、`schema` 等字段。
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
300
441
|
## UI 选项 `ui`
|
|
301
442
|
|
|
302
443
|
`MailEditor` 的 `ui?: EditorUiOptions`:
|
|
@@ -327,6 +468,8 @@ editor.getTheme();
|
|
|
327
468
|
|------|------|
|
|
328
469
|
| `container` | 挂载 DOM(必填) |
|
|
329
470
|
| `blocks?` | 块定义列表 |
|
|
471
|
+
| `locale?` | `zh-CN` \| `en-US`,默认 `zh-CN` |
|
|
472
|
+
| `messages?` | 覆盖内置 i18n 文案 |
|
|
330
473
|
| `engine?` | 目前仅 `'mjml'`;预留 `'table'` |
|
|
331
474
|
| `initialDoc?` | 首次进入画布 |
|
|
332
475
|
| `presetDoc?` | 「重置内容」目标 |
|
|
@@ -451,7 +594,7 @@ import {
|
|
|
451
594
|
|------|------|
|
|
452
595
|
| `uploadImage?(file, ctx)` | 右栏「上传」→ HTTPS URL |
|
|
453
596
|
| `imageGallery?` | 内置图库弹层(`showGallery: true`) |
|
|
454
|
-
| `pickImageFromGallery?(ctx)` |
|
|
597
|
+
| `pickImageFromGallery?(ctx)` | 自管图库;与 `imageGallery` 并存时**优先内置图库** |
|
|
455
598
|
| `showUpload?` | 默认 `true`(有 `uploadImage` 时) |
|
|
456
599
|
| `showGallery?` | 默认 `false` |
|
|
457
600
|
|
|
@@ -473,31 +616,34 @@ import { openImageGalleryModal } from '@simple-mail/core';
|
|
|
473
616
|
|
|
474
617
|
```tsx
|
|
475
618
|
import { useEffect, useRef } from 'react';
|
|
476
|
-
import { MailEditor, type EmailDoc } from '@simple-mail/core';
|
|
619
|
+
import { MailEditor, type EmailDoc, type SimpleMailLocale } from '@simple-mail/core';
|
|
477
620
|
import '@simple-mail/core/style.css';
|
|
478
|
-
import {
|
|
621
|
+
import { createAllBlocks } from '@simple-mail/blocks';
|
|
479
622
|
|
|
480
623
|
export function MailEditorView({
|
|
481
624
|
value,
|
|
482
625
|
presetDoc,
|
|
483
626
|
onChange,
|
|
627
|
+
locale = 'zh-CN',
|
|
484
628
|
}: {
|
|
485
629
|
value?: Partial<EmailDoc>;
|
|
486
630
|
presetDoc?: Partial<EmailDoc>;
|
|
631
|
+
locale?: SimpleMailLocale;
|
|
487
632
|
onChange?: (d: EmailDoc) => void;
|
|
488
633
|
}) {
|
|
489
634
|
const ref = useRef<HTMLDivElement>(null);
|
|
490
635
|
useEffect(() => {
|
|
491
636
|
const editor = new MailEditor({
|
|
492
637
|
container: ref.current!,
|
|
493
|
-
|
|
638
|
+
locale,
|
|
639
|
+
blocks: createAllBlocks({ locale }),
|
|
494
640
|
initialDoc: value,
|
|
495
641
|
presetDoc,
|
|
496
642
|
ui: { hideTopbarTitle: true },
|
|
497
643
|
onChange,
|
|
498
644
|
});
|
|
499
645
|
return () => editor.destroy();
|
|
500
|
-
}, []);
|
|
646
|
+
}, [locale]);
|
|
501
647
|
return <div ref={ref} style={{ height: '100vh' }} />;
|
|
502
648
|
}
|
|
503
649
|
```
|
|
@@ -507,13 +653,19 @@ export function MailEditorView({
|
|
|
507
653
|
```vue
|
|
508
654
|
<script setup lang="ts">
|
|
509
655
|
import { markRaw, onMounted, onBeforeUnmount, ref } from 'vue';
|
|
510
|
-
import {
|
|
656
|
+
import {
|
|
657
|
+
MailEditor,
|
|
658
|
+
type BlockDefinition,
|
|
659
|
+
type EmailDoc,
|
|
660
|
+
type SimpleMailLocale,
|
|
661
|
+
} from '@simple-mail/core';
|
|
511
662
|
import '@simple-mail/core/style.css';
|
|
512
|
-
import {
|
|
663
|
+
import { createAllBlocks } from '@simple-mail/blocks';
|
|
513
664
|
|
|
514
665
|
const props = defineProps<{
|
|
515
666
|
modelValue?: Partial<EmailDoc>;
|
|
516
667
|
presetDoc?: Partial<EmailDoc>;
|
|
668
|
+
locale?: SimpleMailLocale;
|
|
517
669
|
blocks?: BlockDefinition<any>[];
|
|
518
670
|
}>();
|
|
519
671
|
const emit = defineEmits<{ 'update:modelValue': [EmailDoc] }>();
|
|
@@ -521,10 +673,12 @@ const el = ref<HTMLDivElement>();
|
|
|
521
673
|
let editor: MailEditor | null = null;
|
|
522
674
|
|
|
523
675
|
onMounted(() => {
|
|
524
|
-
const
|
|
676
|
+
const locale = props.locale ?? 'zh-CN';
|
|
677
|
+
const defs = props.blocks ?? createAllBlocks({ locale });
|
|
525
678
|
const stable = defs.map((d) => markRaw(d));
|
|
526
679
|
editor = new MailEditor({
|
|
527
680
|
container: el.value!,
|
|
681
|
+
locale,
|
|
528
682
|
blocks: stable,
|
|
529
683
|
initialDoc: props.modelValue,
|
|
530
684
|
presetDoc: props.presetDoc,
|
|
@@ -548,7 +702,8 @@ onBeforeUnmount(() => editor?.destroy());
|
|
|
548
702
|
## 自定义组件
|
|
549
703
|
|
|
550
704
|
```ts
|
|
551
|
-
import { defineBlock } from '@simple-mail/core';
|
|
705
|
+
import { defineBlock, MailEditor } from '@simple-mail/core';
|
|
706
|
+
import { createAllBlocks } from '@simple-mail/blocks';
|
|
552
707
|
|
|
553
708
|
export const couponBlock = defineBlock<{ title: string; code: string }>({
|
|
554
709
|
type: 'custom:coupon',
|
|
@@ -571,7 +726,12 @@ export const couponBlock = defineBlock<{ title: string; code: string }>({
|
|
|
571
726
|
renderPreview: (p) => `<div class="coupon-title">…</div>`,
|
|
572
727
|
});
|
|
573
728
|
|
|
574
|
-
|
|
729
|
+
const locale = 'zh-CN';
|
|
730
|
+
new MailEditor({
|
|
731
|
+
container,
|
|
732
|
+
locale,
|
|
733
|
+
blocks: [...createAllBlocks({ locale }), couponBlock],
|
|
734
|
+
});
|
|
575
735
|
```
|
|
576
736
|
|
|
577
737
|
### `schema` 字段类型
|