@useinsider/guido 3.12.0-beta.840207a → 3.12.0-beta.86e34da

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 (54) hide show
  1. package/dist/@types/config/schemas.js +6 -6
  2. package/dist/components/organisms/email-preview/desktop-preview/DesktopPreview.vue.js +1 -1
  3. package/dist/components/organisms/email-preview/desktop-preview/DesktopPreview.vue2.js +8 -8
  4. package/dist/components/organisms/email-preview/mobile-preview/ContentView.vue.js +5 -5
  5. package/dist/components/organisms/email-preview/mobile-preview/ContentView.vue2.js +10 -10
  6. package/dist/components/organisms/extensions/recommendation/StrategyDetailDrawer.vue.js +8 -6
  7. package/dist/components/organisms/extensions/recommendation/StrategyDetailDrawer.vue2.js +98 -71
  8. package/dist/composables/usePreviewInteractionGuard.js +36 -11
  9. package/dist/composables/useRecommendation.js +63 -78
  10. package/dist/composables/useRecommendationPreview.js +61 -60
  11. package/dist/enums/extensions/filteringV2.js +1017 -1
  12. package/dist/enums/extensions/recommendationBlock.js +34 -19
  13. package/dist/extensions/Blocks/Recommendation/constants/controlIds.js +1 -1
  14. package/dist/extensions/Blocks/Recommendation/controls/blockBackground/index.js +10 -0
  15. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +24 -20
  16. package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +245 -106
  17. package/dist/extensions/Blocks/Recommendation/extension.js +23 -21
  18. package/dist/extensions/Blocks/Recommendation/iconsRegistry.js +17 -3
  19. package/dist/extensions/Blocks/Recommendation/recommendation.css.js +257 -0
  20. package/dist/extensions/Blocks/Recommendation/settingsPanel.js +41 -40
  21. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +310 -117
  22. package/dist/extensions/Blocks/Recommendation/utils/strategyNavigation.js +8 -0
  23. package/dist/extensions/Blocks/Recommendation/utils/strategySummary.js +93 -8
  24. package/dist/extensions/Blocks/Recommendation/utils/strategyUrl.js +43 -0
  25. package/dist/extensions/Blocks/Recommendation/validation/requiredFields.js +18 -16
  26. package/dist/extensions/Blocks/Unsubscribe/utils/constants.js +3 -2
  27. package/dist/extensions/Blocks/common-control.js +2 -1
  28. package/dist/extensions/Blocks/controlFactories.js +125 -75
  29. package/dist/guido.css +1 -1
  30. package/dist/services/recommendationApi.js +90 -45
  31. package/dist/services/stripoApi.js +18 -18
  32. package/dist/src/@types/config/schemas.d.ts +2 -2
  33. package/dist/src/composables/usePreviewInteractionGuard.d.ts +1 -1
  34. package/dist/src/enums/extensions/recommendationBlock.d.ts +25 -2
  35. package/dist/src/extensions/Blocks/Recommendation/constants/controlIds.d.ts +1 -0
  36. package/dist/src/extensions/Blocks/Recommendation/controls/blockBackground/index.d.ts +14 -0
  37. package/dist/src/extensions/Blocks/Recommendation/controls/index.d.ts +1 -0
  38. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +7 -3
  39. package/dist/src/extensions/Blocks/Recommendation/controls/main/strategy.d.ts +59 -45
  40. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +301 -4
  41. package/dist/src/extensions/Blocks/Recommendation/utils/strategyNavigation.d.ts +10 -0
  42. package/dist/src/extensions/Blocks/Recommendation/utils/strategyUrl.d.ts +51 -0
  43. package/dist/src/extensions/Blocks/Recommendation/validation/requiredFields.d.ts +5 -0
  44. package/dist/src/extensions/Blocks/Unsubscribe/utils/constants.d.ts +1 -0
  45. package/dist/src/extensions/Blocks/common-control.d.ts +2 -1
  46. package/dist/src/extensions/Blocks/controlFactories.d.ts +36 -0
  47. package/dist/src/mock/api/recommendation-strategies.d.ts +2 -0
  48. package/dist/src/services/recommendationApi.d.ts +3 -1
  49. package/dist/src/utils/urlSchemes.d.ts +6 -0
  50. package/dist/static/styles/base.css.js +0 -15
  51. package/dist/static/styles/components/base-input.css.js +6 -7
  52. package/dist/utils/templatePreparation.js +56 -47
  53. package/dist/utils/urlSchemes.js +4 -0
  54. package/package.json +1 -1
@@ -1,23 +1,27 @@
1
- import { useConfig as D } from "../../../../composables/useConfig.js";
2
- import { useTranslations as F } from "../../../../composables/useTranslations.js";
3
- import { getRecommendationFeedSourceMaps as y, getOperatorOptions as w, PriceAttributes as G } from "../../../../enums/extensions/recommendationBlock.js";
4
- import { useRecommendationApi as P } from "../../../../services/recommendationApi.js";
5
- import { useConfigStore as v } from "../../../../stores/config.js";
6
- import { defineStore as L } 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 F } from "../../../../stores/config.js";
7
+ import { useToasterStore as f } from "../../../../stores/toaster.js";
8
+ import { defineStore as N } from "pinia";
7
9
  import "../constants/selectors.js";
8
- import { DEFAULT_MOBILE_CARDS_IN_ROW as U, DEFAULT_CARDS_IN_ROW as N } from "../constants/layout.js";
9
- import { EXCLUDED_ALGORITHM_IDS as x } from "../constants/defaultConfig.js";
10
- import { getDefaultProducts as I } from "../templates/utils.js";
11
- import { generateCompleteFilterQuery as R } from "../utils/filterUtil.js";
12
- import { getPartnerRecommendationParams as _ } from "../utils/partnerCustomizations.js";
13
- import { isFilterValid as O } from "../validation/filterSchema.js";
14
- import { isConfigValid as A } from "../validation/requiredFields.js";
15
- const m = P();
16
- let u = null, d = null, f = null, g = null, S = 0;
17
- const p = {};
18
- function C() {
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 A } from "../constants/defaultConfig.js";
12
+ import { getDefaultProducts as D } from "../templates/utils.js";
13
+ import { generateCompleteFilterQuery as v } from "../utils/filterUtil.js";
14
+ import { getPartnerRecommendationParams as E } from "../utils/partnerCustomizations.js";
15
+ import { resolveStrategyUrl as O, buildStrategyUrlValues as V } 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, C = 0;
20
+ const y = {};
21
+ let b = 0;
22
+ function P() {
19
23
  return {
20
- cardsInRow: N,
24
+ cardsInRow: B,
21
25
  currencySettings: {
22
26
  name: "USD",
23
27
  value: "USD",
@@ -31,7 +35,7 @@ function C() {
31
35
  productIds: [],
32
36
  id: 1,
33
37
  language: "en_US",
34
- mobileCardsInRow: U,
38
+ mobileCardsInRow: x,
35
39
  mobileLayoutEnabled: !1,
36
40
  orientation: "grid",
37
41
  recommendedProducts: [],
@@ -47,9 +51,9 @@ function C() {
47
51
  priceHideIfSameAsDiscounted: !1
48
52
  };
49
53
  }
50
- function k() {
54
+ function G() {
51
55
  return {
52
- recommendationConfigs: C(),
56
+ recommendationConfigs: P(),
53
57
  recommendationProducts: [],
54
58
  filterStatus: !1,
55
59
  filterSelectionDrawerStatus: !1,
@@ -59,7 +63,7 @@ function k() {
59
63
  filterSnapshot: null
60
64
  };
61
65
  }
62
- const B = () => ({
66
+ const j = () => ({
63
67
  recommendationCampaignUrls: {},
64
68
  activePredictiveAlgorithms: [],
65
69
  languages: {},
@@ -71,11 +75,13 @@ const B = () => ({
71
75
  strategyCampaignsLoading: !1,
72
76
  strategyCampaignsFetchedFor: null,
73
77
  strategyUrlTemplates: {},
78
+ strategiesLoadFailed: !1,
79
+ strategyDuplicating: !1,
74
80
  blockStates: {},
75
81
  currentRecommendationId: null,
76
82
  configVersion: 0
77
- }), Y = L("guidoRecommendationExtension", {
78
- state: () => B(),
83
+ }), ot = N("guidoRecommendationExtension", {
84
+ state: () => j(),
79
85
  getters: {
80
86
  // ====================================================================
81
87
  // Proxy Getters — Backward Compatible Access to Current Block State
@@ -85,7 +91,7 @@ const B = () => ({
85
91
  * This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
86
92
  */
87
93
  recommendationConfigs(t) {
88
- return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : C();
94
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : P();
89
95
  },
90
96
  /**
91
97
  * Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
@@ -136,9 +142,9 @@ const B = () => ({
136
142
  return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
137
143
  },
138
144
  getActivePredictiveAlgorithms: (t) => {
139
- const e = y(), r = [];
140
- return t.activePredictiveAlgorithms.filter((i) => !x.includes(i)).forEach((i) => {
141
- r.push(...e.filter((a) => a.id === i));
145
+ const e = w(), r = [];
146
+ return t.activePredictiveAlgorithms.filter((i) => !A.includes(i)).forEach((i) => {
147
+ r.push(...e.filter((s) => s.id === i));
142
148
  }), r.map((i) => ({
143
149
  text: i.name,
144
150
  value: i.key
@@ -174,8 +180,15 @@ const B = () => ({
174
180
  * missing strategy blocks the save.
175
181
  */
176
182
  isRrsEnabled() {
177
- return D().isFeatureEnabled("reusableRecommendationStrategy");
183
+ return U().isFeatureEnabled("reusableRecommendationStrategy");
178
184
  },
185
+ /** Strategy ids currently offered for email, for the save-time deleted-strategy check. */
186
+ strategyIds: (t) => t.strategies.map((e) => String(e.strategy_id)),
187
+ /**
188
+ * Whether the listing is trustworthy enough to judge an assigned id against.
189
+ * Mid-fetch it is empty, and every assigned id would look deleted.
190
+ */
191
+ canValidateStrategy: (t) => !t.strategiesLoading && t.strategies.length > 0,
179
192
  /**
180
193
  * True when a strategy id is set but absent from the fetched listing.
181
194
  *
@@ -196,7 +209,7 @@ const B = () => ({
196
209
  getFilterList() {
197
210
  return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
198
211
  let e;
199
- return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : G.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
212
+ return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : T.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
200
213
  text: t.displayName,
201
214
  value: e,
202
215
  type: t.attributeType
@@ -218,7 +231,7 @@ const B = () => ({
218
231
  setCurrentBlock(t) {
219
232
  this.blockStates[t] || (this.blockStates = {
220
233
  ...this.blockStates,
221
- [t]: k()
234
+ [t]: G()
222
235
  }), this.currentRecommendationId = t;
223
236
  },
224
237
  /**
@@ -244,16 +257,16 @@ const B = () => ({
244
257
  cloneBlockState(t, e) {
245
258
  const r = this.blockStates[t];
246
259
  if (r) {
247
- const o = structuredClone(r);
248
- o.recommendationConfigs.id = e, this.blockStates = {
260
+ const a = structuredClone(r);
261
+ a.recommendationConfigs.id = e, this.blockStates = {
249
262
  ...this.blockStates,
250
- [e]: o
263
+ [e]: a
251
264
  };
252
265
  }
253
- const i = t.toString(), a = e.toString(), n = this.recommendationCampaignUrls[i];
266
+ const i = t.toString(), s = e.toString(), n = this.recommendationCampaignUrls[i];
254
267
  n && (this.recommendationCampaignUrls = {
255
268
  ...this.recommendationCampaignUrls,
256
- [a]: n
269
+ [s]: n
257
270
  });
258
271
  },
259
272
  /**
@@ -278,7 +291,7 @@ const B = () => ({
278
291
  * `isInitialized`) alone.
279
292
  */
280
293
  seedBlockUrlConfig(t, e) {
281
- const r = (o, s) => o === "." || o === "," || o === " " ? o : s, i = {
294
+ const r = (a, o) => a === "." || a === "," || a === " " ? a : o, i = {
282
295
  name: e.currencyCode,
283
296
  value: e.currencyCode,
284
297
  symbol: e.currencyCode,
@@ -286,7 +299,7 @@ const B = () => ({
286
299
  decimalCount: String(e.currencyDecimalCount),
287
300
  decimalSeparator: r(e.currencyDecimalSeparator, ","),
288
301
  thousandSeparator: r(e.currencyThousandSeparator, ".")
289
- }, a = !this.blockStates[t], n = a ? k() : this.blockStates[t];
302
+ }, s = !this.blockStates[t], n = s ? G() : this.blockStates[t];
290
303
  n.recommendationConfigs = {
291
304
  ...n.recommendationConfigs,
292
305
  strategy: e.strategy,
@@ -297,7 +310,7 @@ const B = () => ({
297
310
  filters: e.filters,
298
311
  shuffleProducts: e.shuffleProducts,
299
312
  currencySettings: i
300
- }, a && (this.blockStates = {
313
+ }, s && (this.blockStates = {
301
314
  ...this.blockStates,
302
315
  [t]: n
303
316
  });
@@ -325,7 +338,7 @@ const B = () => ({
325
338
  * Creates a filter with the first available attribute and operator pre-selected.
326
339
  */
327
340
  createDefaultFilter(t, e) {
328
- const [r] = this.getFilterList, [i] = w(r == null ? void 0 : r.type);
341
+ const [r] = this.getFilterList, [i] = L(r == null ? void 0 : r.type);
329
342
  return {
330
343
  type: "standardFilter",
331
344
  attribute: (r == null ? void 0 : r.value) ?? "",
@@ -362,6 +375,106 @@ const B = () => ({
362
375
  const t = this.blockStates[this.currentRecommendationId];
363
376
  t.filterSnapshot = null, t.filterSelectionDrawerStatus = !1;
364
377
  },
378
+ /**
379
+ * Find a strategy record by id, fetching the listing once if it is not loaded.
380
+ *
381
+ * The endpoint-generate call needs the record, and the save/hydrate path can
382
+ * reach it on a template the marketer never clicked into — where no panel has
383
+ * ever triggered the listing fetch.
384
+ *
385
+ * THROWS when the listing could not be loaded, rather than reporting "not
386
+ * found". The caller caches a not-found as a settled "this strategy has no
387
+ * URL"; doing that for a network blip would strand the block on its old
388
+ * algorithm feed for the rest of the session while the panel still showed
389
+ * the strategy as assigned. A throw leaves the cache key unset, so the next
390
+ * selection or save tries again.
391
+ */
392
+ async _resolveStrategyRecord(t) {
393
+ const e = () => this.strategies.find((i) => String(i.strategy_id) === t) ?? null, r = e();
394
+ if (r || this.strategies.length)
395
+ return r;
396
+ if (await this.fetchRecommendationStrategies(), this.strategiesLoadFailed)
397
+ throw new Error("Recommendation strategy listing is unavailable.");
398
+ return e();
399
+ },
400
+ /**
401
+ * Assign a strategy to the current block.
402
+ *
403
+ * Only patches Pinia: the `configVersion` bump makes the main control persist
404
+ * `strategyId` into the block's node config (see `_persistStoreConfigToNode`),
405
+ * which is the same route filters take. Resolving the URL template here rather
406
+ * than at compile time matters — `buildCampaignUrl` is synchronous and would
407
+ * otherwise silently fall back to the algorithm URL.
408
+ */
409
+ async selectStrategy(t) {
410
+ if (this.recommendationConfigs.strategyId === t)
411
+ return;
412
+ const e = this.currentRecommendationId, r = ++b;
413
+ this.patchCurrentBlockConfig({ strategyId: t }, { triggerRefetch: !0 });
414
+ const i = await this.fetchStrategyUrlTemplate(t), s = this.strategies.find((n) => String(n.strategy_id) === t);
415
+ if (!(this.currentRecommendationId !== e || r !== b)) {
416
+ if (!i) {
417
+ f().showToaster({
418
+ type: g.Error,
419
+ message: u()("discovery-strategy.error-description")
420
+ });
421
+ return;
422
+ }
423
+ f().showToaster({
424
+ type: g.Success,
425
+ message: u()("campaign-builder.variation-strategy-assign", {
426
+ strategyName: (s == null ? void 0 : s.name) ?? t
427
+ })
428
+ });
429
+ }
430
+ },
431
+ /**
432
+ * Duplicate the selected strategy and assign the copy to this block.
433
+ *
434
+ * Runs inline — the marketer never leaves the editor. Two things the web flow
435
+ * does that we deliberately skip: its follow-up `POST /{id}/assign` (that binds
436
+ * a strategy to a campaign *variation*, a concept email blocks do not have —
437
+ * here the assignment is the block's own config), and displaying the copy's
438
+ * name from the response, which only carries the new id. Hence the refetch.
439
+ */
440
+ async duplicateAndAssignStrategy() {
441
+ const t = this.getSelectedStrategy;
442
+ if (!t || this.strategyDuplicating)
443
+ return;
444
+ this.strategyDuplicating = !0;
445
+ const e = this.currentRecommendationId, r = ++b;
446
+ try {
447
+ const i = await c.duplicateStrategy(String(t.strategy_id));
448
+ if (!i) {
449
+ f().showToaster({
450
+ type: g.Error,
451
+ message: u()("discovery-strategy.error-description")
452
+ });
453
+ return;
454
+ }
455
+ if (await this.refreshRecommendationStrategies(), this.currentRecommendationId !== e || r !== b)
456
+ return;
457
+ this.patchCurrentBlockConfig({ strategyId: i }, { triggerRefetch: !0 });
458
+ const s = await this.fetchStrategyUrlTemplate(i);
459
+ if (this.currentRecommendationId !== e || r !== b)
460
+ return;
461
+ if (this.closeStrategyDrawer(), !s) {
462
+ f().showToaster({
463
+ type: g.Error,
464
+ message: u()("discovery-strategy.error-description")
465
+ });
466
+ return;
467
+ }
468
+ f().showToaster({
469
+ type: g.Success,
470
+ message: u()("recommendation-strategies.duplicate-and-assign-text", {
471
+ strategyName: t.name
472
+ })
473
+ });
474
+ } finally {
475
+ this.strategyDuplicating = !1;
476
+ }
477
+ },
365
478
  /**
366
479
  * Opens the RRS detail drawer for the current block.
367
480
  *
@@ -380,37 +493,74 @@ const B = () => ({
380
493
  },
381
494
  /** Drops the campaign list and lets the next open refetch. */
382
495
  clearStrategyCampaigns() {
383
- this.strategyCampaigns = [], this.strategyCampaignsLoading = !1, this.strategyCampaignsFetchedFor = null, S += 1;
496
+ this.strategyCampaigns = [], this.strategyCampaignsLoading = !1, this.strategyCampaignsFetchedFor = null, C += 1;
384
497
  },
385
498
  /**
386
499
  * Resolve and cache a strategy's recommendation-URL template.
387
500
  *
501
+ * Keyed by id for caching, but the request itself carries the whole strategy
502
+ * record — the algorithm inside it decides the feed's path and params, so an
503
+ * id alone cannot describe the URL. The listing is therefore a prerequisite;
504
+ * it is fetched on demand for callers (template hydrate) that may run before
505
+ * the panel ever opened.
506
+ *
388
507
  * Must be awaited before `buildCampaignUrl` runs for a strategy-driven block,
389
508
  * because that builder is synchronous and can only read what is already
390
- * cached. Safe to call repeatedly: a cached id (including one cached as the
391
- * empty string after a failure) short-circuits, and concurrent callers for
392
- * the same id share one request.
509
+ * cached. Safe to call repeatedly: a resolved id short-circuits, and
510
+ * concurrent callers for the same id share one request.
511
+ *
512
+ * Only a *settled* answer is cached. An id the server rejects caches as '',
513
+ * so the block falls back instead of retrying on every compile. A request
514
+ * that merely failed to complete leaves the key unset and resolves to false,
515
+ * so the next selection or save tries again — caching a network blip as "no
516
+ * URL" would strand the block on its old algorithm feed for the session while
517
+ * the panel still showed the strategy as assigned.
518
+ * @returns whether the strategy now has a USABLE url — not merely a cache
519
+ * entry. A rejected id caches as '' to stop the retry storm, but reporting
520
+ * that as success would let the caller confirm an assignment the compiled
521
+ * email cannot honour.
393
522
  */
394
523
  async fetchStrategyUrlTemplate(t) {
395
- if (!t || t in this.strategyUrlTemplates)
396
- return;
397
- const e = p[t];
398
- if (e) {
399
- await e;
400
- return;
401
- }
402
- p[t] = (async () => {
403
- const r = await m.fetchStrategyRecommendationUrl(t);
404
- this.strategyUrlTemplates = {
405
- ...this.strategyUrlTemplates,
406
- [t]: (r == null ? void 0 : r.full_path) ?? ""
407
- };
524
+ if (!t)
525
+ return !1;
526
+ if (t in this.strategyUrlTemplates)
527
+ return !!this.strategyUrlTemplates[t];
528
+ const e = y[t];
529
+ if (e)
530
+ return await e, !!this.strategyUrlTemplates[t];
531
+ y[t] = (async () => {
532
+ try {
533
+ const r = await this._resolveStrategyRecord(t);
534
+ if (!r) {
535
+ this.strategyUrlTemplates = { ...this.strategyUrlTemplates, [t]: "" };
536
+ return;
537
+ }
538
+ const i = await c.generateStrategyEndpoint(r, F().variationId);
539
+ this.strategyUrlTemplates = {
540
+ ...this.strategyUrlTemplates,
541
+ [t]: (i == null ? void 0 : i.full_path) ?? ""
542
+ };
543
+ } catch (r) {
544
+ console.error("fetchStrategyUrlTemplate error:", r);
545
+ }
408
546
  })();
409
547
  try {
410
- await p[t];
548
+ await y[t];
411
549
  } finally {
412
- delete p[t];
550
+ delete y[t];
413
551
  }
552
+ return !!this.strategyUrlTemplates[t];
553
+ },
554
+ /**
555
+ * Wait for every strategy-URL request still in flight.
556
+ *
557
+ * The compiler is synchronous: whatever is not cached by the time it runs
558
+ * compiles as the block's old algorithm URL. Selection and template hydrate
559
+ * both await their own request, but a save fired inside that round trip would
560
+ * still race — so the save path drains the queue first.
561
+ */
562
+ async awaitPendingStrategyUrls() {
563
+ await Promise.all(Object.values(y).filter(Boolean));
414
564
  },
415
565
  /**
416
566
  * Fetch the campaigns a strategy is used in, for the drawer's second tab.
@@ -423,10 +573,10 @@ const B = () => ({
423
573
  async fetchStrategyCampaigns(t) {
424
574
  if (!t || this.strategyCampaignsFetchedFor === t)
425
575
  return;
426
- const e = ++S;
576
+ const e = ++C;
427
577
  this.strategyCampaignsLoading = !0;
428
- const r = await m.fetchStrategyCampaigns(t, F());
429
- e === S && (this.strategyCampaigns = r, this.strategyCampaignsLoading = !1, this.strategyCampaignsFetchedFor = t);
578
+ const r = await c.fetchStrategyCampaigns(t, u());
579
+ e === C && (this.strategyCampaigns = r, this.strategyCampaignsLoading = !1, this.strategyCampaignsFetchedFor = t);
430
580
  },
431
581
  /**
432
582
  * Cancels the filter selection drawer and reverts filters
@@ -443,16 +593,16 @@ const B = () => ({
443
593
  // ====================================================================
444
594
  async fetchRecommendationCreateData() {
445
595
  if (!this.activePredictiveAlgorithms.length) {
446
- if (u) {
447
- await u;
596
+ if (h) {
597
+ await h;
448
598
  return;
449
599
  }
450
- u = (async () => {
600
+ h = (async () => {
451
601
  const {
452
602
  activePredictiveAlgorithms: t,
453
603
  languages: e,
454
604
  currencies: r
455
- } = await m.fetchRecommendationCreateData();
605
+ } = await c.fetchRecommendationCreateData();
456
606
  if (this.activePredictiveAlgorithms = t, this.languages = e, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
457
607
  const i = this.blockStates[this.currentRecommendationId];
458
608
  i.filterStatus = !!i.recommendationConfigs.filters.length;
@@ -460,9 +610,9 @@ const B = () => ({
460
610
  this.currencyList = r;
461
611
  })();
462
612
  try {
463
- await u;
613
+ await h;
464
614
  } finally {
465
- u = null;
615
+ h = null;
466
616
  }
467
617
  }
468
618
  },
@@ -475,40 +625,50 @@ const B = () => ({
475
625
  */
476
626
  async fetchRecommendationStrategies() {
477
627
  if (!this.strategies.length) {
478
- if (g) {
479
- await g;
628
+ if (d) {
629
+ await d;
480
630
  return;
481
631
  }
482
- this.strategiesLoading = !0, g = (async () => {
632
+ this.strategiesLoading = !0, d = (async () => {
483
633
  try {
484
- this.strategies = await m.fetchRecommendationStrategies();
634
+ this.strategies = await c.fetchRecommendationStrategies() ?? [], this.strategiesLoadFailed = !1;
485
635
  } catch {
486
- this.strategies = [];
636
+ this.strategies = [], this.strategiesLoadFailed = !0;
487
637
  } finally {
488
638
  this.strategiesLoading = !1;
489
639
  }
490
640
  })();
491
641
  try {
492
- await g;
642
+ await d;
493
643
  } finally {
494
- g = null;
644
+ d = null;
495
645
  }
496
646
  }
497
647
  },
648
+ /**
649
+ * Refetch the listing, ignoring the once-per-session cache.
650
+ *
651
+ * Needed after Duplicate and Assign: the copy exists server-side but not in
652
+ * `strategies`, so without this the panel would read the freshly assigned id
653
+ * back as a deleted strategy.
654
+ */
655
+ async refreshRecommendationStrategies() {
656
+ this.strategies = [], d = null, await this.fetchRecommendationStrategies();
657
+ },
498
658
  async fetchRecommendationFilters() {
499
659
  if (!Object.keys(this.filterList).length) {
500
- if (d) {
501
- await d;
660
+ if (p) {
661
+ await p;
502
662
  return;
503
663
  }
504
- d = (async () => {
505
- const t = await m.fetchRecommendationFilters();
664
+ p = (async () => {
665
+ const t = await c.fetchRecommendationFilters();
506
666
  this.filterList = t;
507
667
  })();
508
668
  try {
509
- await d;
669
+ await p;
510
670
  } finally {
511
- d = null;
671
+ p = null;
512
672
  }
513
673
  }
514
674
  },
@@ -523,10 +683,10 @@ const B = () => ({
523
683
  deleteFilterGroup(t) {
524
684
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
525
685
  return;
526
- 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), a = new Map(i.map((n, o) => [n, o + 1]));
686
+ 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, a) => n - a), s = new Map(i.map((n, a) => [n, a + 1]));
527
687
  e.recommendationConfigs.filters = r.map((n) => ({
528
688
  ...n,
529
- filterGroup: a.get(n.filterGroup) ?? n.filterGroup
689
+ filterGroup: s.get(n.filterGroup) ?? n.filterGroup
530
690
  }));
531
691
  },
532
692
  updateFilter(t) {
@@ -537,7 +697,7 @@ const B = () => ({
537
697
  const i = [...e.recommendationConfigs.filters];
538
698
  i[r] = {
539
699
  ...t,
540
- isValid: O(t)
700
+ isValid: z(t)
541
701
  }, e.recommendationConfigs.filters = i;
542
702
  }
543
703
  },
@@ -549,12 +709,12 @@ const B = () => ({
549
709
  let i = [...e.recommendationConfigs.filters];
550
710
  if (i.splice(r, 1), i.some((n) => n.filterGroup === t.filterGroup)) {
551
711
  let n = 1;
552
- i = i.map((o) => o.filterGroup === t.filterGroup ? { ...o, filterNumber: n++ } : o);
712
+ i = i.map((a) => a.filterGroup === t.filterGroup ? { ...a, filterNumber: n++ } : a);
553
713
  } else {
554
- const n = [...new Set(i.map((s) => s.filterGroup))].sort((s, c) => s - c), o = new Map(n.map((s, c) => [s, c + 1]));
555
- i = i.map((s) => ({
556
- ...s,
557
- filterGroup: o.get(s.filterGroup) ?? s.filterGroup
714
+ const n = [...new Set(i.map((o) => o.filterGroup))].sort((o, l) => o - l), a = new Map(n.map((o, l) => [o, l + 1]));
715
+ i = i.map((o) => ({
716
+ ...o,
717
+ filterGroup: a.get(o.filterGroup) ?? o.filterGroup
558
718
  }));
559
719
  }
560
720
  e.recommendationConfigs.filters = i;
@@ -563,19 +723,19 @@ const B = () => ({
563
723
  addFilter(t) {
564
724
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
565
725
  return;
566
- const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters], a = r.filter(
567
- (o) => o.filterGroup === t.filterGroup
568
- ).length + 1, n = r.findLastIndex((o) => o.filterGroup === t.filterGroup);
726
+ const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters], s = r.filter(
727
+ (a) => a.filterGroup === t.filterGroup
728
+ ).length + 1, n = r.findLastIndex((a) => a.filterGroup === t.filterGroup);
569
729
  n !== -1 ? r.splice(n + 1, 0, {
570
730
  ...t,
571
- filterNumber: a
731
+ filterNumber: s
572
732
  }) : r.push({
573
733
  ...t,
574
- filterNumber: a
734
+ filterNumber: s
575
735
  }), e.recommendationConfigs.filters = r;
576
736
  },
577
737
  generateFilterQuery() {
578
- return R(this.recommendationConfigs.filters);
738
+ return v(this.recommendationConfigs.filters);
579
739
  },
580
740
  /**
581
741
  * Validation-only check invoked at save-CTA time. Defined as an action
@@ -583,55 +743,88 @@ const B = () => ({
583
743
  * every block's recommendationConfigs across user edits.
584
744
  */
585
745
  hasInvalidBlock() {
586
- return Object.values(this.blockStates).some((t) => !A(t.recommendationConfigs, this));
746
+ return Object.values(this.blockStates).some((t) => !M(t.recommendationConfigs, this));
587
747
  },
588
748
  // ====================================================================
589
749
  // Per-Block Product Fetching
590
750
  // ====================================================================
591
751
  async fetchRecommendationProducts() {
592
752
  if (!(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])) {
593
- if (f) {
594
- await f;
753
+ if (S) {
754
+ await S;
595
755
  return;
596
756
  }
597
- f = this._doFetchProducts();
757
+ S = this._doFetchProducts();
598
758
  try {
599
- await f;
759
+ await S;
600
760
  } finally {
601
- f = null;
761
+ S = null;
602
762
  }
603
763
  }
604
764
  },
605
765
  async _doFetchProducts() {
606
- var b;
607
- const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, i = r.filters.filter((l) => l.isValid), a = R(i), n = ((b = y().find((l) => l.key === r.strategy)) == null ? void 0 : b.path) || "", o = v(), s = parseInt(r.size) || 6, c = {
766
+ var I, k;
767
+ const t = this.currentRecommendationId, e = this.blockStates[t];
768
+ e.recommendationConfigs.strategyId && await this.fetchStrategyUrlTemplate(e.recommendationConfigs.strategyId);
769
+ const r = ((I = this.blockStates[t]) == null ? void 0 : I.recommendationConfigs) ?? e.recommendationConfigs, i = r.strategyId ? this.strategyUrlTemplates[r.strategyId] : "", s = O(
770
+ i ?? "",
771
+ V({
772
+ language: r.language,
773
+ currencyCode: r.currencySettings.value,
774
+ size: r.size,
775
+ strategyId: r.strategyId
776
+ }),
777
+ "preview"
778
+ );
779
+ if (s) {
780
+ await this._applyFetchedProducts(
781
+ t,
782
+ () => c.fetchRecommendationProductsByUrl(s)
783
+ );
784
+ return;
785
+ }
786
+ const n = r.filters.filter((R) => R.isValid), a = v(n), o = ((k = w().find((R) => R.key === r.strategy)) == null ? void 0 : k.path) || "", l = F(), m = {
608
787
  locale: r.language,
609
788
  currency: r.currencySettings.value,
610
- partnerName: o.partnerName,
789
+ partnerName: l.partnerName,
611
790
  size: r.size,
612
791
  details: !0,
613
- campaignId: o.variationId
792
+ campaignId: l.variationId
614
793
  };
615
- r.strategy === "manualMerchandising" ? c.productId = r.productIds.join(",") : r.strategy === "similarViewed" && (c.productId = "{itemId}"), r.strategy === "userBased" && (c.userId = "{user_id}"), a && (c.filter = a), r.shuffleProducts && (c.shuffle = !0), Object.assign(
616
- c,
617
- _(o.partnerName, r.strategy)
794
+ r.strategy === "manualMerchandising" ? m.productId = r.productIds.join(",") : r.strategy === "similarViewed" && (m.productId = "{itemId}"), r.strategy === "userBased" && (m.userId = "{user_id}"), a && (m.filter = a), r.shuffleProducts && (m.shuffle = !0), Object.assign(
795
+ m,
796
+ E(l.partnerName, r.strategy)
797
+ ), await this._applyFetchedProducts(
798
+ t,
799
+ () => c.fetchRecommendationProducts(o, m)
618
800
  );
619
- let h;
801
+ },
802
+ /**
803
+ * Runs a product fetch and writes the result to `blockId`.
804
+ *
805
+ * Shared by the strategy and algorithm paths so the size padding/clipping —
806
+ * which the in-place card renderer depends on for its element-count match —
807
+ * cannot drift between them.
808
+ */
809
+ async _applyFetchedProducts(t, e) {
810
+ var s;
811
+ const r = parseInt(((s = this.blockStates[t]) == null ? void 0 : s.recommendationConfigs.size) ?? "") || 6;
812
+ let i;
620
813
  try {
621
- h = await m.fetchRecommendationProducts(n, c);
814
+ i = await e();
622
815
  } catch {
623
- h = [];
816
+ i = [];
624
817
  }
625
818
  if (this.blockStates[t]) {
626
- const l = h.length > 0 ? h : I(s);
627
- l.length < s ? this.blockStates[t].recommendationProducts = [
628
- ...l,
629
- ...I(s - l.length)
630
- ] : l.length > s ? this.blockStates[t].recommendationProducts = l.slice(0, s) : this.blockStates[t].recommendationProducts = l;
819
+ const n = i.length > 0 ? i : D(r);
820
+ n.length < r ? this.blockStates[t].recommendationProducts = [
821
+ ...n,
822
+ ...D(r - n.length)
823
+ ] : n.length > r ? this.blockStates[t].recommendationProducts = n.slice(0, r) : this.blockStates[t].recommendationProducts = n;
631
824
  }
632
825
  }
633
826
  }
634
827
  });
635
828
  export {
636
- Y as useRecommendationExtensionStore
829
+ ot as useRecommendationExtensionStore
637
830
  };