@dev.smartpricing/message-composer-utils 4.4.1-next.0 → 4.5.1-next.1
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/brand.js +2 -0
- package/dist/schemas/brand.js.map +1 -1
- package/dist/schemas/dynamic-values.js +2 -0
- package/dist/schemas/dynamic-values.js.map +1 -1
- package/dist/schemas/message-compilation.js +2 -0
- package/dist/schemas/message-compilation.js.map +1 -1
- package/dist/schemas/message-group.js +6 -0
- package/dist/schemas/message-group.js.map +1 -1
- package/dist/schemas/message-group.test.js +14 -0
- package/dist/schemas/message-group.test.js.map +1 -0
- package/dist/schemas/meta-integration.js +20 -0
- package/dist/schemas/meta-integration.js.map +1 -1
- package/dist/schemas/meta-integration.test.js +18 -0
- package/dist/schemas/meta-integration.test.js.map +1 -0
- package/dist/schemas/organization.js +31 -0
- package/dist/schemas/organization.js.map +1 -1
- package/dist/schemas/organization.test.js +86 -0
- package/dist/schemas/organization.test.js.map +1 -0
- package/dist/schemas/tracking.js +2 -0
- package/dist/schemas/tracking.js.map +1 -1
- package/dist/types/app.js.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/message.js +2 -0
- package/dist/types/message.js.map +1 -1
- package/dist/types/organization.js +2 -0
- package/dist/types/organization.js.map +1 -0
- package/dist/utils/templates/meta.test.js +49 -2
- package/dist/utils/templates/meta.test.js.map +1 -1
- package/dist/utils/templates/whatsapp.js +29 -14
- package/dist/utils/templates/whatsapp.js.map +1 -1
- package/package.json +1 -1
- package/src/schemas/brand.ts +2 -0
- package/src/schemas/dynamic-values.ts +2 -0
- package/src/schemas/message-compilation.ts +2 -0
- package/src/schemas/message-group.test.ts +20 -0
- package/src/schemas/message-group.ts +9 -0
- package/src/schemas/meta-integration.test.ts +20 -0
- package/src/schemas/meta-integration.ts +30 -1
- package/src/schemas/organization.test.ts +143 -0
- package/src/schemas/organization.ts +39 -0
- package/src/schemas/tracking.ts +2 -0
- package/src/types/api/apiMessages.ts +2 -0
- package/src/types/app.ts +5 -24
- package/src/types/index.ts +1 -0
- package/src/types/message.ts +14 -0
- package/src/types/organization.ts +19 -0
- package/src/types/submission-job.ts +5 -0
- package/src/utils/templates/meta.test.ts +76 -2
- package/src/utils/templates/whatsapp.ts +35 -13
|
@@ -6,7 +6,11 @@ import type {
|
|
|
6
6
|
WithDbTimestamps,
|
|
7
7
|
} from "../../types/index.js";
|
|
8
8
|
import { translateWhatsappToMeta } from "./meta.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
getWhatsappEmpty,
|
|
11
|
+
isWhatsappHeaderMultiline,
|
|
12
|
+
isWhatsappValid,
|
|
13
|
+
} from "./whatsapp.js";
|
|
10
14
|
|
|
11
15
|
function messageWithHeader(
|
|
12
16
|
content: JSONContent,
|
|
@@ -33,6 +37,17 @@ function messageWithHeader(
|
|
|
33
37
|
} as WithDbTimestamps<WhatsappMessage>;
|
|
34
38
|
}
|
|
35
39
|
|
|
40
|
+
function paragraphs(...lines: string[]): JSONContent {
|
|
41
|
+
return {
|
|
42
|
+
type: "doc",
|
|
43
|
+
content: lines.map((text) =>
|
|
44
|
+
text
|
|
45
|
+
? { type: "paragraph", content: [{ type: "text", text }] }
|
|
46
|
+
: { type: "paragraph" },
|
|
47
|
+
),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
function headerText(content: JSONContent): string | undefined {
|
|
37
52
|
const template = translateWhatsappToMeta(
|
|
38
53
|
messageWithHeader(content),
|
|
@@ -81,6 +96,65 @@ describe("translateWhatsappToMeta header", () => {
|
|
|
81
96
|
],
|
|
82
97
|
};
|
|
83
98
|
|
|
84
|
-
expect(headerText(content)).toBe("Welcome
|
|
99
|
+
expect(headerText(content)).toBe("Welcome {{guest_name}}");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("sends header text to Meta without emojis typed as plain text", () => {
|
|
103
|
+
expect(headerText(paragraphs("Welcome 👋🏽 back ❤️ 1️⃣ 🇮🇹"))).toBe(
|
|
104
|
+
"Welcome back",
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("keeps characters the user typed themselves", () => {
|
|
109
|
+
expect(headerText(paragraphs("*Offer* for room #1 ©"))).toBe(
|
|
110
|
+
"*Offer* for room #1 ©",
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("sends header text to Meta on a single line", () => {
|
|
115
|
+
expect(
|
|
116
|
+
headerText(paragraphs("", "Your stay", "", "is confirmed", "")),
|
|
117
|
+
).toBe("Your stay is confirmed");
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("joins legacy hard breaks into the single line", () => {
|
|
121
|
+
const content: JSONContent = {
|
|
122
|
+
type: "doc",
|
|
123
|
+
content: [
|
|
124
|
+
{
|
|
125
|
+
type: "paragraph",
|
|
126
|
+
content: [
|
|
127
|
+
{ type: "text", text: "Your stay" },
|
|
128
|
+
{ type: "hardBreak" },
|
|
129
|
+
{ type: "text", text: "is confirmed" },
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
expect(headerText(content)).toBe("Your stay is confirmed");
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("isWhatsappHeaderMultiline", () => {
|
|
140
|
+
it("is true only when the header has several lines", () => {
|
|
141
|
+
expect(isWhatsappHeaderMultiline(paragraphs("Your stay"))).toBe(false);
|
|
142
|
+
expect(
|
|
143
|
+
isWhatsappHeaderMultiline(paragraphs("Your stay", "is confirmed")),
|
|
144
|
+
).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("isWhatsappValid header length", () => {
|
|
149
|
+
const line = "a".repeat(30);
|
|
150
|
+
|
|
151
|
+
it("measures the single line Meta receives", () => {
|
|
152
|
+
expect(
|
|
153
|
+
isWhatsappValid(messageWithHeader(paragraphs(line, "a".repeat(29))))
|
|
154
|
+
.errors,
|
|
155
|
+
).not.toContain("editor.validation.error.headerTooLong");
|
|
156
|
+
expect(
|
|
157
|
+
isWhatsappValid(messageWithHeader(paragraphs(line, line))).errors,
|
|
158
|
+
).toContain("editor.validation.error.headerTooLong");
|
|
85
159
|
});
|
|
86
160
|
});
|
|
@@ -308,28 +308,50 @@ export function convertToWhatsappMarkdown(
|
|
|
308
308
|
/**
|
|
309
309
|
* Converts a WhatsApp header to the text Meta receives.
|
|
310
310
|
* Meta rejects formatting in header text ("Header Format is Incorrect",
|
|
311
|
-
* subcode 2388072), so marks and
|
|
312
|
-
*
|
|
311
|
+
* subcode 2388072), so marks and emojis are dropped — including on legacy
|
|
312
|
+
* content saved before the header editor stopped offering them.
|
|
313
|
+
* A header is a single line: paragraphs are joined with a space.
|
|
313
314
|
*/
|
|
314
315
|
export function convertToWhatsappHeaderText(
|
|
315
316
|
content: Content | undefined,
|
|
316
317
|
): string {
|
|
317
|
-
return
|
|
318
|
+
return headerLines(content).replace(/\s+/g, " ").trim();
|
|
318
319
|
}
|
|
319
320
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
321
|
+
/**
|
|
322
|
+
* Whether a WhatsApp header spans several lines, which
|
|
323
|
+
* `convertToWhatsappHeaderText` joins into one.
|
|
324
|
+
*/
|
|
325
|
+
export function isWhatsappHeaderMultiline(
|
|
326
|
+
content: Content | undefined,
|
|
327
|
+
): boolean {
|
|
328
|
+
return headerLines(content).includes("\n");
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Emojis typed or pasted as plain text (only picker emojis are nodes)
|
|
332
|
+
const EMOJI_CHARS =
|
|
333
|
+
/.\uFE0F\u20E3?|\p{Emoji_Presentation}|\p{Emoji_Modifier}|\u200D/gu;
|
|
334
|
+
|
|
335
|
+
/** Header text without formatting or emojis, one line per paragraph */
|
|
336
|
+
function headerLines(content: Content | undefined): string {
|
|
337
|
+
const plain = Array.isArray(content)
|
|
338
|
+
? content.map(stripMarksAndEmoji)
|
|
339
|
+
: content && typeof content === "object"
|
|
340
|
+
? stripMarksAndEmoji(content)
|
|
341
|
+
: content;
|
|
342
|
+
return convertToWhatsappMarkdown(plain).replace(EMOJI_CHARS, "");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function stripMarksAndEmoji(node: JSONContent): JSONContent {
|
|
346
|
+
const { marks: _marks, ...rest } = node;
|
|
347
|
+
return rest.content
|
|
326
348
|
? {
|
|
327
|
-
...
|
|
328
|
-
content:
|
|
349
|
+
...rest,
|
|
350
|
+
content: rest.content
|
|
329
351
|
.filter((n) => n.type !== "emoji")
|
|
330
|
-
.map(
|
|
352
|
+
.map(stripMarksAndEmoji),
|
|
331
353
|
}
|
|
332
|
-
:
|
|
354
|
+
: rest;
|
|
333
355
|
}
|
|
334
356
|
|
|
335
357
|
/**
|