@dev.smartpricing/message-composer-utils 3.1.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/types/api/aiIntegration.js +7 -0
- package/dist/types/api/aiIntegration.js.map +1 -0
- package/dist/types/api/apiMessages.js +2 -0
- package/dist/types/api/apiMessages.js.map +1 -0
- package/dist/types/api/index.js +3 -0
- package/dist/types/api/index.js.map +1 -0
- package/dist/types/app.js +23 -0
- package/dist/types/app.js.map +1 -0
- package/dist/types/brand.js +31 -0
- package/dist/types/brand.js.map +1 -0
- package/dist/types/dynamic-values.js +5 -0
- package/dist/types/dynamic-values.js.map +1 -0
- package/dist/types/emailblocks.v1.defaults.js +83 -0
- package/dist/types/emailblocks.v1.defaults.js.map +1 -0
- package/dist/types/emailblocks.v1.js +339 -0
- package/dist/types/emailblocks.v1.js.map +1 -0
- package/dist/types/index.js +16 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/kafka-events.js +2 -0
- package/dist/types/kafka-events.js.map +1 -0
- package/dist/types/language.js +2 -0
- package/dist/types/language.js.map +1 -0
- package/dist/types/media.js +6 -0
- package/dist/types/media.js.map +1 -0
- package/dist/types/message.js +41 -0
- package/dist/types/message.js.map +1 -0
- package/dist/types/meta-template.js +2 -0
- package/dist/types/meta-template.js.map +1 -0
- package/dist/types/meta-webhook.js +2 -0
- package/dist/types/meta-webhook.js.map +1 -0
- package/dist/types/submission-job.js +2 -0
- package/dist/types/submission-job.js.map +1 -0
- package/dist/types/tag.js +2 -0
- package/dist/types/tag.js.map +1 -0
- package/dist/types/utils.js +2 -0
- package/dist/types/utils.js.map +1 -0
- package/dist/types/whatsappblocks.v1.js +24 -0
- package/dist/types/whatsappblocks.v1.js.map +1 -0
- package/dist/utils/array.js +26 -0
- package/dist/utils/array.js.map +1 -0
- package/dist/utils/async.js +22 -0
- package/dist/utils/async.js.map +1 -0
- package/dist/utils/date.js +37 -0
- package/dist/utils/date.js.map +1 -0
- package/dist/utils/index.js +34 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/intl.js +37 -0
- package/dist/utils/intl.js.map +1 -0
- package/dist/utils/meta-status.js +21 -0
- package/dist/utils/meta-status.js.map +1 -0
- package/dist/utils/number.js +24 -0
- package/dist/utils/number.js.map +1 -0
- package/dist/utils/object.js +87 -0
- package/dist/utils/object.js.map +1 -0
- package/dist/utils/string.js +22 -0
- package/dist/utils/string.js.map +1 -0
- package/dist/utils/templates/brand-cascade.js +116 -0
- package/dist/utils/templates/brand-cascade.js.map +1 -0
- package/dist/utils/templates/email-compiler.js +126 -0
- package/dist/utils/templates/email-compiler.js.map +1 -0
- package/dist/utils/templates/email-mjml.js +444 -0
- package/dist/utils/templates/email-mjml.js.map +1 -0
- package/dist/utils/templates/email.js +580 -0
- package/dist/utils/templates/email.js.map +1 -0
- package/dist/utils/templates/html-sanitizer.js +95 -0
- package/dist/utils/templates/html-sanitizer.js.map +1 -0
- package/dist/utils/templates/messages.js +33 -0
- package/dist/utils/templates/messages.js.map +1 -0
- package/dist/utils/templates/meta.js +246 -0
- package/dist/utils/templates/meta.js.map +1 -0
- package/dist/utils/templates/tiptap.js +167 -0
- package/dist/utils/templates/tiptap.js.map +1 -0
- package/dist/utils/templates/whatsapp-compiler.js +168 -0
- package/dist/utils/templates/whatsapp-compiler.js.map +1 -0
- package/dist/utils/templates/whatsapp.js +329 -0
- package/dist/utils/templates/whatsapp.js.map +1 -0
- package/dist/utils/validations.js +39 -0
- package/dist/utils/validations.js.map +1 -0
- package/package.json +43 -0
- package/src/types/api/aiIntegration.ts +59 -0
- package/src/types/api/apiMessages.ts +22 -0
- package/src/types/api/index.ts +2 -0
- package/src/types/app.ts +76 -0
- package/src/types/brand.ts +80 -0
- package/src/types/dynamic-values.ts +47 -0
- package/src/types/emailblocks.v1.defaults.ts +123 -0
- package/src/types/emailblocks.v1.ts +655 -0
- package/src/types/index.ts +15 -0
- package/src/types/kafka-events.ts +46 -0
- package/src/types/language.ts +9 -0
- package/src/types/media.ts +26 -0
- package/src/types/message.ts +635 -0
- package/src/types/meta-template.ts +50 -0
- package/src/types/meta-webhook.ts +32 -0
- package/src/types/submission-job.ts +30 -0
- package/src/types/tag.ts +8 -0
- package/src/types/utils.ts +14 -0
- package/src/types/whatsappblocks.v1.ts +102 -0
- package/src/utils/array.ts +33 -0
- package/src/utils/async.ts +30 -0
- package/src/utils/date.ts +39 -0
- package/src/utils/index.ts +44 -0
- package/src/utils/intl.ts +51 -0
- package/src/utils/meta-status.ts +24 -0
- package/src/utils/number.ts +36 -0
- package/src/utils/object.ts +101 -0
- package/src/utils/string.ts +24 -0
- package/src/utils/templates/brand-cascade.ts +156 -0
- package/src/utils/templates/email-compiler.ts +155 -0
- package/src/utils/templates/email-mjml.ts +602 -0
- package/src/utils/templates/email.ts +713 -0
- package/src/utils/templates/html-sanitizer.ts +121 -0
- package/src/utils/templates/messages.ts +44 -0
- package/src/utils/templates/meta.ts +336 -0
- package/src/utils/templates/tiptap.ts +247 -0
- package/src/utils/templates/whatsapp-compiler.ts +212 -0
- package/src/utils/templates/whatsapp.ts +408 -0
- package/src/utils/validations.ts +49 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EmailMessage,
|
|
3
|
+
EmailGlobalSettings,
|
|
4
|
+
EmailBlock,
|
|
5
|
+
BrandDefaultEmailSettings,
|
|
6
|
+
BrandDefaultButtonSettings,
|
|
7
|
+
BrandSocials,
|
|
8
|
+
SocialPlatform,
|
|
9
|
+
} from "../../types/index.js";
|
|
10
|
+
import { DEFAULT_EMAIL_GLOBAL_SETTINGS } from "../../types/index.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Brand data needed for cascade resolution.
|
|
14
|
+
* This is the subset of BrandFromDb used at compile time.
|
|
15
|
+
*/
|
|
16
|
+
export interface BrandCascadeData {
|
|
17
|
+
default_email_settings: BrandDefaultEmailSettings;
|
|
18
|
+
default_button_settings: BrandDefaultButtonSettings;
|
|
19
|
+
default_socials: BrandSocials | null;
|
|
20
|
+
logo_url: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolves EmailGlobalSettings by cascading:
|
|
25
|
+
* generalSettings → brand → hardcoded defaults
|
|
26
|
+
*/
|
|
27
|
+
export function resolveEmailSettings(
|
|
28
|
+
settings: EmailGlobalSettings,
|
|
29
|
+
brand: BrandCascadeData | null,
|
|
30
|
+
): Required<EmailGlobalSettings> {
|
|
31
|
+
const brandSettings = brand?.default_email_settings ?? {};
|
|
32
|
+
const brandButtons = brand?.default_button_settings ?? {};
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
backgroundColor:
|
|
36
|
+
settings.backgroundColor ??
|
|
37
|
+
brandSettings.backgroundColor ??
|
|
38
|
+
DEFAULT_EMAIL_GLOBAL_SETTINGS.backgroundColor,
|
|
39
|
+
contentBackgroundColor:
|
|
40
|
+
settings.contentBackgroundColor ??
|
|
41
|
+
brandSettings.contentBackgroundColor ??
|
|
42
|
+
DEFAULT_EMAIL_GLOBAL_SETTINGS.contentBackgroundColor,
|
|
43
|
+
contentWidth:
|
|
44
|
+
settings.contentWidth ?? DEFAULT_EMAIL_GLOBAL_SETTINGS.contentWidth,
|
|
45
|
+
fontFamily:
|
|
46
|
+
settings.fontFamily ??
|
|
47
|
+
(brandSettings.fontFamily as typeof DEFAULT_EMAIL_GLOBAL_SETTINGS.fontFamily) ??
|
|
48
|
+
DEFAULT_EMAIL_GLOBAL_SETTINGS.fontFamily,
|
|
49
|
+
textColor:
|
|
50
|
+
settings.textColor ??
|
|
51
|
+
brandSettings.textColor ??
|
|
52
|
+
DEFAULT_EMAIL_GLOBAL_SETTINGS.textColor,
|
|
53
|
+
verticalSpacing:
|
|
54
|
+
settings.verticalSpacing ?? DEFAULT_EMAIL_GLOBAL_SETTINGS.verticalSpacing,
|
|
55
|
+
horizontalSpacing:
|
|
56
|
+
settings.horizontalSpacing ??
|
|
57
|
+
DEFAULT_EMAIL_GLOBAL_SETTINGS.horizontalSpacing,
|
|
58
|
+
button: {
|
|
59
|
+
backgroundColor: brandButtons.backgroundColor ?? "#000000",
|
|
60
|
+
textColor: brandButtons.textColor ?? "#FFFFFF",
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolves blocks with brand cascade data.
|
|
67
|
+
* - Header: fills empty src with brand logo
|
|
68
|
+
* - Socials: fills missing URLs from brand socials
|
|
69
|
+
* - Buttons: fills undefined colors from brand button defaults
|
|
70
|
+
*/
|
|
71
|
+
function resolveBlocks(
|
|
72
|
+
blocks: EmailBlock[],
|
|
73
|
+
brand: BrandCascadeData | null,
|
|
74
|
+
): EmailBlock[] {
|
|
75
|
+
if (!brand) return blocks;
|
|
76
|
+
|
|
77
|
+
return blocks.map((block) => {
|
|
78
|
+
switch (block.type) {
|
|
79
|
+
case "header": {
|
|
80
|
+
if (!block.settings.src && brand.logo_url) {
|
|
81
|
+
return {
|
|
82
|
+
...block,
|
|
83
|
+
settings: { ...block.settings, src: brand.logo_url },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return block;
|
|
87
|
+
}
|
|
88
|
+
case "socials": {
|
|
89
|
+
if (!brand.default_socials) return block;
|
|
90
|
+
const resolvedSocials = { ...block.settings.socials };
|
|
91
|
+
for (const platform of Object.keys(
|
|
92
|
+
resolvedSocials,
|
|
93
|
+
) as SocialPlatform[]) {
|
|
94
|
+
const blockSocial = resolvedSocials[platform];
|
|
95
|
+
if (!blockSocial?.enabled) continue;
|
|
96
|
+
// Enabled + no block URL → cascade from brand
|
|
97
|
+
if (!blockSocial.url) {
|
|
98
|
+
const brandUrl = brand.default_socials[platform];
|
|
99
|
+
if (brandUrl) {
|
|
100
|
+
resolvedSocials[platform] = { ...blockSocial, url: brandUrl };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
...block,
|
|
106
|
+
settings: { ...block.settings, socials: resolvedSocials },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
case "button": {
|
|
110
|
+
const brandButton = brand.default_button_settings;
|
|
111
|
+
return {
|
|
112
|
+
...block,
|
|
113
|
+
settings: {
|
|
114
|
+
...block.settings,
|
|
115
|
+
textColor: block.settings.textColor ?? brandButton.textColor,
|
|
116
|
+
backgroundColor:
|
|
117
|
+
block.settings.backgroundColor ?? brandButton.backgroundColor,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
case "grid": {
|
|
122
|
+
// Recurse into grid column blocks
|
|
123
|
+
return {
|
|
124
|
+
...block,
|
|
125
|
+
settings: {
|
|
126
|
+
...block.settings,
|
|
127
|
+
columnData: block.settings.columnData.map((col) => ({
|
|
128
|
+
...col,
|
|
129
|
+
blocks: resolveBlocks(col.blocks, brand) as typeof col.blocks,
|
|
130
|
+
})),
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
default:
|
|
135
|
+
return block;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Resolves the full email message body with brand cascade.
|
|
142
|
+
* Returns a new body where all undefined settings and block values
|
|
143
|
+
* are filled from the brand, then from hardcoded defaults.
|
|
144
|
+
*
|
|
145
|
+
* The returned body can be passed directly to translateEmailToMjml.
|
|
146
|
+
*/
|
|
147
|
+
export function resolveEmailBodyWithBrand(
|
|
148
|
+
body: EmailMessage["body"],
|
|
149
|
+
brand: BrandCascadeData | null,
|
|
150
|
+
): EmailMessage["body"] {
|
|
151
|
+
return {
|
|
152
|
+
subject: body.subject,
|
|
153
|
+
blocks: resolveBlocks(body.blocks, brand),
|
|
154
|
+
settings: resolveEmailSettings(body.settings, brand),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EmailMessage,
|
|
3
|
+
EmailBlock,
|
|
4
|
+
GridAllowedBlock,
|
|
5
|
+
PostEntity,
|
|
6
|
+
} from "../../types/index.js";
|
|
7
|
+
import type { JSONContent } from "@tiptap/core";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Extract parameters from TipTap JSONContent
|
|
11
|
+
*/
|
|
12
|
+
function extractParamsFromContent(content: JSONContent | undefined): string[] {
|
|
13
|
+
if (!content) return [];
|
|
14
|
+
|
|
15
|
+
const results: string[] = [];
|
|
16
|
+
|
|
17
|
+
// Extract from text nodes
|
|
18
|
+
if (content.type === "text" && content.text) {
|
|
19
|
+
const matches = content.text.matchAll(/\{\{([^}]+)\}\}/g);
|
|
20
|
+
for (const match of matches) {
|
|
21
|
+
results.push(match[1]);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Extract from mentions
|
|
26
|
+
if (content.type === "mention" && content.attrs?.label) {
|
|
27
|
+
results.push(content.attrs.label);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Recurse through content
|
|
31
|
+
if (content.content && Array.isArray(content.content)) {
|
|
32
|
+
for (const child of content.content) {
|
|
33
|
+
results.push(...extractParamsFromContent(child));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return results;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Extract parameters from email blocks recursively
|
|
42
|
+
*/
|
|
43
|
+
function extractParamsFromBlocks(
|
|
44
|
+
blocks: (EmailBlock | GridAllowedBlock)[],
|
|
45
|
+
): string[] {
|
|
46
|
+
const results: string[] = [];
|
|
47
|
+
|
|
48
|
+
for (const block of blocks) {
|
|
49
|
+
switch (block.type) {
|
|
50
|
+
case "heading":
|
|
51
|
+
case "paragraph":
|
|
52
|
+
case "discount":
|
|
53
|
+
case "footer":
|
|
54
|
+
case "unsubscribe_link":
|
|
55
|
+
results.push(...extractParamsFromContent(block.settings.content));
|
|
56
|
+
break;
|
|
57
|
+
case "socials":
|
|
58
|
+
for (const social of Object.values(block.settings.socials)) {
|
|
59
|
+
if (social.enabled && social.url) {
|
|
60
|
+
const matches = social.url.matchAll(/\{\{([^}]+)\}\}/g);
|
|
61
|
+
for (const match of matches) {
|
|
62
|
+
results.push(match[1]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
break;
|
|
67
|
+
case "button":
|
|
68
|
+
results.push(...extractParamsFromContent(block.settings.content));
|
|
69
|
+
// Extract from URL
|
|
70
|
+
if (block.settings.url) {
|
|
71
|
+
const matches = block.settings.url.matchAll(/\{\{([^}]+)\}\}/g);
|
|
72
|
+
for (const match of matches) {
|
|
73
|
+
results.push(match[1]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case "grid":
|
|
79
|
+
// Recurse into grid columns
|
|
80
|
+
for (const col of block.settings.columnData) {
|
|
81
|
+
results.push(...extractParamsFromBlocks(col.blocks));
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
case "image":
|
|
85
|
+
// Extract from image href
|
|
86
|
+
if (block.settings.href) {
|
|
87
|
+
const matches = block.settings.href.matchAll(/\{\{([^}]+)\}\}/g);
|
|
88
|
+
for (const match of matches) {
|
|
89
|
+
results.push(match[1]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
break;
|
|
93
|
+
case "top_products":
|
|
94
|
+
if (block.settings.storeId) {
|
|
95
|
+
const matches = block.settings.storeId.matchAll(/\{\{([^}]+)\}\}/g);
|
|
96
|
+
for (const match of matches) {
|
|
97
|
+
results.push(match[1]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
// Other blocks don't have parameters
|
|
102
|
+
default:
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Extract required parameters from email template
|
|
112
|
+
*/
|
|
113
|
+
export function extractEmailParameters(
|
|
114
|
+
message: EmailMessage | PostEntity<EmailMessage>,
|
|
115
|
+
): string[] {
|
|
116
|
+
const paramRegex = /\{\{([^}]+)\}\}/g;
|
|
117
|
+
const results: string[] = [];
|
|
118
|
+
|
|
119
|
+
// Extract from subject
|
|
120
|
+
if (message.body.subject) {
|
|
121
|
+
for (const match of message.body.subject.matchAll(paramRegex)) {
|
|
122
|
+
results.push(match[1]);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Extract from preheader
|
|
127
|
+
if (message.metadata.preheader) {
|
|
128
|
+
for (const match of message.metadata.preheader.matchAll(paramRegex)) {
|
|
129
|
+
results.push(match[1]);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Extract from blocks recursively
|
|
134
|
+
results.push(...extractParamsFromBlocks(message.body.blocks));
|
|
135
|
+
|
|
136
|
+
// Dedupe using Set
|
|
137
|
+
return [...new Set(results)];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Validate email parameters
|
|
142
|
+
*/
|
|
143
|
+
export function validateEmailParameters(
|
|
144
|
+
message: EmailMessage,
|
|
145
|
+
parameters: Record<string, unknown>,
|
|
146
|
+
): { valid: boolean; missing: string[] } {
|
|
147
|
+
const required = extractEmailParameters(message);
|
|
148
|
+
const provided = Object.keys(parameters);
|
|
149
|
+
const missing = required.filter((p) => !provided.includes(p));
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
valid: missing.length === 0,
|
|
153
|
+
missing,
|
|
154
|
+
};
|
|
155
|
+
}
|