@useinsider/guido 3.2.0-beta.51b7991 → 3.2.0-beta.556da4b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +25 -1
  2. package/dist/@types/config/schemas.js +38 -36
  3. package/dist/components/Guido.vue.js +4 -4
  4. package/dist/components/Guido.vue2.js +35 -34
  5. package/dist/components/organisms/AutoSaveController.vue.js +17 -0
  6. package/dist/components/organisms/AutoSaveController.vue2.js +13 -0
  7. package/dist/components/organisms/header/AutoSaveToggle.vue.js +22 -0
  8. package/dist/components/organisms/header/AutoSaveToggle.vue2.js +19 -0
  9. package/dist/components/organisms/header/RightSlot.vue.js +8 -8
  10. package/dist/components/organisms/header/RightSlot.vue2.js +9 -8
  11. package/dist/composables/useAutoSave.js +71 -0
  12. package/dist/composables/useSave.js +16 -16
  13. package/dist/extensions/Blocks/Recommendation/block.js +28 -35
  14. package/dist/extensions/Blocks/Recommendation/services/configService.js +4 -11
  15. package/dist/guido.css +1 -1
  16. package/dist/src/@types/config/schemas.d.ts +4 -0
  17. package/dist/src/components/Guido.vue.d.ts +1 -1
  18. package/dist/src/components/organisms/AutoSaveController.vue.d.ts +2 -0
  19. package/dist/src/components/organisms/header/AutoSaveToggle.vue.d.ts +2 -0
  20. package/dist/src/components/organisms/header/EditorActions.vue.d.ts +1 -1
  21. package/dist/src/components/organisms/header/HeaderWrapper.vue.d.ts +1 -1
  22. package/dist/src/components/organisms/header/RightSlot.vue.d.ts +1 -1
  23. package/dist/src/composables/useAutoSave.d.ts +3 -0
  24. package/dist/src/composables/useConfig.d.ts +2 -0
  25. package/dist/src/composables/useSave.d.ts +1 -1
  26. package/dist/src/extensions/Blocks/Recommendation/services/configService.d.ts +3 -11
  27. package/dist/src/stores/autosave.d.ts +12 -0
  28. package/dist/src/stores/config.d.ts +18 -0
  29. package/dist/src/stores/editor.d.ts +23 -0
  30. package/dist/src/utils/timeUtil.d.ts +8 -0
  31. package/dist/stores/autosave.js +17 -0
  32. package/dist/stores/editor.js +3 -1
  33. package/dist/utils/timeUtil.js +19 -0
  34. package/package.json +1 -1
package/README.md CHANGED
@@ -143,7 +143,8 @@ const config: GuidoConfigInput = {
143
143
  displayConditions?: boolean, // Default: true
144
144
  unsubscribe?: boolean, // Default: true
145
145
  modulesDisabled?: boolean, // Default: false - Disable modules panel
146
- liquidSyntax?: boolean, // Default: false - Enable Liquid template syntax
146
+ liquidSyntax?: boolean, // Default: false - Enable Liquid template syntax
147
+ autosave?: boolean, // Default: false - Show the Auto Save toggle in the header. See wiki/AUTOSAVE.md.
147
148
  },
148
149
 
149
150
  // Optional: Callbacks
@@ -202,6 +203,7 @@ interface SavedTemplateDetails {
202
203
  config: number[];
203
204
  };
204
205
  metadata: Metadata;
206
+ silent: boolean; // true when triggered by autosave, false when user clicked Save
205
207
  }
206
208
 
207
209
  interface Metadata {
@@ -327,6 +329,28 @@ const config: GuidoConfigInput = {
327
329
 
328
330
  ---
329
331
 
332
+ ## Autosave
333
+
334
+ Guido ships an opt-in **autosave** that saves on a 3-minute interval and when the user leaves the tab. Enable it with the `features.autosave` feature flag — this **shows an "Auto Save" toggle in the editor header**; the end user switches autosave on per session.
335
+
336
+ ```typescript
337
+ const config: GuidoConfigInput = {
338
+ identity: { templateId: 'tpl-123', userId: 'user-456' },
339
+ partner: { name: 'partner' },
340
+ features: {
341
+ autosave: true, // Default: false — shows the Auto Save toggle in the header
342
+ },
343
+ };
344
+ ```
345
+
346
+ - Default is `false` — integrations see no change unless they opt in.
347
+ - Autosave reuses the same save pipeline as the Save button, so your existing `@save:complete` handler receives autosave output identically to a manual save. No new events or callbacks.
348
+ - Toggle state is **session-only** (Pinia) — resets to OFF on reload.
349
+
350
+ For a deep dive on triggers, guards, and limitations, see **[wiki/AUTOSAVE.md](wiki/AUTOSAVE.md)**.
351
+
352
+ ---
353
+
330
354
  ## HTML Compiler Rules
331
355
 
332
356
  Add custom rules to transform HTML during export:
@@ -1,5 +1,5 @@
1
1
  import { ModuleFolderDefaults as b } from "../../enums/defaults.js";
2
- import { object as o, number as p, optional as e, string as t, pipe as u, picklist as n, minLength as d, custom as S, boolean as a, array as c, literal as l, variant as k } from "../../node_modules/valibot/dist/index.js";
2
+ import { object as a, number as p, optional as e, string as t, pipe as u, picklist as n, minLength as d, custom as S, boolean as o, array as c, literal as l, variant as k } from "../../node_modules/valibot/dist/index.js";
3
3
  const m = {
4
4
  /** Promotional/marketing emails */
5
5
  PROMOTIONAL: 1,
@@ -12,7 +12,7 @@ const m = {
12
12
  ARCHITECT: 49,
13
13
  /** Unsubscribe page builder */
14
14
  UNSUBSCRIBE_PAGES: 97
15
- }, h = o({
15
+ }, h = a({
16
16
  /** Unique identifier for the template being edited */
17
17
  templateId: u(
18
18
  t(),
@@ -25,12 +25,12 @@ const m = {
25
25
  ),
26
26
  /** Optional variation ID for A/B testing */
27
27
  variationId: e(t())
28
- }), y = o({
28
+ }), f = a({
29
29
  /** Fallback font name (e.g., "Georgia") */
30
30
  name: t(),
31
31
  /** Fallback font family (e.g., "serif" or "sans-serif") */
32
32
  family: t()
33
- }), f = o({
33
+ }), y = a({
34
34
  /** Partner/organization name (required) */
35
35
  name: u(
36
36
  t(),
@@ -53,8 +53,8 @@ const m = {
53
53
  /** Display name for the current user */
54
54
  username: e(t(), "Guido User"),
55
55
  /** Fallback font settings from partner settings — used to match backend size calculation */
56
- fallbackFont: e(y)
57
- }), A = o({
56
+ fallbackFont: e(f)
57
+ }), A = a({
58
58
  /** Display text for the dynamic content */
59
59
  text: t(),
60
60
  /** Template variable value (e.g., {{username}}) */
@@ -63,12 +63,12 @@ const m = {
63
63
  fallback: e(t()),
64
64
  /** Optional formatting options */
65
65
  format: e(
66
- o({
66
+ a({
67
67
  key: t(),
68
68
  value: t()
69
69
  })
70
70
  )
71
- }), R = o({
71
+ }), R = a({
72
72
  /** Initial HTML content */
73
73
  html: e(t(), ""),
74
74
  /** Initial CSS content */
@@ -83,13 +83,13 @@ const m = {
83
83
  /** Selected unsubscribe page IDs */
84
84
  selectedUnsubscribePages: e(c(p()), []),
85
85
  /** Force recreate template in Stripo storage (use true when updating externally modified templates) */
86
- forceRecreate: e(a(), !1)
87
- }), C = o({
86
+ forceRecreate: e(o(), !1)
87
+ }), C = a({
88
88
  /** Sender display name */
89
89
  senderName: e(t(), ""),
90
90
  /** Email subject line */
91
91
  subject: e(t(), "")
92
- }), T = o({
92
+ }), T = a({
93
93
  /** Locale for the editor UI */
94
94
  locale: e(t(), "en"),
95
95
  /** Path to translations object */
@@ -102,28 +102,30 @@ const m = {
102
102
  savedModulesFolderName: e(t(), b.SAVED_MODULES),
103
103
  /** Folder name for default/prebuilt modules (used by Stripo plugin panel for path construction) */
104
104
  defaultModulesFolderName: e(t(), b.DEFAULT_MODULES)
105
- }), I = o({
105
+ }), I = a({
106
106
  /** Whether to show the header bar */
107
- showHeader: e(a(), !0),
107
+ showHeader: e(o(), !0),
108
108
  /** Custom label for back button (if shown) */
109
109
  backButtonLabel: e(t())
110
- }), E = o({
110
+ }), E = a({
111
111
  /** Enable dynamic content insertion */
112
- dynamicContent: e(a(), !0),
112
+ dynamicContent: e(o(), !0),
113
113
  /** Enable save as template functionality */
114
- saveAsTemplate: e(a(), !0),
114
+ saveAsTemplate: e(o(), !0),
115
115
  /** Enable version history */
116
- versionHistory: e(a(), !0),
116
+ versionHistory: e(o(), !0),
117
117
  /** Enable test message sending */
118
- testMessage: e(a(), !0),
118
+ testMessage: e(o(), !0),
119
119
  /** Enable display conditions */
120
- displayConditions: e(a(), !0),
120
+ displayConditions: e(o(), !0),
121
121
  /** Enable unsubscribe block */
122
- unsubscribe: e(a(), !0),
122
+ unsubscribe: e(o(), !0),
123
123
  /** Disable modules panel in the editor */
124
- modulesDisabled: e(a(), !1),
124
+ modulesDisabled: e(o(), !1),
125
125
  /** Enable Liquid template syntax */
126
- liquidSyntax: e(a(), !1)
126
+ liquidSyntax: e(o(), !1),
127
+ /** Enable autosave (3-min interval + tab-hide). User toggles on/off from the header. */
128
+ autosave: e(o(), !1)
127
129
  }), g = n([
128
130
  "amp-accordion",
129
131
  "amp-carousel",
@@ -146,7 +148,7 @@ const m = {
146
148
  "unsubscribe-block",
147
149
  "coupon-block",
148
150
  "items-block"
149
- ]), L = o({
151
+ ]), L = a({
150
152
  /** Default blocks to exclude from the editor */
151
153
  excludeDefaults: e(
152
154
  c(g),
@@ -157,14 +159,14 @@ const m = {
157
159
  c(O),
158
160
  []
159
161
  )
160
- }), r = o({
162
+ }), r = a({
161
163
  /** Unique identifier for the rule */
162
164
  id: t(),
163
165
  /** Human-readable description */
164
166
  description: e(t()),
165
167
  /** Priority for rule ordering (lower = earlier) */
166
168
  priority: p()
167
- }), M = o({
169
+ }), M = a({
168
170
  ...r.entries,
169
171
  type: l("replace"),
170
172
  /** String to search for */
@@ -172,8 +174,8 @@ const m = {
172
174
  /** Replacement string */
173
175
  replacement: t(),
174
176
  /** Replace all occurrences (default: false) */
175
- replaceAll: e(a())
176
- }), N = o({
177
+ replaceAll: e(o())
178
+ }), N = a({
177
179
  ...r.entries,
178
180
  type: l("regex"),
179
181
  /** Regex pattern string */
@@ -182,12 +184,12 @@ const m = {
182
184
  replacement: t(),
183
185
  /** Regex flags (e.g., 'gi') */
184
186
  flags: e(t())
185
- }), D = o({
187
+ }), D = a({
186
188
  ...r.entries,
187
189
  type: l("remove"),
188
190
  /** Strings or patterns to remove */
189
191
  targets: c(t())
190
- }), v = o({
192
+ }), v = a({
191
193
  ...r.entries,
192
194
  type: l("custom"),
193
195
  /** Custom processor function */
@@ -200,12 +202,12 @@ const m = {
200
202
  N,
201
203
  D,
202
204
  v
203
- ]), U = o({
205
+ ]), U = a({
204
206
  /** Custom compiler rules to apply */
205
207
  customRules: e(c(x), []),
206
208
  /** Skip default compiler rules */
207
- ignoreDefaultRules: e(a(), !1)
208
- }), B = o({
209
+ ignoreDefaultRules: e(o(), !1)
210
+ }), B = a({
209
211
  /**
210
212
  * External validation handler called before save completes.
211
213
  * Return false to cancel the save operation.
@@ -216,12 +218,12 @@ const m = {
216
218
  "externalValidation must be a function"
217
219
  )
218
220
  )
219
- }), H = o({
221
+ }), H = a({
220
222
  // Required sections
221
223
  /** Identity configuration (required) */
222
224
  identity: h,
223
225
  /** Partner configuration (required) */
224
- partner: f,
226
+ partner: y,
225
227
  // Optional sections (with defaults)
226
228
  /** Template content and presets */
227
229
  template: e(R, {}),
@@ -249,12 +251,12 @@ export {
249
251
  A as DynamicContentSchema,
250
252
  T as EditorSchema,
251
253
  C as EmailHeaderSchema,
252
- y as FallbackFontSchema,
254
+ f as FallbackFontSchema,
253
255
  E as FeaturesSchema,
254
256
  H as GuidoConfigSchema,
255
257
  h as IdentitySchema,
256
258
  m as MessageType,
257
- f as PartnerSchema,
259
+ y as PartnerSchema,
258
260
  s as ProductType,
259
261
  N as RegexRuleSchema,
260
262
  D as RemoveRuleSchema,
@@ -3,7 +3,7 @@ import i from "./Guido.vue2.js";
3
3
  import a from "../_virtual/_plugin-vue2_normalizer.js";
4
4
  var t = function() {
5
5
  var o = this, r = o._self._c, e = o._self._setupProxy;
6
- return r("div", { ref: "wrapperRef", staticClass: "guido-editor__wrapper", class: { "guido-editor__no-header": e.noHeader } }, [r(e.HeaderWrapper, { ref: "headerWrapperRef" }), e.editorStore.isPreviewModeOpen ? r(e.PreviewContainer) : o._e(), r("div", { directives: [{ name: "show", rawName: "v-show", value: !e.previewStore.isLoaded, expression: "!previewStore.isLoaded" }], staticClass: "guido-editor__container", class: { "guido-editor__no-header": e.noHeader }, attrs: { id: "guido-editor" } }), r(e.Toaster), r(e.FilterSelectionDrawer), r(e.SaveAsTemplateDrawer), e.isTestPartner() ? o._e() : r(e.OnboardingWrapper, { on: { "onboarding-finished": function(p) {
6
+ return r("div", { ref: "wrapperRef", staticClass: "guido-editor__wrapper", class: { "guido-editor__no-header": e.noHeader } }, [r(e.HeaderWrapper, { ref: "headerWrapperRef" }), r(e.AutoSaveController), e.editorStore.isPreviewModeOpen ? r(e.PreviewContainer) : o._e(), r("div", { directives: [{ name: "show", rawName: "v-show", value: !e.previewStore.isLoaded, expression: "!previewStore.isLoaded" }], staticClass: "guido-editor__container", class: { "guido-editor__no-header": e.noHeader }, attrs: { id: "guido-editor" } }), r(e.Toaster), r(e.FilterSelectionDrawer), r(e.SaveAsTemplateDrawer), e.isTestPartner() ? o._e() : r(e.OnboardingWrapper, { on: { "onboarding-finished": function(p) {
7
7
  return e.emit("onboarding:finished");
8
8
  } } }), r(e.UnsubscribeWrapper), r(e.LoadingWrapper)], 1);
9
9
  }, n = [], s = /* @__PURE__ */ a(
@@ -12,9 +12,9 @@ var t = function() {
12
12
  n,
13
13
  !1,
14
14
  null,
15
- "25780af6"
15
+ "fffc13d6"
16
16
  );
17
- const u = s.exports;
17
+ const l = s.exports;
18
18
  export {
19
- u as default
19
+ l as default
20
20
  };
@@ -1,4 +1,4 @@
1
- import { defineComponent as j, defineAsyncComponent as R, ref as A, computed as I, watch as J, onMounted as Q, onUnmounted as X } from "vue";
1
+ import { defineComponent as j, defineAsyncComponent as N, ref as R, computed as I, watch as J, onMounted as Q, onUnmounted as X } from "vue";
2
2
  import { provideGuidoActions as Y } from "../composables/useGuidoActions.js";
3
3
  import { usePartner as Z } from "../composables/usePartner.js";
4
4
  import { useStripo as ee } from "../composables/useStripo.js";
@@ -6,42 +6,43 @@ import { useTimerClone as te } from "../composables/useTimerClone.js";
6
6
  import { migrate as W } from "../config/migrator/index.js";
7
7
  import { ModuleFolderDefaults as B } from "../enums/defaults.js";
8
8
  import { RIBBON_SELECTOR as oe } from "../enums/onboarding.js";
9
- import ne from "./organisms/base/Toaster.vue.js";
9
+ import ne from "./organisms/AutoSaveController.vue.js";
10
+ import re from "./organisms/base/Toaster.vue.js";
10
11
  import se from "./organisms/extensions/recommendation/FilterSelectionDrawer.vue.js";
11
- import re from "./organisms/header/HeaderWrapper.vue.js";
12
- import ce from "./organisms/LoadingWrapper.vue.js";
13
- import ae from "./organisms/save-as-template/SaveAsTemplateDrawer.vue.js";
14
- import ie from "./organisms/unsubscribe/UnsubscribeWrapper.vue.js";
15
- import { useStripoApi as me } from "../services/stripoApi.js";
16
- import { useConfigStore as de } from "../stores/config.js";
17
- import { useDynamicContentStore as le } from "../stores/dynamic-content.js";
18
- import { useEditorStore as ue } from "../stores/editor.js";
19
- import { usePreviewStore as pe } from "../stores/preview.js";
20
- import { useUnsubscribeStore as fe } from "../stores/unsubscribe.js";
21
- const Re = /* @__PURE__ */ j({
12
+ import ce from "./organisms/header/HeaderWrapper.vue.js";
13
+ import ae from "./organisms/LoadingWrapper.vue.js";
14
+ import ie from "./organisms/save-as-template/SaveAsTemplateDrawer.vue.js";
15
+ import me from "./organisms/unsubscribe/UnsubscribeWrapper.vue.js";
16
+ import { useStripoApi as de } from "../services/stripoApi.js";
17
+ import { useConfigStore as le } from "../stores/config.js";
18
+ import { useDynamicContentStore as ue } from "../stores/dynamic-content.js";
19
+ import { useEditorStore as pe } from "../stores/editor.js";
20
+ import { usePreviewStore as fe } from "../stores/preview.js";
21
+ import { useUnsubscribeStore as ve } from "../stores/unsubscribe.js";
22
+ const Ie = /* @__PURE__ */ j({
22
23
  __name: "Guido",
23
24
  props: {
24
25
  config: null
25
26
  },
26
27
  emits: ["dynamic-content:open", "back", "save:start", "save:complete", "on-change", "ready", "onboarding:finished", "test-email:click"],
27
- setup(H, { expose: x, emit: s }) {
28
- const b = H, G = R(
28
+ setup(H, { expose: x, emit: r }) {
29
+ const S = H, G = N(
29
30
  () => import("./organisms/email-preview/PreviewContainer.vue.js")
30
- ), z = R(
31
+ ), z = N(
31
32
  () => import("./organisms/onboarding/OnboardingWrapper.vue.js")
32
- ), S = A(), d = A(), l = le(), g = fe(), a = de();
33
- a.init(b.config);
34
- const u = ue(), q = pe(), i = I(() => u.hasChanges), { isTestPartner: K } = Z(), w = () => {
33
+ ), b = R(), d = R(), l = ue(), g = ve(), a = le();
34
+ a.init(S.config);
35
+ const u = pe(), q = fe(), i = I(() => u.hasChanges), { isTestPartner: K } = Z(), w = () => {
35
36
  var e;
36
- return (e = S.value) == null ? void 0 : e.handleSave(!0);
37
+ return (e = b.value) == null ? void 0 : e.handleSave(!0);
37
38
  }, {
38
39
  templateId: p,
39
40
  userId: E,
40
41
  partnerName: D,
41
42
  username: C,
42
43
  template: t,
43
- editor: r
44
- } = a, m = (t == null ? void 0 : t.html) || "", T = (t == null ? void 0 : t.css) || "", f = (t == null ? void 0 : t.preselectedDynamicContent) || [], k = (r == null ? void 0 : r.savedModulesFolderName) || B.SAVED_MODULES, F = (r == null ? void 0 : r.defaultModulesFolderName) || B.DEFAULT_MODULES;
44
+ editor: s
45
+ } = a, m = (t == null ? void 0 : t.html) || "", T = (t == null ? void 0 : t.css) || "", f = (t == null ? void 0 : t.preselectedDynamicContent) || [], k = (s == null ? void 0 : s.savedModulesFolderName) || B.SAVED_MODULES, F = (s == null ? void 0 : s.defaultModulesFolderName) || B.DEFAULT_MODULES;
45
46
  u.templateId = p;
46
47
  const v = {
47
48
  emailId: p,
@@ -53,38 +54,38 @@ const Re = /* @__PURE__ */ j({
53
54
  }, L = {
54
55
  preselectedDynamicContentList: f,
55
56
  onReady: () => {
56
- console.debug("guido:ready"), s("ready");
57
+ console.debug("guido:ready"), r("ready");
57
58
  }
58
- }, { initPlugin: U } = ee(v, L), { getDefaultTemplate: _ } = me(), { cloneTimersOnSave: M, hasTimerBlocks: O } = te(), V = I(() => {
59
+ }, { initPlugin: U } = ee(v, L), { getDefaultTemplate: _ } = de(), { cloneTimersOnSave: M, hasTimerBlocks: O } = te(), V = I(() => {
59
60
  var e;
60
61
  return !((e = a.ui) != null && e.showHeader);
61
62
  });
62
63
  Y({
63
64
  onBack: () => {
64
- console.debug("guido:back"), s("back");
65
+ console.debug("guido:back"), r("back");
65
66
  },
66
67
  onSaveStart: () => {
67
- console.debug("guido:save:start"), s("save:start");
68
+ console.debug("guido:save:start"), r("save:start");
68
69
  },
69
70
  onSaveComplete: (e) => {
70
71
  const n = { ...e, metadata: v };
71
- console.debug("guido:save:complete", n), s("save:complete", n);
72
+ console.debug("guido:save:complete", n), r("save:complete", n);
72
73
  },
73
74
  onTestEmailClick: () => {
74
- console.debug("guido:test-email:click"), s("test-email:click");
75
+ console.debug("guido:test-email:click"), r("test-email:click");
75
76
  }
76
77
  });
77
78
  const P = (e) => {
78
79
  console.debug("dynamic-content:close", e), l.setSelectedDynamicContent(e), document.dispatchEvent(new CustomEvent("dynamic-content:close", { detail: e }));
79
- }, N = () => {
80
+ }, A = () => {
80
81
  console.debug("dynamic-content:close", "Without Data"), document.dispatchEvent(new CustomEvent("dynamic-content:close", { detail: { text: "", value: "" } }));
81
82
  };
82
83
  J(() => i.value, () => {
83
- s("on-change", i.value);
84
+ r("on-change", i.value);
84
85
  });
85
86
  const y = (e) => {
86
87
  const n = e, { attribute: o, position: $ } = n.detail;
87
- console.debug("dynamic-content:open", n.detail), s("dynamic-content:open", o, $);
88
+ console.debug("dynamic-content:open", n.detail), r("dynamic-content:open", o, $);
88
89
  };
89
90
  let c = null;
90
91
  const h = () => {
@@ -119,13 +120,13 @@ const Re = /* @__PURE__ */ j({
119
120
  }), x({
120
121
  dynamicContent: {
121
122
  insert: P,
122
- close: N
123
+ close: A
123
124
  },
124
125
  hasChanges: i,
125
126
  saveSilent: w
126
- }), { __sfc: !0, PreviewContainer: G, OnboardingWrapper: z, headerWrapperRef: S, wrapperRef: d, dynamicContentStore: l, unsubscribeStore: g, props: b, configStore: a, editorStore: u, previewStore: q, hasChanges: i, isTestPartner: K, saveSilent: w, templateId: p, userId: E, partnerName: D, username: C, templateConfig: t, editorConfig: r, html: m, css: T, preselectedDynamicContentList: f, savedModulesFolderName: k, defaultModulesFolderName: F, emit: s, metadata: v, options: L, initPlugin: U, getDefaultTemplate: _, cloneTimersOnSave: M, hasTimerBlocks: O, noHeader: V, insertDynamicContent: P, closeDynamicContent: N, handleDynamicContentOpen: y, ribbonObserver: c, updateRibbonOffset: h, Toaster: ne, FilterSelectionDrawer: se, HeaderWrapper: re, LoadingWrapper: ce, SaveAsTemplateDrawer: ae, UnsubscribeWrapper: ie };
127
+ }), { __sfc: !0, PreviewContainer: G, OnboardingWrapper: z, headerWrapperRef: b, wrapperRef: d, dynamicContentStore: l, unsubscribeStore: g, props: S, configStore: a, editorStore: u, previewStore: q, hasChanges: i, isTestPartner: K, saveSilent: w, templateId: p, userId: E, partnerName: D, username: C, templateConfig: t, editorConfig: s, html: m, css: T, preselectedDynamicContentList: f, savedModulesFolderName: k, defaultModulesFolderName: F, emit: r, metadata: v, options: L, initPlugin: U, getDefaultTemplate: _, cloneTimersOnSave: M, hasTimerBlocks: O, noHeader: V, insertDynamicContent: P, closeDynamicContent: A, handleDynamicContentOpen: y, ribbonObserver: c, updateRibbonOffset: h, AutoSaveController: ne, Toaster: re, FilterSelectionDrawer: se, HeaderWrapper: ce, LoadingWrapper: ae, SaveAsTemplateDrawer: ie, UnsubscribeWrapper: me };
127
128
  }
128
129
  });
129
130
  export {
130
- Re as default
131
+ Ie as default
131
132
  };
@@ -0,0 +1,17 @@
1
+ import n from "./AutoSaveController.vue2.js";
2
+ import t from "../../_virtual/_plugin-vue2_normalizer.js";
3
+ var o = function() {
4
+ var r = this, e = r._self._c;
5
+ return r._self._setupProxy, e("div", { staticClass: "d-n" });
6
+ }, s = [], _ = /* @__PURE__ */ t(
7
+ n,
8
+ o,
9
+ s,
10
+ !1,
11
+ null,
12
+ null
13
+ );
14
+ const f = _.exports;
15
+ export {
16
+ f as default
17
+ };
@@ -0,0 +1,13 @@
1
+ import { defineComponent as t } from "vue";
2
+ import { useAutoSave as r } from "../../composables/useAutoSave.js";
3
+ import { useSave as s } from "../../composables/useSave.js";
4
+ const f = /* @__PURE__ */ t({
5
+ __name: "AutoSaveController",
6
+ setup(a) {
7
+ const { save: e } = s(), o = () => e(!1, !0);
8
+ return r(o), { __sfc: !0, save: e, backgroundSave: o };
9
+ }
10
+ });
11
+ export {
12
+ f as default
13
+ };
@@ -0,0 +1,22 @@
1
+ import o from "./AutoSaveToggle.vue2.js";
2
+ /* empty css */
3
+ import i from "../../../_virtual/_plugin-vue2_normalizer.js";
4
+ var n = function() {
5
+ var e = this, a = e._self._c, t = e._self._setupProxy;
6
+ return t.isFeatureEnabled("autosave") ? a("div", { staticClass: "d-f a-i-c mr-3 auto-save-toggle", on: { mouseenter: function(s) {
7
+ t.isHovered = !0;
8
+ }, mouseleave: function(s) {
9
+ t.isHovered = !1;
10
+ } } }, [a(t.InToggle, { attrs: { id: "guido__autosave-toggle", name: "guido-autosave-toggle", checked: t.autosaveStore.isOn, disable: t.editorStore.loadingStatus }, on: { click: t.toggle } }), a("span", { staticClass: "ml-2 auto-save-toggle__label t-c-55" }, [e._v(" " + e._s(t.trans("email-editor.auto-save")) + " ")]), t.autosaveStore.status === t.AUTOSAVE_STATUS.SAVING ? a("span", { staticClass: "ml-2 d-f a-i-c f-s-1" }, [a(t.InLoading, { attrs: { "color-class": "i-c-53", size: "16" } }), a("span", { staticClass: "ml-1 t-c-53" }, [e._v(" " + e._s(t.trans("newsletter.saving")) + " ")])], 1) : t.lastSavedLabel ? a("span", { staticClass: "ml-2 f-s-1 t-c-53" }, [e._v(" " + e._s(t.lastSavedLabel) + " ")]) : e._e(), t.isHovered ? a(t.InInfoBox, { staticClass: "auto-save-toggle__info-box", attrs: { id: "guido__autosave-info-box", size: "small", variant: "information", "description-text": t.trans("email-editor.auto-save-description"), "title-text": t.trans("email-editor.auto-save-title") } }) : e._e()], 1) : e._e();
11
+ }, l = [], r = /* @__PURE__ */ i(
12
+ o,
13
+ n,
14
+ l,
15
+ !1,
16
+ null,
17
+ "2c964af4"
18
+ );
19
+ const d = r.exports;
20
+ export {
21
+ d as default
22
+ };
@@ -0,0 +1,19 @@
1
+ import { defineComponent as i, ref as f, computed as u } from "vue";
2
+ import { useConfig as l } from "../../../composables/useConfig.js";
3
+ import { useTranslations as p } from "../../../composables/useTranslations.js";
4
+ import { useAutosaveStore as c, AUTOSAVE_STATUS as t } from "../../../stores/autosave.js";
5
+ import { useEditorStore as S } from "../../../stores/editor.js";
6
+ import { formatLocalTime as d } from "../../../utils/timeUtil.js";
7
+ import { InToggle as g, InLoading as _, InInfoBox as A } from "@useinsider/design-system-vue";
8
+ const U = /* @__PURE__ */ i({
9
+ __name: "AutoSaveToggle",
10
+ setup(v) {
11
+ const { isFeatureEnabled: e } = l(), r = S(), o = c(), s = p(), n = f(!1), a = u(() => o.status !== t.SAVED || !o.lastSavedAt ? "" : d(o.lastSavedAt));
12
+ return { __sfc: !0, isFeatureEnabled: e, editorStore: r, autosaveStore: o, trans: s, isHovered: n, lastSavedLabel: a, toggle: (m) => {
13
+ o.isOn = m;
14
+ }, AUTOSAVE_STATUS: t, InInfoBox: A, InLoading: _, InToggle: g };
15
+ }
16
+ });
17
+ export {
18
+ U as default
19
+ };
@@ -1,11 +1,11 @@
1
- import i from "./RightSlot.vue2.js";
2
- import r from "../../../_virtual/_plugin-vue2_normalizer.js";
3
- var o = function() {
4
- var s = this, e = s._self._c, t = s._self._setupProxy;
5
- return e("div", { staticClass: "d-f a-i-c" }, [t.isLiquidEnabled && !t.editorStore.isVersionHistoryOpen ? e(t.InChips, { staticClass: "mr-3", attrs: { id: "guido__liquid-tag-chip", styles: "stroke", type: "default", value: "liquid-tags", "close-button": !1, "disabled-status": t.editorStore.loadingStatus, interactive: !1, text: t.trans("email-editor.liquid-tags-enabled") } }) : s._e(), t.editorStore.isVersionHistoryOpen ? e(t.RestoreButton) : e(t.EditorActions, { ref: "editorActionsRef" })], 1);
6
- }, a = [], n = /* @__PURE__ */ r(
7
- i,
8
- o,
1
+ import s from "./RightSlot.vue2.js";
2
+ import o from "../../../_virtual/_plugin-vue2_normalizer.js";
3
+ var r = function() {
4
+ var i = this, e = i._self._c, t = i._self._setupProxy;
5
+ return e("div", { staticClass: "d-f a-i-c" }, [t.editorStore.isVersionHistoryOpen ? i._e() : e(t.AutoSaveToggle), t.isLiquidEnabled && !t.editorStore.isVersionHistoryOpen ? e(t.InChips, { staticClass: "mr-1", attrs: { id: "guido__liquid-tag-chip", styles: "stroke", type: "default", value: "liquid-tags", "close-button": !1, "disabled-status": t.editorStore.loadingStatus, interactive: !1, text: t.trans("email-editor.liquid-tags-enabled") } }) : i._e(), t.editorStore.isVersionHistoryOpen ? e(t.RestoreButton) : e(t.EditorActions, { ref: "editorActionsRef" })], 1);
6
+ }, a = [], n = /* @__PURE__ */ o(
7
+ s,
8
+ r,
9
9
  a,
10
10
  !1,
11
11
  null,
@@ -1,22 +1,23 @@
1
1
  import { defineComponent as a, ref as f, computed as p } from "vue";
2
2
  import { useConfig as u } from "../../../composables/useConfig.js";
3
3
  import { useTranslations as d } from "../../../composables/useTranslations.js";
4
- import { useEditorStore as c } from "../../../stores/editor.js";
5
- import { InChips as l } from "@useinsider/design-system-vue";
6
- import _ from "./EditorActions.vue.js";
7
- import S from "./version-history/RestoreButton.vue.js";
8
- const x = /* @__PURE__ */ a({
4
+ import { useEditorStore as l } from "../../../stores/editor.js";
5
+ import { InChips as c } from "@useinsider/design-system-vue";
6
+ import _ from "./AutoSaveToggle.vue.js";
7
+ import S from "./EditorActions.vue.js";
8
+ import g from "./version-history/RestoreButton.vue.js";
9
+ const T = /* @__PURE__ */ a({
9
10
  __name: "RightSlot",
10
11
  setup(h, { expose: r }) {
11
- const { isFeatureEnabled: o } = u(), n = d(), i = c(), t = f(null), s = p(() => o("liquidSyntax"));
12
+ const { isFeatureEnabled: o } = u(), i = d(), n = l(), t = f(null), s = p(() => o("liquidSyntax"));
12
13
  return r({
13
14
  handleSave: (m) => {
14
15
  var e;
15
16
  return (e = t.value) == null ? void 0 : e.handleSave(m);
16
17
  }
17
- }), { __sfc: !0, isFeatureEnabled: o, trans: n, editorStore: i, editorActionsRef: t, isLiquidEnabled: s, InChips: l, EditorActions: _, RestoreButton: S };
18
+ }), { __sfc: !0, isFeatureEnabled: o, trans: i, editorStore: n, editorActionsRef: t, isLiquidEnabled: s, InChips: c, AutoSaveToggle: _, EditorActions: S, RestoreButton: g };
18
19
  }
19
20
  });
20
21
  export {
21
- x as default
22
+ T as default
22
23
  };
@@ -0,0 +1,71 @@
1
+ import { useConfig as m } from "./useConfig.js";
2
+ import { useAutosaveStore as A, AUTOSAVE_STATUS as s } from "../stores/autosave.js";
3
+ import { useEditorStore as E } from "../stores/editor.js";
4
+ import { computed as f, watch as p, onUnmounted as y } from "vue";
5
+ const I = 18e4, r = 6e4, T = (b) => {
6
+ const { isFeatureEnabled: S } = m(), a = E(), t = A();
7
+ let i = null, c = 0;
8
+ const u = f(
9
+ () => S("autosave") && t.isOn
10
+ ), g = () => a.hasChanges && a.isInSaveableState, h = () => ({
11
+ hasChanges: a.hasChanges,
12
+ isInSaveableState: a.isInSaveableState
13
+ }), l = async (e) => {
14
+ if (!u.value) {
15
+ console.debug("guido:autosave:skipped", { trigger: e, reason: "not-active" });
16
+ return;
17
+ }
18
+ if (!g()) {
19
+ console.debug("guido:autosave:skipped", { trigger: e, reason: "gates-blocked", gates: h() });
20
+ return;
21
+ }
22
+ if (t.status === s.SAVING) {
23
+ console.debug("guido:autosave:skipped", { trigger: e, reason: "already-saving" });
24
+ return;
25
+ }
26
+ console.debug("guido:autosave:save-start", { trigger: e }), t.status = s.SAVING;
27
+ try {
28
+ if (await b() === void 0) {
29
+ console.debug("guido:autosave:blocked", { trigger: e, reason: "save-returned-undefined" }), t.status = s.ERROR;
30
+ return;
31
+ }
32
+ a.hasChanges = !1, t.status = s.SAVED, t.lastSavedAt = /* @__PURE__ */ new Date(), console.debug("guido:autosave:save-complete", { trigger: e, at: t.lastSavedAt });
33
+ } catch (o) {
34
+ console.debug("guido:autosave:error", { trigger: e, error: o }), t.status = s.ERROR;
35
+ }
36
+ }, n = () => {
37
+ const { visibilityState: e } = document;
38
+ if (console.debug("guido:autosave:visibility-change", {
39
+ visibilityState: e,
40
+ isActive: u.value
41
+ }), e !== "hidden")
42
+ return;
43
+ const o = Date.now() - c;
44
+ if (o < r) {
45
+ console.debug("guido:autosave:visibility-debounced", {
46
+ sinceLastMs: o,
47
+ debounceMs: r
48
+ });
49
+ return;
50
+ }
51
+ c = Date.now(), l("visibility");
52
+ }, d = () => {
53
+ i && (clearInterval(i), i = null);
54
+ }, v = () => {
55
+ document.removeEventListener("visibilitychange", n), window.removeEventListener("pagehide", n);
56
+ };
57
+ p(
58
+ u,
59
+ (e) => {
60
+ console.debug("guido:autosave:active-changed", { active: e }), e ? (d(), i = setInterval(() => {
61
+ console.debug("guido:autosave:interval-tick"), l("interval");
62
+ }, I), document.addEventListener("visibilitychange", n), window.addEventListener("pagehide", n)) : (d(), v(), t.status = s.IDLE);
63
+ },
64
+ { immediate: !0 }
65
+ ), y(() => {
66
+ d(), v(), t.status = s.IDLE, t.lastSavedAt = null;
67
+ });
68
+ };
69
+ export {
70
+ T as useAutoSave
71
+ };
@@ -1,18 +1,18 @@
1
- import { useActionsApi as V } from "./useActionsApi.js";
2
- import { useConfig as x } from "./useConfig.js";
3
- import { useSaveStart as y, useSaveComplete as w } from "./useGuidoActions.js";
4
- import { useSyncModuleExtractor as C } from "./useSyncModuleExtractor.js";
5
- import { useStripoApi as H } from "../services/stripoApi.js";
6
- import { useTemplatePreparation as b } from "../utils/templatePreparation.js";
7
- import { useHtmlValidator as q } from "./useHtmlValidator.js";
8
- import { useCouponBlockValidator as L } from "./validators/useCouponBlockValidator.js";
9
- import { useLiquidValidator as P } from "./validators/useLiquidValidator.js";
10
- const h = () => {
11
- const o = y(), s = w(), { validateHtml: r } = q(), { validateLiquidSyntax: l } = P(), { validateCouponBlockTags: n } = L(), { callbacks: a, isFeatureEnabled: d } = x(), { extractSyncModuleData: u } = C(), { setSyncModuleUnsubscriptionPages: c } = H(), { editorSave: m } = V();
12
- return { save: async (p = !1) => {
1
+ import { useActionsApi as x } from "./useActionsApi.js";
2
+ import { useConfig as y } from "./useConfig.js";
3
+ import { useSaveStart as w, useSaveComplete as C } from "./useGuidoActions.js";
4
+ import { useSyncModuleExtractor as H } from "./useSyncModuleExtractor.js";
5
+ import { useStripoApi as b } from "../services/stripoApi.js";
6
+ import { useTemplatePreparation as q } from "../utils/templatePreparation.js";
7
+ import { useHtmlValidator as L } from "./useHtmlValidator.js";
8
+ import { useCouponBlockValidator as P } from "./validators/useCouponBlockValidator.js";
9
+ import { useLiquidValidator as E } from "./validators/useLiquidValidator.js";
10
+ const j = () => {
11
+ const o = w(), s = C(), { validateHtml: r } = L(), { validateLiquidSyntax: l } = E(), { validateCouponBlockTags: n } = P(), { callbacks: a, isFeatureEnabled: d } = y(), { extractSyncModuleData: u } = H(), { setSyncModuleUnsubscriptionPages: c } = b(), { editorSave: m } = x();
12
+ return { save: async (p = !1, f = !1) => {
13
13
  var i;
14
14
  o();
15
- const { prepareTemplateDetails: f } = b(), t = await f();
15
+ const { prepareTemplateDetails: v } = q(), t = await v();
16
16
  if (!n(t.compiledHtml))
17
17
  return;
18
18
  if (d("liquidSyntax")) {
@@ -23,10 +23,10 @@ const h = () => {
23
23
  if ((i = a.value) != null && i.externalValidation && !await a.value.externalValidation(t))
24
24
  return;
25
25
  await m();
26
- const { unsubscribePayload: v, stripoModules: S } = u(t.rawHtml);
27
- return await c(v), t.modules = S, p || s(t), t;
26
+ const { unsubscribePayload: S, stripoModules: V } = u(t.rawHtml);
27
+ return await c(S), t.modules = V, p || s({ ...t, silent: f }), t;
28
28
  } };
29
29
  };
30
30
  export {
31
- h as useSave
31
+ j as useSave
32
32
  };
@@ -8,8 +8,8 @@ import { regenerateMobileProductRows as C } from "./controls/main/utils.js";
8
8
  import { ensureMobileCssRulesExist as g, setMobileLayoutOptOut as d, hasMobileLayoutOptOut as A } from "./controls/mobileLayout/cssRules.js";
9
9
  import { RecommendationConfigService as c } from "./services/configService.js";
10
10
  import { useRecommendationExtensionStore as p } from "./store/recommendation.js";
11
- import { getDefaultTemplate as D } from "./templates/grid/template.js";
12
- const f = B.Recommendation, l = "recommendation-block-v2", m = "recommendation-id";
11
+ import { getDefaultTemplate as E } from "./templates/grid/template.js";
12
+ const f = B.Recommendation, a = "recommendation-block-v2", l = "recommendation-id";
13
13
  let h = !1;
14
14
  class q extends b {
15
15
  constructor() {
@@ -56,7 +56,7 @@ class q extends b {
56
56
  */
57
57
  getTemplate() {
58
58
  const t = this._generateNextId();
59
- return this._pendingBlockId = t, D(t);
59
+ return this._pendingBlockId = t, E(t);
60
60
  }
61
61
  /**
62
62
  * Called when a new block is dropped into the template
@@ -68,20 +68,13 @@ class q extends b {
68
68
  onCreated(t) {
69
69
  const e = this._pendingBlockId ?? this._generateNextId();
70
70
  this._pendingBlockId = null, this._assignRecommendationId(t, e);
71
- const { config: i, wasFreshDrop: o } = c.initializeConfig(
72
- this.api,
73
- t,
74
- { recommendationId: e }
75
- ), s = p();
76
- if (s.setCurrentBlock(e), o) {
77
- g(this.api);
78
- const a = this._getBlockElement(t);
79
- a && (d(this.api, a, !0), C({
80
- currentNode: t,
81
- documentModifier: this.api.getDocumentModifier()
82
- }));
83
- }
84
- s.patchCurrentBlockConfig({ language: i.language }, { triggerRefetch: !1 });
71
+ const o = c.initializeConfig(this.api, t, { recommendationId: e }), i = p();
72
+ i.setCurrentBlock(e), g(this.api);
73
+ const s = this._getBlockElement(t);
74
+ s && (d(this.api, s, !0), C({
75
+ currentNode: t,
76
+ documentModifier: this.api.getDocumentModifier()
77
+ })), i.patchCurrentBlockConfig({ language: o.language }, { triggerRefetch: !1 });
85
78
  }
86
79
  /**
87
80
  * Called when the document changes or template is loaded
@@ -104,10 +97,10 @@ class q extends b {
104
97
  c.needsMigration(t) && this._migrateFromLegacy(t);
105
98
  try {
106
99
  h || (g(this.api), h = !0);
107
- const e = c.getConfig(t), i = this._getBlockElement(t);
108
- if (i) {
109
- const o = !e.mobileLayoutEnabled;
110
- A(i) !== o && d(this.api, i, o);
100
+ const e = c.getConfig(t), o = this._getBlockElement(t);
101
+ if (o) {
102
+ const i = !e.mobileLayoutEnabled;
103
+ A(o) !== i && d(this.api, o, i);
111
104
  }
112
105
  } catch {
113
106
  }
@@ -131,10 +124,10 @@ class q extends b {
131
124
  let t = 0;
132
125
  try {
133
126
  const e = this.api.getDocumentRoot();
134
- e && "querySelectorAll" in e && e.querySelectorAll(`.${l}`).forEach((o) => {
135
- if ("getAttribute" in o) {
136
- const s = o.getAttribute(m), a = s ? parseInt(s) : 0;
137
- a > t && (t = a);
127
+ e && "querySelectorAll" in e && e.querySelectorAll(`.${a}`).forEach((i) => {
128
+ if ("getAttribute" in i) {
129
+ const s = i.getAttribute(l), m = s ? parseInt(s) : 0;
130
+ m > t && (t = m);
138
131
  }
139
132
  });
140
133
  } catch {
@@ -148,11 +141,11 @@ class q extends b {
148
141
  * added classes via setAttribute.
149
142
  */
150
143
  _assignRecommendationId(t, e) {
151
- const i = this._getBlockElement(t);
152
- if (!i)
144
+ const o = this._getBlockElement(t);
145
+ if (!o)
153
146
  return;
154
- const o = this.api.getDocumentModifier();
155
- o.modifyHtml(i).setAttribute(m, e.toString()), o.apply(new y(`Assign recommendation ID ${e}`));
147
+ const i = this.api.getDocumentModifier();
148
+ i.modifyHtml(o).setAttribute(l, e.toString()), i.apply(new y(`Assign recommendation ID ${e}`));
156
149
  }
157
150
  /**
158
151
  * Gets the recommendation-id from a block node
@@ -161,11 +154,11 @@ class q extends b {
161
154
  const e = this._getBlockElement(t);
162
155
  if (!e || !("getAttribute" in e))
163
156
  return null;
164
- const i = e.getAttribute(m);
165
- if (!i)
157
+ const o = e.getAttribute(l);
158
+ if (!o)
166
159
  return null;
167
- const o = parseInt(i);
168
- return Number.isNaN(o) ? null : o;
160
+ const i = parseInt(o);
161
+ return Number.isNaN(i) ? null : i;
169
162
  }
170
163
  /**
171
164
  * Gets the block element (the element with BLOCK_CLASS)
@@ -173,10 +166,10 @@ class q extends b {
173
166
  _getBlockElement(t) {
174
167
  if ("getAttribute" in t) {
175
168
  const e = t.getAttribute("class");
176
- if (e && e.includes(l))
169
+ if (e && e.includes(a))
177
170
  return t;
178
171
  }
179
- return "querySelector" in t ? t.querySelector(`.${l}`) ?? null : null;
172
+ return "querySelector" in t ? t.querySelector(`.${a}`) ?? null : null;
180
173
  }
181
174
  /**
182
175
  * Migrate configuration from legacy format
@@ -85,28 +85,21 @@ class C {
85
85
  *
86
86
  * Called when a block is first created (dropped into template).
87
87
  * Can optionally merge in partial config from migration.
88
- *
89
- * The `wasFreshDrop` flag distinguishes a brand-new drop (no prior config)
90
- * from a clone (Stripo replays the source's setNodeConfig payload before
91
- * onCreated fires). Callers use this to skip side-effects already inherited
92
- * from the source.
93
88
  * @example
94
89
  * // In Block.onCreated lifecycle
95
- * const { config, wasFreshDrop } = RecommendationConfigService.initializeConfig(this.api, node);
90
+ * RecommendationConfigService.initializeConfig(this.api, node);
96
91
  * @param api - Stripo extension API with document modifier
97
92
  * @param node - The immutable HTML node to initialize
98
93
  * @param partialConfig - Optional partial config to merge with defaults
99
- * @returns The initialized configuration and whether the node was a fresh drop
94
+ * @returns The initialized configuration
100
95
  */
101
96
  static initializeConfig(i, t, o) {
102
- if (this.hasConfig(t))
103
- return { config: o ? this.updateConfig(i, t, o, "Initialize recommendation block") : this.getConfig(t), wasFreshDrop: !1 };
104
97
  const n = o ? this.mergeWithDefaults(o) : this.cloneDefaults();
105
98
  return this.saveConfig(i, t, n, "Initialize recommendation block"), g({
106
99
  currentNode: t,
107
100
  documentModifier: i.getDocumentModifier(),
108
101
  currency: n.currency
109
- }), { config: n, wasFreshDrop: !0 };
102
+ }), n;
110
103
  }
111
104
  /**
112
105
  * Save complete configuration to a node
@@ -150,7 +143,7 @@ class C {
150
143
  const c = t.getAttribute("data-row-spacing");
151
144
  c && (o.rowSpacing = parseInt(c) || e.rowSpacing);
152
145
  }
153
- return this.initializeConfig(i, t, o).config;
146
+ return this.initializeConfig(i, t, o);
154
147
  }
155
148
  /**
156
149
  * Check if configuration needs migration
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}.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-25780af6]{--ribbon-offset: 0px;position:relative;width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__container[data-v-25780af6]{width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__no-header[data-v-25780af6]{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-fffc13d6]{--ribbon-offset: 0px;position:relative;width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__container[data-v-fffc13d6]{width:100%;height:calc(100vh - 128px - var(--ribbon-offset))}.guido-editor__no-header[data-v-fffc13d6]{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}
@@ -178,6 +178,8 @@ export declare const FeaturesSchema: v.ObjectSchema<{
178
178
  readonly modulesDisabled: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
179
179
  /** Enable Liquid template syntax */
180
180
  readonly liquidSyntax: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
181
+ /** Enable autosave (3-min interval + tab-hide). User toggles on/off from the header. */
182
+ readonly autosave: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
181
183
  }, undefined>;
182
184
  /**
183
185
  * Default block types available in Stripo
@@ -501,6 +503,8 @@ export declare const GuidoConfigSchema: v.ObjectSchema<{
501
503
  readonly modulesDisabled: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
502
504
  /** Enable Liquid template syntax */
503
505
  readonly liquidSyntax: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
506
+ /** Enable autosave (3-min interval + tab-hide). User toggles on/off from the header. */
507
+ readonly autosave: v.OptionalSchema<v.BooleanSchema<undefined>, false>;
504
508
  }, undefined>, {}>;
505
509
  /** Block configuration */
506
510
  readonly blocks: v.OptionalSchema<v.ObjectSchema<{
@@ -11,7 +11,7 @@ declare const _default: import("vue").DefineComponent<__VLS_TypePropsToOption<__
11
11
  close: () => void;
12
12
  };
13
13
  hasChanges: import("vue").ComputedRef<boolean>;
14
- saveSilent: () => Promise<Omit<SavedTemplateDetails, "metadata"> | undefined> | undefined;
14
+ saveSilent: () => Promise<Omit<SavedTemplateDetails, "metadata" | "silent"> | undefined> | undefined;
15
15
  }, {}, {}, {}, import("vue/types/v3-component-options.js").ComponentOptionsMixin, import("vue/types/v3-component-options.js").ComponentOptionsMixin, {
16
16
  "dynamic-content:open": (detail: {
17
17
  text: string;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue/types/v3-component-options.js").ComponentOptionsMixin, import("vue/types/v3-component-options.js").ComponentOptionsMixin, {}, string, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue/types/v3-component-options.js").ComponentOptionsMixin, import("vue/types/v3-component-options.js").ComponentOptionsMixin, {}, string, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
2
+ export default _default;
@@ -1,4 +1,4 @@
1
1
  declare const _default: import("vue").DefineComponent<{}, {
2
- handleSave: (isSilent: boolean) => Promise<Omit<import("../../../@types/stripo.js").SavedTemplateDetails, "metadata"> | undefined> | undefined;
2
+ handleSave: (isSilent: boolean) => Promise<Omit<import("../../../@types/stripo.js").SavedTemplateDetails, "metadata" | "silent"> | undefined> | undefined;
3
3
  }, {}, {}, {}, import("vue/types/v3-component-options.js").ComponentOptionsMixin, import("vue/types/v3-component-options.js").ComponentOptionsMixin, {}, string, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
4
4
  export default _default;
@@ -1,4 +1,4 @@
1
1
  declare const _default: import("vue").DefineComponent<{}, {
2
- handleSave: (isSilent: boolean) => Promise<Omit<import("../../../@types/stripo.js").SavedTemplateDetails, "metadata"> | undefined> | undefined;
2
+ handleSave: (isSilent: boolean) => Promise<Omit<import("../../../@types/stripo.js").SavedTemplateDetails, "metadata" | "silent"> | undefined> | undefined;
3
3
  }, {}, {}, {}, import("vue/types/v3-component-options.js").ComponentOptionsMixin, import("vue/types/v3-component-options.js").ComponentOptionsMixin, {}, string, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
4
4
  export default _default;
@@ -1,4 +1,4 @@
1
1
  declare const _default: import("vue").DefineComponent<{}, {
2
- handleSave: (isSilent: boolean) => Promise<Omit<import("../../../@types/stripo.js").SavedTemplateDetails, "metadata"> | undefined> | undefined;
2
+ handleSave: (isSilent: boolean) => Promise<Omit<import("../../../@types/stripo.js").SavedTemplateDetails, "metadata" | "silent"> | undefined> | undefined;
3
3
  }, {}, {}, {}, import("vue/types/v3-component-options.js").ComponentOptionsMixin, import("vue/types/v3-component-options.js").ComponentOptionsMixin, {}, string, Readonly<import("vue").ExtractPropTypes<{}>>, {}>;
4
4
  export default _default;
@@ -0,0 +1,3 @@
1
+ type SaveSilent = () => Promise<unknown>;
2
+ export declare const useAutoSave: (saveSilent: SaveSilent) => void;
3
+ export {};
@@ -62,6 +62,7 @@ export declare const useConfig: () => {
62
62
  unsubscribe: boolean;
63
63
  modulesDisabled: boolean;
64
64
  liquidSyntax: boolean;
65
+ autosave: boolean;
65
66
  };
66
67
  blocks: {
67
68
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -159,6 +160,7 @@ export declare const useConfig: () => {
159
160
  unsubscribe: boolean;
160
161
  modulesDisabled: boolean;
161
162
  liquidSyntax: boolean;
163
+ autosave: boolean;
162
164
  } | null>;
163
165
  blocks: import("vue").ComputedRef<{
164
166
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -1,4 +1,4 @@
1
1
  import type { SavedTemplateDetails } from '@@/Types/stripo';
2
2
  export declare const useSave: () => {
3
- save: (isSilent?: boolean) => Promise<Omit<SavedTemplateDetails, "metadata"> | undefined>;
3
+ save: (suppressEmit?: boolean, silent?: boolean) => Promise<Omit<SavedTemplateDetails, "metadata" | "silent"> | undefined>;
4
4
  };
@@ -95,23 +95,15 @@ 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.
103
98
  * @example
104
99
  * // In Block.onCreated lifecycle
105
- * const { config, wasFreshDrop } = RecommendationConfigService.initializeConfig(this.api, node);
100
+ * RecommendationConfigService.initializeConfig(this.api, node);
106
101
  * @param api - Stripo extension API with document modifier
107
102
  * @param node - The immutable HTML node to initialize
108
103
  * @param partialConfig - Optional partial config to merge with defaults
109
- * @returns The initialized configuration and whether the node was a fresh drop
104
+ * @returns The initialized configuration
110
105
  */
111
- static initializeConfig(api: DocumentModifierApi, node: ImmutableHtmlNode, partialConfig?: PartialNodeConfig): {
112
- config: RecommendationNodeConfig;
113
- wasFreshDrop: boolean;
114
- };
106
+ static initializeConfig(api: DocumentModifierApi, node: ImmutableHtmlNode, partialConfig?: PartialNodeConfig): RecommendationNodeConfig;
115
107
  /**
116
108
  * Save complete configuration to a node
117
109
  *
@@ -0,0 +1,12 @@
1
+ export declare const AUTOSAVE_STATUS: {
2
+ readonly IDLE: "idle";
3
+ readonly SAVING: "saving";
4
+ readonly SAVED: "saved";
5
+ readonly ERROR: "error";
6
+ };
7
+ export type AutosaveStatus = typeof AUTOSAVE_STATUS[keyof typeof AUTOSAVE_STATUS];
8
+ export declare const useAutosaveStore: import("pinia").StoreDefinition<"guidoAutosave", {
9
+ isOn: boolean;
10
+ lastSavedAt: Date | null;
11
+ status: AutosaveStatus;
12
+ }, {}, {}>;
@@ -67,6 +67,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
67
67
  unsubscribe: boolean;
68
68
  modulesDisabled: boolean;
69
69
  liquidSyntax: boolean;
70
+ autosave: boolean;
70
71
  };
71
72
  blocks: {
72
73
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -170,6 +171,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
170
171
  unsubscribe: boolean;
171
172
  modulesDisabled: boolean;
172
173
  liquidSyntax: boolean;
174
+ autosave: boolean;
173
175
  };
174
176
  blocks: {
175
177
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -273,6 +275,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
273
275
  unsubscribe: boolean;
274
276
  modulesDisabled: boolean;
275
277
  liquidSyntax: boolean;
278
+ autosave: boolean;
276
279
  };
277
280
  blocks: {
278
281
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -376,6 +379,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
376
379
  unsubscribe: boolean;
377
380
  modulesDisabled: boolean;
378
381
  liquidSyntax: boolean;
382
+ autosave: boolean;
379
383
  };
380
384
  blocks: {
381
385
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -479,6 +483,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
479
483
  unsubscribe: boolean;
480
484
  modulesDisabled: boolean;
481
485
  liquidSyntax: boolean;
486
+ autosave: boolean;
482
487
  };
483
488
  blocks: {
484
489
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -582,6 +587,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
582
587
  unsubscribe: boolean;
583
588
  modulesDisabled: boolean;
584
589
  liquidSyntax: boolean;
590
+ autosave: boolean;
585
591
  };
586
592
  blocks: {
587
593
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -685,6 +691,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
685
691
  unsubscribe: boolean;
686
692
  modulesDisabled: boolean;
687
693
  liquidSyntax: boolean;
694
+ autosave: boolean;
688
695
  };
689
696
  blocks: {
690
697
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -788,6 +795,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
788
795
  unsubscribe: boolean;
789
796
  modulesDisabled: boolean;
790
797
  liquidSyntax: boolean;
798
+ autosave: boolean;
791
799
  };
792
800
  blocks: {
793
801
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -891,6 +899,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
891
899
  unsubscribe: boolean;
892
900
  modulesDisabled: boolean;
893
901
  liquidSyntax: boolean;
902
+ autosave: boolean;
894
903
  };
895
904
  blocks: {
896
905
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -994,6 +1003,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
994
1003
  unsubscribe: boolean;
995
1004
  modulesDisabled: boolean;
996
1005
  liquidSyntax: boolean;
1006
+ autosave: boolean;
997
1007
  };
998
1008
  blocks: {
999
1009
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -1097,6 +1107,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1097
1107
  unsubscribe: boolean;
1098
1108
  modulesDisabled: boolean;
1099
1109
  liquidSyntax: boolean;
1110
+ autosave: boolean;
1100
1111
  };
1101
1112
  blocks: {
1102
1113
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -1200,6 +1211,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1200
1211
  unsubscribe: boolean;
1201
1212
  modulesDisabled: boolean;
1202
1213
  liquidSyntax: boolean;
1214
+ autosave: boolean;
1203
1215
  };
1204
1216
  blocks: {
1205
1217
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -1303,6 +1315,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1303
1315
  unsubscribe: boolean;
1304
1316
  modulesDisabled: boolean;
1305
1317
  liquidSyntax: boolean;
1318
+ autosave: boolean;
1306
1319
  };
1307
1320
  blocks: {
1308
1321
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -1406,6 +1419,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1406
1419
  unsubscribe: boolean;
1407
1420
  modulesDisabled: boolean;
1408
1421
  liquidSyntax: boolean;
1422
+ autosave: boolean;
1409
1423
  };
1410
1424
  blocks: {
1411
1425
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -1509,6 +1523,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1509
1523
  unsubscribe: boolean;
1510
1524
  modulesDisabled: boolean;
1511
1525
  liquidSyntax: boolean;
1526
+ autosave: boolean;
1512
1527
  };
1513
1528
  blocks: {
1514
1529
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -1612,6 +1627,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1612
1627
  unsubscribe: boolean;
1613
1628
  modulesDisabled: boolean;
1614
1629
  liquidSyntax: boolean;
1630
+ autosave: boolean;
1615
1631
  };
1616
1632
  blocks: {
1617
1633
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -1715,6 +1731,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1715
1731
  unsubscribe: boolean;
1716
1732
  modulesDisabled: boolean;
1717
1733
  liquidSyntax: boolean;
1734
+ autosave: boolean;
1718
1735
  };
1719
1736
  blocks: {
1720
1737
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -1818,6 +1835,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1818
1835
  unsubscribe: boolean;
1819
1836
  modulesDisabled: boolean;
1820
1837
  liquidSyntax: boolean;
1838
+ autosave: boolean;
1821
1839
  };
1822
1840
  blocks: {
1823
1841
  excludeDefaults: ("amp-accordion" | "amp-carousel" | "amp-form-controls" | "banner-block" | "button-block" | "html-block" | "image-block" | "menu-block" | "social-block" | "spacer-block" | "text-block" | "timer-block" | "video-block")[];
@@ -263,4 +263,27 @@ export declare const useEditorStore: import("pinia").StoreDefinition<"guidoEdito
263
263
  templateId: string;
264
264
  syncModulesEnabled: boolean;
265
265
  }>) => boolean;
266
+ isInSaveableState: (state: {
267
+ loadingStatus: boolean;
268
+ isCodeEditorOpen: boolean;
269
+ isSaveAsTemplateDrawerOpen: boolean;
270
+ isVersionHistoryOpen: boolean;
271
+ isPreviewModeOpen: boolean;
272
+ editorVisualMode: string;
273
+ hasChanges: boolean;
274
+ isStripoInitialized: boolean;
275
+ templateId: string;
276
+ syncModulesEnabled: boolean;
277
+ } & import("pinia").PiniaCustomStateProperties<{
278
+ loadingStatus: boolean;
279
+ isCodeEditorOpen: boolean;
280
+ isSaveAsTemplateDrawerOpen: boolean;
281
+ isVersionHistoryOpen: boolean;
282
+ isPreviewModeOpen: boolean;
283
+ editorVisualMode: string;
284
+ hasChanges: boolean;
285
+ isStripoInitialized: boolean;
286
+ templateId: string;
287
+ syncModulesEnabled: boolean;
288
+ }>) => boolean;
266
289
  }, {}>;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Formats a Date as local time in 24-hour format.
3
+ * Pass `useTimezone = true` to append the UTC offset and city name — useful when the
4
+ * timestamp must clearly communicate the client's timezone.
5
+ * @example formatLocalTime(date) -> 12:20:02
6
+ * @example formatLocalTime(date, true) -> 12:20:02 (UTC+3 Istanbul)
7
+ */
8
+ export declare const formatLocalTime: (date: Date, useTimezone?: boolean) => string;
@@ -0,0 +1,17 @@
1
+ import { defineStore as e } from "pinia";
2
+ const t = {
3
+ IDLE: "idle",
4
+ SAVING: "saving",
5
+ SAVED: "saved",
6
+ ERROR: "error"
7
+ }, o = e("guidoAutosave", {
8
+ state: () => ({
9
+ isOn: !1,
10
+ lastSavedAt: null,
11
+ status: t.IDLE
12
+ })
13
+ });
14
+ export {
15
+ t as AUTOSAVE_STATUS,
16
+ o as useAutosaveStore
17
+ };
@@ -25,7 +25,9 @@ const o = e("guidoEditor", {
25
25
  isExportButtonDisabled: (i) => i.loadingStatus || i.isVersionHistoryOpen,
26
26
  isSaveAsButtonDisabled: (i) => i.loadingStatus || i.isVersionHistoryOpen,
27
27
  isTestButtonDisabled: (i) => i.loadingStatus || i.isVersionHistoryOpen,
28
- isSaveButtonDisabled: (i) => i.loadingStatus || i.isVersionHistoryOpen || i.isPreviewModeOpen
28
+ isSaveButtonDisabled: (i) => i.loadingStatus || i.isVersionHistoryOpen || i.isPreviewModeOpen,
29
+ // Autosave
30
+ isInSaveableState: (i) => !i.loadingStatus && !i.isVersionHistoryOpen && !i.isPreviewModeOpen && !i.isCodeEditorOpen
29
31
  }
30
32
  });
31
33
  export {
@@ -0,0 +1,19 @@
1
+ const r = (t) => {
2
+ const e = -t.getTimezoneOffset(), o = e >= 0 ? "+" : "-", n = Math.abs(e), s = Math.floor(n / 60), i = n % 60;
3
+ return i === 0 ? `UTC${o}${s}` : `UTC${o}${s}:${String(i).padStart(2, "0")}`;
4
+ }, a = () => {
5
+ var e;
6
+ const { timeZone: t } = new Intl.DateTimeFormat().resolvedOptions();
7
+ return ((e = t.split("/").pop()) == null ? void 0 : e.replace(/_/g, " ")) ?? t;
8
+ }, c = (t, e = !1) => {
9
+ const o = t.toLocaleTimeString([], {
10
+ hour: "2-digit",
11
+ hour12: !1,
12
+ minute: "2-digit",
13
+ second: "2-digit"
14
+ });
15
+ return e ? `${o} (${r(t)} ${a()})` : o;
16
+ };
17
+ export {
18
+ c as formatLocalTime
19
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@useinsider/guido",
3
- "version": "3.2.0-beta.51b7991",
3
+ "version": "3.2.0-beta.556da4b",
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",