@designcrowd/library.brand-page 6.0.6-json-prereq-5 → 6.0.6-json-prereq-7
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-CZmzW2kQ.js → CreatedOnBrandCrowd-de-de-SPVLPi8n.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-D2tXzTdS.js → CreatedOnBrandCrowd-en-us-CvtrCPHd.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-BDXmktFP.js → CreatedOnBrandCrowd-es-es-BSsac7jB.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-BniZQPhE.js → CreatedOnBrandCrowd-fr-ca-BauENV2a.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-BniZQPhE.js → CreatedOnBrandCrowd-fr-fr-BauENV2a.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-mngAiD6i.js → CreatedOnBrandCrowd-pt-br-Be0hawmr.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-mngAiD6i.js → CreatedOnBrandCrowd-pt-pt-Be0hawmr.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-Bacn6DYk.js → CreatedOnDcom-de-de-CTjE2Tmn.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-CtQMBrOW.js → CreatedOnDcom-en-us-DnZRp5Pg.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-ndMBMTWL.js → CreatedOnDcom-es-es-J-9qvjy8.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-dQuX1eXb.js → CreatedOnDcom-fr-ca-BDNHqm4G.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-74uI3jen.js → CreatedOnDcom-fr-fr-B_2-ceuO.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-BAr2KEUU.js → CreatedOnDcom-pt-br-76lJVrAY.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-CIOnL7w9.js → CreatedOnDcom-pt-pt-TQp3CNcu.js} +1 -1
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.d.ts +14 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.js +39 -0
- package/lib/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.js +1 -1
- package/lib/src/brand-page-maker/validators/field.validator.js +10 -7
- package/lib/src/{bundled-translations-DY44n5a1.js → bundled-translations-BUyt4scr.js} +1 -1
- package/lib/src/bundles/bundled-translations.json +0 -1
- package/lib/src/css/library.brand-page-brandCrowd.min.css +36 -36
- package/lib/src/css/library.brand-page-brandPage.min.css +36 -36
- package/lib/src/css/library.brand-page-designCom.min.css +36 -36
- package/lib/src/css/library.brand-page-preview.min.css +36 -36
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-CfYQhXJl.js → lib-Bh9Y5_-x.js} +8597 -8588
- package/package.json +1 -1
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +0 -19
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.test.ts +52 -0
- package/src/brand-page-maker/composables/rich-text-editor/extensions/FontFamily.ts +42 -0
- package/src/brand-page-maker/composables/rich-text-editor/getRteExtensions.ts +1 -1
- package/src/brand-page-maker/validators/field.validator.test.ts +15 -10
- package/src/brand-page-maker/validators/field.validator.ts +10 -7
- package/src/bundles/bundled-translations.json +0 -1
- package/src/shared/content-settings/components/content-settings/i18n/content-settings.json +0 -1
package/package.json
CHANGED
|
@@ -342,15 +342,7 @@
|
|
|
342
342
|
<TextInput
|
|
343
343
|
v-model="markedText"
|
|
344
344
|
:placeholder="contentSettingsTr('displayTextPlaceholder')"
|
|
345
|
-
:error="isDisplayTextTooLong"
|
|
346
345
|
/>
|
|
347
|
-
<p
|
|
348
|
-
v-if="isDisplayTextTooLong"
|
|
349
|
-
class="tw-text-error-500 tw-text-sm"
|
|
350
|
-
data-test-tag="hyperlink-display-text-error"
|
|
351
|
-
>
|
|
352
|
-
{{ contentSettingsTr('displayTextMaxLimitError') }}
|
|
353
|
-
</p>
|
|
354
346
|
|
|
355
347
|
<FieldSubHeader :title="contentSettingsTr('linkToLabel')" />
|
|
356
348
|
<ButtonGroup
|
|
@@ -669,18 +661,7 @@ const urlValidationSettings = computed(() => {
|
|
|
669
661
|
};
|
|
670
662
|
});
|
|
671
663
|
|
|
672
|
-
// BP-5859: the hyperlink display text is inserted into the RTE, so it must
|
|
673
|
-
// respect the same max-char limit. Without this the over-limit text was
|
|
674
|
-
// silently truncated on insert with no feedback.
|
|
675
|
-
const isDisplayTextTooLong = computed(
|
|
676
|
-
() => markedText.value.length > props.maxLength,
|
|
677
|
-
);
|
|
678
|
-
|
|
679
664
|
const isAddLinkButtonDisabled = computed(() => {
|
|
680
|
-
if (isDisplayTextTooLong.value) {
|
|
681
|
-
return true;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
665
|
if (selectedLinkType.value === HYPERLINK_TYPE.url) {
|
|
685
666
|
const urlValue = hyperlinkUrlValue.value;
|
|
686
667
|
// Check if URL is invalid OR doesn't have https protocol
|
|
@@ -0,0 +1,52 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
});
|
|
@@ -3,11 +3,11 @@ import StarterKit from '@tiptap/starter-kit';
|
|
|
3
3
|
import Underline from '@tiptap/extension-underline';
|
|
4
4
|
import TextStyle from '@tiptap/extension-text-style';
|
|
5
5
|
import TextAlign from '@tiptap/extension-text-align';
|
|
6
|
-
import FontFamily from '@tiptap/extension-font-family';
|
|
7
6
|
import ColorExtension from '@tiptap/extension-color';
|
|
8
7
|
import Highlight from '@tiptap/extension-highlight';
|
|
9
8
|
import Placeholder from '@tiptap/extension-placeholder';
|
|
10
9
|
import Link from '@tiptap/extension-link';
|
|
10
|
+
import { FontFamily } from './extensions/FontFamily';
|
|
11
11
|
import { FontSize } from './extensions/FontSize';
|
|
12
12
|
import { LetterSpacing } from './extensions/LetterSpacing';
|
|
13
13
|
import { LineHeight } from './extensions/LineHeight';
|
|
@@ -2,11 +2,12 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
import { validatorDict } from './field.validator';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* BP-5857 — the `title`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* BP-5857 — the `title` validator must preserve pre-JSON (staging) semantics.
|
|
6
|
+
* An RTE field's value used to be inline HTML: an empty editor was "<p></p>",
|
|
7
|
+
* a non-empty string, so the required check always PASSED for RTE fields — it
|
|
8
|
+
* only failed for genuinely empty PLAIN inputs. RTE fields are now Tiptap JSON
|
|
9
|
+
* docs, so any doc must likewise count as "present" (valid); a plain string is
|
|
10
|
+
* still required to be non-empty.
|
|
10
11
|
*/
|
|
11
12
|
describe('field.validator — title (nonEmptyString)', () => {
|
|
12
13
|
const titleValidator = validatorDict.title;
|
|
@@ -23,22 +24,26 @@ describe('field.validator — title (nonEmptyString)', () => {
|
|
|
23
24
|
],
|
|
24
25
|
});
|
|
25
26
|
|
|
26
|
-
it('accepts a non-empty
|
|
27
|
+
it('accepts a non-empty plain string', () => {
|
|
27
28
|
expect(titleValidator.validate('title', 'Payments').isValid).toBe(true);
|
|
28
29
|
});
|
|
29
30
|
|
|
30
|
-
it('rejects an empty string', () => {
|
|
31
|
+
it('rejects an empty plain string (required still enforced for plain inputs)', () => {
|
|
31
32
|
expect(titleValidator.validate('title', '').isValid).toBe(false);
|
|
32
33
|
});
|
|
33
34
|
|
|
34
|
-
it('accepts
|
|
35
|
+
it('accepts legacy empty RTE HTML "<p></p>" (non-empty string — staging parity)', () => {
|
|
36
|
+
expect(titleValidator.validate('title', '<p></p>').isValid).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('accepts a Tiptap JSON doc with visible text', () => {
|
|
35
40
|
expect(titleValidator.validate('title', jsonDoc('Payments')).isValid).toBe(
|
|
36
41
|
true,
|
|
37
42
|
);
|
|
38
43
|
});
|
|
39
44
|
|
|
40
|
-
it('
|
|
41
|
-
expect(titleValidator.validate('title', jsonDoc('')).isValid).toBe(
|
|
45
|
+
it('accepts an empty Tiptap JSON doc (RTE parity — empty editor is "present")', () => {
|
|
46
|
+
expect(titleValidator.validate('title', jsonDoc('')).isValid).toBe(true);
|
|
42
47
|
});
|
|
43
48
|
|
|
44
49
|
it('rejects null/undefined without throwing', () => {
|
|
@@ -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,
|
|
3
|
+
import { YOUTUBE_URL_RE, isTiptapDocument } from '@brand-page-maker/utils';
|
|
4
4
|
import { validatorTr } from '@src/composables/useTranslate';
|
|
5
5
|
|
|
6
6
|
const addressValueValidator: Validator = {
|
|
@@ -76,12 +76,15 @@ export const dummyValidator: Validator = {
|
|
|
76
76
|
|
|
77
77
|
const nonEmptyStringValidator: Validator = {
|
|
78
78
|
validate: (name, value: any) => {
|
|
79
|
-
// BP-5787/BP-5857:
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
|
|
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;
|
|
85
88
|
|
|
86
89
|
return {
|
|
87
90
|
isValid,
|
|
@@ -1609,7 +1609,6 @@
|
|
|
1609
1609
|
"linkToLabel": "Link to",
|
|
1610
1610
|
"addLinkButtonText": "Add Link",
|
|
1611
1611
|
"enterValidUrlError": "Please enter a valid URL",
|
|
1612
|
-
"displayTextMaxLimitError": "Display text exceeds the maximum character limit",
|
|
1613
1612
|
"enterTextHerePlaceholder": "Enter text here...",
|
|
1614
1613
|
"bookNowLabel": "Book Now",
|
|
1615
1614
|
"shippingLabel": "Enable Shipping Address",
|
|
@@ -203,7 +203,6 @@
|
|
|
203
203
|
"linkToLabel": "Link to",
|
|
204
204
|
"addLinkButtonText": "Add Link",
|
|
205
205
|
"enterValidUrlError": "Please enter a valid URL",
|
|
206
|
-
"displayTextMaxLimitError": "Display text exceeds the maximum character limit",
|
|
207
206
|
"enterTextHerePlaceholder": "Enter text here...",
|
|
208
207
|
"bookNowLabel": "Book Now",
|
|
209
208
|
"shippingLabel": "Enable Shipping Address",
|