@savvycal/mjml-editor 0.8.0 → 0.9.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.
@@ -1 +1 @@
1
- {"version":3,"file":"InteractivePreview.d.ts","sourceRoot":"","sources":["../../../src/components/editor/InteractivePreview.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,UAAU,uBAAuB;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAID,wBAAgB,kBAAkB,CAAC,EACjC,UAAiB,EACjB,WAAuB,GACxB,EAAE,uBAAuB,2CAuLzB"}
1
+ {"version":3,"file":"InteractivePreview.d.ts","sourceRoot":"","sources":["../../../src/components/editor/InteractivePreview.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,UAAU,uBAAuB;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B;AAID,wBAAgB,kBAAkB,CAAC,EACjC,UAAiB,EACjB,WAAuB,GACxB,EAAE,uBAAuB,2CAwLzB"}
@@ -1,38 +1,39 @@
1
1
  import { jsxs as r, jsx as l } from "react/jsx-runtime";
2
- import { useRef as E, useState as p, useEffect as a, useCallback as y } from "react";
3
- import { useEditor as C } from "../../context/EditorContext.js";
4
- import { useNonce as I } from "../../context/NonceContext.js";
5
- import { renderMjmlInteractive as N } from "../../lib/mjml/renderer.js";
6
- const L = 375;
7
- function R({
8
- showHeader: g = !0,
9
- previewMode: x = "desktop"
2
+ import { useRef as y, useState as g, useEffect as a, useCallback as C } from "react";
3
+ import { useEditor as I } from "../../context/EditorContext.js";
4
+ import { useNonce as N } from "../../context/NonceContext.js";
5
+ import { useRenderEndpoint as L } from "../../context/RenderEndpointContext.js";
6
+ import { renderMjmlInteractive as D } from "../../lib/mjml/renderer.js";
7
+ const B = 375;
8
+ function W({
9
+ showHeader: x = !0,
10
+ previewMode: k = "desktop"
10
11
  }) {
11
- const { state: n, selectBlock: m } = C(), i = I(), o = E(null), [u, k] = p(n.document), [v, w] = p({
12
+ const { state: n, selectBlock: m } = I(), i = N(), u = L(), o = y(null), [f, v] = g(n.document), [E, w] = g({
12
13
  html: "",
13
14
  errors: []
14
15
  });
15
16
  a(() => {
16
17
  const e = setTimeout(() => {
17
- k(n.document);
18
+ v(n.document);
18
19
  }, 300);
19
20
  return () => clearTimeout(e);
20
21
  }, [n.document]), a(() => {
21
22
  let e = !1;
22
- return N(u).then((t) => {
23
+ return D(f, u).then((t) => {
23
24
  e || w(t);
24
25
  }), () => {
25
26
  e = !0;
26
27
  };
27
- }, [u]);
28
- const { html: f, errors: s } = v, d = y(
28
+ }, [f, u]);
29
+ const { html: b, errors: s } = E, d = C(
29
30
  (e) => {
30
31
  e.data?.type === "BLOCK_SELECTED" && m(e.data.blockId);
31
32
  },
32
33
  [m]
33
34
  );
34
35
  a(() => (window.addEventListener("message", d), () => window.removeEventListener("message", d)), [d]);
35
- const c = n.selectedBlockId ? `.block-${n.selectedBlockId} { outline: 2px solid #6366f1 !important; outline-offset: -2px; position: relative; }` : "", b = `
36
+ const c = n.selectedBlockId ? `.block-${n.selectedBlockId} { outline: 2px solid #6366f1 !important; outline-offset: -2px; position: relative; }` : "", p = `
36
37
  (function() {
37
38
  document.addEventListener('click', function(e) {
38
39
  e.preventDefault();
@@ -65,9 +66,9 @@ function R({
65
66
  if (o.current) {
66
67
  const e = o.current.contentDocument;
67
68
  if (e) {
68
- e.open(), e.write(f), e.close();
69
+ e.open(), e.write(b), e.close();
69
70
  const t = e.createElement("script");
70
- i && (t.nonce = i), t.textContent = b, e.body.appendChild(t);
71
+ i && (t.nonce = i), t.textContent = p, e.body.appendChild(t);
71
72
  const h = e.createElement("style");
72
73
  h.textContent = `
73
74
  [class*="block-"] {
@@ -82,7 +83,7 @@ function R({
82
83
  `, e.head.appendChild(h);
83
84
  }
84
85
  }
85
- }, [f, c, b, i]), a(() => {
86
+ }, [b, c, p, i]), a(() => {
86
87
  if (o.current) {
87
88
  const e = o.current.contentDocument;
88
89
  if (e) {
@@ -91,7 +92,7 @@ function R({
91
92
  }
92
93
  }
93
94
  }, [n.selectedBlockId, c]), /* @__PURE__ */ r("div", { className: "flex flex-col h-full", children: [
94
- g && /* @__PURE__ */ r("div", { className: "h-11 px-4 flex items-center justify-between border-b border-border bg-background", children: [
95
+ x && /* @__PURE__ */ r("div", { className: "h-11 px-4 flex items-center justify-between border-b border-border bg-background", children: [
95
96
  /* @__PURE__ */ l("span", { className: "text-sm font-semibold text-foreground", children: "Preview" }),
96
97
  s.length > 0 && /* @__PURE__ */ r("span", { className: "text-xs font-medium text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 rounded-md", children: [
97
98
  s.length,
@@ -106,7 +107,7 @@ function R({
106
107
  title: "Email Preview",
107
108
  className: "h-full border-0 bg-white transition-all duration-200",
108
109
  style: {
109
- width: x === "mobile" ? L : "100%",
110
+ width: k === "mobile" ? B : "100%",
110
111
  maxWidth: "100%"
111
112
  },
112
113
  sandbox: "allow-same-origin allow-scripts"
@@ -134,5 +135,5 @@ function R({
134
135
  ] });
135
136
  }
136
137
  export {
137
- R as InteractivePreview
138
+ W as InteractivePreview
138
139
  };
@@ -3,6 +3,12 @@ import { LiquidSchema } from '../../types/liquid';
3
3
  interface MjmlEditorProps {
4
4
  value: string;
5
5
  onChange: (mjml: string) => void;
6
+ /**
7
+ * URL to POST raw MJML content to for server-side rendering.
8
+ * The endpoint should accept a POST with `Content-Type: text/plain` body
9
+ * and return JSON: `{ html: string, errors: Array<{ line: number, message: string, tagName: string }> }`
10
+ */
11
+ renderEndpoint: string;
6
12
  className?: string;
7
13
  defaultTheme?: 'light' | 'dark' | 'system';
8
14
  liquidSchema?: LiquidSchema;
@@ -55,6 +61,6 @@ interface MjmlEditorProps {
55
61
  */
56
62
  nonce?: string;
57
63
  }
58
- export declare function MjmlEditor({ value, onChange, className, defaultTheme, liquidSchema, extensions, applyThemeToDocument, showThemeToggle, defaultLeftPanelOpen, defaultRightPanelOpen, nonce, }: MjmlEditorProps): import("react/jsx-runtime").JSX.Element;
64
+ export declare function MjmlEditor({ value, onChange, renderEndpoint, className, defaultTheme, liquidSchema, extensions, applyThemeToDocument, showThemeToggle, defaultLeftPanelOpen, defaultRightPanelOpen, nonce, }: MjmlEditorProps): import("react/jsx-runtime").JSX.Element;
59
65
  export {};
60
66
  //# sourceMappingURL=MjmlEditor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"MjmlEditor.d.ts","sourceRoot":"","sources":["../../../src/components/editor/MjmlEditor.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAY,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAcnD,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC3C,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;;;;;;OASG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoKD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,SAAS,EACT,YAAuB,EACvB,YAAY,EACZ,UAAU,EACV,oBAA2B,EAC3B,eAAsB,EACtB,oBAA2B,EAC3B,qBAA6B,EAC7B,KAAK,GACN,EAAE,eAAe,2CAgDjB"}
1
+ {"version":3,"file":"MjmlEditor.d.ts","sourceRoot":"","sources":["../../../src/components/editor/MjmlEditor.tsx"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAY,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAcnD,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;IAC3C,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B;;;;;;;;;;;;;;OAcG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B;;;;;;;;;OASG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoKD,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,QAAQ,EACR,cAAc,EACd,SAAS,EACT,YAAuB,EACvB,YAAY,EACZ,UAAU,EACV,oBAA2B,EAC3B,eAAsB,EACtB,oBAA2B,EAC3B,qBAA6B,EAC7B,KAAK,GACN,EAAE,eAAe,2CAkDjB"}
@@ -1,158 +1,160 @@
1
- import { jsx as e, jsxs as P, Fragment as L } from "react/jsx-runtime";
1
+ import { jsx as e, jsxs as w, Fragment as L } from "react/jsx-runtime";
2
2
  import { useState as a, useEffect as s, useCallback as D, useRef as M } from "react";
3
3
  import { EditorProvider as O, useEditor as j } from "../../context/EditorContext.js";
4
4
  import { ThemeProvider as x, useTheme as C } from "../../context/ThemeContext.js";
5
- import { LiquidSchemaProvider as S } from "../../context/LiquidSchemaContext.js";
6
- import { ExtensionsProvider as K } from "../../context/ExtensionsContext.js";
7
- import { NonceProvider as N } from "../../context/NonceContext.js";
8
- import { OutlineTree as R, GLOBAL_STYLES_ID as w } from "./OutlineTree.js";
9
- import { EditorCanvas as A } from "./EditorCanvas.js";
10
- import { BlockInspector as F } from "./BlockInspector.js";
11
- import { GlobalStylesPanel as $ } from "./GlobalStylesPanel.js";
12
- import { FloatingPanel as B } from "../ui/floating-panel.js";
13
- import { createEmptyDocument as I, parseMjml as z, serializeMjml as G } from "../../lib/mjml/parser.js";
14
- function H(n) {
5
+ import { LiquidSchemaProvider as R } from "../../context/LiquidSchemaContext.js";
6
+ import { ExtensionsProvider as S } from "../../context/ExtensionsContext.js";
7
+ import { NonceProvider as K } from "../../context/NonceContext.js";
8
+ import { RenderEndpointProvider as N } from "../../context/RenderEndpointContext.js";
9
+ import { OutlineTree as A, GLOBAL_STYLES_ID as B } from "./OutlineTree.js";
10
+ import { EditorCanvas as F } from "./EditorCanvas.js";
11
+ import { BlockInspector as $ } from "./BlockInspector.js";
12
+ import { GlobalStylesPanel as z } from "./GlobalStylesPanel.js";
13
+ import { FloatingPanel as I } from "../ui/floating-panel.js";
14
+ import { createEmptyDocument as b, parseMjml as G, serializeMjml as H } from "../../lib/mjml/parser.js";
15
+ function _(n) {
15
16
  if (!n || n.trim() === "")
16
- return I();
17
+ return b();
17
18
  try {
18
- return z(n);
19
+ return G(n);
19
20
  } catch (o) {
20
- return console.error("Failed to parse MJML:", o), I();
21
+ return console.error("Failed to parse MJML:", o), b();
21
22
  }
22
23
  }
23
- function _({
24
+ function q({
24
25
  className: n,
25
26
  children: o
26
27
  }) {
27
- const { resolvedTheme: l } = C();
28
+ const { resolvedTheme: i } = C();
28
29
  return /* @__PURE__ */ e(
29
30
  "div",
30
31
  {
31
- className: `mjml-editor ${l} relative h-full w-full overflow-hidden bg-background text-foreground antialiased ${n || ""}`,
32
+ className: `mjml-editor ${i} relative h-full w-full overflow-hidden bg-background text-foreground antialiased ${n || ""}`,
32
33
  children: o
33
34
  }
34
35
  );
35
36
  }
36
- function q({
37
+ function J({
37
38
  onChange: n,
38
39
  showThemeToggle: o = !0,
39
- defaultLeftPanelOpen: l = !0,
40
- defaultRightPanelOpen: E = !1
40
+ defaultLeftPanelOpen: i = !0,
41
+ defaultRightPanelOpen: m = !1
41
42
  }) {
42
- const { state: r, undo: m, redo: f, canUndo: u, canRedo: p, deleteBlock: h, selectBlock: g } = j(), [c, v] = a(l), [d, i] = a(E), [k, b] = a("edit"), y = M(n);
43
+ const { state: r, undo: f, redo: u, canUndo: p, canRedo: h, deleteBlock: g, selectBlock: v } = j(), [d, k] = a(i), [c, l] = a(m), [T, y] = a("edit"), P = M(n);
43
44
  return s(() => {
44
- y.current = n;
45
+ P.current = n;
45
46
  }, [n]), s(() => {
46
- r.selectedBlockId && i(!0);
47
+ r.selectedBlockId && l(!0);
47
48
  }, [r.selectedBlockId]), s(() => {
48
- const T = G(r.document);
49
- y.current(T);
49
+ const E = H(r.document);
50
+ P.current(E);
50
51
  }, [r.document]), s(() => {
51
- const T = (t) => {
52
+ const E = (t) => {
52
53
  if (!(t.target instanceof HTMLInputElement || t.target instanceof HTMLTextAreaElement || t.target?.isContentEditable)) {
53
54
  if ((t.metaKey || t.ctrlKey) && t.key === "z") {
54
- t.preventDefault(), t.shiftKey ? p && f() : u && m();
55
+ t.preventDefault(), t.shiftKey ? h && u() : p && f();
55
56
  return;
56
57
  }
57
- if ((t.key === "Delete" || t.key === "Backspace") && r.selectedBlockId && r.selectedBlockId !== w) {
58
- t.preventDefault(), h(r.selectedBlockId);
58
+ if ((t.key === "Delete" || t.key === "Backspace") && r.selectedBlockId && r.selectedBlockId !== B) {
59
+ t.preventDefault(), g(r.selectedBlockId);
59
60
  return;
60
61
  }
61
62
  if (t.key === "Escape" && r.selectedBlockId) {
62
- t.preventDefault(), g(null);
63
+ t.preventDefault(), v(null);
63
64
  return;
64
65
  }
65
66
  }
66
67
  };
67
- return window.addEventListener("keydown", T), () => window.removeEventListener("keydown", T);
68
+ return window.addEventListener("keydown", E), () => window.removeEventListener("keydown", E);
68
69
  }, [
69
- m,
70
70
  f,
71
71
  u,
72
72
  p,
73
73
  h,
74
74
  g,
75
+ v,
75
76
  r.selectedBlockId
76
- ]), /* @__PURE__ */ P("div", { className: "relative h-full overflow-hidden", children: [
77
+ ]), /* @__PURE__ */ w("div", { className: "relative h-full overflow-hidden", children: [
77
78
  /* @__PURE__ */ e("div", { className: "absolute inset-0 bg-canvas", children: /* @__PURE__ */ e(
78
- A,
79
+ F,
79
80
  {
80
- activeTab: k,
81
- onTabChange: b,
82
- leftPanelOpen: c,
83
- rightPanelOpen: d,
81
+ activeTab: T,
82
+ onTabChange: y,
83
+ leftPanelOpen: d,
84
+ rightPanelOpen: c,
84
85
  showThemeToggle: o
85
86
  }
86
87
  ) }),
87
- k === "edit" && /* @__PURE__ */ P(L, { children: [
88
+ T === "edit" && /* @__PURE__ */ w(L, { children: [
88
89
  /* @__PURE__ */ e(
89
- B,
90
+ I,
90
91
  {
91
92
  side: "left",
92
- isOpen: c,
93
- onToggle: () => v(!c),
93
+ isOpen: d,
94
+ onToggle: () => k(!d),
94
95
  width: 256,
95
- children: /* @__PURE__ */ e(R, { onTogglePanel: () => v(!1) })
96
+ children: /* @__PURE__ */ e(A, { onTogglePanel: () => k(!1) })
96
97
  }
97
98
  ),
98
99
  /* @__PURE__ */ e(
99
- B,
100
+ I,
100
101
  {
101
102
  side: "right",
102
- isOpen: d,
103
- onToggle: () => i(!d),
103
+ isOpen: c,
104
+ onToggle: () => l(!c),
104
105
  width: 300,
105
- children: r.selectedBlockId === w ? /* @__PURE__ */ e(
106
- $,
106
+ children: r.selectedBlockId === B ? /* @__PURE__ */ e(
107
+ z,
107
108
  {
108
- onTogglePanel: () => i(!1)
109
+ onTogglePanel: () => l(!1)
109
110
  }
110
- ) : /* @__PURE__ */ e(F, { onTogglePanel: () => i(!1) })
111
+ ) : /* @__PURE__ */ e($, { onTogglePanel: () => l(!1) })
111
112
  }
112
113
  )
113
114
  ] })
114
115
  ] });
115
116
  }
116
- function le({
117
+ function de({
117
118
  value: n,
118
119
  onChange: o,
119
- className: l,
120
- defaultTheme: E = "system",
121
- liquidSchema: r,
122
- extensions: m,
123
- applyThemeToDocument: f = !0,
124
- showThemeToggle: u = !0,
125
- defaultLeftPanelOpen: p = !0,
126
- defaultRightPanelOpen: h = !1,
127
- nonce: g
120
+ renderEndpoint: i,
121
+ className: m,
122
+ defaultTheme: r = "system",
123
+ liquidSchema: f,
124
+ extensions: u,
125
+ applyThemeToDocument: p = !0,
126
+ showThemeToggle: h = !0,
127
+ defaultLeftPanelOpen: g = !0,
128
+ defaultRightPanelOpen: v = !1,
129
+ nonce: d
128
130
  }) {
129
- const [c, v] = a(!1);
131
+ const [k, c] = a(!1);
130
132
  s(() => {
131
- v(!0);
133
+ c(!0);
132
134
  }, []);
133
- const [d] = a(() => H(n)), i = D(
134
- (k) => {
135
- o(k);
135
+ const [l] = a(() => _(n)), T = D(
136
+ (y) => {
137
+ o(y);
136
138
  },
137
139
  [o]
138
140
  );
139
- return c ? /* @__PURE__ */ e(
141
+ return k ? /* @__PURE__ */ e(
140
142
  x,
141
143
  {
142
- defaultTheme: E,
143
- applyToDocument: f,
144
- children: /* @__PURE__ */ e(N, { nonce: g, children: /* @__PURE__ */ e(K, { extensions: m, children: /* @__PURE__ */ e(S, { schema: r, children: /* @__PURE__ */ e(_, { className: l, children: /* @__PURE__ */ e(O, { initialDocument: d, children: /* @__PURE__ */ e(
145
- q,
144
+ defaultTheme: r,
145
+ applyToDocument: p,
146
+ children: /* @__PURE__ */ e(N, { renderEndpoint: i, children: /* @__PURE__ */ e(K, { nonce: d, children: /* @__PURE__ */ e(S, { extensions: u, children: /* @__PURE__ */ e(R, { schema: f, children: /* @__PURE__ */ e(q, { className: m, children: /* @__PURE__ */ e(O, { initialDocument: l, children: /* @__PURE__ */ e(
147
+ J,
146
148
  {
147
- onChange: i,
148
- showThemeToggle: u,
149
- defaultLeftPanelOpen: p,
150
- defaultRightPanelOpen: h
149
+ onChange: T,
150
+ showThemeToggle: h,
151
+ defaultLeftPanelOpen: g,
152
+ defaultRightPanelOpen: v
151
153
  }
152
- ) }) }) }) }) })
154
+ ) }) }) }) }) }) })
153
155
  }
154
- ) : /* @__PURE__ */ e("div", { className: `h-full w-full bg-background ${l || ""}` });
156
+ ) : /* @__PURE__ */ e("div", { className: `h-full w-full bg-background ${m || ""}` });
155
157
  }
156
158
  export {
157
- le as MjmlEditor
159
+ de as MjmlEditor
158
160
  };
@@ -1 +1 @@
1
- {"version":3,"file":"SourcePreview.d.ts","sourceRoot":"","sources":["../../../src/components/editor/SourcePreview.tsx"],"names":[],"mappings":"AAGA,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAC5B,UAAU,EACV,UAAgB,GACjB,EAAE,kBAAkB,2CAqFpB"}
1
+ {"version":3,"file":"SourcePreview.d.ts","sourceRoot":"","sources":["../../../src/components/editor/SourcePreview.tsx"],"names":[],"mappings":"AAIA,UAAU,kBAAkB;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAC5B,UAAU,EACV,UAAgB,GACjB,EAAE,kBAAkB,2CAsFpB"}
@@ -1,38 +1,39 @@
1
1
  import { jsxs as r, jsx as n } from "react/jsx-runtime";
2
- import { useRef as x, useState as b, useEffect as i } from "react";
3
- import { renderMjmlString as h } from "../../lib/mjml/renderer.js";
4
- function w({
2
+ import { useRef as h, useState as f, useEffect as i } from "react";
3
+ import { renderMjmlString as g } from "../../lib/mjml/renderer.js";
4
+ import { useRenderEndpoint as p } from "../../context/RenderEndpointContext.js";
5
+ function R({
5
6
  mjmlSource: s,
6
- debounceMs: m = 300
7
+ debounceMs: d = 300
7
8
  }) {
8
- const a = x(null), [l, f] = b(s), [u, d] = b({
9
+ const m = p(), o = h(null), [a, u] = f(s), [x, c] = f({
9
10
  html: "",
10
11
  errors: []
11
12
  });
12
13
  i(() => {
13
14
  const e = setTimeout(() => {
14
- f(s);
15
- }, m);
15
+ u(s);
16
+ }, d);
16
17
  return () => clearTimeout(e);
17
- }, [s, m]), i(() => {
18
- if (!l.trim()) {
19
- d({ html: "", errors: [] });
18
+ }, [s, d]), i(() => {
19
+ if (!a.trim()) {
20
+ c({ html: "", errors: [] });
20
21
  return;
21
22
  }
22
23
  let e = !1;
23
- return h(l).then((o) => {
24
- e || d(o);
24
+ return g(a, m).then((l) => {
25
+ e || c(l);
25
26
  }), () => {
26
27
  e = !0;
27
28
  };
28
- }, [l]);
29
- const { html: c, errors: t } = u;
29
+ }, [a, m]);
30
+ const { html: b, errors: t } = x;
30
31
  return i(() => {
31
- if (a.current) {
32
- const e = a.current.contentDocument;
33
- e && (e.open(), e.write(c), e.close());
32
+ if (o.current) {
33
+ const e = o.current.contentDocument;
34
+ e && (e.open(), e.write(b), e.close());
34
35
  }
35
- }, [c]), /* @__PURE__ */ r("div", { className: "flex flex-col h-full", children: [
36
+ }, [b]), /* @__PURE__ */ r("div", { className: "flex flex-col h-full", children: [
36
37
  /* @__PURE__ */ r("div", { className: "h-11 px-4 flex items-center justify-between border-b border-border bg-background", children: [
37
38
  /* @__PURE__ */ n("span", { className: "text-sm font-semibold text-foreground", children: "Preview" }),
38
39
  t.length > 0 && /* @__PURE__ */ r("span", { className: "text-xs font-medium text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/30 px-2 py-0.5 rounded-md", children: [
@@ -44,7 +45,7 @@ function w({
44
45
  /* @__PURE__ */ n("div", { className: "flex-1 overflow-auto bg-muted", children: /* @__PURE__ */ n(
45
46
  "iframe",
46
47
  {
47
- ref: a,
48
+ ref: o,
48
49
  title: "Source Preview",
49
50
  className: "w-full h-full border-0 bg-white",
50
51
  sandbox: "allow-same-origin"
@@ -52,7 +53,7 @@ function w({
52
53
  ) }),
53
54
  t.length > 0 && /* @__PURE__ */ r("div", { className: "max-h-28 overflow-auto border-t border-border bg-amber-50/50 dark:bg-amber-900/20 p-3", children: [
54
55
  /* @__PURE__ */ n("div", { className: "text-xs font-semibold text-amber-700 dark:text-amber-300 mb-2", children: "Warnings" }),
55
- /* @__PURE__ */ n("div", { className: "space-y-1", children: t.map((e, o) => /* @__PURE__ */ r(
56
+ /* @__PURE__ */ n("div", { className: "space-y-1", children: t.map((e, l) => /* @__PURE__ */ r(
56
57
  "div",
57
58
  {
58
59
  className: "text-xs text-amber-600 dark:text-amber-400",
@@ -66,11 +67,11 @@ function w({
66
67
  e.message
67
68
  ]
68
69
  },
69
- o
70
+ l
70
71
  )) })
71
72
  ] })
72
73
  ] });
73
74
  }
74
75
  export {
75
- w as SourcePreview
76
+ R as SourcePreview
76
77
  };
@@ -0,0 +1,9 @@
1
+ import { ReactNode } from 'react';
2
+ interface RenderEndpointProviderProps {
3
+ children: ReactNode;
4
+ renderEndpoint: string;
5
+ }
6
+ export declare function RenderEndpointProvider({ children, renderEndpoint, }: RenderEndpointProviderProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function useRenderEndpoint(): string;
8
+ export {};
9
+ //# sourceMappingURL=RenderEndpointContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RenderEndpointContext.d.ts","sourceRoot":"","sources":["../../src/context/RenderEndpointContext.tsx"],"names":[],"mappings":"AACA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAIlE,UAAU,2BAA2B;IACnC,QAAQ,EAAE,SAAS,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,cAAc,GACf,EAAE,2BAA2B,2CAM7B;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAQ1C"}
@@ -0,0 +1,21 @@
1
+ import { jsx as r } from "react/jsx-runtime";
2
+ import { useContext as t, createContext as d } from "react";
3
+ const e = d(void 0);
4
+ function u({
5
+ children: n,
6
+ renderEndpoint: o
7
+ }) {
8
+ return /* @__PURE__ */ r(e.Provider, { value: o, children: n });
9
+ }
10
+ function s() {
11
+ const n = t(e);
12
+ if (!n)
13
+ throw new Error(
14
+ "useRenderEndpoint must be used within MjmlEditor (no renderEndpoint provided)"
15
+ );
16
+ return n;
17
+ }
18
+ export {
19
+ u as RenderEndpointProvider,
20
+ s as useRenderEndpoint
21
+ };
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { MjmlEditor } from './components/editor/MjmlEditor';
2
2
  export { ThemeProvider, useTheme } from './context/ThemeContext';
3
3
  export { ThemeToggle } from './components/ui/theme-toggle';
4
4
  export { useNonce } from './context/NonceContext';
5
+ export { useRenderEndpoint } from './context/RenderEndpointContext';
5
6
  export type { MjmlNode, MjmlTagName, ContentBlockType, EditorState, EditorAction, EditorExtensions, } from './types/mjml';
6
7
  export type { LiquidSchema, LiquidSchemaItem } from './types/liquid';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAG5D,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG3D,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGlD,YAAY,EACV,QAAQ,EACR,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAG5D,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAG3D,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAGlD,OAAO,EAAE,iBAAiB,EAAE,MAAM,iCAAiC,CAAC;AAGpE,YAAY,EACV,QAAQ,EACR,WAAW,EACX,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,gBAAgB,GACjB,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -1,11 +1,13 @@
1
1
  import { MjmlEditor as r } from "./components/editor/MjmlEditor.js";
2
- import { ThemeProvider as t, useTheme as f } from "./context/ThemeContext.js";
2
+ import { ThemeProvider as t, useTheme as p } from "./context/ThemeContext.js";
3
3
  import { ThemeToggle as x } from "./components/ui/theme-toggle.js";
4
- import { useNonce as h } from "./context/NonceContext.js";
4
+ import { useNonce as n } from "./context/NonceContext.js";
5
+ import { useRenderEndpoint as h } from "./context/RenderEndpointContext.js";
5
6
  export {
6
7
  r as MjmlEditor,
7
8
  t as ThemeProvider,
8
9
  x as ThemeToggle,
9
- h as useNonce,
10
- f as useTheme
10
+ n as useNonce,
11
+ h as useRenderEndpoint,
12
+ p as useTheme
11
13
  };
@@ -8,19 +8,19 @@ export interface RenderResult {
8
8
  }[];
9
9
  }
10
10
  /**
11
- * Render MJML JSON to HTML.
12
- * Custom attributes (like sc-if) are stripped to prevent mjml-browser warnings.
11
+ * Render MJML JSON to HTML via the render endpoint.
12
+ * Custom attributes (like sc-if) are stripped to prevent warnings.
13
13
  */
14
- export declare function renderMjml(document: MjmlNode): Promise<RenderResult>;
14
+ export declare function renderMjml(document: MjmlNode, renderEndpoint: string): Promise<RenderResult>;
15
15
  /**
16
- * Render MJML string to HTML.
17
- * Custom attributes (like sc-if) are stripped to prevent mjml-browser warnings.
16
+ * Render MJML string to HTML via the render endpoint.
17
+ * Custom attributes (like sc-if) are stripped to prevent warnings.
18
18
  */
19
- export declare function renderMjmlString(mjmlString: string): Promise<RenderResult>;
19
+ export declare function renderMjmlString(mjmlString: string, renderEndpoint: string): Promise<RenderResult>;
20
20
  /**
21
21
  * Render MJML JSON to HTML with block IDs preserved as CSS classes.
22
22
  * This allows clicking elements in the preview to identify the source block.
23
- * Custom attributes (like sc-if) are stripped to prevent mjml-browser warnings.
23
+ * Custom attributes (like sc-if) are stripped to prevent warnings.
24
24
  */
25
- export declare function renderMjmlInteractive(document: MjmlNode): Promise<RenderResult>;
25
+ export declare function renderMjmlInteractive(document: MjmlNode, renderEndpoint: string): Promise<RenderResult>;
26
26
  //# sourceMappingURL=renderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/lib/mjml/renderer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC9D;AAqHD;;;GAGG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,YAAY,CAAC,CAqB1E;AAkBD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,YAAY,CAAC,CAoBvB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,QAAQ,GACjB,OAAO,CAAC,YAAY,CAAC,CAuBvB"}
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../../src/lib/mjml/renderer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAE7C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC9D;AAuHD;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,YAAY,CAAC,CAYvB;AAkBD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,YAAY,CAAC,CAWvB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,YAAY,CAAC,CAavB"}
@@ -1,73 +1,71 @@
1
1
  import "uuid";
2
- const u = ["sc-if"];
3
- function p(t) {
4
- const r = Object.fromEntries(
2
+ const p = ["sc-if"];
3
+ function u(t) {
4
+ const e = Object.fromEntries(
5
5
  Object.entries(t.attributes || {}).filter(
6
- ([e]) => !u.includes(e)
6
+ ([r]) => !p.includes(r)
7
7
  )
8
8
  );
9
9
  return {
10
10
  ...t,
11
- attributes: r,
12
- children: t.children?.map(p)
11
+ attributes: e,
12
+ children: t.children?.map(u)
13
13
  };
14
14
  }
15
- let o = null;
16
- async function g() {
17
- return o || (o = (await import("mjml-browser")).default), o;
15
+ async function m(t, e) {
16
+ const r = await fetch(t, {
17
+ method: "POST",
18
+ headers: { "Content-Type": "text/plain" },
19
+ body: e
20
+ });
21
+ if (!r.ok)
22
+ throw new Error(`Render endpoint returned ${r.status}`);
23
+ return r.json();
18
24
  }
19
- function m(t) {
25
+ function l(t) {
20
26
  if (!t._id || !t.tagName.startsWith("mj-"))
21
27
  return {
22
28
  ...t,
23
- children: t.children?.map(m)
29
+ children: t.children?.map(l)
24
30
  };
25
- const r = t.attributes["mj-class"] || "", e = `block-${t._id}`, s = r ? `${r} ${e}` : e;
31
+ const e = t.attributes["mj-class"] || "", r = `block-${t._id}`, n = e ? `${e} ${r}` : r;
26
32
  return {
27
33
  ...t,
28
- attributes: { ...t.attributes, "mj-class": s },
29
- children: t.children?.map(m)
34
+ attributes: { ...t.attributes, "mj-class": n },
35
+ children: t.children?.map(l)
30
36
  };
31
37
  }
32
- function d(t) {
33
- return h(t);
34
- }
35
- function h(t, r = 0) {
36
- const e = " ".repeat(r), s = t.tagName, l = Object.entries(t.attributes || {}).filter(
37
- ([c, i]) => c !== "_id" && i !== "" && i !== void 0
38
- ).map(([c, i]) => `${c}="${f(i)}"`).join(" "), n = l ? `<${s} ${l}>` : `<${s}>`, a = `</${s}>`;
38
+ function g(t, e = 0) {
39
+ const r = " ".repeat(e), n = t.tagName, i = Object.entries(t.attributes || {}).filter(
40
+ ([c, s]) => c !== "_id" && s !== "" && s !== void 0
41
+ ).map(([c, s]) => `${c}="${d(s)}"`).join(" "), a = i ? `<${n} ${i}>` : `<${n}>`, o = `</${n}>`;
39
42
  if (t.content !== void 0)
40
- return `${e}${n}${t.content}${a}`;
43
+ return `${r}${a}${t.content}${o}`;
41
44
  if (t.children && t.children.length > 0) {
42
- const c = t.children.map((i) => h(i, r + 1)).join(`
45
+ const c = t.children.map((s) => g(s, e + 1)).join(`
43
46
  `);
44
- return `${e}${n}
47
+ return `${r}${a}
45
48
  ${c}
46
- ${e}${a}`;
49
+ ${r}${o}`;
47
50
  }
48
- return `${e}${n}${a}`;
51
+ return `${r}${a}${o}`;
49
52
  }
50
- function f(t) {
53
+ function d(t) {
51
54
  return t.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
52
55
  }
53
- function $(t) {
54
- for (const r of u) {
55
- const e = new RegExp(`\\s+${r}="[^"]*"`, "g");
56
- t = t.replace(e, "");
57
- const s = new RegExp(`\\s+${r}='[^']*'`, "g");
58
- t = t.replace(s, "");
56
+ function h(t) {
57
+ for (const e of p) {
58
+ const r = new RegExp(`\\s+${e}="[^"]*"`, "g");
59
+ t = t.replace(r, "");
60
+ const n = new RegExp(`\\s+${e}='[^']*'`, "g");
61
+ t = t.replace(n, "");
59
62
  }
60
63
  return t;
61
64
  }
62
- async function b(t) {
65
+ async function f(t, e) {
63
66
  try {
64
- const r = await g(), e = $(t), s = r(e, {
65
- validationLevel: "soft"
66
- });
67
- return {
68
- html: s.html,
69
- errors: s.errors || []
70
- };
67
+ const r = h(t);
68
+ return await m(e, r);
71
69
  } catch (r) {
72
70
  return console.error("MJML render error:", r), {
73
71
  html: '<p style="color: #dc2626; padding: 20px; background: white; margin: 0;">Error rendering email preview</p>',
@@ -75,15 +73,10 @@ async function b(t) {
75
73
  };
76
74
  }
77
75
  }
78
- async function M(t) {
76
+ async function b(t, e) {
79
77
  try {
80
- const r = await g(), e = p(t), s = m(e), l = d(s), n = r(l, {
81
- validationLevel: "soft"
82
- });
83
- return {
84
- html: n.html,
85
- errors: n.errors || []
86
- };
78
+ const r = u(t), n = l(r), i = g(n);
79
+ return await m(e, i);
87
80
  } catch (r) {
88
81
  return console.error("MJML render error:", r), {
89
82
  html: '<p style="color: #dc2626; padding: 20px; background: white; margin: 0;">Error rendering email preview</p>',
@@ -92,6 +85,6 @@ async function M(t) {
92
85
  }
93
86
  }
94
87
  export {
95
- M as renderMjmlInteractive,
96
- b as renderMjmlString
88
+ b as renderMjmlInteractive,
89
+ f as renderMjmlString
97
90
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@savvycal/mjml-editor",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -62,7 +62,6 @@
62
62
  "class-variance-authority": "^0.7.1",
63
63
  "clsx": "^2.1.1",
64
64
  "lucide-react": "^0.562.0",
65
- "mjml-browser": "^4.18.0",
66
65
  "react-arborist": "^3.4.3",
67
66
  "tailwind-merge": "^3.4.0",
68
67
  "uuid": "^13.0.0"