@kontakto/email-template-editor 2.1.0 → 2.2.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/dist/index.cjs +294 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +296 -103
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,9 +4,9 @@ import React58, { createContext, forwardRef, useRef, useEffect, useImperativeHan
|
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { renderToStaticMarkup as renderToStaticMarkup$1 } from 'react-dom/server';
|
|
6
6
|
import { createTheme, alpha, lighten, darken } from '@mui/material/styles';
|
|
7
|
-
import { MenuItem, Stack, ThemeProvider, CssBaseline, useTheme, Drawer, Box, Tabs, Tab, Typography, Tooltip, IconButton, TextField, InputAdornment, Chip, CircularProgress, Alert, Divider as Divider$1, ToggleButtonGroup, ToggleButton, Snackbar, Dialog, DialogTitle, DialogContent, DialogActions, Button as Button$1, InputBase, AlertTitle, FormControlLabel, Switch, InputLabel, Menu, Slider, ButtonBase, Paper, Fade } from '@mui/material';
|
|
7
|
+
import { MenuItem, Stack, ThemeProvider, CssBaseline, useTheme, Drawer, Box, Tabs, Tab, Typography, Tooltip, IconButton, TextField, InputAdornment, Chip, CircularProgress, Alert, Divider as Divider$1, ToggleButtonGroup, ToggleButton, Snackbar, Dialog, DialogTitle, DialogContent, DialogActions, Button as Button$1, InputBase, AlertTitle, FormControlLabel, Switch, InputLabel, Menu, Slider, ButtonBase, Popper, Paper, Fade } from '@mui/material';
|
|
8
8
|
import { create } from 'zustand';
|
|
9
|
-
import { AddOutlined, SearchOutlined, MonitorOutlined, PhoneIphoneOutlined, LibraryAddOutlined, ContentCopyOutlined, DriveFileRenameOutlineOutlined, FileUploadOutlined, FileDownloadOutlined, DeleteOutlined, EditOutlined, PreviewOutlined, CodeOutlined, SubjectOutlined, DataObjectOutlined, Add, SaveOutlined, LastPageOutlined, AppRegistrationOutlined, CloudUploadOutlined, FirstPageOutlined, MenuOutlined, InputOutlined, DeleteOutline, RoundedCornerOutlined, AspectRatioOutlined, HeightOutlined, CollectionsOutlined, ErrorOutlineOutlined, VerticalAlignTopOutlined, VerticalAlignCenterOutlined, VerticalAlignBottomOutlined, SpaceBarOutlined, CloseOutlined, AlignVerticalTopOutlined, AlignVerticalBottomOutlined, AlignHorizontalLeftOutlined, AlignHorizontalRightOutlined, FormatAlignLeftOutlined, FormatAlignCenterOutlined, FormatAlignRightOutlined, FormatLineSpacingOutlined, TextFieldsOutlined, ArrowUpwardOutlined, ArrowDownwardOutlined, HMobiledataOutlined, NotesOutlined, SmartButtonOutlined, ImageOutlined, AccountCircleOutlined, ContactMailOutlined, BusinessOutlined, HorizontalRuleOutlined, Crop32Outlined, HtmlOutlined, ViewColumnOutlined } from '@mui/icons-material';
|
|
9
|
+
import { AddOutlined, SearchOutlined, MonitorOutlined, PhoneIphoneOutlined, LibraryAddOutlined, ContentCopyOutlined, DriveFileRenameOutlineOutlined, FileUploadOutlined, FileDownloadOutlined, DeleteOutlined, EditOutlined, PreviewOutlined, CodeOutlined, SubjectOutlined, DataObjectOutlined, Add, SaveOutlined, LastPageOutlined, AppRegistrationOutlined, CloudUploadOutlined, FirstPageOutlined, MenuOutlined, InputOutlined, DeleteOutline, RoundedCornerOutlined, AspectRatioOutlined, HeightOutlined, CollectionsOutlined, ErrorOutlineOutlined, VerticalAlignTopOutlined, VerticalAlignCenterOutlined, VerticalAlignBottomOutlined, SpaceBarOutlined, CloseOutlined, AlignVerticalTopOutlined, AlignVerticalBottomOutlined, AlignHorizontalLeftOutlined, AlignHorizontalRightOutlined, FormatAlignLeftOutlined, FormatAlignCenterOutlined, FormatAlignRightOutlined, FormatLineSpacingOutlined, TextFieldsOutlined, FormatBoldOutlined, FormatItalicOutlined, LinkOutlined, ArrowUpwardOutlined, ArrowDownwardOutlined, HMobiledataOutlined, NotesOutlined, SmartButtonOutlined, ImageOutlined, AccountCircleOutlined, ContactMailOutlined, BusinessOutlined, HorizontalRuleOutlined, Crop32Outlined, HtmlOutlined, ViewColumnOutlined } from '@mui/icons-material';
|
|
10
10
|
import { HexColorPicker, HexColorInput } from 'react-colorful';
|
|
11
11
|
import hljs from 'highlight.js';
|
|
12
12
|
import jsonHighlighter from 'highlight.js/lib/languages/json';
|
|
@@ -156,6 +156,20 @@ function renderMarkdownString(str) {
|
|
|
156
156
|
}
|
|
157
157
|
return sanitizer(html2);
|
|
158
158
|
}
|
|
159
|
+
function renderInlineMarkdownString(str) {
|
|
160
|
+
const html2 = marked.parseInline(str, {
|
|
161
|
+
async: false,
|
|
162
|
+
breaks: true,
|
|
163
|
+
gfm: true,
|
|
164
|
+
pedantic: false,
|
|
165
|
+
silent: false,
|
|
166
|
+
renderer: new CustomRenderer()
|
|
167
|
+
});
|
|
168
|
+
if (typeof html2 !== "string") {
|
|
169
|
+
throw new Error("marked.parseInline did not return a string");
|
|
170
|
+
}
|
|
171
|
+
return sanitizer(html2);
|
|
172
|
+
}
|
|
159
173
|
function EmailMarkdown(_a) {
|
|
160
174
|
var _b = _a, { markdown } = _b, props = __objRest(_b, ["markdown"]);
|
|
161
175
|
const data = useMemo(() => renderMarkdownString(markdown), [markdown]);
|
|
@@ -681,7 +695,8 @@ function getFontFamily3(fontFamily) {
|
|
|
681
695
|
var HeadingPropsSchema = z.object({
|
|
682
696
|
props: z.object({
|
|
683
697
|
text: z.string().optional().nullable(),
|
|
684
|
-
level: z.enum(["h1", "h2", "h3"]).optional().nullable()
|
|
698
|
+
level: z.enum(["h1", "h2", "h3"]).optional().nullable(),
|
|
699
|
+
markdown: z.boolean().optional().nullable()
|
|
685
700
|
}).optional().nullable(),
|
|
686
701
|
style: z.object({
|
|
687
702
|
color: COLOR_SCHEMA6,
|
|
@@ -700,28 +715,31 @@ var HeadingPropsDefaults = {
|
|
|
700
715
|
text: ""
|
|
701
716
|
};
|
|
702
717
|
function Heading({ props, style }) {
|
|
703
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
718
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
704
719
|
const level = (_a = props == null ? void 0 : props.level) != null ? _a : HeadingPropsDefaults.level;
|
|
705
720
|
const text = (_b = props == null ? void 0 : props.text) != null ? _b : HeadingPropsDefaults.text;
|
|
721
|
+
const isMarkdown = (_c = props == null ? void 0 : props.markdown) != null ? _c : false;
|
|
706
722
|
const hStyle = {
|
|
707
|
-
color: (
|
|
708
|
-
backgroundColor: (
|
|
709
|
-
fontWeight: (
|
|
710
|
-
lineHeight: (
|
|
723
|
+
color: (_d = style == null ? void 0 : style.color) != null ? _d : void 0,
|
|
724
|
+
backgroundColor: (_e = style == null ? void 0 : style.backgroundColor) != null ? _e : void 0,
|
|
725
|
+
fontWeight: (_f = style == null ? void 0 : style.fontWeight) != null ? _f : "bold",
|
|
726
|
+
lineHeight: (_g = style == null ? void 0 : style.lineHeight) != null ? _g : void 0,
|
|
711
727
|
letterSpacing: (style == null ? void 0 : style.letterSpacing) != null ? `${style.letterSpacing}px` : void 0,
|
|
712
|
-
textAlign: (
|
|
728
|
+
textAlign: (_h = style == null ? void 0 : style.textAlign) != null ? _h : void 0,
|
|
713
729
|
margin: 0,
|
|
714
730
|
fontFamily: getFontFamily3(style == null ? void 0 : style.fontFamily),
|
|
715
731
|
fontSize: getFontSize(level),
|
|
716
732
|
padding: getPadding7(style == null ? void 0 : style.padding)
|
|
717
733
|
};
|
|
734
|
+
const html2 = useMemo(() => isMarkdown ? renderInlineMarkdownString(text) : null, [isMarkdown, text]);
|
|
735
|
+
const renderProps = isMarkdown ? { style: hStyle, dangerouslySetInnerHTML: { __html: html2 != null ? html2 : "" } } : { style: hStyle, children: text };
|
|
718
736
|
switch (level) {
|
|
719
737
|
case "h1":
|
|
720
|
-
return /* @__PURE__ */ React58.createElement("h1", {
|
|
738
|
+
return /* @__PURE__ */ React58.createElement("h1", __spreadValues({}, renderProps));
|
|
721
739
|
case "h2":
|
|
722
|
-
return /* @__PURE__ */ React58.createElement("h2", {
|
|
740
|
+
return /* @__PURE__ */ React58.createElement("h2", __spreadValues({}, renderProps));
|
|
723
741
|
case "h3":
|
|
724
|
-
return /* @__PURE__ */ React58.createElement("h3", {
|
|
742
|
+
return /* @__PURE__ */ React58.createElement("h3", __spreadValues({}, renderProps));
|
|
725
743
|
}
|
|
726
744
|
}
|
|
727
745
|
function getFontSize(level) {
|
|
@@ -2494,59 +2512,46 @@ function FontWeightInput({ label, defaultValue, onChange }) {
|
|
|
2494
2512
|
);
|
|
2495
2513
|
}
|
|
2496
2514
|
function LetterSpacingInput({ label, defaultValue, onChange }) {
|
|
2497
|
-
const
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
onChange(isNaN(value) ? null : value);
|
|
2515
|
+
const [value, setValue] = useState(defaultValue != null ? defaultValue : 0);
|
|
2516
|
+
useEffect(() => {
|
|
2517
|
+
setValue(defaultValue != null ? defaultValue : 0);
|
|
2518
|
+
}, [defaultValue]);
|
|
2519
|
+
const handleChange = (v) => {
|
|
2520
|
+
setValue(v);
|
|
2521
|
+
onChange(v === 0 ? null : v);
|
|
2505
2522
|
};
|
|
2506
|
-
return /* @__PURE__ */ React58.createElement(Stack, { spacing: 1, alignItems: "flex-start" }, /* @__PURE__ */ React58.createElement(
|
|
2507
|
-
|
|
2523
|
+
return /* @__PURE__ */ React58.createElement(Stack, { spacing: 1, alignItems: "flex-start" }, /* @__PURE__ */ React58.createElement(InputLabel, { shrink: true }, label), /* @__PURE__ */ React58.createElement(
|
|
2524
|
+
RawSliderInput,
|
|
2508
2525
|
{
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
type: "number",
|
|
2517
|
-
inputProps: { step: 0.5 },
|
|
2518
|
-
InputProps: {
|
|
2519
|
-
startAdornment: /* @__PURE__ */ React58.createElement(InputAdornment, { position: "start" }, /* @__PURE__ */ React58.createElement(SpaceBarOutlined, { sx: { fontSize: 16 } })),
|
|
2520
|
-
endAdornment: /* @__PURE__ */ React58.createElement(Typography, { variant: "body2", color: "text.secondary" }, "px")
|
|
2521
|
-
}
|
|
2526
|
+
iconLabel: /* @__PURE__ */ React58.createElement(SpaceBarOutlined, { sx: { fontSize: 16 } }),
|
|
2527
|
+
value,
|
|
2528
|
+
setValue: handleChange,
|
|
2529
|
+
units: "px",
|
|
2530
|
+
step: 0.1,
|
|
2531
|
+
min: 0,
|
|
2532
|
+
max: 2
|
|
2522
2533
|
}
|
|
2523
2534
|
));
|
|
2524
2535
|
}
|
|
2525
2536
|
function LineHeightInput({ label, defaultValue, onChange }) {
|
|
2526
|
-
const
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
onChange(isNaN(value) ? null : value);
|
|
2537
|
+
const [value, setValue] = useState(defaultValue != null ? defaultValue : 0);
|
|
2538
|
+
useEffect(() => {
|
|
2539
|
+
setValue(defaultValue != null ? defaultValue : 0);
|
|
2540
|
+
}, [defaultValue]);
|
|
2541
|
+
const handleChange = (v) => {
|
|
2542
|
+
setValue(v);
|
|
2543
|
+
onChange(v === 0 ? null : v);
|
|
2534
2544
|
};
|
|
2535
|
-
return /* @__PURE__ */ React58.createElement(Stack, { spacing: 1, alignItems: "flex-start" }, /* @__PURE__ */ React58.createElement(
|
|
2536
|
-
|
|
2545
|
+
return /* @__PURE__ */ React58.createElement(Stack, { spacing: 1, alignItems: "flex-start" }, /* @__PURE__ */ React58.createElement(InputLabel, { shrink: true }, label), /* @__PURE__ */ React58.createElement(
|
|
2546
|
+
RawSliderInput,
|
|
2537
2547
|
{
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
type: "number",
|
|
2546
|
-
inputProps: { step: 0.1 },
|
|
2547
|
-
InputProps: {
|
|
2548
|
-
startAdornment: /* @__PURE__ */ React58.createElement(InputAdornment, { position: "start" }, /* @__PURE__ */ React58.createElement(FormatLineSpacingOutlined, { sx: { fontSize: 16 } }))
|
|
2549
|
-
}
|
|
2548
|
+
iconLabel: /* @__PURE__ */ React58.createElement(FormatLineSpacingOutlined, { sx: { fontSize: 16 } }),
|
|
2549
|
+
value,
|
|
2550
|
+
setValue: handleChange,
|
|
2551
|
+
units: "",
|
|
2552
|
+
step: 0.1,
|
|
2553
|
+
min: 0,
|
|
2554
|
+
max: 2
|
|
2550
2555
|
}
|
|
2551
2556
|
));
|
|
2552
2557
|
}
|
|
@@ -6219,6 +6224,174 @@ function ButtonEditor({ style, props }) {
|
|
|
6219
6224
|
}
|
|
6220
6225
|
return /* @__PURE__ */ React58.createElement("div", { style: wrapperStyle }, /* @__PURE__ */ React58.createElement("span", { style: linkStyle }, /* @__PURE__ */ React58.createElement("span", null, text)));
|
|
6221
6226
|
}
|
|
6227
|
+
function useMarkdownToolbar({ text, isSelected, commitText, trackSelection }) {
|
|
6228
|
+
const textareaRef = useRef(null);
|
|
6229
|
+
const [selection, setSelection] = useState({ start: 0, end: 0 });
|
|
6230
|
+
const [linkPrompt, setLinkPrompt] = useState(false);
|
|
6231
|
+
const pendingSelectionRef = useRef(null);
|
|
6232
|
+
const textRef = useRef(text);
|
|
6233
|
+
useEffect(() => {
|
|
6234
|
+
textRef.current = text;
|
|
6235
|
+
}, [text]);
|
|
6236
|
+
const syncSelection = useCallback(
|
|
6237
|
+
(start, end) => {
|
|
6238
|
+
const next = { start, end };
|
|
6239
|
+
setSelection(next);
|
|
6240
|
+
trackSelection == null ? void 0 : trackSelection(next);
|
|
6241
|
+
},
|
|
6242
|
+
[trackSelection]
|
|
6243
|
+
);
|
|
6244
|
+
const trackFocus = useCallback(
|
|
6245
|
+
(e) => {
|
|
6246
|
+
var _a, _b;
|
|
6247
|
+
const el = e.currentTarget;
|
|
6248
|
+
const start = (_a = el.selectionStart) != null ? _a : el.value.length;
|
|
6249
|
+
const end = (_b = el.selectionEnd) != null ? _b : el.value.length;
|
|
6250
|
+
syncSelection(start, end);
|
|
6251
|
+
},
|
|
6252
|
+
[syncSelection]
|
|
6253
|
+
);
|
|
6254
|
+
useEffect(() => {
|
|
6255
|
+
const target = pendingSelectionRef.current;
|
|
6256
|
+
if (!target) return;
|
|
6257
|
+
const ta = textareaRef.current;
|
|
6258
|
+
if (!ta) return;
|
|
6259
|
+
ta.focus();
|
|
6260
|
+
ta.setSelectionRange(target.start, target.end);
|
|
6261
|
+
syncSelection(target.start, target.end);
|
|
6262
|
+
pendingSelectionRef.current = null;
|
|
6263
|
+
}, [text, syncSelection]);
|
|
6264
|
+
useEffect(() => {
|
|
6265
|
+
if (!isSelected || selection.start === selection.end) {
|
|
6266
|
+
setLinkPrompt(false);
|
|
6267
|
+
}
|
|
6268
|
+
}, [isSelected, selection.start, selection.end]);
|
|
6269
|
+
const wrapSelection = (prefix, suffix) => {
|
|
6270
|
+
var _a, _b;
|
|
6271
|
+
const ta = textareaRef.current;
|
|
6272
|
+
if (!ta) return;
|
|
6273
|
+
const start = (_a = ta.selectionStart) != null ? _a : selection.start;
|
|
6274
|
+
const end = (_b = ta.selectionEnd) != null ? _b : selection.end;
|
|
6275
|
+
if (start === end) return;
|
|
6276
|
+
const current = textRef.current;
|
|
6277
|
+
const selected = current.slice(start, end);
|
|
6278
|
+
const before = current.slice(0, start);
|
|
6279
|
+
const after = current.slice(end);
|
|
6280
|
+
const wrapped = `${prefix}${selected}${suffix}`;
|
|
6281
|
+
const newText = `${before}${wrapped}${after}`;
|
|
6282
|
+
const newStart = start + prefix.length;
|
|
6283
|
+
const newEnd = newStart + selected.length;
|
|
6284
|
+
pendingSelectionRef.current = { start: newStart, end: newEnd };
|
|
6285
|
+
commitText(newText);
|
|
6286
|
+
};
|
|
6287
|
+
const handleBold = () => wrapSelection("**", "**");
|
|
6288
|
+
const handleItalic = () => wrapSelection("*", "*");
|
|
6289
|
+
const handleLinkRequest = () => {
|
|
6290
|
+
if (selection.start === selection.end) return;
|
|
6291
|
+
setLinkPrompt(true);
|
|
6292
|
+
};
|
|
6293
|
+
const handleLinkSubmit = (url) => {
|
|
6294
|
+
const start = selection.start;
|
|
6295
|
+
const end = selection.end;
|
|
6296
|
+
if (start === end) {
|
|
6297
|
+
setLinkPrompt(false);
|
|
6298
|
+
return;
|
|
6299
|
+
}
|
|
6300
|
+
const current = textRef.current;
|
|
6301
|
+
const selected = current.slice(start, end);
|
|
6302
|
+
const before = current.slice(0, start);
|
|
6303
|
+
const after = current.slice(end);
|
|
6304
|
+
const wrapped = `[${selected}](${url})`;
|
|
6305
|
+
const newText = `${before}${wrapped}${after}`;
|
|
6306
|
+
const newStart = start + wrapped.length;
|
|
6307
|
+
pendingSelectionRef.current = { start: newStart, end: newStart };
|
|
6308
|
+
commitText(newText);
|
|
6309
|
+
setLinkPrompt(false);
|
|
6310
|
+
};
|
|
6311
|
+
const handleLinkCancel = () => {
|
|
6312
|
+
var _a;
|
|
6313
|
+
setLinkPrompt(false);
|
|
6314
|
+
(_a = textareaRef.current) == null ? void 0 : _a.focus();
|
|
6315
|
+
};
|
|
6316
|
+
const handleKeyDown = (e) => {
|
|
6317
|
+
if (!(e.metaKey || e.ctrlKey)) return;
|
|
6318
|
+
const key = e.key.toLowerCase();
|
|
6319
|
+
if (key === "b") {
|
|
6320
|
+
e.preventDefault();
|
|
6321
|
+
handleBold();
|
|
6322
|
+
} else if (key === "i") {
|
|
6323
|
+
e.preventDefault();
|
|
6324
|
+
handleItalic();
|
|
6325
|
+
} else if (key === "k") {
|
|
6326
|
+
e.preventDefault();
|
|
6327
|
+
handleLinkRequest();
|
|
6328
|
+
}
|
|
6329
|
+
};
|
|
6330
|
+
const toolbarVisible = isSelected && (selection.start !== selection.end || linkPrompt);
|
|
6331
|
+
return {
|
|
6332
|
+
textareaRef,
|
|
6333
|
+
selection,
|
|
6334
|
+
trackFocus,
|
|
6335
|
+
handleKeyDown,
|
|
6336
|
+
toolbarProps: {
|
|
6337
|
+
visible: toolbarVisible,
|
|
6338
|
+
linkPrompt,
|
|
6339
|
+
onBold: handleBold,
|
|
6340
|
+
onItalic: handleItalic,
|
|
6341
|
+
onLinkRequest: handleLinkRequest,
|
|
6342
|
+
onLinkSubmit: handleLinkSubmit,
|
|
6343
|
+
onLinkCancel: handleLinkCancel
|
|
6344
|
+
}
|
|
6345
|
+
};
|
|
6346
|
+
}
|
|
6347
|
+
function InlineFormattingToolbar({
|
|
6348
|
+
anchorEl,
|
|
6349
|
+
visible,
|
|
6350
|
+
linkPrompt,
|
|
6351
|
+
onBold,
|
|
6352
|
+
onItalic,
|
|
6353
|
+
onLinkRequest,
|
|
6354
|
+
onLinkSubmit,
|
|
6355
|
+
onLinkCancel
|
|
6356
|
+
}) {
|
|
6357
|
+
const [url, setUrl] = useState("");
|
|
6358
|
+
const inputRef = useRef(null);
|
|
6359
|
+
useEffect(() => {
|
|
6360
|
+
if (linkPrompt) {
|
|
6361
|
+
setUrl("");
|
|
6362
|
+
setTimeout(() => {
|
|
6363
|
+
var _a;
|
|
6364
|
+
return (_a = inputRef.current) == null ? void 0 : _a.focus();
|
|
6365
|
+
}, 0);
|
|
6366
|
+
}
|
|
6367
|
+
}, [linkPrompt]);
|
|
6368
|
+
const preventBlur = (e) => e.preventDefault();
|
|
6369
|
+
return /* @__PURE__ */ React58.createElement(Popper, { open: visible, anchorEl, placement: "top-start", style: { zIndex: 1300 } }, /* @__PURE__ */ React58.createElement(Paper, { elevation: 4, sx: { px: 0.5, py: 0.25, mb: 0.5 }, onMouseDown: preventBlur }, linkPrompt ? /* @__PURE__ */ React58.createElement(Stack, { direction: "row", alignItems: "center", spacing: 0.5, sx: { px: 0.5 } }, /* @__PURE__ */ React58.createElement(
|
|
6370
|
+
TextField,
|
|
6371
|
+
{
|
|
6372
|
+
inputRef,
|
|
6373
|
+
value: url,
|
|
6374
|
+
onChange: (e) => setUrl(e.target.value),
|
|
6375
|
+
placeholder: "https://example.com",
|
|
6376
|
+
variant: "standard",
|
|
6377
|
+
size: "small",
|
|
6378
|
+
onKeyDown: (e) => {
|
|
6379
|
+
if (e.key === "Enter") {
|
|
6380
|
+
e.preventDefault();
|
|
6381
|
+
const trimmed = url.trim();
|
|
6382
|
+
if (trimmed) onLinkSubmit(trimmed);
|
|
6383
|
+
else onLinkCancel();
|
|
6384
|
+
} else if (e.key === "Escape") {
|
|
6385
|
+
e.preventDefault();
|
|
6386
|
+
onLinkCancel();
|
|
6387
|
+
}
|
|
6388
|
+
},
|
|
6389
|
+
sx: { width: 220 }
|
|
6390
|
+
}
|
|
6391
|
+
)) : /* @__PURE__ */ React58.createElement(Stack, { direction: "row", spacing: 0.25 }, /* @__PURE__ */ React58.createElement(IconButton, { size: "small", onClick: onBold, title: "Bold (Cmd+B)", "aria-label": "Bold" }, /* @__PURE__ */ React58.createElement(FormatBoldOutlined, { fontSize: "small" })), /* @__PURE__ */ React58.createElement(IconButton, { size: "small", onClick: onItalic, title: "Italic (Cmd+I)", "aria-label": "Italic" }, /* @__PURE__ */ React58.createElement(FormatItalicOutlined, { fontSize: "small" })), /* @__PURE__ */ React58.createElement(IconButton, { size: "small", onClick: onLinkRequest, title: "Link (Cmd+K)", "aria-label": "Link" }, /* @__PURE__ */ React58.createElement(LinkOutlined, { fontSize: "small" })))));
|
|
6392
|
+
}
|
|
6393
|
+
|
|
6394
|
+
// src/editor/blocks/heading/heading-editor.tsx
|
|
6222
6395
|
function getFontFamily9(fontFamily) {
|
|
6223
6396
|
switch (fontFamily) {
|
|
6224
6397
|
case "MODERN_SANS":
|
|
@@ -6281,7 +6454,7 @@ function getFontSize2(level) {
|
|
|
6281
6454
|
}
|
|
6282
6455
|
}
|
|
6283
6456
|
function HeadingEditor({ style, props }) {
|
|
6284
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
6457
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
6285
6458
|
const blockId = useCurrentBlockId();
|
|
6286
6459
|
const selectedBlockId = useSelectedBlockId();
|
|
6287
6460
|
const document2 = useDocument();
|
|
@@ -6289,6 +6462,7 @@ function HeadingEditor({ style, props }) {
|
|
|
6289
6462
|
const level = (_a = props == null ? void 0 : props.level) != null ? _a : HeadingPropsDefaults.level;
|
|
6290
6463
|
const textContent = (_b = props == null ? void 0 : props.text) != null ? _b : HeadingPropsDefaults.text;
|
|
6291
6464
|
const [localText, setLocalText] = useState(textContent);
|
|
6465
|
+
const isMarkdown = (_c = props == null ? void 0 : props.markdown) != null ? _c : false;
|
|
6292
6466
|
const rootBlock = document2.root;
|
|
6293
6467
|
const rootFontFamily = rootBlock && rootBlock.type === "EmailLayout" ? getFontFamily9(rootBlock.data.fontFamily) : '"Helvetica Neue", Arial, sans-serif';
|
|
6294
6468
|
useEffect(() => {
|
|
@@ -6296,12 +6470,12 @@ function HeadingEditor({ style, props }) {
|
|
|
6296
6470
|
}, [textContent]);
|
|
6297
6471
|
const fontFamily = getFontFamily9(style == null ? void 0 : style.fontFamily) || rootFontFamily;
|
|
6298
6472
|
const hStyle = {
|
|
6299
|
-
color: (
|
|
6300
|
-
backgroundColor: (
|
|
6301
|
-
fontWeight: (
|
|
6302
|
-
lineHeight: (
|
|
6473
|
+
color: (_d = style == null ? void 0 : style.color) != null ? _d : void 0,
|
|
6474
|
+
backgroundColor: (_e = style == null ? void 0 : style.backgroundColor) != null ? _e : void 0,
|
|
6475
|
+
fontWeight: (_f = style == null ? void 0 : style.fontWeight) != null ? _f : "bold",
|
|
6476
|
+
lineHeight: (_g = style == null ? void 0 : style.lineHeight) != null ? _g : void 0,
|
|
6303
6477
|
letterSpacing: (style == null ? void 0 : style.letterSpacing) != null ? `${style.letterSpacing}px` : void 0,
|
|
6304
|
-
textAlign: (
|
|
6478
|
+
textAlign: (_h = style == null ? void 0 : style.textAlign) != null ? _h : void 0,
|
|
6305
6479
|
margin: 0,
|
|
6306
6480
|
fontFamily,
|
|
6307
6481
|
fontSize: getFontSize2(level),
|
|
@@ -6315,69 +6489,79 @@ function HeadingEditor({ style, props }) {
|
|
|
6315
6489
|
resize: "none",
|
|
6316
6490
|
backgroundColor: "transparent",
|
|
6317
6491
|
overflow: "hidden",
|
|
6318
|
-
lineHeight: (
|
|
6492
|
+
lineHeight: (_i = hStyle.lineHeight) != null ? _i : "inherit",
|
|
6319
6493
|
margin: 0,
|
|
6320
6494
|
display: "block",
|
|
6321
6495
|
width: "100%"
|
|
6322
6496
|
});
|
|
6323
|
-
const
|
|
6324
|
-
const newText = e.target.value;
|
|
6497
|
+
const commitText = (newText, opts) => {
|
|
6325
6498
|
setLocalText(newText);
|
|
6326
6499
|
setDocument({
|
|
6327
6500
|
[blockId]: {
|
|
6328
6501
|
type: "Heading",
|
|
6329
6502
|
data: {
|
|
6330
6503
|
style,
|
|
6331
|
-
props: __spreadProps(__spreadValues({}, props), {
|
|
6504
|
+
props: __spreadValues(__spreadProps(__spreadValues({}, props), {
|
|
6332
6505
|
text: newText
|
|
6333
|
-
})
|
|
6506
|
+
}), (opts == null ? void 0 : opts.enableMarkdown) ? { markdown: true } : {})
|
|
6334
6507
|
}
|
|
6335
6508
|
}
|
|
6336
6509
|
});
|
|
6337
6510
|
};
|
|
6511
|
+
const handleTextChange = (e) => {
|
|
6512
|
+
commitText(e.target.value);
|
|
6513
|
+
};
|
|
6338
6514
|
const adjustTextareaHeight = (element) => {
|
|
6339
6515
|
if (element) {
|
|
6340
6516
|
element.style.height = "auto";
|
|
6341
6517
|
element.style.height = `${element.scrollHeight}px`;
|
|
6342
6518
|
}
|
|
6343
6519
|
};
|
|
6344
|
-
const trackFocus = (
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6520
|
+
const { textareaRef, trackFocus, handleKeyDown, toolbarProps } = useMarkdownToolbar({
|
|
6521
|
+
text: localText,
|
|
6522
|
+
isSelected,
|
|
6523
|
+
commitText: (newText) => commitText(newText, { enableMarkdown: true }),
|
|
6524
|
+
trackSelection: (sel) => {
|
|
6525
|
+
setLastFocusedEditable({
|
|
6526
|
+
blockId,
|
|
6527
|
+
field: "text",
|
|
6528
|
+
selectionStart: sel.start,
|
|
6529
|
+
selectionEnd: sel.end
|
|
6530
|
+
});
|
|
6531
|
+
}
|
|
6532
|
+
});
|
|
6533
|
+
useEffect(() => {
|
|
6534
|
+
if (textareaRef.current) adjustTextareaHeight(textareaRef.current);
|
|
6535
|
+
}, [localText, textareaRef]);
|
|
6354
6536
|
if (isSelected) {
|
|
6355
|
-
return /* @__PURE__ */ React58.createElement(
|
|
6537
|
+
return /* @__PURE__ */ React58.createElement(React58.Fragment, null, /* @__PURE__ */ React58.createElement(
|
|
6356
6538
|
"textarea",
|
|
6357
6539
|
{
|
|
6540
|
+
ref: textareaRef,
|
|
6358
6541
|
value: localText,
|
|
6359
6542
|
onChange: handleTextChange,
|
|
6360
6543
|
onFocus: trackFocus,
|
|
6361
6544
|
onSelect: trackFocus,
|
|
6362
6545
|
onKeyUp: trackFocus,
|
|
6546
|
+
onKeyDown: handleKeyDown,
|
|
6363
6547
|
onClick: (e) => {
|
|
6364
6548
|
e.stopPropagation();
|
|
6365
6549
|
trackFocus(e);
|
|
6366
6550
|
},
|
|
6367
6551
|
style: textareaStyle,
|
|
6368
6552
|
rows: 1,
|
|
6369
|
-
onInput: (e) => adjustTextareaHeight(e.target)
|
|
6370
|
-
ref: (el) => el && adjustTextareaHeight(el)
|
|
6553
|
+
onInput: (e) => adjustTextareaHeight(e.target)
|
|
6371
6554
|
}
|
|
6372
|
-
);
|
|
6555
|
+
), /* @__PURE__ */ React58.createElement(InlineFormattingToolbar, __spreadValues({ anchorEl: textareaRef.current }, toolbarProps)));
|
|
6373
6556
|
}
|
|
6557
|
+
const headingProps = isMarkdown ? { style: hStyle, dangerouslySetInnerHTML: { __html: renderInlineMarkdownString(textContent) } } : { style: hStyle, children: textContent };
|
|
6374
6558
|
switch (level) {
|
|
6375
6559
|
case "h1":
|
|
6376
|
-
return /* @__PURE__ */ React58.createElement("h1", {
|
|
6560
|
+
return /* @__PURE__ */ React58.createElement("h1", __spreadValues({}, headingProps));
|
|
6377
6561
|
case "h2":
|
|
6378
|
-
return /* @__PURE__ */ React58.createElement("h2", {
|
|
6562
|
+
return /* @__PURE__ */ React58.createElement("h2", __spreadValues({}, headingProps));
|
|
6379
6563
|
case "h3":
|
|
6380
|
-
return /* @__PURE__ */ React58.createElement("h3", {
|
|
6564
|
+
return /* @__PURE__ */ React58.createElement("h3", __spreadValues({}, headingProps));
|
|
6381
6565
|
}
|
|
6382
6566
|
}
|
|
6383
6567
|
function HtmlEditor({ style, props }) {
|
|
@@ -6785,56 +6969,65 @@ function TextEditor({ style, props }) {
|
|
|
6785
6969
|
fontWeight: wStyle.fontWeight,
|
|
6786
6970
|
textAlign: wStyle.textAlign
|
|
6787
6971
|
});
|
|
6788
|
-
const
|
|
6789
|
-
const newText = e.target.value;
|
|
6972
|
+
const commitText = (newText, opts) => {
|
|
6790
6973
|
setLocalText(newText);
|
|
6791
6974
|
setDocument({
|
|
6792
6975
|
[blockId]: {
|
|
6793
6976
|
type: "Text",
|
|
6794
6977
|
data: {
|
|
6795
6978
|
style,
|
|
6796
|
-
props: __spreadProps(__spreadValues({}, props), {
|
|
6979
|
+
props: __spreadValues(__spreadProps(__spreadValues({}, props), {
|
|
6797
6980
|
text: newText
|
|
6798
|
-
})
|
|
6981
|
+
}), (opts == null ? void 0 : opts.enableMarkdown) ? { markdown: true } : {})
|
|
6799
6982
|
}
|
|
6800
6983
|
}
|
|
6801
6984
|
});
|
|
6802
6985
|
};
|
|
6986
|
+
const handleTextChange = (e) => {
|
|
6987
|
+
commitText(e.target.value);
|
|
6988
|
+
};
|
|
6803
6989
|
const adjustTextareaHeight = (element) => {
|
|
6804
6990
|
if (element) {
|
|
6805
6991
|
element.style.height = "auto";
|
|
6806
6992
|
element.style.height = `${element.scrollHeight}px`;
|
|
6807
6993
|
}
|
|
6808
6994
|
};
|
|
6809
|
-
const trackFocus = (
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6995
|
+
const { textareaRef, trackFocus, handleKeyDown, toolbarProps } = useMarkdownToolbar({
|
|
6996
|
+
text: localText,
|
|
6997
|
+
isSelected,
|
|
6998
|
+
commitText: (newText) => commitText(newText, { enableMarkdown: true }),
|
|
6999
|
+
trackSelection: (sel) => {
|
|
7000
|
+
setLastFocusedEditable({
|
|
7001
|
+
blockId,
|
|
7002
|
+
field: "text",
|
|
7003
|
+
selectionStart: sel.start,
|
|
7004
|
+
selectionEnd: sel.end
|
|
7005
|
+
});
|
|
7006
|
+
}
|
|
7007
|
+
});
|
|
7008
|
+
useEffect(() => {
|
|
7009
|
+
if (textareaRef.current) adjustTextareaHeight(textareaRef.current);
|
|
7010
|
+
}, [localText, textareaRef]);
|
|
6819
7011
|
if (isSelected) {
|
|
6820
|
-
return /* @__PURE__ */ React58.createElement(
|
|
7012
|
+
return /* @__PURE__ */ React58.createElement(React58.Fragment, null, /* @__PURE__ */ React58.createElement(
|
|
6821
7013
|
"textarea",
|
|
6822
7014
|
{
|
|
7015
|
+
ref: textareaRef,
|
|
6823
7016
|
value: localText,
|
|
6824
7017
|
onChange: handleTextChange,
|
|
6825
7018
|
onFocus: trackFocus,
|
|
6826
7019
|
onSelect: trackFocus,
|
|
6827
7020
|
onKeyUp: trackFocus,
|
|
7021
|
+
onKeyDown: handleKeyDown,
|
|
6828
7022
|
onClick: (e) => {
|
|
6829
7023
|
e.stopPropagation();
|
|
6830
7024
|
trackFocus(e);
|
|
6831
7025
|
},
|
|
6832
7026
|
style: textareaStyle,
|
|
6833
7027
|
rows: 1,
|
|
6834
|
-
onInput: (e) => adjustTextareaHeight(e.target)
|
|
6835
|
-
ref: (el) => el && adjustTextareaHeight(el)
|
|
7028
|
+
onInput: (e) => adjustTextareaHeight(e.target)
|
|
6836
7029
|
}
|
|
6837
|
-
);
|
|
7030
|
+
), /* @__PURE__ */ React58.createElement(InlineFormattingToolbar, __spreadValues({ anchorEl: textareaRef.current }, toolbarProps)));
|
|
6838
7031
|
}
|
|
6839
7032
|
if (isMarkdown) {
|
|
6840
7033
|
return /* @__PURE__ */ React58.createElement(EmailMarkdown, { style: wStyle, markdown: textContent });
|