@designcrowd/library.brand-page 6.0.26-json-prereq-2 → 6.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/src/{CreatedOnBrandCrowd-de-de-CKBqjDH3.js → CreatedOnBrandCrowd-de-de-DU6YEVcW.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-CRXiimvT.js → CreatedOnBrandCrowd-en-us-DNWWK6Tj.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-DCYdY6sj.js → CreatedOnBrandCrowd-es-es-DVFvV9Qp.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-Dk7LOhxE.js → CreatedOnBrandCrowd-fr-ca-B4ZkjNdw.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-Dk7LOhxE.js → CreatedOnBrandCrowd-fr-fr-B4ZkjNdw.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-Cp4ufHJP.js → CreatedOnBrandCrowd-pt-br-DP7AQGgW.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-Cp4ufHJP.js → CreatedOnBrandCrowd-pt-pt-DP7AQGgW.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-ZSLkWMwM.js → CreatedOnDcom-de-de-CaC3GtG9.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-BgdIGfT3.js → CreatedOnDcom-en-us-D16yT0-P.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-D0KemkCZ.js → CreatedOnDcom-es-es-DA5sHgD_.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-BuwNjNbC.js → CreatedOnDcom-fr-ca-CDv2SG1-.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-DCQcsFPf.js → CreatedOnDcom-fr-fr-Cygc7c5d.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-DD-oLAS1.js → CreatedOnDcom-pt-br-B_3RIzgF.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-D8qLq2wW.js → CreatedOnDcom-pt-pt-Bz5w-rrD.js} +1 -1
- package/lib/src/brand-page/components/contact-form/ContactForm.mixin.js +2 -5
- package/lib/src/brand-page/models/index.d.ts +26 -34
- package/lib/src/brand-page/utils/migration.util.js +0 -11
- package/lib/src/brand-page/utils/text.util.d.ts +1 -1
- package/lib/src/brand-page/utils/text.util.js +0 -32
- package/lib/src/brand-page/utils/vcard.util.js +4 -4
- package/lib/src/brand-page/utils/view-model.util.d.ts +0 -1
- package/lib/src/brand-page/utils/view-model.util.js +20 -68
- package/lib/src/brand-page/view-models.d.ts +3 -3
- package/lib/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.d.ts +1 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.d.ts +9 -2
- package/lib/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.js +10 -6
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.d.ts +20 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.js +44 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.d.ts +27 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.js +44 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.d.ts +1 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.js +103 -24
- package/lib/src/brand-page-maker/utils/index.d.ts +0 -2
- package/lib/src/brand-page-maker/utils/index.js +0 -2
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.d.ts +5 -28
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +5 -48
- package/lib/src/brand-page-maker/validators/field.validator.js +2 -10
- package/lib/src/css/library.brand-page-brandCrowd.min.css +39 -39
- package/lib/src/css/library.brand-page-brandPage.min.css +39 -39
- package/lib/src/css/library.brand-page-designCom.min.css +39 -39
- package/lib/src/css/library.brand-page-preview.min.css +39 -39
- package/lib/src/index.d.ts +0 -1
- package/lib/src/index.mjs +138 -143
- package/lib/src/{lib-Db6j9fQZ.js → lib-6_P6kXPu.js} +51709 -55194
- package/lib/src/lib.js +0 -2
- package/lib/src/metadata-builder/ai-metadata-executor.d.ts +1 -0
- package/lib/src/metadata-builder/ai-metadata-executor.js +19 -0
- package/lib/src/metadata-builder/builders/section-builders/about-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/banner-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/booking-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/contact-form-builder.js +4 -3
- package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-builder.js +2 -1
- package/lib/src/metadata-builder/builders/section-builders/mailing-list-section-builder.js +2 -1
- package/lib/src/metadata-builder/builders/section-builders/profile-section-builder.js +4 -3
- package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +3 -2
- package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +2 -1
- package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +11 -15
- package/lib/src/metadata-builder/builders/update-content-text-builder.d.ts +17 -0
- package/lib/src/metadata-builder/builders/update-content-text-builder.js +103 -0
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.d.ts +16 -0
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +216 -0
- package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +13 -11
- package/lib/src/metadata-builder/helpers/rte.helpers.js +73 -14
- package/lib/src/metadata-builder/metadata-executor.js +4 -0
- package/lib/src/metadata-builder/models.d.ts +2 -0
- package/lib/src/metadata-builder/models.js +2 -0
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +19 -0
- package/lib/src/metadata-builder/parsers/ai-toolcall-parser.js +4 -0
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.d.ts +8 -7
- package/lib/src/metadata-builder/sanitizers/rte-schema-check.js +52 -21
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +1 -14
- package/lib/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.js +3 -12
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.d.ts +2 -3
- package/lib/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.js +3 -10
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.d.ts +1 -2
- package/lib/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.js +1 -4
- package/lib/src/utils/minify-brand-page-metadata.js +1 -4
- package/package.json +15 -17
- package/src/brand-page/components/contact-form/ContactForm.mixin.ts +2 -5
- package/src/brand-page/components/text-wrapper/TextWrapper.vue +6 -17
- package/src/brand-page/models/index.ts +26 -35
- package/src/brand-page/utils/__snapshots__/migration.util.test.ts.snap +3 -3
- package/src/brand-page/utils/migration.util.ts +0 -12
- package/src/brand-page/utils/style.util.test.ts +0 -51
- package/src/brand-page/utils/text.util.ts +4 -43
- package/src/brand-page/utils/vcard.util.ts +4 -6
- package/src/brand-page/utils/view-model.util.ts +26 -94
- package/src/brand-page/view-models.ts +2 -3
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +3 -4
- package/src/brand-page-maker/composables/ai-chat/useAiMetadataBuilder.ts +1 -0
- package/src/brand-page-maker/composables/rich-text-editor/bubbleMenu.ts +10 -6
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontSize.ts +73 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/LineHeight.ts +85 -0
- package/src/brand-page-maker/composables/rich-text-editor/useRichTextEditor.ts +111 -27
- package/src/brand-page-maker/utils/index.ts +0 -2
- package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +3 -37
- package/src/brand-page-maker/utils/rich-text-editor.util.ts +11 -66
- package/src/brand-page-maker/validators/field.validator.ts +2 -10
- package/src/lib.ts +0 -9
- package/src/metadata-builder/ai-metadata-executor.ts +26 -0
- package/src/metadata-builder/builders/section-builders/about-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/banner-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/booking-section-builder.ts +5 -2
- package/src/metadata-builder/builders/section-builders/contact-form-builder.ts +4 -3
- package/src/metadata-builder/builders/section-builders/images-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/mailing-list-builder.ts +2 -1
- package/src/metadata-builder/builders/section-builders/mailing-list-section-builder.ts +2 -1
- package/src/metadata-builder/builders/section-builders/profile-section-builder.ts +6 -3
- package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +3 -2
- package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +2 -1
- package/src/metadata-builder/builders/section-builders/text-section-builder.ts +13 -19
- package/src/metadata-builder/builders/update-content-text-builder.ts +148 -0
- package/src/metadata-builder/builders/update-content-text-style-builder.ts +352 -0
- package/src/metadata-builder/helpers/rte.helpers.ts +101 -15
- package/src/metadata-builder/metadata-executor.ts +5 -0
- package/src/metadata-builder/models.ts +3 -0
- package/src/metadata-builder/parsers/ai-toolcall-parser.ts +27 -0
- package/src/metadata-builder/sanitizers/rte-schema-check.ts +53 -24
- package/src/shared/content-settings/components/GenericTextInput.mixin.ts +1 -17
- package/src/shared/content-settings/components/content-settings-item/ContentSettingsItemField.mixin.ts +3 -16
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.mixin.ts +4 -13
- package/src/shared/content-settings/components/testimonial-items-field/TestimonialItemsField.vue +4 -6
- package/src/shared/content-settings/components/text-image-field/TextImageField.vue +3 -6
- package/src/shared/content-settings/components/text-items-field/TextItemsField.mixin.ts +1 -6
- package/src/shared/content-settings/components/text-items-field/TextItemsField.vue +1 -1
- package/src/utils/minify-brand-page-metadata.ts +1 -4
- package/lib/src/brand-page/utils/migration/migration-20.d.ts +0 -2
- package/lib/src/brand-page/utils/migration/migration-20.js +0 -66
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.d.ts +0 -22
- package/lib/src/brand-page-maker/__fixtures__/complex-rte.fixture.js +0 -22
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +0 -14
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +0 -39
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.d.ts +0 -28
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +0 -90
- package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +0 -63
- package/lib/src/brand-page-maker/utils/rte-render.util.js +0 -95
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.d.ts +0 -6
- package/lib/src/brand-page-maker/utils/rte-sanitize.util.js +0 -158
- package/src/brand-page/utils/migration/migration-20.server.test.ts +0 -125
- package/src/brand-page/utils/migration/migration-20.test.ts +0 -72
- package/src/brand-page/utils/migration/migration-20.ts +0 -87
- package/src/brand-page/utils/text.util.test.ts +0 -47
- package/src/brand-page/utils/view-model.pageid-rte.test.ts +0 -104
- package/src/brand-page-maker/__fixtures__/complex-rte.fixture.ts +0 -23
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +0 -52
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +0 -42
- package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +0 -137
- package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +0 -202
- package/src/brand-page-maker/utils/rte-features.test.ts +0 -351
- package/src/brand-page-maker/utils/rte-render-parity.test.ts +0 -157
- package/src/brand-page-maker/utils/rte-render.util.ts +0 -165
- package/src/brand-page-maker/utils/rte-sanitize-json.test.ts +0 -158
- package/src/brand-page-maker/utils/rte-sanitize.util.ts +0 -195
- package/src/brand-page-maker/validators/field.validator.test.ts +0 -55
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { resolvePageIdsInRteJson } from './view-model.util';
|
|
3
|
-
import type { NavViewModel } from '../view-models';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* BP-5847 — internal RTE hyperlinks are stored as `href="pageid:<id>"`. On the
|
|
7
|
-
* published site these must resolve to the page's relative path. The string
|
|
8
|
-
* (legacy HTML) path already did this; once RTE moved to Tiptap JSON the link
|
|
9
|
-
* marks were skipped, so links rendered as raw `pageid:` and didn't navigate.
|
|
10
|
-
*/
|
|
11
|
-
describe('resolvePageIdsInRteJson (BP-5847)', () => {
|
|
12
|
-
const nav: NavViewModel = {
|
|
13
|
-
navItems: [
|
|
14
|
-
{
|
|
15
|
-
pageId: 'home-id',
|
|
16
|
-
slug: 'home',
|
|
17
|
-
displayName: 'Home',
|
|
18
|
-
routeName: 'home',
|
|
19
|
-
routeParams: { slug: 'mysite' },
|
|
20
|
-
isRoot: true,
|
|
21
|
-
isHidden: false,
|
|
22
|
-
relativePath: '',
|
|
23
|
-
nestedNavItems: [],
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
pageId: 'about-id',
|
|
27
|
-
slug: 'about',
|
|
28
|
-
displayName: 'About',
|
|
29
|
-
routeName: 'about',
|
|
30
|
-
routeParams: { slug: 'mysite' },
|
|
31
|
-
isRoot: false,
|
|
32
|
-
isHidden: false,
|
|
33
|
-
relativePath: 'about',
|
|
34
|
-
nestedNavItems: [],
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const options = { brandPageSlug: 'mysite' } as any;
|
|
40
|
-
|
|
41
|
-
const docWithLink = (href: string) => ({
|
|
42
|
-
type: 'doc',
|
|
43
|
-
content: [
|
|
44
|
-
{
|
|
45
|
-
type: 'paragraph',
|
|
46
|
-
content: [
|
|
47
|
-
{
|
|
48
|
-
type: 'text',
|
|
49
|
-
text: 'link',
|
|
50
|
-
marks: [{ type: 'link', attrs: { href, target: '_blank' } }],
|
|
51
|
-
},
|
|
52
|
-
],
|
|
53
|
-
},
|
|
54
|
-
],
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const linkHref = (doc: any) => doc.content[0].content[0].marks[0].attrs.href;
|
|
58
|
-
|
|
59
|
-
it('resolves a pageid link to a sub-page relative path', () => {
|
|
60
|
-
const out = resolvePageIdsInRteJson(
|
|
61
|
-
docWithLink('pageid:about-id'),
|
|
62
|
-
nav,
|
|
63
|
-
options,
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
expect(linkHref(out)).toBe('/mysite/about');
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('resolves a root pageid link to the site root', () => {
|
|
70
|
-
const out = resolvePageIdsInRteJson(
|
|
71
|
-
docWithLink('pageid:home-id'),
|
|
72
|
-
nav,
|
|
73
|
-
options,
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
expect(linkHref(out)).toBe('/mysite');
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('falls back to root when the pageid cannot be matched', () => {
|
|
80
|
-
const out = resolvePageIdsInRteJson(
|
|
81
|
-
docWithLink('pageid:missing-id'),
|
|
82
|
-
nav,
|
|
83
|
-
options,
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
expect(linkHref(out)).toBe('/mysite');
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('leaves external links untouched and returns the same reference', () => {
|
|
90
|
-
const doc = docWithLink('https://example.com');
|
|
91
|
-
const out = resolvePageIdsInRteJson(doc, nav, options);
|
|
92
|
-
|
|
93
|
-
expect(linkHref(out)).toBe('https://example.com');
|
|
94
|
-
expect(out).toBe(doc);
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('does not mutate the source doc (returns a new object) when resolving', () => {
|
|
98
|
-
const doc = docWithLink('pageid:about-id');
|
|
99
|
-
const out = resolvePageIdsInRteJson(doc, nav, options);
|
|
100
|
-
|
|
101
|
-
expect(out).not.toBe(doc);
|
|
102
|
-
expect(linkHref(doc)).toBe('pageid:about-id');
|
|
103
|
-
});
|
|
104
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BP-5787 — golden-master fixture for the RTE HTML→JSON migration.
|
|
3
|
-
*
|
|
4
|
-
* A single rich-text value that exercises every mark/node the editor can emit,
|
|
5
|
-
* so one snapshot guards the whole serialisation surface. Captured as a baseline
|
|
6
|
-
* on the current HTML pipeline; after the migration the JSON pipeline must
|
|
7
|
-
* reproduce byte-identical rendered output (see rte-render-parity.test.ts).
|
|
8
|
-
*
|
|
9
|
-
* Coverage:
|
|
10
|
-
* - block nodes: heading (h2), paragraph, text-align (center / right)
|
|
11
|
-
* - inline marks: bold, italic, underline, strike
|
|
12
|
-
* - textStyle attrs: fontFamily, fontSize (em), letterSpacing (em), lineHeight (unitless)
|
|
13
|
-
* - colour: theme TOKEN ref (var(--logo-text-color)) AND a literal hex (#FF0000)
|
|
14
|
-
* - highlight (background colour via <mark>)
|
|
15
|
-
* - links: internal (target=_self) and external (target=_blank rel=noopener)
|
|
16
|
-
*/
|
|
17
|
-
export const COMPLEX_RTE_HTML = `<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">highlight</mark> <span style="color: #FF0000">literal hex</span> <a target="_self" href="/about">internal link</a> <a target="_blank" rel="noopener noreferrer nofollow" href="https://example.com">external link</a></p><p style="text-align: right">second paragraph, right-aligned</p>`;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* The theme-token CSS variable that MUST survive the HTML→JSON round-trip
|
|
21
|
-
* (BP-5787 stores token refs instead of flattening them to rgb()).
|
|
22
|
-
*/
|
|
23
|
-
export const THEME_TOKEN_REF = 'var(--logo-text-color)';
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
-
import { Editor } from '@tiptap/vue-3';
|
|
3
|
-
import { getRteExtensions } from '../getRteExtensions';
|
|
4
|
-
import { sanitizeRteJson } from '@brand-page-maker/utils/rte-sanitize.util';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* BP-5787 — multi-word font families ("Open Sans") are quoted in CSS, so
|
|
8
|
-
* re-parsing HTML (pasting RTE-styled text between editors) previously yielded
|
|
9
|
-
* a value WITH quote chars, which the native sanitiser then dropped — losing
|
|
10
|
-
* the font. The custom FontFamily extension strips the quotes on parse.
|
|
11
|
-
*/
|
|
12
|
-
describe('FontFamily — quote-stripping parse (BP-5787)', () => {
|
|
13
|
-
let editors: Editor[] = [];
|
|
14
|
-
const mk = () => {
|
|
15
|
-
const e = new Editor({
|
|
16
|
-
extensions: getRteExtensions({ restrictStarterKit: true }),
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
editors.push(e);
|
|
20
|
-
|
|
21
|
-
return e;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
afterEach(() => {
|
|
25
|
-
editors.forEach((e) => e.destroy());
|
|
26
|
-
editors = [];
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const fontFamilyOf = (json: any) =>
|
|
30
|
-
json.content?.[0]?.content?.[0]?.marks?.find(
|
|
31
|
-
(m: any) => m.type === 'textStyle',
|
|
32
|
-
)?.attrs?.fontFamily;
|
|
33
|
-
|
|
34
|
-
for (const font of ['Montserrat', 'Open Sans', 'Playwrite AU']) {
|
|
35
|
-
it(`"${font}" survives an HTML re-parse + sanitise unquoted`, () => {
|
|
36
|
-
const src = mk();
|
|
37
|
-
|
|
38
|
-
src.commands.setContent('<p>Hello world</p>');
|
|
39
|
-
src.commands.selectAll();
|
|
40
|
-
src.commands.setFontFamily(font);
|
|
41
|
-
|
|
42
|
-
// Re-parse the serialised HTML — mirrors an RTE→RTE paste.
|
|
43
|
-
const tgt = mk();
|
|
44
|
-
|
|
45
|
-
tgt.commands.setContent(src.getHTML());
|
|
46
|
-
|
|
47
|
-
expect(fontFamilyOf(tgt.getJSON())).toBe(font);
|
|
48
|
-
// and the sanitiser must keep it (isSafeCssValue rejects quote chars).
|
|
49
|
-
expect(fontFamilyOf(sanitizeRteJson(tgt.getJSON()))).toBe(font);
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
});
|
|
@@ -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,137 +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, FontSize, LineHeight } 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 { LetterSpacing } from './extensions/LetterSpacing';
|
|
12
|
-
import { MaxLength } from './extensions/MaxLength';
|
|
13
|
-
import { AutoLink } from './extensions/AutoLink';
|
|
14
|
-
import { isHrefPageId } from './hyperlink';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Single source of truth for the Tiptap extension set (BP-5787).
|
|
18
|
-
*
|
|
19
|
-
* The live editor, the headless serialiser (useExternalRichTextEditor), and
|
|
20
|
-
* the upcoming render-from-JSON path must all use the SAME extensions, or
|
|
21
|
-
* rendered output drifts from what the user authored. This factory removes the
|
|
22
|
-
* previous duplication while preserving each consumer's exact configuration.
|
|
23
|
-
*
|
|
24
|
-
* Known, intentional differences are parameterised:
|
|
25
|
-
* - `restrictStarterKit` — the live editor disables list/hr/code nodes.
|
|
26
|
-
* - `includeLinkRel` — the headless/render path emits rel="noopener noreferrer";
|
|
27
|
-
* the live editing surface does not.
|
|
28
|
-
* - `editing` — only the live editor carries the Placeholder + MaxLength plugins
|
|
29
|
-
* (neither affects serialised output; they are editing-time only).
|
|
30
|
-
*/
|
|
31
|
-
export interface RteExtensionOptions {
|
|
32
|
-
/** Disable StarterKit nodes the editor doesn't use (lists, hr, code). */
|
|
33
|
-
restrictStarterKit?: boolean;
|
|
34
|
-
/** Emit rel="noopener noreferrer" on link HTMLAttributes (render path). */
|
|
35
|
-
includeLinkRel?: boolean;
|
|
36
|
-
/** When present, add the live-editing-only plugins (Placeholder, MaxLength). */
|
|
37
|
-
editing?: {
|
|
38
|
-
placeholder?: string;
|
|
39
|
-
maxLength?: number;
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const getRteExtensions = (
|
|
44
|
-
options: RteExtensionOptions = {},
|
|
45
|
-
): Extensions => {
|
|
46
|
-
const {
|
|
47
|
-
restrictStarterKit = false,
|
|
48
|
-
includeLinkRel = false,
|
|
49
|
-
editing,
|
|
50
|
-
} = options;
|
|
51
|
-
|
|
52
|
-
const extensions: Extensions = [
|
|
53
|
-
// v3 StarterKit bundles Link + Underline; we register both separately (with
|
|
54
|
-
// custom Link config), so disable the bundled copies to avoid duplicate
|
|
55
|
-
// extension errors.
|
|
56
|
-
// trailingNode: false — v3 StarterKit adds a TrailingNode plugin (not in v2)
|
|
57
|
-
// that appends an empty paragraph after any non-paragraph trailing block.
|
|
58
|
-
// Disabled to keep editor/stored-content behaviour identical to v2 (BP-5787
|
|
59
|
-
// is backward-compat-first); revisit as a standalone UX change if wanted.
|
|
60
|
-
restrictStarterKit
|
|
61
|
-
? StarterKit.configure({
|
|
62
|
-
link: false,
|
|
63
|
-
underline: false,
|
|
64
|
-
trailingNode: false,
|
|
65
|
-
bulletList: false,
|
|
66
|
-
orderedList: false,
|
|
67
|
-
listItem: false,
|
|
68
|
-
horizontalRule: false,
|
|
69
|
-
codeBlock: false,
|
|
70
|
-
code: false,
|
|
71
|
-
})
|
|
72
|
-
: StarterKit.configure({
|
|
73
|
-
link: false,
|
|
74
|
-
underline: false,
|
|
75
|
-
trailingNode: false,
|
|
76
|
-
}),
|
|
77
|
-
Underline,
|
|
78
|
-
// priority: 99 — render the textStyle <span> (which carries font-size) as the
|
|
79
|
-
// OUTERMOST inline mark, so decoration marks (<u>/<s>) nest INSIDE it and
|
|
80
|
-
// inherit the applied font-size. v3's extension-text-style defaults to
|
|
81
|
-
// priority 101, which flips this: <u>/<s> end up outside the font-size span
|
|
82
|
-
// and inherit only the base paragraph size. Since text-decoration thickness
|
|
83
|
-
// AND vertical position are computed from the decorating element's own font,
|
|
84
|
-
// that produced thin, mis-centred underline/strike on large text — a
|
|
85
|
-
// regression vs v2's serializer, which emitted the font-size span outermost
|
|
86
|
-
// (BP-5787, backward-compat-first).
|
|
87
|
-
TextStyle.extend({ priority: 99 }).configure({
|
|
88
|
-
mergeNestedSpanStyles: true,
|
|
89
|
-
}),
|
|
90
|
-
TextAlign.configure({
|
|
91
|
-
types: ['heading', 'paragraph'],
|
|
92
|
-
}),
|
|
93
|
-
FontFamily.configure({
|
|
94
|
-
types: ['textStyle'],
|
|
95
|
-
}),
|
|
96
|
-
// FontSize + LineHeight are now shipped natively by @tiptap/extension-text-style.
|
|
97
|
-
FontSize.configure({ types: ['textStyle'] }),
|
|
98
|
-
LetterSpacing,
|
|
99
|
-
LineHeight.configure({ types: ['textStyle'] }),
|
|
100
|
-
ColorExtension.configure({
|
|
101
|
-
types: ['textStyle'],
|
|
102
|
-
}),
|
|
103
|
-
Highlight.configure({
|
|
104
|
-
multicolor: true,
|
|
105
|
-
}),
|
|
106
|
-
];
|
|
107
|
-
|
|
108
|
-
// Live-editing-only plugins sit between Highlight and Link (original order).
|
|
109
|
-
if (editing) {
|
|
110
|
-
extensions.push(
|
|
111
|
-
Placeholder.configure({ placeholder: editing.placeholder }),
|
|
112
|
-
MaxLength.configure({ maxLength: editing.maxLength }),
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
extensions.push(
|
|
117
|
-
Link.configure({
|
|
118
|
-
openOnClick: false,
|
|
119
|
-
defaultProtocol: 'https',
|
|
120
|
-
autolink: true,
|
|
121
|
-
HTMLAttributes: (attrs) => {
|
|
122
|
-
const href = attrs.href;
|
|
123
|
-
const isExternal =
|
|
124
|
-
href && (href.startsWith('http://') || href.startsWith('https://'));
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
...(includeLinkRel && { rel: 'noopener noreferrer' }),
|
|
128
|
-
target: isExternal ? '_blank' : null,
|
|
129
|
-
};
|
|
130
|
-
},
|
|
131
|
-
isAllowedUri: (url, ctx) => ctx.defaultValidate(url) || isHrefPageId(url),
|
|
132
|
-
}),
|
|
133
|
-
AutoLink,
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
return extensions;
|
|
137
|
-
};
|
|
@@ -1,202 +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 renderer) 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: #FF0000">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": "#FF0000",
|
|
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": null,
|
|
153
|
-
"href": "/about",
|
|
154
|
-
"rel": null,
|
|
155
|
-
"target": "_self",
|
|
156
|
-
"title": null,
|
|
157
|
-
},
|
|
158
|
-
"type": "link",
|
|
159
|
-
},
|
|
160
|
-
],
|
|
161
|
-
"text": "internal link",
|
|
162
|
-
"type": "text",
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
"text": " ",
|
|
166
|
-
"type": "text",
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
"marks": [
|
|
170
|
-
{
|
|
171
|
-
"attrs": {
|
|
172
|
-
"class": null,
|
|
173
|
-
"href": "https://example.com",
|
|
174
|
-
"rel": "noopener noreferrer nofollow",
|
|
175
|
-
"target": "_blank",
|
|
176
|
-
"title": null,
|
|
177
|
-
},
|
|
178
|
-
"type": "link",
|
|
179
|
-
},
|
|
180
|
-
],
|
|
181
|
-
"text": "external link",
|
|
182
|
-
"type": "text",
|
|
183
|
-
},
|
|
184
|
-
],
|
|
185
|
-
"type": "paragraph",
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
"attrs": {
|
|
189
|
-
"textAlign": "right",
|
|
190
|
-
},
|
|
191
|
-
"content": [
|
|
192
|
-
{
|
|
193
|
-
"text": "second paragraph, right-aligned",
|
|
194
|
-
"type": "text",
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
"type": "paragraph",
|
|
198
|
-
},
|
|
199
|
-
],
|
|
200
|
-
"type": "doc",
|
|
201
|
-
}
|
|
202
|
-
`;
|