@useinsider/guido 3.18.2-beta.c3e75f6 → 3.18.2-beta.dfce34a

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 (49) hide show
  1. package/dist/components/Guido.vue.js +3 -3
  2. package/dist/components/Guido.vue2.js +30 -30
  3. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue.js +13 -11
  4. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue2.js +76 -52
  5. package/dist/components/organisms/extensions/recommendation/RemoveStrategyModal.vue2.js +23 -37
  6. package/dist/components/organisms/header/EditorActions.vue.js +4 -4
  7. package/dist/components/organisms/header/EditorActions.vue2.js +25 -23
  8. package/dist/components/organisms/onboarding/OnboardingWrapper.vue2.js +20 -18
  9. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue.js +21 -0
  10. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue2.js +47 -0
  11. package/dist/composables/useActionsApi.js +47 -52
  12. package/dist/composables/useCodeEditorApi.js +9 -23
  13. package/dist/composables/useCustomInterfaceAppearance.js +56 -46
  14. package/dist/composables/useHtmlValidator.js +87 -82
  15. package/dist/composables/useRecommendation.js +40 -35
  16. package/dist/composables/useStripo.js +30 -30
  17. package/dist/composables/useTranslations.js +2 -3
  18. package/dist/composables/useVersionHistoryApi.js +24 -33
  19. package/dist/enums/extensions/recommendationBlock.js +38 -31
  20. package/dist/extensions/Blocks/Recommendation/block.js +61 -42
  21. package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +21 -20
  22. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +40 -35
  23. package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +84 -52
  24. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +74 -74
  25. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +254 -140
  26. package/dist/extensions/Blocks/Recommendation/utils/nodeConfigWriter.js +26 -16
  27. package/dist/extensions/Blocks/Recommendation/utils/strategyCommit.js +24 -0
  28. package/dist/extensions/Blocks/Recommendation/validation/requiredFields.js +21 -12
  29. package/dist/guido.css +1 -1
  30. package/dist/src/components/organisms/onboarding/RecommendationStrategyOnboarding.vue.d.ts +2 -0
  31. package/dist/src/enums/extensions/recommendationBlock.d.ts +2 -0
  32. package/dist/src/extensions/Blocks/Recommendation/block.d.ts +10 -0
  33. package/dist/src/extensions/Blocks/Recommendation/constants/defaultConfig.d.ts +1 -0
  34. package/dist/src/extensions/Blocks/Recommendation/constants/index.d.ts +1 -1
  35. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +2 -0
  36. package/dist/src/extensions/Blocks/Recommendation/controls/main/strategy.d.ts +9 -0
  37. package/dist/src/extensions/Blocks/Recommendation/controls/main/utils.d.ts +7 -2
  38. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +86 -0
  39. package/dist/src/extensions/Blocks/Recommendation/utils/strategyCommit.d.ts +14 -0
  40. package/dist/src/extensions/Blocks/Recommendation/validation/requiredFields.d.ts +4 -0
  41. package/dist/src/stores/onboarding.d.ts +377 -0
  42. package/dist/static/styles/components/recommendation-strategy.css.js +34 -4
  43. package/dist/static/styles/components/skeleton.css.js +23 -0
  44. package/dist/stores/onboarding.js +41 -36
  45. package/dist/stores/unsubscribe.js +3 -3
  46. package/dist/utils/templatePreparation.js +55 -55
  47. package/package.json +5 -5
  48. package/dist/src/utils/stripoEditorApi.d.ts +0 -4
  49. package/dist/utils/stripoEditorApi.js +0 -11
@@ -1,22 +1,23 @@
1
- import { getRecommendationFeedSourceMaps as b, getOperatorOptions as G, PriceAttributes as v } from "../../../../enums/extensions/recommendationBlock.js";
2
- import { useRecommendationApi as D } from "../../../../services/recommendationApi.js";
3
- import { useConfigStore as F } from "../../../../stores/config.js";
4
- import { defineStore as P } from "pinia";
1
+ import { getRecommendationFeedSourceMaps as v, strategyEditPath as D, URLS as _, getOperatorOptions as A, PriceAttributes as N } from "../../../../enums/extensions/recommendationBlock.js";
2
+ import { useRecommendationApi as U } from "../../../../services/recommendationApi.js";
3
+ import { useConfigStore as k } from "../../../../stores/config.js";
4
+ import { defineStore as E } from "pinia";
5
5
  import "../constants/selectors.js";
6
- import { DEFAULT_MOBILE_CARDS_IN_ROW as _, DEFAULT_CARDS_IN_ROW as U } from "../constants/layout.js";
7
- import { DEFAULT_NODE_CONFIG as N, EXCLUDED_ALGORITHM_IDS as L } from "../constants/defaultConfig.js";
8
- import { getDefaultProducts as R } from "../templates/utils.js";
9
- import { generateCompleteFilterQuery as k } from "../utils/filterUtil.js";
10
- import { getPartnerRecommendationParams as x } from "../utils/partnerCustomizations.js";
11
- import { sanitizeProductIds as A } from "../utils/productIds.js";
12
- import { isFilterValid as B } from "../validation/filterSchema.js";
13
- import { isConfigValid as M } from "../validation/requiredFields.js";
14
- const u = D();
15
- let f = null, g = null, h = null, S = null;
16
- const p = /* @__PURE__ */ new Map();
17
- function w() {
6
+ import { DEFAULT_MOBILE_CARDS_IN_ROW as L, DEFAULT_CARDS_IN_ROW as B } from "../constants/layout.js";
7
+ import { DEFAULT_NODE_CONFIG as x, EXCLUDED_ALGORITHM_IDS as M } from "../constants/defaultConfig.js";
8
+ import { getDefaultProducts as F } from "../templates/utils.js";
9
+ import { generateCompleteFilterQuery as w } from "../utils/filterUtil.js";
10
+ import { getPartnerRecommendationParams as O } from "../utils/partnerCustomizations.js";
11
+ import { sanitizeProductIds as z } from "../utils/productIds.js";
12
+ import { isFilterValid as T } from "../validation/filterSchema.js";
13
+ import { getInvalidFields as V, isConfigValid as j } from "../validation/requiredFields.js";
14
+ const f = U();
15
+ let S = null, p = null, y = null, I = "", R = null, b = 0;
16
+ const g = /* @__PURE__ */ new Map();
17
+ let l = null;
18
+ function G() {
18
19
  return {
19
- cardsInRow: U,
20
+ cardsInRow: B,
20
21
  currencySettings: {
21
22
  name: "USD",
22
23
  value: "USD",
@@ -30,13 +31,17 @@ function w() {
30
31
  productIds: [],
31
32
  id: 1,
32
33
  language: "en_US",
33
- mobileCardsInRow: _,
34
+ mobileCardsInRow: L,
34
35
  mobileLayoutEnabled: !1,
35
36
  orientation: "grid",
36
37
  recommendedProducts: [],
37
38
  sendProductRequestFlag: !1,
38
39
  shuffleProducts: !1,
39
- strategy: "mostPopular",
40
+ // With RRS on, a block that has not been synced yet must not claim an algorithm:
41
+ // that default is what reached buildCampaignUrl and baked a most-popular feed URL
42
+ // for a block the user never configured (SD-149916). Legacy blocks still get their real
43
+ // algorithm from the node-config sync or the save-time seed.
44
+ strategy: k().isFeatureEnabled("reusableRecommendationStrategy") ? "" : "mostPopular",
40
45
  strategyId: "",
41
46
  textTrimming: !0,
42
47
  unresponsive: !1,
@@ -46,9 +51,9 @@ function w() {
46
51
  priceHideIfSameAsDiscounted: !1
47
52
  };
48
53
  }
49
- function C() {
54
+ function P() {
50
55
  return {
51
- recommendationConfigs: w(),
56
+ recommendationConfigs: G(),
52
57
  recommendationProducts: [],
53
58
  filterStatus: !1,
54
59
  filterSelectionDrawerStatus: !1,
@@ -60,7 +65,7 @@ function C() {
60
65
  syncedConfigFingerprint: null
61
66
  };
62
67
  }
63
- const z = () => ({
68
+ const $ = () => ({
64
69
  recommendationCampaignUrls: {},
65
70
  activePredictiveAlgorithms: [],
66
71
  languages: {},
@@ -69,11 +74,14 @@ const z = () => ({
69
74
  strategies: [],
70
75
  strategiesLoading: !1,
71
76
  strategyUrlTemplates: {},
77
+ strategiesFetched: !1,
78
+ strategiesSettled: !1,
72
79
  blockStates: {},
73
80
  currentRecommendationId: null,
81
+ strategyPanelOpen: !1,
74
82
  configVersion: 0
75
- }), Y = P("guidoRecommendationExtension", {
76
- state: () => z(),
83
+ }), nt = E("guidoRecommendationExtension", {
84
+ state: () => $(),
77
85
  getters: {
78
86
  // ====================================================================
79
87
  // Proxy Getters — Backward Compatible Access to Current Block State
@@ -83,7 +91,7 @@ const z = () => ({
83
91
  * This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
84
92
  */
85
93
  recommendationConfigs(t) {
86
- return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : w();
94
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : G();
87
95
  },
88
96
  /**
89
97
  * Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
@@ -138,12 +146,12 @@ const z = () => ({
138
146
  return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
139
147
  },
140
148
  getActivePredictiveAlgorithms: (t) => {
141
- const e = b(), r = [];
142
- return t.activePredictiveAlgorithms.filter((n) => !L.includes(n)).forEach((n) => {
143
- r.push(...e.filter((s) => s.id === n));
144
- }), r.map((n) => ({
145
- text: n.name,
146
- value: n.key
149
+ const e = v(), r = [];
150
+ return t.activePredictiveAlgorithms.filter((i) => !M.includes(i)).forEach((i) => {
151
+ r.push(...e.filter((s) => s.id === i));
152
+ }), r.map((i) => ({
153
+ text: i.name,
154
+ value: i.key
147
155
  }));
148
156
  },
149
157
  getLanguages: (t) => Object.entries(t.languages).map(([e, r]) => ({
@@ -157,7 +165,7 @@ const z = () => ({
157
165
  getFilterList() {
158
166
  return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
159
167
  let e;
160
- return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : v.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
168
+ return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : N.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
161
169
  text: t.displayName,
162
170
  value: e,
163
171
  type: t.attributeType
@@ -179,7 +187,7 @@ const z = () => ({
179
187
  setCurrentBlock(t) {
180
188
  this.blockStates[t] || (this.blockStates = {
181
189
  ...this.blockStates,
182
- [t]: C()
190
+ [t]: P()
183
191
  }), this.currentRecommendationId = t;
184
192
  },
185
193
  /**
@@ -189,32 +197,36 @@ const z = () => ({
189
197
  removeBlockState(t) {
190
198
  const e = t.toString();
191
199
  if (this.recommendationCampaignUrls[e]) {
192
- const n = { ...this.recommendationCampaignUrls };
193
- delete n[e], this.recommendationCampaignUrls = n;
200
+ const i = { ...this.recommendationCampaignUrls };
201
+ delete i[e], this.recommendationCampaignUrls = i;
194
202
  }
195
203
  const r = { ...this.blockStates };
196
204
  if (delete r[t], this.blockStates = r, this.currentRecommendationId === t) {
197
- const n = Object.keys(this.blockStates).map(Number);
198
- this.currentRecommendationId = n.length > 0 ? n[0] : null;
205
+ const i = Object.keys(this.blockStates).map(Number);
206
+ this.currentRecommendationId = i.length > 0 ? i[0] : null;
199
207
  }
200
208
  },
201
209
  /**
202
210
  * Deep-clones a block's state and mirrors its campaign URL under a new id.
203
211
  * Invoked from `onCreated` when a duplicate is detected (SD-142352).
212
+ *
213
+ * The clone keeps the source's data but drops its "already fetched / already
214
+ * synced" claims for strategy blocks, so it fetches again like a fresh drop
215
+ * (SD-149916).
204
216
  */
205
217
  cloneBlockState(t, e) {
206
218
  const r = this.blockStates[t];
207
219
  if (r) {
208
220
  const o = structuredClone(r);
209
- o.recommendationConfigs.id = e, this.blockStates = {
221
+ o.recommendationConfigs.id = e, o.recommendationConfigs.strategyId && k().isFeatureEnabled("reusableRecommendationStrategy") && (o.isInitialized = !1, o.syncedConfigFingerprint = null), this.blockStates = {
210
222
  ...this.blockStates,
211
223
  [e]: o
212
224
  };
213
225
  }
214
- const n = t.toString(), s = e.toString(), i = this.recommendationCampaignUrls[n];
215
- i && (this.recommendationCampaignUrls = {
226
+ const i = t.toString(), s = e.toString(), n = this.recommendationCampaignUrls[i];
227
+ n && (this.recommendationCampaignUrls = {
216
228
  ...this.recommendationCampaignUrls,
217
- [s]: i
229
+ [s]: n
218
230
  });
219
231
  },
220
232
  /**
@@ -239,7 +251,7 @@ const z = () => ({
239
251
  * `isInitialized`) alone.
240
252
  */
241
253
  seedBlockUrlConfig(t, e) {
242
- const r = (o, c) => o === "." || o === "," || o === " " ? o : c, n = {
254
+ const r = (o, a) => o === "." || o === "," || o === " " ? o : a, i = {
243
255
  name: e.currencyCode,
244
256
  value: e.currencyCode,
245
257
  symbol: e.currencySymbol,
@@ -247,9 +259,9 @@ const z = () => ({
247
259
  decimalCount: String(e.currencyDecimalCount),
248
260
  decimalSeparator: r(e.currencyDecimalSeparator, ","),
249
261
  thousandSeparator: r(e.currencyThousandSeparator, ".")
250
- }, s = !this.blockStates[t], i = s ? C() : this.blockStates[t];
251
- i.recommendationConfigs = {
252
- ...i.recommendationConfigs,
262
+ }, s = !this.blockStates[t], n = s ? P() : this.blockStates[t];
263
+ n.recommendationConfigs = {
264
+ ...n.recommendationConfigs,
253
265
  strategy: e.strategy,
254
266
  strategyId: e.strategyId,
255
267
  language: e.language,
@@ -257,10 +269,10 @@ const z = () => ({
257
269
  productIds: e.productIds,
258
270
  filters: e.filters,
259
271
  shuffleProducts: e.shuffleProducts,
260
- currencySettings: n
272
+ currencySettings: i
261
273
  }, s && (this.blockStates = {
262
274
  ...this.blockStates,
263
- [t]: i
275
+ [t]: n
264
276
  });
265
277
  },
266
278
  /**
@@ -279,18 +291,18 @@ const z = () => ({
279
291
  ...t.currencySettings || {}
280
292
  }
281
293
  };
282
- const { triggerRefetch: n = !0 } = e;
283
- n && this.configVersion++;
294
+ const { triggerRefetch: i = !0 } = e;
295
+ i && this.configVersion++;
284
296
  },
285
297
  /**
286
298
  * Creates a filter with the first available attribute and operator pre-selected.
287
299
  */
288
300
  createDefaultFilter(t, e) {
289
- const [r] = this.getFilterList, [n] = G(r == null ? void 0 : r.type);
301
+ const [r] = this.getFilterList, [i] = A(r == null ? void 0 : r.type);
290
302
  return {
291
303
  type: "standardFilter",
292
304
  attribute: (r == null ? void 0 : r.value) ?? "",
293
- operator: (n == null ? void 0 : n.value) ?? "",
305
+ operator: (i == null ? void 0 : i.value) ?? "",
294
306
  innerGroupOperator: "*",
295
307
  outerGroupOperator: "*",
296
308
  filterNumber: e,
@@ -346,33 +358,112 @@ const z = () => ({
346
358
  this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].removeStrategyModalStatus = !1);
347
359
  },
348
360
  async fetchStrategies() {
349
- if (S) {
350
- await S;
361
+ for (; R; )
362
+ try {
363
+ await R;
364
+ } catch {
365
+ }
366
+ if (this.strategiesFetched)
351
367
  return;
352
- }
353
- S = (async () => {
368
+ const t = b, e = (async () => {
354
369
  this.strategiesLoading = !0;
355
370
  try {
356
- this.strategies = await u.fetchRecommendationStrategies();
371
+ const r = await f.fetchRecommendationStrategies();
372
+ if (t !== b)
373
+ return;
374
+ this.strategies = r, this.strategiesFetched = !0;
357
375
  } finally {
358
- this.strategiesLoading = !1;
376
+ this.strategiesLoading = !1, t === b && (this.strategiesSettled = !0);
359
377
  }
360
378
  })();
379
+ R = e;
361
380
  try {
362
- await S;
381
+ await e;
363
382
  } finally {
364
- S = null;
383
+ R = null;
365
384
  }
366
385
  },
386
+ /**
387
+ * Clears what a block inherited from the id it arrived holding, so the panel fetches for it.
388
+ * A re-dropped saved module can keep the source's id — and its "already fetched" entry —
389
+ * while the strategy's product count has moved on (SD-149916).
390
+ */
391
+ resetArrivedBlock(t) {
392
+ const e = this.blockStates[t];
393
+ e != null && e.recommendationConfigs.strategyId && (e.isInitialized = !1, e.syncedConfigFingerprint = null, this.invalidateStrategies(), this.fetchStrategies().catch(() => {
394
+ }));
395
+ },
396
+ invalidateStrategies() {
397
+ this.strategiesFetched = !1, this.strategiesSettled = !1, b += 1;
398
+ },
399
+ /**
400
+ * Opens a Smart Recommender strategy page in a new tab and refreshes the list every time
401
+ * the user comes back. `visibilitychange` only: a `focus` listener would also fire when
402
+ * focus merely returns from the Stripo canvas iframe to the panel, refetching and
403
+ * regenerating the canvas while the user never left the editor (SD-149916).
404
+ *
405
+ * Deliberately NOT one-shot: the strategy tab stays open, so a user can create a second
406
+ * strategy in it and come back again — a listener that disarmed on the first return would
407
+ * leave that one missing from the list. It disarms on the first return AFTER that tab is
408
+ * closed instead, so an ordinary alt-tab later in the session costs nothing. Armed only
409
+ * once the user actually opens a strategy page, so anyone who never does pays nothing.
410
+ * @returns whether a tab actually opened, so callers can skip work that assumes one did
411
+ */
412
+ _armStrategyPageReturn(t) {
413
+ const e = window.open(t, "_blank");
414
+ if (!e)
415
+ return !1;
416
+ e.opener = null, l == null || l();
417
+ let r = !1;
418
+ const i = () => {
419
+ document.hidden || r || (e != null && e.closed && (l == null || l()), r = !0, this.invalidateStrategies(), this.fetchStrategies().then(() => this._refreshAssignedStrategyProducts()).catch(() => {
420
+ }).finally(() => {
421
+ r = !1;
422
+ }));
423
+ };
424
+ return document.addEventListener("visibilitychange", i), l = () => {
425
+ document.removeEventListener("visibilitychange", i), l = null;
426
+ }, !0;
427
+ },
428
+ /**
429
+ * Reloads the products of the block the user is on, so an edit made in the strategy tab
430
+ * lands on the canvas instead of waiting for the assign drawer to be reopened.
431
+ *
432
+ * Only the selected block: the canvas regeneration is driven off the active block's
433
+ * product array, so refreshing an unselected block would fetch without redrawing it.
434
+ */
435
+ async _refreshAssignedStrategyProducts() {
436
+ var e;
437
+ const t = this.currentRecommendationId;
438
+ t === null || !((e = this.blockStates[t]) != null && e.recommendationConfigs.strategyId) || await this.fetchRecommendationProducts();
439
+ },
440
+ /** Lets the editor tear the return listeners down when it unmounts. */
441
+ disposeStrategyPageReturn() {
442
+ l == null || l();
443
+ },
444
+ /**
445
+ * Opens Smart Recommender's strategy creation page. Every return refreshes the list so the
446
+ * new strategy is offered; the user picks it themselves.
447
+ */
448
+ openStrategyCreation() {
449
+ return this._armStrategyPageReturn(_.RECOMMENDATION_STRATEGY_CREATE_PATH);
450
+ },
451
+ /**
452
+ * Opens the strategy's edit page. Every return refreshes the list so the block reflects the
453
+ * edited configuration.
454
+ */
455
+ openStrategyEdit(t) {
456
+ return this._armStrategyPageReturn(D(t));
457
+ },
367
458
  async resolveStrategyUrlTemplate(t, e) {
368
- const r = typeof t.updated_at == "string" ? t.updated_at : "", n = r ? `${t.strategy_id}:${r}:${e}` : "", s = n ? this.strategyUrlTemplates[n] : void 0;
459
+ const r = typeof t.updated_at == "string" ? t.updated_at : "", i = r ? `${t.strategy_id}:${r}:${e}` : "", s = i ? this.strategyUrlTemplates[i] : void 0;
369
460
  if (s)
370
461
  return s;
371
- const { full_path: i = "" } = await u.generateStrategyEndpoint(t, e) ?? {};
372
- return n && i && (this.strategyUrlTemplates = {
462
+ const { full_path: n = "" } = await f.generateStrategyEndpoint(t, e) ?? {};
463
+ return i && n && (this.strategyUrlTemplates = {
373
464
  ...this.strategyUrlTemplates,
374
- [n]: i
375
- }), i;
465
+ [i]: n
466
+ }), n;
376
467
  },
377
468
  assignStrategy(t) {
378
469
  this.patchCurrentBlockConfig({
@@ -381,50 +472,54 @@ const z = () => ({
381
472
  });
382
473
  },
383
474
  removeStrategy() {
384
- this.patchCurrentBlockConfig({ strategyId: "", size: N.size });
475
+ this.patchCurrentBlockConfig({
476
+ strategyId: "",
477
+ strategy: "",
478
+ size: x.size
479
+ });
385
480
  },
386
481
  // ====================================================================
387
482
  // Shared Data Fetching (fetched once, used by all blocks)
388
483
  // ====================================================================
389
484
  async fetchRecommendationCreateData() {
390
485
  if (!this.activePredictiveAlgorithms.length) {
391
- if (f) {
392
- await f;
486
+ if (S) {
487
+ await S;
393
488
  return;
394
489
  }
395
- f = (async () => {
490
+ S = (async () => {
396
491
  const {
397
492
  activePredictiveAlgorithms: t,
398
493
  languages: e,
399
494
  currencies: r
400
- } = await u.fetchRecommendationCreateData();
495
+ } = await f.fetchRecommendationCreateData();
401
496
  if (this.activePredictiveAlgorithms = t, this.languages = e, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
402
- const n = this.blockStates[this.currentRecommendationId];
403
- n.filterStatus = !!n.recommendationConfigs.filters.length;
497
+ const i = this.blockStates[this.currentRecommendationId];
498
+ i.filterStatus = !!i.recommendationConfigs.filters.length;
404
499
  }
405
500
  this.currencyList = r;
406
501
  })();
407
502
  try {
408
- await f;
503
+ await S;
409
504
  } finally {
410
- f = null;
505
+ S = null;
411
506
  }
412
507
  }
413
508
  },
414
509
  async fetchRecommendationFilters() {
415
510
  if (!Object.keys(this.filterList).length) {
416
- if (g) {
417
- await g;
511
+ if (p) {
512
+ await p;
418
513
  return;
419
514
  }
420
- g = (async () => {
421
- const t = await u.fetchRecommendationFilters();
515
+ p = (async () => {
516
+ const t = await f.fetchRecommendationFilters();
422
517
  this.filterList = t;
423
518
  })();
424
519
  try {
425
- await g;
520
+ await p;
426
521
  } finally {
427
- g = null;
522
+ p = null;
428
523
  }
429
524
  }
430
525
  },
@@ -439,41 +534,41 @@ const z = () => ({
439
534
  deleteFilterGroup(t) {
440
535
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
441
536
  return;
442
- 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), s = new Map(n.map((i, o) => [i, o + 1]));
443
- e.recommendationConfigs.filters = r.map((i) => ({
444
- ...i,
445
- filterGroup: s.get(i.filterGroup) ?? i.filterGroup
537
+ const e = this.blockStates[this.currentRecommendationId], r = e.recommendationConfigs.filters.filter((n) => n.filterGroup !== t), i = [...new Set(r.map((n) => n.filterGroup))].sort((n, o) => n - o), s = new Map(i.map((n, o) => [n, o + 1]));
538
+ e.recommendationConfigs.filters = r.map((n) => ({
539
+ ...n,
540
+ filterGroup: s.get(n.filterGroup) ?? n.filterGroup
446
541
  }));
447
542
  },
448
543
  updateFilter(t) {
449
544
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
450
545
  return;
451
- const e = this.blockStates[this.currentRecommendationId], r = e.recommendationConfigs.filters.findIndex((n) => n.filterNumber === t.filterNumber && n.filterGroup === t.filterGroup);
546
+ const e = this.blockStates[this.currentRecommendationId], r = e.recommendationConfigs.filters.findIndex((i) => i.filterNumber === t.filterNumber && i.filterGroup === t.filterGroup);
452
547
  if (r !== -1) {
453
- const n = [...e.recommendationConfigs.filters];
454
- n[r] = {
548
+ const i = [...e.recommendationConfigs.filters];
549
+ i[r] = {
455
550
  ...t,
456
- isValid: B(t)
457
- }, e.recommendationConfigs.filters = n;
551
+ isValid: T(t)
552
+ }, e.recommendationConfigs.filters = i;
458
553
  }
459
554
  },
460
555
  deleteFilter(t) {
461
556
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
462
557
  return;
463
- const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters].findIndex((n) => n.filterNumber === t.filterNumber && n.filterGroup === t.filterGroup);
558
+ const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters].findIndex((i) => i.filterNumber === t.filterNumber && i.filterGroup === t.filterGroup);
464
559
  if (r !== -1) {
465
- let n = [...e.recommendationConfigs.filters];
466
- if (n.splice(r, 1), n.some((i) => i.filterGroup === t.filterGroup)) {
467
- let i = 1;
468
- n = n.map((o) => o.filterGroup === t.filterGroup ? { ...o, filterNumber: i++ } : o);
560
+ let i = [...e.recommendationConfigs.filters];
561
+ if (i.splice(r, 1), i.some((n) => n.filterGroup === t.filterGroup)) {
562
+ let n = 1;
563
+ i = i.map((o) => o.filterGroup === t.filterGroup ? { ...o, filterNumber: n++ } : o);
469
564
  } else {
470
- const i = [...new Set(n.map((c) => c.filterGroup))].sort((c, a) => c - a), o = new Map(i.map((c, a) => [c, a + 1]));
471
- n = n.map((c) => ({
472
- ...c,
473
- filterGroup: o.get(c.filterGroup) ?? c.filterGroup
565
+ const n = [...new Set(i.map((a) => a.filterGroup))].sort((a, u) => a - u), o = new Map(n.map((a, u) => [a, u + 1]));
566
+ i = i.map((a) => ({
567
+ ...a,
568
+ filterGroup: o.get(a.filterGroup) ?? a.filterGroup
474
569
  }));
475
570
  }
476
- e.recommendationConfigs.filters = n;
571
+ e.recommendationConfigs.filters = i;
477
572
  }
478
573
  },
479
574
  addFilter(t) {
@@ -481,8 +576,8 @@ const z = () => ({
481
576
  return;
482
577
  const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters], s = r.filter(
483
578
  (o) => o.filterGroup === t.filterGroup
484
- ).length + 1, i = r.findLastIndex((o) => o.filterGroup === t.filterGroup);
485
- i !== -1 ? r.splice(i + 1, 0, {
579
+ ).length + 1, n = r.findLastIndex((o) => o.filterGroup === t.filterGroup);
580
+ n !== -1 ? r.splice(n + 1, 0, {
486
581
  ...t,
487
582
  filterNumber: s
488
583
  }) : r.push({
@@ -491,7 +586,7 @@ const z = () => ({
491
586
  }), e.recommendationConfigs.filters = r;
492
587
  },
493
588
  generateFilterQuery() {
494
- return k(this.recommendationConfigs.filters);
589
+ return w(this.recommendationConfigs.filters);
495
590
  },
496
591
  /**
497
592
  * Validation-only check invoked at save-CTA time. Defined as an action
@@ -499,83 +594,102 @@ const z = () => ({
499
594
  * every block's recommendationConfigs across user edits.
500
595
  */
501
596
  hasInvalidBlock() {
502
- return Object.values(this.blockStates).some((t) => !M(t.recommendationConfigs, this));
597
+ return Object.values(this.blockStates).some((t) => !j(t.recommendationConfigs, this));
598
+ },
599
+ /**
600
+ * Which required fields are missing across the design's blocks, so the save
601
+ * toaster can name the cause instead of always showing the generic message.
602
+ */
603
+ invalidBlockFields() {
604
+ return Object.values(this.blockStates).flatMap((t) => V(t.recommendationConfigs, this));
503
605
  },
504
606
  // ====================================================================
505
607
  // Per-Block Product Fetching
506
608
  // ====================================================================
507
609
  async fetchRecommendationProducts() {
508
- if (!(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])) {
509
- if (h) {
510
- await h;
511
- return;
512
- }
513
- h = this._doFetchProducts();
514
- try {
515
- await h;
516
- } finally {
517
- h = null;
518
- }
610
+ if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
611
+ return;
612
+ const t = [
613
+ this.currentRecommendationId,
614
+ this.configVersion,
615
+ b
616
+ ].join(":");
617
+ if (y && I === t) {
618
+ await y;
619
+ return;
620
+ }
621
+ I = t, y = this._doFetchProducts();
622
+ try {
623
+ await y;
624
+ } finally {
625
+ I === t && (y = null, I = "");
519
626
  }
520
627
  },
521
628
  async _doFetchProducts() {
522
- var d;
523
- const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = F();
524
- if (r.strategyId && n.isFeatureEnabled("reusableRecommendationStrategy")) {
629
+ var h;
630
+ const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, i = k();
631
+ if (r.strategyId && i.isFeatureEnabled("reusableRecommendationStrategy")) {
525
632
  await this._doFetchStrategyProducts(t);
526
633
  return;
527
634
  }
528
- const s = r.filters.filter((m) => m.isValid), i = k(s), o = ((d = b().find((m) => m.key === r.strategy)) == null ? void 0 : d.path) || "", c = parseInt(r.size) || 6, a = {
635
+ const s = (g.get(t) ?? 0) + 1;
636
+ g.set(t, s);
637
+ const n = parseInt(r.size) || 6;
638
+ if (!r.strategy) {
639
+ this._setBlockProducts(t, [], n);
640
+ return;
641
+ }
642
+ const o = r.filters.filter((d) => d.isValid), a = w(o), u = ((h = v().find((d) => d.key === r.strategy)) == null ? void 0 : h.path) || "", c = {
529
643
  locale: r.language,
530
644
  currency: r.currencySettings.value,
531
- partnerName: n.partnerName,
645
+ partnerName: i.partnerName,
532
646
  size: r.size,
533
647
  details: !0,
534
- campaignId: n.variationId
648
+ campaignId: i.variationId
535
649
  };
536
- r.strategy === "manualMerchandising" ? a.productId = A(r.productIds) : r.strategy === "similarViewed" && (a.productId = "{itemId}"), r.strategy === "userBased" && (a.userId = "{user_id}"), i && (a.filter = i), r.shuffleProducts && (a.shuffle = !0), Object.assign(
537
- a,
538
- x(n.partnerName, r.strategy)
650
+ r.strategy === "manualMerchandising" ? c.productId = z(r.productIds) : r.strategy === "similarViewed" && (c.productId = "{itemId}"), r.strategy === "userBased" && (c.userId = "{user_id}"), a && (c.filter = a), r.shuffleProducts && (c.shuffle = !0), Object.assign(
651
+ c,
652
+ O(i.partnerName, r.strategy)
539
653
  );
540
- let l;
654
+ let m;
541
655
  try {
542
- l = await u.fetchRecommendationProducts(o, a);
656
+ m = await f.fetchRecommendationProducts(u, c);
543
657
  } catch {
544
- l = [];
658
+ m = [];
545
659
  }
546
- this._setBlockProducts(t, l, c);
660
+ g.get(t) === s && this._setBlockProducts(t, m, n);
547
661
  },
548
662
  async _doFetchStrategyProducts(t) {
549
- const e = (p.get(t) ?? 0) + 1;
550
- p.set(t, e);
663
+ const e = (g.get(t) ?? 0) + 1;
664
+ g.set(t, e);
551
665
  const { recommendationConfigs: r } = this.blockStates[t];
552
- let n = [], s = parseInt(r.size) || 6;
666
+ let i = [], s = parseInt(r.size) || 6;
553
667
  try {
554
668
  await this.fetchStrategies();
555
- const i = this.strategies.find(
669
+ const n = this.strategies.find(
556
670
  (o) => String(o.strategy_id) === r.strategyId
557
671
  );
558
- if (i) {
559
- s = parseInt(String(i.size ?? "")) || s;
560
- const o = r.currencySettings.value, a = (await this.resolveStrategyUrlTemplate(i, o)).replaceAll("{locale}", r.language).replaceAll("{currency}", o), l = a.indexOf("?"), d = l === -1 ? a : a.slice(0, l), m = (l === -1 ? "" : a.slice(l + 1)).split("&").filter((I) => I && !I.startsWith("user_id=")), y = m.length ? `${d}?${m.join("&")}` : d;
561
- y && (n = await u.fetchRecommendationProductsByUrl(y));
672
+ if (n) {
673
+ s = parseInt(String(n.size ?? "")) || s;
674
+ const o = r.currencySettings.value, u = (await this.resolveStrategyUrlTemplate(n, o)).replaceAll("{locale}", r.language).replaceAll("{currency}", o), c = u.indexOf("?"), m = c === -1 ? u : u.slice(0, c), h = (c === -1 ? "" : u.slice(c + 1)).split("&").filter((C) => C && !C.startsWith("user_id=")), d = h.length ? `${m}?${h.join("&")}` : m;
675
+ d && (i = await f.fetchRecommendationProductsByUrl(d));
562
676
  }
563
677
  } catch {
564
- n = [];
678
+ i = [];
565
679
  }
566
- p.get(t) === e && this._setBlockProducts(t, n, s);
680
+ g.get(t) === e && this._setBlockProducts(t, i, s);
567
681
  },
568
682
  _setBlockProducts(t, e, r) {
569
683
  if (!this.blockStates[t])
570
684
  return;
571
- const n = e.length > 0 ? e : R(r);
685
+ const i = e.length > 0 ? e : F(r);
572
686
  this.blockStates[t].recommendationProducts = [
573
- ...n.slice(0, r),
574
- ...R(Math.max(0, r - n.length))
687
+ ...i.slice(0, r),
688
+ ...F(Math.max(0, r - i.length))
575
689
  ];
576
690
  }
577
691
  }
578
692
  });
579
693
  export {
580
- Y as useRecommendationExtensionStore
694
+ nt as useRecommendationExtensionStore
581
695
  };