@useinsider/guido 3.18.3-beta.ec88281 → 3.19.0

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.
package/README.md CHANGED
@@ -561,6 +561,26 @@ import {
561
561
  import '@useinsider/guido/style';
562
562
  ```
563
563
 
564
+ ### Subpath entries
565
+
566
+ Two surfaces also ship as their own entry points. Importing one pulls in only its
567
+ own dependencies, so a consumer that needs the store or the tracking-id helpers
568
+ never loads the editor:
569
+
570
+ ```typescript
571
+ // Live editor state, published by the editor into the host's shared Pinia instance.
572
+ // Store id `guidoEmailEditor` is the cross-app contract — read it, do not redeclare it.
573
+ import { useGuidoEmailEditorStore } from '@useinsider/guido/editor-state';
574
+ import type { GuidoEmailEditorState } from '@useinsider/guido/editor-state';
575
+
576
+ // Persistent link ids for the click heatmap.
577
+ import { assignTrackingIds, TRACK_ID_ATTRIBUTE } from '@useinsider/guido/tracking-ids';
578
+ import type { TrackingIdResult } from '@useinsider/guido/tracking-ids';
579
+ ```
580
+
581
+ Both are re-exported from the root barrel as well; the subpath is what keeps the
582
+ editor out of the consumer's module graph.
583
+
564
584
  ---
565
585
 
566
586
  ## Constants
@@ -1,54 +1,59 @@
1
- import { useActionsApi as b } from "./useActionsApi.js";
2
- import { useEditorLock as v } from "./useEditorLock.js";
1
+ import { useActionsApi as v } from "./useActionsApi.js";
2
+ import { useEditorLock as C } from "./useEditorLock.js";
3
3
  import { useConfigStore as T } from "../stores/config.js";
4
4
  import { useEditorStore as w } from "../stores/editor.js";
5
5
  import { useGuidoEmailEditorStore as y } from "../stores/guido-email-editor.js";
6
- import { watch as a, onUnmounted as B } from "vue";
7
- const C = 500, L = () => {
8
- const i = w(), u = y(), l = T(), { getTemplateData: d } = b(), { runExclusive: m, isEditorBusy: p } = v();
9
- let o = null, s = !1;
10
- const n = () => {
6
+ import { watch as c, onUnmounted as z } from "vue";
7
+ const B = 500, G = () => {
8
+ const i = w(), u = y(), d = T(), { getTemplateData: l } = v(), { runExclusive: m, isEditorBusy: p } = C();
9
+ let o = null, n = !1;
10
+ const r = () => {
11
11
  o && (clearTimeout(o), o = null);
12
- }, c = (t) => {
13
- n(), o = setTimeout(() => {
12
+ }, a = (t) => {
13
+ r(), o = setTimeout(() => {
14
14
  o = null, t();
15
- }, C);
15
+ }, B);
16
16
  }, e = async () => {
17
- var t, r;
18
- if (!(s || !i.isStripoInitialized)) {
17
+ var t, s;
18
+ if (!(n || !i.isStripoInitialized)) {
19
19
  if (p()) {
20
- c(() => void e());
20
+ a(() => void e());
21
21
  return;
22
22
  }
23
23
  try {
24
- const { html: E, css: g } = await m(d), I = ((r = (t = l.config) == null ? void 0 : t.identity) == null ? void 0 : r.templateId) ?? "";
25
- if (s)
24
+ const { html: E, css: I } = await m(l), b = ((s = (t = d.config) == null ? void 0 : t.identity) == null ? void 0 : s.templateId) ?? "";
25
+ if (n)
26
26
  return;
27
27
  u.$patch({
28
28
  html: E ?? "",
29
- css: g ?? "",
30
- templateId: I,
29
+ css: I ?? "",
30
+ templateId: b,
31
31
  lastUpdatedAt: Date.now()
32
32
  });
33
33
  } catch {
34
34
  }
35
35
  }
36
- }, f = () => c(() => void e()), h = a(
36
+ }, f = () => a(() => void e()), h = c(
37
37
  () => i.isStripoInitialized,
38
38
  (t) => {
39
39
  t && e();
40
40
  },
41
41
  { immediate: !0 }
42
- ), S = a(
42
+ ), S = c(
43
+ () => i.changeSeq,
44
+ () => {
45
+ i.isStripoInitialized && f();
46
+ }
47
+ ), g = c(
43
48
  () => i.hasChanges,
44
- (t, r) => {
45
- i.isStripoInitialized && (t ? f() : r && (n(), e()));
49
+ (t, s) => {
50
+ !t && s && i.isStripoInitialized && (r(), e());
46
51
  }
47
52
  );
48
- B(() => {
49
- s = !0, n(), h(), S();
53
+ z(() => {
54
+ n = !0, r(), h(), S(), g();
50
55
  });
51
56
  };
52
57
  export {
53
- L as useGuidoStateBridge
58
+ G as useGuidoStateBridge
54
59
  };
@@ -1,6 +1,6 @@
1
- import { useActionsApi as L } from "./useActionsApi.js";
2
- import { useBlocksConfig as O } from "./useBlocksConfig.js";
3
- import { useConfig as q } from "./useConfig.js";
1
+ import { useActionsApi as x } from "./useActionsApi.js";
2
+ import { useBlocksConfig as L } from "./useBlocksConfig.js";
3
+ import { useConfig as O } from "./useConfig.js";
4
4
  import { useCustomInterfaceAppearance as H } from "./useCustomInterfaceAppearance.js";
5
5
  import { useFullStoryBridge as N } from "./useFullStoryBridge.js";
6
6
  import { useModuleRepair as j } from "./useModuleRepair.js";
@@ -17,7 +17,7 @@ import { buildMergeTagEntries as W, flattenDynamicContentList as Y } from "../ut
17
17
  import Z from "../package.json.js";
18
18
  let n = null;
19
19
  const Ce = () => {
20
- const { features: c, template: u, isFeatureEnabled: m } = q(), { handleError: p, showToaster: k } = G(), { getToken: B, getCustomFonts: M, getSyncModulesStatus: T } = K(), { handleEvent: v } = z(), { handleModuleAdd: F } = j(), { getStripoBlocksConfig: A } = O(), { getStripoNotifications: V } = $(), f = () => n || (n = new Promise((i, s) => {
20
+ const { features: c, template: u, isFeatureEnabled: m } = O(), { handleError: p, showToaster: k } = G(), { getToken: B, getCustomFonts: M, getSyncModulesStatus: T } = K(), { handleEvent: v } = z(), { handleModuleAdd: F } = j(), { getStripoBlocksConfig: A } = L(), { getStripoNotifications: V } = $(), f = () => n || (n = new Promise((i, s) => {
21
21
  var d;
22
22
  if (document.getElementById("UiEditorScript")) {
23
23
  i();
@@ -88,8 +88,8 @@ const Ce = () => {
88
88
  },
89
89
  onTemplateLoaded() {
90
90
  try {
91
- const { importCss: t } = H(), { activateCustomViewStyles: l, updateTimerInClonedTemplate: I } = L(), { injectFullStory: x } = N();
92
- t(), l(), x(), I(), o.onReady(), e.isStripoInitialized = !0, e.loadingStatus = !1, setTimeout(() => {
91
+ const { importCss: t } = H(), { activateCustomViewStyles: l, updateTimerInClonedTemplate: I } = x(), { injectFullStory: q } = N();
92
+ t(), l(), q(), I(), o.onReady(), e.isStripoInitialized = !0, e.loadingStatus = !1, setTimeout(() => {
93
93
  e.hasChanges = !1;
94
94
  }, 1e3);
95
95
  } catch (t) {
@@ -106,7 +106,7 @@ const Ce = () => {
106
106
  e.isVersionHistoryOpen = t;
107
107
  },
108
108
  onDataChanged() {
109
- e.hasChanges = !0;
109
+ e.hasChanges = !0, e.changeSeq += 1;
110
110
  },
111
111
  onEvent: v,
112
112
  onModuleAdd: F,
@@ -67,7 +67,7 @@ class se extends ut {
67
67
  (d, c) => `
68
68
  <div data-reorder-icon-key="${P}${c}" style="display: none;">
69
69
  <${_.BUTTON}
70
- class="drag-handle-btn flat-inline flat-white"
70
+ class="guido-drag-handle-btn flat-inline flat-white"
71
71
  ${O.BUTTON.name}="${P}${c}"
72
72
  >
73
73
  <${_.ICON}
@@ -328,7 +328,7 @@ class se extends ut {
328
328
  _createBuiltInItemHtml(t, e) {
329
329
  return `
330
330
  <div class="orderable-item" draggable="true" data-key="${t.key}">
331
- <span class="drag-handle" data-reorder-icon-slot="${e}"></span>
331
+ <span class="guido-drag-handle" data-reorder-icon-slot="${e}"></span>
332
332
  <span class="item-label">${this.api.translate(t.label)}</span>
333
333
  <div class="item-action" data-action-for="${t.key}"></div>
334
334
  </div>
@@ -353,7 +353,7 @@ class se extends ut {
353
353
  <div class="orderable-item price-group-item" draggable="true"
354
354
  data-key="${C}" data-group="prices">
355
355
  <div class="price-group-header">
356
- <span class="drag-handle" data-reorder-icon-slot="${t}"></span>
356
+ <span class="guido-drag-handle" data-reorder-icon-slot="${t}"></span>
357
357
  <span class="item-label">${this.api.translate("Product Prices")}</span>
358
358
  <div class="item-action" data-action-for="${C}"></div>
359
359
  </div>
@@ -365,7 +365,7 @@ class se extends ut {
365
365
  return `
366
366
  <div class="orderable-item" draggable="true"
367
367
  data-key="${t}" data-custom-index="${e}">
368
- <span class="drag-handle" data-reorder-icon-slot="${r}"></span>
368
+ <span class="guido-drag-handle" data-reorder-icon-slot="${r}"></span>
369
369
  <div class="custom-attr-select-wrap"
370
370
  data-custom-select-slot="${e}"></div>
371
371
  <div class="item-action"
@@ -134,7 +134,10 @@ ue-orderable.orderable-disabled .droppable-icon {
134
134
  border-top: 2px solid var(--guido-color-primary-500, #0A2ECC);
135
135
  }
136
136
 
137
- .drag-handle {
137
+ /* Namespaced: Stripo ships a global \`.drag-handle { position: absolute }\` for its
138
+ floating popups, which knocked this handle out of flow (SD-151734). */
139
+ .guido-drag-handle {
140
+ cursor: move;
138
141
  user-select: none;
139
142
  display: flex;
140
143
  align-items: center;
@@ -144,13 +147,13 @@ ue-orderable.orderable-disabled .droppable-icon {
144
147
  flex-shrink: 0;
145
148
  }
146
149
 
147
- .drag-handle .drag-handle-btn .button {
150
+ .guido-drag-handle .guido-drag-handle-btn .button {
148
151
  padding: 0;
149
152
  min-width: auto;
150
153
  cursor: move;
151
154
  }
152
155
 
153
- .drag-handle .drag-handle-btn .button .icon-button {
156
+ .guido-drag-handle .guido-drag-handle-btn .button .icon-button {
154
157
  color: var(--guido-color-gray-600);
155
158
  }
156
159
 
package/dist/library.js CHANGED
@@ -1,18 +1,20 @@
1
- import { default as e } from "./components/Guido.vue.js";
2
- import { MessageType as f, ProductType as s } from "./@types/config/schemas.js";
1
+ import { default as a } from "./components/Guido.vue.js";
2
+ import { MessageType as t, ProductType as d } from "./@types/config/schemas.js";
3
3
  import "./@types/config/defaults.js";
4
- import { getValidationErrors as d, isValidConfig as p, parseConfig as g, parseConfigSafe as n, validateConfig as l } from "./@types/config/validator.js";
5
- import { assignTrackingIds as T, readTrackFloor as c, readTrackSeq as x } from "./utils/linkTrackingIds.js";
4
+ import { getValidationErrors as s, isValidConfig as p, parseConfig as g, parseConfigSafe as m, validateConfig as n } from "./@types/config/validator.js";
5
+ import { assignTrackingIds as u, readTrackFloor as x, readTrackSeq as T } from "./utils/linkTrackingIds.js";
6
+ import { useGuidoEmailEditorStore as C } from "./stores/guido-email-editor.js";
6
7
  export {
7
- e as Guido,
8
- f as MessageType,
9
- s as ProductType,
10
- T as assignTrackingIds,
11
- d as getValidationErrors,
8
+ a as Guido,
9
+ t as MessageType,
10
+ d as ProductType,
11
+ u as assignTrackingIds,
12
+ s as getValidationErrors,
12
13
  p as isValidConfig,
13
14
  g as parseConfig,
14
- n as parseConfigSafe,
15
- c as readTrackFloor,
16
- x as readTrackSeq,
17
- l as validateConfig
15
+ m as parseConfigSafe,
16
+ x as readTrackFloor,
17
+ T as readTrackSeq,
18
+ C as useGuidoEmailEditorStore,
19
+ n as validateConfig
18
20
  };
@@ -1,4 +1,4 @@
1
- const o = { stripo: { version: "2.76.0" } }, s = {
1
+ const o = { stripo: { version: "2.77.0" } }, s = {
2
2
  guido: o
3
3
  };
4
4
  export {
@@ -0,0 +1,4 @@
1
+ import { useGuidoEmailEditorStore as e } from "../stores/guido-email-editor.js";
2
+ export {
3
+ e as useGuidoEmailEditorStore
4
+ };
@@ -1,22 +1,26 @@
1
1
  /**
2
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
3
+ * Pinia store `guidoEmailEditor`. cortex-fe reads it at chat-stream submit
4
+ * time and forwards it as `clientState.editor`, which is what enables
5
5
  * sub-case 1.3 (refine an already-loaded template via the email-agent).
6
6
  *
7
7
  * The bridge writes on three triggers:
8
8
  *
9
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.
10
+ * 2. Every document change, via the `changeSeq` counter — debounced 500 ms
11
+ * so a streaming Stripo edit doesn't thrash the store.
12
12
  * 3. Editor `hasChanges` flips back to false (post-save / autosave) —
13
13
  * immediate, since this is the canonical "saved" snapshot.
14
14
  *
15
15
  * `getTemplateData()` is a Stripo iframe call; it must not run before
16
16
  * `isStripoInitialized` is true. The bridge guards against that.
17
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.
18
+ * The store is written even with no reader attached — it has zero overhead,
19
+ * and a late reader still finds consistent state.
20
+ *
21
+ * Readers are not optional any more, so treat the publish timing as part of
22
+ * the contract: the store ships as `@useinsider/guido/editor-state`, and
23
+ * email-fe imports it to scan the live html for its Array of Objects lock.
24
+ * Weakening a trigger here silently breaks that lock in the consumer.
21
25
  */
22
26
  export declare const useGuidoStateBridge: () => void;
@@ -2,5 +2,7 @@ export { default as Guido } from './components/Guido.vue';
2
2
  export type { GuidoConfig, GuidoConfigInput, IdentityConfig, PartnerConfig, TemplateConfig, TemplateMigrationConfig, LegacyRecommendationConfig, EditorConfig, UIConfig, FeaturesConfig, BlocksConfig, CompilerConfig, DynamicContent, EmailHeader, DefaultBlockType, CustomBlockType, ValidationResult, ValidationError, } from './@types/config';
3
3
  export { validateConfig, parseConfig, parseConfigSafe, isValidConfig, getValidationErrors, MessageType, ProductType, } from './@types/config';
4
4
  export { assignTrackingIds, readTrackFloor, readTrackSeq } from './public/trackingIds';
5
+ export { useGuidoEmailEditorStore } from './public/editorState';
6
+ export type { GuidoEmailEditorState } from './public/editorState';
5
7
  export type { StripoEventType } from './@types/events';
6
8
  export type { PositionData, DynamicContentEvent, MergeTagClickEvent, MergeTag, BaseDynamicContent, DynamicContentNode, DynamicContentList, TooltipOptions, TextValueObject, L10n, } from './@types/generic';
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Published as `@useinsider/guido/editor-state` — the live editor snapshot Guido
3
+ * writes into the host's shared Pinia singleton.
4
+ *
5
+ * Importing from here pulls in nothing but pinia, so the editor never enters a
6
+ * consumer's module graph. Re-export surface only: this is the contract,
7
+ * `stores/guido-email-editor` is the implementation and stays free to move.
8
+ *
9
+ * Consumers read the store by importing it; the id `guidoEmailEditor` remains
10
+ * part of the contract for cortex-fe, which probes `pinia._s` at runtime instead
11
+ * of taking a build-time dependency on this package.
12
+ */
13
+ export { useGuidoEmailEditorStore } from '../stores/guido-email-editor';
14
+ export type { GuidoEmailEditorState } from '../stores/guido-email-editor';
@@ -4,6 +4,10 @@
4
4
  * Importing from here pulls in nothing else, so the editor never enters a
5
5
  * consumer's module graph. Re-export surface only: this is the contract,
6
6
  * `utils/linkTrackingIds` is the implementation and stays free to move.
7
+ *
8
+ * Relative, not the `@@/Utils/` alias: vue-tsc copies the specifier into the emitted
9
+ * .d.ts verbatim, and a consumer's TypeScript cannot resolve our alias table. A relative
10
+ * path resolves against dist/src/, so the published types actually load.
7
11
  */
8
- export { assignTrackingIds, readTrackFloor, readTrackSeq, TRACK_ID_ATTRIBUTE, TRACK_SEQ_ATTRIBUTE, } from '@@/Utils/linkTrackingIds';
9
- export type { TrackingIdResult } from '@@/Utils/linkTrackingIds';
12
+ export { assignTrackingIds, readTrackFloor, readTrackSeq, TRACK_ID_ATTRIBUTE, TRACK_SEQ_ATTRIBUTE, } from '../utils/linkTrackingIds';
13
+ export type { TrackingIdResult } from '../utils/linkTrackingIds';
@@ -6,6 +6,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
6
6
  isPreviewModeOpen: boolean;
7
7
  editorVisualMode: string;
8
8
  hasChanges: boolean;
9
+ changeSeq: number;
9
10
  isStripoInitialized: boolean;
10
11
  templateId: string;
11
12
  syncModulesEnabled: boolean;
@@ -19,6 +20,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
19
20
  isPreviewModeOpen: boolean;
20
21
  editorVisualMode: string;
21
22
  hasChanges: boolean;
23
+ changeSeq: number;
22
24
  isStripoInitialized: boolean;
23
25
  templateId: string;
24
26
  syncModulesEnabled: boolean;
@@ -31,6 +33,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
31
33
  isPreviewModeOpen: boolean;
32
34
  editorVisualMode: string;
33
35
  hasChanges: boolean;
36
+ changeSeq: number;
34
37
  isStripoInitialized: boolean;
35
38
  templateId: string;
36
39
  syncModulesEnabled: boolean;
@@ -44,6 +47,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
44
47
  isPreviewModeOpen: boolean;
45
48
  editorVisualMode: string;
46
49
  hasChanges: boolean;
50
+ changeSeq: number;
47
51
  isStripoInitialized: boolean;
48
52
  templateId: string;
49
53
  syncModulesEnabled: boolean;
@@ -56,6 +60,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
56
60
  isPreviewModeOpen: boolean;
57
61
  editorVisualMode: string;
58
62
  hasChanges: boolean;
63
+ changeSeq: number;
59
64
  isStripoInitialized: boolean;
60
65
  templateId: string;
61
66
  syncModulesEnabled: boolean;
@@ -69,6 +74,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
69
74
  isPreviewModeOpen: boolean;
70
75
  editorVisualMode: string;
71
76
  hasChanges: boolean;
77
+ changeSeq: number;
72
78
  isStripoInitialized: boolean;
73
79
  templateId: string;
74
80
  syncModulesEnabled: boolean;
@@ -81,6 +87,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
81
87
  isPreviewModeOpen: boolean;
82
88
  editorVisualMode: string;
83
89
  hasChanges: boolean;
90
+ changeSeq: number;
84
91
  isStripoInitialized: boolean;
85
92
  templateId: string;
86
93
  syncModulesEnabled: boolean;
@@ -94,6 +101,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
94
101
  isPreviewModeOpen: boolean;
95
102
  editorVisualMode: string;
96
103
  hasChanges: boolean;
104
+ changeSeq: number;
97
105
  isStripoInitialized: boolean;
98
106
  templateId: string;
99
107
  syncModulesEnabled: boolean;
@@ -106,6 +114,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
106
114
  isPreviewModeOpen: boolean;
107
115
  editorVisualMode: string;
108
116
  hasChanges: boolean;
117
+ changeSeq: number;
109
118
  isStripoInitialized: boolean;
110
119
  templateId: string;
111
120
  syncModulesEnabled: boolean;
@@ -119,6 +128,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
119
128
  isPreviewModeOpen: boolean;
120
129
  editorVisualMode: string;
121
130
  hasChanges: boolean;
131
+ changeSeq: number;
122
132
  isStripoInitialized: boolean;
123
133
  templateId: string;
124
134
  syncModulesEnabled: boolean;
@@ -131,6 +141,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
131
141
  isPreviewModeOpen: boolean;
132
142
  editorVisualMode: string;
133
143
  hasChanges: boolean;
144
+ changeSeq: number;
134
145
  isStripoInitialized: boolean;
135
146
  templateId: string;
136
147
  syncModulesEnabled: boolean;
@@ -144,6 +155,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
144
155
  isPreviewModeOpen: boolean;
145
156
  editorVisualMode: string;
146
157
  hasChanges: boolean;
158
+ changeSeq: number;
147
159
  isStripoInitialized: boolean;
148
160
  templateId: string;
149
161
  syncModulesEnabled: boolean;
@@ -156,6 +168,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
156
168
  isPreviewModeOpen: boolean;
157
169
  editorVisualMode: string;
158
170
  hasChanges: boolean;
171
+ changeSeq: number;
159
172
  isStripoInitialized: boolean;
160
173
  templateId: string;
161
174
  syncModulesEnabled: boolean;
@@ -169,6 +182,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
169
182
  isPreviewModeOpen: boolean;
170
183
  editorVisualMode: string;
171
184
  hasChanges: boolean;
185
+ changeSeq: number;
172
186
  isStripoInitialized: boolean;
173
187
  templateId: string;
174
188
  syncModulesEnabled: boolean;
@@ -181,6 +195,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
181
195
  isPreviewModeOpen: boolean;
182
196
  editorVisualMode: string;
183
197
  hasChanges: boolean;
198
+ changeSeq: number;
184
199
  isStripoInitialized: boolean;
185
200
  templateId: string;
186
201
  syncModulesEnabled: boolean;
@@ -194,6 +209,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
194
209
  isPreviewModeOpen: boolean;
195
210
  editorVisualMode: string;
196
211
  hasChanges: boolean;
212
+ changeSeq: number;
197
213
  isStripoInitialized: boolean;
198
214
  templateId: string;
199
215
  syncModulesEnabled: boolean;
@@ -206,6 +222,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
206
222
  isPreviewModeOpen: boolean;
207
223
  editorVisualMode: string;
208
224
  hasChanges: boolean;
225
+ changeSeq: number;
209
226
  isStripoInitialized: boolean;
210
227
  templateId: string;
211
228
  syncModulesEnabled: boolean;
@@ -219,6 +236,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
219
236
  isPreviewModeOpen: boolean;
220
237
  editorVisualMode: string;
221
238
  hasChanges: boolean;
239
+ changeSeq: number;
222
240
  isStripoInitialized: boolean;
223
241
  templateId: string;
224
242
  syncModulesEnabled: boolean;
@@ -231,6 +249,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
231
249
  isPreviewModeOpen: boolean;
232
250
  editorVisualMode: string;
233
251
  hasChanges: boolean;
252
+ changeSeq: number;
234
253
  isStripoInitialized: boolean;
235
254
  templateId: string;
236
255
  syncModulesEnabled: boolean;
@@ -244,6 +263,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
244
263
  isPreviewModeOpen: boolean;
245
264
  editorVisualMode: string;
246
265
  hasChanges: boolean;
266
+ changeSeq: number;
247
267
  isStripoInitialized: boolean;
248
268
  templateId: string;
249
269
  syncModulesEnabled: boolean;
@@ -256,6 +276,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
256
276
  isPreviewModeOpen: boolean;
257
277
  editorVisualMode: string;
258
278
  hasChanges: boolean;
279
+ changeSeq: number;
259
280
  isStripoInitialized: boolean;
260
281
  templateId: string;
261
282
  syncModulesEnabled: boolean;
@@ -269,6 +290,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
269
290
  isPreviewModeOpen: boolean;
270
291
  editorVisualMode: string;
271
292
  hasChanges: boolean;
293
+ changeSeq: number;
272
294
  isStripoInitialized: boolean;
273
295
  templateId: string;
274
296
  syncModulesEnabled: boolean;
@@ -281,6 +303,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
281
303
  isPreviewModeOpen: boolean;
282
304
  editorVisualMode: string;
283
305
  hasChanges: boolean;
306
+ changeSeq: number;
284
307
  isStripoInitialized: boolean;
285
308
  templateId: string;
286
309
  syncModulesEnabled: boolean;
@@ -294,6 +317,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
294
317
  isPreviewModeOpen: boolean;
295
318
  editorVisualMode: string;
296
319
  hasChanges: boolean;
320
+ changeSeq: number;
297
321
  isStripoInitialized: boolean;
298
322
  templateId: string;
299
323
  syncModulesEnabled: boolean;
@@ -306,6 +330,7 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
306
330
  isPreviewModeOpen: boolean;
307
331
  editorVisualMode: string;
308
332
  hasChanges: boolean;
333
+ changeSeq: number;
309
334
  isStripoInitialized: boolean;
310
335
  templateId: string;
311
336
  syncModulesEnabled: boolean;
@@ -1,41 +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", {
1
+ /** The published editor snapshot — the shape a consumer reads off the shared store. */
2
+ export interface GuidoEmailEditorState {
16
3
  /** Current Stripo HTML — stripe tables only, no DOCTYPE/wrapper. */
17
4
  html: string;
18
5
  /** Current Stripo CSS — es-p* utility classes used by the html. */
19
6
  css: string;
20
7
  /** Wall-clock ms when html/css were last written by the bridge. */
21
8
  lastUpdatedAt: number;
22
- /** Template id from the editor config, for cortex-fe to scope per-template. */
9
+ /** Template id from the editor config, so a consumer can scope per-template. */
23
10
  templateId: string;
24
- }, {
11
+ }
12
+ /**
13
+ * Outbound contract Guido publishes to the host's shared Pinia singleton so a
14
+ * consumer can read the current editor state: cortex-fe forwards it as
15
+ * `clientState.editor` on the next chat-stream POST, email-fe scans it for the
16
+ * Array of Objects lock.
17
+ *
18
+ * Published as `@useinsider/guido/editor-state` and from the root barrel, so a
19
+ * consumer imports this store instead of re-declaring one under the same id.
20
+ *
21
+ * State + getters only, no actions. `useGuidoStateBridge` is the only intended
22
+ * writer, but that is Guido's own convention rather than something the store
23
+ * enforces: publishing the definition hands a consumer `$patch` as well, exactly
24
+ * as the runtime `pinia._s` probe always did.
25
+ *
26
+ * Store id `guidoEmailEditor` is cross-MFE: cortex-fe reads it via
27
+ * `pinia._s.get('guidoEmailEditor')` at runtime rather than importing it, so the
28
+ * id stays part of the contract. The internal Guido editor store keeps id
29
+ * `guidoEditor` — these are intentionally separate. The internal store carries
30
+ * UI state a consumer should not see; this public store carries the contract
31
+ * Guido is willing to share.
32
+ */
33
+ export declare const useGuidoEmailEditorStore: import("pinia").StoreDefinition<"guidoEmailEditor", GuidoEmailEditorState, {
25
34
  /** True once the bridge has published at least one snapshot. */
26
35
  hasSnapshot: (state: {
27
36
  html: string;
28
37
  css: string;
29
38
  lastUpdatedAt: number;
30
39
  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;
40
+ } & import("pinia").PiniaCustomStateProperties<GuidoEmailEditorState>) => boolean;
41
41
  }, {}>;
@@ -0,0 +1,20 @@
1
+ import type { BaseDynamicContent } from '@@/Types/generic';
2
+ export type DynamicContentFormat = NonNullable<BaseDynamicContent['format']>;
3
+ /** Format key the dynamic-content picker sends for a custom date/time format. */
4
+ export declare const TIME_FORMAT_KEY = "time-format";
5
+ /** Wraps a Liquid filter argument in whichever quote style the value itself does not use. */
6
+ export declare const quoteArgument: (value: string) => string | null;
7
+ /**
8
+ * `{ key: 'time-format', value: '%Y-%m-%d' }` -> `date: "%Y-%m-%d"`.
9
+ *
10
+ * Only a custom date/time strftime pattern has a Liquid equivalent. Anything else returns null so
11
+ * the caller emits a plain tag rather than one that silently lost — or literally printed — its
12
+ * format.
13
+ * @example formatToLiquidFilter({ key: 'time-format', value: '%b %-d, %Y' }) -> date: "%b %-d, %Y"
14
+ */
15
+ export declare const formatToLiquidFilter: (format: DynamicContentFormat) => string | null;
16
+ /**
17
+ * Inverse of `formatToLiquidFilter` — reads the picker format back out of one filter segment, so a
18
+ * reopened tag restores its selection in the host's format dropdown.
19
+ */
20
+ export declare const liquidFilterToFormat: (segment: string) => DynamicContentFormat | null;
@@ -8,6 +8,10 @@ const o = e("guidoEditor", {
8
8
  isPreviewModeOpen: !1,
9
9
  editorVisualMode: "desktop",
10
10
  hasChanges: !1,
11
+ // Bumped on every Stripo document change. `hasChanges` is a latch that stays
12
+ // true until a save, so it cannot signal the second edit onwards; watchers
13
+ // that need every change (the outbound state bridge) watch this instead.
14
+ changeSeq: 0,
11
15
  isStripoInitialized: !1,
12
16
  templateId: "",
13
17
  syncModulesEnabled: !1,
@@ -1,13 +1,9 @@
1
1
  import { defineStore as e } from "pinia";
2
2
  const d = e("guidoEmailEditor", {
3
3
  state: () => ({
4
- /** Current Stripo HTML — stripe tables only, no DOCTYPE/wrapper. */
5
4
  html: "",
6
- /** Current Stripo CSS — es-p* utility classes used by the html. */
7
5
  css: "",
8
- /** Wall-clock ms when html/css were last written by the bridge. */
9
6
  lastUpdatedAt: 0,
10
- /** Template id from the editor config, for cortex-fe to scope per-template. */
11
7
  templateId: ""
12
8
  }),
13
9
  getters: {
@@ -1,75 +1,78 @@
1
- const o = (a) => {
2
- const t = a.value.match(/\{\{([^}]+)\}\}/)[1].split("|").map((e) => e.trim()), [s] = t, r = {
3
- text: a.label,
4
- value: s || ""
1
+ import { formatToLiquidFilter as b, quoteArgument as y, liquidFilterToFormat as h } from "./liquidDynamicContent.js";
2
+ const g = (t) => {
3
+ if (!t.includes("="))
4
+ return null;
5
+ const [r, e] = t.split("=");
6
+ return { key: r.trim(), value: e.trim() };
7
+ }, c = "default:", f = (t) => {
8
+ if (!t.startsWith(c))
9
+ return t;
10
+ const r = t.slice(c.length).trim(), e = (l) => r.length >= 2 && r.startsWith(l) && r.endsWith(l);
11
+ return e('"') || e("'") ? r.slice(1, -1) : r;
12
+ }, k = (t) => {
13
+ const e = t.value.match(/\{\{([^}]+)\}\}/)[1].split("|").map((a) => a.trim()), [l] = e, s = {
14
+ text: t.label,
15
+ value: l || ""
5
16
  };
6
- if (t.length >= 2) {
7
- const [, e, l] = t;
8
- if (e.startsWith("default:")) {
9
- let n = e.slice(8).trim();
10
- n.startsWith('"') && n.endsWith('"') && (n = n.slice(1, -1)), r.fallback = n;
11
- } else {
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);
17
+ if (e.length >= 2) {
18
+ const [, a, n] = e;
19
+ if (a.startsWith(c))
20
+ s.fallback = f(a);
21
+ else {
22
+ const o = h(a) ?? g(a);
23
+ o ? s.format = o : n || (s.fallback = a), n && (s.fallback = f(n));
14
24
  }
15
25
  }
16
- return r;
17
- }, u = (a, c = !1) => a.map((t) => {
18
- if (t.format)
19
- return {
20
- label: `${t.text} | ${t.format.key}=${t.format.value}`,
21
- value: `{{${t.value}|${t.format.key}=${t.format.value}}}`
22
- };
23
- if (c) {
24
- if (!t.fallback)
25
- return {
26
- label: t.text,
27
- value: `{{ ${t.value} }}`
28
- };
29
- const r = !Number.isNaN(Number(t.fallback)) && t.fallback.trim() !== "" ? t.fallback : `"${t.fallback}"`;
30
- return {
31
- label: `${t.text} | ${t.fallback}`,
32
- value: `{{ ${t.value} | default: ${r} }}`
33
- };
34
- }
26
+ return s;
27
+ }, p = (t) => {
28
+ const e = !Number.isNaN(Number(t)) && t.trim() !== "" ? t : y(t);
29
+ return e === null ? null : `${c} ${e}`;
30
+ }, v = (t) => {
31
+ const r = t.format ? b(t.format) : null, e = t.fallback ? p(t.fallback) : null, l = [t.value, r, e].filter(Boolean);
35
32
  return {
36
- label: t.fallback ? `${t.text} | ${t.fallback}` : t.text,
37
- value: t.fallback ? `{{${t.value}|${t.fallback}}}` : `{{${t.value}}}`
33
+ label: [t.text, r, e && t.fallback].filter(Boolean).join(" | "),
34
+ value: `{{ ${l.join(" | ")} }}`
38
35
  };
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
- if (n.length > 0) {
44
- s(n);
36
+ }, i = (t, r = !1) => t.map((e) => r ? v(e) : e.format ? {
37
+ label: `${e.text} | ${e.format.key}=${e.format.value}`,
38
+ value: `{{${e.value}|${e.format.key}=${e.format.value}}}`
39
+ } : {
40
+ label: e.fallback ? `${e.text} | ${e.fallback}` : e.text,
41
+ value: e.fallback ? `{{${e.value}|${e.fallback}}}` : `{{${e.value}}}`
42
+ }), $ = (t) => {
43
+ const r = [], e = /* @__PURE__ */ new Set(), l = (a) => {
44
+ Array.isArray(a) && a.forEach((n) => {
45
+ const o = [...n.children ?? [], ...n.select_items ?? []];
46
+ if (o.length > 0) {
47
+ l(o);
45
48
  return;
46
49
  }
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 }));
50
+ const u = n.text || n.tag_text;
51
+ u && n.value && !e.has(n.value) && (e.add(n.value), r.push({ text: u, value: n.value }));
49
52
  });
50
- }, [r] = Object.values(a ?? {});
51
- return s(r), c;
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));
53
+ }, [s] = Object.values(t ?? {});
54
+ return l(s), r;
55
+ }, d = (t, r, e = !1) => {
56
+ const l = new Set(t.map((a) => a.value)), s = r.filter((a) => l.has(a.value) ? !1 : (l.add(a.value), !0));
54
57
  return [
55
- ...u(a, t),
56
- ...u(r, t)
58
+ ...i(t, e),
59
+ ...i(s, e)
57
60
  ];
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))
61
+ }, F = () => {
62
+ var t;
63
+ return ((t = document.head.querySelector('meta[name="csrf-token"]')) == null ? void 0 : t.getAttribute("content")) ?? "";
64
+ }, T = (t) => typeof t == "string" ? t : typeof t == "number" ? String(t) : "", x = (t) => {
65
+ if (t === null || typeof t != "object" || Array.isArray(t))
63
66
  return !1;
64
- const c = Object.getPrototypeOf(a);
65
- return c === Object.prototype || c === null;
67
+ const r = Object.getPrototypeOf(t);
68
+ return r === Object.prototype || r === null;
66
69
  };
67
70
  export {
68
- v as asText,
69
- i as buildMergeTagEntries,
70
- u as dynamicContentToMergeTags,
71
- b as flattenDynamicContentList,
72
- k as getCsrfToken,
73
- y as isPlainObject,
74
- o as mergeTagToDynamicContent
71
+ T as asText,
72
+ d as buildMergeTagEntries,
73
+ i as dynamicContentToMergeTags,
74
+ $ as flattenDynamicContentList,
75
+ F as getCsrfToken,
76
+ x as isPlainObject,
77
+ k as mergeTagToDynamicContent
75
78
  };
@@ -0,0 +1,15 @@
1
+ const E = "time-format", r = "date", u = new RegExp(`^${r}\\s*:\\s*(?:"([^"]*)"|'([^']*)')$`), s = /%[-_0^#]?\d*[a-zA-Z%]/, c = new RegExp(s, "g"), l = /\b(?:Y{2,4}|M{2,4}|D{1,4}|d{3,4}|H{2}|h{2}|m{2}|s{2}|S{1,3}|A|a)\b/, T = (t) => t.includes('"') ? t.includes("'") ? null : `'${t}'` : `"${t}"`, i = (t) => s.test(t) && !l.test(t.replace(c, "")), R = (t) => {
2
+ if (t.key !== E || !i(t.value ?? ""))
3
+ return null;
4
+ const n = T(t.value);
5
+ return n === null ? null : `${r}: ${n}`;
6
+ }, _ = (t) => {
7
+ const [, n, o] = t.match(u) ?? [], e = n ?? o;
8
+ return e ? { key: E, value: e } : null;
9
+ };
10
+ export {
11
+ E as TIME_FORMAT_KEY,
12
+ R as formatToLiquidFilter,
13
+ _ as liquidFilterToFormat,
14
+ T as quoteArgument
15
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useinsider/guido",
3
- "version": "3.18.3-beta.ec88281",
3
+ "version": "3.19.0",
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",
@@ -18,6 +18,10 @@
18
18
  "types": "./dist/src/public/trackingIds.d.ts",
19
19
  "import": "./dist/public/trackingIds.js"
20
20
  },
21
+ "./editor-state": {
22
+ "types": "./dist/src/public/editorState.d.ts",
23
+ "import": "./dist/public/editorState.js"
24
+ },
21
25
  "./style": "./dist/guido.css"
22
26
  },
23
27
  "scripts": {
@@ -77,7 +81,7 @@
77
81
  "@typescript-eslint/parser": "7.18.0",
78
82
  "@useinsider/eslint-config": "1.5.3",
79
83
  "@vitejs/plugin-vue2": "2.3.1",
80
- "@vitest/coverage-v8": "2.1.9",
84
+ "@vitest/coverage-v8": "4.1.11",
81
85
  "eslint": "8.57.1",
82
86
  "eslint-config-airbnb-base": "15.0.0",
83
87
  "eslint-config-airbnb-typescript": "18.0.0",
@@ -90,17 +94,17 @@
90
94
  "happy-dom": "15.11.7",
91
95
  "husky": "9.1.7",
92
96
  "msw": "2.10.3",
93
- "nyc": "17.1.0",
97
+ "nyc": "18.0.0",
94
98
  "sass": "1.69.7",
95
99
  "typescript": "5.5.4",
96
100
  "typescript-eslint": "8.26.0",
97
101
  "vite-plugin-dts": "4.5.4",
98
102
  "vite-plugin-istanbul": "7.0.0",
99
- "vitest": "2.1.9",
103
+ "vitest": "4.1.11",
100
104
  "vue-tsc": "2.2.10"
101
105
  },
102
106
  "overrides": {
103
- "axios": "1.8.2",
107
+ "axios": "1.20.0",
104
108
  "flatted": "3.4.2",
105
109
  "micromatch": "4.0.8",
106
110
  "path-to-regexp": "6.3.0"
@@ -118,7 +122,7 @@
118
122
  },
119
123
  "guido": {
120
124
  "stripo": {
121
- "version": "2.76.0"
125
+ "version": "2.77.0"
122
126
  }
123
127
  }
124
128
  }