@kontakto/email-template-editor 2.5.0 → 2.6.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
@@ -8,8 +8,9 @@ import { createTheme, alpha, lighten, darken } from '@mui/material/styles';
8
8
  import { I18nProvider, Trans } from '@lingui/react';
9
9
  import { MenuItem, Stack, ThemeProvider, CssBaseline, useTheme, Drawer, Box, Tabs, Tab, Typography, Tooltip, IconButton, TextField, InputAdornment, Chip, CircularProgress, Alert, ToggleButtonGroup, ToggleButton, Snackbar, Menu, ListItemIcon, ListItemText, Divider as Divider$1, Dialog, DialogTitle, DialogContent, Button as Button$1, DialogActions, List, ListItemButton, InputBase, AlertTitle, FormControlLabel, Switch, InputLabel, Slider, ButtonBase, Popper, Paper, Fade } from '@mui/material';
10
10
  import { i18n } from '@lingui/core';
11
- import { create } from 'zustand';
12
- import { AddOutlined, SearchOutlined, MonitorOutlined, PhoneIphoneOutlined, MoreVertOutlined, DriveFileRenameOutlineOutlined, ContentCopyOutlined, LibraryAddOutlined, FileUploadOutlined, FileDownloadOutlined, DeleteOutlined, InsertDriveFileOutlined, DescriptionOutlined, EditOutlined, PreviewOutlined, CodeOutlined, SubjectOutlined, DataObjectOutlined, LastPageOutlined, AppRegistrationOutlined, CloudUploadOutlined, SaveOutlined, SaveAsOutlined, ViewColumnSharp, ExpandMore, ChevronRight, KeyboardArrowUp, KeyboardArrowDown, FirstPageOutlined, MenuOutlined, GridOnOutlined, SquareOutlined, CheckOutlined, InputOutlined, DeleteOutline, RoundedCornerOutlined, AspectRatioOutlined, HeightOutlined, CollectionsOutlined, ErrorOutlineOutlined, VerticalAlignTopOutlined, VerticalAlignCenterOutlined, VerticalAlignBottomOutlined, SpaceBarOutlined, BusinessOutlined, ViewColumnOutlined, HtmlOutlined, Crop32Outlined, HorizontalRuleOutlined, ContactMailOutlined, AccountCircleOutlined, ImageOutlined, SmartButtonOutlined, NotesOutlined, HMobiledataOutlined, DashboardOutlined, CloseOutlined, AlignVerticalTopOutlined, AlignVerticalBottomOutlined, AlignHorizontalLeftOutlined, AlignHorizontalRightOutlined, FormatAlignLeftOutlined, FormatAlignCenterOutlined, FormatAlignRightOutlined, FormatLineSpacingOutlined, TextFieldsOutlined, FormatBoldOutlined, FormatItalicOutlined, LinkOutlined, ArrowUpwardOutlined, ArrowDownwardOutlined } from '@mui/icons-material';
11
+ import { create, useStore } from 'zustand';
12
+ import { temporal } from 'zundo';
13
+ import { AddOutlined, SearchOutlined, MonitorOutlined, PhoneIphoneOutlined, MoreVertOutlined, DriveFileRenameOutlineOutlined, ContentCopyOutlined, LibraryAddOutlined, FileUploadOutlined, FileDownloadOutlined, DeleteOutlined, InsertDriveFileOutlined, DescriptionOutlined, EditOutlined, PreviewOutlined, CodeOutlined, SubjectOutlined, DataObjectOutlined, UndoOutlined, RedoOutlined, LastPageOutlined, AppRegistrationOutlined, CloudUploadOutlined, SaveOutlined, SaveAsOutlined, ViewColumnSharp, ExpandMore, ChevronRight, KeyboardArrowUp, KeyboardArrowDown, FirstPageOutlined, MenuOutlined, GridOnOutlined, SquareOutlined, CheckOutlined, InputOutlined, DeleteOutline, RoundedCornerOutlined, AspectRatioOutlined, HeightOutlined, CollectionsOutlined, ErrorOutlineOutlined, VerticalAlignTopOutlined, VerticalAlignCenterOutlined, VerticalAlignBottomOutlined, SpaceBarOutlined, BusinessOutlined, ViewColumnOutlined, HtmlOutlined, Crop32Outlined, HorizontalRuleOutlined, ContactMailOutlined, AccountCircleOutlined, ImageOutlined, SmartButtonOutlined, NotesOutlined, HMobiledataOutlined, DashboardOutlined, CloseOutlined, AlignVerticalTopOutlined, AlignVerticalBottomOutlined, AlignHorizontalLeftOutlined, AlignHorizontalRightOutlined, FormatAlignLeftOutlined, FormatAlignCenterOutlined, FormatAlignRightOutlined, FormatLineSpacingOutlined, TextFieldsOutlined, FormatBoldOutlined, FormatItalicOutlined, LinkOutlined, ArrowUpwardOutlined, ArrowDownwardOutlined } from '@mui/icons-material';
13
14
  import { HexColorPicker, HexColorInput } from 'react-colorful';
14
15
  import hljs from 'highlight.js';
15
16
  import jsonHighlighter from 'highlight.js/lib/languages/json';
@@ -2045,20 +2046,48 @@ var EMPTY_DOCUMENT = {
2045
2046
  }
2046
2047
  }
2047
2048
  };
2048
- var editorStateStore = create(() => ({
2049
- document: EMPTY_DOCUMENT,
2050
- selectedBlockId: null,
2051
- selectedSidebarTab: "styles",
2052
- selectedMainTab: "editor",
2053
- selectedScreenSize: "desktop",
2054
- inspectorDrawerOpen: true,
2055
- samplesDrawerOpen: true,
2056
- persistenceEnabled: false,
2057
- lastFocusedEditable: null,
2058
- hoveredBlockId: null,
2059
- draggingBlock: null,
2060
- workspaceBackground: "checkerboard"
2061
- }));
2049
+ var COALESCE_MS = 300;
2050
+ function leadingThrottle(fn, wait) {
2051
+ let last = Number.NEGATIVE_INFINITY;
2052
+ return (...args) => {
2053
+ const now = Date.now();
2054
+ if (now - last >= wait) {
2055
+ last = now;
2056
+ fn(...args);
2057
+ }
2058
+ };
2059
+ }
2060
+ var editorStateStore = create()(
2061
+ temporal(
2062
+ () => ({
2063
+ document: EMPTY_DOCUMENT,
2064
+ selectedBlockId: null,
2065
+ selectedSidebarTab: "styles",
2066
+ selectedMainTab: "editor",
2067
+ selectedScreenSize: "desktop",
2068
+ inspectorDrawerOpen: true,
2069
+ samplesDrawerOpen: true,
2070
+ persistenceEnabled: false,
2071
+ lastFocusedEditable: null,
2072
+ hoveredBlockId: null,
2073
+ draggingBlock: null,
2074
+ workspaceBackground: "checkerboard"
2075
+ }),
2076
+ {
2077
+ limit: 100,
2078
+ // Only the document participates in history — selection, drawers, tabs
2079
+ // and other UI state are intentionally excluded.
2080
+ partialize: (state) => ({ document: state.document }),
2081
+ // Skip UI-only state changes: if the document reference is unchanged,
2082
+ // no history entry is recorded.
2083
+ equality: (a, b) => a.document === b.document,
2084
+ handleSet: (handleSet) => leadingThrottle(
2085
+ (pastState, replace, currentState) => handleSet(pastState, replace, currentState),
2086
+ COALESCE_MS
2087
+ )
2088
+ }
2089
+ )
2090
+ );
2062
2091
  function useDocument() {
2063
2092
  return editorStateStore((s) => s.document);
2064
2093
  }
@@ -2101,11 +2130,15 @@ function setSidebarTab(selectedSidebarTab) {
2101
2130
  return editorStateStore.setState({ selectedSidebarTab });
2102
2131
  }
2103
2132
  function resetDocument(document2) {
2104
- return editorStateStore.setState({
2133
+ const temporalApi = editorStateStore.temporal.getState();
2134
+ temporalApi.pause();
2135
+ editorStateStore.setState({
2105
2136
  document: document2,
2106
2137
  selectedSidebarTab: "styles",
2107
2138
  selectedBlockId: null
2108
2139
  });
2140
+ temporalApi.clear();
2141
+ temporalApi.resume();
2109
2142
  }
2110
2143
  function getDocument() {
2111
2144
  return editorStateStore.getState().document;
@@ -2116,6 +2149,9 @@ function setDocument(document2) {
2116
2149
  document: __spreadValues(__spreadValues({}, originalDocument), document2)
2117
2150
  });
2118
2151
  }
2152
+ function replaceDocument(document2) {
2153
+ editorStateStore.setState({ document: document2 });
2154
+ }
2119
2155
  function toggleInspectorDrawerOpen() {
2120
2156
  const inspectorDrawerOpen = !editorStateStore.getState().inspectorDrawerOpen;
2121
2157
  return editorStateStore.setState({ inspectorDrawerOpen });
@@ -2160,6 +2196,18 @@ function setWorkspaceBackground(workspaceBackground) {
2160
2196
  function setLastFocusedEditable(lastFocusedEditable) {
2161
2197
  return editorStateStore.setState({ lastFocusedEditable });
2162
2198
  }
2199
+ function undo() {
2200
+ editorStateStore.temporal.getState().undo();
2201
+ }
2202
+ function redo() {
2203
+ editorStateStore.temporal.getState().redo();
2204
+ }
2205
+ function useCanUndo() {
2206
+ return useStore(editorStateStore.temporal, (s) => s.pastStates.length > 0);
2207
+ }
2208
+ function useCanRedo() {
2209
+ return useStore(editorStateStore.temporal, (s) => s.futureStates.length > 0);
2210
+ }
2163
2211
 
2164
2212
  // src/app/save-payload.ts
2165
2213
  var ROOT_BLOCK_ID = "root";
@@ -6852,7 +6900,7 @@ function EmailLayoutEditor(props) {
6852
6900
  }
6853
6901
  }
6854
6902
  delete nDocument[selectedBlockId];
6855
- resetDocument(nDocument);
6903
+ replaceDocument(nDocument);
6856
6904
  }, [selectedBlockId, document2]);
6857
6905
  const handleCopy = useCallback((e) => {
6858
6906
  if (!(e.metaKey || e.ctrlKey) || e.key !== "c") return;
@@ -6895,7 +6943,7 @@ function EmailLayoutEditor(props) {
6895
6943
  childrenIds: currentChildrenIds
6896
6944
  })
6897
6945
  };
6898
- resetDocument(doc);
6946
+ replaceDocument(doc);
6899
6947
  setSelectedBlockId(newRootId);
6900
6948
  }), [document2, childrenIds, selectedBlockId, currentBlockId]);
6901
6949
  useEffect(() => {
@@ -6936,7 +6984,6 @@ function EmailLayoutEditor(props) {
6936
6984
  }
6937
6985
  }
6938
6986
  );
6939
- const WORKSPACE_BG = "#e7e8ec";
6940
6987
  const CARD_MAX_WIDTH = 664;
6941
6988
  const cardStyle = {
6942
6989
  maxWidth: CARD_MAX_WIDTH,
@@ -6952,7 +6999,6 @@ function EmailLayoutEditor(props) {
6952
6999
  setSelectedBlockId(null);
6953
7000
  },
6954
7001
  style: __spreadProps(__spreadValues({}, baseStyle), {
6955
- backgroundColor: WORKSPACE_BG,
6956
7002
  padding: "32px",
6957
7003
  width: "100%",
6958
7004
  minHeight: "100%"
@@ -6977,7 +7023,6 @@ function EmailLayoutEditor(props) {
6977
7023
  setSelectedBlockId(null);
6978
7024
  },
6979
7025
  style: __spreadProps(__spreadValues({}, baseStyle), {
6980
- backgroundColor: WORKSPACE_BG,
6981
7026
  padding: "32px 16px",
6982
7027
  width: "100%",
6983
7028
  minHeight: "100%"
@@ -8626,6 +8671,64 @@ function SaveBar({ loadTemplates, saveAs }) {
8626
8671
  }
8627
8672
  ));
8628
8673
  }
8674
+ function isMac() {
8675
+ if (typeof navigator === "undefined") return false;
8676
+ const platform = (navigator.platform || "").toLowerCase();
8677
+ if (platform.includes("mac")) return true;
8678
+ const ua = (navigator.userAgent || "").toLowerCase();
8679
+ return ua.includes("mac") && !ua.includes("windows");
8680
+ }
8681
+ function isEditableTarget(target) {
8682
+ if (!(target instanceof HTMLElement)) return false;
8683
+ const tag = target.tagName;
8684
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
8685
+ if (target.isContentEditable) return true;
8686
+ return false;
8687
+ }
8688
+ function UndoRedoButtons() {
8689
+ const canUndo = useCanUndo();
8690
+ const canRedo = useCanRedo();
8691
+ const mac = isMac();
8692
+ const modKey = mac ? "\u2318" : "Ctrl";
8693
+ const undoHint = `${modKey}+Z`;
8694
+ const redoHint = mac ? `${modKey}+\u21E7+Z` : `${modKey}+Shift+Z / ${modKey}+Y`;
8695
+ useEffect(() => {
8696
+ const onKeyDown = (e) => {
8697
+ const mod = mac ? e.metaKey : e.ctrlKey;
8698
+ if (!mod) return;
8699
+ if (isEditableTarget(e.target)) return;
8700
+ const key = e.key.toLowerCase();
8701
+ if (key === "z" && !e.shiftKey) {
8702
+ e.preventDefault();
8703
+ undo();
8704
+ } else if (key === "z" && e.shiftKey || key === "y" && !mac) {
8705
+ e.preventDefault();
8706
+ redo();
8707
+ }
8708
+ };
8709
+ window.addEventListener("keydown", onKeyDown);
8710
+ return () => window.removeEventListener("keydown", onKeyDown);
8711
+ }, [mac]);
8712
+ return /* @__PURE__ */ React57.createElement(Stack, { direction: "row", spacing: 0.5, alignItems: "center" }, /* @__PURE__ */ React57.createElement(Tooltip, { title: `${t("undo.tooltip", "Undo")} (${undoHint})` }, /* @__PURE__ */ React57.createElement("span", null, /* @__PURE__ */ React57.createElement(
8713
+ IconButton,
8714
+ {
8715
+ size: "small",
8716
+ onClick: undo,
8717
+ disabled: !canUndo,
8718
+ "aria-label": t("undo.label", "Undo")
8719
+ },
8720
+ /* @__PURE__ */ React57.createElement(UndoOutlined, { fontSize: "small" })
8721
+ ))), /* @__PURE__ */ React57.createElement(Tooltip, { title: `${t("redo.tooltip", "Redo")} (${redoHint})` }, /* @__PURE__ */ React57.createElement("span", null, /* @__PURE__ */ React57.createElement(
8722
+ IconButton,
8723
+ {
8724
+ size: "small",
8725
+ onClick: redo,
8726
+ disabled: !canRedo,
8727
+ "aria-label": t("redo.label", "Redo")
8728
+ },
8729
+ /* @__PURE__ */ React57.createElement(RedoOutlined, { fontSize: "small" })
8730
+ ))));
8731
+ }
8629
8732
  function SubjectInput() {
8630
8733
  var _a;
8631
8734
  const document2 = useDocument();
@@ -8854,7 +8957,7 @@ function ImageDropPasteHandler({ enabled, children }) {
8854
8957
 
8855
8958
  // src/app/email-canvas/index.tsx
8856
8959
  var WORKSPACE_SOLID = "#e7e8ec";
8857
- var WORKSPACE_CHECKERBOARD = "repeating-conic-gradient(#eceef2 0% 25%, #dfe1e6 0% 50%) 50% / 24px 24px";
8960
+ var WORKSPACE_CHECKERBOARD = "repeating-conic-gradient(#eceef2 0% 25%, #dfe1e6 0% 50%) 50% / 12px 12px";
8858
8961
  function TemplatePanel2({ loadTemplates, saveAs, samplesDrawerEnabled = true }) {
8859
8962
  const document2 = useDocument();
8860
8963
  const selectedMainTab = useSelectedMainTab();
@@ -8924,7 +9027,7 @@ function TemplatePanel2({ loadTemplates, saveAs, samplesDrawerEnabled = true })
8924
9027
  alignItems: "center"
8925
9028
  },
8926
9029
  samplesDrawerEnabled && /* @__PURE__ */ React57.createElement(ToggleSamplesPanelButton, null),
8927
- /* @__PURE__ */ React57.createElement(Stack, { px: 2, direction: "row", gap: 2, width: "100%", justifyContent: "space-between", alignItems: "center" }, /* @__PURE__ */ React57.createElement(Stack, { direction: "row", spacing: 2 }, /* @__PURE__ */ React57.createElement(MainTabsGroup, null)), /* @__PURE__ */ React57.createElement(Stack, { direction: "row", spacing: 2 }, /* @__PURE__ */ React57.createElement(ToggleButtonGroup, { value: selectedScreenSize, exclusive: true, size: "small", onChange: handleScreenSizeChange }, /* @__PURE__ */ React57.createElement(ToggleButton, { value: "desktop" }, /* @__PURE__ */ React57.createElement(Tooltip, { title: "Desktop view" }, /* @__PURE__ */ React57.createElement(MonitorOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57.createElement(ToggleButton, { value: "mobile" }, /* @__PURE__ */ React57.createElement(Tooltip, { title: "Mobile view" }, /* @__PURE__ */ React57.createElement(PhoneIphoneOutlined, { fontSize: "small" })))))),
9030
+ /* @__PURE__ */ React57.createElement(Stack, { px: 2, direction: "row", gap: 2, width: "100%", justifyContent: "space-between", alignItems: "center" }, /* @__PURE__ */ React57.createElement(Stack, { direction: "row", spacing: 2 }, /* @__PURE__ */ React57.createElement(MainTabsGroup, null)), /* @__PURE__ */ React57.createElement(Stack, { direction: "row", spacing: 2, alignItems: "center" }, selectedMainTab === "editor" && /* @__PURE__ */ React57.createElement(UndoRedoButtons, null), /* @__PURE__ */ React57.createElement(ToggleButtonGroup, { value: selectedScreenSize, exclusive: true, size: "small", onChange: handleScreenSizeChange }, /* @__PURE__ */ React57.createElement(ToggleButton, { value: "desktop" }, /* @__PURE__ */ React57.createElement(Tooltip, { title: "Desktop view" }, /* @__PURE__ */ React57.createElement(MonitorOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57.createElement(ToggleButton, { value: "mobile" }, /* @__PURE__ */ React57.createElement(Tooltip, { title: "Mobile view" }, /* @__PURE__ */ React57.createElement(PhoneIphoneOutlined, { fontSize: "small" })))))),
8928
9031
  /* @__PURE__ */ React57.createElement(ToggleInspectorPanelButton, null)
8929
9032
  ), selectedMainTab === "editor" && /* @__PURE__ */ React57.createElement(SubjectInput, null), selectedMainTab === "preview" && /* @__PURE__ */ React57.createElement(SubjectPreview, null), /* @__PURE__ */ React57.createElement(ImageDropPasteHandler, { enabled: selectedMainTab === "editor" }, /* @__PURE__ */ React57.createElement(
8930
9033
  Box,