mail-editor-pancake 0.0.8 → 0.1.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.
Files changed (48) hide show
  1. package/README.md +498 -218
  2. package/package.json +2 -2
  3. package/packages/blocks/dist/index.d.ts +12 -0
  4. package/packages/blocks/dist/index.js +187 -145
  5. package/packages/blocks/dist/index.js.map +1 -1
  6. package/packages/blocks/src/html.ts +11 -3
  7. package/packages/blocks/src/index.ts +2 -0
  8. package/packages/blocks/src/social.ts +8 -11
  9. package/packages/blocks/src/socialGroup.ts +8 -11
  10. package/packages/blocks/src/socialShared.ts +52 -0
  11. package/packages/blocks/src/text.ts +8 -3
  12. package/packages/core/dist/index.d.ts +275 -12
  13. package/packages/core/dist/index.js +4099 -2194
  14. package/packages/core/dist/index.js.map +1 -1
  15. package/packages/core/dist/style.css +1 -1
  16. package/packages/core/package.json +1 -1
  17. package/packages/core/src/editor/BlockCodeModal.ts +84 -2
  18. package/packages/core/src/editor/Canvas.ts +191 -26
  19. package/packages/core/src/editor/Editor.ts +589 -76
  20. package/packages/core/src/editor/ExportModal.ts +1 -1
  21. package/packages/core/src/editor/ImportDocModal.ts +129 -0
  22. package/packages/core/src/editor/InlineEditor.ts +246 -22
  23. package/packages/core/src/editor/LeftPanel.ts +55 -14
  24. package/packages/core/src/editor/Modal.ts +20 -1
  25. package/packages/core/src/editor/PreviewModal.ts +2 -2
  26. package/packages/core/src/editor/RichTextToolbar.ts +125 -18
  27. package/packages/core/src/editor/RightPanel.ts +165 -7
  28. package/packages/core/src/editor/Topbar.ts +306 -132
  29. package/packages/core/src/editor/VariablePickerPanel.ts +145 -0
  30. package/packages/core/src/editor/styles.css +458 -59
  31. package/packages/core/src/index.ts +43 -2
  32. package/packages/core/src/renderer/index.ts +9 -1
  33. package/packages/core/src/renderer/mjml.ts +4 -5
  34. package/packages/core/src/store/store.ts +13 -2
  35. package/packages/core/src/types.ts +67 -2
  36. package/packages/core/src/utils/docClipboard.ts +89 -0
  37. package/packages/core/src/utils/dynamicVariantHtml.ts +45 -0
  38. package/packages/core/src/utils/dynamicVariantKey.ts +24 -0
  39. package/packages/core/src/utils/dynamicVariantSection.ts +98 -0
  40. package/packages/core/src/utils/emailListStyles.ts +188 -0
  41. package/packages/core/src/utils/inlineListEditing.ts +774 -0
  42. package/packages/core/src/utils/lockedMjml.ts +146 -12
  43. package/packages/core/src/utils/modalSize.ts +27 -0
  44. package/packages/core/src/utils/richHtmlEmpty.ts +43 -0
  45. package/packages/core/src/utils/richHtmlInsert.ts +16 -0
  46. package/packages/core/src/utils/sectionLayout.ts +2 -2
  47. package/packages/core/src/variables/index.ts +90 -0
  48. package/playground/vanilla/src/main.ts +26 -1
@@ -1,4 +1,4 @@
1
- import { defineBlock, FONT_WEIGHT_STEP_OPTIONS, normalizeFontWeightStep } from '@simple-mail/core';
1
+ import { defineBlock, FONT_WEIGHT_STEP_OPTIONS, isRichHtmlEffectivelyEmpty, normalizeFontWeightStep } from '@simple-mail/core';
2
2
  import { icons } from './icons';
3
3
 
4
4
  interface TextProps {
@@ -19,6 +19,8 @@ interface TextProps {
19
19
  const escapeAttr = (s: string) =>
20
20
  s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
21
21
 
22
+ const TEXT_PLACEHOLDER = '输入文本…';
23
+
22
24
  export const textBlock = defineBlock<TextProps>({
23
25
  type: 'text',
24
26
  name: '文本',
@@ -94,7 +96,7 @@ export const textBlock = defineBlock<TextProps>({
94
96
  mode: 'rich',
95
97
  multiline: true,
96
98
  propKey: 'content',
97
- placeholder: '输入文本…',
99
+ placeholder: TEXT_PLACEHOLDER,
98
100
  },
99
101
  toMjml: (p) => {
100
102
  const padding = `${p.paddingTop}px ${p.paddingRight}px ${p.paddingBottom}px ${p.paddingLeft}px`;
@@ -119,6 +121,9 @@ export const textBlock = defineBlock<TextProps>({
119
121
  const colorCss = colorRaw ? `color:${colorRaw};` : '';
120
122
  const fsRaw = String(p.fontSize ?? '').trim();
121
123
  const fsCss = fsRaw ? `font-size:${fsRaw};` : '';
122
- return `<div class="sm-text-content" style="text-align:${p.align};${colorCss}${fsCss}${fwCss}${lh}${ff}padding:${p.paddingTop}px ${p.paddingRight}px ${p.paddingBottom}px ${p.paddingLeft}px;">${p.content}</div>`;
124
+ const empty = isRichHtmlEffectivelyEmpty(p.content);
125
+ const emptyClass = empty ? ' is-empty' : '';
126
+ const placeholderAttr = empty ? ` data-placeholder="${TEXT_PLACEHOLDER}"` : '';
127
+ return `<div class="sm-text-content${emptyClass}"${placeholderAttr} style="text-align:${p.align};${colorCss}${fsCss}${fwCss}${lh}${ff}padding:${p.paddingTop}px ${p.paddingRight}px ${p.paddingBottom}px ${p.paddingLeft}px;">${p.content}</div>`;
123
128
  },
124
129
  });
@@ -1,3 +1,14 @@
1
+ /**
2
+ * 编译后为带 `dynamicVariantKey` 的 Section 根节点写入 `data-dv="{key}"`,供宿主 DOM 抽取。
3
+ */
4
+ export declare function annotateDynamicVariantHtmlAttributes(html: string, doc: EmailDoc): string;
5
+
6
+ /**
7
+ * 向富文本 HTML 末尾「行内」追加内容,避免落在 `</p>` 外产生换行。
8
+ * 无块级标签时包一层 `<p style="margin:0;">`。
9
+ */
10
+ export declare function appendInlineToRichHtml(html: string, insert: string): string;
11
+
1
12
  /**
2
13
  * Block 是注册表驱动的:所有内置/自定义组件都遵循同一个数据形状。
3
14
  * 渲染、属性面板、代码模式三方都从注册表读取该 type 的定义。
@@ -22,8 +33,10 @@ export declare interface BlockDefinition<P extends object = Record<string, unkno
22
33
  type: string;
23
34
  /** 显示名(左栏) */
24
35
  name: string;
25
- /** 分类,用于左栏分组 */
36
+ /** 分类,用于左栏分组与定制块角标 */
26
37
  category: 'content' | 'custom';
38
+ /** 左栏卡片悬停提示;未设时 custom 类块使用 UI 默认后缀 */
39
+ paletteTooltip?: string;
27
40
  /** 16x16 SVG 字符串 */
28
41
  icon: string;
29
42
  /** 默认 props(拖入时使用) */
@@ -49,7 +62,14 @@ export declare interface BlockDefinition<P extends object = Record<string, unkno
49
62
  * 用于「组合模板」:画布内仅为通用 image/text 等,便于逐项修改。
50
63
  * 文档里不会出现本定义的 `type`,仅作左栏入口;若需提供请返回内置块的实例。
51
64
  */
52
- expandPaletteDrop?: (createBlock: (type: string) => Block) => Block[];
65
+ expandPaletteDrop?: (createBlock: (type: string) => Block) => Block[] | PaletteDropResult;
66
+ }
67
+
68
+ declare interface BlockPaddingBox {
69
+ top: number;
70
+ right: number;
71
+ bottom: number;
72
+ left: number;
53
73
  }
54
74
 
55
75
  export declare interface BlockSchemaField {
@@ -76,6 +96,28 @@ export declare interface BlockSchemaField {
76
96
  inheritGlobal?: boolean;
77
97
  }
78
98
 
99
+ export declare interface BodyVariableInsert {
100
+ content: string;
101
+ asHtml: boolean;
102
+ }
103
+
104
+ /** 插入链接 / 图片等元素片段 */
105
+ export declare function buildBodyVariableElementInsert(v: Variable, options?: {
106
+ linkColor?: string;
107
+ token?: string;
108
+ }): BodyVariableInsert;
109
+
110
+ /** @deprecated 使用 buildBodyVariableKeyInsert / buildBodyVariableElementInsert */
111
+ export declare function buildBodyVariableInsert(v: Variable, options?: {
112
+ linkColor?: string;
113
+ token?: string;
114
+ }): BodyVariableInsert;
115
+
116
+ /** 插入变量 key(`{{key}}`) */
117
+ export declare function buildBodyVariableKeyInsert(v: Variable): BodyVariableInsert;
118
+
119
+ export declare function buildLinkVariableHtml(token: string, linkColor: string): string;
120
+
79
121
  export declare interface Column {
80
122
  id: string;
81
123
  attrs: ColumnAttrs;
@@ -89,14 +131,28 @@ declare interface ColumnAttrs {
89
131
 
90
132
  export declare function createSection(layout: SectionLayout): Section;
91
133
 
134
+ /** 无显式 sample 时保留 Mustache 占位符;仅当宿主传入 sample 才用于可选的示例替换。 */
135
+ export declare function defaultVariableSample(v: Pick<Variable, 'key' | 'label' | 'kind' | 'sample'>): string;
136
+
92
137
  /** 工厂函数,便于外部以函数式风格定义组件。 */
93
138
  export declare function defineBlock<P extends object>(def: BlockDefinition<P>): BlockDefinition<P>;
94
139
 
140
+ export declare const DOC_CLIPBOARD_FORMAT_VERSION: 1;
141
+
142
+ /** 剪贴板 / 文件 interchange 信封标识 */
143
+ export declare const DOC_CLIPBOARD_KIND: "simple-mail/doc";
144
+
95
145
  /** 文档变更通知来源(右栏等可按来源决定是否保留焦点) */
96
146
  declare type DocChangedDetail = {
97
147
  source: 'mutate' | 'history' | 'replace';
98
148
  };
99
149
 
150
+ export declare interface DocClipboardEnvelope {
151
+ kind: typeof DOC_CLIPBOARD_KIND;
152
+ formatVersion: typeof DOC_CLIPBOARD_FORMAT_VERSION;
153
+ doc: EmailDoc;
154
+ }
155
+
100
156
  /**
101
157
  * 内容区 / 按钮等「布局宽度」解析:数字与无单位数字按 px;支持显式 `px` 与 `%`。
102
158
  */
@@ -111,6 +167,13 @@ export declare function docContentWidthCss(raw: unknown): string;
111
167
  */
112
168
  export declare function docToMjml(doc: EmailDoc, registry: Registry): string;
113
169
 
170
+ /**
171
+ * 导出 HTML 上标记动态变量组合的宿主约定属性名(值为 `Section.attrs.dynamicVariantKey`)。
172
+ */
173
+ export declare const DYNAMIC_VARIANT_HTML_ATTR = "data-dv";
174
+
175
+ export declare function dynamicVariantPlaceholder(key: string): string;
176
+
114
177
  export declare interface EditorEvents {
115
178
  change: {
116
179
  doc: EmailDoc;
@@ -205,6 +268,45 @@ export declare interface EditorUiOptions {
205
268
  hideTopbarClearCanvas?: boolean;
206
269
  /** 为 true 时隐藏顶栏「重置内容」按钮。 */
207
270
  hideTopbarResetContent?: boolean;
271
+ /** 为 true 时隐藏顶栏「复制设计稿」「导入设计稿」按钮。 */
272
+ hideTopbarDocClipboard?: boolean;
273
+ /** 为 true 时隐藏顶栏「插入变量」按钮(由宿主在编辑器外提供时可用)。 */
274
+ hideTopbarInsertVariable?: boolean;
275
+ /**
276
+ * 为 true 时顶栏默认可切换为仅图标(文案收入 `title` / `aria-label`),适合窄屏或嵌入宿主。
277
+ * 默认 false;嵌入场景可在宿主侧设为 true。
278
+ */
279
+ topbarCompact?: boolean;
280
+ /**
281
+ * 顶栏按钮文案展示策略(在 `topbarCompact === true` 时生效)。
282
+ * - `auto`(默认):仅图标;根节点进入浏览器全屏或宽度 ≥ `topbarCompactMinWidth` 时展示文案。
283
+ * - `never`:始终仅图标(含全屏与宽屏)。
284
+ * - `always`:始终展示图标旁文案(等同不启用紧凑顶栏)。
285
+ */
286
+ topbarLabels?: 'always' | 'never' | 'auto';
287
+ /**
288
+ * `topbarLabels: 'auto'` 时,根节点宽度不低于该值(px)则展示顶栏文案。默认 1200。
289
+ */
290
+ topbarCompactMinWidth?: number;
291
+ /**
292
+ * @deprecated 左栏已合并为单组,请用 `paletteBlockGroupTitle`。
293
+ * 仍兼容:仅 `content` 键会作为合并后分组标题的回退。
294
+ */
295
+ blockCategoryLabels?: Partial<Record<BlockDefinition['category'], string>>;
296
+ /** 左栏合并后的组件分组标题,默认「组件」。 */
297
+ paletteBlockGroupTitle?: string;
298
+ /** custom 类块无 `paletteTooltip` 时拼入 title 的说明后缀。 */
299
+ customPaletteTooltipSuffix?: string;
300
+ /**
301
+ * 仍注册、渲染、可被 palette 展开引用,但不显示在左栏的 block type(如组合块内部用的 divider)。
302
+ */
303
+ hiddenPaletteBlockTypes?: string[];
304
+ /**
305
+ * 为 true 时:右栏 Section 可配置「动态变量名」、画布展示动态变量节标识,
306
+ * 左栏拖入带 `sectionAttrs.dynamicVariantKey` 的组合块会创建动态变量 Section。
307
+ * 默认 false;宿主(如后管)按需开启。已写入文档的 key 仍会在导出时生效。
308
+ */
309
+ enableDynamicVariantKey?: boolean;
208
310
  }
209
311
 
210
312
  /**
@@ -231,6 +333,22 @@ export declare interface EmailMeta {
231
333
  width: number | string;
232
334
  }
233
335
 
336
+ /**
337
+ * 将带 `dynamicVariantKey` 的 Section 从全文 HTML 中替换为占位符,并返回各 key 的编译 HTML。
338
+ * 优先使用导出 HTML 上的 `data-dv="{key}"` 标记。
339
+ */
340
+ export declare function extractDynamicVariantSlots(html: string, doc: EmailDoc, registry: Registry): ExtractDynamicVariantSlotsResult;
341
+
342
+ export declare interface ExtractDynamicVariantSlotsResult {
343
+ html: string;
344
+ variants: Record<string, string>;
345
+ }
346
+
347
+ /**
348
+ * 从锁定 MJML 片段提取可在画布内渲染的 HTML;无法识别时返回 null(回退源码视图)。
349
+ */
350
+ export declare function extractPreviewHtmlFromLockedMjml(lockedMjml: string): string | null;
351
+
234
352
  export declare function findBlockLocation(doc: EmailDoc, blockId: string): {
235
353
  section: Section;
236
354
  columnIndex: number;
@@ -258,6 +376,9 @@ export declare interface GalleryItem {
258
376
  title?: string;
259
377
  }
260
378
 
379
+ /** 本节导出后替换为 `{{dynamicVariantKey}}`,内容由宿主写入 dynamicVariant */
380
+ export declare function getSectionDynamicVariantKey(section: Section): string | undefined;
381
+
261
382
  export declare interface GlobalStyles {
262
383
  backgroundColor: string;
263
384
  contentBackgroundColor: string;
@@ -269,8 +390,16 @@ export declare interface GlobalStyles {
269
390
  linkColor: string;
270
391
  /** 无单位倍数(如 1.25)或固定值(如 24px) */
271
392
  lineHeight: string;
393
+ /**
394
+ * 未单独设置缩进的列表使用的全局左缩进(px)。
395
+ * 不设时等价于内置默认 24px。
396
+ */
397
+ listIndentDefaultPx?: number;
272
398
  }
273
399
 
400
+ /** HTML 块内容是否误含 MJML 标签(应只写 HTML,MJML 标签在 mj-raw 内不会被编译)。 */
401
+ export declare function htmlContainsMjmlTags(html: string): boolean;
402
+
274
403
  export declare interface ImageAssetsHandlers {
275
404
  /**
276
405
  * 用户点击右侧「上传」并选择本地文件。宿主完成上传后返回 **绝对 HTTPS URL**。
@@ -346,6 +475,17 @@ declare interface InlineEditableConfig<P extends object = Record<string, unknown
346
475
  placeholder?: string;
347
476
  }
348
477
 
478
+ export declare function isDynamicVariantSection(section: Section): boolean;
479
+
480
+ export declare function isMultiColumnLayout(layout: SectionLayout): layout is MultiColumnLayout;
481
+
482
+ /**
483
+ * 富文本是否「完全无内容」。
484
+ * 空格、&nbsp;、换行(<br>)均视为有效内容;仅空串或空标签壳子算空。
485
+ */
486
+ export declare function isRichHtmlEffectivelyEmpty(html: string): boolean;
487
+
488
+ /** @deprecated 使用 isMultiColumnLayout */
349
489
  export declare function isTwoColumnLayout(layout: SectionLayout): layout is TwoColumnLayout;
350
490
 
351
491
  declare type Listener = (detail?: DocChangedDetail) => void;
@@ -376,6 +516,10 @@ export declare class MailEditor {
376
516
  private sourceView;
377
517
  private toolbar;
378
518
  private body;
519
+ /** 浮层(变量列表、toast),避免挂到 grid 的 .sm-root 上被挤成窄条 */
520
+ private overlayLayer;
521
+ /** design 态右栏变量列表:未固定时点击外部收起 */
522
+ private variablePickerOutsideClick;
379
523
  private changeTimer;
380
524
  /** 显式品牌色时覆盖 CSS 变量;未设置则由 styles 按 light/dark 使用默认紫/靛 */
381
525
  private accentColorOverride;
@@ -383,16 +527,27 @@ export declare class MailEditor {
383
527
  private showLayoutBorders;
384
528
  /** 「重置内容」时恢复的文档快照(与当前画布内容无关) */
385
529
  private presetContentDoc;
530
+ /** 宿主通过 setVariables 注入的列表;setValue 恢复文档后仍会写回,避免被 doc JSON 里的空数组覆盖 */
531
+ private configuredVariables;
532
+ private importDocModal;
533
+ private topbarLayoutObserver;
534
+ private topbarLayoutRaf;
386
535
  private systemThemeMq;
387
536
  private readonly _onSystemThemeMqChange;
388
537
  private readonly _onFullscreenChange;
389
538
  /** design 态 Esc:块 → 父级 Section → 文档级面板(邮件设置或版式/全局样式,取决于 `ui.hideMailMeta`) */
390
539
  private _mailEscHandler;
391
540
  /**
392
- * 文档级撤销/重做(捕获阶段):焦点在右栏 input/CodeMirror 内时,冒泡到 root 的监听太晚,
393
- * 浏览器会先处理控件内撤销;仅在 store 仍有历史时拦截并走全局 undo/redo。
541
+ * 文档级撤销/重做(捕获阶段)。
542
+ * - 画布内联 contenteditable:交给浏览器撤销文字,不走 store。
543
+ * - 右栏 input / CodeMirror:控件自带撤销,不走 store。
544
+ * - 其余区域:在 store 有历史时拦截并 undo/redo 整份文档。
394
545
  */
395
546
  private _mailUndoRedoHandler;
547
+ /** 内联编辑、右栏表单等应使用控件/浏览器本地撤销,而非文档 history */
548
+ private _shouldDeferToLocalUndoRedo;
549
+ private _storeUndo;
550
+ private _storeRedo;
396
551
  constructor(opts: EditorOptions);
397
552
  setValue(doc: EmailDoc): void;
398
553
  getValue(): EmailDoc;
@@ -401,12 +556,28 @@ export declare class MailEditor {
401
556
  * 会提交内联编辑并清空选中。
402
557
  */
403
558
  clearCanvas(): void;
404
- /** 将画布恢复为构造时 `presetDoc`(或 `initialDoc`)对应的预置内容。 */
559
+ /**
560
+ * 将画布恢复为 `presetDoc` 快照(未传时等同构造时的预置合并结果)。
561
+ * 记入撤销栈(⌘Z 可恢复);与 `setValue` 不同,不会清空 history。
562
+ */
405
563
  resetToPreset(): void;
406
564
  /** 更新「重置内容」的目标(例如宿主异步加载默认模板后)。 */
407
565
  setPresetDoc(partial: Partial<EmailDoc>): void;
408
566
  setVariables(vars: Variable[]): void;
409
567
  getVariables(): Variable[];
568
+ private _applyConfiguredVariables;
569
+ /**
570
+ * 插入变量 key(`{{key}}`)。
571
+ * @returns 是否成功插入
572
+ */
573
+ insertVariableKey(v: Variable): boolean;
574
+ /**
575
+ * 插入链接 / 图片变量对应元素(`<a>` / image 块等)。
576
+ * 非 link/image 时退化为 insertVariableKey。
577
+ */
578
+ insertVariableElement(v: Variable): boolean;
579
+ /** 同 {@link insertVariableKey} */
580
+ insertVariable(v: Variable): boolean;
410
581
  /** 当前界面主题(`system` 不解析为 light/dark)。 */
411
582
  getTheme(): EditorTheme;
412
583
  /** 切换界面主题;写入 `data-sm-theme`,顶栏选择器同步。 */
@@ -421,6 +592,17 @@ export declare class MailEditor {
421
592
  export(opts?: {
422
593
  withSampleVariables?: boolean;
423
594
  }): RenderResult;
595
+ /**
596
+ * 将当前画布设计稿(EmailDoc JSON 信封)写入剪贴板,供另一实例「导入设计稿」使用。
597
+ */
598
+ copyDocDesign(): Promise<boolean>;
599
+ /** 打开导入设计稿对话框(从剪贴板或手动粘贴 JSON,覆盖当前画布)。 */
600
+ openImportDocDesign(): void;
601
+ /**
602
+ * 程序化导入设计稿(与对话框「应用」相同逻辑)。
603
+ * @returns 是否成功解析并应用
604
+ */
605
+ importDocDesignFromJson(raw: string): boolean;
424
606
  registerBlock<P extends object>(def: BlockDefinition<P>): void;
425
607
  setSelection(sel: Selection_2 | null): void;
426
608
  destroy(): void;
@@ -436,15 +618,36 @@ export declare class MailEditor {
436
618
  private _blurRightPanelIfFocused;
437
619
  /** 提交内联编辑并清空选中,右栏回到文档级面板 */
438
620
  private _focusMailSettings;
439
- private _confirmClearCanvas;
440
- private _confirmResetContent;
441
621
  private _setMode;
442
622
  private _bindKeyboard;
443
623
  private _onChange;
444
- private _showVariablePopover;
445
- private _insertVariable;
624
+ private _unbindVariablePickerOutsideClick;
625
+ private _bindVariablePickerOutsideClick;
626
+ private _closeVariablePicker;
627
+ private _toggleVariablePanel;
628
+ private _dismissVariablePopover;
629
+ /** 源码模式无右栏时仍用浮层选择变量 */
630
+ private _toggleVariablePopover;
631
+ private _showToast;
632
+ private _insertImageVariable;
633
+ /**
634
+ * 按当前选中插入块:block → 其后;section → 该 section 首列末尾;无选中 → 文档最后一节首列末尾。
635
+ */
636
+ private _insertBlockRelativeToSelection;
637
+ /** 链接变量:写入图片/按钮等块的 `href`(MJML 可点击),而非 alt 或右栏纯文本。 */
638
+ private _tryApplyLinkVariableToken;
639
+ private _schemaUrlPropKey;
640
+ private _setBlockUrlProp;
641
+ /** 在焦点处插入文本或 HTML;失败时返回 false */
642
+ private _insertAtFocus;
643
+ private _ensureImportDocModal;
644
+ private _applyImportedDoc;
446
645
  private _showExport;
447
646
  private _showPreview;
647
+ private _topbarLayoutContext;
648
+ private _syncTopbarLayout;
649
+ private _bindTopbarLayoutWatch;
650
+ private _unbindTopbarLayoutWatch;
448
651
  private _bindFullscreen;
449
652
  private _unbindFullscreen;
450
653
  private _toggleFullscreen;
@@ -475,9 +678,20 @@ export declare function mjRawCellTypographyFromStyles(s: GlobalStyles): MjRawCel
475
678
  */
476
679
  export declare function mjRawEmailTableWrap(paddingCss: string, cellInnerHtml: string, typo: MjRawCellTypography): string;
477
680
 
681
+ /** 多列 Section 的列布局(左栏拖入默认 `1-1`,右栏可切换列数/比例) */
682
+ export declare const MULTI_COLUMN_LAYOUTS: readonly ["1-1", "1-2", "2-1", "1-1-1"];
683
+
684
+ export declare type MultiColumnLayout = (typeof MULTI_COLUMN_LAYOUTS)[number];
685
+
478
686
  /** 将任意历史写法映射到最近一档(normal→400,bold→700 等) */
479
687
  export declare function normalizeFontWeightStep(raw: string | undefined | null): FontWeightStep;
480
688
 
689
+ /** 提交前:完全无内容时统一为 '' */
690
+ export declare function normalizeRichHtmlForCommit(html: string): string;
691
+
692
+ /** 规范化 Variable(补 kind、sample) */
693
+ export declare function normalizeVariable(v: Variable): Variable;
694
+
481
695
  /**
482
696
  * 打开内置图库弹层:搜索、分页、选图插入;可选「链接添加」「上传到图库」「删除」由 adapter 决定。
483
697
  */
@@ -491,6 +705,17 @@ export declare interface OpenImageGalleryModalOptions {
491
705
  onClose?: () => void;
492
706
  }
493
707
 
708
+ /** 左栏组合拖入:可附带新建 Section 的 attrs(如 meta 扩展袋) */
709
+ export declare interface PaletteDropResult {
710
+ blocks: Block[];
711
+ sectionAttrs?: Partial<SectionAttrs>;
712
+ }
713
+
714
+ /**
715
+ * 解析剪贴板或手动粘贴的 JSON。支持信封格式与裸 `EmailDoc`(version: '1')。
716
+ */
717
+ export declare function parseDocClipboard(raw: string): EmailDoc | null;
718
+
494
719
  /** 右栏输入解析存库:纯整数可存 number,其它存 string */
495
720
  export declare function parseMetaWidthFromUserInput(input: string): number | string;
496
721
 
@@ -508,6 +733,9 @@ export declare function pruneEmptySections(draft: EmailDoc): void;
508
733
  */
509
734
  export declare function pruneSectionIfEmpty(draft: EmailDoc, sectionId: string): void;
510
735
 
736
+ /** 导入前重生成 section / column / block id,避免与当前文档冲突。 */
737
+ export declare function regenerateDocIds(doc: EmailDoc): EmailDoc;
738
+
511
739
  /**
512
740
  * 组件注册表。所有内置/自定义组件都通过 `defineBlock` 注册进来;
513
741
  * 渲染、属性面板、左栏拖拽源、组件级代码模式四方都从这里读取。
@@ -538,7 +766,8 @@ declare interface RenderOptions {
538
766
  /** 渲染引擎,目前仅 'mjml';'table' 预留作将来直输 table HTML。 */
539
767
  engine?: RenderEngine;
540
768
  /**
541
- * 是否把 {{var}} 替换为示例值。设计态画布预览传 true,导出原始 HTML 传 false。
769
+ * 是否把 {{var}} 替换为变量的 sample 值。
770
+ * 设计态预览默认不替换,保持占位符;仅当变量显式配置了 sample 且调用方传 true 时才替换。
542
771
  */
543
772
  withSampleVariables?: boolean;
544
773
  }
@@ -551,12 +780,24 @@ declare interface RenderResult {
551
780
  }[];
552
781
  }
553
782
 
783
+ /** 仅编译某一 Section,返回邮件 body 内 HTML 片段 */
784
+ export declare function renderSectionBodyHtml(doc: EmailDoc, sectionId: string, registry: Registry): string;
785
+
554
786
  /**
555
787
  * 用 sample 值替换 `{{var.name}}` 占位符。仅做最简单的字符串替换,
556
788
  * 不做任何沙箱式表达式求值(邮件场景里下游模板引擎会再处理一次)。
557
789
  */
558
790
  export declare function replaceVariables(text: string, vars: Variable[]): string;
559
791
 
792
+ /** 锁定块在画布上的渲染结果:preview 为 WYSIWYG HTML,source 为无法解析时的源码回退。 */
793
+ export declare function resolveLockedMjmlCanvasContent(lockedMjml: string, padding: BlockPaddingBox): {
794
+ mode: 'preview';
795
+ html: string;
796
+ } | {
797
+ mode: 'source';
798
+ source: string;
799
+ };
800
+
560
801
  export declare interface Section {
561
802
  id: string;
562
803
  type: 'section';
@@ -585,10 +826,21 @@ export declare interface SectionAttrs {
585
826
  * 本节内容区最大宽度(窄于邮件 `meta.width` 时居中)。支持 `480`、`480px`、`90%`;留空则与邮件同宽。
586
827
  */
587
828
  width?: string | number;
829
+ /**
830
+ * 动态变量组合:设置后本节编译结果在导出时替换为 `{{dynamicVariantKey}}`;
831
+ * 具体存贮与业务含义由宿主处理(如券包 couponGroup)。
832
+ */
833
+ dynamicVariantKey?: string;
834
+ /**
835
+ * 宿主扩展元数据;编辑器不解释、不参与 MJML 编译,仅随 EmailDoc 序列化。
836
+ */
837
+ meta?: Record<string, unknown>;
588
838
  }
589
839
 
590
840
  export declare type SectionLayout = '1' | '1-1' | '1-2' | '2-1' | '1-1-1';
591
841
 
842
+ export declare function sectionMjClassName(sectionId: string): string;
843
+
592
844
  export declare function sectionWidthInputString(raw: unknown): string;
593
845
 
594
846
  declare type Selection_2 = {
@@ -604,7 +856,9 @@ export { Selection_2 as Selection }
604
856
 
605
857
  declare type SelectionListener = () => void;
606
858
 
607
- /** 仅更新 layout 字段;两列间切换时列数不变,列内块按列下标保留 */
859
+ export declare function serializeDocClipboard(doc: EmailDoc): string;
860
+
861
+ /** 更新 layout;列数变化时增删列,被删列内块并入最后一列 */
608
862
  export declare function setSectionLayout(section: Section, layout: SectionLayout): void;
609
863
 
610
864
  /**
@@ -643,7 +897,10 @@ export declare class Store {
643
897
  private _notify;
644
898
  }
645
899
 
646
- /** 两列 Section 的列宽比例(左栏拖入默认 `1-1`,右栏可改为 `1-2` / `2-1`) */
900
+ /** 从 Mustache token 提取 key,如 `{{username}}` → `username` */
901
+ export declare function tokenToVariableKey(token: string): string;
902
+
903
+ /** @deprecated 使用 MULTI_COLUMN_LAYOUTS */
647
904
  export declare const TWO_COLUMN_LAYOUTS: readonly ["1-1", "1-2", "2-1"];
648
905
 
649
906
  export declare type TwoColumnLayout = (typeof TWO_COLUMN_LAYOUTS)[number];
@@ -654,6 +911,12 @@ export declare interface Variable {
654
911
  label: string;
655
912
  /** 预览数据,仅设计态预览使用 */
656
913
  sample?: string;
914
+ /** 插入语义:纯文本占位 / 链接片段 / 图片块 */
915
+ kind?: 'text' | 'link' | 'image';
657
916
  }
658
917
 
918
+ export declare type VariableKind = 'text' | 'link' | 'image';
919
+
920
+ export declare function variablePlaceholder(key: string): string;
921
+
659
922
  export { }