@useinsider/guido 2.0.0-beta.1c331b8 → 2.0.0-beta.1f72712

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 (52) hide show
  1. package/dist/extensions/Blocks/Recommendation/block.js +40 -6
  2. package/dist/extensions/Blocks/Recommendation/constants/defaultConfig.js +64 -0
  3. package/dist/extensions/Blocks/Recommendation/constants/layout.js +9 -4
  4. package/dist/extensions/Blocks/Recommendation/constants/selectors.js +17 -9
  5. package/dist/extensions/Blocks/Recommendation/controls/cardComposition/index.js +48 -54
  6. package/dist/extensions/Blocks/Recommendation/controls/layout/index.js +42 -36
  7. package/dist/extensions/Blocks/Recommendation/controls/main/algorithm.js +51 -27
  8. package/dist/extensions/Blocks/Recommendation/controls/main/currency.js +146 -73
  9. package/dist/extensions/Blocks/Recommendation/controls/main/index.js +49 -35
  10. package/dist/extensions/Blocks/Recommendation/controls/main/locale.js +23 -13
  11. package/dist/extensions/Blocks/Recommendation/controls/main/productLayout.js +41 -41
  12. package/dist/extensions/Blocks/Recommendation/controls/main/shuffle.js +27 -16
  13. package/dist/extensions/Blocks/Recommendation/controls/main/utils.js +79 -67
  14. package/dist/extensions/Blocks/Recommendation/controls/omnibusDiscount/textAfter.js +1 -1
  15. package/dist/extensions/Blocks/Recommendation/controls/omnibusDiscount/textBefore.js +3 -3
  16. package/dist/extensions/Blocks/Recommendation/controls/spacing/index.js +85 -79
  17. package/dist/extensions/Blocks/Recommendation/recommendation.css.js +8 -4
  18. package/dist/extensions/Blocks/Recommendation/services/configService.js +239 -0
  19. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +30 -28
  20. package/dist/extensions/Blocks/Recommendation/templates/horizontal/elementRenderer.js +49 -25
  21. package/dist/extensions/Blocks/Recommendation/templates/horizontal/template.js +18 -19
  22. package/dist/extensions/Blocks/Recommendation/templates/utils.js +44 -101
  23. package/dist/extensions/Blocks/Recommendation/templates/vertical/elementRenderer.js +60 -35
  24. package/dist/extensions/Blocks/Recommendation/types/nodeConfig.js +6 -0
  25. package/dist/extensions/Blocks/Recommendation/utils/priceFormatter.js +29 -0
  26. package/dist/extensions/Blocks/Recommendation/utils/tagName.js +46 -0
  27. package/dist/extensions/Blocks/common-control.js +1 -1
  28. package/dist/node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js +214 -157
  29. package/dist/src/components/wrappers/WpDrawer.vue.d.ts +1 -1
  30. package/dist/src/extensions/Blocks/Recommendation/block.d.ts +34 -0
  31. package/dist/src/extensions/Blocks/Recommendation/constants/defaultConfig.d.ts +49 -0
  32. package/dist/src/extensions/Blocks/Recommendation/constants/index.d.ts +3 -2
  33. package/dist/src/extensions/Blocks/Recommendation/constants/layout.d.ts +10 -0
  34. package/dist/src/extensions/Blocks/Recommendation/constants/selectors.d.ts +12 -0
  35. package/dist/src/extensions/Blocks/Recommendation/controls/layout/index.d.ts +3 -1
  36. package/dist/src/extensions/Blocks/Recommendation/controls/main/algorithm.d.ts +13 -1
  37. package/dist/src/extensions/Blocks/Recommendation/controls/main/currency.d.ts +30 -1
  38. package/dist/src/extensions/Blocks/Recommendation/controls/main/index.d.ts +19 -0
  39. package/dist/src/extensions/Blocks/Recommendation/controls/main/locale.d.ts +9 -0
  40. package/dist/src/extensions/Blocks/Recommendation/controls/main/productLayout.d.ts +4 -2
  41. package/dist/src/extensions/Blocks/Recommendation/controls/main/shuffle.d.ts +8 -0
  42. package/dist/src/extensions/Blocks/Recommendation/controls/main/utils.d.ts +19 -0
  43. package/dist/src/extensions/Blocks/Recommendation/controls/spacing/index.d.ts +13 -2
  44. package/dist/src/extensions/Blocks/Recommendation/services/configService.d.ts +151 -0
  45. package/dist/src/extensions/Blocks/Recommendation/services/index.d.ts +6 -0
  46. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +1 -0
  47. package/dist/src/extensions/Blocks/Recommendation/types/index.d.ts +7 -0
  48. package/dist/src/extensions/Blocks/Recommendation/types/nodeConfig.d.ts +154 -0
  49. package/dist/src/extensions/Blocks/Recommendation/utils/priceFormatter.d.ts +33 -0
  50. package/dist/src/extensions/Blocks/Recommendation/utils/stylePreserver.d.ts +113 -0
  51. package/dist/src/extensions/Blocks/Recommendation/utils/tagName.d.ts +77 -0
  52. package/package.json +1 -1
@@ -1,61 +1,50 @@
1
- var P = Object.defineProperty;
2
- var O = (n, i, t) => i in n ? P(n, i, { enumerable: !0, configurable: !0, writable: !0, value: t }) : n[i] = t;
3
- var p = (n, i, t) => O(n, typeof i != "symbol" ? i + "" : i, t);
4
- import { ModificationDescription as d } from "../../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
5
- import { CommonControl as b } from "../../../common-control.js";
6
- import { useRecommendationExtensionStore as I } from "../../store/recommendation.js";
7
- import { getCurrentLayout as S } from "../main/utils.js";
8
- import { useDebounceFn as _ } from "../../../../../node_modules/@vueuse/shared/index.js";
9
- const y = "recommendation-spacing-control", a = {
1
+ var G = Object.defineProperty;
2
+ var I = (o, n, t) => n in o ? G(o, n, { enumerable: !0, configurable: !0, writable: !0, value: t }) : o[n] = t;
3
+ var p = (o, n, t) => I(o, typeof n != "symbol" ? n + "" : n, t);
4
+ import { ModificationDescription as g } from "../../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
5
+ import { CommonControl as L } from "../../../common-control.js";
6
+ import { SPACING_STEP as b, MAX_SPACING as y, MIN_SPACING as A, DEFAULT_COLUMN_SPACING as s, DEFAULT_ROW_SPACING as m } from "../../constants/layout.js";
7
+ import { RecommendationConfigService as d } from "../../services/configService.js";
8
+ import { useRecommendationExtensionStore as w } from "../../store/recommendation.js";
9
+ import { safeGetStyle as S, safeGetParent as V } from "../../utils/tagName.js";
10
+ import { getCurrentLayout as C } from "../main/utils.js";
11
+ import { useDebounceFn as O } from "../../../../../node_modules/@vueuse/shared/index.js";
12
+ const R = "recommendation-spacing-control", a = {
10
13
  COLUMN_SPACING: "columnSpacing",
11
14
  COLUMN_SPACING_LABEL: "columnSpacingLabel",
12
15
  ROW_SPACING: "rowSpacing"
13
- }, f = {
16
+ }, P = {
14
17
  COLUMN_SPACING: "data-column-spacing",
15
18
  ROW_SPACING: "data-row-spacing"
16
- }, s = {
17
- COLUMN_SPACING: 10,
18
- // 5px on each side = 10px total
19
- ROW_SPACING: 20
20
- }, u = {
21
- MIN_SPACING: 0,
22
- MAX_SPACING: 50,
23
- STEP: 5
24
19
  };
25
- function C(n, i) {
26
- return !n || !("getStyle" in n) ? null : n.getStyle(i);
20
+ function N(o, n) {
21
+ if (!o)
22
+ return n;
23
+ const t = parseFloat(o);
24
+ return Number.isNaN(t) ? n : t;
27
25
  }
28
- function G(n) {
29
- return !n || !("parent" in n) || typeof n.parent != "function" ? null : n.parent();
30
- }
31
- function m(n, i) {
32
- if (!n)
33
- return i;
34
- const t = parseFloat(n);
35
- return Number.isNaN(t) ? i : t;
36
- }
37
- class E extends b {
26
+ class $ extends L {
38
27
  constructor() {
39
28
  super(...arguments);
40
- p(this, "store", I());
29
+ p(this, "store", w());
41
30
  p(this, "unsubscribeOrientation", null);
42
31
  /**
43
32
  * Debounced version of _onColumnSpacingChange
44
33
  * Prevents excessive DOM updates when user rapidly adjusts the counter
45
34
  */
46
- p(this, "_debouncedOnColumnSpacingChange", _((t) => {
35
+ p(this, "_debouncedOnColumnSpacingChange", O((t) => {
47
36
  this._onColumnSpacingChange(t);
48
37
  }, 300));
49
38
  /**
50
39
  * Debounced version of _onRowSpacingChange
51
40
  * Prevents excessive DOM updates when user rapidly adjusts the counter
52
41
  */
53
- p(this, "_debouncedOnRowSpacingChange", _((t) => {
42
+ p(this, "_debouncedOnRowSpacingChange", O((t) => {
54
43
  this._onRowSpacingChange(t);
55
44
  }, 300));
56
45
  }
57
46
  getId() {
58
- return y;
47
+ return R;
59
48
  }
60
49
  getTemplate() {
61
50
  return `
@@ -64,16 +53,16 @@ class E extends b {
64
53
  this._GuLabel({ text: "Column Spacing (px)", name: a.COLUMN_SPACING_LABEL }),
65
54
  this._GuCounter({
66
55
  name: a.COLUMN_SPACING,
67
- minValue: u.MIN_SPACING,
68
- maxValue: u.MAX_SPACING,
69
- step: u.STEP
56
+ minValue: A,
57
+ maxValue: y,
58
+ step: b
70
59
  }),
71
60
  this._GuLabel({ text: "Row Spacing (px)" }),
72
61
  this._GuCounter({
73
62
  name: a.ROW_SPACING,
74
- minValue: u.MIN_SPACING,
75
- maxValue: u.MAX_SPACING,
76
- step: u.STEP
63
+ minValue: A,
64
+ maxValue: y,
65
+ step: b
77
66
  })
78
67
  ])}
79
68
  </div>
@@ -85,23 +74,30 @@ class E extends b {
85
74
  onTemplateNodeUpdated(t) {
86
75
  super.onTemplateNodeUpdated(t), this._setFormValues(), this._updateColumnSpacingVisibility();
87
76
  }
77
+ onDestroy() {
78
+ this.unsubscribeOrientation && (this.unsubscribeOrientation(), this.unsubscribeOrientation = null);
79
+ }
88
80
  /**
89
81
  * Updates column spacing visibility based on layout orientation
90
82
  * Column spacing is hidden for horizontal layout (products are stacked vertically)
91
- * Reads from store first for reliability, falls back to DOM
83
+ * Reads from node config first, falls back to store then DOM
92
84
  */
93
85
  _updateColumnSpacingVisibility() {
94
- const r = (this.store.recommendationConfigs.orientation || S(this.currentNode)) === "vertical";
95
- this.api.setVisibility(a.COLUMN_SPACING, r), this.api.setVisibility(a.COLUMN_SPACING_LABEL, r);
86
+ if (!this.api)
87
+ return;
88
+ const t = d.getConfig(this.currentNode), e = this.store.recommendationConfigs.orientation, i = (t.layout || e || C(this.currentNode)) === "vertical";
89
+ this.api.setVisibility(a.COLUMN_SPACING, i), this.api.setVisibility(a.COLUMN_SPACING_LABEL, i);
96
90
  }
97
91
  /**
98
- * Reads spacing values from data attributes or returns defaults
92
+ * Reads spacing values from node config first, falls back to DOM styles
99
93
  */
100
94
  _setFormValues() {
101
- const t = this._getStoredColumnSpacing(), e = this._getStoredRowSpacing();
95
+ if (!this.api)
96
+ return;
97
+ const t = d.getConfig(this.currentNode), e = t.columnSpacing !== s ? t.columnSpacing : this._getStoredColumnSpacing(), r = t.rowSpacing !== m ? t.rowSpacing : this._getStoredRowSpacing();
102
98
  this.api.updateValues({
103
- [a.COLUMN_SPACING]: t,
104
- [a.ROW_SPACING]: e
99
+ [a.COLUMN_SPACING]: e,
100
+ [a.ROW_SPACING]: r
105
101
  });
106
102
  }
107
103
  /**
@@ -112,28 +108,28 @@ class E extends b {
112
108
  */
113
109
  _getStoredColumnSpacing() {
114
110
  if (!this.currentNode)
115
- return s.COLUMN_SPACING;
116
- if (S(this.currentNode) === "vertical") {
117
- const o = this.currentNode.querySelector(".recommendation-attribute-row"), A = o == null ? void 0 : o.querySelector("td"), h = C(A, "padding");
118
- if (!h)
119
- return s.COLUMN_SPACING;
120
- const g = h.trim().split(/\s+/);
121
- return g.length < 2 ? s.COLUMN_SPACING : m(g[1], s.COLUMN_SPACING / 2) * 2;
111
+ return s;
112
+ if (C(this.currentNode) === "vertical") {
113
+ const l = this.currentNode.querySelector(".recommendation-attribute-row"), c = l == null ? void 0 : l.querySelector("td"), f = S(c, "padding");
114
+ if (!f)
115
+ return s;
116
+ const _ = f.trim().split(/\s+/);
117
+ return _.length < 2 ? s : N(_[1], s / 2) * 2;
122
118
  }
123
- const e = this.currentNode.querySelector(".product-card-wrapper"), r = G(e), l = C(r, "padding");
124
- if (!l)
125
- return s.COLUMN_SPACING;
126
- const c = l.trim().split(/\s+/);
127
- return c.length < 2 ? s.COLUMN_SPACING : m(c[1], s.COLUMN_SPACING / 2) * 2;
119
+ const e = this.currentNode.querySelector(".product-card-wrapper"), r = V(e), i = S(r, "padding");
120
+ if (!i)
121
+ return s;
122
+ const u = i.trim().split(/\s+/);
123
+ return u.length < 2 ? s : N(u[1], s / 2) * 2;
128
124
  }
129
125
  /**
130
126
  * Gets stored row spacing from the first spacer element's height style
131
127
  */
132
128
  _getStoredRowSpacing() {
133
129
  if (!this.currentNode)
134
- return s.ROW_SPACING;
135
- const t = this.currentNode.querySelector(".spacer"), e = C(t, "height");
136
- return m(e, s.ROW_SPACING);
130
+ return m;
131
+ const t = this.currentNode.querySelector(".spacer"), e = S(t, "height");
132
+ return N(e, m);
137
133
  }
138
134
  /**
139
135
  * Handles column spacing changes.
@@ -143,17 +139,22 @@ class E extends b {
143
139
  _onColumnSpacingChange(t) {
144
140
  if (!this.currentNode)
145
141
  return;
146
- this._storeColumnSpacing(t);
147
- const e = S(this.currentNode), r = this.api.getDocumentModifier(), c = `0 ${t / 2}px`;
148
- e === "vertical" ? Array.from(
142
+ d.updateConfig(
143
+ this.api,
144
+ this.currentNode,
145
+ { columnSpacing: t },
146
+ `Changed column spacing to ${t}px`
147
+ ), this._storeColumnSpacing(t);
148
+ const r = d.getConfig(this.currentNode).layout || C(this.currentNode), i = this.api.getDocumentModifier(), h = `0 ${t / 2}px`;
149
+ r === "vertical" ? Array.from(
149
150
  this.currentNode.querySelectorAll(".attribute-cell")
150
- ).forEach((o) => {
151
- r.modifyHtml(o).setStyle("padding", c);
151
+ ).forEach((c) => {
152
+ i.modifyHtml(c).setStyle("padding", h);
152
153
  }) : Array.from(
153
154
  this.currentNode.querySelectorAll(".product-card-wrapper")
154
- ).forEach((o) => {
155
- "parent" in o && o.parent() && r.modifyHtml(o.parent()).setStyle("padding", c);
156
- }), r.apply(new d(`Update column spacing to ${t}px`));
155
+ ).forEach((c) => {
156
+ "parent" in c && c.parent() && i.modifyHtml(c.parent()).setStyle("padding", h);
157
+ }), i.apply(new g(`Update column spacing to ${t}px`));
157
158
  }
158
159
  /**
159
160
  * Handles row spacing changes
@@ -162,16 +163,21 @@ class E extends b {
162
163
  _onRowSpacingChange(t) {
163
164
  if (!this.currentNode)
164
165
  return;
165
- this._storeRowSpacing(t);
166
+ d.updateConfig(
167
+ this.api,
168
+ this.currentNode,
169
+ { rowSpacing: t },
170
+ `Changed row spacing to ${t}px`
171
+ ), this._storeRowSpacing(t);
166
172
  const e = Array.from(
167
173
  this.currentNode.querySelectorAll(".spacer")
168
174
  );
169
175
  if (!e.length)
170
176
  return;
171
- const r = this.api.getDocumentModifier(), l = `${t}px`;
172
- e.forEach((c) => {
173
- r.modifyHtml(c).setStyle("height", l);
174
- }), r.apply(new d(`Update row spacing to ${t}px`));
177
+ const r = this.api.getDocumentModifier(), i = `${t}px`;
178
+ e.forEach((u) => {
179
+ r.modifyHtml(u).setStyle("height", i);
180
+ }), r.apply(new g(`Update row spacing to ${t}px`));
175
181
  }
176
182
  /**
177
183
  * Stores column spacing value in block data attribute
@@ -180,7 +186,7 @@ class E extends b {
180
186
  if (!this.currentNode)
181
187
  return;
182
188
  const e = this.currentNode.querySelector(".ins-recommendation-v3-block-v2");
183
- e && this.api.getDocumentModifier().modifyHtml(e).setAttribute(f.COLUMN_SPACING, t.toString()).apply(new d("Store column spacing"));
189
+ e && this.api.getDocumentModifier().modifyHtml(e).setAttribute(P.COLUMN_SPACING, t.toString()).apply(new g("Store column spacing"));
184
190
  }
185
191
  /**
186
192
  * Stores row spacing value in block data attribute
@@ -189,7 +195,7 @@ class E extends b {
189
195
  if (!this.currentNode)
190
196
  return;
191
197
  const e = this.currentNode.querySelector(".ins-recommendation-v3-block-v2");
192
- e && this.api.getDocumentModifier().modifyHtml(e).setAttribute(f.ROW_SPACING, t.toString()).apply(new d("Store row spacing"));
198
+ e && this.api.getDocumentModifier().modifyHtml(e).setAttribute(P.ROW_SPACING, t.toString()).apply(new g("Store row spacing"));
193
199
  }
194
200
  _listenToFormUpdates() {
195
201
  this.api.onValueChanged(a.COLUMN_SPACING, (t) => {
@@ -211,6 +217,6 @@ class E extends b {
211
217
  }
212
218
  }
213
219
  export {
214
- y as SPACING_CONTROL_ID,
215
- E as SpacingControl
220
+ R as SPACING_CONTROL_ID,
221
+ $ as SpacingControl
216
222
  };
@@ -1,15 +1,19 @@
1
1
  const n = `/* Utils */
2
2
  .es-180w { width: 180px; }
3
3
 
4
- /* Recommendation Controls */
5
- .recommendation-controls-container > .container {
6
- padding: 16px;
4
+ /* Recommendation Controls - Main settings panel separators */
5
+ .recommendation-controls-container > div {
6
+ border-bottom: 1px solid #e0e0e0;
7
7
  }
8
8
 
9
- .recommendation-controls-container > .container:first-child {
9
+ .recommendation-controls-container > div:first-child {
10
10
  padding-top: 0;
11
11
  }
12
12
 
13
+ .recommendation-controls-container > div:last-child {
14
+ border-bottom: none;
15
+ }
16
+
13
17
  .container:has(.recommendation-controls-container) {
14
18
  padding: 0
15
19
  }
@@ -0,0 +1,239 @@
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 y {
6
+ // ========================================================================
7
+ // Read Operations
8
+ // ========================================================================
9
+ /**
10
+ * Get configuration from a node, with defaults for missing values
11
+ *
12
+ * This is the primary way to read configuration from a block.
13
+ * Always returns a complete config object with defaults merged in.
14
+ * @example
15
+ * const config = RecommendationConfigService.getConfig(this.currentNode);
16
+ * console.log(config.strategy); // 'mostPopular'
17
+ * @param node - The immutable HTML node to read config from
18
+ * @returns Complete configuration with defaults for missing values
19
+ */
20
+ static getConfig(i) {
21
+ if (!i)
22
+ return this.cloneDefaults();
23
+ if (!("getNodeConfig" in i) || typeof i.getNodeConfig != "function")
24
+ return this.cloneDefaults();
25
+ try {
26
+ const t = i.getNodeConfig();
27
+ return t ? this.mergeWithDefaults(t) : this.cloneDefaults();
28
+ } catch {
29
+ return this.cloneDefaults();
30
+ }
31
+ }
32
+ /**
33
+ * Check if a node has valid configuration stored
34
+ *
35
+ * Used for migration detection - returns false for legacy templates
36
+ * that need their configuration migrated.
37
+ * @param node - The immutable HTML node to check
38
+ * @returns True if node has valid config with version number
39
+ */
40
+ static hasConfig(i) {
41
+ if (!i || !("getNodeConfig" in i) || typeof i.getNodeConfig != "function")
42
+ return !1;
43
+ try {
44
+ const t = i.getNodeConfig();
45
+ return a(t);
46
+ } catch {
47
+ return !1;
48
+ }
49
+ }
50
+ /**
51
+ * Get the configuration version from a node
52
+ * @param node - The immutable HTML node to check
53
+ * @returns Config version number, or 0 if no config exists
54
+ */
55
+ static getConfigVersion(i) {
56
+ return this.hasConfig(i) && this.getConfig(i).configVersion || 0;
57
+ }
58
+ // ========================================================================
59
+ // Write Operations
60
+ // ========================================================================
61
+ /**
62
+ * Update specific configuration values
63
+ *
64
+ * Merges the updates with existing config and persists to node.
65
+ * This is the primary way to update configuration from controls.
66
+ * @example
67
+ * RecommendationConfigService.updateConfig(
68
+ * this.api,
69
+ * this.currentNode,
70
+ * { strategy: 'complementaryItems' },
71
+ * 'Changed recommendation algorithm'
72
+ * );
73
+ * @param api - Stripo extension API with document modifier
74
+ * @param node - The immutable HTML node to update
75
+ * @param updates - Partial config with values to update
76
+ * @param description - Human-readable description for undo/redo
77
+ * @returns The new complete configuration
78
+ */
79
+ static updateConfig(i, t, o, n) {
80
+ const s = this.getConfig(t), c = this.deepMerge(s, o);
81
+ return this.saveConfig(i, t, c, n), c;
82
+ }
83
+ /**
84
+ * Initialize configuration for a new block
85
+ *
86
+ * Called when a block is first created (dropped into template).
87
+ * Can optionally merge in partial config from migration.
88
+ * @example
89
+ * // In Block.onCreated lifecycle
90
+ * RecommendationConfigService.initializeConfig(this.api, node);
91
+ * @param api - Stripo extension API with document modifier
92
+ * @param node - The immutable HTML node to initialize
93
+ * @param partialConfig - Optional partial config to merge with defaults
94
+ * @returns The initialized configuration
95
+ */
96
+ static initializeConfig(i, t, o) {
97
+ const n = o ? this.mergeWithDefaults(o) : this.cloneDefaults();
98
+ return this.saveConfig(i, t, n, "Initialize recommendation block"), g({
99
+ currentNode: t,
100
+ documentModifier: i.getDocumentModifier(),
101
+ currency: n.currency
102
+ }), n;
103
+ }
104
+ /**
105
+ * Save complete configuration to a node
106
+ *
107
+ * Low-level method - prefer `updateConfig` or `initializeConfig` in most cases.
108
+ * @param api - Stripo extension API with document modifier
109
+ * @param node - The immutable HTML node to save to
110
+ * @param config - Complete configuration to save
111
+ * @param description - Human-readable description for undo/redo
112
+ */
113
+ static saveConfig(i, t, o, n) {
114
+ try {
115
+ i.getDocumentModifier().modifyHtml(t).setNodeConfig(o).apply(new f(n));
116
+ } catch (s) {
117
+ console.warn("[RecommendationConfigService] Failed to save config:", s);
118
+ }
119
+ }
120
+ // ========================================================================
121
+ // Migration Helpers
122
+ // ========================================================================
123
+ /**
124
+ * Migrate configuration from legacy data-attributes
125
+ *
126
+ * Reads existing data-attributes and creates a proper node config.
127
+ * Used when loading templates created before node config was implemented.
128
+ * @param api - Stripo extension API with document modifier
129
+ * @param node - The block node to migrate
130
+ * @returns The migrated configuration
131
+ */
132
+ static migrateFromDataAttributes(i, t) {
133
+ const o = {
134
+ configVersion: u
135
+ };
136
+ if ("getAttribute" in t && typeof t.getAttribute == "function") {
137
+ const n = t.getAttribute("data-layout");
138
+ (n === "horizontal" || n === "vertical") && (o.layout = n);
139
+ const s = t.getAttribute("data-card-composition");
140
+ s && (o.composition = s.split(",").filter(Boolean));
141
+ const c = t.getAttribute("data-column-spacing");
142
+ c && (o.columnSpacing = parseInt(c) || e.columnSpacing);
143
+ const r = t.getAttribute("data-row-spacing");
144
+ r && (o.rowSpacing = parseInt(r) || e.rowSpacing);
145
+ }
146
+ return this.initializeConfig(i, t, o);
147
+ }
148
+ /**
149
+ * Check if configuration needs migration
150
+ * @param node - The block node to check
151
+ * @returns True if migration is needed
152
+ */
153
+ static needsMigration(i) {
154
+ return i ? this.hasConfig(i) ? this.getConfigVersion(i) < u : !0 : !1;
155
+ }
156
+ // ========================================================================
157
+ // Internal Helpers
158
+ // ========================================================================
159
+ /**
160
+ * Clone default config to avoid mutations
161
+ */
162
+ static cloneDefaults() {
163
+ return {
164
+ ...e,
165
+ currency: { ...e.currency },
166
+ omnibusPrice: { ...e.omnibusPrice },
167
+ omnibusDiscount: { ...e.omnibusDiscount },
168
+ composition: [...e.composition],
169
+ visibility: { ...e.visibility },
170
+ filters: [],
171
+ productIds: []
172
+ };
173
+ }
174
+ /**
175
+ * Merge partial config with defaults
176
+ *
177
+ * Deep merges nested objects like currency, omnibus settings.
178
+ */
179
+ static mergeWithDefaults(i) {
180
+ const t = this.cloneDefaults();
181
+ return {
182
+ ...t,
183
+ ...i,
184
+ currency: {
185
+ ...t.currency,
186
+ ...i.currency || {}
187
+ },
188
+ omnibusPrice: {
189
+ ...t.omnibusPrice,
190
+ ...i.omnibusPrice || {}
191
+ },
192
+ omnibusDiscount: {
193
+ ...t.omnibusDiscount,
194
+ ...i.omnibusDiscount || {}
195
+ },
196
+ visibility: {
197
+ ...t.visibility,
198
+ ...i.visibility || {}
199
+ },
200
+ composition: i.composition || t.composition,
201
+ filters: i.filters || [],
202
+ productIds: i.productIds || []
203
+ };
204
+ }
205
+ /**
206
+ * Deep merge for nested objects
207
+ *
208
+ * Recursively merges source into target, preserving existing values
209
+ * that aren't explicitly overwritten.
210
+ */
211
+ static deepMerge(i, t) {
212
+ return {
213
+ ...i,
214
+ ...t,
215
+ currency: {
216
+ ...i.currency,
217
+ ...t.currency || {}
218
+ },
219
+ omnibusPrice: {
220
+ ...i.omnibusPrice,
221
+ ...t.omnibusPrice || {}
222
+ },
223
+ omnibusDiscount: {
224
+ ...i.omnibusDiscount,
225
+ ...t.omnibusDiscount || {}
226
+ },
227
+ visibility: {
228
+ ...i.visibility,
229
+ ...t.visibility || {}
230
+ },
231
+ composition: t.composition ?? i.composition,
232
+ filters: t.filters ?? i.filters,
233
+ productIds: t.productIds ?? i.productIds
234
+ };
235
+ }
236
+ }
237
+ export {
238
+ y as RecommendationConfigService
239
+ };
@@ -1,14 +1,14 @@
1
- import { RecommendationFeedSourceMaps as u, PriceAttributes as c } from "../../../../enums/extensions/recommendationBlock.js";
2
- import { useRecommendationApi as p } from "../../../../services/recommendationApi.js";
3
- import { useConfigStore as m } from "../../../../stores/config.js";
4
- import { defineStore as f } from "pinia";
5
- import { DEFAULT_CARDS_IN_ROW as d } from "../constants/layout.js";
6
- import { generateCompleteFilterQuery as l } from "../utils/filterUtil.js";
7
- const s = p(), g = () => ({
1
+ import { RecommendationFeedSourceMaps as l, PriceAttributes as p } from "../../../../enums/extensions/recommendationBlock.js";
2
+ import { useRecommendationApi as m } from "../../../../services/recommendationApi.js";
3
+ import { useConfigStore as f } from "../../../../stores/config.js";
4
+ import { defineStore as d } from "pinia";
5
+ import { DEFAULT_CARDS_IN_ROW as g } from "../constants/layout.js";
6
+ import { generateCompleteFilterQuery as c } from "../utils/filterUtil.js";
7
+ const s = m(), h = () => ({
8
8
  recommendationCampaignUrls: {},
9
9
  recommendationProducts: [],
10
10
  recommendationConfigs: {
11
- cardsInRow: d,
11
+ cardsInRow: g,
12
12
  currencySettings: {
13
13
  name: "USD",
14
14
  value: "USD",
@@ -135,8 +135,8 @@ const s = p(), g = () => ({
135
135
  filterSelectionDrawerStatus: !1,
136
136
  filterList: {},
137
137
  filterGroup: 1
138
- }), S = f("guidoRecommendationExtension", {
139
- state: () => g(),
138
+ }), R = d("guidoRecommendationExtension", {
139
+ state: () => h(),
140
140
  getters: {
141
141
  hasFilters: (e) => !!e.recommendationConfigs.filters.length,
142
142
  getFilterGroupCount: (e) => {
@@ -146,7 +146,7 @@ const s = p(), g = () => ({
146
146
  getActivePredictiveAlgorithms: (e) => {
147
147
  const t = [];
148
148
  return e.activePredictiveAlgorithms.forEach((r) => {
149
- t.push(...u.filter((i) => i.id === r));
149
+ t.push(...l.filter((i) => i.id === r));
150
150
  }), t.map((r) => ({
151
151
  text: r.name,
152
152
  value: r.key
@@ -161,7 +161,7 @@ const s = p(), g = () => ({
161
161
  value: t.text
162
162
  })),
163
163
  getFilterList: (e) => Object.values(e.filterList).map((t) => {
164
- const r = t.type === "defaultAttribute", i = c.includes(t.attributeName);
164
+ const r = t.type === "defaultAttribute", i = p.includes(t.attributeName);
165
165
  let o = r ? t.attributeName : `product_attributes.${t.attributeName}`;
166
166
  return o = i ? `${o}.${e.recommendationConfigs.currencySettings.value}` : o, {
167
167
  text: t.displayName,
@@ -241,30 +241,32 @@ const s = p(), g = () => ({
241
241
  });
242
242
  },
243
243
  generateFilterQuery() {
244
- return l(this.recommendationConfigs.filters);
244
+ return c(this.recommendationConfigs.filters);
245
245
  },
246
246
  async fetchRecommendationProducts() {
247
- var a;
248
- const e = this.recommendationConfigs.filters.filter((n) => n.isValid), t = l(e), r = ((a = u.find((n) => n.key === this.recommendationConfigs.strategy)) == null ? void 0 : a.path) || "", i = m(), o = await s.fetchRecommendationProducts(
247
+ var u;
248
+ const e = this.recommendationConfigs.filters.filter((n) => n.isValid), t = c(e), r = ((u = l.find((n) => n.key === this.recommendationConfigs.strategy)) == null ? void 0 : u.path) || "", i = f(), o = {
249
+ // Note: {itemId} and {campaignId} are template placeholders replaced at runtime
250
+ locale: this.recommendationConfigs.language,
251
+ currency: this.recommendationConfigs.currencySettings.value,
252
+ partnerName: i.partnerName,
253
+ productId: this.recommendationConfigs.strategy === "manualMerchandising" ? this.recommendationConfigs.productIds.join(",") : "{itemId}",
254
+ size: this.recommendationConfigs.size,
255
+ details: !0,
256
+ campaignId: "{campaignId}",
257
+ filter: t
258
+ };
259
+ this.recommendationConfigs.shuffleProducts && (o.shuffle = !0);
260
+ const a = await s.fetchRecommendationProducts(
249
261
  r,
250
- {
251
- // TODO: Here will be optimized and filled carefully
252
- locale: this.recommendationConfigs.language,
253
- currency: this.recommendationConfigs.currencySettings.value,
254
- partnerName: i.partnerName,
255
- productId: this.recommendationConfigs.strategy === "manualMerchandising" ? this.recommendationConfigs.productIds.join(",") : "{itemId}",
256
- size: this.recommendationConfigs.size,
257
- details: !0,
258
- campaignId: "{campaignId}",
259
- filter: t
260
- }
262
+ o
261
263
  );
262
264
  this.$patch({
263
- recommendationProducts: o
265
+ recommendationProducts: a
264
266
  });
265
267
  }
266
268
  }
267
269
  });
268
270
  export {
269
- S as useRecommendationExtensionStore
271
+ R as useRecommendationExtensionStore
270
272
  };