@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/README.md +14 -0
- package/dist/index.cjs +125 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +127 -24
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
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 {
|
|
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
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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% /
|
|
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,
|