@useinsider/guido 3.18.2-beta.ec25841 → 3.18.2-beta.f42547e

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.
@@ -4,13 +4,14 @@ import { useToaster as C } from "../composables/useToaster.js";
4
4
  import { useTranslations as h } from "../composables/useTranslations.js";
5
5
  import { useLiquidValidator as S } from "../composables/validators/useLiquidValidator.js";
6
6
  import { EditorType as v } from "../enums/defaults.js";
7
- import { ToasterTypeOptions as N } from "../enums/toaster.js";
8
- import { useRecommendationExtensionStore as x } from "../extensions/Blocks/Recommendation/store/recommendation.js";
9
- import { useSaveAsTemplateStore as E } from "../stores/save-as-template.js";
7
+ import { ToasterTypeOptions as x } from "../enums/toaster.js";
8
+ import { useRecommendationExtensionStore as E } from "../extensions/Blocks/Recommendation/store/recommendation.js";
9
+ import { useSaveAsTemplateStore as N } from "../stores/save-as-template.js";
10
10
  import { base64EncodeWithSpecialChars as r } from "../utils/base64.js";
11
+ import { isPlainObject as P } from "../utils/genericUtil.js";
11
12
  import { useTemplatePreparation as A } from "../utils/templatePreparation.js";
12
- const j = () => {
13
- const { get: n, post: m } = b(), { handleError: o, showToaster: l } = C(), { config: c, isFeatureEnabled: p } = w(), { validateLiquidSyntax: g } = S(), u = h();
13
+ const B = () => {
14
+ const { get: n, post: m } = b(), { handleError: o, showToaster: l } = C(), { config: c, isFeatureEnabled: p } = w(), { validateLiquidSyntax: f } = S(), u = h();
14
15
  return {
15
16
  getCategories: async () => {
16
17
  try {
@@ -40,10 +41,10 @@ const j = () => {
40
41
  createTemplate: async () => {
41
42
  var e, t, d, y;
42
43
  try {
43
- const { prepareTemplateDetails: i } = A(), s = E(), a = await i();
44
- if (p("liquidSyntax") && !await g(a.compiledHtml))
44
+ const { prepareTemplateDetails: i } = A(), s = N(), a = await i();
45
+ if (p("liquidSyntax") && !await f(a.compiledHtml))
45
46
  return !1;
46
- const f = {
47
+ const T = {
47
48
  name: s.getTemplateName,
48
49
  categories: s.getSelectedCategoryIds,
49
50
  productId: ((t = (e = c.value) == null ? void 0 : e.partner) == null ? void 0 : t.productType) || 0,
@@ -54,7 +55,7 @@ const j = () => {
54
55
  unsubscriptionPreferencePageStatus: !1,
55
56
  unsubscriptionPreferencePages: [],
56
57
  recommendationCampaignUrls: r(
57
- x().recommendationCampaignUrls
58
+ E().recommendationCampaignUrls
58
59
  ),
59
60
  recommendationConfigs: r({}),
60
61
  isGuido: !0,
@@ -66,16 +67,18 @@ const j = () => {
66
67
  css: a.css,
67
68
  dynamicContentList: a.dynamicContentList
68
69
  })
69
- }, { data: { type: T } } = await m(
70
+ }, { data: g } = await m(
70
71
  "/newsletter/template-library/create-template",
71
- f
72
+ T
72
73
  );
73
- return T === "alert" ? (l({
74
+ if (!P(g))
75
+ throw new Error("Unexpected create-template response");
76
+ return g.type === "alert" ? (l({
74
77
  message: u(
75
78
  "newsletter.template-library-save-alert",
76
79
  { templateName: s.getTemplateName }
77
80
  ),
78
- type: N.Error
81
+ type: x.Error
79
82
  }), !1) : (l({
80
83
  message: u(
81
84
  "newsletter.template-library-save-success",
@@ -90,5 +93,5 @@ const j = () => {
90
93
  };
91
94
  };
92
95
  export {
93
- j as useTemplateLibraryApi
96
+ B as useTemplateLibraryApi
94
97
  };
@@ -107,7 +107,7 @@ export interface RegenerateWithStylesOptions extends Omit<RegenerateProductRowsO
107
107
  * HTML removes the need for any post-rebuild reapply pass.
108
108
  * @param options - Configuration options for regeneration
109
109
  */
110
- export declare function regenerateProductRowsWithStyles(options: RegenerateWithStylesOptions): void;
110
+ export declare function regenerateProductRowsWithStyles(options: RegenerateWithStylesOptions): boolean;
111
111
  /**
112
112
  * Adjusts a products array to a target size.
113
113
  * Used by ProductLayoutControl to synchronously match products to the new size
@@ -151,6 +151,11 @@ export interface UpdateProductContentInPlaceOptions {
151
151
  currentNode: ImmutableHtmlNode | null | undefined;
152
152
  documentModifier: DocumentModifier;
153
153
  products: RecommendationProduct[];
154
+ /**
155
+ * Called when the batch was actually committed. Identical products commit nothing, which
156
+ * leaves anything a caller staged into the same batch unwritten unless it applies itself.
157
+ */
158
+ onApplied?: () => void;
154
159
  }
155
160
  /**
156
161
  * Updates product content in-place without regenerating HTML structure.
@@ -161,7 +166,7 @@ export interface UpdateProductContentInPlaceOptions {
161
166
  * After updating desktop, also updates mobile container to keep them in sync.
162
167
  *
163
168
  * Falls back to false when product count changed (need to add/remove elements).
164
- * @param options - Configuration options
169
+ * @param options - Configuration options; `onApplied` fires only when a batch was committed
165
170
  * @returns true if in-place update was successful, false to fall back to full regeneration
166
171
  */
167
172
  export declare function updateProductContentInPlace(options: UpdateProductContentInPlaceOptions): boolean;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Writes a strategy change and its products straight to the document, without the settings panel.
3
+ *
4
+ * Every entry point is host-rendered UI outside `ui-editor`, and none of them can rely on the
5
+ * panel control: opening the strategy page deselects the block, so by the time the user comes back
6
+ * and presses Assign the control — and with it the subscription that does the node write, the
7
+ * product fetch and the repaint — is gone. The assignment then reaches Pinia and nothing else
8
+ * (SD-149916).
9
+ *
10
+ * Call AFTER the store change (`assignStrategy` / `removeStrategy`).
11
+ * @param description - labels the undo entry
12
+ * @returns whether the write reached a node
13
+ */
14
+ export declare function commitStrategyChange(blockId: number, description: string): Promise<boolean>;
@@ -30,3 +30,9 @@ export declare const replaceString: (string?: string, values?: Record<string, st
30
30
  * a string nor a number becomes ''.
31
31
  */
32
32
  export declare const asText: (value: unknown) => string;
33
+ /**
34
+ * Narrows an unknown API value to a plain JSON object. Arrays and exotic
35
+ * objects (Blob, Date, FormData) are rejected, so a response body that useHttp
36
+ * parsed as text or blob can never be read as a JSON payload.
37
+ */
38
+ export declare const isPlainObject: <T>(value: T) => value is T & Record<string, unknown>;
@@ -1,69 +1,75 @@
1
- const o = (t) => {
2
- const a = t.value.match(/\{\{([^}]+)\}\}/)[1].split("|").map((l) => l.trim()), [s] = a, r = {
3
- text: t.label,
1
+ const o = (a) => {
2
+ const t = a.value.match(/\{\{([^}]+)\}\}/)[1].split("|").map((e) => e.trim()), [s] = t, r = {
3
+ text: a.label,
4
4
  value: s || ""
5
5
  };
6
- if (a.length >= 2) {
7
- const [, l, e] = a;
8
- if (l.startsWith("default:")) {
9
- let n = l.slice(8).trim();
6
+ if (t.length >= 2) {
7
+ const [, e, l] = t;
8
+ if (e.startsWith("default:")) {
9
+ let n = e.slice(8).trim();
10
10
  n.startsWith('"') && n.endsWith('"') && (n = n.slice(1, -1)), r.fallback = n;
11
11
  } else {
12
- const n = l.includes("=") ? { key: l.split("=")[0].trim(), value: l.split("=")[1].trim() } : null;
13
- n ? r.format = n : e || (r.fallback = l), e && (r.fallback = e);
12
+ const n = e.includes("=") ? { key: e.split("=")[0].trim(), value: e.split("=")[1].trim() } : null;
13
+ n ? r.format = n : l || (r.fallback = e), l && (r.fallback = l);
14
14
  }
15
15
  }
16
16
  return r;
17
- }, f = (t, c = !1) => t.map((a) => {
18
- if (a.format)
17
+ }, u = (a, c = !1) => a.map((t) => {
18
+ if (t.format)
19
19
  return {
20
- label: `${a.text} | ${a.format.key}=${a.format.value}`,
21
- value: `{{${a.value}|${a.format.key}=${a.format.value}}}`
20
+ label: `${t.text} | ${t.format.key}=${t.format.value}`,
21
+ value: `{{${t.value}|${t.format.key}=${t.format.value}}}`
22
22
  };
23
23
  if (c) {
24
- if (!a.fallback)
24
+ if (!t.fallback)
25
25
  return {
26
- label: a.text,
27
- value: `{{ ${a.value} }}`
26
+ label: t.text,
27
+ value: `{{ ${t.value} }}`
28
28
  };
29
- const r = !Number.isNaN(Number(a.fallback)) && a.fallback.trim() !== "" ? a.fallback : `"${a.fallback}"`;
29
+ const r = !Number.isNaN(Number(t.fallback)) && t.fallback.trim() !== "" ? t.fallback : `"${t.fallback}"`;
30
30
  return {
31
- label: `${a.text} | ${a.fallback}`,
32
- value: `{{ ${a.value} | default: ${r} }}`
31
+ label: `${t.text} | ${t.fallback}`,
32
+ value: `{{ ${t.value} | default: ${r} }}`
33
33
  };
34
34
  }
35
35
  return {
36
- label: a.fallback ? `${a.text} | ${a.fallback}` : a.text,
37
- value: a.fallback ? `{{${a.value}|${a.fallback}}}` : `{{${a.value}}}`
36
+ label: t.fallback ? `${t.text} | ${t.fallback}` : t.text,
37
+ value: t.fallback ? `{{${t.value}|${t.fallback}}}` : `{{${t.value}}}`
38
38
  };
39
- }), b = (t) => {
40
- const c = [], a = /* @__PURE__ */ new Set(), s = (l) => {
41
- Array.isArray(l) && l.forEach((e) => {
42
- const n = [...e.children ?? [], ...e.select_items ?? []];
39
+ }), b = (a) => {
40
+ const c = [], t = /* @__PURE__ */ new Set(), s = (e) => {
41
+ Array.isArray(e) && e.forEach((l) => {
42
+ const n = [...l.children ?? [], ...l.select_items ?? []];
43
43
  if (n.length > 0) {
44
44
  s(n);
45
45
  return;
46
46
  }
47
- const u = e.text || e.tag_text;
48
- u && e.value && !a.has(e.value) && (a.add(e.value), c.push({ text: u, value: e.value }));
47
+ const f = l.text || l.tag_text;
48
+ f && l.value && !t.has(l.value) && (t.add(l.value), c.push({ text: f, value: l.value }));
49
49
  });
50
- }, [r] = Object.values(t ?? {});
50
+ }, [r] = Object.values(a ?? {});
51
51
  return s(r), c;
52
- }, i = (t, c, a = !1) => {
53
- const s = new Set(t.map((l) => l.value)), r = c.filter((l) => s.has(l.value) ? !1 : (s.add(l.value), !0));
52
+ }, i = (a, c, t = !1) => {
53
+ const s = new Set(a.map((e) => e.value)), r = c.filter((e) => s.has(e.value) ? !1 : (s.add(e.value), !0));
54
54
  return [
55
- ...f(t, a),
56
- ...f(r, a)
55
+ ...u(a, t),
56
+ ...u(r, t)
57
57
  ];
58
- }, v = () => {
59
- var t;
60
- return ((t = document.head.querySelector('meta[name="csrf-token"]')) == null ? void 0 : t.getAttribute("content")) ?? "";
61
- }, k = (t) => typeof t == "string" ? t : typeof t == "number" ? String(t) : "";
58
+ }, k = () => {
59
+ var a;
60
+ return ((a = document.head.querySelector('meta[name="csrf-token"]')) == null ? void 0 : a.getAttribute("content")) ?? "";
61
+ }, v = (a) => typeof a == "string" ? a : typeof a == "number" ? String(a) : "", y = (a) => {
62
+ if (a === null || typeof a != "object" || Array.isArray(a))
63
+ return !1;
64
+ const c = Object.getPrototypeOf(a);
65
+ return c === Object.prototype || c === null;
66
+ };
62
67
  export {
63
- k as asText,
68
+ v as asText,
64
69
  i as buildMergeTagEntries,
65
- f as dynamicContentToMergeTags,
70
+ u as dynamicContentToMergeTags,
66
71
  b as flattenDynamicContentList,
67
- v as getCsrfToken,
72
+ k as getCsrfToken,
73
+ y as isPlainObject,
68
74
  o as mergeTagToDynamicContent
69
75
  };
@@ -1,30 +1,31 @@
1
1
  import { useActionsApi as R } from "../composables/useActionsApi.js";
2
2
  import { useConfig as k } from "../composables/useConfig.js";
3
3
  import { useHtmlCompiler as N } from "../composables/useHtmlCompiler.js";
4
- import { RRS_CONFIG_VERSION as U, DEFAULT_CURRENCY as m, DEFAULT_NODE_CONFIG as a } from "../extensions/Blocks/Recommendation/constants/defaultConfig.js";
4
+ import { RRS_CONFIG_VERSION as U, DEFAULT_CURRENCY as m, DEFAULT_NODE_CONFIG as n } from "../extensions/Blocks/Recommendation/constants/defaultConfig.js";
5
5
  import { useRecommendationExtensionStore as I } from "../extensions/Blocks/Recommendation/store/recommendation.js";
6
6
  import { mapLegacyStrategy as x } from "../extensions/Blocks/Recommendation/utils/legacyStrategyMap.js";
7
- import { DATA_ATTRIBUTES as T } from "../extensions/Blocks/Unsubscribe/utils/constants.js";
7
+ import { DATA_ATTRIBUTES as A } from "../extensions/Blocks/Unsubscribe/utils/constants.js";
8
8
  import { parsePageList as L } from "../extensions/Blocks/Unsubscribe/utils/utils.js";
9
9
  import { useDynamicContentStore as z } from "../stores/dynamic-content.js";
10
- import { useEditorStore as _ } from "../stores/editor.js";
11
- import { useUnsubscribeStore as O } from "../stores/unsubscribe.js";
12
- import { assignTrackingIds as v } from "./linkTrackingIds.js";
13
- function B(l) {
14
- const s = new DOMParser().parseFromString(l, "text/html").querySelectorAll(`[${T.PAGE_LIST}]`), t = [];
15
- return s.forEach((c) => {
16
- const r = c.getAttribute(T.PAGE_LIST);
10
+ import { useEditorStore as O } from "../stores/editor.js";
11
+ import { useUnsubscribeStore as _ } from "../stores/unsubscribe.js";
12
+ import { isPlainObject as v } from "./genericUtil.js";
13
+ import { assignTrackingIds as B } from "./linkTrackingIds.js";
14
+ function G(l) {
15
+ const i = new DOMParser().parseFromString(l, "text/html").querySelectorAll(`[${A.PAGE_LIST}]`), t = [];
16
+ return i.forEach((s) => {
17
+ const r = s.getAttribute(A.PAGE_LIST);
17
18
  r && t.push(...L(r));
18
19
  }), [...new Set(t)];
19
20
  }
20
- async function G(l) {
21
- const n = new DOMParser().parseFromString(l, "text/html").querySelectorAll(".recommendation-block-v2");
22
- if (n.length === 0)
21
+ async function M(l) {
22
+ const a = new DOMParser().parseFromString(l, "text/html").querySelectorAll(".recommendation-block-v2");
23
+ if (a.length === 0)
23
24
  return;
24
- const s = I();
25
- n.forEach((t) => {
26
- var d, g, f, i, y, S;
27
- const c = t.getAttribute("recommendation-id"), r = c ? Number(c) : NaN;
25
+ const i = I();
26
+ a.forEach((t) => {
27
+ var d, g, f, c, y, S;
28
+ const s = t.getAttribute("recommendation-id"), r = s ? Number(s) : NaN;
28
29
  if (!Number.isFinite(r))
29
30
  return;
30
31
  const p = t.getAttribute("esd-ext-config");
@@ -36,32 +37,32 @@ async function G(l) {
36
37
  } catch {
37
38
  return;
38
39
  }
39
- if (!e || typeof e != "object" || Array.isArray(e))
40
+ if (!v(e))
40
41
  return;
41
- const A = !!e.strategyId || (e.configVersion ?? 0) >= U, o = {
42
+ const C = !!e.strategyId || (e.configVersion ?? 0) >= U, o = {
42
43
  // Normalize legacy strategy keys (e.g. `mostViewed` → `mostPopular`) so
43
44
  // already-saved blocks that never re-run the migrator don't emit a
44
45
  // strategy-less campaign URL. `||` also catches '' / undefined (SD-144882).
45
- strategy: x(e.strategy) || (A ? "" : a.strategy),
46
- strategyId: e.strategyId ?? a.strategyId,
47
- language: e.language ?? a.language,
48
- size: e.size ?? a.size,
46
+ strategy: x(e.strategy) || (C ? "" : n.strategy),
47
+ strategyId: e.strategyId ?? n.strategyId,
48
+ language: e.language ?? n.language,
49
+ size: e.size ?? n.size,
49
50
  // Spread the default arrays so each block gets a fresh reference
50
51
  // instead of sharing the singleton in DEFAULT_NODE_CONFIG.
51
- productIds: e.productIds ?? [...a.productIds],
52
- filters: e.filters ?? [...a.filters],
53
- shuffleProducts: e.shuffleProducts ?? a.shuffleProducts,
52
+ productIds: e.productIds ?? [...n.productIds],
53
+ filters: e.filters ?? [...n.filters],
54
+ shuffleProducts: e.shuffleProducts ?? n.shuffleProducts,
54
55
  currencyCode: ((d = e.currency) == null ? void 0 : d.code) ?? m.code,
55
56
  currencySymbol: ((g = e.currency) == null ? void 0 : g.symbol) ?? m.symbol,
56
57
  currencyAlignment: ((f = e.currency) == null ? void 0 : f.alignment) ?? m.alignment,
57
- currencyDecimalCount: ((i = e.currency) == null ? void 0 : i.decimalCount) ?? m.decimalCount,
58
+ currencyDecimalCount: ((c = e.currency) == null ? void 0 : c.decimalCount) ?? m.decimalCount,
58
59
  currencyDecimalSeparator: ((y = e.currency) == null ? void 0 : y.decimalSeparator) ?? m.decimalSeparator,
59
60
  currencyThousandSeparator: ((S = e.currency) == null ? void 0 : S.thousandSeparator) ?? m.thousandSeparator
60
61
  };
61
- s.seedBlockUrlConfig(r, o);
62
+ i.seedBlockUrlConfig(r, o);
62
63
  });
63
64
  try {
64
- await s.fetchRecommendationCreateData();
65
+ await i.fetchRecommendationCreateData();
65
66
  } catch (t) {
66
67
  console.warn(
67
68
  "Recommendation reference data pre-load failed; validator will skip the availability check.",
@@ -69,24 +70,24 @@ async function G(l) {
69
70
  );
70
71
  }
71
72
  }
72
- const ee = () => {
73
- const l = z(), u = _(), n = O(), { getCompiledEmail: s, getTemplateData: t, updateHtmlAndCss: c } = R(), { compileHtml: r, compileAmpHtml: p } = N(), { isFeatureEnabled: e } = k();
73
+ const re = () => {
74
+ const l = z(), u = O(), a = _(), { getCompiledEmail: i, getTemplateData: t, updateHtmlAndCss: s } = R(), { compileHtml: r, compileAmpHtml: p } = N(), { isFeatureEnabled: e } = k();
74
75
  return {
75
76
  prepareTemplateDetails: async () => {
76
77
  let { html: o, css: d, syncModulesIds: g = [] } = await t();
77
78
  if (e("linkTrackingIds"))
78
79
  try {
79
- const { html: b, changed: P, nextSeq: H } = v(o, u.trackIdSessionFloor);
80
- u.trackIdSessionFloor = H, P && (o = b, await c(b, d));
80
+ const { html: b, changed: F, nextSeq: H } = B(o, u.trackIdSessionFloor);
81
+ u.trackIdSessionFloor = H, F && (o = b, await s(b, d));
81
82
  } catch (b) {
82
83
  console.error("Tracking-id step failed, saving anyway:", b);
83
84
  }
84
- const { html: f, ampHtml: i = "", ampErrors: y = [] } = await s({
85
+ const { html: f, ampHtml: c = "", ampErrors: y = [] } = await i({
85
86
  minimize: !0,
86
87
  resetDataSavedFlag: !1
87
88
  });
88
- n.selectedUnsubscribePages.length && await n.fetchTemplates(), await G(o);
89
- const { compiledHtml: S, stats: h, appliedRules: w } = r(f), D = i && p(i).compiledHtml, E = l.getSelectedDynamicContentList, F = I(), C = B(o);
89
+ a.selectedUnsubscribePages.length && await a.fetchTemplates(), await M(o);
90
+ const { compiledHtml: S, stats: h, appliedRules: w } = r(f), D = c && p(c).compiledHtml, E = l.getSelectedDynamicContentList, P = I(), T = G(o);
90
91
  return console.debug("HTML Compilation Stats:", {
91
92
  originalSize: h.originalSize,
92
93
  compiledSize: h.compiledSize,
@@ -102,18 +103,18 @@ const ee = () => {
102
103
  ampErrors: y,
103
104
  modules: g.map(Number),
104
105
  recommendation: {
105
- campaignUrls: F.recommendationCampaignUrls,
106
+ campaignUrls: P.recommendationCampaignUrls,
106
107
  configs: {}
107
108
  },
108
109
  unsubscribe: {
109
- status: C.length > 0,
110
- config: C
110
+ status: T.length > 0,
111
+ config: T
111
112
  }
112
113
  };
113
114
  }
114
115
  };
115
116
  };
116
117
  export {
117
- B as getHtmlReferencedUnsubscribePages,
118
- ee as useTemplatePreparation
118
+ G as getHtmlReferencedUnsubscribePages,
119
+ re as useTemplatePreparation
119
120
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useinsider/guido",
3
- "version": "3.18.2-beta.ec25841",
3
+ "version": "3.18.2-beta.f42547e",
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,19 +0,0 @@
1
- import { useRecommendationExtensionStore as c } from "../store/recommendation.js";
2
- import { commitBlockChangeOutsidePanel as m } from "./nodeConfigWriter.js";
3
- async function s(o) {
4
- const e = c(), { strategyId: t, size: r } = e.recommendationConfigs;
5
- try {
6
- await e.fetchRecommendationProducts();
7
- } catch (n) {
8
- console.error("[recommendation] product refetch after strategy removal failed", n);
9
- }
10
- m(
11
- o,
12
- { strategyId: t, size: r },
13
- e.recommendationProducts,
14
- "Removed recommendation strategy"
15
- );
16
- }
17
- export {
18
- s as commitStrategyDetach
19
- };
@@ -1,12 +0,0 @@
1
- /**
2
- * Repaints a block whose strategy assignment was just cleared, straight to the document.
3
- *
4
- * Both callers are host-rendered UI outside `ui-editor`, so neither can leave this to the
5
- * settings panel: the remove modal's click deselects the block and destroys the control, and
6
- * the edit button opens the strategy page in the same gesture, so the panel's 500 ms debounced
7
- * refetch races a tab that is already gone. Either way the canvas kept the old strategy's
8
- * products while the block read as unassigned (SD-149916).
9
- *
10
- * Call AFTER the store assignment is cleared (`removeStrategy`).
11
- */
12
- export declare function commitStrategyDetach(blockId: number): Promise<void>;