@useinsider/guido 3.5.0-beta.17c0032 → 3.5.0-beta.6fa034d

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.
@@ -2,128 +2,126 @@ import { useConfig as S } from "../../../composables/useConfig.js";
2
2
  import { useRecommendation as g } from "../../../composables/useRecommendation.js";
3
3
  import { CSS_CLASS_RECO_BUTTON as _ } from "../../../extensions/Blocks/Recommendation/constants/selectors.js";
4
4
  import { useRecommendationExtensionStore as q } from "../../../extensions/Blocks/Recommendation/store/recommendation.js";
5
- function y(r, i, e, o, s = "") {
6
- const c = `{{${s}${r}_${i}_${e}}}`, t = `{{${s}${r}_${i}_currency}}`;
7
- return o === "before" ? `${t} ${c}` : `${c} ${t}`;
5
+ function y(r, c, e, o, s = "") {
6
+ const n = `{{${s}${r}_${c}_${e}}}`, t = `{{${s}${r}_${c}_currency}}`;
7
+ return o === "before" ? `${t} ${n}` : `${n} ${t}`;
8
8
  }
9
- function f(r, i, e, o, s, c) {
10
- switch (i) {
9
+ function f(r, c, e, o, s, n) {
10
+ switch (c) {
11
11
  case "productImage": {
12
12
  const t = r.querySelector("img");
13
- t && (t.setAttribute("src", `{{${c}${e}_${o}_image_url}}`), t.setAttribute("alt", `{{${c}${e}_${o}_name}}`));
14
- const n = r.querySelector("a");
15
- n && (n.setAttribute("href", `{{${c}${e}_${o}_url}}`), n.classList.add(_));
13
+ t && (t.setAttribute("src", `{{${n}${e}_${o}_image_url}}`), t.setAttribute("alt", `{{${n}${e}_${o}_name}}`));
14
+ const i = r.querySelector("a");
15
+ i && (i.setAttribute("href", `{{${n}${e}_${o}_url}}`), i.classList.add(_));
16
16
  break;
17
17
  }
18
18
  case "productName": {
19
19
  const t = r.querySelector("p");
20
20
  if (t) {
21
- const n = t.querySelector("strong") || t;
22
- n.textContent = `{{${c}${e}_${o}_name}}`;
21
+ const i = t.querySelector("strong") || t;
22
+ i.textContent = `{{${n}${e}_${o}_name}}`;
23
23
  }
24
24
  break;
25
25
  }
26
26
  case "productPrice": {
27
27
  const t = r.querySelector("p");
28
28
  if (t) {
29
- const n = t.querySelector("strong") || t;
30
- n.textContent = y(e, o, "price", s, c);
29
+ const i = t.querySelector("strong") || t;
30
+ i.textContent = y(e, o, "price", s, n);
31
31
  }
32
32
  break;
33
33
  }
34
34
  case "productOldPrice": {
35
35
  const t = r.querySelector("p");
36
36
  if (t) {
37
- let n = t;
38
- for (; n.children.length === 1; )
39
- [n] = n.children;
40
- n.textContent = y(
37
+ const i = t.querySelector("strong") || t;
38
+ i.textContent = y(
41
39
  e,
42
40
  o,
43
41
  "original_price",
44
42
  s,
45
- c
43
+ n
46
44
  ), t.setAttribute("product-attr", "discount");
47
45
  }
48
46
  break;
49
47
  }
50
48
  case "productButton": {
51
49
  const t = r.querySelector("a");
52
- t && t.setAttribute("href", `{{${c}${e}_${o}_url}}`);
50
+ t && t.setAttribute("href", `{{${n}${e}_${o}_url}}`);
53
51
  break;
54
52
  }
55
53
  case "productOmnibusPrice": {
56
54
  const t = r.querySelector(".omnibus-price-value");
57
55
  if (t) {
58
- t.textContent = `{{${c}${e}_${o}_omnibus_price}}`;
59
- const n = t.closest("p");
60
- n && (n.setAttribute("product-attr", "omnibus_price"), n.setAttribute("composition", "true"));
56
+ t.textContent = `{{${n}${e}_${o}_omnibus_price}}`;
57
+ const i = t.closest("p");
58
+ i && (i.setAttribute("product-attr", "omnibus_price"), i.setAttribute("composition", "true"));
61
59
  }
62
60
  break;
63
61
  }
64
62
  case "productOmnibusDiscount": {
65
63
  const t = r.querySelector(".omnibus-discount-value");
66
64
  if (t) {
67
- t.textContent = `{{${c}${e}_${o}_omnibus_discount}}`;
68
- const n = t.closest("p");
69
- n && (n.setAttribute("product-attr", "omnibus_discount"), n.setAttribute("composition", "true"));
65
+ t.textContent = `{{${n}${e}_${o}_omnibus_discount}}`;
66
+ const i = t.closest("p");
67
+ i && (i.setAttribute("product-attr", "omnibus_discount"), i.setAttribute("composition", "true"));
70
68
  }
71
69
  break;
72
70
  }
73
71
  default: {
74
72
  const t = r.getAttribute("product-attr") ? r : r.querySelector("[product-attr]");
75
73
  if (t) {
76
- const n = t.getAttribute("product-attr"), a = t.querySelector("p");
74
+ const i = t.getAttribute("product-attr"), a = t.querySelector("p");
77
75
  if (a) {
78
76
  const u = a.querySelector("strong") || a;
79
- u.textContent = `{{${c}${e}_${o}_${n}}}`;
77
+ u.textContent = `{{${n}${e}_${o}_${i}}}`;
80
78
  }
81
79
  }
82
80
  break;
83
81
  }
84
82
  }
85
83
  }
86
- function C(r, i, e, o) {
87
- r.querySelectorAll(".recommendation-product-row").forEach((c, t) => {
88
- c.querySelectorAll("[data-attribute-type]").forEach((a) => {
84
+ function C(r, c, e, o) {
85
+ r.querySelectorAll(".recommendation-product-row").forEach((n, t) => {
86
+ n.querySelectorAll("[data-attribute-type]").forEach((a) => {
89
87
  const u = a.getAttribute("data-attribute-type") || "", p = a.querySelectorAll(".attribute-cell");
90
88
  p.length > 0 ? p.forEach((l) => {
91
- f(l, u, i, t, e, o);
92
- }) : f(a, u, i, t, e, o);
89
+ f(l, u, c, t, e, o);
90
+ }) : f(a, u, c, t, e, o);
93
91
  });
94
92
  });
95
93
  }
96
- function w(r, i, e, o) {
94
+ function E(r, c, e, o) {
97
95
  const s = r.querySelectorAll(".recommendation-product-row");
98
96
  if (!s.length)
99
97
  return;
100
- const [c] = s, t = c.querySelector("[data-attribute-type]"), n = t ? t.querySelectorAll(".attribute-cell").length : 1;
98
+ const [n] = s, t = n.querySelector("[data-attribute-type]"), i = t ? t.querySelectorAll(".attribute-cell").length : 1;
101
99
  s.forEach((a, u) => {
102
100
  a.querySelectorAll("[data-attribute-type]").forEach((l) => {
103
101
  const d = l.getAttribute("data-attribute-type") || "";
104
102
  l.querySelectorAll(".attribute-cell").forEach((A, h) => {
105
- const $ = u * n + h;
106
- f(A, d, i, $, e, o);
103
+ const $ = u * i + h;
104
+ f(A, d, c, $, e, o);
107
105
  });
108
106
  });
109
107
  });
110
108
  }
111
- function E(r, i, e, o) {
112
- r.querySelectorAll(".ins-recommendation-product-container").forEach((c) => {
113
- w(c, i, e, o);
109
+ function w(r, c, e, o) {
110
+ r.querySelectorAll(".ins-recommendation-product-container").forEach((n) => {
111
+ E(n, c, e, o);
114
112
  });
115
113
  }
116
- function R(r, i, e) {
114
+ function R(r, c, e) {
117
115
  const o = r.getAttribute("data-layout") || "grid", s = r.getAttribute("currency-alignment") || "after";
118
- o === "list" ? C(r, i, s, e) : E(r, i, s, e);
116
+ o === "list" ? C(r, c, s, e) : w(r, c, s, e);
119
117
  }
120
- function b(r, i, e) {
121
- const o = new RegExp(`${i}\\s*:\\s*(\\d+)\\s*px`, "i"), s = r.match(o);
118
+ function b(r, c, e) {
119
+ const o = new RegExp(`${c}\\s*:\\s*(\\d+)\\s*px`, "i"), s = r.match(o);
122
120
  return s ? parseInt(s[1]) : e;
123
121
  }
124
- function P(r, i) {
122
+ function P(r, c) {
125
123
  let e = r.parentElement;
126
- for (; e && e !== i; ) {
124
+ for (; e && e !== c; ) {
127
125
  if (e.tagName === "TD") {
128
126
  const o = e.getAttribute("width");
129
127
  if (o && o.endsWith("%") && parseFloat(o) < 100)
@@ -134,39 +132,39 @@ function P(r, i) {
134
132
  return null;
135
133
  }
136
134
  function T(r) {
137
- const i = r.getAttribute("style") || "", e = b(i, "width", 600), o = b(i, "padding", 0) * 2, s = Math.max(0, e - o);
138
- s !== 0 && r.querySelectorAll("img.adapt-img").forEach((c) => {
139
- if (c.hasAttribute("width"))
135
+ const c = r.getAttribute("style") || "", e = b(c, "width", 600), o = b(c, "padding", 0) * 2, s = Math.max(0, e - o);
136
+ s !== 0 && r.querySelectorAll("img.adapt-img").forEach((n) => {
137
+ if (n.hasAttribute("width"))
140
138
  return;
141
- const t = P(c, r);
139
+ const t = P(n, r);
142
140
  if (!t)
143
141
  return;
144
- const n = t.getAttribute("width"), a = parseFloat(n), u = b(t.getAttribute("style") || "", "padding", 0) * 2, p = Math.floor(s * a / 100), l = Math.max(1, p - u);
145
- c.setAttribute("width", String(l));
146
- const d = c.getAttribute("style") || "";
142
+ const i = t.getAttribute("width"), a = parseFloat(i), u = b(t.getAttribute("style") || "", "padding", 0) * 2, p = Math.floor(s * a / 100), l = Math.max(1, p - u);
143
+ n.setAttribute("width", String(l));
144
+ const d = n.getAttribute("style") || "";
147
145
  if (!/\bwidth\s*:\s*\d/i.test(d)) {
148
146
  const m = d && !d.trim().endsWith(";") ? "; " : "";
149
- c.setAttribute("style", `${d}${m}width: ${l}px`);
147
+ n.setAttribute("style", `${d}${m}width: ${l}px`);
150
148
  }
151
149
  });
152
150
  }
153
- function v(r, i) {
151
+ function v(r, c) {
154
152
  const e = r.match(/<!DOCTYPE[^>]*>/i);
155
153
  return (e ? `${e[0]}
156
- ` : "") + i.documentElement.outerHTML;
154
+ ` : "") + c.documentElement.outerHTML;
157
155
  }
158
156
  function N(r) {
159
- const i = r.replaceAll("{%", "<!--{%").replaceAll("%}", "%}-->"), e = new DOMParser().parseFromString(i, "text/html"), o = e.querySelectorAll(".recommendation-block-v2");
157
+ const c = r.replaceAll("{%", "<!--{%").replaceAll("%}", "%}-->"), e = new DOMParser().parseFromString(c, "text/html"), o = e.querySelectorAll(".recommendation-block-v2");
160
158
  if (!o.length)
161
159
  return r;
162
- const { buildCampaignUrl: s } = g(), c = q();
163
- c.recommendationCampaignUrls = {};
164
- const { isFeatureEnabled: t } = S(), n = t("liquidSyntax") ? "reco_" : "";
160
+ const { buildCampaignUrl: s } = g(), n = q();
161
+ n.recommendationCampaignUrls = {};
162
+ const { isFeatureEnabled: t } = S(), i = t("liquidSyntax") ? "reco_" : "";
165
163
  return o.forEach((u) => {
166
164
  var l, d;
167
165
  const p = u.getAttribute("recommendation-id");
168
- p && ((l = u.parentNode) == null || l.insertBefore(e.createComment("REC_START"), u), (d = u.parentNode) == null || d.insertBefore(e.createComment("REC_END"), u.nextSibling), u.querySelectorAll('[data-visibility="0"]').forEach((m) => m.remove()), s(p), R(u, p, n), T(u));
169
- }), v(i, e).replaceAll("<!--{%", "{%").replaceAll("%}-->", "%}").replaceAll("&lt;!--{%", "{%").replaceAll("%}--&gt;", "%}");
166
+ p && ((l = u.parentNode) == null || l.insertBefore(e.createComment("REC_START"), u), (d = u.parentNode) == null || d.insertBefore(e.createComment("REC_END"), u.nextSibling), u.querySelectorAll('[data-visibility="0"]').forEach((m) => m.remove()), s(p), R(u, p, i), T(u));
167
+ }), v(c, e).replaceAll("<!--{%", "{%").replaceAll("%}-->", "%}").replaceAll("&lt;!--{%", "{%").replaceAll("%}--&gt;", "%}");
170
168
  }
171
169
  export {
172
170
  y as formatPriceVariable,
@@ -1,33 +1,33 @@
1
- var B = Object.defineProperty;
2
- var C = (a, s, t) => s in a ? B(a, s, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[s] = t;
3
- var p = (a, s, t) => C(a, typeof s != "symbol" ? s + "" : s, t);
4
- import { BlockId as R } from "../../../enums/block.js";
5
- import { getMigrationBannerHtml as b } from "../../../utils/migrationBannerHtml.js";
6
- import { Block as A, BlockCompositionType as y, ModificationDescription as f } from "../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
7
- import { regenerateMobileProductRows as D } from "./controls/main/utils.js";
8
- import { ensureMobileCssRulesExist as h, setMobileLayoutOptOut as I, hasMobileLayoutOptOut as E } from "./controls/mobileLayout/cssRules.js";
1
+ var k = Object.defineProperty;
2
+ var y = (a, r, t) => r in a ? k(a, r, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[r] = t;
3
+ var d = (a, r, t) => y(a, typeof r != "symbol" ? r + "" : r, t);
4
+ import { BlockId as B } from "../../../enums/block.js";
5
+ import { getMigrationBannerHtml as D } from "../../../utils/migrationBannerHtml.js";
6
+ import { Block as R, BlockCompositionType as C, ModificationDescription as h } from "../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
7
+ import { regenerateMobileProductRows as b } from "./controls/main/utils.js";
8
+ import { ensureMobileCssRulesExist as p, setMobileLayoutOptOut as f, hasMobileLayoutOptOut as A } from "./controls/mobileLayout/cssRules.js";
9
9
  import { RecommendationConfigService as c } from "./services/configService.js";
10
10
  import { useRecommendationExtensionStore as g } from "./store/recommendation.js";
11
- import { getDefaultTemplate as N } from "./templates/grid/template.js";
12
- const k = R.Recommendation, m = "recommendation-block-v2", u = "recommendation-id";
13
- let _ = !1;
14
- class q extends A {
11
+ import { getDefaultTemplate as E } from "./templates/grid/template.js";
12
+ const _ = B.Recommendation, m = "recommendation-block-v2", u = "recommendation-id";
13
+ let I = !1;
14
+ class q extends R {
15
15
  constructor() {
16
16
  super();
17
17
  /**
18
18
  * Stores the ID generated in getTemplate() so onCreated() can reuse it.
19
19
  * This avoids generating a new (different) ID in onCreated().
20
20
  */
21
- p(this, "_pendingBlockId", null);
21
+ d(this, "_pendingBlockId", null);
22
22
  }
23
23
  getId() {
24
- return k;
24
+ return _;
25
25
  }
26
26
  getIcon() {
27
27
  return "recommendation-icon";
28
28
  }
29
29
  getBlockCompositionType() {
30
- return y.CONTAINER;
30
+ return C.CONTAINER;
31
31
  }
32
32
  getName() {
33
33
  return this.api.translate("Recommendation Block");
@@ -38,8 +38,8 @@ class q extends A {
38
38
  );
39
39
  }
40
40
  getSettingsPanelTitleHtml() {
41
- return b(
42
- k,
41
+ return D(
42
+ _,
43
43
  this.api.translate("Recommendation Block"),
44
44
  this.api.translate("This block is switched from the Old Version to the New Version. We recommend you check the Recommendation block and test your message to ensure it works properly.")
45
45
  );
@@ -59,7 +59,7 @@ class q extends A {
59
59
  */
60
60
  getTemplate() {
61
61
  const t = this._generateNextId();
62
- return this._pendingBlockId = t, N(t);
62
+ return this._pendingBlockId = t, E(t);
63
63
  }
64
64
  /**
65
65
  * Called when a new block is dropped into the template
@@ -71,7 +71,13 @@ class q extends A {
71
71
  onCreated(t) {
72
72
  const e = this._getRecommendationId(t);
73
73
  if (e !== null && e > 0) {
74
- this._detectDuplicate(e) && this._handleDuplicate(t, e);
74
+ if (this._detectDuplicate(e)) {
75
+ this._handleDuplicate(t, e);
76
+ return;
77
+ }
78
+ this._pendingBlockId === null && setTimeout(() => {
79
+ this._getBlockElement(t) && this._detectDuplicate(e) && this._handleDuplicate(t, e);
80
+ }, 0);
75
81
  return;
76
82
  }
77
83
  const i = this._pendingBlockId ?? this._generateNextId();
@@ -80,16 +86,16 @@ class q extends A {
80
86
  this.api,
81
87
  t,
82
88
  { recommendationId: i }
83
- ), r = g();
84
- if (r.setCurrentBlock(i), o) {
85
- h(this.api);
89
+ ), s = g();
90
+ if (s.setCurrentBlock(i), o) {
91
+ p(this.api);
86
92
  const l = this._getBlockElement(t);
87
- l && (I(this.api, l, !0), D({
93
+ l && (f(this.api, l, !0), b({
88
94
  currentNode: t,
89
95
  documentModifier: this.api.getDocumentModifier()
90
96
  }));
91
97
  }
92
- r.patchCurrentBlockConfig({ language: n.language }, { triggerRefetch: !1 });
98
+ s.patchCurrentBlockConfig({ language: n.language }, { triggerRefetch: !1 });
93
99
  }
94
100
  /**
95
101
  * Called when the document changes or template is loaded
@@ -109,13 +115,13 @@ class q extends A {
109
115
  "Assign recommendation ID to legacy block"
110
116
  );
111
117
  }
112
- c.needsMigration(t) && this._migrateFromLegacy(t);
118
+ this._healLingeringDuplicate(t), c.needsMigration(t) && this._migrateFromLegacy(t);
113
119
  try {
114
- _ || (h(this.api), _ = !0);
120
+ I || (p(this.api), I = !0);
115
121
  const e = c.getConfig(t), i = this._getBlockElement(t);
116
122
  if (i) {
117
123
  const n = !e.mobileLayoutEnabled;
118
- E(i) !== n && I(this.api, i, n);
124
+ A(i) !== n && f(this.api, i, n);
119
125
  }
120
126
  } catch {
121
127
  }
@@ -141,14 +147,53 @@ class q extends A {
141
147
  const e = this.api.getDocumentRoot();
142
148
  e && "querySelectorAll" in e && e.querySelectorAll(`.${m}`).forEach((n) => {
143
149
  if ("getAttribute" in n) {
144
- const o = n.getAttribute(u), r = o ? parseInt(o) : 0;
145
- r > t && (t = r);
150
+ const o = n.getAttribute(u), s = o ? parseInt(o) : 0;
151
+ s > t && (t = s);
146
152
  }
147
153
  });
148
154
  } catch {
149
155
  }
150
156
  return t + 1;
151
157
  }
158
+ /**
159
+ * Heals a duplicate that slipped past the create-time `_detectDuplicate`
160
+ * scan (or that was persisted by an older template before SD-142352).
161
+ * When two blocks share a `recommendation-id`, the first occurrence in DOM
162
+ * order is treated as the source and any later sibling is reassigned a
163
+ * fresh id. The store entry and active-block focus are intentionally not
164
+ * touched here: each block's persisted `esd-ext-config` carries its own
165
+ * truth and the regular save-time hydration (`hydrateRecommendationStore`)
166
+ * will seed the new id from that. Skipping `setCurrentBlock` avoids
167
+ * yanking the user's current selection mid-edit when the heal fires from
168
+ * `onDocumentChanged`.
169
+ */
170
+ _healLingeringDuplicate(t) {
171
+ const e = this._getRecommendationId(t);
172
+ if (!(e === null || e <= 0))
173
+ try {
174
+ const i = this.api.getDocumentRoot();
175
+ if (!i || !("querySelectorAll" in i))
176
+ return;
177
+ const n = i.querySelectorAll(
178
+ `.${m}[${u}="${e}"]`
179
+ );
180
+ if (n.length <= 1)
181
+ return;
182
+ const o = this._getBlockElement(t);
183
+ if (!o)
184
+ return;
185
+ let s = -1;
186
+ for (let l = 0; l < n.length; l++)
187
+ if (n[l] === o) {
188
+ s = l;
189
+ break;
190
+ }
191
+ if (s <= 0)
192
+ return;
193
+ this._reassignDuplicateId(t, e);
194
+ } catch {
195
+ }
196
+ }
152
197
  /** True when another block already holds the same id — Stripo's duplication signal. */
153
198
  _detectDuplicate(t) {
154
199
  try {
@@ -163,33 +208,42 @@ class q extends A {
163
208
  }
164
209
  /** Assigns a fresh id to a duplicated block and syncs DOM, node config and store. */
165
210
  _handleDuplicate(t, e) {
211
+ const i = this._reassignDuplicateId(t, e), n = g();
212
+ n.cloneBlockState(e, i), n.setCurrentBlock(i);
213
+ }
214
+ /**
215
+ * Core id-reassignment for a duplicated block: rewrites the DOM
216
+ * `recommendation-id`, the instance class and the persisted node config to
217
+ * a fresh id. Returns the new id.
218
+ *
219
+ * Used by `_handleDuplicate` at create-time (which additionally clones the
220
+ * store entry and focuses the new block) and by `_healLingeringDuplicate`
221
+ * at document-change time (which intentionally skips store/focus side
222
+ * effects so older bug-affected templates can self-repair without
223
+ * disturbing the user's current selection).
224
+ */
225
+ _reassignDuplicateId(t, e) {
166
226
  const i = this._generateNextId(), n = this._getBlockElement(t);
167
- this._assignRecommendationId(t, i), n && this._reassignInstanceClass(n, e, i), c.hasConfig(t) && c.updateConfig(
227
+ return this._assignRecommendationId(t, i), n && this._reassignInstanceClass(n, e, i), c.hasConfig(t) && c.updateConfig(
168
228
  this.api,
169
229
  t,
170
230
  { recommendationId: i },
171
231
  `Reassign recommendation ID on duplicate (was ${e})`
172
- );
173
- const o = g();
174
- o.cloneBlockState(e, i), o.setCurrentBlock(i);
232
+ ), i;
175
233
  }
176
234
  /**
177
235
  * Rewrites `ins-recommendation-v3-block-{id}` on the clone — the instance
178
- * class scopes per-block CSS rules, so leaving the source's class would
179
- * make stylistic edits leak between source and clone.
236
+ * class scopes per-block CSS rules in the editor and is also the key the
237
+ * mail render service reads to look up per-id campaign config, so leaving
238
+ * the source's class would both leak stylistic edits across blocks and
239
+ * cause the mail render to apply the source block's config to the clone.
180
240
  */
181
241
  _reassignInstanceClass(t, e, i) {
182
242
  if (!("getAttribute" in t))
183
243
  return;
184
- const n = t.getAttribute("class");
185
- if (!n)
186
- return;
187
- const o = `ins-recommendation-v3-block-${e}`, r = `ins-recommendation-v3-block-${i}`;
188
- if (!n.includes(o))
189
- return;
190
- const l = n.replace(o, r), d = this.api.getDocumentModifier();
191
- d.modifyHtml(t).setAttribute("class", l), d.apply(new f(
192
- `Reassign recommendation instance class ${o} -> ${r}`
244
+ const n = `ins-recommendation-v3-block-${e}`, o = `ins-recommendation-v3-block-${i}`, s = this.api.getDocumentModifier();
245
+ s.modifyHtml(t).removeClass(n).setClass(o), s.apply(new h(
246
+ `Reassign recommendation instance class ${n} -> ${o}`
193
247
  ));
194
248
  }
195
249
  /**
@@ -203,7 +257,7 @@ class q extends A {
203
257
  if (!i)
204
258
  return;
205
259
  const n = this.api.getDocumentModifier();
206
- n.modifyHtml(i).setAttribute(u, e.toString()), n.apply(new f(`Assign recommendation ID ${e}`));
260
+ n.modifyHtml(i).setAttribute(u, e.toString()), n.apply(new h(`Assign recommendation ID ${e}`));
207
261
  }
208
262
  /**
209
263
  * Gets the recommendation-id from a block node
@@ -237,6 +291,6 @@ class q extends A {
237
291
  }
238
292
  }
239
293
  export {
240
- k as BLOCK_ID,
294
+ _ as BLOCK_ID,
241
295
  q as RecommendationBlock
242
296
  };
@@ -60,14 +60,41 @@ export declare class RecommendationBlock extends Block {
60
60
  * in the document and finding the maximum existing ID + 1.
61
61
  */
62
62
  private _generateNextId;
63
+ /**
64
+ * Heals a duplicate that slipped past the create-time `_detectDuplicate`
65
+ * scan (or that was persisted by an older template before SD-142352).
66
+ * When two blocks share a `recommendation-id`, the first occurrence in DOM
67
+ * order is treated as the source and any later sibling is reassigned a
68
+ * fresh id. The store entry and active-block focus are intentionally not
69
+ * touched here: each block's persisted `esd-ext-config` carries its own
70
+ * truth and the regular save-time hydration (`hydrateRecommendationStore`)
71
+ * will seed the new id from that. Skipping `setCurrentBlock` avoids
72
+ * yanking the user's current selection mid-edit when the heal fires from
73
+ * `onDocumentChanged`.
74
+ */
75
+ private _healLingeringDuplicate;
63
76
  /** True when another block already holds the same id — Stripo's duplication signal. */
64
77
  private _detectDuplicate;
65
78
  /** Assigns a fresh id to a duplicated block and syncs DOM, node config and store. */
66
79
  private _handleDuplicate;
80
+ /**
81
+ * Core id-reassignment for a duplicated block: rewrites the DOM
82
+ * `recommendation-id`, the instance class and the persisted node config to
83
+ * a fresh id. Returns the new id.
84
+ *
85
+ * Used by `_handleDuplicate` at create-time (which additionally clones the
86
+ * store entry and focuses the new block) and by `_healLingeringDuplicate`
87
+ * at document-change time (which intentionally skips store/focus side
88
+ * effects so older bug-affected templates can self-repair without
89
+ * disturbing the user's current selection).
90
+ */
91
+ private _reassignDuplicateId;
67
92
  /**
68
93
  * Rewrites `ins-recommendation-v3-block-{id}` on the clone — the instance
69
- * class scopes per-block CSS rules, so leaving the source's class would
70
- * make stylistic edits leak between source and clone.
94
+ * class scopes per-block CSS rules in the editor and is also the key the
95
+ * mail render service reads to look up per-id campaign config, so leaving
96
+ * the source's class would both leak stylistic edits across blocks and
97
+ * cause the mail render to apply the source block's config to the clone.
71
98
  */
72
99
  private _reassignInstanceClass;
73
100
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useinsider/guido",
3
- "version": "3.5.0-beta.17c0032",
3
+ "version": "3.5.0-beta.6fa034d",
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",