@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
@@ -1,42 +0,0 @@
1
- import TiptapFontFamily from '@tiptap/extension-font-family';
2
-
3
- /**
4
- * BP-5787 — font-family with quote-stripping parse.
5
- *
6
- * The stock extension stores whatever `element.style.fontFamily` returns. For
7
- * multi-word families (e.g. "Open Sans", "Playwrite AU") the browser quotes the
8
- * value, so parsing HTML — pasting RTE-styled text between editors, or the
9
- * HTML→JSON migration — yields a value that still contains the quote chars
10
- * (`"Open Sans"`). That value then (a) no longer matches the font list and,
11
- * worse, (b) is dropped by the native RTE sanitiser (`sanitizeRteJson`), whose
12
- * `isSafeCssValue` rejects any value containing `"` `'` `;` — so the font is
13
- * silently lost. Mirror the FontSize/LetterSpacing extensions and strip the
14
- * quotes on parse so the stored value is always the bare family name.
15
- */
16
- export const FontFamily = TiptapFontFamily.extend({
17
- addGlobalAttributes() {
18
- return [
19
- {
20
- types: this.options.types,
21
- attributes: {
22
- fontFamily: {
23
- default: null,
24
- // Strip quotes only — keep '' for an absent value so the stored
25
- // JSON shape matches the other textStyle attrs (FontSize etc.).
26
- parseHTML: (element) =>
27
- element.style.fontFamily?.replace(/['"]+/g, ''),
28
- renderHTML: (attributes) => {
29
- if (!attributes.fontFamily) {
30
- return {};
31
- }
32
-
33
- return {
34
- style: `font-family: ${attributes.fontFamily}`,
35
- };
36
- },
37
- },
38
- },
39
- },
40
- ];
41
- },
42
- });
@@ -1,113 +0,0 @@
1
- import type { Extensions } from '@tiptap/vue-3';
2
- import StarterKit from '@tiptap/starter-kit';
3
- import Underline from '@tiptap/extension-underline';
4
- import TextStyle from '@tiptap/extension-text-style';
5
- import TextAlign from '@tiptap/extension-text-align';
6
- import ColorExtension from '@tiptap/extension-color';
7
- import Highlight from '@tiptap/extension-highlight';
8
- import Placeholder from '@tiptap/extension-placeholder';
9
- import Link from '@tiptap/extension-link';
10
- import { FontFamily } from './extensions/FontFamily';
11
- import { FontSize } from './extensions/FontSize';
12
- import { LetterSpacing } from './extensions/LetterSpacing';
13
- import { LineHeight } from './extensions/LineHeight';
14
- import { MaxLength } from './extensions/MaxLength';
15
- import { AutoLink } from './extensions/AutoLink';
16
- import { isHrefPageId } from './hyperlink';
17
-
18
- /**
19
- * Single source of truth for the Tiptap extension set (BP-5787).
20
- *
21
- * The live editor, the headless serialiser (useExternalRichTextEditor), and
22
- * the upcoming render-from-JSON path must all use the SAME extensions, or
23
- * rendered output drifts from what the user authored. This factory removes the
24
- * previous duplication while preserving each consumer's exact configuration.
25
- *
26
- * Known, intentional differences are parameterised:
27
- * - `restrictStarterKit` — the live editor disables list/hr/code nodes.
28
- * - `includeLinkRel` — the headless/render path emits rel="noopener noreferrer";
29
- * the live editing surface does not.
30
- * - `editing` — only the live editor carries the Placeholder + MaxLength plugins
31
- * (neither affects serialised output; they are editing-time only).
32
- */
33
- export interface RteExtensionOptions {
34
- /** Disable StarterKit nodes the editor doesn't use (lists, hr, code). */
35
- restrictStarterKit?: boolean;
36
- /** Emit rel="noopener noreferrer" on link HTMLAttributes (render path). */
37
- includeLinkRel?: boolean;
38
- /** When present, add the live-editing-only plugins (Placeholder, MaxLength). */
39
- editing?: {
40
- placeholder?: string;
41
- maxLength?: number;
42
- };
43
- }
44
-
45
- export const getRteExtensions = (
46
- options: RteExtensionOptions = {},
47
- ): Extensions => {
48
- const {
49
- restrictStarterKit = false,
50
- includeLinkRel = false,
51
- editing,
52
- } = options;
53
-
54
- const extensions: Extensions = [
55
- restrictStarterKit
56
- ? StarterKit.configure({
57
- bulletList: false,
58
- orderedList: false,
59
- listItem: false,
60
- horizontalRule: false,
61
- codeBlock: false,
62
- code: false,
63
- })
64
- : StarterKit,
65
- Underline,
66
- TextStyle.configure({ mergeNestedSpanStyles: true }),
67
- TextAlign.configure({
68
- types: ['heading', 'paragraph'],
69
- }),
70
- FontFamily.configure({
71
- types: ['textStyle'],
72
- }),
73
- FontSize,
74
- LetterSpacing,
75
- LineHeight,
76
- ColorExtension.configure({
77
- types: ['textStyle'],
78
- }),
79
- Highlight.configure({
80
- multicolor: true,
81
- }),
82
- ];
83
-
84
- // Live-editing-only plugins sit between Highlight and Link (original order).
85
- if (editing) {
86
- extensions.push(
87
- Placeholder.configure({ placeholder: editing.placeholder }),
88
- MaxLength.configure({ maxLength: editing.maxLength }),
89
- );
90
- }
91
-
92
- extensions.push(
93
- Link.configure({
94
- openOnClick: false,
95
- defaultProtocol: 'https',
96
- autolink: true,
97
- HTMLAttributes: (attrs) => {
98
- const href = attrs.href;
99
- const isExternal =
100
- href && (href.startsWith('http://') || href.startsWith('https://'));
101
-
102
- return {
103
- ...(includeLinkRel && { rel: 'noopener noreferrer' }),
104
- target: isExternal ? '_blank' : null,
105
- };
106
- },
107
- isAllowedUri: (url, ctx) => ctx.defaultValidate(url) || isHrefPageId(url),
108
- }),
109
- AutoLink,
110
- );
111
-
112
- return extensions;
113
- };
@@ -1,200 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`BP-5787 RTE render parity (golden master) > T1 — editor renders the complex fixture to the frozen HTML baseline > editor-normalized-html 1`] = `"<h2 style="text-align: center;"><span style="color: var(--logo-text-color);">Themed heading</span></h2><p><strong>bold</strong> <em>italic</em> <u>underline</u> <s>strike</s> <span style="font-family: Montserrat; font-size: 1.5em; letter-spacing: 0.05em; line-height: 1.4;">styled span</span> <mark data-color="#ffe08a" style="background-color: rgb(255, 224, 138); color: inherit;">highlight</mark> <span style="color: rgb(255, 0, 0);">literal hex</span> <a href="/about" target="_self">internal link</a> <a href="https://example.com" target="_blank" rel="noopener noreferrer nofollow">external link</a></p><p style="text-align: right;">second paragraph, right-aligned</p>"`;
4
-
5
- exports[`BP-5787 RTE render parity (golden master) > T1 — render-from-JSON (DOM-less serialiser) produces equivalent HTML > serializer-html 1`] = `"<h2 style="text-align: center"><span style="color: var(--logo-text-color)">Themed heading</span></h2><p><strong>bold</strong> <em>italic</em> <u>underline</u> <s>strike</s> <span style="font-family: Montserrat; font-size: 1.5em; letter-spacing: 0.05em; line-height: 1.4">styled span</span> <mark data-color="#ffe08a" style="background-color: #ffe08a; color: inherit;">highlight</mark> <span style="color: rgb(255, 0, 0)">literal hex</span> <a href="/about" target="_self">internal link</a> <a href="https://example.com" target="_blank" rel="noopener noreferrer nofollow">external link</a></p><p style="text-align: right">second paragraph, right-aligned</p>"`;
6
-
7
- exports[`BP-5787 RTE render parity (golden master) > T6 — freezes the JSON shape that becomes the stored form > editor-json 1`] = `
8
- {
9
- "content": [
10
- {
11
- "attrs": {
12
- "level": 2,
13
- "textAlign": "center",
14
- },
15
- "content": [
16
- {
17
- "marks": [
18
- {
19
- "attrs": {
20
- "color": "var(--logo-text-color)",
21
- "fontFamily": "",
22
- "fontSize": "",
23
- "letterSpacing": "",
24
- "lineHeight": "",
25
- },
26
- "type": "textStyle",
27
- },
28
- ],
29
- "text": "Themed heading",
30
- "type": "text",
31
- },
32
- ],
33
- "type": "heading",
34
- },
35
- {
36
- "attrs": {
37
- "textAlign": null,
38
- },
39
- "content": [
40
- {
41
- "marks": [
42
- {
43
- "type": "bold",
44
- },
45
- ],
46
- "text": "bold",
47
- "type": "text",
48
- },
49
- {
50
- "text": " ",
51
- "type": "text",
52
- },
53
- {
54
- "marks": [
55
- {
56
- "type": "italic",
57
- },
58
- ],
59
- "text": "italic",
60
- "type": "text",
61
- },
62
- {
63
- "text": " ",
64
- "type": "text",
65
- },
66
- {
67
- "marks": [
68
- {
69
- "type": "underline",
70
- },
71
- ],
72
- "text": "underline",
73
- "type": "text",
74
- },
75
- {
76
- "text": " ",
77
- "type": "text",
78
- },
79
- {
80
- "marks": [
81
- {
82
- "type": "strike",
83
- },
84
- ],
85
- "text": "strike",
86
- "type": "text",
87
- },
88
- {
89
- "text": " ",
90
- "type": "text",
91
- },
92
- {
93
- "marks": [
94
- {
95
- "attrs": {
96
- "color": "",
97
- "fontFamily": "Montserrat",
98
- "fontSize": "1.5em",
99
- "letterSpacing": "0.05em",
100
- "lineHeight": "1.4",
101
- },
102
- "type": "textStyle",
103
- },
104
- ],
105
- "text": "styled span",
106
- "type": "text",
107
- },
108
- {
109
- "text": " ",
110
- "type": "text",
111
- },
112
- {
113
- "marks": [
114
- {
115
- "attrs": {
116
- "color": "#ffe08a",
117
- },
118
- "type": "highlight",
119
- },
120
- ],
121
- "text": "highlight",
122
- "type": "text",
123
- },
124
- {
125
- "text": " ",
126
- "type": "text",
127
- },
128
- {
129
- "marks": [
130
- {
131
- "attrs": {
132
- "color": "rgb(255, 0, 0)",
133
- "fontFamily": "",
134
- "fontSize": "",
135
- "letterSpacing": "",
136
- "lineHeight": "",
137
- },
138
- "type": "textStyle",
139
- },
140
- ],
141
- "text": "literal hex",
142
- "type": "text",
143
- },
144
- {
145
- "text": " ",
146
- "type": "text",
147
- },
148
- {
149
- "marks": [
150
- {
151
- "attrs": {
152
- "class": undefined,
153
- "href": "/about",
154
- "rel": undefined,
155
- "target": "_self",
156
- },
157
- "type": "link",
158
- },
159
- ],
160
- "text": "internal link",
161
- "type": "text",
162
- },
163
- {
164
- "text": " ",
165
- "type": "text",
166
- },
167
- {
168
- "marks": [
169
- {
170
- "attrs": {
171
- "class": undefined,
172
- "href": "https://example.com",
173
- "rel": "noopener noreferrer nofollow",
174
- "target": "_blank",
175
- },
176
- "type": "link",
177
- },
178
- ],
179
- "text": "external link",
180
- "type": "text",
181
- },
182
- ],
183
- "type": "paragraph",
184
- },
185
- {
186
- "attrs": {
187
- "textAlign": "right",
188
- },
189
- "content": [
190
- {
191
- "text": "second paragraph, right-aligned",
192
- "type": "text",
193
- },
194
- ],
195
- "type": "paragraph",
196
- },
197
- ],
198
- "type": "doc",
199
- }
200
- `;
@@ -1,156 +0,0 @@
1
- import { describe, it, expect, afterEach } from 'vitest';
2
- import type { Editor } from '@tiptap/vue-3';
3
- import { useExternalRichTextEditor } from '../composables/rich-text-editor/useRichTextEditor';
4
- import {
5
- renderRteToHtml,
6
- toRteJson,
7
- getRteJsonPlainText,
8
- } from './rte-render.util';
9
- import {
10
- COMPLEX_RTE_HTML,
11
- THEME_TOKEN_REF,
12
- } from '../__fixtures__/complex-rte.fixture';
13
-
14
- /**
15
- * BP-5787 — golden-master render-parity baseline.
16
- *
17
- * Captured on the CURRENT (HTML) pipeline. These committed snapshots are the
18
- * frozen reference for the HTML→JSON migration:
19
- *
20
- * - `editor-normalized-html` is what the editor renders the fixture to today.
21
- * After the migration, `htmlFromJson(getJSON())` MUST equal this snapshot —
22
- * i.e. the JSON pipeline reproduces identical rendered output (no drift).
23
- *
24
- * - `editor-json` freezes the JSON shape that BECOMES the stored form. It is
25
- * expected to change exactly once, intentionally, when storage flips to JSON
26
- * (see "expected vs forbidden drift" in docs/plans/BP-5787-...html).
27
- *
28
- * The headless editor uses the SAME extension set as the live editor
29
- * (useExternalRichTextEditor), so it is a faithful stand-in for the renderer.
30
- */
31
- describe('BP-5787 RTE render parity (golden master)', () => {
32
- let editor: Editor;
33
-
34
- const makeEditor = (): Editor => {
35
- editor = useExternalRichTextEditor().externalEditor;
36
-
37
- return editor;
38
- };
39
-
40
- afterEach(() => {
41
- editor?.destroy();
42
- });
43
-
44
- it('T1 — editor renders the complex fixture to the frozen HTML baseline', () => {
45
- const ed = makeEditor();
46
-
47
- ed.commands.setContent(COMPLEX_RTE_HTML);
48
-
49
- // The golden master. The JSON pipeline must reproduce this exactly.
50
- expect(ed.getHTML()).toMatchSnapshot('editor-normalized-html');
51
- });
52
-
53
- it('T1 — round-trip is idempotent (html → json → html === json → html)', () => {
54
- const ed = makeEditor();
55
-
56
- ed.commands.setContent(COMPLEX_RTE_HTML);
57
- const pass1 = ed.getHTML();
58
- const json = ed.getJSON();
59
-
60
- // Re-seed from the JSON the migration will store, render again.
61
- ed.commands.setContent(json);
62
- const pass2 = ed.getHTML();
63
-
64
- expect(pass2).toEqual(pass1);
65
- });
66
-
67
- it('T6 — freezes the JSON shape that becomes the stored form', () => {
68
- const ed = makeEditor();
69
-
70
- ed.commands.setContent(COMPLEX_RTE_HTML);
71
-
72
- expect(ed.getJSON()).toMatchSnapshot('editor-json');
73
- });
74
-
75
- it('T2 — theme token ref survives the round-trip (NOT flattened to rgb)', () => {
76
- const ed = makeEditor();
77
-
78
- ed.commands.setContent(COMPLEX_RTE_HTML);
79
-
80
- // Today the editor preserves var(--…) in marks; the migration must keep it.
81
- expect(ed.getHTML()).toContain(THEME_TOKEN_REF);
82
- expect(JSON.stringify(ed.getJSON())).toContain(THEME_TOKEN_REF);
83
- });
84
-
85
- it('T1 — render-from-JSON (DOM-less serialiser) produces equivalent HTML', () => {
86
- // renderRteToHtml now serialises JSON → HTML with a pure function (no
87
- // editor/DOM, so it works on the server). Output is semantically identical
88
- // to the editor's — the Playwright visual golden-master is the authority on
89
- // pixel parity; here we freeze the serialiser output and assert structure.
90
- const viaJson = renderRteToHtml(toRteJson(COMPLEX_RTE_HTML));
91
-
92
- expect(viaJson).toMatchSnapshot('serializer-html');
93
-
94
- // marks/nodes preserved
95
- expect(viaJson).toContain('<strong>bold</strong>');
96
- expect(viaJson).toContain('<em>italic</em>');
97
- expect(viaJson).toContain('<u>underline</u>');
98
- expect(viaJson).toContain('<s>strike</s>');
99
- expect(viaJson).toContain('font-family: Montserrat');
100
- expect(viaJson).toContain('font-size: 1.5em');
101
- // theme token ref preserved (the crux of BP-5787)
102
- expect(viaJson).toContain('color: var(--logo-text-color)');
103
- // links + alignment
104
- expect(viaJson).toContain('href="/about"');
105
- expect(viaJson).toContain('href="https://example.com"');
106
- expect(viaJson).toContain('text-align: center');
107
- expect(viaJson).toContain('text-align: right');
108
- });
109
-
110
- it('T4 — legacy HTML strings render unchanged (back-compat passthrough)', () => {
111
- expect(renderRteToHtml(COMPLEX_RTE_HTML)).toEqual(COMPLEX_RTE_HTML);
112
- });
113
-
114
- it('T8 — getRteJsonPlainText extracts visible text only (for fraud/translate)', () => {
115
- const json = toRteJson(COMPLEX_RTE_HTML)!;
116
- const text = getRteJsonPlainText(json);
117
-
118
- expect(text).toContain('Themed heading');
119
- expect(text).toContain('bold');
120
- expect(text).toContain('external link');
121
- // no markup or token refs leak into the extracted text
122
- expect(text).not.toContain('<');
123
- expect(text).not.toContain('var(--');
124
- expect(text).not.toContain('font-size');
125
- });
126
-
127
- it('T1 — preserves every mark/node in the fixture', () => {
128
- const ed = makeEditor();
129
-
130
- ed.commands.setContent(COMPLEX_RTE_HTML);
131
- const html = ed.getHTML();
132
-
133
- // bold / italic / underline / strike
134
- expect(html).toContain('<strong>');
135
- expect(html).toContain('<em>');
136
- expect(html).toContain('<u>');
137
- expect(html).toContain('<s>');
138
- // textStyle attrs
139
- expect(html).toContain('font-family: Montserrat');
140
- expect(html).toContain('font-size: 1.5em');
141
- expect(html).toContain('letter-spacing: 0.05em');
142
- expect(html).toContain('line-height: 1.4');
143
- // highlight: data-color keeps the hex; the rendered style is normalised to rgb()
144
- expect(html).toContain('data-color="#ffe08a"');
145
- expect(html).toContain('background-color: rgb(255, 224, 138)');
146
- // literal hex colour is normalised to rgb() by the editor...
147
- expect(html).toContain('color: rgb(255, 0, 0)');
148
- // ...but the THEME TOKEN ref is preserved verbatim (the crux of BP-5787)
149
- expect(html).toContain('color: var(--logo-text-color)');
150
- expect(html).toContain('text-align: center');
151
- expect(html).toContain('text-align: right');
152
- // links
153
- expect(html).toContain('href="/about"');
154
- expect(html).toContain('href="https://example.com"');
155
- });
156
- });
@@ -1,77 +0,0 @@
1
- import { Editor, type JSONContent } from '@tiptap/vue-3';
2
- import { getRteExtensions } from '../composables/rich-text-editor/getRteExtensions';
3
- import { isTiptapDocument, getRteJsonPlainText } from './rich-text-editor.util';
4
- import { serializeRteJsonToHtml } from './rte-serializer';
5
-
6
- export { isTiptapDocument, getRteJsonPlainText };
7
-
8
- /**
9
- * BP-5787 — the boundary plumbing for storing RTE as Tiptap JSON.
10
- *
11
- * An RTE field is either a legacy inline-HTML string (pre-migration, or a
12
- * not-yet-migrated published site) or a Tiptap JSON document (post-migration).
13
- * These helpers let every consumer treat the two uniformly:
14
- *
15
- * - isTiptapDocument — the canonical discriminator (object with type 'doc').
16
- * - renderRteToHtml — render either form to final HTML for display.
17
- * - toRteJson — normalise either form to JSON for storage.
18
- *
19
- * - renderRteToHtml uses a DOM-less serialiser (works on the server).
20
- * - toRteJson uses a headless editor to PARSE HTML → JSON, so it only runs
21
- * where a DOM exists (browser, or jsdom in tests); the server never parses
22
- * HTML (migration-18 is DOM-guarded, translation/fraud walk JSON directly).
23
- */
24
- export type RichTextValue = string | JSONContent;
25
-
26
- /**
27
- * Run a function with a short-lived headless editor (created then destroyed).
28
- * Used only by toRteJson (HTML → JSON parsing), which needs a real DOM. Not
29
- * used by the render path, which is DOM-less.
30
- */
31
- const withHeadlessEditor = <T>(fn: (editor: Editor) => T): T => {
32
- const editor = new Editor({
33
- editable: false,
34
- extensions: getRteExtensions({ includeLinkRel: true }),
35
- });
36
-
37
- try {
38
- return fn(editor);
39
- } finally {
40
- editor.destroy();
41
- }
42
- };
43
-
44
- /**
45
- * Render an RTE value to final HTML. Legacy HTML strings pass through untouched
46
- * (they are already HTML); Tiptap JSON is serialised by a DOM-less pure
47
- * function (rte-serializer) — no editor, no DOM — so it works on the server
48
- * (SSR/publish), not just the browser, and is cheap enough to run per render.
49
- */
50
- export const renderRteToHtml = (
51
- value: RichTextValue | null | undefined,
52
- ): string => {
53
- if (value == null || value === '') return '';
54
-
55
- if (typeof value === 'string') return value;
56
-
57
- return serializeRteJsonToHtml(value);
58
- };
59
-
60
- /**
61
- * Normalise an RTE value to a Tiptap JSON document. HTML strings are parsed
62
- * via the headless editor; documents pass through unchanged. Returns
63
- * undefined for empty/blank input so callers can omit empty fields.
64
- */
65
- export const toRteJson = (
66
- value: RichTextValue | null | undefined,
67
- ): JSONContent | undefined => {
68
- if (value == null || value === '') return undefined;
69
-
70
- if (isTiptapDocument(value)) return value;
71
-
72
- return withHeadlessEditor((editor) => {
73
- editor.commands.setContent(value);
74
-
75
- return editor.getJSON();
76
- });
77
- };