@useinsider/guido 3.18.1-beta.280b4e1 → 3.18.1-beta.2e195ed

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 (36) 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 +65 -53
  5. package/dist/components/organisms/onboarding/OnboardingWrapper.vue2.js +20 -18
  6. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue.js +21 -0
  7. package/dist/components/organisms/onboarding/RecommendationStrategyOnboarding.vue2.js +47 -0
  8. package/dist/composables/useCustomInterfaceAppearance.js +56 -48
  9. package/dist/composables/useHtmlValidator.js +84 -79
  10. package/dist/composables/useRecommendation.js +21 -18
  11. package/dist/composables/useStripo.js +30 -30
  12. package/dist/enums/extensions/recommendationBlock.js +38 -31
  13. package/dist/extensions/Blocks/Recommendation/block.js +43 -39
  14. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +62 -57
  15. package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +84 -52
  16. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +165 -78
  17. package/dist/extensions/Blocks/Recommendation/validation/requiredFields.js +21 -12
  18. package/dist/guido.css +1 -1
  19. package/dist/library.js +13 -15
  20. package/dist/src/components/organisms/onboarding/RecommendationStrategyOnboarding.vue.d.ts +2 -0
  21. package/dist/src/enums/extensions/recommendationBlock.d.ts +2 -0
  22. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +2 -0
  23. package/dist/src/extensions/Blocks/Recommendation/controls/main/strategy.d.ts +9 -0
  24. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +74 -0
  25. package/dist/src/extensions/Blocks/Recommendation/validation/requiredFields.d.ts +4 -0
  26. package/dist/src/library.d.ts +0 -2
  27. package/dist/src/public/trackingIds.d.ts +2 -6
  28. package/dist/src/stores/guido-email-editor.d.ts +27 -27
  29. package/dist/src/stores/onboarding.d.ts +377 -0
  30. package/dist/static/styles/components/recommendation-strategy.css.js +51 -4
  31. package/dist/stores/guido-email-editor.js +4 -0
  32. package/dist/stores/onboarding.js +41 -36
  33. package/dist/utils/templatePreparation.js +50 -50
  34. package/package.json +1 -5
  35. package/dist/public/editorState.js +0 -4
  36. package/dist/src/public/editorState.d.ts +0 -14
@@ -1,22 +1,23 @@
1
- import { getRecommendationFeedSourceMaps as b, getOperatorOptions as G, PriceAttributes as v } from "../../../../enums/extensions/recommendationBlock.js";
2
- import { useRecommendationApi as D } from "../../../../services/recommendationApi.js";
3
- import { useConfigStore as P } from "../../../../stores/config.js";
4
- import { defineStore as F } from "pinia";
1
+ import { getRecommendationFeedSourceMaps as k, strategyEditPath as G, URLS as D, getOperatorOptions as _, PriceAttributes as L } from "../../../../enums/extensions/recommendationBlock.js";
2
+ import { useRecommendationApi as E } from "../../../../services/recommendationApi.js";
3
+ import { useConfigStore as F } from "../../../../stores/config.js";
4
+ import { defineStore as N } from "pinia";
5
5
  import "../constants/selectors.js";
6
- import { DEFAULT_MOBILE_CARDS_IN_ROW as _, DEFAULT_CARDS_IN_ROW as U } from "../constants/layout.js";
7
- import { DEFAULT_NODE_CONFIG as N, EXCLUDED_ALGORITHM_IDS as L } from "../constants/defaultConfig.js";
8
- import { getDefaultProducts as R } from "../templates/utils.js";
9
- import { generateCompleteFilterQuery as k } from "../utils/filterUtil.js";
10
- import { getPartnerRecommendationParams as x } from "../utils/partnerCustomizations.js";
11
- import { sanitizeProductIds as A } from "../utils/productIds.js";
12
- import { isFilterValid as B } from "../validation/filterSchema.js";
13
- import { isConfigValid as M } from "../validation/requiredFields.js";
14
- const u = D();
15
- let f = null, h = null, g = null, S = null;
16
- const p = /* @__PURE__ */ new Map();
17
- function w() {
6
+ import { DEFAULT_MOBILE_CARDS_IN_ROW as U, DEFAULT_CARDS_IN_ROW as A } from "../constants/layout.js";
7
+ import { DEFAULT_NODE_CONFIG as x, EXCLUDED_ALGORITHM_IDS as B } from "../constants/defaultConfig.js";
8
+ import { getDefaultProducts as C } from "../templates/utils.js";
9
+ import { generateCompleteFilterQuery as v } from "../utils/filterUtil.js";
10
+ import { getPartnerRecommendationParams as M } from "../utils/partnerCustomizations.js";
11
+ import { sanitizeProductIds as O } from "../utils/productIds.js";
12
+ import { isFilterValid as T } from "../validation/filterSchema.js";
13
+ import { getInvalidFields as z, isConfigValid as V } from "../validation/requiredFields.js";
14
+ const d = E();
15
+ let h = null, g = null, S = null, p = null, y = 0;
16
+ const b = /* @__PURE__ */ new Map();
17
+ let l = null;
18
+ function P() {
18
19
  return {
19
- cardsInRow: U,
20
+ cardsInRow: A,
20
21
  currencySettings: {
21
22
  name: "USD",
22
23
  value: "USD",
@@ -30,13 +31,17 @@ function w() {
30
31
  productIds: [],
31
32
  id: 1,
32
33
  language: "en_US",
33
- mobileCardsInRow: _,
34
+ mobileCardsInRow: U,
34
35
  mobileLayoutEnabled: !1,
35
36
  orientation: "grid",
36
37
  recommendedProducts: [],
37
38
  sendProductRequestFlag: !1,
38
39
  shuffleProducts: !1,
39
- strategy: "mostPopular",
40
+ // With RRS on, a block that has not been synced yet must not claim an algorithm:
41
+ // that default is what reached buildCampaignUrl and baked a most-popular feed URL
42
+ // for a block the user never configured (SD-149916). Legacy blocks still get their real
43
+ // algorithm from the node-config sync or the save-time seed.
44
+ strategy: F().isFeatureEnabled("reusableRecommendationStrategy") ? "" : "mostPopular",
40
45
  strategyId: "",
41
46
  textTrimming: !0,
42
47
  unresponsive: !1,
@@ -46,9 +51,9 @@ function w() {
46
51
  priceHideIfSameAsDiscounted: !1
47
52
  };
48
53
  }
49
- function C() {
54
+ function w() {
50
55
  return {
51
- recommendationConfigs: w(),
56
+ recommendationConfigs: P(),
52
57
  recommendationProducts: [],
53
58
  filterStatus: !1,
54
59
  filterSelectionDrawerStatus: !1,
@@ -59,7 +64,7 @@ function C() {
59
64
  filterSnapshot: null
60
65
  };
61
66
  }
62
- const z = () => ({
67
+ const j = () => ({
63
68
  recommendationCampaignUrls: {},
64
69
  activePredictiveAlgorithms: [],
65
70
  languages: {},
@@ -68,11 +73,14 @@ const z = () => ({
68
73
  strategies: [],
69
74
  strategiesLoading: !1,
70
75
  strategyUrlTemplates: {},
76
+ strategiesFetched: !1,
77
+ strategiesSettled: !1,
71
78
  blockStates: {},
72
79
  currentRecommendationId: null,
80
+ strategyPanelOpen: !1,
73
81
  configVersion: 0
74
- }), Y = F("guidoRecommendationExtension", {
75
- state: () => z(),
82
+ }), nt = N("guidoRecommendationExtension", {
83
+ state: () => j(),
76
84
  getters: {
77
85
  // ====================================================================
78
86
  // Proxy Getters — Backward Compatible Access to Current Block State
@@ -82,7 +90,7 @@ const z = () => ({
82
90
  * This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
83
91
  */
84
92
  recommendationConfigs(t) {
85
- return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : w();
93
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : P();
86
94
  },
87
95
  /**
88
96
  * Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
@@ -137,8 +145,8 @@ const z = () => ({
137
145
  return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
138
146
  },
139
147
  getActivePredictiveAlgorithms: (t) => {
140
- const e = b(), r = [];
141
- return t.activePredictiveAlgorithms.filter((n) => !L.includes(n)).forEach((n) => {
148
+ const e = k(), r = [];
149
+ return t.activePredictiveAlgorithms.filter((n) => !B.includes(n)).forEach((n) => {
142
150
  r.push(...e.filter((s) => s.id === n));
143
151
  }), r.map((n) => ({
144
152
  text: n.name,
@@ -156,7 +164,7 @@ const z = () => ({
156
164
  getFilterList() {
157
165
  return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
158
166
  let e;
159
- return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : v.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
167
+ return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : L.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
160
168
  text: t.displayName,
161
169
  value: e,
162
170
  type: t.attributeType
@@ -178,7 +186,7 @@ const z = () => ({
178
186
  setCurrentBlock(t) {
179
187
  this.blockStates[t] || (this.blockStates = {
180
188
  ...this.blockStates,
181
- [t]: C()
189
+ [t]: w()
182
190
  }), this.currentRecommendationId = t;
183
191
  },
184
192
  /**
@@ -246,7 +254,7 @@ const z = () => ({
246
254
  decimalCount: String(e.currencyDecimalCount),
247
255
  decimalSeparator: r(e.currencyDecimalSeparator, ","),
248
256
  thousandSeparator: r(e.currencyThousandSeparator, ".")
249
- }, s = !this.blockStates[t], i = s ? C() : this.blockStates[t];
257
+ }, s = !this.blockStates[t], i = s ? w() : this.blockStates[t];
250
258
  i.recommendationConfigs = {
251
259
  ...i.recommendationConfigs,
252
260
  strategy: e.strategy,
@@ -285,7 +293,7 @@ const z = () => ({
285
293
  * Creates a filter with the first available attribute and operator pre-selected.
286
294
  */
287
295
  createDefaultFilter(t, e) {
288
- const [r] = this.getFilterList, [n] = G(r == null ? void 0 : r.type);
296
+ const [r] = this.getFilterList, [n] = _(r == null ? void 0 : r.type);
289
297
  return {
290
298
  type: "standardFilter",
291
299
  attribute: (r == null ? void 0 : r.value) ?? "",
@@ -345,29 +353,96 @@ const z = () => ({
345
353
  this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].removeStrategyModalStatus = !1);
346
354
  },
347
355
  async fetchStrategies() {
348
- if (S) {
349
- await S;
356
+ for (; p; )
357
+ try {
358
+ await p;
359
+ } catch {
360
+ }
361
+ if (this.strategiesFetched)
350
362
  return;
351
- }
352
- S = (async () => {
363
+ const t = y, e = (async () => {
353
364
  this.strategiesLoading = !0;
354
365
  try {
355
- this.strategies = await u.fetchRecommendationStrategies();
366
+ const r = await d.fetchRecommendationStrategies();
367
+ if (t !== y)
368
+ return;
369
+ this.strategies = r, this.strategiesFetched = !0;
356
370
  } finally {
357
- this.strategiesLoading = !1;
371
+ this.strategiesLoading = !1, t === y && (this.strategiesSettled = !0);
358
372
  }
359
373
  })();
374
+ p = e;
360
375
  try {
361
- await S;
376
+ await e;
362
377
  } finally {
363
- S = null;
378
+ p = null;
364
379
  }
365
380
  },
381
+ invalidateStrategies() {
382
+ this.strategiesFetched = !1, this.strategiesSettled = !1, y += 1;
383
+ },
384
+ /**
385
+ * Opens a Smart Recommender strategy page in a new tab and refreshes the list every time
386
+ * the user comes back. `visibilitychange` covers the tab case; `focus` covers the page
387
+ * landing in a separate window, where this document never goes hidden.
388
+ *
389
+ * Deliberately NOT one-shot: the strategy tab stays open, so a user can create a second
390
+ * strategy in it and come back again — a listener that disarmed on the first return would
391
+ * leave that one missing from the list. It disarms on the first return AFTER that tab is
392
+ * closed instead, so an ordinary alt-tab later in the session costs nothing. Armed only
393
+ * once the user actually opens a strategy page, so anyone who never does pays nothing.
394
+ */
395
+ _armStrategyPageReturn(t) {
396
+ const e = window.open(t, "_blank");
397
+ if (!e)
398
+ return;
399
+ e.opener = null, l == null || l();
400
+ let r = !1;
401
+ const n = () => {
402
+ document.hidden || r || (e != null && e.closed && (l == null || l()), r = !0, this.invalidateStrategies(), this.fetchStrategies().then(() => this._refreshAssignedStrategyProducts()).catch(() => {
403
+ }).finally(() => {
404
+ r = !1;
405
+ }));
406
+ };
407
+ document.addEventListener("visibilitychange", n), window.addEventListener("focus", n), l = () => {
408
+ document.removeEventListener("visibilitychange", n), window.removeEventListener("focus", n), l = null;
409
+ };
410
+ },
411
+ /**
412
+ * Reloads the products of the block the user is on, so an edit made in the strategy tab
413
+ * lands on the canvas instead of waiting for the assign drawer to be reopened.
414
+ *
415
+ * Only the selected block: the canvas regeneration is driven off the active block's
416
+ * product array, so refreshing an unselected block would fetch without redrawing it.
417
+ */
418
+ async _refreshAssignedStrategyProducts() {
419
+ var e;
420
+ const t = this.currentRecommendationId;
421
+ t === null || !((e = this.blockStates[t]) != null && e.recommendationConfigs.strategyId) || await this.fetchRecommendationProducts();
422
+ },
423
+ /** Lets the editor tear the return listeners down when it unmounts. */
424
+ disposeStrategyPageReturn() {
425
+ l == null || l();
426
+ },
427
+ /**
428
+ * Opens Smart Recommender's strategy creation page. Every return refreshes the list so the
429
+ * new strategy is offered; the user picks it themselves.
430
+ */
431
+ openStrategyCreation() {
432
+ this._armStrategyPageReturn(D.RECOMMENDATION_STRATEGY_CREATE_PATH);
433
+ },
434
+ /**
435
+ * Opens the strategy's edit page. Every return refreshes the list so the block reflects the
436
+ * edited configuration.
437
+ */
438
+ openStrategyEdit(t) {
439
+ this._armStrategyPageReturn(G(t));
440
+ },
366
441
  async resolveStrategyUrlTemplate(t, e) {
367
442
  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
443
  if (s)
369
444
  return s;
370
- const { full_path: i = "" } = await u.generateStrategyEndpoint(t, e) ?? {};
445
+ const { full_path: i = "" } = await d.generateStrategyEndpoint(t, e) ?? {};
371
446
  return n && i && (this.strategyUrlTemplates = {
372
447
  ...this.strategyUrlTemplates,
373
448
  [n]: i
@@ -380,23 +455,23 @@ const z = () => ({
380
455
  });
381
456
  },
382
457
  removeStrategy() {
383
- this.patchCurrentBlockConfig({ strategyId: "", size: N.size });
458
+ this.patchCurrentBlockConfig({ strategyId: "", size: x.size });
384
459
  },
385
460
  // ====================================================================
386
461
  // Shared Data Fetching (fetched once, used by all blocks)
387
462
  // ====================================================================
388
463
  async fetchRecommendationCreateData() {
389
464
  if (!this.activePredictiveAlgorithms.length) {
390
- if (f) {
391
- await f;
465
+ if (h) {
466
+ await h;
392
467
  return;
393
468
  }
394
- f = (async () => {
469
+ h = (async () => {
395
470
  const {
396
471
  activePredictiveAlgorithms: t,
397
472
  languages: e,
398
473
  currencies: r
399
- } = await u.fetchRecommendationCreateData();
474
+ } = await d.fetchRecommendationCreateData();
400
475
  if (this.activePredictiveAlgorithms = t, this.languages = e, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
401
476
  const n = this.blockStates[this.currentRecommendationId];
402
477
  n.filterStatus = !!n.recommendationConfigs.filters.length;
@@ -404,26 +479,26 @@ const z = () => ({
404
479
  this.currencyList = r;
405
480
  })();
406
481
  try {
407
- await f;
482
+ await h;
408
483
  } finally {
409
- f = null;
484
+ h = null;
410
485
  }
411
486
  }
412
487
  },
413
488
  async fetchRecommendationFilters() {
414
489
  if (!Object.keys(this.filterList).length) {
415
- if (h) {
416
- await h;
490
+ if (g) {
491
+ await g;
417
492
  return;
418
493
  }
419
- h = (async () => {
420
- const t = await u.fetchRecommendationFilters();
494
+ g = (async () => {
495
+ const t = await d.fetchRecommendationFilters();
421
496
  this.filterList = t;
422
497
  })();
423
498
  try {
424
- await h;
499
+ await g;
425
500
  } finally {
426
- h = null;
501
+ g = null;
427
502
  }
428
503
  }
429
504
  },
@@ -452,7 +527,7 @@ const z = () => ({
452
527
  const n = [...e.recommendationConfigs.filters];
453
528
  n[r] = {
454
529
  ...t,
455
- isValid: B(t)
530
+ isValid: T(t)
456
531
  }, e.recommendationConfigs.filters = n;
457
532
  }
458
533
  },
@@ -490,7 +565,7 @@ const z = () => ({
490
565
  }), e.recommendationConfigs.filters = r;
491
566
  },
492
567
  generateFilterQuery() {
493
- return k(this.recommendationConfigs.filters);
568
+ return v(this.recommendationConfigs.filters);
494
569
  },
495
570
  /**
496
571
  * Validation-only check invoked at save-CTA time. Defined as an action
@@ -498,33 +573,45 @@ const z = () => ({
498
573
  * every block's recommendationConfigs across user edits.
499
574
  */
500
575
  hasInvalidBlock() {
501
- return Object.values(this.blockStates).some((t) => !M(t.recommendationConfigs, this));
576
+ return Object.values(this.blockStates).some((t) => !V(t.recommendationConfigs, this));
577
+ },
578
+ /**
579
+ * Which required fields are missing across the design's blocks, so the save
580
+ * toaster can name the cause instead of always showing the generic message.
581
+ */
582
+ invalidBlockFields() {
583
+ return Object.values(this.blockStates).flatMap((t) => z(t.recommendationConfigs, this));
502
584
  },
503
585
  // ====================================================================
504
586
  // Per-Block Product Fetching
505
587
  // ====================================================================
506
588
  async fetchRecommendationProducts() {
507
589
  if (!(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])) {
508
- if (g) {
509
- await g;
590
+ if (S) {
591
+ await S;
510
592
  return;
511
593
  }
512
- g = this._doFetchProducts();
594
+ S = this._doFetchProducts();
513
595
  try {
514
- await g;
596
+ await S;
515
597
  } finally {
516
- g = null;
598
+ S = null;
517
599
  }
518
600
  }
519
601
  },
520
602
  async _doFetchProducts() {
521
- var d;
522
- const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = P();
603
+ var f;
604
+ const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = F();
523
605
  if (r.strategyId && n.isFeatureEnabled("reusableRecommendationStrategy")) {
524
606
  await this._doFetchStrategyProducts(t);
525
607
  return;
526
608
  }
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 = {
609
+ const s = parseInt(r.size) || 6;
610
+ if (!r.strategy) {
611
+ this._setBlockProducts(t, [], s);
612
+ return;
613
+ }
614
+ const i = r.filters.filter((m) => m.isValid), o = v(i), c = ((f = k().find((m) => m.key === r.strategy)) == null ? void 0 : f.path) || "", a = {
528
615
  locale: r.language,
529
616
  currency: r.currencySettings.value,
530
617
  partnerName: n.partnerName,
@@ -532,21 +619,21 @@ const z = () => ({
532
619
  details: !0,
533
620
  campaignId: n.variationId
534
621
  };
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(
622
+ r.strategy === "manualMerchandising" ? a.productId = O(r.productIds) : r.strategy === "similarViewed" && (a.productId = "{itemId}"), r.strategy === "userBased" && (a.userId = "{user_id}"), o && (a.filter = o), r.shuffleProducts && (a.shuffle = !0), Object.assign(
536
623
  a,
537
- x(n.partnerName, r.strategy)
624
+ M(n.partnerName, r.strategy)
538
625
  );
539
- let l;
626
+ let u;
540
627
  try {
541
- l = await u.fetchRecommendationProducts(o, a);
628
+ u = await d.fetchRecommendationProducts(c, a);
542
629
  } catch {
543
- l = [];
630
+ u = [];
544
631
  }
545
- this._setBlockProducts(t, l, c);
632
+ this._setBlockProducts(t, u, s);
546
633
  },
547
634
  async _doFetchStrategyProducts(t) {
548
- const e = (p.get(t) ?? 0) + 1;
549
- p.set(t, e);
635
+ const e = (b.get(t) ?? 0) + 1;
636
+ b.set(t, e);
550
637
  const { recommendationConfigs: r } = this.blockStates[t];
551
638
  let n = [], s = parseInt(r.size) || 6;
552
639
  try {
@@ -556,25 +643,25 @@ const z = () => ({
556
643
  );
557
644
  if (i) {
558
645
  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));
646
+ const o = r.currencySettings.value, a = (await this.resolveStrategyUrlTemplate(i, o)).replaceAll("{locale}", r.language).replaceAll("{currency}", o), u = a.indexOf("?"), f = u === -1 ? a : a.slice(0, u), m = (u === -1 ? "" : a.slice(u + 1)).split("&").filter((R) => R && !R.startsWith("user_id=")), I = m.length ? `${f}?${m.join("&")}` : f;
647
+ I && (n = await d.fetchRecommendationProductsByUrl(I));
561
648
  }
562
649
  } catch {
563
650
  n = [];
564
651
  }
565
- p.get(t) === e && this._setBlockProducts(t, n, s);
652
+ b.get(t) === e && this._setBlockProducts(t, n, s);
566
653
  },
567
654
  _setBlockProducts(t, e, r) {
568
655
  if (!this.blockStates[t])
569
656
  return;
570
- const n = e.length > 0 ? e : R(r);
657
+ const n = e.length > 0 ? e : C(r);
571
658
  this.blockStates[t].recommendationProducts = [
572
659
  ...n.slice(0, r),
573
- ...R(Math.max(0, r - n.length))
660
+ ...C(Math.max(0, r - n.length))
574
661
  ];
575
662
  }
576
663
  }
577
664
  });
578
665
  export {
579
- Y as useRecommendationExtensionStore
666
+ nt as useRecommendationExtensionStore
580
667
  };
@@ -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-b447f626]{width:100%;max-width:none}.assign-strategy-drawer-content .assign-strategy-select[data-v-b447f626] .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}
package/dist/library.js CHANGED
@@ -1,20 +1,18 @@
1
- import { default as a } from "./components/Guido.vue.js";
2
- import { MessageType as t, ProductType as d } from "./@types/config/schemas.js";
1
+ import { default as e } from "./components/Guido.vue.js";
2
+ import { MessageType as f, ProductType as s } from "./@types/config/schemas.js";
3
3
  import "./@types/config/defaults.js";
4
- import { getValidationErrors as s, isValidConfig as p, parseConfig as g, parseConfigSafe as m, validateConfig as n } from "./@types/config/validator.js";
5
- import { assignTrackingIds as u, readTrackFloor as x, readTrackSeq as T } from "./utils/linkTrackingIds.js";
6
- import { useGuidoEmailEditorStore as C } from "./stores/guido-email-editor.js";
4
+ import { getValidationErrors as d, isValidConfig as p, parseConfig as g, parseConfigSafe as n, validateConfig as l } from "./@types/config/validator.js";
5
+ import { assignTrackingIds as T, readTrackFloor as c, readTrackSeq as x } from "./utils/linkTrackingIds.js";
7
6
  export {
8
- a as Guido,
9
- t as MessageType,
10
- d as ProductType,
11
- u as assignTrackingIds,
12
- s as getValidationErrors,
7
+ e as Guido,
8
+ f as MessageType,
9
+ s as ProductType,
10
+ T as assignTrackingIds,
11
+ d as getValidationErrors,
13
12
  p as isValidConfig,
14
13
  g as parseConfig,
15
- m as parseConfigSafe,
16
- x as readTrackFloor,
17
- T as readTrackSeq,
18
- C as useGuidoEmailEditorStore,
19
- n as validateConfig
14
+ n as parseConfigSafe,
15
+ c as readTrackFloor,
16
+ x as readTrackSeq,
17
+ l as validateConfig
20
18
  };
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue/types/v3-component-options.js").ComponentOptionsMixin, import("vue/types/v3-component-options.js").ComponentOptionsMixin, {}, string, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
2
+ export default _default;
@@ -5,7 +5,9 @@ export declare const URLS: {
5
5
  PRODUCT_ATTRIBUTES_PATH: string;
6
6
  RECOMMENDATION_STRATEGIES_PATH: string;
7
7
  RECOMMENDATION_STRATEGIES_PANEL_PATH: string;
8
+ RECOMMENDATION_STRATEGY_CREATE_PATH: string;
8
9
  };
10
+ export declare const strategyEditPath: (strategyId: string | number) => string;
9
11
  export declare const QUERY_PARAMS: {
10
12
  CLIENT_ID: string;
11
13
  };
@@ -66,6 +66,8 @@ export declare class RecommendationBlockControl extends CommonControl {
66
66
  * edits are never clobbered (SD-141049).
67
67
  */
68
68
  private _syncNodeConfigIfChanged;
69
+ /** Whether the deferred write would move the node's strategyId — see the caller (SD-149916). */
70
+ private _isPendingStrategyIdChange;
69
71
  private _capturePendingNodeConfigWrite;
70
72
  /**
71
73
  * Accumulates the pending write into `documentModifier` without applying, so the
@@ -2,6 +2,7 @@ import { CommonControl } from '../../../common-control';
2
2
  export declare const STRATEGY_CONTROL_ID = "recommendation-strategy-control";
3
3
  export declare class StrategyControl extends CommonControl {
4
4
  private store;
5
+ private onboardingStore;
5
6
  private storeUnsubscription;
6
7
  private listeners;
7
8
  private lastPaintedState;
@@ -9,6 +10,14 @@ export declare class StrategyControl extends CommonControl {
9
10
  getTemplate(): string;
10
11
  private _strategyAction;
11
12
  onRender(): void;
13
+ /**
14
+ * Whether the document itself says this block has no strategy.
15
+ *
16
+ * Read the node, not the store: when the main control cannot resolve the block id off the
17
+ * node it never syncs the persisted config, so the store still holds defaults (empty
18
+ * strategyId) for an assigned block.
19
+ */
20
+ private _isNodeUnassigned;
12
21
  onDestroy(): void;
13
22
  private _teardown;
14
23
  private _setupButtonListeners;