monto-email-builder 1.0.6 → 1.0.8

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.
Files changed (31) hide show
  1. package/README.md +32 -1
  2. package/dist/App/InspectorDrawer/ConfigurationPanel/input-panels/ImageSidebarPanel.d.ts.map +1 -1
  3. package/dist/App/InspectorDrawer/index.d.ts.map +1 -1
  4. package/dist/App/SamplesDrawer/SidebarButton.d.ts +2 -2
  5. package/dist/App/SamplesDrawer/SidebarButton.d.ts.map +1 -1
  6. package/dist/App/SamplesDrawer/index.d.ts.map +1 -1
  7. package/dist/App/TemplatePanel/DownloadJson/index.d.ts.map +1 -1
  8. package/dist/App/TemplatePanel/ImportJson/index.d.ts.map +1 -1
  9. package/dist/App/TemplatePanel/NameInput.d.ts +3 -0
  10. package/dist/App/TemplatePanel/NameInput.d.ts.map +1 -0
  11. package/dist/App/TemplatePanel/SaveAndExitButton.d.ts +3 -0
  12. package/dist/App/TemplatePanel/SaveAndExitButton.d.ts.map +1 -0
  13. package/dist/App/TemplatePanel/SaveButton.d.ts +3 -0
  14. package/dist/App/TemplatePanel/SaveButton.d.ts.map +1 -0
  15. package/dist/App/TemplatePanel/helper/highlighters.d.ts.map +1 -1
  16. package/dist/App/TemplatePanel/index.d.ts.map +1 -1
  17. package/dist/App/index.d.ts.map +1 -1
  18. package/dist/EmailBuilder/index.d.ts +28 -3
  19. package/dist/EmailBuilder/index.d.ts.map +1 -1
  20. package/dist/documents/editor/EditorContext.d.ts +14 -1
  21. package/dist/documents/editor/EditorContext.d.ts.map +1 -1
  22. package/dist/getConfiguration/index.d.ts +7 -0
  23. package/dist/getConfiguration/index.d.ts.map +1 -1
  24. package/dist/getConfiguration/sample/basic-template.d.ts +4 -0
  25. package/dist/getConfiguration/sample/basic-template.d.ts.map +1 -0
  26. package/dist/getConfiguration/sample/reservation-reminder.d.ts.map +1 -1
  27. package/dist/index.js +523 -228
  28. package/dist/{samples-SfLrANbd.js → samples-BnRgwIcn.js} +268 -2259
  29. package/package.json +8 -13
  30. package/dist/main.d.ts +0 -2
  31. package/dist/main.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import React, { useState, useRef, useEffect, Fragment as Fragment$1, useContext, createContext, useMemo } from "react";
3
- import { Box, Typography, Stack, InputLabel, ToggleButtonGroup, Slider, TextField, Button, Menu, ButtonBase, MenuItem, ToggleButton, FormControlLabel, Switch, Drawer, Tabs, Tab, IconButton, Divider, Fade, Paper, Tooltip, Dialog, DialogTitle, DialogContent, Link, DialogActions, Alert, Snackbar, useTheme, ThemeProvider, CssBaseline } from "@mui/material";
2
+ import { useState, useRef, useEffect, Fragment as Fragment$1, useContext, createContext, useMemo } from "react";
3
+ import { Box, Typography, Stack, InputLabel, ToggleButtonGroup, Slider, TextField, Button, Menu, ButtonBase, MenuItem, ToggleButton, CircularProgress, FormControlLabel, Switch, Drawer, Tabs, Tab, Divider, Fade, IconButton, Paper, Tooltip, Dialog, DialogTitle, DialogContent, Link, DialogActions, Alert, useTheme, ThemeProvider, CssBaseline } from "@mui/material";
4
4
  import { create } from "zustand";
5
- import { E as EMPTY_EMAIL_MESSAGE } from "./samples-SfLrANbd.js";
5
+ import { E as EMPTY_EMAIL_MESSAGE } from "./samples-BnRgwIcn.js";
6
6
  import { createTheme, alpha, lighten, darken } from "@mui/material/styles";
7
- import { CloseOutlined, AddOutlined, TextFieldsOutlined, AlignVerticalTopOutlined, AlignVerticalBottomOutlined, AlignHorizontalLeftOutlined, AlignHorizontalRightOutlined, FormatAlignLeftOutlined, FormatAlignCenterOutlined, FormatAlignRightOutlined, RoundedCornerOutlined, AspectRatioOutlined, SpaceBarOutlined, VerticalAlignTopOutlined, VerticalAlignCenterOutlined, VerticalAlignBottomOutlined, HeightOutlined, LinkOutlined, CloudUploadOutlined, LanguageOutlined, HMobiledataOutlined, NotesOutlined, SmartButtonOutlined, ImageOutlined, AccountCircleOutlined, HorizontalRuleOutlined, Crop32Outlined, HtmlOutlined, ViewColumnOutlined, LibraryAddOutlined, ArrowUpwardOutlined, ArrowDownwardOutlined, DeleteOutlined, LastPageOutlined, AppRegistrationOutlined, FirstPageOutlined, MenuOutlined, FileDownloadOutlined, FileUploadOutlined, EditOutlined, PreviewOutlined, CodeOutlined, DataObjectOutlined, IosShareOutlined, MonitorOutlined, PhoneIphoneOutlined } from "@mui/icons-material";
7
+ import { CloseOutlined, AddOutlined, TextFieldsOutlined, AlignVerticalTopOutlined, AlignVerticalBottomOutlined, AlignHorizontalLeftOutlined, AlignHorizontalRightOutlined, FormatAlignLeftOutlined, FormatAlignCenterOutlined, FormatAlignRightOutlined, RoundedCornerOutlined, AspectRatioOutlined, SpaceBarOutlined, VerticalAlignTopOutlined, VerticalAlignCenterOutlined, VerticalAlignBottomOutlined, HeightOutlined, LinkOutlined, CloudUploadOutlined, HMobiledataOutlined, NotesOutlined, SmartButtonOutlined, ImageOutlined, AccountCircleOutlined, HorizontalRuleOutlined, Crop32Outlined, HtmlOutlined, ViewColumnOutlined, LibraryAddOutlined, ArrowUpwardOutlined, ArrowDownwardOutlined, DeleteOutlined, LastPageOutlined, AppRegistrationOutlined, FirstPageOutlined, MenuOutlined, FileDownloadOutlined, FileUploadOutlined, EditOutlined, PreviewOutlined, CodeOutlined, DataObjectOutlined, ExitToAppOutlined, SaveOutlined, MonitorOutlined, PhoneIphoneOutlined } from "@mui/icons-material";
8
8
  import { AvatarPropsDefaults, AvatarPropsSchema, Avatar } from "@usewaypoint/block-avatar";
9
9
  import { HexColorPicker, HexColorInput } from "react-colorful";
10
10
  import { ButtonPropsDefaults, ButtonPropsSchema, Button as Button$1 } from "@usewaypoint/block-button";
@@ -19,58 +19,30 @@ import { SpacerPropsDefaults, SpacerPropsSchema, Spacer } from "@usewaypoint/blo
19
19
  import { TextPropsSchema, Text } from "@usewaypoint/block-text";
20
20
  import { renderToStaticMarkup, Reader } from "@usewaypoint/email-builder";
21
21
  import { buildBlockConfigurationDictionary, buildBlockComponent, buildBlockConfigurationSchema } from "@usewaypoint/document-core";
22
- const sampleTemplates = {
23
- welcome: () => import("./samples-SfLrANbd.js").then((n) => n.w).then((m) => m.default),
24
- "one-time-password": () => import("./samples-SfLrANbd.js").then((n) => n.o).then((m) => m.default),
25
- "order-ecomerce": () => import("./samples-SfLrANbd.js").then((n) => n.a).then((m) => m.default),
26
- "post-metrics-report": () => import("./samples-SfLrANbd.js").then((n) => n.p).then((m) => m.default),
27
- "reservation-reminder": () => import("./samples-SfLrANbd.js").then((n) => n.r).then((m) => m.default),
28
- "reset-password": () => import("./samples-SfLrANbd.js").then((n) => n.b).then((m) => m.default),
29
- "respond-to-message": () => import("./samples-SfLrANbd.js").then((n) => n.c).then((m) => m.default),
30
- "subscription-receipt": () => import("./samples-SfLrANbd.js").then((n) => n.s).then((m) => m.default)
31
- };
32
- const templateCache = {};
33
- function getConfiguration(template) {
34
- if (template.startsWith("#sample/")) {
35
- return EMPTY_EMAIL_MESSAGE;
36
- }
37
- if (template.startsWith("#code/")) {
38
- const encodedString = template.replace("#code/", "");
39
- const configurationString = decodeURIComponent(atob(encodedString));
40
- try {
41
- return JSON.parse(configurationString);
42
- } catch {
43
- console.error(`Couldn't load configuration from hash.`);
44
- }
45
- }
46
- return EMPTY_EMAIL_MESSAGE;
47
- }
48
- async function loadSampleTemplate(sampleName) {
49
- const loader = sampleTemplates[sampleName];
50
- if (!loader) {
51
- return EMPTY_EMAIL_MESSAGE;
52
- }
53
- if (templateCache[sampleName]) {
54
- return templateCache[sampleName];
55
- }
56
- const template = await loader();
57
- templateCache[sampleName] = template;
58
- return template;
59
- }
60
22
  const common$1 = {
61
23
  newDocument: "New Blank Document",
62
24
  useBuiltInTemplates: "Use Built-in Templates",
63
- emailBuilder: "Email Builder"
25
+ emailBuilder: "Email Builder",
26
+ save: "Save",
27
+ saving: "Saving...",
28
+ saveAndExit: "Save and Exit",
29
+ namePlaceholder: "Enter template name",
30
+ unnamedTemplate: "",
31
+ desktopView: "Desktop view",
32
+ mobileView: "Mobile view",
33
+ downloadJson: "Download JSON file",
34
+ importJson: "Import JSON"
64
35
  };
65
36
  const samples$1 = {
66
- welcomeEmail: "Welcome email",
37
+ welcomeEmail: "Marketing Template",
67
38
  oneTimePasscode: "One-time passcode (OTP)",
68
39
  resetPassword: "Reset password",
69
40
  orderEcommerce: "E-commerce receipt",
70
41
  subscriptionReceipt: "Subscription receipt",
71
- reservationReminder: "Reservation reminder",
42
+ reservationReminder: "Verification Code Template",
72
43
  postMetrics: "Post metrics",
73
- respondToMessage: "Respond to inquiry"
44
+ respondToMessage: "Respond to inquiry",
45
+ quickStart: "Quick Start"
74
46
  };
75
47
  const inspector$1 = {
76
48
  styles: "Styles",
@@ -121,17 +93,27 @@ const en = {
121
93
  const common = {
122
94
  newDocument: "新建空白文档",
123
95
  useBuiltInTemplates: "使用内置模板",
124
- emailBuilder: "邮件编辑器"
96
+ emailBuilder: "邮件编辑器",
97
+ save: "保存",
98
+ saving: "保存中...",
99
+ saveAndExit: "保存并退出",
100
+ namePlaceholder: "这里放置接受的 name",
101
+ unnamedTemplate: "",
102
+ desktopView: "桌面视图",
103
+ mobileView: "移动视图",
104
+ downloadJson: "下载 JSON 文件",
105
+ importJson: "导入 JSON"
125
106
  };
126
107
  const samples = {
127
- welcomeEmail: "欢迎邮件",
108
+ welcomeEmail: "营销模板",
128
109
  oneTimePasscode: "一次性密码 (OTP)",
129
110
  resetPassword: "重置密码",
130
111
  orderEcommerce: "电商收据",
131
112
  subscriptionReceipt: "订阅收据",
132
- reservationReminder: "预约提醒",
113
+ reservationReminder: "验证码模板",
133
114
  postMetrics: "指标报告",
134
- respondToMessage: "回复询问"
115
+ respondToMessage: "回复询问",
116
+ quickStart: "快速开始"
135
117
  };
136
118
  const inspector = {
137
119
  styles: "样式",
@@ -222,7 +204,7 @@ function setLanguage$1(lang) {
222
204
  }
223
205
  }
224
206
  const editorStateStore = create((set, get) => ({
225
- document: getConfiguration(typeof window !== "undefined" ? window.location.hash : ""),
207
+ document: EMPTY_EMAIL_MESSAGE,
226
208
  selectedBlockId: null,
227
209
  selectedSidebarTab: "styles",
228
210
  selectedMainTab: "editor",
@@ -230,7 +212,11 @@ const editorStateStore = create((set, get) => ({
230
212
  inspectorDrawerOpen: true,
231
213
  samplesDrawerOpen: true,
232
214
  language: getLanguage(),
233
- onChange: void 0
215
+ onChange: void 0,
216
+ saveHandler: void 0,
217
+ saveAndExitHandler: void 0,
218
+ name: "",
219
+ onNameChange: void 0
234
220
  }));
235
221
  function useDocument() {
236
222
  return editorStateStore((s) => s.document);
@@ -271,22 +257,22 @@ function setSelectedBlockId(selectedBlockId) {
271
257
  function setSidebarTab(selectedSidebarTab) {
272
258
  return editorStateStore.setState({ selectedSidebarTab });
273
259
  }
274
- function resetDocument(document) {
260
+ function resetDocument(document2) {
275
261
  editorStateStore.setState({
276
- document,
262
+ document: document2,
277
263
  selectedSidebarTab: "styles",
278
264
  selectedBlockId: null
279
265
  });
280
266
  const onChange = editorStateStore.getState().onChange;
281
267
  if (onChange) {
282
- onChange(document);
268
+ onChange(document2);
283
269
  }
284
270
  }
285
- function setDocument(document) {
271
+ function setDocument(document2) {
286
272
  const originalDocument = editorStateStore.getState().document;
287
273
  const newDocument = {
288
274
  ...originalDocument,
289
- ...document
275
+ ...document2
290
276
  };
291
277
  editorStateStore.setState({
292
278
  document: newDocument
@@ -323,8 +309,48 @@ function setLanguage(lang) {
323
309
  setLanguage$1(lang);
324
310
  return editorStateStore.setState({ language: lang });
325
311
  }
312
+ function useSaveHandler() {
313
+ return editorStateStore((s) => s.saveHandler);
314
+ }
315
+ function setSaveHandler(handler) {
316
+ return editorStateStore.setState({ saveHandler: handler });
317
+ }
318
+ async function saveDocument() {
319
+ const document2 = editorStateStore.getState().document;
320
+ const saveHandler = editorStateStore.getState().saveHandler;
321
+ if (saveHandler) {
322
+ await saveHandler(document2);
323
+ }
324
+ }
325
+ function useSaveAndExitHandler() {
326
+ return editorStateStore((s) => s.saveAndExitHandler);
327
+ }
328
+ function setSaveAndExitHandler(handler) {
329
+ return editorStateStore.setState({ saveAndExitHandler: handler });
330
+ }
331
+ function saveAndExitDocument(onExit) {
332
+ const document2 = editorStateStore.getState().document;
333
+ if (onExit) {
334
+ Promise.resolve(onExit(document2)).catch((error) => {
335
+ console.error("Error in exit handler:", error);
336
+ });
337
+ }
338
+ }
339
+ function useName() {
340
+ return editorStateStore((s) => s.name);
341
+ }
342
+ function setName(name) {
343
+ editorStateStore.setState({ name });
344
+ const onNameChange = editorStateStore.getState().onNameChange;
345
+ if (onNameChange) {
346
+ onNameChange(name);
347
+ }
348
+ }
349
+ function setOnNameChange(handler) {
350
+ return editorStateStore.setState({ onNameChange: handler });
351
+ }
326
352
  const BRAND_NAVY = "#212443";
327
- const BRAND_BLUE = "#0079CC";
353
+ const BRAND_BLUE = "#1E40AF";
328
354
  const BRAND_GREEN = "#1F8466";
329
355
  const BRAND_RED = "#E81212";
330
356
  const BRAND_YELLOW = "#F6DC9F";
@@ -1944,6 +1970,7 @@ function HtmlSidebarPanel({ data, setData }) {
1944
1970
  }
1945
1971
  function ImageSidebarPanel({ data, setData }) {
1946
1972
  var _a, _b, _c, _d, _e, _f;
1973
+ const { t: t2 } = useTranslation();
1947
1974
  const [, setErrors] = useState(null);
1948
1975
  const [uploadMode, setUploadMode] = useState("url");
1949
1976
  const [uploading, setUploading] = useState(false);
@@ -1984,7 +2011,7 @@ function ImageSidebarPanel({ data, setData }) {
1984
2011
  var _a2;
1985
2012
  (_a2 = fileInputRef.current) == null ? void 0 : _a2.click();
1986
2013
  };
1987
- return /* @__PURE__ */ jsxs(BaseSidebarPanel, { title: "Image block", children: [
2014
+ return /* @__PURE__ */ jsxs(BaseSidebarPanel, { title: t2("image.imageBlock"), children: [
1988
2015
  /* @__PURE__ */ jsxs(Stack, { spacing: 1, children: [
1989
2016
  /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, children: [
1990
2017
  /* @__PURE__ */ jsxs(
@@ -1997,7 +2024,7 @@ function ImageSidebarPanel({ data, setData }) {
1997
2024
  fullWidth: true,
1998
2025
  children: [
1999
2026
  /* @__PURE__ */ jsx(LinkOutlined, { fontSize: "small", sx: { mr: 0.5 } }),
2000
- "URL"
2027
+ t2("image.url")
2001
2028
  ]
2002
2029
  }
2003
2030
  ),
@@ -2012,7 +2039,7 @@ function ImageSidebarPanel({ data, setData }) {
2012
2039
  disabled: !imageUploadHandler,
2013
2040
  children: [
2014
2041
  /* @__PURE__ */ jsx(CloudUploadOutlined, { fontSize: "small", sx: { mr: 0.5 } }),
2015
- "上传"
2042
+ t2("image.upload")
2016
2043
  ]
2017
2044
  }
2018
2045
  )
@@ -2020,7 +2047,7 @@ function ImageSidebarPanel({ data, setData }) {
2020
2047
  uploadMode === "url" ? /* @__PURE__ */ jsx(
2021
2048
  TextInput,
2022
2049
  {
2023
- label: "Source URL",
2050
+ label: t2("image.sourceUrl"),
2024
2051
  defaultValue: ((_a = data.props) == null ? void 0 : _a.url) ?? "",
2025
2052
  onChange: (v) => {
2026
2053
  const url = v.trim().length === 0 ? null : v.trim();
@@ -2042,20 +2069,20 @@ function ImageSidebarPanel({ data, setData }) {
2042
2069
  Button,
2043
2070
  {
2044
2071
  variant: "outlined",
2045
- startIcon: /* @__PURE__ */ jsx(CloudUploadOutlined, {}),
2072
+ startIcon: uploading ? /* @__PURE__ */ jsx(CircularProgress, { size: 16 }) : /* @__PURE__ */ jsx(CloudUploadOutlined, {}),
2046
2073
  onClick: handleUploadClick,
2047
2074
  disabled: uploading || !imageUploadHandler,
2048
2075
  fullWidth: true,
2049
- children: uploading ? "上传中..." : "选择图片"
2076
+ children: uploading ? t2("image.uploading") : t2("image.selectImage")
2050
2077
  }
2051
2078
  ),
2052
- !imageUploadHandler && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", children: "请配置 imageUploadHandler 以启用本地上传功能" })
2079
+ !imageUploadHandler && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", children: t2("image.uploadHandlerNotConfigured") })
2053
2080
  ] })
2054
2081
  ] }),
2055
2082
  /* @__PURE__ */ jsx(
2056
2083
  TextInput,
2057
2084
  {
2058
- label: "Alt text",
2085
+ label: t2("image.altText"),
2059
2086
  defaultValue: ((_b = data.props) == null ? void 0 : _b.alt) ?? "",
2060
2087
  onChange: (alt) => updateData({ ...data, props: { ...data.props, alt } })
2061
2088
  }
@@ -2063,7 +2090,7 @@ function ImageSidebarPanel({ data, setData }) {
2063
2090
  /* @__PURE__ */ jsx(
2064
2091
  TextInput,
2065
2092
  {
2066
- label: "Click through URL",
2093
+ label: t2("image.clickThroughUrl"),
2067
2094
  defaultValue: ((_c = data.props) == null ? void 0 : _c.linkHref) ?? "",
2068
2095
  onChange: (v) => {
2069
2096
  const linkHref = v.trim().length === 0 ? null : v.trim();
@@ -2075,7 +2102,7 @@ function ImageSidebarPanel({ data, setData }) {
2075
2102
  /* @__PURE__ */ jsx(
2076
2103
  TextDimensionInput,
2077
2104
  {
2078
- label: "Width",
2105
+ label: t2("image.width"),
2079
2106
  defaultValue: (_d = data.props) == null ? void 0 : _d.width,
2080
2107
  onChange: (width) => updateData({ ...data, props: { ...data.props, width } })
2081
2108
  }
@@ -2083,7 +2110,7 @@ function ImageSidebarPanel({ data, setData }) {
2083
2110
  /* @__PURE__ */ jsx(
2084
2111
  TextDimensionInput,
2085
2112
  {
2086
- label: "Height",
2113
+ label: t2("image.height"),
2087
2114
  defaultValue: (_e = data.props) == null ? void 0 : _e.height,
2088
2115
  onChange: (height) => updateData({ ...data, props: { ...data.props, height } })
2089
2116
  }
@@ -2092,7 +2119,7 @@ function ImageSidebarPanel({ data, setData }) {
2092
2119
  /* @__PURE__ */ jsxs(
2093
2120
  RadioGroupInput,
2094
2121
  {
2095
- label: "Alignment",
2122
+ label: t2("image.alignment"),
2096
2123
  defaultValue: ((_f = data.props) == null ? void 0 : _f.contentAlignment) ?? "middle",
2097
2124
  onChange: (contentAlignment) => updateData({ ...data, props: { ...data.props, contentAlignment } }),
2098
2125
  children: [
@@ -2202,12 +2229,12 @@ function renderMessage(val) {
2202
2229
  }
2203
2230
  function ConfigurationPanel() {
2204
2231
  const { t: t2 } = useTranslation();
2205
- const document = useDocument();
2232
+ const document2 = useDocument();
2206
2233
  const selectedBlockId = useSelectedBlockId();
2207
2234
  if (!selectedBlockId) {
2208
2235
  return renderMessage(t2("inspector.clickBlockToInspect"));
2209
2236
  }
2210
- const block = document[selectedBlockId];
2237
+ const block = document2[selectedBlockId];
2211
2238
  if (!block) {
2212
2239
  return renderMessage(t2("inspector.blockNotFound", { id: selectedBlockId }));
2213
2240
  }
@@ -2273,7 +2300,8 @@ function InspectorDrawer() {
2273
2300
  PaperProps: {
2274
2301
  sx: {
2275
2302
  position: "relative",
2276
- height: "100%"
2303
+ height: "100%",
2304
+ zIndex: 0
2277
2305
  }
2278
2306
  },
2279
2307
  sx: {
@@ -2282,10 +2310,12 @@ function InspectorDrawer() {
2282
2310
  flexShrink: 0,
2283
2311
  flexGrow: 0,
2284
2312
  overflow: "hidden",
2313
+ zIndex: 0,
2285
2314
  "& .MuiDrawer-paper": {
2286
2315
  position: "relative",
2287
2316
  height: "100%",
2288
- width: "100%"
2317
+ width: "100%",
2318
+ zIndex: 0
2289
2319
  }
2290
2320
  },
2291
2321
  children: [
@@ -2298,59 +2328,51 @@ function InspectorDrawer() {
2298
2328
  }
2299
2329
  );
2300
2330
  }
2301
- function LanguageSwitcher() {
2302
- const language = useLanguage();
2303
- const [anchorEl, setAnchorEl] = React.useState(null);
2304
- const open = Boolean(anchorEl);
2305
- const handleClick = (event) => {
2306
- setAnchorEl(event.currentTarget);
2307
- };
2308
- const handleClose = () => {
2309
- setAnchorEl(null);
2310
- };
2311
- const handleLanguageChange = (lang) => {
2312
- setLanguage(lang);
2313
- handleClose();
2314
- };
2315
- return /* @__PURE__ */ jsxs(Fragment, { children: [
2316
- /* @__PURE__ */ jsx(IconButton, { onClick: handleClick, size: "small", sx: { color: "text.secondary" }, children: /* @__PURE__ */ jsx(LanguageOutlined, { fontSize: "small" }) }),
2317
- /* @__PURE__ */ jsxs(Menu, { anchorEl, open, onClose: handleClose, children: [
2318
- /* @__PURE__ */ jsx(MenuItem, { onClick: () => handleLanguageChange("zh"), selected: language === "zh", children: "中文" }),
2319
- /* @__PURE__ */ jsx(MenuItem, { onClick: () => handleLanguageChange("en"), selected: language === "en", children: "English" })
2320
- ] })
2321
- ] });
2331
+ const sampleTemplates = {
2332
+ welcome: () => import("./samples-BnRgwIcn.js").then((n) => n.w).then((m) => m.default),
2333
+ "one-time-password": () => import("./samples-BnRgwIcn.js").then((n) => n.o).then((m) => m.default),
2334
+ "order-ecomerce": () => import("./samples-BnRgwIcn.js").then((n) => n.a).then((m) => m.default),
2335
+ "post-metrics-report": () => import("./samples-BnRgwIcn.js").then((n) => n.p).then((m) => m.default),
2336
+ "reservation-reminder": () => import("./samples-BnRgwIcn.js").then((n) => n.r).then((m) => m.default),
2337
+ "reset-password": () => import("./samples-BnRgwIcn.js").then((n) => n.b).then((m) => m.default),
2338
+ "respond-to-message": () => import("./samples-BnRgwIcn.js").then((n) => n.c).then((m) => m.default),
2339
+ "subscription-receipt": () => import("./samples-BnRgwIcn.js").then((n) => n.s).then((m) => m.default),
2340
+ "basic-template": () => import("./samples-BnRgwIcn.js").then((n) => n.d).then((m) => m.default)
2341
+ };
2342
+ const templateCache = {};
2343
+ async function loadSampleTemplate(sampleName) {
2344
+ const loader = sampleTemplates[sampleName];
2345
+ if (!loader) {
2346
+ return EMPTY_EMAIL_MESSAGE;
2347
+ }
2348
+ if (templateCache[sampleName]) {
2349
+ return templateCache[sampleName];
2350
+ }
2351
+ const template = await loader();
2352
+ templateCache[sampleName] = template;
2353
+ return template;
2322
2354
  }
2323
- function SidebarButton({ href, children }) {
2355
+ function SidebarButton({ sampleName, children }) {
2324
2356
  const [loading, setLoading] = useState(false);
2325
2357
  const handleClick = async () => {
2326
- if (href.startsWith("#sample/")) {
2327
- setLoading(true);
2328
- try {
2329
- const sampleName = href.replace("#sample/", "");
2330
- const template = await loadSampleTemplate(sampleName);
2331
- resetDocument(template);
2332
- } catch (error) {
2333
- console.error("Failed to load template:", error);
2334
- } finally {
2335
- setLoading(false);
2336
- }
2337
- } else {
2338
- resetDocument(getConfiguration(href));
2358
+ setLoading(true);
2359
+ try {
2360
+ const template = await loadSampleTemplate(sampleName);
2361
+ resetDocument(template);
2362
+ } catch (error) {
2363
+ console.error("Failed to load template:", error);
2364
+ } finally {
2365
+ setLoading(false);
2339
2366
  }
2340
2367
  };
2341
- return /* @__PURE__ */ jsx(Button, { size: "small", href, onClick: handleClick, disabled: loading, children: loading ? "Loading..." : children });
2368
+ return /* @__PURE__ */ jsx(Button, { size: "small", onClick: handleClick, disabled: loading, children: loading ? "Loading..." : children });
2342
2369
  }
2343
2370
  const SAMPLES_DRAWER_WIDTH = 240;
2344
2371
  function SamplesDrawer() {
2345
2372
  const { t: t2 } = useTranslation();
2346
2373
  const samplesDrawerOpen = useSamplesDrawerOpen();
2347
- const currentHash = typeof window !== "undefined" ? window.location.hash : "";
2348
- const isNewDocument = !currentHash || currentHash === "#";
2349
2374
  const handleNewDocumentClick = () => {
2350
- resetDocument(getConfiguration("#"));
2351
- if (typeof window !== "undefined") {
2352
- window.location.hash = "#";
2353
- }
2375
+ resetDocument(EMPTY_EMAIL_MESSAGE);
2354
2376
  };
2355
2377
  return /* @__PURE__ */ jsx(
2356
2378
  Drawer,
@@ -2361,7 +2383,8 @@ function SamplesDrawer() {
2361
2383
  PaperProps: {
2362
2384
  sx: {
2363
2385
  position: "relative",
2364
- height: "100%"
2386
+ height: "100%",
2387
+ zIndex: 0
2365
2388
  }
2366
2389
  },
2367
2390
  sx: {
@@ -2370,10 +2393,12 @@ function SamplesDrawer() {
2370
2393
  flexShrink: 0,
2371
2394
  flexGrow: 0,
2372
2395
  overflow: "hidden",
2396
+ zIndex: 0,
2373
2397
  "& .MuiDrawer-paper": {
2374
2398
  position: "relative",
2375
2399
  height: "100%",
2376
- width: "100%"
2400
+ width: "100%",
2401
+ zIndex: 0
2377
2402
  }
2378
2403
  },
2379
2404
  children: /* @__PURE__ */ jsx(Stack, { spacing: 3, py: 1, px: 2, width: SAMPLES_DRAWER_WIDTH, children: /* @__PURE__ */ jsxs(Stack, { spacing: 2, sx: { "& .MuiButtonBase-root": { width: "100%", justifyContent: "flex-start" } }, children: [
@@ -2384,10 +2409,9 @@ function SamplesDrawer() {
2384
2409
  size: "small",
2385
2410
  variant: "contained",
2386
2411
  color: "primary",
2387
- href: "#",
2388
2412
  onClick: handleNewDocumentClick,
2389
2413
  sx: {
2390
- fontWeight: isNewDocument ? 600 : 400,
2414
+ fontWeight: 600,
2391
2415
  width: "100%",
2392
2416
  justifyContent: "flex-start"
2393
2417
  },
@@ -2398,8 +2422,9 @@ function SamplesDrawer() {
2398
2422
  /* @__PURE__ */ jsxs(Stack, { spacing: 1, children: [
2399
2423
  /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", sx: { px: 0.75, fontWeight: 500 }, children: t2("common.useBuiltInTemplates") }),
2400
2424
  /* @__PURE__ */ jsxs(Stack, { alignItems: "flex-start", children: [
2401
- /* @__PURE__ */ jsx(SidebarButton, { href: "#sample/welcome", children: t2("samples.welcomeEmail") }),
2402
- /* @__PURE__ */ jsx(SidebarButton, { href: "#sample/reservation-reminder", children: t2("samples.reservationReminder") })
2425
+ /* @__PURE__ */ jsx(SidebarButton, { sampleName: "basic-template", children: t2("samples.quickStart") }),
2426
+ /* @__PURE__ */ jsx(SidebarButton, { sampleName: "welcome", children: t2("samples.welcomeEmail") }),
2427
+ /* @__PURE__ */ jsx(SidebarButton, { sampleName: "reservation-reminder", children: t2("samples.reservationReminder") })
2403
2428
  ] })
2404
2429
  ] })
2405
2430
  ] }) })
@@ -2763,7 +2788,7 @@ function ColumnsContainerEditor({ style, props }) {
2763
2788
  }
2764
2789
  function ContainerEditor({ style, props }) {
2765
2790
  const childrenIds = (props == null ? void 0 : props.childrenIds) ?? [];
2766
- const document = useDocument();
2791
+ const document2 = useDocument();
2767
2792
  const currentBlockId = useCurrentBlockId();
2768
2793
  return /* @__PURE__ */ jsx(Container, { style, children: /* @__PURE__ */ jsx(
2769
2794
  EditorChildrenIds,
@@ -2775,7 +2800,7 @@ function ContainerEditor({ style, props }) {
2775
2800
  [currentBlockId]: {
2776
2801
  type: "Container",
2777
2802
  data: {
2778
- ...document[currentBlockId].data,
2803
+ ...document2[currentBlockId].data,
2779
2804
  props: { childrenIds: childrenIds2 }
2780
2805
  }
2781
2806
  }
@@ -2810,7 +2835,7 @@ function getFontFamily(fontFamily) {
2810
2835
  }
2811
2836
  function EmailLayoutEditor(props) {
2812
2837
  const childrenIds = props.childrenIds ?? [];
2813
- const document = useDocument();
2838
+ const document2 = useDocument();
2814
2839
  const currentBlockId = useCurrentBlockId();
2815
2840
  return /* @__PURE__ */ jsx(
2816
2841
  "div",
@@ -2863,7 +2888,7 @@ function EmailLayoutEditor(props) {
2863
2888
  [currentBlockId]: {
2864
2889
  type: "EmailLayout",
2865
2890
  data: {
2866
- ...document[currentBlockId].data,
2891
+ ...document2[currentBlockId].data,
2867
2892
  childrenIds: childrenIds2
2868
2893
  }
2869
2894
  }
@@ -2910,7 +2935,7 @@ const sx = {
2910
2935
  zIndex: "fab"
2911
2936
  };
2912
2937
  function TuneMenu({ blockId }) {
2913
- const document = useDocument();
2938
+ const document2 = useDocument();
2914
2939
  const handleDeleteClick = () => {
2915
2940
  var _a, _b, _c;
2916
2941
  const filterChildrenIds = (childrenIds) => {
@@ -2919,7 +2944,7 @@ function TuneMenu({ blockId }) {
2919
2944
  }
2920
2945
  return childrenIds.filter((f) => f !== blockId);
2921
2946
  };
2922
- const nDocument = { ...document };
2947
+ const nDocument = { ...document2 };
2923
2948
  for (const [id, b] of Object.entries(nDocument)) {
2924
2949
  const block = b;
2925
2950
  if (id === blockId) {
@@ -2986,7 +3011,7 @@ function TuneMenu({ blockId }) {
2986
3011
  }
2987
3012
  return childrenIds;
2988
3013
  };
2989
- const nDocument = { ...document };
3014
+ const nDocument = { ...document2 };
2990
3015
  for (const [id, b] of Object.entries(nDocument)) {
2991
3016
  const block = b;
2992
3017
  if (id === blockId) {
@@ -3139,8 +3164,8 @@ const EditorConfigurationSchema = z.record(z.string(), EditorBlockSchema);
3139
3164
  const EditorBlockContext = createContext(null);
3140
3165
  const useCurrentBlockId = () => useContext(EditorBlockContext);
3141
3166
  function EditorBlock({ id }) {
3142
- const document = useDocument();
3143
- const block = document[id];
3167
+ const document2 = useDocument();
3168
+ const block = document2[id];
3144
3169
  if (!block) {
3145
3170
  throw new Error("Could not find block");
3146
3171
  }
@@ -3168,28 +3193,60 @@ function ToggleSamplesPanelButton() {
3168
3193
  return /* @__PURE__ */ jsx(IconButton, { onClick: toggleSamplesDrawerOpen, children: icon });
3169
3194
  }
3170
3195
  function DownloadJson() {
3196
+ const { t: t2 } = useTranslation();
3171
3197
  const doc = useDocument();
3172
3198
  const href = useMemo(() => {
3173
3199
  return `data:text/plain,${encodeURIComponent(JSON.stringify(doc, null, " "))}`;
3174
3200
  }, [doc]);
3175
- return /* @__PURE__ */ jsx(Tooltip, { title: "Download JSON file", children: /* @__PURE__ */ jsx(IconButton, { href, download: "emailTemplate.json", children: /* @__PURE__ */ jsx(FileDownloadOutlined, { fontSize: "small" }) }) });
3201
+ return /* @__PURE__ */ jsx(Tooltip, { title: t2("common.downloadJson"), children: /* @__PURE__ */ jsx(IconButton, { href, download: "emailTemplate.json", children: /* @__PURE__ */ jsx(FileDownloadOutlined, { fontSize: "small" }) }) });
3176
3202
  }
3177
3203
  let hljs;
3178
3204
  let jsonHighlighter;
3179
3205
  let xmlHighlighter;
3180
3206
  let prettierFormat;
3181
- let prettierPluginBabel;
3182
- let prettierPluginEstree;
3183
3207
  let prettierPluginHtml;
3208
+ function escapeHtml(text) {
3209
+ if (typeof document === "undefined") {
3210
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
3211
+ }
3212
+ const div = document.createElement("div");
3213
+ div.textContent = text;
3214
+ return div.innerHTML;
3215
+ }
3216
+ function loadHighlightJsCSS() {
3217
+ if (typeof document === "undefined") return;
3218
+ const existingLink = document.querySelector("link[data-highlight-js-css]");
3219
+ if (existingLink) return;
3220
+ try {
3221
+ const link = document.createElement("link");
3222
+ link.rel = "stylesheet";
3223
+ link.href = "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css";
3224
+ link.setAttribute("data-highlight-js-css", "true");
3225
+ document.head.appendChild(link);
3226
+ } catch (error) {
3227
+ console.warn("Failed to load highlight.js CSS:", error);
3228
+ }
3229
+ }
3184
3230
  async function loadHighlightJs() {
3185
3231
  if (!hljs) {
3186
- hljs = await import("highlight.js");
3187
- jsonHighlighter = await import("highlight.js/lib/languages/json");
3188
- xmlHighlighter = await import("highlight.js/lib/languages/xml");
3189
- hljs.default.registerLanguage("json", jsonHighlighter.default);
3190
- hljs.default.registerLanguage("html", xmlHighlighter.default);
3232
+ try {
3233
+ const hljsModule = await import("highlight.js");
3234
+ hljs = hljsModule.default || hljsModule;
3235
+ jsonHighlighter = await import("highlight.js/lib/languages/json");
3236
+ xmlHighlighter = await import("highlight.js/lib/languages/xml");
3237
+ const jsonLang = jsonHighlighter.default || jsonHighlighter;
3238
+ const xmlLang = xmlHighlighter.default || xmlHighlighter;
3239
+ hljs.registerLanguage("json", jsonLang);
3240
+ hljs.registerLanguage("html", xmlLang);
3241
+ loadHighlightJsCSS();
3242
+ } catch (error) {
3243
+ console.error("Failed to load highlight.js. Please ensure highlight.js is installed:", error);
3244
+ hljs = {
3245
+ highlight: (code) => ({ value: escapeHtml(code) })
3246
+ };
3247
+ }
3191
3248
  }
3192
- return hljs.default;
3249
+ return hljs;
3193
3250
  }
3194
3251
  function dynamicImport(path) {
3195
3252
  const importFunc = new Function("path", "return import(path)");
@@ -3199,28 +3256,27 @@ async function loadPrettier() {
3199
3256
  if (!prettierFormat) {
3200
3257
  try {
3201
3258
  const prettierMain = await import("prettier");
3259
+ const prettierVersion = prettierMain.version || "2.0.0";
3260
+ const majorVersion = parseInt(prettierVersion.split(".")[0], 10);
3261
+ if (majorVersion < 3) {
3262
+ prettierFormat = prettierMain.format;
3263
+ prettierPluginHtml = null;
3264
+ return prettierFormat;
3265
+ }
3202
3266
  try {
3203
3267
  const prettierStandalone = await dynamicImport("prettier/standalone");
3204
3268
  prettierFormat = prettierStandalone.format;
3205
3269
  try {
3206
- prettierPluginBabel = await dynamicImport("prettier/plugins/babel");
3270
+ prettierPluginHtml = await dynamicImport("prettier/parser-html");
3207
3271
  } catch {
3208
- prettierPluginBabel = null;
3209
- }
3210
- try {
3211
- prettierPluginEstree = await dynamicImport("prettier/plugins/estree");
3212
- } catch {
3213
- prettierPluginEstree = null;
3214
- }
3215
- try {
3216
- prettierPluginHtml = await dynamicImport("prettier/plugins/html");
3217
- } catch {
3218
- prettierPluginHtml = null;
3272
+ try {
3273
+ prettierPluginHtml = await dynamicImport("prettier/plugins/html");
3274
+ } catch {
3275
+ prettierPluginHtml = null;
3276
+ }
3219
3277
  }
3220
3278
  } catch {
3221
3279
  prettierFormat = prettierMain.format;
3222
- prettierPluginBabel = null;
3223
- prettierPluginEstree = null;
3224
3280
  prettierPluginHtml = null;
3225
3281
  }
3226
3282
  } catch (error) {
@@ -3231,28 +3287,200 @@ async function loadPrettier() {
3231
3287
  return prettierFormat;
3232
3288
  }
3233
3289
  async function html(value) {
3234
- const [hljsInstance, format] = await Promise.all([loadHighlightJs(), loadPrettier()]);
3290
+ let hljsInstance;
3291
+ let format;
3292
+ try {
3293
+ [hljsInstance, format] = await Promise.all([loadHighlightJs(), loadPrettier()]);
3294
+ } catch (error) {
3295
+ console.error("Failed to load highlight.js or prettier:", error);
3296
+ return escapeHtml(value);
3297
+ }
3235
3298
  const formatOptions = {
3236
- parser: "html"
3299
+ parser: "html",
3300
+ printWidth: 120,
3301
+ tabWidth: 2,
3302
+ useTabs: false,
3303
+ htmlWhitespaceSensitivity: "ignore",
3304
+ // 忽略HTML空白敏感度,避免不必要的缩进
3305
+ bracketSameLine: false
3306
+ // 确保开始和结束标签在不同行,便于对齐
3237
3307
  };
3238
3308
  if (prettierPluginHtml) {
3239
- formatOptions.plugins = [prettierPluginHtml.default];
3309
+ const htmlParser = prettierPluginHtml.default || prettierPluginHtml;
3310
+ formatOptions.plugins = [htmlParser];
3311
+ }
3312
+ const removeLeadingIndent = (text) => {
3313
+ const lines = text.split("\n");
3314
+ if (lines.length === 0) return text;
3315
+ const firstNonEmptyLine = lines.find((line) => line.trim().length > 0);
3316
+ if (!firstNonEmptyLine) return text;
3317
+ const trimmedFirstLine = firstNonEmptyLine.trim();
3318
+ const isRootTag = trimmedFirstLine.startsWith("<!DOCTYPE") || trimmedFirstLine.startsWith("<html");
3319
+ if (isRootTag) {
3320
+ const leadingSpaces = firstNonEmptyLine.length - firstNonEmptyLine.trimStart().length;
3321
+ if (leadingSpaces > 0) {
3322
+ return lines.map((line) => {
3323
+ if (line.trim().length === 0) return line;
3324
+ const currentLeading = line.length - line.trimStart().length;
3325
+ if (currentLeading >= leadingSpaces) {
3326
+ return line.substring(leadingSpaces);
3327
+ }
3328
+ return line;
3329
+ }).join("\n");
3330
+ }
3331
+ }
3332
+ return text;
3333
+ };
3334
+ const fixTagAlignment = (text) => {
3335
+ var _a, _b;
3336
+ const lines = text.split("\n");
3337
+ const stack = [];
3338
+ const result = [];
3339
+ for (let i = 0; i < lines.length; i++) {
3340
+ const line = lines[i];
3341
+ const trimmed = line.trim();
3342
+ if (!trimmed) {
3343
+ result.push(line);
3344
+ continue;
3345
+ }
3346
+ const currentIndent = line.length - line.trimStart().length;
3347
+ const isClosingTag = trimmed.startsWith("</");
3348
+ const isSelfClosing = trimmed.endsWith("/>");
3349
+ const isOpeningTag = trimmed.startsWith("<") && !isClosingTag && !isSelfClosing;
3350
+ if (isClosingTag) {
3351
+ const tagName = (_a = trimmed.match(/<\/(\w+)/)) == null ? void 0 : _a[1];
3352
+ if (tagName && stack.length > 0) {
3353
+ const opening = stack[stack.length - 1];
3354
+ if (opening.tag === tagName) {
3355
+ const correctIndent = opening.indent;
3356
+ result.push(" ".repeat(correctIndent) + trimmed);
3357
+ stack.pop();
3358
+ continue;
3359
+ }
3360
+ }
3361
+ result.push(line);
3362
+ } else if (isOpeningTag) {
3363
+ const tagName = (_b = trimmed.match(/<(\w+)/)) == null ? void 0 : _b[1];
3364
+ if (tagName) {
3365
+ stack.push({ tag: tagName, indent: currentIndent });
3366
+ }
3367
+ result.push(line);
3368
+ } else {
3369
+ result.push(line);
3370
+ }
3371
+ }
3372
+ return result.join("\n");
3373
+ };
3374
+ let prettyValue;
3375
+ try {
3376
+ prettyValue = await format(value, formatOptions);
3377
+ if (!prettyValue || typeof prettyValue !== "string") {
3378
+ throw new Error("Prettier returned invalid value");
3379
+ }
3380
+ prettyValue = removeLeadingIndent(prettyValue);
3381
+ prettyValue = fixTagAlignment(prettyValue);
3382
+ } catch (error) {
3383
+ try {
3384
+ const prettierMain = await import("prettier");
3385
+ const mainFormatOptions = {
3386
+ parser: "html",
3387
+ printWidth: 120,
3388
+ tabWidth: 2,
3389
+ useTabs: false,
3390
+ htmlWhitespaceSensitivity: "ignore",
3391
+ bracketSameLine: false
3392
+ // 确保开始和结束标签在不同行,便于对齐
3393
+ };
3394
+ prettyValue = await prettierMain.format(value, mainFormatOptions);
3395
+ if (!prettyValue || typeof prettyValue !== "string") {
3396
+ throw new Error("Prettier main package returned invalid value");
3397
+ }
3398
+ prettyValue = removeLeadingIndent(prettyValue);
3399
+ prettyValue = fixTagAlignment(prettyValue);
3400
+ } catch (fallbackError) {
3401
+ try {
3402
+ let formatted = value.replace(/></g, ">\n<").replace(/\n\s*\n/g, "\n").split("\n");
3403
+ let indent = 0;
3404
+ const indentSize = 2;
3405
+ const result = [];
3406
+ for (let i = 0; i < formatted.length; i++) {
3407
+ const line = formatted[i];
3408
+ const trimmed = line.trim();
3409
+ if (!trimmed) {
3410
+ result.push("");
3411
+ continue;
3412
+ }
3413
+ const isClosingTag = trimmed.startsWith("</");
3414
+ const isSelfClosing = trimmed.endsWith("/>");
3415
+ const isOpeningTag = trimmed.startsWith("<") && !isClosingTag && !isSelfClosing;
3416
+ if (isClosingTag) {
3417
+ indent = Math.max(0, indent - indentSize);
3418
+ result.push(" ".repeat(indent) + trimmed);
3419
+ } else if (isOpeningTag) {
3420
+ result.push(" ".repeat(indent) + trimmed);
3421
+ indent += indentSize;
3422
+ } else if (isSelfClosing) {
3423
+ result.push(" ".repeat(indent) + trimmed);
3424
+ } else {
3425
+ result.push(" ".repeat(indent) + trimmed);
3426
+ }
3427
+ }
3428
+ prettyValue = result.filter((line) => line.length > 0).join("\n");
3429
+ } catch {
3430
+ console.warn("Failed to format HTML, returning original value:", error);
3431
+ prettyValue = value;
3432
+ }
3433
+ }
3434
+ }
3435
+ try {
3436
+ return hljsInstance.highlight(prettyValue, { language: "html" }).value;
3437
+ } catch (error) {
3438
+ console.error("Failed to highlight HTML:", error);
3439
+ return escapeHtml(prettyValue);
3240
3440
  }
3241
- const prettyValue = await format(value, formatOptions);
3242
- return hljsInstance.highlight(prettyValue, { language: "html" }).value;
3243
3441
  }
3244
3442
  async function json(value) {
3245
- const [hljsInstance, format] = await Promise.all([loadHighlightJs(), loadPrettier()]);
3443
+ let hljsInstance;
3444
+ let format;
3445
+ try {
3446
+ [hljsInstance, format] = await Promise.all([loadHighlightJs(), loadPrettier()]);
3447
+ } catch (error) {
3448
+ console.error("Failed to load highlight.js or prettier:", error);
3449
+ return escapeHtml(value);
3450
+ }
3246
3451
  const formatOptions = {
3247
- parser: "json",
3248
- printWidth: 0,
3249
- trailingComma: "all"
3452
+ parser: "json"
3453
+ // 移除 printWidth: 0,使用默认值或设置一个合理的值
3454
+ // trailingComma: 'all' 在 JSON 中可能不支持,移除
3250
3455
  };
3251
- if (prettierPluginBabel && prettierPluginEstree) {
3252
- formatOptions.plugins = [prettierPluginBabel.default, prettierPluginEstree.default];
3456
+ let prettyValue;
3457
+ try {
3458
+ prettyValue = await format(value, formatOptions);
3459
+ } catch (error) {
3460
+ try {
3461
+ const prettierMain = await import("prettier");
3462
+ prettyValue = await prettierMain.format(value, {
3463
+ parser: "json"
3464
+ });
3465
+ } catch (fallbackError) {
3466
+ try {
3467
+ const parsed = JSON.parse(value);
3468
+ prettyValue = JSON.stringify(parsed, null, 2);
3469
+ } catch {
3470
+ console.warn("Failed to format JSON, returning original value:", error);
3471
+ prettyValue = value;
3472
+ }
3473
+ }
3474
+ }
3475
+ if (typeof prettyValue !== "string") {
3476
+ prettyValue = JSON.stringify(prettyValue, null, 2);
3477
+ }
3478
+ try {
3479
+ return hljsInstance.highlight(prettyValue, { language: "json" }).value;
3480
+ } catch (error) {
3481
+ console.error("Failed to highlight JSON:", error);
3482
+ return escapeHtml(prettyValue);
3253
3483
  }
3254
- const prettyValue = await format(value, formatOptions);
3255
- return hljsInstance.highlight(prettyValue, { language: "javascript" }).value;
3256
3484
  }
3257
3485
  function HighlightedCodePanel({ type, value }) {
3258
3486
  const [code, setCode] = useState(null);
@@ -3285,8 +3513,8 @@ function HighlightedCodePanel({ type, value }) {
3285
3513
  );
3286
3514
  }
3287
3515
  function HtmlPanel() {
3288
- const document = useDocument();
3289
- const code = useMemo(() => renderToStaticMarkup(document, { rootBlockId: "root" }), [document]);
3516
+ const document2 = useDocument();
3517
+ const code = useMemo(() => renderToStaticMarkup(document2, { rootBlockId: "root" }), [document2]);
3290
3518
  return /* @__PURE__ */ jsx(HighlightedCodePanel, { type: "html", value: code });
3291
3519
  }
3292
3520
  function validateTextAreaValue(value) {
@@ -3374,19 +3602,20 @@ function ImportJsonDialog({ onClose }) {
3374
3602
  ] });
3375
3603
  }
3376
3604
  function ImportJson() {
3605
+ const { t: t2 } = useTranslation();
3377
3606
  const [open, setOpen] = useState(false);
3378
3607
  let dialog = null;
3379
3608
  if (open) {
3380
3609
  dialog = /* @__PURE__ */ jsx(ImportJsonDialog, { onClose: () => setOpen(false) });
3381
3610
  }
3382
3611
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3383
- /* @__PURE__ */ jsx(Tooltip, { title: "Import JSON", children: /* @__PURE__ */ jsx(IconButton, { onClick: () => setOpen(true), children: /* @__PURE__ */ jsx(FileUploadOutlined, { fontSize: "small" }) }) }),
3612
+ /* @__PURE__ */ jsx(Tooltip, { title: t2("common.importJson"), children: /* @__PURE__ */ jsx(IconButton, { onClick: () => setOpen(true), children: /* @__PURE__ */ jsx(FileUploadOutlined, { fontSize: "small" }) }) }),
3384
3613
  dialog
3385
3614
  ] });
3386
3615
  }
3387
3616
  function JsonPanel() {
3388
- const document = useDocument();
3389
- const code = useMemo(() => JSON.stringify(document, null, " "), [document]);
3617
+ const document2 = useDocument();
3618
+ const code = useMemo(() => JSON.stringify(document2, null, " "), [document2]);
3390
3619
  return /* @__PURE__ */ jsx(HighlightedCodePanel, { type: "json", value: code });
3391
3620
  }
3392
3621
  function MainTabsGroup() {
@@ -3435,32 +3664,93 @@ function MainTabsGroup() {
3435
3664
  )
3436
3665
  ] });
3437
3666
  }
3438
- function ShareButton() {
3439
- const document = useDocument();
3440
- const [message, setMessage] = useState(null);
3441
- const onClick = async () => {
3442
- const c = encodeURIComponent(JSON.stringify(document));
3443
- location.hash = `#code/${btoa(c)}`;
3444
- setMessage("The URL was updated. Copy it to share your current template.");
3667
+ function NameInput() {
3668
+ const { t: t2 } = useTranslation();
3669
+ const name = useName();
3670
+ return /* @__PURE__ */ jsx(
3671
+ Typography,
3672
+ {
3673
+ component: "span",
3674
+ variant: "body2",
3675
+ sx: {
3676
+ position: "absolute",
3677
+ left: "50%",
3678
+ transform: "translateX(-50%)",
3679
+ maxWidth: "300px",
3680
+ overflow: "hidden",
3681
+ textOverflow: "ellipsis",
3682
+ whiteSpace: "nowrap",
3683
+ color: "text.primary",
3684
+ fontWeight: 500
3685
+ },
3686
+ children: name || t2("common.unnamedTemplate")
3687
+ }
3688
+ );
3689
+ }
3690
+ function SaveAndExitButton() {
3691
+ const { t: t2 } = useTranslation();
3692
+ const saveHandler = useSaveHandler();
3693
+ const saveAndExitHandler = useSaveAndExitHandler();
3694
+ const [saving, setSaving] = useState(false);
3695
+ const handleSaveAndExit = async () => {
3696
+ if (!saveAndExitHandler) {
3697
+ console.warn("Save and exit handler is not configured.");
3698
+ return;
3699
+ }
3700
+ setSaving(true);
3701
+ try {
3702
+ if (saveHandler) {
3703
+ await saveDocument();
3704
+ }
3705
+ saveAndExitDocument(saveAndExitHandler);
3706
+ } catch (error) {
3707
+ console.error("Failed to save and exit:", error);
3708
+ } finally {
3709
+ setSaving(false);
3710
+ }
3445
3711
  };
3446
- const onClose = () => {
3447
- setMessage(null);
3712
+ return /* @__PURE__ */ jsx(Tooltip, { title: t2("common.saveAndExit"), children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(
3713
+ IconButton,
3714
+ {
3715
+ color: "primary",
3716
+ size: "small",
3717
+ onClick: handleSaveAndExit,
3718
+ disabled: saving || !saveAndExitHandler,
3719
+ children: saving ? /* @__PURE__ */ jsx(CircularProgress, { size: 16, color: "inherit" }) : /* @__PURE__ */ jsx(ExitToAppOutlined, { fontSize: "small" })
3720
+ }
3721
+ ) }) });
3722
+ }
3723
+ function SaveButton() {
3724
+ const { t: t2 } = useTranslation();
3725
+ const saveHandler = useSaveHandler();
3726
+ const [saving, setSaving] = useState(false);
3727
+ const handleSave = async () => {
3728
+ if (!saveHandler) {
3729
+ console.warn("Save handler is not configured. Please set saveHandler in EmailBuilder props.");
3730
+ return;
3731
+ }
3732
+ setSaving(true);
3733
+ try {
3734
+ await saveDocument();
3735
+ } catch (error) {
3736
+ console.error("Failed to save document:", error);
3737
+ } finally {
3738
+ setSaving(false);
3739
+ }
3448
3740
  };
3449
- return /* @__PURE__ */ jsxs(Fragment, { children: [
3450
- /* @__PURE__ */ jsx(IconButton, { onClick, children: /* @__PURE__ */ jsx(Tooltip, { title: "Share current template", children: /* @__PURE__ */ jsx(IosShareOutlined, { fontSize: "small" }) }) }),
3451
- /* @__PURE__ */ jsx(
3452
- Snackbar,
3453
- {
3454
- anchorOrigin: { vertical: "top", horizontal: "center" },
3455
- open: message !== null,
3456
- onClose,
3457
- message
3458
- }
3459
- )
3460
- ] });
3741
+ return /* @__PURE__ */ jsx(Tooltip, { title: t2("common.save"), children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(
3742
+ IconButton,
3743
+ {
3744
+ color: "primary",
3745
+ onClick: handleSave,
3746
+ disabled: saving || !saveHandler,
3747
+ children: saving ? /* @__PURE__ */ jsx(CircularProgress, { size: 16 }) : /* @__PURE__ */ jsx(SaveOutlined, { fontSize: "small" })
3748
+ }
3749
+ ) }) });
3461
3750
  }
3462
3751
  function TemplatePanel() {
3463
- const document = useDocument();
3752
+ const { t: t2 } = useTranslation();
3753
+ const document2 = useDocument();
3464
3754
  const selectedMainTab = useSelectedMainTab();
3465
3755
  const selectedScreenSize = useSelectedScreenSize();
3466
3756
  let mainBoxSx = {
@@ -3491,7 +3781,7 @@ function TemplatePanel() {
3491
3781
  case "editor":
3492
3782
  return /* @__PURE__ */ jsx(Box, { sx: mainBoxSx, children: /* @__PURE__ */ jsx(EditorBlock, { id: "root" }) });
3493
3783
  case "preview":
3494
- return /* @__PURE__ */ jsx(Box, { sx: mainBoxSx, children: /* @__PURE__ */ jsx(Reader, { document, rootBlockId: "root" }) });
3784
+ return /* @__PURE__ */ jsx(Box, { sx: mainBoxSx, children: /* @__PURE__ */ jsx(Reader, { document: document2, rootBlockId: "root" }) });
3495
3785
  case "html":
3496
3786
  return /* @__PURE__ */ jsx(HtmlPanel, {});
3497
3787
  case "json":
@@ -3508,7 +3798,8 @@ function TemplatePanel() {
3508
3798
  borderColor: "divider",
3509
3799
  backgroundColor: "white",
3510
3800
  flexShrink: 0,
3511
- zIndex: "appBar",
3801
+ position: "relative",
3802
+ zIndex: 1,
3512
3803
  px: 1
3513
3804
  },
3514
3805
  direction: "row",
@@ -3516,16 +3807,18 @@ function TemplatePanel() {
3516
3807
  alignItems: "center",
3517
3808
  children: [
3518
3809
  /* @__PURE__ */ jsx(ToggleSamplesPanelButton, {}),
3810
+ /* @__PURE__ */ jsx(NameInput, {}),
3519
3811
  /* @__PURE__ */ jsxs(Stack, { px: 2, direction: "row", gap: 2, width: "100%", justifyContent: "space-between", alignItems: "center", children: [
3520
3812
  /* @__PURE__ */ jsx(Stack, { direction: "row", spacing: 2, children: /* @__PURE__ */ jsx(MainTabsGroup, {}) }),
3521
- /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 2, children: [
3813
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 2, alignItems: "center", children: [
3522
3814
  /* @__PURE__ */ jsx(DownloadJson, {}),
3523
3815
  /* @__PURE__ */ jsx(ImportJson, {}),
3524
- /* @__PURE__ */ jsxs(ToggleButtonGroup, { value: selectedScreenSize, exclusive: true, size: "small", onChange: handleScreenSizeChange, children: [
3525
- /* @__PURE__ */ jsx(ToggleButton, { value: "desktop", children: /* @__PURE__ */ jsx(Tooltip, { title: "Desktop view", children: /* @__PURE__ */ jsx(MonitorOutlined, { fontSize: "small" }) }) }),
3526
- /* @__PURE__ */ jsx(ToggleButton, { value: "mobile", children: /* @__PURE__ */ jsx(Tooltip, { title: "Mobile view", children: /* @__PURE__ */ jsx(PhoneIphoneOutlined, { fontSize: "small" }) }) })
3527
- ] }),
3528
- /* @__PURE__ */ jsx(ShareButton, {})
3816
+ /* @__PURE__ */ jsx(SaveButton, {}),
3817
+ /* @__PURE__ */ jsx(SaveAndExitButton, {}),
3818
+ /* @__PURE__ */ jsxs(ToggleButtonGroup, { size: "small", value: selectedScreenSize, exclusive: true, onChange: handleScreenSizeChange, children: [
3819
+ /* @__PURE__ */ jsx(ToggleButton, { value: "desktop", children: /* @__PURE__ */ jsx(Tooltip, { title: t2("common.desktopView"), children: /* @__PURE__ */ jsx(MonitorOutlined, { fontSize: "small" }) }) }),
3820
+ /* @__PURE__ */ jsx(ToggleButton, { value: "mobile", children: /* @__PURE__ */ jsx(Tooltip, { title: t2("common.mobileView"), children: /* @__PURE__ */ jsx(PhoneIphoneOutlined, { fontSize: "small" }) }) })
3821
+ ] })
3529
3822
  ] })
3530
3823
  ] }),
3531
3824
  /* @__PURE__ */ jsx(ToggleInspectorPanelButton, {})
@@ -3559,7 +3852,7 @@ function App() {
3559
3852
  },
3560
3853
  children: [
3561
3854
  /* @__PURE__ */ jsx(SamplesDrawer, {}),
3562
- /* @__PURE__ */ jsxs(
3855
+ /* @__PURE__ */ jsx(
3563
3856
  Stack,
3564
3857
  {
3565
3858
  sx: {
@@ -3568,12 +3861,10 @@ function App() {
3568
3861
  // 允许 flex 项目收缩到内容以下
3569
3862
  transition: `${marginLeftTransition}, ${marginRightTransition}`,
3570
3863
  position: "relative",
3571
- overflow: "hidden"
3864
+ overflow: "hidden",
3865
+ zIndex: 0
3572
3866
  },
3573
- children: [
3574
- /* @__PURE__ */ jsx(Box, { sx: { position: "absolute", top: 8, right: 8, zIndex: 1e3 }, children: /* @__PURE__ */ jsx(LanguageSwitcher, {}) }),
3575
- /* @__PURE__ */ jsx(TemplatePanel, {})
3576
- ]
3867
+ children: /* @__PURE__ */ jsx(TemplatePanel, {})
3577
3868
  }
3578
3869
  ),
3579
3870
  /* @__PURE__ */ jsx(InspectorDrawer, {})
@@ -3586,38 +3877,42 @@ function EmailBuilder({
3586
3877
  initialLanguage = "en",
3587
3878
  imageUploadHandler,
3588
3879
  onChange,
3880
+ saveHandler,
3881
+ saveAndExitHandler,
3882
+ initialName,
3883
+ onNameChange,
3589
3884
  theme: customTheme
3590
3885
  }) {
3591
- const currentLanguage = useLanguage();
3592
3886
  useEffect(() => {
3593
- if (initialDocument) {
3887
+ if (initialDocument !== void 0) {
3594
3888
  resetDocument(initialDocument);
3595
3889
  }
3596
- if (initialLanguage) {
3597
- setLanguage(initialLanguage);
3598
- }
3599
- if (imageUploadHandler) {
3600
- setImageUploadHandler(imageUploadHandler);
3601
- }
3602
- if (onChange) {
3603
- setOnChange(onChange);
3604
- }
3605
- }, []);
3890
+ }, [initialDocument]);
3606
3891
  useEffect(() => {
3607
- if (initialLanguage && initialLanguage !== currentLanguage) {
3892
+ if (initialLanguage !== void 0) {
3608
3893
  setLanguage(initialLanguage);
3609
3894
  }
3610
- }, [initialLanguage, currentLanguage]);
3895
+ }, [initialLanguage]);
3611
3896
  useEffect(() => {
3612
- if (imageUploadHandler) {
3613
- setImageUploadHandler(imageUploadHandler);
3614
- }
3897
+ setImageUploadHandler(imageUploadHandler);
3615
3898
  }, [imageUploadHandler]);
3616
3899
  useEffect(() => {
3617
- if (onChange) {
3618
- setOnChange(onChange);
3619
- }
3900
+ setOnChange(onChange);
3620
3901
  }, [onChange]);
3902
+ useEffect(() => {
3903
+ setSaveHandler(saveHandler);
3904
+ }, [saveHandler]);
3905
+ useEffect(() => {
3906
+ setSaveAndExitHandler(saveAndExitHandler);
3907
+ }, [saveAndExitHandler]);
3908
+ useEffect(() => {
3909
+ if (initialName !== void 0) {
3910
+ setName(initialName);
3911
+ }
3912
+ }, [initialName]);
3913
+ useEffect(() => {
3914
+ setOnNameChange(onNameChange);
3915
+ }, [onNameChange]);
3621
3916
  return /* @__PURE__ */ jsxs(ThemeProvider, { theme: customTheme || THEME, children: [
3622
3917
  /* @__PURE__ */ jsx(CssBaseline, {}),
3623
3918
  /* @__PURE__ */ jsx(