mail-editor-pancake 0.0.7 → 0.0.8
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 +50 -1
- package/package.json +1 -1
- package/packages/blocks/dist/index.js +88 -89
- package/packages/blocks/dist/index.js.map +1 -1
- package/packages/blocks/src/text.ts +21 -14
- package/packages/core/dist/index.d.ts +45 -4
- package/packages/core/dist/index.js +2252 -1449
- package/packages/core/dist/index.js.map +1 -1
- package/packages/core/dist/style.css +1 -1
- package/packages/core/src/editor/Canvas.ts +68 -34
- package/packages/core/src/editor/ColorPickerPopover.ts +291 -0
- package/packages/core/src/editor/Editor.ts +72 -2
- package/packages/core/src/editor/ExportModal.ts +2 -1
- package/packages/core/src/editor/FocusBreadcrumb.ts +120 -0
- package/packages/core/src/editor/InlineEditor.ts +306 -65
- package/packages/core/src/editor/LeftPanel.ts +1 -2
- package/packages/core/src/editor/RichTextToolbar.ts +66 -44
- package/packages/core/src/editor/RightPanel.ts +491 -74
- package/packages/core/src/editor/SourceView.ts +2 -1
- package/packages/core/src/editor/Topbar.ts +98 -7
- package/packages/core/src/editor/styles.css +378 -25
- package/packages/core/src/index.ts +12 -1
- package/packages/core/src/store/store.ts +44 -3
- package/packages/core/src/types.ts +6 -0
- package/packages/core/src/utils/richTextCommand.ts +30 -0
- package/packages/core/src/utils/sectionLayout.ts +13 -0
|
@@ -5,6 +5,7 @@ import type { Registry } from '../registry/registry';
|
|
|
5
5
|
import { renderDoc } from '../renderer';
|
|
6
6
|
import type { Store } from '../store/store';
|
|
7
7
|
import { clear, h } from '../utils/dom';
|
|
8
|
+
import { richTextExecCommand } from '../utils/richTextCommand';
|
|
8
9
|
|
|
9
10
|
export interface SourceViewOptions {
|
|
10
11
|
store: Store;
|
|
@@ -110,7 +111,7 @@ export class SourceView {
|
|
|
110
111
|
ta.value = text;
|
|
111
112
|
document.body.appendChild(ta);
|
|
112
113
|
ta.select();
|
|
113
|
-
|
|
114
|
+
richTextExecCommand('copy');
|
|
114
115
|
document.body.removeChild(ta);
|
|
115
116
|
}
|
|
116
117
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Store } from '../store/store';
|
|
2
|
+
import { bindColorPickerInput } from './ColorPickerPopover';
|
|
2
3
|
import { normalizeAccentHex, rgbCssToHex } from '../utils/accentColor';
|
|
3
4
|
import { h, clear } from '../utils/dom';
|
|
4
5
|
import type { EditorTheme } from './theme';
|
|
@@ -25,12 +26,20 @@ export interface TopbarOptions {
|
|
|
25
26
|
onExport: () => void;
|
|
26
27
|
/** 切换编辑器根节点浏览器全屏(由 Editor 实现 Fullscreen API) */
|
|
27
28
|
onFullscreenToggle?: () => void;
|
|
29
|
+
/** 切换画布 Section / Block 布局虚线边框 */
|
|
30
|
+
onLayoutBordersToggle?: () => void;
|
|
28
31
|
/** 为 false 时不展示全屏按钮(也可用 `ui.hideTopbarFullscreen`) */
|
|
29
32
|
showFullscreenButton?: boolean;
|
|
30
33
|
/** 为 false 时不展示「邮件设置」按钮(也可用 `ui.hideTopbarMailSettings`) */
|
|
31
34
|
showMailSettingsButton?: boolean;
|
|
32
35
|
/** 为 false 时不渲染左侧产品标题 */
|
|
33
36
|
showTitle?: boolean;
|
|
37
|
+
/** 为 false 时不展示「清空画布」 */
|
|
38
|
+
showClearCanvasButton?: boolean;
|
|
39
|
+
onClearCanvas?: () => void;
|
|
40
|
+
/** 为 false 时不展示「重置内容」 */
|
|
41
|
+
showResetContentButton?: boolean;
|
|
42
|
+
onResetContent?: () => void;
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
export class Topbar {
|
|
@@ -42,6 +51,7 @@ export class Topbar {
|
|
|
42
51
|
private modeButtons: Record<EditorMode, HTMLButtonElement> = {} as any;
|
|
43
52
|
private accentInput?: HTMLInputElement;
|
|
44
53
|
private fullscreenBtn?: HTMLButtonElement;
|
|
54
|
+
private layoutBordersBtn?: HTMLButtonElement;
|
|
45
55
|
|
|
46
56
|
constructor(opts: TopbarOptions) {
|
|
47
57
|
this.opts = opts;
|
|
@@ -89,6 +99,15 @@ export class Topbar {
|
|
|
89
99
|
btn.setAttribute('aria-pressed', active ? 'true' : 'false');
|
|
90
100
|
}
|
|
91
101
|
|
|
102
|
+
/** 与画布布局边框开关同步按钮高亮(按钮不存在时忽略) */
|
|
103
|
+
setLayoutBordersActive(active: boolean) {
|
|
104
|
+
const btn = this.layoutBordersBtn;
|
|
105
|
+
if (!btn) return;
|
|
106
|
+
btn.setAttribute('aria-pressed', active ? 'true' : 'false');
|
|
107
|
+
btn.title = active ? '隐藏 Section / Block 边框' : '显示 Section / Block 边框';
|
|
108
|
+
btn.setAttribute('aria-label', active ? '隐藏 Section / Block 边框' : '显示 Section / Block 边框');
|
|
109
|
+
}
|
|
110
|
+
|
|
92
111
|
private _render() {
|
|
93
112
|
clear(this.el);
|
|
94
113
|
|
|
@@ -147,7 +166,11 @@ export class Topbar {
|
|
|
147
166
|
title: '主题色',
|
|
148
167
|
value: '#4f46e5',
|
|
149
168
|
}) as HTMLInputElement;
|
|
150
|
-
inputEl
|
|
169
|
+
bindColorPickerInput(inputEl, {
|
|
170
|
+
layerRoot: this.opts.accentPickerRoot,
|
|
171
|
+
liveCommit: true,
|
|
172
|
+
onCommit: (hex) => this.opts.onAccentChange!(hex),
|
|
173
|
+
});
|
|
151
174
|
this.accentInput = inputEl;
|
|
152
175
|
themeGroupKids.push(h('div', { class: 'sm-topbar__accent' }, [inputEl]));
|
|
153
176
|
} else {
|
|
@@ -175,6 +198,41 @@ export class Topbar {
|
|
|
175
198
|
[iconRedo(), '重做'],
|
|
176
199
|
);
|
|
177
200
|
|
|
201
|
+
const contentActionKids: HTMLElement[] = [];
|
|
202
|
+
if (this.opts.showClearCanvasButton !== false && typeof this.opts.onClearCanvas === 'function') {
|
|
203
|
+
contentActionKids.push(
|
|
204
|
+
h(
|
|
205
|
+
'button',
|
|
206
|
+
{
|
|
207
|
+
class: 'sm-btn sm-btn--ghost',
|
|
208
|
+
type: 'button',
|
|
209
|
+
title: '移除画布上所有 Section 与组件',
|
|
210
|
+
onclick: () => this.opts.onClearCanvas!(),
|
|
211
|
+
},
|
|
212
|
+
['清空画布'],
|
|
213
|
+
),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (
|
|
217
|
+
this.opts.showResetContentButton !== false &&
|
|
218
|
+
typeof this.opts.onResetContent === 'function'
|
|
219
|
+
) {
|
|
220
|
+
contentActionKids.push(
|
|
221
|
+
h(
|
|
222
|
+
'button',
|
|
223
|
+
{
|
|
224
|
+
class: 'sm-btn sm-btn--ghost',
|
|
225
|
+
type: 'button',
|
|
226
|
+
title: '恢复为预置邮件结构',
|
|
227
|
+
onclick: () => this.opts.onResetContent!(),
|
|
228
|
+
},
|
|
229
|
+
['重置内容'],
|
|
230
|
+
),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
const contentActionsGroup =
|
|
234
|
+
contentActionKids.length > 0 && h('div', { class: 'sm-topbar__group' }, contentActionKids);
|
|
235
|
+
|
|
178
236
|
const insertVar = h(
|
|
179
237
|
'button',
|
|
180
238
|
{
|
|
@@ -215,12 +273,32 @@ export class Topbar {
|
|
|
215
273
|
}
|
|
216
274
|
trailingGroup.push(insertVar, previewBtn, exportBtn);
|
|
217
275
|
|
|
276
|
+
const canvasToolKids: HTMLElement[] = [];
|
|
277
|
+
|
|
278
|
+
if (typeof this.opts.onLayoutBordersToggle === 'function') {
|
|
279
|
+
canvasToolKids.push(
|
|
280
|
+
(this.layoutBordersBtn = h(
|
|
281
|
+
'button',
|
|
282
|
+
{
|
|
283
|
+
class: 'sm-btn sm-btn--ghost sm-topbar__icon-btn sm-topbar__toggle-btn',
|
|
284
|
+
type: 'button',
|
|
285
|
+
title: '显示 Section / Block 边框',
|
|
286
|
+
'aria-label': '显示 Section / Block 边框',
|
|
287
|
+
'aria-pressed': 'false',
|
|
288
|
+
onclick: () => this.opts.onLayoutBordersToggle!(),
|
|
289
|
+
},
|
|
290
|
+
[iconLayoutBorders()],
|
|
291
|
+
) as HTMLButtonElement),
|
|
292
|
+
);
|
|
293
|
+
} else {
|
|
294
|
+
this.layoutBordersBtn = undefined;
|
|
295
|
+
}
|
|
296
|
+
|
|
218
297
|
const showFs =
|
|
219
298
|
this.opts.showFullscreenButton !== false &&
|
|
220
299
|
typeof this.opts.onFullscreenToggle === 'function';
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
h('div', { class: 'sm-topbar__group' }, [
|
|
300
|
+
if (showFs) {
|
|
301
|
+
canvasToolKids.push(
|
|
224
302
|
(this.fullscreenBtn = h(
|
|
225
303
|
'button',
|
|
226
304
|
{
|
|
@@ -233,8 +311,13 @@ export class Topbar {
|
|
|
233
311
|
},
|
|
234
312
|
[iconFullscreenEnter()],
|
|
235
313
|
) as HTMLButtonElement),
|
|
236
|
-
|
|
237
|
-
|
|
314
|
+
);
|
|
315
|
+
} else {
|
|
316
|
+
this.fullscreenBtn = undefined;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const canvasToolsGroup =
|
|
320
|
+
canvasToolKids.length > 0 && h('div', { class: 'sm-topbar__group' }, canvasToolKids);
|
|
238
321
|
|
|
239
322
|
const headKids: HTMLElement[] = [];
|
|
240
323
|
if (this.opts.showTitle !== false) {
|
|
@@ -246,7 +329,8 @@ export class Topbar {
|
|
|
246
329
|
h('div', { class: 'sm-topbar__group' }, [segmented]),
|
|
247
330
|
h('div', { class: 'sm-topbar__group sm-topbar__theme-group' }, themeGroupKids),
|
|
248
331
|
h('div', { class: 'sm-topbar__group' }, [this.undoBtn, this.redoBtn]),
|
|
249
|
-
...(
|
|
332
|
+
...(contentActionsGroup ? [contentActionsGroup] : []),
|
|
333
|
+
...(canvasToolsGroup ? [canvasToolsGroup] : []),
|
|
250
334
|
h('div', { class: 'sm-topbar__spacer' }),
|
|
251
335
|
h('div', { class: 'sm-topbar__group' }, trailingGroup),
|
|
252
336
|
);
|
|
@@ -276,6 +360,13 @@ function iconEye(): SVGElement {
|
|
|
276
360
|
'<path d="M2 10s3-5 8-5 8 5 8 5-3 5-8 5-8-5-8-5z" stroke="currentColor" stroke-width="1.5" fill="none"/><circle cx="10" cy="10" r="2.4" stroke="currentColor" stroke-width="1.5" fill="none"/>',
|
|
277
361
|
);
|
|
278
362
|
}
|
|
363
|
+
/** 布局边框:虚线框 + 勾选(点亮时显示勾) */
|
|
364
|
+
function iconLayoutBorders(): SVGElement {
|
|
365
|
+
const el = svg(
|
|
366
|
+
'<rect x="4.5" y="4.5" width="11" height="11" rx="1.5" stroke="currentColor" stroke-width="1.5" fill="none" stroke-dasharray="2.5 2"/><path d="M7.2 10.1l1.8 1.8 3.8-3.9" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round" stroke-linejoin="round" class="sm-layout-borders-check"/>',
|
|
367
|
+
);
|
|
368
|
+
return el;
|
|
369
|
+
}
|
|
279
370
|
/** 进入全屏:四角向外 */
|
|
280
371
|
function iconFullscreenEnter(): SVGElement {
|
|
281
372
|
return svg(
|