@useinsider/guido 3.17.0-beta.668ca65 → 3.17.0-beta.6cedc6c

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 (23) hide show
  1. package/README.md +5 -5
  2. package/dist/components/Guido.vue.js +7 -7
  3. package/dist/components/Guido.vue2.js +59 -57
  4. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue.js +5 -5
  5. package/dist/components/organisms/extensions/recommendation/AssignStrategyDrawer.vue2.js +31 -30
  6. package/dist/components/organisms/extensions/recommendation/RemoveStrategyModal.vue.js +17 -0
  7. package/dist/components/organisms/extensions/recommendation/RemoveStrategyModal.vue2.js +52 -0
  8. package/dist/components/organisms/extensions/recommendation/StrategyInfoBoxContent.vue2.js +36 -35
  9. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +198 -135
  10. package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +1 -1
  11. package/dist/extensions/Blocks/Recommendation/services/configService.js +34 -16
  12. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +47 -37
  13. package/dist/extensions/Blocks/Recommendation/utils/nodeConfigWriter.js +22 -0
  14. package/dist/guido.css +1 -1
  15. package/dist/services/recommendationApi.js +1 -1
  16. package/dist/src/components/organisms/extensions/recommendation/RemoveStrategyModal.vue.d.ts +2 -0
  17. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +38 -8
  18. package/dist/src/extensions/Blocks/Recommendation/services/configService.d.ts +12 -1
  19. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +26 -0
  20. package/dist/src/extensions/Blocks/Recommendation/utils/nodeConfigWriter.d.ts +31 -0
  21. package/dist/src/utils/genericUtil.d.ts +5 -0
  22. package/dist/utils/genericUtil.js +28 -27
  23. package/package.json +2 -2
@@ -1,18 +1,18 @@
1
- import { getRecommendationFeedSourceMaps as I, getOperatorOptions as G, PriceAttributes as D } from "../../../../enums/extensions/recommendationBlock.js";
2
- import { useRecommendationApi as P } from "../../../../services/recommendationApi.js";
3
- import { useConfigStore as F } from "../../../../stores/config.js";
4
- import { defineStore as v } from "pinia";
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";
5
5
  import "../constants/selectors.js";
6
6
  import { DEFAULT_MOBILE_CARDS_IN_ROW as _, DEFAULT_CARDS_IN_ROW as U } from "../constants/layout.js";
7
7
  import { DEFAULT_NODE_CONFIG as N, EXCLUDED_ALGORITHM_IDS as L } from "../constants/defaultConfig.js";
8
- import { getDefaultProducts as k } from "../templates/utils.js";
9
- import { generateCompleteFilterQuery as R } from "../utils/filterUtil.js";
8
+ import { getDefaultProducts as R } from "../templates/utils.js";
9
+ import { generateCompleteFilterQuery as k } from "../utils/filterUtil.js";
10
10
  import { getPartnerRecommendationParams as x } from "../utils/partnerCustomizations.js";
11
11
  import { sanitizeProductIds as A } from "../utils/productIds.js";
12
12
  import { isFilterValid as B } from "../validation/filterSchema.js";
13
- import { isConfigValid as z } from "../validation/requiredFields.js";
14
- const m = P();
15
- let f = null, g = null, h = null, S = null;
13
+ import { isConfigValid as M } from "../validation/requiredFields.js";
14
+ const u = D();
15
+ let f = null, h = null, g = null, S = null;
16
16
  const p = /* @__PURE__ */ new Map();
17
17
  function w() {
18
18
  return {
@@ -53,12 +53,13 @@ function C() {
53
53
  filterStatus: !1,
54
54
  filterSelectionDrawerStatus: !1,
55
55
  assignStrategyDrawerStatus: !1,
56
+ removeStrategyModalStatus: !1,
56
57
  filterGroup: 1,
57
58
  isInitialized: !1,
58
59
  filterSnapshot: null
59
60
  };
60
61
  }
61
- const O = () => ({
62
+ const z = () => ({
62
63
  recommendationCampaignUrls: {},
63
64
  activePredictiveAlgorithms: [],
64
65
  languages: {},
@@ -70,8 +71,8 @@ const O = () => ({
70
71
  blockStates: {},
71
72
  currentRecommendationId: null,
72
73
  configVersion: 0
73
- }), Y = v("guidoRecommendationExtension", {
74
- state: () => O(),
74
+ }), Y = F("guidoRecommendationExtension", {
75
+ state: () => z(),
75
76
  getters: {
76
77
  // ====================================================================
77
78
  // Proxy Getters — Backward Compatible Access to Current Block State
@@ -98,6 +99,9 @@ const O = () => ({
98
99
  assignStrategyDrawerStatus(t) {
99
100
  return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].assignStrategyDrawerStatus : !1;
100
101
  },
102
+ removeStrategyModalStatus(t) {
103
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].removeStrategyModalStatus : !1;
104
+ },
101
105
  assignedStrategy(t) {
102
106
  const { strategyId: e } = this.recommendationConfigs;
103
107
  return e ? t.strategies.find((r) => String(r.strategy_id) === e) ?? null : null;
@@ -133,7 +137,7 @@ const O = () => ({
133
137
  return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
134
138
  },
135
139
  getActivePredictiveAlgorithms: (t) => {
136
- const e = I(), r = [];
140
+ const e = b(), r = [];
137
141
  return t.activePredictiveAlgorithms.filter((n) => !L.includes(n)).forEach((n) => {
138
142
  r.push(...e.filter((s) => s.id === n));
139
143
  }), r.map((n) => ({
@@ -152,7 +156,7 @@ const O = () => ({
152
156
  getFilterList() {
153
157
  return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
154
158
  let e;
155
- return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : D.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
159
+ return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : v.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
156
160
  text: t.displayName,
157
161
  value: e,
158
162
  type: t.attributeType
@@ -334,6 +338,12 @@ const O = () => ({
334
338
  closeAssignStrategyDrawer() {
335
339
  this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].assignStrategyDrawerStatus = !1);
336
340
  },
341
+ openRemoveStrategyModal() {
342
+ this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].removeStrategyModalStatus = !0);
343
+ },
344
+ closeRemoveStrategyModal() {
345
+ this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].removeStrategyModalStatus = !1);
346
+ },
337
347
  async fetchStrategies() {
338
348
  if (S) {
339
349
  await S;
@@ -342,7 +352,7 @@ const O = () => ({
342
352
  S = (async () => {
343
353
  this.strategiesLoading = !0;
344
354
  try {
345
- this.strategies = await m.fetchRecommendationStrategies();
355
+ this.strategies = await u.fetchRecommendationStrategies();
346
356
  } finally {
347
357
  this.strategiesLoading = !1;
348
358
  }
@@ -357,7 +367,7 @@ const O = () => ({
357
367
  const r = typeof t.updated_at == "string" ? t.updated_at : "", n = r ? `${t.strategy_id}:${r}:${e}` : "", s = n ? this.strategyUrlTemplates[n] : void 0;
358
368
  if (s)
359
369
  return s;
360
- const { full_path: i = "" } = await m.generateStrategyEndpoint(t, e) ?? {};
370
+ const { full_path: i = "" } = await u.generateStrategyEndpoint(t, e) ?? {};
361
371
  return n && i && (this.strategyUrlTemplates = {
362
372
  ...this.strategyUrlTemplates,
363
373
  [n]: i
@@ -386,7 +396,7 @@ const O = () => ({
386
396
  activePredictiveAlgorithms: t,
387
397
  languages: e,
388
398
  currencies: r
389
- } = await m.fetchRecommendationCreateData();
399
+ } = await u.fetchRecommendationCreateData();
390
400
  if (this.activePredictiveAlgorithms = t, this.languages = e, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
391
401
  const n = this.blockStates[this.currentRecommendationId];
392
402
  n.filterStatus = !!n.recommendationConfigs.filters.length;
@@ -402,18 +412,18 @@ const O = () => ({
402
412
  },
403
413
  async fetchRecommendationFilters() {
404
414
  if (!Object.keys(this.filterList).length) {
405
- if (g) {
406
- await g;
415
+ if (h) {
416
+ await h;
407
417
  return;
408
418
  }
409
- g = (async () => {
410
- const t = await m.fetchRecommendationFilters();
419
+ h = (async () => {
420
+ const t = await u.fetchRecommendationFilters();
411
421
  this.filterList = t;
412
422
  })();
413
423
  try {
414
- await g;
424
+ await h;
415
425
  } finally {
416
- g = null;
426
+ h = null;
417
427
  }
418
428
  }
419
429
  },
@@ -480,7 +490,7 @@ const O = () => ({
480
490
  }), e.recommendationConfigs.filters = r;
481
491
  },
482
492
  generateFilterQuery() {
483
- return R(this.recommendationConfigs.filters);
493
+ return k(this.recommendationConfigs.filters);
484
494
  },
485
495
  /**
486
496
  * Validation-only check invoked at save-CTA time. Defined as an action
@@ -488,33 +498,33 @@ const O = () => ({
488
498
  * every block's recommendationConfigs across user edits.
489
499
  */
490
500
  hasInvalidBlock() {
491
- return Object.values(this.blockStates).some((t) => !z(t.recommendationConfigs, this));
501
+ return Object.values(this.blockStates).some((t) => !M(t.recommendationConfigs, this));
492
502
  },
493
503
  // ====================================================================
494
504
  // Per-Block Product Fetching
495
505
  // ====================================================================
496
506
  async fetchRecommendationProducts() {
497
507
  if (!(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])) {
498
- if (h) {
499
- await h;
508
+ if (g) {
509
+ await g;
500
510
  return;
501
511
  }
502
- h = this._doFetchProducts();
512
+ g = this._doFetchProducts();
503
513
  try {
504
- await h;
514
+ await g;
505
515
  } finally {
506
- h = null;
516
+ g = null;
507
517
  }
508
518
  }
509
519
  },
510
520
  async _doFetchProducts() {
511
521
  var d;
512
- const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = F();
522
+ const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = P();
513
523
  if (r.strategyId && n.isFeatureEnabled("reusableRecommendationStrategy")) {
514
524
  await this._doFetchStrategyProducts(t);
515
525
  return;
516
526
  }
517
- const s = r.filters.filter((u) => u.isValid), i = R(s), o = ((d = I().find((u) => u.key === r.strategy)) == null ? void 0 : d.path) || "", c = parseInt(r.size) || 6, a = {
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 = {
518
528
  locale: r.language,
519
529
  currency: r.currencySettings.value,
520
530
  partnerName: n.partnerName,
@@ -528,7 +538,7 @@ const O = () => ({
528
538
  );
529
539
  let l;
530
540
  try {
531
- l = await m.fetchRecommendationProducts(o, a);
541
+ l = await u.fetchRecommendationProducts(o, a);
532
542
  } catch {
533
543
  l = [];
534
544
  }
@@ -546,8 +556,8 @@ const O = () => ({
546
556
  );
547
557
  if (i) {
548
558
  s = parseInt(String(i.size ?? "")) || s;
549
- 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((b) => b && !b.startsWith("user_id=")), y = u.length ? `${d}?${u.join("&")}` : d;
550
- y && (n = await m.fetchRecommendationProductsByUrl(y));
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));
551
561
  }
552
562
  } catch {
553
563
  n = [];
@@ -557,10 +567,10 @@ const O = () => ({
557
567
  _setBlockProducts(t, e, r) {
558
568
  if (!this.blockStates[t])
559
569
  return;
560
- const n = e.length > 0 ? e : k(r);
570
+ const n = e.length > 0 ? e : R(r);
561
571
  this.blockStates[t].recommendationProducts = [
562
572
  ...n.slice(0, r),
563
- ...k(Math.max(0, r - n.length))
573
+ ...R(Math.max(0, r - n.length))
564
574
  ];
565
575
  }
566
576
  }
@@ -0,0 +1,22 @@
1
+ import { ModificationDescription as f } from "../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
2
+ import { isPartnerManagedBlock as d, updateProductContentInPlace as m, regenerateProductRowsWithStyles as a } from "../controls/main/utils.js";
3
+ import { RecommendationConfigService as l } from "../services/configService.js";
4
+ const s = "recommendation-id";
5
+ let e = null;
6
+ function M(r) {
7
+ e = r;
8
+ }
9
+ function N(r, c, i, u) {
10
+ const n = e == null ? void 0 : e.getDocumentRootHtmlNode();
11
+ if (!e || !n || !("querySelector" in n))
12
+ return !1;
13
+ const t = n.querySelector(`[${s}="${r}"]`);
14
+ if (!t)
15
+ return !1;
16
+ const o = e.getDocumentModifier();
17
+ return l.stageConfig(o, t, c), d(t) ? (o.apply(new f(u)), !0) : (m({ currentNode: t, documentModifier: o, products: i }) || a({ currentNode: t, documentModifier: o, products: i }), !0);
18
+ }
19
+ export {
20
+ N as commitBlockChangeOutsidePanel,
21
+ M as registerNodeConfigWriter
22
+ };
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-6e711fd7]{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-e6f99d29]{--ribbon-offset: 0px;position:relative;width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__container[data-v-e6f99d29]{width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__no-header[data-v-e6f99d29]{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-d3080c55]{width:100%;max-width:none}.assign-strategy-drawer-content .assign-strategy-select[data-v-d3080c55] .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-6e711fd7]{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-5c588dc5]{--ribbon-offset: 0px;position:relative;width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__container[data-v-5c588dc5]{width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__no-header[data-v-5c588dc5]{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}
@@ -63,7 +63,7 @@ const y = "[sr_strategies.status][=][active]*[sr_strategies.suitable_channels][~
63
63
  );
64
64
  return Array.isArray(e) ? e : (e == null ? void 0 : e.data) ?? [];
65
65
  } catch (e) {
66
- return console.error("fetchRecommendationStrategies error:", e), [];
66
+ throw console.error("fetchRecommendationStrategies error:", e), e;
67
67
  }
68
68
  },
69
69
  generateStrategyEndpoint: async (e, o) => {
@@ -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;
@@ -20,7 +20,9 @@ export * from './utils';
20
20
  export declare class RecommendationBlockControl extends CommonControl {
21
21
  private store;
22
22
  private storeUnsubscription;
23
- private syncedBlockIds;
23
+ private syncedConfigs;
24
+ private pendingNodeConfigWrite;
25
+ private pendingConfigFlushTimer;
24
26
  private algorithmControl;
25
27
  private localeControl;
26
28
  private currencyControl;
@@ -54,6 +56,41 @@ export declare class RecommendationBlockControl extends CommonControl {
54
56
  * Uses triggerRefetch: false because the fetch hasn't happened yet —
55
57
  * values are being prepared for the upcoming initial fetch.
56
58
  */
59
+ /**
60
+ * Re-syncs the store when the node config moved since we last read it.
61
+ *
62
+ * Undo/redo, the code editor and version-history restore rewrite the node
63
+ * config behind the store's back — Stripo exposes no undo signal, so a
64
+ * fingerprint diff on this hook is the only way to notice (SD-149105).
65
+ * An unchanged fingerprint means the move came from us, so runtime store
66
+ * edits are never clobbered (SD-141049).
67
+ */
68
+ private _syncNodeConfigIfChanged;
69
+ private _capturePendingNodeConfigWrite;
70
+ /**
71
+ * Accumulates the pending write into `documentModifier` without applying, so the
72
+ * caller's apply() commits config and re-render as ONE undo entry.
73
+ *
74
+ * Deliberately does not consume the pending record: the product helpers call
75
+ * apply() only when they actually changed something, so this batch may never be
76
+ * committed and the flush has to stay able to write it standalone.
77
+ * @param documentModifier - The batch to accumulate into
78
+ * @returns Whether anything was staged
79
+ */
80
+ private _stagePendingNodeConfig;
81
+ /**
82
+ * Arms the guaranteed fallback write, once the product fetch has settled. The
83
+ * delay only has to outlast the re-render debounce that fetch triggers — firing
84
+ * earlier writes the config on its own and the undo entry splits in two again.
85
+ */
86
+ private _scheduleConfigFlush;
87
+ private _clearConfigFlush;
88
+ /**
89
+ * Writes the deferred change standalone when the batch it was staged into never
90
+ * committed. Always targets the LIVE node — the captured one is an immutable
91
+ * snapshot the intervening re-render staled, and saveConfig swallows that failure.
92
+ */
93
+ private _flushPendingNodeConfig;
57
94
  private _syncNodeConfigToStore;
58
95
  /**
59
96
  * Fetches initial data for a block in three phases:
@@ -110,11 +147,4 @@ export declare class RecommendationBlockControl extends CommonControl {
110
147
  * We skip that tick and update tracking references instead.
111
148
  */
112
149
  private _listenStateUpdates;
113
- /**
114
- * Persists filter + strategy state to the Stripo node config so it survives
115
- * save/reload (strategyId must reach esd-ext-config before any save).
116
- * `size` rides along: assigning a strategy writes it to the store only, and a
117
- * stale node config would rebuild the block against the old slot count.
118
- */
119
- private _persistFiltersToNodeConfig;
120
150
  }
@@ -25,7 +25,7 @@ import { ModificationDescription } from '@stripoinc/ui-editor-extensions';
25
25
  * Stripo Extension API interface for document modifications
26
26
  * Extracted to allow type-safe usage without importing full Stripo types
27
27
  */
28
- interface DocumentModifier {
28
+ export interface DocumentModifier {
29
29
  modifyHtml: (node: ImmutableHtmlNode) => {
30
30
  setNodeConfig: (config: Record<string, unknown>) => DocumentModifier;
31
31
  setAttribute: (name: string, value: string) => DocumentModifier;
@@ -90,6 +90,17 @@ export declare class RecommendationConfigService {
90
90
  * @returns The new complete configuration
91
91
  */
92
92
  static updateConfig(api: DocumentModifierApi, node: ImmutableHtmlNode, updates: PartialNodeConfig, description: string): RecommendationNodeConfig;
93
+ /**
94
+ * Accumulate a config update into an existing modifier batch, WITHOUT applying.
95
+ *
96
+ * The caller owns the apply(), so the write shares one undo entry with the rest
97
+ * of that batch — a change whose visible result only lands after an async product
98
+ * fetch would otherwise cost the user two undo presses (SD-149105).
99
+ * @param documentModifier - The batch to accumulate into (caller applies)
100
+ * @param node - The immutable HTML node to write to
101
+ * @param updates - Partial config with values to update
102
+ */
103
+ static stageConfig(documentModifier: DocumentModifier, node: ImmutableHtmlNode, updates: PartialNodeConfig): void;
93
104
  /**
94
105
  * Initialize configuration for a new block
95
106
  *
@@ -35,6 +35,7 @@ interface PerBlockState {
35
35
  filterStatus: boolean;
36
36
  filterSelectionDrawerStatus: boolean;
37
37
  assignStrategyDrawerStatus: boolean;
38
+ removeStrategyModalStatus: boolean;
38
39
  filterGroup: number;
39
40
  /** Whether initial API data (filters, algorithms, products) has been fetched for this block */
40
41
  isInitialized: boolean;
@@ -177,6 +178,29 @@ export declare const useRecommendationExtensionStore: import("pinia").StoreDefin
177
178
  currentRecommendationId: number | null;
178
179
  configVersion: number;
179
180
  } & import("pinia").PiniaCustomStateProperties<StoreState>): boolean;
181
+ removeStrategyModalStatus(state: {
182
+ recommendationCampaignUrls: Record<string, RecommendationCampaignUrlValue>;
183
+ activePredictiveAlgorithms: number[];
184
+ languages: Languages;
185
+ currencyList: {
186
+ text: string;
187
+ value: string;
188
+ }[];
189
+ filterList: FiltersResponse;
190
+ strategies: {
191
+ [x: string]: unknown;
192
+ strategy_id: number;
193
+ name: string;
194
+ status: string;
195
+ suitable_channels: string;
196
+ size?: (number | string) | undefined;
197
+ }[];
198
+ strategiesLoading: boolean;
199
+ strategyUrlTemplates: Record<string, string>;
200
+ blockStates: Record<number, PerBlockState>;
201
+ currentRecommendationId: number | null;
202
+ configVersion: number;
203
+ } & import("pinia").PiniaCustomStateProperties<StoreState>): boolean;
180
204
  assignedStrategy(state: {
181
205
  recommendationCampaignUrls: Record<string, RecommendationCampaignUrlValue>;
182
206
  activePredictiveAlgorithms: number[];
@@ -406,6 +430,8 @@ export declare const useRecommendationExtensionStore: import("pinia").StoreDefin
406
430
  cancelFilterDrawer(): void;
407
431
  openAssignStrategyDrawer(): void;
408
432
  closeAssignStrategyDrawer(): void;
433
+ openRemoveStrategyModal(): void;
434
+ closeRemoveStrategyModal(): void;
409
435
  fetchStrategies(): Promise<void>;
410
436
  resolveStrategyUrlTemplate(strategy: RrsStrategy, currency: string): Promise<string>;
411
437
  assignStrategy(strategy: RrsStrategy): void;
@@ -0,0 +1,31 @@
1
+ import type { DocumentModifier } from '../services/configService';
2
+ import type { PartialNodeConfig } from '../types/nodeConfig';
3
+ import type { RecommendationProduct } from '@@/Types/recommendation';
4
+ import { ImmutableHtmlNode } from '@stripoinc/ui-editor-extensions';
5
+ interface ConfigWriterApi {
6
+ getDocumentModifier: () => DocumentModifier;
7
+ getDocumentRootHtmlNode: () => ImmutableHtmlNode;
8
+ }
9
+ /**
10
+ * Keeps a document handle alive for writes the settings panel can no longer make.
11
+ * Deliberately not cleared on deselect — that is the exact moment the handle is
12
+ * needed. It is replaced on the next render, and goes stale only if the editor
13
+ * itself is torn down.
14
+ */
15
+ export declare function registerNodeConfigWriter(api: ConfigWriterApi): void;
16
+ /**
17
+ * Commits a config change and its product re-render straight to the document,
18
+ * bypassing the settings panel.
19
+ *
20
+ * Host-rendered UI (the remove-strategy modal) lives outside `ui-editor`, and
21
+ * Stripo deselects the block on the first click that lands there — destroying the
22
+ * control that normally does both, before the store change even happens.
23
+ *
24
+ * Mirrors the control's regenerate routine so config and cards land in ONE undo
25
+ * entry, and so partner-managed blocks keep their hand-authored cells.
26
+ * @param description - labels the undo entry only when there are no cards to
27
+ * re-render; the product helpers label their own batch.
28
+ * @returns whether the write reached a node
29
+ */
30
+ export declare function commitBlockChangeOutsidePanel(blockId: number, updates: PartialNodeConfig, products: RecommendationProduct[], description: string): boolean;
31
+ export {};
@@ -25,3 +25,8 @@ export declare const getCsrfToken: () => string;
25
25
  export declare const getUrlParams: () => URLSearchParams;
26
26
  export declare const getUrlParam: (paramName: string, defaultValue?: string) => string;
27
27
  export declare const replaceString: (string?: string, values?: Record<string, string | number>, isRemoveEmptyValues?: boolean) => string;
28
+ /**
29
+ * Narrows an unknown API value to a printable string; anything that is neither
30
+ * a string nor a number becomes ''.
31
+ */
32
+ export declare const asText: (value: unknown) => string;
@@ -1,26 +1,26 @@
1
- const o = (l) => {
2
- const a = l.value.match(/\{\{([^}]+)\}\}/)[1].split("|").map((t) => t.trim()), [c] = a, r = {
3
- text: l.label,
4
- value: c || ""
1
+ const o = (t) => {
2
+ const a = t.value.match(/\{\{([^}]+)\}\}/)[1].split("|").map((l) => l.trim()), [s] = a, r = {
3
+ text: t.label,
4
+ value: s || ""
5
5
  };
6
6
  if (a.length >= 2) {
7
- const [, t, e] = a;
8
- if (t.startsWith("default:")) {
9
- let n = t.slice(8).trim();
7
+ const [, l, e] = a;
8
+ if (l.startsWith("default:")) {
9
+ let n = l.slice(8).trim();
10
10
  n.startsWith('"') && n.endsWith('"') && (n = n.slice(1, -1)), r.fallback = n;
11
11
  } else {
12
- const n = t.includes("=") ? { key: t.split("=")[0].trim(), value: t.split("=")[1].trim() } : null;
13
- n ? r.format = n : e || (r.fallback = t), e && (r.fallback = e);
12
+ const n = l.includes("=") ? { key: l.split("=")[0].trim(), value: l.split("=")[1].trim() } : null;
13
+ n ? r.format = n : e || (r.fallback = l), e && (r.fallback = e);
14
14
  }
15
15
  }
16
16
  return r;
17
- }, f = (l, s = !1) => l.map((a) => {
17
+ }, f = (t, c = !1) => t.map((a) => {
18
18
  if (a.format)
19
19
  return {
20
20
  label: `${a.text} | ${a.format.key}=${a.format.value}`,
21
21
  value: `{{${a.value}|${a.format.key}=${a.format.value}}}`
22
22
  };
23
- if (s) {
23
+ if (c) {
24
24
  if (!a.fallback)
25
25
  return {
26
26
  label: a.text,
@@ -36,33 +36,34 @@ const o = (l) => {
36
36
  label: a.fallback ? `${a.text} | ${a.fallback}` : a.text,
37
37
  value: a.fallback ? `{{${a.value}|${a.fallback}}}` : `{{${a.value}}}`
38
38
  };
39
- }), b = (l) => {
40
- const s = [], a = /* @__PURE__ */ new Set(), c = (t) => {
41
- Array.isArray(t) && t.forEach((e) => {
39
+ }), b = (t) => {
40
+ const c = [], a = /* @__PURE__ */ new Set(), s = (l) => {
41
+ Array.isArray(l) && l.forEach((e) => {
42
42
  const n = [...e.children ?? [], ...e.select_items ?? []];
43
43
  if (n.length > 0) {
44
- c(n);
44
+ s(n);
45
45
  return;
46
46
  }
47
47
  const u = e.text || e.tag_text;
48
- u && e.value && !a.has(e.value) && (a.add(e.value), s.push({ text: u, value: e.value }));
48
+ u && e.value && !a.has(e.value) && (a.add(e.value), c.push({ text: u, value: e.value }));
49
49
  });
50
- }, [r] = Object.values(l ?? {});
51
- return c(r), s;
52
- }, v = (l, s, a = !1) => {
53
- const c = new Set(l.map((t) => t.value)), r = s.filter((t) => c.has(t.value) ? !1 : (c.add(t.value), !0));
50
+ }, [r] = Object.values(t ?? {});
51
+ return s(r), c;
52
+ }, i = (t, c, a = !1) => {
53
+ const s = new Set(t.map((l) => l.value)), r = c.filter((l) => s.has(l.value) ? !1 : (s.add(l.value), !0));
54
54
  return [
55
- ...f(l, a),
55
+ ...f(t, a),
56
56
  ...f(r, a)
57
57
  ];
58
- }, i = () => {
59
- var l;
60
- return ((l = document.head.querySelector('meta[name="csrf-token"]')) == null ? void 0 : l.getAttribute("content")) ?? "";
61
- };
58
+ }, v = () => {
59
+ var t;
60
+ return ((t = document.head.querySelector('meta[name="csrf-token"]')) == null ? void 0 : t.getAttribute("content")) ?? "";
61
+ }, k = (t) => typeof t == "string" ? t : typeof t == "number" ? String(t) : "";
62
62
  export {
63
- v as buildMergeTagEntries,
63
+ k as asText,
64
+ i as buildMergeTagEntries,
64
65
  f as dynamicContentToMergeTags,
65
66
  b as flattenDynamicContentList,
66
- i as getCsrfToken,
67
+ v as getCsrfToken,
67
68
  o as mergeTagToDynamicContent
68
69
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useinsider/guido",
3
- "version": "3.17.0-beta.668ca65",
3
+ "version": "3.17.0-beta.6cedc6c",
4
4
  "description": "Guido is a Vue + TypeScript wrapper for Email Plugin. Easily embed the email editor in your Vue applications.",
5
5
  "main": "./dist/guido.umd.cjs",
6
6
  "module": "./dist/library.js",
@@ -25,7 +25,7 @@
25
25
  "start": "vite",
26
26
  "build": "vite build && vue-tsc --declaration --emitDeclarationOnly --project tsconfig.app.json",
27
27
  "preview": "vite preview",
28
- "lint": "NODE_OPTIONS=--max-old-space-size=2048 eslint ./ && bun run type-check",
28
+ "lint": "NODE_OPTIONS=--max-old-space-size=2048 eslint ./ && npm run type-check",
29
29
  "lint:fix": "NODE_OPTIONS=--max-old-space-size=2048 eslint --fix ./",
30
30
  "lint:e2e-quality": "node scripts/lint-e2e-quality.mjs",
31
31
  "lint:e2e-quality:ci": "node scripts/lint-e2e-quality.mjs --ci",