@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.
- package/dist/schemas/ai-integration.js +34 -0
- package/dist/schemas/ai-integration.js.map +1 -0
- package/dist/schemas/brand.js +40 -0
- package/dist/schemas/brand.js.map +1 -0
- package/dist/schemas/common.js +34 -0
- package/dist/schemas/common.js.map +1 -0
- package/dist/schemas/dynamic-values.js +16 -0
- package/dist/schemas/dynamic-values.js.map +1 -0
- package/dist/schemas/index.js +15 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/language.js +13 -0
- package/dist/schemas/language.js.map +1 -0
- package/dist/schemas/media.js +35 -0
- package/dist/schemas/media.js.map +1 -0
- package/dist/schemas/message-compilation.js +31 -0
- package/dist/schemas/message-compilation.js.map +1 -0
- package/dist/schemas/message-group.js +70 -0
- package/dist/schemas/message-group.js.map +1 -0
- package/dist/schemas/message.js +33 -0
- package/dist/schemas/message.js.map +1 -0
- package/dist/schemas/meta-integration.js +42 -0
- package/dist/schemas/meta-integration.js.map +1 -0
- package/dist/schemas/onboarding.js +42 -0
- package/dist/schemas/onboarding.js.map +1 -0
- package/dist/schemas/organization.js +7 -0
- package/dist/schemas/organization.js.map +1 -0
- package/dist/schemas/property.js +8 -0
- package/dist/schemas/property.js.map +1 -0
- package/dist/schemas/tracking.js +6 -0
- package/dist/schemas/tracking.js.map +1 -0
- package/dist/types/app.js +1 -0
- package/dist/types/app.js.map +1 -1
- package/dist/types/brand.js +1 -0
- package/dist/types/brand.js.map +1 -1
- package/dist/types/emailblocks.v1.defaults.js +4 -0
- package/dist/types/emailblocks.v1.defaults.js.map +1 -1
- package/dist/types/emailblocks.v1.js +1 -0
- package/dist/types/emailblocks.v1.js.map +1 -1
- package/dist/utils/templates/brand-cascade.js +8 -3
- package/dist/utils/templates/brand-cascade.js.map +1 -1
- package/dist/utils/templates/brand-cascade.test.js +31 -0
- package/dist/utils/templates/brand-cascade.test.js.map +1 -0
- package/dist/utils/templates/email-mjml.js +17 -7
- package/dist/utils/templates/email-mjml.js.map +1 -1
- package/dist/utils/templates/email-mjml.test.js +44 -0
- package/dist/utils/templates/email-mjml.test.js.map +1 -0
- package/dist/utils/templates/meta.js +2 -2
- package/dist/utils/templates/meta.js.map +1 -1
- package/dist/utils/templates/tiptap.js +15 -1
- package/dist/utils/templates/tiptap.js.map +1 -1
- package/dist/utils/templates/tiptap.test.js +18 -0
- package/dist/utils/templates/tiptap.test.js.map +1 -0
- package/package.json +1 -1
- package/src/types/app.ts +1 -0
- package/src/types/brand.ts +1 -0
- package/src/types/emailblocks.v1.defaults.ts +4 -0
- package/src/types/emailblocks.v1.ts +6 -0
- package/src/types/language.ts +7 -0
- package/src/utils/templates/brand-cascade.test.ts +41 -0
- package/src/utils/templates/brand-cascade.ts +10 -4
- package/src/utils/templates/email-mjml.test.ts +53 -0
- package/src/utils/templates/email-mjml.ts +20 -7
- package/src/utils/templates/meta.ts +2 -1
- package/src/utils/templates/tiptap.test.ts +22 -0
- 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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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(
|
|
38
|
+
function generateEmailHtml(
|
|
39
|
+
content: JSONContent | undefined,
|
|
40
|
+
linkColor?: string,
|
|
41
|
+
): string {
|
|
36
42
|
if (!content) return "";
|
|
37
|
-
const extensions = getTipTapExtensions({
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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,
|