@dev.smartpricing/message-composer-utils 4.2.11-ux.0 → 4.2.11-ux.2

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 (65) hide show
  1. package/dist/schemas/ai-integration.js +34 -0
  2. package/dist/schemas/ai-integration.js.map +1 -0
  3. package/dist/schemas/brand.js +40 -0
  4. package/dist/schemas/brand.js.map +1 -0
  5. package/dist/schemas/common.js +34 -0
  6. package/dist/schemas/common.js.map +1 -0
  7. package/dist/schemas/dynamic-values.js +16 -0
  8. package/dist/schemas/dynamic-values.js.map +1 -0
  9. package/dist/schemas/index.js +15 -0
  10. package/dist/schemas/index.js.map +1 -0
  11. package/dist/schemas/language.js +13 -0
  12. package/dist/schemas/language.js.map +1 -0
  13. package/dist/schemas/media.js +35 -0
  14. package/dist/schemas/media.js.map +1 -0
  15. package/dist/schemas/message-compilation.js +31 -0
  16. package/dist/schemas/message-compilation.js.map +1 -0
  17. package/dist/schemas/message-group.js +70 -0
  18. package/dist/schemas/message-group.js.map +1 -0
  19. package/dist/schemas/message.js +33 -0
  20. package/dist/schemas/message.js.map +1 -0
  21. package/dist/schemas/meta-integration.js +42 -0
  22. package/dist/schemas/meta-integration.js.map +1 -0
  23. package/dist/schemas/onboarding.js +42 -0
  24. package/dist/schemas/onboarding.js.map +1 -0
  25. package/dist/schemas/organization.js +7 -0
  26. package/dist/schemas/organization.js.map +1 -0
  27. package/dist/schemas/property.js +8 -0
  28. package/dist/schemas/property.js.map +1 -0
  29. package/dist/schemas/tracking.js +6 -0
  30. package/dist/schemas/tracking.js.map +1 -0
  31. package/dist/types/app.js +1 -0
  32. package/dist/types/app.js.map +1 -1
  33. package/dist/types/brand.js +1 -0
  34. package/dist/types/brand.js.map +1 -1
  35. package/dist/types/emailblocks.v1.defaults.js +4 -0
  36. package/dist/types/emailblocks.v1.defaults.js.map +1 -1
  37. package/dist/types/emailblocks.v1.js +1 -0
  38. package/dist/types/emailblocks.v1.js.map +1 -1
  39. package/dist/utils/templates/brand-cascade.js +8 -3
  40. package/dist/utils/templates/brand-cascade.js.map +1 -1
  41. package/dist/utils/templates/brand-cascade.test.js +31 -0
  42. package/dist/utils/templates/brand-cascade.test.js.map +1 -0
  43. package/dist/utils/templates/email-mjml.js +17 -7
  44. package/dist/utils/templates/email-mjml.js.map +1 -1
  45. package/dist/utils/templates/email-mjml.test.js +44 -0
  46. package/dist/utils/templates/email-mjml.test.js.map +1 -0
  47. package/dist/utils/templates/meta.js +2 -2
  48. package/dist/utils/templates/meta.js.map +1 -1
  49. package/dist/utils/templates/tiptap.js +15 -1
  50. package/dist/utils/templates/tiptap.js.map +1 -1
  51. package/dist/utils/templates/tiptap.test.js +18 -0
  52. package/dist/utils/templates/tiptap.test.js.map +1 -0
  53. package/package.json +1 -1
  54. package/src/types/app.ts +1 -0
  55. package/src/types/brand.ts +1 -0
  56. package/src/types/emailblocks.v1.defaults.ts +4 -0
  57. package/src/types/emailblocks.v1.ts +6 -0
  58. package/src/types/language.ts +7 -0
  59. package/src/utils/templates/brand-cascade.test.ts +41 -0
  60. package/src/utils/templates/brand-cascade.ts +10 -4
  61. package/src/utils/templates/email-mjml.test.ts +53 -0
  62. package/src/utils/templates/email-mjml.ts +20 -7
  63. package/src/utils/templates/meta.ts +2 -1
  64. package/src/utils/templates/tiptap.test.ts +22 -0
  65. package/src/utils/templates/tiptap.ts +32 -0
@@ -36,6 +36,11 @@ export function resolveEmailSettings(
36
36
  const brandSettings = brand?.default_email_settings ?? {};
37
37
  const brandButtons = brand?.default_button_settings ?? {};
38
38
 
39
+ const textColor =
40
+ settings.textColor ??
41
+ brandSettings.textColor ??
42
+ DEFAULT_EMAIL_GLOBAL_SETTINGS.textColor;
43
+
39
44
  return {
40
45
  backgroundColor:
41
46
  settings.backgroundColor ??
@@ -51,10 +56,11 @@ export function resolveEmailSettings(
51
56
  settings.fontFamily ??
52
57
  (brandSettings.fontFamily as typeof DEFAULT_EMAIL_GLOBAL_SETTINGS.fontFamily) ??
53
58
  DEFAULT_EMAIL_GLOBAL_SETTINGS.fontFamily,
54
- textColor:
55
- settings.textColor ??
56
- brandSettings.textColor ??
57
- DEFAULT_EMAIL_GLOBAL_SETTINGS.textColor,
59
+ textColor,
60
+ // No hardcoded blue: an unstyled link should just match the resolved
61
+ // text color (underline is what marks it as a link), not an arbitrary
62
+ // brand-less default.
63
+ linkColor: settings.linkColor ?? brandSettings.linkColor ?? textColor,
58
64
  verticalSpacing:
59
65
  settings.verticalSpacing ?? DEFAULT_EMAIL_GLOBAL_SETTINGS.verticalSpacing,
60
66
  horizontalSpacing:
@@ -0,0 +1,53 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { translateEmailToMjml } from "./email-mjml.js";
3
+ import type { EmailMessage, ParagraphBlock } from "../../types/index.js";
4
+ import { emptyParagraphBlock } from "../../types/index.js";
5
+
6
+ function linkContent(text: string) {
7
+ return {
8
+ type: "doc",
9
+ content: [
10
+ {
11
+ type: "paragraph",
12
+ content: [
13
+ {
14
+ type: "text",
15
+ text,
16
+ marks: [{ type: "link", attrs: { href: "https://example.com" } }],
17
+ },
18
+ ],
19
+ },
20
+ ],
21
+ };
22
+ }
23
+
24
+ function messageWithParagraph(block: ParagraphBlock): EmailMessage {
25
+ return {
26
+ body: { subject: "Subject", blocks: [block], settings: {} },
27
+ metadata: {},
28
+ } as EmailMessage;
29
+ }
30
+
31
+ describe("translateEmailToMjml - link color", () => {
32
+ it("bakes the block's explicit linkColor as a literal inline style on <a>", () => {
33
+ const block = emptyParagraphBlock();
34
+ block.settings.content = linkContent("click here");
35
+ block.settings.linkColor = "#FF0000";
36
+
37
+ const mjml = translateEmailToMjml(messageWithParagraph(block));
38
+
39
+ expect(mjml).toContain('style="color: #FF0000;"');
40
+ });
41
+
42
+ it("falls back to the resolved global/brand linkColor when the block has none", () => {
43
+ const block = emptyParagraphBlock();
44
+ block.settings.content = linkContent("click here");
45
+
46
+ const message = messageWithParagraph(block);
47
+ message.body.settings.linkColor = "#00FF00";
48
+
49
+ const mjml = translateEmailToMjml(message);
50
+
51
+ expect(mjml).toContain('style="color: #00FF00;"');
52
+ });
53
+ });
@@ -30,11 +30,20 @@ import { getTipTapExtensions, sanitizeContent } from "./tiptap.js";
30
30
  import { generateText } from "./messages.js";
31
31
 
32
32
  /**
33
- * Converts TipTap JSONContent to HTML for email rendering
33
+ * Converts TipTap JSONContent to HTML for email rendering.
34
+ * `linkColor`, when given, is baked as a literal inline style onto `<a>`
35
+ * tags (not a CSS var) — see docs/adr/0004 for why this compile path can't
36
+ * reuse the live-editor CSS custom property approach.
34
37
  */
35
- function generateEmailHtml(content: JSONContent | undefined): string {
38
+ function generateEmailHtml(
39
+ content: JSONContent | undefined,
40
+ linkColor?: string,
41
+ ): string {
36
42
  if (!content) return "";
37
- const extensions = getTipTapExtensions({ preset: "email" });
43
+ const extensions = getTipTapExtensions({
44
+ preset: "email",
45
+ linkColor: linkColor ? { mode: "compile", color: linkColor } : undefined,
46
+ });
38
47
  return generateHTML(sanitizeContent(content, extensions), extensions);
39
48
  }
40
49
 
@@ -97,7 +106,8 @@ function translateHeadingBlock(
97
106
  block: HeadingBlock,
98
107
  settings: EmailGlobalSettings,
99
108
  ): string {
100
- const html = generateEmailHtml(block.settings.content);
109
+ const linkColor = block.settings.linkColor ?? settings.linkColor;
110
+ const html = generateEmailHtml(block.settings.content, linkColor);
101
111
  const bgColor =
102
112
  block.settings.backgroundColor === "#FFFFFF00"
103
113
  ? ""
@@ -123,7 +133,8 @@ function translateParagraphBlock(
123
133
  block: ParagraphBlock,
124
134
  settings: EmailGlobalSettings,
125
135
  ): string {
126
- const html = generateEmailHtml(block.settings.content);
136
+ const linkColor = block.settings.linkColor ?? settings.linkColor;
137
+ const html = generateEmailHtml(block.settings.content, linkColor);
127
138
 
128
139
  const bgColor =
129
140
  block.settings.backgroundColor === "#FFFFFF00"
@@ -322,7 +333,8 @@ function translateFooterBlock(
322
333
  block: FooterBlock,
323
334
  settings: EmailGlobalSettings,
324
335
  ): string {
325
- const html = generateEmailHtml(block.settings.content);
336
+ const linkColor = block.settings.linkColor ?? settings.linkColor;
337
+ const html = generateEmailHtml(block.settings.content, linkColor);
326
338
 
327
339
  const bgColor =
328
340
  block.settings.backgroundColor === "#FFFFFF00"
@@ -350,7 +362,8 @@ function translateUnsubscribeLinkBlock(
350
362
  block: UnsubscribeLinkBlock,
351
363
  settings: EmailGlobalSettings,
352
364
  ): string {
353
- const html = generateEmailHtml(block.settings.content);
365
+ const linkColor = block.settings.linkColor ?? settings.linkColor;
366
+ const html = generateEmailHtml(block.settings.content, linkColor);
354
367
 
355
368
  const bgColor =
356
369
  block.settings.backgroundColor === "#FFFFFF00"
@@ -298,6 +298,7 @@ export function translateWhatsappToMeta(
298
298
  groupName: string,
299
299
  groupCategory: "marketing" | "utility",
300
300
  prefix: string,
301
+ metaLanguageCode: string,
301
302
  ): MetaTemplate {
302
303
  const components: MetaTemplate["components"] = [];
303
304
 
@@ -327,7 +328,7 @@ export function translateWhatsappToMeta(
327
328
  name:
328
329
  message.metadata?.meta?.template_name ||
329
330
  generateMetaTemplateName(groupName, message.language_id, prefix),
330
- language: message.language_id,
331
+ language: metaLanguageCode,
331
332
  category: groupCategory.toUpperCase() as "MARKETING" | "UTILITY",
332
333
  parameter_format: "NAMED",
333
334
  allow_category_change: true,
@@ -0,0 +1,22 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { buildLinkHTMLAttributes } from "./tiptap.js";
3
+
4
+ describe("buildLinkHTMLAttributes", () => {
5
+ it("returns undefined when no link color option is given", () => {
6
+ expect(buildLinkHTMLAttributes(undefined)).toBeUndefined();
7
+ });
8
+
9
+ it("renders a CSS custom property in live mode", () => {
10
+ expect(buildLinkHTMLAttributes({ mode: "live" })).toEqual({
11
+ style: "color: var(--link-color);",
12
+ });
13
+ });
14
+
15
+ it("renders a literal hex in compile mode", () => {
16
+ expect(
17
+ buildLinkHTMLAttributes({ mode: "compile", color: "#2563EB" }),
18
+ ).toEqual({
19
+ style: "color: #2563EB;",
20
+ });
21
+ });
22
+ });
@@ -14,6 +14,19 @@ import type { JSONContent } from "@tiptap/core";
14
14
  import { type RenderType, WA_BODY_MAX_LENGTH } from "../../types/index.js";
15
15
  import { generateHtml, generateText } from "./messages.js";
16
16
 
17
+ /**
18
+ * How a link's color should be rendered:
19
+ * - "live": the color is driven by a `--link-color` CSS custom property set
20
+ * on a wrapping element, so it updates without recreating the editor
21
+ * instance. Used for the canvas preview and settings-panel editor.
22
+ * - "compile": a literal hex is baked into the inline style. Required for
23
+ * the final email HTML — Outlook and other strict clients don't support
24
+ * CSS custom properties. See docs/adr/0004.
25
+ */
26
+ export type LinkColorOption =
27
+ | { mode: "live" }
28
+ | { mode: "compile"; color: string };
29
+
17
30
  export interface TipTapExtensionOptions {
18
31
  preset?: "whatsapp" | "whatsapp-header" | "email";
19
32
  bold?: boolean;
@@ -21,6 +34,7 @@ export interface TipTapExtensionOptions {
21
34
  emoji?: boolean;
22
35
  underline?: boolean;
23
36
  link?: boolean;
37
+ linkColor?: LinkColorOption;
24
38
  bulletList?: boolean;
25
39
  orderedList?: boolean;
26
40
  textAlign?: boolean;
@@ -30,6 +44,20 @@ export interface TipTapExtensionOptions {
30
44
  hardBreak?: boolean;
31
45
  }
32
46
 
47
+ /**
48
+ * Builds the `style` HTMLAttributes for the TipTap Link mark from a
49
+ * LinkColorOption. Returns undefined when no color option is given, so the
50
+ * Link mark renders with no color styling (unchanged default behavior).
51
+ */
52
+ export function buildLinkHTMLAttributes(
53
+ linkColor?: LinkColorOption,
54
+ ): Record<string, string> | undefined {
55
+ if (!linkColor) return undefined;
56
+ const color =
57
+ linkColor.mode === "live" ? "var(--link-color)" : linkColor.color;
58
+ return { style: `color: ${color};` };
59
+ }
60
+
33
61
  const PRESETS: Record<
34
62
  NonNullable<TipTapExtensionOptions["preset"]>,
35
63
  Omit<TipTapExtensionOptions, "preset" | "dynamicValues" | "characterLimit">
@@ -70,6 +98,7 @@ export function getTipTapExtensions(
70
98
  emoji = false,
71
99
  underline = false,
72
100
  link = false,
101
+ linkColor,
73
102
  bulletList = false,
74
103
  orderedList = false,
75
104
  textAlign = false,
@@ -144,6 +173,9 @@ export function getTipTapExtensions(
144
173
  link: link
145
174
  ? {
146
175
  openOnClick: false,
176
+ ...(linkColor
177
+ ? { HTMLAttributes: buildLinkHTMLAttributes(linkColor) }
178
+ : {}),
147
179
  }
148
180
  : false,
149
181
  heading: false,