@useinsider/guido 3.12.0-beta.840207a → 3.12.0-beta.86e34da
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/dist/@types/config/schemas.js +6 -6
- package/dist/components/organisms/email-preview/desktop-preview/DesktopPreview.vue.js +1 -1
- package/dist/components/organisms/email-preview/desktop-preview/DesktopPreview.vue2.js +8 -8
- package/dist/components/organisms/email-preview/mobile-preview/ContentView.vue.js +5 -5
- package/dist/components/organisms/email-preview/mobile-preview/ContentView.vue2.js +10 -10
- package/dist/components/organisms/extensions/recommendation/StrategyDetailDrawer.vue.js +8 -6
- package/dist/components/organisms/extensions/recommendation/StrategyDetailDrawer.vue2.js +98 -71
- package/dist/composables/usePreviewInteractionGuard.js +36 -11
- package/dist/composables/useRecommendation.js +63 -78
- package/dist/composables/useRecommendationPreview.js +61 -60
- package/dist/enums/extensions/filteringV2.js +1017 -1
- package/dist/enums/extensions/recommendationBlock.js +34 -19
- package/dist/extensions/Blocks/Recommendation/constants/controlIds.js +1 -1
- package/dist/extensions/Blocks/Recommendation/controls/blockBackground/index.js +10 -0
- package/dist/extensions/Blocks/Recommendation/controls/main/index.js +24 -20
- package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +245 -106
- package/dist/extensions/Blocks/Recommendation/extension.js +23 -21
- package/dist/extensions/Blocks/Recommendation/iconsRegistry.js +17 -3
- package/dist/extensions/Blocks/Recommendation/recommendation.css.js +257 -0
- package/dist/extensions/Blocks/Recommendation/settingsPanel.js +41 -40
- package/dist/extensions/Blocks/Recommendation/store/recommendation.js +310 -117
- package/dist/extensions/Blocks/Recommendation/utils/strategyNavigation.js +8 -0
- package/dist/extensions/Blocks/Recommendation/utils/strategySummary.js +93 -8
- package/dist/extensions/Blocks/Recommendation/utils/strategyUrl.js +43 -0
- package/dist/extensions/Blocks/Recommendation/validation/requiredFields.js +18 -16
- package/dist/extensions/Blocks/Unsubscribe/utils/constants.js +3 -2
- package/dist/extensions/Blocks/common-control.js +2 -1
- package/dist/extensions/Blocks/controlFactories.js +125 -75
- package/dist/guido.css +1 -1
- package/dist/services/recommendationApi.js +90 -45
- package/dist/services/stripoApi.js +18 -18
- package/dist/src/@types/config/schemas.d.ts +2 -2
- package/dist/src/composables/usePreviewInteractionGuard.d.ts +1 -1
- package/dist/src/enums/extensions/recommendationBlock.d.ts +25 -2
- package/dist/src/extensions/Blocks/Recommendation/constants/controlIds.d.ts +1 -0
- package/dist/src/extensions/Blocks/Recommendation/controls/blockBackground/index.d.ts +14 -0
- package/dist/src/extensions/Blocks/Recommendation/controls/index.d.ts +1 -0
- package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +7 -3
- package/dist/src/extensions/Blocks/Recommendation/controls/main/strategy.d.ts +59 -45
- package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +301 -4
- package/dist/src/extensions/Blocks/Recommendation/utils/strategyNavigation.d.ts +10 -0
- package/dist/src/extensions/Blocks/Recommendation/utils/strategyUrl.d.ts +51 -0
- package/dist/src/extensions/Blocks/Recommendation/validation/requiredFields.d.ts +5 -0
- package/dist/src/extensions/Blocks/Unsubscribe/utils/constants.d.ts +1 -0
- package/dist/src/extensions/Blocks/common-control.d.ts +2 -1
- package/dist/src/extensions/Blocks/controlFactories.d.ts +36 -0
- package/dist/src/mock/api/recommendation-strategies.d.ts +2 -0
- package/dist/src/services/recommendationApi.d.ts +3 -1
- package/dist/src/utils/urlSchemes.d.ts +6 -0
- package/dist/static/styles/base.css.js +0 -15
- package/dist/static/styles/components/base-input.css.js +6 -7
- package/dist/utils/templatePreparation.js +56 -47
- package/dist/utils/urlSchemes.js +4 -0
- package/package.json +1 -1
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
import { useConfig as
|
|
2
|
-
import { useTranslations as
|
|
3
|
-
import { getRecommendationFeedSourceMaps as
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { useConfig as U } from "../../../../composables/useConfig.js";
|
|
2
|
+
import { useTranslations as u } from "../../../../composables/useTranslations.js";
|
|
3
|
+
import { getRecommendationFeedSourceMaps as w, getOperatorOptions as L, PriceAttributes as T } from "../../../../enums/extensions/recommendationBlock.js";
|
|
4
|
+
import { ToasterTypeOptions as g } from "../../../../enums/toaster.js";
|
|
5
|
+
import { useRecommendationApi as _ } from "../../../../services/recommendationApi.js";
|
|
6
|
+
import { useConfigStore as F } from "../../../../stores/config.js";
|
|
7
|
+
import { useToasterStore as f } from "../../../../stores/toaster.js";
|
|
8
|
+
import { defineStore as N } from "pinia";
|
|
7
9
|
import "../constants/selectors.js";
|
|
8
|
-
import { DEFAULT_MOBILE_CARDS_IN_ROW as
|
|
9
|
-
import { EXCLUDED_ALGORITHM_IDS as
|
|
10
|
-
import { getDefaultProducts as
|
|
11
|
-
import { generateCompleteFilterQuery as
|
|
12
|
-
import { getPartnerRecommendationParams as
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
import { DEFAULT_MOBILE_CARDS_IN_ROW as x, DEFAULT_CARDS_IN_ROW as B } from "../constants/layout.js";
|
|
11
|
+
import { EXCLUDED_ALGORITHM_IDS as A } from "../constants/defaultConfig.js";
|
|
12
|
+
import { getDefaultProducts as D } from "../templates/utils.js";
|
|
13
|
+
import { generateCompleteFilterQuery as v } from "../utils/filterUtil.js";
|
|
14
|
+
import { getPartnerRecommendationParams as E } from "../utils/partnerCustomizations.js";
|
|
15
|
+
import { resolveStrategyUrl as O, buildStrategyUrlValues as V } from "../utils/strategyUrl.js";
|
|
16
|
+
import { isFilterValid as z } from "../validation/filterSchema.js";
|
|
17
|
+
import { isConfigValid as M } from "../validation/requiredFields.js";
|
|
18
|
+
const c = _();
|
|
19
|
+
let h = null, p = null, S = null, d = null, C = 0;
|
|
20
|
+
const y = {};
|
|
21
|
+
let b = 0;
|
|
22
|
+
function P() {
|
|
19
23
|
return {
|
|
20
|
-
cardsInRow:
|
|
24
|
+
cardsInRow: B,
|
|
21
25
|
currencySettings: {
|
|
22
26
|
name: "USD",
|
|
23
27
|
value: "USD",
|
|
@@ -31,7 +35,7 @@ function C() {
|
|
|
31
35
|
productIds: [],
|
|
32
36
|
id: 1,
|
|
33
37
|
language: "en_US",
|
|
34
|
-
mobileCardsInRow:
|
|
38
|
+
mobileCardsInRow: x,
|
|
35
39
|
mobileLayoutEnabled: !1,
|
|
36
40
|
orientation: "grid",
|
|
37
41
|
recommendedProducts: [],
|
|
@@ -47,9 +51,9 @@ function C() {
|
|
|
47
51
|
priceHideIfSameAsDiscounted: !1
|
|
48
52
|
};
|
|
49
53
|
}
|
|
50
|
-
function
|
|
54
|
+
function G() {
|
|
51
55
|
return {
|
|
52
|
-
recommendationConfigs:
|
|
56
|
+
recommendationConfigs: P(),
|
|
53
57
|
recommendationProducts: [],
|
|
54
58
|
filterStatus: !1,
|
|
55
59
|
filterSelectionDrawerStatus: !1,
|
|
@@ -59,7 +63,7 @@ function k() {
|
|
|
59
63
|
filterSnapshot: null
|
|
60
64
|
};
|
|
61
65
|
}
|
|
62
|
-
const
|
|
66
|
+
const j = () => ({
|
|
63
67
|
recommendationCampaignUrls: {},
|
|
64
68
|
activePredictiveAlgorithms: [],
|
|
65
69
|
languages: {},
|
|
@@ -71,11 +75,13 @@ const B = () => ({
|
|
|
71
75
|
strategyCampaignsLoading: !1,
|
|
72
76
|
strategyCampaignsFetchedFor: null,
|
|
73
77
|
strategyUrlTemplates: {},
|
|
78
|
+
strategiesLoadFailed: !1,
|
|
79
|
+
strategyDuplicating: !1,
|
|
74
80
|
blockStates: {},
|
|
75
81
|
currentRecommendationId: null,
|
|
76
82
|
configVersion: 0
|
|
77
|
-
}),
|
|
78
|
-
state: () =>
|
|
83
|
+
}), ot = N("guidoRecommendationExtension", {
|
|
84
|
+
state: () => j(),
|
|
79
85
|
getters: {
|
|
80
86
|
// ====================================================================
|
|
81
87
|
// Proxy Getters — Backward Compatible Access to Current Block State
|
|
@@ -85,7 +91,7 @@ const B = () => ({
|
|
|
85
91
|
* This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
|
|
86
92
|
*/
|
|
87
93
|
recommendationConfigs(t) {
|
|
88
|
-
return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs :
|
|
94
|
+
return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : P();
|
|
89
95
|
},
|
|
90
96
|
/**
|
|
91
97
|
* Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
|
|
@@ -136,9 +142,9 @@ const B = () => ({
|
|
|
136
142
|
return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
|
|
137
143
|
},
|
|
138
144
|
getActivePredictiveAlgorithms: (t) => {
|
|
139
|
-
const e =
|
|
140
|
-
return t.activePredictiveAlgorithms.filter((i) => !
|
|
141
|
-
r.push(...e.filter((
|
|
145
|
+
const e = w(), r = [];
|
|
146
|
+
return t.activePredictiveAlgorithms.filter((i) => !A.includes(i)).forEach((i) => {
|
|
147
|
+
r.push(...e.filter((s) => s.id === i));
|
|
142
148
|
}), r.map((i) => ({
|
|
143
149
|
text: i.name,
|
|
144
150
|
value: i.key
|
|
@@ -174,8 +180,15 @@ const B = () => ({
|
|
|
174
180
|
* missing strategy blocks the save.
|
|
175
181
|
*/
|
|
176
182
|
isRrsEnabled() {
|
|
177
|
-
return
|
|
183
|
+
return U().isFeatureEnabled("reusableRecommendationStrategy");
|
|
178
184
|
},
|
|
185
|
+
/** Strategy ids currently offered for email, for the save-time deleted-strategy check. */
|
|
186
|
+
strategyIds: (t) => t.strategies.map((e) => String(e.strategy_id)),
|
|
187
|
+
/**
|
|
188
|
+
* Whether the listing is trustworthy enough to judge an assigned id against.
|
|
189
|
+
* Mid-fetch it is empty, and every assigned id would look deleted.
|
|
190
|
+
*/
|
|
191
|
+
canValidateStrategy: (t) => !t.strategiesLoading && t.strategies.length > 0,
|
|
179
192
|
/**
|
|
180
193
|
* True when a strategy id is set but absent from the fetched listing.
|
|
181
194
|
*
|
|
@@ -196,7 +209,7 @@ const B = () => ({
|
|
|
196
209
|
getFilterList() {
|
|
197
210
|
return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
|
|
198
211
|
let e;
|
|
199
|
-
return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` :
|
|
212
|
+
return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : T.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
|
|
200
213
|
text: t.displayName,
|
|
201
214
|
value: e,
|
|
202
215
|
type: t.attributeType
|
|
@@ -218,7 +231,7 @@ const B = () => ({
|
|
|
218
231
|
setCurrentBlock(t) {
|
|
219
232
|
this.blockStates[t] || (this.blockStates = {
|
|
220
233
|
...this.blockStates,
|
|
221
|
-
[t]:
|
|
234
|
+
[t]: G()
|
|
222
235
|
}), this.currentRecommendationId = t;
|
|
223
236
|
},
|
|
224
237
|
/**
|
|
@@ -244,16 +257,16 @@ const B = () => ({
|
|
|
244
257
|
cloneBlockState(t, e) {
|
|
245
258
|
const r = this.blockStates[t];
|
|
246
259
|
if (r) {
|
|
247
|
-
const
|
|
248
|
-
|
|
260
|
+
const a = structuredClone(r);
|
|
261
|
+
a.recommendationConfigs.id = e, this.blockStates = {
|
|
249
262
|
...this.blockStates,
|
|
250
|
-
[e]:
|
|
263
|
+
[e]: a
|
|
251
264
|
};
|
|
252
265
|
}
|
|
253
|
-
const i = t.toString(),
|
|
266
|
+
const i = t.toString(), s = e.toString(), n = this.recommendationCampaignUrls[i];
|
|
254
267
|
n && (this.recommendationCampaignUrls = {
|
|
255
268
|
...this.recommendationCampaignUrls,
|
|
256
|
-
[
|
|
269
|
+
[s]: n
|
|
257
270
|
});
|
|
258
271
|
},
|
|
259
272
|
/**
|
|
@@ -278,7 +291,7 @@ const B = () => ({
|
|
|
278
291
|
* `isInitialized`) alone.
|
|
279
292
|
*/
|
|
280
293
|
seedBlockUrlConfig(t, e) {
|
|
281
|
-
const r = (
|
|
294
|
+
const r = (a, o) => a === "." || a === "," || a === " " ? a : o, i = {
|
|
282
295
|
name: e.currencyCode,
|
|
283
296
|
value: e.currencyCode,
|
|
284
297
|
symbol: e.currencyCode,
|
|
@@ -286,7 +299,7 @@ const B = () => ({
|
|
|
286
299
|
decimalCount: String(e.currencyDecimalCount),
|
|
287
300
|
decimalSeparator: r(e.currencyDecimalSeparator, ","),
|
|
288
301
|
thousandSeparator: r(e.currencyThousandSeparator, ".")
|
|
289
|
-
},
|
|
302
|
+
}, s = !this.blockStates[t], n = s ? G() : this.blockStates[t];
|
|
290
303
|
n.recommendationConfigs = {
|
|
291
304
|
...n.recommendationConfigs,
|
|
292
305
|
strategy: e.strategy,
|
|
@@ -297,7 +310,7 @@ const B = () => ({
|
|
|
297
310
|
filters: e.filters,
|
|
298
311
|
shuffleProducts: e.shuffleProducts,
|
|
299
312
|
currencySettings: i
|
|
300
|
-
},
|
|
313
|
+
}, s && (this.blockStates = {
|
|
301
314
|
...this.blockStates,
|
|
302
315
|
[t]: n
|
|
303
316
|
});
|
|
@@ -325,7 +338,7 @@ const B = () => ({
|
|
|
325
338
|
* Creates a filter with the first available attribute and operator pre-selected.
|
|
326
339
|
*/
|
|
327
340
|
createDefaultFilter(t, e) {
|
|
328
|
-
const [r] = this.getFilterList, [i] =
|
|
341
|
+
const [r] = this.getFilterList, [i] = L(r == null ? void 0 : r.type);
|
|
329
342
|
return {
|
|
330
343
|
type: "standardFilter",
|
|
331
344
|
attribute: (r == null ? void 0 : r.value) ?? "",
|
|
@@ -362,6 +375,106 @@ const B = () => ({
|
|
|
362
375
|
const t = this.blockStates[this.currentRecommendationId];
|
|
363
376
|
t.filterSnapshot = null, t.filterSelectionDrawerStatus = !1;
|
|
364
377
|
},
|
|
378
|
+
/**
|
|
379
|
+
* Find a strategy record by id, fetching the listing once if it is not loaded.
|
|
380
|
+
*
|
|
381
|
+
* The endpoint-generate call needs the record, and the save/hydrate path can
|
|
382
|
+
* reach it on a template the marketer never clicked into — where no panel has
|
|
383
|
+
* ever triggered the listing fetch.
|
|
384
|
+
*
|
|
385
|
+
* THROWS when the listing could not be loaded, rather than reporting "not
|
|
386
|
+
* found". The caller caches a not-found as a settled "this strategy has no
|
|
387
|
+
* URL"; doing that for a network blip would strand the block on its old
|
|
388
|
+
* algorithm feed for the rest of the session while the panel still showed
|
|
389
|
+
* the strategy as assigned. A throw leaves the cache key unset, so the next
|
|
390
|
+
* selection or save tries again.
|
|
391
|
+
*/
|
|
392
|
+
async _resolveStrategyRecord(t) {
|
|
393
|
+
const e = () => this.strategies.find((i) => String(i.strategy_id) === t) ?? null, r = e();
|
|
394
|
+
if (r || this.strategies.length)
|
|
395
|
+
return r;
|
|
396
|
+
if (await this.fetchRecommendationStrategies(), this.strategiesLoadFailed)
|
|
397
|
+
throw new Error("Recommendation strategy listing is unavailable.");
|
|
398
|
+
return e();
|
|
399
|
+
},
|
|
400
|
+
/**
|
|
401
|
+
* Assign a strategy to the current block.
|
|
402
|
+
*
|
|
403
|
+
* Only patches Pinia: the `configVersion` bump makes the main control persist
|
|
404
|
+
* `strategyId` into the block's node config (see `_persistStoreConfigToNode`),
|
|
405
|
+
* which is the same route filters take. Resolving the URL template here rather
|
|
406
|
+
* than at compile time matters — `buildCampaignUrl` is synchronous and would
|
|
407
|
+
* otherwise silently fall back to the algorithm URL.
|
|
408
|
+
*/
|
|
409
|
+
async selectStrategy(t) {
|
|
410
|
+
if (this.recommendationConfigs.strategyId === t)
|
|
411
|
+
return;
|
|
412
|
+
const e = this.currentRecommendationId, r = ++b;
|
|
413
|
+
this.patchCurrentBlockConfig({ strategyId: t }, { triggerRefetch: !0 });
|
|
414
|
+
const i = await this.fetchStrategyUrlTemplate(t), s = this.strategies.find((n) => String(n.strategy_id) === t);
|
|
415
|
+
if (!(this.currentRecommendationId !== e || r !== b)) {
|
|
416
|
+
if (!i) {
|
|
417
|
+
f().showToaster({
|
|
418
|
+
type: g.Error,
|
|
419
|
+
message: u()("discovery-strategy.error-description")
|
|
420
|
+
});
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
f().showToaster({
|
|
424
|
+
type: g.Success,
|
|
425
|
+
message: u()("campaign-builder.variation-strategy-assign", {
|
|
426
|
+
strategyName: (s == null ? void 0 : s.name) ?? t
|
|
427
|
+
})
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
/**
|
|
432
|
+
* Duplicate the selected strategy and assign the copy to this block.
|
|
433
|
+
*
|
|
434
|
+
* Runs inline — the marketer never leaves the editor. Two things the web flow
|
|
435
|
+
* does that we deliberately skip: its follow-up `POST /{id}/assign` (that binds
|
|
436
|
+
* a strategy to a campaign *variation*, a concept email blocks do not have —
|
|
437
|
+
* here the assignment is the block's own config), and displaying the copy's
|
|
438
|
+
* name from the response, which only carries the new id. Hence the refetch.
|
|
439
|
+
*/
|
|
440
|
+
async duplicateAndAssignStrategy() {
|
|
441
|
+
const t = this.getSelectedStrategy;
|
|
442
|
+
if (!t || this.strategyDuplicating)
|
|
443
|
+
return;
|
|
444
|
+
this.strategyDuplicating = !0;
|
|
445
|
+
const e = this.currentRecommendationId, r = ++b;
|
|
446
|
+
try {
|
|
447
|
+
const i = await c.duplicateStrategy(String(t.strategy_id));
|
|
448
|
+
if (!i) {
|
|
449
|
+
f().showToaster({
|
|
450
|
+
type: g.Error,
|
|
451
|
+
message: u()("discovery-strategy.error-description")
|
|
452
|
+
});
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (await this.refreshRecommendationStrategies(), this.currentRecommendationId !== e || r !== b)
|
|
456
|
+
return;
|
|
457
|
+
this.patchCurrentBlockConfig({ strategyId: i }, { triggerRefetch: !0 });
|
|
458
|
+
const s = await this.fetchStrategyUrlTemplate(i);
|
|
459
|
+
if (this.currentRecommendationId !== e || r !== b)
|
|
460
|
+
return;
|
|
461
|
+
if (this.closeStrategyDrawer(), !s) {
|
|
462
|
+
f().showToaster({
|
|
463
|
+
type: g.Error,
|
|
464
|
+
message: u()("discovery-strategy.error-description")
|
|
465
|
+
});
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
f().showToaster({
|
|
469
|
+
type: g.Success,
|
|
470
|
+
message: u()("recommendation-strategies.duplicate-and-assign-text", {
|
|
471
|
+
strategyName: t.name
|
|
472
|
+
})
|
|
473
|
+
});
|
|
474
|
+
} finally {
|
|
475
|
+
this.strategyDuplicating = !1;
|
|
476
|
+
}
|
|
477
|
+
},
|
|
365
478
|
/**
|
|
366
479
|
* Opens the RRS detail drawer for the current block.
|
|
367
480
|
*
|
|
@@ -380,37 +493,74 @@ const B = () => ({
|
|
|
380
493
|
},
|
|
381
494
|
/** Drops the campaign list and lets the next open refetch. */
|
|
382
495
|
clearStrategyCampaigns() {
|
|
383
|
-
this.strategyCampaigns = [], this.strategyCampaignsLoading = !1, this.strategyCampaignsFetchedFor = null,
|
|
496
|
+
this.strategyCampaigns = [], this.strategyCampaignsLoading = !1, this.strategyCampaignsFetchedFor = null, C += 1;
|
|
384
497
|
},
|
|
385
498
|
/**
|
|
386
499
|
* Resolve and cache a strategy's recommendation-URL template.
|
|
387
500
|
*
|
|
501
|
+
* Keyed by id for caching, but the request itself carries the whole strategy
|
|
502
|
+
* record — the algorithm inside it decides the feed's path and params, so an
|
|
503
|
+
* id alone cannot describe the URL. The listing is therefore a prerequisite;
|
|
504
|
+
* it is fetched on demand for callers (template hydrate) that may run before
|
|
505
|
+
* the panel ever opened.
|
|
506
|
+
*
|
|
388
507
|
* Must be awaited before `buildCampaignUrl` runs for a strategy-driven block,
|
|
389
508
|
* because that builder is synchronous and can only read what is already
|
|
390
|
-
* cached. Safe to call repeatedly: a
|
|
391
|
-
*
|
|
392
|
-
*
|
|
509
|
+
* cached. Safe to call repeatedly: a resolved id short-circuits, and
|
|
510
|
+
* concurrent callers for the same id share one request.
|
|
511
|
+
*
|
|
512
|
+
* Only a *settled* answer is cached. An id the server rejects caches as '',
|
|
513
|
+
* so the block falls back instead of retrying on every compile. A request
|
|
514
|
+
* that merely failed to complete leaves the key unset and resolves to false,
|
|
515
|
+
* so the next selection or save tries again — caching a network blip as "no
|
|
516
|
+
* URL" would strand the block on its old algorithm feed for the session while
|
|
517
|
+
* the panel still showed the strategy as assigned.
|
|
518
|
+
* @returns whether the strategy now has a USABLE url — not merely a cache
|
|
519
|
+
* entry. A rejected id caches as '' to stop the retry storm, but reporting
|
|
520
|
+
* that as success would let the caller confirm an assignment the compiled
|
|
521
|
+
* email cannot honour.
|
|
393
522
|
*/
|
|
394
523
|
async fetchStrategyUrlTemplate(t) {
|
|
395
|
-
if (!t
|
|
396
|
-
return;
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
524
|
+
if (!t)
|
|
525
|
+
return !1;
|
|
526
|
+
if (t in this.strategyUrlTemplates)
|
|
527
|
+
return !!this.strategyUrlTemplates[t];
|
|
528
|
+
const e = y[t];
|
|
529
|
+
if (e)
|
|
530
|
+
return await e, !!this.strategyUrlTemplates[t];
|
|
531
|
+
y[t] = (async () => {
|
|
532
|
+
try {
|
|
533
|
+
const r = await this._resolveStrategyRecord(t);
|
|
534
|
+
if (!r) {
|
|
535
|
+
this.strategyUrlTemplates = { ...this.strategyUrlTemplates, [t]: "" };
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const i = await c.generateStrategyEndpoint(r, F().variationId);
|
|
539
|
+
this.strategyUrlTemplates = {
|
|
540
|
+
...this.strategyUrlTemplates,
|
|
541
|
+
[t]: (i == null ? void 0 : i.full_path) ?? ""
|
|
542
|
+
};
|
|
543
|
+
} catch (r) {
|
|
544
|
+
console.error("fetchStrategyUrlTemplate error:", r);
|
|
545
|
+
}
|
|
408
546
|
})();
|
|
409
547
|
try {
|
|
410
|
-
await
|
|
548
|
+
await y[t];
|
|
411
549
|
} finally {
|
|
412
|
-
delete
|
|
550
|
+
delete y[t];
|
|
413
551
|
}
|
|
552
|
+
return !!this.strategyUrlTemplates[t];
|
|
553
|
+
},
|
|
554
|
+
/**
|
|
555
|
+
* Wait for every strategy-URL request still in flight.
|
|
556
|
+
*
|
|
557
|
+
* The compiler is synchronous: whatever is not cached by the time it runs
|
|
558
|
+
* compiles as the block's old algorithm URL. Selection and template hydrate
|
|
559
|
+
* both await their own request, but a save fired inside that round trip would
|
|
560
|
+
* still race — so the save path drains the queue first.
|
|
561
|
+
*/
|
|
562
|
+
async awaitPendingStrategyUrls() {
|
|
563
|
+
await Promise.all(Object.values(y).filter(Boolean));
|
|
414
564
|
},
|
|
415
565
|
/**
|
|
416
566
|
* Fetch the campaigns a strategy is used in, for the drawer's second tab.
|
|
@@ -423,10 +573,10 @@ const B = () => ({
|
|
|
423
573
|
async fetchStrategyCampaigns(t) {
|
|
424
574
|
if (!t || this.strategyCampaignsFetchedFor === t)
|
|
425
575
|
return;
|
|
426
|
-
const e = ++
|
|
576
|
+
const e = ++C;
|
|
427
577
|
this.strategyCampaignsLoading = !0;
|
|
428
|
-
const r = await
|
|
429
|
-
e ===
|
|
578
|
+
const r = await c.fetchStrategyCampaigns(t, u());
|
|
579
|
+
e === C && (this.strategyCampaigns = r, this.strategyCampaignsLoading = !1, this.strategyCampaignsFetchedFor = t);
|
|
430
580
|
},
|
|
431
581
|
/**
|
|
432
582
|
* Cancels the filter selection drawer and reverts filters
|
|
@@ -443,16 +593,16 @@ const B = () => ({
|
|
|
443
593
|
// ====================================================================
|
|
444
594
|
async fetchRecommendationCreateData() {
|
|
445
595
|
if (!this.activePredictiveAlgorithms.length) {
|
|
446
|
-
if (
|
|
447
|
-
await
|
|
596
|
+
if (h) {
|
|
597
|
+
await h;
|
|
448
598
|
return;
|
|
449
599
|
}
|
|
450
|
-
|
|
600
|
+
h = (async () => {
|
|
451
601
|
const {
|
|
452
602
|
activePredictiveAlgorithms: t,
|
|
453
603
|
languages: e,
|
|
454
604
|
currencies: r
|
|
455
|
-
} = await
|
|
605
|
+
} = await c.fetchRecommendationCreateData();
|
|
456
606
|
if (this.activePredictiveAlgorithms = t, this.languages = e, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
|
|
457
607
|
const i = this.blockStates[this.currentRecommendationId];
|
|
458
608
|
i.filterStatus = !!i.recommendationConfigs.filters.length;
|
|
@@ -460,9 +610,9 @@ const B = () => ({
|
|
|
460
610
|
this.currencyList = r;
|
|
461
611
|
})();
|
|
462
612
|
try {
|
|
463
|
-
await
|
|
613
|
+
await h;
|
|
464
614
|
} finally {
|
|
465
|
-
|
|
615
|
+
h = null;
|
|
466
616
|
}
|
|
467
617
|
}
|
|
468
618
|
},
|
|
@@ -475,40 +625,50 @@ const B = () => ({
|
|
|
475
625
|
*/
|
|
476
626
|
async fetchRecommendationStrategies() {
|
|
477
627
|
if (!this.strategies.length) {
|
|
478
|
-
if (
|
|
479
|
-
await
|
|
628
|
+
if (d) {
|
|
629
|
+
await d;
|
|
480
630
|
return;
|
|
481
631
|
}
|
|
482
|
-
this.strategiesLoading = !0,
|
|
632
|
+
this.strategiesLoading = !0, d = (async () => {
|
|
483
633
|
try {
|
|
484
|
-
this.strategies = await
|
|
634
|
+
this.strategies = await c.fetchRecommendationStrategies() ?? [], this.strategiesLoadFailed = !1;
|
|
485
635
|
} catch {
|
|
486
|
-
this.strategies = [];
|
|
636
|
+
this.strategies = [], this.strategiesLoadFailed = !0;
|
|
487
637
|
} finally {
|
|
488
638
|
this.strategiesLoading = !1;
|
|
489
639
|
}
|
|
490
640
|
})();
|
|
491
641
|
try {
|
|
492
|
-
await
|
|
642
|
+
await d;
|
|
493
643
|
} finally {
|
|
494
|
-
|
|
644
|
+
d = null;
|
|
495
645
|
}
|
|
496
646
|
}
|
|
497
647
|
},
|
|
648
|
+
/**
|
|
649
|
+
* Refetch the listing, ignoring the once-per-session cache.
|
|
650
|
+
*
|
|
651
|
+
* Needed after Duplicate and Assign: the copy exists server-side but not in
|
|
652
|
+
* `strategies`, so without this the panel would read the freshly assigned id
|
|
653
|
+
* back as a deleted strategy.
|
|
654
|
+
*/
|
|
655
|
+
async refreshRecommendationStrategies() {
|
|
656
|
+
this.strategies = [], d = null, await this.fetchRecommendationStrategies();
|
|
657
|
+
},
|
|
498
658
|
async fetchRecommendationFilters() {
|
|
499
659
|
if (!Object.keys(this.filterList).length) {
|
|
500
|
-
if (
|
|
501
|
-
await
|
|
660
|
+
if (p) {
|
|
661
|
+
await p;
|
|
502
662
|
return;
|
|
503
663
|
}
|
|
504
|
-
|
|
505
|
-
const t = await
|
|
664
|
+
p = (async () => {
|
|
665
|
+
const t = await c.fetchRecommendationFilters();
|
|
506
666
|
this.filterList = t;
|
|
507
667
|
})();
|
|
508
668
|
try {
|
|
509
|
-
await
|
|
669
|
+
await p;
|
|
510
670
|
} finally {
|
|
511
|
-
|
|
671
|
+
p = null;
|
|
512
672
|
}
|
|
513
673
|
}
|
|
514
674
|
},
|
|
@@ -523,10 +683,10 @@ const B = () => ({
|
|
|
523
683
|
deleteFilterGroup(t) {
|
|
524
684
|
if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
|
|
525
685
|
return;
|
|
526
|
-
const e = this.blockStates[this.currentRecommendationId], r = e.recommendationConfigs.filters.filter((n) => n.filterGroup !== t), i = [...new Set(r.map((n) => n.filterGroup))].sort((n,
|
|
686
|
+
const e = this.blockStates[this.currentRecommendationId], r = e.recommendationConfigs.filters.filter((n) => n.filterGroup !== t), i = [...new Set(r.map((n) => n.filterGroup))].sort((n, a) => n - a), s = new Map(i.map((n, a) => [n, a + 1]));
|
|
527
687
|
e.recommendationConfigs.filters = r.map((n) => ({
|
|
528
688
|
...n,
|
|
529
|
-
filterGroup:
|
|
689
|
+
filterGroup: s.get(n.filterGroup) ?? n.filterGroup
|
|
530
690
|
}));
|
|
531
691
|
},
|
|
532
692
|
updateFilter(t) {
|
|
@@ -537,7 +697,7 @@ const B = () => ({
|
|
|
537
697
|
const i = [...e.recommendationConfigs.filters];
|
|
538
698
|
i[r] = {
|
|
539
699
|
...t,
|
|
540
|
-
isValid:
|
|
700
|
+
isValid: z(t)
|
|
541
701
|
}, e.recommendationConfigs.filters = i;
|
|
542
702
|
}
|
|
543
703
|
},
|
|
@@ -549,12 +709,12 @@ const B = () => ({
|
|
|
549
709
|
let i = [...e.recommendationConfigs.filters];
|
|
550
710
|
if (i.splice(r, 1), i.some((n) => n.filterGroup === t.filterGroup)) {
|
|
551
711
|
let n = 1;
|
|
552
|
-
i = i.map((
|
|
712
|
+
i = i.map((a) => a.filterGroup === t.filterGroup ? { ...a, filterNumber: n++ } : a);
|
|
553
713
|
} else {
|
|
554
|
-
const n = [...new Set(i.map((
|
|
555
|
-
i = i.map((
|
|
556
|
-
...
|
|
557
|
-
filterGroup:
|
|
714
|
+
const n = [...new Set(i.map((o) => o.filterGroup))].sort((o, l) => o - l), a = new Map(n.map((o, l) => [o, l + 1]));
|
|
715
|
+
i = i.map((o) => ({
|
|
716
|
+
...o,
|
|
717
|
+
filterGroup: a.get(o.filterGroup) ?? o.filterGroup
|
|
558
718
|
}));
|
|
559
719
|
}
|
|
560
720
|
e.recommendationConfigs.filters = i;
|
|
@@ -563,19 +723,19 @@ const B = () => ({
|
|
|
563
723
|
addFilter(t) {
|
|
564
724
|
if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
|
|
565
725
|
return;
|
|
566
|
-
const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters],
|
|
567
|
-
(
|
|
568
|
-
).length + 1, n = r.findLastIndex((
|
|
726
|
+
const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters], s = r.filter(
|
|
727
|
+
(a) => a.filterGroup === t.filterGroup
|
|
728
|
+
).length + 1, n = r.findLastIndex((a) => a.filterGroup === t.filterGroup);
|
|
569
729
|
n !== -1 ? r.splice(n + 1, 0, {
|
|
570
730
|
...t,
|
|
571
|
-
filterNumber:
|
|
731
|
+
filterNumber: s
|
|
572
732
|
}) : r.push({
|
|
573
733
|
...t,
|
|
574
|
-
filterNumber:
|
|
734
|
+
filterNumber: s
|
|
575
735
|
}), e.recommendationConfigs.filters = r;
|
|
576
736
|
},
|
|
577
737
|
generateFilterQuery() {
|
|
578
|
-
return
|
|
738
|
+
return v(this.recommendationConfigs.filters);
|
|
579
739
|
},
|
|
580
740
|
/**
|
|
581
741
|
* Validation-only check invoked at save-CTA time. Defined as an action
|
|
@@ -583,55 +743,88 @@ const B = () => ({
|
|
|
583
743
|
* every block's recommendationConfigs across user edits.
|
|
584
744
|
*/
|
|
585
745
|
hasInvalidBlock() {
|
|
586
|
-
return Object.values(this.blockStates).some((t) => !
|
|
746
|
+
return Object.values(this.blockStates).some((t) => !M(t.recommendationConfigs, this));
|
|
587
747
|
},
|
|
588
748
|
// ====================================================================
|
|
589
749
|
// Per-Block Product Fetching
|
|
590
750
|
// ====================================================================
|
|
591
751
|
async fetchRecommendationProducts() {
|
|
592
752
|
if (!(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])) {
|
|
593
|
-
if (
|
|
594
|
-
await
|
|
753
|
+
if (S) {
|
|
754
|
+
await S;
|
|
595
755
|
return;
|
|
596
756
|
}
|
|
597
|
-
|
|
757
|
+
S = this._doFetchProducts();
|
|
598
758
|
try {
|
|
599
|
-
await
|
|
759
|
+
await S;
|
|
600
760
|
} finally {
|
|
601
|
-
|
|
761
|
+
S = null;
|
|
602
762
|
}
|
|
603
763
|
}
|
|
604
764
|
},
|
|
605
765
|
async _doFetchProducts() {
|
|
606
|
-
var
|
|
607
|
-
const t = this.currentRecommendationId, e = this.blockStates[t]
|
|
766
|
+
var I, k;
|
|
767
|
+
const t = this.currentRecommendationId, e = this.blockStates[t];
|
|
768
|
+
e.recommendationConfigs.strategyId && await this.fetchStrategyUrlTemplate(e.recommendationConfigs.strategyId);
|
|
769
|
+
const r = ((I = this.blockStates[t]) == null ? void 0 : I.recommendationConfigs) ?? e.recommendationConfigs, i = r.strategyId ? this.strategyUrlTemplates[r.strategyId] : "", s = O(
|
|
770
|
+
i ?? "",
|
|
771
|
+
V({
|
|
772
|
+
language: r.language,
|
|
773
|
+
currencyCode: r.currencySettings.value,
|
|
774
|
+
size: r.size,
|
|
775
|
+
strategyId: r.strategyId
|
|
776
|
+
}),
|
|
777
|
+
"preview"
|
|
778
|
+
);
|
|
779
|
+
if (s) {
|
|
780
|
+
await this._applyFetchedProducts(
|
|
781
|
+
t,
|
|
782
|
+
() => c.fetchRecommendationProductsByUrl(s)
|
|
783
|
+
);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
const n = r.filters.filter((R) => R.isValid), a = v(n), o = ((k = w().find((R) => R.key === r.strategy)) == null ? void 0 : k.path) || "", l = F(), m = {
|
|
608
787
|
locale: r.language,
|
|
609
788
|
currency: r.currencySettings.value,
|
|
610
|
-
partnerName:
|
|
789
|
+
partnerName: l.partnerName,
|
|
611
790
|
size: r.size,
|
|
612
791
|
details: !0,
|
|
613
|
-
campaignId:
|
|
792
|
+
campaignId: l.variationId
|
|
614
793
|
};
|
|
615
|
-
r.strategy === "manualMerchandising" ?
|
|
616
|
-
|
|
617
|
-
|
|
794
|
+
r.strategy === "manualMerchandising" ? m.productId = r.productIds.join(",") : r.strategy === "similarViewed" && (m.productId = "{itemId}"), r.strategy === "userBased" && (m.userId = "{user_id}"), a && (m.filter = a), r.shuffleProducts && (m.shuffle = !0), Object.assign(
|
|
795
|
+
m,
|
|
796
|
+
E(l.partnerName, r.strategy)
|
|
797
|
+
), await this._applyFetchedProducts(
|
|
798
|
+
t,
|
|
799
|
+
() => c.fetchRecommendationProducts(o, m)
|
|
618
800
|
);
|
|
619
|
-
|
|
801
|
+
},
|
|
802
|
+
/**
|
|
803
|
+
* Runs a product fetch and writes the result to `blockId`.
|
|
804
|
+
*
|
|
805
|
+
* Shared by the strategy and algorithm paths so the size padding/clipping —
|
|
806
|
+
* which the in-place card renderer depends on for its element-count match —
|
|
807
|
+
* cannot drift between them.
|
|
808
|
+
*/
|
|
809
|
+
async _applyFetchedProducts(t, e) {
|
|
810
|
+
var s;
|
|
811
|
+
const r = parseInt(((s = this.blockStates[t]) == null ? void 0 : s.recommendationConfigs.size) ?? "") || 6;
|
|
812
|
+
let i;
|
|
620
813
|
try {
|
|
621
|
-
|
|
814
|
+
i = await e();
|
|
622
815
|
} catch {
|
|
623
|
-
|
|
816
|
+
i = [];
|
|
624
817
|
}
|
|
625
818
|
if (this.blockStates[t]) {
|
|
626
|
-
const
|
|
627
|
-
|
|
628
|
-
...
|
|
629
|
-
...
|
|
630
|
-
] :
|
|
819
|
+
const n = i.length > 0 ? i : D(r);
|
|
820
|
+
n.length < r ? this.blockStates[t].recommendationProducts = [
|
|
821
|
+
...n,
|
|
822
|
+
...D(r - n.length)
|
|
823
|
+
] : n.length > r ? this.blockStates[t].recommendationProducts = n.slice(0, r) : this.blockStates[t].recommendationProducts = n;
|
|
631
824
|
}
|
|
632
825
|
}
|
|
633
826
|
}
|
|
634
827
|
});
|
|
635
828
|
export {
|
|
636
|
-
|
|
829
|
+
ot as useRecommendationExtensionStore
|
|
637
830
|
};
|