@useinsider/guido 3.20.0 → 3.20.1-beta.5e7765e

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 (33) hide show
  1. package/dist/config/compiler/itemsCompilerRules.js +21 -5
  2. package/dist/config/compiler/utils/itemsCompilerUtils.js +64 -17
  3. package/dist/config/migrator/itemsBlockMigrator.js +112 -108
  4. package/dist/extensions/Blocks/Items/block.js +78 -67
  5. package/dist/extensions/Blocks/Items/constants/cardWidth.js +20 -0
  6. package/dist/extensions/Blocks/Items/layouts/vertical.html.js +1 -1
  7. package/dist/extensions/Blocks/Recommendation/block.js +51 -44
  8. package/dist/extensions/Blocks/Recommendation/controls/cardComposition/index.js +103 -91
  9. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +90 -73
  10. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +209 -203
  11. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +122 -90
  12. package/dist/extensions/Blocks/Recommendation/utils/blockUrlConfig.js +25 -0
  13. package/dist/extensions/Blocks/Recommendation/utils/captureStyleTemplates.js +72 -70
  14. package/dist/extensions/Blocks/Recommendation/utils/compositionKeys.js +69 -60
  15. package/dist/extensions/Blocks/Recommendation/utils/nodeConfigWriter.js +46 -25
  16. package/dist/extensions/Blocks/Recommendation/utils/strategyCommit.js +40 -17
  17. package/dist/extensions/Blocks/Unsubscribe/block.js +82 -83
  18. package/dist/src/config/compiler/utils/itemsCompilerUtils.d.ts +10 -0
  19. package/dist/src/extensions/Blocks/Items/block.d.ts +5 -0
  20. package/dist/src/extensions/Blocks/Items/constants/cardWidth.d.ts +12 -0
  21. package/dist/src/extensions/Blocks/Recommendation/block.d.ts +2 -0
  22. package/dist/src/extensions/Blocks/Recommendation/controls/cardComposition/index.d.ts +3 -0
  23. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +2 -0
  24. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +18 -5
  25. package/dist/src/extensions/Blocks/Recommendation/utils/blockUrlConfig.d.ts +4 -0
  26. package/dist/src/extensions/Blocks/Recommendation/utils/compositionKeys.d.ts +4 -0
  27. package/dist/src/extensions/Blocks/Recommendation/utils/nodeConfigWriter.d.ts +3 -1
  28. package/dist/src/extensions/Blocks/Recommendation/utils/strategyCommit.d.ts +7 -0
  29. package/dist/src/extensions/Blocks/Unsubscribe/block.d.ts +0 -2
  30. package/dist/src/utils/liquidProtection.d.ts +3 -0
  31. package/dist/utils/liquidProtection.js +5 -0
  32. package/dist/utils/templatePreparation.js +61 -85
  33. package/package.json +1 -1
@@ -1,42 +1,41 @@
1
- var S = Object.defineProperty;
2
- var f = (c, o, e) => o in c ? S(c, o, { enumerable: !0, configurable: !0, writable: !0, value: e }) : c[o] = e;
3
- var a = (c, o, e) => f(c, typeof o != "symbol" ? o + "" : o, e);
4
- import { useToaster as L } from "../../../composables/useToaster.js";
5
- import { ToasterTypeOptions as A } from "../../../enums/toaster.js";
1
+ var A = Object.defineProperty;
2
+ var y = (c, o, e) => o in c ? A(c, o, { enumerable: !0, configurable: !0, writable: !0, value: e }) : c[o] = e;
3
+ var g = (c, o, e) => y(c, typeof o != "symbol" ? o + "" : o, e);
4
+ import { useToaster as T } from "../../../composables/useToaster.js";
5
+ import { ToasterTypeOptions as I } from "../../../enums/toaster.js";
6
6
  import { PAGE_TYPES as h } from "../../../enums/unsubscribe.js";
7
- import { useEditorStore as y } from "../../../stores/editor.js";
8
- import { useUnsubscribeStore as l } from "../../../stores/unsubscribe.js";
9
- import { Block as T, BlockCompositionType as I, ContextActionType as B, ModificationDescription as d, BlockType as v } from "../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
10
- import { getDefaultTemplate as C } from "./template.js";
11
- import { UNSUBSCRIBE_BLOCK_SELECTOR as m, DATA_ATTRIBUTES as n, UNSUBSCRIBE_EVENTS as b } from "./utils/constants.js";
12
- import { parsePageList as _ } from "./utils/utils.js";
13
- const N = "unsubscribe-block", E = 'a[data-unsubscribe-link="true"]', U = "{{ins-unsubscribe-link}}", R = "https://academy.insiderone.com/docs/adding-unsubscribe-pages-into-emails", D = "Removing the unsubscribe text leaves an empty block, but an active unsubscribe link is required. Undo your last action to restore the text, or delete the unsubscribe block entirely.", w = {
7
+ import { useEditorStore as L } from "../../../stores/editor.js";
8
+ import { useUnsubscribeStore as a } from "../../../stores/unsubscribe.js";
9
+ import { Block as C, BlockCompositionType as v, ContextActionType as S, ModificationDescription as u, BlockType as N } from "../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
10
+ import { getDefaultTemplate as U } from "./template.js";
11
+ import { UNSUBSCRIBE_BLOCK_SELECTOR as B, DATA_ATTRIBUTES as n, UNSUBSCRIBE_EVENTS as m } from "./utils/constants.js";
12
+ import { parsePageList as f } from "./utils/utils.js";
13
+ const R = "unsubscribe-block", E = 'a[data-unsubscribe-link="true"]', D = "{{ins-unsubscribe-link}}", w = "https://academy.insiderone.com/docs/adding-unsubscribe-pages-into-emails", P = "Removing the unsubscribe text leaves an empty block, but an active unsubscribe link is required. Undo your last action to restore the text, or delete the unsubscribe block entirely.", x = {
14
14
  [h.GLOBAL_UNSUBSCRIBE]: "{{ins-global-unsubscribe-link}}",
15
15
  [h.SUBSCRIPTION_PREFERENCE_CENTER]: "{{ins-preferences-unsubscribe-link}}"
16
- }, P = 3;
17
- class W extends T {
16
+ }, G = 3;
17
+ let d = null, p = null;
18
+ class j extends C {
18
19
  constructor() {
19
20
  super();
20
- a(this, "selectEventListener", null);
21
- a(this, "cancelEventListener", null);
22
- a(this, "currentNode");
21
+ g(this, "currentNode");
23
22
  // Per-block "did this instance have a link last time we saw it?" tracking.
24
23
  // Keyed by the stable `data-unsubscribe-block-id` we assign, because
25
24
  // ImmutableHtmlNode instances are recreated on every document mutation and
26
25
  // can't be used as Map keys directly. A singleton boolean here would be
27
26
  // polluted across blocks since Stripo fires onDocumentChanged per-instance.
28
- a(this, "linkStateByBlockId", /* @__PURE__ */ new Map());
27
+ g(this, "linkStateByBlockId", /* @__PURE__ */ new Map());
29
28
  // Per-block prepend retry counter for the leading text guard (SD-143097).
30
- a(this, "guardAttemptsByBlockId", /* @__PURE__ */ new Map());
29
+ g(this, "guardAttemptsByBlockId", /* @__PURE__ */ new Map());
31
30
  }
32
31
  getId() {
33
- return N;
32
+ return R;
34
33
  }
35
34
  getIcon() {
36
35
  return "unsubscribe-icon";
37
36
  }
38
37
  getBlockCompositionType() {
39
- return I.BLOCK;
38
+ return v.BLOCK;
40
39
  }
41
40
  getName() {
42
41
  return this.api.translate("Unsubscribe Block");
@@ -45,16 +44,16 @@ class W extends T {
45
44
  return this.api.translate("Unsubscribe Block Description");
46
45
  }
47
46
  getTemplate() {
48
- return C();
47
+ return U();
49
48
  }
50
49
  getContextActionsIds() {
51
- return [B.MOVE, B.REMOVE];
50
+ return [S.MOVE, S.REMOVE];
52
51
  }
53
52
  onSelect(e) {
54
- if (this.currentNode = e, y().isPreviewModeOpen)
53
+ if (this.currentNode = e, L().isPreviewModeOpen)
55
54
  return;
56
55
  const t = this._getOrAssignBlockId(e);
57
- t && this.linkStateByBlockId.set(t, this._hasUnsubscribeLink(e)), this._setupSelectEventListener(), this._setupCancelEventListener(t), !("getAttribute" in e && e.getAttribute("data-migration")) && (this._resetStoreState(), t && (l().pendingBlockId = t), this._loadBlockState(e), this._checkExistingBlocks(), this._openDrawer());
56
+ t && this.linkStateByBlockId.set(t, this._hasUnsubscribeLink(e)), this._setupSelectEventListener(), this._setupCancelEventListener(t), !("getAttribute" in e && e.getAttribute("data-migration")) && (this._resetStoreState(), t && (a().pendingBlockId = t), this._loadBlockState(e), this._checkExistingBlocks(), this._openDrawer());
58
57
  }
59
58
  onCreated(e) {
60
59
  this._ensureLeadingTextGuard(e);
@@ -62,7 +61,7 @@ class W extends T {
62
61
  onDocumentInit() {
63
62
  try {
64
63
  const e = this.api.getDocumentRoot();
65
- e && "querySelectorAll" in e && e.querySelectorAll(m).forEach((t) => {
64
+ e && "querySelectorAll" in e && e.querySelectorAll(B).forEach((t) => {
66
65
  this._ensureLeadingTextGuard(t);
67
66
  });
68
67
  } catch {
@@ -73,8 +72,8 @@ class W extends T {
73
72
  const t = this._getOrAssignBlockId(e);
74
73
  if (!t)
75
74
  return;
76
- const s = this._hasUnsubscribeLink(e);
77
- this.linkStateByBlockId.get(t) === !0 && !s && this._warnLinkRemoved(), this.linkStateByBlockId.set(t, s);
75
+ const i = this._hasUnsubscribeLink(e);
76
+ this.linkStateByBlockId.get(t) === !0 && !i && this._warnLinkRemoved(), this.linkStateByBlockId.set(t, i);
78
77
  }
79
78
  onDelete(e) {
80
79
  this._removeEventListeners(), this._removeBlockTemplatesFromStore(e), this._resetStoreState();
@@ -95,27 +94,27 @@ class W extends T {
95
94
  const t = e.querySelector(E);
96
95
  if (!t)
97
96
  return;
98
- let s = t, r = s.parent();
97
+ let i = t, r = i.parent();
99
98
  for (; r && "getTagName" in r && r.getTagName().toLowerCase() !== "p"; )
100
- s = r, r = r.parent();
99
+ i = r, r = r.parent();
101
100
  if (!r || !("getTagName" in r) || r.getTagName().toLowerCase() !== "p")
102
101
  return;
103
- const i = s.previousSibling();
104
- if (!!(i && i.getType() === "text" && i.getTextContent())) {
105
- const p = this._readBlockId(e);
106
- p && this.guardAttemptsByBlockId.delete(p);
102
+ const s = i.previousSibling();
103
+ if (!!(s && s.getType() === "text" && s.getTextContent())) {
104
+ const b = this._readBlockId(e);
105
+ b && this.guardAttemptsByBlockId.delete(b);
107
106
  return;
108
107
  }
109
- const g = this._getOrAssignBlockId(e);
110
- if (!g)
108
+ const k = this._getOrAssignBlockId(e);
109
+ if (!k)
111
110
  return;
112
- const k = this.guardAttemptsByBlockId.get(g) ?? 0;
113
- if (!(k >= P)) {
114
- this.guardAttemptsByBlockId.set(g, k + 1);
111
+ const _ = this.guardAttemptsByBlockId.get(k) ?? 0;
112
+ if (!(_ >= G)) {
113
+ this.guardAttemptsByBlockId.set(k, _ + 1);
115
114
  try {
116
- this.api.getDocumentModifier().modifyHtml(r).prepend(" ").apply(new d("Ensure unsubscribe link text guard"));
117
- } catch (p) {
118
- console.warn("[UnsubscribeBlock] Failed to ensure link text guard:", p);
115
+ this.api.getDocumentModifier().modifyHtml(r).prepend(" ").apply(new u("Ensure unsubscribe link text guard"));
116
+ } catch (b) {
117
+ console.warn("[UnsubscribeBlock] Failed to ensure link text guard:", b);
119
118
  }
120
119
  }
121
120
  }
@@ -137,13 +136,13 @@ class W extends T {
137
136
  return t;
138
137
  if (!("getAttribute" in e))
139
138
  return null;
140
- const s = this._generateNextBlockId();
139
+ const i = this._generateNextBlockId();
141
140
  try {
142
- this.api.getDocumentModifier().modifyHtml(e).setAttribute(n.BLOCK_ID, s).apply(new d(`Assign unsubscribe block id ${s}`));
141
+ this.api.getDocumentModifier().modifyHtml(e).setAttribute(n.BLOCK_ID, i).apply(new u(`Assign unsubscribe block id ${i}`));
143
142
  } catch (r) {
144
143
  return console.warn("[UnsubscribeBlock] Failed to assign block id:", r), null;
145
144
  }
146
- return s;
145
+ return i;
147
146
  }
148
147
  /**
149
148
  * Generates a unique id by scanning the document for the highest existing
@@ -154,10 +153,10 @@ class W extends T {
154
153
  let e = 0;
155
154
  try {
156
155
  const t = this.api.getDocumentRoot();
157
- t && "querySelectorAll" in t && t.querySelectorAll(m).forEach((r) => {
156
+ t && "querySelectorAll" in t && t.querySelectorAll(B).forEach((r) => {
158
157
  if ("getAttribute" in r) {
159
- const i = r.getAttribute(n.BLOCK_ID), u = i ? parseInt(i) : 0;
160
- u > e && (e = u);
158
+ const s = r.getAttribute(n.BLOCK_ID), l = s ? parseInt(s) : 0;
159
+ l > e && (e = l);
161
160
  }
162
161
  });
163
162
  } catch {
@@ -166,14 +165,14 @@ class W extends T {
166
165
  }
167
166
  _warnLinkRemoved() {
168
167
  try {
169
- const { showToaster: e } = L();
168
+ const { showToaster: e } = T();
170
169
  e({
171
- type: A.Warning,
172
- message: this.api.translate(D),
170
+ type: I.Warning,
171
+ message: this.api.translate(P),
173
172
  actionButton: {
174
173
  text: this.api.translate("Visit Academy"),
175
174
  onClick: () => {
176
- window.open(R, "_blank", "noopener,noreferrer");
175
+ window.open(w, "_blank", "noopener,noreferrer");
177
176
  }
178
177
  }
179
178
  });
@@ -182,28 +181,28 @@ class W extends T {
182
181
  }
183
182
  }
184
183
  _setupSelectEventListener() {
185
- this._removeSelectEventListener(), this.selectEventListener = (e) => {
186
- const t = e, { collectionType: s, selectedPages: r } = t.detail;
187
- this._updateBlock(s, r.join(","));
188
- }, document.addEventListener(b.SELECT, this.selectEventListener);
184
+ this._removeSelectEventListener(), d = (e) => {
185
+ const t = e, { collectionType: i, selectedPages: r } = t.detail;
186
+ this._updateBlock(i, r.join(","));
187
+ }, document.addEventListener(m.SELECT, d);
189
188
  }
190
189
  _removeSelectEventListener() {
191
- this.selectEventListener && (document.removeEventListener(b.SELECT, this.selectEventListener), this.selectEventListener = null);
190
+ d && (document.removeEventListener(m.SELECT, d), d = null);
192
191
  }
193
192
  _setupCancelEventListener(e) {
194
- this._removeCancelEventListener(), this.cancelEventListener = (t) => {
195
- const { detail: s } = t;
196
- ((s == null ? void 0 : s.blockId) ?? null) === e && this._handleCancel();
197
- }, document.addEventListener(b.CANCEL, this.cancelEventListener);
193
+ this._removeCancelEventListener(), p = (t) => {
194
+ const { detail: i } = t;
195
+ ((i == null ? void 0 : i.blockId) ?? null) === e && this._handleCancel();
196
+ }, document.addEventListener(m.CANCEL, p);
198
197
  }
199
198
  _removeCancelEventListener() {
200
- this.cancelEventListener && (document.removeEventListener(b.CANCEL, this.cancelEventListener), this.cancelEventListener = null);
199
+ p && (document.removeEventListener(m.CANCEL, p), p = null);
201
200
  }
202
201
  _handleCancel() {
203
202
  try {
204
203
  if (!this.currentNode)
205
204
  return;
206
- this.api.getDocumentModifier().modifyHtml(this.currentNode).replaceWith(`<${v.EMPTY_CONTAINER}/>`).apply(new d("Removed unsubscribe block due to cancel"));
205
+ this.api.getDocumentModifier().modifyHtml(this.currentNode).replaceWith(`<${N.EMPTY_CONTAINER}/>`).apply(new u("Removed unsubscribe block due to cancel"));
207
206
  } catch (e) {
208
207
  console.warn("[UnsubscribeBlock] Failed to remove unsubscribe block:", e);
209
208
  }
@@ -214,32 +213,32 @@ class W extends T {
214
213
  _updateBlock(e, t) {
215
214
  if (!this.currentNode || !("querySelector" in this.currentNode))
216
215
  return;
217
- const s = this.currentNode.querySelector(E);
218
- if (!s)
216
+ const i = this.currentNode.querySelector(E);
217
+ if (!i)
219
218
  return;
220
219
  const r = this._getMergeTag(e);
221
- this.api.getDocumentModifier().modifyHtml(s).setAttribute("href", r).apply(new d(`Updated unsubscribe link to ${r}`)), this.api.getDocumentModifier().modifyHtml(this.currentNode).setAttribute(n.PAGE_TYPE, e.toString()).setAttribute(n.PAGE_LIST, t).apply(new d("Updated unsubscribe block metadata"));
220
+ this.api.getDocumentModifier().modifyHtml(i).setAttribute("href", r).apply(new u(`Updated unsubscribe link to ${r}`)), this.api.getDocumentModifier().modifyHtml(this.currentNode).setAttribute(n.PAGE_TYPE, e.toString()).setAttribute(n.PAGE_LIST, t).apply(new u("Updated unsubscribe block metadata"));
222
221
  }
223
222
  _getMergeTag(e) {
224
- return w[e] ?? U;
223
+ return x[e] ?? D;
225
224
  }
226
225
  _openDrawer() {
227
226
  if (!(this.currentNode && this.currentNode.getAttribute("data-unsubscribe-page-type")))
228
227
  try {
229
- const e = l();
228
+ const e = a();
230
229
  e.typeSelectionDrawerStatus = !0;
231
230
  } catch (e) {
232
231
  console.error("[UnsubscribeBlock] Failed to open drawer:", e);
233
232
  }
234
233
  }
235
234
  _checkExistingBlocks() {
236
- const e = l();
237
- e.isGlobalUnsubscribeDisabled = !1, e.isSubscriptionPreferencesCenterDisabled = !1, this.api.getDocumentRoot().querySelectorAll(m).forEach((s) => {
238
- if ("getAttribute" in s) {
239
- const r = s.getAttribute(n.PAGE_TYPE);
235
+ const e = a();
236
+ e.isGlobalUnsubscribeDisabled = !1, e.isSubscriptionPreferencesCenterDisabled = !1, this.api.getDocumentRoot().querySelectorAll(B).forEach((i) => {
237
+ if ("getAttribute" in i) {
238
+ const r = i.getAttribute(n.PAGE_TYPE);
240
239
  if (r) {
241
- const i = Number(r);
242
- i === h.GLOBAL_UNSUBSCRIBE ? e.isGlobalUnsubscribeDisabled = !0 : i === h.SUBSCRIPTION_PREFERENCE_CENTER && (e.isSubscriptionPreferencesCenterDisabled = !0);
240
+ const s = Number(r);
241
+ s === h.GLOBAL_UNSUBSCRIBE ? e.isGlobalUnsubscribeDisabled = !0 : s === h.SUBSCRIPTION_PREFERENCE_CENTER && (e.isSubscriptionPreferencesCenterDisabled = !0);
243
242
  }
244
243
  }
245
244
  });
@@ -247,14 +246,14 @@ class W extends T {
247
246
  async _loadBlockState(e) {
248
247
  if (!("getAttribute" in e))
249
248
  return;
250
- const t = e.getAttribute(n.PAGE_TYPE), s = e.getAttribute(n.PAGE_LIST);
251
- if (!t || !s)
249
+ const t = e.getAttribute(n.PAGE_TYPE), i = e.getAttribute(n.PAGE_LIST);
250
+ if (!t || !i)
252
251
  return;
253
- const r = l(), i = Number(t), u = _(s);
254
- await r.fetchTemplates(), r.setCollectionWithoutAutoSelection(i), r.loadSelectedTemplates(u);
252
+ const r = a(), s = Number(t), l = f(i);
253
+ await r.fetchTemplates(), r.setCollectionWithoutAutoSelection(s), r.loadSelectedTemplates(l);
255
254
  }
256
255
  _resetStoreState() {
257
- l().$reset();
256
+ a().$reset();
258
257
  }
259
258
  _removeBlockTemplatesFromStore(e) {
260
259
  if (!("getAttribute" in e))
@@ -262,11 +261,11 @@ class W extends T {
262
261
  const t = e.getAttribute(n.PAGE_LIST);
263
262
  if (!t)
264
263
  return;
265
- const s = l(), r = _(t);
266
- s.removeUnsubscribePages(r);
264
+ const i = a(), r = f(t);
265
+ i.removeUnsubscribePages(r);
267
266
  }
268
267
  }
269
268
  export {
270
- N as UNSUBSCRIBE_BLOCK_ID,
271
- W as UnsubscribeBlock
269
+ R as UNSUBSCRIBE_BLOCK_ID,
270
+ j as UnsubscribeBlock
272
271
  };
@@ -10,3 +10,13 @@ export declare const MSO_ITEMS_HIDE_END = "MSO_ITEMS_HIDE_END";
10
10
  * and collapse them inline for web-engine Outlook.
11
11
  */
12
12
  export declare function wrapHiddenPriceTdsForOutlook(html: string): string;
13
+ /**
14
+ * Export safety net for a zero Items width: the wrapper gets the 100% pin, the cell's zero is
15
+ * dropped so it keeps its column width (a pin there overflows 33%/50% columns).
16
+ */
17
+ export declare function normalizeItemsBlockWidths(html: string): string;
18
+ /**
19
+ * The mobile `.adapt-img { width: 100% }` rule sizes the horizontal thumbnail by its auto-width column,
20
+ * so it grows in a roomy row and collapses to nothing in a crowded one — bound it to [default, width].
21
+ */
22
+ export declare function lockHorizontalItemImageWidths(html: string): string;
@@ -46,6 +46,11 @@ export declare class ItemsBlock extends Block {
46
46
  * relies on this td-level fallback in pairProductVariables.
47
47
  */
48
48
  private _stampProductTdDomAttributes;
49
+ /**
50
+ * Repairs a saved module's collapsed card into the state the load migrator produces. Acts only
51
+ * on a zero width, so Stripo's non-zero drop-time width never triggers it and a healed block no-ops.
52
+ */
53
+ private _healCollapsedWidths;
49
54
  /**
50
55
  * Whether a currency self-heal pass should run for this block (SD-147588). The only gate here is
51
56
  * once-per-session: heal each instance at most once, so the auto-fix can't fight undo (undo
@@ -0,0 +1,12 @@
1
+ export declare const PRODUCT_TD_SELECTOR = ".ins-product-td";
2
+ export declare const PRODUCT_WRAPPER_SELECTOR = ".ins-product-wrapper";
3
+ /** The declaration `layouts/vertical.html` and `layouts/horizontal.html` ship inline. */
4
+ export declare const WIDTH_PIN_DECLARATION = "width: 100%";
5
+ /** True for a `width` attribute value that renders the card zero-wide (`0`, `0px`, `0%`). */
6
+ export declare const isZeroWidthValue: (value?: string | null) => boolean;
7
+ /** True when an inline style declares a zero `width` (ignores `max-width` / `min-width`). */
8
+ export declare const hasZeroWidthDeclaration: (style: string) => boolean;
9
+ /** Rewrites a zero width to the pin or appends it; a non-zero width is kept. Idempotent. */
10
+ export declare const withWidthPin: (style: string) => string;
11
+ /** Drops a zero `width` declaration; the product cell must inherit its column width, not be pinned. */
12
+ export declare const withoutZeroWidth: (style: string) => string;
@@ -17,6 +17,8 @@ export declare class RecommendationBlock extends Block {
17
17
  private _pendingBlockId;
18
18
  /** Ids already reset under their new name, so the deferred pass skips their stale snapshot. */
19
19
  private _resetSourceIds;
20
+ /** Ids handed to duplicates the document root does not show yet: a copied module renames all its blocks at once. */
21
+ private _unsettledDuplicateIds;
20
22
  constructor();
21
23
  getId(): string;
22
24
  getIcon(): string;
@@ -189,6 +189,9 @@ export declare class RecommendationCardCompositionControl extends CommonControl
189
189
  */
190
190
  private _buildCompositionHtml;
191
191
  private _applyVisibilityToBlock;
192
+ /** Inline "Product Prices" group row is tagged with whichever price comes first, so it drives both keys. */
193
+ private _resolveVisibilityKeys;
194
+ private _isInlinePrices;
192
195
  private _applyTitleVisibilityToBlock;
193
196
  private _resolveTitleInjection;
194
197
  private _readTitleHtml;
@@ -139,6 +139,8 @@ export declare class RecommendationBlockControl extends CommonControl {
139
139
  * fails when the size actually changed.
140
140
  */
141
141
  private _debouncedRegenerateWithProducts;
142
+ /** Draws the current block's products into this panel's node. */
143
+ private _regenerateWithProducts;
142
144
  /**
143
145
  * Listen to store changes that require product refresh or regeneration.
144
146
  *
@@ -620,12 +620,25 @@ export declare const useRecommendationExtensionStore: import("pinia").StoreDefin
620
620
  fetchRecommendationProducts(): Promise<void>;
621
621
  _doFetchProducts(): Promise<void>;
622
622
  /**
623
- * Block-scoped counterpart of `fetchRecommendationProducts`, which only ever serves the
624
- * selected block. The legacy conversion must refresh the products of the block that
625
- * started it even after the user selected another one.
623
+ * Block-scoped counterpart of `fetchRecommendationProducts`, which only serves the selected block:
624
+ * the legacy conversion and the duplicate refresh must reach a block that may no longer be selected.
625
+ * @returns whether real products arrived
626
626
  */
627
- fetchStrategyProductsForBlock(blockId: number): Promise<void>;
628
- _doFetchStrategyProducts(blockId: number): Promise<void>;
627
+ fetchStrategyProductsForBlock(blockId: number, options?: {
628
+ keepOnFailure?: boolean;
629
+ }): Promise<boolean>;
630
+ /**
631
+ * Reloads a block's products and adopts its strategy's current size, which may have
632
+ * moved since the block was assigned.
633
+ * @returns whether a fresh listing holds the strategy and its products arrived
634
+ */
635
+ refreshStrategyBlock(blockId: number): Promise<boolean>;
636
+ _doFetchStrategyProducts(blockId: number, options?: {
637
+ keepOnFailure?: boolean;
638
+ }): Promise<boolean>;
639
+ _loadStrategyProducts(blockId: number, token: number, { keepOnFailure }: {
640
+ keepOnFailure?: boolean;
641
+ }): Promise<boolean>;
629
642
  _setBlockProducts(blockId: number, products: RecommendationProduct[], size: number): void;
630
643
  }>;
631
644
  export {};
@@ -0,0 +1,4 @@
1
+ import type { PerBlockUrlConfig } from '../store/recommendation';
2
+ import type { RecommendationNodeConfig } from '../types/nodeConfig';
3
+ /** Maps a persisted node config onto the store's per-block seed, defaulting what an older config lacks. */
4
+ export declare function toBlockUrlConfig(config: Partial<RecommendationNodeConfig>): PerBlockUrlConfig;
@@ -33,6 +33,10 @@ export declare function normalizeCustomAttrValues(values: string[], blockNode?:
33
33
  * value, which stays correct on rows still tagged with a legacy bare name.
34
34
  */
35
35
  export declare function resolveRowCompositionKey(row: ImmutableHtmlNode): string | null;
36
+ /** Inline price mode renders sale + original price in ONE row, tagged with whichever comes first. */
37
+ export declare function isMergedInlinePriceRow(row: ImmutableHtmlNode | null | undefined): boolean;
38
+ /** Keys whose visibility a row carries: the merged inline price row stands for both prices. */
39
+ export declare function resolveRowVisibilityKeys(row: ImmutableHtmlNode): string[];
36
40
  /**
37
41
  * Finds an attribute row by composition key, tolerating rows still tagged with the
38
42
  * legacy bare-name key. Custom candidates are validated against their `product-attr`,
@@ -1,5 +1,5 @@
1
1
  import type { DocumentModifier } from '../services/configService';
2
- import type { PartialNodeConfig } from '../types/nodeConfig';
2
+ import type { PartialNodeConfig, RecommendationNodeConfig } from '../types/nodeConfig';
3
3
  import type { RecommendationProduct } from '@@/Types/recommendation';
4
4
  import { ImmutableHtmlNode } from '@stripoinc/ui-editor-extensions';
5
5
  interface ConfigWriterApi {
@@ -13,6 +13,8 @@ interface ConfigWriterApi {
13
13
  * itself is torn down.
14
14
  */
15
15
  export declare function registerNodeConfigWriter(api: ConfigWriterApi): void;
16
+ /** Reads the persisted config of every node carrying a block id (two while a duplicate still shares it). */
17
+ export declare function readBlockNodeConfigs(blockId: number): RecommendationNodeConfig[];
16
18
  /**
17
19
  * Commits a config change and its product re-render straight to the document,
18
20
  * bypassing the settings panel.
@@ -1,3 +1,10 @@
1
+ import type { RecommendationNodeConfig } from '../types/nodeConfig';
2
+ /**
3
+ * Re-reads the strategy of a duplicated block and its source and redraws both: the clone copies the
4
+ * cards the source last drew, so a strategy edited since then only reached a block once selected (SD-153266).
5
+ * @param config - the arriving block's persisted config; seeds either block its store entry does not match
6
+ */
7
+ export declare function refreshDuplicatedStrategyBlocks(sourceId: number, duplicateId: number, config: RecommendationNodeConfig): Promise<void>;
1
8
  /**
2
9
  * Writes a strategy change and its products straight to the document, without the settings panel.
3
10
  *
@@ -2,8 +2,6 @@ import type { ImmutableHtmlElementNode, ImmutableHtmlNode } from '@stripoinc/ui-
2
2
  import { Block, BlockCompositionType } from '@stripoinc/ui-editor-extensions';
3
3
  export declare const UNSUBSCRIBE_BLOCK_ID = "unsubscribe-block";
4
4
  export declare class UnsubscribeBlock extends Block {
5
- private selectEventListener;
6
- private cancelEventListener;
7
5
  private currentNode?;
8
6
  private linkStateByBlockId;
9
7
  private guardAttemptsByBlockId;
@@ -0,0 +1,3 @@
1
+ export declare const protectLiquid: (html: string) => string;
2
+ /** Reverses `protectLiquid`, including markers the serializer escaped inside attribute values. */
3
+ export declare const restoreLiquid: (html: string) => string;
@@ -0,0 +1,5 @@
1
+ const p = (l) => l.split(/(<!--[\s\S]*?-->)/).map((e, c) => c % 2 ? e : e.replaceAll("{%", "<!--lq{%").replaceAll("%}", "%}lq-->")).join(""), r = (l) => l.replaceAll("<!--lq{%", "{%").replaceAll("%}lq-->", "%}").replaceAll("&lt;!--lq{%", "{%").replaceAll("%}lq--&gt;", "%}");
2
+ export {
3
+ p as protectLiquid,
4
+ r as restoreLiquid
5
+ };