@useinsider/guido 3.12.0-beta.d2745e7 → 3.12.0-beta.d77b0d2
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 +9 -4
- package/dist/components/Guido.vue.js +6 -6
- package/dist/components/Guido.vue2.js +50 -49
- package/dist/components/organisms/extensions/recommendation/StrategyDetailDrawer.vue.js +38 -0
- package/dist/components/organisms/extensions/recommendation/StrategyDetailDrawer.vue2.js +271 -0
- package/dist/components/organisms/extensions/recommendation/strategy-detail/CampaignTable.vue.js +18 -0
- package/dist/components/organisms/extensions/recommendation/strategy-detail/CampaignTable.vue2.js +42 -0
- package/dist/components/organisms/extensions/recommendation/strategy-detail/FilterHumanizer.vue.js +20 -0
- package/dist/components/organisms/extensions/recommendation/strategy-detail/FilterHumanizer.vue2.js +18 -0
- package/dist/components/organisms/extensions/recommendation/strategy-detail/HumanizerCondition.vue.js +20 -0
- package/dist/components/organisms/extensions/recommendation/strategy-detail/HumanizerCondition.vue2.js +60 -0
- package/dist/components/organisms/extensions/recommendation/strategy-detail/HumanizerGroup.vue.js +19 -0
- package/dist/components/organisms/extensions/recommendation/strategy-detail/HumanizerGroup.vue2.js +19 -0
- package/dist/components/organisms/extensions/recommendation/strategy-detail/InfoDisplayValue.vue.js +22 -0
- package/dist/components/organisms/extensions/recommendation/strategy-detail/InfoDisplayValue.vue2.js +29 -0
- package/dist/composables/useHtmlValidator.js +68 -60
- package/dist/composables/useHttp.js +61 -52
- package/dist/composables/useRecommendation.js +56 -40
- package/dist/composables/useSave.js +22 -22
- package/dist/composables/useStrategyFilters.js +16 -0
- package/dist/config/migrator/recommendationMigrator.js +7 -4
- package/dist/enums/date.js +4 -3
- package/dist/enums/extensions/filteringV2.js +1024 -0
- package/dist/enums/extensions/recommendationBlock.js +45 -24
- package/dist/enums/extensions/strategyDetail.js +149 -0
- package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +12 -11
- package/dist/extensions/Blocks/Recommendation/controls/main/index.js +141 -123
- package/dist/extensions/Blocks/Recommendation/controls/main/strategy.js +297 -0
- package/dist/extensions/Blocks/Recommendation/iconsRegistry.js +16 -2
- package/dist/extensions/Blocks/Recommendation/recommendation.css.js +137 -0
- package/dist/extensions/Blocks/Recommendation/store/recommendation.js +549 -165
- package/dist/extensions/Blocks/Recommendation/utils/strategyHumanizer.js +97 -0
- package/dist/extensions/Blocks/Recommendation/utils/strategyNavigation.js +8 -0
- package/dist/extensions/Blocks/Recommendation/utils/strategySummary.js +95 -0
- package/dist/extensions/Blocks/Recommendation/utils/strategyUrl.js +43 -0
- package/dist/extensions/Blocks/Recommendation/validation/requiredFields.js +24 -10
- package/dist/extensions/Blocks/common-control.js +37 -35
- package/dist/guido.css +1 -1
- package/dist/services/recommendationApi.js +127 -33
- package/dist/services/stripoApi.js +18 -18
- package/dist/src/@types/config/schemas.d.ts +10 -0
- package/dist/src/components/organisms/extensions/recommendation/StrategyDetailDrawer.vue.d.ts +2 -0
- package/dist/src/components/organisms/extensions/recommendation/strategy-detail/CampaignTable.vue.d.ts +43 -0
- package/dist/src/components/organisms/extensions/recommendation/strategy-detail/FilterHumanizer.vue.d.ts +29 -0
- package/dist/src/components/organisms/extensions/recommendation/strategy-detail/HumanizerCondition.vue.d.ts +40 -0
- package/dist/src/components/organisms/extensions/recommendation/strategy-detail/HumanizerGroup.vue.d.ts +38 -0
- package/dist/src/components/organisms/extensions/recommendation/strategy-detail/InfoDisplayValue.vue.d.ts +51 -0
- package/dist/src/composables/useConfig.d.ts +2 -0
- package/dist/src/composables/useHtmlValidator.d.ts +2 -0
- package/dist/src/composables/useRecommendation.d.ts +2 -0
- package/dist/src/composables/useStrategyFilters.d.ts +24 -0
- package/dist/src/enums/extensions/filteringV2.d.ts +72 -0
- package/dist/src/enums/extensions/recommendationBlock.d.ts +33 -0
- package/dist/src/enums/extensions/strategyDetail.d.ts +90 -0
- package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +10 -4
- package/dist/src/extensions/Blocks/Recommendation/controls/main/strategy.d.ts +118 -0
- package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +958 -1
- package/dist/src/extensions/Blocks/Recommendation/types/nodeConfig.d.ts +7 -0
- package/dist/src/extensions/Blocks/Recommendation/utils/strategyHumanizer.d.ts +50 -0
- package/dist/src/extensions/Blocks/Recommendation/utils/strategyNavigation.d.ts +10 -0
- package/dist/src/extensions/Blocks/Recommendation/utils/strategySummary.d.ts +34 -0
- package/dist/src/extensions/Blocks/Recommendation/utils/strategyUrl.d.ts +51 -0
- package/dist/src/extensions/Blocks/Recommendation/validation/requiredFields.d.ts +8 -1
- package/dist/src/extensions/Blocks/common-control.d.ts +5 -2
- package/dist/src/mock/api/recommendation-strategies.d.ts +2 -0
- package/dist/src/services/recommendationApi.d.ts +9 -1
- package/dist/src/stores/config.d.ts +18 -0
- package/dist/src/utils/genericUtil.d.ts +9 -0
- package/dist/static/styles/components/base-input.css.js +6 -7
- package/dist/utils/dateUtil.js +23 -10
- package/dist/utils/genericUtil.js +10 -1
- package/dist/utils/templatePreparation.js +70 -57
- package/package.json +1 -1
|
@@ -1,20 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
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 D } from "../../../../stores/config.js";
|
|
7
|
+
import { useToasterStore as f } from "../../../../stores/toaster.js";
|
|
8
|
+
import { defineStore as N } from "pinia";
|
|
5
9
|
import "../constants/selectors.js";
|
|
6
|
-
import { DEFAULT_MOBILE_CARDS_IN_ROW as
|
|
7
|
-
import { EXCLUDED_ALGORITHM_IDS as
|
|
8
|
-
import { getDefaultProducts as
|
|
9
|
-
import { generateCompleteFilterQuery as
|
|
10
|
-
import { getPartnerRecommendationParams as
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 V } from "../constants/defaultConfig.js";
|
|
12
|
+
import { getDefaultProducts as F } from "../templates/utils.js";
|
|
13
|
+
import { generateCompleteFilterQuery as v } from "../utils/filterUtil.js";
|
|
14
|
+
import { getPartnerRecommendationParams as A } from "../utils/partnerCustomizations.js";
|
|
15
|
+
import { resolveStrategyUrl as E, buildStrategyUrlValues as O } 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, I = 0;
|
|
20
|
+
const y = {};
|
|
21
|
+
let b = 0;
|
|
22
|
+
function P() {
|
|
16
23
|
return {
|
|
17
|
-
cardsInRow:
|
|
24
|
+
cardsInRow: B,
|
|
18
25
|
currencySettings: {
|
|
19
26
|
name: "USD",
|
|
20
27
|
value: "USD",
|
|
@@ -28,13 +35,14 @@ function k() {
|
|
|
28
35
|
productIds: [],
|
|
29
36
|
id: 1,
|
|
30
37
|
language: "en_US",
|
|
31
|
-
mobileCardsInRow:
|
|
38
|
+
mobileCardsInRow: x,
|
|
32
39
|
mobileLayoutEnabled: !1,
|
|
33
40
|
orientation: "grid",
|
|
34
41
|
recommendedProducts: [],
|
|
35
42
|
sendProductRequestFlag: !1,
|
|
36
43
|
shuffleProducts: !1,
|
|
37
44
|
strategy: "mostPopular",
|
|
45
|
+
strategyId: "",
|
|
38
46
|
textTrimming: !0,
|
|
39
47
|
unresponsive: !1,
|
|
40
48
|
size: "6",
|
|
@@ -43,28 +51,39 @@ function k() {
|
|
|
43
51
|
priceHideIfSameAsDiscounted: !1
|
|
44
52
|
};
|
|
45
53
|
}
|
|
46
|
-
function
|
|
54
|
+
function G() {
|
|
47
55
|
return {
|
|
48
|
-
recommendationConfigs:
|
|
56
|
+
recommendationConfigs: P(),
|
|
49
57
|
recommendationProducts: [],
|
|
50
58
|
filterStatus: !1,
|
|
51
59
|
filterSelectionDrawerStatus: !1,
|
|
60
|
+
strategyDetailDrawerStatus: !1,
|
|
61
|
+
strategyDetailId: "",
|
|
52
62
|
filterGroup: 1,
|
|
53
63
|
isInitialized: !1,
|
|
54
64
|
filterSnapshot: null
|
|
55
65
|
};
|
|
56
66
|
}
|
|
57
|
-
const
|
|
67
|
+
const j = () => ({
|
|
58
68
|
recommendationCampaignUrls: {},
|
|
59
69
|
activePredictiveAlgorithms: [],
|
|
60
70
|
languages: {},
|
|
61
71
|
currencyList: [],
|
|
62
72
|
filterList: {},
|
|
73
|
+
strategies: [],
|
|
74
|
+
strategiesLoading: !1,
|
|
75
|
+
strategyCampaigns: [],
|
|
76
|
+
strategyCampaignsLoading: !1,
|
|
77
|
+
strategyCampaignsFetchedFor: null,
|
|
78
|
+
strategyUrlTemplates: {},
|
|
79
|
+
strategiesLoadFailed: !1,
|
|
80
|
+
strategiesLoaded: !1,
|
|
81
|
+
strategyDuplicating: !1,
|
|
63
82
|
blockStates: {},
|
|
64
83
|
currentRecommendationId: null,
|
|
65
84
|
configVersion: 0
|
|
66
|
-
}),
|
|
67
|
-
state: () =>
|
|
85
|
+
}), ot = N("guidoRecommendationExtension", {
|
|
86
|
+
state: () => j(),
|
|
68
87
|
getters: {
|
|
69
88
|
// ====================================================================
|
|
70
89
|
// Proxy Getters — Backward Compatible Access to Current Block State
|
|
@@ -74,7 +93,7 @@ const L = () => ({
|
|
|
74
93
|
* This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
|
|
75
94
|
*/
|
|
76
95
|
recommendationConfigs(t) {
|
|
77
|
-
return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs :
|
|
96
|
+
return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : P();
|
|
78
97
|
},
|
|
79
98
|
/**
|
|
80
99
|
* Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
|
|
@@ -88,6 +107,25 @@ const L = () => ({
|
|
|
88
107
|
filterSelectionDrawerStatus(t) {
|
|
89
108
|
return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].filterSelectionDrawerStatus : !1;
|
|
90
109
|
},
|
|
110
|
+
/**
|
|
111
|
+
* Proxy getter: delegates to blockStates[currentRecommendationId].strategyDetailDrawerStatus
|
|
112
|
+
*/
|
|
113
|
+
strategyDetailDrawerStatus(t) {
|
|
114
|
+
return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].strategyDetailDrawerStatus : !1;
|
|
115
|
+
},
|
|
116
|
+
/**
|
|
117
|
+
* The strategy the detail drawer is describing.
|
|
118
|
+
*
|
|
119
|
+
* Usually the assigned one (the gear opens the drawer for the block), but
|
|
120
|
+
* View More opens it for a strategy the marketer is only inspecting — so an
|
|
121
|
+
* explicit id wins. An id that is no longer in the listing falls back to the
|
|
122
|
+
* assigned strategy rather than showing an empty drawer.
|
|
123
|
+
*/
|
|
124
|
+
getDrawerStrategy() {
|
|
125
|
+
var r;
|
|
126
|
+
const t = this.currentRecommendationId !== null ? (r = this.blockStates[this.currentRecommendationId]) == null ? void 0 : r.strategyDetailId : "";
|
|
127
|
+
return t ? this.strategies.find((e) => String(e.strategy_id) === t) ?? this.getSelectedStrategy : this.getSelectedStrategy;
|
|
128
|
+
},
|
|
91
129
|
/**
|
|
92
130
|
* Proxy getter: delegates to blockStates[currentRecommendationId].filterStatus
|
|
93
131
|
*/
|
|
@@ -108,45 +146,111 @@ const L = () => ({
|
|
|
108
146
|
},
|
|
109
147
|
hasValidFilters() {
|
|
110
148
|
const { filters: t } = this.recommendationConfigs;
|
|
111
|
-
return t.length ? t.every((
|
|
149
|
+
return t.length ? t.every((r) => r.isValid) : !1;
|
|
112
150
|
},
|
|
113
151
|
getFilterGroupCount() {
|
|
114
152
|
const { filters: t } = this.recommendationConfigs;
|
|
115
|
-
return t.length ? new Set(t.map((
|
|
153
|
+
return t.length ? new Set(t.map((r) => r.filterGroup)).size : 0;
|
|
116
154
|
},
|
|
117
155
|
getUniqueFilterGroups() {
|
|
118
156
|
const { filters: t } = this.recommendationConfigs;
|
|
119
|
-
return [...new Set(t.map((
|
|
157
|
+
return [...new Set(t.map((r) => r.filterGroup))].sort((r, e) => r - e);
|
|
120
158
|
},
|
|
121
159
|
getActivePredictiveAlgorithms: (t) => {
|
|
122
|
-
const
|
|
123
|
-
return t.activePredictiveAlgorithms.filter((
|
|
124
|
-
|
|
125
|
-
}),
|
|
126
|
-
text:
|
|
127
|
-
value:
|
|
160
|
+
const r = w(), e = [];
|
|
161
|
+
return t.activePredictiveAlgorithms.filter((i) => !V.includes(i)).forEach((i) => {
|
|
162
|
+
e.push(...r.filter((s) => s.id === i));
|
|
163
|
+
}), e.map((i) => ({
|
|
164
|
+
text: i.name,
|
|
165
|
+
value: i.key
|
|
166
|
+
}));
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* Reusable Recommendation Strategies as dropdown options.
|
|
170
|
+
*
|
|
171
|
+
* Unlike algorithms, strategy ids come straight from Smart Recommender —
|
|
172
|
+
* there is no local id→key table to join against.
|
|
173
|
+
*/
|
|
174
|
+
/**
|
|
175
|
+
* Options for the picker.
|
|
176
|
+
*
|
|
177
|
+
* A name shared by two strategies is disambiguated with its id. Smart
|
|
178
|
+
* Recommender does not enforce unique names, and an ambiguous label is a
|
|
179
|
+
* problem twice over: the marketer cannot tell the rows apart, and the hover
|
|
180
|
+
* card resolves a row back to a strategy by that very text — so it would
|
|
181
|
+
* describe the wrong one. Fixing the label fixes both.
|
|
182
|
+
*/
|
|
183
|
+
getStrategyOptions: (t) => {
|
|
184
|
+
const r = t.strategies.reduce((e, i) => (e[i.name] = (e[i.name] ?? 0) + 1, e), {});
|
|
185
|
+
return t.strategies.map((e) => ({
|
|
186
|
+
text: r[e.name] > 1 ? `${e.name} (${e.strategy_id})` : e.name,
|
|
187
|
+
value: String(e.strategy_id)
|
|
128
188
|
}));
|
|
129
189
|
},
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
190
|
+
/**
|
|
191
|
+
* The current block's selected strategy, or null when none is selected
|
|
192
|
+
* or the selected id is no longer in the listing.
|
|
193
|
+
*
|
|
194
|
+
* A null return with a non-empty `strategyId` means the strategy was
|
|
195
|
+
* deleted in Smart Recommender after this block was configured — the
|
|
196
|
+
* panel flags that rather than silently clearing the selection.
|
|
197
|
+
*/
|
|
198
|
+
getSelectedStrategy() {
|
|
199
|
+
const { strategyId: t } = this.recommendationConfigs;
|
|
200
|
+
return t ? this.strategies.find((r) => String(r.strategy_id) === t) ?? null : null;
|
|
201
|
+
},
|
|
202
|
+
/**
|
|
203
|
+
* Whether the RRS feature is enabled for this editor.
|
|
204
|
+
*
|
|
205
|
+
* Read through a getter so `hasInvalidBlock` can pass the store straight to
|
|
206
|
+
* the required-field descriptors, which need the flag to decide whether a
|
|
207
|
+
* missing strategy blocks the save.
|
|
208
|
+
*/
|
|
209
|
+
isRrsEnabled() {
|
|
210
|
+
return U().isFeatureEnabled("reusableRecommendationStrategy");
|
|
211
|
+
},
|
|
212
|
+
/** Strategy ids currently offered for email, for the save-time deleted-strategy check. */
|
|
213
|
+
strategyIds: (t) => t.strategies.map((r) => String(r.strategy_id)),
|
|
214
|
+
/**
|
|
215
|
+
* Whether the listing is trustworthy enough to judge an assigned id against.
|
|
216
|
+
*
|
|
217
|
+
* The bar is a settled, successful fetch — NOT a non-empty result. A partner
|
|
218
|
+
* whose EMAIL listing legitimately comes back empty (every strategy paused,
|
|
219
|
+
* none suitable for the channel) still makes an assigned id deleted, and
|
|
220
|
+
* keying on `strategies.length` silently disabled the save gate for exactly
|
|
221
|
+
* those partners.
|
|
222
|
+
*/
|
|
223
|
+
canValidateStrategy: (t) => t.strategiesLoaded && !t.strategiesLoading && !t.strategiesLoadFailed,
|
|
224
|
+
/**
|
|
225
|
+
* True when a strategy id is set but absent from the fetched listing.
|
|
226
|
+
*
|
|
227
|
+
* Shares `canValidateStrategy` so the panel warning and the save gate can
|
|
228
|
+
* never disagree about whether the listing can be judged.
|
|
229
|
+
*/
|
|
230
|
+
hasMissingStrategy() {
|
|
231
|
+
const { strategyId: t } = this.recommendationConfigs;
|
|
232
|
+
return !t || !this.canValidateStrategy ? !1 : this.getSelectedStrategy === null;
|
|
233
|
+
},
|
|
234
|
+
getLanguages: (t) => Object.entries(t.languages).map(([r, e]) => ({
|
|
235
|
+
text: e,
|
|
236
|
+
value: r
|
|
133
237
|
})),
|
|
134
|
-
getCurrencySymbolList: (t) => t.currencyList.map((
|
|
135
|
-
text:
|
|
136
|
-
value:
|
|
238
|
+
getCurrencySymbolList: (t) => t.currencyList.map((r) => ({
|
|
239
|
+
text: r.text,
|
|
240
|
+
value: r.text
|
|
137
241
|
})),
|
|
138
242
|
getFilterList() {
|
|
139
243
|
return Object.values(this.filterList).filter((t) => t.isFilterable !== 0 && t.isFilterable !== !1).map((t) => {
|
|
140
|
-
let
|
|
141
|
-
return t.type === "productAttribute" ?
|
|
244
|
+
let r;
|
|
245
|
+
return t.type === "productAttribute" ? r = `product_attributes.${t.attributeName}` : T.includes(t.attributeName) ? r = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : r = t.attributeName, {
|
|
142
246
|
text: t.displayName,
|
|
143
|
-
value:
|
|
247
|
+
value: r,
|
|
144
248
|
type: t.attributeType
|
|
145
249
|
};
|
|
146
250
|
});
|
|
147
251
|
},
|
|
148
252
|
getSelectedFilterGroup() {
|
|
149
|
-
return (t) => [...this.recommendationConfigs.filters].filter((
|
|
253
|
+
return (t) => [...this.recommendationConfigs.filters].filter((r) => r.filterGroup === t);
|
|
150
254
|
}
|
|
151
255
|
},
|
|
152
256
|
actions: {
|
|
@@ -160,7 +264,7 @@ const L = () => ({
|
|
|
160
264
|
setCurrentBlock(t) {
|
|
161
265
|
this.blockStates[t] || (this.blockStates = {
|
|
162
266
|
...this.blockStates,
|
|
163
|
-
[t]:
|
|
267
|
+
[t]: G()
|
|
164
268
|
}), this.currentRecommendationId = t;
|
|
165
269
|
},
|
|
166
270
|
/**
|
|
@@ -168,34 +272,34 @@ const L = () => ({
|
|
|
168
272
|
* Resets currentRecommendationId if it was the deleted block.
|
|
169
273
|
*/
|
|
170
274
|
removeBlockState(t) {
|
|
171
|
-
const
|
|
172
|
-
if (this.recommendationCampaignUrls[
|
|
173
|
-
const
|
|
174
|
-
delete
|
|
275
|
+
const r = t.toString();
|
|
276
|
+
if (this.recommendationCampaignUrls[r]) {
|
|
277
|
+
const i = { ...this.recommendationCampaignUrls };
|
|
278
|
+
delete i[r], this.recommendationCampaignUrls = i;
|
|
175
279
|
}
|
|
176
|
-
const
|
|
177
|
-
if (delete
|
|
178
|
-
const
|
|
179
|
-
this.currentRecommendationId =
|
|
280
|
+
const e = { ...this.blockStates };
|
|
281
|
+
if (delete e[t], this.blockStates = e, this.currentRecommendationId === t) {
|
|
282
|
+
const i = Object.keys(this.blockStates).map(Number);
|
|
283
|
+
this.currentRecommendationId = i.length > 0 ? i[0] : null;
|
|
180
284
|
}
|
|
181
285
|
},
|
|
182
286
|
/**
|
|
183
287
|
* Deep-clones a block's state and mirrors its campaign URL under a new id.
|
|
184
288
|
* Invoked from `onCreated` when a duplicate is detected (SD-142352).
|
|
185
289
|
*/
|
|
186
|
-
cloneBlockState(t,
|
|
187
|
-
const
|
|
188
|
-
if (
|
|
189
|
-
const
|
|
190
|
-
|
|
290
|
+
cloneBlockState(t, r) {
|
|
291
|
+
const e = this.blockStates[t];
|
|
292
|
+
if (e) {
|
|
293
|
+
const a = structuredClone(e);
|
|
294
|
+
a.recommendationConfigs.id = r, this.blockStates = {
|
|
191
295
|
...this.blockStates,
|
|
192
|
-
[
|
|
296
|
+
[r]: a
|
|
193
297
|
};
|
|
194
298
|
}
|
|
195
|
-
const
|
|
196
|
-
|
|
299
|
+
const i = t.toString(), s = r.toString(), n = this.recommendationCampaignUrls[i];
|
|
300
|
+
n && (this.recommendationCampaignUrls = {
|
|
197
301
|
...this.recommendationCampaignUrls,
|
|
198
|
-
[
|
|
302
|
+
[s]: n
|
|
199
303
|
});
|
|
200
304
|
},
|
|
201
305
|
/**
|
|
@@ -219,61 +323,62 @@ const L = () => ({
|
|
|
219
323
|
* relevant subset and leaves runtime fields (e.g., `recommendedProducts`,
|
|
220
324
|
* `isInitialized`) alone.
|
|
221
325
|
*/
|
|
222
|
-
seedBlockUrlConfig(t,
|
|
223
|
-
const
|
|
224
|
-
name:
|
|
225
|
-
value:
|
|
226
|
-
symbol:
|
|
227
|
-
alignment:
|
|
228
|
-
decimalCount: String(
|
|
229
|
-
decimalSeparator: r
|
|
230
|
-
thousandSeparator: r
|
|
231
|
-
},
|
|
232
|
-
|
|
233
|
-
...
|
|
234
|
-
strategy:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
326
|
+
seedBlockUrlConfig(t, r) {
|
|
327
|
+
const e = (a, o) => a === "." || a === "," || a === " " ? a : o, i = {
|
|
328
|
+
name: r.currencyCode,
|
|
329
|
+
value: r.currencyCode,
|
|
330
|
+
symbol: r.currencyCode,
|
|
331
|
+
alignment: r.currencyAlignment === "before" ? "0" : "1",
|
|
332
|
+
decimalCount: String(r.currencyDecimalCount),
|
|
333
|
+
decimalSeparator: e(r.currencyDecimalSeparator, ","),
|
|
334
|
+
thousandSeparator: e(r.currencyThousandSeparator, ".")
|
|
335
|
+
}, s = !this.blockStates[t], n = s ? G() : this.blockStates[t];
|
|
336
|
+
n.recommendationConfigs = {
|
|
337
|
+
...n.recommendationConfigs,
|
|
338
|
+
strategy: r.strategy,
|
|
339
|
+
strategyId: r.strategyId,
|
|
340
|
+
language: r.language,
|
|
341
|
+
size: r.size,
|
|
342
|
+
productIds: r.productIds,
|
|
343
|
+
filters: r.filters,
|
|
344
|
+
shuffleProducts: r.shuffleProducts,
|
|
345
|
+
currencySettings: i
|
|
346
|
+
}, s && (this.blockStates = {
|
|
242
347
|
...this.blockStates,
|
|
243
|
-
[t]:
|
|
348
|
+
[t]: n
|
|
244
349
|
});
|
|
245
350
|
},
|
|
246
351
|
/**
|
|
247
352
|
* Patches the current block's recommendationConfigs.
|
|
248
353
|
* Replaces `store.$patch({ recommendationConfigs: { ... } })` pattern.
|
|
249
354
|
*/
|
|
250
|
-
patchCurrentBlockConfig(t,
|
|
355
|
+
patchCurrentBlockConfig(t, r = {}) {
|
|
251
356
|
if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
|
|
252
357
|
return;
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
...
|
|
358
|
+
const e = this.blockStates[this.currentRecommendationId];
|
|
359
|
+
e.recommendationConfigs = {
|
|
360
|
+
...e.recommendationConfigs,
|
|
256
361
|
...t,
|
|
257
362
|
currencySettings: {
|
|
258
|
-
...
|
|
363
|
+
...e.recommendationConfigs.currencySettings,
|
|
259
364
|
...t.currencySettings || {}
|
|
260
365
|
}
|
|
261
366
|
};
|
|
262
|
-
const { triggerRefetch:
|
|
263
|
-
|
|
367
|
+
const { triggerRefetch: i = !0 } = r;
|
|
368
|
+
i && this.configVersion++;
|
|
264
369
|
},
|
|
265
370
|
/**
|
|
266
371
|
* Creates a filter with the first available attribute and operator pre-selected.
|
|
267
372
|
*/
|
|
268
|
-
createDefaultFilter(t,
|
|
269
|
-
const [
|
|
373
|
+
createDefaultFilter(t, r) {
|
|
374
|
+
const [e] = this.getFilterList, [i] = L(e == null ? void 0 : e.type);
|
|
270
375
|
return {
|
|
271
376
|
type: "standardFilter",
|
|
272
|
-
attribute: (
|
|
273
|
-
operator: (
|
|
377
|
+
attribute: (e == null ? void 0 : e.value) ?? "",
|
|
378
|
+
operator: (i == null ? void 0 : i.value) ?? "",
|
|
274
379
|
innerGroupOperator: "*",
|
|
275
380
|
outerGroupOperator: "*",
|
|
276
|
-
filterNumber:
|
|
381
|
+
filterNumber: r,
|
|
277
382
|
filterGroup: t,
|
|
278
383
|
isValid: !1,
|
|
279
384
|
value: ""
|
|
@@ -303,6 +408,213 @@ const L = () => ({
|
|
|
303
408
|
const t = this.blockStates[this.currentRecommendationId];
|
|
304
409
|
t.filterSnapshot = null, t.filterSelectionDrawerStatus = !1;
|
|
305
410
|
},
|
|
411
|
+
/**
|
|
412
|
+
* Find a strategy record by id, fetching the listing once if it is not loaded.
|
|
413
|
+
*
|
|
414
|
+
* The endpoint-generate call needs the record, and the save/hydrate path can
|
|
415
|
+
* reach it on a template the marketer never clicked into — where no panel has
|
|
416
|
+
* ever triggered the listing fetch.
|
|
417
|
+
*
|
|
418
|
+
* THROWS when the listing could not be loaded, rather than reporting "not
|
|
419
|
+
* found". The caller caches a not-found as a settled "this strategy has no
|
|
420
|
+
* URL"; doing that for a network blip would strand the block on its old
|
|
421
|
+
* algorithm feed for the rest of the session while the panel still showed
|
|
422
|
+
* the strategy as assigned. A throw leaves the cache key unset, so the next
|
|
423
|
+
* selection or save tries again.
|
|
424
|
+
*/
|
|
425
|
+
async _resolveStrategyRecord(t) {
|
|
426
|
+
const r = () => this.strategies.find((i) => String(i.strategy_id) === t) ?? null, e = r();
|
|
427
|
+
if (e || this.strategies.length)
|
|
428
|
+
return e;
|
|
429
|
+
if (await this.fetchRecommendationStrategies(), this.strategiesLoadFailed)
|
|
430
|
+
throw new Error("Recommendation strategy listing is unavailable.");
|
|
431
|
+
return r();
|
|
432
|
+
},
|
|
433
|
+
/**
|
|
434
|
+
* Assign a strategy to the current block.
|
|
435
|
+
*
|
|
436
|
+
* Only patches Pinia: the `configVersion` bump makes the main control persist
|
|
437
|
+
* `strategyId` into the block's node config (see `_persistStoreConfigToNode`),
|
|
438
|
+
* which is the same route filters take. Resolving the URL template here rather
|
|
439
|
+
* than at compile time matters — `buildCampaignUrl` is synchronous and would
|
|
440
|
+
* otherwise silently fall back to the algorithm URL.
|
|
441
|
+
*/
|
|
442
|
+
async selectStrategy(t) {
|
|
443
|
+
if (this.recommendationConfigs.strategyId === t)
|
|
444
|
+
return;
|
|
445
|
+
const r = this.currentRecommendationId, e = ++b;
|
|
446
|
+
this.patchCurrentBlockConfig({ strategyId: t }, { triggerRefetch: !0 });
|
|
447
|
+
const i = await this.fetchStrategyUrlTemplate(t), s = this.strategies.find((n) => String(n.strategy_id) === t);
|
|
448
|
+
if (!(this.currentRecommendationId !== r || e !== b)) {
|
|
449
|
+
if (!i) {
|
|
450
|
+
f().showToaster({
|
|
451
|
+
type: g.Error,
|
|
452
|
+
message: u()("discovery-strategy.error-description")
|
|
453
|
+
});
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
f().showToaster({
|
|
457
|
+
type: g.Success,
|
|
458
|
+
message: u()("campaign-builder.variation-strategy-assign", {
|
|
459
|
+
strategyName: (s == null ? void 0 : s.name) ?? t
|
|
460
|
+
})
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
/**
|
|
465
|
+
* Duplicate the selected strategy and assign the copy to this block.
|
|
466
|
+
*
|
|
467
|
+
* Runs inline — the marketer never leaves the editor. Two things the web flow
|
|
468
|
+
* does that we deliberately skip: its follow-up `POST /{id}/assign` (that binds
|
|
469
|
+
* a strategy to a campaign *variation*, a concept email blocks do not have —
|
|
470
|
+
* here the assignment is the block's own config), and displaying the copy's
|
|
471
|
+
* name from the response, which only carries the new id. Hence the refetch.
|
|
472
|
+
*/
|
|
473
|
+
async duplicateAndAssignStrategy() {
|
|
474
|
+
const t = this.getSelectedStrategy;
|
|
475
|
+
if (!t || this.strategyDuplicating)
|
|
476
|
+
return;
|
|
477
|
+
this.strategyDuplicating = !0;
|
|
478
|
+
const r = this.currentRecommendationId, e = ++b;
|
|
479
|
+
try {
|
|
480
|
+
const i = await c.duplicateStrategy(String(t.strategy_id));
|
|
481
|
+
if (!i) {
|
|
482
|
+
f().showToaster({
|
|
483
|
+
type: g.Error,
|
|
484
|
+
message: u()("discovery-strategy.error-description")
|
|
485
|
+
});
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if (await this.refreshRecommendationStrategies(), this.currentRecommendationId !== r || e !== b)
|
|
489
|
+
return;
|
|
490
|
+
this.patchCurrentBlockConfig({ strategyId: i }, { triggerRefetch: !0 });
|
|
491
|
+
const s = await this.fetchStrategyUrlTemplate(i);
|
|
492
|
+
if (this.currentRecommendationId !== r || e !== b)
|
|
493
|
+
return;
|
|
494
|
+
if (this.closeStrategyDrawer(), !s) {
|
|
495
|
+
f().showToaster({
|
|
496
|
+
type: g.Error,
|
|
497
|
+
message: u()("discovery-strategy.error-description")
|
|
498
|
+
});
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
f().showToaster({
|
|
502
|
+
type: g.Success,
|
|
503
|
+
message: u()("recommendation-strategies.duplicate-and-assign-text", {
|
|
504
|
+
strategyName: t.name
|
|
505
|
+
})
|
|
506
|
+
});
|
|
507
|
+
} finally {
|
|
508
|
+
this.strategyDuplicating = !1;
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
/**
|
|
512
|
+
* Opens the RRS detail drawer for the current block.
|
|
513
|
+
*
|
|
514
|
+
* `strategyId` describes a strategy without assigning it — View More reads
|
|
515
|
+
* the details of an option the marketer is still only considering. Omitted,
|
|
516
|
+
* the drawer describes whatever the block already has.
|
|
517
|
+
*
|
|
518
|
+
* Unlike the filter drawer this takes no snapshot — the drawer is
|
|
519
|
+
* read-only, so there is nothing to revert on close.
|
|
520
|
+
*/
|
|
521
|
+
openStrategyDrawer(t = "") {
|
|
522
|
+
this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId] || (this.blockStates[this.currentRecommendationId].strategyDetailId = t, this.blockStates[this.currentRecommendationId].strategyDetailDrawerStatus = !0);
|
|
523
|
+
},
|
|
524
|
+
/**
|
|
525
|
+
* Closes the RRS detail drawer for the current block and drops the campaign
|
|
526
|
+
* list, so reopening on a different strategy never flashes stale rows.
|
|
527
|
+
*/
|
|
528
|
+
closeStrategyDrawer() {
|
|
529
|
+
this.clearStrategyCampaigns(), !(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId]) && (this.blockStates[this.currentRecommendationId].strategyDetailId = "", this.blockStates[this.currentRecommendationId].strategyDetailDrawerStatus = !1);
|
|
530
|
+
},
|
|
531
|
+
/** Drops the campaign list and lets the next open refetch. */
|
|
532
|
+
clearStrategyCampaigns() {
|
|
533
|
+
this.strategyCampaigns = [], this.strategyCampaignsLoading = !1, this.strategyCampaignsFetchedFor = null, I += 1;
|
|
534
|
+
},
|
|
535
|
+
/**
|
|
536
|
+
* Resolve and cache a strategy's recommendation-URL template.
|
|
537
|
+
*
|
|
538
|
+
* Keyed by id for caching, but the request itself carries the whole strategy
|
|
539
|
+
* record — the algorithm inside it decides the feed's path and params, so an
|
|
540
|
+
* id alone cannot describe the URL. The listing is therefore a prerequisite;
|
|
541
|
+
* it is fetched on demand for callers (template hydrate) that may run before
|
|
542
|
+
* the panel ever opened.
|
|
543
|
+
*
|
|
544
|
+
* Must be awaited before `buildCampaignUrl` runs for a strategy-driven block,
|
|
545
|
+
* because that builder is synchronous and can only read what is already
|
|
546
|
+
* cached. Safe to call repeatedly: a resolved id short-circuits, and
|
|
547
|
+
* concurrent callers for the same id share one request.
|
|
548
|
+
*
|
|
549
|
+
* Only a *settled* answer is cached. An id the server rejects caches as '',
|
|
550
|
+
* so the block falls back instead of retrying on every compile. A request
|
|
551
|
+
* that merely failed to complete leaves the key unset and resolves to false,
|
|
552
|
+
* so the next selection or save tries again — caching a network blip as "no
|
|
553
|
+
* URL" would strand the block on its old algorithm feed for the session while
|
|
554
|
+
* the panel still showed the strategy as assigned.
|
|
555
|
+
* @returns whether the strategy now has a USABLE url — not merely a cache
|
|
556
|
+
* entry. A rejected id caches as '' to stop the retry storm, but reporting
|
|
557
|
+
* that as success would let the caller confirm an assignment the compiled
|
|
558
|
+
* email cannot honour.
|
|
559
|
+
*/
|
|
560
|
+
async fetchStrategyUrlTemplate(t) {
|
|
561
|
+
if (!t)
|
|
562
|
+
return !1;
|
|
563
|
+
if (t in this.strategyUrlTemplates)
|
|
564
|
+
return !!this.strategyUrlTemplates[t];
|
|
565
|
+
const r = y[t];
|
|
566
|
+
if (r)
|
|
567
|
+
return await r, !!this.strategyUrlTemplates[t];
|
|
568
|
+
y[t] = (async () => {
|
|
569
|
+
try {
|
|
570
|
+
const e = await this._resolveStrategyRecord(t);
|
|
571
|
+
if (!e) {
|
|
572
|
+
this.strategyUrlTemplates = { ...this.strategyUrlTemplates, [t]: "" };
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const i = await c.generateStrategyEndpoint(e, D().variationId);
|
|
576
|
+
this.strategyUrlTemplates = {
|
|
577
|
+
...this.strategyUrlTemplates,
|
|
578
|
+
[t]: (i == null ? void 0 : i.full_path) ?? ""
|
|
579
|
+
};
|
|
580
|
+
} catch (e) {
|
|
581
|
+
console.error("fetchStrategyUrlTemplate error:", e);
|
|
582
|
+
}
|
|
583
|
+
})();
|
|
584
|
+
try {
|
|
585
|
+
await y[t];
|
|
586
|
+
} finally {
|
|
587
|
+
delete y[t];
|
|
588
|
+
}
|
|
589
|
+
return !!this.strategyUrlTemplates[t];
|
|
590
|
+
},
|
|
591
|
+
/**
|
|
592
|
+
* Wait for every strategy-URL request still in flight.
|
|
593
|
+
*
|
|
594
|
+
* The compiler is synchronous: whatever is not cached by the time it runs
|
|
595
|
+
* compiles as the block's old algorithm URL. Selection and template hydrate
|
|
596
|
+
* both await their own request, but a save fired inside that round trip would
|
|
597
|
+
* still race — so the save path drains the queue first.
|
|
598
|
+
*/
|
|
599
|
+
async awaitPendingStrategyUrls() {
|
|
600
|
+
await Promise.all(Object.values(y).filter(Boolean));
|
|
601
|
+
},
|
|
602
|
+
/**
|
|
603
|
+
* Fetch the campaigns a strategy is used in, for the drawer's second tab.
|
|
604
|
+
*
|
|
605
|
+
* Lazy and once-per-strategy: the caller invokes this only while the drawer is
|
|
606
|
+
* open on that tab, and a repeat call for the same id is a no-op. A version
|
|
607
|
+
* counter discards a response that arrives after the user switched strategies,
|
|
608
|
+
* which would otherwise show one strategy's campaigns under another's name.
|
|
609
|
+
*/
|
|
610
|
+
async fetchStrategyCampaigns(t) {
|
|
611
|
+
if (!t || this.strategyCampaignsFetchedFor === t)
|
|
612
|
+
return;
|
|
613
|
+
const r = ++I;
|
|
614
|
+
this.strategyCampaignsLoading = !0;
|
|
615
|
+
const e = await c.fetchStrategyCampaigns(t, u());
|
|
616
|
+
r === I && (this.strategyCampaigns = e, this.strategyCampaignsLoading = !1, this.strategyCampaignsFetchedFor = t);
|
|
617
|
+
},
|
|
306
618
|
/**
|
|
307
619
|
* Cancels the filter selection drawer and reverts filters
|
|
308
620
|
* to the snapshot taken when the drawer was opened.
|
|
@@ -318,43 +630,82 @@ const L = () => ({
|
|
|
318
630
|
// ====================================================================
|
|
319
631
|
async fetchRecommendationCreateData() {
|
|
320
632
|
if (!this.activePredictiveAlgorithms.length) {
|
|
321
|
-
if (
|
|
322
|
-
await
|
|
633
|
+
if (h) {
|
|
634
|
+
await h;
|
|
323
635
|
return;
|
|
324
636
|
}
|
|
325
|
-
|
|
637
|
+
h = (async () => {
|
|
326
638
|
const {
|
|
327
639
|
activePredictiveAlgorithms: t,
|
|
328
|
-
languages:
|
|
329
|
-
currencies:
|
|
330
|
-
} = await
|
|
331
|
-
if (this.activePredictiveAlgorithms = t, this.languages =
|
|
332
|
-
const
|
|
333
|
-
|
|
640
|
+
languages: r,
|
|
641
|
+
currencies: e
|
|
642
|
+
} = await c.fetchRecommendationCreateData();
|
|
643
|
+
if (this.activePredictiveAlgorithms = t, this.languages = r, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
|
|
644
|
+
const i = this.blockStates[this.currentRecommendationId];
|
|
645
|
+
i.filterStatus = !!i.recommendationConfigs.filters.length;
|
|
646
|
+
}
|
|
647
|
+
this.currencyList = e;
|
|
648
|
+
})();
|
|
649
|
+
try {
|
|
650
|
+
await h;
|
|
651
|
+
} finally {
|
|
652
|
+
h = null;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
/**
|
|
657
|
+
* Fetch the partner's Reusable Recommendation Strategies for the EMAIL channel.
|
|
658
|
+
*
|
|
659
|
+
* Same cache-guard + in-flight dedupe shape as fetchRecommendationCreateData.
|
|
660
|
+
* A failure leaves `strategies` empty so the dropdown renders empty rather
|
|
661
|
+
* than blocking the panel — the block keeps working on its algorithm.
|
|
662
|
+
*/
|
|
663
|
+
async fetchRecommendationStrategies() {
|
|
664
|
+
if (!this.strategies.length) {
|
|
665
|
+
if (d) {
|
|
666
|
+
await d;
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
this.strategiesLoading = !0, d = (async () => {
|
|
670
|
+
try {
|
|
671
|
+
this.strategies = await c.fetchRecommendationStrategies() ?? [], this.strategiesLoadFailed = !1;
|
|
672
|
+
} catch {
|
|
673
|
+
this.strategies = [], this.strategiesLoadFailed = !0;
|
|
674
|
+
} finally {
|
|
675
|
+
this.strategiesLoading = !1, this.strategiesLoaded = !0;
|
|
334
676
|
}
|
|
335
|
-
this.currencyList = r;
|
|
336
677
|
})();
|
|
337
678
|
try {
|
|
338
|
-
await
|
|
679
|
+
await d;
|
|
339
680
|
} finally {
|
|
340
|
-
|
|
681
|
+
d = null;
|
|
341
682
|
}
|
|
342
683
|
}
|
|
343
684
|
},
|
|
685
|
+
/**
|
|
686
|
+
* Refetch the listing, ignoring the once-per-session cache.
|
|
687
|
+
*
|
|
688
|
+
* Needed after Duplicate and Assign: the copy exists server-side but not in
|
|
689
|
+
* `strategies`, so without this the panel would read the freshly assigned id
|
|
690
|
+
* back as a deleted strategy.
|
|
691
|
+
*/
|
|
692
|
+
async refreshRecommendationStrategies() {
|
|
693
|
+
this.strategies = [], this.strategiesLoaded = !1, d = null, await this.fetchRecommendationStrategies();
|
|
694
|
+
},
|
|
344
695
|
async fetchRecommendationFilters() {
|
|
345
696
|
if (!Object.keys(this.filterList).length) {
|
|
346
|
-
if (
|
|
347
|
-
await
|
|
697
|
+
if (p) {
|
|
698
|
+
await p;
|
|
348
699
|
return;
|
|
349
700
|
}
|
|
350
|
-
|
|
351
|
-
const t = await
|
|
701
|
+
p = (async () => {
|
|
702
|
+
const t = await c.fetchRecommendationFilters();
|
|
352
703
|
this.filterList = t;
|
|
353
704
|
})();
|
|
354
705
|
try {
|
|
355
|
-
await
|
|
706
|
+
await p;
|
|
356
707
|
} finally {
|
|
357
|
-
|
|
708
|
+
p = null;
|
|
358
709
|
}
|
|
359
710
|
}
|
|
360
711
|
},
|
|
@@ -369,59 +720,59 @@ const L = () => ({
|
|
|
369
720
|
deleteFilterGroup(t) {
|
|
370
721
|
if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
|
|
371
722
|
return;
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
...
|
|
375
|
-
filterGroup:
|
|
723
|
+
const r = this.blockStates[this.currentRecommendationId], e = r.recommendationConfigs.filters.filter((n) => n.filterGroup !== t), i = [...new Set(e.map((n) => n.filterGroup))].sort((n, a) => n - a), s = new Map(i.map((n, a) => [n, a + 1]));
|
|
724
|
+
r.recommendationConfigs.filters = e.map((n) => ({
|
|
725
|
+
...n,
|
|
726
|
+
filterGroup: s.get(n.filterGroup) ?? n.filterGroup
|
|
376
727
|
}));
|
|
377
728
|
},
|
|
378
729
|
updateFilter(t) {
|
|
379
730
|
if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
|
|
380
731
|
return;
|
|
381
|
-
const
|
|
382
|
-
if (
|
|
383
|
-
const
|
|
384
|
-
|
|
732
|
+
const r = this.blockStates[this.currentRecommendationId], e = r.recommendationConfigs.filters.findIndex((i) => i.filterNumber === t.filterNumber && i.filterGroup === t.filterGroup);
|
|
733
|
+
if (e !== -1) {
|
|
734
|
+
const i = [...r.recommendationConfigs.filters];
|
|
735
|
+
i[e] = {
|
|
385
736
|
...t,
|
|
386
|
-
isValid:
|
|
387
|
-
},
|
|
737
|
+
isValid: z(t)
|
|
738
|
+
}, r.recommendationConfigs.filters = i;
|
|
388
739
|
}
|
|
389
740
|
},
|
|
390
741
|
deleteFilter(t) {
|
|
391
742
|
if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
|
|
392
743
|
return;
|
|
393
|
-
const
|
|
394
|
-
if (
|
|
395
|
-
let
|
|
396
|
-
if (
|
|
397
|
-
let
|
|
398
|
-
|
|
744
|
+
const r = this.blockStates[this.currentRecommendationId], e = [...r.recommendationConfigs.filters].findIndex((i) => i.filterNumber === t.filterNumber && i.filterGroup === t.filterGroup);
|
|
745
|
+
if (e !== -1) {
|
|
746
|
+
let i = [...r.recommendationConfigs.filters];
|
|
747
|
+
if (i.splice(e, 1), i.some((n) => n.filterGroup === t.filterGroup)) {
|
|
748
|
+
let n = 1;
|
|
749
|
+
i = i.map((a) => a.filterGroup === t.filterGroup ? { ...a, filterNumber: n++ } : a);
|
|
399
750
|
} else {
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
...
|
|
403
|
-
filterGroup:
|
|
751
|
+
const n = [...new Set(i.map((o) => o.filterGroup))].sort((o, l) => o - l), a = new Map(n.map((o, l) => [o, l + 1]));
|
|
752
|
+
i = i.map((o) => ({
|
|
753
|
+
...o,
|
|
754
|
+
filterGroup: a.get(o.filterGroup) ?? o.filterGroup
|
|
404
755
|
}));
|
|
405
756
|
}
|
|
406
|
-
|
|
757
|
+
r.recommendationConfigs.filters = i;
|
|
407
758
|
}
|
|
408
759
|
},
|
|
409
760
|
addFilter(t) {
|
|
410
761
|
if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
|
|
411
762
|
return;
|
|
412
|
-
const
|
|
413
|
-
(
|
|
414
|
-
).length + 1,
|
|
415
|
-
|
|
763
|
+
const r = this.blockStates[this.currentRecommendationId], e = [...r.recommendationConfigs.filters], s = e.filter(
|
|
764
|
+
(a) => a.filterGroup === t.filterGroup
|
|
765
|
+
).length + 1, n = e.findLastIndex((a) => a.filterGroup === t.filterGroup);
|
|
766
|
+
n !== -1 ? e.splice(n + 1, 0, {
|
|
416
767
|
...t,
|
|
417
|
-
filterNumber:
|
|
418
|
-
}) :
|
|
768
|
+
filterNumber: s
|
|
769
|
+
}) : e.push({
|
|
419
770
|
...t,
|
|
420
|
-
filterNumber:
|
|
421
|
-
}),
|
|
771
|
+
filterNumber: s
|
|
772
|
+
}), r.recommendationConfigs.filters = e;
|
|
422
773
|
},
|
|
423
774
|
generateFilterQuery() {
|
|
424
|
-
return
|
|
775
|
+
return v(this.recommendationConfigs.filters);
|
|
425
776
|
},
|
|
426
777
|
/**
|
|
427
778
|
* Validation-only check invoked at save-CTA time. Defined as an action
|
|
@@ -429,55 +780,88 @@ const L = () => ({
|
|
|
429
780
|
* every block's recommendationConfigs across user edits.
|
|
430
781
|
*/
|
|
431
782
|
hasInvalidBlock() {
|
|
432
|
-
return Object.values(this.blockStates).some((t) => !
|
|
783
|
+
return Object.values(this.blockStates).some((t) => !M(t.recommendationConfigs, this));
|
|
433
784
|
},
|
|
434
785
|
// ====================================================================
|
|
435
786
|
// Per-Block Product Fetching
|
|
436
787
|
// ====================================================================
|
|
437
788
|
async fetchRecommendationProducts() {
|
|
438
789
|
if (!(this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])) {
|
|
439
|
-
if (
|
|
440
|
-
await
|
|
790
|
+
if (S) {
|
|
791
|
+
await S;
|
|
441
792
|
return;
|
|
442
793
|
}
|
|
443
|
-
|
|
794
|
+
S = this._doFetchProducts();
|
|
444
795
|
try {
|
|
445
|
-
await
|
|
796
|
+
await S;
|
|
446
797
|
} finally {
|
|
447
|
-
|
|
798
|
+
S = null;
|
|
448
799
|
}
|
|
449
800
|
}
|
|
450
801
|
},
|
|
451
802
|
async _doFetchProducts() {
|
|
452
|
-
var
|
|
453
|
-
const t = this.currentRecommendationId,
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
803
|
+
var C, k;
|
|
804
|
+
const t = this.currentRecommendationId, r = this.blockStates[t];
|
|
805
|
+
r.recommendationConfigs.strategyId && await this.fetchStrategyUrlTemplate(r.recommendationConfigs.strategyId);
|
|
806
|
+
const e = ((C = this.blockStates[t]) == null ? void 0 : C.recommendationConfigs) ?? r.recommendationConfigs, i = e.strategyId ? this.strategyUrlTemplates[e.strategyId] : "", s = E(
|
|
807
|
+
i ?? "",
|
|
808
|
+
O({
|
|
809
|
+
language: e.language,
|
|
810
|
+
currencyCode: e.currencySettings.value,
|
|
811
|
+
size: e.size,
|
|
812
|
+
strategyId: e.strategyId
|
|
813
|
+
}),
|
|
814
|
+
"preview"
|
|
815
|
+
);
|
|
816
|
+
if (s) {
|
|
817
|
+
await this._applyFetchedProducts(
|
|
818
|
+
t,
|
|
819
|
+
() => c.fetchRecommendationProductsByUrl(s)
|
|
820
|
+
);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
const n = e.filters.filter((R) => R.isValid), a = v(n), o = ((k = w().find((R) => R.key === e.strategy)) == null ? void 0 : k.path) || "", l = D(), m = {
|
|
824
|
+
locale: e.language,
|
|
825
|
+
currency: e.currencySettings.value,
|
|
826
|
+
partnerName: l.partnerName,
|
|
827
|
+
size: e.size,
|
|
458
828
|
details: !0,
|
|
459
|
-
campaignId:
|
|
829
|
+
campaignId: l.variationId
|
|
460
830
|
};
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
831
|
+
e.strategy === "manualMerchandising" ? m.productId = e.productIds.join(",") : e.strategy === "similarViewed" && (m.productId = "{itemId}"), e.strategy === "userBased" && (m.userId = "{user_id}"), a && (m.filter = a), e.shuffleProducts && (m.shuffle = !0), Object.assign(
|
|
832
|
+
m,
|
|
833
|
+
A(l.partnerName, e.strategy)
|
|
834
|
+
), await this._applyFetchedProducts(
|
|
835
|
+
t,
|
|
836
|
+
() => c.fetchRecommendationProducts(o, m)
|
|
464
837
|
);
|
|
465
|
-
|
|
838
|
+
},
|
|
839
|
+
/**
|
|
840
|
+
* Runs a product fetch and writes the result to `blockId`.
|
|
841
|
+
*
|
|
842
|
+
* Shared by the strategy and algorithm paths so the size padding/clipping —
|
|
843
|
+
* which the in-place card renderer depends on for its element-count match —
|
|
844
|
+
* cannot drift between them.
|
|
845
|
+
*/
|
|
846
|
+
async _applyFetchedProducts(t, r) {
|
|
847
|
+
var s;
|
|
848
|
+
const e = parseInt(((s = this.blockStates[t]) == null ? void 0 : s.recommendationConfigs.size) ?? "") || 6;
|
|
849
|
+
let i;
|
|
466
850
|
try {
|
|
467
|
-
|
|
851
|
+
i = await r();
|
|
468
852
|
} catch {
|
|
469
|
-
|
|
853
|
+
i = [];
|
|
470
854
|
}
|
|
471
855
|
if (this.blockStates[t]) {
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
...
|
|
475
|
-
...
|
|
476
|
-
] :
|
|
856
|
+
const n = i.length > 0 ? i : F(e);
|
|
857
|
+
n.length < e ? this.blockStates[t].recommendationProducts = [
|
|
858
|
+
...n,
|
|
859
|
+
...F(e - n.length)
|
|
860
|
+
] : n.length > e ? this.blockStates[t].recommendationProducts = n.slice(0, e) : this.blockStates[t].recommendationProducts = n;
|
|
477
861
|
}
|
|
478
862
|
}
|
|
479
863
|
}
|
|
480
864
|
});
|
|
481
865
|
export {
|
|
482
|
-
|
|
866
|
+
ot as useRecommendationExtensionStore
|
|
483
867
|
};
|