@useinsider/guido 3.6.0-beta.59a5e5f → 3.6.0-beta.5cbaa8f

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.
@@ -1,40 +1,35 @@
1
- import { useRecommendationBlockWarning as l } from "../extensions/Blocks/Recommendation/useRecommendationBlockWarning.js";
2
- import { useStripoApi as m } from "../services/stripoApi.js";
3
- import { useEditorStore as b } from "../stores/editor.js";
4
- import { useOnboardingStore as p } from "../stores/onboarding.js";
5
- import { useUnsubscribeStore as f } from "../stores/unsubscribe.js";
6
- const B = () => {
7
- const { updateSyncModule: r, getSyncModule: c } = m(), { warnIfMultipleBlocks: s } = l(), d = b(), a = f(), i = () => {
8
- var o, t, n;
9
- const e = (n = (t = (o = document.querySelector("ui-editor")) == null ? void 0 : o.shadowRoot) == null ? void 0 : t.querySelector("iframe")) == null ? void 0 : n.contentDocument;
10
- e && s(e);
11
- }, u = {
1
+ import { useStripoApi as i } from "../services/stripoApi.js";
2
+ import { useEditorStore as u } from "../stores/editor.js";
3
+ import { useOnboardingStore as l } from "../stores/onboarding.js";
4
+ import { useUnsubscribeStore as b } from "../stores/unsubscribe.js";
5
+ const v = () => {
6
+ const { updateSyncModule: d, getSyncModule: r } = i(), n = u(), s = b(), a = {
12
7
  block_dropped: ({ blockName: e }) => {
13
8
  if (e === "BLOCK_TEXT") {
14
- const o = p(), t = !o.shouldShowOnboarding("textBlockOnboarding"), n = o.isActive("textBlockOnboarding");
15
- if (t || n)
9
+ const o = l(), t = !o.shouldShowOnboarding("textBlockOnboarding"), c = o.isActive("textBlockOnboarding");
10
+ if (t || c)
16
11
  return;
17
12
  o.start("textBlockOnboarding");
18
13
  }
19
14
  },
20
15
  module_saved: async (e) => {
21
- d.syncModulesEnabled && (console.debug("[module_saved] Saved module data:", e), await r(e));
16
+ n.syncModulesEnabled && (console.debug("[module_saved] Saved module data:", e), await d(e));
22
17
  },
23
18
  module_dropped: async (e) => {
24
- if (i(), !d.syncModulesEnabled)
19
+ if (!n.syncModulesEnabled)
25
20
  return;
26
- const { moduleId: o } = e, t = await c(o);
27
- console.debug("[module_dropped] Sync module data:", t), t.unsubscriptionPreferencePages.length && await a.fetchTemplates();
21
+ const { moduleId: o } = e, t = await r(o);
22
+ console.debug("[module_dropped] Sync module data:", t), t.unsubscriptionPreferencePages.length && await s.fetchTemplates();
28
23
  },
29
24
  module_updated: async (e) => {
30
- d.syncModulesEnabled && (console.debug("[module_updated] Updated module data:", e), await r(e));
25
+ n.syncModulesEnabled && (console.debug("[module_updated] Updated module data:", e), await d(e));
31
26
  }
32
27
  };
33
28
  return { handleEvent: async (e, o) => {
34
- const t = u[e];
29
+ const t = a[e];
35
30
  console.debug("Stripo Event: ", e, o), t && await t(o);
36
31
  } };
37
32
  };
38
33
  export {
39
- B as useStripoEventHandler
34
+ v as useStripoEventHandler
40
35
  };
@@ -1,4 +1,4 @@
1
- var c = /* @__PURE__ */ ((r) => (r.Success = "success", r.Warning = "warning", r.Alert = "alert", r.Dark = "dark", r))(c || {});
1
+ var c = /* @__PURE__ */ ((r) => (r.Success = "success", r.Warning = "warning", r.Alert = "alert", r))(c || {});
2
2
  export {
3
3
  c as ToasterTypeOptions
4
4
  };
@@ -1,18 +1,17 @@
1
1
  var k = Object.defineProperty;
2
- var B = (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) => B(a, typeof r != "symbol" ? r + "" : r, t);
4
- import { BlockId as R } from "../../../enums/block.js";
5
- import { getMigrationBannerHtml as y } from "../../../utils/migrationBannerHtml.js";
6
- import { Block as D, BlockCompositionType as C, ModificationDescription as h } from "../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
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
7
  import { regenerateMobileProductRows as b } from "./controls/main/utils.js";
8
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
11
  import { getDefaultTemplate as E } from "./templates/grid/template.js";
12
- import { useRecommendationBlockWarning as M } from "./useRecommendationBlockWarning.js";
13
- const I = R.Recommendation, m = "recommendation-block-v2", u = "recommendation-id";
14
- let _ = !1;
15
- class H extends D {
12
+ const _ = B.Recommendation, m = "recommendation-block-v2", u = "recommendation-id";
13
+ let I = !1;
14
+ class q extends R {
16
15
  constructor() {
17
16
  super();
18
17
  /**
@@ -22,7 +21,7 @@ class H extends D {
22
21
  d(this, "_pendingBlockId", null);
23
22
  }
24
23
  getId() {
25
- return I;
24
+ return _;
26
25
  }
27
26
  getIcon() {
28
27
  return "recommendation-icon";
@@ -39,8 +38,8 @@ class H extends D {
39
38
  );
40
39
  }
41
40
  getSettingsPanelTitleHtml() {
42
- return y(
43
- I,
41
+ return D(
42
+ _,
44
43
  this.api.translate("Recommendation Block"),
45
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.")
46
45
  );
@@ -96,7 +95,7 @@ class H extends D {
96
95
  documentModifier: this.api.getDocumentModifier()
97
96
  }));
98
97
  }
99
- s.patchCurrentBlockConfig({ language: n.language }, { triggerRefetch: !1 }), this._warnIfMultipleBlocks();
98
+ s.patchCurrentBlockConfig({ language: n.language }, { triggerRefetch: !1 });
100
99
  }
101
100
  /**
102
101
  * Called when the document changes or template is loaded
@@ -118,7 +117,7 @@ class H extends D {
118
117
  }
119
118
  this._healLingeringDuplicate(t), c.needsMigration(t) && this._migrateFromLegacy(t);
120
119
  try {
121
- _ || (p(this.api), _ = !0);
120
+ I || (p(this.api), I = !0);
122
121
  const e = c.getConfig(t), i = this._getBlockElement(t);
123
122
  if (i) {
124
123
  const n = !e.mobileLayoutEnabled;
@@ -138,24 +137,6 @@ class H extends D {
138
137
  const e = this._getRecommendationId(t);
139
138
  e && g().removeBlockState(e);
140
139
  }
141
- /**
142
- * Warns (dark advisory toaster) when the design holds more than one live
143
- * recommendation block. Triggered from the user-add paths only (fresh drop
144
- * and duplicate) — never from document-load/migration, so opening a template
145
- * that already has multiple blocks does not fire it.
146
- *
147
- * Counting lives in `useRecommendationBlockWarning` so the same DOM-based
148
- * logic (which ignores the empty shells deletions leave behind, SD-143028)
149
- * is shared with the `module_dropped` handler — recommendation blocks can
150
- * also arrive inside a saved structure. Wrapped in try/catch because the
151
- * document root may be unavailable during initial load.
152
- */
153
- _warnIfMultipleBlocks() {
154
- try {
155
- M().warnIfMultipleBlocks(this.api.getDocumentRootHtmlNode());
156
- } catch {
157
- }
158
- }
159
140
  /**
160
141
  * Generates the next unique recommendation ID by scanning all existing blocks
161
142
  * in the document and finding the maximum existing ID + 1.
@@ -228,7 +209,7 @@ class H extends D {
228
209
  /** Assigns a fresh id to a duplicated block and syncs DOM, node config and store. */
229
210
  _handleDuplicate(t, e) {
230
211
  const i = this._reassignDuplicateId(t, e), n = g();
231
- n.cloneBlockState(e, i), n.setCurrentBlock(i), this._warnIfMultipleBlocks();
212
+ n.cloneBlockState(e, i), n.setCurrentBlock(i);
232
213
  }
233
214
  /**
234
215
  * Core id-reassignment for a duplicated block: rewrites the DOM
@@ -310,6 +291,6 @@ class H extends D {
310
291
  }
311
292
  }
312
293
  export {
313
- I as BLOCK_ID,
314
- H as RecommendationBlock
294
+ _ as BLOCK_ID,
295
+ q as RecommendationBlock
315
296
  };
@@ -1,39 +1,41 @@
1
- var g = Object.defineProperty;
2
- var k = (c, n, e) => n in c ? g(c, n, { enumerable: !0, configurable: !0, writable: !0, value: e }) : c[n] = e;
3
- var l = (c, n, e) => k(c, typeof n != "symbol" ? n + "" : n, e);
4
- import { useToaster as B } from "../../../composables/useToaster.js";
5
- import { ToasterTypeOptions as S } from "../../../enums/toaster.js";
6
- import { PAGE_TYPES as E } from "../../../enums/unsubscribe.js";
7
- import { useUnsubscribeStore as a } from "../../../stores/unsubscribe.js";
8
- import { Block as L, BlockCompositionType as f, ContextActionType as h, ModificationDescription as b, BlockType as v } from "../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
9
- import { getDefaultTemplate as A } from "./template.js";
10
- import { DATA_ATTRIBUTES as i, UNSUBSCRIBE_EVENTS as d } from "./utils/constants.js";
11
- import { parsePageList as p } from "./utils/utils.js";
12
- const y = "unsubscribe-block", m = 'a[data-unsubscribe-link="true"]', _ = ".unsubscribe-block-v2", I = "{{ins-unsubscribe-link}}", T = "https://academy.insiderone.com/docs/adding-unsubscribe-pages-into-emails", C = "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.", N = {
13
- [E.GLOBAL_UNSUBSCRIBE]: "{{ins-global-unsubscribe-link}}",
14
- [E.SUBSCRIPTION_PREFERENCE_CENTER]: "{{ins-preferences-unsubscribe-link}}"
15
- };
16
- class F extends L {
1
+ var S = Object.defineProperty;
2
+ var L = (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) => L(c, typeof o != "symbol" ? o + "" : o, e);
4
+ import { useToaster as f } from "../../../composables/useToaster.js";
5
+ import { ToasterTypeOptions as A } from "../../../enums/toaster.js";
6
+ import { PAGE_TYPES as h } from "../../../enums/unsubscribe.js";
7
+ import { useUnsubscribeStore as u } from "../../../stores/unsubscribe.js";
8
+ import { Block as y, BlockCompositionType as T, ContextActionType as k, ModificationDescription as d, BlockType as I } from "../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
9
+ import { getDefaultTemplate as v } from "./template.js";
10
+ import { DATA_ATTRIBUTES as n, UNSUBSCRIBE_EVENTS as p } from "./utils/constants.js";
11
+ import { parsePageList as B } from "./utils/utils.js";
12
+ const C = "unsubscribe-block", m = 'a[data-unsubscribe-link="true"]', E = ".unsubscribe-block-v2", N = "{{ins-unsubscribe-link}}", U = "https://academy.insiderone.com/docs/adding-unsubscribe-pages-into-emails", R = "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.", D = {
13
+ [h.GLOBAL_UNSUBSCRIBE]: "{{ins-global-unsubscribe-link}}",
14
+ [h.SUBSCRIPTION_PREFERENCE_CENTER]: "{{ins-preferences-unsubscribe-link}}"
15
+ }, w = 3;
16
+ class H extends y {
17
17
  constructor() {
18
18
  super();
19
- l(this, "selectEventListener", null);
20
- l(this, "cancelEventListener", null);
21
- l(this, "currentNode");
19
+ a(this, "selectEventListener", null);
20
+ a(this, "cancelEventListener", null);
21
+ a(this, "currentNode");
22
22
  // Per-block "did this instance have a link last time we saw it?" tracking.
23
23
  // Keyed by the stable `data-unsubscribe-block-id` we assign, because
24
24
  // ImmutableHtmlNode instances are recreated on every document mutation and
25
25
  // can't be used as Map keys directly. A singleton boolean here would be
26
26
  // polluted across blocks since Stripo fires onDocumentChanged per-instance.
27
- l(this, "linkStateByBlockId", /* @__PURE__ */ new Map());
27
+ a(this, "linkStateByBlockId", /* @__PURE__ */ new Map());
28
+ // Per-block prepend retry counter for the leading text guard (SD-143097).
29
+ a(this, "guardAttemptsByBlockId", /* @__PURE__ */ new Map());
28
30
  }
29
31
  getId() {
30
- return y;
32
+ return C;
31
33
  }
32
34
  getIcon() {
33
35
  return "unsubscribe-icon";
34
36
  }
35
37
  getBlockCompositionType() {
36
- return f.BLOCK;
38
+ return T.BLOCK;
37
39
  }
38
40
  getName() {
39
41
  return this.api.translate("Unsubscribe Block");
@@ -42,36 +44,84 @@ class F extends L {
42
44
  return this.api.translate("Unsubscribe Block Description");
43
45
  }
44
46
  getTemplate() {
45
- return A();
47
+ return v();
46
48
  }
47
49
  getContextActionsIds() {
48
- return [h.MOVE, h.REMOVE];
50
+ return [k.MOVE, k.REMOVE];
49
51
  }
50
52
  onSelect(e) {
51
53
  this.currentNode = e;
52
54
  const t = this._getOrAssignBlockId(e);
53
55
  t && this.linkStateByBlockId.set(t, this._hasUnsubscribeLink(e)), !("getAttribute" in e && e.getAttribute("data-migration")) && (this._resetStoreState(), this._loadBlockState(e), this._setupSelectEventListener(), this._setupCancelEventListener(), this._checkExistingBlocks(), this._openDrawer());
54
56
  }
57
+ onCreated(e) {
58
+ this._ensureLeadingTextGuard(e);
59
+ }
60
+ onDocumentInit() {
61
+ try {
62
+ const e = this.api.getDocumentRoot();
63
+ e && "querySelectorAll" in e && e.querySelectorAll(E).forEach((t) => {
64
+ this._ensureLeadingTextGuard(t);
65
+ });
66
+ } catch {
67
+ }
68
+ }
55
69
  onDocumentChanged(e) {
70
+ this._ensureLeadingTextGuard(e);
56
71
  const t = this._getOrAssignBlockId(e);
57
72
  if (!t)
58
73
  return;
59
- const r = this._hasUnsubscribeLink(e);
60
- this.linkStateByBlockId.get(t) === !0 && !r && this._warnLinkRemoved(), this.linkStateByBlockId.set(t, r);
74
+ const s = this._hasUnsubscribeLink(e);
75
+ this.linkStateByBlockId.get(t) === !0 && !s && this._warnLinkRemoved(), this.linkStateByBlockId.set(t, s);
61
76
  }
62
77
  onDelete(e) {
63
78
  this._removeEventListeners(), this._removeBlockTemplatesFromStore(e), this._resetStoreState();
64
79
  const t = this._readBlockId(e);
65
- t && this.linkStateByBlockId.delete(t);
80
+ t && (this.linkStateByBlockId.delete(t), this.guardAttemptsByBlockId.delete(t));
66
81
  }
67
82
  onDestroy() {
68
- this._removeEventListeners(), this.currentNode = void 0, this.linkStateByBlockId.clear();
83
+ this._removeEventListeners(), this.currentNode = void 0, this.linkStateByBlockId.clear(), this.guardAttemptsByBlockId.clear();
84
+ }
85
+ /**
86
+ * Guarantees a plain-text node before the link's wrapper inside the <p>.
87
+ * Without it the only caret position left of the link is inside the anchor,
88
+ * so anything typed there becomes part of the link (SD-143097).
89
+ */
90
+ _ensureLeadingTextGuard(e) {
91
+ if (!("querySelector" in e))
92
+ return;
93
+ const t = e.querySelector(m);
94
+ if (!t)
95
+ return;
96
+ let s = t, r = s.parent();
97
+ for (; r && "getTagName" in r && r.getTagName().toLowerCase() !== "p"; )
98
+ s = r, r = r.parent();
99
+ if (!r || !("getTagName" in r) || r.getTagName().toLowerCase() !== "p")
100
+ return;
101
+ const i = s.previousSibling();
102
+ if (!!(i && i.getType() === "text" && i.getTextContent())) {
103
+ const b = this._readBlockId(e);
104
+ b && this.guardAttemptsByBlockId.delete(b);
105
+ return;
106
+ }
107
+ const g = this._getOrAssignBlockId(e);
108
+ if (!g)
109
+ return;
110
+ const _ = this.guardAttemptsByBlockId.get(g) ?? 0;
111
+ if (!(_ >= w)) {
112
+ this.guardAttemptsByBlockId.set(g, _ + 1);
113
+ try {
114
+ this.api.getDocumentModifier().modifyHtml(r).prepend("&nbsp;").apply(new d("Ensure unsubscribe link text guard"));
115
+ } catch (b) {
116
+ console.warn("[UnsubscribeBlock] Failed to ensure link text guard:", b);
117
+ }
118
+ }
69
119
  }
70
120
  _hasUnsubscribeLink(e) {
71
121
  return "querySelector" in e ? !!e.querySelector(m) : !1;
72
122
  }
73
123
  _readBlockId(e) {
74
- return "getAttribute" in e ? e.getAttribute(i.BLOCK_ID) : null;
124
+ return "getAttribute" in e ? e.getAttribute(n.BLOCK_ID) : null;
75
125
  }
76
126
  /**
77
127
  * Returns the block's stable id, assigning one via the document modifier if
@@ -85,13 +135,13 @@ class F extends L {
85
135
  return t;
86
136
  if (!("getAttribute" in e))
87
137
  return null;
88
- const r = this._generateNextBlockId();
138
+ const s = this._generateNextBlockId();
89
139
  try {
90
- this.api.getDocumentModifier().modifyHtml(e).setAttribute(i.BLOCK_ID, r).apply(new b(`Assign unsubscribe block id ${r}`));
91
- } catch (s) {
92
- return console.warn("[UnsubscribeBlock] Failed to assign block id:", s), null;
140
+ this.api.getDocumentModifier().modifyHtml(e).setAttribute(n.BLOCK_ID, s).apply(new d(`Assign unsubscribe block id ${s}`));
141
+ } catch (r) {
142
+ return console.warn("[UnsubscribeBlock] Failed to assign block id:", r), null;
93
143
  }
94
- return r;
144
+ return s;
95
145
  }
96
146
  /**
97
147
  * Generates a unique id by scanning the document for the highest existing
@@ -102,10 +152,10 @@ class F extends L {
102
152
  let e = 0;
103
153
  try {
104
154
  const t = this.api.getDocumentRoot();
105
- t && "querySelectorAll" in t && t.querySelectorAll(_).forEach((s) => {
106
- if ("getAttribute" in s) {
107
- const o = s.getAttribute(i.BLOCK_ID), u = o ? parseInt(o) : 0;
108
- u > e && (e = u);
155
+ t && "querySelectorAll" in t && t.querySelectorAll(E).forEach((r) => {
156
+ if ("getAttribute" in r) {
157
+ const i = r.getAttribute(n.BLOCK_ID), l = i ? parseInt(i) : 0;
158
+ l > e && (e = l);
109
159
  }
110
160
  });
111
161
  } catch {
@@ -114,14 +164,14 @@ class F extends L {
114
164
  }
115
165
  _warnLinkRemoved() {
116
166
  try {
117
- const { showToaster: e } = B();
167
+ const { showToaster: e } = f();
118
168
  e({
119
- type: S.Warning,
120
- message: this.api.translate(C),
169
+ type: A.Warning,
170
+ message: this.api.translate(R),
121
171
  actionButton: {
122
172
  text: this.api.translate("Visit Academy"),
123
173
  onClick: () => {
124
- window.open(T, "_blank", "noopener,noreferrer");
174
+ window.open(U, "_blank", "noopener,noreferrer");
125
175
  }
126
176
  }
127
177
  });
@@ -131,26 +181,26 @@ class F extends L {
131
181
  }
132
182
  _setupSelectEventListener() {
133
183
  this._removeSelectEventListener(), this.selectEventListener = (e) => {
134
- const t = e, { collectionType: r, selectedPages: s } = t.detail;
135
- this._updateBlock(r, s.join(","));
136
- }, document.addEventListener(d.SELECT, this.selectEventListener);
184
+ const t = e, { collectionType: s, selectedPages: r } = t.detail;
185
+ this._updateBlock(s, r.join(","));
186
+ }, document.addEventListener(p.SELECT, this.selectEventListener);
137
187
  }
138
188
  _removeSelectEventListener() {
139
- this.selectEventListener && (document.removeEventListener(d.SELECT, this.selectEventListener), this.selectEventListener = null);
189
+ this.selectEventListener && (document.removeEventListener(p.SELECT, this.selectEventListener), this.selectEventListener = null);
140
190
  }
141
191
  _setupCancelEventListener() {
142
192
  this._removeCancelEventListener(), this.cancelEventListener = () => {
143
193
  this._handleCancel();
144
- }, document.addEventListener(d.CANCEL, this.cancelEventListener);
194
+ }, document.addEventListener(p.CANCEL, this.cancelEventListener);
145
195
  }
146
196
  _removeCancelEventListener() {
147
- this.cancelEventListener && (document.removeEventListener(d.CANCEL, this.cancelEventListener), this.cancelEventListener = null);
197
+ this.cancelEventListener && (document.removeEventListener(p.CANCEL, this.cancelEventListener), this.cancelEventListener = null);
148
198
  }
149
199
  _handleCancel() {
150
200
  try {
151
201
  if (!this.currentNode)
152
202
  return;
153
- this.api.getDocumentModifier().modifyHtml(this.currentNode).replaceWith(`<${v.EMPTY_CONTAINER}/>`).apply(new b("Removed unsubscribe block due to cancel"));
203
+ this.api.getDocumentModifier().modifyHtml(this.currentNode).replaceWith(`<${I.EMPTY_CONTAINER}/>`).apply(new d("Removed unsubscribe block due to cancel"));
154
204
  } catch (e) {
155
205
  console.warn("[UnsubscribeBlock] Failed to remove unsubscribe block:", e);
156
206
  }
@@ -161,32 +211,32 @@ class F extends L {
161
211
  _updateBlock(e, t) {
162
212
  if (!this.currentNode || !("querySelector" in this.currentNode))
163
213
  return;
164
- const r = this.currentNode.querySelector(m);
165
- if (!r)
214
+ const s = this.currentNode.querySelector(m);
215
+ if (!s)
166
216
  return;
167
- const s = this._getMergeTag(e);
168
- this.api.getDocumentModifier().modifyHtml(r).setAttribute("href", s).apply(new b(`Updated unsubscribe link to ${s}`)), this.api.getDocumentModifier().modifyHtml(this.currentNode).setAttribute(i.PAGE_TYPE, e.toString()).setAttribute(i.PAGE_LIST, t).apply(new b("Updated unsubscribe block metadata"));
217
+ const r = this._getMergeTag(e);
218
+ 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"));
169
219
  }
170
220
  _getMergeTag(e) {
171
- return N[e] ?? I;
221
+ return D[e] ?? N;
172
222
  }
173
223
  _openDrawer() {
174
224
  if (!(this.currentNode && this.currentNode.getAttribute("data-unsubscribe-page-type")))
175
225
  try {
176
- const e = a();
226
+ const e = u();
177
227
  e.typeSelectionDrawerStatus = !0;
178
228
  } catch (e) {
179
229
  console.error("[UnsubscribeBlock] Failed to open drawer:", e);
180
230
  }
181
231
  }
182
232
  _checkExistingBlocks() {
183
- const e = a();
184
- e.isGlobalUnsubscribeDisabled = !1, e.isSubscriptionPreferencesCenterDisabled = !1, this.api.getDocumentRoot().querySelectorAll(_).forEach((r) => {
185
- if ("getAttribute" in r) {
186
- const s = r.getAttribute(i.PAGE_TYPE);
187
- if (s) {
188
- const o = Number(s);
189
- o === E.GLOBAL_UNSUBSCRIBE ? e.isGlobalUnsubscribeDisabled = !0 : o === E.SUBSCRIPTION_PREFERENCE_CENTER && (e.isSubscriptionPreferencesCenterDisabled = !0);
233
+ const e = u();
234
+ e.isGlobalUnsubscribeDisabled = !1, e.isSubscriptionPreferencesCenterDisabled = !1, this.api.getDocumentRoot().querySelectorAll(E).forEach((s) => {
235
+ if ("getAttribute" in s) {
236
+ const r = s.getAttribute(n.PAGE_TYPE);
237
+ if (r) {
238
+ const i = Number(r);
239
+ i === h.GLOBAL_UNSUBSCRIBE ? e.isGlobalUnsubscribeDisabled = !0 : i === h.SUBSCRIPTION_PREFERENCE_CENTER && (e.isSubscriptionPreferencesCenterDisabled = !0);
190
240
  }
191
241
  }
192
242
  });
@@ -194,26 +244,26 @@ class F extends L {
194
244
  async _loadBlockState(e) {
195
245
  if (!("getAttribute" in e))
196
246
  return;
197
- const t = e.getAttribute(i.PAGE_TYPE), r = e.getAttribute(i.PAGE_LIST);
198
- if (!t || !r)
247
+ const t = e.getAttribute(n.PAGE_TYPE), s = e.getAttribute(n.PAGE_LIST);
248
+ if (!t || !s)
199
249
  return;
200
- const s = a(), o = Number(t), u = p(r);
201
- await s.fetchTemplates(), s.setCollectionWithoutAutoSelection(o), s.loadSelectedTemplates(u);
250
+ const r = u(), i = Number(t), l = B(s);
251
+ await r.fetchTemplates(), r.setCollectionWithoutAutoSelection(i), r.loadSelectedTemplates(l);
202
252
  }
203
253
  _resetStoreState() {
204
- a().$reset();
254
+ u().$reset();
205
255
  }
206
256
  _removeBlockTemplatesFromStore(e) {
207
257
  if (!("getAttribute" in e))
208
258
  return;
209
- const t = e.getAttribute(i.PAGE_LIST);
259
+ const t = e.getAttribute(n.PAGE_LIST);
210
260
  if (!t)
211
261
  return;
212
- const r = a(), s = p(t);
213
- r.removeUnsubscribePages(s);
262
+ const s = u(), r = B(t);
263
+ s.removeUnsubscribePages(r);
214
264
  }
215
265
  }
216
266
  export {
217
- y as UNSUBSCRIBE_BLOCK_ID,
218
- F as UnsubscribeBlock
267
+ C as UNSUBSCRIBE_BLOCK_ID,
268
+ H as UnsubscribeBlock
219
269
  };
@@ -1,6 +1,5 @@
1
1
  export declare enum ToasterTypeOptions {
2
2
  Success = "success",
3
3
  Warning = "warning",
4
- Alert = "alert",
5
- Dark = "dark"
4
+ Alert = "alert"
6
5
  }
@@ -55,19 +55,6 @@ export declare class RecommendationBlock extends Block {
55
55
  * @param node - The block node being deleted
56
56
  */
57
57
  onDelete(node: ImmutableHtmlNode): void;
58
- /**
59
- * Warns (dark advisory toaster) when the design holds more than one live
60
- * recommendation block. Triggered from the user-add paths only (fresh drop
61
- * and duplicate) — never from document-load/migration, so opening a template
62
- * that already has multiple blocks does not fire it.
63
- *
64
- * Counting lives in `useRecommendationBlockWarning` so the same DOM-based
65
- * logic (which ignores the empty shells deletions leave behind, SD-143028)
66
- * is shared with the `module_dropped` handler — recommendation blocks can
67
- * also arrive inside a saved structure. Wrapped in try/catch because the
68
- * document root may be unavailable during initial load.
69
- */
70
- private _warnIfMultipleBlocks;
71
58
  /**
72
59
  * Generates the next unique recommendation ID by scanning all existing blocks
73
60
  * in the document and finding the maximum existing ID + 1.
@@ -6,6 +6,7 @@ export declare class UnsubscribeBlock extends Block {
6
6
  private cancelEventListener;
7
7
  private currentNode?;
8
8
  private linkStateByBlockId;
9
+ private guardAttemptsByBlockId;
9
10
  constructor();
10
11
  getId(): string;
11
12
  getIcon(): string;
@@ -15,9 +16,17 @@ export declare class UnsubscribeBlock extends Block {
15
16
  getTemplate(): string;
16
17
  getContextActionsIds(): string[];
17
18
  onSelect(node: ImmutableHtmlElementNode): void;
19
+ onCreated(node: ImmutableHtmlNode): void;
20
+ onDocumentInit(): void;
18
21
  onDocumentChanged(node: ImmutableHtmlNode): void;
19
22
  onDelete(node: ImmutableHtmlElementNode): void;
20
23
  onDestroy(): void;
24
+ /**
25
+ * Guarantees a plain-text node before the link's wrapper inside the <p>.
26
+ * Without it the only caret position left of the link is inside the anchor,
27
+ * so anything typed there becomes part of the link (SD-143097).
28
+ */
29
+ private _ensureLeadingTextGuard;
21
30
  private _hasUnsubscribeLink;
22
31
  private _readBlockId;
23
32
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useinsider/guido",
3
- "version": "3.6.0-beta.59a5e5f",
3
+ "version": "3.6.0-beta.5cbaa8f",
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",
@@ -1,16 +0,0 @@
1
- import { useToaster as r } from "../../../composables/useToaster.js";
2
- import { useTranslations as s } from "../../../composables/useTranslations.js";
3
- import { ToasterTypeOptions as n } from "../../../enums/toaster.js";
4
- import { countLiveRecommendationBlocks as i } from "./utils/recommendationBlockCount.js";
5
- const a = "newsletter.multiple-reco-blocks-warning", f = () => {
6
- const { showToaster: o } = r(), t = s();
7
- return { warnIfMultipleBlocks: (e) => {
8
- i(e) > 1 && o({
9
- type: n.Dark,
10
- message: t(a)
11
- });
12
- } };
13
- };
14
- export {
15
- f as useRecommendationBlockWarning
16
- };
@@ -1,9 +0,0 @@
1
- const r = ".recommendation-block-v2", t = ".recommendation-product-row";
2
- function n(o) {
3
- return Array.from(o.querySelectorAll(r)).filter((e) => e.querySelector(t)).length;
4
- }
5
- export {
6
- r as RECOMMENDATION_BLOCK_SELECTOR,
7
- t as RECOMMENDATION_PRODUCT_ROW_SELECTOR,
8
- n as countLiveRecommendationBlocks
9
- };
@@ -1,11 +0,0 @@
1
- import type { QueryableRoot } from './utils/recommendationBlockCount';
2
- /**
3
- * Advisory shown when a design ends up with more than one recommendation block,
4
- * since multiple blocks can slow campaign delivery. Shared by the block
5
- * lifecycle (direct drop / duplicate, counting the editor's document root) and
6
- * the `module_dropped` event (counting the live editor iframe DOM, so
7
- * recommendation blocks that arrive inside a saved structure are also covered).
8
- */
9
- export declare const useRecommendationBlockWarning: () => {
10
- warnIfMultipleBlocks: (root: QueryableRoot) => void;
11
- };
@@ -1,28 +0,0 @@
1
- /**
2
- * Counts *live* recommendation blocks in a document.
3
- *
4
- * Deleting a recommendation block strips its inner content but leaves an empty
5
- * `.recommendation-block-v2` container shell behind — present in every document
6
- * representation the editor exposes (`getDocumentRoot`, `getDocumentRootHtmlNode`
7
- * and even `getTemplateData`). A raw class count therefore over-reports after a
8
- * delete→add cycle (SD-143028). The product row (`.recommendation-product-row`)
9
- * is present on every live block from drop time (the default template ships
10
- * product rows, both grid and list layouts) and is removed from a deleted
11
- * block's leftover shell, so filtering by it cleanly separates live blocks from
12
- * ghosts without reading any store.
13
- */
14
- export declare const RECOMMENDATION_BLOCK_SELECTOR = ".recommendation-block-v2";
15
- export declare const RECOMMENDATION_PRODUCT_ROW_SELECTOR = ".recommendation-product-row";
16
- interface QueryableNode {
17
- querySelector(selector: string): unknown;
18
- }
19
- export interface QueryableRoot {
20
- querySelectorAll(selector: string): Iterable<QueryableNode>;
21
- }
22
- /**
23
- * Counts the live recommendation blocks reachable from `root`. Works on the
24
- * editor's `ImmutableHtmlNode` document root (drop / duplicate paths) and on a
25
- * DOM `Document` parsed from `getTemplateData()` HTML (saved-module drop path).
26
- */
27
- export declare function countLiveRecommendationBlocks(root: QueryableRoot): number;
28
- export {};