@useinsider/guido 3.2.0-beta.4655fd0 → 3.2.0-beta.488f8eb

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.
Files changed (120) hide show
  1. package/README.md +117 -1
  2. package/dist/@types/config/schemas.js +153 -95
  3. package/dist/components/Guido.vue.js +4 -4
  4. package/dist/components/Guido.vue2.js +90 -88
  5. package/dist/components/organisms/AutoSaveController.vue.js +17 -0
  6. package/dist/components/organisms/AutoSaveController.vue2.js +13 -0
  7. package/dist/components/organisms/header/AutoSaveToggle.vue.js +22 -0
  8. package/dist/components/organisms/header/AutoSaveToggle.vue2.js +19 -0
  9. package/dist/components/organisms/header/RightSlot.vue.js +8 -8
  10. package/dist/components/organisms/header/RightSlot.vue2.js +9 -8
  11. package/dist/components/organisms/onboarding/AMPOnboarding.vue2.js +51 -31
  12. package/dist/components/organisms/onboarding/GenericOnboarding.vue.js +1 -1
  13. package/dist/components/organisms/onboarding/GenericOnboarding.vue2.js +23 -22
  14. package/dist/components/organisms/onboarding/ItemsOnboarding.vue.js +1 -1
  15. package/dist/components/organisms/onboarding/ItemsOnboarding.vue2.js +37 -39
  16. package/dist/components/organisms/onboarding/TextBlockOnboarding.vue.js +3 -3
  17. package/dist/components/organisms/onboarding/TextBlockOnboarding.vue2.js +30 -41
  18. package/dist/components/organisms/onboarding/VersionHistoryOnboarding.vue2.js +15 -14
  19. package/dist/composables/useAutoSave.js +71 -0
  20. package/dist/composables/useHtmlValidator.js +41 -36
  21. package/dist/composables/useRecommendation.js +46 -26
  22. package/dist/composables/useRibbonOffset.js +21 -0
  23. package/dist/composables/useSave.js +19 -16
  24. package/dist/composables/useStripo.js +14 -16
  25. package/dist/composables/validators/useCouponBlockValidator.js +24 -0
  26. package/dist/config/compiler/recommendationCompilerRules.js +25 -25
  27. package/dist/config/compiler/unsubscribeCompilerRules.js +40 -37
  28. package/dist/config/compiler/utils/recommendationCompilerUtils.js +104 -71
  29. package/dist/config/migrator/index.js +9 -9
  30. package/dist/config/migrator/recommendation/compositionMapper.js +98 -0
  31. package/dist/config/migrator/recommendation/extractors.js +27 -0
  32. package/dist/config/migrator/recommendation/htmlBuilder.js +496 -0
  33. package/dist/config/migrator/recommendation/parseLegacyConfig.js +33 -0
  34. package/dist/config/migrator/recommendation/settingsMapper.js +70 -0
  35. package/dist/config/migrator/recommendation/themeMapper.js +93 -0
  36. package/dist/config/migrator/recommendationMigrator.js +74 -290
  37. package/dist/enums/extensions/recommendationBlock.js +2 -1
  38. package/dist/enums/onboarding.js +7 -2
  39. package/dist/enums/unsubscribe.js +34 -27
  40. package/dist/extensions/Blocks/Recommendation/block.js +35 -32
  41. package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +5 -5
  42. package/dist/extensions/Blocks/Recommendation/controls/main/algorithm.js +25 -24
  43. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +228 -181
  44. package/dist/extensions/Blocks/Recommendation/services/configService.js +65 -29
  45. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +130 -81
  46. package/dist/extensions/Blocks/Recommendation/templates/grid/elementRenderer.js +19 -10
  47. package/dist/extensions/Blocks/Recommendation/templates/grid/template.js +8 -8
  48. package/dist/extensions/Blocks/Recommendation/templates/list/elementRenderer.js +25 -15
  49. package/dist/extensions/Blocks/Recommendation/templates/utils.js +1 -1
  50. package/dist/extensions/Blocks/Recommendation/utils/legacyStrategyMap.js +21 -0
  51. package/dist/extensions/Blocks/Recommendation/utils/preserveTextStyles.js +13 -22
  52. package/dist/extensions/Blocks/Recommendation/validation/requiredFields.js +33 -0
  53. package/dist/guido.css +1 -1
  54. package/dist/node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js +258 -235
  55. package/dist/node_modules/valibot/dist/index.js +450 -235
  56. package/dist/package.json.js +1 -1
  57. package/dist/services/templateLibraryApi.js +5 -4
  58. package/dist/src/@types/config/defaults.d.ts +5 -1
  59. package/dist/src/@types/config/index.d.ts +3 -3
  60. package/dist/src/@types/config/schemas.d.ts +217 -0
  61. package/dist/src/@types/config/types.d.ts +9 -1
  62. package/dist/src/components/Guido.vue.d.ts +1 -1
  63. package/dist/src/components/organisms/AutoSaveController.vue.d.ts +2 -0
  64. package/dist/src/components/organisms/header/AutoSaveToggle.vue.d.ts +2 -0
  65. package/dist/src/components/organisms/header/EditorActions.vue.d.ts +1 -1
  66. package/dist/src/components/organisms/header/HeaderWrapper.vue.d.ts +1 -1
  67. package/dist/src/components/organisms/header/RightSlot.vue.d.ts +1 -1
  68. package/dist/src/components/wrappers/WpModal.vue.d.ts +1 -1
  69. package/dist/src/composables/useAutoSave.d.ts +3 -0
  70. package/dist/src/composables/useConfig.d.ts +58 -0
  71. package/dist/src/composables/useRecommendation.d.ts +10 -1
  72. package/dist/src/composables/useRecommendation.test.d.ts +1 -0
  73. package/dist/src/composables/useRibbonOffset.d.ts +4 -0
  74. package/dist/src/composables/useSave.d.ts +1 -1
  75. package/dist/src/composables/validators/useCouponBlockValidator.d.ts +3 -0
  76. package/dist/src/config/migrator/index.d.ts +2 -1
  77. package/dist/src/config/migrator/recommendation/compositionMapper.d.ts +2 -0
  78. package/dist/src/config/migrator/recommendation/compositionMapper.test.d.ts +1 -0
  79. package/dist/src/config/migrator/recommendation/extractors.d.ts +7 -0
  80. package/dist/src/config/migrator/recommendation/extractors.test.d.ts +1 -0
  81. package/dist/src/config/migrator/recommendation/htmlBuilder.d.ts +11 -0
  82. package/dist/src/config/migrator/recommendation/parseLegacyConfig.d.ts +15 -0
  83. package/dist/src/config/migrator/recommendation/parseLegacyConfig.test.d.ts +1 -0
  84. package/dist/src/config/migrator/recommendation/settingsMapper.d.ts +7 -0
  85. package/dist/src/config/migrator/recommendation/settingsMapper.test.d.ts +1 -0
  86. package/dist/src/config/migrator/recommendation/themeMapper.d.ts +5 -0
  87. package/dist/src/config/migrator/recommendation/themeMapper.test.d.ts +1 -0
  88. package/dist/src/config/migrator/recommendation/types.d.ts +205 -0
  89. package/dist/src/config/migrator/recommendationMigrator.d.ts +13 -1
  90. package/dist/src/config/migrator/recommendationMigrator.test.d.ts +1 -0
  91. package/dist/src/enums/onboarding.d.ts +6 -0
  92. package/dist/src/enums/unsubscribe.d.ts +5 -0
  93. package/dist/src/extensions/Blocks/Recommendation/controls/main/utils.test.d.ts +1 -0
  94. package/dist/src/extensions/Blocks/Recommendation/services/configService.d.ts +10 -0
  95. package/dist/src/extensions/Blocks/Recommendation/services/configService.test.d.ts +1 -0
  96. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +41 -1
  97. package/dist/src/extensions/Blocks/Recommendation/types/nodeConfig.d.ts +1 -1
  98. package/dist/src/extensions/Blocks/Recommendation/utils/legacyStrategyMap.d.ts +21 -0
  99. package/dist/src/extensions/Blocks/Recommendation/utils/legacyStrategyMap.test.d.ts +1 -0
  100. package/dist/src/extensions/Blocks/Recommendation/utils/preserveTextStyles.d.ts +0 -3
  101. package/dist/src/extensions/Blocks/Recommendation/validation/requiredFields.d.ts +21 -0
  102. package/dist/src/library.d.ts +1 -1
  103. package/dist/src/stores/autosave.d.ts +12 -0
  104. package/dist/src/stores/config.d.ts +522 -0
  105. package/dist/src/stores/editor.d.ts +23 -0
  106. package/dist/src/stores/onboarding.d.ts +4 -0
  107. package/dist/src/utils/htmlEscape.d.ts +5 -0
  108. package/dist/src/utils/htmlEscape.test.d.ts +1 -0
  109. package/dist/src/utils/timeUtil.d.ts +8 -0
  110. package/dist/stores/autosave.js +17 -0
  111. package/dist/stores/editor.js +3 -1
  112. package/dist/stores/onboarding.js +4 -0
  113. package/dist/utils/htmlEscape.js +13 -0
  114. package/dist/utils/pairProductVariables.js +89 -88
  115. package/dist/utils/templatePreparation.js +72 -32
  116. package/dist/utils/timeUtil.js +19 -0
  117. package/package.json +7 -3
  118. package/dist/enums/displayConditions.js +0 -80
  119. package/dist/extensions/Blocks/Recommendation/templates/grid/migration.js +0 -251
  120. package/dist/src/enums/displayConditions.d.ts +0 -2
@@ -1,8 +1,14 @@
1
- import { ModificationDescription as f } from "../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
2
- import { CURRENT_CONFIG_VERSION as u, DEFAULT_NODE_CONFIG as e } from "../constants/defaultConfig.js";
3
- import { setCurrencyAttributes as g } from "../controls/main/utils.js";
4
- import { hasMinimalConfig as a } from "../types/nodeConfig.js";
5
- class C {
1
+ import { ModificationDescription as h } from "../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
2
+ import { CURRENT_CONFIG_VERSION as l, DEFAULT_NODE_CONFIG as r } from "../constants/defaultConfig.js";
3
+ import { setCurrencyAttributes as D } from "../controls/main/utils.js";
4
+ import { hasMinimalConfig as A } from "../types/nodeConfig.js";
5
+ function S(e) {
6
+ return e === "." || e === "," || e === " ";
7
+ }
8
+ function N(e) {
9
+ return e === "." || e === "," || e === " " || e === "";
10
+ }
11
+ class V {
6
12
  // ========================================================================
7
13
  // Read Operations
8
14
  // ========================================================================
@@ -42,7 +48,7 @@ class C {
42
48
  return !1;
43
49
  try {
44
50
  const t = i.getNodeConfig();
45
- return a(t);
51
+ return A(t);
46
52
  } catch {
47
53
  return !1;
48
54
  }
@@ -77,8 +83,8 @@ class C {
77
83
  * @returns The new complete configuration
78
84
  */
79
85
  static updateConfig(i, t, o, n) {
80
- const s = this.getConfig(t), r = this.deepMerge(s, o);
81
- return this.saveConfig(i, t, r, n), r;
86
+ const c = this.getConfig(t), u = this.deepMerge(c, o);
87
+ return this.saveConfig(i, t, u, n), u;
82
88
  }
83
89
  /**
84
90
  * Initialize configuration for a new block
@@ -95,7 +101,7 @@ class C {
95
101
  */
96
102
  static initializeConfig(i, t, o) {
97
103
  const n = o ? this.mergeWithDefaults(o) : this.cloneDefaults();
98
- return this.saveConfig(i, t, n, "Initialize recommendation block"), g({
104
+ return this.saveConfig(i, t, n, "Initialize recommendation block"), D({
99
105
  currentNode: t,
100
106
  documentModifier: i.getDocumentModifier(),
101
107
  currency: n.currency
@@ -112,9 +118,9 @@ class C {
112
118
  */
113
119
  static saveConfig(i, t, o, n) {
114
120
  try {
115
- i.getDocumentModifier().modifyHtml(t).setNodeConfig(o).apply(new f(n));
116
- } catch (s) {
117
- console.warn("[RecommendationConfigService] Failed to save config:", s);
121
+ i.getDocumentModifier().modifyHtml(t).setNodeConfig(o).apply(new h(n));
122
+ } catch (c) {
123
+ console.warn("[RecommendationConfigService] Failed to save config:", c);
118
124
  }
119
125
  }
120
126
  // ========================================================================
@@ -125,23 +131,53 @@ class C {
125
131
  *
126
132
  * Reads existing data-attributes and creates a proper node config.
127
133
  * Used when loading templates created before node config was implemented.
134
+ *
135
+ * Sources, in priority order (later overrides earlier):
136
+ * 1. `esd-ext-config` JSON blob — emitted by the recommendation migrator and
137
+ * by hand-authored new templates. Carries the full RecommendationNodeConfig
138
+ * (strategy, language, currency, filters, productIds, etc.) which the
139
+ * discrete data-* attrs below cannot capture.
140
+ * 2. Discrete `data-*` attributes — runtime source of truth for
141
+ * layout/composition/spacing; controls keep these in sync as the user
142
+ * edits, so we let them override the JSON blob if they disagree.
143
+ * 3. `currency-*` attributes — durable currency source on the block element.
128
144
  * @param api - Stripo extension API with document modifier
129
145
  * @param node - The block node to migrate
130
146
  * @returns The migrated configuration
131
147
  */
132
148
  static migrateFromDataAttributes(i, t) {
133
149
  const o = {
134
- configVersion: u
150
+ configVersion: l
135
151
  };
136
152
  if ("getAttribute" in t && typeof t.getAttribute == "function") {
137
- const n = t.getAttribute("data-layout");
138
- n === "list" || n === "horizontal" ? o.layout = "list" : (n === "grid" || n === "vertical") && (o.layout = "grid");
139
- const s = t.getAttribute("data-card-composition");
140
- s && (o.composition = s.split(",").filter(Boolean));
141
- const r = t.getAttribute("data-column-spacing");
142
- r && (o.columnSpacing = parseInt(r) || e.columnSpacing);
143
- const c = t.getAttribute("data-row-spacing");
144
- c && (o.rowSpacing = parseInt(c) || e.rowSpacing);
153
+ const n = t.getAttribute("esd-ext-config");
154
+ if (n)
155
+ try {
156
+ const s = JSON.parse(n);
157
+ s && typeof s == "object" && (Object.assign(o, s), o.configVersion = l);
158
+ } catch {
159
+ }
160
+ const c = t.getAttribute("data-layout");
161
+ c === "list" || c === "horizontal" ? o.layout = "list" : (c === "grid" || c === "vertical") && (o.layout = "grid");
162
+ const u = t.getAttribute("data-card-composition");
163
+ u && (o.composition = u.split(",").filter(Boolean));
164
+ const p = t.getAttribute("data-column-spacing");
165
+ p && (o.columnSpacing = parseInt(p) || r.columnSpacing);
166
+ const b = t.getAttribute("data-row-spacing");
167
+ if (b && (o.rowSpacing = parseInt(b) || r.rowSpacing), !o.currency) {
168
+ const s = t.getAttribute("currency"), y = t.getAttribute("currency-symbol"), d = t.getAttribute("currency-alignment"), f = t.getAttribute("currency-thousand-separator"), g = t.getAttribute("currency-decimal-separator"), m = t.getAttribute("currency-decimal-count");
169
+ if (s || y || d || f || g || m) {
170
+ const a = r.currency, C = m ? parseInt(m) : NaN;
171
+ o.currency = {
172
+ code: s ?? a.code,
173
+ symbol: y ?? a.symbol,
174
+ alignment: d === "0" ? "before" : "after",
175
+ decimalCount: Number.isFinite(C) ? C : a.decimalCount,
176
+ decimalSeparator: S(g) ? g : a.decimalSeparator,
177
+ thousandSeparator: N(f) ? f : a.thousandSeparator
178
+ };
179
+ }
180
+ }
145
181
  }
146
182
  return this.initializeConfig(i, t, o);
147
183
  }
@@ -151,7 +187,7 @@ class C {
151
187
  * @returns True if migration is needed
152
188
  */
153
189
  static needsMigration(i) {
154
- return i ? this.hasConfig(i) ? this.getConfigVersion(i) < u : !0 : !1;
190
+ return i ? this.hasConfig(i) ? this.getConfigVersion(i) < l : !0 : !1;
155
191
  }
156
192
  // ========================================================================
157
193
  // Internal Helpers
@@ -161,12 +197,12 @@ class C {
161
197
  */
162
198
  static cloneDefaults() {
163
199
  return {
164
- ...e,
165
- currency: { ...e.currency },
166
- omnibusPrice: { ...e.omnibusPrice },
167
- omnibusDiscount: { ...e.omnibusDiscount },
168
- composition: [...e.composition],
169
- visibility: { ...e.visibility },
200
+ ...r,
201
+ currency: { ...r.currency },
202
+ omnibusPrice: { ...r.omnibusPrice },
203
+ omnibusDiscount: { ...r.omnibusDiscount },
204
+ composition: [...r.composition],
205
+ visibility: { ...r.visibility },
170
206
  filters: [],
171
207
  productIds: [],
172
208
  recommendationId: 0
@@ -236,5 +272,5 @@ class C {
236
272
  }
237
273
  }
238
274
  export {
239
- C as RecommendationConfigService
275
+ V as RecommendationConfigService
240
276
  };
@@ -1,15 +1,16 @@
1
- import { RecommendationFeedSourceMaps as g, getOperatorOptions as R, PriceAttributes as k } from "../../../../enums/extensions/recommendationBlock.js";
1
+ import { RecommendationFeedSourceMaps as S, getOperatorOptions as R, PriceAttributes as y } from "../../../../enums/extensions/recommendationBlock.js";
2
2
  import { useRecommendationApi as C } from "../../../../services/recommendationApi.js";
3
- import { useConfigStore as y } from "../../../../stores/config.js";
4
- import { defineStore as G } from "pinia";
3
+ import { useConfigStore as G } from "../../../../stores/config.js";
4
+ import { defineStore as P } from "pinia";
5
5
  import { DEFAULT_CARDS_IN_ROW as F } from "../constants/layout.js";
6
- import { EXCLUDED_ALGORITHM_IDS as w } from "../constants/defaultConfig.js";
7
- import { getDefaultProducts as S } from "../templates/utils.js";
6
+ import { EXCLUDED_ALGORITHM_IDS as D } from "../constants/defaultConfig.js";
7
+ import { getDefaultProducts as g } from "../templates/utils.js";
8
8
  import { generateCompleteFilterQuery as b } from "../utils/filterUtil.js";
9
- import { isFilterValid as D } from "../validation/filterSchema.js";
9
+ import { isFilterValid as w } from "../validation/filterSchema.js";
10
+ import { isConfigValid as v } from "../validation/requiredFields.js";
10
11
  const h = C();
11
12
  let m = null, u = null, d = null;
12
- function I() {
13
+ function k() {
13
14
  return {
14
15
  cardsInRow: F,
15
16
  currencySettings: {
@@ -37,9 +38,9 @@ function I() {
37
38
  customAttributes: []
38
39
  };
39
40
  }
40
- function P() {
41
+ function I() {
41
42
  return {
42
- recommendationConfigs: I(),
43
+ recommendationConfigs: k(),
43
44
  recommendationProducts: [],
44
45
  filterStatus: !1,
45
46
  filterSelectionDrawerStatus: !1,
@@ -48,7 +49,7 @@ function P() {
48
49
  filterSnapshot: null
49
50
  };
50
51
  }
51
- const v = () => ({
52
+ const N = () => ({
52
53
  recommendationCampaignUrls: {},
53
54
  activePredictiveAlgorithms: [],
54
55
  languages: {},
@@ -57,8 +58,8 @@ const v = () => ({
57
58
  blockStates: {},
58
59
  currentRecommendationId: null,
59
60
  configVersion: 0
60
- }), z = G("guidoRecommendationExtension", {
61
- state: () => v(),
61
+ }), M = P("guidoRecommendationExtension", {
62
+ state: () => N(),
62
63
  getters: {
63
64
  // ====================================================================
64
65
  // Proxy Getters — Backward Compatible Access to Current Block State
@@ -68,7 +69,7 @@ const v = () => ({
68
69
  * This allows all existing code that reads `store.recommendationConfigs` to work unchanged.
69
70
  */
70
71
  recommendationConfigs(t) {
71
- return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : I();
72
+ return t.currentRecommendationId !== null && t.blockStates[t.currentRecommendationId] ? t.blockStates[t.currentRecommendationId].recommendationConfigs : k();
72
73
  },
73
74
  /**
74
75
  * Proxy getter: delegates to blockStates[currentRecommendationId].recommendationProducts
@@ -102,45 +103,45 @@ const v = () => ({
102
103
  },
103
104
  hasValidFilters() {
104
105
  const { filters: t } = this.recommendationConfigs;
105
- return t.length ? t.every((r) => r.isValid) : !1;
106
+ return t.length ? t.every((e) => e.isValid) : !1;
106
107
  },
107
108
  getFilterGroupCount() {
108
109
  const { filters: t } = this.recommendationConfigs;
109
- return t.length ? new Set(t.map((r) => r.filterGroup)).size : 0;
110
+ return t.length ? new Set(t.map((e) => e.filterGroup)).size : 0;
110
111
  },
111
112
  getUniqueFilterGroups() {
112
113
  const { filters: t } = this.recommendationConfigs;
113
- return [...new Set(t.map((r) => r.filterGroup))].sort((r, e) => r - e);
114
+ return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
114
115
  },
115
116
  getActivePredictiveAlgorithms: (t) => {
116
- const r = [];
117
- return t.activePredictiveAlgorithms.filter((e) => !w.includes(e)).forEach((e) => {
118
- r.push(...g.filter((n) => n.id === e));
119
- }), r.map((e) => ({
120
- text: e.name,
121
- value: e.key
117
+ const e = [];
118
+ return t.activePredictiveAlgorithms.filter((r) => !D.includes(r)).forEach((r) => {
119
+ e.push(...S.filter((n) => n.id === r));
120
+ }), e.map((r) => ({
121
+ text: r.name,
122
+ value: r.key
122
123
  }));
123
124
  },
124
- getLanguages: (t) => Object.entries(t.languages).map(([r, e]) => ({
125
- text: e,
126
- value: r
125
+ getLanguages: (t) => Object.entries(t.languages).map(([e, r]) => ({
126
+ text: r,
127
+ value: e
127
128
  })),
128
- getCurrencySymbolList: (t) => t.currencyList.map((r) => ({
129
- text: r.text,
130
- value: r.text
129
+ getCurrencySymbolList: (t) => t.currencyList.map((e) => ({
130
+ text: e.text,
131
+ value: e.text
131
132
  })),
132
133
  getFilterList() {
133
134
  return Object.values(this.filterList).map((t) => {
134
- let r;
135
- return t.type === "productAttribute" ? r = `product_attributes.${t.attributeName}` : k.includes(t.attributeName) ? r = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : r = t.attributeName, {
135
+ let e;
136
+ return t.type === "productAttribute" ? e = `product_attributes.${t.attributeName}` : y.includes(t.attributeName) ? e = `${t.attributeName}.${this.recommendationConfigs.currencySettings.value}` : e = t.attributeName, {
136
137
  text: t.displayName,
137
- value: r,
138
+ value: e,
138
139
  type: t.attributeType
139
140
  };
140
141
  });
141
142
  },
142
143
  getSelectedFilterGroup() {
143
- return (t) => [...this.recommendationConfigs.filters].filter((r) => r.filterGroup === t);
144
+ return (t) => [...this.recommendationConfigs.filters].filter((e) => e.filterGroup === t);
144
145
  }
145
146
  },
146
147
  actions: {
@@ -154,7 +155,7 @@ const v = () => ({
154
155
  setCurrentBlock(t) {
155
156
  this.blockStates[t] || (this.blockStates = {
156
157
  ...this.blockStates,
157
- [t]: P()
158
+ [t]: I()
158
159
  }), this.currentRecommendationId = t;
159
160
  },
160
161
  /**
@@ -162,13 +163,13 @@ const v = () => ({
162
163
  * Resets currentRecommendationId if it was the deleted block.
163
164
  */
164
165
  removeBlockState(t) {
165
- const r = t.toString();
166
- if (this.recommendationCampaignUrls[r]) {
166
+ const e = t.toString();
167
+ if (this.recommendationCampaignUrls[e]) {
167
168
  const n = { ...this.recommendationCampaignUrls };
168
- delete n[r], this.recommendationCampaignUrls = n;
169
+ delete n[e], this.recommendationCampaignUrls = n;
169
170
  }
170
- const e = { ...this.blockStates };
171
- if (delete e[t], this.blockStates = e, this.currentRecommendationId === t) {
171
+ const r = { ...this.blockStates };
172
+ if (delete r[t], this.blockStates = r, this.currentRecommendationId === t) {
172
173
  const n = Object.keys(this.blockStates).map(Number);
173
174
  this.currentRecommendationId = n.length > 0 ? n[0] : null;
174
175
  }
@@ -180,37 +181,75 @@ const v = () => ({
180
181
  markBlockInitialized(t) {
181
182
  this.blockStates[t] && (this.blockStates[t].isInitialized = !0);
182
183
  },
184
+ /**
185
+ * Seeds the URL-relevant fields of a block from a persisted node config.
186
+ *
187
+ * Used at save-time to ensure the campaign URL is built from the
188
+ * persisted truth (the `esd-ext-config` blob in the raw HTML) even when
189
+ * the user never selected the block in this editor session — without
190
+ * this seed, `_syncNodeConfigToStore` would never have run for that
191
+ * block and the store would hold default values (USD/en_US/mostPopular)
192
+ * instead of the real config.
193
+ *
194
+ * Creates the block entry if missing; otherwise patches only the URL-
195
+ * relevant subset and leaves runtime fields (e.g., `recommendedProducts`,
196
+ * `isInitialized`) alone.
197
+ */
198
+ seedBlockUrlConfig(t, e) {
199
+ const r = (o, s) => o === "." || o === "," || o === " " ? o : s, n = {
200
+ name: e.currencyCode,
201
+ value: e.currencyCode,
202
+ symbol: e.currencyCode,
203
+ alignment: e.currencyAlignment === "before" ? "0" : "1",
204
+ decimalCount: String(e.currencyDecimalCount),
205
+ decimalSeparator: r(e.currencyDecimalSeparator, ","),
206
+ thousandSeparator: r(e.currencyThousandSeparator, ".")
207
+ }, c = !this.blockStates[t], i = c ? I() : this.blockStates[t];
208
+ i.recommendationConfigs = {
209
+ ...i.recommendationConfigs,
210
+ strategy: e.strategy,
211
+ language: e.language,
212
+ size: e.size,
213
+ productIds: e.productIds,
214
+ filters: e.filters,
215
+ shuffleProducts: e.shuffleProducts,
216
+ currencySettings: n
217
+ }, c && (this.blockStates = {
218
+ ...this.blockStates,
219
+ [t]: i
220
+ });
221
+ },
183
222
  /**
184
223
  * Patches the current block's recommendationConfigs.
185
224
  * Replaces `store.$patch({ recommendationConfigs: { ... } })` pattern.
186
225
  */
187
- patchCurrentBlockConfig(t, r = {}) {
226
+ patchCurrentBlockConfig(t, e = {}) {
188
227
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
189
228
  return;
190
- const e = this.blockStates[this.currentRecommendationId];
191
- e.recommendationConfigs = {
192
- ...e.recommendationConfigs,
229
+ const r = this.blockStates[this.currentRecommendationId];
230
+ r.recommendationConfigs = {
231
+ ...r.recommendationConfigs,
193
232
  ...t,
194
233
  currencySettings: {
195
- ...e.recommendationConfigs.currencySettings,
234
+ ...r.recommendationConfigs.currencySettings,
196
235
  ...t.currencySettings || {}
197
236
  }
198
237
  };
199
- const { triggerRefetch: n = !0 } = r;
238
+ const { triggerRefetch: n = !0 } = e;
200
239
  n && this.configVersion++;
201
240
  },
202
241
  /**
203
242
  * Creates a filter with the first available attribute and operator pre-selected.
204
243
  */
205
- createDefaultFilter(t, r) {
206
- const [e] = this.getFilterList, [n] = R(e == null ? void 0 : e.type);
244
+ createDefaultFilter(t, e) {
245
+ const [r] = this.getFilterList, [n] = R(r == null ? void 0 : r.type);
207
246
  return {
208
247
  type: "standardFilter",
209
- attribute: (e == null ? void 0 : e.value) ?? "",
248
+ attribute: (r == null ? void 0 : r.value) ?? "",
210
249
  operator: (n == null ? void 0 : n.value) ?? "",
211
250
  innerGroupOperator: "*",
212
251
  outerGroupOperator: "*",
213
- filterNumber: r,
252
+ filterNumber: e,
214
253
  filterGroup: t,
215
254
  isValid: !1,
216
255
  value: ""
@@ -262,14 +301,14 @@ const v = () => ({
262
301
  m = (async () => {
263
302
  const {
264
303
  activePredictiveAlgorithms: t,
265
- languages: r,
266
- currencies: e
304
+ languages: e,
305
+ currencies: r
267
306
  } = await h.fetchRecommendationCreateData();
268
- if (this.activePredictiveAlgorithms = t, this.languages = r, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
307
+ if (this.activePredictiveAlgorithms = t, this.languages = e, this.currentRecommendationId !== null && this.blockStates[this.currentRecommendationId]) {
269
308
  const n = this.blockStates[this.currentRecommendationId];
270
309
  n.filterStatus = !!n.recommendationConfigs.filters.length;
271
310
  }
272
- this.currencyList = e;
311
+ this.currencyList = r;
273
312
  })();
274
313
  try {
275
314
  await m;
@@ -306,8 +345,8 @@ const v = () => ({
306
345
  deleteFilterGroup(t) {
307
346
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
308
347
  return;
309
- const r = this.blockStates[this.currentRecommendationId], e = r.recommendationConfigs.filters.filter((i) => i.filterGroup !== t), n = [...new Set(e.map((i) => i.filterGroup))].sort((i, o) => i - o), c = new Map(n.map((i, o) => [i, o + 1]));
310
- r.recommendationConfigs.filters = e.map((i) => ({
348
+ const e = this.blockStates[this.currentRecommendationId], r = e.recommendationConfigs.filters.filter((i) => i.filterGroup !== t), n = [...new Set(r.map((i) => i.filterGroup))].sort((i, o) => i - o), c = new Map(n.map((i, o) => [i, o + 1]));
349
+ e.recommendationConfigs.filters = r.map((i) => ({
311
350
  ...i,
312
351
  filterGroup: c.get(i.filterGroup) ?? i.filterGroup
313
352
  }));
@@ -315,51 +354,59 @@ const v = () => ({
315
354
  updateFilter(t) {
316
355
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
317
356
  return;
318
- const r = this.blockStates[this.currentRecommendationId], e = r.recommendationConfigs.filters.findIndex((n) => n.filterNumber === t.filterNumber && n.filterGroup === t.filterGroup);
319
- if (e !== -1) {
320
- const n = [...r.recommendationConfigs.filters];
321
- n[e] = {
357
+ const e = this.blockStates[this.currentRecommendationId], r = e.recommendationConfigs.filters.findIndex((n) => n.filterNumber === t.filterNumber && n.filterGroup === t.filterGroup);
358
+ if (r !== -1) {
359
+ const n = [...e.recommendationConfigs.filters];
360
+ n[r] = {
322
361
  ...t,
323
- isValid: D(t)
324
- }, r.recommendationConfigs.filters = n;
362
+ isValid: w(t)
363
+ }, e.recommendationConfigs.filters = n;
325
364
  }
326
365
  },
327
366
  deleteFilter(t) {
328
367
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
329
368
  return;
330
- const r = this.blockStates[this.currentRecommendationId], e = [...r.recommendationConfigs.filters].findIndex((n) => n.filterNumber === t.filterNumber && n.filterGroup === t.filterGroup);
331
- if (e !== -1) {
332
- let n = [...r.recommendationConfigs.filters];
333
- if (n.splice(e, 1), n.some((i) => i.filterGroup === t.filterGroup)) {
369
+ const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters].findIndex((n) => n.filterNumber === t.filterNumber && n.filterGroup === t.filterGroup);
370
+ if (r !== -1) {
371
+ let n = [...e.recommendationConfigs.filters];
372
+ if (n.splice(r, 1), n.some((i) => i.filterGroup === t.filterGroup)) {
334
373
  let i = 1;
335
374
  n = n.map((o) => o.filterGroup === t.filterGroup ? { ...o, filterNumber: i++ } : o);
336
375
  } else {
337
- const i = [...new Set(n.map((s) => s.filterGroup))].sort((s, l) => s - l), o = new Map(i.map((s, l) => [s, l + 1]));
376
+ const i = [...new Set(n.map((s) => s.filterGroup))].sort((s, a) => s - a), o = new Map(i.map((s, a) => [s, a + 1]));
338
377
  n = n.map((s) => ({
339
378
  ...s,
340
379
  filterGroup: o.get(s.filterGroup) ?? s.filterGroup
341
380
  }));
342
381
  }
343
- r.recommendationConfigs.filters = n;
382
+ e.recommendationConfigs.filters = n;
344
383
  }
345
384
  },
346
385
  addFilter(t) {
347
386
  if (this.currentRecommendationId === null || !this.blockStates[this.currentRecommendationId])
348
387
  return;
349
- const r = this.blockStates[this.currentRecommendationId], e = [...r.recommendationConfigs.filters], c = e.filter(
388
+ const e = this.blockStates[this.currentRecommendationId], r = [...e.recommendationConfigs.filters], c = r.filter(
350
389
  (o) => o.filterGroup === t.filterGroup
351
- ).length + 1, i = e.findLastIndex((o) => o.filterGroup === t.filterGroup);
352
- i !== -1 ? e.splice(i + 1, 0, {
390
+ ).length + 1, i = r.findLastIndex((o) => o.filterGroup === t.filterGroup);
391
+ i !== -1 ? r.splice(i + 1, 0, {
353
392
  ...t,
354
393
  filterNumber: c
355
- }) : e.push({
394
+ }) : r.push({
356
395
  ...t,
357
396
  filterNumber: c
358
- }), r.recommendationConfigs.filters = e;
397
+ }), e.recommendationConfigs.filters = r;
359
398
  },
360
399
  generateFilterQuery() {
361
400
  return b(this.recommendationConfigs.filters);
362
401
  },
402
+ /**
403
+ * Validation-only check invoked at save-CTA time. Defined as an action
404
+ * (not a getter) so reading it does not register reactive tracking on
405
+ * every block's recommendationConfigs across user edits.
406
+ */
407
+ hasInvalidBlock() {
408
+ return Object.values(this.blockStates).some((t) => !v(t.recommendationConfigs, this));
409
+ },
363
410
  // ====================================================================
364
411
  // Per-Block Product Fetching
365
412
  // ====================================================================
@@ -379,29 +426,31 @@ const v = () => ({
379
426
  },
380
427
  async _doFetchProducts() {
381
428
  var p;
382
- const t = this.currentRecommendationId, r = this.blockStates[t], { recommendationConfigs: e } = r, n = e.filters.filter((a) => a.isValid), c = b(n), i = ((p = g.find((a) => a.key === e.strategy)) == null ? void 0 : p.path) || "", o = y(), s = {
383
- locale: e.language,
384
- currency: e.currencySettings.value,
429
+ const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = r.filters.filter((l) => l.isValid), c = b(n), i = ((p = S.find((l) => l.key === r.strategy)) == null ? void 0 : p.path) || "", o = G(), s = parseInt(r.size) || 6, a = {
430
+ locale: r.language,
431
+ currency: r.currencySettings.value,
385
432
  partnerName: o.partnerName,
386
- size: e.size,
433
+ size: r.size,
387
434
  details: !0,
388
435
  campaignId: o.variationId
389
436
  };
390
- e.strategy === "manualMerchandising" ? s.productId = e.productIds.join(",") : e.strategy === "similarViewed" && (s.productId = "{itemId}"), e.strategy === "userBased" && (s.userId = "{user_id}"), c && (s.filter = c), e.shuffleProducts && (s.shuffle = !0);
391
- const l = parseInt(e.size) || 6;
437
+ r.strategy === "manualMerchandising" ? a.productId = r.productIds.slice(0, s).join(",") : r.strategy === "similarViewed" && (a.productId = "{itemId}"), r.strategy === "userBased" && (a.userId = "{user_id}"), c && (a.filter = c), r.shuffleProducts && (a.shuffle = !0);
392
438
  let f;
393
439
  try {
394
- f = await h.fetchRecommendationProducts(i, s);
440
+ f = await h.fetchRecommendationProducts(i, a);
395
441
  } catch {
396
442
  f = [];
397
443
  }
398
444
  if (this.blockStates[t]) {
399
- const a = f.length > 0 ? f : S(l);
400
- this.blockStates[t].recommendationProducts = a.length < l ? [...a, ...S(l - a.length)] : a;
445
+ const l = f.length > 0 ? f : g(s);
446
+ l.length < s ? this.blockStates[t].recommendationProducts = [
447
+ ...l,
448
+ ...g(s - l.length)
449
+ ] : l.length > s ? this.blockStates[t].recommendationProducts = l.slice(0, s) : this.blockStates[t].recommendationProducts = l;
401
450
  }
402
451
  }
403
452
  }
404
453
  });
405
454
  export {
406
- z as useRecommendationExtensionStore
455
+ M as useRecommendationExtensionStore
407
456
  };
@@ -1,5 +1,5 @@
1
1
  import { RecommendationBlockId as s } from "../../constants/blockIds.js";
2
- import { ATTR_PRODUCT_ATTR as g, ATTR_PRODUCT_BUTTON as u, ATTR_PRODUCT_OMNIBUS_DISCOUNT as m, ATTR_PRODUCT_OMNIBUS_PRICE as h, ATTR_PRODUCT_OLD_PRICE as y, ATTR_PRODUCT_PRICE as T, ATTR_PRODUCT_NAME as f, ATTR_PRODUCT_IMAGE as x } from "../../constants/selectors.js";
2
+ import { ATTR_PRODUCT_ATTR as b, ATTR_PRODUCT_BUTTON as u, ATTR_PRODUCT_OMNIBUS_DISCOUNT as m, ATTR_PRODUCT_OMNIBUS_PRICE as h, ATTR_PRODUCT_OLD_PRICE as y, ATTR_PRODUCT_PRICE as T, ATTR_PRODUCT_NAME as f, ATTR_PRODUCT_IMAGE as x } from "../../constants/selectors.js";
3
3
  import { useRecommendationExtensionStore as _ } from "../../store/recommendation.js";
4
4
  import { formatPrice as $ } from "../../utils/priceFormatter.js";
5
5
  import { sanitizeImageUrl as C, CUSTOM_CELL_HTML as R } from "../utils.js";
@@ -15,7 +15,7 @@ function p() {
15
15
  thousandSeparator: e.thousandSeparator
16
16
  };
17
17
  }
18
- function r(t, e = "price") {
18
+ function i(t, e = "price") {
19
19
  const o = p(), n = t[e], d = (n == null ? void 0 : n[o.code]) ?? Object.values(n ?? {})[0] ?? 0;
20
20
  return $({
21
21
  price: d,
@@ -92,7 +92,7 @@ const I = {
92
92
  align="center"
93
93
  esd-extension-block-id="${s.PRICE}">
94
94
  <p contenteditable="false" style="font-size: 16px; color: #333333;">
95
- <strong>${r(t, "price")}</strong>
95
+ <strong>${i(t, "price")}</strong>
96
96
  </p>
97
97
  </td>
98
98
  </tr>
@@ -116,7 +116,7 @@ const I = {
116
116
  align="center"
117
117
  esd-extension-block-id="${s.OLD_PRICE}">
118
118
  <p contenteditable="false" style="font-size: 14px; color: #999999;">
119
- <strong>${r(t, "original_price")}</strong>
119
+ <strong>${i(t, "original_price")}</strong>
120
120
  </p>
121
121
  </td>
122
122
  </tr>
@@ -143,7 +143,7 @@ const I = {
143
143
  esd-extension-block-id="${s.OMNIBUS_PRICE}">
144
144
  <p contenteditable="false" style="font-size: 12px; color: #666666;">
145
145
  <span class="omnibus-text-before">Lowest 30-day price: </span>
146
- <span class="omnibus-price-value">${r(t, "original_price")}</span>
146
+ <span class="omnibus-price-value">${i(t, "original_price")}</span>
147
147
  <span class="omnibus-text-after"></span>
148
148
  </p>
149
149
  </td>
@@ -153,8 +153,8 @@ const I = {
153
153
  </td>
154
154
  `,
155
155
  [m]: (t) => {
156
- var c, i;
157
- const e = p(), o = ((c = t.original_price) == null ? void 0 : c[e.code]) ?? Object.values(t.original_price ?? {})[0] ?? 0, n = ((i = t.price) == null ? void 0 : i[e.code]) ?? Object.values(t.price ?? {})[0] ?? 0, d = o > 0 ? Math.round((o - n) / o * 100) : 0, b = d > 0 ? `-${d}%` : "0%";
156
+ var r, c;
157
+ const e = p(), o = ((r = t.original_price) == null ? void 0 : r[e.code]) ?? Object.values(t.original_price ?? {})[0] ?? 0, n = ((c = t.price) == null ? void 0 : c[e.code]) ?? Object.values(t.price ?? {})[0] ?? 0, d = o > 0 ? Math.round((o - n) / o * 100) : 0, g = d > 0 ? `-${d}%` : "0%";
158
158
  return `
159
159
  <td class="${l}" style="padding: ${a}; height: 100%;" valign="top">
160
160
  <table
@@ -174,7 +174,7 @@ const I = {
174
174
  esd-extension-block-id="${s.OMNIBUS_DISCOUNT}">
175
175
  <p contenteditable="false" style="font-size: 12px; color: #666666;">
176
176
  <span class="omnibus-text-before"></span>
177
- <span class="omnibus-discount-value">${b}</span>
177
+ <span class="omnibus-discount-value">${g}</span>
178
178
  <span class="omnibus-text-after"></span>
179
179
  </p>
180
180
  </td>
@@ -210,7 +210,16 @@ const I = {
210
210
  href="#"
211
211
  class="es-button buy-button"
212
212
  target="_blank"
213
- style="color: rgb(56, 118, 29); background: rgb(217, 234, 211);">
213
+ style="
214
+ color: rgb(56, 118, 29);
215
+ background: rgb(217, 234, 211);
216
+ font-family: arial, 'helvetica neue', helvetica, sans-serif;
217
+ font-size: 16px;
218
+ font-weight: normal;
219
+ line-height: 120%;
220
+ mso-border-alt: 10px solid rgb(217, 234, 211);
221
+ mso-padding-alt: 0;
222
+ ">
214
223
  Buy
215
224
  </a>
216
225
  </span>
@@ -240,7 +249,7 @@ const I = {
240
249
  <tbody>
241
250
  <tr valign="top">
242
251
  <td
243
- ${g}="${t}"
252
+ ${b}="${t}"
244
253
  class="esd-block-text product-custom-attribute es-p0t es-p0b es-p15l es-p15r"
245
254
  align="center"
246
255
  esd-extension-block-id="${s.CUSTOM_ATTRIBUTE}">