@useinsider/guido 3.21.0-beta.6fc739e → 3.21.0-beta.7e166b5

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,85 +1,84 @@
1
- import { useActionsApi as q } from "./useActionsApi.js";
2
- import { useConfig as O } from "./useConfig.js";
3
- import { useEditorLock as D } from "./useEditorLock.js";
4
- import { useSaveStart as F, useSaveComplete as j } from "./useGuidoActions.js";
5
- import { useSaveValidation as z } from "./useSaveValidation.js";
6
- import { useSaveValidationRollout as G } from "./useSaveValidationRollout.js";
7
- import { useSyncModuleExtractor as I } from "./useSyncModuleExtractor.js";
8
- import { ToasterTypeOptions as J } from "../enums/toaster.js";
9
- import { useStripoApi as K } from "../services/stripoApi.js";
10
- import { useConfigStore as N } from "../stores/config.js";
11
- import { useDynamicContentStore as Q } from "../stores/dynamic-content.js";
12
- import { useSaveValidationStore as X } from "../stores/save-validation.js";
13
- import { useTemplatePreparation as Y, getHtmlReferencedUnsubscribePages as Z } from "../utils/templatePreparation.js";
14
- import { useAllowlist as _ } from "./useAllowlist.js";
15
- import { useHtmlValidator as $ } from "./useHtmlValidator.js";
16
- import { useToaster as tt } from "./useToaster.js";
17
- import { useCouponBlockValidator as et } from "./validators/useCouponBlockValidator.js";
18
- import { useHtmlDocumentCountValidator as ot } from "./validators/useHtmlDocumentCountValidator.js";
19
- import { useLiquidValidator as it } from "./validators/useLiquidValidator.js";
20
- import { useSyncModuleBlockValidator as rt } from "./validators/useSyncModuleBlockValidator.js";
1
+ import { useActionsApi as R } from "./useActionsApi.js";
2
+ import { useConfig as q } from "./useConfig.js";
3
+ import { useEditorLock as O } from "./useEditorLock.js";
4
+ import { useSaveStart as D, useSaveComplete as F } from "./useGuidoActions.js";
5
+ import { useSaveValidation as j } from "./useSaveValidation.js";
6
+ import { useSaveValidationRollout as z } from "./useSaveValidationRollout.js";
7
+ import { useSyncModuleExtractor as G } from "./useSyncModuleExtractor.js";
8
+ import { ToasterTypeOptions as I } from "../enums/toaster.js";
9
+ import { useStripoApi as J } from "../services/stripoApi.js";
10
+ import { useConfigStore as K } from "../stores/config.js";
11
+ import { useDynamicContentStore as N } from "../stores/dynamic-content.js";
12
+ import { useSaveValidationStore as Q } from "../stores/save-validation.js";
13
+ import { useTemplatePreparation as X, getHtmlReferencedUnsubscribePages as Y } from "../utils/templatePreparation.js";
14
+ import { useAllowlist as Z } from "./useAllowlist.js";
15
+ import { useHtmlValidator as _ } from "./useHtmlValidator.js";
16
+ import { useToaster as $ } from "./useToaster.js";
17
+ import { useCouponBlockValidator as tt } from "./validators/useCouponBlockValidator.js";
18
+ import { useLiquidValidator as et } from "./validators/useLiquidValidator.js";
19
+ import { useSyncModuleBlockValidator as ot } from "./validators/useSyncModuleBlockValidator.js";
21
20
  import { useUnsubscribeBlockValidator as st } from "./validators/useUnsubscribeBlockValidator.js";
22
- const Pt = () => {
23
- const a = F(), p = j(), { validateHtml: S } = $(), { validateLiquidSyntax: w } = it(), { validateCouponBlockTags: y } = et(), { validateUnsubscribeBlockUniqueness: H, validateUnsubscribeBlockHasTemplate: E } = st(), { validateSyncModuleBlocks: n } = rt(), { validateSingleHtmlDocument: l } = ot(), { callbacks: s, isFeatureEnabled: d } = O(), { extractSyncModuleData: b } = I(), { setSyncModuleUnsubscriptionPages: g } = K(), { editorSave: V, getTemplateData: x } = q(), { showToaster: C } = tt(), { getBlockedDomains: k, resolveToast: B } = _(), { prepareTemplateDetails: f } = Y(), { collectSaveValidationErrors: h } = z(), { isEnabled: T } = G(), P = X(), L = Q(), v = N(), { runExclusive: c } = D(), u = async (t, e, o) => {
21
+ const Bt = () => {
22
+ const a = D(), p = F(), { validateHtml: v } = _(), { validateLiquidSyntax: S } = et(), { validateCouponBlockTags: y } = tt(), { validateUnsubscribeBlockUniqueness: w, validateUnsubscribeBlockHasTemplate: E } = st(), { validateSyncModuleBlocks: n } = ot(), { callbacks: r, isFeatureEnabled: d } = q(), { extractSyncModuleData: b } = G(), { setSyncModuleUnsubscriptionPages: g } = J(), { editorSave: H, getTemplateData: V } = R(), { showToaster: x } = $(), { getBlockedDomains: k, resolveToast: C } = Z(), { prepareTemplateDetails: m } = X(), { collectSaveValidationErrors: B } = j(), { isEnabled: h } = z(), T = Q(), P = N(), f = K(), { runExclusive: l } = O(), c = async (t, e, o) => {
24
23
  if (!d("allowlistEnabled"))
25
24
  return !1;
26
- const r = await k(t, e);
27
- if (!r.length)
25
+ const i = await k(t, e);
26
+ if (!i.length)
28
27
  return !1;
29
28
  if (!o) {
30
- const i = await B(r);
31
- i != null && i.message && C({
32
- type: J.Warning,
33
- message: i.message,
34
- actionButton: i.actionButton
29
+ const s = await C(i);
30
+ s != null && s.message && x({
31
+ type: I.Warning,
32
+ message: s.message,
33
+ actionButton: s.actionButton
35
34
  });
36
35
  }
37
36
  return !0;
38
- }, U = async (t) => !y(t.compiledHtml) || !H(t.compiledHtml) || !E(t.compiledHtml) ? !1 : d("liquidSyntax") ? w(t.compiledHtml) : S(t.compiledHtml, t.dynamicContentList, !0), m = async (t, e) => {
39
- if (!await V())
37
+ }, L = async (t) => !y(t.compiledHtml) || !w(t.compiledHtml) || !E(t.compiledHtml) ? !1 : d("liquidSyntax") ? S(t.compiledHtml) : v(t.compiledHtml, t.dynamicContentList, !0), u = async (t, e) => {
38
+ if (!await H())
40
39
  return;
41
- const { unsubscribePayload: r, stripoModules: i } = b(t.rawHtml);
42
- if (await g(r), t.modules = i, !e.suppress) {
43
- const R = e.blankCompiled ? { compiledHtml: "", ampHtml: "" } : {};
44
- p({ ...t, ...R, silent: e.silent });
40
+ const { unsubscribePayload: i, stripoModules: s } = b(t.rawHtml);
41
+ if (await g(i), t.modules = s, !e.suppress) {
42
+ const W = e.blankCompiled ? { compiledHtml: "", ampHtml: "" } : {};
43
+ p({ ...t, ...W, silent: e.silent });
45
44
  }
46
45
  return t;
47
- }, A = async (t, e) => {
48
- var r;
46
+ }, U = async (t, e) => {
47
+ var i;
49
48
  a();
50
- const o = await f();
51
- if (l(o.rawHtml, e) && n(o.rawHtml, e)) {
52
- if (T.value && !e) {
53
- const i = await h(
49
+ const o = await m();
50
+ if (n(o.rawHtml, e)) {
51
+ if (h.value && !e) {
52
+ const s = await B(
54
53
  o.compiledHtml,
55
54
  o.dynamicContentList
56
55
  );
57
- if (i.length) {
58
- P.open(i, "save");
56
+ if (s.length) {
57
+ T.open(s, "save");
59
58
  return;
60
59
  }
61
- } else if (!await U(o))
60
+ } else if (!await L(o))
62
61
  return;
63
- if (!((r = s.value) != null && r.externalValidation && !await s.value.externalValidation(o)) && !await u(o.compiledHtml, o.css, e))
64
- return m(o, { silent: e, suppress: t });
62
+ if (!((i = r.value) != null && i.externalValidation && !await r.value.externalValidation(o)) && !await c(o.compiledHtml, o.css, e))
63
+ return u(o, { silent: e, suppress: t });
65
64
  }
66
- }, M = async () => {
65
+ }, A = async () => {
67
66
  var e;
68
- if (!v.allowSaveWithErrors)
67
+ if (!f.allowSaveWithErrors)
69
68
  return;
70
69
  a();
71
- const t = await f();
72
- if (l(t.rawHtml, !1) && n(t.rawHtml, !1) && !((e = s.value) != null && e.externalValidation && !await s.value.externalValidation(t)) && !await u(t.compiledHtml, t.css, !1))
73
- return m(t, { silent: !1, blankCompiled: !0 });
74
- }, W = async () => {
75
- if (!v.allowSaveWithErrors)
70
+ const t = await m();
71
+ if (n(t.rawHtml, !1) && !((e = r.value) != null && e.externalValidation && !await r.value.externalValidation(t)) && !await c(t.compiledHtml, t.css, !1))
72
+ return u(t, { silent: !1, blankCompiled: !0 });
73
+ }, M = async () => {
74
+ if (!f.allowSaveWithErrors)
76
75
  return;
77
76
  a();
78
- const { html: t, css: e } = await x();
79
- if (!l(t, !0) || !n(t, !0) || await u(t, e, !0))
77
+ const { html: t, css: e } = await V();
78
+ if (!n(t, !0) || await c(t, e, !0))
80
79
  return;
81
- const o = Z(t), r = {
82
- dynamicContentList: L.getSelectedDynamicContentList,
80
+ const o = Y(t), i = {
81
+ dynamicContentList: P.getSelectedDynamicContentList,
83
82
  compiledHtml: "",
84
83
  rawHtml: t,
85
84
  css: e,
@@ -93,12 +92,12 @@ const Pt = () => {
93
92
  config: o
94
93
  }
95
94
  };
96
- return m(r, { silent: !0 });
95
+ return u(i, { silent: !0 });
97
96
  };
98
- return { save: (t = !1, e = !1) => c(
99
- () => A(t, e)
100
- ), saveWithErrors: () => c(M), saveRawOnly: () => c(W) };
97
+ return { save: (t = !1, e = !1) => l(
98
+ () => U(t, e)
99
+ ), saveWithErrors: () => l(A), saveRawOnly: () => l(M) };
101
100
  };
102
101
  export {
103
- Pt as useSave
102
+ Bt as useSave
104
103
  };
@@ -1,43 +1,46 @@
1
- var B = Object.defineProperty;
2
- var b = (a, l, t) => l in a ? B(a, l, { enumerable: !0, configurable: !0, writable: !0, value: t }) : a[l] = t;
3
- var d = (a, l, t) => b(a, typeof l != "symbol" ? l + "" : l, t);
4
- import { useConfig as g } from "../../../composables/useConfig.js";
5
- import { BlockId as C } from "../../../enums/block.js";
6
- import { getMigrationBannerHtml as A } from "../../../utils/migrationBannerHtml.js";
7
- import { Block as E, BlockCompositionType as S, ModificationDescription as p } from "../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
8
- import { CURRENT_CONFIG_VERSION as N, PRE_RRS_CONFIG_VERSION as w } from "./constants/defaultConfig.js";
9
- import { RECOMMENDATION_ID_ATTR as m } from "./constants/selectors.js";
1
+ var A = Object.defineProperty;
2
+ var b = (c, l, e) => l in c ? A(c, l, { enumerable: !0, configurable: !0, writable: !0, value: e }) : c[l] = e;
3
+ var m = (c, l, e) => b(c, typeof l != "symbol" ? l + "" : l, e);
4
+ import { useConfig as d } from "../../../composables/useConfig.js";
5
+ import { BlockId as E } from "../../../enums/block.js";
6
+ import { getMigrationBannerHtml as C } from "../../../utils/migrationBannerHtml.js";
7
+ import { Block as S, BlockCompositionType as N, ModificationDescription as p } from "../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
8
+ import { CURRENT_CONFIG_VERSION as v, PRE_RRS_CONFIG_VERSION as w } from "./constants/defaultConfig.js";
9
+ import { RECOMMENDATION_ID_ATTR as h } from "./constants/selectors.js";
10
10
  import { regenerateMobileProductRows as M } from "./controls/main/utils.js";
11
11
  import { ensureMobileCssRulesExist as I, setMobileLayoutOptOut as R, hasMobileLayoutOptOut as O } from "./controls/mobileLayout/cssRules.js";
12
- import { RecommendationConfigService as s } from "./services/configService.js";
13
- import { useRecommendationExtensionStore as h } from "./store/recommendation.js";
14
- import { getDefaultTemplate as x } from "./templates/grid/template.js";
15
- import { useRecommendationBlockWarning as T } from "./useRecommendationBlockWarning.js";
16
- import { registerNodeConfigWriter as H } from "./utils/nodeConfigWriter.js";
17
- import { refreshDuplicatedStrategyBlocks as $ } from "./utils/strategyCommit.js";
18
- const k = C.Recommendation, u = "recommendation-block-v2", D = "hide-price";
19
- let y = !1;
20
- class Y extends E {
12
+ import { RecommendationConfigService as o } from "./services/configService.js";
13
+ import { useRecommendationExtensionStore as f } from "./store/recommendation.js";
14
+ import { getDefaultTemplate as T } from "./templates/grid/template.js";
15
+ import { useRecommendationBlockWarning as x } from "./useRecommendationBlockWarning.js";
16
+ import { registerNodeConfigWriter as k } from "./utils/nodeConfigWriter.js";
17
+ import { refreshArrivedStrategyBlocks as $, refreshDuplicatedStrategyBlocks as H } from "./utils/strategyCommit.js";
18
+ const y = E.Recommendation, u = "recommendation-block-v2", B = "hide-price";
19
+ let D = !1;
20
+ class Z extends S {
21
21
  constructor() {
22
22
  super();
23
23
  /**
24
24
  * Stores the ID generated in getTemplate() so onCreated() can reuse it.
25
25
  * This avoids generating a new (different) ID in onCreated().
26
26
  */
27
- d(this, "_pendingBlockId", null);
27
+ m(this, "_pendingBlockId", null);
28
28
  /** Ids already reset under their new name, so the deferred pass skips their stale snapshot. */
29
- d(this, "_resetSourceIds", /* @__PURE__ */ new Set());
29
+ m(this, "_resetSourceIds", /* @__PURE__ */ new Set());
30
30
  /** Ids handed to duplicates the document root does not show yet: a copied module renames all its blocks at once. */
31
- d(this, "_unsettledDuplicateIds", /* @__PURE__ */ new Set());
31
+ m(this, "_unsettledDuplicateIds", /* @__PURE__ */ new Set());
32
+ m(this, "_arrivedNodes", []);
33
+ /** Ids whose arrival or duplicate refresh is in flight, so a second refresh of the same block does not race it. */
34
+ m(this, "_refreshingIds", /* @__PURE__ */ new Set());
32
35
  }
33
36
  getId() {
34
- return k;
37
+ return y;
35
38
  }
36
39
  getIcon() {
37
40
  return "recommendation-icon";
38
41
  }
39
42
  getBlockCompositionType() {
40
- return S.CONTAINER;
43
+ return N.CONTAINER;
41
44
  }
42
45
  getName() {
43
46
  return this.api.translate("Recommendation Block");
@@ -48,8 +51,8 @@ class Y extends E {
48
51
  );
49
52
  }
50
53
  getSettingsPanelTitleHtml() {
51
- return A(
52
- k,
54
+ return C(
55
+ y,
53
56
  this.api.translate("Recommendation Block"),
54
57
  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.")
55
58
  );
@@ -71,8 +74,8 @@ class Y extends E {
71
74
  * attribute and overwrites dynamically added classes via setAttribute.
72
75
  */
73
76
  getTemplate() {
74
- const t = this._generateNextId();
75
- return this._pendingBlockId = t, x(t);
77
+ const e = this._generateNextId();
78
+ return this._pendingBlockId = e, T(e);
76
79
  }
77
80
  /**
78
81
  * Called when a new block is dropped into the template
@@ -81,42 +84,58 @@ class Y extends E {
81
84
  * and assigns the recommendation-id attribute + initializes configuration.
82
85
  * @param node - The newly created block node
83
86
  */
84
- onCreated(t) {
85
- const e = this._getRecommendationId(t);
86
- if (e !== null && e > 0) {
87
- if (this._detectDuplicate(e)) {
88
- this._handleDuplicate(t, e), this._scheduleArrivalReset(t);
87
+ onCreated(e) {
88
+ const t = this._getRecommendationId(e);
89
+ if (t !== null && t > 0) {
90
+ if (this._detectDuplicate(t)) {
91
+ this._handleDuplicate(e, t), this._scheduleArrivalReset(e);
89
92
  return;
90
93
  }
91
- this._pendingBlockId === null && setTimeout(() => {
92
- this._getBlockElement(t) && this._detectDuplicate(e) && this._handleDuplicate(t, e);
93
- }, 0), this._scheduleArrivalReset(t);
94
+ const g = this._pendingBlockId === null;
95
+ setTimeout(() => {
96
+ if (this._getBlockElement(e)) {
97
+ if (g && this._detectDuplicate(t)) {
98
+ this._handleDuplicate(e, t);
99
+ return;
100
+ }
101
+ this._queueArrivalRefresh(e);
102
+ }
103
+ }, 0), this._scheduleArrivalReset(e);
94
104
  return;
95
105
  }
96
106
  const i = this._pendingBlockId ?? this._generateNextId();
97
- this._pendingBlockId = null, this._assignRecommendationId(t, i);
98
- const n = g().isFeatureEnabled("reusableRecommendationStrategy"), o = n && !s.hasConfig(t), { config: r, wasFreshDrop: c } = s.initializeConfig(
107
+ this._pendingBlockId = null, this._assignRecommendationId(e, i);
108
+ const r = d().isFeatureEnabled("reusableRecommendationStrategy"), s = r && !o.hasConfig(e), { config: n, wasFreshDrop: a } = o.initializeConfig(
99
109
  this.api,
100
- t,
110
+ e,
101
111
  {
102
112
  recommendationId: i,
103
- configVersion: n ? N : w,
104
- ...o ? { partnerName: g().partnerName.value } : {},
113
+ configVersion: r ? v : w,
114
+ ...s ? { partnerName: d().partnerName.value } : {},
105
115
  // An RRS block picks a strategy, not an algorithm, and its algorithm control is
106
116
  // hidden. Keeping the `mostPopular` default would bake a most-popular feed URL
107
117
  // at save for a block the user never configured.
108
- ...o ? { strategy: "" } : {}
118
+ ...s ? { strategy: "" } : {}
109
119
  }
110
- ), f = h();
111
- if (f.setCurrentBlock(i), this._stampHidePrice(t, r.priceHideIfSameAsDiscounted), c) {
120
+ ), _ = f();
121
+ if (_.setCurrentBlock(i), this._stampHidePrice(e, n.priceHideIfSameAsDiscounted), a) {
112
122
  I(this.api);
113
- const _ = this._getBlockElement(t);
114
- _ && (R(this.api, _, !0), M({
115
- currentNode: t,
123
+ const g = this._getBlockElement(e);
124
+ g && (R(this.api, g, !0), M({
125
+ currentNode: e,
116
126
  documentModifier: this.api.getDocumentModifier()
117
127
  }));
118
128
  }
119
- f.patchCurrentBlockConfig({ language: r.language }, { triggerRefetch: !1 }), this._warnIfMultipleBlocks();
129
+ _.patchCurrentBlockConfig({ language: n.language }, { triggerRefetch: !1 }), this._warnIfMultipleBlocks();
130
+ }
131
+ /** Refreshes every block of an opened template: Stripo restores a stored document without `onCreated`. */
132
+ onDocumentInit() {
133
+ try {
134
+ const e = this.api.getDocumentRoot();
135
+ e && "querySelectorAll" in e && e.querySelectorAll(`.${u}`).forEach((t) => this._queueArrivalRefresh(t));
136
+ } catch (e) {
137
+ console.error("[recommendation] refreshing an opened strategy failed", e);
138
+ }
120
139
  }
121
140
  /**
122
141
  * Called when the document changes or template is loaded
@@ -125,24 +144,24 @@ class Y extends E {
125
144
  * and assigns recommendation-id to legacy blocks that lack one.
126
145
  * @param node - The block node that may need migration
127
146
  */
128
- onDocumentChanged(t) {
129
- if (!(!t || !("getNodeConfig" in t))) {
130
- if (!this._getRecommendationId(t)) {
131
- const e = this._generateNextId();
132
- this._assignRecommendationId(t, e), s.hasConfig(t) && s.updateConfig(
147
+ onDocumentChanged(e) {
148
+ if (!(!e || !("getNodeConfig" in e))) {
149
+ if (!this._getRecommendationId(e)) {
150
+ const t = this._generateNextId();
151
+ this._assignRecommendationId(e, t), o.hasConfig(e) && o.updateConfig(
133
152
  this.api,
134
- t,
135
- { recommendationId: e },
153
+ e,
154
+ { recommendationId: t },
136
155
  "Assign recommendation ID to legacy block"
137
156
  );
138
157
  }
139
- this._healLingeringDuplicate(t), s.needsMigration(t) && this._migrateFromLegacy(t), this._healHidePriceAttr(t);
158
+ this._healLingeringDuplicate(e), o.needsMigration(e) && this._migrateFromLegacy(e), this._healHidePriceAttr(e);
140
159
  try {
141
- y || (I(this.api), y = !0);
142
- const e = s.getConfig(t), i = this._getBlockElement(t);
160
+ D || (I(this.api), D = !0);
161
+ const t = o.getConfig(e), i = this._getBlockElement(e);
143
162
  if (i) {
144
- const n = !e.mobileLayoutEnabled;
145
- O(i) !== n && R(this.api, i, n);
163
+ const r = !t.mobileLayoutEnabled;
164
+ O(i) !== r && R(this.api, i, r);
146
165
  }
147
166
  } catch {
148
167
  }
@@ -154,9 +173,9 @@ class Y extends E {
154
173
  * Cleans up the per-block state from the Pinia store.
155
174
  * @param node - The block node being deleted
156
175
  */
157
- onDelete(t) {
158
- const e = this._getRecommendationId(t);
159
- e && h().removeBlockState(e);
176
+ onDelete(e) {
177
+ const t = this._getRecommendationId(e);
178
+ t && f().removeBlockState(t);
160
179
  }
161
180
  /**
162
181
  * Warns (dark advisory toaster) when the design holds more than one live
@@ -172,7 +191,7 @@ class Y extends E {
172
191
  */
173
192
  _warnIfMultipleBlocks() {
174
193
  try {
175
- T().warnIfMultipleBlocks(this.api.getDocumentRootHtmlNode());
194
+ x().warnIfMultipleBlocks(this.api.getDocumentRootHtmlNode());
176
195
  } catch {
177
196
  }
178
197
  }
@@ -181,21 +200,21 @@ class Y extends E {
181
200
  * in the document and finding the maximum existing ID + 1.
182
201
  */
183
202
  _generateNextId() {
184
- let t = 0;
185
- const e = /* @__PURE__ */ new Set();
203
+ let e = 0;
204
+ const t = /* @__PURE__ */ new Set();
186
205
  try {
187
206
  const i = this.api.getDocumentRoot();
188
- i && "querySelectorAll" in i && i.querySelectorAll(`.${u}`).forEach((o) => {
189
- if ("getAttribute" in o) {
190
- const r = o.getAttribute(m), c = r ? parseInt(r) : 0;
191
- e.add(c), c > t && (t = c);
207
+ i && "querySelectorAll" in i && i.querySelectorAll(`.${u}`).forEach((s) => {
208
+ if ("getAttribute" in s) {
209
+ const n = s.getAttribute(h), a = n ? parseInt(n) : 0;
210
+ t.add(a), a > e && (e = a);
192
211
  }
193
212
  });
194
213
  } catch {
195
214
  }
196
215
  return this._unsettledDuplicateIds.forEach((i) => {
197
- e.has(i) && this._unsettledDuplicateIds.delete(i);
198
- }), Math.max(t, ...this._unsettledDuplicateIds) + 1;
216
+ t.has(i) && this._unsettledDuplicateIds.delete(i);
217
+ }), Math.max(e, ...this._unsettledDuplicateIds) + 1;
199
218
  }
200
219
  /**
201
220
  * Drops the "already fetched" claims of a block that arrived holding a baked id
@@ -204,12 +223,34 @@ class Y extends E {
204
223
  * Deferred a tick: the collision handlers rename a sibling first, and either sibling can be
205
224
  * the renamed one depending on where the module was dropped (SD-149916).
206
225
  */
207
- _scheduleArrivalReset(t) {
208
- g().isFeatureEnabled("reusableRecommendationStrategy") && setTimeout(() => {
209
- const e = this._getRecommendationId(t);
210
- e !== null && e > 0 && !this._resetSourceIds.delete(e) && h().resetArrivedBlock(e);
226
+ _scheduleArrivalReset(e) {
227
+ d().isFeatureEnabled("reusableRecommendationStrategy") && setTimeout(() => {
228
+ const t = this._getRecommendationId(e);
229
+ t !== null && t > 0 && !this._resetSourceIds.delete(t) && f().resetArrivedBlock(t);
211
230
  }, 0);
212
231
  }
232
+ /**
233
+ * Re-reads the strategy of a block that arrived with saved cards, without waiting for a click.
234
+ * Blocks of one opened template or dropped module arrive in one tick and refresh together.
235
+ */
236
+ _queueArrivalRefresh(e) {
237
+ d().isFeatureEnabled("reusableRecommendationStrategy") && (this._arrivedNodes.push(e) > 1 || setTimeout(() => {
238
+ const t = this._arrivedNodes;
239
+ this._arrivedNodes = [];
240
+ try {
241
+ this._refreshArrivedBlocks(t);
242
+ } catch (i) {
243
+ console.error("[recommendation] refreshing an arrived strategy failed", i);
244
+ }
245
+ }, 0));
246
+ }
247
+ _refreshArrivedBlocks(e) {
248
+ const t = e.flatMap((i) => {
249
+ const r = this._getRecommendationId(i), s = this._getBlockElement(i);
250
+ return !r || !s || this._refreshingIds.has(r) ? [] : (this._refreshingIds.add(r), [{ id: r, config: o.getConfig(s) }]);
251
+ });
252
+ t.length && (k(this.api), $(t).catch((i) => console.error("[recommendation] refreshing an arrived strategy failed", i)).finally(() => t.forEach(({ id: i }) => this._refreshingIds.delete(i))));
253
+ }
213
254
  /**
214
255
  * Heals a duplicate that slipped past the create-time `_detectDuplicate`
215
256
  * scan (or that was persisted by an older template before SD-142352).
@@ -222,49 +263,54 @@ class Y extends E {
222
263
  * yanking the user's current selection mid-edit when the heal fires from
223
264
  * `onDocumentChanged`.
224
265
  */
225
- _healLingeringDuplicate(t) {
226
- const e = this._getRecommendationId(t);
227
- if (!(e === null || e <= 0))
266
+ _healLingeringDuplicate(e) {
267
+ const t = this._getRecommendationId(e);
268
+ if (!(t === null || t <= 0))
228
269
  try {
229
270
  const i = this.api.getDocumentRoot();
230
271
  if (!i || !("querySelectorAll" in i))
231
272
  return;
232
- const n = i.querySelectorAll(
233
- `.${u}[${m}="${e}"]`
273
+ const r = i.querySelectorAll(
274
+ `.${u}[${h}="${t}"]`
234
275
  );
235
- if (n.length <= 1)
276
+ if (r.length <= 1)
236
277
  return;
237
- const o = this._getBlockElement(t);
238
- if (!o)
278
+ const s = this._getBlockElement(e);
279
+ if (!s)
239
280
  return;
240
- let r = -1;
241
- for (let c = 0; c < n.length; c++)
242
- if (n[c] === o) {
243
- r = c;
281
+ let n = -1;
282
+ for (let a = 0; a < r.length; a++)
283
+ if (r[a] === s) {
284
+ n = a;
244
285
  break;
245
286
  }
246
- if (r <= 0)
287
+ if (n <= 0)
247
288
  return;
248
- this._reassignDuplicateId(t, e);
289
+ this._reassignDuplicateId(e, t);
249
290
  } catch {
250
291
  }
251
292
  }
252
293
  /** True when another block already holds the same id — Stripo's duplication signal. */
253
- _detectDuplicate(t) {
294
+ _detectDuplicate(e) {
254
295
  try {
255
- const e = this.api.getDocumentRoot();
256
- if (e && "querySelectorAll" in e)
257
- return e.querySelectorAll(
258
- `.${u}[${m}="${t}"]`
296
+ const t = this.api.getDocumentRoot();
297
+ if (t && "querySelectorAll" in t)
298
+ return t.querySelectorAll(
299
+ `.${u}[${h}="${e}"]`
259
300
  ).length > 1;
260
301
  } catch {
261
302
  }
262
303
  return !1;
263
304
  }
264
305
  /** Assigns a fresh id to a duplicated block and syncs DOM, node config and store. */
265
- _handleDuplicate(t, e) {
266
- const i = this._reassignDuplicateId(t, e), n = h();
267
- n.cloneBlockState(e, i), n.setCurrentBlock(i), g().isFeatureEnabled("reusableRecommendationStrategy") && (this._resetSourceIds.add(e), H(this.api), $(e, i, s.getConfig(t)).catch((o) => console.error("[recommendation] refreshing a duplicated strategy failed", o))), this._warnIfMultipleBlocks();
306
+ _handleDuplicate(e, t) {
307
+ const i = this._reassignDuplicateId(e, t), r = f();
308
+ if (r.cloneBlockState(t, i), r.setCurrentBlock(i), d().isFeatureEnabled("reusableRecommendationStrategy")) {
309
+ this._resetSourceIds.add(t), k(this.api);
310
+ const s = [t, i].filter((n) => !this._refreshingIds.has(n));
311
+ s.forEach((n) => this._refreshingIds.add(n)), H(t, i, o.getConfig(e)).catch((n) => console.error("[recommendation] refreshing a duplicated strategy failed", n)).finally(() => s.forEach((n) => this._refreshingIds.delete(n)));
312
+ }
313
+ this._warnIfMultipleBlocks();
268
314
  }
269
315
  /**
270
316
  * Core id-reassignment for a duplicated block: rewrites the DOM
@@ -277,13 +323,13 @@ class Y extends E {
277
323
  * effects so older bug-affected templates can self-repair without
278
324
  * disturbing the user's current selection).
279
325
  */
280
- _reassignDuplicateId(t, e) {
281
- const i = this._generateNextId(), n = this._getBlockElement(t);
282
- return this._unsettledDuplicateIds.add(i), this._assignRecommendationId(t, i), n && this._reassignInstanceClass(n, e, i), s.hasConfig(t) && s.updateConfig(
326
+ _reassignDuplicateId(e, t) {
327
+ const i = this._generateNextId(), r = this._getBlockElement(e);
328
+ return this._unsettledDuplicateIds.add(i), this._assignRecommendationId(e, i), r && this._reassignInstanceClass(r, t, i), o.hasConfig(e) && o.updateConfig(
283
329
  this.api,
284
- t,
330
+ e,
285
331
  { recommendationId: i },
286
- `Reassign recommendation ID on duplicate (was ${e})`
332
+ `Reassign recommendation ID on duplicate (was ${t})`
287
333
  ), i;
288
334
  }
289
335
  /**
@@ -293,12 +339,12 @@ class Y extends E {
293
339
  * the source's class would both leak stylistic edits across blocks and
294
340
  * cause the mail render to apply the source block's config to the clone.
295
341
  */
296
- _reassignInstanceClass(t, e, i) {
297
- if (!("getAttribute" in t))
342
+ _reassignInstanceClass(e, t, i) {
343
+ if (!("getAttribute" in e))
298
344
  return;
299
- const n = `ins-recommendation-v3-block-${e}`, o = `ins-recommendation-v3-block-${i}`, r = this.api.getDocumentModifier();
300
- r.modifyHtml(t).removeClass(n).setClass(o), r.apply(new p(
301
- `Reassign recommendation instance class ${n} -> ${o}`
345
+ const r = `ins-recommendation-v3-block-${t}`, s = `ins-recommendation-v3-block-${i}`, n = this.api.getDocumentModifier();
346
+ n.modifyHtml(e).removeClass(r).setClass(s), n.apply(new p(
347
+ `Reassign recommendation instance class ${r} -> ${s}`
302
348
  ));
303
349
  }
304
350
  /**
@@ -307,64 +353,64 @@ class Y extends E {
307
353
  * HTML by getTemplate(), since Stripo's Angular rendering overwrites dynamically
308
354
  * added classes via setAttribute.
309
355
  */
310
- _assignRecommendationId(t, e) {
311
- const i = this._getBlockElement(t);
356
+ _assignRecommendationId(e, t) {
357
+ const i = this._getBlockElement(e);
312
358
  if (!i)
313
359
  return;
314
- const n = this.api.getDocumentModifier();
315
- n.modifyHtml(i).setAttribute(m, e.toString()), n.apply(new p(`Assign recommendation ID ${e}`));
360
+ const r = this.api.getDocumentModifier();
361
+ r.modifyHtml(i).setAttribute(h, t.toString()), r.apply(new p(`Assign recommendation ID ${t}`));
316
362
  }
317
363
  /**
318
364
  * Stamps the `hide-price` attribute (read by email-service at send time) on
319
365
  * the block element from the config flag.
320
366
  */
321
- _stampHidePrice(t, e) {
322
- const i = this._getBlockElement(t);
323
- i && this.api.getDocumentModifier().modifyHtml(i).setAttribute(D, String(e)).apply(new p("Stamp hide-price"));
367
+ _stampHidePrice(e, t) {
368
+ const i = this._getBlockElement(e);
369
+ i && this.api.getDocumentModifier().modifyHtml(i).setAttribute(B, String(t)).apply(new p("Stamp hide-price"));
324
370
  }
325
371
  /**
326
372
  * Stamps `hide-price` from config only when the block lacks the attribute —
327
373
  * heals templates saved before this feature without dirtying current ones.
328
374
  */
329
- _healHidePriceAttr(t) {
330
- const e = this._getBlockElement(t);
331
- if (!e || !("getAttribute" in e) || e.getAttribute(D) !== null)
375
+ _healHidePriceAttr(e) {
376
+ const t = this._getBlockElement(e);
377
+ if (!t || !("getAttribute" in t) || t.getAttribute(B) !== null)
332
378
  return;
333
- const { priceHideIfSameAsDiscounted: i } = s.getConfig(t);
334
- this._stampHidePrice(t, i);
379
+ const { priceHideIfSameAsDiscounted: i } = o.getConfig(e);
380
+ this._stampHidePrice(e, i);
335
381
  }
336
382
  /**
337
383
  * Gets the recommendation-id from a block node
338
384
  */
339
- _getRecommendationId(t) {
340
- const e = this._getBlockElement(t);
341
- if (!e || !("getAttribute" in e))
385
+ _getRecommendationId(e) {
386
+ const t = this._getBlockElement(e);
387
+ if (!t || !("getAttribute" in t))
342
388
  return null;
343
- const i = e.getAttribute(m);
389
+ const i = t.getAttribute(h);
344
390
  if (!i)
345
391
  return null;
346
- const n = parseInt(i);
347
- return Number.isNaN(n) ? null : n;
392
+ const r = parseInt(i);
393
+ return Number.isNaN(r) ? null : r;
348
394
  }
349
395
  /**
350
396
  * Gets the block element (the element with BLOCK_CLASS)
351
397
  */
352
- _getBlockElement(t) {
353
- if ("getAttribute" in t) {
354
- const e = t.getAttribute("class");
355
- if (e && e.includes(u))
356
- return t;
398
+ _getBlockElement(e) {
399
+ if ("getAttribute" in e) {
400
+ const t = e.getAttribute("class");
401
+ if (t && t.includes(u))
402
+ return e;
357
403
  }
358
- return "querySelector" in t ? t.querySelector(`.${u}`) ?? null : null;
404
+ return "querySelector" in e ? e.querySelector(`.${u}`) ?? null : null;
359
405
  }
360
406
  /**
361
407
  * Migrate configuration from legacy format
362
408
  */
363
- _migrateFromLegacy(t) {
364
- s.migrateFromDataAttributes(this.api, t);
409
+ _migrateFromLegacy(e) {
410
+ o.migrateFromDataAttributes(this.api, e);
365
411
  }
366
412
  }
367
413
  export {
368
- k as BLOCK_ID,
369
- Y as RecommendationBlock
414
+ y as BLOCK_ID,
415
+ Z as RecommendationBlock
370
416
  };
@@ -1,43 +1,60 @@
1
- import { useRecommendationExtensionStore as g } from "../store/recommendation.js";
2
- import { refreshBlockOutsidePanel as y } from "./blockRefresh.js";
3
- import { toBlockUrlConfig as a } from "./blockUrlConfig.js";
4
- import { commitBlockChangeOutsidePanel as l, readBlockNodeConfigs as S } from "./nodeConfigWriter.js";
5
- function d(e, t) {
6
- const r = JSON.stringify(a(t));
7
- return S(e).find((o) => JSON.stringify(a(o)) !== r) ?? t;
1
+ import { useRecommendationExtensionStore as c } from "../store/recommendation.js";
2
+ import { refreshBlockOutsidePanel as l } from "./blockRefresh.js";
3
+ import { toBlockUrlConfig as i } from "./blockUrlConfig.js";
4
+ import { commitBlockChangeOutsidePanel as y, readBlockNodeConfigs as d } from "./nodeConfigWriter.js";
5
+ function S(o, t) {
6
+ const r = JSON.stringify(i(t));
7
+ return d(o).find((n) => JSON.stringify(i(n)) !== r) ?? t;
8
8
  }
9
- async function p(e, t, r) {
10
- const o = g(), s = [e, t];
11
- (/* @__PURE__ */ new Map([[e, d(e, r)], [t, r]])).forEach((n, i) => {
12
- var m;
13
- (!!n.strategyId || i === t) && ((m = o.blockStates[i]) == null ? void 0 : m.recommendationConfigs.strategyId) !== n.strategyId && o.seedBlockUrlConfig(i, a(n));
14
- }), o.resetArrivedBlock(t), await Promise.all(s.map(async (n) => {
15
- if (!await o.refreshStrategyBlock(n))
9
+ async function m(o, t, r = () => !0) {
10
+ const n = c();
11
+ await Promise.all(o.map(async (e) => {
12
+ if (!await n.refreshStrategyBlock(e))
16
13
  return;
17
- const { recommendationConfigs: i, recommendationProducts: f } = o.blockStates[n];
18
- l(
19
- n,
20
- { size: i.size },
21
- f,
22
- "Refresh duplicated recommendation strategy"
14
+ const { recommendationConfigs: s, recommendationProducts: a } = n.blockStates[e];
15
+ r(e, s.size) && y(
16
+ e,
17
+ { size: s.size },
18
+ a,
19
+ t
23
20
  );
24
21
  }));
25
22
  }
26
- async function k(e, t) {
27
- if (!g().blockStates[e])
23
+ async function C(o, t, r) {
24
+ const n = c(), e = [o, t];
25
+ (/* @__PURE__ */ new Map([[o, S(o, r)], [t, r]])).forEach((a, f) => {
26
+ var g;
27
+ (!!a.strategyId || f === t) && ((g = n.blockStates[f]) == null ? void 0 : g.recommendationConfigs.strategyId) !== a.strategyId && n.seedBlockUrlConfig(f, i(a));
28
+ }), n.resetArrivedBlock(t), await m(e, "Refresh duplicated recommendation strategy");
29
+ }
30
+ async function k(o) {
31
+ const t = c(), r = o.filter(({ config: e }) => !!e.strategyId);
32
+ if (!r.length)
33
+ return;
34
+ r.forEach(({ id: e, config: s }) => t.seedBlockUrlConfig(e, i(s))), t.invalidateStrategies();
35
+ const n = new Map(r.map(({ id: e, config: s }) => [e, s.size]));
36
+ await m(
37
+ r.map(({ id: e }) => e),
38
+ "Refresh recommendation strategy",
39
+ (e, s) => s !== n.get(e)
40
+ );
41
+ }
42
+ async function O(o, t) {
43
+ if (!c().blockStates[o])
28
44
  return !1;
29
- const r = await y(e, {
30
- pickUpdates: ({ strategyId: o, strategy: s, size: c, nodeConfigVersion: n }) => ({
31
- strategyId: o,
32
- strategy: s,
33
- size: c,
34
- configVersion: n
45
+ const r = await l(o, {
46
+ pickUpdates: ({ strategyId: n, strategy: e, size: s, nodeConfigVersion: a }) => ({
47
+ strategyId: n,
48
+ strategy: e,
49
+ size: s,
50
+ configVersion: a
35
51
  }),
36
52
  description: t
37
53
  });
38
- return r || console.error("[recommendation] strategy change did not reach a node", { blockId: e }), r;
54
+ return r || console.error("[recommendation] strategy change did not reach a node", { blockId: o }), r;
39
55
  }
40
56
  export {
41
- k as commitStrategyChange,
42
- p as refreshDuplicatedStrategyBlocks
57
+ O as commitStrategyChange,
58
+ k as refreshArrivedStrategyBlocks,
59
+ C as refreshDuplicatedStrategyBlocks
43
60
  };
@@ -19,6 +19,9 @@ export declare class RecommendationBlock extends Block {
19
19
  private _resetSourceIds;
20
20
  /** Ids handed to duplicates the document root does not show yet: a copied module renames all its blocks at once. */
21
21
  private _unsettledDuplicateIds;
22
+ private _arrivedNodes;
23
+ /** Ids whose arrival or duplicate refresh is in flight, so a second refresh of the same block does not race it. */
24
+ private _refreshingIds;
22
25
  constructor();
23
26
  getId(): string;
24
27
  getIcon(): string;
@@ -44,6 +47,8 @@ export declare class RecommendationBlock extends Block {
44
47
  * @param node - The newly created block node
45
48
  */
46
49
  onCreated(node: ImmutableHtmlNode): void;
50
+ /** Refreshes every block of an opened template: Stripo restores a stored document without `onCreated`. */
51
+ onDocumentInit(): void;
47
52
  /**
48
53
  * Called when the document changes or template is loaded
49
54
  *
@@ -85,6 +90,12 @@ export declare class RecommendationBlock extends Block {
85
90
  * the renamed one depending on where the module was dropped (SD-149916).
86
91
  */
87
92
  private _scheduleArrivalReset;
93
+ /**
94
+ * Re-reads the strategy of a block that arrived with saved cards, without waiting for a click.
95
+ * Blocks of one opened template or dropped module arrive in one tick and refresh together.
96
+ */
97
+ private _queueArrivalRefresh;
98
+ private _refreshArrivedBlocks;
88
99
  /**
89
100
  * Heals a duplicate that slipped past the create-time `_detectDuplicate`
90
101
  * scan (or that was persisted by an older template before SD-142352).
@@ -5,6 +5,15 @@ import type { RecommendationNodeConfig } from '../types/nodeConfig';
5
5
  * @param config - the arriving block's persisted config; seeds either block its store entry does not match
6
6
  */
7
7
  export declare function refreshDuplicatedStrategyBlocks(sourceId: number, duplicateId: number, config: RecommendationNodeConfig): Promise<void>;
8
+ /**
9
+ * Re-reads the strategy of blocks that arrived from a saved template or module, so the panel and
10
+ * preview show a strategy edited since the design was saved without selecting the block.
11
+ * @param blocks - each block's id with its persisted config, the truth its store entry is seeded from
12
+ */
13
+ export declare function refreshArrivedStrategyBlocks(blocks: {
14
+ id: number;
15
+ config: RecommendationNodeConfig;
16
+ }[]): Promise<void>;
8
17
  /**
9
18
  * Writes a strategy change and its products straight to the document, without the settings panel.
10
19
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useinsider/guido",
3
- "version": "3.21.0-beta.6fc739e",
3
+ "version": "3.21.0-beta.7e166b5",
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,22 +0,0 @@
1
- import { ToasterTypeOptions as i } from "../../enums/toaster.js";
2
- import { useToaster as c } from "../useToaster.js";
3
- import { useTranslations as m } from "../useTranslations.js";
4
- const u = "newsletter.multiple-html-designs-detected", r = `(?:[^>"']|"[^"]*"|'[^']*')*`, a = /<html(?=[\s>/])/gi, T = new RegExp(
5
- `<!--[\\s\\S]*?-->|<(style|script)\\b${r}>[\\s\\S]*?</\\1\\s*>|<([a-zA-Z][\\w:-]*)${r}>`,
6
- "gi"
7
- ), p = (e) => Array.from(e.matchAll(T)).filter(([, , t]) => (t == null ? void 0 : t.toLowerCase()) === "html").length, g = () => {
8
- const { showToaster: e } = c(), t = m();
9
- return { validateSingleHtmlDocument: (o, l = !1) => {
10
- var n;
11
- if ((((n = o.match(a)) == null ? void 0 : n.length) ?? 0) < 2)
12
- return !0;
13
- const s = p(o);
14
- return s <= 1 ? !0 : (console.debug("guido:save:blocked", { reason: "multiple-html-documents", htmlRootCount: s }), l || e({
15
- type: i.Warning,
16
- message: t(u)
17
- }), !1);
18
- } };
19
- };
20
- export {
21
- g as useHtmlDocumentCountValidator
22
- };
@@ -1,3 +0,0 @@
1
- export declare const useHtmlDocumentCountValidator: () => {
2
- validateSingleHtmlDocument: (rawHtml: string, silent?: boolean) => boolean;
3
- };