@useinsider/guido 3.18.1 → 3.18.2-beta.320e07f

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 (48) hide show
  1. package/dist/components/Guido.vue.js +3 -3
  2. package/dist/components/Guido.vue2.js +30 -30
  3. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue.js +13 -11
  4. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue2.js +76 -57
  5. package/dist/components/organisms/extensions/recommendation/RemoveStrategyModal.vue2.js +23 -37
  6. package/dist/components/organisms/onboarding/OnboardingWrapper.vue2.js +20 -18
  7. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue.js +21 -0
  8. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue2.js +47 -0
  9. package/dist/components/organisms/save-as-template/SaveAsTemplateDrawer.vue2.js +34 -34
  10. package/dist/composables/useCustomInterfaceAppearance.js +56 -46
  11. package/dist/composables/useHtmlValidator.js +87 -82
  12. package/dist/composables/useHttp.js +24 -22
  13. package/dist/composables/useRecommendation.js +40 -35
  14. package/dist/composables/useStripo.js +30 -30
  15. package/dist/enums/extensions/recommendationBlock.js +38 -31
  16. package/dist/extensions/Blocks/Recommendation/block.js +43 -39
  17. package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +21 -20
  18. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +55 -44
  19. package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +84 -52
  20. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +74 -74
  21. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +190 -90
  22. package/dist/extensions/Blocks/Recommendation/utils/nodeConfigWriter.js +26 -16
  23. package/dist/extensions/Blocks/Recommendation/utils/strategyCommit.js +24 -0
  24. package/dist/extensions/Blocks/Recommendation/validation/requiredFields.js +21 -12
  25. package/dist/guido.css +1 -1
  26. package/dist/node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js +29 -28
  27. package/dist/package.json.js +1 -1
  28. package/dist/services/templateLibraryApi.js +17 -14
  29. package/dist/src/components/organisms/onboarding/RecommendationStrategyOnboarding.vue.d.ts +2 -0
  30. package/dist/src/enums/extensions/recommendationBlock.d.ts +2 -0
  31. package/dist/src/extensions/Blocks/Recommendation/constants/defaultConfig.d.ts +1 -0
  32. package/dist/src/extensions/Blocks/Recommendation/constants/index.d.ts +1 -1
  33. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +7 -1
  34. package/dist/src/extensions/Blocks/Recommendation/controls/main/strategy.d.ts +9 -0
  35. package/dist/src/extensions/Blocks/Recommendation/controls/main/utils.d.ts +7 -2
  36. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +87 -0
  37. package/dist/src/extensions/Blocks/Recommendation/utils/strategyCommit.d.ts +14 -0
  38. package/dist/src/extensions/Blocks/Recommendation/validation/requiredFields.d.ts +4 -0
  39. package/dist/src/stores/onboarding.d.ts +377 -0
  40. package/dist/src/utils/genericUtil.d.ts +6 -0
  41. package/dist/src/utils/liquidDynamicContent.d.ts +20 -0
  42. package/dist/static/styles/components/recommendation-strategy.css.js +34 -4
  43. package/dist/static/styles/components/skeleton.css.js +23 -0
  44. package/dist/stores/onboarding.js +41 -36
  45. package/dist/utils/genericUtil.js +63 -54
  46. package/dist/utils/liquidDynamicContent.js +15 -0
  47. package/dist/utils/templatePreparation.js +61 -59
  48. package/package.json +7 -7
@@ -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";
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
3
  import { useConfigStore as P } from "../../../../stores/config.js";
4
- import { defineStore as F } from "pinia";
4
+ import { defineStore as L } 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 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;
18
+ function G() {
18
19
  return {
19
- cardsInRow: U,
20
+ cardsInRow: x,
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: E,
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: P().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: G(),
52
57
  recommendationProducts: [],
53
58
  filterStatus: !1,
54
59
  filterSelectionDrawerStatus: !1,
@@ -56,10 +61,11 @@ function C() {
56
61
  removeStrategyModalStatus: !1,
57
62
  filterGroup: 1,
58
63
  isInitialized: !1,
59
- filterSnapshot: null
64
+ filterSnapshot: null,
65
+ syncedConfigFingerprint: null
60
66
  };
61
67
  }
62
- const z = () => ({
68
+ const $ = () => ({
63
69
  recommendationCampaignUrls: {},
64
70
  activePredictiveAlgorithms: [],
65
71
  languages: {},
@@ -68,11 +74,14 @@ const z = () => ({
68
74
  strategies: [],
69
75
  strategiesLoading: !1,
70
76
  strategyUrlTemplates: {},
77
+ strategiesFetched: !1,
78
+ strategiesSettled: !1,
71
79
  blockStates: {},
72
80
  currentRecommendationId: null,
81
+ strategyPanelOpen: !1,
73
82
  configVersion: 0
74
- }), Y = F("guidoRecommendationExtension", {
75
- state: () => z(),
83
+ }), it = L("guidoRecommendationExtension", {
84
+ state: () => $(),
76
85
  getters: {
77
86
  // ====================================================================
78
87
  // Proxy Getters — Backward Compatible Access to Current Block State
@@ -82,7 +91,7 @@ const z = () => ({
82
91
  * This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
83
92
  */
84
93
  recommendationConfigs(t) {
85
- return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : w();
94
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : G();
86
95
  },
87
96
  /**
88
97
  * Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
@@ -137,8 +146,8 @@ const z = () => ({
137
146
  return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
138
147
  },
139
148
  getActivePredictiveAlgorithms: (t) => {
140
- const e = b(), r = [];
141
- return t.activePredictiveAlgorithms.filter((n) => !L.includes(n)).forEach((n) => {
149
+ const e = C(), r = [];
150
+ return t.activePredictiveAlgorithms.filter((n) => !M.includes(n)).forEach((n) => {
142
151
  r.push(...e.filter((s) => s.id === n));
143
152
  }), r.map((n) => ({
144
153
  text: n.name,
@@ -156,7 +165,7 @@ const z = () => ({
156
165
  getFilterList() {
157
166
  return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
158
167
  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, {
168
+ return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : U.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
160
169
  text: t.displayName,
161
170
  value: e,
162
171
  type: t.attributeType
@@ -178,7 +187,7 @@ const z = () => ({
178
187
  setCurrentBlock(t) {
179
188
  this.blockStates[t] || (this.blockStates = {
180
189
  ...this.blockStates,
181
- [t]: C()
190
+ [t]: w()
182
191
  }), this.currentRecommendationId = t;
183
192
  },
184
193
  /**
@@ -238,15 +247,15 @@ const z = () => ({
238
247
  * `isInitialized`) alone.
239
248
  */
240
249
  seedBlockUrlConfig(t, e) {
241
- const r = (o, c) => o === "." || o === "," || o === " " ? o : c, n = {
250
+ const r = (o, a) => o === "." || o === "," || o === " " ? o : a, n = {
242
251
  name: e.currencyCode,
243
252
  value: e.currencyCode,
244
- symbol: e.currencyCode,
253
+ symbol: e.currencySymbol,
245
254
  alignment: e.currencyAlignment === "before" ? "0" : "1",
246
255
  decimalCount: String(e.currencyDecimalCount),
247
256
  decimalSeparator: r(e.currencyDecimalSeparator, ","),
248
257
  thousandSeparator: r(e.currencyThousandSeparator, ".")
249
- }, s = !this.blockStates[t], i = s ? C() : this.blockStates[t];
258
+ }, s = !this.blockStates[t], i = s ? w() : this.blockStates[t];
250
259
  i.recommendationConfigs = {
251
260
  ...i.recommendationConfigs,
252
261
  strategy: e.strategy,
@@ -285,7 +294,7 @@ const z = () => ({
285
294
  * Creates a filter with the first available attribute and operator pre-selected.
286
295
  */
287
296
  createDefaultFilter(t, e) {
288
- const [r] = this.getFilterList, [n] = G(r == null ? void 0 : r.type);
297
+ const [r] = this.getFilterList, [n] = N(r == null ? void 0 : r.type);
289
298
  return {
290
299
  type: "standardFilter",
291
300
  attribute: (r == null ? void 0 : r.value) ?? "",
@@ -345,29 +354,97 @@ const z = () => ({
345
354
  this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].removeStrategyModalStatus = !1);
346
355
  },
347
356
  async fetchStrategies() {
348
- if (S) {
349
- await S;
357
+ for (; R; )
358
+ try {
359
+ await R;
360
+ } catch {
361
+ }
362
+ if (this.strategiesFetched)
350
363
  return;
351
- }
352
- S = (async () => {
364
+ const t = b, e = (async () => {
353
365
  this.strategiesLoading = !0;
354
366
  try {
355
- this.strategies = await u.fetchRecommendationStrategies();
367
+ const r = await f.fetchRecommendationStrategies();
368
+ if (t !== b)
369
+ return;
370
+ this.strategies = r, this.strategiesFetched = !0;
356
371
  } finally {
357
- this.strategiesLoading = !1;
372
+ this.strategiesLoading = !1, t === b && (this.strategiesSettled = !0);
358
373
  }
359
374
  })();
375
+ R = e;
360
376
  try {
361
- await S;
377
+ await e;
362
378
  } finally {
363
- S = null;
379
+ R = null;
364
380
  }
365
381
  },
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
+ },
366
443
  async resolveStrategyUrlTemplate(t, e) {
367
444
  const r = typeof t.updated_at == "string" ? t.updated_at : "", n = r ? `${t.strategy_id}:${r}:${e}` : "", s = n ? this.strategyUrlTemplates[n] : void 0;
368
445
  if (s)
369
446
  return s;
370
- const { full_path: i = "" } = await u.generateStrategyEndpoint(t, e) ?? {};
447
+ const { full_path: i = "" } = await f.generateStrategyEndpoint(t, e) ?? {};
371
448
  return n && i && (this.strategyUrlTemplates = {
372
449
  ...this.strategyUrlTemplates,
373
450
  [n]: i
@@ -380,23 +457,27 @@ const z = () => ({
380
457
  });
381
458
  },
382
459
  removeStrategy() {
383
- this.patchCurrentBlockConfig({ strategyId: "", size: N.size });
460
+ this.patchCurrentBlockConfig({
461
+ strategyId: "",
462
+ strategy: "",
463
+ size: B.size
464
+ });
384
465
  },
385
466
  // ====================================================================
386
467
  // Shared Data Fetching (fetched once, used by all blocks)
387
468
  // ====================================================================
388
469
  async fetchRecommendationCreateData() {
389
470
  if (!this.activePredictiveAlgorithms.length) {
390
- if (f) {
391
- await f;
471
+ if (S) {
472
+ await S;
392
473
  return;
393
474
  }
394
- f = (async () => {
475
+ S = (async () => {
395
476
  const {
396
477
  activePredictiveAlgorithms: t,
397
478
  languages: e,
398
479
  currencies: r
399
- } = await u.fetchRecommendationCreateData();
480
+ } = await f.fetchRecommendationCreateData();
400
481
  if (this.activePredictiveAlgorithms = t, this.languages = e, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
401
482
  const n = this.blockStates[this.currentRecommendationId];
402
483
  n.filterStatus = !!n.recommendationConfigs.filters.length;
@@ -404,26 +485,26 @@ const z = () => ({
404
485
  this.currencyList = r;
405
486
  })();
406
487
  try {
407
- await f;
488
+ await S;
408
489
  } finally {
409
- f = null;
490
+ S = null;
410
491
  }
411
492
  }
412
493
  },
413
494
  async fetchRecommendationFilters() {
414
495
  if (!Object.keys(this.filterList).length) {
415
- if (h) {
416
- await h;
496
+ if (p) {
497
+ await p;
417
498
  return;
418
499
  }
419
- h = (async () => {
420
- const t = await u.fetchRecommendationFilters();
500
+ p = (async () => {
501
+ const t = await f.fetchRecommendationFilters();
421
502
  this.filterList = t;
422
503
  })();
423
504
  try {
424
- await h;
505
+ await p;
425
506
  } finally {
426
- h = null;
507
+ p = null;
427
508
  }
428
509
  }
429
510
  },
@@ -452,7 +533,7 @@ const z = () => ({
452
533
  const n = [...e.recommendationConfigs.filters];
453
534
  n[r] = {
454
535
  ...t,
455
- isValid: B(t)
536
+ isValid: z(t)
456
537
  }, e.recommendationConfigs.filters = n;
457
538
  }
458
539
  },
@@ -466,10 +547,10 @@ const z = () => ({
466
547
  let i = 1;
467
548
  n = n.map((o) => o.filterGroup === t.filterGroup ? { ...o, filterNumber: i++ } : o);
468
549
  } 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) => ({
471
- ...c,
472
- filterGroup: o.get(c.filterGroup) ?? c.filterGroup
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
473
554
  }));
474
555
  }
475
556
  e.recommendationConfigs.filters = n;
@@ -490,7 +571,7 @@ const z = () => ({
490
571
  }), e.recommendationConfigs.filters = r;
491
572
  },
492
573
  generateFilterQuery() {
493
- return k(this.recommendationConfigs.filters);
574
+ return F(this.recommendationConfigs.filters);
494
575
  },
495
576
  /**
496
577
  * Validation-only check invoked at save-CTA time. Defined as an action
@@ -498,33 +579,52 @@ const z = () => ({
498
579
  * every block's recommendationConfigs across user edits.
499
580
  */
500
581
  hasInvalidBlock() {
501
- return Object.values(this.blockStates).some((t) => !M(t.recommendationConfigs, this));
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));
502
590
  },
503
591
  // ====================================================================
504
592
  // Per-Block Product Fetching
505
593
  // ====================================================================
506
594
  async fetchRecommendationProducts() {
507
- if (!(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])) {
508
- if (g) {
509
- await g;
510
- return;
511
- }
512
- g = this._doFetchProducts();
513
- try {
514
- await g;
515
- } finally {
516
- g = null;
517
- }
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 = "");
518
611
  }
519
612
  },
520
613
  async _doFetchProducts() {
521
- var d;
614
+ var g;
522
615
  const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = P();
523
616
  if (r.strategyId && n.isFeatureEnabled("reusableRecommendationStrategy")) {
524
617
  await this._doFetchStrategyProducts(t);
525
618
  return;
526
619
  }
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 = {
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 = {
528
628
  locale: r.language,
529
629
  currency: r.currencySettings.value,
530
630
  partnerName: n.partnerName,
@@ -532,21 +632,21 @@ const z = () => ({
532
632
  details: !0,
533
633
  campaignId: n.variationId
534
634
  };
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(
536
- a,
537
- x(n.partnerName, r.strategy)
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)
538
638
  );
539
- let l;
639
+ let m;
540
640
  try {
541
- l = await u.fetchRecommendationProducts(o, a);
641
+ m = await f.fetchRecommendationProducts(u, c);
542
642
  } catch {
543
- l = [];
643
+ m = [];
544
644
  }
545
- this._setBlockProducts(t, l, c);
645
+ h.get(t) === s && this._setBlockProducts(t, m, i);
546
646
  },
547
647
  async _doFetchStrategyProducts(t) {
548
- const e = (p.get(t) ?? 0) + 1;
549
- p.set(t, e);
648
+ const e = (h.get(t) ?? 0) + 1;
649
+ h.set(t, e);
550
650
  const { recommendationConfigs: r } = this.blockStates[t];
551
651
  let n = [], s = parseInt(r.size) || 6;
552
652
  try {
@@ -556,25 +656,25 @@ const z = () => ({
556
656
  );
557
657
  if (i) {
558
658
  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));
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));
561
661
  }
562
662
  } catch {
563
663
  n = [];
564
664
  }
565
- p.get(t) === e && this._setBlockProducts(t, n, s);
665
+ h.get(t) === e && this._setBlockProducts(t, n, s);
566
666
  },
567
667
  _setBlockProducts(t, e, r) {
568
668
  if (!this.blockStates[t])
569
669
  return;
570
- const n = e.length > 0 ? e : R(r);
670
+ const n = e.length > 0 ? e : v(r);
571
671
  this.blockStates[t].recommendationProducts = [
572
672
  ...n.slice(0, r),
573
- ...R(Math.max(0, r - n.length))
673
+ ...v(Math.max(0, r - n.length))
574
674
  ];
575
675
  }
576
676
  }
577
677
  });
578
678
  export {
579
- Y as useRecommendationExtensionStore
679
+ it as useRecommendationExtensionStore
580
680
  };
@@ -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 a, updateProductContentInPlace as d, regenerateProductRowsWithStyles as l } from "../controls/main/utils.js";
4
- import { RecommendationConfigService as s } from "../services/configService.js";
5
- let e = null;
6
- function N(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 D(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 s.stageConfig(o, t, c), a(t) ? (o.apply(new f(u)), !0) : (d({ currentNode: t, documentModifier: o, products: i }) || l({ currentNode: t, documentModifier: o, products: i }), !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
- D as commitBlockChangeOutsidePanel,
21
- N as registerNodeConfigWriter
30
+ P as commitBlockChangeOutsidePanel,
31
+ D as registerNodeConfigWriter
22
32
  };
@@ -0,0 +1,24 @@
1
+ import { useRecommendationExtensionStore as s } from "../store/recommendation.js";
2
+ import { commitBlockChangeOutsidePanel as d } from "./nodeConfigWriter.js";
3
+ async function u(e, n) {
4
+ const t = s(), o = t.blockStates[e];
5
+ if (!o)
6
+ return !1;
7
+ const { strategyId: a, strategy: c, size: i } = 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 r = d(
15
+ e,
16
+ { strategyId: a, strategy: c, size: i },
17
+ o.recommendationProducts,
18
+ n
19
+ );
20
+ return r || console.error("[recommendation] strategy change did not reach a node", { blockId: e }), r;
21
+ }
22
+ export {
23
+ u as commitStrategyChange
24
+ };
@@ -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
  };
package/dist/guido.css CHANGED
@@ -1 +1 @@
1
- @charset "UTF-8";.gap-16[data-v-5553d071],.gap-16[data-v-0e1b0c54]{gap:16px}[data-v-73199fa4] .in-button-v2__wrapper{line-height:0}[data-v-22226124] .in-segments-wrapper__button_selected,[data-v-22226124] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb;color:#0010ac;border-color:#0010ac}[data-v-2cb418af] .in-progress-wrapper__progress p span:last-child{display:none!important}[data-v-2cb418af] .in-progress-description-status{display:none!important}.view-options-wrapper[data-v-87033101]{position:relative;display:inline-block}.new-tag[data-v-87033101]{position:absolute;top:-8px;right:-16px;z-index:10}[data-v-87033101] .guido__view-option-selection-desktop svg,[data-v-87033101] .guido__view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-87033101] .in-segments-wrapper__button_selected,[data-v-87033101] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}[data-v-87033101] .in-tooltip-wrapper-v2__icon{cursor:pointer}.editor-toolbar[data-v-173c3a40]{gap:4px}.version-history-item[data-v-ee4b9c3f]{flex-basis:200px}.version-history[data-v-fad98586]{gap:8px}.version-history__toolbar[data-v-fad98586]{gap:4px}.view-options-wrapper[data-v-6ebabfe4]{position:relative;display:inline-block}.new-tag[data-v-6ebabfe4]{position:absolute;top:-8px;right:-16px;z-index:10}[data-v-6ebabfe4] .guido__verion-history-view-option-selection-desktop svg,[data-v-6ebabfe4] .guido__verion-history-view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-6ebabfe4] .in-segments-wrapper__button_selected,[data-v-6ebabfe4] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}[data-v-6ebabfe4] .in-tooltip-wrapper-v2__icon{cursor:pointer}.auto-save-toggle[data-v-2c964af4]{position:relative}.auto-save-toggle__info-box[data-v-2c964af4]{position:absolute;top:100%;left:0;z-index:10;width:280px}.editor-actions[data-v-1ca6baf0]{gap:4px}.header-wrapper[data-v-d11dd577]{min-width:1000px}.guido-loading__wrapper[data-v-07c4b2d8]{height:100%;top:75px!important;bottom:0!important}.guido-editor__wrapper[data-v-050dfa52]{--ribbon-offset: 0px;position:relative;width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__container[data-v-050dfa52]{width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__no-header[data-v-050dfa52]{height:calc(100vh - 75px - var(--ribbon-offset))}[data-v-293f1c47] .in-breadcrumb-wrapper__links{cursor:pointer}.templates-wrapper[data-v-91a26acd]{gap:16px;grid-template-columns:repeat(3,1fr)}.templates-wrapper .template-wrapper[data-v-91a26acd]{cursor:pointer}.templates-wrapper .template-wrapper .template-container[data-v-91a26acd]{height:274px;padding:2px;transition:none}.templates-wrapper .template-wrapper .template-container.selected[data-v-91a26acd]{padding:1px}.templates-wrapper .template-wrapper .template-container .thumbnail[data-v-91a26acd]{object-fit:cover;transform:scale(1)}[data-v-b9a93c6e] .guido__verion-history-view-option-selection-desktop svg,[data-v-b9a93c6e] .guido__verion-history-view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-b9a93c6e] .in-segments-wrapper__button_selected,[data-v-b9a93c6e] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}.error-list[data-v-c3fd5d4b]{gap:16px}.desktop-browser-header[data-v-d86c5af5]{height:79px;min-height:79px}.desktop-browser-header__left[data-v-d86c5af5]{-webkit-user-drag:none;height:79px;width:378px}.desktop-browser-header__center[data-v-d86c5af5]{height:79px;background-repeat:repeat-x;background-size:auto 100%;background-position:left top}.desktop-browser-header__right[data-v-d86c5af5]{-webkit-user-drag:none;height:79px;width:112px}.desktop-preview[data-v-cf946232]{min-width:602px;height:70vh;min-height:583px;border-radius:10px}.desktop-preview iframe[data-v-cf946232]{min-height:504px}.iframe-wrapper[data-v-f255b2bb]{width:258px}.iframe-scaled[data-v-f255b2bb]{width:320px;height:124.0310077519%;transform:scale(.80625);transform-origin:top left}.cropped-text[data-v-eb3d05d7]{width:220px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mobile-preview-wrapper__phone[data-v-3f472f96]{width:282px}.mobile-preview-wrapper__phone img[data-v-3f472f96]{object-fit:cover;border-radius:44px}.mobile-preview-wrapper__content[data-v-3f472f96]{width:258px;height:450px;left:12px}[data-v-6bcaca8a] .vueperslides__parallax-wrapper{padding-bottom:110px!important}[data-v-f16f20f8] .vueperslides__bullets{pointer-events:none!important}[data-v-f16f20f8] .vueperslides__parallax-wrapper{padding-bottom:110px!important}[data-v-bb3cca55] .vueperslides__bullets{pointer-events:none!important}[data-v-bb3cca55] .vueperslides__parallax-wrapper{height:110px!important;padding-bottom:110px!important}[data-v-b09e80c4] .vueperslides__bullets{pointer-events:none!important}[data-v-b09e80c4] .vueperslides__parallax-wrapper{height:110px!important;padding-bottom:110px!important}[data-v-9bdef600] .vueperslides__parallax-wrapper{padding-bottom:110px!important}.assign-strategy-drawer-content .assign-strategy-select[data-v-2580a7d9]{width:100%;max-width:none}.assign-strategy-drawer-content .assign-strategy-select[data-v-2580a7d9] .in-dropdown-box__container{width:413px}.assign-strategy-drawer .spaced-dot-sep{background-image:linear-gradient(90deg,#91949f 20%,#fff0 0);background-position:center bottom;background-repeat:repeat-x;background-size:5px 1px}.assign-strategy-drawer .spaced-dot-sep--top{background-position:center top}.assign-strategy-drawer .spaced-dot-sep--fc-n:first-child{background:none}
1
+ @charset "UTF-8";.gap-16[data-v-5553d071],.gap-16[data-v-0e1b0c54]{gap:16px}[data-v-73199fa4] .in-button-v2__wrapper{line-height:0}[data-v-22226124] .in-segments-wrapper__button_selected,[data-v-22226124] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb;color:#0010ac;border-color:#0010ac}[data-v-2cb418af] .in-progress-wrapper__progress p span:last-child{display:none!important}[data-v-2cb418af] .in-progress-description-status{display:none!important}.view-options-wrapper[data-v-87033101]{position:relative;display:inline-block}.new-tag[data-v-87033101]{position:absolute;top:-8px;right:-16px;z-index:10}[data-v-87033101] .guido__view-option-selection-desktop svg,[data-v-87033101] .guido__view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-87033101] .in-segments-wrapper__button_selected,[data-v-87033101] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}[data-v-87033101] .in-tooltip-wrapper-v2__icon{cursor:pointer}.editor-toolbar[data-v-173c3a40]{gap:4px}.version-history-item[data-v-ee4b9c3f]{flex-basis:200px}.version-history[data-v-fad98586]{gap:8px}.version-history__toolbar[data-v-fad98586]{gap:4px}.view-options-wrapper[data-v-6ebabfe4]{position:relative;display:inline-block}.new-tag[data-v-6ebabfe4]{position:absolute;top:-8px;right:-16px;z-index:10}[data-v-6ebabfe4] .guido__verion-history-view-option-selection-desktop svg,[data-v-6ebabfe4] .guido__verion-history-view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-6ebabfe4] .in-segments-wrapper__button_selected,[data-v-6ebabfe4] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}[data-v-6ebabfe4] .in-tooltip-wrapper-v2__icon{cursor:pointer}.auto-save-toggle[data-v-2c964af4]{position:relative}.auto-save-toggle__info-box[data-v-2c964af4]{position:absolute;top:100%;left:0;z-index:10;width:280px}.editor-actions[data-v-1ca6baf0]{gap:4px}.header-wrapper[data-v-d11dd577]{min-width:1000px}.guido-loading__wrapper[data-v-07c4b2d8]{height:100%;top:75px!important;bottom:0!important}.guido-editor__wrapper[data-v-cc5ee925]{--ribbon-offset: 0px;position:relative;width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__container[data-v-cc5ee925]{width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__no-header[data-v-cc5ee925]{height:calc(100vh - 75px - var(--ribbon-offset))}[data-v-293f1c47] .in-breadcrumb-wrapper__links{cursor:pointer}.templates-wrapper[data-v-91a26acd]{gap:16px;grid-template-columns:repeat(3,1fr)}.templates-wrapper .template-wrapper[data-v-91a26acd]{cursor:pointer}.templates-wrapper .template-wrapper .template-container[data-v-91a26acd]{height:274px;padding:2px;transition:none}.templates-wrapper .template-wrapper .template-container.selected[data-v-91a26acd]{padding:1px}.templates-wrapper .template-wrapper .template-container .thumbnail[data-v-91a26acd]{object-fit:cover;transform:scale(1)}[data-v-b9a93c6e] .guido__verion-history-view-option-selection-desktop svg,[data-v-b9a93c6e] .guido__verion-history-view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-b9a93c6e] .in-segments-wrapper__button_selected,[data-v-b9a93c6e] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}.error-list[data-v-c3fd5d4b]{gap:16px}.desktop-browser-header[data-v-d86c5af5]{height:79px;min-height:79px}.desktop-browser-header__left[data-v-d86c5af5]{-webkit-user-drag:none;height:79px;width:378px}.desktop-browser-header__center[data-v-d86c5af5]{height:79px;background-repeat:repeat-x;background-size:auto 100%;background-position:left top}.desktop-browser-header__right[data-v-d86c5af5]{-webkit-user-drag:none;height:79px;width:112px}.desktop-preview[data-v-cf946232]{min-width:602px;height:70vh;min-height:583px;border-radius:10px}.desktop-preview iframe[data-v-cf946232]{min-height:504px}.iframe-wrapper[data-v-f255b2bb]{width:258px}.iframe-scaled[data-v-f255b2bb]{width:320px;height:124.0310077519%;transform:scale(.80625);transform-origin:top left}.cropped-text[data-v-eb3d05d7]{width:220px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mobile-preview-wrapper__phone[data-v-3f472f96]{width:282px}.mobile-preview-wrapper__phone img[data-v-3f472f96]{object-fit:cover;border-radius:44px}.mobile-preview-wrapper__content[data-v-3f472f96]{width:258px;height:450px;left:12px}[data-v-6bcaca8a] .vueperslides__parallax-wrapper{padding-bottom:110px!important}[data-v-f16f20f8] .vueperslides__bullets{pointer-events:none!important}[data-v-f16f20f8] .vueperslides__parallax-wrapper{padding-bottom:110px!important}[data-v-bb3cca55] .vueperslides__bullets{pointer-events:none!important}[data-v-bb3cca55] .vueperslides__parallax-wrapper{height:110px!important;padding-bottom:110px!important}[data-v-ec2027ff] .vueperslides__bullets,[data-v-b09e80c4] .vueperslides__bullets{pointer-events:none!important}[data-v-b09e80c4] .vueperslides__parallax-wrapper{height:110px!important;padding-bottom:110px!important}[data-v-9bdef600] .vueperslides__parallax-wrapper{padding-bottom:110px!important}.assign-strategy-drawer-content .assign-strategy-select[data-v-800656f5]{width:100%;max-width:none}.assign-strategy-drawer-content .assign-strategy-select[data-v-800656f5] .in-skeleton{width:100%!important}.assign-strategy-drawer-content .assign-strategy-select[data-v-800656f5] .in-dropdown-box__container{width:413px}.assign-strategy-drawer .spaced-dot-sep{background-image:linear-gradient(90deg,#91949f 20%,#fff0 0);background-position:center bottom;background-repeat:repeat-x;background-size:5px 1px}.assign-strategy-drawer .spaced-dot-sep--top{background-position:center top}.assign-strategy-drawer .spaced-dot-sep--fc-n:first-child{background:none}