@useinsider/guido 3.3.0-beta.402c269 → 3.3.0-beta.64c7838

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.
@@ -12,7 +12,7 @@ var t = function() {
12
12
  n,
13
13
  !1,
14
14
  null,
15
- "1a4e7084"
15
+ "cdee3452"
16
16
  );
17
17
  const l = s.exports;
18
18
  export {
@@ -1,38 +1,40 @@
1
- import { defineComponent as J, defineAsyncComponent as I, ref as W, computed as B, watch as Q, onMounted as X, onUnmounted as Y } from "vue";
2
- import { provideGuidoActions as Z } from "../composables/useGuidoActions.js";
3
- import { usePartner as ee } from "../composables/usePartner.js";
4
- import { useStripo as oe } from "../composables/useStripo.js";
5
- import { useTimerClone as te } from "../composables/useTimerClone.js";
6
- import { migrate as H } from "../config/migrator/index.js";
7
- import { ModuleFolderDefaults as x } from "../enums/defaults.js";
8
- import { RIBBON_SELECTOR as ne } from "../enums/onboarding.js";
9
- import re from "./organisms/AutoSaveController.vue.js";
10
- import se from "./organisms/base/Toaster.vue.js";
11
- import ce from "./organisms/extensions/recommendation/FilterSelectionDrawer.vue.js";
12
- import ae from "./organisms/header/HeaderWrapper.vue.js";
13
- import ie from "./organisms/LoadingWrapper.vue.js";
14
- import me from "./organisms/save-as-template/SaveAsTemplateDrawer.vue.js";
15
- import de from "./organisms/unsubscribe/UnsubscribeWrapper.vue.js";
16
- import { useStripoApi as le } from "../services/stripoApi.js";
17
- import { useConfigStore as ue } from "../stores/config.js";
18
- import { useDynamicContentStore as pe } from "../stores/dynamic-content.js";
19
- import { useEditorStore as fe } from "../stores/editor.js";
20
- import { usePreviewStore as ve } from "../stores/preview.js";
21
- import { useUnsubscribeStore as ye } from "../stores/unsubscribe.js";
22
- const We = /* @__PURE__ */ J({
1
+ import { defineComponent as J, defineAsyncComponent as R, ref as I, computed as W, watch as Q, onMounted as X, onUnmounted as Y } from "vue";
2
+ import { useCortexBlueprintBridge as Z } from "../composables/useCortexBlueprintBridge.js";
3
+ import { provideGuidoActions as ee } from "../composables/useGuidoActions.js";
4
+ import { useGuidoStateBridge as oe } from "../composables/useGuidoStateBridge.js";
5
+ import { usePartner as te } from "../composables/usePartner.js";
6
+ import { useStripo as ne } from "../composables/useStripo.js";
7
+ import { useTimerClone as re } from "../composables/useTimerClone.js";
8
+ import { migrate as x } from "../config/migrator/index.js";
9
+ import { ModuleFolderDefaults as G } from "../enums/defaults.js";
10
+ import { RIBBON_SELECTOR as se } from "../enums/onboarding.js";
11
+ import ce from "./organisms/AutoSaveController.vue.js";
12
+ import ae from "./organisms/base/Toaster.vue.js";
13
+ import ie from "./organisms/extensions/recommendation/FilterSelectionDrawer.vue.js";
14
+ import me from "./organisms/header/HeaderWrapper.vue.js";
15
+ import de from "./organisms/LoadingWrapper.vue.js";
16
+ import le from "./organisms/save-as-template/SaveAsTemplateDrawer.vue.js";
17
+ import ue from "./organisms/unsubscribe/UnsubscribeWrapper.vue.js";
18
+ import { useStripoApi as pe } from "../services/stripoApi.js";
19
+ import { useConfigStore as fe } from "../stores/config.js";
20
+ import { useDynamicContentStore as ve } from "../stores/dynamic-content.js";
21
+ import { useEditorStore as ye } from "../stores/editor.js";
22
+ import { usePreviewStore as he } from "../stores/preview.js";
23
+ import { useUnsubscribeStore as Se } from "../stores/unsubscribe.js";
24
+ const He = /* @__PURE__ */ J({
23
25
  __name: "Guido",
24
26
  props: {
25
27
  config: null
26
28
  },
27
29
  emits: ["dynamic-content:open", "back", "save:start", "save:complete", "on-change", "ready", "onboarding:finished", "test-email:click"],
28
- setup(G, { expose: z, emit: n }) {
29
- const g = G, q = I(
30
+ setup(H, { expose: z, emit: n }) {
31
+ const g = H, q = R(
30
32
  () => import("./organisms/email-preview/PreviewContainer.vue.js")
31
- ), K = I(
33
+ ), K = R(
32
34
  () => import("./organisms/onboarding/OnboardingWrapper.vue.js")
33
- ), w = W(), u = W(), p = pe(), E = ye(), i = ue();
35
+ ), w = I(), u = I(), p = ve(), E = Se(), i = fe();
34
36
  i.init(g.config);
35
- const f = fe(), V = ve(), m = B(() => f.hasChanges), { isTestPartner: $ } = ee(), D = () => {
37
+ const f = ye(), V = he(), m = W(() => f.hasChanges), { isTestPartner: $ } = te(), D = () => {
36
38
  var e;
37
39
  return (e = w.value) == null ? void 0 : e.handleSave(!0);
38
40
  }, {
@@ -42,8 +44,8 @@ const We = /* @__PURE__ */ J({
42
44
  username: k,
43
45
  template: o,
44
46
  editor: s
45
- } = i, d = (o == null ? void 0 : o.html) || "", F = (o == null ? void 0 : o.css) || "", y = (o == null ? void 0 : o.preselectedDynamicContent) || [], L = (s == null ? void 0 : s.savedModulesFolderName) || x.SAVED_MODULES, U = (s == null ? void 0 : s.defaultModulesFolderName) || x.DEFAULT_MODULES;
46
- f.templateId = v;
47
+ } = i, d = (o == null ? void 0 : o.html) || "", F = (o == null ? void 0 : o.css) || "", y = (o == null ? void 0 : o.preselectedDynamicContent) || [], L = (s == null ? void 0 : s.savedModulesFolderName) || G.SAVED_MODULES, U = (s == null ? void 0 : s.defaultModulesFolderName) || G.DEFAULT_MODULES;
48
+ f.templateId = v, Z(), oe();
47
49
  const h = {
48
50
  emailId: v,
49
51
  userId: C,
@@ -56,11 +58,11 @@ const We = /* @__PURE__ */ J({
56
58
  onReady: () => {
57
59
  console.debug("guido:ready"), n("ready");
58
60
  }
59
- }, { initPlugin: M } = oe(h, _), { getDefaultTemplate: O } = le(), { cloneTimersOnSave: P, hasTimerBlocks: A } = te(), j = B(() => {
61
+ }, { initPlugin: M } = ne(h, _), { getDefaultTemplate: O } = pe(), { cloneTimersOnSave: P, hasTimerBlocks: A } = re(), j = W(() => {
60
62
  var e;
61
63
  return !((e = i.ui) != null && e.showHeader);
62
64
  });
63
- Z({
65
+ ee({
64
66
  onBack: () => {
65
67
  console.debug("guido:back"), n("back");
66
68
  },
@@ -75,9 +77,9 @@ const We = /* @__PURE__ */ J({
75
77
  console.debug("guido:test-email:click"), n("test-email:click");
76
78
  }
77
79
  });
78
- const N = (e) => {
80
+ const B = (e) => {
79
81
  console.debug("dynamic-content:close", e), p.setSelectedDynamicContent(e), document.dispatchEvent(new CustomEvent("dynamic-content:close", { detail: e }));
80
- }, R = () => {
82
+ }, N = () => {
81
83
  console.debug("dynamic-content:close", "Without Data"), document.dispatchEvent(new CustomEvent("dynamic-content:close", { detail: { text: "", value: "" } }));
82
84
  };
83
85
  Q(() => m.value, () => {
@@ -90,7 +92,7 @@ const We = /* @__PURE__ */ J({
90
92
  let c = null;
91
93
  const b = () => {
92
94
  var t;
93
- const e = document.querySelector(ne);
95
+ const e = document.querySelector(se);
94
96
  (t = u.value) == null || t.style.setProperty("--ribbon-offset", `${(e == null ? void 0 : e.offsetHeight) ?? 0}px`);
95
97
  };
96
98
  return X(async () => {
@@ -102,10 +104,10 @@ const We = /* @__PURE__ */ J({
102
104
  E.selectedUnsubscribePages = (o == null ? void 0 : o.selectedUnsubscribePages) || [];
103
105
  const a = ((l = o == null ? void 0 : o.migration) == null ? void 0 : l.recommendationConfigs) ?? {};
104
106
  let r = {
105
- html: d && await H(d, a),
107
+ html: d && await x(d, a),
106
108
  css: F
107
109
  };
108
- r.html || (r = await O(), r.html = await H(r.html, a)), A(r.html) && (r.html = await P(r.html)), await M(r), p.selectedDynamicContentList = y;
110
+ r.html || (r = await O(), r.html = await x(r.html, a)), A(r.html) && (r.html = await P(r.html)), await M(r), p.selectedDynamicContentList = y;
109
111
  } catch (a) {
110
112
  console.error("Failed to initialize Stripo editor:", a);
111
113
  }
@@ -120,14 +122,14 @@ const We = /* @__PURE__ */ J({
120
122
  i.reset();
121
123
  }), z({
122
124
  dynamicContent: {
123
- insert: N,
124
- close: R
125
+ insert: B,
126
+ close: N
125
127
  },
126
128
  hasChanges: m,
127
129
  saveSilent: D
128
- }), { __sfc: !0, PreviewContainer: q, OnboardingWrapper: K, headerWrapperRef: w, wrapperRef: u, dynamicContentStore: p, unsubscribeStore: E, props: g, configStore: i, editorStore: f, previewStore: V, hasChanges: m, isTestPartner: $, saveSilent: D, templateId: v, userId: C, partnerName: T, username: k, templateConfig: o, editorConfig: s, html: d, css: F, preselectedDynamicContentList: y, savedModulesFolderName: L, defaultModulesFolderName: U, emit: n, metadata: h, options: _, initPlugin: M, getDefaultTemplate: O, cloneTimersOnSave: P, hasTimerBlocks: A, noHeader: j, insertDynamicContent: N, closeDynamicContent: R, handleDynamicContentOpen: S, ribbonObserver: c, updateRibbonOffset: b, AutoSaveController: re, Toaster: se, FilterSelectionDrawer: ce, HeaderWrapper: ae, LoadingWrapper: ie, SaveAsTemplateDrawer: me, UnsubscribeWrapper: de };
130
+ }), { __sfc: !0, PreviewContainer: q, OnboardingWrapper: K, headerWrapperRef: w, wrapperRef: u, dynamicContentStore: p, unsubscribeStore: E, props: g, configStore: i, editorStore: f, previewStore: V, hasChanges: m, isTestPartner: $, saveSilent: D, templateId: v, userId: C, partnerName: T, username: k, templateConfig: o, editorConfig: s, html: d, css: F, preselectedDynamicContentList: y, savedModulesFolderName: L, defaultModulesFolderName: U, emit: n, metadata: h, options: _, initPlugin: M, getDefaultTemplate: O, cloneTimersOnSave: P, hasTimerBlocks: A, noHeader: j, insertDynamicContent: B, closeDynamicContent: N, handleDynamicContentOpen: S, ribbonObserver: c, updateRibbonOffset: b, AutoSaveController: ce, Toaster: ae, FilterSelectionDrawer: ie, HeaderWrapper: me, LoadingWrapper: de, SaveAsTemplateDrawer: le, UnsubscribeWrapper: ue };
129
131
  }
130
132
  });
131
133
  export {
132
- We as default
134
+ He as default
133
135
  };
@@ -1,17 +1,17 @@
1
- import i from "./MiddleSlot.vue2.js";
1
+ import r from "./MiddleSlot.vue2.js";
2
2
  import s from "../../../_virtual/_plugin-vue2_normalizer.js";
3
3
  var t = function() {
4
- var r = this, e = r._self._c, o = r._self._setupProxy;
5
- return e("div", [o.editorStore.isVersionHistoryOpen ? e("div", { staticClass: "d-f" }, [e(o.VersionHistory), e(o.VersionHistoryViewOptions)], 1) : o.editorStore.isPreviewModeOpen ? e("div", { staticClass: "d-f" }, [e(o.EmailSizeIndicator), e(o.AmpToggle)], 1) : r._e(), e(o.EditorToolbar, { directives: [{ name: "show", rawName: "v-show", value: o.editorStore.isEditorToolbarVisible, expression: "editorStore.isEditorToolbarVisible" }] })], 1);
6
- }, n = [], a = /* @__PURE__ */ s(
7
- i,
4
+ var o = this, e = o._self._c, i = o._self._setupProxy;
5
+ return e("div", { staticClass: "d-f a-i-c" }, [i.editorStore.isVersionHistoryOpen ? e("div", { staticClass: "d-f" }, [e(i.VersionHistory), e(i.VersionHistoryViewOptions)], 1) : i.editorStore.isPreviewModeOpen ? e("div", { staticClass: "d-f" }, [e(i.EmailSizeIndicator), e(i.AmpToggle)], 1) : o._e(), e(i.EditorToolbar, { directives: [{ name: "show", rawName: "v-show", value: i.editorStore.isEditorToolbarVisible, expression: "editorStore.isEditorToolbarVisible" }] })], 1);
6
+ }, a = [], n = /* @__PURE__ */ s(
7
+ r,
8
8
  t,
9
- n,
9
+ a,
10
10
  !1,
11
11
  null,
12
12
  null
13
13
  );
14
- const c = a.exports;
14
+ const c = n.exports;
15
15
  export {
16
16
  c as default
17
17
  };
@@ -0,0 +1,66 @@
1
+ import { useEmailTemplateApplier as m, resetEmailTemplateApplier as w } from "./useEmailTemplateApplier.js";
2
+ import { useToaster as _ } from "./useToaster.js";
3
+ import { useTranslations as I } from "./useTranslations.js";
4
+ import { ToasterTypeOptions as S } from "../enums/toaster.js";
5
+ import { getActivePinia as v } from "pinia";
6
+ import { watch as B, onUnmounted as D } from "vue";
7
+ const P = "email_template", x = "chat", L = "guido:debug:ai", M = () => {
8
+ if (typeof window > "u")
9
+ return !1;
10
+ try {
11
+ return window.localStorage.getItem(L) === "1";
12
+ } catch {
13
+ return !1;
14
+ }
15
+ }, O = () => {
16
+ const a = v();
17
+ if (!a)
18
+ return;
19
+ const l = a._s, p = l == null ? void 0 : l.get(x);
20
+ if (!p)
21
+ return;
22
+ const { applyTemplate: d, applyTemplateDebounced: f } = m(), { showToaster: T } = _(), h = I(), A = (e, t) => {
23
+ const r = h(e);
24
+ return r === e ? t : r;
25
+ }, i = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Set(), u = M(), E = (e, t) => {
26
+ u && console.debug(`[guido:cortex] msg=${e ?? "?"} type=${(t == null ? void 0 : t.type) ?? "?"}`, t);
27
+ }, y = (e) => {
28
+ if (e.blueprintType !== P)
29
+ return;
30
+ const t = e.blueprintId, r = e.blueprintData;
31
+ if (!t || !(r != null && r.html))
32
+ return;
33
+ const o = i.get(t);
34
+ o === void 0 ? (i.set(t, r.html), d(t, r)) : o !== r.html && (i.set(t, r.html), f(t, r));
35
+ }, s = (e) => e === void 0 || c.has(e) ? !1 : (c.add(e), T({
36
+ type: S.Alert,
37
+ message: A("newsletter.ai-template-failed", "AI couldn't generate the template. Please try again.")
38
+ }), !0), b = B(
39
+ () => p.messages,
40
+ (e) => {
41
+ Array.isArray(e) && e.forEach((t) => {
42
+ const r = t == null ? void 0 : t.id;
43
+ (t == null ? void 0 : t.isError) === !0 && s(r) && u && console.debug(`[guido:cortex] message-level error id=${r}`, t);
44
+ const o = t == null ? void 0 : t.segments;
45
+ Array.isArray(o) && o.forEach((n) => {
46
+ if (n)
47
+ switch (E(r, n), n.type) {
48
+ case "blueprint":
49
+ y(n);
50
+ break;
51
+ case "error":
52
+ s(r);
53
+ break;
54
+ }
55
+ });
56
+ });
57
+ },
58
+ { deep: !0, immediate: !0 }
59
+ );
60
+ D(() => {
61
+ b(), i.clear(), c.clear(), w();
62
+ });
63
+ };
64
+ export {
65
+ O as useCortexBlueprintBridge
66
+ };
@@ -0,0 +1,41 @@
1
+ import { useActionsApi as u } from "./useActionsApi.js";
2
+ import { useToaster as T } from "./useToaster.js";
3
+ import { useTranslations as f } from "./useTranslations.js";
4
+ import { ToasterTypeOptions as n } from "../enums/toaster.js";
5
+ import { ref as y } from "vue";
6
+ const h = 250, s = y({}), o = {}, j = () => {
7
+ const { updateHtmlAndCss: l } = u(), { showToaster: r } = T(), i = f(), c = (e, t, p) => {
8
+ s.value = { ...s.value, [e]: "applying" };
9
+ try {
10
+ l(t, p), s.value = { ...s.value, [e]: "applied" }, r({
11
+ type: n.Success,
12
+ message: i("newsletter.ai-template-applied")
13
+ });
14
+ } catch (a) {
15
+ s.value = { ...s.value, [e]: "failed" }, r({
16
+ type: n.Alert,
17
+ message: a instanceof Error ? a.message : "Failed to apply template"
18
+ });
19
+ }
20
+ }, m = (e) => {
21
+ const t = o[e];
22
+ t && (clearTimeout(t), delete o[e]);
23
+ };
24
+ return { applyStatus: s, applyTemplate: (e, t) => {
25
+ t.html && (m(e), c(e, t.html, t.css ?? ""));
26
+ }, applyTemplateDebounced: (e, t) => {
27
+ if (!t.html)
28
+ return;
29
+ m(e);
30
+ const { html: p } = t, a = t.css ?? "";
31
+ o[e] = setTimeout(() => {
32
+ delete o[e], c(e, p, a);
33
+ }, h);
34
+ } };
35
+ }, w = () => {
36
+ Object.values(o).forEach(clearTimeout), Object.keys(o).forEach((l) => delete o[l]), s.value = {};
37
+ };
38
+ export {
39
+ w as resetEmailTemplateApplier,
40
+ j as useEmailTemplateApplier
41
+ };
@@ -0,0 +1,48 @@
1
+ import { useActionsApi as g } from "./useActionsApi.js";
2
+ import { useConfigStore as I } from "../stores/config.js";
3
+ import { useEditorStore as b } from "../stores/editor.js";
4
+ import { useGuidoEmailEditorStore as E } from "../stores/guido-email-editor.js";
5
+ import { watch as c, onUnmounted as T } from "vue";
6
+ const w = 500, z = () => {
7
+ const i = b(), a = E(), l = I(), { getTemplateData: m } = g();
8
+ let e = null, n = !1;
9
+ const r = () => {
10
+ e && (clearTimeout(e), e = null);
11
+ }, s = async () => {
12
+ var t, o;
13
+ if (!(n || !i.isStripoInitialized))
14
+ try {
15
+ const { html: f, css: h } = await m(), S = ((o = (t = l.config) == null ? void 0 : t.identity) == null ? void 0 : o.templateId) ?? "";
16
+ if (n)
17
+ return;
18
+ a.$patch({
19
+ html: f ?? "",
20
+ css: h ?? "",
21
+ templateId: S,
22
+ lastUpdatedAt: Date.now()
23
+ });
24
+ } catch {
25
+ }
26
+ }, u = () => {
27
+ r(), e = setTimeout(() => {
28
+ e = null, s();
29
+ }, w);
30
+ }, d = c(
31
+ () => i.isStripoInitialized,
32
+ (t) => {
33
+ t && s();
34
+ },
35
+ { immediate: !0 }
36
+ ), p = c(
37
+ () => i.hasChanges,
38
+ (t, o) => {
39
+ i.isStripoInitialized && (t ? u() : o && (r(), s()));
40
+ }
41
+ );
42
+ T(() => {
43
+ n = !0, r(), d(), p();
44
+ });
45
+ };
46
+ export {
47
+ z as useGuidoStateBridge
48
+ };
@@ -1,35 +1,37 @@
1
- import { useConfig as L } from "./useConfig.js";
2
- import { TemplateTypes as V } from "../enums/defaults.js";
3
- import { DISPLAY_CONDITIONS_REGEX as _, DISPLAY_CONDITIONS_EXCEPTIONS_REGEX as H, CampaignCouldNotBeSavedKey as P, CanNotMakeAnyChangesForRunningKey as G } from "../enums/html-validator.js";
1
+ import { useConfig as _ } from "./useConfig.js";
2
+ import { TemplateTypes as H } from "../enums/defaults.js";
3
+ import { DISPLAY_CONDITIONS_REGEX as P, DISPLAY_CONDITIONS_EXCEPTIONS_REGEX as G, CampaignCouldNotBeSavedKey as M, CanNotMakeAnyChangesForRunningKey as $ } from "../enums/html-validator.js";
4
4
  import { ToasterTypeOptions as c } from "../enums/toaster.js";
5
- import { itemsBlockDynamicVariables as M } from "../extensions/Blocks/Items/enums/productEnums.js";
6
- import { useRecommendationStore as $ } from "../stores/recommendation.js";
7
- import { base64EncodeWithSpecialChars as X } from "../utils/base64.js";
8
- import { useHttp as j } from "./useHttp.js";
9
- import { useToaster as q } from "./useToaster.js";
10
- import { useTranslations as z } from "./useTranslations.js";
11
- const K = /recommendation-id="(\d+)"/g;
12
- function U(a) {
13
- return [...a.matchAll(K)].map((u) => u[1]);
5
+ import { itemsBlockDynamicVariables as q } from "../extensions/Blocks/Items/enums/productEnums.js";
6
+ import { useRecommendationExtensionStore as X } from "../extensions/Blocks/Recommendation/store/recommendation.js";
7
+ import { RecommendationRequiredFieldsKey as j } from "../extensions/Blocks/Recommendation/validation/requiredFields.js";
8
+ import { useRecommendationStore as K } from "../stores/recommendation.js";
9
+ import { base64EncodeWithSpecialChars as z } from "../utils/base64.js";
10
+ import { useHttp as U } from "./useHttp.js";
11
+ import { useToaster as Y } from "./useToaster.js";
12
+ import { useTranslations as Z } from "./useTranslations.js";
13
+ const J = /recommendation-id="(\d+)"/g;
14
+ function Q(a) {
15
+ return [...a.matchAll(J)].map((u) => u[1]);
14
16
  }
15
- function Y(a, u) {
17
+ function ee(a, u) {
16
18
  return u.some((d) => a.startsWith(`${d}_`));
17
19
  }
18
- const ce = () => {
20
+ const ge = () => {
19
21
  var y, h;
20
- const { showToaster: a } = q(), { post: u } = j(), { config: d } = L(), r = z(), g = $(), p = ((h = (y = d.value) == null ? void 0 : y.partner) == null ? void 0 : h.messageType) === V.transactional, b = async (e) => {
22
+ const { showToaster: a } = Y(), { post: u } = U(), { config: d } = _(), r = Z(), g = K(), S = X(), p = ((h = (y = d.value) == null ? void 0 : y.partner) == null ? void 0 : h.messageType) === H.transactional, b = async (e) => {
21
23
  const t = await u(
22
24
  "/newsletter/template-library/check-template-html-body",
23
- { html: X(e) }
25
+ { html: z(e) }
24
26
  ), { status: n, message: l } = t.data;
25
27
  return n || a({
26
28
  type: c.Alert,
27
29
  message: n === void 0 ? l : r("newsletter.invalid-url-link-for-toaster")
28
- }), r(P), l === r(G) && a({
30
+ }), r(M), l === r($) && a({
29
31
  type: c.Alert,
30
32
  message: r("newsletter.already-in-progress")
31
33
  }), n;
32
- }, w = (e) => !["if", "endif", "else", "elif", "now"].includes(e.toLowerCase()), S = (e) => ["if", "endif"].includes(e.toLowerCase()), E = (e, s) => {
34
+ }, w = (e) => !["if", "endif", "else", "elif", "now"].includes(e.toLowerCase()), E = (e) => ["if", "endif"].includes(e.toLowerCase()), A = (e, s) => {
33
35
  const t = e.match(/({%(.*?)%})/g);
34
36
  let n = !0;
35
37
  return t !== null && !p && t.forEach((l) => {
@@ -42,13 +44,13 @@ const ce = () => {
42
44
  }), n = !1);
43
45
  }
44
46
  }), n;
45
- }, A = async (e, s, t) => {
47
+ }, k = async (e, s, t) => {
46
48
  const n = t ? await b(e) : !0;
47
- return E(e, s) && n;
48
- }, I = (e) => e.length > 0 ? !0 : (a({
49
+ return A(e, s) && n;
50
+ }, x = (e) => e.length > 0 ? !0 : (a({
49
51
  type: c.Warning,
50
52
  message: r("newsletter.html-content-is-empty")
51
- }), !1), k = (e) => {
53
+ }), !1), I = (e) => {
52
54
  const s = (e.match(/{/gm) || []).length, t = (e.match(/}/gm) || []).length;
53
55
  return s > t && a({
54
56
  type: c.Warning,
@@ -57,7 +59,7 @@ const ce = () => {
57
59
  type: c.Warning,
58
60
  message: r("custom-fields.missing-opening-braces")
59
61
  }), s === t;
60
- }, x = (e) => {
62
+ }, F = (e) => {
61
63
  const s = e.match(/{{\s*(\w+\s+((\w+\|\w+)|(\w+)))\s*}}/gm) === null;
62
64
  return s || a({
63
65
  type: c.Warning,
@@ -66,10 +68,10 @@ const ce = () => {
66
68
  }, T = (e, s) => {
67
69
  const t = e.match(/{{([a-zA-Z0-9_\s]*)}}/gm);
68
70
  if (t && !p) {
69
- const n = new Set(s.map((i) => i.toLowerCase())), l = U(e), o = [];
71
+ const n = new Set(s.map((i) => i.toLowerCase())), l = Q(e), o = [];
70
72
  if (t.forEach((i) => {
71
73
  const m = i.slice(2, -2).trim().toLowerCase();
72
- (!n.has(m) || m === "") && !Y(m, l) && o.push(m);
74
+ (!n.has(m) || m === "") && !ee(m, l) && o.push(m);
73
75
  }), o.length > 0) {
74
76
  const i = `
75
77
  <ul>
@@ -83,11 +85,11 @@ const ce = () => {
83
85
  }
84
86
  }
85
87
  return !0;
86
- }, F = (e) => {
88
+ }, R = (e) => {
87
89
  const s = e.match(/{%(.*?)%}/g), t = [];
88
90
  let n = !0;
89
91
  if (s && s.forEach((l) => {
90
- const o = l.match(_), i = l.match(H), m = (o == null ? void 0 : o.join("")) || "";
92
+ const o = l.match(P), i = l.match(G), m = (o == null ? void 0 : o.join("")) || "";
91
93
  (!o || l !== m) && !i && (a({
92
94
  type: c.Alert,
93
95
  message: r("newsletter.display-conditions-invalid-syntax")
@@ -98,7 +100,7 @@ const ce = () => {
98
100
  }), n = !1);
99
101
  const v = f.match(/^[a-zA-Z]*$/g);
100
102
  v && v.forEach((C) => {
101
- S(C) && t.push(C);
103
+ E(C) && t.push(C);
102
104
  });
103
105
  });
104
106
  }), t.length) {
@@ -109,25 +111,28 @@ const ce = () => {
109
111
  }), n = !1);
110
112
  }
111
113
  return n;
112
- }, W = (e) => {
114
+ }, B = (e) => {
113
115
  const s = (e.match(/{% /gm) || []).length, t = (e.match(/ %}/gm) || []).length, n = s === t;
114
116
  return n || a({
115
117
  type: c.Warning,
116
118
  message: r("custom-conditions.no-space-after-braces")
117
119
  }), n;
118
- }, N = (e) => (e.match(/({%(.*?)%})/g) || []).filter((t) => t.includes("if")).map((t) => (t.match(/{{.*}}/gm) || []).length).reduce((t, n) => t + n, 0) > 0 ? (a({
120
+ }, W = (e) => (e.match(/({%(.*?)%})/g) || []).filter((t) => t.includes("if")).map((t) => (t.match(/{{.*}}/gm) || []).length).reduce((t, n) => t + n, 0) > 0 ? (a({
119
121
  type: c.Warning,
120
122
  message: r("custom-conditions.no-braces-inside-if-tag")
123
+ }), !1) : !0, N = () => S.hasInvalidBlock() ? (a({
124
+ type: c.Alert,
125
+ message: r(j)
121
126
  }), !1) : !0, O = () => g.recommendationConfigs && Object.values(g.recommendationConfigs).find((s) => s.filters.find((t) => t.value === "")) !== void 0 ? (a({
122
127
  type: c.Alert,
123
128
  message: r("newsletter.fill-all-necessary-fields")
124
- }), !1) : !0, B = (e) => {
129
+ }), !1) : !0, D = (e) => {
125
130
  const s = /src="[^"]*\.(svg|pst)"/gm;
126
131
  return e.match(s) === null ? !0 : (a({
127
132
  type: c.Alert,
128
133
  message: r("newsletter.invalid-image-type")
129
134
  }), !1);
130
- }, R = (e) => {
135
+ }, L = (e) => {
131
136
  const n = new DOMParser().parseFromString(e, "text/html").querySelectorAll(".checkbox-block-v2");
132
137
  return Array.from(n).find((o) => {
133
138
  var i;
@@ -136,7 +141,7 @@ const ce = () => {
136
141
  type: c.Alert,
137
142
  message: r("unsubscribe-templates.select-checkbox-groups")
138
143
  }), !1) : !0;
139
- }, D = (e) => {
144
+ }, V = (e) => {
140
145
  const n = new DOMParser().parseFromString(e, "text/html").querySelectorAll(".radio-button-v2");
141
146
  return Array.from(n).find((o) => {
142
147
  var i;
@@ -150,12 +155,12 @@ const ce = () => {
150
155
  var o, i;
151
156
  const n = [
152
157
  ...s.map((m) => m.value),
153
- ...M,
158
+ ...q,
154
159
  ...((i = (o = d.value) == null ? void 0 : o.template) == null ? void 0 : i.customFieldAttributes) ?? []
155
160
  ];
156
- return await A(e, n, t) && I(e) && k(e) && x(e) && T(e, n) && F(e) && W(e) && N(e) && O() && B(e) && R(e) && D(e);
161
+ return await k(e, n, t) && x(e) && I(e) && F(e) && T(e, n) && R(e) && B(e) && W(e) && N() && O() && D(e) && L(e) && V(e);
157
162
  } };
158
163
  };
159
164
  export {
160
- ce as useHtmlValidator
165
+ ge as useHtmlValidator
161
166
  };
@@ -1,33 +1,33 @@
1
- var I = Object.defineProperty;
2
- var k = (r, n, t) => n in r ? I(r, n, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[n] = t;
3
- var u = (r, n, t) => k(r, typeof n != "symbol" ? n + "" : n, t);
4
- import { BlockId as _ } from "../../../enums/block.js";
5
- import { getMigrationBannerHtml as B } from "../../../utils/migrationBannerHtml.js";
6
- import { Block as b, BlockCompositionType as R, ModificationDescription as y } from "../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
7
- import { regenerateMobileProductRows as C } from "./controls/main/utils.js";
8
- import { ensureMobileCssRulesExist as g, setMobileLayoutOptOut as d, hasMobileLayoutOptOut as A } from "./controls/mobileLayout/cssRules.js";
9
- import { RecommendationConfigService as c } from "./services/configService.js";
10
- import { useRecommendationExtensionStore as p } from "./store/recommendation.js";
1
+ var k = Object.defineProperty;
2
+ var _ = (r, n, t) => n in r ? k(r, n, { enumerable: !0, configurable: !0, writable: !0, value: t }) : r[n] = t;
3
+ var g = (r, n, t) => _(r, typeof n != "symbol" ? n + "" : n, t);
4
+ import { BlockId as B } from "../../../enums/block.js";
5
+ import { getMigrationBannerHtml as b } from "../../../utils/migrationBannerHtml.js";
6
+ import { Block as R, BlockCompositionType as y, ModificationDescription as C } from "../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
7
+ import { regenerateMobileProductRows as A } from "./controls/main/utils.js";
8
+ import { ensureMobileCssRulesExist as d, setMobileLayoutOptOut as p, hasMobileLayoutOptOut as D } from "./controls/mobileLayout/cssRules.js";
9
+ import { RecommendationConfigService as s } from "./services/configService.js";
10
+ import { useRecommendationExtensionStore as f } from "./store/recommendation.js";
11
11
  import { getDefaultTemplate as E } from "./templates/grid/template.js";
12
- const f = _.Recommendation, l = "recommendation-block-v2", m = "recommendation-id";
13
- let h = !1;
14
- class q extends b {
12
+ const h = B.Recommendation, l = "recommendation-block-v2", m = "recommendation-id";
13
+ let I = !1;
14
+ class v extends R {
15
15
  constructor() {
16
16
  super();
17
17
  /**
18
18
  * Stores the ID generated in getTemplate() so onCreated() can reuse it.
19
19
  * This avoids generating a new (different) ID in onCreated().
20
20
  */
21
- u(this, "_pendingBlockId", null);
21
+ g(this, "_pendingBlockId", null);
22
22
  }
23
23
  getId() {
24
- return f;
24
+ return h;
25
25
  }
26
26
  getIcon() {
27
27
  return "recommendation-icon";
28
28
  }
29
29
  getBlockCompositionType() {
30
- return R.CONTAINER;
30
+ return y.CONTAINER;
31
31
  }
32
32
  getName() {
33
33
  return this.api.translate("Recommendation Block");
@@ -38,8 +38,8 @@ class q extends b {
38
38
  );
39
39
  }
40
40
  getSettingsPanelTitleHtml() {
41
- return B(
42
- f,
41
+ return b(
42
+ h,
43
43
  this.api.translate("Recommendation Block"),
44
44
  this.api.translate("This block is switched from the Old Version to the New Version. We recommend you check the Recommendation block and test your message to ensure it works properly.")
45
45
  );
@@ -71,13 +71,20 @@ class q extends b {
71
71
  return;
72
72
  const i = this._pendingBlockId ?? this._generateNextId();
73
73
  this._pendingBlockId = null, this._assignRecommendationId(t, i);
74
- const o = c.initializeConfig(this.api, t, { recommendationId: i }), s = p();
75
- s.setCurrentBlock(i), g(this.api);
76
- const a = this._getBlockElement(t);
77
- a && (d(this.api, a, !0), C({
78
- currentNode: t,
79
- documentModifier: this.api.getDocumentModifier()
80
- })), s.patchCurrentBlockConfig({ language: o.language }, { triggerRefetch: !1 });
74
+ const { config: o, wasFreshDrop: c } = s.initializeConfig(
75
+ this.api,
76
+ t,
77
+ { recommendationId: i }
78
+ ), a = f();
79
+ if (a.setCurrentBlock(i), c) {
80
+ d(this.api);
81
+ const u = this._getBlockElement(t);
82
+ u && (p(this.api, u, !0), A({
83
+ currentNode: t,
84
+ documentModifier: this.api.getDocumentModifier()
85
+ }));
86
+ }
87
+ a.patchCurrentBlockConfig({ language: o.language }, { triggerRefetch: !1 });
81
88
  }
82
89
  /**
83
90
  * Called when the document changes or template is loaded
@@ -90,20 +97,20 @@ class q extends b {
90
97
  if (!(!t || !("getNodeConfig" in t))) {
91
98
  if (!this._getRecommendationId(t)) {
92
99
  const e = this._generateNextId();
93
- this._assignRecommendationId(t, e), c.hasConfig(t) && c.updateConfig(
100
+ this._assignRecommendationId(t, e), s.hasConfig(t) && s.updateConfig(
94
101
  this.api,
95
102
  t,
96
103
  { recommendationId: e },
97
104
  "Assign recommendation ID to legacy block"
98
105
  );
99
106
  }
100
- c.needsMigration(t) && this._migrateFromLegacy(t);
107
+ s.needsMigration(t) && this._migrateFromLegacy(t);
101
108
  try {
102
- h || (g(this.api), h = !0);
103
- const e = c.getConfig(t), i = this._getBlockElement(t);
109
+ I || (d(this.api), I = !0);
110
+ const e = s.getConfig(t), i = this._getBlockElement(t);
104
111
  if (i) {
105
112
  const o = !e.mobileLayoutEnabled;
106
- A(i) !== o && d(this.api, i, o);
113
+ D(i) !== o && p(this.api, i, o);
107
114
  }
108
115
  } catch {
109
116
  }
@@ -117,7 +124,7 @@ class q extends b {
117
124
  */
118
125
  onDelete(t) {
119
126
  const e = this._getRecommendationId(t);
120
- e && p().removeBlockState(e);
127
+ e && f().removeBlockState(e);
121
128
  }
122
129
  /**
123
130
  * Generates the next unique recommendation ID by scanning all existing blocks
@@ -129,7 +136,7 @@ class q extends b {
129
136
  const e = this.api.getDocumentRoot();
130
137
  e && "querySelectorAll" in e && e.querySelectorAll(`.${l}`).forEach((o) => {
131
138
  if ("getAttribute" in o) {
132
- const s = o.getAttribute(m), a = s ? parseInt(s) : 0;
139
+ const c = o.getAttribute(m), a = c ? parseInt(c) : 0;
133
140
  a > t && (t = a);
134
141
  }
135
142
  });
@@ -148,7 +155,7 @@ class q extends b {
148
155
  if (!i)
149
156
  return;
150
157
  const o = this.api.getDocumentModifier();
151
- o.modifyHtml(i).setAttribute(m, e.toString()), o.apply(new y(`Assign recommendation ID ${e}`));
158
+ o.modifyHtml(i).setAttribute(m, e.toString()), o.apply(new C(`Assign recommendation ID ${e}`));
152
159
  }
153
160
  /**
154
161
  * Gets the recommendation-id from a block node
@@ -178,10 +185,10 @@ class q extends b {
178
185
  * Migrate configuration from legacy format
179
186
  */
180
187
  _migrateFromLegacy(t) {
181
- c.migrateFromDataAttributes(this.api, t);
188
+ s.migrateFromDataAttributes(this.api, t);
182
189
  }
183
190
  }
184
191
  export {
185
- f as BLOCK_ID,
186
- q as RecommendationBlock
192
+ h as BLOCK_ID,
193
+ v as RecommendationBlock
187
194
  };
@@ -1,5 +1,5 @@
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";
1
+ import { ModificationDescription as C } from "../../../../node_modules/@stripoinc/ui-editor-extensions/dist/esm/index.js";
2
+ import { CURRENT_CONFIG_VERSION as l, DEFAULT_NODE_CONFIG as c } from "../constants/defaultConfig.js";
3
3
  import { setCurrencyAttributes as D } from "../controls/main/utils.js";
4
4
  import { hasMinimalConfig as A } from "../types/nodeConfig.js";
5
5
  function S(e) {
@@ -8,7 +8,7 @@ function S(e) {
8
8
  function N(e) {
9
9
  return e === "." || e === "," || e === " " || e === "";
10
10
  }
11
- class V {
11
+ class M {
12
12
  // ========================================================================
13
13
  // Read Operations
14
14
  // ========================================================================
@@ -83,7 +83,7 @@ class V {
83
83
  * @returns The new complete configuration
84
84
  */
85
85
  static updateConfig(i, t, o, n) {
86
- const c = this.getConfig(t), u = this.deepMerge(c, o);
86
+ const r = this.getConfig(t), u = this.deepMerge(r, o);
87
87
  return this.saveConfig(i, t, u, n), u;
88
88
  }
89
89
  /**
@@ -91,21 +91,28 @@ class V {
91
91
  *
92
92
  * Called when a block is first created (dropped into template).
93
93
  * Can optionally merge in partial config from migration.
94
+ *
95
+ * The `wasFreshDrop` flag distinguishes a brand-new drop (no prior config)
96
+ * from a clone (Stripo replays the source's setNodeConfig payload before
97
+ * onCreated fires). Callers use this to skip side-effects already inherited
98
+ * from the source.
94
99
  * @example
95
100
  * // In Block.onCreated lifecycle
96
- * RecommendationConfigService.initializeConfig(this.api, node);
101
+ * const { config, wasFreshDrop } = RecommendationConfigService.initializeConfig(this.api, node);
97
102
  * @param api - Stripo extension API with document modifier
98
103
  * @param node - The immutable HTML node to initialize
99
104
  * @param partialConfig - Optional partial config to merge with defaults
100
- * @returns The initialized configuration
105
+ * @returns The initialized configuration and whether the node was a fresh drop
101
106
  */
102
107
  static initializeConfig(i, t, o) {
108
+ if (this.hasConfig(t))
109
+ return { config: o ? this.updateConfig(i, t, o, "Initialize recommendation block") : this.getConfig(t), wasFreshDrop: !1 };
103
110
  const n = o ? this.mergeWithDefaults(o) : this.cloneDefaults();
104
111
  return this.saveConfig(i, t, n, "Initialize recommendation block"), D({
105
112
  currentNode: t,
106
113
  documentModifier: i.getDocumentModifier(),
107
114
  currency: n.currency
108
- }), n;
115
+ }), { config: n, wasFreshDrop: !0 };
109
116
  }
110
117
  /**
111
118
  * Save complete configuration to a node
@@ -118,9 +125,9 @@ class V {
118
125
  */
119
126
  static saveConfig(i, t, o, n) {
120
127
  try {
121
- i.getDocumentModifier().modifyHtml(t).setNodeConfig(o).apply(new h(n));
122
- } catch (c) {
123
- console.warn("[RecommendationConfigService] Failed to save config:", c);
128
+ i.getDocumentModifier().modifyHtml(t).setNodeConfig(o).apply(new C(n));
129
+ } catch (r) {
130
+ console.warn("[RecommendationConfigService] Failed to save config:", r);
124
131
  }
125
132
  }
126
133
  // ========================================================================
@@ -157,29 +164,29 @@ class V {
157
164
  s && typeof s == "object" && (Object.assign(o, s), o.configVersion = l);
158
165
  } catch {
159
166
  }
160
- const c = t.getAttribute("data-layout");
161
- c === "list" || c === "horizontal" ? o.layout = "list" : (c === "grid" || c === "vertical") && (o.layout = "grid");
167
+ const r = t.getAttribute("data-layout");
168
+ r === "list" || r === "horizontal" ? o.layout = "list" : (r === "grid" || r === "vertical") && (o.layout = "grid");
162
169
  const u = t.getAttribute("data-card-composition");
163
170
  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) {
171
+ const b = t.getAttribute("data-column-spacing");
172
+ b && (o.columnSpacing = parseInt(b) || c.columnSpacing);
173
+ const p = t.getAttribute("data-row-spacing");
174
+ if (p && (o.rowSpacing = parseInt(p) || c.rowSpacing), !o.currency) {
168
175
  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
176
  if (s || y || d || f || g || m) {
170
- const a = r.currency, C = m ? parseInt(m) : NaN;
177
+ const a = c.currency, h = m ? parseInt(m) : NaN;
171
178
  o.currency = {
172
179
  code: s ?? a.code,
173
180
  symbol: y ?? a.symbol,
174
181
  alignment: d === "0" ? "before" : "after",
175
- decimalCount: Number.isFinite(C) ? C : a.decimalCount,
182
+ decimalCount: Number.isFinite(h) ? h : a.decimalCount,
176
183
  decimalSeparator: S(g) ? g : a.decimalSeparator,
177
184
  thousandSeparator: N(f) ? f : a.thousandSeparator
178
185
  };
179
186
  }
180
187
  }
181
188
  }
182
- return this.initializeConfig(i, t, o);
189
+ return this.initializeConfig(i, t, o).config;
183
190
  }
184
191
  /**
185
192
  * Check if configuration needs migration
@@ -197,12 +204,12 @@ class V {
197
204
  */
198
205
  static cloneDefaults() {
199
206
  return {
200
- ...r,
201
- currency: { ...r.currency },
202
- omnibusPrice: { ...r.omnibusPrice },
203
- omnibusDiscount: { ...r.omnibusDiscount },
204
- composition: [...r.composition],
205
- visibility: { ...r.visibility },
207
+ ...c,
208
+ currency: { ...c.currency },
209
+ omnibusPrice: { ...c.omnibusPrice },
210
+ omnibusDiscount: { ...c.omnibusDiscount },
211
+ composition: [...c.composition],
212
+ visibility: { ...c.visibility },
206
213
  filters: [],
207
214
  productIds: [],
208
215
  recommendationId: 0
@@ -272,5 +279,5 @@ class V {
272
279
  }
273
280
  }
274
281
  export {
275
- V as RecommendationConfigService
282
+ M as RecommendationConfigService
276
283
  };
@@ -1,14 +1,15 @@
1
- import { getRecommendationFeedSourceMaps as S, getOperatorOptions as R, PriceAttributes as y } from "../../../../enums/extensions/recommendationBlock.js";
1
+ import { getRecommendationFeedSourceMaps as g, getOperatorOptions as R, PriceAttributes as y } from "../../../../enums/extensions/recommendationBlock.js";
2
2
  import { useRecommendationApi as C } from "../../../../services/recommendationApi.js";
3
3
  import { useConfigStore as G } from "../../../../stores/config.js";
4
4
  import { defineStore as P } from "pinia";
5
5
  import { DEFAULT_CARDS_IN_ROW as F } from "../constants/layout.js";
6
6
  import { EXCLUDED_ALGORITHM_IDS as D } from "../constants/defaultConfig.js";
7
- import { getDefaultProducts as g } from "../templates/utils.js";
7
+ import { getDefaultProducts as S } from "../templates/utils.js";
8
8
  import { generateCompleteFilterQuery as b } from "../utils/filterUtil.js";
9
9
  import { isFilterValid as w } from "../validation/filterSchema.js";
10
+ import { isConfigValid as v } from "../validation/requiredFields.js";
10
11
  const h = C();
11
- let u = null, m = null, d = null;
12
+ let m = null, u = null, d = null;
12
13
  function k() {
13
14
  return {
14
15
  cardsInRow: F,
@@ -48,7 +49,7 @@ function I() {
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
- }), _ = P("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
@@ -113,7 +114,7 @@ const v = () => ({
113
114
  return [...new Set(t.map((e) => e.filterGroup))].sort((e, r) => e - r);
114
115
  },
115
116
  getActivePredictiveAlgorithms: (t) => {
116
- const e = S(), r = [];
117
+ const e = g(), r = [];
117
118
  return t.activePredictiveAlgorithms.filter((n) => !D.includes(n)).forEach((n) => {
118
119
  r.push(...e.filter((c) => c.id === n));
119
120
  }), r.map((n) => ({
@@ -293,11 +294,11 @@ const v = () => ({
293
294
  // ====================================================================
294
295
  async fetchRecommendationCreateData() {
295
296
  if (!this.activePredictiveAlgorithms.length) {
296
- if (u) {
297
- await u;
297
+ if (m) {
298
+ await m;
298
299
  return;
299
300
  }
300
- u = (async () => {
301
+ m = (async () => {
301
302
  const {
302
303
  activePredictiveAlgorithms: t,
303
304
  languages: e,
@@ -310,26 +311,26 @@ const v = () => ({
310
311
  this.currencyList = r;
311
312
  })();
312
313
  try {
313
- await u;
314
+ await m;
314
315
  } finally {
315
- u = null;
316
+ m = null;
316
317
  }
317
318
  }
318
319
  },
319
320
  async fetchRecommendationFilters() {
320
321
  if (!Object.keys(this.filterList).length) {
321
- if (m) {
322
- await m;
322
+ if (u) {
323
+ await u;
323
324
  return;
324
325
  }
325
- m = (async () => {
326
+ u = (async () => {
326
327
  const t = await h.fetchRecommendationFilters();
327
328
  this.filterList = t;
328
329
  })();
329
330
  try {
330
- await m;
331
+ await u;
331
332
  } finally {
332
- m = null;
333
+ u = null;
333
334
  }
334
335
  }
335
336
  },
@@ -398,6 +399,14 @@ const v = () => ({
398
399
  generateFilterQuery() {
399
400
  return b(this.recommendationConfigs.filters);
400
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
+ },
401
410
  // ====================================================================
402
411
  // Per-Block Product Fetching
403
412
  // ====================================================================
@@ -417,7 +426,7 @@ const v = () => ({
417
426
  },
418
427
  async _doFetchProducts() {
419
428
  var p;
420
- 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 = {
429
+ const t = this.currentRecommendationId, e = this.blockStates[t], { recommendationConfigs: r } = e, n = r.filters.filter((l) => l.isValid), c = b(n), i = ((p = g().find((l) => l.key === r.strategy)) == null ? void 0 : p.path) || "", o = G(), s = parseInt(r.size) || 6, a = {
421
430
  locale: r.language,
422
431
  currency: r.currencySettings.value,
423
432
  partnerName: o.partnerName,
@@ -433,15 +442,15 @@ const v = () => ({
433
442
  f = [];
434
443
  }
435
444
  if (this.blockStates[t]) {
436
- const l = f.length > 0 ? f : g(s);
445
+ const l = f.length > 0 ? f : S(s);
437
446
  l.length < s ? this.blockStates[t].recommendationProducts = [
438
447
  ...l,
439
- ...g(s - l.length)
448
+ ...S(s - l.length)
440
449
  ] : l.length > s ? this.blockStates[t].recommendationProducts = l.slice(0, s) : this.blockStates[t].recommendationProducts = l;
441
450
  }
442
451
  }
443
452
  }
444
453
  });
445
454
  export {
446
- _ as useRecommendationExtensionStore
455
+ M as useRecommendationExtensionStore
447
456
  };
@@ -0,0 +1,33 @@
1
+ const o = [
2
+ {
3
+ key: "locale",
4
+ getValue: (e) => e.language,
5
+ getAvailableOptions: (e) => Object.keys(e.languages)
6
+ },
7
+ {
8
+ key: "currency",
9
+ getValue: (e) => e.currencySettings.value,
10
+ getAvailableOptions: (e) => e.currencyList.map((n) => n.text)
11
+ }
12
+ ], l = "newsletter.recommendation-fill-required-fields";
13
+ function u(e, n) {
14
+ return o.filter((t) => {
15
+ var a;
16
+ if (t.condition && !t.condition(e))
17
+ return !1;
18
+ const i = t.getValue(e);
19
+ if (!i)
20
+ return !0;
21
+ const r = (a = t.getAvailableOptions) == null ? void 0 : a.call(t, n);
22
+ return r !== void 0 && !r.includes(i);
23
+ }).map((t) => t.key);
24
+ }
25
+ function s(e, n) {
26
+ return u(e, n).length === 0;
27
+ }
28
+ export {
29
+ o as REQUIRED_RECOMMENDATION_FIELDS,
30
+ l as RecommendationRequiredFieldsKey,
31
+ u as getInvalidFields,
32
+ s as isConfigValid
33
+ };
package/dist/guido.css CHANGED
@@ -1 +1 @@
1
- .gap-16[data-v-3b53a736],.gap-16[data-v-0e1b0c54]{gap:16px}[data-v-cd76c125] .in-button-v2__wrapper{line-height:0}[data-v-22226124] .in-segments-wrapper__button_selected,[data-v-22226124] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb;color:#0010ac;border-color:#0010ac}[data-v-2cb418af] .in-progress-wrapper__progress p span:last-child{display:none!important}[data-v-2cb418af] .in-progress-description-status{display:none!important}.view-options-wrapper[data-v-195ab6d4]{position:relative;display:inline-block}.new-tag[data-v-195ab6d4]{position:absolute;top:-8px;right:-16px;z-index:10}[data-v-195ab6d4] .guido__view-option-selection-desktop svg,[data-v-195ab6d4] .guido__view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-195ab6d4] .in-segments-wrapper__button_selected,[data-v-195ab6d4] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}[data-v-195ab6d4] .in-tooltip-wrapper__icon{cursor:pointer}.editor-toolbar[data-v-173c3a40]{gap:4px}.version-history-item[data-v-ee4b9c3f]{flex-basis:200px}.version-history[data-v-64c52560]{gap:8px}.version-history__toolbar[data-v-64c52560]{gap:4px}.view-options-wrapper[data-v-d405ca59]{position:relative;display:inline-block}.new-tag[data-v-d405ca59]{position:absolute;top:-8px;right:-16px;z-index:10}[data-v-d405ca59] .guido__verion-history-view-option-selection-desktop svg,[data-v-d405ca59] .guido__verion-history-view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-d405ca59] .in-segments-wrapper__button_selected,[data-v-d405ca59] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}[data-v-d405ca59] .in-tooltip-wrapper__icon{cursor:pointer}.auto-save-toggle[data-v-2c964af4]{position:relative}.auto-save-toggle__info-box[data-v-2c964af4]{position:absolute;top:100%;left:0;z-index:10;width:280px}.editor-actions[data-v-4e2a4adb]{gap:4px}.header-wrapper[data-v-5c02dcc7]{min-width:1000px}.guido-loading__wrapper[data-v-07c4b2d8]{height:100%;top:75px!important;bottom:0!important}.guido-editor__wrapper[data-v-1a4e7084]{--ribbon-offset: 0px;position:relative;width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__container[data-v-1a4e7084]{width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__no-header[data-v-1a4e7084]{height:calc(100vh - 75px - var(--ribbon-offset))}[data-v-293f1c47] .in-breadcrumb-wrapper__links{cursor:pointer}.templates-wrapper[data-v-df672485]{gap:16px;grid-template-columns:repeat(3,1fr)}.templates-wrapper .template-wrapper[data-v-df672485]{cursor:pointer}.templates-wrapper .template-wrapper .template-container[data-v-df672485]{height:274px;padding:2px;transition:none}.templates-wrapper .template-wrapper .template-container.selected[data-v-df672485]{padding:1px}.templates-wrapper .template-wrapper .template-container .thumbnail[data-v-df672485]{object-fit:cover;transform:scale(1)}[data-v-43c617a7] .guido__verion-history-view-option-selection-desktop svg,[data-v-43c617a7] .guido__verion-history-view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-43c617a7] .in-segments-wrapper__button_selected,[data-v-43c617a7] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}.error-list[data-v-c3fd5d4b]{gap:16px}.desktop-browser-header[data-v-d86c5af5]{height:79px;min-height:79px}.desktop-browser-header__left[data-v-d86c5af5]{-webkit-user-drag:none;height:79px;width:378px}.desktop-browser-header__center[data-v-d86c5af5]{height:79px;background-repeat:repeat-x;background-size:auto 100%;background-position:left top}.desktop-browser-header__right[data-v-d86c5af5]{-webkit-user-drag:none;height:79px;width:112px}.desktop-preview[data-v-988f8da6]{min-width:602px;height:70vh;min-height:583px;border-radius:10px}.desktop-preview iframe[data-v-988f8da6]{min-height:504px}.iframe-wrapper[data-v-e0424e99]{width:258px}.iframe-scaled[data-v-e0424e99]{width:320px;height:124.0310077519%;transform:scale(.80625);transform-origin:top left}.cropped-text[data-v-eb3d05d7]{width:220px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mobile-preview-wrapper__phone[data-v-3f472f96]{width:282px}.mobile-preview-wrapper__phone img[data-v-3f472f96]{object-fit:cover;border-radius:44px}.mobile-preview-wrapper__content[data-v-3f472f96]{width:258px;height:450px;left:12px}[data-v-7419ae06] .vueperslides__bullets,[data-v-796d193b] .vueperslides__bullets{pointer-events:none!important}[data-v-796d193b] .vueperslides__parallax-wrapper{height:110px!important}[data-v-cadfc82d] .vueperslides__bullets{pointer-events:none!important}[data-v-cadfc82d] .vueperslides__parallax-wrapper{height:110px!important}
1
+ .gap-16[data-v-3b53a736],.gap-16[data-v-0e1b0c54]{gap:16px}[data-v-cd76c125] .in-button-v2__wrapper{line-height:0}[data-v-22226124] .in-segments-wrapper__button_selected,[data-v-22226124] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb;color:#0010ac;border-color:#0010ac}[data-v-2cb418af] .in-progress-wrapper__progress p span:last-child{display:none!important}[data-v-2cb418af] .in-progress-description-status{display:none!important}.view-options-wrapper[data-v-195ab6d4]{position:relative;display:inline-block}.new-tag[data-v-195ab6d4]{position:absolute;top:-8px;right:-16px;z-index:10}[data-v-195ab6d4] .guido__view-option-selection-desktop svg,[data-v-195ab6d4] .guido__view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-195ab6d4] .in-segments-wrapper__button_selected,[data-v-195ab6d4] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}[data-v-195ab6d4] .in-tooltip-wrapper__icon{cursor:pointer}.editor-toolbar[data-v-173c3a40]{gap:4px}.version-history-item[data-v-ee4b9c3f]{flex-basis:200px}.version-history[data-v-64c52560]{gap:8px}.version-history__toolbar[data-v-64c52560]{gap:4px}.view-options-wrapper[data-v-d405ca59]{position:relative;display:inline-block}.new-tag[data-v-d405ca59]{position:absolute;top:-8px;right:-16px;z-index:10}[data-v-d405ca59] .guido__verion-history-view-option-selection-desktop svg,[data-v-d405ca59] .guido__verion-history-view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-d405ca59] .in-segments-wrapper__button_selected,[data-v-d405ca59] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}[data-v-d405ca59] .in-tooltip-wrapper__icon{cursor:pointer}.auto-save-toggle[data-v-2c964af4]{position:relative}.auto-save-toggle__info-box[data-v-2c964af4]{position:absolute;top:100%;left:0;z-index:10;width:280px}.editor-actions[data-v-4e2a4adb]{gap:4px}.header-wrapper[data-v-5c02dcc7]{min-width:1000px}.guido-loading__wrapper[data-v-07c4b2d8]{height:100%;top:75px!important;bottom:0!important}.guido-editor__wrapper[data-v-cdee3452]{--ribbon-offset: 0px;position:relative;width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__container[data-v-cdee3452]{width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__no-header[data-v-cdee3452]{height:calc(100vh - 75px - var(--ribbon-offset))}[data-v-293f1c47] .in-breadcrumb-wrapper__links{cursor:pointer}.templates-wrapper[data-v-df672485]{gap:16px;grid-template-columns:repeat(3,1fr)}.templates-wrapper .template-wrapper[data-v-df672485]{cursor:pointer}.templates-wrapper .template-wrapper .template-container[data-v-df672485]{height:274px;padding:2px;transition:none}.templates-wrapper .template-wrapper .template-container.selected[data-v-df672485]{padding:1px}.templates-wrapper .template-wrapper .template-container .thumbnail[data-v-df672485]{object-fit:cover;transform:scale(1)}[data-v-43c617a7] .guido__verion-history-view-option-selection-desktop svg,[data-v-43c617a7] .guido__verion-history-view-option-selection-mobile svg{margin:0 0 0 2px}[data-v-43c617a7] .in-segments-wrapper__button_selected,[data-v-43c617a7] .in-segments-wrapper__button_selected:hover{background-color:#dae1fb}.error-list[data-v-c3fd5d4b]{gap:16px}.desktop-browser-header[data-v-d86c5af5]{height:79px;min-height:79px}.desktop-browser-header__left[data-v-d86c5af5]{-webkit-user-drag:none;height:79px;width:378px}.desktop-browser-header__center[data-v-d86c5af5]{height:79px;background-repeat:repeat-x;background-size:auto 100%;background-position:left top}.desktop-browser-header__right[data-v-d86c5af5]{-webkit-user-drag:none;height:79px;width:112px}.desktop-preview[data-v-988f8da6]{min-width:602px;height:70vh;min-height:583px;border-radius:10px}.desktop-preview iframe[data-v-988f8da6]{min-height:504px}.iframe-wrapper[data-v-e0424e99]{width:258px}.iframe-scaled[data-v-e0424e99]{width:320px;height:124.0310077519%;transform:scale(.80625);transform-origin:top left}.cropped-text[data-v-eb3d05d7]{width:220px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mobile-preview-wrapper__phone[data-v-3f472f96]{width:282px}.mobile-preview-wrapper__phone img[data-v-3f472f96]{object-fit:cover;border-radius:44px}.mobile-preview-wrapper__content[data-v-3f472f96]{width:258px;height:450px;left:12px}[data-v-7419ae06] .vueperslides__bullets,[data-v-796d193b] .vueperslides__bullets{pointer-events:none!important}[data-v-796d193b] .vueperslides__parallax-wrapper{height:110px!important}[data-v-cadfc82d] .vueperslides__bullets{pointer-events:none!important}[data-v-cadfc82d] .vueperslides__parallax-wrapper{height:110px!important}
@@ -1,13 +1,13 @@
1
1
  import { useHttp as t } from "../composables/useHttp.js";
2
- import { useToaster as n } from "../composables/useToaster.js";
2
+ import { useToaster as a } from "../composables/useToaster.js";
3
3
  const u = () => {
4
- const { get: r } = t(), { handleError: s } = n();
4
+ const { get: e } = t(), { handleError: s } = a();
5
5
  return { getUnsubscribePages: async () => {
6
6
  try {
7
- const { data: e = [] } = await r("/unsubscribe-pages/get-all");
8
- return e;
9
- } catch (e) {
10
- return s(e, "Failed to fetch unsubscribe pages"), [];
7
+ const { data: r } = await e("/unsubscribe-pages/get-all");
8
+ return Array.isArray(r) ? r : [];
9
+ } catch (r) {
10
+ return s(r, "Failed to fetch unsubscribe pages"), [];
11
11
  }
12
12
  } };
13
13
  };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Bridges cortex-fe's chat store to Guido's Stripo editor.
3
+ *
4
+ * cortex-fe is a Module Federation MFE that registers `defineStore('chat', …)`
5
+ * on the host's shared (singleton) Pinia. Guido — bundled inside MFE consumers
6
+ * that also share that pinia — reads the same store at runtime via
7
+ * `getActivePinia()._s.get('chat')`. No build-time dep on cortex-fe.
8
+ *
9
+ * The bridge handles two segment shapes:
10
+ *
11
+ * - `blueprint` segments with `blueprintType === 'email_template'` are
12
+ * applied to the Stripo editor (immediate on first sighting, debounced on
13
+ * update, identical-html no-ops).
14
+ * - `error` segments (and message-level `isError` / `isCancelled`) trigger
15
+ * a localized toaster.
16
+ *
17
+ * In dev (`import.meta.env.DEV`) every observed segment + message-level flag
18
+ * change is also `console.debug`-logged with the prefix `[guido:cortex]` so a
19
+ * developer can watch the agent stream in real time. In production this is
20
+ * gated behind `localStorage.guido:debug:ai === '1'`.
21
+ *
22
+ * If cortex-fe isn't loaded the chat store is undefined and the bridge is a
23
+ * no-op (intentional — Guido must still work standalone).
24
+ */
25
+ export declare const useCortexBlueprintBridge: () => void;
@@ -0,0 +1,21 @@
1
+ type ApplyStatus = 'pending' | 'applying' | 'applied' | 'failed';
2
+ /**
3
+ * Applies email_template blueprints from the chat agent to the Stripo editor.
4
+ *
5
+ * - blueprint_create + manual Apply click: immediate.
6
+ * - blueprint_update: debounced 250 ms trailing so rapid mid-stream updates
7
+ * don't thrash updateHtmlAndCss.
8
+ */
9
+ export declare const useEmailTemplateApplier: () => {
10
+ applyStatus: import("vue").Ref<Record<string, ApplyStatus>>;
11
+ applyTemplate: (blueprintId: string, data: {
12
+ html?: string;
13
+ css?: string;
14
+ }) => void;
15
+ applyTemplateDebounced: (blueprintId: string, data: {
16
+ html?: string;
17
+ css?: string;
18
+ }) => void;
19
+ };
20
+ export declare const resetEmailTemplateApplier: () => void;
21
+ export {};
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Outbound bridge — publishes the current editor's html/css to the shared
3
+ * Pinia store `guidoEmailEditor` so cortex-fe can read it at chat-stream
4
+ * submit time and forward it as `clientState.editor`. This is what enables
5
+ * sub-case 1.3 (refine an already-loaded template via the email-agent).
6
+ *
7
+ * The bridge writes on three triggers:
8
+ *
9
+ * 1. Stripo finishes initializing — first snapshot.
10
+ * 2. Editor `hasChanges` flips to true — debounced 500 ms so a streaming
11
+ * Stripo edit doesn't thrash the store.
12
+ * 3. Editor `hasChanges` flips back to false (post-save / autosave) —
13
+ * immediate, since this is the canonical "saved" snapshot.
14
+ *
15
+ * `getTemplateData()` is a Stripo iframe call; it must not run before
16
+ * `isStripoInitialized` is true. The bridge guards against that.
17
+ *
18
+ * If cortex-fe isn't loaded the store still gets written — that's fine;
19
+ * the store has zero overhead and any future reader will find consistent
20
+ * state. The bridge is a one-way pipe with no consumer dependency.
21
+ */
22
+ export declare const useGuidoStateBridge: () => void;
@@ -95,15 +95,23 @@ export declare class RecommendationConfigService {
95
95
  *
96
96
  * Called when a block is first created (dropped into template).
97
97
  * Can optionally merge in partial config from migration.
98
+ *
99
+ * The `wasFreshDrop` flag distinguishes a brand-new drop (no prior config)
100
+ * from a clone (Stripo replays the source's setNodeConfig payload before
101
+ * onCreated fires). Callers use this to skip side-effects already inherited
102
+ * from the source.
98
103
  * @example
99
104
  * // In Block.onCreated lifecycle
100
- * RecommendationConfigService.initializeConfig(this.api, node);
105
+ * const { config, wasFreshDrop } = RecommendationConfigService.initializeConfig(this.api, node);
101
106
  * @param api - Stripo extension API with document modifier
102
107
  * @param node - The immutable HTML node to initialize
103
108
  * @param partialConfig - Optional partial config to merge with defaults
104
- * @returns The initialized configuration
109
+ * @returns The initialized configuration and whether the node was a fresh drop
105
110
  */
106
- static initializeConfig(api: DocumentModifierApi, node: ImmutableHtmlNode, partialConfig?: PartialNodeConfig): RecommendationNodeConfig;
111
+ static initializeConfig(api: DocumentModifierApi, node: ImmutableHtmlNode, partialConfig?: PartialNodeConfig): {
112
+ config: RecommendationNodeConfig;
113
+ wasFreshDrop: boolean;
114
+ };
107
115
  /**
108
116
  * Save complete configuration to a node
109
117
  *
@@ -1,5 +1,5 @@
1
1
  import type { Orientation, Languages, Currency, NumericSeparator, FiltersResponse, Filter, RecommendationProduct } from '@@/Types/recommendation';
2
- interface PerBlockConfigs {
2
+ export interface PerBlockConfigs {
3
3
  cardsInRow: number;
4
4
  currencySettings: {
5
5
  name: string;
@@ -272,6 +272,12 @@ export declare const useRecommendationExtensionStore: import("pinia").StoreDefin
272
272
  deleteFilter(filter: Filter): void;
273
273
  addFilter(filter: Filter): void;
274
274
  generateFilterQuery(): string;
275
+ /**
276
+ * Validation-only check invoked at save-CTA time. Defined as an action
277
+ * (not a getter) so reading it does not register reactive tracking on
278
+ * every block's recommendationConfigs across user edits.
279
+ */
280
+ hasInvalidBlock(): boolean;
275
281
  fetchRecommendationProducts(): Promise<void>;
276
282
  _doFetchProducts(): Promise<void>;
277
283
  }>;
@@ -0,0 +1,21 @@
1
+ import type { PerBlockConfigs } from '../store/recommendation';
2
+ import type { Currency, Languages } from '@@/Types/recommendation';
3
+ /**
4
+ * Structural slice of the recommendation extension store that descriptors may read.
5
+ * Add new fields here when a future descriptor needs them.
6
+ */
7
+ export interface ExtensionStoreSlice {
8
+ languages: Languages;
9
+ currencyList: Currency[];
10
+ }
11
+ interface RequiredField {
12
+ key: string;
13
+ getValue: (config: PerBlockConfigs) => string;
14
+ getAvailableOptions?: (store: ExtensionStoreSlice) => string[];
15
+ condition?: (config: PerBlockConfigs) => boolean;
16
+ }
17
+ export declare const REQUIRED_RECOMMENDATION_FIELDS: RequiredField[];
18
+ export declare const RecommendationRequiredFieldsKey = "newsletter.recommendation-fill-required-fields";
19
+ export declare function getInvalidFields(config: PerBlockConfigs, store: ExtensionStoreSlice): string[];
20
+ export declare function isConfigValid(config: PerBlockConfigs, store: ExtensionStoreSlice): boolean;
21
+ export {};
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Outbound contract Guido publishes to the host's shared Pinia singleton so
3
+ * cortex-fe can pick up the current editor state and forward it as
4
+ * `clientState.editor` on the next chat-stream POST.
5
+ *
6
+ * State + getters only — `useGuidoStateBridge` writes via `$patch`, no actions
7
+ * live here (architecture invariant).
8
+ *
9
+ * Store id `guidoEmailEditor` is cross-MFE: cortex-fe reads it via
10
+ * `pinia._s.get('guidoEmailEditor')` at runtime. The internal Guido editor
11
+ * store keeps id `guidoEditor` — these are intentionally separate. The
12
+ * internal store carries UI state that cortex-fe should not see; this public
13
+ * store carries the contract Guido is willing to share.
14
+ */
15
+ export declare const useGuidoEmailEditorStore: import("pinia").StoreDefinition<"guidoEmailEditor", {
16
+ /** Current Stripo HTML — stripe tables only, no DOCTYPE/wrapper. */
17
+ html: string;
18
+ /** Current Stripo CSS — es-p* utility classes used by the html. */
19
+ css: string;
20
+ /** Wall-clock ms when html/css were last written by the bridge. */
21
+ lastUpdatedAt: number;
22
+ /** Template id from the editor config, for cortex-fe to scope per-template. */
23
+ templateId: string;
24
+ }, {
25
+ /** True once the bridge has published at least one snapshot. */
26
+ hasSnapshot: (state: {
27
+ html: string;
28
+ css: string;
29
+ lastUpdatedAt: number;
30
+ templateId: string;
31
+ } & import("pinia").PiniaCustomStateProperties<{
32
+ /** Current Stripo HTML — stripe tables only, no DOCTYPE/wrapper. */
33
+ html: string;
34
+ /** Current Stripo CSS — es-p* utility classes used by the html. */
35
+ css: string;
36
+ /** Wall-clock ms when html/css were last written by the bridge. */
37
+ lastUpdatedAt: number;
38
+ /** Template id from the editor config, for cortex-fe to scope per-template. */
39
+ templateId: string;
40
+ }>) => boolean;
41
+ }, {}>;
@@ -0,0 +1,20 @@
1
+ import { defineStore as e } from "pinia";
2
+ const d = e("guidoEmailEditor", {
3
+ state: () => ({
4
+ /** Current Stripo HTML — stripe tables only, no DOCTYPE/wrapper. */
5
+ html: "",
6
+ /** Current Stripo CSS — es-p* utility classes used by the html. */
7
+ css: "",
8
+ /** Wall-clock ms when html/css were last written by the bridge. */
9
+ lastUpdatedAt: 0,
10
+ /** Template id from the editor config, for cortex-fe to scope per-template. */
11
+ templateId: ""
12
+ }),
13
+ getters: {
14
+ /** True once the bridge has published at least one snapshot. */
15
+ hasSnapshot: (t) => t.lastUpdatedAt > 0 && t.html !== ""
16
+ }
17
+ });
18
+ export {
19
+ d as useGuidoEmailEditorStore
20
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useinsider/guido",
3
- "version": "3.3.0-beta.402c269",
3
+ "version": "3.3.0-beta.64c7838",
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",