@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 CHANGED
@@ -166,6 +166,20 @@ function renderMarkdownString(str) {
166
166
  }
167
167
  return sanitizer(html2);
168
168
  }
169
+ function renderInlineMarkdownString(str) {
170
+ const html2 = marked.marked.parseInline(str, {
171
+ async: false,
172
+ breaks: true,
173
+ gfm: true,
174
+ pedantic: false,
175
+ silent: false,
176
+ renderer: new CustomRenderer()
177
+ });
178
+ if (typeof html2 !== "string") {
179
+ throw new Error("marked.parseInline did not return a string");
180
+ }
181
+ return sanitizer(html2);
182
+ }
169
183
  function EmailMarkdown(_a) {
170
184
  var _b = _a, { markdown } = _b, props = __objRest(_b, ["markdown"]);
171
185
  const data = React58.useMemo(() => renderMarkdownString(markdown), [markdown]);
@@ -691,7 +705,8 @@ function getFontFamily3(fontFamily) {
691
705
  var HeadingPropsSchema = zod.z.object({
692
706
  props: zod.z.object({
693
707
  text: zod.z.string().optional().nullable(),
694
- level: zod.z.enum(["h1", "h2", "h3"]).optional().nullable()
708
+ level: zod.z.enum(["h1", "h2", "h3"]).optional().nullable(),
709
+ markdown: zod.z.boolean().optional().nullable()
695
710
  }).optional().nullable(),
696
711
  style: zod.z.object({
697
712
  color: COLOR_SCHEMA6,
@@ -710,28 +725,31 @@ var HeadingPropsDefaults = {
710
725
  text: ""
711
726
  };
712
727
  function Heading({ props, style }) {
713
- var _a, _b, _c, _d, _e, _f, _g;
728
+ var _a, _b, _c, _d, _e, _f, _g, _h;
714
729
  const level = (_a = props == null ? void 0 : props.level) != null ? _a : HeadingPropsDefaults.level;
715
730
  const text = (_b = props == null ? void 0 : props.text) != null ? _b : HeadingPropsDefaults.text;
731
+ const isMarkdown = (_c = props == null ? void 0 : props.markdown) != null ? _c : false;
716
732
  const hStyle = {
717
- color: (_c = style == null ? void 0 : style.color) != null ? _c : void 0,
718
- backgroundColor: (_d = style == null ? void 0 : style.backgroundColor) != null ? _d : void 0,
719
- fontWeight: (_e = style == null ? void 0 : style.fontWeight) != null ? _e : "bold",
720
- lineHeight: (_f = style == null ? void 0 : style.lineHeight) != null ? _f : void 0,
733
+ color: (_d = style == null ? void 0 : style.color) != null ? _d : void 0,
734
+ backgroundColor: (_e = style == null ? void 0 : style.backgroundColor) != null ? _e : void 0,
735
+ fontWeight: (_f = style == null ? void 0 : style.fontWeight) != null ? _f : "bold",
736
+ lineHeight: (_g = style == null ? void 0 : style.lineHeight) != null ? _g : void 0,
721
737
  letterSpacing: (style == null ? void 0 : style.letterSpacing) != null ? `${style.letterSpacing}px` : void 0,
722
- textAlign: (_g = style == null ? void 0 : style.textAlign) != null ? _g : void 0,
738
+ textAlign: (_h = style == null ? void 0 : style.textAlign) != null ? _h : void 0,
723
739
  margin: 0,
724
740
  fontFamily: getFontFamily3(style == null ? void 0 : style.fontFamily),
725
741
  fontSize: getFontSize(level),
726
742
  padding: getPadding7(style == null ? void 0 : style.padding)
727
743
  };
744
+ const html2 = React58.useMemo(() => isMarkdown ? renderInlineMarkdownString(text) : null, [isMarkdown, text]);
745
+ const renderProps = isMarkdown ? { style: hStyle, dangerouslySetInnerHTML: { __html: html2 != null ? html2 : "" } } : { style: hStyle, children: text };
728
746
  switch (level) {
729
747
  case "h1":
730
- return /* @__PURE__ */ React58__default.default.createElement("h1", { style: hStyle }, text);
748
+ return /* @__PURE__ */ React58__default.default.createElement("h1", __spreadValues({}, renderProps));
731
749
  case "h2":
732
- return /* @__PURE__ */ React58__default.default.createElement("h2", { style: hStyle }, text);
750
+ return /* @__PURE__ */ React58__default.default.createElement("h2", __spreadValues({}, renderProps));
733
751
  case "h3":
734
- return /* @__PURE__ */ React58__default.default.createElement("h3", { style: hStyle }, text);
752
+ return /* @__PURE__ */ React58__default.default.createElement("h3", __spreadValues({}, renderProps));
735
753
  }
736
754
  }
737
755
  function getFontSize(level) {
@@ -2504,59 +2522,46 @@ function FontWeightInput({ label, defaultValue, onChange }) {
2504
2522
  );
2505
2523
  }
2506
2524
  function LetterSpacingInput({ label, defaultValue, onChange }) {
2507
- const handleChange = (ev) => {
2508
- const raw = ev.target.value.trim();
2509
- if (raw === "") {
2510
- onChange(null);
2511
- return;
2512
- }
2513
- const value = parseFloat(raw);
2514
- onChange(isNaN(value) ? null : value);
2525
+ const [value, setValue] = React58.useState(defaultValue != null ? defaultValue : 0);
2526
+ React58.useEffect(() => {
2527
+ setValue(defaultValue != null ? defaultValue : 0);
2528
+ }, [defaultValue]);
2529
+ const handleChange = (v) => {
2530
+ setValue(v);
2531
+ onChange(v === 0 ? null : v);
2515
2532
  };
2516
- return /* @__PURE__ */ React58__default.default.createElement(material.Stack, { spacing: 1, alignItems: "flex-start" }, /* @__PURE__ */ React58__default.default.createElement(
2517
- material.TextField,
2533
+ return /* @__PURE__ */ React58__default.default.createElement(material.Stack, { spacing: 1, alignItems: "flex-start" }, /* @__PURE__ */ React58__default.default.createElement(material.InputLabel, { shrink: true }, label), /* @__PURE__ */ React58__default.default.createElement(
2534
+ RawSliderInput,
2518
2535
  {
2519
- fullWidth: true,
2520
- onChange: handleChange,
2521
- defaultValue: defaultValue != null ? defaultValue : "",
2522
- label,
2523
- variant: "standard",
2524
- placeholder: "normal",
2525
- size: "small",
2526
- type: "number",
2527
- inputProps: { step: 0.5 },
2528
- InputProps: {
2529
- startAdornment: /* @__PURE__ */ React58__default.default.createElement(material.InputAdornment, { position: "start" }, /* @__PURE__ */ React58__default.default.createElement(iconsMaterial.SpaceBarOutlined, { sx: { fontSize: 16 } })),
2530
- endAdornment: /* @__PURE__ */ React58__default.default.createElement(material.Typography, { variant: "body2", color: "text.secondary" }, "px")
2531
- }
2536
+ iconLabel: /* @__PURE__ */ React58__default.default.createElement(iconsMaterial.SpaceBarOutlined, { sx: { fontSize: 16 } }),
2537
+ value,
2538
+ setValue: handleChange,
2539
+ units: "px",
2540
+ step: 0.1,
2541
+ min: 0,
2542
+ max: 2
2532
2543
  }
2533
2544
  ));
2534
2545
  }
2535
2546
  function LineHeightInput({ label, defaultValue, onChange }) {
2536
- const handleChange = (ev) => {
2537
- const raw = ev.target.value.trim();
2538
- if (raw === "") {
2539
- onChange(null);
2540
- return;
2541
- }
2542
- const value = parseFloat(raw);
2543
- onChange(isNaN(value) ? null : value);
2547
+ const [value, setValue] = React58.useState(defaultValue != null ? defaultValue : 0);
2548
+ React58.useEffect(() => {
2549
+ setValue(defaultValue != null ? defaultValue : 0);
2550
+ }, [defaultValue]);
2551
+ const handleChange = (v) => {
2552
+ setValue(v);
2553
+ onChange(v === 0 ? null : v);
2544
2554
  };
2545
- return /* @__PURE__ */ React58__default.default.createElement(material.Stack, { spacing: 1, alignItems: "flex-start" }, /* @__PURE__ */ React58__default.default.createElement(
2546
- material.TextField,
2555
+ return /* @__PURE__ */ React58__default.default.createElement(material.Stack, { spacing: 1, alignItems: "flex-start" }, /* @__PURE__ */ React58__default.default.createElement(material.InputLabel, { shrink: true }, label), /* @__PURE__ */ React58__default.default.createElement(
2556
+ RawSliderInput,
2547
2557
  {
2548
- fullWidth: true,
2549
- onChange: handleChange,
2550
- defaultValue: defaultValue != null ? defaultValue : "",
2551
- label,
2552
- variant: "standard",
2553
- placeholder: "default",
2554
- size: "small",
2555
- type: "number",
2556
- inputProps: { step: 0.1 },
2557
- InputProps: {
2558
- startAdornment: /* @__PURE__ */ React58__default.default.createElement(material.InputAdornment, { position: "start" }, /* @__PURE__ */ React58__default.default.createElement(iconsMaterial.FormatLineSpacingOutlined, { sx: { fontSize: 16 } }))
2559
- }
2558
+ iconLabel: /* @__PURE__ */ React58__default.default.createElement(iconsMaterial.FormatLineSpacingOutlined, { sx: { fontSize: 16 } }),
2559
+ value,
2560
+ setValue: handleChange,
2561
+ units: "",
2562
+ step: 0.1,
2563
+ min: 0,
2564
+ max: 2
2560
2565
  }
2561
2566
  ));
2562
2567
  }
@@ -6229,6 +6234,174 @@ function ButtonEditor({ style, props }) {
6229
6234
  }
6230
6235
  return /* @__PURE__ */ React58__default.default.createElement("div", { style: wrapperStyle }, /* @__PURE__ */ React58__default.default.createElement("span", { style: linkStyle }, /* @__PURE__ */ React58__default.default.createElement("span", null, text)));
6231
6236
  }
6237
+ function useMarkdownToolbar({ text, isSelected, commitText, trackSelection }) {
6238
+ const textareaRef = React58.useRef(null);
6239
+ const [selection, setSelection] = React58.useState({ start: 0, end: 0 });
6240
+ const [linkPrompt, setLinkPrompt] = React58.useState(false);
6241
+ const pendingSelectionRef = React58.useRef(null);
6242
+ const textRef = React58.useRef(text);
6243
+ React58.useEffect(() => {
6244
+ textRef.current = text;
6245
+ }, [text]);
6246
+ const syncSelection = React58.useCallback(
6247
+ (start, end) => {
6248
+ const next = { start, end };
6249
+ setSelection(next);
6250
+ trackSelection == null ? void 0 : trackSelection(next);
6251
+ },
6252
+ [trackSelection]
6253
+ );
6254
+ const trackFocus = React58.useCallback(
6255
+ (e) => {
6256
+ var _a, _b;
6257
+ const el = e.currentTarget;
6258
+ const start = (_a = el.selectionStart) != null ? _a : el.value.length;
6259
+ const end = (_b = el.selectionEnd) != null ? _b : el.value.length;
6260
+ syncSelection(start, end);
6261
+ },
6262
+ [syncSelection]
6263
+ );
6264
+ React58.useEffect(() => {
6265
+ const target = pendingSelectionRef.current;
6266
+ if (!target) return;
6267
+ const ta = textareaRef.current;
6268
+ if (!ta) return;
6269
+ ta.focus();
6270
+ ta.setSelectionRange(target.start, target.end);
6271
+ syncSelection(target.start, target.end);
6272
+ pendingSelectionRef.current = null;
6273
+ }, [text, syncSelection]);
6274
+ React58.useEffect(() => {
6275
+ if (!isSelected || selection.start === selection.end) {
6276
+ setLinkPrompt(false);
6277
+ }
6278
+ }, [isSelected, selection.start, selection.end]);
6279
+ const wrapSelection = (prefix, suffix) => {
6280
+ var _a, _b;
6281
+ const ta = textareaRef.current;
6282
+ if (!ta) return;
6283
+ const start = (_a = ta.selectionStart) != null ? _a : selection.start;
6284
+ const end = (_b = ta.selectionEnd) != null ? _b : selection.end;
6285
+ if (start === end) return;
6286
+ const current = textRef.current;
6287
+ const selected = current.slice(start, end);
6288
+ const before = current.slice(0, start);
6289
+ const after = current.slice(end);
6290
+ const wrapped = `${prefix}${selected}${suffix}`;
6291
+ const newText = `${before}${wrapped}${after}`;
6292
+ const newStart = start + prefix.length;
6293
+ const newEnd = newStart + selected.length;
6294
+ pendingSelectionRef.current = { start: newStart, end: newEnd };
6295
+ commitText(newText);
6296
+ };
6297
+ const handleBold = () => wrapSelection("**", "**");
6298
+ const handleItalic = () => wrapSelection("*", "*");
6299
+ const handleLinkRequest = () => {
6300
+ if (selection.start === selection.end) return;
6301
+ setLinkPrompt(true);
6302
+ };
6303
+ const handleLinkSubmit = (url) => {
6304
+ const start = selection.start;
6305
+ const end = selection.end;
6306
+ if (start === end) {
6307
+ setLinkPrompt(false);
6308
+ return;
6309
+ }
6310
+ const current = textRef.current;
6311
+ const selected = current.slice(start, end);
6312
+ const before = current.slice(0, start);
6313
+ const after = current.slice(end);
6314
+ const wrapped = `[${selected}](${url})`;
6315
+ const newText = `${before}${wrapped}${after}`;
6316
+ const newStart = start + wrapped.length;
6317
+ pendingSelectionRef.current = { start: newStart, end: newStart };
6318
+ commitText(newText);
6319
+ setLinkPrompt(false);
6320
+ };
6321
+ const handleLinkCancel = () => {
6322
+ var _a;
6323
+ setLinkPrompt(false);
6324
+ (_a = textareaRef.current) == null ? void 0 : _a.focus();
6325
+ };
6326
+ const handleKeyDown = (e) => {
6327
+ if (!(e.metaKey || e.ctrlKey)) return;
6328
+ const key = e.key.toLowerCase();
6329
+ if (key === "b") {
6330
+ e.preventDefault();
6331
+ handleBold();
6332
+ } else if (key === "i") {
6333
+ e.preventDefault();
6334
+ handleItalic();
6335
+ } else if (key === "k") {
6336
+ e.preventDefault();
6337
+ handleLinkRequest();
6338
+ }
6339
+ };
6340
+ const toolbarVisible = isSelected && (selection.start !== selection.end || linkPrompt);
6341
+ return {
6342
+ textareaRef,
6343
+ selection,
6344
+ trackFocus,
6345
+ handleKeyDown,
6346
+ toolbarProps: {
6347
+ visible: toolbarVisible,
6348
+ linkPrompt,
6349
+ onBold: handleBold,
6350
+ onItalic: handleItalic,
6351
+ onLinkRequest: handleLinkRequest,
6352
+ onLinkSubmit: handleLinkSubmit,
6353
+ onLinkCancel: handleLinkCancel
6354
+ }
6355
+ };
6356
+ }
6357
+ function InlineFormattingToolbar({
6358
+ anchorEl,
6359
+ visible,
6360
+ linkPrompt,
6361
+ onBold,
6362
+ onItalic,
6363
+ onLinkRequest,
6364
+ onLinkSubmit,
6365
+ onLinkCancel
6366
+ }) {
6367
+ const [url, setUrl] = React58.useState("");
6368
+ const inputRef = React58.useRef(null);
6369
+ React58.useEffect(() => {
6370
+ if (linkPrompt) {
6371
+ setUrl("");
6372
+ setTimeout(() => {
6373
+ var _a;
6374
+ return (_a = inputRef.current) == null ? void 0 : _a.focus();
6375
+ }, 0);
6376
+ }
6377
+ }, [linkPrompt]);
6378
+ const preventBlur = (e) => e.preventDefault();
6379
+ return /* @__PURE__ */ React58__default.default.createElement(material.Popper, { open: visible, anchorEl, placement: "top-start", style: { zIndex: 1300 } }, /* @__PURE__ */ React58__default.default.createElement(material.Paper, { elevation: 4, sx: { px: 0.5, py: 0.25, mb: 0.5 }, onMouseDown: preventBlur }, linkPrompt ? /* @__PURE__ */ React58__default.default.createElement(material.Stack, { direction: "row", alignItems: "center", spacing: 0.5, sx: { px: 0.5 } }, /* @__PURE__ */ React58__default.default.createElement(
6380
+ material.TextField,
6381
+ {
6382
+ inputRef,
6383
+ value: url,
6384
+ onChange: (e) => setUrl(e.target.value),
6385
+ placeholder: "https://example.com",
6386
+ variant: "standard",
6387
+ size: "small",
6388
+ onKeyDown: (e) => {
6389
+ if (e.key === "Enter") {
6390
+ e.preventDefault();
6391
+ const trimmed = url.trim();
6392
+ if (trimmed) onLinkSubmit(trimmed);
6393
+ else onLinkCancel();
6394
+ } else if (e.key === "Escape") {
6395
+ e.preventDefault();
6396
+ onLinkCancel();
6397
+ }
6398
+ },
6399
+ sx: { width: 220 }
6400
+ }
6401
+ )) : /* @__PURE__ */ React58__default.default.createElement(material.Stack, { direction: "row", spacing: 0.25 }, /* @__PURE__ */ React58__default.default.createElement(material.IconButton, { size: "small", onClick: onBold, title: "Bold (Cmd+B)", "aria-label": "Bold" }, /* @__PURE__ */ React58__default.default.createElement(iconsMaterial.FormatBoldOutlined, { fontSize: "small" })), /* @__PURE__ */ React58__default.default.createElement(material.IconButton, { size: "small", onClick: onItalic, title: "Italic (Cmd+I)", "aria-label": "Italic" }, /* @__PURE__ */ React58__default.default.createElement(iconsMaterial.FormatItalicOutlined, { fontSize: "small" })), /* @__PURE__ */ React58__default.default.createElement(material.IconButton, { size: "small", onClick: onLinkRequest, title: "Link (Cmd+K)", "aria-label": "Link" }, /* @__PURE__ */ React58__default.default.createElement(iconsMaterial.LinkOutlined, { fontSize: "small" })))));
6402
+ }
6403
+
6404
+ // src/editor/blocks/heading/heading-editor.tsx
6232
6405
  function getFontFamily9(fontFamily) {
6233
6406
  switch (fontFamily) {
6234
6407
  case "MODERN_SANS":
@@ -6291,7 +6464,7 @@ function getFontSize2(level) {
6291
6464
  }
6292
6465
  }
6293
6466
  function HeadingEditor({ style, props }) {
6294
- var _a, _b, _c, _d, _e, _f, _g, _h;
6467
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
6295
6468
  const blockId = useCurrentBlockId();
6296
6469
  const selectedBlockId = useSelectedBlockId();
6297
6470
  const document2 = useDocument();
@@ -6299,6 +6472,7 @@ function HeadingEditor({ style, props }) {
6299
6472
  const level = (_a = props == null ? void 0 : props.level) != null ? _a : HeadingPropsDefaults.level;
6300
6473
  const textContent = (_b = props == null ? void 0 : props.text) != null ? _b : HeadingPropsDefaults.text;
6301
6474
  const [localText, setLocalText] = React58.useState(textContent);
6475
+ const isMarkdown = (_c = props == null ? void 0 : props.markdown) != null ? _c : false;
6302
6476
  const rootBlock = document2.root;
6303
6477
  const rootFontFamily = rootBlock && rootBlock.type === "EmailLayout" ? getFontFamily9(rootBlock.data.fontFamily) : '"Helvetica Neue", Arial, sans-serif';
6304
6478
  React58.useEffect(() => {
@@ -6306,12 +6480,12 @@ function HeadingEditor({ style, props }) {
6306
6480
  }, [textContent]);
6307
6481
  const fontFamily = getFontFamily9(style == null ? void 0 : style.fontFamily) || rootFontFamily;
6308
6482
  const hStyle = {
6309
- color: (_c = style == null ? void 0 : style.color) != null ? _c : void 0,
6310
- backgroundColor: (_d = style == null ? void 0 : style.backgroundColor) != null ? _d : void 0,
6311
- fontWeight: (_e = style == null ? void 0 : style.fontWeight) != null ? _e : "bold",
6312
- lineHeight: (_f = style == null ? void 0 : style.lineHeight) != null ? _f : void 0,
6483
+ color: (_d = style == null ? void 0 : style.color) != null ? _d : void 0,
6484
+ backgroundColor: (_e = style == null ? void 0 : style.backgroundColor) != null ? _e : void 0,
6485
+ fontWeight: (_f = style == null ? void 0 : style.fontWeight) != null ? _f : "bold",
6486
+ lineHeight: (_g = style == null ? void 0 : style.lineHeight) != null ? _g : void 0,
6313
6487
  letterSpacing: (style == null ? void 0 : style.letterSpacing) != null ? `${style.letterSpacing}px` : void 0,
6314
- textAlign: (_g = style == null ? void 0 : style.textAlign) != null ? _g : void 0,
6488
+ textAlign: (_h = style == null ? void 0 : style.textAlign) != null ? _h : void 0,
6315
6489
  margin: 0,
6316
6490
  fontFamily,
6317
6491
  fontSize: getFontSize2(level),
@@ -6325,69 +6499,79 @@ function HeadingEditor({ style, props }) {
6325
6499
  resize: "none",
6326
6500
  backgroundColor: "transparent",
6327
6501
  overflow: "hidden",
6328
- lineHeight: (_h = hStyle.lineHeight) != null ? _h : "inherit",
6502
+ lineHeight: (_i = hStyle.lineHeight) != null ? _i : "inherit",
6329
6503
  margin: 0,
6330
6504
  display: "block",
6331
6505
  width: "100%"
6332
6506
  });
6333
- const handleTextChange = (e) => {
6334
- const newText = e.target.value;
6507
+ const commitText = (newText, opts) => {
6335
6508
  setLocalText(newText);
6336
6509
  setDocument({
6337
6510
  [blockId]: {
6338
6511
  type: "Heading",
6339
6512
  data: {
6340
6513
  style,
6341
- props: __spreadProps(__spreadValues({}, props), {
6514
+ props: __spreadValues(__spreadProps(__spreadValues({}, props), {
6342
6515
  text: newText
6343
- })
6516
+ }), (opts == null ? void 0 : opts.enableMarkdown) ? { markdown: true } : {})
6344
6517
  }
6345
6518
  }
6346
6519
  });
6347
6520
  };
6521
+ const handleTextChange = (e) => {
6522
+ commitText(e.target.value);
6523
+ };
6348
6524
  const adjustTextareaHeight = (element) => {
6349
6525
  if (element) {
6350
6526
  element.style.height = "auto";
6351
6527
  element.style.height = `${element.scrollHeight}px`;
6352
6528
  }
6353
6529
  };
6354
- const trackFocus = (e) => {
6355
- var _a2, _b2;
6356
- const el = e.currentTarget;
6357
- setLastFocusedEditable({
6358
- blockId,
6359
- field: "text",
6360
- selectionStart: (_a2 = el.selectionStart) != null ? _a2 : el.value.length,
6361
- selectionEnd: (_b2 = el.selectionEnd) != null ? _b2 : el.value.length
6362
- });
6363
- };
6530
+ const { textareaRef, trackFocus, handleKeyDown, toolbarProps } = useMarkdownToolbar({
6531
+ text: localText,
6532
+ isSelected,
6533
+ commitText: (newText) => commitText(newText, { enableMarkdown: true }),
6534
+ trackSelection: (sel) => {
6535
+ setLastFocusedEditable({
6536
+ blockId,
6537
+ field: "text",
6538
+ selectionStart: sel.start,
6539
+ selectionEnd: sel.end
6540
+ });
6541
+ }
6542
+ });
6543
+ React58.useEffect(() => {
6544
+ if (textareaRef.current) adjustTextareaHeight(textareaRef.current);
6545
+ }, [localText, textareaRef]);
6364
6546
  if (isSelected) {
6365
- return /* @__PURE__ */ React58__default.default.createElement(
6547
+ return /* @__PURE__ */ React58__default.default.createElement(React58__default.default.Fragment, null, /* @__PURE__ */ React58__default.default.createElement(
6366
6548
  "textarea",
6367
6549
  {
6550
+ ref: textareaRef,
6368
6551
  value: localText,
6369
6552
  onChange: handleTextChange,
6370
6553
  onFocus: trackFocus,
6371
6554
  onSelect: trackFocus,
6372
6555
  onKeyUp: trackFocus,
6556
+ onKeyDown: handleKeyDown,
6373
6557
  onClick: (e) => {
6374
6558
  e.stopPropagation();
6375
6559
  trackFocus(e);
6376
6560
  },
6377
6561
  style: textareaStyle,
6378
6562
  rows: 1,
6379
- onInput: (e) => adjustTextareaHeight(e.target),
6380
- ref: (el) => el && adjustTextareaHeight(el)
6563
+ onInput: (e) => adjustTextareaHeight(e.target)
6381
6564
  }
6382
- );
6565
+ ), /* @__PURE__ */ React58__default.default.createElement(InlineFormattingToolbar, __spreadValues({ anchorEl: textareaRef.current }, toolbarProps)));
6383
6566
  }
6567
+ const headingProps = isMarkdown ? { style: hStyle, dangerouslySetInnerHTML: { __html: renderInlineMarkdownString(textContent) } } : { style: hStyle, children: textContent };
6384
6568
  switch (level) {
6385
6569
  case "h1":
6386
- return /* @__PURE__ */ React58__default.default.createElement("h1", { style: hStyle }, textContent);
6570
+ return /* @__PURE__ */ React58__default.default.createElement("h1", __spreadValues({}, headingProps));
6387
6571
  case "h2":
6388
- return /* @__PURE__ */ React58__default.default.createElement("h2", { style: hStyle }, textContent);
6572
+ return /* @__PURE__ */ React58__default.default.createElement("h2", __spreadValues({}, headingProps));
6389
6573
  case "h3":
6390
- return /* @__PURE__ */ React58__default.default.createElement("h3", { style: hStyle }, textContent);
6574
+ return /* @__PURE__ */ React58__default.default.createElement("h3", __spreadValues({}, headingProps));
6391
6575
  }
6392
6576
  }
6393
6577
  function HtmlEditor({ style, props }) {
@@ -6795,56 +6979,65 @@ function TextEditor({ style, props }) {
6795
6979
  fontWeight: wStyle.fontWeight,
6796
6980
  textAlign: wStyle.textAlign
6797
6981
  });
6798
- const handleTextChange = (e) => {
6799
- const newText = e.target.value;
6982
+ const commitText = (newText, opts) => {
6800
6983
  setLocalText(newText);
6801
6984
  setDocument({
6802
6985
  [blockId]: {
6803
6986
  type: "Text",
6804
6987
  data: {
6805
6988
  style,
6806
- props: __spreadProps(__spreadValues({}, props), {
6989
+ props: __spreadValues(__spreadProps(__spreadValues({}, props), {
6807
6990
  text: newText
6808
- })
6991
+ }), (opts == null ? void 0 : opts.enableMarkdown) ? { markdown: true } : {})
6809
6992
  }
6810
6993
  }
6811
6994
  });
6812
6995
  };
6996
+ const handleTextChange = (e) => {
6997
+ commitText(e.target.value);
6998
+ };
6813
6999
  const adjustTextareaHeight = (element) => {
6814
7000
  if (element) {
6815
7001
  element.style.height = "auto";
6816
7002
  element.style.height = `${element.scrollHeight}px`;
6817
7003
  }
6818
7004
  };
6819
- const trackFocus = (e) => {
6820
- var _a2, _b2;
6821
- const el = e.currentTarget;
6822
- setLastFocusedEditable({
6823
- blockId,
6824
- field: "text",
6825
- selectionStart: (_a2 = el.selectionStart) != null ? _a2 : el.value.length,
6826
- selectionEnd: (_b2 = el.selectionEnd) != null ? _b2 : el.value.length
6827
- });
6828
- };
7005
+ const { textareaRef, trackFocus, handleKeyDown, toolbarProps } = useMarkdownToolbar({
7006
+ text: localText,
7007
+ isSelected,
7008
+ commitText: (newText) => commitText(newText, { enableMarkdown: true }),
7009
+ trackSelection: (sel) => {
7010
+ setLastFocusedEditable({
7011
+ blockId,
7012
+ field: "text",
7013
+ selectionStart: sel.start,
7014
+ selectionEnd: sel.end
7015
+ });
7016
+ }
7017
+ });
7018
+ React58.useEffect(() => {
7019
+ if (textareaRef.current) adjustTextareaHeight(textareaRef.current);
7020
+ }, [localText, textareaRef]);
6829
7021
  if (isSelected) {
6830
- return /* @__PURE__ */ React58__default.default.createElement(
7022
+ return /* @__PURE__ */ React58__default.default.createElement(React58__default.default.Fragment, null, /* @__PURE__ */ React58__default.default.createElement(
6831
7023
  "textarea",
6832
7024
  {
7025
+ ref: textareaRef,
6833
7026
  value: localText,
6834
7027
  onChange: handleTextChange,
6835
7028
  onFocus: trackFocus,
6836
7029
  onSelect: trackFocus,
6837
7030
  onKeyUp: trackFocus,
7031
+ onKeyDown: handleKeyDown,
6838
7032
  onClick: (e) => {
6839
7033
  e.stopPropagation();
6840
7034
  trackFocus(e);
6841
7035
  },
6842
7036
  style: textareaStyle,
6843
7037
  rows: 1,
6844
- onInput: (e) => adjustTextareaHeight(e.target),
6845
- ref: (el) => el && adjustTextareaHeight(el)
7038
+ onInput: (e) => adjustTextareaHeight(e.target)
6846
7039
  }
6847
- );
7040
+ ), /* @__PURE__ */ React58__default.default.createElement(InlineFormattingToolbar, __spreadValues({ anchorEl: textareaRef.current }, toolbarProps)));
6848
7041
  }
6849
7042
  if (isMarkdown) {
6850
7043
  return /* @__PURE__ */ React58__default.default.createElement(EmailMarkdown, { style: wStyle, markdown: textContent });