@kgalexander/mcreate 1.0.8 → 1.0.11

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.
@@ -142,6 +142,57 @@ var SOCIAL_OPTIONS = [
142
142
  }
143
143
  ];
144
144
 
145
+ // src/core/editor/constant/fonts.ts
146
+ var emailSafeFonts = [
147
+ { name: "Arial", value: "arial" },
148
+ { name: "Helvetica", value: "helvetica" },
149
+ { name: "Times New Roman", value: "times-new-roman" },
150
+ { name: "Tahoma", value: "tahoma" },
151
+ { name: "Trebuchet MS", value: "trebuchet-ms" },
152
+ { name: "Lucida Console", value: "lucida-console" },
153
+ { name: "Courier New", value: "courier-new" },
154
+ { name: "Georgia", value: "georgia" },
155
+ { name: "Verdana", value: "verdana" }
156
+ ];
157
+ var OtherFonts = [
158
+ { name: "Abril Fatface", value: "abril-fatface-regular", url: "https://fonts.googleapis.com/css?family=Abril+Fatface" },
159
+ { name: "Alegreya", value: "alegreya", url: "https://fonts.googleapis.com/css?family=Alegreya" },
160
+ { name: "Alegreya Sans", value: "alegreya-sans", url: "https://fonts.googleapis.com/css?family=Alegreya+Sans" },
161
+ { name: "Anton", value: "anton", url: "https://fonts.googleapis.com/css?family=Anton" },
162
+ { name: "Arimo", value: "arimo", url: "https://fonts.googleapis.com/css?family=Arimo" },
163
+ { name: "Arvo", value: "arvo", url: "https://fonts.googleapis.com/css?family=Arvo" },
164
+ { name: "Catamaran", value: "catamaran", url: "https://fonts.googleapis.com/css?family=Catamaran" },
165
+ { name: "Comic Neue", value: "comic-neue", url: "https://fonts.googleapis.com/css?family=Comic+Neue" },
166
+ { name: "Della Respira", value: "della-respira", url: "https://fonts.googleapis.com/css2?family=Della+Respira&display=swap" },
167
+ { name: "DM Sans", value: "dm-sans", url: "https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600;700&display=swap" },
168
+ { name: "Gilda Display", value: "gilda-display", url: "https://fonts.googleapis.com/css2?family=Gilda+Display&display=swap" },
169
+ { name: "Lato", value: "lato", url: "https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap" },
170
+ { name: "Lora", value: "lora", url: "https://fonts.googleapis.com/css2?family=Lora:wght@400;500;600;700&display=swap" },
171
+ { name: "Marcellus", value: "marcellus", url: "https://fonts.googleapis.com/css2?family=Marcellus&display=swap" },
172
+ { name: "Merriweather", value: "merriweather", url: "https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&display=swap" },
173
+ { name: "Merriweather Sans", value: "merriweather-sans", url: "https://fonts.googleapis.com/css2?family=Merriweather+Sans:wght@300;400;500;600;700&display=swap" },
174
+ { name: "Nanum Gothic Coding", value: "nanum-gothic-coding", url: "https://fonts.googleapis.com/css2?family=Nanum+Gothic+Coding:wght@400;700&display=swap" },
175
+ { name: "Neuton", value: "neuton", url: "https://fonts.googleapis.com/css2?family=Neuton:wght@300;400;700&display=swap" },
176
+ { name: "Noticia Text", value: "noticia-text", url: "https://fonts.googleapis.com/css2?family=Noticia+Text:wght@400;700&display=swap" },
177
+ { name: "Noto Sans", value: "noto-sans", url: "https://fonts.googleapis.com/css2?family=Noto+Sans:wght@300;400;500;600;700&display=swap" },
178
+ { name: "Open Sans", value: "open-sans", url: "https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;500;600;700&display=swap" },
179
+ { name: "Playfair Display", value: "playfair-display", url: "https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700&display=swap" },
180
+ { name: "Raleway", value: "raleway", url: "https://fonts.googleapis.com/css2?family=Raleway:wght@300;400;500;600;700&display=swap" },
181
+ { name: "Recursive", value: "recursive", url: "https://fonts.googleapis.com/css2?family=Recursive:wght@300;400;500;600;700&display=swap" },
182
+ { name: "Roboto", value: "roboto", url: "https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" },
183
+ { name: "Source Sans 3", value: "source-sans-3", url: "https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@300;400;500;600;700&display=swap" },
184
+ { name: "Source Code Pro", value: "source-code-pro", url: "https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@300;400;500;600;700&display=swap" },
185
+ { name: "Space Mono", value: "space-mono", url: "https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap" },
186
+ { name: "Syncopate", value: "syncopate", url: "https://fonts.googleapis.com/css2?family=Syncopate:wght@400;700&display=swap" },
187
+ { name: "Work Sans", value: "work-sans", url: "https://fonts.googleapis.com/css2?family=Work+Sans:wght@300;400;500;600;700&display=swap" }
188
+ ];
189
+ function getGoogleFont(fontName) {
190
+ return OtherFonts.find((f) => f.name.toLowerCase() === fontName.toLowerCase());
191
+ }
192
+ function getGoogleFontUrl(fontName) {
193
+ return getGoogleFont(fontName)?.url;
194
+ }
195
+
145
196
  // src/core/editor/utils/border.ts
146
197
  var DEFAULT_BORDER = {
147
198
  width: 0,
@@ -222,16 +273,6 @@ var ALIGNMENT_ICONS = {
222
273
  center: "https://mzyngaqmbvhpgmmipndy.supabase.co/storage/v1/object/public/Maillow/icons/align-vertical-space-around-center.svg",
223
274
  right: "https://mzyngaqmbvhpgmmipndy.supabase.co/storage/v1/object/public/Maillow/icons/align-vertical-space-around-right.svg"
224
275
  };
225
- var FONTS = [
226
- "Arial",
227
- "Helvetica",
228
- "Times New Roman",
229
- "Courier New",
230
- "Verdana",
231
- "Tahoma",
232
- "Trebuchet MS",
233
- "Georgia"
234
- ];
235
276
  var TEXT_ALIGNMENT_ICONS = {
236
277
  left: AlignLeftIcon,
237
278
  center: AlignCenterIcon,
@@ -1120,6 +1161,35 @@ function rootChildrenToMjml(children, parentContext) {
1120
1161
  return "";
1121
1162
  }).join("\n");
1122
1163
  }
1164
+ function collectUsedGoogleFonts(template) {
1165
+ const fontNames = /* @__PURE__ */ new Set();
1166
+ const googleFontNames = new Set(OtherFonts.map((f) => f.name.toLowerCase()));
1167
+ function extractFromFontFamily(fontFamily) {
1168
+ if (!fontFamily) return;
1169
+ const fonts = fontFamily.split(",").map((f) => f.trim().replace(/['"]/g, ""));
1170
+ for (const f of fonts) {
1171
+ if (googleFontNames.has(f.toLowerCase())) {
1172
+ const original = OtherFonts.find((of) => of.name.toLowerCase() === f.toLowerCase());
1173
+ if (original) fontNames.add(original.name);
1174
+ }
1175
+ }
1176
+ }
1177
+ function scanElement(el) {
1178
+ if (!el) return;
1179
+ extractFromFontFamily(el.attributes?.["font-family"]);
1180
+ const content = el.data?.value?.content;
1181
+ if (typeof content === "string") {
1182
+ const matches = content.matchAll(/font-family:\s*([^;"]+)/gi);
1183
+ for (const m of matches) extractFromFontFamily(m[1]);
1184
+ }
1185
+ if (Array.isArray(el.children)) el.children.forEach(scanElement);
1186
+ }
1187
+ template.content.forEach(scanElement);
1188
+ return [...fontNames].map((name) => {
1189
+ const url = getGoogleFontUrl(name);
1190
+ return url ? { name, url } : null;
1191
+ }).filter((f) => f !== null);
1192
+ }
1123
1193
  function json2mjml(template, mode = "production", options = {}) {
1124
1194
  const showCompanyFooter = template.content[0]?.data?.value?.showCompanyFooter ?? true;
1125
1195
  const footerMutation = needsCompanyFooterMutation(template);
@@ -1165,12 +1235,15 @@ function json2mjml(template, mode = "production", options = {}) {
1165
1235
  allContent += pageContent;
1166
1236
  }
1167
1237
  const linkColor = template.content[0]?.data?.value?.linkColor || "#0000ff";
1238
+ const usedGoogleFonts = collectUsedGoogleFonts(template);
1239
+ const mjFontTags = usedGoogleFonts.map((f) => `<mj-font name="${f.name}" href="${f.url}" />`).join("\n ");
1168
1240
  return `
1169
1241
  <mjml>
1170
1242
 
1171
1243
  <mj-head>
1172
1244
 
1173
1245
  <mj-breakpoint width="480px" />
1246
+ ${mjFontTags}
1174
1247
 
1175
1248
  <mj-style>
1176
1249
  p { margin: 0px 0px 0px 0px !important; }
@@ -3527,6 +3600,26 @@ var ShadowDomRenderer = memo(function ShadowDomRenderer2({
3527
3600
  shadowRootRef.current.appendChild(styleEl);
3528
3601
  shadowRootRef.current.appendChild(contentWrapper);
3529
3602
  }, [html]);
3603
+ useEffect(() => {
3604
+ const templateState = useEditorStore.getState().template;
3605
+ if (!templateState) return;
3606
+ const htmlStr = html + JSON.stringify(templateState);
3607
+ const neededUrls = /* @__PURE__ */ new Set();
3608
+ for (const font of OtherFonts) {
3609
+ if (font.url && htmlStr.includes(font.name)) {
3610
+ neededUrls.add(font.url);
3611
+ }
3612
+ }
3613
+ for (const url of neededUrls) {
3614
+ if (!document.querySelector(`link[data-google-font][href="${url}"]`)) {
3615
+ const link = document.createElement("link");
3616
+ link.rel = "stylesheet";
3617
+ link.href = url;
3618
+ link.setAttribute("data-google-font", "true");
3619
+ document.head.appendChild(link);
3620
+ }
3621
+ }
3622
+ }, [html]);
3530
3623
  useEffect(() => {
3531
3624
  if (!shadowRootRef.current) return;
3532
3625
  const styleEl = shadowRootRef.current.querySelector("#editor-styles");
@@ -12942,13 +13035,14 @@ import { MoreHorizontalIcon as MoreHorizontalIcon3, PlusCircleIcon } from "lucid
12942
13035
  import { createContext, useContext, useState as useState5 } from "react";
12943
13036
  import { jsx as jsx31 } from "react/jsx-runtime";
12944
13037
  var SidebarContext = createContext(null);
12945
- var PICKER_VIEWS = ["color", "images", "add-social"];
13038
+ var PICKER_VIEWS = ["color", "images", "add-social", "fonts"];
12946
13039
  function SidebarProvider({ children }) {
12947
13040
  const [activeView, setActiveViewState] = useState5("elements");
12948
13041
  const [lastView, setLastView] = useState5("elements");
12949
13042
  const [colorType, setColorType] = useState5("Color");
12950
13043
  const [colorTarget, setColorTarget] = useState5(null);
12951
13044
  const [imageTarget, setImageTarget] = useState5(null);
13045
+ const [fontTarget, setFontTarget] = useState5(null);
12952
13046
  const setActiveView = (view) => {
12953
13047
  if (view !== activeView) {
12954
13048
  if (!PICKER_VIEWS.includes(activeView)) {
@@ -12957,7 +13051,7 @@ function SidebarProvider({ children }) {
12957
13051
  }
12958
13052
  setActiveViewState(view);
12959
13053
  };
12960
- return /* @__PURE__ */ jsx31(SidebarContext.Provider, { value: { activeView, setActiveView, lastView, colorType, setColorType, colorTarget, setColorTarget, imageTarget, setImageTarget }, children });
13054
+ return /* @__PURE__ */ jsx31(SidebarContext.Provider, { value: { activeView, setActiveView, lastView, colorType, setColorType, colorTarget, setColorTarget, imageTarget, setImageTarget, fontTarget, setFontTarget }, children });
12961
13055
  }
12962
13056
  function useSidebarContext() {
12963
13057
  const context = useContext(SidebarContext);
@@ -14702,6 +14796,16 @@ function validate_editor_onPreview(template, mergeFields) {
14702
14796
  const is_over_size_limit = templateSize > 50 * 1024;
14703
14797
  return { invalid_merge_fields, missing_links, is_over_size_limit, placeholder_property_images };
14704
14798
  }
14799
+ function validate_campaign_onCreate() {
14800
+ return true;
14801
+ }
14802
+ function campaign_validation_warnings() {
14803
+ return {
14804
+ invalid_merge_fields: true,
14805
+ missing_properties_attributes: true,
14806
+ missing_links: true
14807
+ };
14808
+ }
14705
14809
 
14706
14810
  // src/core/editor/components/preview.tsx
14707
14811
  import { jsx as jsx49, jsxs as jsxs30 } from "react/jsx-runtime";
@@ -16026,13 +16130,14 @@ export {
16026
16130
  getParentByIdx,
16027
16131
  SOCIAL_ITEMS,
16028
16132
  SOCIAL_OPTIONS,
16133
+ emailSafeFonts,
16134
+ OtherFonts,
16029
16135
  parseBorder,
16030
16136
  formatBorder,
16031
16137
  parsePrice,
16032
16138
  MAX_TEMPLATE_SIZE,
16033
16139
  BUTTON_ALIGNMENTS,
16034
16140
  ALIGNMENT_ICONS,
16035
- FONTS,
16036
16141
  TEXT_ALIGNMENT_ICONS,
16037
16142
  TEXT_TYPE_OPTIONS,
16038
16143
  DEFAULT_FONT_SIZE,
@@ -16079,6 +16184,9 @@ export {
16079
16184
  SidebarProvider,
16080
16185
  useSidebarContext,
16081
16186
  Textarea,
16187
+ validate_editor_onPreview,
16188
+ validate_campaign_onCreate,
16189
+ campaign_validation_warnings,
16082
16190
  Preview,
16083
16191
  History,
16084
16192
  MAILLOW_EMAIL_EDITOR_VERSION,
@@ -5,7 +5,7 @@ import {
5
5
  MAILLOW_EMAIL_EDITOR_VERSION,
6
6
  Preview,
7
7
  useEditorStore
8
- } from "./chunk-FJ4KXNNM.mjs";
8
+ } from "./chunk-CLU5KQRY.mjs";
9
9
  export {
10
10
  Editor,
11
11
  History,
package/dist/index.d.mts CHANGED
@@ -320,6 +320,46 @@ declare function TemplatePage({ templateId, initialTemplate, onSave, onToast, on
320
320
 
321
321
  declare const MAX_TEMPLATE_SIZE: number;
322
322
 
323
+ type FontEntry = {
324
+ name: string;
325
+ value: string;
326
+ url?: string;
327
+ };
328
+ declare const emailSafeFonts: FontEntry[];
329
+ declare const OtherFonts: FontEntry[];
330
+
331
+ interface MergeFieldType {
332
+ label: string;
333
+ value: string;
334
+ }
335
+ interface MissingLinkType {
336
+ type: 'button' | 'social-item';
337
+ label: string;
338
+ }
339
+ interface PreviewValidationType {
340
+ invalid_merge_fields: string[];
341
+ missing_links: MissingLinkType[];
342
+ is_over_size_limit: boolean;
343
+ placeholder_property_images: number;
344
+ }
345
+
346
+ /**
347
+ * Validate a template when the user previews the template
348
+ */
349
+ declare function validate_editor_onPreview(template: any, mergeFields: MergeFieldType[]): PreviewValidationType;
350
+ /**
351
+ * Validate a campaign template when the user creates a new campaign
352
+ */
353
+ declare function validate_campaign_onCreate(): boolean;
354
+ /**
355
+ * Return validation warnings for a campaign template
356
+ */
357
+ declare function campaign_validation_warnings(): {
358
+ invalid_merge_fields: boolean;
359
+ missing_properties_attributes: boolean;
360
+ missing_links: boolean;
361
+ };
362
+
323
363
  /**
324
364
  * JSON to MJML Converter
325
365
  * Converts template JSON to MJML string for rendering
@@ -338,4 +378,4 @@ interface RenderOptions {
338
378
  */
339
379
  declare function json2mjml(template: TemplateJSON, mode?: RenderMode, options?: RenderOptions): string;
340
380
 
341
- export { Editor, type ImageData, MAX_TEMPLATE_SIZE, type MergeField, type OnDeleteCallback, type OnDuplicateCallback, type OnExitCallback, type OnImageUploadCallback, type OnSaveCallback, type OnToastCallback, type PaidLevel, type TemplateJSON, TemplatePage, type ToastOptions, type ToastType, json2mjml };
381
+ export { Editor, type ImageData, MAX_TEMPLATE_SIZE, type MergeField, type MergeFieldType, type MissingLinkType, type OnDeleteCallback, type OnDuplicateCallback, type OnExitCallback, type OnImageUploadCallback, type OnSaveCallback, type OnToastCallback, OtherFonts, type PaidLevel, type PreviewValidationType, type TemplateJSON, TemplatePage, type ToastOptions, type ToastType, campaign_validation_warnings, emailSafeFonts, json2mjml, validate_campaign_onCreate, validate_editor_onPreview };
package/dist/index.d.ts CHANGED
@@ -320,6 +320,46 @@ declare function TemplatePage({ templateId, initialTemplate, onSave, onToast, on
320
320
 
321
321
  declare const MAX_TEMPLATE_SIZE: number;
322
322
 
323
+ type FontEntry = {
324
+ name: string;
325
+ value: string;
326
+ url?: string;
327
+ };
328
+ declare const emailSafeFonts: FontEntry[];
329
+ declare const OtherFonts: FontEntry[];
330
+
331
+ interface MergeFieldType {
332
+ label: string;
333
+ value: string;
334
+ }
335
+ interface MissingLinkType {
336
+ type: 'button' | 'social-item';
337
+ label: string;
338
+ }
339
+ interface PreviewValidationType {
340
+ invalid_merge_fields: string[];
341
+ missing_links: MissingLinkType[];
342
+ is_over_size_limit: boolean;
343
+ placeholder_property_images: number;
344
+ }
345
+
346
+ /**
347
+ * Validate a template when the user previews the template
348
+ */
349
+ declare function validate_editor_onPreview(template: any, mergeFields: MergeFieldType[]): PreviewValidationType;
350
+ /**
351
+ * Validate a campaign template when the user creates a new campaign
352
+ */
353
+ declare function validate_campaign_onCreate(): boolean;
354
+ /**
355
+ * Return validation warnings for a campaign template
356
+ */
357
+ declare function campaign_validation_warnings(): {
358
+ invalid_merge_fields: boolean;
359
+ missing_properties_attributes: boolean;
360
+ missing_links: boolean;
361
+ };
362
+
323
363
  /**
324
364
  * JSON to MJML Converter
325
365
  * Converts template JSON to MJML string for rendering
@@ -338,4 +378,4 @@ interface RenderOptions {
338
378
  */
339
379
  declare function json2mjml(template: TemplateJSON, mode?: RenderMode, options?: RenderOptions): string;
340
380
 
341
- export { Editor, type ImageData, MAX_TEMPLATE_SIZE, type MergeField, type OnDeleteCallback, type OnDuplicateCallback, type OnExitCallback, type OnImageUploadCallback, type OnSaveCallback, type OnToastCallback, type PaidLevel, type TemplateJSON, TemplatePage, type ToastOptions, type ToastType, json2mjml };
381
+ export { Editor, type ImageData, MAX_TEMPLATE_SIZE, type MergeField, type MergeFieldType, type MissingLinkType, type OnDeleteCallback, type OnDuplicateCallback, type OnExitCallback, type OnImageUploadCallback, type OnSaveCallback, type OnToastCallback, OtherFonts, type PaidLevel, type PreviewValidationType, type TemplateJSON, TemplatePage, type ToastOptions, type ToastType, campaign_validation_warnings, emailSafeFonts, json2mjml, validate_campaign_onCreate, validate_editor_onPreview };