@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.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: (_c = style == null ? void 0 : style.color) != null ? _c : void 0,
708
- backgroundColor: (_d = style == null ? void 0 : style.backgroundColor) != null ? _d : void 0,
709
- fontWeight: (_e = style == null ? void 0 : style.fontWeight) != null ? _e : "bold",
710
- lineHeight: (_f = style == null ? void 0 : style.lineHeight) != null ? _f : void 0,
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: (_g = style == null ? void 0 : style.textAlign) != null ? _g : void 0,
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", { style: hStyle }, text);
738
+ return /* @__PURE__ */ React58.createElement("h1", __spreadValues({}, renderProps));
721
739
  case "h2":
722
- return /* @__PURE__ */ React58.createElement("h2", { style: hStyle }, text);
740
+ return /* @__PURE__ */ React58.createElement("h2", __spreadValues({}, renderProps));
723
741
  case "h3":
724
- return /* @__PURE__ */ React58.createElement("h3", { style: hStyle }, text);
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 handleChange = (ev) => {
2498
- const raw = ev.target.value.trim();
2499
- if (raw === "") {
2500
- onChange(null);
2501
- return;
2502
- }
2503
- const value = parseFloat(raw);
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
- TextField,
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
- fullWidth: true,
2510
- onChange: handleChange,
2511
- defaultValue: defaultValue != null ? defaultValue : "",
2512
- label,
2513
- variant: "standard",
2514
- placeholder: "normal",
2515
- size: "small",
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 handleChange = (ev) => {
2527
- const raw = ev.target.value.trim();
2528
- if (raw === "") {
2529
- onChange(null);
2530
- return;
2531
- }
2532
- const value = parseFloat(raw);
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
- TextField,
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
- fullWidth: true,
2539
- onChange: handleChange,
2540
- defaultValue: defaultValue != null ? defaultValue : "",
2541
- label,
2542
- variant: "standard",
2543
- placeholder: "default",
2544
- size: "small",
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: (_c = style == null ? void 0 : style.color) != null ? _c : void 0,
6300
- backgroundColor: (_d = style == null ? void 0 : style.backgroundColor) != null ? _d : void 0,
6301
- fontWeight: (_e = style == null ? void 0 : style.fontWeight) != null ? _e : "bold",
6302
- lineHeight: (_f = style == null ? void 0 : style.lineHeight) != null ? _f : void 0,
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: (_g = style == null ? void 0 : style.textAlign) != null ? _g : void 0,
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: (_h = hStyle.lineHeight) != null ? _h : "inherit",
6492
+ lineHeight: (_i = hStyle.lineHeight) != null ? _i : "inherit",
6319
6493
  margin: 0,
6320
6494
  display: "block",
6321
6495
  width: "100%"
6322
6496
  });
6323
- const handleTextChange = (e) => {
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 = (e) => {
6345
- var _a2, _b2;
6346
- const el = e.currentTarget;
6347
- setLastFocusedEditable({
6348
- blockId,
6349
- field: "text",
6350
- selectionStart: (_a2 = el.selectionStart) != null ? _a2 : el.value.length,
6351
- selectionEnd: (_b2 = el.selectionEnd) != null ? _b2 : el.value.length
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", { style: hStyle }, textContent);
6560
+ return /* @__PURE__ */ React58.createElement("h1", __spreadValues({}, headingProps));
6377
6561
  case "h2":
6378
- return /* @__PURE__ */ React58.createElement("h2", { style: hStyle }, textContent);
6562
+ return /* @__PURE__ */ React58.createElement("h2", __spreadValues({}, headingProps));
6379
6563
  case "h3":
6380
- return /* @__PURE__ */ React58.createElement("h3", { style: hStyle }, textContent);
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 handleTextChange = (e) => {
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 = (e) => {
6810
- var _a2, _b2;
6811
- const el = e.currentTarget;
6812
- setLastFocusedEditable({
6813
- blockId,
6814
- field: "text",
6815
- selectionStart: (_a2 = el.selectionStart) != null ? _a2 : el.value.length,
6816
- selectionEnd: (_b2 = el.selectionEnd) != null ? _b2 : el.value.length
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 });