@useinsider/guido 3.21.0-beta.78e343a → 3.21.0-beta.8a9421f
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.
- package/README.md +34 -4
- package/dist/components/Guido.vue.js +3 -3
- package/dist/components/Guido.vue2.js +118 -108
- package/dist/composables/useEditorOpenMetrics.js +164 -0
- package/dist/composables/useStripo.js +94 -82
- package/dist/config/compiler/utils/recommendationCompilerUtils.js +53 -55
- package/dist/extensions/Blocks/Recommendation/block.js +211 -148
- package/dist/extensions/Blocks/Recommendation/controls/image/index.js +16 -60
- package/dist/extensions/Blocks/Recommendation/utils/captureStyleTemplates.js +15 -16
- package/dist/extensions/Blocks/Recommendation/utils/strategyCommit.js +52 -31
- package/dist/guido.css +1 -1
- package/dist/library.js +12 -10
- package/dist/package.json.js +6 -4
- package/dist/public/prefetchRuntime.js +4 -0
- package/dist/src/composables/useEditorOpenMetrics.d.ts +27 -0
- package/dist/src/extensions/Blocks/Recommendation/block.d.ts +23 -8
- package/dist/src/extensions/Blocks/Recommendation/controls/image/index.d.ts +17 -30
- package/dist/src/extensions/Blocks/Recommendation/utils/strategyCommit.d.ts +11 -0
- package/dist/src/library.d.ts +1 -0
- package/dist/src/public/prefetchRuntime.d.ts +1 -0
- package/dist/src/utils/runtimePrefetch.d.ts +4 -0
- package/dist/src/utils/stripoRuntime.d.ts +2 -0
- package/dist/static/stripo-runtime-manifest.json.js +9 -0
- package/dist/utils/runtimePrefetch.js +26 -0
- package/dist/utils/stripoRuntime.js +9 -0
- package/package.json +6 -1
|
@@ -1,44 +1,46 @@
|
|
|
1
|
-
var
|
|
2
|
-
var
|
|
3
|
-
var
|
|
1
|
+
var A = Object.defineProperty;
|
|
2
|
+
var D = (m, c, e) => c in m ? A(m, c, { enumerable: !0, configurable: !0, writable: !0, value: e }) : m[c] = e;
|
|
3
|
+
var u = (m, c, e) => D(m, typeof c != "symbol" ? c + "" : c, e);
|
|
4
4
|
import { useConfig as g } from "../../../composables/useConfig.js";
|
|
5
|
-
import { BlockId as
|
|
6
|
-
import { getMigrationBannerHtml as
|
|
7
|
-
import { Block as
|
|
8
|
-
import { CURRENT_CONFIG_VERSION as N, PRE_RRS_CONFIG_VERSION as
|
|
9
|
-
import { RECOMMENDATION_ID_ATTR as
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { useRecommendationExtensionStore as h } from "./store/recommendation.js";
|
|
5
|
+
import { BlockId as b } from "../../../enums/block.js";
|
|
6
|
+
import { getMigrationBannerHtml as E } from "../../../utils/migrationBannerHtml.js";
|
|
7
|
+
import { Block as S, BlockCompositionType as v, ModificationDescription as _ } 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 f } from "./constants/selectors.js";
|
|
10
|
+
import { regenerateMobileProductRows as M } from "./controls/main/utils.js";
|
|
11
|
+
import { ensureMobileCssRulesExist as I, setMobileLayoutOptOut as R, hasMobileLayoutOptOut as O } from "./controls/mobileLayout/cssRules.js";
|
|
12
|
+
import { RecommendationConfigService as a } from "./services/configService.js";
|
|
13
|
+
import { useRecommendationExtensionStore as p } from "./store/recommendation.js";
|
|
15
14
|
import { getDefaultTemplate as T } from "./templates/grid/template.js";
|
|
16
|
-
import { useRecommendationBlockWarning as
|
|
17
|
-
import { registerNodeConfigWriter as
|
|
18
|
-
import { refreshDuplicatedStrategyBlocks as
|
|
19
|
-
const
|
|
20
|
-
let
|
|
21
|
-
class
|
|
15
|
+
import { useRecommendationBlockWarning as x } from "./useRecommendationBlockWarning.js";
|
|
16
|
+
import { registerNodeConfigWriter as k, readBlockNodeConfigs as $ } from "./utils/nodeConfigWriter.js";
|
|
17
|
+
import { refreshArrivedStrategyBlocks as q, resolveSourceConfig as H, refreshDuplicatedStrategyBlocks as F } from "./utils/strategyCommit.js";
|
|
18
|
+
const y = b.Recommendation, d = "recommendation-block-v2", C = "hide-price";
|
|
19
|
+
let B = !1;
|
|
20
|
+
class te extends S {
|
|
22
21
|
constructor() {
|
|
23
22
|
super();
|
|
24
23
|
/**
|
|
25
24
|
* Stores the ID generated in getTemplate() so onCreated() can reuse it.
|
|
26
25
|
* This avoids generating a new (different) ID in onCreated().
|
|
27
26
|
*/
|
|
28
|
-
|
|
27
|
+
u(this, "_pendingBlockId", null);
|
|
29
28
|
/** Ids already reset under their new name, so the deferred pass skips their stale snapshot. */
|
|
30
|
-
|
|
29
|
+
u(this, "_resetSourceIds", /* @__PURE__ */ new Set());
|
|
31
30
|
/** Ids handed to duplicates the document root does not show yet: a copied module renames all its blocks at once. */
|
|
32
|
-
|
|
31
|
+
u(this, "_unsettledDuplicateIds", /* @__PURE__ */ new Set());
|
|
32
|
+
u(this, "_arrivedBlocks", []);
|
|
33
|
+
/** Strategy each in-flight arrival or duplicate refresh is fetching, by block id, so a second one does not race it. */
|
|
34
|
+
u(this, "_refreshClaims", /* @__PURE__ */ new Map());
|
|
33
35
|
}
|
|
34
36
|
getId() {
|
|
35
|
-
return
|
|
37
|
+
return y;
|
|
36
38
|
}
|
|
37
39
|
getIcon() {
|
|
38
40
|
return "recommendation-icon";
|
|
39
41
|
}
|
|
40
42
|
getBlockCompositionType() {
|
|
41
|
-
return
|
|
43
|
+
return v.CONTAINER;
|
|
42
44
|
}
|
|
43
45
|
getName() {
|
|
44
46
|
return this.api.translate("Recommendation Block");
|
|
@@ -49,8 +51,8 @@ class tt extends E {
|
|
|
49
51
|
);
|
|
50
52
|
}
|
|
51
53
|
getSettingsPanelTitleHtml() {
|
|
52
|
-
return
|
|
53
|
-
|
|
54
|
+
return E(
|
|
55
|
+
y,
|
|
54
56
|
this.api.translate("Recommendation Block"),
|
|
55
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.")
|
|
56
58
|
);
|
|
@@ -72,8 +74,8 @@ class tt extends E {
|
|
|
72
74
|
* attribute and overwrites dynamically added classes via setAttribute.
|
|
73
75
|
*/
|
|
74
76
|
getTemplate() {
|
|
75
|
-
const
|
|
76
|
-
return this._pendingBlockId =
|
|
77
|
+
const e = this._generateNextId();
|
|
78
|
+
return this._pendingBlockId = e, T(e);
|
|
77
79
|
}
|
|
78
80
|
/**
|
|
79
81
|
* Called when a new block is dropped into the template
|
|
@@ -82,42 +84,57 @@ class tt extends E {
|
|
|
82
84
|
* and assigns the recommendation-id attribute + initializes configuration.
|
|
83
85
|
* @param node - The newly created block node
|
|
84
86
|
*/
|
|
85
|
-
onCreated(
|
|
86
|
-
const
|
|
87
|
-
if (
|
|
88
|
-
if (this.
|
|
89
|
-
this._handleDuplicate(
|
|
87
|
+
onCreated(e) {
|
|
88
|
+
const t = this._getRecommendationId(e);
|
|
89
|
+
if (t !== null && t > 0) {
|
|
90
|
+
if (this._pendingBlockId = null, this._detectDuplicate(t)) {
|
|
91
|
+
this._handleDuplicate(e, t), this._scheduleArrivalReset(e);
|
|
90
92
|
return;
|
|
91
93
|
}
|
|
92
|
-
|
|
93
|
-
this._getBlockElement(
|
|
94
|
-
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
if (this._getBlockElement(e)) {
|
|
96
|
+
if (this._detectDuplicate(t)) {
|
|
97
|
+
this._handleDuplicate(e, t);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this._queueArrivalRefresh(e);
|
|
101
|
+
}
|
|
102
|
+
}, 0), this._scheduleArrivalReset(e);
|
|
95
103
|
return;
|
|
96
104
|
}
|
|
97
105
|
const i = this._pendingBlockId ?? this._generateNextId();
|
|
98
|
-
this._pendingBlockId = null, this._assignRecommendationId(
|
|
99
|
-
const
|
|
106
|
+
this._pendingBlockId = null, this._assignRecommendationId(e, i);
|
|
107
|
+
const r = g().isFeatureEnabled("reusableRecommendationStrategy"), s = r && !a.hasConfig(e), { config: n, wasFreshDrop: o } = a.initializeConfig(
|
|
100
108
|
this.api,
|
|
101
|
-
|
|
109
|
+
e,
|
|
102
110
|
{
|
|
103
111
|
recommendationId: i,
|
|
104
|
-
configVersion:
|
|
105
|
-
...
|
|
112
|
+
configVersion: r ? N : w,
|
|
113
|
+
...s ? { partnerName: g().partnerName.value } : {},
|
|
106
114
|
// An RRS block picks a strategy, not an algorithm, and its algorithm control is
|
|
107
115
|
// hidden. Keeping the `mostPopular` default would bake a most-popular feed URL
|
|
108
116
|
// at save for a block the user never configured.
|
|
109
|
-
...
|
|
117
|
+
...s ? { strategy: "" } : {}
|
|
110
118
|
}
|
|
111
|
-
),
|
|
112
|
-
if (
|
|
119
|
+
), l = p();
|
|
120
|
+
if (l.setCurrentBlock(i), this._stampHidePrice(e, n.priceHideIfSameAsDiscounted), o) {
|
|
113
121
|
I(this.api);
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
currentNode:
|
|
122
|
+
const h = this._getBlockElement(e);
|
|
123
|
+
h && (R(this.api, h, !0), M({
|
|
124
|
+
currentNode: e,
|
|
117
125
|
documentModifier: this.api.getDocumentModifier()
|
|
118
126
|
}));
|
|
119
127
|
}
|
|
120
|
-
|
|
128
|
+
l.patchCurrentBlockConfig({ language: n.language }, { triggerRefetch: !1 }), this._warnIfMultipleBlocks();
|
|
129
|
+
}
|
|
130
|
+
/** Refreshes every block of an opened template: Stripo restores a stored document without `onCreated`. */
|
|
131
|
+
onDocumentInit() {
|
|
132
|
+
try {
|
|
133
|
+
const e = this.api.getDocumentRoot();
|
|
134
|
+
e && "querySelectorAll" in e && e.querySelectorAll(`.${d}`).forEach((t) => this._queueArrivalRefresh(t));
|
|
135
|
+
} catch (e) {
|
|
136
|
+
console.error("[recommendation] refreshing an opened strategy failed", e);
|
|
137
|
+
}
|
|
121
138
|
}
|
|
122
139
|
/**
|
|
123
140
|
* Called when the document changes or template is loaded
|
|
@@ -126,24 +143,24 @@ class tt extends E {
|
|
|
126
143
|
* and assigns recommendation-id to legacy blocks that lack one.
|
|
127
144
|
* @param node - The block node that may need migration
|
|
128
145
|
*/
|
|
129
|
-
onDocumentChanged(
|
|
130
|
-
if (!(!
|
|
131
|
-
if (!this._getRecommendationId(
|
|
132
|
-
const
|
|
133
|
-
this._assignRecommendationId(
|
|
146
|
+
onDocumentChanged(e) {
|
|
147
|
+
if (!(!e || !("getNodeConfig" in e))) {
|
|
148
|
+
if (!this._getRecommendationId(e)) {
|
|
149
|
+
const t = this._generateNextId();
|
|
150
|
+
this._assignRecommendationId(e, t), a.hasConfig(e) && a.updateConfig(
|
|
134
151
|
this.api,
|
|
135
|
-
|
|
136
|
-
{ recommendationId:
|
|
152
|
+
e,
|
|
153
|
+
{ recommendationId: t },
|
|
137
154
|
"Assign recommendation ID to legacy block"
|
|
138
155
|
);
|
|
139
156
|
}
|
|
140
|
-
this._healLingeringDuplicate(
|
|
157
|
+
this._healLingeringDuplicate(e), a.needsMigration(e) && this._migrateFromLegacy(e), this._healHidePriceAttr(e);
|
|
141
158
|
try {
|
|
142
|
-
|
|
143
|
-
const
|
|
159
|
+
B || (I(this.api), B = !0);
|
|
160
|
+
const t = a.getConfig(e), i = this._getBlockElement(e);
|
|
144
161
|
if (i) {
|
|
145
|
-
const
|
|
146
|
-
|
|
162
|
+
const r = !t.mobileLayoutEnabled;
|
|
163
|
+
O(i) !== r && R(this.api, i, r);
|
|
147
164
|
}
|
|
148
165
|
} catch {
|
|
149
166
|
}
|
|
@@ -155,15 +172,9 @@ class tt extends E {
|
|
|
155
172
|
* Cleans up the per-block state from the Pinia store.
|
|
156
173
|
* @param node - The block node being deleted
|
|
157
174
|
*/
|
|
158
|
-
onDelete(
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
}
|
|
162
|
-
_ensureMobileFitRule(t) {
|
|
163
|
-
try {
|
|
164
|
-
w(this.api, t);
|
|
165
|
-
} catch {
|
|
166
|
-
}
|
|
175
|
+
onDelete(e) {
|
|
176
|
+
const t = this._getRecommendationId(e);
|
|
177
|
+
t && p().removeBlockState(t);
|
|
167
178
|
}
|
|
168
179
|
/**
|
|
169
180
|
* Warns (dark advisory toaster) when the design holds more than one live
|
|
@@ -179,7 +190,7 @@ class tt extends E {
|
|
|
179
190
|
*/
|
|
180
191
|
_warnIfMultipleBlocks() {
|
|
181
192
|
try {
|
|
182
|
-
|
|
193
|
+
x().warnIfMultipleBlocks(this.api.getDocumentRootHtmlNode());
|
|
183
194
|
} catch {
|
|
184
195
|
}
|
|
185
196
|
}
|
|
@@ -188,21 +199,21 @@ class tt extends E {
|
|
|
188
199
|
* in the document and finding the maximum existing ID + 1.
|
|
189
200
|
*/
|
|
190
201
|
_generateNextId() {
|
|
191
|
-
let
|
|
192
|
-
const
|
|
202
|
+
let e = 0;
|
|
203
|
+
const t = /* @__PURE__ */ new Set();
|
|
193
204
|
try {
|
|
194
205
|
const i = this.api.getDocumentRoot();
|
|
195
|
-
i && "querySelectorAll" in i && i.querySelectorAll(`.${
|
|
196
|
-
if ("getAttribute" in
|
|
197
|
-
const
|
|
198
|
-
|
|
206
|
+
i && "querySelectorAll" in i && i.querySelectorAll(`.${d}`).forEach((s) => {
|
|
207
|
+
if ("getAttribute" in s) {
|
|
208
|
+
const n = s.getAttribute(f), o = n ? parseInt(n) : 0;
|
|
209
|
+
t.add(o), o > e && (e = o);
|
|
199
210
|
}
|
|
200
211
|
});
|
|
201
212
|
} catch {
|
|
202
213
|
}
|
|
203
214
|
return this._unsettledDuplicateIds.forEach((i) => {
|
|
204
|
-
|
|
205
|
-
}), Math.max(
|
|
215
|
+
t.has(i) && this._unsettledDuplicateIds.delete(i);
|
|
216
|
+
}), Math.max(e, ...this._unsettledDuplicateIds) + 1;
|
|
206
217
|
}
|
|
207
218
|
/**
|
|
208
219
|
* Drops the "already fetched" claims of a block that arrived holding a baked id
|
|
@@ -211,67 +222,119 @@ class tt extends E {
|
|
|
211
222
|
* Deferred a tick: the collision handlers rename a sibling first, and either sibling can be
|
|
212
223
|
* the renamed one depending on where the module was dropped (SD-149916).
|
|
213
224
|
*/
|
|
214
|
-
_scheduleArrivalReset(
|
|
225
|
+
_scheduleArrivalReset(e) {
|
|
215
226
|
g().isFeatureEnabled("reusableRecommendationStrategy") && setTimeout(() => {
|
|
216
|
-
const
|
|
217
|
-
|
|
227
|
+
const t = this._getRecommendationId(e);
|
|
228
|
+
t !== null && t > 0 && !this._resetSourceIds.delete(t) && p().resetArrivedBlock(t);
|
|
218
229
|
}, 0);
|
|
219
230
|
}
|
|
231
|
+
/**
|
|
232
|
+
* Re-reads the strategy of a block that arrived with saved cards, without waiting for a click.
|
|
233
|
+
* Blocks of one opened template or dropped module arrive in one tick and refresh together.
|
|
234
|
+
*/
|
|
235
|
+
_queueArrivalRefresh(e, t) {
|
|
236
|
+
g().isFeatureEnabled("reusableRecommendationStrategy") && (this._arrivedBlocks.push({ node: e, id: t }) > 1 || setTimeout(() => {
|
|
237
|
+
const i = this._arrivedBlocks;
|
|
238
|
+
this._arrivedBlocks = [];
|
|
239
|
+
try {
|
|
240
|
+
this._refreshArrivedBlocks(i);
|
|
241
|
+
} catch (r) {
|
|
242
|
+
console.error("[recommendation] refreshing an arrived strategy failed", r);
|
|
243
|
+
}
|
|
244
|
+
}, 0));
|
|
245
|
+
}
|
|
246
|
+
_refreshArrivedBlocks(e) {
|
|
247
|
+
k(this.api);
|
|
248
|
+
const t = /* @__PURE__ */ new Set(), i = e.flatMap(({ node: r, id: s }) => {
|
|
249
|
+
const n = s ?? this._getRecommendationId(r), o = this._getBlockElement(r);
|
|
250
|
+
if (!n || !o || t.has(n))
|
|
251
|
+
return [];
|
|
252
|
+
const l = a.getConfig(o);
|
|
253
|
+
if (!this._canClaimRefresh(n, l.strategyId))
|
|
254
|
+
return [];
|
|
255
|
+
const h = { strategyId: l.strategyId };
|
|
256
|
+
return t.add(n), this._refreshClaims.set(n, h), [{ id: n, config: l, claim: h }];
|
|
257
|
+
});
|
|
258
|
+
i.length && q(i.map(({ id: r, config: s }) => ({ id: r, config: s }))).catch((r) => console.error("[recommendation] refreshing an arrived strategy failed", r)).finally(() => i.forEach(({ id: r, claim: s }) => this._releaseRefreshClaim(r, s)));
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* A new document reports each block to onCreated and onDocumentInit, not always in one batch, so a
|
|
262
|
+
* claimed block refreshes again only once the document shows it under another strategy (a restored version).
|
|
263
|
+
*/
|
|
264
|
+
_canClaimRefresh(e, t) {
|
|
265
|
+
const i = this._refreshClaims.get(e);
|
|
266
|
+
if (!i)
|
|
267
|
+
return !0;
|
|
268
|
+
if (i.strategyId === t)
|
|
269
|
+
return !1;
|
|
270
|
+
const r = $(e);
|
|
271
|
+
return r.length > 0 && r.every((s) => s.strategyId === t);
|
|
272
|
+
}
|
|
273
|
+
_releaseRefreshClaim(e, t) {
|
|
274
|
+
this._refreshClaims.get(e) === t && this._refreshClaims.delete(e);
|
|
275
|
+
}
|
|
220
276
|
/**
|
|
221
277
|
* Heals a duplicate that slipped past the create-time `_detectDuplicate`
|
|
222
278
|
* scan (or that was persisted by an older template before SD-142352).
|
|
223
279
|
* When two blocks share a `recommendation-id`, the first occurrence in DOM
|
|
224
280
|
* order is treated as the source and any later sibling is reassigned a
|
|
225
|
-
* fresh id
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
* will seed the new id from that. Skipping `setCurrentBlock` avoids
|
|
281
|
+
* fresh id and queued for the arrival refresh, which seeds the new id's
|
|
282
|
+
* store entry from its persisted `esd-ext-config`. The source's store
|
|
283
|
+
* entry is not cloned and focus is not moved. Skipping `setCurrentBlock` avoids
|
|
229
284
|
* yanking the user's current selection mid-edit when the heal fires from
|
|
230
285
|
* `onDocumentChanged`.
|
|
231
286
|
*/
|
|
232
|
-
_healLingeringDuplicate(
|
|
233
|
-
const
|
|
234
|
-
if (!(
|
|
287
|
+
_healLingeringDuplicate(e) {
|
|
288
|
+
const t = this._getRecommendationId(e);
|
|
289
|
+
if (!(t === null || t <= 0))
|
|
235
290
|
try {
|
|
236
291
|
const i = this.api.getDocumentRoot();
|
|
237
292
|
if (!i || !("querySelectorAll" in i))
|
|
238
293
|
return;
|
|
239
|
-
const
|
|
240
|
-
`.${
|
|
294
|
+
const r = i.querySelectorAll(
|
|
295
|
+
`.${d}[${f}="${t}"]`
|
|
241
296
|
);
|
|
242
|
-
if (
|
|
297
|
+
if (r.length <= 1)
|
|
243
298
|
return;
|
|
244
|
-
const
|
|
245
|
-
if (!
|
|
299
|
+
const s = this._getBlockElement(e);
|
|
300
|
+
if (!s)
|
|
246
301
|
return;
|
|
247
|
-
let
|
|
248
|
-
for (let
|
|
249
|
-
if (
|
|
250
|
-
|
|
302
|
+
let n = -1;
|
|
303
|
+
for (let o = 0; o < r.length; o++)
|
|
304
|
+
if (r[o] === s) {
|
|
305
|
+
n = o;
|
|
251
306
|
break;
|
|
252
307
|
}
|
|
253
|
-
if (
|
|
308
|
+
if (n <= 0)
|
|
254
309
|
return;
|
|
255
|
-
this._reassignDuplicateId(
|
|
310
|
+
this._queueArrivalRefresh(e, this._reassignDuplicateId(e, t));
|
|
256
311
|
} catch {
|
|
257
312
|
}
|
|
258
313
|
}
|
|
259
314
|
/** True when another block already holds the same id — Stripo's duplication signal. */
|
|
260
|
-
_detectDuplicate(
|
|
315
|
+
_detectDuplicate(e) {
|
|
261
316
|
try {
|
|
262
|
-
const
|
|
263
|
-
if (
|
|
264
|
-
return
|
|
265
|
-
`.${
|
|
317
|
+
const t = this.api.getDocumentRoot();
|
|
318
|
+
if (t && "querySelectorAll" in t)
|
|
319
|
+
return t.querySelectorAll(
|
|
320
|
+
`.${d}[${f}="${e}"]`
|
|
266
321
|
).length > 1;
|
|
267
322
|
} catch {
|
|
268
323
|
}
|
|
269
324
|
return !1;
|
|
270
325
|
}
|
|
271
326
|
/** Assigns a fresh id to a duplicated block and syncs DOM, node config and store. */
|
|
272
|
-
_handleDuplicate(
|
|
273
|
-
const i = this._reassignDuplicateId(
|
|
274
|
-
|
|
327
|
+
_handleDuplicate(e, t) {
|
|
328
|
+
const i = this._reassignDuplicateId(e, t), r = p();
|
|
329
|
+
if (r.cloneBlockState(t, i), r.setCurrentBlock(i), g().isFeatureEnabled("reusableRecommendationStrategy")) {
|
|
330
|
+
this._resetSourceIds.add(t), k(this.api);
|
|
331
|
+
const s = a.getConfig(e), n = [
|
|
332
|
+
{ id: t, claim: { strategyId: H(t, s).strategyId } },
|
|
333
|
+
{ id: i, claim: { strategyId: s.strategyId } }
|
|
334
|
+
].filter(({ id: o }) => !this._refreshClaims.has(o));
|
|
335
|
+
n.forEach(({ id: o, claim: l }) => this._refreshClaims.set(o, l)), F(t, i, s).catch((o) => console.error("[recommendation] refreshing a duplicated strategy failed", o)).finally(() => n.forEach(({ id: o, claim: l }) => this._releaseRefreshClaim(o, l)));
|
|
336
|
+
}
|
|
337
|
+
this._warnIfMultipleBlocks();
|
|
275
338
|
}
|
|
276
339
|
/**
|
|
277
340
|
* Core id-reassignment for a duplicated block: rewrites the DOM
|
|
@@ -280,17 +343,17 @@ class tt extends E {
|
|
|
280
343
|
*
|
|
281
344
|
* Used by `_handleDuplicate` at create-time (which additionally clones the
|
|
282
345
|
* store entry and focuses the new block) and by `_healLingeringDuplicate`
|
|
283
|
-
* at document-change time (which
|
|
284
|
-
*
|
|
285
|
-
*
|
|
346
|
+
* at document-change time (which skips the clone and focus change so older
|
|
347
|
+
* bug-affected templates can self-repair without disturbing the user's
|
|
348
|
+
* current selection, and queues the arrival refresh for the new id).
|
|
286
349
|
*/
|
|
287
|
-
_reassignDuplicateId(
|
|
288
|
-
const i = this._generateNextId(),
|
|
289
|
-
return this._unsettledDuplicateIds.add(i), this._assignRecommendationId(
|
|
350
|
+
_reassignDuplicateId(e, t) {
|
|
351
|
+
const i = this._generateNextId(), r = this._getBlockElement(e);
|
|
352
|
+
return this._unsettledDuplicateIds.add(i), this._assignRecommendationId(e, i), r && this._reassignInstanceClass(r, t, i), a.hasConfig(e) && a.updateConfig(
|
|
290
353
|
this.api,
|
|
291
|
-
|
|
354
|
+
e,
|
|
292
355
|
{ recommendationId: i },
|
|
293
|
-
`Reassign recommendation ID on duplicate (was ${
|
|
356
|
+
`Reassign recommendation ID on duplicate (was ${t})`
|
|
294
357
|
), i;
|
|
295
358
|
}
|
|
296
359
|
/**
|
|
@@ -300,12 +363,12 @@ class tt extends E {
|
|
|
300
363
|
* the source's class would both leak stylistic edits across blocks and
|
|
301
364
|
* cause the mail render to apply the source block's config to the clone.
|
|
302
365
|
*/
|
|
303
|
-
_reassignInstanceClass(
|
|
304
|
-
if (!("getAttribute" in
|
|
366
|
+
_reassignInstanceClass(e, t, i) {
|
|
367
|
+
if (!("getAttribute" in e))
|
|
305
368
|
return;
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
`Reassign recommendation instance class ${
|
|
369
|
+
const r = `ins-recommendation-v3-block-${t}`, s = `ins-recommendation-v3-block-${i}`, n = this.api.getDocumentModifier();
|
|
370
|
+
n.modifyHtml(e).removeClass(r).setClass(s), n.apply(new _(
|
|
371
|
+
`Reassign recommendation instance class ${r} -> ${s}`
|
|
309
372
|
));
|
|
310
373
|
}
|
|
311
374
|
/**
|
|
@@ -314,64 +377,64 @@ class tt extends E {
|
|
|
314
377
|
* HTML by getTemplate(), since Stripo's Angular rendering overwrites dynamically
|
|
315
378
|
* added classes via setAttribute.
|
|
316
379
|
*/
|
|
317
|
-
_assignRecommendationId(
|
|
318
|
-
const i = this._getBlockElement(
|
|
380
|
+
_assignRecommendationId(e, t) {
|
|
381
|
+
const i = this._getBlockElement(e);
|
|
319
382
|
if (!i)
|
|
320
383
|
return;
|
|
321
|
-
const
|
|
322
|
-
|
|
384
|
+
const r = this.api.getDocumentModifier();
|
|
385
|
+
r.modifyHtml(i).setAttribute(f, t.toString()), r.apply(new _(`Assign recommendation ID ${t}`));
|
|
323
386
|
}
|
|
324
387
|
/**
|
|
325
388
|
* Stamps the `hide-price` attribute (read by email-service at send time) on
|
|
326
389
|
* the block element from the config flag.
|
|
327
390
|
*/
|
|
328
|
-
_stampHidePrice(
|
|
329
|
-
const i = this._getBlockElement(
|
|
330
|
-
i && this.api.getDocumentModifier().modifyHtml(i).setAttribute(
|
|
391
|
+
_stampHidePrice(e, t) {
|
|
392
|
+
const i = this._getBlockElement(e);
|
|
393
|
+
i && this.api.getDocumentModifier().modifyHtml(i).setAttribute(C, String(t)).apply(new _("Stamp hide-price"));
|
|
331
394
|
}
|
|
332
395
|
/**
|
|
333
396
|
* Stamps `hide-price` from config only when the block lacks the attribute —
|
|
334
397
|
* heals templates saved before this feature without dirtying current ones.
|
|
335
398
|
*/
|
|
336
|
-
_healHidePriceAttr(
|
|
337
|
-
const
|
|
338
|
-
if (!
|
|
399
|
+
_healHidePriceAttr(e) {
|
|
400
|
+
const t = this._getBlockElement(e);
|
|
401
|
+
if (!t || !("getAttribute" in t) || t.getAttribute(C) !== null)
|
|
339
402
|
return;
|
|
340
|
-
const { priceHideIfSameAsDiscounted: i } =
|
|
341
|
-
this._stampHidePrice(
|
|
403
|
+
const { priceHideIfSameAsDiscounted: i } = a.getConfig(e);
|
|
404
|
+
this._stampHidePrice(e, i);
|
|
342
405
|
}
|
|
343
406
|
/**
|
|
344
407
|
* Gets the recommendation-id from a block node
|
|
345
408
|
*/
|
|
346
|
-
_getRecommendationId(
|
|
347
|
-
const
|
|
348
|
-
if (!
|
|
409
|
+
_getRecommendationId(e) {
|
|
410
|
+
const t = this._getBlockElement(e);
|
|
411
|
+
if (!t || !("getAttribute" in t))
|
|
349
412
|
return null;
|
|
350
|
-
const i =
|
|
413
|
+
const i = t.getAttribute(f);
|
|
351
414
|
if (!i)
|
|
352
415
|
return null;
|
|
353
|
-
const
|
|
354
|
-
return Number.isNaN(
|
|
416
|
+
const r = parseInt(i);
|
|
417
|
+
return Number.isNaN(r) ? null : r;
|
|
355
418
|
}
|
|
356
419
|
/**
|
|
357
420
|
* Gets the block element (the element with BLOCK_CLASS)
|
|
358
421
|
*/
|
|
359
|
-
_getBlockElement(
|
|
360
|
-
if ("getAttribute" in
|
|
361
|
-
const
|
|
362
|
-
if (
|
|
363
|
-
return
|
|
422
|
+
_getBlockElement(e) {
|
|
423
|
+
if ("getAttribute" in e) {
|
|
424
|
+
const t = e.getAttribute("class");
|
|
425
|
+
if (t && t.includes(d))
|
|
426
|
+
return e;
|
|
364
427
|
}
|
|
365
|
-
return "querySelector" in
|
|
428
|
+
return "querySelector" in e ? e.querySelector(`.${d}`) ?? null : null;
|
|
366
429
|
}
|
|
367
430
|
/**
|
|
368
431
|
* Migrate configuration from legacy format
|
|
369
432
|
*/
|
|
370
|
-
_migrateFromLegacy(
|
|
371
|
-
|
|
433
|
+
_migrateFromLegacy(e) {
|
|
434
|
+
a.migrateFromDataAttributes(this.api, e);
|
|
372
435
|
}
|
|
373
436
|
}
|
|
374
437
|
export {
|
|
375
|
-
|
|
376
|
-
|
|
438
|
+
y as BLOCK_ID,
|
|
439
|
+
te as RecommendationBlock
|
|
377
440
|
};
|
|
@@ -1,63 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
function p(t) {
|
|
18
|
-
const e = t.getDocumentRootCssNode();
|
|
19
|
-
return e && !T(e) ? e : null;
|
|
20
|
-
}
|
|
21
|
-
function G(t, e) {
|
|
22
|
-
const o = e.querySelectorAll("img").some((r) => g(r) && !!r.getStyle("max-height")) ? p(t) : null;
|
|
23
|
-
if (!o)
|
|
24
|
-
return;
|
|
25
|
-
const n = t.getDocumentModifier();
|
|
26
|
-
n.modifyCss(o).appendRule(f), n.apply(new y("Add the reco image mobile fit rule"));
|
|
27
|
-
}
|
|
28
|
-
class x extends M(
|
|
29
|
-
u.IMAGE_SIZE,
|
|
30
|
-
d.IMAGE,
|
|
31
|
-
l
|
|
32
|
-
) {
|
|
33
|
-
getAdditionalModifications(e) {
|
|
34
|
-
if (this.api.getEditorState()[S.previewDeviceMode] === E.MOBILE)
|
|
35
|
-
return;
|
|
36
|
-
const i = this.getFitStyles(), o = this.getTargetNodes(e).map((s) => s.querySelector("img")).filter((s) => _(s, i)), n = i["max-height"] ? p(this.api) : null;
|
|
37
|
-
if (!o.length && !n)
|
|
38
|
-
return;
|
|
39
|
-
const r = this.api.getDocumentModifier();
|
|
40
|
-
return n && r.modifyCss(n).appendRule(f), o.forEach((s) => {
|
|
41
|
-
const a = r.modifyHtml(s);
|
|
42
|
-
Object.entries(i).forEach(([c, m]) => {
|
|
43
|
-
m ? a.setStyle(c, m) : a.removeStyle(c);
|
|
44
|
-
});
|
|
45
|
-
}), r;
|
|
46
|
-
}
|
|
47
|
-
getFitStyles() {
|
|
48
|
-
const { size: e } = this.api.getValues();
|
|
49
|
-
return (e == null ? void 0 : e.type) === "height" && e.value ? O(e.value) : C;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
const A = I(
|
|
53
|
-
u.IMAGE_MARGINS,
|
|
54
|
-
d.IMAGE,
|
|
55
|
-
l
|
|
56
|
-
), b = {
|
|
57
|
-
size: x,
|
|
58
|
-
margins: A
|
|
1
|
+
import { createImageMarginsControl as m, createImageSizeControl as n } from "../../../controlFactories.js";
|
|
2
|
+
import { RecommendationBlockId as o } from "../../constants/blockIds.js";
|
|
3
|
+
import { RecommendationControlId as r } from "../../constants/controlIds.js";
|
|
4
|
+
import { BLOCK_ROOT_SELECTOR as t } from "../../constants/selectors.js";
|
|
5
|
+
const e = n(
|
|
6
|
+
r.IMAGE_SIZE,
|
|
7
|
+
o.IMAGE,
|
|
8
|
+
t
|
|
9
|
+
), i = m(
|
|
10
|
+
r.IMAGE_MARGINS,
|
|
11
|
+
o.IMAGE,
|
|
12
|
+
t
|
|
13
|
+
), C = {
|
|
14
|
+
size: e,
|
|
15
|
+
margins: i
|
|
59
16
|
};
|
|
60
17
|
export {
|
|
61
|
-
|
|
62
|
-
G as ensureMobileFitRule
|
|
18
|
+
C as ImageControls
|
|
63
19
|
};
|