@kontakto/email-template-editor 2.1.0 → 2.2.1
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 +333 -111
- 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 +336 -114
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import DOMPurify from 'dompurify';
|
|
2
2
|
import { marked, Renderer } from 'marked';
|
|
3
|
-
import React58, { createContext, forwardRef, useRef, useEffect, useImperativeHandle, useMemo, useContext, useState, useCallback, Fragment } from 'react';
|
|
3
|
+
import React58, { createContext, forwardRef, useRef, useEffect, useImperativeHandle, useMemo, useContext, useState, useCallback, useLayoutEffect, Fragment } from 'react';
|
|
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,92 @@ 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
|
};
|
|
6338
|
-
const
|
|
6339
|
-
|
|
6340
|
-
element.style.height = "auto";
|
|
6341
|
-
element.style.height = `${element.scrollHeight}px`;
|
|
6342
|
-
}
|
|
6511
|
+
const handleTextChange = (e) => {
|
|
6512
|
+
commitText(e.target.value);
|
|
6343
6513
|
};
|
|
6344
|
-
const
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6514
|
+
const displayRef = useRef(null);
|
|
6515
|
+
const lastDisplayHeightRef = useRef(0);
|
|
6516
|
+
useLayoutEffect(() => {
|
|
6517
|
+
if (!isSelected && displayRef.current) {
|
|
6518
|
+
const h = displayRef.current.offsetHeight;
|
|
6519
|
+
if (h > 0) lastDisplayHeightRef.current = h;
|
|
6520
|
+
}
|
|
6521
|
+
}, [isSelected, textContent, isMarkdown, level]);
|
|
6522
|
+
const adjustTextareaHeight = (element) => {
|
|
6523
|
+
if (!element) return;
|
|
6524
|
+
element.style.height = "auto";
|
|
6525
|
+
const scrollH = element.scrollHeight;
|
|
6526
|
+
const floor = lastDisplayHeightRef.current;
|
|
6527
|
+
element.style.height = `${Math.max(scrollH, floor)}px`;
|
|
6353
6528
|
};
|
|
6529
|
+
const { textareaRef, trackFocus, handleKeyDown, toolbarProps } = useMarkdownToolbar({
|
|
6530
|
+
text: localText,
|
|
6531
|
+
isSelected,
|
|
6532
|
+
commitText: (newText) => commitText(newText, { enableMarkdown: true }),
|
|
6533
|
+
trackSelection: (sel) => {
|
|
6534
|
+
setLastFocusedEditable({
|
|
6535
|
+
blockId,
|
|
6536
|
+
field: "text",
|
|
6537
|
+
selectionStart: sel.start,
|
|
6538
|
+
selectionEnd: sel.end
|
|
6539
|
+
});
|
|
6540
|
+
}
|
|
6541
|
+
});
|
|
6542
|
+
useLayoutEffect(() => {
|
|
6543
|
+
if (textareaRef.current) adjustTextareaHeight(textareaRef.current);
|
|
6544
|
+
}, [localText, isSelected]);
|
|
6354
6545
|
if (isSelected) {
|
|
6355
|
-
return /* @__PURE__ */ React58.createElement(
|
|
6546
|
+
return /* @__PURE__ */ React58.createElement(React58.Fragment, null, /* @__PURE__ */ React58.createElement(
|
|
6356
6547
|
"textarea",
|
|
6357
6548
|
{
|
|
6549
|
+
ref: textareaRef,
|
|
6358
6550
|
value: localText,
|
|
6359
6551
|
onChange: handleTextChange,
|
|
6360
6552
|
onFocus: trackFocus,
|
|
6361
6553
|
onSelect: trackFocus,
|
|
6362
6554
|
onKeyUp: trackFocus,
|
|
6555
|
+
onKeyDown: handleKeyDown,
|
|
6363
6556
|
onClick: (e) => {
|
|
6364
6557
|
e.stopPropagation();
|
|
6365
6558
|
trackFocus(e);
|
|
6366
6559
|
},
|
|
6367
6560
|
style: textareaStyle,
|
|
6368
6561
|
rows: 1,
|
|
6369
|
-
onInput: (e) => adjustTextareaHeight(e.target)
|
|
6370
|
-
ref: (el) => el && adjustTextareaHeight(el)
|
|
6562
|
+
onInput: (e) => adjustTextareaHeight(e.target)
|
|
6371
6563
|
}
|
|
6372
|
-
);
|
|
6564
|
+
), /* @__PURE__ */ React58.createElement(InlineFormattingToolbar, __spreadValues({ anchorEl: textareaRef.current }, toolbarProps)));
|
|
6373
6565
|
}
|
|
6566
|
+
const headingProps = isMarkdown ? {
|
|
6567
|
+
ref: displayRef,
|
|
6568
|
+
style: hStyle,
|
|
6569
|
+
dangerouslySetInnerHTML: { __html: renderInlineMarkdownString(textContent) }
|
|
6570
|
+
} : { ref: displayRef, style: hStyle, children: textContent };
|
|
6374
6571
|
switch (level) {
|
|
6375
6572
|
case "h1":
|
|
6376
|
-
return /* @__PURE__ */ React58.createElement("h1", {
|
|
6573
|
+
return /* @__PURE__ */ React58.createElement("h1", __spreadValues({}, headingProps));
|
|
6377
6574
|
case "h2":
|
|
6378
|
-
return /* @__PURE__ */ React58.createElement("h2", {
|
|
6575
|
+
return /* @__PURE__ */ React58.createElement("h2", __spreadValues({}, headingProps));
|
|
6379
6576
|
case "h3":
|
|
6380
|
-
return /* @__PURE__ */ React58.createElement("h3", {
|
|
6577
|
+
return /* @__PURE__ */ React58.createElement("h3", __spreadValues({}, headingProps));
|
|
6381
6578
|
}
|
|
6382
6579
|
}
|
|
6383
6580
|
function HtmlEditor({ style, props }) {
|
|
@@ -6785,61 +6982,86 @@ function TextEditor({ style, props }) {
|
|
|
6785
6982
|
fontWeight: wStyle.fontWeight,
|
|
6786
6983
|
textAlign: wStyle.textAlign
|
|
6787
6984
|
});
|
|
6788
|
-
const
|
|
6789
|
-
const newText = e.target.value;
|
|
6985
|
+
const commitText = (newText, opts) => {
|
|
6790
6986
|
setLocalText(newText);
|
|
6791
6987
|
setDocument({
|
|
6792
6988
|
[blockId]: {
|
|
6793
6989
|
type: "Text",
|
|
6794
6990
|
data: {
|
|
6795
6991
|
style,
|
|
6796
|
-
props: __spreadProps(__spreadValues({}, props), {
|
|
6992
|
+
props: __spreadValues(__spreadProps(__spreadValues({}, props), {
|
|
6797
6993
|
text: newText
|
|
6798
|
-
})
|
|
6994
|
+
}), (opts == null ? void 0 : opts.enableMarkdown) ? { markdown: true } : {})
|
|
6799
6995
|
}
|
|
6800
6996
|
}
|
|
6801
6997
|
});
|
|
6802
6998
|
};
|
|
6803
|
-
const
|
|
6804
|
-
|
|
6805
|
-
element.style.height = "auto";
|
|
6806
|
-
element.style.height = `${element.scrollHeight}px`;
|
|
6807
|
-
}
|
|
6999
|
+
const handleTextChange = (e) => {
|
|
7000
|
+
commitText(e.target.value);
|
|
6808
7001
|
};
|
|
6809
|
-
const
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
7002
|
+
const displayRef = useRef(null);
|
|
7003
|
+
const lastDisplayHeightRef = useRef(0);
|
|
7004
|
+
useLayoutEffect(() => {
|
|
7005
|
+
if (!isSelected && displayRef.current) {
|
|
7006
|
+
const h = displayRef.current.offsetHeight;
|
|
7007
|
+
if (h > 0) lastDisplayHeightRef.current = h;
|
|
7008
|
+
}
|
|
7009
|
+
}, [isSelected, textContent, isMarkdown]);
|
|
7010
|
+
const adjustTextareaHeight = (element) => {
|
|
7011
|
+
if (!element) return;
|
|
7012
|
+
element.style.height = "auto";
|
|
7013
|
+
const scrollH = element.scrollHeight;
|
|
7014
|
+
const floor = lastDisplayHeightRef.current;
|
|
7015
|
+
element.style.height = `${Math.max(scrollH, floor)}px`;
|
|
6818
7016
|
};
|
|
7017
|
+
const { textareaRef, trackFocus, handleKeyDown, toolbarProps } = useMarkdownToolbar({
|
|
7018
|
+
text: localText,
|
|
7019
|
+
isSelected,
|
|
7020
|
+
commitText: (newText) => commitText(newText, { enableMarkdown: true }),
|
|
7021
|
+
trackSelection: (sel) => {
|
|
7022
|
+
setLastFocusedEditable({
|
|
7023
|
+
blockId,
|
|
7024
|
+
field: "text",
|
|
7025
|
+
selectionStart: sel.start,
|
|
7026
|
+
selectionEnd: sel.end
|
|
7027
|
+
});
|
|
7028
|
+
}
|
|
7029
|
+
});
|
|
7030
|
+
useLayoutEffect(() => {
|
|
7031
|
+
if (textareaRef.current) adjustTextareaHeight(textareaRef.current);
|
|
7032
|
+
}, [localText, isSelected]);
|
|
6819
7033
|
if (isSelected) {
|
|
6820
|
-
return /* @__PURE__ */ React58.createElement(
|
|
7034
|
+
return /* @__PURE__ */ React58.createElement(React58.Fragment, null, /* @__PURE__ */ React58.createElement(
|
|
6821
7035
|
"textarea",
|
|
6822
7036
|
{
|
|
7037
|
+
ref: textareaRef,
|
|
6823
7038
|
value: localText,
|
|
6824
7039
|
onChange: handleTextChange,
|
|
6825
7040
|
onFocus: trackFocus,
|
|
6826
7041
|
onSelect: trackFocus,
|
|
6827
7042
|
onKeyUp: trackFocus,
|
|
7043
|
+
onKeyDown: handleKeyDown,
|
|
6828
7044
|
onClick: (e) => {
|
|
6829
7045
|
e.stopPropagation();
|
|
6830
7046
|
trackFocus(e);
|
|
6831
7047
|
},
|
|
6832
7048
|
style: textareaStyle,
|
|
6833
7049
|
rows: 1,
|
|
6834
|
-
onInput: (e) => adjustTextareaHeight(e.target)
|
|
6835
|
-
ref: (el) => el && adjustTextareaHeight(el)
|
|
7050
|
+
onInput: (e) => adjustTextareaHeight(e.target)
|
|
6836
7051
|
}
|
|
6837
|
-
);
|
|
7052
|
+
), /* @__PURE__ */ React58.createElement(InlineFormattingToolbar, __spreadValues({ anchorEl: textareaRef.current }, toolbarProps)));
|
|
6838
7053
|
}
|
|
6839
7054
|
if (isMarkdown) {
|
|
6840
|
-
return /* @__PURE__ */ React58.createElement(
|
|
7055
|
+
return /* @__PURE__ */ React58.createElement(
|
|
7056
|
+
"div",
|
|
7057
|
+
{
|
|
7058
|
+
ref: displayRef,
|
|
7059
|
+
style: wStyle,
|
|
7060
|
+
dangerouslySetInnerHTML: { __html: renderMarkdownString(textContent) }
|
|
7061
|
+
}
|
|
7062
|
+
);
|
|
6841
7063
|
}
|
|
6842
|
-
return /* @__PURE__ */ React58.createElement("div", { style: wStyle }, textContent);
|
|
7064
|
+
return /* @__PURE__ */ React58.createElement("div", { ref: displayRef, style: wStyle }, textContent);
|
|
6843
7065
|
}
|
|
6844
7066
|
|
|
6845
7067
|
// src/editor/core.tsx
|