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