@useinsider/guido 3.18.0 → 3.18.1-beta.0725bd5

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