@designcrowd/library.brand-page 6.0.6-json-prereq-7 → 6.0.6-undo-2

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 (134) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-SPVLPi8n.js → CreatedOnBrandCrowd-de-de-BSFTp6yl.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-CvtrCPHd.js → CreatedOnBrandCrowd-en-us-rPQrOY1L.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-BSsac7jB.js → CreatedOnBrandCrowd-es-es-awd8-QFa.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-BauENV2a.js → CreatedOnBrandCrowd-fr-ca-CwJ4J7Ow.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-BauENV2a.js → CreatedOnBrandCrowd-fr-fr-CwJ4J7Ow.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-Be0hawmr.js → CreatedOnBrandCrowd-pt-br-BXYfTLlD.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-Be0hawmr.js → CreatedOnBrandCrowd-pt-pt-BXYfTLlD.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-CTjE2Tmn.js → CreatedOnDcom-de-de-kX6u1lNK.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-DnZRp5Pg.js → CreatedOnDcom-en-us-Bkq5sgFs.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-J-9qvjy8.js → CreatedOnDcom-es-es-zCAC9ujj.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-BDNHqm4G.js → CreatedOnDcom-fr-ca-BYz6Wzg5.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-B_2-ceuO.js → CreatedOnDcom-fr-fr-CN-wAaSH.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-76lJVrAY.js → CreatedOnDcom-pt-br-BrK4Ngg-.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-TQp3CNcu.js → CreatedOnDcom-pt-pt-AfI_g7xJ.js} +1 -1
  15. package/lib/src/brand-page/models/index.d.ts +26 -34
  16. package/lib/src/brand-page/utils/logo.util.d.ts +14 -0
  17. package/lib/src/brand-page/utils/logo.util.js +19 -0
  18. package/lib/src/brand-page/utils/migration.util.js +0 -11
  19. package/lib/src/brand-page/utils/text.util.d.ts +1 -1
  20. package/lib/src/brand-page/utils/text.util.js +0 -32
  21. package/lib/src/brand-page/utils/vcard.util.js +4 -4
  22. package/lib/src/brand-page/utils/view-model.util.d.ts +0 -1
  23. package/lib/src/brand-page/utils/view-model.util.js +20 -68
  24. package/lib/src/brand-page/view-models.d.ts +3 -3
  25. package/lib/src/brand-page-maker/components/action-bar/ActionBar.mixin.d.ts +1 -1
  26. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.d.ts +1 -1
  27. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.js +1 -1
  28. package/lib/src/brand-page-maker/components/brand-page-maker/BrandPageMakerTemplateSettings.mixin.d.ts +1 -1
  29. package/lib/src/brand-page-maker/components/brandpage-maker-page/BrandPageMakerPage.mixin.d.ts +1 -1
  30. package/lib/src/brand-page-maker/components/brandpage-settings/ReplaceImageSettingsForBrandPageSettings.mixin.d.ts +1 -1
  31. package/lib/src/brand-page-maker/components/seo-settings/SeoPageSettings.mixin.d.ts +1 -1
  32. package/lib/src/brand-page-maker/components/seo-settings/UrlSettings.mixin.d.ts +1 -1
  33. package/lib/src/brand-page-maker/components/social-share/SocialShare.mixin.d.ts +1 -1
  34. package/lib/src/brand-page-maker/components/template-preview/TemplatePreview.mixin.d.ts +1 -1
  35. package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +115 -20
  36. package/lib/src/brand-page-maker/composables/useUndoRedoFlag.d.ts +6 -7
  37. package/lib/src/brand-page-maker/composables/useUndoRedoFlag.js +7 -12
  38. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.d.ts +1 -1
  39. package/lib/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.js +12 -7
  40. package/lib/src/brand-page-maker/stores/history/history-store.d.ts +6 -0
  41. package/lib/src/brand-page-maker/stores/history/history-store.js +11 -1
  42. package/lib/src/brand-page-maker/utils/index.d.ts +0 -2
  43. package/lib/src/brand-page-maker/utils/index.js +0 -2
  44. package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +5 -19
  45. package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +5 -35
  46. package/lib/src/brand-page-maker/validators/field.validator.js +2 -10
  47. package/lib/src/css/library.brand-page-brandCrowd.min.css +1 -1
  48. package/lib/src/css/library.brand-page-brandPage.min.css +1 -1
  49. package/lib/src/css/library.brand-page-designCom.min.css +1 -1
  50. package/lib/src/css/library.brand-page-preview.min.css +1 -1
  51. package/lib/src/index.d.ts +0 -1
  52. package/lib/src/index.mjs +145 -150
  53. package/lib/src/{lib-Bh9Y5_-x.js → lib-BYFWAvIq.js} +55864 -56119
  54. package/lib/src/lib.js +0 -2
  55. package/lib/src/metadata-builder/builders/reorder-pages-builder.d.ts +13 -0
  56. package/lib/src/metadata-builder/builders/reorder-pages-builder.js +49 -0
  57. package/lib/src/metadata-builder/builders/update-content-text-builder.js +15 -20
  58. package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +3 -5
  59. package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +1 -1
  60. package/lib/src/metadata-builder/helpers/rte.helpers.js +3 -6
  61. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +1 -14
  62. package/lib/src/shared/content-settings/components/content-settings/ContentSettings.mixin.d.ts +1 -1
  63. package/lib/src/shared/content-settings/components/content-settings/ReplaceImageSettingsForContent.mixin.d.ts +1 -1
  64. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.d.ts +1 -1
  65. package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +3 -12
  66. package/lib/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.d.ts +1 -0
  67. package/lib/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.js +18 -7
  68. package/lib/src/shared/logo-settings/components/LogoSettings.mixin.d.ts +1 -1
  69. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +1 -1
  70. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +1 -1
  71. package/lib/src/shared/publish-brand-page-with-state/PublishBrandPageModalWithState.mixin.d.ts +1 -1
  72. package/lib/src/shared/style-settings/components/StyleSettings.mixin.d.ts +1 -1
  73. package/lib/src/utils/minify-brand-page-metadata.js +1 -4
  74. package/package.json +1 -1
  75. package/src/brand-page/components/text-wrapper/TextWrapper.vue +6 -17
  76. package/src/brand-page/models/index.ts +26 -35
  77. package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
  78. package/src/brand-page/utils/logo.util.test.ts +54 -1
  79. package/src/brand-page/utils/logo.util.ts +23 -0
  80. package/src/brand-page/utils/migration.util.ts +0 -12
  81. package/src/brand-page/utils/style.util.test.ts +0 -51
  82. package/src/brand-page/utils/text.util.ts +4 -43
  83. package/src/brand-page/utils/vcard.util.ts +4 -6
  84. package/src/brand-page/utils/view-model.util.ts +26 -94
  85. package/src/brand-page/view-models.ts +2 -3
  86. package/src/brand-page-maker/components/brand-page-maker/BrandPageMaker.mixin.ts +1 -1
  87. package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +123 -22
  88. package/src/brand-page-maker/composables/useUndoRedoFlag.ts +7 -13
  89. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.test.ts +53 -0
  90. package/src/brand-page-maker/stores/brandpage-maker/brandpage-maker-store.ts +13 -16
  91. package/src/brand-page-maker/stores/history/history-store.test.ts +51 -0
  92. package/src/brand-page-maker/stores/history/history-store.ts +19 -0
  93. package/src/brand-page-maker/utils/index.ts +0 -2
  94. package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +3 -4
  95. package/src/brand-page-maker/utils/rich-text-editor.util.ts +11 -50
  96. package/src/brand-page-maker/validators/field.validator.ts +2 -10
  97. package/src/lib.ts +0 -9
  98. package/src/metadata-builder/builders/reorder-pages-builder.ts +80 -0
  99. package/src/metadata-builder/builders/update-content-text-builder.ts +18 -32
  100. package/src/metadata-builder/builders/update-content-text-style-builder.ts +4 -6
  101. package/src/metadata-builder/helpers/rte.helpers.ts +7 -11
  102. package/src/shared/content-settings/components/GenericTextInput.mixin.ts +1 -17
  103. package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +3 -16
  104. package/src/shared/content-settings/components/text-style-field/TextStyleField.mixin.ts +19 -11
  105. package/src/utils/minify-brand-page-metadata.ts +1 -4
  106. package/lib/src/brand-page/utils/migration/migration-18.d.ts +0 -2
  107. package/lib/src/brand-page/utils/migration/migration-18.js +0 -71
  108. package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.d.ts +0 -22
  109. package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.js +0 -22
  110. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +0 -14
  111. package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +0 -39
  112. package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +0 -28
  113. package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +0 -66
  114. package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +0 -33
  115. package/lib/src/brand-page-maker/utils/rte-render.util.js +0 -50
  116. package/lib/src/brand-page-maker/utils/rte-sanitize.util.d.ts +0 -6
  117. package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +0 -158
  118. package/lib/src/brand-page-maker/utils/rte-serializer.d.ts +0 -5
  119. package/lib/src/brand-page-maker/utils/rte-serializer.js +0 -112
  120. package/src/brand-page/utils/migration/migration-18.test.ts +0 -72
  121. package/src/brand-page/utils/migration/migration-18.ts +0 -83
  122. package/src/brand-page/utils/text.util.test.ts +0 -47
  123. package/src/brand-page/utils/view-model.pageid-rte.test.ts +0 -104
  124. package/src/brand-page-maker/__fixtures__/complex-rte.fixture.ts +0 -23
  125. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +0 -52
  126. package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +0 -42
  127. package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +0 -113
  128. package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +0 -200
  129. package/src/brand-page-maker/utils/rte-render-parity.test.ts +0 -156
  130. package/src/brand-page-maker/utils/rte-render.util.ts +0 -77
  131. package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +0 -158
  132. package/src/brand-page-maker/utils/rte-sanitize.util.ts +0 -195
  133. package/src/brand-page-maker/utils/rte-serializer.ts +0 -172
  134. package/src/brand-page-maker/validators/field.validator.test.ts +0 -55
@@ -727,6 +727,59 @@ describe('brand page maker store module', () => {
727
727
  expect(pageNavigationStore.activePageId).toBe(expectedFirstId);
728
728
  });
729
729
 
730
+ // BP-5813: the templates sidebar highlight binds to `selectedTemplateId`,
731
+ // which isn't part of `brandPageData`. It rides along as a history-entry
732
+ // sidecar; applyHistorySnapshot restores it so the highlight reverts in
733
+ // lock-step with the canvas.
734
+ test('applyHistorySnapshot restores selectedTemplateId from the sidecar', async () => {
735
+ const makerStore = useBrandPageMakerStore();
736
+
737
+ await makerStore.initialise(INITIALISE_PAYLOAD);
738
+
739
+ makerStore.setSelectedTemplateId({ templateId: 'template-current' });
740
+
741
+ // Undo lands on an entry captured while 'template-previous' was selected.
742
+ await makerStore.applyHistorySnapshot(
743
+ makerStore.brandPageData,
744
+ undefined,
745
+ undefined,
746
+ 'template-previous',
747
+ );
748
+
749
+ expect(makerStore.selectedTemplateId).toBe('template-previous');
750
+ });
751
+
752
+ test('applyHistorySnapshot restores a cleared (false) selectedTemplateId', async () => {
753
+ const makerStore = useBrandPageMakerStore();
754
+
755
+ await makerStore.initialise(INITIALISE_PAYLOAD);
756
+
757
+ makerStore.setSelectedTemplateId({ templateId: 'template-current' });
758
+
759
+ await makerStore.applyHistorySnapshot(
760
+ makerStore.brandPageData,
761
+ undefined,
762
+ undefined,
763
+ false,
764
+ );
765
+
766
+ expect(makerStore.selectedTemplateId).toBe(false);
767
+ });
768
+
769
+ test('applyHistorySnapshot leaves selectedTemplateId untouched when the sidecar is undefined', async () => {
770
+ const makerStore = useBrandPageMakerStore();
771
+
772
+ await makerStore.initialise(INITIALISE_PAYLOAD);
773
+
774
+ makerStore.setSelectedTemplateId({ templateId: 'template-current' });
775
+
776
+ // An entry with no sidecar (e.g. captured before the field existed) must
777
+ // not clobber the current selection.
778
+ await makerStore.applyHistorySnapshot(makerStore.brandPageData);
779
+
780
+ expect(makerStore.selectedTemplateId).toBe('template-current');
781
+ });
782
+
730
783
  // BP-5506 follow-up: undoing a non-page-structural edit (e.g. a title
731
784
  // change) must NOT push a new route. The router push fires
732
785
  // `router.afterEach`, and BrandPageMaker.mixin's afterEach hook resets
@@ -793,6 +793,7 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
793
793
  snapshot: BrandPageModel,
794
794
  activePageId?: string,
795
795
  logoEditsStr?: string,
796
+ selectedTemplateId?: string | false,
796
797
  ): Promise<void> {
797
798
  const styleSettingsStore = useUiStyleSettingsStore();
798
799
  const navigationStyleSettingsStore = useUiNavigationStyleSettingsStore();
@@ -883,6 +884,16 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
883
884
  this.cookieOptions = snapshot.cookieOptions;
884
885
  this.saveContactSettings = snapshot.saveContactSettings;
885
886
 
887
+ // 7b. Restore the selected-template pointer that drives the templates
888
+ // sidebar highlight. It's a UI pointer, not part of the persisted
889
+ // model, so it rides along as a history-entry sidecar (like
890
+ // `activePageId`). Its watchers only reset scroll position (no
891
+ // template re-application), so setting it here is safe during apply.
892
+ // (BP-5813)
893
+ if (selectedTemplateId !== undefined) {
894
+ this.setSelectedTemplateId({ templateId: selectedTemplateId });
895
+ }
896
+
886
897
  // 8. Re-sync the logo-settings sidebar's `selectedLogoImage` from the
887
898
  // restored header logo. The preview rehydrates via the page contents,
888
899
  // but `uiLogoSettings` holds its own copy that drives the sidebar
@@ -945,14 +956,7 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
945
956
  }
946
957
  }
947
958
 
948
- const logoImage =
949
- snapshot.brandPageType === BRAND_PAGE_TYPES.default
950
- ? (
951
- snapshot.pages?.[0]?.contents?.find(
952
- (c) => c.type === 'logo',
953
- ) as any
954
- )?.logoImage
955
- : snapshot.pageHeaderDesktop?.logoImage;
959
+ const logoImage = logoUtil.getLogoImageFromModel(snapshot);
956
960
 
957
961
  if (!logoImage) return;
958
962
 
@@ -1273,14 +1277,7 @@ export const useBrandPageMakerStore = defineStore('brandPageMaker', {
1273
1277
  brandPageType: _brandPageType,
1274
1278
  });
1275
1279
 
1276
- let logoImage =
1277
- brandPageModel.brandPageType === BRAND_PAGE_TYPES.default
1278
- ? (
1279
- brandPageModel.pages?.[0].contents.find(
1280
- (c) => c.type === 'logo',
1281
- ) as any
1282
- )?.logoImage
1283
- : brandPageModel.pageHeaderDesktop?.logoImage;
1280
+ let logoImage = logoUtil.getLogoImageFromModel(brandPageModel);
1284
1281
 
1285
1282
  if (!logoImage?.logoDraftToken) {
1286
1283
  logoImage = logoUtil.safeGetLogoImageFromLogoImageUrl(
@@ -16,6 +16,8 @@ const mockMakerStore = {
16
16
  isBrandPageLoaded: false,
17
17
  applyHistorySnapshot: vi.fn(async () => undefined),
18
18
  setIsBrandPageLoaded: vi.fn(),
19
+ selectedTemplateId: false as string | false,
20
+ setSelectedTemplateId: vi.fn(),
19
21
  };
20
22
 
21
23
  const mockPageNavigationStore = {
@@ -64,6 +66,8 @@ describe('history-store (single stack + JSON-string snapshots)', () => {
64
66
  beforeEach(() => {
65
67
  setActivePinia(createPinia());
66
68
  mockMakerStore.applyHistorySnapshot.mockClear();
69
+ mockMakerStore.setSelectedTemplateId.mockClear();
70
+ mockMakerStore.selectedTemplateId = false;
67
71
  mutationCounter = 0;
68
72
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
73
  (mockMakerStore.brandPageData as any).v = 1;
@@ -443,6 +447,46 @@ describe('history-store (single stack + JSON-string snapshots)', () => {
443
447
  });
444
448
  });
445
449
 
450
+ // BP-5813: the templates sidebar highlight binds to
451
+ // `brandPageMakerStore.selectedTemplateId`, which is NOT part of
452
+ // `brandPageData`. It rides along as a per-entry sidecar so undo/redo can
453
+ // restore the highlight in lock-step with the canvas.
454
+ describe('selectedTemplateId sidecar (BP-5813 — template sidebar highlight)', () => {
455
+ it('captures the current selectedTemplateId on each entry', async () => {
456
+ const store = useHistoryStore();
457
+
458
+ mockMakerStore.selectedTemplateId = 'template-a';
459
+ await store.captureBaseline();
460
+
461
+ expect(store.entries[0].selectedTemplateId).toBe('template-a');
462
+
463
+ mockMakerStore.selectedTemplateId = 'template-b';
464
+ captureMutated(store);
465
+
466
+ expect(store.entries[1].selectedTemplateId).toBe('template-b');
467
+ });
468
+
469
+ it('passes the target entry selectedTemplateId to applyHistorySnapshot on undo', async () => {
470
+ const store = useHistoryStore();
471
+
472
+ mockMakerStore.selectedTemplateId = 'template-a';
473
+ await store.captureBaseline();
474
+
475
+ mockMakerStore.selectedTemplateId = 'template-b';
476
+ captureMutated(store);
477
+
478
+ await store.undo();
479
+
480
+ // 4th arg is the baseline entry's template id — the sidebar reverts
481
+ // with the canvas instead of lagging on the undone template.
482
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
483
+ const callArgs = mockMakerStore.applyHistorySnapshot.mock
484
+ .calls[0] as any[];
485
+
486
+ expect(callArgs[3]).toBe('template-a');
487
+ });
488
+ });
489
+
446
490
  it('reentrancy guard: capture no-ops when isApplyingSnapshot is true', async () => {
447
491
  const store = useHistoryStore();
448
492
 
@@ -505,6 +549,7 @@ describe('history-store (single stack + JSON-string snapshots)', () => {
505
549
  expect.objectContaining(baselineBrandPageData),
506
550
  undefined,
507
551
  expect.any(String),
552
+ false,
508
553
  );
509
554
  });
510
555
 
@@ -767,6 +812,7 @@ describe('history-store (single stack + JSON-string snapshots)', () => {
767
812
  expect.anything(),
768
813
  'page-home',
769
814
  expect.any(String),
815
+ false,
770
816
  );
771
817
  });
772
818
 
@@ -801,6 +847,7 @@ describe('history-store (single stack + JSON-string snapshots)', () => {
801
847
  expect.anything(),
802
848
  'page-home',
803
849
  expect.any(String),
850
+ false,
804
851
  );
805
852
  });
806
853
 
@@ -842,6 +889,7 @@ describe('history-store (single stack + JSON-string snapshots)', () => {
842
889
  expect.anything(),
843
890
  'page-about',
844
891
  expect.any(String),
892
+ false,
845
893
  );
846
894
  });
847
895
 
@@ -879,6 +927,7 @@ describe('history-store (single stack + JSON-string snapshots)', () => {
879
927
  expect.anything(),
880
928
  'page-new',
881
929
  expect.any(String),
930
+ false,
882
931
  );
883
932
  });
884
933
 
@@ -912,6 +961,7 @@ describe('history-store (single stack + JSON-string snapshots)', () => {
912
961
  expect.anything(),
913
962
  'page-home',
914
963
  expect.any(String),
964
+ false,
915
965
  );
916
966
 
917
967
  // Now redo. Pointer moves forward to entry 1 (page-x captured),
@@ -922,6 +972,7 @@ describe('history-store (single stack + JSON-string snapshots)', () => {
922
972
  expect.anything(),
923
973
  'page-x',
924
974
  expect.any(String),
975
+ false,
925
976
  );
926
977
  });
927
978
  });
@@ -26,6 +26,12 @@ export interface HistoryEntry {
26
26
  activePageId: string;
27
27
  /** JSON-stringified `editableLogoStore.logoEdits`. Undefined if logo tab never opened. */
28
28
  logoEdits?: string;
29
+ /**
30
+ * Selected template id at capture time — drives the templates sidebar
31
+ * highlight. `false` when no template is selected. Sidecar to
32
+ * `brandPageData` (not part of the persisted model), like `activePageId`.
33
+ */
34
+ selectedTemplateId?: string | false;
29
35
  }
30
36
 
31
37
  // Monotonic burst — first mutation starts the timer, subsequent mutations
@@ -150,6 +156,10 @@ export const useHistoryStore = defineStore('history', () => {
150
156
  return usePageNavigationStore().activePageId ?? '';
151
157
  }
152
158
 
159
+ function getCurrentSelectedTemplateId(): string | false {
160
+ return useBrandPageMakerStore().selectedTemplateId;
161
+ }
162
+
153
163
  function serializeCurrentLogoEdits(): string | undefined {
154
164
  try {
155
165
  const editableLogoStore = useUiEditableLogoStore();
@@ -187,6 +197,7 @@ export const useHistoryStore = defineStore('history', () => {
187
197
  label?: string;
188
198
  activePageId: string;
189
199
  logoEdits?: string;
200
+ selectedTemplateId?: string | false;
190
201
  }): 'committed' | 'oversized' | 'noop' {
191
202
  const current = entries.value[currentIndex.value];
192
203
 
@@ -219,6 +230,7 @@ export const useHistoryStore = defineStore('history', () => {
219
230
  sizeBytes: args.snapshot.length,
220
231
  activePageId: args.activePageId,
221
232
  logoEdits: args.logoEdits,
233
+ selectedTemplateId: args.selectedTemplateId,
222
234
  };
223
235
 
224
236
  // FR-11: truncate redo tail, then append.
@@ -293,6 +305,7 @@ export const useHistoryStore = defineStore('history', () => {
293
305
 
294
306
  const activePageId = getCurrentActivePageId();
295
307
  const logoEdits = serializeCurrentLogoEdits();
308
+ const selectedTemplateId = getCurrentSelectedTemplateId();
296
309
 
297
310
  // Settle window: fold post-init reactive cascades back into the
298
311
  // baseline so undo doesn't light up before the user has done
@@ -317,6 +330,7 @@ export const useHistoryStore = defineStore('history', () => {
317
330
  sizeBytes: snapshot.length,
318
331
  activePageId,
319
332
  logoEdits,
333
+ selectedTemplateId,
320
334
  },
321
335
  ];
322
336
 
@@ -333,6 +347,7 @@ export const useHistoryStore = defineStore('history', () => {
333
347
  sizeBytes: snapshot.length,
334
348
  activePageId,
335
349
  logoEdits,
350
+ selectedTemplateId,
336
351
  };
337
352
  entries.value = next;
338
353
 
@@ -345,6 +360,7 @@ export const useHistoryStore = defineStore('history', () => {
345
360
  actor: 'user',
346
361
  activePageId,
347
362
  logoEdits,
363
+ selectedTemplateId,
348
364
  });
349
365
  };
350
366
 
@@ -402,6 +418,7 @@ export const useHistoryStore = defineStore('history', () => {
402
418
  sizeBytes: snapshot.length,
403
419
  activePageId: getCurrentActivePageId(),
404
420
  logoEdits: serializeCurrentLogoEdits(),
421
+ selectedTemplateId: getCurrentSelectedTemplateId(),
405
422
  },
406
423
  ];
407
424
  currentIndex.value = 0;
@@ -427,6 +444,7 @@ export const useHistoryStore = defineStore('history', () => {
427
444
  snapshot,
428
445
  activePageId: getCurrentActivePageId(),
429
446
  logoEdits: serializeCurrentLogoEdits(),
447
+ selectedTemplateId: getCurrentSelectedTemplateId(),
430
448
  ...args,
431
449
  });
432
450
  }
@@ -531,6 +549,7 @@ export const useHistoryStore = defineStore('history', () => {
531
549
  snapshot,
532
550
  resolvedActivePageId,
533
551
  targetEntry?.logoEdits,
552
+ targetEntry?.selectedTemplateId,
534
553
  );
535
554
  } finally {
536
555
  isApplyingSnapshot.value = false;
@@ -7,8 +7,6 @@ export * from './url.util';
7
7
  export * from './slug.util';
8
8
  export * from './text.util';
9
9
  export * from './rich-text-editor.util';
10
- export * from './rte-render.util';
11
- export * from './rte-sanitize.util';
12
10
 
13
11
  export const isColorMatched = (color: string | null | undefined): boolean => {
14
12
  if (!color) return false;
@@ -76,7 +76,7 @@ describe('hasHTML', () => {
76
76
  });
77
77
 
78
78
  describe('getUpdatedHTMLFromEditor', () => {
79
- it('applies style commands and returns updated JSON', () => {
79
+ it('applies style commands and returns updated HTML', () => {
80
80
  // Mock editor
81
81
  const runMock = vi.fn();
82
82
  const chain = {
@@ -119,7 +119,7 @@ describe('getUpdatedHTMLFromEditor', () => {
119
119
  },
120
120
  },
121
121
  chain: () => chain as any,
122
- getJSON: () => ({ type: 'doc', content: [{ type: 'paragraph' }] }),
122
+ getHTML: () => '<div>styled</div>',
123
123
  } as unknown as Editor;
124
124
 
125
125
  const newStyle = {
@@ -137,8 +137,7 @@ describe('getUpdatedHTMLFromEditor', () => {
137
137
 
138
138
  expect(externalEditor.commands.setContent).toHaveBeenCalledWith('foo');
139
139
  expect(runMock).toHaveBeenCalled();
140
- // BP-5787: returns the JSON form (consistent with JSON storage).
141
- expect(result).toEqual({ type: 'doc', content: [{ type: 'paragraph' }] });
140
+ expect(result).toBe('<div>styled</div>');
142
141
  });
143
142
  });
144
143
 
@@ -1,4 +1,4 @@
1
- import type { ChainedCommands, Editor, JSONContent } from '@tiptap/vue-3';
1
+ import type { ChainedCommands, Editor } from '@tiptap/vue-3';
2
2
  import type { EditorState, Transaction, Selection } from 'prosemirror-state';
3
3
  import {
4
4
  STYLE_THEME_KEYS,
@@ -19,7 +19,6 @@ import {
19
19
  type RequestPaymentContent,
20
20
  type SupportMeContent,
21
21
  type ShopContent,
22
- type RichTextValue,
23
22
  } from '@brand-page';
24
23
  import type { PageSettingsState } from '@shared/page-settings/store';
25
24
  import type { StyleSettingsState } from '@shared/style-settings/store';
@@ -244,38 +243,8 @@ export const skipStyleDetection = (chain: ChainedCommands, marks?: Mark[]) =>
244
243
  return true;
245
244
  });
246
245
 
247
- /**
248
- * BP-5787 — canonical discriminator for the JSON RTE model: a ProseMirror
249
- * document is an object with `type: 'doc'`.
250
- */
251
- export const isTiptapDocument = (value: unknown): value is JSONContent =>
252
- typeof value === 'object' &&
253
- value !== null &&
254
- (value as JSONContent).type === 'doc';
255
-
256
- /**
257
- * Extract visible plain text from a Tiptap JSON document (concatenated text
258
- * nodes). Dependency-free — no editor instantiation.
259
- */
260
- export const getRteJsonPlainText = (doc: JSONContent): string => {
261
- const parts: string[] = [];
262
-
263
- const walk = (node: JSONContent): void => {
264
- if (typeof node.text === 'string') parts.push(node.text);
265
-
266
- node.content?.forEach(walk);
267
- };
268
-
269
- walk(doc);
270
-
271
- return parts.join(' ').replace(/\s+/g, ' ').trim();
272
- };
273
-
274
- // Accepts a legacy inline-HTML string or a Tiptap JSON document (BP-5787).
275
- export const hasRTEContent = (content: unknown): boolean => {
276
- if (isTiptapDocument(content)) return true;
277
-
278
- if (typeof content !== 'string' || content.length === 0) return false;
246
+ export const hasRTEContent = (content: string): boolean => {
247
+ if (!content || content.length === 0) return false;
279
248
 
280
249
  const trimmed = content.trim();
281
250
  const tagWrapRegex = /^<([A-Za-z][A-Za-z0-9-]*)\b[^>]*>[\s\S]*<\/\1>$/;
@@ -283,11 +252,7 @@ export const hasRTEContent = (content: unknown): boolean => {
283
252
  return tagWrapRegex.test(trimmed);
284
253
  };
285
254
 
286
- export const getPlainTextFromRTE = (content: unknown): string => {
287
- if (isTiptapDocument(content)) {
288
- return getRteJsonPlainText(content);
289
- }
290
-
255
+ export const getPlainTextFromRTE = (content: string): string => {
291
256
  if (typeof content !== 'string') {
292
257
  return '';
293
258
  }
@@ -756,20 +721,16 @@ export const switchAllContentToRTE = (
756
721
 
757
722
  export const getUpdatedHTMLFromEditor = (
758
723
  externalEditor: Editor,
759
- contentText: RichTextValue,
724
+ contentText: string,
760
725
  newContentStyle: TextStyleDeclaration,
761
726
  ) => {
762
727
  if (!contentText) {
763
728
  return contentText;
764
729
  }
765
730
 
766
- // BP-5787: accept a string (legacy HTML) or a Tiptap JSON doc. setContent
767
- // handles both; only the newline-escaping shim applies to strings.
768
- const content =
769
- typeof contentText === 'string' &&
770
- contentText.includes(ESCAPED_NEWLINE_CHARACTER)
771
- ? getRenderText(contentText)
772
- : contentText;
731
+ const content = contentText.includes(ESCAPED_NEWLINE_CHARACTER)
732
+ ? getRenderText(contentText)
733
+ : contentText;
773
734
 
774
735
  externalEditor.commands.setContent(content);
775
736
 
@@ -827,9 +788,9 @@ export const getUpdatedHTMLFromEditor = (
827
788
  chain = newContentStyle?.fontStyle ? chain.setItalic() : chain.unsetItalic();
828
789
 
829
790
  chain.run();
791
+ const html = externalEditor.getHTML();
830
792
 
831
- // BP-5787: return the JSON form, consistent with how RTE is now stored.
832
- return externalEditor.getJSON();
793
+ return html;
833
794
  };
834
795
 
835
796
  // Helper to build stored marks from a style declaration
@@ -953,7 +914,7 @@ export const sanitiseClientSideRTE = (html: string): SanitizationResult => {
953
914
  /**
954
915
  * Validates if an href value is safe and returns a sanitized version
955
916
  */
956
- export const validateAndSanitizeHref = (href: string): string | null => {
917
+ const validateAndSanitizeHref = (href: string): string | null => {
957
918
  if (!href || typeof href !== 'string') return null;
958
919
 
959
920
  // Trim whitespace and decode entities
@@ -1,6 +1,6 @@
1
1
  import { isURL, isEmail, isTel } from '@utils/validator.util';
2
2
  import type { ItemFieldType, Validator } from '@brand-page-maker/models';
3
- import { YOUTUBE_URL_RE, isTiptapDocument } from '@brand-page-maker/utils';
3
+ import { YOUTUBE_URL_RE } from '@brand-page-maker/utils';
4
4
  import { validatorTr } from '@src/composables/useTranslate';
5
5
 
6
6
  const addressValueValidator: Validator = {
@@ -76,15 +76,7 @@ export const dummyValidator: Validator = {
76
76
 
77
77
  const nonEmptyStringValidator: Validator = {
78
78
  validate: (name, value: any) => {
79
- // BP-5787/BP-5857: match legacy (pre-JSON) validation semantics.
80
- // An RTE field's stored value used to be inline HTML — an empty editor was
81
- // "<p></p>", a NON-empty string, so the required check always passed for RTE
82
- // fields (it only ever failed for genuinely empty PLAIN inputs). Now RTE
83
- // fields are Tiptap JSON docs; treat any doc as "present" so behaviour
84
- // stays identical. A plain string is still required to be non-empty.
85
- const isValid = isTiptapDocument(value)
86
- ? true
87
- : typeof value === 'string' && value.length > 0;
79
+ const isValid = typeof value === 'string' && value.length > 0;
88
80
 
89
81
  return {
90
82
  isValid,
package/src/lib.ts CHANGED
@@ -85,15 +85,6 @@ export {
85
85
  recalculateHeaderLogoSizes,
86
86
  } from './brand-page/utils/logo.util';
87
87
  export { sanitizeRTEHtml } from './brand-page-maker/utils';
88
- // BP-5787: RTE JSON storage boundary helpers (render/normalise/sanitise/extract).
89
- export {
90
- renderRteToHtml,
91
- toRteJson,
92
- isTiptapDocument,
93
- sanitizeRteJson,
94
- getRteJsonPlainText,
95
- type RichTextValue,
96
- } from './brand-page-maker/utils';
97
88
  export { getPageSlugFromPageTitle } from './brand-page-maker/utils/slug.util';
98
89
 
99
90
  // AI-pipeline preview (longclaw per-turn snapshots): web-renderer's
@@ -0,0 +1,80 @@
1
+ import type { BrandPageModel, NavOrder } from '../../brand-page/models';
2
+ import {
3
+ BaseMetadataBuilder,
4
+ type BuilderResult,
5
+ type MetadataBuilderRequest,
6
+ } from '../models';
7
+ import type { Logger } from '../logger';
8
+
9
+ export interface ReorderPagesPayload {
10
+ pageOrder: string[];
11
+ }
12
+
13
+ export class ReorderPagesBuilder extends BaseMetadataBuilder<ReorderPagesPayload> {
14
+ constructor(
15
+ request: MetadataBuilderRequest<ReorderPagesPayload>,
16
+ logger?: Logger,
17
+ ) {
18
+ super(request, logger);
19
+ }
20
+
21
+ async validateAsync(): Promise<{ errors: string[] }> {
22
+ const errors: string[] = [];
23
+ const payload = this.request.payload;
24
+
25
+ if (!payload?.pageOrder?.length) {
26
+ errors.push('page_order is required for reorder_pages');
27
+ }
28
+
29
+ return { errors };
30
+ }
31
+
32
+ async buildAsync(model: BrandPageModel): Promise<BuilderResult> {
33
+ this.log('Build called');
34
+
35
+ const validation = await this.validateAsync();
36
+
37
+ if (validation.errors.length) {
38
+ return { request: this.request, model, errors: validation.errors };
39
+ }
40
+
41
+ const { pageOrder } = this.request.payload!;
42
+
43
+ // Build pageId → routeId lookup from nav.routes
44
+ const pageIdToRouteId = new Map<string, string>();
45
+
46
+ for (const route of model.nav.routes) {
47
+ pageIdToRouteId.set(route.pageId, route.id);
48
+ }
49
+
50
+ // Build new nav.orders from the flat page_order (array of pageIds)
51
+ const newOrders: NavOrder[] = [];
52
+ const seenRouteIds = new Set<string>();
53
+
54
+ for (const pageId of pageOrder) {
55
+ const routeId = pageIdToRouteId.get(pageId);
56
+
57
+ if (!routeId) continue;
58
+
59
+ newOrders.push({ routeId, subRoutes: [] });
60
+ seenRouteIds.add(routeId);
61
+ }
62
+
63
+ // Safety: append any routes not mentioned (don't lose pages)
64
+ for (const route of model.nav.routes) {
65
+ const routeId = pageIdToRouteId.get(route.pageId);
66
+
67
+ if (routeId && !seenRouteIds.has(routeId)) {
68
+ newOrders.push({ routeId, subRoutes: [] });
69
+ }
70
+ }
71
+
72
+ model.nav.orders = newOrders;
73
+
74
+ this.log({
75
+ message: `Reordered pages: ${pageOrder.join(', ')}`,
76
+ });
77
+
78
+ return { request: this.request, model };
79
+ }
80
+ }
@@ -6,36 +6,7 @@ import {
6
6
  type BuilderResult,
7
7
  type MetadataBuilderRequest,
8
8
  } from '../models';
9
- import {
10
- hasRTEContent,
11
- replaceTextInRTE,
12
- isTiptapDocument,
13
- renderRteToHtml,
14
- toRteJson,
15
- } from '../../brand-page-maker/utils';
16
-
17
- /**
18
- * BP-5787 — replace the text of an RTE field while preserving formatting, for
19
- * either storage form. JSON is rendered to HTML, edited via the existing
20
- * HTML-preserving replace, then converted back to JSON; plain/legacy strings
21
- * keep the original behaviour.
22
- */
23
- const updateRteFieldValue = (existing: unknown, newText: string): unknown => {
24
- const wasJson = isTiptapDocument(existing);
25
- const existingHtml = wasJson ? renderRteToHtml(existing) : existing;
26
-
27
- if (
28
- existingHtml &&
29
- typeof existingHtml === 'string' &&
30
- hasRTEContent(existingHtml)
31
- ) {
32
- const updatedHtml = replaceTextInRTE(existingHtml, newText);
33
-
34
- return wasJson ? toRteJson(updatedHtml) : updatedHtml;
35
- }
36
-
37
- return newText;
38
- };
9
+ import { hasRTEContent, replaceTextInRTE } from '../../brand-page-maker/utils';
39
10
 
40
11
  export interface UpdateTextPayload {
41
12
  contentId: string;
@@ -65,7 +36,12 @@ const updateTextField = (target: any, fieldName: string, newText: string) => {
65
36
 
66
37
  if (Array.isArray(array) && array[+index]) {
67
38
  const existingText = array[+index][property];
68
- const updatedValue = updateRteFieldValue(existingText, newText);
39
+ const updatedValue =
40
+ existingText &&
41
+ typeof existingText === 'string' &&
42
+ hasRTEContent(existingText)
43
+ ? replaceTextInRTE(existingText, newText)
44
+ : newText;
69
45
 
70
46
  // Create a new array with the updated item to trigger reactivity
71
47
  const newArray = [...array];
@@ -78,7 +54,17 @@ const updateTextField = (target: any, fieldName: string, newText: string) => {
78
54
  }
79
55
  } else {
80
56
  // Simple field
81
- target[fieldName] = updateRteFieldValue(target[fieldName], newText);
57
+ const existingText = target[fieldName];
58
+
59
+ if (
60
+ existingText &&
61
+ typeof existingText === 'string' &&
62
+ hasRTEContent(existingText)
63
+ ) {
64
+ target[fieldName] = replaceTextInRTE(existingText, newText);
65
+ } else {
66
+ target[fieldName] = newText;
67
+ }
82
68
  }
83
69
  };
84
70