mail-editor-pancake 0.2.2 → 0.2.4
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/package.json +1 -1
- package/packages/core/dist/index.d.ts +24 -2
- package/packages/core/dist/index.js +2016 -1844
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/src/editor/Canvas.ts +40 -16
- package/packages/core/src/editor/Editor.ts +22 -3
- package/packages/core/src/editor/InlineEditor.ts +186 -64
- package/packages/core/src/editor/RichTextToolbar.ts +16 -2
- package/packages/core/src/editor/RightPanel.ts +36 -9
- package/packages/core/src/i18n/index.ts +4 -2
- package/packages/core/src/renderer/mjml.ts +81 -2
- package/packages/core/src/types.ts +24 -2
|
@@ -34,6 +34,7 @@ export function docToMjml(doc: EmailDoc, registry: Registry): string {
|
|
|
34
34
|
<mj-style>
|
|
35
35
|
a { color: ${escapeAttr(attrs.linkColor)}; }
|
|
36
36
|
${sectionWidthConstraintCss(doc)}
|
|
37
|
+
${columnGapMobileCss(doc)}
|
|
37
38
|
</mj-style>
|
|
38
39
|
</mj-head>
|
|
39
40
|
<mj-body background-color="${escapeAttr(attrs.backgroundColor)}" width="${escapeAttr(docContentWidthCss(doc.meta.width))}">
|
|
@@ -55,6 +56,65 @@ function sectionWidthConstraintCss(doc: EmailDoc): string {
|
|
|
55
56
|
.join('\n');
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/**
|
|
60
|
+
* 列间距相关的小屏规则。
|
|
61
|
+
*
|
|
62
|
+
* 1) 水平间距(columnGap)用 mj-column 对称 padding 实现,小屏堆叠后 padding 不消失会导致
|
|
63
|
+
* 相邻列内容纵向错位(首列右缩、次列左缩),需要在堆叠时清零。
|
|
64
|
+
* 2) 堆叠纵向间距(columnStackedGap)只挂在非末列上,作为堆叠后相邻列之间的额外间距。
|
|
65
|
+
*
|
|
66
|
+
* 共同注意点:mj-column 的 padding 不落在带 css-class 的外层 div 上,编译结果是
|
|
67
|
+
* `div[class] > table > tbody > tr > td{padding:...}`,所以规则必须命中这一层 td,
|
|
68
|
+
* 用直接子代链避免波及块自身的 padding。
|
|
69
|
+
* 断点取 MJML 默认堆叠断点 480px;!important 用于覆盖行内样式。
|
|
70
|
+
* 仅对会小屏堆叠的列生效;mj-group(preserveColumnsOnMobile)不堆叠,不注入。
|
|
71
|
+
*/
|
|
72
|
+
const COLUMN_GAP_MOBILE_BREAKPOINT = 480;
|
|
73
|
+
|
|
74
|
+
function columnGapClassName(sectionId: string): string {
|
|
75
|
+
return `${sectionMjClassName(sectionId)}-cg`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function columnStackedGapClassName(sectionId: string): string {
|
|
79
|
+
return `${sectionMjClassName(sectionId)}-svg`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 该 Section 的列是否会在小屏堆叠(未包 mj-group 的多列) */
|
|
83
|
+
function stacksOnMobile(section: Section): boolean {
|
|
84
|
+
return section.columns.length > 1 && section.attrs.preserveColumnsOnMobile !== true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function columnGapMobileCss(doc: EmailDoc): string {
|
|
88
|
+
const lines: string[] = [];
|
|
89
|
+
|
|
90
|
+
for (const s of doc.sections) {
|
|
91
|
+
if (!stacksOnMobile(s)) continue;
|
|
92
|
+
const gap = Math.max(0, s.attrs.columnGap ?? 0);
|
|
93
|
+
const stackedGap = Math.max(0, Math.round(s.attrs.columnStackedGap ?? 0));
|
|
94
|
+
if (gap <= 0 && stackedGap <= 0) continue;
|
|
95
|
+
|
|
96
|
+
if (gap > 0) {
|
|
97
|
+
const cls = columnGapClassName(s.id);
|
|
98
|
+
lines.push(
|
|
99
|
+
` .${cls},`,
|
|
100
|
+
` .${cls} > table > tbody > tr > td { padding-left: 0 !important; padding-right: 0 !important; }`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (stackedGap > 0) {
|
|
104
|
+
lines.push(
|
|
105
|
+
` .${columnStackedGapClassName(
|
|
106
|
+
s.id,
|
|
107
|
+
)} > table > tbody > tr > td { padding-bottom: ${stackedGap}px !important; }`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!lines.length) return '';
|
|
113
|
+
return ` @media only screen and (max-width:${COLUMN_GAP_MOBILE_BREAKPOINT}px) {
|
|
114
|
+
${lines.join('\n')}
|
|
115
|
+
}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
58
118
|
function sectionToMjml(section: Section, registry: Registry, ctx: RenderContext): string {
|
|
59
119
|
const a = section.attrs;
|
|
60
120
|
const padding = [a.paddingTop, a.paddingRight, a.paddingBottom, a.paddingLeft]
|
|
@@ -67,10 +127,27 @@ function sectionToMjml(section: Section, registry: Registry, ctx: RenderContext)
|
|
|
67
127
|
secW || dvKey ? ` css-class="${escapeAttr(sectionMjClassName(section.id))}"` : '';
|
|
68
128
|
const widths = layoutWidths(section.layout);
|
|
69
129
|
const gapPx = Math.max(0, section.attrs.columnGap ?? 0);
|
|
130
|
+
/** 仅小屏堆叠时才有水平复位与纵向间距;与下方 grouped 判定保持一致 */
|
|
131
|
+
const stacked = stacksOnMobile(section);
|
|
132
|
+
const hCls = stacked && gapPx > 0 ? columnGapClassName(section.id) : '';
|
|
133
|
+
const vCls =
|
|
134
|
+
stacked && Math.max(0, Math.round(section.attrs.columnStackedGap ?? 0)) > 0
|
|
135
|
+
? columnStackedGapClassName(section.id)
|
|
136
|
+
: '';
|
|
137
|
+
const lastIndex = section.columns.length - 1;
|
|
70
138
|
|
|
71
139
|
const columns = section.columns
|
|
72
140
|
.map((col, i) =>
|
|
73
|
-
columnToMjml(
|
|
141
|
+
columnToMjml(
|
|
142
|
+
col,
|
|
143
|
+
widths[i],
|
|
144
|
+
i,
|
|
145
|
+
section.columns.length,
|
|
146
|
+
gapPx,
|
|
147
|
+
[hCls, vCls && i < lastIndex ? vCls : ''].filter(Boolean).join(' '),
|
|
148
|
+
registry,
|
|
149
|
+
ctx,
|
|
150
|
+
),
|
|
74
151
|
)
|
|
75
152
|
.join('\n');
|
|
76
153
|
|
|
@@ -92,6 +169,7 @@ function columnToMjml(
|
|
|
92
169
|
columnIndex: number,
|
|
93
170
|
columnCount: number,
|
|
94
171
|
columnGapPx: number,
|
|
172
|
+
gapClassNames: string,
|
|
95
173
|
registry: Registry,
|
|
96
174
|
ctx: RenderContext,
|
|
97
175
|
): string {
|
|
@@ -106,6 +184,7 @@ function columnToMjml(
|
|
|
106
184
|
const pr = columnIndex < columnCount - 1 ? `${half}px` : '0px';
|
|
107
185
|
gapPad = ` padding="0px ${pr} 0px ${pl}"`;
|
|
108
186
|
}
|
|
187
|
+
const clsAttr = gapClassNames ? ` css-class="${escapeAttr(gapClassNames)}"` : '';
|
|
109
188
|
|
|
110
189
|
const rawTypo = mjRawCellTypographyFromStyles(ctx.doc.styles);
|
|
111
190
|
const blocks = column.blocks
|
|
@@ -129,7 +208,7 @@ function columnToMjml(
|
|
|
129
208
|
})
|
|
130
209
|
.join('\n');
|
|
131
210
|
|
|
132
|
-
return ` <mj-column width="${width}"${va}${bg}${gapPad}>
|
|
211
|
+
return ` <mj-column width="${width}"${va}${bg}${gapPad}${clsAttr}>
|
|
133
212
|
${blocks || ' <!-- empty column -->'}
|
|
134
213
|
</mj-column>`;
|
|
135
214
|
}
|
|
@@ -24,8 +24,12 @@ export interface EmailMeta {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export interface GlobalStyles {
|
|
27
|
+
/**
|
|
28
|
+
* 邮件背景色:导出时写入 `<mj-body background-color>`,画布内容纸取同一值。
|
|
29
|
+
* 旧字段 `contentBackgroundColor`(内容背景)已废弃并合并到此处,
|
|
30
|
+
* 由 `createDefaultDoc` 在读入旧文档时迁移。
|
|
31
|
+
*/
|
|
27
32
|
backgroundColor: string;
|
|
28
|
-
contentBackgroundColor: string;
|
|
29
33
|
fontFamily: string;
|
|
30
34
|
fontSize: string;
|
|
31
35
|
/** 全局字重:数值档 300 | 400 | 500 | 600 | 700(兼容读写 normal / bold 等别名) */
|
|
@@ -77,6 +81,13 @@ export interface SectionAttrs {
|
|
|
77
81
|
* MJML 通过相邻 `mj-column` 对称内边距实现;画布用 flex `gap` 对齐观感。
|
|
78
82
|
*/
|
|
79
83
|
columnGap?: number;
|
|
84
|
+
/**
|
|
85
|
+
* 多列在小屏堆叠为单列后,相邻列之间的纵向间距(px)。
|
|
86
|
+
* 仅在多列且未开启 `preserveColumnsOnMobile` 时生效;未设置/0 表示不额外加间距
|
|
87
|
+
* (沿用块自身 padding 形成的间距),避免改变已有邮件的移动端排版。
|
|
88
|
+
* 通过小屏媒体查询实现,不会影响桌面端行高。
|
|
89
|
+
*/
|
|
90
|
+
columnStackedGap?: number;
|
|
80
91
|
/**
|
|
81
92
|
* 本节内容区最大宽度(窄于邮件 `meta.width` 时居中)。支持 `480`、`480px`、`90%`;留空则与邮件同宽。
|
|
82
93
|
*/
|
|
@@ -195,10 +206,21 @@ export interface BlockDefinition<P extends object = Record<string, unknown>> {
|
|
|
195
206
|
) => Block[] | PaletteDropResult;
|
|
196
207
|
}
|
|
197
208
|
|
|
198
|
-
/** 左栏组合拖入:可附带新建 Section 的 attrs
|
|
209
|
+
/** 左栏组合拖入:可附带新建 Section 的 attrs、布局和按列分发的 Block。 */
|
|
199
210
|
export interface PaletteDropResult {
|
|
211
|
+
/** 单列组合或落入既有列时插入的 Block。 */
|
|
200
212
|
blocks: Block[];
|
|
201
213
|
sectionAttrs?: Partial<SectionAttrs>;
|
|
214
|
+
/**
|
|
215
|
+
* 组合入口可声明要新建的 Section 布局;多列布局会始终创建独立 Section,
|
|
216
|
+
* 不会被插入到用户当前选中的单列中。
|
|
217
|
+
*/
|
|
218
|
+
sectionLayout?: SectionLayout;
|
|
219
|
+
/**
|
|
220
|
+
* 仅创建新 Section 时使用;每个数组对应一个 Column,索引与 Section 列顺序一致。
|
|
221
|
+
* 超出布局列数的数组会被忽略,缺少的列保持为空。
|
|
222
|
+
*/
|
|
223
|
+
columnBlocks?: Block[][];
|
|
202
224
|
}
|
|
203
225
|
|
|
204
226
|
export interface InlineEditableConfig<P extends object = Record<string, unknown>> {
|