@useinsider/guido 3.12.0-beta.cfe6433 → 3.12.0-beta.d77b0d2

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.
Files changed (92) hide show
  1. package/dist/@types/config/schemas.js +9 -4
  2. package/dist/components/Guido.vue.js +6 -6
  3. package/dist/components/Guido.vue2.js +50 -49
  4. package/dist/components/organisms/email-preview/desktop-preview/DesktopPreview.vue.js +1 -1
  5. package/dist/components/organisms/email-preview/desktop-preview/DesktopPreview.vue2.js +8 -8
  6. package/dist/components/organisms/email-preview/mobile-preview/ContentView.vue.js +5 -5
  7. package/dist/components/organisms/email-preview/mobile-preview/ContentView.vue2.js +10 -10
  8. package/dist/components/organisms/extensions/recommendation/StrategyDetailDrawer.vue.js +38 -0
  9. package/dist/components/organisms/extensions/recommendation/StrategyDetailDrawer.vue2.js +271 -0
  10. package/dist/components/organisms/extensions/recommendation/strategy-detail/CampaignTable.vue.js +18 -0
  11. package/dist/components/organisms/extensions/recommendation/strategy-detail/CampaignTable.vue2.js +42 -0
  12. package/dist/components/organisms/extensions/recommendation/strategy-detail/FilterHumanizer.vue.js +20 -0
  13. package/dist/components/organisms/extensions/recommendation/strategy-detail/FilterHumanizer.vue2.js +18 -0
  14. package/dist/components/organisms/extensions/recommendation/strategy-detail/HumanizerCondition.vue.js +20 -0
  15. package/dist/components/organisms/extensions/recommendation/strategy-detail/HumanizerCondition.vue2.js +60 -0
  16. package/dist/components/organisms/extensions/recommendation/strategy-detail/HumanizerGroup.vue.js +19 -0
  17. package/dist/components/organisms/extensions/recommendation/strategy-detail/HumanizerGroup.vue2.js +19 -0
  18. package/dist/components/organisms/extensions/recommendation/strategy-detail/InfoDisplayValue.vue.js +22 -0
  19. package/dist/components/organisms/extensions/recommendation/strategy-detail/InfoDisplayValue.vue2.js +29 -0
  20. package/dist/composables/useHtmlValidator.js +68 -60
  21. package/dist/composables/useHttp.js +61 -52
  22. package/dist/composables/usePreviewInteractionGuard.js +36 -11
  23. package/dist/composables/useRecommendation.js +56 -40
  24. package/dist/composables/useRecommendationPreview.js +61 -60
  25. package/dist/composables/useSave.js +22 -22
  26. package/dist/composables/useStrategyFilters.js +16 -0
  27. package/dist/config/migrator/recommendationMigrator.js +7 -4
  28. package/dist/enums/date.js +4 -3
  29. package/dist/enums/extensions/filteringV2.js +1024 -0
  30. package/dist/enums/extensions/recommendationBlock.js +45 -24
  31. package/dist/enums/extensions/strategyDetail.js +149 -0
  32. package/dist/extensions/Blocks/Recommendation/constants/controlIds.js +1 -1
  33. package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +12 -11
  34. package/dist/extensions/Blocks/Recommendation/controls/blockBackground/index.js +10 -0
  35. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +141 -123
  36. package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +297 -0
  37. package/dist/extensions/Blocks/Recommendation/extension.js +23 -21
  38. package/dist/extensions/Blocks/Recommendation/iconsRegistry.js +16 -2
  39. package/dist/extensions/Blocks/Recommendation/recommendation.css.js +137 -0
  40. package/dist/extensions/Blocks/Recommendation/settingsPanel.js +41 -40
  41. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +549 -165
  42. package/dist/extensions/Blocks/Recommendation/utils/strategyHumanizer.js +97 -0
  43. package/dist/extensions/Blocks/Recommendation/utils/strategyNavigation.js +8 -0
  44. package/dist/extensions/Blocks/Recommendation/utils/strategySummary.js +95 -0
  45. package/dist/extensions/Blocks/Recommendation/utils/strategyUrl.js +43 -0
  46. package/dist/extensions/Blocks/Recommendation/validation/requiredFields.js +24 -10
  47. package/dist/extensions/Blocks/Unsubscribe/utils/constants.js +3 -2
  48. package/dist/extensions/Blocks/common-control.js +37 -35
  49. package/dist/extensions/Blocks/controlFactories.js +125 -75
  50. package/dist/guido.css +1 -1
  51. package/dist/services/recommendationApi.js +127 -33
  52. package/dist/services/stripoApi.js +18 -18
  53. package/dist/src/@types/config/schemas.d.ts +10 -0
  54. package/dist/src/components/organisms/extensions/recommendation/StrategyDetailDrawer.vue.d.ts +2 -0
  55. package/dist/src/components/organisms/extensions/recommendation/strategy-detail/CampaignTable.vue.d.ts +43 -0
  56. package/dist/src/components/organisms/extensions/recommendation/strategy-detail/FilterHumanizer.vue.d.ts +29 -0
  57. package/dist/src/components/organisms/extensions/recommendation/strategy-detail/HumanizerCondition.vue.d.ts +40 -0
  58. package/dist/src/components/organisms/extensions/recommendation/strategy-detail/HumanizerGroup.vue.d.ts +38 -0
  59. package/dist/src/components/organisms/extensions/recommendation/strategy-detail/InfoDisplayValue.vue.d.ts +51 -0
  60. package/dist/src/composables/useConfig.d.ts +2 -0
  61. package/dist/src/composables/useHtmlValidator.d.ts +2 -0
  62. package/dist/src/composables/usePreviewInteractionGuard.d.ts +1 -1
  63. package/dist/src/composables/useRecommendation.d.ts +2 -0
  64. package/dist/src/composables/useStrategyFilters.d.ts +24 -0
  65. package/dist/src/enums/extensions/filteringV2.d.ts +72 -0
  66. package/dist/src/enums/extensions/recommendationBlock.d.ts +33 -0
  67. package/dist/src/enums/extensions/strategyDetail.d.ts +90 -0
  68. package/dist/src/extensions/Blocks/Recommendation/constants/controlIds.d.ts +1 -0
  69. package/dist/src/extensions/Blocks/Recommendation/controls/blockBackground/index.d.ts +14 -0
  70. package/dist/src/extensions/Blocks/Recommendation/controls/index.d.ts +1 -0
  71. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +10 -4
  72. package/dist/src/extensions/Blocks/Recommendation/controls/main/strategy.d.ts +118 -0
  73. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +958 -1
  74. package/dist/src/extensions/Blocks/Recommendation/types/nodeConfig.d.ts +7 -0
  75. package/dist/src/extensions/Blocks/Recommendation/utils/strategyHumanizer.d.ts +50 -0
  76. package/dist/src/extensions/Blocks/Recommendation/utils/strategyNavigation.d.ts +10 -0
  77. package/dist/src/extensions/Blocks/Recommendation/utils/strategySummary.d.ts +34 -0
  78. package/dist/src/extensions/Blocks/Recommendation/utils/strategyUrl.d.ts +51 -0
  79. package/dist/src/extensions/Blocks/Recommendation/validation/requiredFields.d.ts +8 -1
  80. package/dist/src/extensions/Blocks/Unsubscribe/utils/constants.d.ts +1 -0
  81. package/dist/src/extensions/Blocks/common-control.d.ts +5 -2
  82. package/dist/src/extensions/Blocks/controlFactories.d.ts +36 -0
  83. package/dist/src/mock/api/recommendation-strategies.d.ts +2 -0
  84. package/dist/src/services/recommendationApi.d.ts +9 -1
  85. package/dist/src/stores/config.d.ts +18 -0
  86. package/dist/src/utils/genericUtil.d.ts +9 -0
  87. package/dist/src/utils/urlSchemes.d.ts +6 -0
  88. package/dist/utils/dateUtil.js +23 -10
  89. package/dist/utils/genericUtil.js +10 -1
  90. package/dist/utils/templatePreparation.js +70 -57
  91. package/dist/utils/urlSchemes.js +4 -0
  92. package/package.json +1 -1
@@ -1,20 +1,27 @@
1
- import { getRecommendationFeedSourceMaps as g, getOperatorOptions as R, PriceAttributes as C } from "../../../../enums/extensions/recommendationBlock.js";
2
- import { useRecommendationApi as y } from "../../../../services/recommendationApi.js";
3
- import { useConfigStore as G } from "../../../../stores/config.js";
4
- import { defineStore as F } from "pinia";
1
+ import { useConfig as U } from "../../../../composables/useConfig.js";
2
+ import { useTranslations as u } from "../../../../composables/useTranslations.js";
3
+ import { getRecommendationFeedSourceMaps as w, getOperatorOptions as L, PriceAttributes as T } from "../../../../enums/extensions/recommendationBlock.js";
4
+ import { ToasterTypeOptions as g } from "../../../../enums/toaster.js";
5
+ import { useRecommendationApi as _ } from "../../../../services/recommendationApi.js";
6
+ import { useConfigStore as D } from "../../../../stores/config.js";
7
+ import { useToasterStore as f } from "../../../../stores/toaster.js";
8
+ import { defineStore as N } from "pinia";
5
9
  import "../constants/selectors.js";
6
- import { DEFAULT_MOBILE_CARDS_IN_ROW as P, DEFAULT_CARDS_IN_ROW as D } from "../constants/layout.js";
7
- import { EXCLUDED_ALGORITHM_IDS as w } from "../constants/defaultConfig.js";
8
- import { getDefaultProducts as S } from "../templates/utils.js";
9
- import { generateCompleteFilterQuery as b } from "../utils/filterUtil.js";
10
- import { getPartnerRecommendationParams as v } from "../utils/partnerCustomizations.js";
11
- import { isFilterValid as N } from "../validation/filterSchema.js";
12
- import { isConfigValid as x } from "../validation/requiredFields.js";
13
- const h = y();
14
- let m = null, u = null, d = null;
15
- function k() {
10
+ import { DEFAULT_MOBILE_CARDS_IN_ROW as x, DEFAULT_CARDS_IN_ROW as B } from "../constants/layout.js";
11
+ import { EXCLUDED_ALGORITHM_IDS as V } from "../constants/defaultConfig.js";
12
+ import { getDefaultProducts as F } from "../templates/utils.js";
13
+ import { generateCompleteFilterQuery as v } from "../utils/filterUtil.js";
14
+ import { getPartnerRecommendationParams as A } from "../utils/partnerCustomizations.js";
15
+ import { resolveStrategyUrl as E, buildStrategyUrlValues as O } from "../utils/strategyUrl.js";
16
+ import { isFilterValid as z } from "../validation/filterSchema.js";
17
+ import { isConfigValid as M } from "../validation/requiredFields.js";
18
+ const c = _();
19
+ let h = null, p = null, S = null, d = null, I = 0;
20
+ const y = {};
21
+ let b = 0;
22
+ function P() {
16
23
  return {
17
- cardsInRow: D,
24
+ cardsInRow: B,
18
25
  currencySettings: {
19
26
  name: "USD",
20
27
  value: "USD",
@@ -28,13 +35,14 @@ function k() {
28
35
  productIds: [],
29
36
  id: 1,
30
37
  language: "en_US",
31
- mobileCardsInRow: P,
38
+ mobileCardsInRow: x,
32
39
  mobileLayoutEnabled: !1,
33
40
  orientation: "grid",
34
41
  recommendedProducts: [],
35
42
  sendProductRequestFlag: !1,
36
43
  shuffleProducts: !1,
37
44
  strategy: "mostPopular",
45
+ strategyId: "",
38
46
  textTrimming: !0,
39
47
  unresponsive: !1,
40
48
  size: "6",
@@ -43,28 +51,39 @@ function k() {
43
51
  priceHideIfSameAsDiscounted: !1
44
52
  };
45
53
  }
46
- function I() {
54
+ function G() {
47
55
  return {
48
- recommendationConfigs: k(),
56
+ recommendationConfigs: P(),
49
57
  recommendationProducts: [],
50
58
  filterStatus: !1,
51
59
  filterSelectionDrawerStatus: !1,
60
+ strategyDetailDrawerStatus: !1,
61
+ strategyDetailId: "",
52
62
  filterGroup: 1,
53
63
  isInitialized: !1,
54
64
  filterSnapshot: null
55
65
  };
56
66
  }
57
- const L = () => ({
67
+ const j = () => ({
58
68
  recommendationCampaignUrls: {},
59
69
  activePredictiveAlgorithms: [],
60
70
  languages: {},
61
71
  currencyList: [],
62
72
  filterList: {},
73
+ strategies: [],
74
+ strategiesLoading: !1,
75
+ strategyCampaigns: [],
76
+ strategyCampaignsLoading: !1,
77
+ strategyCampaignsFetchedFor: null,
78
+ strategyUrlTemplates: {},
79
+ strategiesLoadFailed: !1,
80
+ strategiesLoaded: !1,
81
+ strategyDuplicating: !1,
63
82
  blockStates: {},
64
83
  currentRecommendationId: null,
65
84
  configVersion: 0
66
- }), $ = F("guidoRecommendationExtension", {
67
- state: () => L(),
85
+ }), ot = N("guidoRecommendationExtension", {
86
+ state: () => j(),
68
87
  getters: {
69
88
  // ====================================================================
70
89
  // Proxy Getters — Backward Compatible Access to Current Block State
@@ -74,7 +93,7 @@ const L = () => ({
74
93
  * This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
75
94
  */
76
95
  recommendationConfigs(t) {
77
- return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : k();
96
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : P();
78
97
  },
79
98
  /**
80
99
  * Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
@@ -88,6 +107,25 @@ const L = () => ({
88
107
  filterSelectionDrawerStatus(t) {
89
108
  return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].filterSelectionDrawerStatus : !1;
90
109
  },
110
+ /**
111
+ * Proxy getter: delegates to blockStates[currentRecommendationId].strategyDetailDrawerStatus
112
+ */
113
+ strategyDetailDrawerStatus(t) {
114
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].strategyDetailDrawerStatus : !1;
115
+ },
116
+ /**
117
+ * The strategy the detail drawer is describing.
118
+ *
119
+ * Usually the assigned one (the gear opens the drawer for the block), but
120
+ * View More opens it for a strategy the marketer is only inspecting — so an
121
+ * explicit id wins. An id that is no longer in the listing falls back to the
122
+ * assigned strategy rather than showing an empty drawer.
123
+ */
124
+ getDrawerStrategy() {
125
+ var r;
126
+ const t = this.currentRecommendationId !== null ? (r = this.blockStates[this.currentRecommendationId]) == null ? void 0 : r.strategyDetailId : "";
127
+ return t ? this.strategies.find((e) => String(e.strategy_id) === t) ?? this.getSelectedStrategy : this.getSelectedStrategy;
128
+ },
91
129
  /**
92
130
  * Proxy getter: delegates to blockStates[currentRecommendationId].filterStatus
93
131
  */
@@ -108,45 +146,111 @@ const L = () => ({
108
146
  },
109
147
  hasValidFilters() {
110
148
  const { filters: t } = this.recommendationConfigs;
111
- return t.length ? t.every((e) => e.isValid) : !1;
149
+ return t.length ? t.every((r) => r.isValid) : !1;
112
150
  },
113
151
  getFilterGroupCount() {
114
152
  const { filters: t } = this.recommendationConfigs;
115
- return t.length ? new Set(t.map((e) => e.filterGroup)).size : 0;
153
+ return t.length ? new Set(t.map((r) => r.filterGroup)).size : 0;
116
154
  },
117
155
  getUniqueFilterGroups() {
118
156
  const { filters: t } = this.recommendationConfigs;
119
- return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
157
+ return [...new Set(t.map((r) => r.filterGroup))].sort((r, e) => r - e);
120
158
  },
121
159
  getActivePredictiveAlgorithms: (t) => {
122
- const e = g(), r = [];
123
- return t.activePredictiveAlgorithms.filter((n) => !w.includes(n)).forEach((n) => {
124
- r.push(...e.filter((c) => c.id === n));
125
- }), r.map((n) => ({
126
- text: n.name,
127
- value: n.key
160
+ const r = w(), e = [];
161
+ return t.activePredictiveAlgorithms.filter((i) => !V.includes(i)).forEach((i) => {
162
+ e.push(...r.filter((s) => s.id === i));
163
+ }), e.map((i) => ({
164
+ text: i.name,
165
+ value: i.key
166
+ }));
167
+ },
168
+ /**
169
+ * Reusable Recommendation Strategies as dropdown options.
170
+ *
171
+ * Unlike algorithms, strategy ids come straight from Smart Recommender —
172
+ * there is no local id→key table to join against.
173
+ */
174
+ /**
175
+ * Options for the picker.
176
+ *
177
+ * A name shared by two strategies is disambiguated with its id. Smart
178
+ * Recommender does not enforce unique names, and an ambiguous label is a
179
+ * problem twice over: the marketer cannot tell the rows apart, and the hover
180
+ * card resolves a row back to a strategy by that very text — so it would
181
+ * describe the wrong one. Fixing the label fixes both.
182
+ */
183
+ getStrategyOptions: (t) => {
184
+ const r = t.strategies.reduce((e, i) => (e[i.name] = (e[i.name] ?? 0) + 1, e), {});
185
+ return t.strategies.map((e) => ({
186
+ text: r[e.name] > 1 ? `${e.name} (${e.strategy_id})` : e.name,
187
+ value: String(e.strategy_id)
128
188
  }));
129
189
  },
130
- getLanguages: (t) => Object.entries(t.languages).map(([e, r]) => ({
131
- text: r,
132
- value: e
190
+ /**
191
+ * The current block's selected strategy, or null when none is selected
192
+ * or the selected id is no longer in the listing.
193
+ *
194
+ * A null return with a non-empty `strategyId` means the strategy was
195
+ * deleted in Smart Recommender after this block was configured — the
196
+ * panel flags that rather than silently clearing the selection.
197
+ */
198
+ getSelectedStrategy() {
199
+ const { strategyId: t } = this.recommendationConfigs;
200
+ return t ? this.strategies.find((r) => String(r.strategy_id) === t) ?? null : null;
201
+ },
202
+ /**
203
+ * Whether the RRS feature is enabled for this editor.
204
+ *
205
+ * Read through a getter so `hasInvalidBlock` can pass the store straight to
206
+ * the required-field descriptors, which need the flag to decide whether a
207
+ * missing strategy blocks the save.
208
+ */
209
+ isRrsEnabled() {
210
+ return U().isFeatureEnabled("reusableRecommendationStrategy");
211
+ },
212
+ /** Strategy ids currently offered for email, for the save-time deleted-strategy check. */
213
+ strategyIds: (t) => t.strategies.map((r) => String(r.strategy_id)),
214
+ /**
215
+ * Whether the listing is trustworthy enough to judge an assigned id against.
216
+ *
217
+ * The bar is a settled, successful fetch — NOT a non-empty result. A partner
218
+ * whose EMAIL listing legitimately comes back empty (every strategy paused,
219
+ * none suitable for the channel) still makes an assigned id deleted, and
220
+ * keying on `strategies.length` silently disabled the save gate for exactly
221
+ * those partners.
222
+ */
223
+ canValidateStrategy: (t) => t.strategiesLoaded && !t.strategiesLoading && !t.strategiesLoadFailed,
224
+ /**
225
+ * True when a strategy id is set but absent from the fetched listing.
226
+ *
227
+ * Shares `canValidateStrategy` so the panel warning and the save gate can
228
+ * never disagree about whether the listing can be judged.
229
+ */
230
+ hasMissingStrategy() {
231
+ const { strategyId: t } = this.recommendationConfigs;
232
+ return !t || !this.canValidateStrategy ? !1 : this.getSelectedStrategy === null;
233
+ },
234
+ getLanguages: (t) => Object.entries(t.languages).map(([r, e]) => ({
235
+ text: e,
236
+ value: r
133
237
  })),
134
- getCurrencySymbolList: (t) => t.currencyList.map((e) => ({
135
- text: e.text,
136
- value: e.text
238
+ getCurrencySymbolList: (t) => t.currencyList.map((r) => ({
239
+ text: r.text,
240
+ value: r.text
137
241
  })),
138
242
  getFilterList() {
139
243
  return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
140
- let e;
141
- return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : C.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
244
+ let r;
245
+ return t.type === "productAttribute" ? r = `product_attributes.${t.attributeName}` : T.includes(t.attributeName) ? r = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : r = t.attributeName, {
142
246
  text: t.displayName,
143
- value: e,
247
+ value: r,
144
248
  type: t.attributeType
145
249
  };
146
250
  });
147
251
  },
148
252
  getSelectedFilterGroup() {
149
- return (t) => [...this.recommendationConfigs.filters].filter((e) => e.filterGroup === t);
253
+ return (t) => [...this.recommendationConfigs.filters].filter((r) => r.filterGroup === t);
150
254
  }
151
255
  },
152
256
  actions: {
@@ -160,7 +264,7 @@ const L = () => ({
160
264
  setCurrentBlock(t) {
161
265
  this.blockStates[t] || (this.blockStates = {
162
266
  ...this.blockStates,
163
- [t]: I()
267
+ [t]: G()
164
268
  }), this.currentRecommendationId = t;
165
269
  },
166
270
  /**
@@ -168,34 +272,34 @@ const L = () => ({
168
272
  * Resets currentRecommendationId if it was the deleted block.
169
273
  */
170
274
  removeBlockState(t) {
171
- const e = t.toString();
172
- if (this.recommendationCampaignUrls[e]) {
173
- const n = { ...this.recommendationCampaignUrls };
174
- delete n[e], this.recommendationCampaignUrls = n;
275
+ const r = t.toString();
276
+ if (this.recommendationCampaignUrls[r]) {
277
+ const i = { ...this.recommendationCampaignUrls };
278
+ delete i[r], this.recommendationCampaignUrls = i;
175
279
  }
176
- const r = { ...this.blockStates };
177
- if (delete r[t], this.blockStates = r, this.currentRecommendationId === t) {
178
- const n = Object.keys(this.blockStates).map(Number);
179
- this.currentRecommendationId = n.length > 0 ? n[0] : null;
280
+ const e = { ...this.blockStates };
281
+ if (delete e[t], this.blockStates = e, this.currentRecommendationId === t) {
282
+ const i = Object.keys(this.blockStates).map(Number);
283
+ this.currentRecommendationId = i.length > 0 ? i[0] : null;
180
284
  }
181
285
  },
182
286
  /**
183
287
  * Deep-clones a block's state and mirrors its campaign URL under a new id.
184
288
  * Invoked from `onCreated` when a duplicate is detected (SD-142352).
185
289
  */
186
- cloneBlockState(t, e) {
187
- const r = this.blockStates[t];
188
- if (r) {
189
- const o = structuredClone(r);
190
- o.recommendationConfigs.id = e, this.blockStates = {
290
+ cloneBlockState(t, r) {
291
+ const e = this.blockStates[t];
292
+ if (e) {
293
+ const a = structuredClone(e);
294
+ a.recommendationConfigs.id = r, this.blockStates = {
191
295
  ...this.blockStates,
192
- [e]: o
296
+ [r]: a
193
297
  };
194
298
  }
195
- const n = t.toString(), c = e.toString(), i = this.recommendationCampaignUrls[n];
196
- i && (this.recommendationCampaignUrls = {
299
+ const i = t.toString(), s = r.toString(), n = this.recommendationCampaignUrls[i];
300
+ n && (this.recommendationCampaignUrls = {
197
301
  ...this.recommendationCampaignUrls,
198
- [c]: i
302
+ [s]: n
199
303
  });
200
304
  },
201
305
  /**
@@ -219,61 +323,62 @@ const L = () => ({
219
323
  * relevant subset and leaves runtime fields (e.g., `recommendedProducts`,
220
324
  * `isInitialized`) alone.
221
325
  */
222
- seedBlockUrlConfig(t, e) {
223
- const r = (o, s) => o === "." || o === "," || o === " " ? o : s, n = {
224
- name: e.currencyCode,
225
- value: e.currencyCode,
226
- symbol: e.currencyCode,
227
- alignment: e.currencyAlignment === "before" ? "0" : "1",
228
- decimalCount: String(e.currencyDecimalCount),
229
- decimalSeparator: r(e.currencyDecimalSeparator, ","),
230
- thousandSeparator: r(e.currencyThousandSeparator, ".")
231
- }, c = !this.blockStates[t], i = c ? I() : this.blockStates[t];
232
- i.recommendationConfigs = {
233
- ...i.recommendationConfigs,
234
- strategy: e.strategy,
235
- language: e.language,
236
- size: e.size,
237
- productIds: e.productIds,
238
- filters: e.filters,
239
- shuffleProducts: e.shuffleProducts,
240
- currencySettings: n
241
- }, c && (this.blockStates = {
326
+ seedBlockUrlConfig(t, r) {
327
+ const e = (a, o) => a === "." || a === "," || a === " " ? a : o, i = {
328
+ name: r.currencyCode,
329
+ value: r.currencyCode,
330
+ symbol: r.currencyCode,
331
+ alignment: r.currencyAlignment === "before" ? "0" : "1",
332
+ decimalCount: String(r.currencyDecimalCount),
333
+ decimalSeparator: e(r.currencyDecimalSeparator, ","),
334
+ thousandSeparator: e(r.currencyThousandSeparator, ".")
335
+ }, s = !this.blockStates[t], n = s ? G() : this.blockStates[t];
336
+ n.recommendationConfigs = {
337
+ ...n.recommendationConfigs,
338
+ strategy: r.strategy,
339
+ strategyId: r.strategyId,
340
+ language: r.language,
341
+ size: r.size,
342
+ productIds: r.productIds,
343
+ filters: r.filters,
344
+ shuffleProducts: r.shuffleProducts,
345
+ currencySettings: i
346
+ }, s && (this.blockStates = {
242
347
  ...this.blockStates,
243
- [t]: i
348
+ [t]: n
244
349
  });
245
350
  },
246
351
  /**
247
352
  * Patches the current block's recommendationConfigs.
248
353
  * Replaces `store.$patch({ recommendationConfigs: { ... } })` pattern.
249
354
  */
250
- patchCurrentBlockConfig(t, e = {}) {
355
+ patchCurrentBlockConfig(t, r = {}) {
251
356
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
252
357
  return;
253
- const r = this.blockStates[this.currentRecommendationId];
254
- r.recommendationConfigs = {
255
- ...r.recommendationConfigs,
358
+ const e = this.blockStates[this.currentRecommendationId];
359
+ e.recommendationConfigs = {
360
+ ...e.recommendationConfigs,
256
361
  ...t,
257
362
  currencySettings: {
258
- ...r.recommendationConfigs.currencySettings,
363
+ ...e.recommendationConfigs.currencySettings,
259
364
  ...t.currencySettings || {}
260
365
  }
261
366
  };
262
- const { triggerRefetch: n = !0 } = e;
263
- n && this.configVersion++;
367
+ const { triggerRefetch: i = !0 } = r;
368
+ i && this.configVersion++;
264
369
  },
265
370
  /**
266
371
  * Creates a filter with the first available attribute and operator pre-selected.
267
372
  */
268
- createDefaultFilter(t, e) {
269
- const [r] = this.getFilterList, [n] = R(r == null ? void 0 : r.type);
373
+ createDefaultFilter(t, r) {
374
+ const [e] = this.getFilterList, [i] = L(e == null ? void 0 : e.type);
270
375
  return {
271
376
  type: "standardFilter",
272
- attribute: (r == null ? void 0 : r.value) ?? "",
273
- operator: (n == null ? void 0 : n.value) ?? "",
377
+ attribute: (e == null ? void 0 : e.value) ?? "",
378
+ operator: (i == null ? void 0 : i.value) ?? "",
274
379
  innerGroupOperator: "*",
275
380
  outerGroupOperator: "*",
276
- filterNumber: e,
381
+ filterNumber: r,
277
382
  filterGroup: t,
278
383
  isValid: !1,
279
384
  value: ""
@@ -303,6 +408,213 @@ const L = () => ({
303
408
  const t = this.blockStates[this.currentRecommendationId];
304
409
  t.filterSnapshot = null, t.filterSelectionDrawerStatus = !1;
305
410
  },
411
+ /**
412
+ * Find a strategy record by id, fetching the listing once if it is not loaded.
413
+ *
414
+ * The endpoint-generate call needs the record, and the save/hydrate path can
415
+ * reach it on a template the marketer never clicked into — where no panel has
416
+ * ever triggered the listing fetch.
417
+ *
418
+ * THROWS when the listing could not be loaded, rather than reporting "not
419
+ * found". The caller caches a not-found as a settled "this strategy has no
420
+ * URL"; doing that for a network blip would strand the block on its old
421
+ * algorithm feed for the rest of the session while the panel still showed
422
+ * the strategy as assigned. A throw leaves the cache key unset, so the next
423
+ * selection or save tries again.
424
+ */
425
+ async _resolveStrategyRecord(t) {
426
+ const r = () => this.strategies.find((i) => String(i.strategy_id) === t) ?? null, e = r();
427
+ if (e || this.strategies.length)
428
+ return e;
429
+ if (await this.fetchRecommendationStrategies(), this.strategiesLoadFailed)
430
+ throw new Error("Recommendation strategy listing is unavailable.");
431
+ return r();
432
+ },
433
+ /**
434
+ * Assign a strategy to the current block.
435
+ *
436
+ * Only patches Pinia: the `configVersion` bump makes the main control persist
437
+ * `strategyId` into the block's node config (see `_persistStoreConfigToNode`),
438
+ * which is the same route filters take. Resolving the URL template here rather
439
+ * than at compile time matters — `buildCampaignUrl` is synchronous and would
440
+ * otherwise silently fall back to the algorithm URL.
441
+ */
442
+ async selectStrategy(t) {
443
+ if (this.recommendationConfigs.strategyId === t)
444
+ return;
445
+ const r = this.currentRecommendationId, e = ++b;
446
+ this.patchCurrentBlockConfig({ strategyId: t }, { triggerRefetch: !0 });
447
+ const i = await this.fetchStrategyUrlTemplate(t), s = this.strategies.find((n) => String(n.strategy_id) === t);
448
+ if (!(this.currentRecommendationId !== r || e !== b)) {
449
+ if (!i) {
450
+ f().showToaster({
451
+ type: g.Error,
452
+ message: u()("discovery-strategy.error-description")
453
+ });
454
+ return;
455
+ }
456
+ f().showToaster({
457
+ type: g.Success,
458
+ message: u()("campaign-builder.variation-strategy-assign", {
459
+ strategyName: (s == null ? void 0 : s.name) ?? t
460
+ })
461
+ });
462
+ }
463
+ },
464
+ /**
465
+ * Duplicate the selected strategy and assign the copy to this block.
466
+ *
467
+ * Runs inline — the marketer never leaves the editor. Two things the web flow
468
+ * does that we deliberately skip: its follow-up `POST /{id}/assign` (that binds
469
+ * a strategy to a campaign *variation*, a concept email blocks do not have —
470
+ * here the assignment is the block's own config), and displaying the copy's
471
+ * name from the response, which only carries the new id. Hence the refetch.
472
+ */
473
+ async duplicateAndAssignStrategy() {
474
+ const t = this.getSelectedStrategy;
475
+ if (!t || this.strategyDuplicating)
476
+ return;
477
+ this.strategyDuplicating = !0;
478
+ const r = this.currentRecommendationId, e = ++b;
479
+ try {
480
+ const i = await c.duplicateStrategy(String(t.strategy_id));
481
+ if (!i) {
482
+ f().showToaster({
483
+ type: g.Error,
484
+ message: u()("discovery-strategy.error-description")
485
+ });
486
+ return;
487
+ }
488
+ if (await this.refreshRecommendationStrategies(), this.currentRecommendationId !== r || e !== b)
489
+ return;
490
+ this.patchCurrentBlockConfig({ strategyId: i }, { triggerRefetch: !0 });
491
+ const s = await this.fetchStrategyUrlTemplate(i);
492
+ if (this.currentRecommendationId !== r || e !== b)
493
+ return;
494
+ if (this.closeStrategyDrawer(), !s) {
495
+ f().showToaster({
496
+ type: g.Error,
497
+ message: u()("discovery-strategy.error-description")
498
+ });
499
+ return;
500
+ }
501
+ f().showToaster({
502
+ type: g.Success,
503
+ message: u()("recommendation-strategies.duplicate-and-assign-text", {
504
+ strategyName: t.name
505
+ })
506
+ });
507
+ } finally {
508
+ this.strategyDuplicating = !1;
509
+ }
510
+ },
511
+ /**
512
+ * Opens the RRS detail drawer for the current block.
513
+ *
514
+ * `strategyId` describes a strategy without assigning it — View More reads
515
+ * the details of an option the marketer is still only considering. Omitted,
516
+ * the drawer describes whatever the block already has.
517
+ *
518
+ * Unlike the filter drawer this takes no snapshot — the drawer is
519
+ * read-only, so there is nothing to revert on close.
520
+ */
521
+ openStrategyDrawer(t = "") {
522
+ this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].strategyDetailId = t, this.blockStates[this.currentRecommendationId].strategyDetailDrawerStatus = !0);
523
+ },
524
+ /**
525
+ * Closes the RRS detail drawer for the current block and drops the campaign
526
+ * list, so reopening on a different strategy never flashes stale rows.
527
+ */
528
+ closeStrategyDrawer() {
529
+ this.clearStrategyCampaigns(), !(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId]) && (this.blockStates[this.currentRecommendationId].strategyDetailId = "", this.blockStates[this.currentRecommendationId].strategyDetailDrawerStatus = !1);
530
+ },
531
+ /** Drops the campaign list and lets the next open refetch. */
532
+ clearStrategyCampaigns() {
533
+ this.strategyCampaigns = [], this.strategyCampaignsLoading = !1, this.strategyCampaignsFetchedFor = null, I += 1;
534
+ },
535
+ /**
536
+ * Resolve and cache a strategy's recommendation-URL template.
537
+ *
538
+ * Keyed by id for caching, but the request itself carries the whole strategy
539
+ * record — the algorithm inside it decides the feed's path and params, so an
540
+ * id alone cannot describe the URL. The listing is therefore a prerequisite;
541
+ * it is fetched on demand for callers (template hydrate) that may run before
542
+ * the panel ever opened.
543
+ *
544
+ * Must be awaited before `buildCampaignUrl` runs for a strategy-driven block,
545
+ * because that builder is synchronous and can only read what is already
546
+ * cached. Safe to call repeatedly: a resolved id short-circuits, and
547
+ * concurrent callers for the same id share one request.
548
+ *
549
+ * Only a *settled* answer is cached. An id the server rejects caches as '',
550
+ * so the block falls back instead of retrying on every compile. A request
551
+ * that merely failed to complete leaves the key unset and resolves to false,
552
+ * so the next selection or save tries again — caching a network blip as "no
553
+ * URL" would strand the block on its old algorithm feed for the session while
554
+ * the panel still showed the strategy as assigned.
555
+ * @returns whether the strategy now has a USABLE url — not merely a cache
556
+ * entry. A rejected id caches as '' to stop the retry storm, but reporting
557
+ * that as success would let the caller confirm an assignment the compiled
558
+ * email cannot honour.
559
+ */
560
+ async fetchStrategyUrlTemplate(t) {
561
+ if (!t)
562
+ return !1;
563
+ if (t in this.strategyUrlTemplates)
564
+ return !!this.strategyUrlTemplates[t];
565
+ const r = y[t];
566
+ if (r)
567
+ return await r, !!this.strategyUrlTemplates[t];
568
+ y[t] = (async () => {
569
+ try {
570
+ const e = await this._resolveStrategyRecord(t);
571
+ if (!e) {
572
+ this.strategyUrlTemplates = { ...this.strategyUrlTemplates, [t]: "" };
573
+ return;
574
+ }
575
+ const i = await c.generateStrategyEndpoint(e, D().variationId);
576
+ this.strategyUrlTemplates = {
577
+ ...this.strategyUrlTemplates,
578
+ [t]: (i == null ? void 0 : i.full_path) ?? ""
579
+ };
580
+ } catch (e) {
581
+ console.error("fetchStrategyUrlTemplate error:", e);
582
+ }
583
+ })();
584
+ try {
585
+ await y[t];
586
+ } finally {
587
+ delete y[t];
588
+ }
589
+ return !!this.strategyUrlTemplates[t];
590
+ },
591
+ /**
592
+ * Wait for every strategy-URL request still in flight.
593
+ *
594
+ * The compiler is synchronous: whatever is not cached by the time it runs
595
+ * compiles as the block's old algorithm URL. Selection and template hydrate
596
+ * both await their own request, but a save fired inside that round trip would
597
+ * still race — so the save path drains the queue first.
598
+ */
599
+ async awaitPendingStrategyUrls() {
600
+ await Promise.all(Object.values(y).filter(Boolean));
601
+ },
602
+ /**
603
+ * Fetch the campaigns a strategy is used in, for the drawer's second tab.
604
+ *
605
+ * Lazy and once-per-strategy: the caller invokes this only while the drawer is
606
+ * open on that tab, and a repeat call for the same id is a no-op. A version
607
+ * counter discards a response that arrives after the user switched strategies,
608
+ * which would otherwise show one strategy's campaigns under another's name.
609
+ */
610
+ async fetchStrategyCampaigns(t) {
611
+ if (!t || this.strategyCampaignsFetchedFor === t)
612
+ return;
613
+ const r = ++I;
614
+ this.strategyCampaignsLoading = !0;
615
+ const e = await c.fetchStrategyCampaigns(t, u());
616
+ r === I && (this.strategyCampaigns = e, this.strategyCampaignsLoading = !1, this.strategyCampaignsFetchedFor = t);
617
+ },
306
618
  /**
307
619
  * Cancels the filter selection drawer and reverts filters
308
620
  * to the snapshot taken when the drawer was opened.
@@ -318,43 +630,82 @@ const L = () => ({
318
630
  // ====================================================================
319
631
  async fetchRecommendationCreateData() {
320
632
  if (!this.activePredictiveAlgorithms.length) {
321
- if (m) {
322
- await m;
633
+ if (h) {
634
+ await h;
323
635
  return;
324
636
  }
325
- m = (async () => {
637
+ h = (async () => {
326
638
  const {
327
639
  activePredictiveAlgorithms: t,
328
- languages: e,
329
- currencies: r
330
- } = await h.fetchRecommendationCreateData();
331
- if (this.activePredictiveAlgorithms = t, this.languages = e, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
332
- const n = this.blockStates[this.currentRecommendationId];
333
- n.filterStatus = !!n.recommendationConfigs.filters.length;
640
+ languages: r,
641
+ currencies: e
642
+ } = await c.fetchRecommendationCreateData();
643
+ if (this.activePredictiveAlgorithms = t, this.languages = r, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
644
+ const i = this.blockStates[this.currentRecommendationId];
645
+ i.filterStatus = !!i.recommendationConfigs.filters.length;
646
+ }
647
+ this.currencyList = e;
648
+ })();
649
+ try {
650
+ await h;
651
+ } finally {
652
+ h = null;
653
+ }
654
+ }
655
+ },
656
+ /**
657
+ * Fetch the partner's Reusable Recommendation Strategies for the EMAIL channel.
658
+ *
659
+ * Same cache-guard + in-flight dedupe shape as fetchRecommendationCreateData.
660
+ * A failure leaves `strategies` empty so the dropdown renders empty rather
661
+ * than blocking the panel — the block keeps working on its algorithm.
662
+ */
663
+ async fetchRecommendationStrategies() {
664
+ if (!this.strategies.length) {
665
+ if (d) {
666
+ await d;
667
+ return;
668
+ }
669
+ this.strategiesLoading = !0, d = (async () => {
670
+ try {
671
+ this.strategies = await c.fetchRecommendationStrategies() ?? [], this.strategiesLoadFailed = !1;
672
+ } catch {
673
+ this.strategies = [], this.strategiesLoadFailed = !0;
674
+ } finally {
675
+ this.strategiesLoading = !1, this.strategiesLoaded = !0;
334
676
  }
335
- this.currencyList = r;
336
677
  })();
337
678
  try {
338
- await m;
679
+ await d;
339
680
  } finally {
340
- m = null;
681
+ d = null;
341
682
  }
342
683
  }
343
684
  },
685
+ /**
686
+ * Refetch the listing, ignoring the once-per-session cache.
687
+ *
688
+ * Needed after Duplicate and Assign: the copy exists server-side but not in
689
+ * `strategies`, so without this the panel would read the freshly assigned id
690
+ * back as a deleted strategy.
691
+ */
692
+ async refreshRecommendationStrategies() {
693
+ this.strategies = [], this.strategiesLoaded = !1, d = null, await this.fetchRecommendationStrategies();
694
+ },
344
695
  async fetchRecommendationFilters() {
345
696
  if (!Object.keys(this.filterList).length) {
346
- if (u) {
347
- await u;
697
+ if (p) {
698
+ await p;
348
699
  return;
349
700
  }
350
- u = (async () => {
351
- const t = await h.fetchRecommendationFilters();
701
+ p = (async () => {
702
+ const t = await c.fetchRecommendationFilters();
352
703
  this.filterList = t;
353
704
  })();
354
705
  try {
355
- await u;
706
+ await p;
356
707
  } finally {
357
- u = null;
708
+ p = null;
358
709
  }
359
710
  }
360
711
  },
@@ -369,59 +720,59 @@ const L = () => ({
369
720
  deleteFilterGroup(t) {
370
721
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
371
722
  return;
372
- const e = this.blockStates[this.currentRecommendationId], r = e.recommendationConfigs.filters.filter((i) => i.filterGroup !== t), n = [...new Set(r.map((i) => i.filterGroup))].sort((i, o) => i - o), c = new Map(n.map((i, o) => [i, o + 1]));
373
- e.recommendationConfigs.filters = r.map((i) => ({
374
- ...i,
375
- filterGroup: c.get(i.filterGroup) ?? i.filterGroup
723
+ const r = this.blockStates[this.currentRecommendationId], e = r.recommendationConfigs.filters.filter((n) => n.filterGroup !== t), i = [...new Set(e.map((n) => n.filterGroup))].sort((n, a) => n - a), s = new Map(i.map((n, a) => [n, a + 1]));
724
+ r.recommendationConfigs.filters = e.map((n) => ({
725
+ ...n,
726
+ filterGroup: s.get(n.filterGroup) ?? n.filterGroup
376
727
  }));
377
728
  },
378
729
  updateFilter(t) {
379
730
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
380
731
  return;
381
- const e = this.blockStates[this.currentRecommendationId], r = e.recommendationConfigs.filters.findIndex((n) => n.filterNumber === t.filterNumber && n.filterGroup === t.filterGroup);
382
- if (r !== -1) {
383
- const n = [...e.recommendationConfigs.filters];
384
- n[r] = {
732
+ const r = this.blockStates[this.currentRecommendationId], e = r.recommendationConfigs.filters.findIndex((i) => i.filterNumber === t.filterNumber && i.filterGroup === t.filterGroup);
733
+ if (e !== -1) {
734
+ const i = [...r.recommendationConfigs.filters];
735
+ i[e] = {
385
736
  ...t,
386
- isValid: N(t)
387
- }, e.recommendationConfigs.filters = n;
737
+ isValid: z(t)
738
+ }, r.recommendationConfigs.filters = i;
388
739
  }
389
740
  },
390
741
  deleteFilter(t) {
391
742
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
392
743
  return;
393
- const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters].findIndex((n) => n.filterNumber === t.filterNumber && n.filterGroup === t.filterGroup);
394
- if (r !== -1) {
395
- let n = [...e.recommendationConfigs.filters];
396
- if (n.splice(r, 1), n.some((i) => i.filterGroup === t.filterGroup)) {
397
- let i = 1;
398
- n = n.map((o) => o.filterGroup === t.filterGroup ? { ...o, filterNumber: i++ } : o);
744
+ const r = this.blockStates[this.currentRecommendationId], e = [...r.recommendationConfigs.filters].findIndex((i) => i.filterNumber === t.filterNumber && i.filterGroup === t.filterGroup);
745
+ if (e !== -1) {
746
+ let i = [...r.recommendationConfigs.filters];
747
+ if (i.splice(e, 1), i.some((n) => n.filterGroup === t.filterGroup)) {
748
+ let n = 1;
749
+ i = i.map((a) => a.filterGroup === t.filterGroup ? { ...a, filterNumber: n++ } : a);
399
750
  } else {
400
- const i = [...new Set(n.map((s) => s.filterGroup))].sort((s, a) => s - a), o = new Map(i.map((s, a) => [s, a + 1]));
401
- n = n.map((s) => ({
402
- ...s,
403
- filterGroup: o.get(s.filterGroup) ?? s.filterGroup
751
+ const n = [...new Set(i.map((o) => o.filterGroup))].sort((o, l) => o - l), a = new Map(n.map((o, l) => [o, l + 1]));
752
+ i = i.map((o) => ({
753
+ ...o,
754
+ filterGroup: a.get(o.filterGroup) ?? o.filterGroup
404
755
  }));
405
756
  }
406
- e.recommendationConfigs.filters = n;
757
+ r.recommendationConfigs.filters = i;
407
758
  }
408
759
  },
409
760
  addFilter(t) {
410
761
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
411
762
  return;
412
- const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters], c = r.filter(
413
- (o) => o.filterGroup === t.filterGroup
414
- ).length + 1, i = r.findLastIndex((o) => o.filterGroup === t.filterGroup);
415
- i !== -1 ? r.splice(i + 1, 0, {
763
+ const r = this.blockStates[this.currentRecommendationId], e = [...r.recommendationConfigs.filters], s = e.filter(
764
+ (a) => a.filterGroup === t.filterGroup
765
+ ).length + 1, n = e.findLastIndex((a) => a.filterGroup === t.filterGroup);
766
+ n !== -1 ? e.splice(n + 1, 0, {
416
767
  ...t,
417
- filterNumber: c
418
- }) : r.push({
768
+ filterNumber: s
769
+ }) : e.push({
419
770
  ...t,
420
- filterNumber: c
421
- }), e.recommendationConfigs.filters = r;
771
+ filterNumber: s
772
+ }), r.recommendationConfigs.filters = e;
422
773
  },
423
774
  generateFilterQuery() {
424
- return b(this.recommendationConfigs.filters);
775
+ return v(this.recommendationConfigs.filters);
425
776
  },
426
777
  /**
427
778
  * Validation-only check invoked at save-CTA time. Defined as an action
@@ -429,55 +780,88 @@ const L = () => ({
429
780
  * every block's recommendationConfigs across user edits.
430
781
  */
431
782
  hasInvalidBlock() {
432
- return Object.values(this.blockStates).some((t) => !x(t.recommendationConfigs, this));
783
+ return Object.values(this.blockStates).some((t) => !M(t.recommendationConfigs, this));
433
784
  },
434
785
  // ====================================================================
435
786
  // Per-Block Product Fetching
436
787
  // ====================================================================
437
788
  async fetchRecommendationProducts() {
438
789
  if (!(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])) {
439
- if (d) {
440
- await d;
790
+ if (S) {
791
+ await S;
441
792
  return;
442
793
  }
443
- d = this._doFetchProducts();
794
+ S = this._doFetchProducts();
444
795
  try {
445
- await d;
796
+ await S;
446
797
  } finally {
447
- d = null;
798
+ S = null;
448
799
  }
449
800
  }
450
801
  },
451
802
  async _doFetchProducts() {
452
- var p;
453
- const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = r.filters.filter((l) => l.isValid), c = b(n), i = ((p = g().find((l) => l.key === r.strategy)) == null ? void 0 : p.path) || "", o = G(), s = parseInt(r.size) || 6, a = {
454
- locale: r.language,
455
- currency: r.currencySettings.value,
456
- partnerName: o.partnerName,
457
- size: r.size,
803
+ var C, k;
804
+ const t = this.currentRecommendationId, r = this.blockStates[t];
805
+ r.recommendationConfigs.strategyId && await this.fetchStrategyUrlTemplate(r.recommendationConfigs.strategyId);
806
+ const e = ((C = this.blockStates[t]) == null ? void 0 : C.recommendationConfigs) ?? r.recommendationConfigs, i = e.strategyId ? this.strategyUrlTemplates[e.strategyId] : "", s = E(
807
+ i ?? "",
808
+ O({
809
+ language: e.language,
810
+ currencyCode: e.currencySettings.value,
811
+ size: e.size,
812
+ strategyId: e.strategyId
813
+ }),
814
+ "preview"
815
+ );
816
+ if (s) {
817
+ await this._applyFetchedProducts(
818
+ t,
819
+ () => c.fetchRecommendationProductsByUrl(s)
820
+ );
821
+ return;
822
+ }
823
+ const n = e.filters.filter((R) => R.isValid), a = v(n), o = ((k = w().find((R) => R.key === e.strategy)) == null ? void 0 : k.path) || "", l = D(), m = {
824
+ locale: e.language,
825
+ currency: e.currencySettings.value,
826
+ partnerName: l.partnerName,
827
+ size: e.size,
458
828
  details: !0,
459
- campaignId: o.variationId
829
+ campaignId: l.variationId
460
830
  };
461
- r.strategy === "manualMerchandising" ? a.productId = r.productIds.join(",") : r.strategy === "similarViewed" && (a.productId = "{itemId}"), r.strategy === "userBased" && (a.userId = "{user_id}"), c && (a.filter = c), r.shuffleProducts && (a.shuffle = !0), Object.assign(
462
- a,
463
- v(o.partnerName, r.strategy)
831
+ e.strategy === "manualMerchandising" ? m.productId = e.productIds.join(",") : e.strategy === "similarViewed" && (m.productId = "{itemId}"), e.strategy === "userBased" && (m.userId = "{user_id}"), a && (m.filter = a), e.shuffleProducts && (m.shuffle = !0), Object.assign(
832
+ m,
833
+ A(l.partnerName, e.strategy)
834
+ ), await this._applyFetchedProducts(
835
+ t,
836
+ () => c.fetchRecommendationProducts(o, m)
464
837
  );
465
- let f;
838
+ },
839
+ /**
840
+ * Runs a product fetch and writes the result to `blockId`.
841
+ *
842
+ * Shared by the strategy and algorithm paths so the size padding/clipping —
843
+ * which the in-place card renderer depends on for its element-count match —
844
+ * cannot drift between them.
845
+ */
846
+ async _applyFetchedProducts(t, r) {
847
+ var s;
848
+ const e = parseInt(((s = this.blockStates[t]) == null ? void 0 : s.recommendationConfigs.size) ?? "") || 6;
849
+ let i;
466
850
  try {
467
- f = await h.fetchRecommendationProducts(i, a);
851
+ i = await r();
468
852
  } catch {
469
- f = [];
853
+ i = [];
470
854
  }
471
855
  if (this.blockStates[t]) {
472
- const l = f.length > 0 ? f : S(s);
473
- l.length < s ? this.blockStates[t].recommendationProducts = [
474
- ...l,
475
- ...S(s - l.length)
476
- ] : l.length > s ? this.blockStates[t].recommendationProducts = l.slice(0, s) : this.blockStates[t].recommendationProducts = l;
856
+ const n = i.length > 0 ? i : F(e);
857
+ n.length < e ? this.blockStates[t].recommendationProducts = [
858
+ ...n,
859
+ ...F(e - n.length)
860
+ ] : n.length > e ? this.blockStates[t].recommendationProducts = n.slice(0, e) : this.blockStates[t].recommendationProducts = n;
477
861
  }
478
862
  }
479
863
  }
480
864
  });
481
865
  export {
482
- $ as useRecommendationExtensionStore
866
+ ot as useRecommendationExtensionStore
483
867
  };