@talqui-oss/template-builder 0.0.0-stage → 0.1.0-alpha.0
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/LICENSE.md +21 -0
- package/dist/adapters/meta.cjs +242 -0
- package/dist/adapters/meta.d.cts +15 -0
- package/dist/adapters/meta.d.ts +15 -0
- package/dist/adapters/meta.js +185 -0
- package/dist/adapters/talqui.cjs +263 -0
- package/dist/adapters/talqui.d.cts +19 -0
- package/dist/adapters/talqui.d.ts +19 -0
- package/dist/adapters/talqui.js +206 -0
- package/dist/adapters/twilio.cjs +215 -0
- package/dist/adapters/twilio.d.cts +16 -0
- package/dist/adapters/twilio.d.ts +16 -0
- package/dist/adapters/twilio.js +124 -0
- package/dist/chunk-SMN4WTO3.js +46 -0
- package/dist/chunk-ULTGMISN.js +70 -0
- package/dist/editor.css +274 -0
- package/dist/index.cjs +1855 -0
- package/dist/index.d.cts +315 -0
- package/dist/index.d.ts +315 -0
- package/dist/index.js +1743 -0
- package/dist/schema.json +689 -0
- package/dist/styles.css +267 -0
- package/dist/types-CRH48UOs.d.cts +194 -0
- package/dist/types-CRH48UOs.d.ts +194 -0
- package/package.json +60 -3
- package/README.md +0 -3
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/adapters/twilio.ts
|
|
21
|
+
var twilio_exports = {};
|
|
22
|
+
__export(twilio_exports, {
|
|
23
|
+
twilio: () => twilio
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(twilio_exports);
|
|
26
|
+
|
|
27
|
+
// src/validate/text.ts
|
|
28
|
+
var PLACEHOLDER = /\{\{(.*?)\}\}/g;
|
|
29
|
+
var VARIABLE_NAME = /^[a-z][a-z0-9_]*$/;
|
|
30
|
+
function placeholders(text) {
|
|
31
|
+
return [...text.matchAll(PLACEHOLDER)].map((match) => {
|
|
32
|
+
const name = match[1] ?? "";
|
|
33
|
+
return { raw: match[0], name, valid: VARIABLE_NAME.test(name) };
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/variables.ts
|
|
38
|
+
function textFields(template) {
|
|
39
|
+
if (template.category === "AUTHENTICATION") return [];
|
|
40
|
+
const fields = [];
|
|
41
|
+
if (template.header?.type === "TEXT")
|
|
42
|
+
fields.push({ path: "header.text", text: template.header.text });
|
|
43
|
+
fields.push({ path: "body.text", text: template.body.text });
|
|
44
|
+
template.carousel?.cards.forEach((card, index) => {
|
|
45
|
+
if (card.body)
|
|
46
|
+
fields.push({
|
|
47
|
+
path: `carousel.cards.${index}.body.text`,
|
|
48
|
+
text: card.body.text
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
template.buttons?.forEach((button, index) => {
|
|
52
|
+
if (button.type === "URL")
|
|
53
|
+
fields.push({ path: `buttons.${index}.url`, text: button.url });
|
|
54
|
+
});
|
|
55
|
+
template.carousel?.cards.forEach((card, cardIndex) => {
|
|
56
|
+
card.buttons?.forEach((button, index) => {
|
|
57
|
+
if (button.type === "URL")
|
|
58
|
+
fields.push({
|
|
59
|
+
path: `carousel.cards.${cardIndex}.buttons.${index}.url`,
|
|
60
|
+
text: button.url
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
return fields;
|
|
65
|
+
}
|
|
66
|
+
var replacePlaceholders = (text, replace) => text.replace(/\{\{(.*?)\}\}/g, (_, name) => replace(name));
|
|
67
|
+
function extractVariables(template) {
|
|
68
|
+
const names = textFields(template).flatMap(
|
|
69
|
+
(field) => placeholders(field.text).map((p) => p.name)
|
|
70
|
+
);
|
|
71
|
+
return [...new Set(names)];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/adapters/types.ts
|
|
75
|
+
function defineAdapter(definition) {
|
|
76
|
+
return (...[options]) => ({
|
|
77
|
+
name: definition.name,
|
|
78
|
+
toRequests: (template) => definition.toRequests(template, options ?? {})
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
var UnsupportedByProviderError = class extends Error {
|
|
82
|
+
constructor(provider, path, message) {
|
|
83
|
+
super(`${provider}: ${message} (${path})`);
|
|
84
|
+
this.provider = provider;
|
|
85
|
+
this.path = path;
|
|
86
|
+
this.name = "UnsupportedByProviderError";
|
|
87
|
+
}
|
|
88
|
+
provider;
|
|
89
|
+
path;
|
|
90
|
+
code = "UNSUPPORTED_BY_PROVIDER";
|
|
91
|
+
};
|
|
92
|
+
function unsupported(provider, path, what) {
|
|
93
|
+
throw new UnsupportedByProviderError(
|
|
94
|
+
provider,
|
|
95
|
+
path,
|
|
96
|
+
`${what} is not supported by this provider.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/adapters/twilio.ts
|
|
101
|
+
var NAME = "twilio";
|
|
102
|
+
var CONTENT_SID = "$CONTENT_SID";
|
|
103
|
+
var twilio = defineAdapter({
|
|
104
|
+
name: NAME,
|
|
105
|
+
toRequests(template, { accountSid = "$TWILIO_ACCOUNT_SID", authToken = "$TWILIO_AUTH_TOKEN" }) {
|
|
106
|
+
if (template.messageSendTtlSeconds !== void 0)
|
|
107
|
+
unsupported(NAME, "messageSendTtlSeconds", "A message TTL");
|
|
108
|
+
const credentials = accountSid.startsWith("$") || authToken.startsWith("$") ? `$(printf %s ${accountSid}:${authToken} | base64)` : btoa(`${accountSid}:${authToken}`);
|
|
109
|
+
const headers = {
|
|
110
|
+
Authorization: `Basic ${credentials}`,
|
|
111
|
+
"Content-Type": "application/json"
|
|
112
|
+
};
|
|
113
|
+
const order = extractVariables(template);
|
|
114
|
+
return [
|
|
115
|
+
{
|
|
116
|
+
method: "POST",
|
|
117
|
+
url: "https://content.twilio.com/v1/Content",
|
|
118
|
+
headers,
|
|
119
|
+
body: {
|
|
120
|
+
friendly_name: template.name,
|
|
121
|
+
language: template.language,
|
|
122
|
+
variables: order.length ? Object.fromEntries(
|
|
123
|
+
order.map((name, index) => [
|
|
124
|
+
String(index + 1),
|
|
125
|
+
template.category === "AUTHENTICATION" ? void 0 : template.variables?.[name]?.example
|
|
126
|
+
])
|
|
127
|
+
) : void 0,
|
|
128
|
+
types: template.category === "AUTHENTICATION" ? authentication(template) : standard(template, positional(order))
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
method: "POST",
|
|
133
|
+
url: `https://content.twilio.com/v1/Content/${CONTENT_SID}/ApprovalRequests/whatsapp`,
|
|
134
|
+
headers,
|
|
135
|
+
body: { name: template.name, category: template.category },
|
|
136
|
+
comments: [
|
|
137
|
+
`Replace ${CONTENT_SID} with the "sid" returned by the request above.`
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
];
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
var positional = (order) => (text) => replacePlaceholders(text, (name) => `{{${order.indexOf(name) + 1}}}`);
|
|
144
|
+
function standard(t, pos) {
|
|
145
|
+
if (t.limitedTimeOffer)
|
|
146
|
+
unsupported(NAME, "limitedTimeOffer", "A limited-time offer");
|
|
147
|
+
if (t.callPermissionRequest)
|
|
148
|
+
unsupported(NAME, "callPermissionRequest", "A call permission request");
|
|
149
|
+
if (t.displayFormat)
|
|
150
|
+
unsupported(NAME, "displayFormat", "The ORDER_DETAILS display format");
|
|
151
|
+
const body = pos(t.body.text);
|
|
152
|
+
if (t.carousel)
|
|
153
|
+
return {
|
|
154
|
+
"twilio/carousel": {
|
|
155
|
+
body,
|
|
156
|
+
cards: t.carousel.cards.map((card, index) => {
|
|
157
|
+
const path = `carousel.cards.${index}`;
|
|
158
|
+
if (card.header.type === "PRODUCT")
|
|
159
|
+
unsupported(NAME, `${path}.header`, "A PRODUCT card header");
|
|
160
|
+
return {
|
|
161
|
+
body: card.body && pos(card.body.text),
|
|
162
|
+
media: "url" in card.header ? card.header.url : void 0,
|
|
163
|
+
actions: actions(card.buttons ?? [], pos, `${path}.buttons`)
|
|
164
|
+
};
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const header = t.header;
|
|
169
|
+
if (header && !["TEXT", "IMAGE", "VIDEO", "DOCUMENT"].includes(header.type))
|
|
170
|
+
unsupported(NAME, "header", `A ${header.type} header`);
|
|
171
|
+
if (!header && !t.footer && !t.buttons?.length)
|
|
172
|
+
return { "twilio/text": { body } };
|
|
173
|
+
return {
|
|
174
|
+
"whatsapp/card": {
|
|
175
|
+
body,
|
|
176
|
+
header_text: header?.type === "TEXT" ? pos(header.text) : void 0,
|
|
177
|
+
media: header && "url" in header ? [header.url] : void 0,
|
|
178
|
+
footer: t.footer?.text,
|
|
179
|
+
actions: t.buttons?.length ? actions(t.buttons, pos, "buttons") : void 0
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function actions(buttons, pos, path) {
|
|
184
|
+
return buttons.map((b, index) => {
|
|
185
|
+
switch (b.type) {
|
|
186
|
+
case "QUICK_REPLY":
|
|
187
|
+
return { type: b.type, title: b.text, id: b.text };
|
|
188
|
+
case "URL":
|
|
189
|
+
return { type: b.type, title: b.text, url: pos(b.url) };
|
|
190
|
+
case "PHONE_NUMBER":
|
|
191
|
+
return { type: b.type, title: b.text, phone: b.phoneNumber };
|
|
192
|
+
case "COPY_CODE":
|
|
193
|
+
return { type: b.type, code: b.example };
|
|
194
|
+
default:
|
|
195
|
+
return unsupported(NAME, `${path}.${index}`, `The ${b.type} button`);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function authentication(t) {
|
|
200
|
+
if (t.button.otpType !== "COPY_CODE")
|
|
201
|
+
unsupported(NAME, "button.otpType", `The ${t.button.otpType} OTP button`);
|
|
202
|
+
return {
|
|
203
|
+
"whatsapp/authentication": {
|
|
204
|
+
add_security_recommendation: t.body?.addSecurityRecommendation,
|
|
205
|
+
code_expiration_minutes: t.footer?.codeExpirationMinutes,
|
|
206
|
+
actions: [
|
|
207
|
+
{ type: "COPY_CODE", copy_code_text: t.button.text ?? "Copy code" }
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
213
|
+
0 && (module.exports = {
|
|
214
|
+
twilio
|
|
215
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Adapter } from '../index.cjs';
|
|
2
|
+
|
|
3
|
+
type TwilioOptions = {
|
|
4
|
+
/** Default: `$TWILIO_ACCOUNT_SID`. */
|
|
5
|
+
accountSid?: string;
|
|
6
|
+
/** Default: `$TWILIO_AUTH_TOKEN`. */
|
|
7
|
+
authToken?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Twilio Content API: creates the content, then asks WhatsApp to approve it.
|
|
11
|
+
* Unverified: built from Twilio's docs, not yet run against a real account.
|
|
12
|
+
*/
|
|
13
|
+
declare const twilio: (options?: TwilioOptions | undefined) => Adapter;
|
|
14
|
+
|
|
15
|
+
export { twilio };
|
|
16
|
+
export type { TwilioOptions };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Adapter } from '../index.js';
|
|
2
|
+
|
|
3
|
+
type TwilioOptions = {
|
|
4
|
+
/** Default: `$TWILIO_ACCOUNT_SID`. */
|
|
5
|
+
accountSid?: string;
|
|
6
|
+
/** Default: `$TWILIO_AUTH_TOKEN`. */
|
|
7
|
+
authToken?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Twilio Content API: creates the content, then asks WhatsApp to approve it.
|
|
11
|
+
* Unverified: built from Twilio's docs, not yet run against a real account.
|
|
12
|
+
*/
|
|
13
|
+
declare const twilio: (options?: TwilioOptions | undefined) => Adapter;
|
|
14
|
+
|
|
15
|
+
export { twilio };
|
|
16
|
+
export type { TwilioOptions };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extractVariables,
|
|
3
|
+
replacePlaceholders
|
|
4
|
+
} from "../chunk-SMN4WTO3.js";
|
|
5
|
+
import {
|
|
6
|
+
defineAdapter,
|
|
7
|
+
unsupported
|
|
8
|
+
} from "../chunk-ULTGMISN.js";
|
|
9
|
+
|
|
10
|
+
// src/adapters/twilio.ts
|
|
11
|
+
var NAME = "twilio";
|
|
12
|
+
var CONTENT_SID = "$CONTENT_SID";
|
|
13
|
+
var twilio = defineAdapter({
|
|
14
|
+
name: NAME,
|
|
15
|
+
toRequests(template, { accountSid = "$TWILIO_ACCOUNT_SID", authToken = "$TWILIO_AUTH_TOKEN" }) {
|
|
16
|
+
if (template.messageSendTtlSeconds !== void 0)
|
|
17
|
+
unsupported(NAME, "messageSendTtlSeconds", "A message TTL");
|
|
18
|
+
const credentials = accountSid.startsWith("$") || authToken.startsWith("$") ? `$(printf %s ${accountSid}:${authToken} | base64)` : btoa(`${accountSid}:${authToken}`);
|
|
19
|
+
const headers = {
|
|
20
|
+
Authorization: `Basic ${credentials}`,
|
|
21
|
+
"Content-Type": "application/json"
|
|
22
|
+
};
|
|
23
|
+
const order = extractVariables(template);
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
method: "POST",
|
|
27
|
+
url: "https://content.twilio.com/v1/Content",
|
|
28
|
+
headers,
|
|
29
|
+
body: {
|
|
30
|
+
friendly_name: template.name,
|
|
31
|
+
language: template.language,
|
|
32
|
+
variables: order.length ? Object.fromEntries(
|
|
33
|
+
order.map((name, index) => [
|
|
34
|
+
String(index + 1),
|
|
35
|
+
template.category === "AUTHENTICATION" ? void 0 : template.variables?.[name]?.example
|
|
36
|
+
])
|
|
37
|
+
) : void 0,
|
|
38
|
+
types: template.category === "AUTHENTICATION" ? authentication(template) : standard(template, positional(order))
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
method: "POST",
|
|
43
|
+
url: `https://content.twilio.com/v1/Content/${CONTENT_SID}/ApprovalRequests/whatsapp`,
|
|
44
|
+
headers,
|
|
45
|
+
body: { name: template.name, category: template.category },
|
|
46
|
+
comments: [
|
|
47
|
+
`Replace ${CONTENT_SID} with the "sid" returned by the request above.`
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
var positional = (order) => (text) => replacePlaceholders(text, (name) => `{{${order.indexOf(name) + 1}}}`);
|
|
54
|
+
function standard(t, pos) {
|
|
55
|
+
if (t.limitedTimeOffer)
|
|
56
|
+
unsupported(NAME, "limitedTimeOffer", "A limited-time offer");
|
|
57
|
+
if (t.callPermissionRequest)
|
|
58
|
+
unsupported(NAME, "callPermissionRequest", "A call permission request");
|
|
59
|
+
if (t.displayFormat)
|
|
60
|
+
unsupported(NAME, "displayFormat", "The ORDER_DETAILS display format");
|
|
61
|
+
const body = pos(t.body.text);
|
|
62
|
+
if (t.carousel)
|
|
63
|
+
return {
|
|
64
|
+
"twilio/carousel": {
|
|
65
|
+
body,
|
|
66
|
+
cards: t.carousel.cards.map((card, index) => {
|
|
67
|
+
const path = `carousel.cards.${index}`;
|
|
68
|
+
if (card.header.type === "PRODUCT")
|
|
69
|
+
unsupported(NAME, `${path}.header`, "A PRODUCT card header");
|
|
70
|
+
return {
|
|
71
|
+
body: card.body && pos(card.body.text),
|
|
72
|
+
media: "url" in card.header ? card.header.url : void 0,
|
|
73
|
+
actions: actions(card.buttons ?? [], pos, `${path}.buttons`)
|
|
74
|
+
};
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const header = t.header;
|
|
79
|
+
if (header && !["TEXT", "IMAGE", "VIDEO", "DOCUMENT"].includes(header.type))
|
|
80
|
+
unsupported(NAME, "header", `A ${header.type} header`);
|
|
81
|
+
if (!header && !t.footer && !t.buttons?.length)
|
|
82
|
+
return { "twilio/text": { body } };
|
|
83
|
+
return {
|
|
84
|
+
"whatsapp/card": {
|
|
85
|
+
body,
|
|
86
|
+
header_text: header?.type === "TEXT" ? pos(header.text) : void 0,
|
|
87
|
+
media: header && "url" in header ? [header.url] : void 0,
|
|
88
|
+
footer: t.footer?.text,
|
|
89
|
+
actions: t.buttons?.length ? actions(t.buttons, pos, "buttons") : void 0
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function actions(buttons, pos, path) {
|
|
94
|
+
return buttons.map((b, index) => {
|
|
95
|
+
switch (b.type) {
|
|
96
|
+
case "QUICK_REPLY":
|
|
97
|
+
return { type: b.type, title: b.text, id: b.text };
|
|
98
|
+
case "URL":
|
|
99
|
+
return { type: b.type, title: b.text, url: pos(b.url) };
|
|
100
|
+
case "PHONE_NUMBER":
|
|
101
|
+
return { type: b.type, title: b.text, phone: b.phoneNumber };
|
|
102
|
+
case "COPY_CODE":
|
|
103
|
+
return { type: b.type, code: b.example };
|
|
104
|
+
default:
|
|
105
|
+
return unsupported(NAME, `${path}.${index}`, `The ${b.type} button`);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function authentication(t) {
|
|
110
|
+
if (t.button.otpType !== "COPY_CODE")
|
|
111
|
+
unsupported(NAME, "button.otpType", `The ${t.button.otpType} OTP button`);
|
|
112
|
+
return {
|
|
113
|
+
"whatsapp/authentication": {
|
|
114
|
+
add_security_recommendation: t.body?.addSecurityRecommendation,
|
|
115
|
+
code_expiration_minutes: t.footer?.codeExpirationMinutes,
|
|
116
|
+
actions: [
|
|
117
|
+
{ type: "COPY_CODE", copy_code_text: t.button.text ?? "Copy code" }
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export {
|
|
123
|
+
twilio
|
|
124
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
placeholders
|
|
3
|
+
} from "./chunk-ULTGMISN.js";
|
|
4
|
+
|
|
5
|
+
// src/variables.ts
|
|
6
|
+
function textFields(template) {
|
|
7
|
+
if (template.category === "AUTHENTICATION") return [];
|
|
8
|
+
const fields = [];
|
|
9
|
+
if (template.header?.type === "TEXT")
|
|
10
|
+
fields.push({ path: "header.text", text: template.header.text });
|
|
11
|
+
fields.push({ path: "body.text", text: template.body.text });
|
|
12
|
+
template.carousel?.cards.forEach((card, index) => {
|
|
13
|
+
if (card.body)
|
|
14
|
+
fields.push({
|
|
15
|
+
path: `carousel.cards.${index}.body.text`,
|
|
16
|
+
text: card.body.text
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
template.buttons?.forEach((button, index) => {
|
|
20
|
+
if (button.type === "URL")
|
|
21
|
+
fields.push({ path: `buttons.${index}.url`, text: button.url });
|
|
22
|
+
});
|
|
23
|
+
template.carousel?.cards.forEach((card, cardIndex) => {
|
|
24
|
+
card.buttons?.forEach((button, index) => {
|
|
25
|
+
if (button.type === "URL")
|
|
26
|
+
fields.push({
|
|
27
|
+
path: `carousel.cards.${cardIndex}.buttons.${index}.url`,
|
|
28
|
+
text: button.url
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
return fields;
|
|
33
|
+
}
|
|
34
|
+
var replacePlaceholders = (text, replace) => text.replace(/\{\{(.*?)\}\}/g, (_, name) => replace(name));
|
|
35
|
+
function extractVariables(template) {
|
|
36
|
+
const names = textFields(template).flatMap(
|
|
37
|
+
(field) => placeholders(field.text).map((p) => p.name)
|
|
38
|
+
);
|
|
39
|
+
return [...new Set(names)];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
textFields,
|
|
44
|
+
replacePlaceholders,
|
|
45
|
+
extractVariables
|
|
46
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// src/validate/text.ts
|
|
2
|
+
var PLACEHOLDER = /\{\{(.*?)\}\}/g;
|
|
3
|
+
var VARIABLE_NAME = /^[a-z][a-z0-9_]*$/;
|
|
4
|
+
var INVISIBLE = /[\p{Default_Ignorable_Code_Point}\u2800]/gu;
|
|
5
|
+
var THAI_LAO = /[\u0E00-\u0E7F]/g;
|
|
6
|
+
var CJK = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/gu;
|
|
7
|
+
var EMOJI = /\p{Regional_Indicator}{2}|\p{Extended_Pictographic}(?:\uFE0F|\p{Emoji_Modifier})*(?:\u200D\p{Extended_Pictographic}(?:\uFE0F|\p{Emoji_Modifier})*)*/gu;
|
|
8
|
+
var MARKUP = /\*[^*\n]+\*|_[^_\n]+_|~[^~\n]+~|```[^`]+```/;
|
|
9
|
+
function placeholders(text) {
|
|
10
|
+
return [...text.matchAll(PLACEHOLDER)].map((match) => {
|
|
11
|
+
const name = match[1] ?? "";
|
|
12
|
+
return { raw: match[0], name, valid: VARIABLE_NAME.test(name) };
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
var length = (text) => [...text].length;
|
|
16
|
+
var countEmoji = (text) => text.match(EMOJI)?.length ?? 0;
|
|
17
|
+
var hasMarkup = (text) => text.includes("*") || MARKUP.test(text);
|
|
18
|
+
function countWords(text) {
|
|
19
|
+
const clean = text.replace(INVISIBLE, "");
|
|
20
|
+
const params = placeholders(clean).length;
|
|
21
|
+
const thai = clean.match(THAI_LAO)?.length ?? 0;
|
|
22
|
+
if (thai > 0) return Math.ceil(thai / 7) + params;
|
|
23
|
+
const cjk = clean.match(CJK)?.length ?? 0;
|
|
24
|
+
if (cjk > 0) return cjk + params;
|
|
25
|
+
return clean.match(/\S+/g)?.length ?? 0;
|
|
26
|
+
}
|
|
27
|
+
var EDGE_START = /^[\s*_~`]*\{\{[^}]*\}\}/;
|
|
28
|
+
var EDGE_END = /\{\{[^}]*\}\}[\s*_~`]*!*\.*\?*[\s*_~`]*$/;
|
|
29
|
+
var ADJACENT = /\{\{[^}]*\}\}[\s*_~`]*\{\{[^}]*\}\}/;
|
|
30
|
+
var startsOrEndsWithPlaceholder = (text) => EDGE_START.test(text) || EDGE_END.test(text);
|
|
31
|
+
var hasAdjacentPlaceholders = (text) => ADJACENT.test(text);
|
|
32
|
+
|
|
33
|
+
// src/adapters/types.ts
|
|
34
|
+
function defineAdapter(definition) {
|
|
35
|
+
return (...[options]) => ({
|
|
36
|
+
name: definition.name,
|
|
37
|
+
toRequests: (template) => definition.toRequests(template, options ?? {})
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
var UnsupportedByProviderError = class extends Error {
|
|
41
|
+
constructor(provider, path, message) {
|
|
42
|
+
super(`${provider}: ${message} (${path})`);
|
|
43
|
+
this.provider = provider;
|
|
44
|
+
this.path = path;
|
|
45
|
+
this.name = "UnsupportedByProviderError";
|
|
46
|
+
}
|
|
47
|
+
provider;
|
|
48
|
+
path;
|
|
49
|
+
code = "UNSUPPORTED_BY_PROVIDER";
|
|
50
|
+
};
|
|
51
|
+
function unsupported(provider, path, what) {
|
|
52
|
+
throw new UnsupportedByProviderError(
|
|
53
|
+
provider,
|
|
54
|
+
path,
|
|
55
|
+
`${what} is not supported by this provider.`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
placeholders,
|
|
61
|
+
length,
|
|
62
|
+
countEmoji,
|
|
63
|
+
hasMarkup,
|
|
64
|
+
countWords,
|
|
65
|
+
startsOrEndsWithPlaceholder,
|
|
66
|
+
hasAdjacentPlaceholders,
|
|
67
|
+
defineAdapter,
|
|
68
|
+
UnsupportedByProviderError,
|
|
69
|
+
unsupported
|
|
70
|
+
};
|