mail-editor-pancake 0.2.8 → 0.3.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/README.md +37 -4
- package/package.json +1 -1
- package/packages/blocks/dist/index.js +275 -263
- package/packages/blocks/dist/index.js.map +1 -1
- package/packages/blocks/package.json +1 -3
- package/packages/blocks/src/button.ts +1 -1
- package/packages/blocks/src/hero.ts +6 -3
- package/packages/blocks/src/i18n.ts +10 -11
- package/packages/blocks/src/image.ts +2 -1
- package/packages/blocks/src/index.ts +5 -3
- package/packages/blocks/src/social.ts +3 -3
- package/packages/blocks/src/socialGroup.ts +4 -4
- package/packages/blocks/src/socialShared.ts +23 -3
- package/packages/blocks/src/text.ts +1 -2
- package/packages/blocks/vite.config.ts +1 -1
- package/packages/core/dist/index.d.ts +96 -1
- package/packages/core/dist/index.js +4346 -3591
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/style.css +1 -1
- package/packages/core/package.json +1 -3
- package/packages/core/src/editor/BlockCodeModal.ts +6 -7
- package/packages/core/src/editor/Canvas.ts +558 -20
- package/packages/core/src/editor/ColorPickerPopover.ts +2 -2
- package/packages/core/src/editor/Editor.ts +379 -39
- package/packages/core/src/editor/ExportModal.ts +7 -9
- package/packages/core/src/editor/FocusBreadcrumb.ts +3 -3
- package/packages/core/src/editor/ImportDocModal.ts +50 -7
- package/packages/core/src/editor/InlineEditor.ts +43 -39
- package/packages/core/src/editor/LeftPanel.ts +2 -4
- package/packages/core/src/editor/Modal.ts +1 -1
- package/packages/core/src/editor/PreviewModal.ts +3 -8
- package/packages/core/src/editor/RichTextToolbar.ts +26 -15
- package/packages/core/src/editor/RightPanel.ts +141 -125
- package/packages/core/src/editor/SourceView.ts +5 -3
- package/packages/core/src/editor/Topbar.ts +79 -34
- package/packages/core/src/editor/VariablePickerPanel.ts +3 -5
- package/packages/core/src/editor/imageAssets.ts +1 -1
- package/packages/core/src/editor/styles.css +258 -86
- package/packages/core/src/i18n/index.ts +139 -41
- package/packages/core/src/index.ts +11 -1
- package/packages/core/src/renderer/index.ts +3 -6
- package/packages/core/src/renderer/mjml.ts +4 -9
- package/packages/core/src/store/store.ts +2 -6
- package/packages/core/src/types.ts +3 -3
- package/packages/core/src/utils/accentColor.ts +3 -3
- package/packages/core/src/utils/docClipboard.ts +107 -2
- package/packages/core/src/utils/dynamicVariantHtml.ts +1 -1
- package/packages/core/src/utils/emailListStyles.ts +1 -1
- package/packages/core/src/utils/fontWeightSteps.ts +7 -6
- package/packages/core/src/utils/html.ts +3 -2
- package/packages/core/src/utils/inlineListEditing.ts +19 -35
- package/packages/core/src/utils/link.ts +3 -6
- package/packages/core/src/utils/lockedMjml.ts +1 -1
- package/packages/core/src/utils/modalSize.ts +4 -3
- package/packages/core/src/utils/richHtmlEmpty.ts +3 -1
- package/packages/core/src/variables/index.ts +4 -5
- package/packages/core/vite.config.ts +1 -1
- package/playground/vanilla/index.html +17 -0
- package/playground/vanilla/src/main.ts +152 -123
- package/playground/vanilla/vite.config.ts +5 -2
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import type { SimpleMailT } from '../i18n';
|
|
1
2
|
import type { Store } from '../store/store';
|
|
2
|
-
import { bindColorPickerInput } from './ColorPickerPopover';
|
|
3
3
|
import { normalizeAccentHex, rgbCssToHex } from '../utils/accentColor';
|
|
4
|
-
import {
|
|
4
|
+
import { clear, h } from '../utils/dom';
|
|
5
|
+
import { bindColorPickerInput } from './ColorPickerPopover';
|
|
5
6
|
import type { EditorTheme } from './theme';
|
|
6
|
-
import type { SimpleMailT } from '../i18n';
|
|
7
7
|
|
|
8
8
|
export type EditorMode = 'design' | 'source';
|
|
9
9
|
|
|
@@ -32,6 +32,8 @@ export interface TopbarOptions {
|
|
|
32
32
|
onFullscreenToggle?: () => void;
|
|
33
33
|
/** 切换画布 Section / Block 布局虚线边框 */
|
|
34
34
|
onLayoutBordersToggle?: () => void;
|
|
35
|
+
/** 进入 / 退出「选择节」批量操作模式(批量复制 / 导出 / 删除 Section) */
|
|
36
|
+
onSectionSelectToggle?: () => void;
|
|
35
37
|
/** 为 false 时不展示全屏按钮(也可用 `ui.hideTopbarFullscreen`) */
|
|
36
38
|
showFullscreenButton?: boolean;
|
|
37
39
|
/** 为 false 时不展示「邮件设置」按钮(也可用 `ui.hideTopbarMailSettings`) */
|
|
@@ -62,6 +64,7 @@ export class Topbar {
|
|
|
62
64
|
private accentInput?: HTMLInputElement;
|
|
63
65
|
private fullscreenBtn?: HTMLButtonElement;
|
|
64
66
|
private layoutBordersBtn?: HTMLButtonElement;
|
|
67
|
+
private sectionSelectBtn?: HTMLButtonElement;
|
|
65
68
|
private insertVariableBtn?: HTMLButtonElement;
|
|
66
69
|
|
|
67
70
|
constructor(opts: TopbarOptions) {
|
|
@@ -97,7 +100,9 @@ export class Topbar {
|
|
|
97
100
|
/** 根据 `.sm-root` 上计算后的 `--sm-primary` 更新拾取器显示(无拾取器或未挂 root 时忽略) */
|
|
98
101
|
syncAccentPicker() {
|
|
99
102
|
if (!this.accentInput || !this.opts.accentPickerRoot) return;
|
|
100
|
-
const raw = getComputedStyle(this.opts.accentPickerRoot)
|
|
103
|
+
const raw = getComputedStyle(this.opts.accentPickerRoot)
|
|
104
|
+
.getPropertyValue('--sm-primary')
|
|
105
|
+
.trim();
|
|
101
106
|
const base = normalizeAccentHex(raw) || rgbCssToHex(raw);
|
|
102
107
|
if (!base) return;
|
|
103
108
|
const norm = normalizeAccentHex(base);
|
|
@@ -117,12 +122,26 @@ export class Topbar {
|
|
|
117
122
|
btn.setAttribute('aria-pressed', active ? 'true' : 'false');
|
|
118
123
|
}
|
|
119
124
|
|
|
125
|
+
/** 「选择节」批量模式激活时高亮顶栏按钮(按钮不存在时忽略) */
|
|
126
|
+
setSectionSelectActive(active: boolean) {
|
|
127
|
+
const btn = this.sectionSelectBtn;
|
|
128
|
+
if (!btn) return;
|
|
129
|
+
btn.setAttribute('aria-pressed', active ? 'true' : 'false');
|
|
130
|
+
const title = active
|
|
131
|
+
? this.opts.t('topbar.sectionSelectActive')
|
|
132
|
+
: this.opts.t('topbar.sectionSelect');
|
|
133
|
+
btn.title = title;
|
|
134
|
+
btn.setAttribute('aria-label', title);
|
|
135
|
+
}
|
|
136
|
+
|
|
120
137
|
/** 与画布布局边框开关同步按钮高亮(按钮不存在时忽略) */
|
|
121
138
|
setLayoutBordersActive(active: boolean) {
|
|
122
139
|
const btn = this.layoutBordersBtn;
|
|
123
140
|
if (!btn) return;
|
|
124
141
|
btn.setAttribute('aria-pressed', active ? 'true' : 'false');
|
|
125
|
-
const title = active
|
|
142
|
+
const title = active
|
|
143
|
+
? this.opts.t('topbar.hideLayoutBorders')
|
|
144
|
+
: this.opts.t('topbar.showLayoutBorders');
|
|
126
145
|
btn.title = title;
|
|
127
146
|
btn.setAttribute('aria-label', title);
|
|
128
147
|
}
|
|
@@ -139,7 +158,11 @@ export class Topbar {
|
|
|
139
158
|
clear(this.el);
|
|
140
159
|
const t = this.opts.t;
|
|
141
160
|
|
|
142
|
-
const segmented = h('div', {
|
|
161
|
+
const segmented = h('div', {
|
|
162
|
+
class: 'sm-segmented sm-topbar__mode-seg',
|
|
163
|
+
role: 'group',
|
|
164
|
+
'aria-label': t('topbar.mode'),
|
|
165
|
+
});
|
|
143
166
|
const designBtn = modeSegBtn({
|
|
144
167
|
active: this.opts.mode === 'design',
|
|
145
168
|
title: t('topbar.design'),
|
|
@@ -219,7 +242,10 @@ export class Topbar {
|
|
|
219
242
|
});
|
|
220
243
|
|
|
221
244
|
const contentActionKids: HTMLElement[] = [];
|
|
222
|
-
if (
|
|
245
|
+
if (
|
|
246
|
+
this.opts.showClearCanvasButton !== false &&
|
|
247
|
+
typeof this.opts.onClearCanvas === 'function'
|
|
248
|
+
) {
|
|
223
249
|
contentActionKids.push(
|
|
224
250
|
actionBtn({
|
|
225
251
|
class: 'sm-btn--ghost',
|
|
@@ -300,15 +326,15 @@ export class Topbar {
|
|
|
300
326
|
);
|
|
301
327
|
}
|
|
302
328
|
if (this.opts.showInsertVariableButton !== false) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
);
|
|
329
|
+
const insertVariableBtn = actionBtn({
|
|
330
|
+
class: 'sm-topbar__insert-var',
|
|
331
|
+
title: t('topbar.insertVariable'),
|
|
332
|
+
icon: iconVariable(),
|
|
333
|
+
label: t('topbar.insertVariable'),
|
|
334
|
+
onclick: (e: Event) => this.opts.onInsertVariable(e.currentTarget as HTMLElement),
|
|
335
|
+
});
|
|
336
|
+
this.insertVariableBtn = insertVariableBtn;
|
|
337
|
+
trailingGroup.push(insertVariableBtn);
|
|
312
338
|
} else {
|
|
313
339
|
this.insertVariableBtn = undefined;
|
|
314
340
|
}
|
|
@@ -316,16 +342,29 @@ export class Topbar {
|
|
|
316
342
|
|
|
317
343
|
const canvasToolKids: HTMLElement[] = [];
|
|
318
344
|
|
|
345
|
+
if (typeof this.opts.onSectionSelectToggle === 'function') {
|
|
346
|
+
this.sectionSelectBtn = iconOnlyBtn({
|
|
347
|
+
class: 'sm-btn--ghost sm-topbar__toggle-btn',
|
|
348
|
+
title: t('topbar.sectionSelect'),
|
|
349
|
+
icon: iconSectionSelect(),
|
|
350
|
+
pressed: false,
|
|
351
|
+
onclick: () => this.opts.onSectionSelectToggle!(),
|
|
352
|
+
});
|
|
353
|
+
canvasToolKids.push(this.sectionSelectBtn);
|
|
354
|
+
} else {
|
|
355
|
+
this.sectionSelectBtn = undefined;
|
|
356
|
+
}
|
|
357
|
+
|
|
319
358
|
if (typeof this.opts.onLayoutBordersToggle === 'function') {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
);
|
|
359
|
+
const layoutBordersBtn = iconOnlyBtn({
|
|
360
|
+
class: 'sm-btn--ghost sm-topbar__toggle-btn',
|
|
361
|
+
title: t('topbar.showLayoutBorders'),
|
|
362
|
+
icon: iconLayoutBorders(),
|
|
363
|
+
pressed: false,
|
|
364
|
+
onclick: () => this.opts.onLayoutBordersToggle!(),
|
|
365
|
+
}) as HTMLButtonElement;
|
|
366
|
+
this.layoutBordersBtn = layoutBordersBtn;
|
|
367
|
+
canvasToolKids.push(layoutBordersBtn);
|
|
329
368
|
} else {
|
|
330
369
|
this.layoutBordersBtn = undefined;
|
|
331
370
|
}
|
|
@@ -334,15 +373,15 @@ export class Topbar {
|
|
|
334
373
|
this.opts.showFullscreenButton !== false &&
|
|
335
374
|
typeof this.opts.onFullscreenToggle === 'function';
|
|
336
375
|
if (showFs) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
);
|
|
376
|
+
const fullscreenBtn = iconOnlyBtn({
|
|
377
|
+
class: 'sm-btn--ghost sm-topbar__toggle-btn',
|
|
378
|
+
title: t('topbar.fullscreen'),
|
|
379
|
+
icon: iconFullscreenEnter(),
|
|
380
|
+
pressed: false,
|
|
381
|
+
onclick: () => this.opts.onFullscreenToggle!(),
|
|
382
|
+
}) as HTMLButtonElement;
|
|
383
|
+
this.fullscreenBtn = fullscreenBtn;
|
|
384
|
+
canvasToolKids.push(fullscreenBtn);
|
|
346
385
|
} else {
|
|
347
386
|
this.fullscreenBtn = undefined;
|
|
348
387
|
}
|
|
@@ -508,6 +547,12 @@ function iconImportDoc(): SVGElement {
|
|
|
508
547
|
'<path d="M10 4.5v6.5M7.2 9.2 10 12l2.8-2.8M5.5 14.5h9" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/><rect x="4" y="14" width="12" height="2.5" rx=".6" fill="currentColor"/>',
|
|
509
548
|
);
|
|
510
549
|
}
|
|
550
|
+
/** 选择节(批量操作):勾选列表 */
|
|
551
|
+
function iconSectionSelect(): SVGElement {
|
|
552
|
+
return svg(
|
|
553
|
+
'<rect x="3" y="3.5" width="14" height="13" rx="2" stroke="currentColor" stroke-width="1.4" fill="none"/><path d="M6.2 10l2.2 2.2 4.6-5" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>',
|
|
554
|
+
);
|
|
555
|
+
}
|
|
511
556
|
/** 显示边框:圆角矩形描边(开启时由 toggle 样式点亮 currentColor) */
|
|
512
557
|
function iconLayoutBorders(): SVGElement {
|
|
513
558
|
return svgPathFill(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import type { SimpleMailT } from '../i18n';
|
|
1
2
|
import type { Variable } from '../types';
|
|
2
|
-
import { variablePlaceholder } from '../variables';
|
|
3
3
|
import { h } from '../utils/dom';
|
|
4
|
-
import
|
|
4
|
+
import { variablePlaceholder } from '../variables';
|
|
5
5
|
|
|
6
6
|
export interface VariablePickerHandlers {
|
|
7
7
|
onPickKey: (v: Variable) => void;
|
|
@@ -88,9 +88,7 @@ export function buildVariablePickerBody(
|
|
|
88
88
|
): HTMLElement {
|
|
89
89
|
const wrap = h('div', { class: 'sm-var-picker' });
|
|
90
90
|
if (!vars.length) {
|
|
91
|
-
wrap.append(
|
|
92
|
-
h('div', { class: 'sm-empty-form' }, [t('variablePicker.empty')]),
|
|
93
|
-
);
|
|
91
|
+
wrap.append(h('div', { class: 'sm-empty-form' }, [t('variablePicker.empty')]));
|
|
94
92
|
return wrap;
|
|
95
93
|
}
|
|
96
94
|
for (const v of vars) {
|
|
@@ -89,7 +89,7 @@ export interface ImageGalleryAdapter {
|
|
|
89
89
|
/** 搜索 + 分页;`query` 为搜索框当前文本,`page` 从 0 起 */
|
|
90
90
|
listItems: (args: { query: string; page: number }) => Promise<ImageGalleryListResult>;
|
|
91
91
|
/** 在弹层内「上传到图库」:成功后编辑器会重新拉取第 0 页 */
|
|
92
|
-
uploadFile?: (file: File) => Promise<
|
|
92
|
+
uploadFile?: (file: File) => Promise<unknown>;
|
|
93
93
|
/** 在弹层内「通过链接添加」:校验/落库后编辑器会重新拉取第 0 页 */
|
|
94
94
|
addByUrl?: (url: string) => Promise<void>;
|
|
95
95
|
/** 从图库删除一条;提供后每张缩略图右上角显示删除按钮 */
|