@kgalexander/mcreate 1.0.11 → 1.0.13

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.
@@ -1243,6 +1243,8 @@ function json2mjml(template, mode = "production", options = {}) {
1243
1243
  <mj-head>
1244
1244
 
1245
1245
  <mj-breakpoint width="480px" />
1246
+ <mj-preview>${options.previewText ?? template?.preview ?? ""}</mj-preview>
1247
+
1246
1248
  ${mjFontTags}
1247
1249
 
1248
1250
  <mj-style>
@@ -14717,6 +14719,7 @@ import { useEffect as useEffect15, useState as useState16, useRef as useRef11 }
14717
14719
 
14718
14720
  // src/validate/helpers.ts
14719
14721
  var MERGE_FIELD_REGEX2 = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
14722
+ var HREF_REGEX = /<a[^>]+href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gi;
14720
14723
  function extractMergeFields(node) {
14721
14724
  const fields = [];
14722
14725
  if (!node || typeof node !== "object") return fields;
@@ -14762,6 +14765,77 @@ function extractEmptyLinks(node) {
14762
14765
  }
14763
14766
  return results;
14764
14767
  }
14768
+ var PROPERTY_TYPES = /* @__PURE__ */ new Set(["property-card", "property-card-single-two", "property-card-triple-item"]);
14769
+ function getLinkType(node) {
14770
+ if (node.type === "button") return "button";
14771
+ if (node.type === "social-item") return "social";
14772
+ if (node.type === "image") return "image";
14773
+ if (PROPERTY_TYPES.has(node.type)) return "property";
14774
+ if (node.type === "text") return "text";
14775
+ return "unknown";
14776
+ }
14777
+ function getLinkText(node) {
14778
+ if (node.type === "button") return node.data?.value?.content || "Button";
14779
+ if (node.type === "social-item") return node.data?.value?.socialType || node.attributes?.alt || "Social";
14780
+ if (node.type === "image") return node.attributes?.alt || "Image";
14781
+ if (PROPERTY_TYPES.has(node.type)) return node.attributes?.address || node.attributes?.city || "Property";
14782
+ return "";
14783
+ }
14784
+ function extractLinks(template) {
14785
+ const seen = /* @__PURE__ */ new Map();
14786
+ const results = [];
14787
+ let position = 0;
14788
+ function addLink(url, linkType, linkText) {
14789
+ if (!url || !url.trim()) return;
14790
+ if (url.startsWith("mailto:")) return;
14791
+ if (url.startsWith("data:")) return;
14792
+ const trimmed = url.trim();
14793
+ if (seen.has(trimmed)) return;
14794
+ position++;
14795
+ seen.set(trimmed, results.length);
14796
+ results.push({
14797
+ url: trimmed,
14798
+ link_type: linkType,
14799
+ link_text: linkText || "",
14800
+ link_position: position
14801
+ });
14802
+ }
14803
+ function walk(node, isCompanyFooter) {
14804
+ if (!node || typeof node !== "object") return;
14805
+ const nodeIsFooter = isCompanyFooter || node.data?.value?.isCompanyFooter === true;
14806
+ const href = node.attributes?.href;
14807
+ if (href && node.type) {
14808
+ const linkType = nodeIsFooter ? "company_brand" : getLinkType(node);
14809
+ const linkText = getLinkText(node);
14810
+ addLink(href, linkType, linkText);
14811
+ }
14812
+ if (node.type === "text") {
14813
+ const content = node.data?.value?.content;
14814
+ if (typeof content === "string") {
14815
+ HREF_REGEX.lastIndex = 0;
14816
+ let match;
14817
+ while ((match = HREF_REGEX.exec(content)) !== null) {
14818
+ const [, linkUrl, linkText] = match;
14819
+ if (linkUrl) {
14820
+ addLink(linkUrl, nodeIsFooter ? "company_brand" : "text", linkText?.replace(/<[^>]*>/g, "") || "");
14821
+ }
14822
+ }
14823
+ }
14824
+ }
14825
+ if (Array.isArray(node.children)) {
14826
+ for (const child of node.children) {
14827
+ walk(child, nodeIsFooter);
14828
+ }
14829
+ }
14830
+ if (Array.isArray(node.content)) {
14831
+ for (const page of node.content) {
14832
+ walk(page, false);
14833
+ }
14834
+ }
14835
+ }
14836
+ walk(template, false);
14837
+ return results;
14838
+ }
14765
14839
 
14766
14840
  // src/validate/index.ts
14767
14841
  var PROPERTY_CARD_TYPES = /* @__PURE__ */ new Set(["property-card", "property-card-single-two", "property-card-triple-item"]);
@@ -16184,6 +16258,7 @@ export {
16184
16258
  SidebarProvider,
16185
16259
  useSidebarContext,
16186
16260
  Textarea,
16261
+ extractLinks,
16187
16262
  validate_editor_onPreview,
16188
16263
  validate_campaign_onCreate,
16189
16264
  campaign_validation_warnings,
@@ -5,7 +5,7 @@ import {
5
5
  MAILLOW_EMAIL_EDITOR_VERSION,
6
6
  Preview,
7
7
  useEditorStore
8
- } from "./chunk-CLU5KQRY.mjs";
8
+ } from "./chunk-JDECYTJI.mjs";
9
9
  export {
10
10
  Editor,
11
11
  History,
package/dist/index.d.mts CHANGED
@@ -254,6 +254,7 @@ type TemplateJSON = {
254
254
  id: string;
255
255
  name: string;
256
256
  image: string;
257
+ preview: string;
257
258
  version: string;
258
259
  published: boolean;
259
260
  creator: string;
@@ -342,6 +343,13 @@ interface PreviewValidationType {
342
343
  is_over_size_limit: boolean;
343
344
  placeholder_property_images: number;
344
345
  }
346
+ type LinkType = 'social' | 'property' | 'button' | 'text' | 'image' | 'company_brand' | 'unknown';
347
+ interface ExtractedLink {
348
+ url: string;
349
+ link_type: LinkType;
350
+ link_text: string;
351
+ link_position: number;
352
+ }
345
353
 
346
354
  /**
347
355
  * Validate a template when the user previews the template
@@ -360,6 +368,13 @@ declare function campaign_validation_warnings(): {
360
368
  missing_links: boolean;
361
369
  };
362
370
 
371
+ /**
372
+ * Recursively extract all links from template JSON.
373
+ * Link type is derived from the element type — no HTML parsing needed
374
+ * (except for inline <a> tags in text elements).
375
+ */
376
+ declare function extractLinks(template: TemplateJSON): ExtractedLink[];
377
+
363
378
  /**
364
379
  * JSON to MJML Converter
365
380
  * Converts template JSON to MJML string for rendering
@@ -368,6 +383,7 @@ declare function campaign_validation_warnings(): {
368
383
  type RenderMode = 'production' | 'editing';
369
384
  interface RenderOptions {
370
385
  isPaidLevel?: number;
386
+ previewText?: string;
371
387
  }
372
388
  /**
373
389
  * Convert template JSON to MJML string
@@ -378,4 +394,4 @@ interface RenderOptions {
378
394
  */
379
395
  declare function json2mjml(template: TemplateJSON, mode?: RenderMode, options?: RenderOptions): string;
380
396
 
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 };
397
+ export { Editor, type ExtractedLink, type ImageData, type LinkType, 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, extractLinks, json2mjml, validate_campaign_onCreate, validate_editor_onPreview };
package/dist/index.d.ts CHANGED
@@ -254,6 +254,7 @@ type TemplateJSON = {
254
254
  id: string;
255
255
  name: string;
256
256
  image: string;
257
+ preview: string;
257
258
  version: string;
258
259
  published: boolean;
259
260
  creator: string;
@@ -342,6 +343,13 @@ interface PreviewValidationType {
342
343
  is_over_size_limit: boolean;
343
344
  placeholder_property_images: number;
344
345
  }
346
+ type LinkType = 'social' | 'property' | 'button' | 'text' | 'image' | 'company_brand' | 'unknown';
347
+ interface ExtractedLink {
348
+ url: string;
349
+ link_type: LinkType;
350
+ link_text: string;
351
+ link_position: number;
352
+ }
345
353
 
346
354
  /**
347
355
  * Validate a template when the user previews the template
@@ -360,6 +368,13 @@ declare function campaign_validation_warnings(): {
360
368
  missing_links: boolean;
361
369
  };
362
370
 
371
+ /**
372
+ * Recursively extract all links from template JSON.
373
+ * Link type is derived from the element type — no HTML parsing needed
374
+ * (except for inline <a> tags in text elements).
375
+ */
376
+ declare function extractLinks(template: TemplateJSON): ExtractedLink[];
377
+
363
378
  /**
364
379
  * JSON to MJML Converter
365
380
  * Converts template JSON to MJML string for rendering
@@ -368,6 +383,7 @@ declare function campaign_validation_warnings(): {
368
383
  type RenderMode = 'production' | 'editing';
369
384
  interface RenderOptions {
370
385
  isPaidLevel?: number;
386
+ previewText?: string;
371
387
  }
372
388
  /**
373
389
  * Convert template JSON to MJML string
@@ -378,4 +394,4 @@ interface RenderOptions {
378
394
  */
379
395
  declare function json2mjml(template: TemplateJSON, mode?: RenderMode, options?: RenderOptions): string;
380
396
 
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 };
397
+ export { Editor, type ExtractedLink, type ImageData, type LinkType, 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, extractLinks, json2mjml, validate_campaign_onCreate, validate_editor_onPreview };
package/dist/index.js CHANGED
@@ -1326,6 +1326,8 @@ function json2mjml(template, mode = "production", options = {}) {
1326
1326
  <mj-head>
1327
1327
 
1328
1328
  <mj-breakpoint width="480px" />
1329
+ <mj-preview>${options.previewText ?? template?.preview ?? ""}</mj-preview>
1330
+
1329
1331
  ${mjFontTags}
1330
1332
 
1331
1333
  <mj-style>
@@ -15471,11 +15473,83 @@ function extractEmptyLinks(node) {
15471
15473
  }
15472
15474
  return results;
15473
15475
  }
15474
- var MERGE_FIELD_REGEX2;
15476
+ function getLinkType(node) {
15477
+ if (node.type === "button") return "button";
15478
+ if (node.type === "social-item") return "social";
15479
+ if (node.type === "image") return "image";
15480
+ if (PROPERTY_TYPES.has(node.type)) return "property";
15481
+ if (node.type === "text") return "text";
15482
+ return "unknown";
15483
+ }
15484
+ function getLinkText(node) {
15485
+ if (node.type === "button") return node.data?.value?.content || "Button";
15486
+ if (node.type === "social-item") return node.data?.value?.socialType || node.attributes?.alt || "Social";
15487
+ if (node.type === "image") return node.attributes?.alt || "Image";
15488
+ if (PROPERTY_TYPES.has(node.type)) return node.attributes?.address || node.attributes?.city || "Property";
15489
+ return "";
15490
+ }
15491
+ function extractLinks(template) {
15492
+ const seen = /* @__PURE__ */ new Map();
15493
+ const results = [];
15494
+ let position = 0;
15495
+ function addLink(url, linkType, linkText) {
15496
+ if (!url || !url.trim()) return;
15497
+ if (url.startsWith("mailto:")) return;
15498
+ if (url.startsWith("data:")) return;
15499
+ const trimmed = url.trim();
15500
+ if (seen.has(trimmed)) return;
15501
+ position++;
15502
+ seen.set(trimmed, results.length);
15503
+ results.push({
15504
+ url: trimmed,
15505
+ link_type: linkType,
15506
+ link_text: linkText || "",
15507
+ link_position: position
15508
+ });
15509
+ }
15510
+ function walk(node, isCompanyFooter) {
15511
+ if (!node || typeof node !== "object") return;
15512
+ const nodeIsFooter = isCompanyFooter || node.data?.value?.isCompanyFooter === true;
15513
+ const href = node.attributes?.href;
15514
+ if (href && node.type) {
15515
+ const linkType = nodeIsFooter ? "company_brand" : getLinkType(node);
15516
+ const linkText = getLinkText(node);
15517
+ addLink(href, linkType, linkText);
15518
+ }
15519
+ if (node.type === "text") {
15520
+ const content = node.data?.value?.content;
15521
+ if (typeof content === "string") {
15522
+ HREF_REGEX.lastIndex = 0;
15523
+ let match;
15524
+ while ((match = HREF_REGEX.exec(content)) !== null) {
15525
+ const [, linkUrl, linkText] = match;
15526
+ if (linkUrl) {
15527
+ addLink(linkUrl, nodeIsFooter ? "company_brand" : "text", linkText?.replace(/<[^>]*>/g, "") || "");
15528
+ }
15529
+ }
15530
+ }
15531
+ }
15532
+ if (Array.isArray(node.children)) {
15533
+ for (const child of node.children) {
15534
+ walk(child, nodeIsFooter);
15535
+ }
15536
+ }
15537
+ if (Array.isArray(node.content)) {
15538
+ for (const page of node.content) {
15539
+ walk(page, false);
15540
+ }
15541
+ }
15542
+ }
15543
+ walk(template, false);
15544
+ return results;
15545
+ }
15546
+ var MERGE_FIELD_REGEX2, HREF_REGEX, PROPERTY_TYPES;
15475
15547
  var init_helpers2 = __esm({
15476
15548
  "src/validate/helpers.ts"() {
15477
15549
  "use strict";
15478
15550
  MERGE_FIELD_REGEX2 = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
15551
+ HREF_REGEX = /<a[^>]+href=["']([^"']+)["'][^>]*>(.*?)<\/a>/gi;
15552
+ PROPERTY_TYPES = /* @__PURE__ */ new Set(["property-card", "property-card-single-two", "property-card-triple-item"]);
15479
15553
  }
15480
15554
  });
15481
15555
 
@@ -16919,6 +16993,7 @@ __export(index_exports, {
16919
16993
  TemplatePage: () => TemplatePage,
16920
16994
  campaign_validation_warnings: () => campaign_validation_warnings,
16921
16995
  emailSafeFonts: () => emailSafeFonts,
16996
+ extractLinks: () => extractLinks,
16922
16997
  json2mjml: () => json2mjml,
16923
16998
  validate_campaign_onCreate: () => validate_campaign_onCreate,
16924
16999
  validate_editor_onPreview: () => validate_editor_onPreview
@@ -18225,11 +18300,11 @@ function PreviewSection({
18225
18300
  /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
18226
18301
  Button,
18227
18302
  {
18228
- variant: "outline",
18303
+ variant: "ghost",
18229
18304
  size: "icon-sm",
18230
18305
  onClick: () => scroll("left"),
18231
18306
  className: cn(
18232
- "absolute left-1 top-1/2 -translate-y-1/2 z-10 cursor-pointer rounded-full",
18307
+ "absolute left-1 top-1/2 -translate-y-1/2 z-10 cursor-pointer shadow-md border border-border rounded-full bg-white text-muted-foreground hover:bg-muted hover:dark:bg-muted-foreground",
18233
18308
  !canGoLeft && "hidden"
18234
18309
  ),
18235
18310
  children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_lucide_react34.ChevronLeftIcon, { className: "size-6 " })
@@ -18248,11 +18323,11 @@ function PreviewSection({
18248
18323
  /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(
18249
18324
  Button,
18250
18325
  {
18251
- variant: "outline",
18326
+ variant: "ghost",
18252
18327
  size: "icon-sm",
18253
18328
  onClick: () => scroll("right"),
18254
18329
  className: cn(
18255
- "absolute right-1 top-1/2 -translate-y-1/2 z-10 cursor-pointer rounded-full",
18330
+ "absolute right-1 top-1/2 -translate-y-1/2 z-10 cursor-pointer shadow-md border border-border rounded-full bg-white text-muted-foreground hover:bg-muted hover:dark:bg-muted-foreground",
18256
18331
  !canGoRight && "hidden"
18257
18332
  ),
18258
18333
  children: /* @__PURE__ */ (0, import_jsx_runtime57.jsx)(import_lucide_react34.ChevronRightIcon, { className: "size-6" })
@@ -29505,6 +29580,7 @@ function TemplatePage({
29505
29580
  init_configuration();
29506
29581
  init_fonts();
29507
29582
  init_validate();
29583
+ init_helpers2();
29508
29584
  init_json2mjml();
29509
29585
  // Annotate the CommonJS export names for ESM import in node:
29510
29586
  0 && (module.exports = {
@@ -29514,6 +29590,7 @@ init_json2mjml();
29514
29590
  TemplatePage,
29515
29591
  campaign_validation_warnings,
29516
29592
  emailSafeFonts,
29593
+ extractLinks,
29517
29594
  json2mjml,
29518
29595
  validate_campaign_onCreate,
29519
29596
  validate_editor_onPreview
package/dist/index.mjs CHANGED
@@ -51,6 +51,7 @@ import {
51
51
  campaign_validation_warnings,
52
52
  cn,
53
53
  emailSafeFonts,
54
+ extractLinks,
54
55
  formatBorder,
55
56
  getElementDisplayName,
56
57
  getParentByIdx,
@@ -64,7 +65,7 @@ import {
64
65
  useSidebarContext,
65
66
  validate_campaign_onCreate,
66
67
  validate_editor_onPreview
67
- } from "./chunk-CLU5KQRY.mjs";
68
+ } from "./chunk-JDECYTJI.mjs";
68
69
 
69
70
  // src/core/editor/components/email-template-v2/header.tsx
70
71
  import { ArrowLeftIcon, CopyIcon, MegaphoneIcon, MoreHorizontalIcon, PencilIcon, SendIcon, TrashIcon } from "lucide-react";
@@ -1338,11 +1339,11 @@ function PreviewSection({
1338
1339
  /* @__PURE__ */ jsx5(
1339
1340
  Button,
1340
1341
  {
1341
- variant: "outline",
1342
+ variant: "ghost",
1342
1343
  size: "icon-sm",
1343
1344
  onClick: () => scroll("left"),
1344
1345
  className: cn(
1345
- "absolute left-1 top-1/2 -translate-y-1/2 z-10 cursor-pointer rounded-full",
1346
+ "absolute left-1 top-1/2 -translate-y-1/2 z-10 cursor-pointer shadow-md border border-border rounded-full bg-white text-muted-foreground hover:bg-muted hover:dark:bg-muted-foreground",
1346
1347
  !canGoLeft && "hidden"
1347
1348
  ),
1348
1349
  children: /* @__PURE__ */ jsx5(ChevronLeftIcon, { className: "size-6 " })
@@ -1361,11 +1362,11 @@ function PreviewSection({
1361
1362
  /* @__PURE__ */ jsx5(
1362
1363
  Button,
1363
1364
  {
1364
- variant: "outline",
1365
+ variant: "ghost",
1365
1366
  size: "icon-sm",
1366
1367
  onClick: () => scroll("right"),
1367
1368
  className: cn(
1368
- "absolute right-1 top-1/2 -translate-y-1/2 z-10 cursor-pointer rounded-full",
1369
+ "absolute right-1 top-1/2 -translate-y-1/2 z-10 cursor-pointer shadow-md border border-border rounded-full bg-white text-muted-foreground hover:bg-muted hover:dark:bg-muted-foreground",
1369
1370
  !canGoRight && "hidden"
1370
1371
  ),
1371
1372
  children: /* @__PURE__ */ jsx5(ChevronRightIcon, { className: "size-6" })
@@ -12283,7 +12284,7 @@ function useAutoSave() {
12283
12284
  // src/core/editor/components/email-template-v2/template-page.tsx
12284
12285
  import "react-json-view-lite/dist/index.css";
12285
12286
  import { jsx as jsx75, jsxs as jsxs60 } from "react/jsx-runtime";
12286
- var Editor2 = lazy(() => import("./core-CH5LA5D6.mjs").then((module) => ({
12287
+ var Editor2 = lazy(() => import("./core-IZXIUSSS.mjs").then((module) => ({
12287
12288
  default: module.Editor
12288
12289
  })));
12289
12290
  function TemplatePage({
@@ -12369,6 +12370,7 @@ export {
12369
12370
  TemplatePage,
12370
12371
  campaign_validation_warnings,
12371
12372
  emailSafeFonts,
12373
+ extractLinks,
12372
12374
  json2mjml,
12373
12375
  validate_campaign_onCreate,
12374
12376
  validate_editor_onPreview
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kgalexander/mcreate",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "Maillow email template editor",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",