PageSourceSearch

https://www.websupport.sk/app/plugins/webikon-loopia-generic-platf…wlgpi-macaly-prompt.js?ver=1.0.0.1789825243

js websupport.sk collected 2026-09-24 15:25:44 UTC 8,161 bytes, 281 lines download raw bytes

1/**
2 * Shared Macaly AI builder prompt box.
3 *
4 * Registers the `wlgpiMacalyPrompt` Alpine component used by every theme that
5 * renders a Macaly prompt. It keeps the typed prompt in localStorage so it
6 * survives navigation, lets idea badges prefill it and adds the AI builder
7 * product to the cart with the prompt appended as an item property.
8 *
9 * Themes that render the shared add-to-cart button (which carries its own
10 * `x-on:click="...addItem(...)"`) are supported through a capture phase click
11 * listener: it pre-empts that handler and calls the cart store with
12 * `properties.prompt` added, parsing product / properties / period back out of
13 * the button's own expression instead of duplicating them. Mutating the
14 * attribute (the configurator's approach) is deliberately avoided: Alpine only
15 * rebinds on the next microtask and retains every compiled expression, so
16 * rewriting a long prompt into the attribute leaks memory on each edit.
17 *
18 * @package Wlgpi
19 */
20
21/**
22 * Matches control characters the Macaly API rejects. Tab, line feed and
23 * carriage return are explicitly allowed and therefore excluded.
24 *
25 * @type {RegExp}
26 */
27// eslint-disable-next-line no-control-regex -- stripping control characters is the point
28const DISALLOWED_CONTROL_CHARS =
29  /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g;
30
31/**
32 * Matches the `x-on:click` expression the add-to-cart button renders, capturing
33 * the product slug, the properties JSON and the period.
34 *
35 * @type {RegExp}
36 */
37const ADD_ITEM_EXPRESSION =
38  /addItem\(\s*'([^']*)'\s*,\s*'(\{.*\})'\s*,\s*'([^']*)'\s*\)/;
39
40/** @type {number} Fallback used when the localized config is missing. */
41const DEFAULT_MAX_LENGTH = 5000;
42
43/** @type {string} Fallback used when the localized config is missing. */
44const DEFAULT_STORAGE_KEY = "macaly_prompt";
45
46/**
47 * Localized plugin config, tolerating themes that do not enqueue it.
48 *
49 * @return {Object} Config object, empty when unavailable.
50 */
51const promptConfig = () => window.wlgpiMacalyPromptConfig || {};
52
53/**
54 * Read a value from localStorage, tolerating restricted browser contexts.
55 *
56 * @param {string} key Storage key.
57 * @return {string} Stored value or an empty string.
58 */
59const readStorage = (key) => {
60  try {
61    return window.localStorage.getItem(key) || "";
62  } catch (error) {
63    return "";
64  }
65};
66
67/**
68 * Persist a value to localStorage, tolerating restricted browser contexts.
69 *
70 * @param {string} key   Storage key.
71 * @param {string} value Value to store.
72 * @return {void}
73 */
74const writeStorage = (key, value) => {
75  try {
76    window.localStorage.setItem(key, value);
77  } catch (error) {
78    // Storage is unavailable (private mode, blocked cookies), skip persisting.
79  }
80};
81
82/**
83 * Read the add-to-cart arguments back out of a CTA's Alpine expression.
84 *
85 * @param {HTMLElement} cta Add-to-cart button.
86 * @return {?{product: string, properties: Object, period: string}} Parsed
87 *   arguments, or null when the expression is missing or unrecognised.
88 */
89const readAddToCartArguments = (cta) => {
90  const expression =
91    cta.getAttribute("x-on:click") || cta.getAttribute("@click") || "";
92  const match = expression.match(ADD_ITEM_EXPRESSION);
93
94  if (!match) {
95    return null;
96  }
97
98  try {
99    return {
100      product: match[1],
101      properties: JSON.parse(match[2]),
102      period: match[3],
103    };
104  } catch (error) {
105    return null;
106  }
107};
108
109/**
110 * Alpine component factory.
111 *
112 * Options may be passed inline (`x-data="wlgpiMacalyPrompt({ product: '...' })"`)
113 * or, preferably, as `data-product`, `data-period` and `data-storage-key` on
114 * the root element with the cart item properties in a child
115 * `<script type="application/json" class="js-macaly-prompt-properties">`.
116 *
117 * @param {Object} [options] Inline options, taking precedence over the dataset.
118 * @return {Object} Alpine component definition.
119 */
120const wlgpiMacalyPrompt = (options = {}) => ({
121  prompt: "",
122  product: "",
123  period: "1",
124  properties: {},
125  storageKey: DEFAULT_STORAGE_KEY,
126  maxLength: DEFAULT_MAX_LENGTH,
127
128  init() {
129    const data = this.$el.dataset;
130    const config = promptConfig();
131
132    this.storageKey =
133      options.storageKey ||
134      data.storageKey ||
135      config.storage_key ||
136      DEFAULT_STORAGE_KEY;
137    this.maxLength =
138      Number(options.maxLength || data.maxLength || config.max_length) ||
139      DEFAULT_MAX_LENGTH;
140    this.product = options.product || data.product || "";
141    this.period = options.period || data.period || "1";
142    this.properties = options.properties || this.readProperties();
143
144    this.prompt = readStorage(this.storageKey).slice(0, this.maxLength);
145
146    this.bindCta();
147  },
148
149  /**
150   * Read the cart item properties for the add-to-cart call.
151   *
152   * The `data-properties` attribute is the primary source: block markup on some
153   * platforms passes through wptexturize(), which corrupts JSON inside a
154   * <script> tag with smart quotes. The JSON script child stays as a fallback.
155   *
156   * @return {Object} Properties, empty when absent or malformed.
157   */
158  readProperties() {
159    if (this.$el.dataset.properties) {
160      try {
161        return JSON.parse(this.$el.dataset.properties);
162      } catch (error) {
163        // Fall through to the JSON script child.
164      }
165    }
166
167    const script = this.$el.querySelector(".js-macaly-prompt-properties");
168
169    try {
170      return script ? JSON.parse(script.textContent || "{}") : {};
171    } catch (error) {
172      return {};
173    }
174  },
175
176  /**
177   * Set the prompt, respecting the maximum length, and persist it.
178   *
179   * @param {string} value Prompt text.
180   * @return {void}
181   */
182  setPrompt(value) {
183    this.prompt = (value || "").slice(0, this.maxLength);
184    this.persist();
185  },
186
187  /**
188   * Persist the current prompt.
189   *
190   * @return {void}
191   */
192  persist() {
193    writeStorage(this.storageKey, (this.prompt || "").slice(0, this.maxLength));
194  },
195
196  /**
197   * Strip the control characters the Macaly API rejects and trim the result.
198   *
199   * @param {string} [value] Raw value, defaults to the current prompt.
200   * @return {string} Value safe to send as the `prompt` property.
201   */
202  sanitize(value) {
203    return (value ?? this.prompt ?? "")
204      .replace(DISALLOWED_CONTROL_CHARS, "")
205      .trim();
206  },
207
208  /**
209   * Add the product to the cart with the prompt appended as an item property.
210   *
211   * @param {Object} [overrides] Per-call product, properties and period.
212   * @return {?Promise} Cart store promise, or null when the store is missing.
213   */
214  addToCart(overrides = {}) {
215    this.persist();
216
217    const cart = window.Alpine ? window.Alpine.store("cart") : null;
218
219    if (!cart) {
220      return null;
221    }
222
223    const prompt = this.sanitize();
224    const properties = {
225      ...this.properties,
226      ...(overrides.properties || {}),
227      // An empty prompt must not send the property at all.
228      ...(prompt ? { prompt } : {}),
229    };
230
231    return cart.addItem(
232      overrides.product || this.product,
233      JSON.stringify(properties),
234      overrides.period ?? this.period
235    );
236  },
237
238  /**
239   * Intercept the shared add-to-cart button rendered inside the prompt box.
240   *
241   * Capture phase on the root, so this runs before the button's own Alpine
242   * click handler and can replace it. Anything unexpected (no prompt,
243   * unparsable expression, cart store missing) falls through to that handler.
244   *
245   * @return {void}
246   */
247  bindCta() {
248    this.$el.addEventListener(
249      "click",
250      (event) => {
251        const cta = event.target.closest(".js-macaly-prompt-cta");
252
253        if (!cta || !this.$el.contains(cta)) {
254          return;
255        }
256
257        // An empty prompt leaves the button's own handler in charge.
258        if (!this.sanitize()) {
259          return;
260        }
261
262        const args = readAddToCartArguments(cta);
263        const cart = window.Alpine ? window.Alpine.store("cart") : null;
264
265        if (!args || !cart) {
266          return;
267        }
268
269        event.stopImmediatePropagation();
270        event.preventDefault();
271
272        this.addToCart(args);
273      },
274      true
275    );
276  },
277});
278
279document.addEventListener("alpine:init", () =>
280  window.Alpine.data("wlgpiMacalyPrompt", wlgpiMacalyPrompt)
281);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.