@useinsider/guido 3.19.0-beta.cac604e → 3.19.0-beta.cd75ff0

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 (77) hide show
  1. package/README.md +1 -0
  2. package/dist/@types/config/schemas.js +39 -37
  3. package/dist/components/Guido.vue.js +4 -4
  4. package/dist/components/Guido.vue2.js +83 -75
  5. package/dist/components/organisms/AutoSaveController.vue2.js +10 -8
  6. package/dist/components/organisms/base/AuthErrorModal.vue.js +18 -0
  7. package/dist/components/organisms/base/AuthErrorModal.vue2.js +42 -0
  8. package/dist/components/organisms/header/EditorActions.vue.js +5 -5
  9. package/dist/components/organisms/header/EditorActions.vue2.js +83 -46
  10. package/dist/components/organisms/header/LeftSlot.vue.js +5 -5
  11. package/dist/components/organisms/header/LeftSlot.vue2.js +16 -12
  12. package/dist/components/organisms/validation/ValidationErrorDrawer.vue.js +20 -0
  13. package/dist/components/organisms/validation/ValidationErrorDrawer.vue2.js +50 -0
  14. package/dist/components/wrappers/WpDrawer.vue.js +9 -7
  15. package/dist/components/wrappers/WpDrawer.vue2.js +1 -1
  16. package/dist/components/wrappers/WpModal.vue.js +1 -1
  17. package/dist/components/wrappers/WpModal.vue2.js +4 -3
  18. package/dist/composables/useAuthError.js +52 -0
  19. package/dist/composables/useAutoSave.js +44 -42
  20. package/dist/composables/useHtmlCompiler.js +37 -26
  21. package/dist/composables/useHtmlValidator.js +2 -0
  22. package/dist/composables/useHttp.js +64 -54
  23. package/dist/composables/useLegacyStrategyConversion.js +23 -19
  24. package/dist/composables/useSave.js +92 -45
  25. package/dist/composables/useSaveValidation.js +284 -0
  26. package/dist/composables/useSaveValidationRollout.js +20 -0
  27. package/dist/composables/useStripo.js +24 -20
  28. package/dist/composables/useToaster.js +17 -15
  29. package/dist/composables/validators/useCouponBlockValidator.js +24 -21
  30. package/dist/composables/validators/useLiquidValidator.js +15 -15
  31. package/dist/composables/validators/useUnsubscribeBlockValidator.js +34 -27
  32. package/dist/config/migrator/index.js +14 -13
  33. package/dist/config/migrator/recommendationPartnerMigrator.js +50 -0
  34. package/dist/enums/extensions/recommendationBlock.js +30 -29
  35. package/dist/enums/save-validation.js +4 -0
  36. package/dist/extensions/Blocks/Recommendation/block.js +54 -88
  37. package/dist/extensions/Blocks/Recommendation/store/recommendation.js +32 -51
  38. package/dist/extensions/Blocks/Recommendation/utils/partnerScope.js +11 -12
  39. package/dist/guido.css +1 -1
  40. package/dist/services/settingsApi.js +11 -0
  41. package/dist/src/@types/config/schemas.d.ts +4 -0
  42. package/dist/src/@types/save-validation.d.ts +19 -0
  43. package/dist/src/components/Guido.vue.d.ts +17 -1
  44. package/dist/src/components/organisms/base/AuthErrorModal.vue.d.ts +2 -0
  45. package/dist/src/components/organisms/header/EditorActions.vue.d.ts +17 -1
  46. package/dist/src/components/organisms/header/HeaderWrapper.vue.d.ts +17 -1
  47. package/dist/src/components/organisms/header/RightSlot.vue.d.ts +17 -1
  48. package/dist/src/components/organisms/validation/ValidationErrorDrawer.vue.d.ts +2 -0
  49. package/dist/src/components/wrappers/WpDrawer.vue.d.ts +4 -3
  50. package/dist/src/components/wrappers/WpModal.vue.d.ts +5 -1
  51. package/dist/src/composables/useAuthError.d.ts +12 -0
  52. package/dist/src/composables/useAutoSave.d.ts +4 -1
  53. package/dist/src/composables/useConfig.d.ts +2 -0
  54. package/dist/src/composables/useHtmlValidator.d.ts +21 -0
  55. package/dist/src/composables/useHttp.d.ts +2 -0
  56. package/dist/src/composables/useSave.d.ts +6 -1
  57. package/dist/src/composables/useSaveValidation.d.ts +6 -0
  58. package/dist/src/composables/useSaveValidationRollout.d.ts +4 -0
  59. package/dist/src/composables/validators/useCouponBlockValidator.d.ts +3 -0
  60. package/dist/src/composables/validators/useUnsubscribeBlockValidator.d.ts +10 -0
  61. package/dist/src/config/migrator/index.d.ts +2 -1
  62. package/dist/src/config/migrator/recommendationPartnerMigrator.d.ts +19 -0
  63. package/dist/src/enums/extensions/recommendationBlock.d.ts +1 -1
  64. package/dist/src/enums/save-validation.d.ts +27 -0
  65. package/dist/src/extensions/Blocks/Recommendation/block.d.ts +0 -6
  66. package/dist/src/extensions/Blocks/Recommendation/store/recommendation.d.ts +0 -5
  67. package/dist/src/extensions/Blocks/Recommendation/utils/partnerScope.d.ts +13 -7
  68. package/dist/src/services/settingsApi.d.ts +6 -0
  69. package/dist/src/stores/config.d.ts +32 -0
  70. package/dist/src/stores/save-validation.d.ts +22 -0
  71. package/dist/src/stores/setup-settings.d.ts +4 -0
  72. package/dist/src/utils/templatePreparation.d.ts +30 -0
  73. package/dist/stores/config.js +84 -67
  74. package/dist/stores/save-validation.js +30 -0
  75. package/dist/stores/setup-settings.js +10 -0
  76. package/dist/utils/templatePreparation.js +39 -36
  77. package/package.json +1 -1
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Empties recommendation blocks copied in from another account and stamps the
3
+ * current one, before the editor ever sees the template (SD-151945).
4
+ *
5
+ * Runs at load rather than on document change: the stamp makes it a one-time
6
+ * pass per template, the campaign-URL rule only ever applies to the loaded HTML,
7
+ * and Stripo takes the result as the original document, so nothing is dirtied.
8
+ */
9
+ export interface PartnerScopeMigrationOptions {
10
+ partnerName: string;
11
+ /** `undefined` when the host does not pass the map — the unstamped check is then off. */
12
+ campaignUrls?: Record<string, unknown>;
13
+ rrsEnabled: boolean;
14
+ }
15
+ /**
16
+ * @param html Template HTML, already normalized by `migrateRecommendation`.
17
+ * @returns Migrated HTML, or the original when no block needed a reset or a stamp.
18
+ */
19
+ export declare function migrateRecommendationPartnerScope(html: string, { partnerName, campaignUrls, rrsEnabled }: PartnerScopeMigrationOptions): string;
@@ -8,8 +8,8 @@ export declare const URLS: {
8
8
  NEWSLETTER_RECOMMENDATIONS_PATH: string;
9
9
  MIGRATE_STRATEGIES_PATH: string;
10
10
  TEMPLATE_MIGRATE_STRATEGIES_PATH: string;
11
- RECOMMENDATION_STRATEGY_CREATE_PATH: string;
12
11
  };
12
+ export declare const strategyCreatePath: () => string;
13
13
  export declare const strategyEditPath: (strategyId: string | number) => string;
14
14
  export declare const MIGRATION_PATHS: Record<RrsMigrationTarget['scope'], string>;
15
15
  export declare const QUERY_PARAMS: {
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Stable discriminators for each save-validation rule site — one per rule that
3
+ * can produce a drawer row, kept in sync with the collector + legacy validator chains.
4
+ */
5
+ export declare enum SaveValidationRule {
6
+ CouponTagModified = "coupon-tag-modified",
7
+ DuplicateUnsubBlock = "duplicate-unsub-block",
8
+ UnsubBlockMissingTemplate = "unsub-block-missing-template",
9
+ LiquidSyntax = "liquid-syntax",
10
+ InvalidLinks = "invalid-links",
11
+ LinkCheckInProgress = "link-check-in-progress",
12
+ CustomFieldScript = "custom-field-script",
13
+ EmptyHtml = "empty-html",
14
+ MismatchedBraces = "mismatched-braces",
15
+ InvalidDynamicTag = "invalid-dynamic-tag",
16
+ InvalidCustomField = "invalid-custom-field",
17
+ ConditionsSyntax = "conditions-syntax",
18
+ WrongEqualityOperator = "wrong-equality-operator",
19
+ MissingIfEndif = "missing-if-endif",
20
+ ConditionTagSpacing = "condition-tag-spacing",
21
+ BracesInsideIfTag = "braces-inside-if-tag",
22
+ RecommendationRequiredFields = "recommendation-required-fields",
23
+ RecommendationFilters = "recommendation-filters",
24
+ InvalidImageType = "invalid-image-type",
25
+ CheckboxUnsubGroup = "checkbox-unsub-group",
26
+ RadioButtonUnsubGroup = "radio-button-unsub-group"
27
+ }
@@ -137,12 +137,6 @@ export declare class RecommendationBlock extends Block {
137
137
  * heals templates saved before this feature without dirtying current ones.
138
138
  */
139
139
  private _healHidePriceAttr;
140
- /**
141
- * Empties a block copied in from another account: its strategy and attribute types
142
- * are account-scoped and would not resolve here (SD-151945). Layout, locale and
143
- * currency stay. The write stamps the current account, so the next pass is a no-op.
144
- */
145
- private _resetForeignPartnerConfig;
146
140
  /**
147
141
  * Gets the recommendation-id from a block node
148
142
  */
@@ -543,11 +543,6 @@ export declare const useRecommendationExtensionStore: import("pinia").StoreDefin
543
543
  openRemoveStrategyModal(): void;
544
544
  closeRemoveStrategyModal(): void;
545
545
  fetchStrategies(): Promise<void>;
546
- /**
547
- * Drops what the store still holds for a block whose account-scoped config was just
548
- * reset in the document (SD-151945), so the panel re-seeds from the emptied node.
549
- */
550
- resetPartnerScopedConfig(id: number): void;
551
546
  /**
552
547
  * Clears what a block inherited from the id it arrived holding, so the panel fetches for it.
553
548
  * A re-dropped saved module can keep the source's id — and its "already fetched" entry —
@@ -4,24 +4,30 @@ import type { RecommendationNodeConfig } from '../types/nodeConfig';
4
4
  * string or an entry that names a strategy.
5
5
  */
6
6
  export declare function isDefinedCampaignUrl(value: unknown): boolean;
7
- /** Recommendation block ids present in the HTML the host loaded the editor with. */
8
- export declare function loadedRecommendationIds(html: string): Set<number>;
9
7
  export interface PartnerScopeInputs {
10
8
  config: Pick<RecommendationNodeConfig, 'strategyId' | 'partnerName'>;
11
9
  blockId: number;
12
10
  partnerName: string;
13
11
  /** `undefined` when the host does not pass the map — the unstamped check is then off. */
14
12
  campaignUrls: Record<string, unknown> | undefined;
15
- loadedIds: Set<number>;
13
+ /** With RRS off `buildCampaignUrl` drops the block's map entry, so the map says nothing. */
14
+ rrsEnabled: boolean;
16
15
  }
17
16
  /**
18
17
  * Whether a block carries another account's strategy config (SD-151945).
19
18
  *
20
19
  * A stamped block is judged by its stamp. An unstamped one predates the stamp, so it
21
20
  * falls back to the backend's own signal: a cross-account duplicate drops the block's
22
- * strategy entry from the campaign URL map but leaves the HTML untouched. That check
23
- * only holds for blocks the host loaded, whose map entry the host actually sent — a
24
- * saved module or a clone arrives under an id the map never had.
21
+ * strategy entry from the campaign URL map but leaves the HTML untouched. That signal
22
+ * only holds while RRS is on, and only for the HTML the host loaded — which is what
23
+ * the caller, a load-time migrator, runs on.
25
24
  */
26
- export declare function carriesForeignPartnerConfig({ config, blockId, partnerName, campaignUrls, loadedIds }: PartnerScopeInputs): boolean;
25
+ export declare function carriesForeignPartnerConfig({ config, blockId, partnerName, campaignUrls, rrsEnabled }: PartnerScopeInputs): boolean;
26
+ /**
27
+ * Whether an unstamped block's ownership can be decided at all. `carriesForeignPartnerConfig`
28
+ * answers `false` both for "clean" and for "cannot tell", and stamping on the latter would
29
+ * record a foreign block as this account's and silence every later check. A block with no
30
+ * strategy reference has nothing foreign to carry, so it is always decidable.
31
+ */
32
+ export declare function canJudgeUnstampedBlock({ config, campaignUrls, rrsEnabled }: Pick<PartnerScopeInputs, 'config' | 'campaignUrls' | 'rrsEnabled'>): boolean;
27
33
  export declare function withoutCustomAttributes(composition: string[]): string[];
@@ -0,0 +1,6 @@
1
+ export interface SetupSettingsResponse {
2
+ isEmailAutoSaveEnabled?: boolean | number;
3
+ }
4
+ export declare const useSettingsApi: () => {
5
+ getSetupSettings: () => Promise<SetupSettingsResponse>;
6
+ };
@@ -107,6 +107,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
107
107
  autosave: boolean;
108
108
  allowlistEnabled: boolean;
109
109
  linkTrackingIds: boolean;
110
+ allowSaveWithErrors: boolean;
110
111
  reusableRecommendationStrategy: boolean;
111
112
  };
112
113
  blocks: {
@@ -251,6 +252,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
251
252
  autosave: boolean;
252
253
  allowlistEnabled: boolean;
253
254
  linkTrackingIds: boolean;
255
+ allowSaveWithErrors: boolean;
254
256
  reusableRecommendationStrategy: boolean;
255
257
  };
256
258
  blocks: {
@@ -395,6 +397,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
395
397
  autosave: boolean;
396
398
  allowlistEnabled: boolean;
397
399
  linkTrackingIds: boolean;
400
+ allowSaveWithErrors: boolean;
398
401
  reusableRecommendationStrategy: boolean;
399
402
  };
400
403
  blocks: {
@@ -539,6 +542,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
539
542
  autosave: boolean;
540
543
  allowlistEnabled: boolean;
541
544
  linkTrackingIds: boolean;
545
+ allowSaveWithErrors: boolean;
542
546
  reusableRecommendationStrategy: boolean;
543
547
  };
544
548
  blocks: {
@@ -683,6 +687,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
683
687
  autosave: boolean;
684
688
  allowlistEnabled: boolean;
685
689
  linkTrackingIds: boolean;
690
+ allowSaveWithErrors: boolean;
686
691
  reusableRecommendationStrategy: boolean;
687
692
  };
688
693
  blocks: {
@@ -827,6 +832,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
827
832
  autosave: boolean;
828
833
  allowlistEnabled: boolean;
829
834
  linkTrackingIds: boolean;
835
+ allowSaveWithErrors: boolean;
830
836
  reusableRecommendationStrategy: boolean;
831
837
  };
832
838
  blocks: {
@@ -971,6 +977,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
971
977
  autosave: boolean;
972
978
  allowlistEnabled: boolean;
973
979
  linkTrackingIds: boolean;
980
+ allowSaveWithErrors: boolean;
974
981
  reusableRecommendationStrategy: boolean;
975
982
  };
976
983
  blocks: {
@@ -1115,6 +1122,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1115
1122
  autosave: boolean;
1116
1123
  allowlistEnabled: boolean;
1117
1124
  linkTrackingIds: boolean;
1125
+ allowSaveWithErrors: boolean;
1118
1126
  reusableRecommendationStrategy: boolean;
1119
1127
  };
1120
1128
  blocks: {
@@ -1259,6 +1267,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1259
1267
  autosave: boolean;
1260
1268
  allowlistEnabled: boolean;
1261
1269
  linkTrackingIds: boolean;
1270
+ allowSaveWithErrors: boolean;
1262
1271
  reusableRecommendationStrategy: boolean;
1263
1272
  };
1264
1273
  blocks: {
@@ -1403,6 +1412,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1403
1412
  autosave: boolean;
1404
1413
  allowlistEnabled: boolean;
1405
1414
  linkTrackingIds: boolean;
1415
+ allowSaveWithErrors: boolean;
1406
1416
  reusableRecommendationStrategy: boolean;
1407
1417
  };
1408
1418
  blocks: {
@@ -1547,6 +1557,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1547
1557
  autosave: boolean;
1548
1558
  allowlistEnabled: boolean;
1549
1559
  linkTrackingIds: boolean;
1560
+ allowSaveWithErrors: boolean;
1550
1561
  reusableRecommendationStrategy: boolean;
1551
1562
  };
1552
1563
  blocks: {
@@ -1691,6 +1702,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1691
1702
  autosave: boolean;
1692
1703
  allowlistEnabled: boolean;
1693
1704
  linkTrackingIds: boolean;
1705
+ allowSaveWithErrors: boolean;
1694
1706
  reusableRecommendationStrategy: boolean;
1695
1707
  };
1696
1708
  blocks: {
@@ -1835,6 +1847,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1835
1847
  autosave: boolean;
1836
1848
  allowlistEnabled: boolean;
1837
1849
  linkTrackingIds: boolean;
1850
+ allowSaveWithErrors: boolean;
1838
1851
  reusableRecommendationStrategy: boolean;
1839
1852
  };
1840
1853
  blocks: {
@@ -1979,6 +1992,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
1979
1992
  autosave: boolean;
1980
1993
  allowlistEnabled: boolean;
1981
1994
  linkTrackingIds: boolean;
1995
+ allowSaveWithErrors: boolean;
1982
1996
  reusableRecommendationStrategy: boolean;
1983
1997
  };
1984
1998
  blocks: {
@@ -2123,6 +2137,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
2123
2137
  autosave: boolean;
2124
2138
  allowlistEnabled: boolean;
2125
2139
  linkTrackingIds: boolean;
2140
+ allowSaveWithErrors: boolean;
2126
2141
  reusableRecommendationStrategy: boolean;
2127
2142
  };
2128
2143
  blocks: {
@@ -2267,6 +2282,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
2267
2282
  autosave: boolean;
2268
2283
  allowlistEnabled: boolean;
2269
2284
  linkTrackingIds: boolean;
2285
+ allowSaveWithErrors: boolean;
2270
2286
  reusableRecommendationStrategy: boolean;
2271
2287
  };
2272
2288
  blocks: {
@@ -2411,6 +2427,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
2411
2427
  autosave: boolean;
2412
2428
  allowlistEnabled: boolean;
2413
2429
  linkTrackingIds: boolean;
2430
+ allowSaveWithErrors: boolean;
2414
2431
  reusableRecommendationStrategy: boolean;
2415
2432
  };
2416
2433
  blocks: {
@@ -2454,6 +2471,18 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
2454
2471
  };
2455
2472
  } | null;
2456
2473
  } & import("pinia").PiniaCustomStateProperties<ConfigStoreState>) => number;
2474
+ /**
2475
+ * Transactional campaigns keep the hard save block. Reactive getter, so
2476
+ * consumers stay safe in setup where a raw config read snapshots null (SD-147945).
2477
+ */
2478
+ isTransactional(): boolean;
2479
+ /**
2480
+ * Soft-save policy: may the editor persist a template with validation errors?
2481
+ * Default true (host sets features.allowSaveWithErrors=false for transactional +
2482
+ * unsub). Reactive getter defaulting true — reading via isFeatureEnabled would
2483
+ * floor to false pre-init and momentarily hide the soft-save option (SD-147430).
2484
+ */
2485
+ allowSaveWithErrors(): boolean;
2457
2486
  /**
2458
2487
  * Get the username
2459
2488
  */
@@ -2555,6 +2584,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
2555
2584
  autosave: boolean;
2556
2585
  allowlistEnabled: boolean;
2557
2586
  linkTrackingIds: boolean;
2587
+ allowSaveWithErrors: boolean;
2558
2588
  reusableRecommendationStrategy: boolean;
2559
2589
  };
2560
2590
  blocks: {
@@ -2699,6 +2729,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
2699
2729
  autosave: boolean;
2700
2730
  allowlistEnabled: boolean;
2701
2731
  linkTrackingIds: boolean;
2732
+ allowSaveWithErrors: boolean;
2702
2733
  reusableRecommendationStrategy: boolean;
2703
2734
  };
2704
2735
  blocks: {
@@ -2845,6 +2876,7 @@ export declare const useConfigStore: import("pinia").StoreDefinition<"guido-conf
2845
2876
  autosave: boolean;
2846
2877
  allowlistEnabled: boolean;
2847
2878
  linkTrackingIds: boolean;
2879
+ allowSaveWithErrors: boolean;
2848
2880
  reusableRecommendationStrategy: boolean;
2849
2881
  };
2850
2882
  blocks: {
@@ -0,0 +1,22 @@
1
+ import type { SaveValidationError, SaveValidationIntent } from '@@/Types/save-validation';
2
+ interface SaveValidationState {
3
+ errors: SaveValidationError[];
4
+ isDrawerOpen: boolean;
5
+ intent: SaveValidationIntent | null;
6
+ }
7
+ export declare const useSaveValidationStore: import("pinia").StoreDefinition<"guidoSaveValidation", SaveValidationState, {
8
+ errorCount: (state: {
9
+ errors: {
10
+ rule: import("../enums/save-validation").SaveValidationRule;
11
+ message: string;
12
+ detail?: string | undefined;
13
+ type: import("../enums/toaster").ToasterTypeOptions;
14
+ }[];
15
+ isDrawerOpen: boolean;
16
+ intent: SaveValidationIntent | null;
17
+ } & import("pinia").PiniaCustomStateProperties<SaveValidationState>) => number;
18
+ }, {
19
+ open(errors: SaveValidationError[], intent: SaveValidationIntent): void;
20
+ close(): void;
21
+ }>;
22
+ export {};
@@ -0,0 +1,4 @@
1
+ export declare const useSetupSettingsStore: import("pinia").StoreDefinition<"guidoSetupSettings", {
2
+ isEmailAutoSaveEnabled: boolean;
3
+ isLoaded: boolean;
4
+ }, {}, {}>;
@@ -15,6 +15,36 @@ import type { SavedTemplateDetails } from '@@/Types/stripo';
15
15
  * same source), so it is reliably present regardless of how the block was added.
16
16
  */
17
17
  export declare function getHtmlReferencedUnsubscribePages(html: string): number[];
18
+ /**
19
+ * Hydrates the recommendation extension store from the persisted `esd-ext-config`
20
+ * JSON blob on each `.recommendation-block-v2` element in the raw editor HTML.
21
+ *
22
+ * Why this runs before `compileHtml`:
23
+ * The HTML compiler's `prepareRecommendationBlocks` rule asks
24
+ * `useRecommendation.buildCampaignUrl(id)` to assemble each block's API URL
25
+ * from `blockStates[id].recommendationConfigs`. That Pinia entry is normally
26
+ * populated by `RecommendationBlockControl._syncNodeConfigToStore`, which
27
+ * only fires when the user *selects* the block. Saving without ever
28
+ * selecting a migrated block would otherwise build the URL from empty/
29
+ * default state and the campaign URL would not be saved.
30
+ *
31
+ * Why we read raw HTML, not compiled HTML:
32
+ * Stripo's `compileEmail()` strips editor-only attributes (`esd-*`) from the
33
+ * compiled output, so `esd-ext-config` is gone by the time the compiler
34
+ * pipeline sees the HTML. The raw HTML from `getTemplateData()` preserves
35
+ * the attribute as the migrator wrote it.
36
+ *
37
+ * Reference data pre-load:
38
+ * When the template has recommendation blocks, this also pre-loads the
39
+ * shared reference data (languages / currency list) so the save-time
40
+ * required-fields validator can check each block's locale and currency
41
+ * against the partner's real options. Best-effort — on a fetch failure the
42
+ * validator degrades to its empty-list guard (`requiredFields.ts`) instead
43
+ * of blocking the save or stranding the Save button.
44
+ */
45
+ declare function hydrateRecommendationStoreFromRawHtml(rawHtml: string): Promise<void>;
18
46
  export declare const useTemplatePreparation: () => {
19
47
  prepareTemplateDetails: () => Promise<Omit<SavedTemplateDetails, "metadata">>;
48
+ hydrateRecommendationStore: typeof hydrateRecommendationStoreFromRawHtml;
20
49
  };
50
+ export {};
@@ -1,8 +1,8 @@
1
- import "../@types/config/schemas.js";
1
+ import { MessageType as r } from "../@types/config/schemas.js";
2
2
  import "../@types/config/defaults.js";
3
- import { parseConfig as o } from "../@types/config/validator.js";
4
- import { defineStore as a } from "pinia";
5
- const s = a("guido-config", {
3
+ import { parseConfig as a } from "../@types/config/validator.js";
4
+ import { defineStore as o } from "pinia";
5
+ const g = o("guido-config", {
6
6
  state: () => ({
7
7
  initialized: !1,
8
8
  config: null
@@ -11,135 +11,152 @@ const s = a("guido-config", {
11
11
  /**
12
12
  * Get the identity configuration
13
13
  */
14
- identity: (e) => {
15
- var i;
16
- return ((i = e.config) == null ? void 0 : i.identity) ?? null;
14
+ identity: (i) => {
15
+ var e;
16
+ return ((e = i.config) == null ? void 0 : e.identity) ?? null;
17
17
  },
18
18
  /**
19
19
  * Get the partner configuration
20
20
  */
21
- partner: (e) => {
22
- var i;
23
- return ((i = e.config) == null ? void 0 : i.partner) ?? null;
21
+ partner: (i) => {
22
+ var e;
23
+ return ((e = i.config) == null ? void 0 : e.partner) ?? null;
24
24
  },
25
25
  /**
26
26
  * Get the template configuration
27
27
  */
28
- template: (e) => {
29
- var i;
30
- return ((i = e.config) == null ? void 0 : i.template) ?? null;
28
+ template: (i) => {
29
+ var e;
30
+ return ((e = i.config) == null ? void 0 : e.template) ?? null;
31
31
  },
32
32
  /**
33
33
  * Get the editor configuration
34
34
  */
35
- editor: (e) => {
36
- var i;
37
- return ((i = e.config) == null ? void 0 : i.editor) ?? null;
35
+ editor: (i) => {
36
+ var e;
37
+ return ((e = i.config) == null ? void 0 : e.editor) ?? null;
38
38
  },
39
39
  /**
40
40
  * Get the UI configuration
41
41
  */
42
- ui: (e) => {
43
- var i;
44
- return ((i = e.config) == null ? void 0 : i.ui) ?? null;
42
+ ui: (i) => {
43
+ var e;
44
+ return ((e = i.config) == null ? void 0 : e.ui) ?? null;
45
45
  },
46
46
  /**
47
47
  * Get the features configuration
48
48
  */
49
- features: (e) => {
50
- var i;
51
- return ((i = e.config) == null ? void 0 : i.features) ?? null;
49
+ features: (i) => {
50
+ var e;
51
+ return ((e = i.config) == null ? void 0 : e.features) ?? null;
52
52
  },
53
53
  /**
54
54
  * Get the blocks configuration
55
55
  */
56
- blocks: (e) => {
57
- var i;
58
- return ((i = e.config) == null ? void 0 : i.blocks) ?? null;
56
+ blocks: (i) => {
57
+ var e;
58
+ return ((e = i.config) == null ? void 0 : e.blocks) ?? null;
59
59
  },
60
60
  /**
61
61
  * Get the compiler configuration
62
62
  */
63
- compiler: (e) => {
64
- var i;
65
- return ((i = e.config) == null ? void 0 : i.compiler) ?? null;
63
+ compiler: (i) => {
64
+ var e;
65
+ return ((e = i.config) == null ? void 0 : e.compiler) ?? null;
66
66
  },
67
67
  /**
68
68
  * Get the callbacks configuration
69
69
  */
70
- callbacks: (e) => {
71
- var i;
72
- return ((i = e.config) == null ? void 0 : i.callbacks) ?? null;
70
+ callbacks: (i) => {
71
+ var e;
72
+ return ((e = i.config) == null ? void 0 : e.callbacks) ?? null;
73
73
  },
74
74
  /**
75
75
  * Get the template ID
76
76
  */
77
- templateId: (e) => {
78
- var i, t;
79
- return ((t = (i = e.config) == null ? void 0 : i.identity) == null ? void 0 : t.templateId) ?? "";
77
+ templateId: (i) => {
78
+ var e, t;
79
+ return ((t = (e = i.config) == null ? void 0 : e.identity) == null ? void 0 : t.templateId) ?? "";
80
80
  },
81
81
  /**
82
82
  * Get the user ID
83
83
  */
84
- userId: (e) => {
85
- var i, t;
86
- return ((t = (i = e.config) == null ? void 0 : i.identity) == null ? void 0 : t.userId) ?? "";
84
+ userId: (i) => {
85
+ var e, t;
86
+ return ((t = (e = i.config) == null ? void 0 : e.identity) == null ? void 0 : t.userId) ?? "";
87
87
  },
88
88
  /**
89
89
  * Get the variation ID
90
90
  */
91
- variationId: (e) => {
92
- var i, t;
93
- return ((t = (i = e.config) == null ? void 0 : i.identity) == null ? void 0 : t.variationId) ?? "";
91
+ variationId: (i) => {
92
+ var e, t;
93
+ return ((t = (e = i.config) == null ? void 0 : e.identity) == null ? void 0 : t.variationId) ?? "";
94
94
  },
95
95
  /**
96
96
  * Get the campaign ID
97
97
  */
98
- campaignId: (e) => {
99
- var i, t;
100
- return ((t = (i = e.config) == null ? void 0 : i.identity) == null ? void 0 : t.campaignId) ?? "";
98
+ campaignId: (i) => {
99
+ var e, t;
100
+ return ((t = (e = i.config) == null ? void 0 : e.identity) == null ? void 0 : t.campaignId) ?? "";
101
101
  },
102
102
  /**
103
103
  * Get the template-library ID of the host template
104
104
  */
105
- templateLibraryId: (e) => {
106
- var i, t;
107
- return ((t = (i = e.config) == null ? void 0 : i.identity) == null ? void 0 : t.templateLibraryId) ?? "";
105
+ templateLibraryId: (i) => {
106
+ var e, t;
107
+ return ((t = (e = i.config) == null ? void 0 : e.identity) == null ? void 0 : t.templateLibraryId) ?? "";
108
108
  },
109
109
  /**
110
110
  * Get the partner name
111
111
  */
112
- partnerName: (e) => {
113
- var i, t;
114
- return ((t = (i = e.config) == null ? void 0 : i.partner) == null ? void 0 : t.name) ?? "";
112
+ partnerName: (i) => {
113
+ var e, t;
114
+ return ((t = (e = i.config) == null ? void 0 : e.partner) == null ? void 0 : t.name) ?? "";
115
115
  },
116
116
  /**
117
117
  * Get the product type
118
118
  */
119
- productType: (e) => {
120
- var i, t;
121
- return ((t = (i = e.config) == null ? void 0 : i.partner) == null ? void 0 : t.productType) ?? 60;
119
+ productType: (i) => {
120
+ var e, t;
121
+ return ((t = (e = i.config) == null ? void 0 : e.partner) == null ? void 0 : t.productType) ?? 60;
122
122
  },
123
123
  /**
124
124
  * Get the message type
125
125
  */
126
- messageType: (e) => {
127
- var i, t;
128
- return ((t = (i = e.config) == null ? void 0 : i.partner) == null ? void 0 : t.messageType) ?? 1;
126
+ messageType: (i) => {
127
+ var e, t;
128
+ return ((t = (e = i.config) == null ? void 0 : e.partner) == null ? void 0 : t.messageType) ?? 1;
129
+ },
130
+ /**
131
+ * Transactional campaigns keep the hard save block. Reactive getter, so
132
+ * consumers stay safe in setup where a raw config read snapshots null (SD-147945).
133
+ */
134
+ isTransactional() {
135
+ return this.messageType === r.TRANSACTIONAL;
136
+ },
137
+ /**
138
+ * Soft-save policy: may the editor persist a template with validation errors?
139
+ * Default true (host sets features.allowSaveWithErrors=false for transactional +
140
+ * unsub). Reactive getter defaulting true — reading via isFeatureEnabled would
141
+ * floor to false pre-init and momentarily hide the soft-save option (SD-147430).
142
+ */
143
+ allowSaveWithErrors() {
144
+ var i;
145
+ return ((i = this.features) == null ? void 0 : i.allowSaveWithErrors) ?? !0;
129
146
  },
130
147
  /**
131
148
  * Get the username
132
149
  */
133
- username: (e) => {
134
- var i, t;
135
- return ((t = (i = e.config) == null ? void 0 : i.partner) == null ? void 0 : t.username) ?? "Guido User";
150
+ username: (i) => {
151
+ var e, t;
152
+ return ((t = (e = i.config) == null ? void 0 : e.partner) == null ? void 0 : t.username) ?? "Guido User";
136
153
  },
137
154
  /**
138
155
  * Check if header should be shown
139
156
  */
140
- showHeader: (e) => {
141
- var i, t;
142
- return ((t = (i = e.config) == null ? void 0 : i.ui) == null ? void 0 : t.showHeader) ?? !0;
157
+ showHeader: (i) => {
158
+ var e, t;
159
+ return ((t = (e = i.config) == null ? void 0 : e.ui) == null ? void 0 : t.showHeader) ?? !0;
143
160
  },
144
161
  /**
145
162
  * Check if a specific feature is enabled.
@@ -147,18 +164,18 @@ const s = a("guido-config", {
147
164
  * flash on during loading, then disappear once config resolves.
148
165
  */
149
166
  // eslint-disable-next-line @stylistic/max-len
150
- isFeatureEnabled: (e) => (i) => {
167
+ isFeatureEnabled: (i) => (e) => {
151
168
  var t, n;
152
- return ((n = (t = e.config) == null ? void 0 : t.features) == null ? void 0 : n[i]) ?? !1;
169
+ return ((n = (t = i.config) == null ? void 0 : t.features) == null ? void 0 : n[e]) ?? !1;
153
170
  }
154
171
  },
155
172
  actions: {
156
173
  /**
157
174
  * Initialize the config store with validated configuration
158
175
  */
159
- init(e) {
160
- const i = o(e);
161
- this.config = i, this.initialized = !0;
176
+ init(i) {
177
+ const e = a(i);
178
+ this.config = e, this.initialized = !0;
162
179
  },
163
180
  /**
164
181
  * Reset the config store
@@ -169,5 +186,5 @@ const s = a("guido-config", {
169
186
  }
170
187
  });
171
188
  export {
172
- s as useConfigStore
189
+ g as useConfigStore
173
190
  };
@@ -0,0 +1,30 @@
1
+ import { defineStore as n } from "pinia";
2
+ function i(e) {
3
+ const t = /* @__PURE__ */ new Set();
4
+ return e.filter((r) => {
5
+ const s = JSON.stringify([r.rule, r.message, r.detail ?? ""]);
6
+ return t.has(s) ? !1 : (t.add(s), !0);
7
+ });
8
+ }
9
+ const o = n("guidoSaveValidation", {
10
+ state: () => ({
11
+ errors: [],
12
+ isDrawerOpen: !1,
13
+ intent: null
14
+ }),
15
+ getters: {
16
+ errorCount: (e) => e.errors.length
17
+ },
18
+ actions: {
19
+ open(e, t) {
20
+ this.errors = i(e), this.intent = t, this.isDrawerOpen = !0;
21
+ },
22
+ // Errors are retained so rows don't blank during the ~300ms close animation.
23
+ close() {
24
+ this.isDrawerOpen = !1;
25
+ }
26
+ }
27
+ });
28
+ export {
29
+ o as useSaveValidationStore
30
+ };