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