@useinsider/guido 3.18.2-beta.45f708c → 3.18.2-beta.4d2b065

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 (58) hide show
  1. package/README.md +21 -3
  2. package/dist/@types/config/schemas.js +59 -51
  3. package/dist/components/Guido.vue.js +4 -4
  4. package/dist/components/Guido.vue2.js +72 -70
  5. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue.js +7 -9
  6. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue2.js +51 -66
  7. package/dist/components/organisms/extensions/recommendation/LegacyStrategyUpdatedModal.vue.js +18 -0
  8. package/dist/components/organisms/extensions/recommendation/LegacyStrategyUpdatedModal.vue2.js +25 -0
  9. package/dist/components/organisms/extensions/recommendation/RemoveStrategyModal.vue2.js +40 -24
  10. package/dist/components/organisms/onboarding/OnboardingWrapper.vue2.js +28 -26
  11. package/dist/components/organisms/onboarding/RecommendationLegacyStrategyOnboarding.vue.js +21 -0
  12. package/dist/components/organisms/onboarding/RecommendationLegacyStrategyOnboarding.vue2.js +46 -0
  13. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue.js +3 -3
  14. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue2.js +20 -21
  15. package/dist/composables/useCustomInterfaceAppearance.js +47 -53
  16. package/dist/composables/useHtmlValidator.js +79 -84
  17. package/dist/composables/useLegacyStrategyConversion.js +61 -0
  18. package/dist/composables/useRecommendation.js +35 -40
  19. package/dist/composables/useStripo.js +30 -30
  20. package/dist/enums/extensions/recommendationBlock.js +41 -39
  21. package/dist/extensions/Blocks/Recommendation/block.js +39 -43
  22. package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +20 -21
  23. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +80 -84
  24. package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +94 -82
  25. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +97 -96
  26. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +144 -179
  27. package/dist/extensions/Blocks/Recommendation/utils/legacyStrategyMigration.js +42 -0
  28. package/dist/extensions/Blocks/Recommendation/utils/nodeConfigWriter.js +16 -26
  29. package/dist/extensions/Blocks/Recommendation/validation/requiredFields.js +12 -21
  30. package/dist/guido.css +1 -1
  31. package/dist/services/recommendationApi.js +46 -37
  32. package/dist/src/@types/config/schemas.d.ts +16 -0
  33. package/dist/src/components/organisms/extensions/recommendation/LegacyStrategyUpdatedModal.vue.d.ts +2 -0
  34. package/dist/src/components/organisms/onboarding/RecommendationLegacyStrategyOnboarding.vue.d.ts +2 -0
  35. package/dist/src/composables/useConfig.d.ts +4 -0
  36. package/dist/src/composables/useLegacyStrategyConversion.d.ts +3 -0
  37. package/dist/src/enums/extensions/recommendationBlock.d.ts +5 -3
  38. package/dist/src/extensions/Blocks/Recommendation/constants/defaultConfig.d.ts +0 -1
  39. package/dist/src/extensions/Blocks/Recommendation/constants/index.d.ts +1 -1
  40. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +0 -2
  41. package/dist/src/extensions/Blocks/Recommendation/controls/main/strategy.d.ts +1 -8
  42. package/dist/src/extensions/Blocks/Recommendation/controls/main/utils.d.ts +6 -7
  43. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +70 -76
  44. package/dist/src/extensions/Blocks/Recommendation/utils/legacyStrategyMigration.d.ts +32 -0
  45. package/dist/src/extensions/Blocks/Recommendation/validation/requiredFields.d.ts +0 -4
  46. package/dist/src/services/recommendationApi.d.ts +2 -1
  47. package/dist/src/stores/config.d.ts +322 -0
  48. package/dist/src/stores/onboarding.d.ts +421 -0
  49. package/dist/static/assets/icons/filled-arrow-rising.svg.js +5 -0
  50. package/dist/static/assets/icons/line-info.svg.js +5 -0
  51. package/dist/static/styles/components/recommendation-strategy.css.js +74 -39
  52. package/dist/stores/config.js +74 -60
  53. package/dist/stores/onboarding.js +6 -1
  54. package/dist/utils/templatePreparation.js +55 -55
  55. package/package.json +5 -5
  56. package/dist/extensions/Blocks/Recommendation/utils/strategyCommit.js +0 -24
  57. package/dist/src/extensions/Blocks/Recommendation/utils/strategyCommit.d.ts +0 -14
  58. package/dist/static/styles/components/skeleton.css.js +0 -23
@@ -1,23 +1,23 @@
1
- import { getRecommendationFeedSourceMaps as C, strategyEditPath as D, URLS as _, getOperatorOptions as N, PriceAttributes as U } from "../../../../enums/extensions/recommendationBlock.js";
2
- import { useRecommendationApi as A } from "../../../../services/recommendationApi.js";
3
- import { useConfigStore as P } from "../../../../stores/config.js";
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";
3
+ import { useConfigStore as k } from "../../../../stores/config.js";
4
4
  import { defineStore as L } from "pinia";
5
5
  import "../constants/selectors.js";
6
- import { DEFAULT_MOBILE_CARDS_IN_ROW as E, DEFAULT_CARDS_IN_ROW as x } from "../constants/layout.js";
7
- import { DEFAULT_NODE_CONFIG as B, EXCLUDED_ALGORITHM_IDS as M } from "../constants/defaultConfig.js";
8
- import { getDefaultProducts as v } from "../templates/utils.js";
9
- import { generateCompleteFilterQuery as F } from "../utils/filterUtil.js";
10
- import { getPartnerRecommendationParams as O } from "../utils/partnerCustomizations.js";
11
- import { sanitizeProductIds as T } from "../utils/productIds.js";
12
- import { isFilterValid as z } from "../validation/filterSchema.js";
13
- import { getInvalidFields as V, isConfigValid as j } from "../validation/requiredFields.js";
14
- const f = A();
15
- let S = null, p = null, y = null, I = "", R = null, b = 0;
16
- const h = /* @__PURE__ */ new Map();
17
- let l = null;
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
18
  function G() {
19
19
  return {
20
- cardsInRow: x,
20
+ cardsInRow: _,
21
21
  currencySettings: {
22
22
  name: "USD",
23
23
  value: "USD",
@@ -31,18 +31,15 @@ function G() {
31
31
  productIds: [],
32
32
  id: 1,
33
33
  language: "en_US",
34
- mobileCardsInRow: E,
34
+ mobileCardsInRow: U,
35
35
  mobileLayoutEnabled: !1,
36
36
  orientation: "grid",
37
37
  recommendedProducts: [],
38
38
  sendProductRequestFlag: !1,
39
39
  shuffleProducts: !1,
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: P().isFeatureEnabled("reusableRecommendationStrategy") ? "" : "mostPopular",
40
+ strategy: "mostPopular",
45
41
  strategyId: "",
42
+ nodeConfigVersion: y,
46
43
  textTrimming: !0,
47
44
  unresponsive: !1,
48
45
  size: "6",
@@ -59,13 +56,15 @@ function w() {
59
56
  filterSelectionDrawerStatus: !1,
60
57
  assignStrategyDrawerStatus: !1,
61
58
  removeStrategyModalStatus: !1,
59
+ legacyStrategyUpdatedModalStatus: !1,
62
60
  filterGroup: 1,
63
61
  isInitialized: !1,
64
62
  filterSnapshot: null,
65
- syncedConfigFingerprint: null
63
+ syncedConfigFingerprint: null,
64
+ legacyConversionLoading: !1
66
65
  };
67
66
  }
68
- const $ = () => ({
67
+ const E = () => ({
69
68
  recommendationCampaignUrls: {},
70
69
  activePredictiveAlgorithms: [],
71
70
  languages: {},
@@ -74,14 +73,11 @@ const $ = () => ({
74
73
  strategies: [],
75
74
  strategiesLoading: !1,
76
75
  strategyUrlTemplates: {},
77
- strategiesFetched: !1,
78
- strategiesSettled: !1,
79
76
  blockStates: {},
80
77
  currentRecommendationId: null,
81
- strategyPanelOpen: !1,
82
78
  configVersion: 0
83
- }), it = L("guidoRecommendationExtension", {
84
- state: () => $(),
79
+ }), et = L("guidoRecommendationExtension", {
80
+ state: () => E(),
85
81
  getters: {
86
82
  // ====================================================================
87
83
  // Proxy Getters — Backward Compatible Access to Current Block State
@@ -111,6 +107,15 @@ const $ = () => ({
111
107
  removeStrategyModalStatus(t) {
112
108
  return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].removeStrategyModalStatus : !1;
113
109
  },
110
+ isLegacyStrategyBlock() {
111
+ return k().isFeatureEnabled("reusableRecommendationStrategy") && x(this.recommendationConfigs);
112
+ },
113
+ legacyConversionLoading(t) {
114
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].legacyConversionLoading : !1;
115
+ },
116
+ legacyStrategyUpdatedModalStatus(t) {
117
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].legacyStrategyUpdatedModalStatus : !1;
118
+ },
114
119
  assignedStrategy(t) {
115
120
  const { strategyId: e } = this.recommendationConfigs;
116
121
  return e ? t.strategies.find((r) => String(r.strategy_id) === e) ?? null : null;
@@ -146,7 +151,7 @@ const $ = () => ({
146
151
  return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
147
152
  },
148
153
  getActivePredictiveAlgorithms: (t) => {
149
- const e = C(), r = [];
154
+ const e = R(), r = [];
150
155
  return t.activePredictiveAlgorithms.filter((n) => !M.includes(n)).forEach((n) => {
151
156
  r.push(...e.filter((s) => s.id === n));
152
157
  }), r.map((n) => ({
@@ -165,7 +170,7 @@ const $ = () => ({
165
170
  getFilterList() {
166
171
  return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
167
172
  let e;
168
- return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : U.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
173
+ return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : P.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
169
174
  text: t.displayName,
170
175
  value: e,
171
176
  type: t.attributeType
@@ -247,7 +252,7 @@ const $ = () => ({
247
252
  * `isInitialized`) alone.
248
253
  */
249
254
  seedBlockUrlConfig(t, e) {
250
- const r = (o, a) => o === "." || o === "," || o === " " ? o : a, n = {
255
+ const r = (o, c) => o === "." || o === "," || o === " " ? o : c, n = {
251
256
  name: e.currencyCode,
252
257
  value: e.currencyCode,
253
258
  symbol: e.currencySymbol,
@@ -294,7 +299,7 @@ const $ = () => ({
294
299
  * Creates a filter with the first available attribute and operator pre-selected.
295
300
  */
296
301
  createDefaultFilter(t, e) {
297
- const [r] = this.getFilterList, [n] = N(r == null ? void 0 : r.type);
302
+ const [r] = this.getFilterList, [n] = F(r == null ? void 0 : r.type);
298
303
  return {
299
304
  type: "standardFilter",
300
305
  attribute: (r == null ? void 0 : r.value) ?? "",
@@ -347,6 +352,12 @@ const $ = () => ({
347
352
  closeAssignStrategyDrawer() {
348
353
  this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].assignStrategyDrawerStatus = !1);
349
354
  },
355
+ openLegacyStrategyUpdatedModal() {
356
+ this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].legacyStrategyUpdatedModalStatus = !0);
357
+ },
358
+ closeLegacyStrategyUpdatedModal() {
359
+ this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].legacyStrategyUpdatedModalStatus = !1);
360
+ },
350
361
  openRemoveStrategyModal() {
351
362
  this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].removeStrategyModalStatus = !0);
352
363
  },
@@ -354,97 +365,29 @@ const $ = () => ({
354
365
  this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].removeStrategyModalStatus = !1);
355
366
  },
356
367
  async fetchStrategies() {
357
- for (; R; )
358
- try {
359
- await R;
360
- } catch {
361
- }
362
- if (this.strategiesFetched)
368
+ if (S) {
369
+ await S;
363
370
  return;
364
- const t = b, e = (async () => {
371
+ }
372
+ S = (async () => {
365
373
  this.strategiesLoading = !0;
366
374
  try {
367
- const r = await f.fetchRecommendationStrategies();
368
- if (t !== b)
369
- return;
370
- this.strategies = r, this.strategiesFetched = !0;
375
+ this.strategies = await m.fetchRecommendationStrategies();
371
376
  } finally {
372
- this.strategiesLoading = !1, t === b && (this.strategiesSettled = !0);
377
+ this.strategiesLoading = !1;
373
378
  }
374
379
  })();
375
- R = e;
376
380
  try {
377
- await e;
381
+ await S;
378
382
  } finally {
379
- R = null;
383
+ S = null;
380
384
  }
381
385
  },
382
- invalidateStrategies() {
383
- this.strategiesFetched = !1, this.strategiesSettled = !1, b += 1;
384
- },
385
- /**
386
- * Opens a Smart Recommender strategy page in a new tab and refreshes the list every time
387
- * the user comes back. `visibilitychange` only: a `focus` listener would also fire when
388
- * focus merely returns from the Stripo canvas iframe to the panel, refetching and
389
- * regenerating the canvas while the user never left the editor (SD-149916).
390
- *
391
- * Deliberately NOT one-shot: the strategy tab stays open, so a user can create a second
392
- * strategy in it and come back again — a listener that disarmed on the first return would
393
- * leave that one missing from the list. It disarms on the first return AFTER that tab is
394
- * closed instead, so an ordinary alt-tab later in the session costs nothing. Armed only
395
- * once the user actually opens a strategy page, so anyone who never does pays nothing.
396
- */
397
- _armStrategyPageReturn(t) {
398
- const e = window.open(t, "_blank");
399
- if (!e)
400
- return;
401
- e.opener = null, l == null || l();
402
- let r = !1;
403
- const n = () => {
404
- document.hidden || r || (e != null && e.closed && (l == null || l()), r = !0, this.invalidateStrategies(), this.fetchStrategies().then(() => this._refreshAssignedStrategyProducts()).catch(() => {
405
- }).finally(() => {
406
- r = !1;
407
- }));
408
- };
409
- document.addEventListener("visibilitychange", n), l = () => {
410
- document.removeEventListener("visibilitychange", n), l = null;
411
- };
412
- },
413
- /**
414
- * Reloads the products of the block the user is on, so an edit made in the strategy tab
415
- * lands on the canvas instead of waiting for the assign drawer to be reopened.
416
- *
417
- * Only the selected block: the canvas regeneration is driven off the active block's
418
- * product array, so refreshing an unselected block would fetch without redrawing it.
419
- */
420
- async _refreshAssignedStrategyProducts() {
421
- var e;
422
- const t = this.currentRecommendationId;
423
- t === null || !((e = this.blockStates[t]) != null && e.recommendationConfigs.strategyId) || await this.fetchRecommendationProducts();
424
- },
425
- /** Lets the editor tear the return listeners down when it unmounts. */
426
- disposeStrategyPageReturn() {
427
- l == null || l();
428
- },
429
- /**
430
- * Opens Smart Recommender's strategy creation page. Every return refreshes the list so the
431
- * new strategy is offered; the user picks it themselves.
432
- */
433
- openStrategyCreation() {
434
- this._armStrategyPageReturn(_.RECOMMENDATION_STRATEGY_CREATE_PATH);
435
- },
436
- /**
437
- * Opens the strategy's edit page. Every return refreshes the list so the block reflects the
438
- * edited configuration.
439
- */
440
- openStrategyEdit(t) {
441
- this._armStrategyPageReturn(D(t));
442
- },
443
386
  async resolveStrategyUrlTemplate(t, e) {
444
387
  const r = typeof t.updated_at == "string" ? t.updated_at : "", n = r ? `${t.strategy_id}:${r}:${e}` : "", s = n ? this.strategyUrlTemplates[n] : void 0;
445
388
  if (s)
446
389
  return s;
447
- const { full_path: i = "" } = await f.generateStrategyEndpoint(t, e) ?? {};
390
+ const { full_path: i = "" } = await m.generateStrategyEndpoint(t, e) ?? {};
448
391
  return n && i && (this.strategyUrlTemplates = {
449
392
  ...this.strategyUrlTemplates,
450
393
  [n]: i
@@ -456,11 +399,43 @@ const $ = () => ({
456
399
  ...t.size !== void 0 ? { size: String(t.size) } : {}
457
400
  });
458
401
  },
402
+ async convertLegacyStrategy(t, e = { scope: "campaign" }) {
403
+ const r = this.currentRecommendationId;
404
+ if (r === null || !this.blockStates[r])
405
+ throw new Error("[Guido] convertLegacyStrategy called without an active block");
406
+ this.blockStates[r].legacyConversionLoading = !0;
407
+ try {
408
+ const n = await m.migrateLegacyStrategy(t, e);
409
+ try {
410
+ await this.fetchStrategies();
411
+ } catch (o) {
412
+ console.error("[Guido] strategy listing refresh after conversion failed", o);
413
+ }
414
+ const s = this.strategies.find((o) => String(o.strategy_id) === n), i = this.blockStates[r];
415
+ return i && (i.recommendationConfigs = {
416
+ ...i.recommendationConfigs,
417
+ strategyId: n,
418
+ nodeConfigVersion: y,
419
+ ...(s == null ? void 0 : s.size) !== void 0 ? { size: String(s.size) } : {}
420
+ }), this.currentRecommendationId === r && this.configVersion++, n;
421
+ } finally {
422
+ this.blockStates[r] && (this.blockStates[r].legacyConversionLoading = !1);
423
+ }
424
+ },
425
+ /**
426
+ * Undoes the config half of a conversion whose document write never landed, so the
427
+ * panel and the drawer fall back to "Legacy Strategy" instead of showing an assignment
428
+ * the document never received.
429
+ */
430
+ revertLegacyConversion(t, e) {
431
+ const r = this.blockStates[t];
432
+ r && (r.recommendationConfigs = { ...r.recommendationConfigs, ...e }, this.currentRecommendationId === t && this.configVersion++);
433
+ },
459
434
  removeStrategy() {
460
435
  this.patchCurrentBlockConfig({
461
436
  strategyId: "",
462
- strategy: "",
463
- size: B.size
437
+ size: N.size,
438
+ nodeConfigVersion: y
464
439
  });
465
440
  },
466
441
  // ====================================================================
@@ -468,16 +443,16 @@ const $ = () => ({
468
443
  // ====================================================================
469
444
  async fetchRecommendationCreateData() {
470
445
  if (!this.activePredictiveAlgorithms.length) {
471
- if (S) {
472
- await S;
446
+ if (f) {
447
+ await f;
473
448
  return;
474
449
  }
475
- S = (async () => {
450
+ f = (async () => {
476
451
  const {
477
452
  activePredictiveAlgorithms: t,
478
453
  languages: e,
479
454
  currencies: r
480
- } = await f.fetchRecommendationCreateData();
455
+ } = await m.fetchRecommendationCreateData();
481
456
  if (this.activePredictiveAlgorithms = t, this.languages = e, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
482
457
  const n = this.blockStates[this.currentRecommendationId];
483
458
  n.filterStatus = !!n.recommendationConfigs.filters.length;
@@ -485,26 +460,26 @@ const $ = () => ({
485
460
  this.currencyList = r;
486
461
  })();
487
462
  try {
488
- await S;
463
+ await f;
489
464
  } finally {
490
- S = null;
465
+ f = null;
491
466
  }
492
467
  }
493
468
  },
494
469
  async fetchRecommendationFilters() {
495
470
  if (!Object.keys(this.filterList).length) {
496
- if (p) {
497
- await p;
471
+ if (g) {
472
+ await g;
498
473
  return;
499
474
  }
500
- p = (async () => {
501
- const t = await f.fetchRecommendationFilters();
475
+ g = (async () => {
476
+ const t = await m.fetchRecommendationFilters();
502
477
  this.filterList = t;
503
478
  })();
504
479
  try {
505
- await p;
480
+ await g;
506
481
  } finally {
507
- p = null;
482
+ g = null;
508
483
  }
509
484
  }
510
485
  },
@@ -533,7 +508,7 @@ const $ = () => ({
533
508
  const n = [...e.recommendationConfigs.filters];
534
509
  n[r] = {
535
510
  ...t,
536
- isValid: z(t)
511
+ isValid: A(t)
537
512
  }, e.recommendationConfigs.filters = n;
538
513
  }
539
514
  },
@@ -547,10 +522,10 @@ const $ = () => ({
547
522
  let i = 1;
548
523
  n = n.map((o) => o.filterGroup === t.filterGroup ? { ...o, filterNumber: i++ } : o);
549
524
  } else {
550
- const i = [...new Set(n.map((a) => a.filterGroup))].sort((a, u) => a - u), o = new Map(i.map((a, u) => [a, u + 1]));
551
- n = n.map((a) => ({
552
- ...a,
553
- filterGroup: o.get(a.filterGroup) ?? a.filterGroup
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
554
529
  }));
555
530
  }
556
531
  e.recommendationConfigs.filters = n;
@@ -571,7 +546,7 @@ const $ = () => ({
571
546
  }), e.recommendationConfigs.filters = r;
572
547
  },
573
548
  generateFilterQuery() {
574
- return F(this.recommendationConfigs.filters);
549
+ return v(this.recommendationConfigs.filters);
575
550
  },
576
551
  /**
577
552
  * Validation-only check invoked at save-CTA time. Defined as an action
@@ -579,52 +554,33 @@ const $ = () => ({
579
554
  * every block's recommendationConfigs across user edits.
580
555
  */
581
556
  hasInvalidBlock() {
582
- return Object.values(this.blockStates).some((t) => !j(t.recommendationConfigs, this));
583
- },
584
- /**
585
- * Which required fields are missing across the design's blocks, so the save
586
- * toaster can name the cause instead of always showing the generic message.
587
- */
588
- invalidBlockFields() {
589
- return Object.values(this.blockStates).flatMap((t) => V(t.recommendationConfigs, this));
557
+ return Object.values(this.blockStates).some((t) => !O(t.recommendationConfigs, this));
590
558
  },
591
559
  // ====================================================================
592
560
  // Per-Block Product Fetching
593
561
  // ====================================================================
594
562
  async fetchRecommendationProducts() {
595
- if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
596
- return;
597
- const t = [
598
- this.currentRecommendationId,
599
- this.configVersion,
600
- b
601
- ].join(":");
602
- if (y && I === t) {
603
- await y;
604
- return;
605
- }
606
- I = t, y = this._doFetchProducts();
607
- try {
608
- await y;
609
- } finally {
610
- I === t && (y = null, I = "");
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
+ }
611
574
  }
612
575
  },
613
576
  async _doFetchProducts() {
614
- var g;
615
- const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = P();
577
+ var d;
578
+ const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = k();
616
579
  if (r.strategyId && n.isFeatureEnabled("reusableRecommendationStrategy")) {
617
580
  await this._doFetchStrategyProducts(t);
618
581
  return;
619
582
  }
620
- const s = (h.get(t) ?? 0) + 1;
621
- h.set(t, s);
622
- const i = parseInt(r.size) || 6;
623
- if (!r.strategy) {
624
- this._setBlockProducts(t, [], i);
625
- return;
626
- }
627
- const o = r.filters.filter((d) => d.isValid), a = F(o), u = ((g = C().find((d) => d.key === r.strategy)) == null ? void 0 : g.path) || "", c = {
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 = {
628
584
  locale: r.language,
629
585
  currency: r.currencySettings.value,
630
586
  partnerName: n.partnerName,
@@ -632,21 +588,30 @@ const $ = () => ({
632
588
  details: !0,
633
589
  campaignId: n.variationId
634
590
  };
635
- r.strategy === "manualMerchandising" ? c.productId = T(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(
636
- c,
637
- O(n.partnerName, r.strategy)
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)
638
594
  );
639
- let m;
595
+ let l;
640
596
  try {
641
- m = await f.fetchRecommendationProducts(u, c);
597
+ l = await m.fetchRecommendationProducts(o, a);
642
598
  } catch {
643
- m = [];
599
+ l = [];
644
600
  }
645
- h.get(t) === s && this._setBlockProducts(t, m, i);
601
+ this._setBlockProducts(t, l, c);
602
+ },
603
+ /**
604
+ * Block-scoped counterpart of `fetchRecommendationProducts`, which only ever serves the
605
+ * selected block. The legacy conversion must refresh the products of the block that
606
+ * started it even after the user selected another one.
607
+ */
608
+ async fetchStrategyProductsForBlock(t) {
609
+ var e;
610
+ (e = this.blockStates[t]) != null && e.recommendationConfigs.strategyId && await this._doFetchStrategyProducts(t);
646
611
  },
647
612
  async _doFetchStrategyProducts(t) {
648
- const e = (h.get(t) ?? 0) + 1;
649
- h.set(t, e);
613
+ const e = (p.get(t) ?? 0) + 1;
614
+ p.set(t, e);
650
615
  const { recommendationConfigs: r } = this.blockStates[t];
651
616
  let n = [], s = parseInt(r.size) || 6;
652
617
  try {
@@ -656,25 +621,25 @@ const $ = () => ({
656
621
  );
657
622
  if (i) {
658
623
  s = parseInt(String(i.size ?? "")) || s;
659
- const o = r.currencySettings.value, u = (await this.resolveStrategyUrlTemplate(i, o)).replaceAll("{locale}", r.language).replaceAll("{currency}", o), c = u.indexOf("?"), m = c === -1 ? u : u.slice(0, c), g = (c === -1 ? "" : u.slice(c + 1)).split("&").filter((k) => k && !k.startsWith("user_id=")), d = g.length ? `${m}?${g.join("&")}` : m;
660
- d && (n = await f.fetchRecommendationProductsByUrl(d));
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));
661
626
  }
662
627
  } catch {
663
628
  n = [];
664
629
  }
665
- h.get(t) === e && this._setBlockProducts(t, n, s);
630
+ p.get(t) === e && this._setBlockProducts(t, n, s);
666
631
  },
667
632
  _setBlockProducts(t, e, r) {
668
633
  if (!this.blockStates[t])
669
634
  return;
670
- const n = e.length > 0 ? e : v(r);
635
+ const n = e.length > 0 ? e : C(r);
671
636
  this.blockStates[t].recommendationProducts = [
672
637
  ...n.slice(0, r),
673
- ...v(Math.max(0, r - n.length))
638
+ ...C(Math.max(0, r - n.length))
674
639
  ];
675
640
  }
676
641
  }
677
642
  });
678
643
  export {
679
- it as useRecommendationExtensionStore
644
+ et as useRecommendationExtensionStore
680
645
  };
@@ -0,0 +1,42 @@
1
+ import "../constants/selectors.js";
2
+ import { CURRENT_CONFIG_VERSION as m } from "../constants/defaultConfig.js";
3
+ import { sanitizeProductIds as p } from "./productIds.js";
4
+ const d = "manualMerchandising";
5
+ function N({ strategyId: e, nodeConfigVersion: t }) {
6
+ return !e && t < m;
7
+ }
8
+ const o = (e) => {
9
+ const t = String(e ?? "").trim();
10
+ return /^[1-9]\d*$/.test(t) ? t : null;
11
+ };
12
+ function f({ campaignId: e, templateLibraryId: t }) {
13
+ if (o(e))
14
+ return { scope: "campaign" };
15
+ const n = o(t);
16
+ return n ? { scope: "template", templateId: n } : { scope: "campaign" };
17
+ }
18
+ function y({
19
+ blockId: e,
20
+ url: t,
21
+ campaignId: n,
22
+ variationId: a,
23
+ strategy: c,
24
+ productIds: s,
25
+ target: r
26
+ }) {
27
+ const i = c === d ? p(s).split(",").filter(Boolean) : [];
28
+ return {
29
+ ...r.scope === "template" ? { templateId: Number.parseInt(r.templateId) } : {
30
+ campaignId: Number.parseInt(n) || 0,
31
+ variationId: Number.parseInt(a) || 0
32
+ },
33
+ blockId: String(e),
34
+ url: t,
35
+ ...i.length ? { productIds: i } : {}
36
+ };
37
+ }
38
+ export {
39
+ y as buildLegacyMigrationPayload,
40
+ N as isLegacyStrategyConfig,
41
+ f as resolveMigrationTarget
42
+ };
@@ -1,32 +1,22 @@
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;
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;
8
8
  }
9
- function P(n, r, c, l) {
10
- const i = o == null ? void 0 : o.getDocumentRootHtmlNode();
11
- if (!o || !i || !("querySelector" in i))
9
+ function N(r, c, i, u) {
10
+ const n = e == null ? void 0 : e.getDocumentRootHtmlNode();
11
+ if (!e || !n || !("querySelector" in n))
12
12
  return !1;
13
- const e = i.querySelector(`[${s}="${n}"]`);
14
- if (!e)
13
+ const t = n.querySelector(`[${m}="${r}"]`);
14
+ if (!t)
15
15
  return !1;
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;
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);
28
18
  }
29
19
  export {
30
- P as commitBlockChangeOutsidePanel,
31
- D as registerNodeConfigWriter
20
+ N as commitBlockChangeOutsidePanel,
21
+ M as registerNodeConfigWriter
32
22
  };