@rebuy/rebuy 3.10.0 → 3.12.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/dist/index.cjs +33 -0
- package/dist/index.mjs +33 -0
- package/dist/schema/cabShopConfig.d.ts +1 -0
- package/dist/schema/checkout-and-beyond/index.cjs +19 -0
- package/dist/schema/checkout-and-beyond/index.mjs +19 -0
- package/dist/schema/smartCart.d.ts +4 -0
- package/dist/schema/user-config.cjs +14 -0
- package/dist/schema/user-config.mjs +14 -0
- package/dist/schema/userConfig.d.ts +1 -0
- package/dist/schema/widget-data.cjs +19 -0
- package/dist/schema/widget-data.mjs +19 -0
- package/dist/schema/widgets/checkout-and-beyond/progressBar.d.ts +2 -0
- package/dist/server/composeOffer.d.ts +2 -2
- package/dist/server/index.cjs +66 -12
- package/dist/server/index.mjs +66 -12
- package/dist/transforms/htmlToTiptap/decodeEntities.d.ts +19 -1
- package/dist/transforms/index.cjs +45 -9
- package/dist/transforms/index.mjs +45 -9
- package/dist/transforms/normalizeTextContent.d.ts +33 -6
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1058,6 +1058,20 @@ var SmartCartTier = import_zod.z.looseObject({
|
|
|
1058
1058
|
productAmountRemainingLabel: import_zod.z.boolean().optional(),
|
|
1059
1059
|
productAmountRemainingLabelText: import_zod.z.string().optional(),
|
|
1060
1060
|
products: import_zod.z.array(SmartCartTierProduct).optional(),
|
|
1061
|
+
/**
|
|
1062
|
+
* onsite `product_tier_element_size`: the step-row icon's square size as a px STRING (`'18px'` …
|
|
1063
|
+
* `'36px'` — the admin's five-value Size control, default `'18px'`). React stamps it onto the icon
|
|
1064
|
+
* SVG's `width`/`height` (`getProgressStepIconSize`), so the milestone renders at the merchant's
|
|
1065
|
+
* chosen size; the converter turns it into `CABProgressTier.iconSizePx` for the CAB renderer.
|
|
1066
|
+
*
|
|
1067
|
+
* `.catch(undefined)` for the same blast-radius reason as `productTierImage` below: this key was
|
|
1068
|
+
* previously unknown-and-ignored by the `looseObject`, and `SmartCartConfig` is parsed
|
|
1069
|
+
* `.nullish().catch(undefined)` with no intermediate catch, so one drifted value (a bare number, a
|
|
1070
|
+
* `'2rem'`) would drop EVERY progress bar for the shop rather than just this setting. Failing open to
|
|
1071
|
+
* `undefined` lands on the documented 18px default — strictly safer than that blast radius. The
|
|
1072
|
+
* converter parses the number out and clamps it, so a junk-but-string value degrades the same way.
|
|
1073
|
+
*/
|
|
1074
|
+
productTierElementSize: import_zod.z.string().optional().catch(void 0),
|
|
1061
1075
|
/**
|
|
1062
1076
|
* onsite `product_tier_image`: on a product (gift) tier, show the gift's product image in the step-row
|
|
1063
1077
|
* slot instead of the generic bag icon (React `TierProgressIcon` swaps in the image when this is set and
|
|
@@ -2703,6 +2717,25 @@ var CABProgressTier = import_zod24.z.object({
|
|
|
2703
2717
|
title: import_zod24.z.string().optional(),
|
|
2704
2718
|
variantIds: import_zod24.z.array(import_zod24.z.number().int().positive()).default(() => [])
|
|
2705
2719
|
}).optional(),
|
|
2720
|
+
/**
|
|
2721
|
+
* The step-row icon's square size in PIXELS (onsite `product_tier_element_size`, an admin px string the
|
|
2722
|
+
* converter parses): 18 (default) / 20 / 24 / 30 / 36. React sizes the milestone by stamping this onto
|
|
2723
|
+
* the icon SVG's `width`/`height` (`getProgressStepIconSize`), so a merchant who deliberately enlarged
|
|
2724
|
+
* their milestones keeps that at checkout instead of every icon collapsing to one hardcoded square.
|
|
2725
|
+
*
|
|
2726
|
+
* A NUMBER, not the raw `'18px'` string: the renderer needs it as a length for the icon slot's grid
|
|
2727
|
+
* track, and a px-suffixed string would push unit parsing into every consumer. Deliberately NOT named
|
|
2728
|
+
* `iconSize` — the `iconSize` on an `image` section is the `s-icon` token enum, and one name covering both a
|
|
2729
|
+
* token and a pixel count is the kind of ambiguity that gets one passed where the other is meant.
|
|
2730
|
+
*
|
|
2731
|
+
* Bounded (not merely positive) because it is a raw merchant-controlled length: the step row is a grid
|
|
2732
|
+
* of per-tier cells, and an out-of-domain value would stretch the row rather than fail loudly. The
|
|
2733
|
+
* converter clamps to this same ceiling, so drifted config lands on a big-but-sane icon and never on a
|
|
2734
|
+
* tier the strict parse drops. `.default(18)` holds React's default at parse time (schema-first — the
|
|
2735
|
+
* renderer never spells a fallback), which also makes a tree authored before this field render at
|
|
2736
|
+
* React's size rather than the old hardcoded 20.
|
|
2737
|
+
*/
|
|
2738
|
+
iconSizePx: import_zod24.z.number().int().min(1).max(64).default(18),
|
|
2706
2739
|
/**
|
|
2707
2740
|
* The step-row label at this tier's slot (onsite `getTierLabel`): the merchant custom label, else the
|
|
2708
2741
|
* type default — 'Free Shipping' or '{n}% Discount'. A PRODUCT tier gets NO converter label: its name is
|
package/dist/index.mjs
CHANGED
|
@@ -772,6 +772,20 @@ var SmartCartTier = z.looseObject({
|
|
|
772
772
|
productAmountRemainingLabel: z.boolean().optional(),
|
|
773
773
|
productAmountRemainingLabelText: z.string().optional(),
|
|
774
774
|
products: z.array(SmartCartTierProduct).optional(),
|
|
775
|
+
/**
|
|
776
|
+
* onsite `product_tier_element_size`: the step-row icon's square size as a px STRING (`'18px'` …
|
|
777
|
+
* `'36px'` — the admin's five-value Size control, default `'18px'`). React stamps it onto the icon
|
|
778
|
+
* SVG's `width`/`height` (`getProgressStepIconSize`), so the milestone renders at the merchant's
|
|
779
|
+
* chosen size; the converter turns it into `CABProgressTier.iconSizePx` for the CAB renderer.
|
|
780
|
+
*
|
|
781
|
+
* `.catch(undefined)` for the same blast-radius reason as `productTierImage` below: this key was
|
|
782
|
+
* previously unknown-and-ignored by the `looseObject`, and `SmartCartConfig` is parsed
|
|
783
|
+
* `.nullish().catch(undefined)` with no intermediate catch, so one drifted value (a bare number, a
|
|
784
|
+
* `'2rem'`) would drop EVERY progress bar for the shop rather than just this setting. Failing open to
|
|
785
|
+
* `undefined` lands on the documented 18px default — strictly safer than that blast radius. The
|
|
786
|
+
* converter parses the number out and clamps it, so a junk-but-string value degrades the same way.
|
|
787
|
+
*/
|
|
788
|
+
productTierElementSize: z.string().optional().catch(void 0),
|
|
775
789
|
/**
|
|
776
790
|
* onsite `product_tier_image`: on a product (gift) tier, show the gift's product image in the step-row
|
|
777
791
|
* slot instead of the generic bag icon (React `TierProgressIcon` swaps in the image when this is set and
|
|
@@ -2417,6 +2431,25 @@ var CABProgressTier = z24.object({
|
|
|
2417
2431
|
title: z24.string().optional(),
|
|
2418
2432
|
variantIds: z24.array(z24.number().int().positive()).default(() => [])
|
|
2419
2433
|
}).optional(),
|
|
2434
|
+
/**
|
|
2435
|
+
* The step-row icon's square size in PIXELS (onsite `product_tier_element_size`, an admin px string the
|
|
2436
|
+
* converter parses): 18 (default) / 20 / 24 / 30 / 36. React sizes the milestone by stamping this onto
|
|
2437
|
+
* the icon SVG's `width`/`height` (`getProgressStepIconSize`), so a merchant who deliberately enlarged
|
|
2438
|
+
* their milestones keeps that at checkout instead of every icon collapsing to one hardcoded square.
|
|
2439
|
+
*
|
|
2440
|
+
* A NUMBER, not the raw `'18px'` string: the renderer needs it as a length for the icon slot's grid
|
|
2441
|
+
* track, and a px-suffixed string would push unit parsing into every consumer. Deliberately NOT named
|
|
2442
|
+
* `iconSize` — the `iconSize` on an `image` section is the `s-icon` token enum, and one name covering both a
|
|
2443
|
+
* token and a pixel count is the kind of ambiguity that gets one passed where the other is meant.
|
|
2444
|
+
*
|
|
2445
|
+
* Bounded (not merely positive) because it is a raw merchant-controlled length: the step row is a grid
|
|
2446
|
+
* of per-tier cells, and an out-of-domain value would stretch the row rather than fail loudly. The
|
|
2447
|
+
* converter clamps to this same ceiling, so drifted config lands on a big-but-sane icon and never on a
|
|
2448
|
+
* tier the strict parse drops. `.default(18)` holds React's default at parse time (schema-first — the
|
|
2449
|
+
* renderer never spells a fallback), which also makes a tree authored before this field render at
|
|
2450
|
+
* React's size rather than the old hardcoded 20.
|
|
2451
|
+
*/
|
|
2452
|
+
iconSizePx: z24.number().int().min(1).max(64).default(18),
|
|
2420
2453
|
/**
|
|
2421
2454
|
* The step-row label at this tier's slot (onsite `getTierLabel`): the merchant custom label, else the
|
|
2422
2455
|
* type default — 'Free Shipping' or '{n}% Discount'. A PRODUCT tier gets NO converter label: its name is
|
|
@@ -144,6 +144,7 @@ export declare const CabUserConfig: z.ZodObject<{
|
|
|
144
144
|
title: z.ZodOptional<z.ZodString>;
|
|
145
145
|
variantIds: z.ZodOptional<z.ZodArray<z.ZodCoercedNumber<unknown>>>;
|
|
146
146
|
}, z.core.$loose>>>;
|
|
147
|
+
productTierElementSize: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
147
148
|
productTierImage: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
|
|
148
149
|
type: z.ZodOptional<z.ZodString>;
|
|
149
150
|
unlockConditionType: z.ZodOptional<z.ZodString>;
|
|
@@ -1272,6 +1272,25 @@ var CABProgressTier = import_zod14.z.object({
|
|
|
1272
1272
|
title: import_zod14.z.string().optional(),
|
|
1273
1273
|
variantIds: import_zod14.z.array(import_zod14.z.number().int().positive()).default(() => [])
|
|
1274
1274
|
}).optional(),
|
|
1275
|
+
/**
|
|
1276
|
+
* The step-row icon's square size in PIXELS (onsite `product_tier_element_size`, an admin px string the
|
|
1277
|
+
* converter parses): 18 (default) / 20 / 24 / 30 / 36. React sizes the milestone by stamping this onto
|
|
1278
|
+
* the icon SVG's `width`/`height` (`getProgressStepIconSize`), so a merchant who deliberately enlarged
|
|
1279
|
+
* their milestones keeps that at checkout instead of every icon collapsing to one hardcoded square.
|
|
1280
|
+
*
|
|
1281
|
+
* A NUMBER, not the raw `'18px'` string: the renderer needs it as a length for the icon slot's grid
|
|
1282
|
+
* track, and a px-suffixed string would push unit parsing into every consumer. Deliberately NOT named
|
|
1283
|
+
* `iconSize` — the `iconSize` on an `image` section is the `s-icon` token enum, and one name covering both a
|
|
1284
|
+
* token and a pixel count is the kind of ambiguity that gets one passed where the other is meant.
|
|
1285
|
+
*
|
|
1286
|
+
* Bounded (not merely positive) because it is a raw merchant-controlled length: the step row is a grid
|
|
1287
|
+
* of per-tier cells, and an out-of-domain value would stretch the row rather than fail loudly. The
|
|
1288
|
+
* converter clamps to this same ceiling, so drifted config lands on a big-but-sane icon and never on a
|
|
1289
|
+
* tier the strict parse drops. `.default(18)` holds React's default at parse time (schema-first — the
|
|
1290
|
+
* renderer never spells a fallback), which also makes a tree authored before this field render at
|
|
1291
|
+
* React's size rather than the old hardcoded 20.
|
|
1292
|
+
*/
|
|
1293
|
+
iconSizePx: import_zod14.z.number().int().min(1).max(64).default(18),
|
|
1275
1294
|
/**
|
|
1276
1295
|
* The step-row label at this tier's slot (onsite `getTierLabel`): the merchant custom label, else the
|
|
1277
1296
|
* type default — 'Free Shipping' or '{n}% Discount'. A PRODUCT tier gets NO converter label: its name is
|
|
@@ -1075,6 +1075,25 @@ var CABProgressTier = z14.object({
|
|
|
1075
1075
|
title: z14.string().optional(),
|
|
1076
1076
|
variantIds: z14.array(z14.number().int().positive()).default(() => [])
|
|
1077
1077
|
}).optional(),
|
|
1078
|
+
/**
|
|
1079
|
+
* The step-row icon's square size in PIXELS (onsite `product_tier_element_size`, an admin px string the
|
|
1080
|
+
* converter parses): 18 (default) / 20 / 24 / 30 / 36. React sizes the milestone by stamping this onto
|
|
1081
|
+
* the icon SVG's `width`/`height` (`getProgressStepIconSize`), so a merchant who deliberately enlarged
|
|
1082
|
+
* their milestones keeps that at checkout instead of every icon collapsing to one hardcoded square.
|
|
1083
|
+
*
|
|
1084
|
+
* A NUMBER, not the raw `'18px'` string: the renderer needs it as a length for the icon slot's grid
|
|
1085
|
+
* track, and a px-suffixed string would push unit parsing into every consumer. Deliberately NOT named
|
|
1086
|
+
* `iconSize` — the `iconSize` on an `image` section is the `s-icon` token enum, and one name covering both a
|
|
1087
|
+
* token and a pixel count is the kind of ambiguity that gets one passed where the other is meant.
|
|
1088
|
+
*
|
|
1089
|
+
* Bounded (not merely positive) because it is a raw merchant-controlled length: the step row is a grid
|
|
1090
|
+
* of per-tier cells, and an out-of-domain value would stretch the row rather than fail loudly. The
|
|
1091
|
+
* converter clamps to this same ceiling, so drifted config lands on a big-but-sane icon and never on a
|
|
1092
|
+
* tier the strict parse drops. `.default(18)` holds React's default at parse time (schema-first — the
|
|
1093
|
+
* renderer never spells a fallback), which also makes a tree authored before this field render at
|
|
1094
|
+
* React's size rather than the old hardcoded 20.
|
|
1095
|
+
*/
|
|
1096
|
+
iconSizePx: z14.number().int().min(1).max(64).default(18),
|
|
1078
1097
|
/**
|
|
1079
1098
|
* The step-row label at this tier's slot (onsite `getTierLabel`): the merchant custom label, else the
|
|
1080
1099
|
* type default — 'Free Shipping' or '{n}% Discount'. A PRODUCT tier gets NO converter label: its name is
|
|
@@ -59,6 +59,7 @@ export declare const SmartCartTier: z.ZodObject<{
|
|
|
59
59
|
title: z.ZodOptional<z.ZodString>;
|
|
60
60
|
variantIds: z.ZodOptional<z.ZodArray<z.ZodCoercedNumber<unknown>>>;
|
|
61
61
|
}, z.core.$loose>>>;
|
|
62
|
+
productTierElementSize: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
62
63
|
productTierImage: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
|
|
63
64
|
type: z.ZodOptional<z.ZodString>;
|
|
64
65
|
unlockConditionType: z.ZodOptional<z.ZodString>;
|
|
@@ -120,6 +121,7 @@ export declare const SmartCartBar: z.ZodObject<{
|
|
|
120
121
|
title: z.ZodOptional<z.ZodString>;
|
|
121
122
|
variantIds: z.ZodOptional<z.ZodArray<z.ZodCoercedNumber<unknown>>>;
|
|
122
123
|
}, z.core.$loose>>>;
|
|
124
|
+
productTierElementSize: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
123
125
|
productTierImage: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
|
|
124
126
|
type: z.ZodOptional<z.ZodString>;
|
|
125
127
|
unlockConditionType: z.ZodOptional<z.ZodString>;
|
|
@@ -180,6 +182,7 @@ export declare const SmartCartComponent: z.ZodObject<{
|
|
|
180
182
|
title: z.ZodOptional<z.ZodString>;
|
|
181
183
|
variantIds: z.ZodOptional<z.ZodArray<z.ZodCoercedNumber<unknown>>>;
|
|
182
184
|
}, z.core.$loose>>>;
|
|
185
|
+
productTierElementSize: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
183
186
|
productTierImage: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
|
|
184
187
|
type: z.ZodOptional<z.ZodString>;
|
|
185
188
|
unlockConditionType: z.ZodOptional<z.ZodString>;
|
|
@@ -251,6 +254,7 @@ export declare const SmartCartConfig: z.ZodObject<{
|
|
|
251
254
|
title: z.ZodOptional<z.ZodString>;
|
|
252
255
|
variantIds: z.ZodOptional<z.ZodArray<z.ZodCoercedNumber<unknown>>>;
|
|
253
256
|
}, z.core.$loose>>>;
|
|
257
|
+
productTierElementSize: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
254
258
|
productTierImage: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
|
|
255
259
|
type: z.ZodOptional<z.ZodString>;
|
|
256
260
|
unlockConditionType: z.ZodOptional<z.ZodString>;
|
|
@@ -151,6 +151,20 @@ var SmartCartTier = import_zod2.z.looseObject({
|
|
|
151
151
|
productAmountRemainingLabel: import_zod2.z.boolean().optional(),
|
|
152
152
|
productAmountRemainingLabelText: import_zod2.z.string().optional(),
|
|
153
153
|
products: import_zod2.z.array(SmartCartTierProduct).optional(),
|
|
154
|
+
/**
|
|
155
|
+
* onsite `product_tier_element_size`: the step-row icon's square size as a px STRING (`'18px'` …
|
|
156
|
+
* `'36px'` — the admin's five-value Size control, default `'18px'`). React stamps it onto the icon
|
|
157
|
+
* SVG's `width`/`height` (`getProgressStepIconSize`), so the milestone renders at the merchant's
|
|
158
|
+
* chosen size; the converter turns it into `CABProgressTier.iconSizePx` for the CAB renderer.
|
|
159
|
+
*
|
|
160
|
+
* `.catch(undefined)` for the same blast-radius reason as `productTierImage` below: this key was
|
|
161
|
+
* previously unknown-and-ignored by the `looseObject`, and `SmartCartConfig` is parsed
|
|
162
|
+
* `.nullish().catch(undefined)` with no intermediate catch, so one drifted value (a bare number, a
|
|
163
|
+
* `'2rem'`) would drop EVERY progress bar for the shop rather than just this setting. Failing open to
|
|
164
|
+
* `undefined` lands on the documented 18px default — strictly safer than that blast radius. The
|
|
165
|
+
* converter parses the number out and clamps it, so a junk-but-string value degrades the same way.
|
|
166
|
+
*/
|
|
167
|
+
productTierElementSize: import_zod2.z.string().optional().catch(void 0),
|
|
154
168
|
/**
|
|
155
169
|
* onsite `product_tier_image`: on a product (gift) tier, show the gift's product image in the step-row
|
|
156
170
|
* slot instead of the generic bag icon (React `TierProgressIcon` swaps in the image when this is set and
|
|
@@ -127,6 +127,20 @@ var SmartCartTier = z2.looseObject({
|
|
|
127
127
|
productAmountRemainingLabel: z2.boolean().optional(),
|
|
128
128
|
productAmountRemainingLabelText: z2.string().optional(),
|
|
129
129
|
products: z2.array(SmartCartTierProduct).optional(),
|
|
130
|
+
/**
|
|
131
|
+
* onsite `product_tier_element_size`: the step-row icon's square size as a px STRING (`'18px'` …
|
|
132
|
+
* `'36px'` — the admin's five-value Size control, default `'18px'`). React stamps it onto the icon
|
|
133
|
+
* SVG's `width`/`height` (`getProgressStepIconSize`), so the milestone renders at the merchant's
|
|
134
|
+
* chosen size; the converter turns it into `CABProgressTier.iconSizePx` for the CAB renderer.
|
|
135
|
+
*
|
|
136
|
+
* `.catch(undefined)` for the same blast-radius reason as `productTierImage` below: this key was
|
|
137
|
+
* previously unknown-and-ignored by the `looseObject`, and `SmartCartConfig` is parsed
|
|
138
|
+
* `.nullish().catch(undefined)` with no intermediate catch, so one drifted value (a bare number, a
|
|
139
|
+
* `'2rem'`) would drop EVERY progress bar for the shop rather than just this setting. Failing open to
|
|
140
|
+
* `undefined` lands on the documented 18px default — strictly safer than that blast radius. The
|
|
141
|
+
* converter parses the number out and clamps it, so a junk-but-string value degrades the same way.
|
|
142
|
+
*/
|
|
143
|
+
productTierElementSize: z2.string().optional().catch(void 0),
|
|
130
144
|
/**
|
|
131
145
|
* onsite `product_tier_image`: on a product (gift) tier, show the gift's product image in the step-row
|
|
132
146
|
* slot instead of the generic bag icon (React `TierProgressIcon` swaps in the image when this is set and
|
|
@@ -133,6 +133,7 @@ export declare const UserConfig: z.ZodObject<{
|
|
|
133
133
|
title: z.ZodOptional<z.ZodString>;
|
|
134
134
|
variantIds: z.ZodOptional<z.ZodArray<z.ZodCoercedNumber<unknown>>>;
|
|
135
135
|
}, z.core.$loose>>>;
|
|
136
|
+
productTierElementSize: z.ZodCatch<z.ZodOptional<z.ZodString>>;
|
|
136
137
|
productTierImage: z.ZodCatch<z.ZodOptional<z.ZodBoolean>>;
|
|
137
138
|
type: z.ZodOptional<z.ZodString>;
|
|
138
139
|
unlockConditionType: z.ZodOptional<z.ZodString>;
|
|
@@ -1113,6 +1113,25 @@ var CABProgressTier = import_zod17.z.object({
|
|
|
1113
1113
|
title: import_zod17.z.string().optional(),
|
|
1114
1114
|
variantIds: import_zod17.z.array(import_zod17.z.number().int().positive()).default(() => [])
|
|
1115
1115
|
}).optional(),
|
|
1116
|
+
/**
|
|
1117
|
+
* The step-row icon's square size in PIXELS (onsite `product_tier_element_size`, an admin px string the
|
|
1118
|
+
* converter parses): 18 (default) / 20 / 24 / 30 / 36. React sizes the milestone by stamping this onto
|
|
1119
|
+
* the icon SVG's `width`/`height` (`getProgressStepIconSize`), so a merchant who deliberately enlarged
|
|
1120
|
+
* their milestones keeps that at checkout instead of every icon collapsing to one hardcoded square.
|
|
1121
|
+
*
|
|
1122
|
+
* A NUMBER, not the raw `'18px'` string: the renderer needs it as a length for the icon slot's grid
|
|
1123
|
+
* track, and a px-suffixed string would push unit parsing into every consumer. Deliberately NOT named
|
|
1124
|
+
* `iconSize` — the `iconSize` on an `image` section is the `s-icon` token enum, and one name covering both a
|
|
1125
|
+
* token and a pixel count is the kind of ambiguity that gets one passed where the other is meant.
|
|
1126
|
+
*
|
|
1127
|
+
* Bounded (not merely positive) because it is a raw merchant-controlled length: the step row is a grid
|
|
1128
|
+
* of per-tier cells, and an out-of-domain value would stretch the row rather than fail loudly. The
|
|
1129
|
+
* converter clamps to this same ceiling, so drifted config lands on a big-but-sane icon and never on a
|
|
1130
|
+
* tier the strict parse drops. `.default(18)` holds React's default at parse time (schema-first — the
|
|
1131
|
+
* renderer never spells a fallback), which also makes a tree authored before this field render at
|
|
1132
|
+
* React's size rather than the old hardcoded 20.
|
|
1133
|
+
*/
|
|
1134
|
+
iconSizePx: import_zod17.z.number().int().min(1).max(64).default(18),
|
|
1116
1135
|
/**
|
|
1117
1136
|
* The step-row label at this tier's slot (onsite `getTierLabel`): the merchant custom label, else the
|
|
1118
1137
|
* type default — 'Free Shipping' or '{n}% Discount'. A PRODUCT tier gets NO converter label: its name is
|
|
@@ -1088,6 +1088,25 @@ var CABProgressTier = z17.object({
|
|
|
1088
1088
|
title: z17.string().optional(),
|
|
1089
1089
|
variantIds: z17.array(z17.number().int().positive()).default(() => [])
|
|
1090
1090
|
}).optional(),
|
|
1091
|
+
/**
|
|
1092
|
+
* The step-row icon's square size in PIXELS (onsite `product_tier_element_size`, an admin px string the
|
|
1093
|
+
* converter parses): 18 (default) / 20 / 24 / 30 / 36. React sizes the milestone by stamping this onto
|
|
1094
|
+
* the icon SVG's `width`/`height` (`getProgressStepIconSize`), so a merchant who deliberately enlarged
|
|
1095
|
+
* their milestones keeps that at checkout instead of every icon collapsing to one hardcoded square.
|
|
1096
|
+
*
|
|
1097
|
+
* A NUMBER, not the raw `'18px'` string: the renderer needs it as a length for the icon slot's grid
|
|
1098
|
+
* track, and a px-suffixed string would push unit parsing into every consumer. Deliberately NOT named
|
|
1099
|
+
* `iconSize` — the `iconSize` on an `image` section is the `s-icon` token enum, and one name covering both a
|
|
1100
|
+
* token and a pixel count is the kind of ambiguity that gets one passed where the other is meant.
|
|
1101
|
+
*
|
|
1102
|
+
* Bounded (not merely positive) because it is a raw merchant-controlled length: the step row is a grid
|
|
1103
|
+
* of per-tier cells, and an out-of-domain value would stretch the row rather than fail loudly. The
|
|
1104
|
+
* converter clamps to this same ceiling, so drifted config lands on a big-but-sane icon and never on a
|
|
1105
|
+
* tier the strict parse drops. `.default(18)` holds React's default at parse time (schema-first — the
|
|
1106
|
+
* renderer never spells a fallback), which also makes a tree authored before this field render at
|
|
1107
|
+
* React's size rather than the old hardcoded 20.
|
|
1108
|
+
*/
|
|
1109
|
+
iconSizePx: z17.number().int().min(1).max(64).default(18),
|
|
1091
1110
|
/**
|
|
1092
1111
|
* The step-row label at this tier's slot (onsite `getTierLabel`): the merchant custom label, else the
|
|
1093
1112
|
* type default — 'Free Shipping' or '{n}% Discount'. A PRODUCT tier gets NO converter label: its name is
|
|
@@ -15,6 +15,7 @@ export declare const CABProgressTier: z.ZodObject<{
|
|
|
15
15
|
title: z.ZodOptional<z.ZodString>;
|
|
16
16
|
variantIds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
|
|
17
17
|
}, z.core.$strip>>;
|
|
18
|
+
iconSizePx: z.ZodDefault<z.ZodNumber>;
|
|
18
19
|
label: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
19
20
|
productTierImage: z.ZodDefault<z.ZodBoolean>;
|
|
20
21
|
threshold: z.ZodNumber;
|
|
@@ -334,6 +335,7 @@ export declare const CABProgressBarSection: z.ZodLazy<z.ZodObject<{
|
|
|
334
335
|
title: z.ZodOptional<z.ZodString>;
|
|
335
336
|
variantIds: z.ZodDefault<z.ZodArray<z.ZodNumber>>;
|
|
336
337
|
}, z.core.$strip>>;
|
|
338
|
+
iconSizePx: z.ZodDefault<z.ZodNumber>;
|
|
337
339
|
label: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
338
340
|
productTierImage: z.ZodDefault<z.ZodBoolean>;
|
|
339
341
|
threshold: z.ZodNumber;
|
|
@@ -3,8 +3,8 @@ import { type AdsEngineOffer } from '../server/adsEngine';
|
|
|
3
3
|
import { type MonetizeInput } from '../server/shared';
|
|
4
4
|
/**
|
|
5
5
|
* Compose one ads-engine offer into a renderable CAB card: a bordered `layout` whose `viewUrl` is the
|
|
6
|
-
* impression pixel, holding the header chrome, the copy (headline + HTML body,
|
|
7
|
-
*
|
|
6
|
+
* impression pixel, holding the header chrome, the copy (headline + HTML body, pre-converted to a
|
|
7
|
+
* small-sized Tiptap doc by {@link smallText}) beside the optional image (75%/fill — the React desktop split),
|
|
8
8
|
* the two CTAs in a 50/50 row, and the right-aligned Privacy Policy footer. `acceptOffer` links out
|
|
9
9
|
* to the advertiser (`destinationUrl` = `click_url`, which self-tracks); `declineOffer` fires the
|
|
10
10
|
* decline pixel (`trackingUrl`) and advances. Parsing through the schema fills defaults and validates
|
package/dist/server/index.cjs
CHANGED
|
@@ -478,6 +478,20 @@ var SmartCartTier = import_zod5.z.looseObject({
|
|
|
478
478
|
productAmountRemainingLabel: import_zod5.z.boolean().optional(),
|
|
479
479
|
productAmountRemainingLabelText: import_zod5.z.string().optional(),
|
|
480
480
|
products: import_zod5.z.array(SmartCartTierProduct).optional(),
|
|
481
|
+
/**
|
|
482
|
+
* onsite `product_tier_element_size`: the step-row icon's square size as a px STRING (`'18px'` …
|
|
483
|
+
* `'36px'` — the admin's five-value Size control, default `'18px'`). React stamps it onto the icon
|
|
484
|
+
* SVG's `width`/`height` (`getProgressStepIconSize`), so the milestone renders at the merchant's
|
|
485
|
+
* chosen size; the converter turns it into `CABProgressTier.iconSizePx` for the CAB renderer.
|
|
486
|
+
*
|
|
487
|
+
* `.catch(undefined)` for the same blast-radius reason as `productTierImage` below: this key was
|
|
488
|
+
* previously unknown-and-ignored by the `looseObject`, and `SmartCartConfig` is parsed
|
|
489
|
+
* `.nullish().catch(undefined)` with no intermediate catch, so one drifted value (a bare number, a
|
|
490
|
+
* `'2rem'`) would drop EVERY progress bar for the shop rather than just this setting. Failing open to
|
|
491
|
+
* `undefined` lands on the documented 18px default — strictly safer than that blast radius. The
|
|
492
|
+
* converter parses the number out and clamps it, so a junk-but-string value degrades the same way.
|
|
493
|
+
*/
|
|
494
|
+
productTierElementSize: import_zod5.z.string().optional().catch(void 0),
|
|
481
495
|
/**
|
|
482
496
|
* onsite `product_tier_image`: on a product (gift) tier, show the gift's product image in the step-row
|
|
483
497
|
* slot instead of the generic bag icon (React `TierProgressIcon` swaps in the image when this is set and
|
|
@@ -1657,6 +1671,25 @@ var CABProgressTier = import_zod21.z.object({
|
|
|
1657
1671
|
title: import_zod21.z.string().optional(),
|
|
1658
1672
|
variantIds: import_zod21.z.array(import_zod21.z.number().int().positive()).default(() => [])
|
|
1659
1673
|
}).optional(),
|
|
1674
|
+
/**
|
|
1675
|
+
* The step-row icon's square size in PIXELS (onsite `product_tier_element_size`, an admin px string the
|
|
1676
|
+
* converter parses): 18 (default) / 20 / 24 / 30 / 36. React sizes the milestone by stamping this onto
|
|
1677
|
+
* the icon SVG's `width`/`height` (`getProgressStepIconSize`), so a merchant who deliberately enlarged
|
|
1678
|
+
* their milestones keeps that at checkout instead of every icon collapsing to one hardcoded square.
|
|
1679
|
+
*
|
|
1680
|
+
* A NUMBER, not the raw `'18px'` string: the renderer needs it as a length for the icon slot's grid
|
|
1681
|
+
* track, and a px-suffixed string would push unit parsing into every consumer. Deliberately NOT named
|
|
1682
|
+
* `iconSize` — the `iconSize` on an `image` section is the `s-icon` token enum, and one name covering both a
|
|
1683
|
+
* token and a pixel count is the kind of ambiguity that gets one passed where the other is meant.
|
|
1684
|
+
*
|
|
1685
|
+
* Bounded (not merely positive) because it is a raw merchant-controlled length: the step row is a grid
|
|
1686
|
+
* of per-tier cells, and an out-of-domain value would stretch the row rather than fail loudly. The
|
|
1687
|
+
* converter clamps to this same ceiling, so drifted config lands on a big-but-sane icon and never on a
|
|
1688
|
+
* tier the strict parse drops. `.default(18)` holds React's default at parse time (schema-first — the
|
|
1689
|
+
* renderer never spells a fallback), which also makes a tree authored before this field render at
|
|
1690
|
+
* React's size rather than the old hardcoded 20.
|
|
1691
|
+
*/
|
|
1692
|
+
iconSizePx: import_zod21.z.number().int().min(1).max(64).default(18),
|
|
1660
1693
|
/**
|
|
1661
1694
|
* The step-row label at this tier's slot (onsite `getTierLabel`): the merchant custom label, else the
|
|
1662
1695
|
* type default — 'Free Shipping' or '{n}% Discount'. A PRODUCT tier gets NO converter label: its name is
|
|
@@ -2386,12 +2419,21 @@ var NAMED_ENTITIES = {
|
|
|
2386
2419
|
rsquo: "\u2019",
|
|
2387
2420
|
trade: "\u2122"
|
|
2388
2421
|
};
|
|
2389
|
-
var decodeEntities = (input = "") => input.replace(/&(#x[\da-f]+|#\d+|[a-z][a-z\d]*);/gi, (match, code) => {
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2422
|
+
var decodeEntities = (input = "", { keepAngles = false } = {}) => input.replace(/&(#x[\da-f]+|#\d+|[a-z][a-z\d]*);/gi, (match, code) => {
|
|
2423
|
+
let decoded;
|
|
2424
|
+
if (code[0] !== "#") {
|
|
2425
|
+
decoded = NAMED_ENTITIES[code.toLowerCase()] ?? match;
|
|
2426
|
+
} else {
|
|
2427
|
+
const isHex = /^#x/i.test(code);
|
|
2428
|
+
const cp = parseInt(code.slice(isHex ? 2 : 1), isHex ? 16 : 10);
|
|
2429
|
+
decoded = Number.isInteger(cp) && cp >= 0 && cp <= 1114111 ? String.fromCodePoint(cp) : match;
|
|
2430
|
+
}
|
|
2431
|
+
return keepAngles && (decoded === "<" || decoded === ">") ? match : decoded;
|
|
2394
2432
|
});
|
|
2433
|
+
var decodeTextEntities = (input = "") => {
|
|
2434
|
+
const decoded = decodeEntities(input);
|
|
2435
|
+
return isHTML(decoded) ? decodeEntities(input, { keepAngles: true }) : decoded;
|
|
2436
|
+
};
|
|
2395
2437
|
|
|
2396
2438
|
// src/transforms/htmlToTiptap/parseAttribs.ts
|
|
2397
2439
|
var ATTRIB_RE = /([a-zA-Z_:][\w:.-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'`=<>]+)))?/g;
|
|
@@ -2446,7 +2488,7 @@ var openTag = (stack, name, attrs) => {
|
|
|
2446
2488
|
// src/transforms/htmlToTiptap/pushText.ts
|
|
2447
2489
|
var pushText = (state, rawText) => {
|
|
2448
2490
|
if (!rawText || isSkipping(state.stack)) return;
|
|
2449
|
-
|
|
2491
|
+
decodeTextEntities(rawText).split("\n").forEach((segment, index) => {
|
|
2450
2492
|
if (index > 0) flushParagraph(state);
|
|
2451
2493
|
if (!segment) return;
|
|
2452
2494
|
const marks = state.stack.flatMap((entry) => entry.marks);
|
|
@@ -2718,11 +2760,11 @@ var isMonetizeV1 = isWidgetV1(WidgetType.ui_extension_ad);
|
|
|
2718
2760
|
var HTML_TAG_RE2 = /<[a-zA-Z]/;
|
|
2719
2761
|
var containsHtml = (text2) => HTML_TAG_RE2.test(text2);
|
|
2720
2762
|
var isObject = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
|
|
2721
|
-
var
|
|
2722
|
-
if (typeof value !== "string") return value;
|
|
2763
|
+
var normalizeTextValue = (value, options = {}) => {
|
|
2723
2764
|
const tokenized = normalizeTokens(value);
|
|
2724
|
-
return containsHtml(tokenized) ? htmlToTiptap(tokenized) : tokenized;
|
|
2765
|
+
return containsHtml(tokenized) || options.defaultTextStyle ? htmlToTiptap(tokenized, options) : decodeTextEntities(tokenized);
|
|
2725
2766
|
};
|
|
2767
|
+
var normalizeLangValue = (value) => typeof value === "string" ? normalizeTextValue(value) : value;
|
|
2726
2768
|
var normalizeTextContent = (node) => {
|
|
2727
2769
|
if (Array.isArray(node)) return node.map(normalizeTextContent);
|
|
2728
2770
|
if (!isObject(node)) return node;
|
|
@@ -3521,6 +3563,7 @@ var feedbackDoc = (raw, tokens) => ({
|
|
|
3521
3563
|
],
|
|
3522
3564
|
type: "doc"
|
|
3523
3565
|
});
|
|
3566
|
+
var MAX_ICON_SIZE_PX = 64;
|
|
3524
3567
|
var IN_SCOPE_TIER_TYPES = new Set(progressTierTypes);
|
|
3525
3568
|
var inScope = (tier) => IN_SCOPE_TIER_TYPES.has(tier.type ?? "");
|
|
3526
3569
|
var thresholdOf = (tier) => tier.unlockConditionType === "item_quantity" ? Math.ceil(Number(tier.minimum)) : Math.round(Number(tier.minimum) * 100);
|
|
@@ -3539,6 +3582,10 @@ var discountOf = (tier) => {
|
|
|
3539
3582
|
const amount = tier.discountAmountFromTotal ?? 0;
|
|
3540
3583
|
return tier.discountType === "fixedAmount" ? { amount: Math.round(amount * 100), type: "fixedAmount" } : { amount, type: "percentage" };
|
|
3541
3584
|
};
|
|
3585
|
+
var iconSizeOf = (tier) => {
|
|
3586
|
+
const px = Math.round(Number.parseFloat(String(tier.productTierElementSize ?? "")));
|
|
3587
|
+
return Number.isFinite(px) && px > 0 ? Math.min(px, MAX_ICON_SIZE_PX) : void 0;
|
|
3588
|
+
};
|
|
3542
3589
|
var giftOf = (tier) => {
|
|
3543
3590
|
if (tier.type !== "product") return void 0;
|
|
3544
3591
|
const product = tier.products?.[0] ?? tier.allProducts?.[0];
|
|
@@ -3555,6 +3602,7 @@ var giftOf = (tier) => {
|
|
|
3555
3602
|
var toTierMeta = (tier) => {
|
|
3556
3603
|
const discount = discountOf(tier);
|
|
3557
3604
|
const gift = giftOf(tier);
|
|
3605
|
+
const iconSizePx = iconSizeOf(tier);
|
|
3558
3606
|
const isItemCount = tier.unlockConditionType === "item_quantity";
|
|
3559
3607
|
const label2 = tierLabel(tier);
|
|
3560
3608
|
const type = tier.type;
|
|
@@ -3563,6 +3611,8 @@ var toTierMeta = (tier) => {
|
|
|
3563
3611
|
/** Carry the OOS-filter flag only on gift tiers that set it — the client drops the tier when its gift is fully out of stock. */
|
|
3564
3612
|
...gift && tier.filterOosVariants && { filterOosVariants: true },
|
|
3565
3613
|
...gift && { gift },
|
|
3614
|
+
/** Carry the merchant's milestone icon size only when set — absent ⇒ the schema's 18px (React) default. */
|
|
3615
|
+
...iconSizePx !== void 0 && { iconSizePx },
|
|
3566
3616
|
...label2 && { label: { en: label2 } },
|
|
3567
3617
|
/** Carry the image-swap flag only on gift tiers that set it — the client shows the gift's product image in the step slot instead of the bag icon. */
|
|
3568
3618
|
...gift && tier.productTierImage && { productTierImage: true },
|
|
@@ -3990,6 +4040,10 @@ var label = (text2) => ({
|
|
|
3990
4040
|
sectionType: SectionType.text
|
|
3991
4041
|
});
|
|
3992
4042
|
var text = (content) => ({ content: { en: content }, sectionType: SectionType.text });
|
|
4043
|
+
var smallText = (raw) => ({
|
|
4044
|
+
content: { en: normalizeTextValue(raw, { defaultTextStyle: { fontSize: "small" } }) },
|
|
4045
|
+
sectionType: SectionType.text
|
|
4046
|
+
});
|
|
3993
4047
|
var styled = (value, { align, bold, color, fontSize, href }) => ({
|
|
3994
4048
|
content: {
|
|
3995
4049
|
en: {
|
|
@@ -4019,8 +4073,8 @@ var shopName = (shop) => shop.replace(/\.myshopify\.com$/, "") || "store";
|
|
|
4019
4073
|
var headerSections = (input) => [
|
|
4020
4074
|
{
|
|
4021
4075
|
sections: [
|
|
4022
|
-
styled("Congratulations!", { bold: true, fontSize: "
|
|
4023
|
-
styled(`Your ${shopName(input.shop)} purchase unlocked a bonus:`, { bold: true, fontSize: "
|
|
4076
|
+
styled("Congratulations!", { bold: true, fontSize: "heading" }),
|
|
4077
|
+
styled(`Your ${shopName(input.shop)} purchase unlocked a bonus:`, { bold: true, fontSize: "heading" })
|
|
4024
4078
|
],
|
|
4025
4079
|
sectionType: SectionType.layout,
|
|
4026
4080
|
spacing: Spacing.none
|
|
@@ -4029,7 +4083,7 @@ var headerSections = (input) => [
|
|
|
4029
4083
|
];
|
|
4030
4084
|
var composeOfferCard = (offer, input) => {
|
|
4031
4085
|
const copy = {
|
|
4032
|
-
sections: [text(offer.headline),
|
|
4086
|
+
sections: [text(offer.headline), smallText(offer.description)],
|
|
4033
4087
|
sectionType: SectionType.layout,
|
|
4034
4088
|
spacing: Spacing.tight
|
|
4035
4089
|
};
|
package/dist/server/index.mjs
CHANGED
|
@@ -430,6 +430,20 @@ var SmartCartTier = z5.looseObject({
|
|
|
430
430
|
productAmountRemainingLabel: z5.boolean().optional(),
|
|
431
431
|
productAmountRemainingLabelText: z5.string().optional(),
|
|
432
432
|
products: z5.array(SmartCartTierProduct).optional(),
|
|
433
|
+
/**
|
|
434
|
+
* onsite `product_tier_element_size`: the step-row icon's square size as a px STRING (`'18px'` …
|
|
435
|
+
* `'36px'` — the admin's five-value Size control, default `'18px'`). React stamps it onto the icon
|
|
436
|
+
* SVG's `width`/`height` (`getProgressStepIconSize`), so the milestone renders at the merchant's
|
|
437
|
+
* chosen size; the converter turns it into `CABProgressTier.iconSizePx` for the CAB renderer.
|
|
438
|
+
*
|
|
439
|
+
* `.catch(undefined)` for the same blast-radius reason as `productTierImage` below: this key was
|
|
440
|
+
* previously unknown-and-ignored by the `looseObject`, and `SmartCartConfig` is parsed
|
|
441
|
+
* `.nullish().catch(undefined)` with no intermediate catch, so one drifted value (a bare number, a
|
|
442
|
+
* `'2rem'`) would drop EVERY progress bar for the shop rather than just this setting. Failing open to
|
|
443
|
+
* `undefined` lands on the documented 18px default — strictly safer than that blast radius. The
|
|
444
|
+
* converter parses the number out and clamps it, so a junk-but-string value degrades the same way.
|
|
445
|
+
*/
|
|
446
|
+
productTierElementSize: z5.string().optional().catch(void 0),
|
|
433
447
|
/**
|
|
434
448
|
* onsite `product_tier_image`: on a product (gift) tier, show the gift's product image in the step-row
|
|
435
449
|
* slot instead of the generic bag icon (React `TierProgressIcon` swaps in the image when this is set and
|
|
@@ -1609,6 +1623,25 @@ var CABProgressTier = z21.object({
|
|
|
1609
1623
|
title: z21.string().optional(),
|
|
1610
1624
|
variantIds: z21.array(z21.number().int().positive()).default(() => [])
|
|
1611
1625
|
}).optional(),
|
|
1626
|
+
/**
|
|
1627
|
+
* The step-row icon's square size in PIXELS (onsite `product_tier_element_size`, an admin px string the
|
|
1628
|
+
* converter parses): 18 (default) / 20 / 24 / 30 / 36. React sizes the milestone by stamping this onto
|
|
1629
|
+
* the icon SVG's `width`/`height` (`getProgressStepIconSize`), so a merchant who deliberately enlarged
|
|
1630
|
+
* their milestones keeps that at checkout instead of every icon collapsing to one hardcoded square.
|
|
1631
|
+
*
|
|
1632
|
+
* A NUMBER, not the raw `'18px'` string: the renderer needs it as a length for the icon slot's grid
|
|
1633
|
+
* track, and a px-suffixed string would push unit parsing into every consumer. Deliberately NOT named
|
|
1634
|
+
* `iconSize` — the `iconSize` on an `image` section is the `s-icon` token enum, and one name covering both a
|
|
1635
|
+
* token and a pixel count is the kind of ambiguity that gets one passed where the other is meant.
|
|
1636
|
+
*
|
|
1637
|
+
* Bounded (not merely positive) because it is a raw merchant-controlled length: the step row is a grid
|
|
1638
|
+
* of per-tier cells, and an out-of-domain value would stretch the row rather than fail loudly. The
|
|
1639
|
+
* converter clamps to this same ceiling, so drifted config lands on a big-but-sane icon and never on a
|
|
1640
|
+
* tier the strict parse drops. `.default(18)` holds React's default at parse time (schema-first — the
|
|
1641
|
+
* renderer never spells a fallback), which also makes a tree authored before this field render at
|
|
1642
|
+
* React's size rather than the old hardcoded 20.
|
|
1643
|
+
*/
|
|
1644
|
+
iconSizePx: z21.number().int().min(1).max(64).default(18),
|
|
1612
1645
|
/**
|
|
1613
1646
|
* The step-row label at this tier's slot (onsite `getTierLabel`): the merchant custom label, else the
|
|
1614
1647
|
* type default — 'Free Shipping' or '{n}% Discount'. A PRODUCT tier gets NO converter label: its name is
|
|
@@ -2338,12 +2371,21 @@ var NAMED_ENTITIES = {
|
|
|
2338
2371
|
rsquo: "\u2019",
|
|
2339
2372
|
trade: "\u2122"
|
|
2340
2373
|
};
|
|
2341
|
-
var decodeEntities = (input = "") => input.replace(/&(#x[\da-f]+|#\d+|[a-z][a-z\d]*);/gi, (match, code) => {
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2374
|
+
var decodeEntities = (input = "", { keepAngles = false } = {}) => input.replace(/&(#x[\da-f]+|#\d+|[a-z][a-z\d]*);/gi, (match, code) => {
|
|
2375
|
+
let decoded;
|
|
2376
|
+
if (code[0] !== "#") {
|
|
2377
|
+
decoded = NAMED_ENTITIES[code.toLowerCase()] ?? match;
|
|
2378
|
+
} else {
|
|
2379
|
+
const isHex = /^#x/i.test(code);
|
|
2380
|
+
const cp = parseInt(code.slice(isHex ? 2 : 1), isHex ? 16 : 10);
|
|
2381
|
+
decoded = Number.isInteger(cp) && cp >= 0 && cp <= 1114111 ? String.fromCodePoint(cp) : match;
|
|
2382
|
+
}
|
|
2383
|
+
return keepAngles && (decoded === "<" || decoded === ">") ? match : decoded;
|
|
2346
2384
|
});
|
|
2385
|
+
var decodeTextEntities = (input = "") => {
|
|
2386
|
+
const decoded = decodeEntities(input);
|
|
2387
|
+
return isHTML(decoded) ? decodeEntities(input, { keepAngles: true }) : decoded;
|
|
2388
|
+
};
|
|
2347
2389
|
|
|
2348
2390
|
// src/transforms/htmlToTiptap/parseAttribs.ts
|
|
2349
2391
|
var ATTRIB_RE = /([a-zA-Z_:][\w:.-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'`=<>]+)))?/g;
|
|
@@ -2398,7 +2440,7 @@ var openTag = (stack, name, attrs) => {
|
|
|
2398
2440
|
// src/transforms/htmlToTiptap/pushText.ts
|
|
2399
2441
|
var pushText = (state, rawText) => {
|
|
2400
2442
|
if (!rawText || isSkipping(state.stack)) return;
|
|
2401
|
-
|
|
2443
|
+
decodeTextEntities(rawText).split("\n").forEach((segment, index) => {
|
|
2402
2444
|
if (index > 0) flushParagraph(state);
|
|
2403
2445
|
if (!segment) return;
|
|
2404
2446
|
const marks = state.stack.flatMap((entry) => entry.marks);
|
|
@@ -2670,11 +2712,11 @@ var isMonetizeV1 = isWidgetV1(WidgetType.ui_extension_ad);
|
|
|
2670
2712
|
var HTML_TAG_RE2 = /<[a-zA-Z]/;
|
|
2671
2713
|
var containsHtml = (text2) => HTML_TAG_RE2.test(text2);
|
|
2672
2714
|
var isObject = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
|
|
2673
|
-
var
|
|
2674
|
-
if (typeof value !== "string") return value;
|
|
2715
|
+
var normalizeTextValue = (value, options = {}) => {
|
|
2675
2716
|
const tokenized = normalizeTokens(value);
|
|
2676
|
-
return containsHtml(tokenized) ? htmlToTiptap(tokenized) : tokenized;
|
|
2717
|
+
return containsHtml(tokenized) || options.defaultTextStyle ? htmlToTiptap(tokenized, options) : decodeTextEntities(tokenized);
|
|
2677
2718
|
};
|
|
2719
|
+
var normalizeLangValue = (value) => typeof value === "string" ? normalizeTextValue(value) : value;
|
|
2678
2720
|
var normalizeTextContent = (node) => {
|
|
2679
2721
|
if (Array.isArray(node)) return node.map(normalizeTextContent);
|
|
2680
2722
|
if (!isObject(node)) return node;
|
|
@@ -3473,6 +3515,7 @@ var feedbackDoc = (raw, tokens) => ({
|
|
|
3473
3515
|
],
|
|
3474
3516
|
type: "doc"
|
|
3475
3517
|
});
|
|
3518
|
+
var MAX_ICON_SIZE_PX = 64;
|
|
3476
3519
|
var IN_SCOPE_TIER_TYPES = new Set(progressTierTypes);
|
|
3477
3520
|
var inScope = (tier) => IN_SCOPE_TIER_TYPES.has(tier.type ?? "");
|
|
3478
3521
|
var thresholdOf = (tier) => tier.unlockConditionType === "item_quantity" ? Math.ceil(Number(tier.minimum)) : Math.round(Number(tier.minimum) * 100);
|
|
@@ -3491,6 +3534,10 @@ var discountOf = (tier) => {
|
|
|
3491
3534
|
const amount = tier.discountAmountFromTotal ?? 0;
|
|
3492
3535
|
return tier.discountType === "fixedAmount" ? { amount: Math.round(amount * 100), type: "fixedAmount" } : { amount, type: "percentage" };
|
|
3493
3536
|
};
|
|
3537
|
+
var iconSizeOf = (tier) => {
|
|
3538
|
+
const px = Math.round(Number.parseFloat(String(tier.productTierElementSize ?? "")));
|
|
3539
|
+
return Number.isFinite(px) && px > 0 ? Math.min(px, MAX_ICON_SIZE_PX) : void 0;
|
|
3540
|
+
};
|
|
3494
3541
|
var giftOf = (tier) => {
|
|
3495
3542
|
if (tier.type !== "product") return void 0;
|
|
3496
3543
|
const product = tier.products?.[0] ?? tier.allProducts?.[0];
|
|
@@ -3507,6 +3554,7 @@ var giftOf = (tier) => {
|
|
|
3507
3554
|
var toTierMeta = (tier) => {
|
|
3508
3555
|
const discount = discountOf(tier);
|
|
3509
3556
|
const gift = giftOf(tier);
|
|
3557
|
+
const iconSizePx = iconSizeOf(tier);
|
|
3510
3558
|
const isItemCount = tier.unlockConditionType === "item_quantity";
|
|
3511
3559
|
const label2 = tierLabel(tier);
|
|
3512
3560
|
const type = tier.type;
|
|
@@ -3515,6 +3563,8 @@ var toTierMeta = (tier) => {
|
|
|
3515
3563
|
/** Carry the OOS-filter flag only on gift tiers that set it — the client drops the tier when its gift is fully out of stock. */
|
|
3516
3564
|
...gift && tier.filterOosVariants && { filterOosVariants: true },
|
|
3517
3565
|
...gift && { gift },
|
|
3566
|
+
/** Carry the merchant's milestone icon size only when set — absent ⇒ the schema's 18px (React) default. */
|
|
3567
|
+
...iconSizePx !== void 0 && { iconSizePx },
|
|
3518
3568
|
...label2 && { label: { en: label2 } },
|
|
3519
3569
|
/** Carry the image-swap flag only on gift tiers that set it — the client shows the gift's product image in the step slot instead of the bag icon. */
|
|
3520
3570
|
...gift && tier.productTierImage && { productTierImage: true },
|
|
@@ -3942,6 +3992,10 @@ var label = (text2) => ({
|
|
|
3942
3992
|
sectionType: SectionType.text
|
|
3943
3993
|
});
|
|
3944
3994
|
var text = (content) => ({ content: { en: content }, sectionType: SectionType.text });
|
|
3995
|
+
var smallText = (raw) => ({
|
|
3996
|
+
content: { en: normalizeTextValue(raw, { defaultTextStyle: { fontSize: "small" } }) },
|
|
3997
|
+
sectionType: SectionType.text
|
|
3998
|
+
});
|
|
3945
3999
|
var styled = (value, { align, bold, color, fontSize, href }) => ({
|
|
3946
4000
|
content: {
|
|
3947
4001
|
en: {
|
|
@@ -3971,8 +4025,8 @@ var shopName = (shop) => shop.replace(/\.myshopify\.com$/, "") || "store";
|
|
|
3971
4025
|
var headerSections = (input) => [
|
|
3972
4026
|
{
|
|
3973
4027
|
sections: [
|
|
3974
|
-
styled("Congratulations!", { bold: true, fontSize: "
|
|
3975
|
-
styled(`Your ${shopName(input.shop)} purchase unlocked a bonus:`, { bold: true, fontSize: "
|
|
4028
|
+
styled("Congratulations!", { bold: true, fontSize: "heading" }),
|
|
4029
|
+
styled(`Your ${shopName(input.shop)} purchase unlocked a bonus:`, { bold: true, fontSize: "heading" })
|
|
3976
4030
|
],
|
|
3977
4031
|
sectionType: SectionType.layout,
|
|
3978
4032
|
spacing: Spacing.none
|
|
@@ -3981,7 +4035,7 @@ var headerSections = (input) => [
|
|
|
3981
4035
|
];
|
|
3982
4036
|
var composeOfferCard = (offer, input) => {
|
|
3983
4037
|
const copy = {
|
|
3984
|
-
sections: [text(offer.headline),
|
|
4038
|
+
sections: [text(offer.headline), smallText(offer.description)],
|
|
3985
4039
|
sectionType: SectionType.layout,
|
|
3986
4040
|
spacing: Spacing.tight
|
|
3987
4041
|
};
|
|
@@ -1 +1,19 @@
|
|
|
1
|
-
export
|
|
1
|
+
export type DecodeEntitiesOptions = {
|
|
2
|
+
/**
|
|
3
|
+
* Leave every entity that would decode to `<` or `>` (named, decimal or hex, any case) as its
|
|
4
|
+
* literal source text. Decided on the DECODED character, not the spelling, so `<`, `<`
|
|
5
|
+
* and `<` are one rule — there is no second list of angle spellings to keep in sync.
|
|
6
|
+
*/
|
|
7
|
+
keepAngles?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare const decodeEntities: (input?: string, { keepAngles }?: DecodeEntitiesOptions) => string;
|
|
10
|
+
/**
|
|
11
|
+
* The entity decode for DISPLAY text — every text run and plain-string content value the CAB
|
|
12
|
+
* pipeline produces. Fully decodes ordinary entities (`&` → `&`, `5 < 10` → `5 < 10`), but
|
|
13
|
+
* when the fully-decoded result is tag-shaped (`<b>` → `<b>`) it re-decodes holding the
|
|
14
|
+
* angle entities, because `CABTextSection.content` carries a `NO_HTML` refine that rejects
|
|
15
|
+
* tag-shaped text and would otherwise drop the whole section — silent content loss, which is worse
|
|
16
|
+
* than showing the escaped literal. Uses the refine's own `isHTML` predicate so guard and gate can't
|
|
17
|
+
* drift. Single-pass by construction: a double-escaped `&lt;` resolves to literal `<` and stops.
|
|
18
|
+
*/
|
|
19
|
+
export declare const decodeTextEntities: (input?: string) => string;
|
|
@@ -1108,6 +1108,25 @@ var CABProgressTier = import_zod15.z.object({
|
|
|
1108
1108
|
title: import_zod15.z.string().optional(),
|
|
1109
1109
|
variantIds: import_zod15.z.array(import_zod15.z.number().int().positive()).default(() => [])
|
|
1110
1110
|
}).optional(),
|
|
1111
|
+
/**
|
|
1112
|
+
* The step-row icon's square size in PIXELS (onsite `product_tier_element_size`, an admin px string the
|
|
1113
|
+
* converter parses): 18 (default) / 20 / 24 / 30 / 36. React sizes the milestone by stamping this onto
|
|
1114
|
+
* the icon SVG's `width`/`height` (`getProgressStepIconSize`), so a merchant who deliberately enlarged
|
|
1115
|
+
* their milestones keeps that at checkout instead of every icon collapsing to one hardcoded square.
|
|
1116
|
+
*
|
|
1117
|
+
* A NUMBER, not the raw `'18px'` string: the renderer needs it as a length for the icon slot's grid
|
|
1118
|
+
* track, and a px-suffixed string would push unit parsing into every consumer. Deliberately NOT named
|
|
1119
|
+
* `iconSize` — the `iconSize` on an `image` section is the `s-icon` token enum, and one name covering both a
|
|
1120
|
+
* token and a pixel count is the kind of ambiguity that gets one passed where the other is meant.
|
|
1121
|
+
*
|
|
1122
|
+
* Bounded (not merely positive) because it is a raw merchant-controlled length: the step row is a grid
|
|
1123
|
+
* of per-tier cells, and an out-of-domain value would stretch the row rather than fail loudly. The
|
|
1124
|
+
* converter clamps to this same ceiling, so drifted config lands on a big-but-sane icon and never on a
|
|
1125
|
+
* tier the strict parse drops. `.default(18)` holds React's default at parse time (schema-first — the
|
|
1126
|
+
* renderer never spells a fallback), which also makes a tree authored before this field render at
|
|
1127
|
+
* React's size rather than the old hardcoded 20.
|
|
1128
|
+
*/
|
|
1129
|
+
iconSizePx: import_zod15.z.number().int().min(1).max(64).default(18),
|
|
1111
1130
|
/**
|
|
1112
1131
|
* The step-row label at this tier's slot (onsite `getTierLabel`): the merchant custom label, else the
|
|
1113
1132
|
* type default — 'Free Shipping' or '{n}% Discount'. A PRODUCT tier gets NO converter label: its name is
|
|
@@ -1837,12 +1856,21 @@ var NAMED_ENTITIES = {
|
|
|
1837
1856
|
rsquo: "\u2019",
|
|
1838
1857
|
trade: "\u2122"
|
|
1839
1858
|
};
|
|
1840
|
-
var decodeEntities = (input = "") => input.replace(/&(#x[\da-f]+|#\d+|[a-z][a-z\d]*);/gi, (match, code) => {
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1859
|
+
var decodeEntities = (input = "", { keepAngles = false } = {}) => input.replace(/&(#x[\da-f]+|#\d+|[a-z][a-z\d]*);/gi, (match, code) => {
|
|
1860
|
+
let decoded;
|
|
1861
|
+
if (code[0] !== "#") {
|
|
1862
|
+
decoded = NAMED_ENTITIES[code.toLowerCase()] ?? match;
|
|
1863
|
+
} else {
|
|
1864
|
+
const isHex = /^#x/i.test(code);
|
|
1865
|
+
const cp = parseInt(code.slice(isHex ? 2 : 1), isHex ? 16 : 10);
|
|
1866
|
+
decoded = Number.isInteger(cp) && cp >= 0 && cp <= 1114111 ? String.fromCodePoint(cp) : match;
|
|
1867
|
+
}
|
|
1868
|
+
return keepAngles && (decoded === "<" || decoded === ">") ? match : decoded;
|
|
1845
1869
|
});
|
|
1870
|
+
var decodeTextEntities = (input = "") => {
|
|
1871
|
+
const decoded = decodeEntities(input);
|
|
1872
|
+
return isHTML(decoded) ? decodeEntities(input, { keepAngles: true }) : decoded;
|
|
1873
|
+
};
|
|
1846
1874
|
|
|
1847
1875
|
// src/transforms/htmlToTiptap/parseAttribs.ts
|
|
1848
1876
|
var ATTRIB_RE = /([a-zA-Z_:][\w:.-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'`=<>]+)))?/g;
|
|
@@ -1897,7 +1925,7 @@ var openTag = (stack, name, attrs) => {
|
|
|
1897
1925
|
// src/transforms/htmlToTiptap/pushText.ts
|
|
1898
1926
|
var pushText = (state, rawText) => {
|
|
1899
1927
|
if (!rawText || isSkipping(state.stack)) return;
|
|
1900
|
-
|
|
1928
|
+
decodeTextEntities(rawText).split("\n").forEach((segment, index) => {
|
|
1901
1929
|
if (index > 0) flushParagraph(state);
|
|
1902
1930
|
if (!segment) return;
|
|
1903
1931
|
const marks = state.stack.flatMap((entry) => entry.marks);
|
|
@@ -2169,11 +2197,11 @@ var isMonetizeV1 = isWidgetV1(WidgetType.ui_extension_ad);
|
|
|
2169
2197
|
var HTML_TAG_RE2 = /<[a-zA-Z]/;
|
|
2170
2198
|
var containsHtml = (text) => HTML_TAG_RE2.test(text);
|
|
2171
2199
|
var isObject = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
|
|
2172
|
-
var
|
|
2173
|
-
if (typeof value !== "string") return value;
|
|
2200
|
+
var normalizeTextValue = (value, options = {}) => {
|
|
2174
2201
|
const tokenized = normalizeTokens(value);
|
|
2175
|
-
return containsHtml(tokenized) ? htmlToTiptap(tokenized) : tokenized;
|
|
2202
|
+
return containsHtml(tokenized) || options.defaultTextStyle ? htmlToTiptap(tokenized, options) : decodeTextEntities(tokenized);
|
|
2176
2203
|
};
|
|
2204
|
+
var normalizeLangValue = (value) => typeof value === "string" ? normalizeTextValue(value) : value;
|
|
2177
2205
|
var normalizeTextContent = (node) => {
|
|
2178
2206
|
if (Array.isArray(node)) return node.map(normalizeTextContent);
|
|
2179
2207
|
if (!isObject(node)) return node;
|
|
@@ -3403,6 +3431,7 @@ var feedbackDoc = (raw, tokens) => ({
|
|
|
3403
3431
|
],
|
|
3404
3432
|
type: "doc"
|
|
3405
3433
|
});
|
|
3434
|
+
var MAX_ICON_SIZE_PX = 64;
|
|
3406
3435
|
var IN_SCOPE_TIER_TYPES = new Set(progressTierTypes);
|
|
3407
3436
|
var inScope = (tier) => IN_SCOPE_TIER_TYPES.has(tier.type ?? "");
|
|
3408
3437
|
var thresholdOf = (tier) => tier.unlockConditionType === "item_quantity" ? Math.ceil(Number(tier.minimum)) : Math.round(Number(tier.minimum) * 100);
|
|
@@ -3421,6 +3450,10 @@ var discountOf = (tier) => {
|
|
|
3421
3450
|
const amount = tier.discountAmountFromTotal ?? 0;
|
|
3422
3451
|
return tier.discountType === "fixedAmount" ? { amount: Math.round(amount * 100), type: "fixedAmount" } : { amount, type: "percentage" };
|
|
3423
3452
|
};
|
|
3453
|
+
var iconSizeOf = (tier) => {
|
|
3454
|
+
const px = Math.round(Number.parseFloat(String(tier.productTierElementSize ?? "")));
|
|
3455
|
+
return Number.isFinite(px) && px > 0 ? Math.min(px, MAX_ICON_SIZE_PX) : void 0;
|
|
3456
|
+
};
|
|
3424
3457
|
var giftOf = (tier) => {
|
|
3425
3458
|
if (tier.type !== "product") return void 0;
|
|
3426
3459
|
const product = tier.products?.[0] ?? tier.allProducts?.[0];
|
|
@@ -3437,6 +3470,7 @@ var giftOf = (tier) => {
|
|
|
3437
3470
|
var toTierMeta = (tier) => {
|
|
3438
3471
|
const discount = discountOf(tier);
|
|
3439
3472
|
const gift = giftOf(tier);
|
|
3473
|
+
const iconSizePx = iconSizeOf(tier);
|
|
3440
3474
|
const isItemCount = tier.unlockConditionType === "item_quantity";
|
|
3441
3475
|
const label = tierLabel(tier);
|
|
3442
3476
|
const type = tier.type;
|
|
@@ -3445,6 +3479,8 @@ var toTierMeta = (tier) => {
|
|
|
3445
3479
|
/** Carry the OOS-filter flag only on gift tiers that set it — the client drops the tier when its gift is fully out of stock. */
|
|
3446
3480
|
...gift && tier.filterOosVariants && { filterOosVariants: true },
|
|
3447
3481
|
...gift && { gift },
|
|
3482
|
+
/** Carry the merchant's milestone icon size only when set — absent ⇒ the schema's 18px (React) default. */
|
|
3483
|
+
...iconSizePx !== void 0 && { iconSizePx },
|
|
3448
3484
|
...label && { label: { en: label } },
|
|
3449
3485
|
/** Carry the image-swap flag only on gift tiers that set it — the client shows the gift's product image in the step slot instead of the bag icon. */
|
|
3450
3486
|
...gift && tier.productTierImage && { productTierImage: true },
|
|
@@ -1051,6 +1051,25 @@ var CABProgressTier = z15.object({
|
|
|
1051
1051
|
title: z15.string().optional(),
|
|
1052
1052
|
variantIds: z15.array(z15.number().int().positive()).default(() => [])
|
|
1053
1053
|
}).optional(),
|
|
1054
|
+
/**
|
|
1055
|
+
* The step-row icon's square size in PIXELS (onsite `product_tier_element_size`, an admin px string the
|
|
1056
|
+
* converter parses): 18 (default) / 20 / 24 / 30 / 36. React sizes the milestone by stamping this onto
|
|
1057
|
+
* the icon SVG's `width`/`height` (`getProgressStepIconSize`), so a merchant who deliberately enlarged
|
|
1058
|
+
* their milestones keeps that at checkout instead of every icon collapsing to one hardcoded square.
|
|
1059
|
+
*
|
|
1060
|
+
* A NUMBER, not the raw `'18px'` string: the renderer needs it as a length for the icon slot's grid
|
|
1061
|
+
* track, and a px-suffixed string would push unit parsing into every consumer. Deliberately NOT named
|
|
1062
|
+
* `iconSize` — the `iconSize` on an `image` section is the `s-icon` token enum, and one name covering both a
|
|
1063
|
+
* token and a pixel count is the kind of ambiguity that gets one passed where the other is meant.
|
|
1064
|
+
*
|
|
1065
|
+
* Bounded (not merely positive) because it is a raw merchant-controlled length: the step row is a grid
|
|
1066
|
+
* of per-tier cells, and an out-of-domain value would stretch the row rather than fail loudly. The
|
|
1067
|
+
* converter clamps to this same ceiling, so drifted config lands on a big-but-sane icon and never on a
|
|
1068
|
+
* tier the strict parse drops. `.default(18)` holds React's default at parse time (schema-first — the
|
|
1069
|
+
* renderer never spells a fallback), which also makes a tree authored before this field render at
|
|
1070
|
+
* React's size rather than the old hardcoded 20.
|
|
1071
|
+
*/
|
|
1072
|
+
iconSizePx: z15.number().int().min(1).max(64).default(18),
|
|
1054
1073
|
/**
|
|
1055
1074
|
* The step-row label at this tier's slot (onsite `getTierLabel`): the merchant custom label, else the
|
|
1056
1075
|
* type default — 'Free Shipping' or '{n}% Discount'. A PRODUCT tier gets NO converter label: its name is
|
|
@@ -1780,12 +1799,21 @@ var NAMED_ENTITIES = {
|
|
|
1780
1799
|
rsquo: "\u2019",
|
|
1781
1800
|
trade: "\u2122"
|
|
1782
1801
|
};
|
|
1783
|
-
var decodeEntities = (input = "") => input.replace(/&(#x[\da-f]+|#\d+|[a-z][a-z\d]*);/gi, (match, code) => {
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1802
|
+
var decodeEntities = (input = "", { keepAngles = false } = {}) => input.replace(/&(#x[\da-f]+|#\d+|[a-z][a-z\d]*);/gi, (match, code) => {
|
|
1803
|
+
let decoded;
|
|
1804
|
+
if (code[0] !== "#") {
|
|
1805
|
+
decoded = NAMED_ENTITIES[code.toLowerCase()] ?? match;
|
|
1806
|
+
} else {
|
|
1807
|
+
const isHex = /^#x/i.test(code);
|
|
1808
|
+
const cp = parseInt(code.slice(isHex ? 2 : 1), isHex ? 16 : 10);
|
|
1809
|
+
decoded = Number.isInteger(cp) && cp >= 0 && cp <= 1114111 ? String.fromCodePoint(cp) : match;
|
|
1810
|
+
}
|
|
1811
|
+
return keepAngles && (decoded === "<" || decoded === ">") ? match : decoded;
|
|
1788
1812
|
});
|
|
1813
|
+
var decodeTextEntities = (input = "") => {
|
|
1814
|
+
const decoded = decodeEntities(input);
|
|
1815
|
+
return isHTML(decoded) ? decodeEntities(input, { keepAngles: true }) : decoded;
|
|
1816
|
+
};
|
|
1789
1817
|
|
|
1790
1818
|
// src/transforms/htmlToTiptap/parseAttribs.ts
|
|
1791
1819
|
var ATTRIB_RE = /([a-zA-Z_:][\w:.-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'`=<>]+)))?/g;
|
|
@@ -1840,7 +1868,7 @@ var openTag = (stack, name, attrs) => {
|
|
|
1840
1868
|
// src/transforms/htmlToTiptap/pushText.ts
|
|
1841
1869
|
var pushText = (state, rawText) => {
|
|
1842
1870
|
if (!rawText || isSkipping(state.stack)) return;
|
|
1843
|
-
|
|
1871
|
+
decodeTextEntities(rawText).split("\n").forEach((segment, index) => {
|
|
1844
1872
|
if (index > 0) flushParagraph(state);
|
|
1845
1873
|
if (!segment) return;
|
|
1846
1874
|
const marks = state.stack.flatMap((entry) => entry.marks);
|
|
@@ -2112,11 +2140,11 @@ var isMonetizeV1 = isWidgetV1(WidgetType.ui_extension_ad);
|
|
|
2112
2140
|
var HTML_TAG_RE2 = /<[a-zA-Z]/;
|
|
2113
2141
|
var containsHtml = (text) => HTML_TAG_RE2.test(text);
|
|
2114
2142
|
var isObject = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
|
|
2115
|
-
var
|
|
2116
|
-
if (typeof value !== "string") return value;
|
|
2143
|
+
var normalizeTextValue = (value, options = {}) => {
|
|
2117
2144
|
const tokenized = normalizeTokens(value);
|
|
2118
|
-
return containsHtml(tokenized) ? htmlToTiptap(tokenized) : tokenized;
|
|
2145
|
+
return containsHtml(tokenized) || options.defaultTextStyle ? htmlToTiptap(tokenized, options) : decodeTextEntities(tokenized);
|
|
2119
2146
|
};
|
|
2147
|
+
var normalizeLangValue = (value) => typeof value === "string" ? normalizeTextValue(value) : value;
|
|
2120
2148
|
var normalizeTextContent = (node) => {
|
|
2121
2149
|
if (Array.isArray(node)) return node.map(normalizeTextContent);
|
|
2122
2150
|
if (!isObject(node)) return node;
|
|
@@ -3346,6 +3374,7 @@ var feedbackDoc = (raw, tokens) => ({
|
|
|
3346
3374
|
],
|
|
3347
3375
|
type: "doc"
|
|
3348
3376
|
});
|
|
3377
|
+
var MAX_ICON_SIZE_PX = 64;
|
|
3349
3378
|
var IN_SCOPE_TIER_TYPES = new Set(progressTierTypes);
|
|
3350
3379
|
var inScope = (tier) => IN_SCOPE_TIER_TYPES.has(tier.type ?? "");
|
|
3351
3380
|
var thresholdOf = (tier) => tier.unlockConditionType === "item_quantity" ? Math.ceil(Number(tier.minimum)) : Math.round(Number(tier.minimum) * 100);
|
|
@@ -3364,6 +3393,10 @@ var discountOf = (tier) => {
|
|
|
3364
3393
|
const amount = tier.discountAmountFromTotal ?? 0;
|
|
3365
3394
|
return tier.discountType === "fixedAmount" ? { amount: Math.round(amount * 100), type: "fixedAmount" } : { amount, type: "percentage" };
|
|
3366
3395
|
};
|
|
3396
|
+
var iconSizeOf = (tier) => {
|
|
3397
|
+
const px = Math.round(Number.parseFloat(String(tier.productTierElementSize ?? "")));
|
|
3398
|
+
return Number.isFinite(px) && px > 0 ? Math.min(px, MAX_ICON_SIZE_PX) : void 0;
|
|
3399
|
+
};
|
|
3367
3400
|
var giftOf = (tier) => {
|
|
3368
3401
|
if (tier.type !== "product") return void 0;
|
|
3369
3402
|
const product = tier.products?.[0] ?? tier.allProducts?.[0];
|
|
@@ -3380,6 +3413,7 @@ var giftOf = (tier) => {
|
|
|
3380
3413
|
var toTierMeta = (tier) => {
|
|
3381
3414
|
const discount = discountOf(tier);
|
|
3382
3415
|
const gift = giftOf(tier);
|
|
3416
|
+
const iconSizePx = iconSizeOf(tier);
|
|
3383
3417
|
const isItemCount = tier.unlockConditionType === "item_quantity";
|
|
3384
3418
|
const label = tierLabel(tier);
|
|
3385
3419
|
const type = tier.type;
|
|
@@ -3388,6 +3422,8 @@ var toTierMeta = (tier) => {
|
|
|
3388
3422
|
/** Carry the OOS-filter flag only on gift tiers that set it — the client drops the tier when its gift is fully out of stock. */
|
|
3389
3423
|
...gift && tier.filterOosVariants && { filterOosVariants: true },
|
|
3390
3424
|
...gift && { gift },
|
|
3425
|
+
/** Carry the merchant's milestone icon size only when set — absent ⇒ the schema's 18px (React) default. */
|
|
3426
|
+
...iconSizePx !== void 0 && { iconSizePx },
|
|
3391
3427
|
...label && { label: { en: label } },
|
|
3392
3428
|
/** Carry the image-swap flag only on gift tiers that set it — the client shows the gift's product image in the step slot instead of the bag icon. */
|
|
3393
3429
|
...gift && tier.productTierImage && { productTierImage: true },
|
|
@@ -1,11 +1,38 @@
|
|
|
1
|
+
import { type TiptapDocument } from '../schema/widgets/checkout-and-beyond/text';
|
|
2
|
+
import { type HtmlToTiptapOptions } from '../transforms/htmlToTiptap/htmlToTiptap';
|
|
3
|
+
export type NormalizeTextOptions = Pick<HtmlToTiptapOptions, 'defaultTextStyle'>;
|
|
1
4
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
5
|
+
* THE text pipeline for one content value — the single path every text a CAB tree renders takes,
|
|
6
|
+
* whether it is merchant-authored, converted from a legacy widget, or third-party ad creative:
|
|
7
|
+
*
|
|
8
|
+
* 1. `{firstName}` (single brace) becomes `{{firstName}}` (canonical).
|
|
9
|
+
* 2. HTML becomes a fully-formed `TiptapDocument` (entities decoded per run by `htmlToTiptap`).
|
|
10
|
+
* 3. Plain text is entity-decoded in place and stays a string, so `Sale & Save` never renders
|
|
11
|
+
* its `&` literally and URL-bearing values (`buttonField: destinationUrl`) keep the string
|
|
12
|
+
* shape their validation expects.
|
|
13
|
+
*
|
|
14
|
+
* Both decodes run through `decodeTextEntities`, which holds tag-shaped angle entities escaped so a
|
|
15
|
+
* decode can never manufacture the text the section's `NO_HTML` refine rejects.
|
|
16
|
+
*
|
|
17
|
+
* `defaultTextStyle` is the server-side analogue of React's `defaultTextStyle` prop: the doc's runs
|
|
18
|
+
* carry that `textStyle` unless the markup set their own. A default forces plain text through the
|
|
19
|
+
* converter too, since only a doc can carry a mark.
|
|
4
20
|
*
|
|
5
|
-
* -
|
|
6
|
-
*
|
|
21
|
+
* NOT idempotent for entity-bearing strings (`&amp;` → `&` → `&`), by nature of decoding.
|
|
22
|
+
* That is safe because this runs at SERVE time on the stored original and its output is returned,
|
|
23
|
+
* never written back — rebuy-api's `/cab/*` routes are read-only and admin-nextjs never invokes the
|
|
24
|
+
* walk. Keep it that way: a save path that persisted this output would degrade one entity level per
|
|
25
|
+
* cycle. (Docs are immune — the walk leaves them untouched.)
|
|
26
|
+
*/
|
|
27
|
+
export declare const normalizeTextValue: (value: string, options?: NormalizeTextOptions) => string | TiptapDocument;
|
|
28
|
+
/**
|
|
29
|
+
* Walks an untyped CABRootSection-shaped object (post deepCamelCase, pre schema
|
|
30
|
+
* parse) and runs every `text` section's `content[lang]` string through
|
|
31
|
+
* {@link normalizeTextValue}. Already-converted Tiptap docs pass through untouched,
|
|
32
|
+
* so a caller that needed {@link normalizeTextValue}'s options can convert first
|
|
33
|
+
* and hand the result to this walk without double-conversion.
|
|
7
34
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
35
|
+
* Non-text sections only recurse into their `sections` array; Tiptap docs and
|
|
36
|
+
* other nested objects are not walked.
|
|
10
37
|
*/
|
|
11
38
|
export declare const normalizeTextContent: (node: unknown) => unknown;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebuy/rebuy",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.12.0",
|
|
4
4
|
"description": "Shared zod schemas, legacy-to-CAB widget transforms, and server orchestrators for Rebuy's Shopify consumers (rebuy-api, admin-nextjs, rebuy-shopify-extensions)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Rebuy, Inc.",
|