@useinsider/guido 3.19.0-beta.f02eaa1 → 3.19.0-beta.f66e9de

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 (56) hide show
  1. package/dist/components/Guido.vue.js +8 -8
  2. package/dist/components/Guido.vue2.js +44 -43
  3. package/dist/components/organisms/base/AuthErrorModal.vue.js +18 -0
  4. package/dist/components/organisms/base/AuthErrorModal.vue2.js +42 -0
  5. package/dist/components/organisms/base/Toaster.vue2.js +13 -12
  6. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue.js +9 -7
  7. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue2.js +69 -52
  8. package/dist/components/organisms/extensions/recommendation/RemoveStrategyModal.vue2.js +28 -44
  9. package/dist/components/organisms/header/EditorActions.vue.js +4 -4
  10. package/dist/components/organisms/header/EditorActions.vue2.js +23 -25
  11. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue.js +3 -3
  12. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue2.js +21 -20
  13. package/dist/composables/useActionsApi.js +52 -47
  14. package/dist/composables/useAuthError.js +54 -0
  15. package/dist/composables/useCodeEditorApi.js +23 -9
  16. package/dist/composables/useCustomInterfaceAppearance.js +61 -51
  17. package/dist/composables/useHtmlValidator.js +87 -82
  18. package/dist/composables/useHttp.js +54 -44
  19. package/dist/composables/useRecommendation.js +40 -35
  20. package/dist/composables/useStripo.js +30 -30
  21. package/dist/composables/useToaster.js +14 -20
  22. package/dist/composables/useTranslations.js +3 -2
  23. package/dist/composables/useVersionHistoryApi.js +33 -24
  24. package/dist/enums/extensions/recommendationBlock.js +32 -25
  25. package/dist/extensions/Blocks/Recommendation/block.js +61 -42
  26. package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +21 -20
  27. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +63 -58
  28. package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +109 -78
  29. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +96 -97
  30. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +209 -92
  31. package/dist/extensions/Blocks/Recommendation/utils/legacyStrategyMigration.js +1 -1
  32. package/dist/extensions/Blocks/Recommendation/utils/nodeConfigWriter.js +26 -16
  33. package/dist/extensions/Blocks/Recommendation/utils/strategyCommit.js +24 -0
  34. package/dist/extensions/Blocks/Recommendation/validation/requiredFields.js +21 -12
  35. package/dist/guido.css +1 -1
  36. package/dist/src/components/organisms/base/AuthErrorModal.vue.d.ts +2 -0
  37. package/dist/src/components/wrappers/WpModal.vue.d.ts +1 -1
  38. package/dist/src/composables/useAuthError.d.ts +13 -0
  39. package/dist/src/enums/extensions/recommendationBlock.d.ts +2 -0
  40. package/dist/src/extensions/Blocks/Recommendation/block.d.ts +10 -0
  41. package/dist/src/extensions/Blocks/Recommendation/constants/defaultConfig.d.ts +1 -0
  42. package/dist/src/extensions/Blocks/Recommendation/constants/index.d.ts +1 -1
  43. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +2 -0
  44. package/dist/src/extensions/Blocks/Recommendation/controls/main/strategy.d.ts +8 -0
  45. package/dist/src/extensions/Blocks/Recommendation/controls/main/utils.d.ts +7 -6
  46. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +92 -0
  47. package/dist/src/extensions/Blocks/Recommendation/utils/legacyStrategyMigration.d.ts +1 -1
  48. package/dist/src/extensions/Blocks/Recommendation/utils/strategyCommit.d.ts +14 -0
  49. package/dist/src/extensions/Blocks/Recommendation/validation/requiredFields.d.ts +4 -0
  50. package/dist/src/utils/stripoEditorApi.d.ts +4 -0
  51. package/dist/static/styles/components/recommendation-strategy.css.js +34 -4
  52. package/dist/static/styles/components/skeleton.css.js +23 -0
  53. package/dist/stores/unsubscribe.js +3 -3
  54. package/dist/utils/stripoEditorApi.js +11 -0
  55. package/dist/utils/templatePreparation.js +55 -55
  56. package/package.json +2 -1
@@ -1,23 +1,27 @@
1
- import { getRecommendationFeedSourceMaps as R, getOperatorOptions as F, PriceAttributes as P } from "../../../../enums/extensions/recommendationBlock.js";
2
- import { useRecommendationApi as D } from "../../../../services/recommendationApi.js";
1
+ import { useToaster as D } from "../../../../composables/useToaster.js";
2
+ import { useTranslations as L } from "../../../../composables/useTranslations.js";
3
+ import { getRecommendationFeedSourceMaps as F, strategyEditPath as U, URLS as E, getOperatorOptions as N, PriceAttributes as M } from "../../../../enums/extensions/recommendationBlock.js";
4
+ import { ToasterTypeOptions as A } from "../../../../enums/toaster.js";
5
+ import { useRecommendationApi as T } from "../../../../services/recommendationApi.js";
3
6
  import { useConfigStore as k } from "../../../../stores/config.js";
4
- import { defineStore as L } from "pinia";
7
+ import { defineStore as B } from "pinia";
5
8
  import "../constants/selectors.js";
6
- import { DEFAULT_MOBILE_CARDS_IN_ROW as U, DEFAULT_CARDS_IN_ROW as _ } from "../constants/layout.js";
7
- import { CURRENT_CONFIG_VERSION as y, DEFAULT_NODE_CONFIG as N, EXCLUDED_ALGORITHM_IDS as M } from "../constants/defaultConfig.js";
8
- import { getDefaultProducts as C } from "../templates/utils.js";
9
- import { generateCompleteFilterQuery as v } from "../utils/filterUtil.js";
10
- import { isLegacyStrategyConfig as x } from "../utils/legacyStrategyMigration.js";
11
- import { getPartnerRecommendationParams as B } from "../utils/partnerCustomizations.js";
12
- import { sanitizeProductIds as z } from "../utils/productIds.js";
13
- import { isFilterValid as A } from "../validation/filterSchema.js";
14
- import { isConfigValid as O } from "../validation/requiredFields.js";
15
- const m = D();
16
- let f = null, g = null, h = null, S = null;
17
- const p = /* @__PURE__ */ new Map();
18
- function G() {
9
+ import { DEFAULT_MOBILE_CARDS_IN_ROW as O, DEFAULT_CARDS_IN_ROW as z } from "../constants/layout.js";
10
+ import { CURRENT_CONFIG_VERSION as C, DEFAULT_NODE_CONFIG as x, EXCLUDED_ALGORITHM_IDS as V } from "../constants/defaultConfig.js";
11
+ import { getDefaultProducts as w } from "../templates/utils.js";
12
+ import { generateCompleteFilterQuery as P } from "../utils/filterUtil.js";
13
+ import { isLegacyStrategyConfig as j } from "../utils/legacyStrategyMigration.js";
14
+ import { getPartnerRecommendationParams as $ } from "../utils/partnerCustomizations.js";
15
+ import { sanitizeProductIds as q } from "../utils/productIds.js";
16
+ import { isFilterValid as K } from "../validation/filterSchema.js";
17
+ import { getInvalidFields as H, isConfigValid as J } from "../validation/requiredFields.js";
18
+ const m = T();
19
+ let S = null, p = null, y = null, I = "", R = null, b = 0;
20
+ const g = /* @__PURE__ */ new Map();
21
+ let l = null;
22
+ function _() {
19
23
  return {
20
- cardsInRow: _,
24
+ cardsInRow: z,
21
25
  currencySettings: {
22
26
  name: "USD",
23
27
  value: "USD",
@@ -31,15 +35,19 @@ function G() {
31
35
  productIds: [],
32
36
  id: 1,
33
37
  language: "en_US",
34
- mobileCardsInRow: U,
38
+ mobileCardsInRow: O,
35
39
  mobileLayoutEnabled: !1,
36
40
  orientation: "grid",
37
41
  recommendedProducts: [],
38
42
  sendProductRequestFlag: !1,
39
43
  shuffleProducts: !1,
40
- strategy: "mostPopular",
44
+ // With RRS on, a block that has not been synced yet must not claim an algorithm:
45
+ // that default is what reached buildCampaignUrl and baked a most-popular feed URL
46
+ // for a block the user never configured (SD-149916). Legacy blocks still get their real
47
+ // algorithm from the node-config sync or the save-time seed.
48
+ strategy: k().isFeatureEnabled("reusableRecommendationStrategy") ? "" : "mostPopular",
41
49
  strategyId: "",
42
- nodeConfigVersion: y,
50
+ nodeConfigVersion: C,
43
51
  textTrimming: !0,
44
52
  unresponsive: !1,
45
53
  size: "6",
@@ -48,9 +56,9 @@ function G() {
48
56
  priceHideIfSameAsDiscounted: !1
49
57
  };
50
58
  }
51
- function w() {
59
+ function G() {
52
60
  return {
53
- recommendationConfigs: G(),
61
+ recommendationConfigs: _(),
54
62
  recommendationProducts: [],
55
63
  filterStatus: !1,
56
64
  filterSelectionDrawerStatus: !1,
@@ -64,7 +72,7 @@ function w() {
64
72
  legacyConversionLoading: !1
65
73
  };
66
74
  }
67
- const E = () => ({
75
+ const Q = () => ({
68
76
  recommendationCampaignUrls: {},
69
77
  activePredictiveAlgorithms: [],
70
78
  languages: {},
@@ -73,11 +81,14 @@ const E = () => ({
73
81
  strategies: [],
74
82
  strategiesLoading: !1,
75
83
  strategyUrlTemplates: {},
84
+ strategiesFetched: !1,
85
+ strategiesSettled: !1,
76
86
  blockStates: {},
77
87
  currentRecommendationId: null,
88
+ strategyPanelOpen: !1,
78
89
  configVersion: 0
79
- }), et = L("guidoRecommendationExtension", {
80
- state: () => E(),
90
+ }), ft = B("guidoRecommendationExtension", {
91
+ state: () => Q(),
81
92
  getters: {
82
93
  // ====================================================================
83
94
  // Proxy Getters — Backward Compatible Access to Current Block State
@@ -87,7 +98,7 @@ const E = () => ({
87
98
  * This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
88
99
  */
89
100
  recommendationConfigs(t) {
90
- return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : G();
101
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : _();
91
102
  },
92
103
  /**
93
104
  * Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
@@ -108,10 +119,10 @@ const E = () => ({
108
119
  return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].removeStrategyModalStatus : !1;
109
120
  },
110
121
  isLegacyStrategyBlock() {
111
- return k().isFeatureEnabled("reusableRecommendationStrategy") && x(this.recommendationConfigs);
122
+ return k().isFeatureEnabled("reusableRecommendationStrategy") && j(this.recommendationConfigs);
112
123
  },
113
124
  legacyConversionLoading(t) {
114
- return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].legacyConversionLoading : !1;
125
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].legacyConversionLoading ?? !1 : !1;
115
126
  },
116
127
  legacyStrategyUpdatedModalStatus(t) {
117
128
  return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].legacyStrategyUpdatedModalStatus : !1;
@@ -151,8 +162,8 @@ const E = () => ({
151
162
  return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
152
163
  },
153
164
  getActivePredictiveAlgorithms: (t) => {
154
- const e = R(), r = [];
155
- return t.activePredictiveAlgorithms.filter((n) => !M.includes(n)).forEach((n) => {
165
+ const e = F(), r = [];
166
+ return t.activePredictiveAlgorithms.filter((n) => !V.includes(n)).forEach((n) => {
156
167
  r.push(...e.filter((s) => s.id === n));
157
168
  }), r.map((n) => ({
158
169
  text: n.name,
@@ -170,7 +181,7 @@ const E = () => ({
170
181
  getFilterList() {
171
182
  return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
172
183
  let e;
173
- return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : P.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
184
+ return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : M.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
174
185
  text: t.displayName,
175
186
  value: e,
176
187
  type: t.attributeType
@@ -192,7 +203,7 @@ const E = () => ({
192
203
  setCurrentBlock(t) {
193
204
  this.blockStates[t] || (this.blockStates = {
194
205
  ...this.blockStates,
195
- [t]: w()
206
+ [t]: G()
196
207
  }), this.currentRecommendationId = t;
197
208
  },
198
209
  /**
@@ -214,12 +225,16 @@ const E = () => ({
214
225
  /**
215
226
  * Deep-clones a block's state and mirrors its campaign URL under a new id.
216
227
  * Invoked from `onCreated` when a duplicate is detected (SD-142352).
228
+ *
229
+ * The clone keeps the source's data but drops its "already fetched / already
230
+ * synced" claims for strategy blocks, so it fetches again like a fresh drop
231
+ * (SD-149916).
217
232
  */
218
233
  cloneBlockState(t, e) {
219
234
  const r = this.blockStates[t];
220
235
  if (r) {
221
236
  const o = structuredClone(r);
222
- o.recommendationConfigs.id = e, this.blockStates = {
237
+ o.recommendationConfigs.id = e, o.recommendationConfigs.strategyId && k().isFeatureEnabled("reusableRecommendationStrategy") && (o.isInitialized = !1, o.syncedConfigFingerprint = null), this.blockStates = {
223
238
  ...this.blockStates,
224
239
  [e]: o
225
240
  };
@@ -252,7 +267,7 @@ const E = () => ({
252
267
  * `isInitialized`) alone.
253
268
  */
254
269
  seedBlockUrlConfig(t, e) {
255
- const r = (o, c) => o === "." || o === "," || o === " " ? o : c, n = {
270
+ const r = (o, a) => o === "." || o === "," || o === " " ? o : a, n = {
256
271
  name: e.currencyCode,
257
272
  value: e.currencyCode,
258
273
  symbol: e.currencySymbol,
@@ -260,7 +275,7 @@ const E = () => ({
260
275
  decimalCount: String(e.currencyDecimalCount),
261
276
  decimalSeparator: r(e.currencyDecimalSeparator, ","),
262
277
  thousandSeparator: r(e.currencyThousandSeparator, ".")
263
- }, s = !this.blockStates[t], i = s ? w() : this.blockStates[t];
278
+ }, s = !this.blockStates[t], i = s ? G() : this.blockStates[t];
264
279
  i.recommendationConfigs = {
265
280
  ...i.recommendationConfigs,
266
281
  strategy: e.strategy,
@@ -299,7 +314,7 @@ const E = () => ({
299
314
  * Creates a filter with the first available attribute and operator pre-selected.
300
315
  */
301
316
  createDefaultFilter(t, e) {
302
- const [r] = this.getFilterList, [n] = F(r == null ? void 0 : r.type);
317
+ const [r] = this.getFilterList, [n] = N(r == null ? void 0 : r.type);
303
318
  return {
304
319
  type: "standardFilter",
305
320
  attribute: (r == null ? void 0 : r.value) ?? "",
@@ -365,24 +380,106 @@ const E = () => ({
365
380
  this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].removeStrategyModalStatus = !1);
366
381
  },
367
382
  async fetchStrategies() {
368
- if (S) {
369
- await S;
383
+ for (; R; )
384
+ try {
385
+ await R;
386
+ } catch {
387
+ }
388
+ if (this.strategiesFetched)
370
389
  return;
371
- }
372
- S = (async () => {
390
+ const t = b, e = (async () => {
373
391
  this.strategiesLoading = !0;
374
392
  try {
375
- this.strategies = await m.fetchRecommendationStrategies();
393
+ const r = await m.fetchRecommendationStrategies();
394
+ if (t !== b)
395
+ return;
396
+ this.strategies = r, this.strategiesFetched = !0;
376
397
  } finally {
377
- this.strategiesLoading = !1;
398
+ this.strategiesLoading = !1, t === b && (this.strategiesSettled = !0);
378
399
  }
379
400
  })();
401
+ R = e;
380
402
  try {
381
- await S;
403
+ await e;
382
404
  } finally {
383
- S = null;
405
+ R = null;
384
406
  }
385
407
  },
408
+ /**
409
+ * Clears what a block inherited from the id it arrived holding, so the panel fetches for it.
410
+ * A re-dropped saved module can keep the source's id — and its "already fetched" entry —
411
+ * while the strategy's product count has moved on (SD-149916).
412
+ */
413
+ resetArrivedBlock(t) {
414
+ const e = this.blockStates[t];
415
+ e != null && e.recommendationConfigs.strategyId && (e.isInitialized = !1, e.syncedConfigFingerprint = null, this.invalidateStrategies(), this.fetchStrategies().catch(() => {
416
+ }));
417
+ },
418
+ invalidateStrategies() {
419
+ this.strategiesFetched = !1, this.strategiesSettled = !1, b += 1;
420
+ },
421
+ /**
422
+ * Opens a Smart Recommender strategy page in a new tab and refreshes the list every time
423
+ * the user comes back. `visibilitychange` only: a `focus` listener would also fire when
424
+ * focus merely returns from the Stripo canvas iframe to the panel, refetching and
425
+ * regenerating the canvas while the user never left the editor (SD-149916).
426
+ *
427
+ * Deliberately NOT one-shot: the strategy tab stays open, so a user can create a second
428
+ * strategy in it and come back again — a listener that disarmed on the first return would
429
+ * leave that one missing from the list. It disarms on the first return AFTER that tab is
430
+ * closed instead, so an ordinary alt-tab later in the session costs nothing. Armed only
431
+ * once the user actually opens a strategy page, so anyone who never does pays nothing.
432
+ * @returns whether a tab actually opened, so callers can skip work that assumes one did
433
+ */
434
+ _armStrategyPageReturn(t) {
435
+ const e = window.open(t, "_blank");
436
+ if (!e)
437
+ return D().showToaster({
438
+ type: A.Error,
439
+ message: L()("discovery-strategy.error-description")
440
+ }), !1;
441
+ e.opener = null, l == null || l();
442
+ let r = !1;
443
+ const n = () => {
444
+ document.hidden || r || (e != null && e.closed && (l == null || l()), r = !0, this.invalidateStrategies(), this.fetchStrategies().then(() => this._refreshAssignedStrategyProducts()).catch(() => {
445
+ }).finally(() => {
446
+ r = !1;
447
+ }));
448
+ };
449
+ return document.addEventListener("visibilitychange", n), l = () => {
450
+ document.removeEventListener("visibilitychange", n), l = null;
451
+ }, !0;
452
+ },
453
+ /**
454
+ * Reloads the products of the block the user is on, so an edit made in the strategy tab
455
+ * lands on the canvas instead of waiting for the assign drawer to be reopened.
456
+ *
457
+ * Only the selected block: the canvas regeneration is driven off the active block's
458
+ * product array, so refreshing an unselected block would fetch without redrawing it.
459
+ */
460
+ async _refreshAssignedStrategyProducts() {
461
+ var e;
462
+ const t = this.currentRecommendationId;
463
+ t === null || !((e = this.blockStates[t]) != null && e.recommendationConfigs.strategyId) || await this.fetchRecommendationProducts();
464
+ },
465
+ /** Lets the editor tear the return listeners down when it unmounts. */
466
+ disposeStrategyPageReturn() {
467
+ l == null || l();
468
+ },
469
+ /**
470
+ * Opens Smart Recommender's strategy creation page. Every return refreshes the list so the
471
+ * new strategy is offered; the user picks it themselves.
472
+ */
473
+ openStrategyCreation() {
474
+ return this._armStrategyPageReturn(E.RECOMMENDATION_STRATEGY_CREATE_PATH);
475
+ },
476
+ /**
477
+ * Opens the strategy's edit page. Every return refreshes the list so the block reflects the
478
+ * edited configuration.
479
+ */
480
+ openStrategyEdit(t) {
481
+ return this._armStrategyPageReturn(U(t));
482
+ },
386
483
  async resolveStrategyUrlTemplate(t, e) {
387
484
  const r = typeof t.updated_at == "string" ? t.updated_at : "", n = r ? `${t.strategy_id}:${r}:${e}` : "", s = n ? this.strategyUrlTemplates[n] : void 0;
388
485
  if (s)
@@ -415,7 +512,7 @@ const E = () => ({
415
512
  return i && (i.recommendationConfigs = {
416
513
  ...i.recommendationConfigs,
417
514
  strategyId: n,
418
- nodeConfigVersion: y,
515
+ nodeConfigVersion: C,
419
516
  ...(s == null ? void 0 : s.size) !== void 0 ? { size: String(s.size) } : {}
420
517
  }), this.currentRecommendationId === r && this.configVersion++, n;
421
518
  } finally {
@@ -434,8 +531,9 @@ const E = () => ({
434
531
  removeStrategy() {
435
532
  this.patchCurrentBlockConfig({
436
533
  strategyId: "",
437
- size: N.size,
438
- nodeConfigVersion: y
534
+ strategy: "",
535
+ size: x.size,
536
+ nodeConfigVersion: C
439
537
  });
440
538
  },
441
539
  // ====================================================================
@@ -443,11 +541,11 @@ const E = () => ({
443
541
  // ====================================================================
444
542
  async fetchRecommendationCreateData() {
445
543
  if (!this.activePredictiveAlgorithms.length) {
446
- if (f) {
447
- await f;
544
+ if (S) {
545
+ await S;
448
546
  return;
449
547
  }
450
- f = (async () => {
548
+ S = (async () => {
451
549
  const {
452
550
  activePredictiveAlgorithms: t,
453
551
  languages: e,
@@ -460,26 +558,26 @@ const E = () => ({
460
558
  this.currencyList = r;
461
559
  })();
462
560
  try {
463
- await f;
561
+ await S;
464
562
  } finally {
465
- f = null;
563
+ S = null;
466
564
  }
467
565
  }
468
566
  },
469
567
  async fetchRecommendationFilters() {
470
568
  if (!Object.keys(this.filterList).length) {
471
- if (g) {
472
- await g;
569
+ if (p) {
570
+ await p;
473
571
  return;
474
572
  }
475
- g = (async () => {
573
+ p = (async () => {
476
574
  const t = await m.fetchRecommendationFilters();
477
575
  this.filterList = t;
478
576
  })();
479
577
  try {
480
- await g;
578
+ await p;
481
579
  } finally {
482
- g = null;
580
+ p = null;
483
581
  }
484
582
  }
485
583
  },
@@ -508,7 +606,7 @@ const E = () => ({
508
606
  const n = [...e.recommendationConfigs.filters];
509
607
  n[r] = {
510
608
  ...t,
511
- isValid: A(t)
609
+ isValid: K(t)
512
610
  }, e.recommendationConfigs.filters = n;
513
611
  }
514
612
  },
@@ -522,10 +620,10 @@ const E = () => ({
522
620
  let i = 1;
523
621
  n = n.map((o) => o.filterGroup === t.filterGroup ? { ...o, filterNumber: i++ } : o);
524
622
  } else {
525
- const i = [...new Set(n.map((c) => c.filterGroup))].sort((c, a) => c - a), o = new Map(i.map((c, a) => [c, a + 1]));
526
- n = n.map((c) => ({
527
- ...c,
528
- filterGroup: o.get(c.filterGroup) ?? c.filterGroup
623
+ const i = [...new Set(n.map((a) => a.filterGroup))].sort((a, d) => a - d), o = new Map(i.map((a, d) => [a, d + 1]));
624
+ n = n.map((a) => ({
625
+ ...a,
626
+ filterGroup: o.get(a.filterGroup) ?? a.filterGroup
529
627
  }));
530
628
  }
531
629
  e.recommendationConfigs.filters = n;
@@ -546,7 +644,7 @@ const E = () => ({
546
644
  }), e.recommendationConfigs.filters = r;
547
645
  },
548
646
  generateFilterQuery() {
549
- return v(this.recommendationConfigs.filters);
647
+ return P(this.recommendationConfigs.filters);
550
648
  },
551
649
  /**
552
650
  * Validation-only check invoked at save-CTA time. Defined as an action
@@ -554,33 +652,52 @@ const E = () => ({
554
652
  * every block's recommendationConfigs across user edits.
555
653
  */
556
654
  hasInvalidBlock() {
557
- return Object.values(this.blockStates).some((t) => !O(t.recommendationConfigs, this));
655
+ return Object.values(this.blockStates).some((t) => !J(t.recommendationConfigs, this));
656
+ },
657
+ /**
658
+ * Which required fields are missing across the design's blocks, so the save
659
+ * toaster can name the cause instead of always showing the generic message.
660
+ */
661
+ invalidBlockFields() {
662
+ return Object.values(this.blockStates).flatMap((t) => H(t.recommendationConfigs, this));
558
663
  },
559
664
  // ====================================================================
560
665
  // Per-Block Product Fetching
561
666
  // ====================================================================
562
667
  async fetchRecommendationProducts() {
563
- if (!(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])) {
564
- if (h) {
565
- await h;
566
- return;
567
- }
568
- h = this._doFetchProducts();
569
- try {
570
- await h;
571
- } finally {
572
- h = null;
573
- }
668
+ if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
669
+ return;
670
+ const t = [
671
+ this.currentRecommendationId,
672
+ this.configVersion,
673
+ b
674
+ ].join(":");
675
+ if (y && I === t) {
676
+ await y;
677
+ return;
678
+ }
679
+ I = t, y = this._doFetchProducts();
680
+ try {
681
+ await y;
682
+ } finally {
683
+ I === t && (y = null, I = "");
574
684
  }
575
685
  },
576
686
  async _doFetchProducts() {
577
- var d;
687
+ var h;
578
688
  const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = k();
579
689
  if (r.strategyId && n.isFeatureEnabled("reusableRecommendationStrategy")) {
580
690
  await this._doFetchStrategyProducts(t);
581
691
  return;
582
692
  }
583
- const s = r.filters.filter((u) => u.isValid), i = v(s), o = ((d = R().find((u) => u.key === r.strategy)) == null ? void 0 : d.path) || "", c = parseInt(r.size) || 6, a = {
693
+ const s = (g.get(t) ?? 0) + 1;
694
+ g.set(t, s);
695
+ const i = parseInt(r.size) || 6;
696
+ if (!r.strategy) {
697
+ this._setBlockProducts(t, [], i);
698
+ return;
699
+ }
700
+ const o = r.filters.filter((f) => f.isValid), a = P(o), d = ((h = F().find((f) => f.key === r.strategy)) == null ? void 0 : h.path) || "", c = {
584
701
  locale: r.language,
585
702
  currency: r.currencySettings.value,
586
703
  partnerName: n.partnerName,
@@ -588,17 +705,17 @@ const E = () => ({
588
705
  details: !0,
589
706
  campaignId: n.variationId
590
707
  };
591
- r.strategy === "manualMerchandising" ? a.productId = z(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(
592
- a,
593
- B(n.partnerName, r.strategy)
708
+ r.strategy === "manualMerchandising" ? c.productId = q(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(
709
+ c,
710
+ $(n.partnerName, r.strategy)
594
711
  );
595
- let l;
712
+ let u;
596
713
  try {
597
- l = await m.fetchRecommendationProducts(o, a);
714
+ u = await m.fetchRecommendationProducts(d, c);
598
715
  } catch {
599
- l = [];
716
+ u = [];
600
717
  }
601
- this._setBlockProducts(t, l, c);
718
+ g.get(t) === s && this._setBlockProducts(t, u, i);
602
719
  },
603
720
  /**
604
721
  * Block-scoped counterpart of `fetchRecommendationProducts`, which only ever serves the
@@ -610,8 +727,8 @@ const E = () => ({
610
727
  (e = this.blockStates[t]) != null && e.recommendationConfigs.strategyId && await this._doFetchStrategyProducts(t);
611
728
  },
612
729
  async _doFetchStrategyProducts(t) {
613
- const e = (p.get(t) ?? 0) + 1;
614
- p.set(t, e);
730
+ const e = (g.get(t) ?? 0) + 1;
731
+ g.set(t, e);
615
732
  const { recommendationConfigs: r } = this.blockStates[t];
616
733
  let n = [], s = parseInt(r.size) || 6;
617
734
  try {
@@ -621,25 +738,25 @@ const E = () => ({
621
738
  );
622
739
  if (i) {
623
740
  s = parseInt(String(i.size ?? "")) || s;
624
- 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), u = (l === -1 ? "" : a.slice(l + 1)).split("&").filter((I) => I && !I.startsWith("user_id=")), b = u.length ? `${d}?${u.join("&")}` : d;
625
- b && (n = await m.fetchRecommendationProductsByUrl(b));
741
+ const o = r.currencySettings.value, d = (await this.resolveStrategyUrlTemplate(i, o)).replaceAll("{locale}", r.language).replaceAll("{currency}", o), c = d.indexOf("?"), u = c === -1 ? d : d.slice(0, c), h = (c === -1 ? "" : d.slice(c + 1)).split("&").filter((v) => v && !v.startsWith("user_id=")), f = h.length ? `${u}?${h.join("&")}` : u;
742
+ f && (n = await m.fetchRecommendationProductsByUrl(f));
626
743
  }
627
744
  } catch {
628
745
  n = [];
629
746
  }
630
- p.get(t) === e && this._setBlockProducts(t, n, s);
747
+ g.get(t) === e && this._setBlockProducts(t, n, s);
631
748
  },
632
749
  _setBlockProducts(t, e, r) {
633
750
  if (!this.blockStates[t])
634
751
  return;
635
- const n = e.length > 0 ? e : C(r);
752
+ const n = e.length > 0 ? e : w(r);
636
753
  this.blockStates[t].recommendationProducts = [
637
754
  ...n.slice(0, r),
638
- ...C(Math.max(0, r - n.length))
755
+ ...w(Math.max(0, r - n.length))
639
756
  ];
640
757
  }
641
758
  }
642
759
  });
643
760
  export {
644
- et as useRecommendationExtensionStore
761
+ ft as useRecommendationExtensionStore
645
762
  };
@@ -1,5 +1,5 @@
1
1
  import "../constants/selectors.js";
2
- import { CURRENT_CONFIG_VERSION as m } from "../constants/defaultConfig.js";
2
+ import { RRS_CONFIG_VERSION as m } from "../constants/defaultConfig.js";
3
3
  import { sanitizeProductIds as p } from "./productIds.js";
4
4
  const d = "manualMerchandising";
5
5
  function N({ strategyId: e, nodeConfigVersion: t }) {
@@ -1,22 +1,32 @@
1
- import { ModificationDescription as f } from "../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
2
- import { RECOMMENDATION_ID_ATTR as m } from "../constants/selectors.js";
3
- import { isPartnerManagedBlock as l, updateProductContentInPlace as a, regenerateProductRowsWithStyles as d } from "../controls/main/utils.js";
4
- import { RecommendationConfigService as p } from "../services/configService.js";
5
- let e = null;
6
- function M(r) {
7
- e = r;
1
+ import { ModificationDescription as g } from "../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
2
+ import { RECOMMENDATION_ID_ATTR as s } from "../constants/selectors.js";
3
+ import { isPartnerManagedBlock as p, updateProductContentInPlace as C, regenerateProductRowsWithStyles as N } from "../controls/main/utils.js";
4
+ import { RecommendationConfigService as m } from "../services/configService.js";
5
+ let o = null;
6
+ function D(n) {
7
+ o = n;
8
8
  }
9
- function N(r, c, i, u) {
10
- const n = e == null ? void 0 : e.getDocumentRootHtmlNode();
11
- if (!e || !n || !("querySelector" in n))
9
+ function P(n, r, c, l) {
10
+ const i = o == null ? void 0 : o.getDocumentRootHtmlNode();
11
+ if (!o || !i || !("querySelector" in i))
12
12
  return !1;
13
- const t = n.querySelector(`[${m}="${r}"]`);
14
- if (!t)
13
+ const e = i.querySelector(`[${s}="${n}"]`);
14
+ if (!e)
15
15
  return !1;
16
- const o = e.getDocumentModifier();
17
- return p.stageConfig(o, t, c), l(t) ? (o.apply(new f(u)), !0) : (a({ currentNode: t, documentModifier: o, products: i, forceApply: !0 }) || d({ currentNode: t, documentModifier: o, products: i, forceApply: !0 }), !0);
16
+ const t = o.getDocumentModifier(), a = m.getConfig(e), d = Object.keys(r).some((u) => JSON.stringify(a[u]) !== JSON.stringify(r[u]));
17
+ if (m.stageConfig(t, e, r), p(e))
18
+ return t.apply(new g(l)), !0;
19
+ let f = !1;
20
+ return C({
21
+ currentNode: e,
22
+ documentModifier: t,
23
+ products: c,
24
+ onApplied: () => {
25
+ f = !0;
26
+ }
27
+ }) || (f = N({ currentNode: e, documentModifier: t, products: c })), !f && d && t.apply(new g(l)), !0;
18
28
  }
19
29
  export {
20
- N as commitBlockChangeOutsidePanel,
21
- M as registerNodeConfigWriter
30
+ P as commitBlockChangeOutsidePanel,
31
+ D as registerNodeConfigWriter
22
32
  };
@@ -0,0 +1,24 @@
1
+ import { useRecommendationExtensionStore as d } from "../store/recommendation.js";
2
+ import { commitBlockChangeOutsidePanel as f } from "./nodeConfigWriter.js";
3
+ async function h(e, r) {
4
+ const t = d(), o = t.blockStates[e];
5
+ if (!o)
6
+ return !1;
7
+ const { strategyId: a, strategy: c, size: i, nodeConfigVersion: s } = o.recommendationConfigs;
8
+ if (t.currentRecommendationId === e)
9
+ try {
10
+ await t.fetchRecommendationProducts();
11
+ } catch (m) {
12
+ console.error("[recommendation] product refetch after a strategy change failed", m);
13
+ }
14
+ const n = f(
15
+ e,
16
+ { strategyId: a, strategy: c, size: i, configVersion: s },
17
+ o.recommendationProducts,
18
+ r
19
+ );
20
+ return n || console.error("[recommendation] strategy change did not reach a node", { blockId: e }), n;
21
+ }
22
+ export {
23
+ h as commitStrategyChange
24
+ };