@samva/markup 0.14.0 → 0.15.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/AGENTS.md +1 -1
- package/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/dist/{brand-B-t62NJB.js → brand-nreeKWxF.js} +1 -1
- package/dist/brand.js +2 -2
- package/dist/compiler.d.ts +1 -1
- package/dist/compiler.js +6 -6
- package/dist/{components-CwWSoG5N.js → components-3XHjYD3i.js} +1 -1
- package/dist/edit.js +1 -1
- package/dist/email.js +1 -1
- package/dist/{html-eOZfDwVf.js → html-BvRFepeO.js} +9 -0
- package/dist/{ir-D-gTGquU.js → ir-DNL6H7jA.js} +1 -1
- package/dist/{profile-Cc8VxOKA.js → profile-AatO_SvP.js} +66 -5
- package/dist/{render-C1X3S5or.d.ts → render-BdvewoQt.d.ts} +14 -0
- package/dist/{render-6gLei61p.js → render-DpYouYYG.js} +3 -3
- package/dist/render.d.ts +1 -1
- package/dist/render.js +3 -3
- package/dist/whatsapp.d.ts +20 -2
- package/dist/whatsapp.js +3 -2
- package/generated/contract.json +30 -0
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -23,7 +23,7 @@ Authoring entries are what a template file imports:
|
|
|
23
23
|
functions a body may call.
|
|
24
24
|
- `@samva/markup/sms` and `@samva/markup/whatsapp` — the channel components (`Sms`; `WhatsApp` with
|
|
25
25
|
`WhatsApp.Header`, `Body`, `Footer`, `Buttons`, `UrlButton`, `QuickReplyButton`, `PhoneButton`,
|
|
26
|
-
`CopyCodeButton`). `whatsappTemplate` derives Meta's positional `{{n}}` text.
|
|
26
|
+
`CopyCodeButton`, `FlowButton`). `whatsappTemplate` derives Meta's positional `{{n}}` text.
|
|
27
27
|
- `@samva/markup/input-schema` — `inputSchema`, which turns any Standard JSON Schema into the
|
|
28
28
|
portable JSON Schema and validator a template's input is checked against.
|
|
29
29
|
- `samva:brand` — `BrandLogo` (light logo, swapped for the dark one where the client honors dark
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## @samva/markup@0.15.0
|
|
2
|
+
|
|
3
|
+
### WhatsApp template FLOW buttons
|
|
4
|
+
|
|
5
|
+
`<WhatsApp.FlowButton flow="order-survey">Take survey</WhatsApp.FlowButton>` adds a Meta FLOW
|
|
6
|
+
button to a WhatsApp template. `flow` is the Samva flow handle (lowercase kebab-case, the file
|
|
7
|
+
`flows/<handle>.json` in the template project); Samva maps it to Meta's flow id per business
|
|
8
|
+
account when it registers the template. `action` is `navigate` by default or `data_exchange`, and
|
|
9
|
+
`screen` names the screen a `navigate` flow opens on (omitted, the flow's first entry screen);
|
|
10
|
+
`screen` is refused with `data_exchange`. `flow`, `action` and `screen` are string literals, and a
|
|
11
|
+
template holds at most one flow button.
|
|
12
|
+
|
|
13
|
+
The IR button is `{ type: "flow", text, flow, action, screen? }`, and the rendered WhatsApp
|
|
14
|
+
channel and the editor preview carry it. The editor shows a flow button as a locked row; it is
|
|
15
|
+
authored in source.
|
|
16
|
+
|
|
1
17
|
## @samva/markup@0.14.0
|
|
2
18
|
|
|
3
19
|
### A semantic theme vocabulary, one dark-mode mechanism and a contrast check
|
package/README.md
CHANGED
|
@@ -194,7 +194,7 @@ explicit argument overrides it.
|
|
|
194
194
|
One template carries one schema and a body for each channel it supports. `@samva/markup/sms` and
|
|
195
195
|
`@samva/markup/whatsapp` export the channel components (`Sms`; `WhatsApp` with `WhatsApp.Header`,
|
|
196
196
|
`.Body`, `.Footer`, `.Buttons`, `.UrlButton`, `.QuickReplyButton`, `.PhoneButton` and
|
|
197
|
-
`.CopyCodeButton`). Their names are distinct per channel, so a wrong prop fails in the type checker.
|
|
197
|
+
`.CopyCodeButton` and `.FlowButton`). Their names are distinct per channel, so a wrong prop fails in the type checker.
|
|
198
198
|
WhatsApp bindings compile to Meta's positional `{{1}}` placeholders (`whatsappTemplate`), and a
|
|
199
199
|
conditional in a WhatsApp text part is refused because Meta templates cannot branch. The platform
|
|
200
200
|
gates these channels.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as samvaBrandPlugin, O as BRAND_FOOTER_MARKER, k as UNSUBSCRIBE_LINK, r as escapeAttribute } from "./html-
|
|
1
|
+
import { D as samvaBrandPlugin, O as BRAND_FOOTER_MARKER, k as UNSUBSCRIBE_LINK, r as escapeAttribute } from "./html-BvRFepeO.js";
|
|
2
2
|
import { a as jsx, n as Fragment } from "./jsx-runtime-CBlexoeR.js";
|
|
3
3
|
//#region src/email/assets.ts
|
|
4
4
|
/** Files a template may import, and what they are served as. */
|
package/dist/brand.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as samvaBrandPlugin } from "./html-
|
|
1
|
+
import { D as samvaBrandPlugin } from "./html-BvRFepeO.js";
|
|
2
2
|
import { a as THEME_COLOR_ROLES, c as foregroundDefaults, i as THEME_COLORS, l as parseBrandCss, n as DEFAULT_THEME_CSS, o as THEME_TOKENS, r as FILL_COLORS, s as contrastForeground, t as DEFAULT_THEME, u as printBrandCss } from "./theme-css-s9QwD5Ko.js";
|
|
3
|
-
import { a as ASSET_CONTENT_TYPES, c as checkAssetBase, i as withoutUnsubscribeRow, l as isAssetPath, n as brandComponents, o as assetEntry, r as renderedFooterMarkers, s as assetUrl, t as BRAND_COMPONENT_CLASSES, u as isExternalAsset } from "./brand-
|
|
3
|
+
import { a as ASSET_CONTENT_TYPES, c as checkAssetBase, i as withoutUnsubscribeRow, l as isAssetPath, n as brandComponents, o as assetEntry, r as renderedFooterMarkers, s as assetUrl, t as BRAND_COMPONENT_CLASSES, u as isExternalAsset } from "./brand-nreeKWxF.js";
|
|
4
4
|
export { ASSET_CONTENT_TYPES, BRAND_COMPONENT_CLASSES, DEFAULT_THEME, DEFAULT_THEME_CSS, FILL_COLORS, THEME_COLORS, THEME_COLOR_ROLES, THEME_TOKENS, assetEntry, assetUrl, brandComponents, checkAssetBase, contrastForeground, foregroundDefaults, isAssetPath, isExternalAsset, parseBrandCss, printBrandCss, renderedFooterMarkers, samvaBrandPlugin, withoutUnsubscribeRow };
|
package/dist/compiler.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { n as parseBrandSpecifier, t as BrandPlugin } from "./brand-plugin-CHhce
|
|
|
2
2
|
import { a as TemplateDiagnostic, r as EmailDiagnostic } from "./diagnostics-CUYlQ_6x.js";
|
|
3
3
|
import { r as EmailNode } from "./jsx-runtime-DC7xG0aA.js";
|
|
4
4
|
import { n as AssetEntry, p as EmailBrand, u as BrandComponents } from "./assets-tISHaQNF.js";
|
|
5
|
-
import { C as Compound, D as EmittedPosition, S as CompiledSelector, T as matchesSelector, _ as Specificity, b as parseStylesheet, ct as TemplateIr, g as CssRule, h as CssDeclaration, r as RenderedEmail, v as Stylesheet, w as classListOf, x as Ancestry, y as compileSelector } from "./render-
|
|
5
|
+
import { C as Compound, D as EmittedPosition, S as CompiledSelector, T as matchesSelector, _ as Specificity, b as parseStylesheet, ct as TemplateIr, g as CssRule, h as CssDeclaration, r as RenderedEmail, v as Stylesheet, w as classListOf, x as Ancestry, y as compileSelector } from "./render-BdvewoQt.js";
|
|
6
6
|
import { a as TEMPLATE_NAME_PATTERN, n as EmailContent } from "./template-B-UDXK8a.js";
|
|
7
7
|
import { caniemail } from "caniemail";
|
|
8
8
|
//#region src/email/compatibility.d.ts
|
package/dist/compiler.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { n as isObject } from "./guards-eV1qLbf2.js";
|
|
2
2
|
import { n as TEMPLATE_NAME_PATTERN } from "./template-zLUjcwfF.js";
|
|
3
|
-
import { D as samvaBrandPlugin, T as parseBrandSpecifier, _ as renderIr, w as brandSpecifierPattern } from "./html-
|
|
4
|
-
import { m as PRIMITIVE_CSS } from "./components-
|
|
3
|
+
import { D as samvaBrandPlugin, T as parseBrandSpecifier, _ as renderIr, w as brandSpecifierPattern } from "./html-BvRFepeO.js";
|
|
4
|
+
import { m as PRIMITIVE_CSS } from "./components-3XHjYD3i.js";
|
|
5
5
|
import { c as foregroundDefaults, d as downlevelColors, f as formatColor, l as parseBrandCss, n as DEFAULT_THEME_CSS, p as parseColor, u as printBrandCss } from "./theme-css-s9QwD5Ko.js";
|
|
6
6
|
import { n as isDarkSchemeCondition, r as isDarkSchemeQuery, t as DARK_SCHEME_MEDIA } from "./dark-scheme-CbCwGDWH.js";
|
|
7
|
-
import { a as downlevelMediaQuery, c as renderHeadCss, d as matchesSelector, f as unquote, i as renderDarkRules, l as resolveVariables, o as finalizeValue, r as applyStylesheet, s as foldCalc, u as classListOf } from "./ir-
|
|
7
|
+
import { a as downlevelMediaQuery, c as renderHeadCss, d as matchesSelector, f as unquote, i as renderDarkRules, l as resolveVariables, o as finalizeValue, r as applyStylesheet, s as foldCalc, u as classListOf } from "./ir-DNL6H7jA.js";
|
|
8
8
|
import { i as buildLineMap, s as lineColumn } from "./diagnostic-codes-UXwOO3A-.js";
|
|
9
|
-
import { a as lowerProject, d as resolveFromDirectory, f as resolveThemeImports, i as completed, l as Findings, n as introducedProfileErrors, r as loadProject, t as checkStaticProfile, u as projectDirectory } from "./profile-
|
|
10
|
-
import { c as checkAssetBase, i as withoutUnsubscribeRow, l as isAssetPath, n as brandComponents, o as assetEntry, s as assetUrl, t as BRAND_COMPONENT_CLASSES } from "./brand-
|
|
9
|
+
import { a as lowerProject, d as resolveFromDirectory, f as resolveThemeImports, i as completed, l as Findings, n as introducedProfileErrors, r as loadProject, t as checkStaticProfile, u as projectDirectory } from "./profile-AatO_SvP.js";
|
|
10
|
+
import { c as checkAssetBase, i as withoutUnsubscribeRow, l as isAssetPath, n as brandComponents, o as assetEntry, s as assetUrl, t as BRAND_COMPONENT_CLASSES } from "./brand-nreeKWxF.js";
|
|
11
11
|
import { t as EmailCompileError } from "./diagnostics-C0Tr8-eQ.js";
|
|
12
|
-
import { n as compileEmail, r as renderEmailContent } from "./render-
|
|
12
|
+
import { n as compileEmail, r as renderEmailContent } from "./render-DpYouYYG.js";
|
|
13
13
|
import { caniemail, parseHtml, rawData } from "caniemail";
|
|
14
14
|
import { generate, ident, parse, walk } from "css-tree";
|
|
15
15
|
import { parse as parse$1 } from "@babel/parser";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as escapeText, r as escapeAttribute } from "./html-
|
|
1
|
+
import { i as escapeText, r as escapeAttribute } from "./html-BvRFepeO.js";
|
|
2
2
|
import { a as jsx, c as rawMarkup, l as reportEmailDiagnostic, n as Fragment, s as normalizeChildren, t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
|
|
3
3
|
//#region src/email/primitive-css.ts
|
|
4
4
|
/** The classes the primitives put on the elements they generate. */
|
package/dist/edit.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as isObject } from "./guards-eV1qLbf2.js";
|
|
2
2
|
import { d as completeJsxSourceLocation, u as JSX_SOURCE_LOCATION_PROP } from "./jsx-runtime-CBlexoeR.js";
|
|
3
3
|
import { i as buildLineMap } from "./diagnostic-codes-UXwOO3A-.js";
|
|
4
|
-
import { c as WHATSAPP_HEADER_TYPES, n as introducedProfileErrors, o as SMS_CATEGORIES, s as WHATSAPP_CATEGORIES, t as checkStaticProfile } from "./profile-
|
|
4
|
+
import { c as WHATSAPP_HEADER_TYPES, n as introducedProfileErrors, o as SMS_CATEGORIES, s as WHATSAPP_CATEGORIES, t as checkStaticProfile } from "./profile-AatO_SvP.js";
|
|
5
5
|
import { parse } from "@babel/parser";
|
|
6
6
|
//#region src/source-edit.ts
|
|
7
7
|
const node = (value) => isObject(value) && "type" in value && typeof value.type === "string" && "start" in value && typeof value.start === "number" && "end" in value && typeof value.end === "number";
|
package/dist/email.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as Email, c as PREHEADER_ATTRIBUTE, d as equalColumnWidth, f as preheaderNode, i as Divider, l as Section, n as Column, o as Image, r as Columns, s as Link, t as Button, u as Spacer } from "./components-
|
|
1
|
+
import { a as Email, c as PREHEADER_ATTRIBUTE, d as equalColumnWidth, f as preheaderNode, i as Divider, l as Section, n as Column, o as Image, r as Columns, s as Link, t as Button, u as Spacer } from "./components-3XHjYD3i.js";
|
|
2
2
|
export { Button, Column, Columns, Divider, Email, Image, Link, PREHEADER_ATTRIBUTE, Section, Spacer, equalColumnWidth, preheaderNode };
|
|
@@ -1137,6 +1137,15 @@ const resolveButtons = (ctx, nodes, out, depth) => {
|
|
|
1137
1137
|
code: value(node.code)
|
|
1138
1138
|
});
|
|
1139
1139
|
break;
|
|
1140
|
+
case "flow":
|
|
1141
|
+
out.push({
|
|
1142
|
+
type: "flow",
|
|
1143
|
+
text: value(node.text),
|
|
1144
|
+
flow: node.flow,
|
|
1145
|
+
action: node.action,
|
|
1146
|
+
...node.screen === void 0 ? {} : { screen: node.screen }
|
|
1147
|
+
});
|
|
1148
|
+
break;
|
|
1140
1149
|
default: invalid("Unknown WhatsApp button.");
|
|
1141
1150
|
}
|
|
1142
1151
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as isObject } from "./guards-eV1qLbf2.js";
|
|
2
|
-
import { a as kebab, n as declarationValue, r as escapeAttribute } from "./html-
|
|
2
|
+
import { a as kebab, n as declarationValue, r as escapeAttribute } from "./html-BvRFepeO.js";
|
|
3
3
|
import { t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
|
|
4
4
|
import { i as withinDarkScheme, t as DARK_SCHEME_MEDIA } from "./dark-scheme-CbCwGDWH.js";
|
|
5
5
|
//#region src/email/font-stacks.ts
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { n as isObject } from "./guards-eV1qLbf2.js";
|
|
2
2
|
import { n as inputSchema, r as jsonSchema } from "./input-schema-zjkgrXn2.js";
|
|
3
3
|
import { n as TEMPLATE_NAME_PATTERN, t as TEMPLATE_ID_PATTERN } from "./template-zLUjcwfF.js";
|
|
4
|
-
import { D as samvaBrandPlugin, E as reservedBrandAttributes, O as BRAND_FOOTER_MARKER, S as emailElement, T as parseBrandSpecifier, b as EMAIL_ELEMENTS, s as serializeStyle, t as checkAttribute } from "./html-
|
|
4
|
+
import { D as samvaBrandPlugin, E as reservedBrandAttributes, O as BRAND_FOOTER_MARKER, S as emailElement, T as parseBrandSpecifier, b as EMAIL_ELEMENTS, s as serializeStyle, t as checkAttribute } from "./html-BvRFepeO.js";
|
|
5
5
|
import { FORMATTER_NAMES } from "./fmt.js";
|
|
6
6
|
import { i as drainEmailDiagnostics, t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
|
|
7
|
-
import { a as Email, d as equalColumnWidth, i as Divider, l as Section, n as Column, o as Image, p as PRIMITIVE_CLASSES, r as Columns, s as Link, t as Button, u as Spacer } from "./components-
|
|
8
|
-
import { i as renderDarkRules, n as DARK_CLASS_PREFIX, r as applyStylesheet } from "./ir-
|
|
7
|
+
import { a as Email, d as equalColumnWidth, i as Divider, l as Section, n as Column, o as Image, p as PRIMITIVE_CLASSES, r as Columns, s as Link, t as Button, u as Spacer } from "./components-3XHjYD3i.js";
|
|
8
|
+
import { i as renderDarkRules, n as DARK_CLASS_PREFIX, r as applyStylesheet } from "./ir-DNL6H7jA.js";
|
|
9
9
|
import { i as buildLineMap, s as lineColumn, t as DIAGNOSTIC_CODES } from "./diagnostic-codes-UXwOO3A-.js";
|
|
10
|
-
import { l as isAssetPath, n as brandComponents } from "./brand-
|
|
10
|
+
import { l as isAssetPath, n as brandComponents } from "./brand-nreeKWxF.js";
|
|
11
11
|
import { parse } from "css-tree";
|
|
12
12
|
import { parse as parse$1 } from "@babel/parser";
|
|
13
13
|
//#region src/email/theme-imports.ts
|
|
@@ -2530,7 +2530,12 @@ const BUTTON_TEXT_LIMIT = 25;
|
|
|
2530
2530
|
const URL_BUTTON_LIMIT = 2;
|
|
2531
2531
|
const PHONE_BUTTON_LIMIT = 1;
|
|
2532
2532
|
const QUICK_REPLY_LIMIT = 10;
|
|
2533
|
+
const FLOW_BUTTON_LIMIT = 1;
|
|
2533
2534
|
const BUTTON_LIMIT = 10;
|
|
2535
|
+
const FLOW_HANDLE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2536
|
+
const FLOW_HANDLE_LIMIT = 64;
|
|
2537
|
+
const FLOW_SCREEN_PATTERN = /^[A-Za-z_]{1,100}$/;
|
|
2538
|
+
const FLOW_ACTIONS = ["navigate", "data_exchange"];
|
|
2534
2539
|
const jsxName = (name) => {
|
|
2535
2540
|
if (name.type === "JSXIdentifier") return text(name, "name") ?? "";
|
|
2536
2541
|
if (name.type === "JSXMemberExpression") return `${jsxName(child(name, "object") ?? name)}.${text(child(name, "property") ?? name, "name") ?? ""}`;
|
|
@@ -2636,6 +2641,7 @@ const ZERO = {
|
|
|
2636
2641
|
url: 0,
|
|
2637
2642
|
phone: 0,
|
|
2638
2643
|
quickReply: 0,
|
|
2644
|
+
flow: 0,
|
|
2639
2645
|
total: 0
|
|
2640
2646
|
};
|
|
2641
2647
|
/** The most buttons of each kind any combination of the conditions can produce. */
|
|
@@ -2650,18 +2656,21 @@ const maximalCounts = (nodes) => {
|
|
|
2650
2656
|
url: Math.max(then.url, otherwise.url),
|
|
2651
2657
|
phone: Math.max(then.phone, otherwise.phone),
|
|
2652
2658
|
quickReply: Math.max(then.quickReply, otherwise.quickReply),
|
|
2659
|
+
flow: Math.max(then.flow, otherwise.flow),
|
|
2653
2660
|
total: Math.max(then.total, otherwise.total)
|
|
2654
2661
|
};
|
|
2655
2662
|
} else add = {
|
|
2656
2663
|
url: node.type === "url" ? 1 : 0,
|
|
2657
2664
|
phone: node.type === "phone" ? 1 : 0,
|
|
2658
2665
|
quickReply: node.type === "quick-reply" ? 1 : 0,
|
|
2666
|
+
flow: node.type === "flow" ? 1 : 0,
|
|
2659
2667
|
total: 1
|
|
2660
2668
|
};
|
|
2661
2669
|
counts = {
|
|
2662
2670
|
url: counts.url + add.url,
|
|
2663
2671
|
phone: counts.phone + add.phone,
|
|
2664
2672
|
quickReply: counts.quickReply + add.quickReply,
|
|
2673
|
+
flow: counts.flow + add.flow,
|
|
2665
2674
|
total: counts.total + add.total
|
|
2666
2675
|
};
|
|
2667
2676
|
}
|
|
@@ -2679,6 +2688,7 @@ const buttonNodes = (buttons) => buttons.flatMap((button) => {
|
|
|
2679
2688
|
case "url": return [button.text, button.url];
|
|
2680
2689
|
case "phone": return [button.text, button.phone];
|
|
2681
2690
|
case "copy-code": return [button.code];
|
|
2691
|
+
case "flow": return [button.text];
|
|
2682
2692
|
}
|
|
2683
2693
|
});
|
|
2684
2694
|
const whatsappChannel = (input, field) => {
|
|
@@ -2772,6 +2782,7 @@ const whatsappChannel = (input, field) => {
|
|
|
2772
2782
|
over("URL buttons", counts.url, URL_BUTTON_LIMIT);
|
|
2773
2783
|
over("phone buttons", counts.phone, PHONE_BUTTON_LIMIT);
|
|
2774
2784
|
over("quick-reply buttons", counts.quickReply, QUICK_REPLY_LIMIT);
|
|
2785
|
+
over("flow buttons", counts.flow, FLOW_BUTTON_LIMIT);
|
|
2775
2786
|
over("buttons in all", counts.total, BUTTON_LIMIT);
|
|
2776
2787
|
roots.push(...buttonNodes(buttons));
|
|
2777
2788
|
}
|
|
@@ -2790,7 +2801,8 @@ const BUTTON_TAGS = [
|
|
|
2790
2801
|
"UrlButton",
|
|
2791
2802
|
"QuickReplyButton",
|
|
2792
2803
|
"PhoneButton",
|
|
2793
|
-
"CopyCodeButton"
|
|
2804
|
+
"CopyCodeButton",
|
|
2805
|
+
"FlowButton"
|
|
2794
2806
|
];
|
|
2795
2807
|
const buttonList = (input, ctx, local, parent) => {
|
|
2796
2808
|
const { lowerer } = input;
|
|
@@ -2815,6 +2827,54 @@ const buttonList = (input, ctx, local, parent) => {
|
|
|
2815
2827
|
if (value === void 0) lowerer.error(ctx, element, "missing-attribute", `This button needs \`${name}\`.`, `Write ${name}="…" or ${name}={input.field}.`);
|
|
2816
2828
|
return value;
|
|
2817
2829
|
};
|
|
2830
|
+
const flowButton = (element) => {
|
|
2831
|
+
const label_ = label(element, "label");
|
|
2832
|
+
const values = /* @__PURE__ */ new Map();
|
|
2833
|
+
let valid = true;
|
|
2834
|
+
const refuse = (node, code, message, fix) => {
|
|
2835
|
+
valid = false;
|
|
2836
|
+
lowerer.error(ctx, node, code, message, fix);
|
|
2837
|
+
};
|
|
2838
|
+
for (const item of attributesOf(element)) {
|
|
2839
|
+
if (item.type === "JSXSpreadAttribute") {
|
|
2840
|
+
refuse(item, "invalid-attribute", "A spread attribute runs code, and templates render without running code.", "Write flow, action and screen as string literals.");
|
|
2841
|
+
continue;
|
|
2842
|
+
}
|
|
2843
|
+
const nameNode = child(item, "name");
|
|
2844
|
+
const name = nameNode === void 0 ? "" : jsxName(nameNode);
|
|
2845
|
+
if (name === "key") continue;
|
|
2846
|
+
if (name !== "flow" && name !== "action" && name !== "screen") {
|
|
2847
|
+
refuse(item, "invalid-attribute", `\`${name}\` is not an attribute of this button.`, "The attributes are flow, action and screen.");
|
|
2848
|
+
continue;
|
|
2849
|
+
}
|
|
2850
|
+
const raw = child(item, "value");
|
|
2851
|
+
const literal = raw?.type === "JSXExpressionContainer" ? child(raw, "expression") : raw;
|
|
2852
|
+
if (literal?.type !== "StringLiteral") {
|
|
2853
|
+
refuse(item, "invalid-attribute", `\`${name}\` is fixed when the template is registered, so it is a string literal, not a binding.`, `Write ${name}="…".`);
|
|
2854
|
+
continue;
|
|
2855
|
+
}
|
|
2856
|
+
values.set(name, text(literal, "value") ?? "");
|
|
2857
|
+
}
|
|
2858
|
+
const flow = values.get("flow");
|
|
2859
|
+
if (flow === void 0) {
|
|
2860
|
+
if (valid) refuse(element, "missing-attribute", "This button needs `flow`.", "Write flow=\"handle\".");
|
|
2861
|
+
} else if (flow.length > FLOW_HANDLE_LIMIT || !FLOW_HANDLE_PATTERN.test(flow)) refuse(element, "invalid-attribute", `\`${flow}\` is not a flow handle.`, `Use the lowercase kebab-case handle of flows/<handle>.json, at most ${FLOW_HANDLE_LIMIT} characters.`);
|
|
2862
|
+
const action = values.get("action") ?? "navigate";
|
|
2863
|
+
if (!FLOW_ACTIONS.includes(action)) refuse(element, "invalid-attribute", `\`${action}\` is not a flow action.`, `Use one of ${FLOW_ACTIONS.join(", ")}.`);
|
|
2864
|
+
const screen = values.get("screen");
|
|
2865
|
+
if (screen !== void 0) {
|
|
2866
|
+
if (action === "data_exchange") refuse(element, "invalid-attribute", "A data_exchange flow has its endpoint choose the first screen, so it takes no `screen`.", "Remove screen, or use action=\"navigate\".");
|
|
2867
|
+
else if (!FLOW_SCREEN_PATTERN.test(screen) || screen === "SUCCESS") refuse(element, "invalid-attribute", `\`${screen}\` is not a flow screen id.`, "Use the screen's id: letters and underscores, at most 100 characters, and not SUCCESS.");
|
|
2868
|
+
}
|
|
2869
|
+
if (!valid || flow === void 0 || label_ === void 0) return void 0;
|
|
2870
|
+
return {
|
|
2871
|
+
type: "flow",
|
|
2872
|
+
text: label_,
|
|
2873
|
+
flow,
|
|
2874
|
+
action,
|
|
2875
|
+
...screen === void 0 ? {} : { screen }
|
|
2876
|
+
};
|
|
2877
|
+
};
|
|
2818
2878
|
const single = (element) => {
|
|
2819
2879
|
const tag = tagOf(element);
|
|
2820
2880
|
switch (tag.startsWith(`${local}.`) ? tag.slice(local.length + 1) : "") {
|
|
@@ -2851,6 +2911,7 @@ const buttonList = (input, ctx, local, parent) => {
|
|
|
2851
2911
|
code
|
|
2852
2912
|
};
|
|
2853
2913
|
}
|
|
2914
|
+
case "FlowButton": return flowButton(element);
|
|
2854
2915
|
default:
|
|
2855
2916
|
lowerer.error(ctx, element, "whatsapp-structure", `<WhatsApp.Buttons> holds ${BUTTON_TAGS.map((name) => `<WhatsApp.${name}>`).join(", ")}.`, "Use one of the button components.");
|
|
2856
2917
|
return;
|
|
@@ -167,6 +167,14 @@ type IrWhatsAppButton = {
|
|
|
167
167
|
} | {
|
|
168
168
|
readonly type: "copy-code";
|
|
169
169
|
readonly code: IrValue;
|
|
170
|
+
} | {
|
|
171
|
+
readonly type: "flow";
|
|
172
|
+
readonly text: IrValue;
|
|
173
|
+
/** The Samva flow handle, resolved to Meta's flow id when the template is registered. */
|
|
174
|
+
readonly flow: string;
|
|
175
|
+
readonly action: "navigate" | "data_exchange";
|
|
176
|
+
/** Meta's `navigate_screen`; absent opens the flow's first entry screen. */
|
|
177
|
+
readonly screen?: string;
|
|
170
178
|
};
|
|
171
179
|
/** A button, or buttons that appear when a condition over the input holds. */
|
|
172
180
|
type IrWhatsAppButtonNode = IrWhatsAppButton | {
|
|
@@ -243,6 +251,12 @@ type RenderedWhatsAppButton = {
|
|
|
243
251
|
} | {
|
|
244
252
|
readonly type: "copy-code";
|
|
245
253
|
readonly code: string;
|
|
254
|
+
} | {
|
|
255
|
+
readonly type: "flow";
|
|
256
|
+
readonly text: string;
|
|
257
|
+
readonly flow: string;
|
|
258
|
+
readonly action: "navigate" | "data_exchange";
|
|
259
|
+
readonly screen?: string;
|
|
246
260
|
};
|
|
247
261
|
/** A WhatsApp message with every text rendered and every conditional button resolved. */
|
|
248
262
|
interface RenderedIrWhatsApp {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { S as emailElement, o as serializeEmailHtml } from "./html-
|
|
1
|
+
import { S as emailElement, o as serializeEmailHtml } from "./html-BvRFepeO.js";
|
|
2
2
|
import { i as drainEmailDiagnostics, t as EmailNode } from "./jsx-runtime-CBlexoeR.js";
|
|
3
|
-
import { f as preheaderNode } from "./components-
|
|
4
|
-
import { i as renderDarkRules, r as applyStylesheet } from "./ir-
|
|
3
|
+
import { f as preheaderNode } from "./components-3XHjYD3i.js";
|
|
4
|
+
import { i as renderDarkRules, r as applyStylesheet } from "./ir-DNL6H7jA.js";
|
|
5
5
|
import { r as hasBlockingDiagnostic, t as EmailCompileError } from "./diagnostics-C0Tr8-eQ.js";
|
|
6
6
|
//#region src/email/text.ts
|
|
7
7
|
const PARAGRAPH = "\n\n";
|
package/dist/render.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { C as isUrlAttribute, S as emailElement, _ as EmailGlobalProps, b as EmailStyle, g as EmailElementSpec, h as EmailAttributeSpec, m as EMAIL_GLOBAL_ATTRIBUTES, p as EMAIL_ELEMENTS, r as EmailNode, v as EmailIntrinsicElements, x as EmailTag, y as EmailLayout } from "./jsx-runtime-DC7xG0aA.js";
|
|
2
|
-
import { $ as IrWhatsApp, A as emailAuthoringTarget, B as IrElement, D as EmittedPosition, E as EmailElementSelection, F as emailSelections, G as IrPredicate, H as IrFormatter, I as renderIrPreview, J as IrRawPart, K as IrPreheader, L as IrArithmetic, M as emailSelectionAncestry, N as emailSelectionIndex, O as INSTANCE_PATH_ATTRIBUTE, P as emailSelectionSiblings, Q as IrValue, R as IrComparison, U as IrIf, V as IrEmail, W as IrNode, X as IrSource, Y as IrSms, Z as IrStyle, a as renderEmailContent, at as RenderedPosition, c as checkAttribute, ct as TemplateIr, d as escapeText, et as IrWhatsAppButton, f as kebab, i as compileEmail, it as RenderedIrWhatsApp, j as emailDefinitionInstances, k as IrPreview, l as declarationValue, m as serializeStyle, n as EmailCompileOutput, nt as RenderOptions, o as EmailHtmlOutput, ot as RenderedWhatsAppButton, p as serializeEmailHtml, q as IrRaw, r as RenderedEmail, rt as RenderedIrEmail, s as SerializeEmailOptions, st as SML_IR_VERSION, t as EMAIL_DOCTYPE, tt as IrWhatsAppButtonNode, u as escapeAttribute, z as IrEach } from "./render-
|
|
2
|
+
import { $ as IrWhatsApp, A as emailAuthoringTarget, B as IrElement, D as EmittedPosition, E as EmailElementSelection, F as emailSelections, G as IrPredicate, H as IrFormatter, I as renderIrPreview, J as IrRawPart, K as IrPreheader, L as IrArithmetic, M as emailSelectionAncestry, N as emailSelectionIndex, O as INSTANCE_PATH_ATTRIBUTE, P as emailSelectionSiblings, Q as IrValue, R as IrComparison, U as IrIf, V as IrEmail, W as IrNode, X as IrSource, Y as IrSms, Z as IrStyle, a as renderEmailContent, at as RenderedPosition, c as checkAttribute, ct as TemplateIr, d as escapeText, et as IrWhatsAppButton, f as kebab, i as compileEmail, it as RenderedIrWhatsApp, j as emailDefinitionInstances, k as IrPreview, l as declarationValue, m as serializeStyle, n as EmailCompileOutput, nt as RenderOptions, o as EmailHtmlOutput, ot as RenderedWhatsAppButton, p as serializeEmailHtml, q as IrRaw, r as RenderedEmail, rt as RenderedIrEmail, s as SerializeEmailOptions, st as SML_IR_VERSION, t as EMAIL_DOCTYPE, tt as IrWhatsAppButtonNode, u as escapeAttribute, z as IrEach } from "./render-BdvewoQt.js";
|
|
3
3
|
//#region src/render-ir.d.ts
|
|
4
4
|
interface IrRenderDiagnostic {
|
|
5
5
|
readonly code: string;
|
package/dist/render.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { C as isUrlAttribute, S as emailElement, _ as renderIr, a as kebab, b as EMAIL_ELEMENTS, c as INSTANCE_PATH_ATTRIBUTE, d as emailSelectionAncestry, f as emailSelectionIndex, g as IrRenderError, h as renderIrPreview, i as escapeText, l as emailAuthoringTarget, m as emailSelections, n as declarationValue, o as serializeEmailHtml, p as emailSelectionSiblings, r as escapeAttribute, s as serializeStyle, t as checkAttribute, u as emailDefinitionInstances, v as renderIrSms, x as EMAIL_GLOBAL_ATTRIBUTES, y as renderIrWhatsApp } from "./html-
|
|
2
|
-
import { t as SML_IR_VERSION } from "./ir-
|
|
3
|
-
import { i as deriveEmailText, n as compileEmail, r as renderEmailContent, t as EMAIL_DOCTYPE } from "./render-
|
|
1
|
+
import { C as isUrlAttribute, S as emailElement, _ as renderIr, a as kebab, b as EMAIL_ELEMENTS, c as INSTANCE_PATH_ATTRIBUTE, d as emailSelectionAncestry, f as emailSelectionIndex, g as IrRenderError, h as renderIrPreview, i as escapeText, l as emailAuthoringTarget, m as emailSelections, n as declarationValue, o as serializeEmailHtml, p as emailSelectionSiblings, r as escapeAttribute, s as serializeStyle, t as checkAttribute, u as emailDefinitionInstances, v as renderIrSms, x as EMAIL_GLOBAL_ATTRIBUTES, y as renderIrWhatsApp } from "./html-BvRFepeO.js";
|
|
2
|
+
import { t as SML_IR_VERSION } from "./ir-DNL6H7jA.js";
|
|
3
|
+
import { i as deriveEmailText, n as compileEmail, r as renderEmailContent, t as EMAIL_DOCTYPE } from "./render-DpYouYYG.js";
|
|
4
4
|
//#region src/sms-segments.ts
|
|
5
5
|
const GSM7_EXTENDED = /* @__PURE__ */ new Set([
|
|
6
6
|
"[",
|
package/dist/whatsapp.d.ts
CHANGED
|
@@ -36,9 +36,26 @@ interface WhatsAppCopyCodeButtonProps {
|
|
|
36
36
|
/** The code the button copies. */
|
|
37
37
|
readonly children?: EmailChild;
|
|
38
38
|
}
|
|
39
|
+
type WhatsAppFlowAction = "navigate" | "data_exchange";
|
|
40
|
+
interface WhatsAppFlowButtonProps {
|
|
41
|
+
/**
|
|
42
|
+
* The Samva flow handle: lowercase kebab-case, the file `flows/<handle>.json` in the template
|
|
43
|
+
* project. Samva maps it to Meta's flow id per business account when it registers the template.
|
|
44
|
+
*/
|
|
45
|
+
readonly flow: string;
|
|
46
|
+
/** `navigate` by default; `data_exchange` has the flow's endpoint choose the first screen. */
|
|
47
|
+
readonly action?: WhatsAppFlowAction | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* The screen a `navigate` flow opens on (Meta's `navigate_screen`); omitted, the flow's first
|
|
50
|
+
* entry screen. Refused with `data_exchange`.
|
|
51
|
+
*/
|
|
52
|
+
readonly screen?: string | undefined;
|
|
53
|
+
/** The button label. */
|
|
54
|
+
readonly children?: EmailChild;
|
|
55
|
+
}
|
|
39
56
|
/**
|
|
40
57
|
* The root of a WhatsApp body. Meta's template anatomy is the vocabulary: `WhatsApp.Header`,
|
|
41
|
-
* `WhatsApp.Body`, `WhatsApp.Footer` and `WhatsApp.Buttons`, whose children are the
|
|
58
|
+
* `WhatsApp.Body`, `WhatsApp.Footer` and `WhatsApp.Buttons`, whose children are the five button
|
|
42
59
|
* kinds.
|
|
43
60
|
*/
|
|
44
61
|
declare const WhatsApp: ((props: WhatsAppProps) => EmailNode) & {
|
|
@@ -50,6 +67,7 @@ declare const WhatsApp: ((props: WhatsAppProps) => EmailNode) & {
|
|
|
50
67
|
QuickReplyButton: (props: WhatsAppQuickReplyButtonProps) => EmailNode;
|
|
51
68
|
PhoneButton: (props: WhatsAppPhoneButtonProps) => EmailNode;
|
|
52
69
|
CopyCodeButton: (props: WhatsAppCopyCodeButtonProps) => EmailNode;
|
|
70
|
+
FlowButton: (props: WhatsAppFlowButtonProps) => EmailNode;
|
|
53
71
|
};
|
|
54
72
|
//#endregion
|
|
55
|
-
export { WhatsApp, WhatsAppButtonsProps, type WhatsAppCategory, WhatsAppCopyCodeButtonProps, WhatsAppHeaderProps, type WhatsAppHeaderType, WhatsAppPhoneButtonProps, WhatsAppProps, WhatsAppQuickReplyButtonProps, WhatsAppTextProps, WhatsAppUrlButtonProps };
|
|
73
|
+
export { WhatsApp, WhatsAppButtonsProps, type WhatsAppCategory, WhatsAppCopyCodeButtonProps, WhatsAppFlowAction, WhatsAppFlowButtonProps, WhatsAppHeaderProps, type WhatsAppHeaderType, WhatsAppPhoneButtonProps, WhatsAppProps, WhatsAppQuickReplyButtonProps, WhatsAppTextProps, WhatsAppUrlButtonProps };
|
package/dist/whatsapp.js
CHANGED
|
@@ -3,7 +3,7 @@ import { a as jsx, n as Fragment } from "./jsx-runtime-CBlexoeR.js";
|
|
|
3
3
|
const fragment = (children) => jsx(Fragment, { children });
|
|
4
4
|
/**
|
|
5
5
|
* The root of a WhatsApp body. Meta's template anatomy is the vocabulary: `WhatsApp.Header`,
|
|
6
|
-
* `WhatsApp.Body`, `WhatsApp.Footer` and `WhatsApp.Buttons`, whose children are the
|
|
6
|
+
* `WhatsApp.Body`, `WhatsApp.Footer` and `WhatsApp.Buttons`, whose children are the five button
|
|
7
7
|
* kinds.
|
|
8
8
|
*/
|
|
9
9
|
const WhatsApp = Object.assign((props) => fragment(props.children), {
|
|
@@ -14,7 +14,8 @@ const WhatsApp = Object.assign((props) => fragment(props.children), {
|
|
|
14
14
|
UrlButton: (props) => fragment(props.children),
|
|
15
15
|
QuickReplyButton: (props) => fragment(props.children),
|
|
16
16
|
PhoneButton: (props) => fragment(props.children),
|
|
17
|
-
CopyCodeButton: (props) => fragment(props.children)
|
|
17
|
+
CopyCodeButton: (props) => fragment(props.children),
|
|
18
|
+
FlowButton: (props) => fragment(props.children)
|
|
18
19
|
});
|
|
19
20
|
//#endregion
|
|
20
21
|
export { WhatsApp };
|
package/generated/contract.json
CHANGED
|
@@ -720,6 +720,36 @@
|
|
|
720
720
|
"description": "The code the button copies."
|
|
721
721
|
}
|
|
722
722
|
]
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
"name": "WhatsApp.FlowButton",
|
|
726
|
+
"import": "@samva/markup/whatsapp",
|
|
727
|
+
"props": [
|
|
728
|
+
{
|
|
729
|
+
"name": "flow",
|
|
730
|
+
"type": "string",
|
|
731
|
+
"optional": false,
|
|
732
|
+
"description": "The Samva flow handle: lowercase kebab-case, the file `flows/<handle>.json` in the template project. Samva maps it to Meta's flow id per business account when it registers the template."
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
"name": "action",
|
|
736
|
+
"type": "WhatsAppFlowAction",
|
|
737
|
+
"optional": true,
|
|
738
|
+
"description": "`navigate` by default; `data_exchange` has the flow's endpoint choose the first screen."
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
"name": "screen",
|
|
742
|
+
"type": "string",
|
|
743
|
+
"optional": true,
|
|
744
|
+
"description": "The screen a `navigate` flow opens on (Meta's `navigate_screen`); omitted, the flow's first entry screen. Refused with `data_exchange`."
|
|
745
|
+
},
|
|
746
|
+
{
|
|
747
|
+
"name": "children",
|
|
748
|
+
"type": "EmailChild",
|
|
749
|
+
"optional": true,
|
|
750
|
+
"description": "The button label."
|
|
751
|
+
}
|
|
752
|
+
]
|
|
723
753
|
}
|
|
724
754
|
],
|
|
725
755
|
"elements": {
|