@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.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: (_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,92 @@ 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
  };
6338
- const adjustTextareaHeight = (element) => {
6339
- if (element) {
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 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
- });
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", { style: hStyle }, textContent);
6573
+ return /* @__PURE__ */ React58.createElement("h1", __spreadValues({}, headingProps));
6377
6574
  case "h2":
6378
- return /* @__PURE__ */ React58.createElement("h2", { style: hStyle }, textContent);
6575
+ return /* @__PURE__ */ React58.createElement("h2", __spreadValues({}, headingProps));
6379
6576
  case "h3":
6380
- return /* @__PURE__ */ React58.createElement("h3", { style: hStyle }, textContent);
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 handleTextChange = (e) => {
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 adjustTextareaHeight = (element) => {
6804
- if (element) {
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 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
- });
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(EmailMarkdown, { style: wStyle, markdown: textContent });
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