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,16 +1,16 @@
|
|
|
1
|
-
import { EditorView, basicSetup } from 'codemirror';
|
|
2
1
|
import { html as cmHtml } from '@codemirror/lang-html';
|
|
3
2
|
import { EditorState } from '@codemirror/state';
|
|
3
|
+
import { EditorView, basicSetup } from 'codemirror';
|
|
4
|
+
import type { SimpleMailT } from '../i18n';
|
|
4
5
|
import type { Registry } from '../registry/registry';
|
|
5
|
-
import {
|
|
6
|
-
import { invalidHrefMessage } from '../utils/link';
|
|
6
|
+
import { defaultSocialIconBackground } from '../socialDefaults';
|
|
7
7
|
import {
|
|
8
|
+
type DocChangedDetail,
|
|
9
|
+
type Store,
|
|
8
10
|
findBlockLocation,
|
|
9
11
|
findSection,
|
|
10
12
|
isMultiColumnLayout,
|
|
11
13
|
setSectionLayout,
|
|
12
|
-
type Store,
|
|
13
|
-
type DocChangedDetail,
|
|
14
14
|
} from '../store/store';
|
|
15
15
|
import type {
|
|
16
16
|
Block,
|
|
@@ -21,27 +21,36 @@ import type {
|
|
|
21
21
|
SectionLayout,
|
|
22
22
|
Variable,
|
|
23
23
|
} from '../types';
|
|
24
|
-
import { getSectionDynamicVariantKey } from '../utils/dynamicVariantSection';
|
|
25
|
-
import { defaultSocialIconBackground } from '../socialDefaults';
|
|
26
|
-
import { bindColorPickerInput } from './ColorPickerPopover';
|
|
27
24
|
import { normalizeAccentHex } from '../utils/accentColor';
|
|
25
|
+
import {
|
|
26
|
+
metaWidthInputString,
|
|
27
|
+
parseMetaWidthFromUserInput,
|
|
28
|
+
parseSectionWidthFromUserInput,
|
|
29
|
+
sectionWidthInputString,
|
|
30
|
+
} from '../utils/contentWidth';
|
|
28
31
|
import { clear, h } from '../utils/dom';
|
|
29
|
-
import {
|
|
32
|
+
import { getSectionDynamicVariantKey } from '../utils/dynamicVariantSection';
|
|
30
33
|
import {
|
|
31
34
|
LIST_INDENT_PRESETS_PX,
|
|
32
35
|
normalizeGlobalListIndentStorage,
|
|
33
36
|
resolveGlobalListIndentPx,
|
|
34
37
|
} from '../utils/emailListStyles';
|
|
35
38
|
import { FONT_WEIGHT_STEP_OPTIONS, normalizeFontWeightStep } from '../utils/fontWeightSteps';
|
|
36
|
-
import {
|
|
39
|
+
import { invalidHrefMessage } from '../utils/link';
|
|
40
|
+
import { htmlContainsMjmlTags } from '../utils/lockedMjml';
|
|
41
|
+
import { bindColorPickerInput } from './ColorPickerPopover';
|
|
37
42
|
import { FocusBreadcrumb } from './FocusBreadcrumb';
|
|
38
43
|
import { openImageGalleryModal } from './ImageGalleryModal';
|
|
39
44
|
import {
|
|
45
|
+
type VariablePickerHandlers,
|
|
40
46
|
buildVariablePickerBody,
|
|
41
47
|
buildVariablePickerHead,
|
|
42
|
-
type VariablePickerHandlers,
|
|
43
48
|
} from './VariablePickerPanel';
|
|
44
|
-
import
|
|
49
|
+
import {
|
|
50
|
+
type ImageAssetsHandlers,
|
|
51
|
+
type ImageFieldContext,
|
|
52
|
+
resolveAssetPermission,
|
|
53
|
+
} from './imageAssets';
|
|
45
54
|
|
|
46
55
|
/** 与社交组 block 中每行元素结构一致(core 不依赖 blocks) */
|
|
47
56
|
type SocialLinkRow = {
|
|
@@ -66,7 +75,7 @@ function layoutWidthNumeric(raw: string, mode: 'px' | '%'): number {
|
|
|
66
75
|
const t = (raw ?? '').trim();
|
|
67
76
|
if (!t) return mode === '%' ? 100 : 280;
|
|
68
77
|
const m = t.match(/^(\d+(\.\d+)?)/);
|
|
69
|
-
const n = m ? parseFloat(m[1]) : mode === '%' ? 100 : 280;
|
|
78
|
+
const n = m ? Number.parseFloat(m[1]) : mode === '%' ? 100 : 280;
|
|
70
79
|
if (mode === '%') return Math.min(100, Math.max(1, Math.round(n)));
|
|
71
80
|
return Math.min(1200, Math.max(1, Math.round(n)));
|
|
72
81
|
}
|
|
@@ -231,10 +240,7 @@ export class RightPanel {
|
|
|
231
240
|
let selEnd = 0;
|
|
232
241
|
if (ae && this.el.contains(ae)) {
|
|
233
242
|
token = ae.getAttribute('data-sm-focus');
|
|
234
|
-
if (
|
|
235
|
-
token &&
|
|
236
|
-
(ae instanceof HTMLInputElement || ae instanceof HTMLTextAreaElement)
|
|
237
|
-
) {
|
|
243
|
+
if (token && (ae instanceof HTMLInputElement || ae instanceof HTMLTextAreaElement)) {
|
|
238
244
|
selStart = ae.selectionStart ?? 0;
|
|
239
245
|
selEnd = ae.selectionEnd ?? 0;
|
|
240
246
|
}
|
|
@@ -311,7 +317,7 @@ export class RightPanel {
|
|
|
311
317
|
onclick: () => {
|
|
312
318
|
this.opts.store.update((d) => {
|
|
313
319
|
const l = findBlockLocation(d, loc.block.id);
|
|
314
|
-
if (l)
|
|
320
|
+
if (l) l.block.lockedMjml = undefined;
|
|
315
321
|
});
|
|
316
322
|
},
|
|
317
323
|
},
|
|
@@ -357,23 +363,31 @@ export class RightPanel {
|
|
|
357
363
|
if (!hideMeta) {
|
|
358
364
|
rows.push(
|
|
359
365
|
h('div', { class: 'sm-panel__title' }, [this.opts.t('rightPanel.doc.title')]),
|
|
360
|
-
this._textField(
|
|
361
|
-
this.opts.
|
|
362
|
-
|
|
363
|
-
|
|
366
|
+
this._textField(
|
|
367
|
+
this.opts.t('rightPanel.doc.subject'),
|
|
368
|
+
doc.meta.subject,
|
|
369
|
+
(v) =>
|
|
370
|
+
this.opts.store.update((d) => {
|
|
371
|
+
d.meta.subject = v;
|
|
372
|
+
}),
|
|
364
373
|
'',
|
|
365
374
|
'doc:meta.subject',
|
|
366
375
|
),
|
|
367
|
-
this._textField(
|
|
368
|
-
this.opts.
|
|
369
|
-
|
|
370
|
-
|
|
376
|
+
this._textField(
|
|
377
|
+
this.opts.t('rightPanel.doc.preheader'),
|
|
378
|
+
doc.meta.preheader ?? '',
|
|
379
|
+
(v) =>
|
|
380
|
+
this.opts.store.update((d) => {
|
|
381
|
+
d.meta.preheader = v;
|
|
382
|
+
}),
|
|
371
383
|
'',
|
|
372
384
|
'doc:meta.preheader',
|
|
373
385
|
),
|
|
374
386
|
);
|
|
375
387
|
} else {
|
|
376
|
-
rows.push(
|
|
388
|
+
rows.push(
|
|
389
|
+
h('div', { class: 'sm-panel__title' }, [this.opts.t('rightPanel.doc.layoutTitle')]),
|
|
390
|
+
);
|
|
377
391
|
}
|
|
378
392
|
rows.push(
|
|
379
393
|
this._textField(
|
|
@@ -509,7 +523,7 @@ export class RightPanel {
|
|
|
509
523
|
this.opts.store.update((d) => {
|
|
510
524
|
const s = findSection(d, section.id);
|
|
511
525
|
if (s) s.attrs.backgroundColor = v || undefined;
|
|
512
|
-
|
|
526
|
+
}),
|
|
513
527
|
`section:${section.id}:attrs.bg`,
|
|
514
528
|
this.opts.t('rightPanel.section.transparentPlaceholder'),
|
|
515
529
|
),
|
|
@@ -520,7 +534,7 @@ export class RightPanel {
|
|
|
520
534
|
this.opts.store.update((d) => {
|
|
521
535
|
const s = findSection(d, section.id);
|
|
522
536
|
if (s) s.attrs.width = parseSectionWidthFromUserInput(v);
|
|
523
|
-
|
|
537
|
+
}),
|
|
524
538
|
`section:${section.id}:attrs.width`,
|
|
525
539
|
this.opts.t('rightPanel.section.widthHelp'),
|
|
526
540
|
),
|
|
@@ -612,8 +626,7 @@ export class RightPanel {
|
|
|
612
626
|
const def = this.opts.registry.get(block.type);
|
|
613
627
|
const defaults = def?.defaultProps as Record<string, unknown> | undefined;
|
|
614
628
|
const raw = (block.props as Record<string, unknown>)[field.key];
|
|
615
|
-
const value: unknown =
|
|
616
|
-
raw !== undefined && raw !== null ? raw : defaults?.[field.key];
|
|
629
|
+
const value: unknown = raw !== undefined && raw !== null ? raw : defaults?.[field.key];
|
|
617
630
|
const onChange = (v: any) => this._updateBlock(blockId, field.key, v);
|
|
618
631
|
|
|
619
632
|
switch (field.type) {
|
|
@@ -625,10 +638,7 @@ export class RightPanel {
|
|
|
625
638
|
onChange,
|
|
626
639
|
field.help,
|
|
627
640
|
fp,
|
|
628
|
-
(v) =>
|
|
629
|
-
htmlContainsMjmlTags(v)
|
|
630
|
-
? this.opts.t('rightPanel.block.mjmlWarning')
|
|
631
|
-
: null,
|
|
641
|
+
(v) => (htmlContainsMjmlTags(v) ? this.opts.t('rightPanel.block.mjmlWarning') : null),
|
|
632
642
|
);
|
|
633
643
|
}
|
|
634
644
|
return this._textareaField(field.label, String(value ?? ''), onChange, field.help, fp);
|
|
@@ -644,8 +654,7 @@ export class RightPanel {
|
|
|
644
654
|
);
|
|
645
655
|
case 'color': {
|
|
646
656
|
const rawColor = (block.props as Record<string, unknown>)[field.key];
|
|
647
|
-
const stored =
|
|
648
|
-
rawColor !== undefined && rawColor !== null ? String(rawColor).trim() : '';
|
|
657
|
+
const stored = rawColor !== undefined && rawColor !== null ? String(rawColor).trim() : '';
|
|
649
658
|
return this._colorField(
|
|
650
659
|
field.label,
|
|
651
660
|
stored,
|
|
@@ -656,14 +665,9 @@ export class RightPanel {
|
|
|
656
665
|
}
|
|
657
666
|
case 'select': {
|
|
658
667
|
const isFw = field.key === 'fontWeight' || field.key === 'labelFontWeight';
|
|
659
|
-
const opts = field.options?.length
|
|
660
|
-
? field.options
|
|
661
|
-
: isFw
|
|
662
|
-
? FONT_WEIGHT_STEP_OPTIONS
|
|
663
|
-
: [];
|
|
668
|
+
const opts = field.options?.length ? field.options : isFw ? FONT_WEIGHT_STEP_OPTIONS : [];
|
|
664
669
|
if (isFw && field.inheritGlobal) {
|
|
665
|
-
const stored =
|
|
666
|
-
raw !== undefined && raw !== null ? String(raw).trim() : '';
|
|
670
|
+
const stored = raw !== undefined && raw !== null ? String(raw).trim() : '';
|
|
667
671
|
return this._fontWeightInheritField(
|
|
668
672
|
field.label,
|
|
669
673
|
stored,
|
|
@@ -707,8 +711,7 @@ export class RightPanel {
|
|
|
707
711
|
);
|
|
708
712
|
}
|
|
709
713
|
if (field.key === 'fontSize' && field.inheritGlobal) {
|
|
710
|
-
const rawFs =
|
|
711
|
-
raw !== undefined && raw !== null ? String(raw).trim() : '';
|
|
714
|
+
const rawFs = raw !== undefined && raw !== null ? String(raw).trim() : '';
|
|
712
715
|
return this._fontSizeSliderField(
|
|
713
716
|
field.label,
|
|
714
717
|
rawFs,
|
|
@@ -747,14 +750,20 @@ export class RightPanel {
|
|
|
747
750
|
Number(dp?.paddingBottom ?? 0),
|
|
748
751
|
Number(dp?.paddingLeft ?? 0),
|
|
749
752
|
];
|
|
750
|
-
return this._spacingField(
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
753
|
+
return this._spacingField(
|
|
754
|
+
field.label,
|
|
755
|
+
quad,
|
|
756
|
+
(vals) => {
|
|
757
|
+
this.opts.store.update((d) => {
|
|
758
|
+
const loc = findBlockLocation(d, blockId);
|
|
759
|
+
if (!loc) return;
|
|
760
|
+
const pr = loc.block.props as Record<string, number>;
|
|
761
|
+
[pr.paddingTop, pr.paddingRight, pr.paddingBottom, pr.paddingLeft] = vals;
|
|
762
|
+
});
|
|
763
|
+
},
|
|
764
|
+
`${fp}:pad`,
|
|
765
|
+
resetQuad,
|
|
766
|
+
);
|
|
758
767
|
}
|
|
759
768
|
case 'socialLinkList': {
|
|
760
769
|
const rows = Array.isArray(value)
|
|
@@ -811,9 +820,7 @@ export class RightPanel {
|
|
|
811
820
|
};
|
|
812
821
|
|
|
813
822
|
const syncItem = (index: number, patch: Partial<SocialLinkRow>) => {
|
|
814
|
-
patchElements((rows) =>
|
|
815
|
-
rows.map((x, i) => (i === index ? { ...x, ...patch } : x)),
|
|
816
|
-
);
|
|
823
|
+
patchElements((rows) => rows.map((x, i) => (i === index ? { ...x, ...patch } : x)));
|
|
817
824
|
};
|
|
818
825
|
|
|
819
826
|
const removeAt = (index: number) => {
|
|
@@ -827,8 +834,7 @@ export class RightPanel {
|
|
|
827
834
|
const sel = h('select', {
|
|
828
835
|
class: 'sm-select sm-social-link-list__network',
|
|
829
836
|
...(focusPrefix ? { 'data-sm-focus': `${focusPrefix}:row:${i}:net` } : {}),
|
|
830
|
-
onchange: (e: Event) =>
|
|
831
|
-
syncItem(i, { network: (e.target as HTMLSelectElement).value }),
|
|
837
|
+
onchange: (e: Event) => syncItem(i, { network: (e.target as HTMLSelectElement).value }),
|
|
832
838
|
});
|
|
833
839
|
for (const o of networkOptions) {
|
|
834
840
|
const opt = document.createElement('option');
|
|
@@ -865,9 +871,9 @@ export class RightPanel {
|
|
|
865
871
|
syncHrefWarn(rowData.href);
|
|
866
872
|
const rm = h(
|
|
867
873
|
'button',
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
874
|
+
{
|
|
875
|
+
class: 'sm-social-link-list__remove',
|
|
876
|
+
type: 'button',
|
|
871
877
|
title: this.opts.t('common.delete'),
|
|
872
878
|
onclick: () => removeAt(i),
|
|
873
879
|
},
|
|
@@ -879,8 +885,7 @@ export class RightPanel {
|
|
|
879
885
|
const extras = h('div', { class: 'sm-social-link-list__row-extras' });
|
|
880
886
|
const bgResolved = defaultSocialIconBackground(rowData.network);
|
|
881
887
|
const bgStored = rowData.backgroundColor?.trim();
|
|
882
|
-
const bgDisplay =
|
|
883
|
-
bgStored && bgStored.startsWith('#') ? bgStored : bgResolved;
|
|
888
|
+
const bgDisplay = bgStored?.startsWith('#') ? bgStored : bgResolved;
|
|
884
889
|
extras.append(
|
|
885
890
|
h('input', {
|
|
886
891
|
class: 'sm-input sm-social-link-list__label',
|
|
@@ -988,8 +993,7 @@ export class RightPanel {
|
|
|
988
993
|
const assets = this.opts.imageAssets;
|
|
989
994
|
const showUploadBtn = !!assets?.uploadImage && assets.showUpload !== false;
|
|
990
995
|
const showGalleryBtn =
|
|
991
|
-
assets?.showGallery === true &&
|
|
992
|
-
(!!assets.imageGallery || !!assets.pickImageFromGallery);
|
|
996
|
+
assets?.showGallery === true && (!!assets.imageGallery || !!assets.pickImageFromGallery);
|
|
993
997
|
const hasAssetCallbacks = !!(
|
|
994
998
|
assets?.uploadImage ||
|
|
995
999
|
assets?.pickImageFromGallery ||
|
|
@@ -1020,7 +1024,7 @@ export class RightPanel {
|
|
|
1020
1024
|
urlInp.value = t;
|
|
1021
1025
|
};
|
|
1022
1026
|
|
|
1023
|
-
const runAsync = (p: Promise<
|
|
1027
|
+
const runAsync = (p: Promise<unknown>, labelErr: string) => {
|
|
1024
1028
|
p.catch((e) => {
|
|
1025
1029
|
console.error(`[simple-mail] ${labelErr}`, e);
|
|
1026
1030
|
});
|
|
@@ -1060,9 +1064,7 @@ export class RightPanel {
|
|
|
1060
1064
|
? this.opts.t('rightPanel.image.uploadTitle')
|
|
1061
1065
|
: uploadPerm.tip,
|
|
1062
1066
|
disabled: !uploadPerm.allowed,
|
|
1063
|
-
...(uploadPerm.allowed
|
|
1064
|
-
? { onclick: () => fileInp.click() }
|
|
1065
|
-
: {}),
|
|
1067
|
+
...(uploadPerm.allowed ? { onclick: () => fileInp.click() } : {}),
|
|
1066
1068
|
},
|
|
1067
1069
|
[this.opts.t('rightPanel.image.upload')],
|
|
1068
1070
|
),
|
|
@@ -1155,16 +1157,12 @@ export class RightPanel {
|
|
|
1155
1157
|
warnEl.style.display = '';
|
|
1156
1158
|
}
|
|
1157
1159
|
}
|
|
1158
|
-
return h(
|
|
1159
|
-
'
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
warnEl,
|
|
1165
|
-
help ? h('div', { class: 'sm-field__help' }, [help]) : null,
|
|
1166
|
-
],
|
|
1167
|
-
);
|
|
1160
|
+
return h('div', { class: 'sm-field' }, [
|
|
1161
|
+
h('label', { class: 'sm-field__label' }, [label]),
|
|
1162
|
+
ta,
|
|
1163
|
+
warnEl,
|
|
1164
|
+
help ? h('div', { class: 'sm-field__help' }, [help]) : null,
|
|
1165
|
+
]);
|
|
1168
1166
|
}
|
|
1169
1167
|
private _numberField(
|
|
1170
1168
|
label: string,
|
|
@@ -1178,10 +1176,11 @@ export class RightPanel {
|
|
|
1178
1176
|
): HTMLElement {
|
|
1179
1177
|
const field = this._buildNumberField(label, value, min, max, onChange, step, focusToken);
|
|
1180
1178
|
if (!help) return field;
|
|
1181
|
-
return h(
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1179
|
+
return h(
|
|
1180
|
+
'div',
|
|
1181
|
+
{ class: 'sm-field', style: 'flex-direction:column;align-items:flex-start;gap:6px;' },
|
|
1182
|
+
[field, h('div', { class: 'sm-field__help' }, [help])],
|
|
1183
|
+
);
|
|
1185
1184
|
}
|
|
1186
1185
|
|
|
1187
1186
|
private _buildNumberField(
|
|
@@ -1464,7 +1463,7 @@ export class RightPanel {
|
|
|
1464
1463
|
(v) =>
|
|
1465
1464
|
this.opts.store.update((d) => {
|
|
1466
1465
|
const stored = normalizeGlobalListIndentStorage(Number(v));
|
|
1467
|
-
if (stored === undefined)
|
|
1466
|
+
if (stored === undefined) d.styles.listIndentDefaultPx = undefined;
|
|
1468
1467
|
else d.styles.listIndentDefaultPx = stored;
|
|
1469
1468
|
}),
|
|
1470
1469
|
'doc:styles.listIndentDefaultPx',
|
|
@@ -1517,23 +1516,28 @@ export class RightPanel {
|
|
|
1517
1516
|
help?: string,
|
|
1518
1517
|
focusToken?: string,
|
|
1519
1518
|
): HTMLElement {
|
|
1520
|
-
const row = h(
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1519
|
+
const row = h(
|
|
1520
|
+
'label',
|
|
1521
|
+
{
|
|
1522
|
+
class: 'sm-field',
|
|
1523
|
+
style: 'flex-direction:row;align-items:center;gap:8px;',
|
|
1524
|
+
},
|
|
1525
|
+
[
|
|
1526
|
+
h('input', {
|
|
1527
|
+
type: 'checkbox',
|
|
1528
|
+
...(focusToken ? { 'data-sm-focus': focusToken } : {}),
|
|
1529
|
+
onchange: (e: Event) => onChange((e.target as HTMLInputElement).checked),
|
|
1530
|
+
...(value ? { checked: true } : {}),
|
|
1531
|
+
} as any),
|
|
1532
|
+
h('span', { class: 'sm-field__label' }, [label]),
|
|
1533
|
+
],
|
|
1534
|
+
);
|
|
1532
1535
|
if (!help) return row;
|
|
1533
|
-
return h(
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1536
|
+
return h(
|
|
1537
|
+
'div',
|
|
1538
|
+
{ class: 'sm-field', style: 'flex-direction:column;align-items:flex-start;gap:6px;' },
|
|
1539
|
+
[row, h('div', { class: 'sm-field__help' }, [help])],
|
|
1540
|
+
);
|
|
1537
1541
|
}
|
|
1538
1542
|
private _spacingField(
|
|
1539
1543
|
label: string,
|
|
@@ -1662,7 +1666,9 @@ export class RightPanel {
|
|
|
1662
1666
|
|
|
1663
1667
|
return h('div', { class: 'sm-field' }, [
|
|
1664
1668
|
h('div', { class: 'sm-spacing-field__label-row' }, [
|
|
1665
|
-
h('label', { class: 'sm-field__label' }, [
|
|
1669
|
+
h('label', { class: 'sm-field__label' }, [
|
|
1670
|
+
`${label}${this.opts.t('rightPanel.spacing.suffix')}`,
|
|
1671
|
+
]),
|
|
1666
1672
|
resetQuad
|
|
1667
1673
|
? h(
|
|
1668
1674
|
'button',
|
|
@@ -1707,10 +1713,14 @@ export class RightPanel {
|
|
|
1707
1713
|
...(focusToken ? { 'data-sm-focus': focusToken } : {}),
|
|
1708
1714
|
...(inheritOn ? { checked: true } : {}),
|
|
1709
1715
|
}) as HTMLInputElement;
|
|
1710
|
-
const valueEl = h(
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1716
|
+
const valueEl = h(
|
|
1717
|
+
'span',
|
|
1718
|
+
{
|
|
1719
|
+
class: 'sm-inherit-switch__value',
|
|
1720
|
+
hidden: !inheritOn,
|
|
1721
|
+
},
|
|
1722
|
+
[globalDisplay],
|
|
1723
|
+
);
|
|
1714
1724
|
const el = h('div', { class: 'sm-inherit-switch' }, [
|
|
1715
1725
|
h('label', { class: 'sm-inherit-switch__label' }, [
|
|
1716
1726
|
input,
|
|
@@ -1737,6 +1747,7 @@ export class RightPanel {
|
|
|
1737
1747
|
help?: string,
|
|
1738
1748
|
): HTMLElement {
|
|
1739
1749
|
let inheriting = !stored;
|
|
1750
|
+
let storedValue = stored;
|
|
1740
1751
|
const group = h('div', {
|
|
1741
1752
|
class: 'sm-segmented sm-segmented--fill',
|
|
1742
1753
|
role: 'group',
|
|
@@ -1745,7 +1756,7 @@ export class RightPanel {
|
|
|
1745
1756
|
|
|
1746
1757
|
const syncGroup = () => {
|
|
1747
1758
|
group.classList.toggle('sm-segmented--disabled', inheriting);
|
|
1748
|
-
const display = inheriting ? '' : normalizeFontWeightStep(
|
|
1759
|
+
const display = inheriting ? '' : normalizeFontWeightStep(storedValue || globalWeight);
|
|
1749
1760
|
clear(group);
|
|
1750
1761
|
for (const o of options) {
|
|
1751
1762
|
const active = !inheriting && o.value === display;
|
|
@@ -1760,7 +1771,7 @@ export class RightPanel {
|
|
|
1760
1771
|
...(focusToken ? { 'data-sm-focus': `${focusToken}:${o.value}` } : {}),
|
|
1761
1772
|
onclick: () => {
|
|
1762
1773
|
inheriting = false;
|
|
1763
|
-
|
|
1774
|
+
storedValue = o.value;
|
|
1764
1775
|
inheritSwitch.setInherit(false);
|
|
1765
1776
|
syncGroup();
|
|
1766
1777
|
onChange(o.value);
|
|
@@ -1778,14 +1789,14 @@ export class RightPanel {
|
|
|
1778
1789
|
(on) => {
|
|
1779
1790
|
if (on) {
|
|
1780
1791
|
inheriting = true;
|
|
1781
|
-
|
|
1792
|
+
storedValue = '';
|
|
1782
1793
|
inheritSwitch.setInherit(true, globalWeight);
|
|
1783
1794
|
syncGroup();
|
|
1784
1795
|
onChange('');
|
|
1785
1796
|
return;
|
|
1786
1797
|
}
|
|
1787
1798
|
inheriting = false;
|
|
1788
|
-
|
|
1799
|
+
storedValue = globalWeight;
|
|
1789
1800
|
inheritSwitch.setInherit(false);
|
|
1790
1801
|
syncGroup();
|
|
1791
1802
|
onChange(globalWeight);
|
|
@@ -1814,6 +1825,7 @@ export class RightPanel {
|
|
|
1814
1825
|
): HTMLElement {
|
|
1815
1826
|
const globalPx = this._snapFontSizeToPx(globalFontSize);
|
|
1816
1827
|
let inheriting = !stored.trim();
|
|
1828
|
+
let storedValue = stored;
|
|
1817
1829
|
const input = h('input', {
|
|
1818
1830
|
class: 'sm-input',
|
|
1819
1831
|
type: 'text',
|
|
@@ -1822,7 +1834,7 @@ export class RightPanel {
|
|
|
1822
1834
|
|
|
1823
1835
|
const syncInput = () => {
|
|
1824
1836
|
input.disabled = inheriting;
|
|
1825
|
-
input.value = inheriting ? '' :
|
|
1837
|
+
input.value = inheriting ? '' : storedValue;
|
|
1826
1838
|
input.placeholder = inheriting ? '' : this.opts.t('rightPanel.fontSize.placeholder');
|
|
1827
1839
|
input.classList.toggle('sm-input--inherit-disabled', inheriting);
|
|
1828
1840
|
};
|
|
@@ -1833,17 +1845,17 @@ export class RightPanel {
|
|
|
1833
1845
|
(on) => {
|
|
1834
1846
|
if (on) {
|
|
1835
1847
|
inheriting = true;
|
|
1836
|
-
|
|
1848
|
+
storedValue = '';
|
|
1837
1849
|
inheritSwitch.setInherit(true, `${globalPx}px`);
|
|
1838
1850
|
syncInput();
|
|
1839
1851
|
onChange('');
|
|
1840
1852
|
return;
|
|
1841
1853
|
}
|
|
1842
1854
|
inheriting = false;
|
|
1843
|
-
|
|
1855
|
+
storedValue = `${globalPx}px`;
|
|
1844
1856
|
inheritSwitch.setInherit(false);
|
|
1845
1857
|
syncInput();
|
|
1846
|
-
onChange(
|
|
1858
|
+
onChange(storedValue);
|
|
1847
1859
|
},
|
|
1848
1860
|
`${focusToken}:inherit`,
|
|
1849
1861
|
);
|
|
@@ -1852,17 +1864,17 @@ export class RightPanel {
|
|
|
1852
1864
|
|
|
1853
1865
|
input.addEventListener('input', () => {
|
|
1854
1866
|
if (inheriting) return;
|
|
1855
|
-
|
|
1856
|
-
onChange(
|
|
1867
|
+
storedValue = input.value;
|
|
1868
|
+
onChange(storedValue);
|
|
1857
1869
|
});
|
|
1858
1870
|
|
|
1859
1871
|
input.addEventListener('blur', () => {
|
|
1860
1872
|
if (inheriting) return;
|
|
1861
1873
|
const t = input.value.trim();
|
|
1862
1874
|
if (!t) {
|
|
1863
|
-
|
|
1864
|
-
input.value =
|
|
1865
|
-
onChange(
|
|
1875
|
+
storedValue = `${globalPx}px`;
|
|
1876
|
+
input.value = storedValue;
|
|
1877
|
+
onChange(storedValue);
|
|
1866
1878
|
}
|
|
1867
1879
|
});
|
|
1868
1880
|
|
|
@@ -1878,7 +1890,7 @@ export class RightPanel {
|
|
|
1878
1890
|
private _snapFontSizeToPx(value: string): number {
|
|
1879
1891
|
const t = (value ?? '').trim();
|
|
1880
1892
|
const m = t.match(/^(\d+(\.\d+)?)/);
|
|
1881
|
-
if (m) return Math.min(48, Math.max(10, Math.round(parseFloat(m[1]))));
|
|
1893
|
+
if (m) return Math.min(48, Math.max(10, Math.round(Number.parseFloat(m[1]))));
|
|
1882
1894
|
return 16;
|
|
1883
1895
|
}
|
|
1884
1896
|
|
|
@@ -2043,9 +2055,7 @@ export class RightPanel {
|
|
|
2043
2055
|
const wrap = h('div', { class: 'sm-field' });
|
|
2044
2056
|
wrap.append(h('label', { class: 'sm-field__label' }, [label]));
|
|
2045
2057
|
wrap.append(
|
|
2046
|
-
h('div', { class: 'sm-field__help' }, [
|
|
2047
|
-
this.opts.t('rightPanel.width.customHelp'),
|
|
2048
|
-
]),
|
|
2058
|
+
h('div', { class: 'sm-field__help' }, [this.opts.t('rightPanel.width.customHelp')]),
|
|
2049
2059
|
);
|
|
2050
2060
|
wrap.append(
|
|
2051
2061
|
h('input', {
|
|
@@ -2165,7 +2175,13 @@ export class RightPanel {
|
|
|
2165
2175
|
|
|
2166
2176
|
private _docFontWeightField(value: string, onChange: (v: string) => void, focusToken: string) {
|
|
2167
2177
|
const cur = normalizeFontWeightStep(value);
|
|
2168
|
-
return this._segmentedSelectField(
|
|
2178
|
+
return this._segmentedSelectField(
|
|
2179
|
+
this.opts.t('rightPanel.doc.fontWeight'),
|
|
2180
|
+
cur,
|
|
2181
|
+
FONT_WEIGHT_STEP_OPTIONS,
|
|
2182
|
+
onChange,
|
|
2183
|
+
focusToken,
|
|
2184
|
+
);
|
|
2169
2185
|
}
|
|
2170
2186
|
|
|
2171
2187
|
private _multiColumnLayoutField(
|
|
@@ -2219,7 +2235,7 @@ export class RightPanel {
|
|
|
2219
2235
|
onclick: () => {
|
|
2220
2236
|
this.opts.store.update((d) => {
|
|
2221
2237
|
const l = findBlockLocation(d, block.id);
|
|
2222
|
-
if (l)
|
|
2238
|
+
if (l) l.block.lockedMjml = undefined;
|
|
2223
2239
|
});
|
|
2224
2240
|
},
|
|
2225
2241
|
},
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { html as cmHtml } from '@codemirror/lang-html';
|
|
1
2
|
import { EditorState } from '@codemirror/state';
|
|
2
3
|
import { EditorView, basicSetup } from 'codemirror';
|
|
3
|
-
import {
|
|
4
|
+
import type { SimpleMailT } from '../i18n';
|
|
4
5
|
import type { Registry } from '../registry/registry';
|
|
5
6
|
import { renderDoc } from '../renderer';
|
|
6
7
|
import type { Store } from '../store/store';
|
|
7
8
|
import { clear, h } from '../utils/dom';
|
|
8
9
|
import { richTextExecCommand } from '../utils/richTextCommand';
|
|
9
|
-
import type { SimpleMailT } from '../i18n';
|
|
10
10
|
|
|
11
11
|
export interface SourceViewOptions {
|
|
12
12
|
store: Store;
|
|
@@ -49,7 +49,9 @@ export class SourceView {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
private _isVisible(): boolean {
|
|
52
|
-
return
|
|
52
|
+
return (
|
|
53
|
+
this.el.isConnected && (this.el.offsetParent !== null || this.el.getClientRects().length > 0)
|
|
54
|
+
);
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
private _render() {
|