@designcrowd/library.brand-page 6.0.19 → 6.0.20
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-DCwwdYPj.js → CreatedOnBrandCrowd-de-de-C52IyXZu.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-DTmZeDQ1.js → CreatedOnBrandCrowd-en-us-DhTy_dEV.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-Dbrz6TXv.js → CreatedOnBrandCrowd-es-es-BJelIAQ4.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-DLQhgn8T.js → CreatedOnBrandCrowd-fr-ca-DwJohy0T.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-DLQhgn8T.js → CreatedOnBrandCrowd-fr-fr-DwJohy0T.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-yAzzD2ik.js → CreatedOnBrandCrowd-pt-br-CMzKbxdG.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-yAzzD2ik.js → CreatedOnBrandCrowd-pt-pt-CMzKbxdG.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-CNmNNE5o.js → CreatedOnDcom-de-de-DAy1GHrw.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-BUmLFlS_.js → CreatedOnDcom-en-us-CgBDAkJN.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-DfygM_0q.js → CreatedOnDcom-es-es-Ds9d5rem.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-DIEft8xE.js → CreatedOnDcom-fr-ca-CZL0BQ-u.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-Cn3jWG2n.js → CreatedOnDcom-fr-fr-BVYh5Wvo.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-DXhgGwEh.js → CreatedOnDcom-pt-br-1hsWNXfq.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-BvE9ckw1.js → CreatedOnDcom-pt-pt-DvM0N-3M.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/rich-text-editor/useRichTextEditor.js +100 -21
- 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 -19
- package/lib/src/brand-page-maker/utils/rich-text-editor.util.js +5 -35
- package/lib/src/brand-page-maker/validators/field.validator.js +2 -10
- package/lib/src/css/library.brand-page-brandCrowd.min.css +1 -1
- package/lib/src/css/library.brand-page-brandPage.min.css +1 -1
- package/lib/src/css/library.brand-page-designCom.min.css +1 -1
- package/lib/src/css/library.brand-page-preview.min.css +1 -1
- package/lib/src/index.d.ts +0 -1
- package/lib/src/index.mjs +138 -143
- package/lib/src/{lib-Bm-fYap1.js → lib-CK2FPBv5.js} +55098 -55367
- package/lib/src/lib.js +0 -2
- package/lib/src/metadata-builder/builders/update-content-text-builder.js +15 -20
- package/lib/src/metadata-builder/builders/update-content-text-style-builder.js +3 -8
- package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +2 -2
- package/lib/src/metadata-builder/helpers/rte.helpers.js +3 -6
- 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/utils/minify-brand-page-metadata.js +1 -4
- package/package.json +1 -1
- 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/composables/rich-text-editor/useRichTextEditor.ts +107 -23
- package/src/brand-page-maker/utils/index.ts +0 -2
- package/src/brand-page-maker/utils/rich-text-editor.util.test.ts +3 -4
- package/src/brand-page-maker/utils/rich-text-editor.util.ts +11 -50
- package/src/brand-page-maker/validators/field.validator.ts +2 -10
- package/src/lib.ts +0 -9
- package/src/metadata-builder/builders/update-content-text-builder.ts +18 -32
- package/src/metadata-builder/builders/update-content-text-style-builder.ts +4 -14
- package/src/metadata-builder/helpers/rte.helpers.ts +7 -11
- 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/utils/minify-brand-page-metadata.ts +1 -4
- package/lib/src/brand-page/utils/migration/migration-19.d.ts +0 -2
- package/lib/src/brand-page/utils/migration/migration-19.js +0 -71
- 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 -66
- package/lib/src/brand-page-maker/utils/rte-render.util.d.ts +0 -33
- package/lib/src/brand-page-maker/utils/rte-render.util.js +0 -50
- 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/lib/src/brand-page-maker/utils/rte-serializer.d.ts +0 -5
- package/lib/src/brand-page-maker/utils/rte-serializer.js +0 -112
- package/src/brand-page/utils/migration/migration-19.test.ts +0 -72
- package/src/brand-page/utils/migration/migration-19.ts +0 -83
- 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 -113
- package/src/brand-page-maker/utils/__snapshots__/rte-render-parity.test.ts.snap +0 -200
- package/src/brand-page-maker/utils/rte-features.test.ts +0 -330
- package/src/brand-page-maker/utils/rte-render-parity.test.ts +0 -156
- package/src/brand-page-maker/utils/rte-render.util.ts +0 -77
- 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/utils/rte-serializer.ts +0 -172
- package/src/brand-page-maker/validators/field.validator.test.ts +0 -55
package/lib/src/lib.js
CHANGED
|
@@ -30,8 +30,6 @@ export { minifyBrandPageMetadata, } from './utils/minify-brand-page-metadata';
|
|
|
30
30
|
export * as brandPageUtils from './brand-page/utils';
|
|
31
31
|
export { getOptimalLogoSize, calculateOptimizedLogoDimensions, applyLogoImageBase64UrlToBrandPageTemplateModel, recalculateLogoContentDimensions, recalculateHeaderLogoSizes, } from './brand-page/utils/logo.util';
|
|
32
32
|
export { sanitizeRTEHtml } from './brand-page-maker/utils';
|
|
33
|
-
// BP-5787: RTE JSON storage boundary helpers (render/normalise/sanitise/extract).
|
|
34
|
-
export { renderRteToHtml, toRteJson, isTiptapDocument, sanitizeRteJson, getRteJsonPlainText, } from './brand-page-maker/utils';
|
|
35
33
|
export { getPageSlugFromPageTitle } from './brand-page-maker/utils/slug.util';
|
|
36
34
|
// AI-pipeline preview (longclaw per-turn snapshots): web-renderer's
|
|
37
35
|
// /preview/ai-pipeline render page + manifest server route apply the agent's
|
|
@@ -1,23 +1,6 @@
|
|
|
1
1
|
import {} from '../../brand-page/models';
|
|
2
2
|
import { BaseMetadataBuilder, BUILDER_TYPES, } from '../models';
|
|
3
|
-
import { hasRTEContent, replaceTextInRTE
|
|
4
|
-
/**
|
|
5
|
-
* BP-5787 — replace the text of an RTE field while preserving formatting, for
|
|
6
|
-
* either storage form. JSON is rendered to HTML, edited via the existing
|
|
7
|
-
* HTML-preserving replace, then converted back to JSON; plain/legacy strings
|
|
8
|
-
* keep the original behaviour.
|
|
9
|
-
*/
|
|
10
|
-
const updateRteFieldValue = (existing, newText) => {
|
|
11
|
-
const wasJson = isTiptapDocument(existing);
|
|
12
|
-
const existingHtml = wasJson ? renderRteToHtml(existing) : existing;
|
|
13
|
-
if (existingHtml &&
|
|
14
|
-
typeof existingHtml === 'string' &&
|
|
15
|
-
hasRTEContent(existingHtml)) {
|
|
16
|
-
const updatedHtml = replaceTextInRTE(existingHtml, newText);
|
|
17
|
-
return wasJson ? toRteJson(updatedHtml) : updatedHtml;
|
|
18
|
-
}
|
|
19
|
-
return newText;
|
|
20
|
-
};
|
|
3
|
+
import { hasRTEContent, replaceTextInRTE } from '../../brand-page-maker/utils';
|
|
21
4
|
export const buildUpdateTextRequest = (id, payload) => {
|
|
22
5
|
return {
|
|
23
6
|
id,
|
|
@@ -33,7 +16,11 @@ const updateTextField = (target, fieldName, newText) => {
|
|
|
33
16
|
const array = target[arrayName];
|
|
34
17
|
if (Array.isArray(array) && array[+index]) {
|
|
35
18
|
const existingText = array[+index][property];
|
|
36
|
-
const updatedValue =
|
|
19
|
+
const updatedValue = existingText &&
|
|
20
|
+
typeof existingText === 'string' &&
|
|
21
|
+
hasRTEContent(existingText)
|
|
22
|
+
? replaceTextInRTE(existingText, newText)
|
|
23
|
+
: newText;
|
|
37
24
|
// Create a new array with the updated item to trigger reactivity
|
|
38
25
|
const newArray = [...array];
|
|
39
26
|
newArray[+index] = {
|
|
@@ -45,7 +32,15 @@ const updateTextField = (target, fieldName, newText) => {
|
|
|
45
32
|
}
|
|
46
33
|
else {
|
|
47
34
|
// Simple field
|
|
48
|
-
|
|
35
|
+
const existingText = target[fieldName];
|
|
36
|
+
if (existingText &&
|
|
37
|
+
typeof existingText === 'string' &&
|
|
38
|
+
hasRTEContent(existingText)) {
|
|
39
|
+
target[fieldName] = replaceTextInRTE(existingText, newText);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
target[fieldName] = newText;
|
|
43
|
+
}
|
|
49
44
|
}
|
|
50
45
|
};
|
|
51
46
|
export class UpdateContentTextBuilder extends BaseMetadataBuilder {
|
|
@@ -35,19 +35,14 @@ const mergeStyles = (currentStyle, stylesToUpdate, textStyleOptions) => {
|
|
|
35
35
|
});
|
|
36
36
|
return merged;
|
|
37
37
|
};
|
|
38
|
-
// BP-5787 — apply a style to RTE text natively. applyStyleToRTEText runs Tiptap
|
|
39
|
-
// commands on the doc (string or JSON in) and returns JSON — no HTML round-trip.
|
|
40
38
|
const updateRTEText = (text, style) => {
|
|
41
|
-
|
|
42
|
-
// hasRTEContent runtime-narrows it to an actual RTE value (HTML string or
|
|
43
|
-
// Tiptap JSON doc) — i.e. a RichTextValue — before we hand it to the editor.
|
|
44
|
-
if (!hasRTEContent(text)) {
|
|
39
|
+
if (!text || typeof text !== 'string' || !hasRTEContent(text)) {
|
|
45
40
|
return text;
|
|
46
41
|
}
|
|
47
42
|
const { externalEditor } = useExternalRichTextEditor();
|
|
48
|
-
const
|
|
43
|
+
const updatedText = applyStyleToRTEText(externalEditor, text, style);
|
|
49
44
|
externalEditor.destroy();
|
|
50
|
-
return
|
|
45
|
+
return updatedText;
|
|
51
46
|
};
|
|
52
47
|
const findStyleKey = (target, property) => {
|
|
53
48
|
// Try exact match first (e.g., 'testimonialsStyle')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TextStyleDeclaration } from '../../brand-page';
|
|
2
2
|
import type { Editor } from '@tiptap/vue-3';
|
|
3
|
-
export declare const applyStyleToRTEText: (externalEditor: Editor, contentText:
|
|
3
|
+
export declare const applyStyleToRTEText: (externalEditor: Editor, contentText: string, newContentStyle: TextStyleDeclaration) => string;
|
|
4
4
|
/**
|
|
5
5
|
* Converts a Tiptap JSON document (ProseMirror document model) to an HTML
|
|
6
6
|
* string using a headless Tiptap Editor instance.
|
|
@@ -7,11 +7,7 @@ export const applyStyleToRTEText = (externalEditor, contentText, newContentStyle
|
|
|
7
7
|
if (!contentText) {
|
|
8
8
|
return contentText;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
// document or an HTML string; the style is applied via editor commands and
|
|
12
|
-
// the result is returned as JSON — no HTML round-trip.
|
|
13
|
-
const content = typeof contentText === 'string' &&
|
|
14
|
-
contentText.includes(ESCAPED_NEWLINE_CHARACTER)
|
|
10
|
+
const content = contentText.includes(ESCAPED_NEWLINE_CHARACTER)
|
|
15
11
|
? getRenderText(contentText)
|
|
16
12
|
: contentText;
|
|
17
13
|
externalEditor.commands.setContent(content);
|
|
@@ -57,7 +53,8 @@ export const applyStyleToRTEText = (externalEditor, contentText, newContentStyle
|
|
|
57
53
|
? chain.setItalic()
|
|
58
54
|
: chain.unsetItalic();
|
|
59
55
|
chain.run();
|
|
60
|
-
|
|
56
|
+
const html = externalEditor.getHTML();
|
|
57
|
+
return html;
|
|
61
58
|
};
|
|
62
59
|
/**
|
|
63
60
|
* Converts a Tiptap JSON document (ProseMirror document model) to an HTML
|
|
@@ -158,20 +158,7 @@ export default defineComponent({
|
|
|
158
158
|
},
|
|
159
159
|
},
|
|
160
160
|
watch: {
|
|
161
|
-
value(newVal
|
|
162
|
-
// BP-5787: RTE values are Tiptap JSON objects. The parent (TextItemsField)
|
|
163
|
-
// re-clones its items array (cloneDeep) on ANY change — including a sibling
|
|
164
|
-
// field's style sync — yielding a NEW object reference with identical text
|
|
165
|
-
// content. For objects Vue fires this watcher on reference change, so that
|
|
166
|
-
// spurious re-clone would overwrite `_value` and clobber an in-flight edit
|
|
167
|
-
// (a mark applied but not yet debounced to the store), silently dropping
|
|
168
|
-
// the user's formatting. Strings never hit this (same content = same
|
|
169
|
-
// reference = no fire), so mirror that: ignore re-clones whose content is
|
|
170
|
-
// unchanged from the previous prop value.
|
|
171
|
-
if (this.showRichTextEditor &&
|
|
172
|
-
JSON.stringify(newVal) === JSON.stringify(oldVal)) {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
161
|
+
value(newVal) {
|
|
175
162
|
this._value = newVal;
|
|
176
163
|
if (this.showRichTextEditor) {
|
|
177
164
|
const textValue = escapeContentText(this._value);
|
|
@@ -8,7 +8,7 @@ import { SIDE_BAR_TAB_NAMES } from '@brand-page-maker/constants';
|
|
|
8
8
|
import { ITEM_FIELD_TYPES } from '@brand-page-maker/models';
|
|
9
9
|
import { useUiPageSettingsStore } from '../../../page-settings/store';
|
|
10
10
|
import { PAGE_SECTIONS } from '../../../page-settings/models';
|
|
11
|
-
import { getPlainTextFromRTE, replaceTextInRTE, RTE_ALLOWED_SECTION_TYPES, isRTEManagedStyleField,
|
|
11
|
+
import { getPlainTextFromRTE, replaceTextInRTE, RTE_ALLOWED_SECTION_TYPES, isRTEManagedStyleField, } from '@brand-page-maker/utils';
|
|
12
12
|
import { isDefaultContentText } from '@shared/content-settings/utils';
|
|
13
13
|
import { useUiLogoSettingsStore } from '../../../logo-settings/store';
|
|
14
14
|
const RTE_DISALLOWED_FIELD_NAMES = ['buttonText'];
|
|
@@ -262,17 +262,8 @@ export default defineComponent({
|
|
|
262
262
|
},
|
|
263
263
|
onUseGeneratedText(generatedText) {
|
|
264
264
|
let formattedText = generatedText;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// (cold AI path, shared editor); string fields use it directly.
|
|
268
|
-
if (this.showRichTextEditor) {
|
|
269
|
-
if (isTiptapDocument(this.value)) {
|
|
270
|
-
formattedText =
|
|
271
|
-
toRteJson(replaceTextInRTE(renderRteToHtml(this.value), generatedText)) ?? generatedText;
|
|
272
|
-
}
|
|
273
|
-
else if (typeof this.value === 'string') {
|
|
274
|
-
formattedText = replaceTextInRTE(this.value, generatedText);
|
|
275
|
-
}
|
|
265
|
+
if (this.showRichTextEditor && typeof this.value === 'string') {
|
|
266
|
+
formattedText = replaceTextInRTE(this.value, generatedText);
|
|
276
267
|
}
|
|
277
268
|
this.contentSettingsStore.updateItemValue({
|
|
278
269
|
id: this.id,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BRAND_PAGE_TYPES, } from '../brand-page/models';
|
|
2
|
-
import { hasRTEContent
|
|
2
|
+
import { hasRTEContent } from '../brand-page-maker/utils';
|
|
3
3
|
import { useExternalRichTextEditor } from '../brand-page-maker/composables/rich-text-editor/useRichTextEditor';
|
|
4
4
|
import { findAssetKey, resolveLogoData, } from './build-asset-manifest';
|
|
5
5
|
const BUTTON_TYPES = [
|
|
@@ -17,9 +17,6 @@ const BUTTON_TYPES = [
|
|
|
17
17
|
'tiktok',
|
|
18
18
|
];
|
|
19
19
|
const convertText = (value, editor) => {
|
|
20
|
-
// Already Tiptap JSON (BP-5787 storage) — keep as-is.
|
|
21
|
-
if (isTiptapDocument(value))
|
|
22
|
-
return value;
|
|
23
20
|
if (typeof value !== 'string' || !value)
|
|
24
21
|
return undefined;
|
|
25
22
|
if (hasRTEContent(value)) {
|
package/package.json
CHANGED
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
mutedTextForBackground,
|
|
32
32
|
backgroundColorFromStyle,
|
|
33
33
|
} from '@brand-page/utils';
|
|
34
|
-
import { hasRTEContent
|
|
34
|
+
import { hasRTEContent } from '@brand-page-maker/utils';
|
|
35
35
|
import { useReCaptcha } from '../../composables/recaptcha.composable';
|
|
36
36
|
|
|
37
37
|
export interface FieldValue {
|
|
@@ -170,10 +170,7 @@ export default defineComponent({
|
|
|
170
170
|
},
|
|
171
171
|
|
|
172
172
|
title(): string {
|
|
173
|
-
|
|
174
|
-
// throw — read the visible text via getPlainTextFromRTE to decide
|
|
175
|
-
// whether to fall back to the default.
|
|
176
|
-
return getPlainTextFromRTE(this.viewModel.title)
|
|
173
|
+
return this.viewModel.title && this.viewModel.title.trim() !== ''
|
|
177
174
|
? this.viewModel.title
|
|
178
175
|
: 'Contact Us';
|
|
179
176
|
},
|
|
@@ -21,13 +21,7 @@
|
|
|
21
21
|
</div>
|
|
22
22
|
</template>
|
|
23
23
|
<script setup lang="ts">
|
|
24
|
-
import {
|
|
25
|
-
hasRTEContent,
|
|
26
|
-
getPlainTextFromRTE,
|
|
27
|
-
renderRteToHtml,
|
|
28
|
-
isTiptapDocument,
|
|
29
|
-
type RichTextValue,
|
|
30
|
-
} from '@brand-page-maker/utils';
|
|
24
|
+
import { hasRTEContent, getPlainTextFromRTE } from '@brand-page-maker/utils';
|
|
31
25
|
import {
|
|
32
26
|
type FluidTextData,
|
|
33
27
|
type TextStyleDeclaration,
|
|
@@ -43,8 +37,7 @@ import {
|
|
|
43
37
|
|
|
44
38
|
interface Props {
|
|
45
39
|
textStyle: TextStyleDeclaration | undefined;
|
|
46
|
-
|
|
47
|
-
text: RichTextValue;
|
|
40
|
+
text: string;
|
|
48
41
|
element?: string;
|
|
49
42
|
slug?: string;
|
|
50
43
|
}
|
|
@@ -86,20 +79,16 @@ const textStyleRTE = computed(() => {
|
|
|
86
79
|
};
|
|
87
80
|
});
|
|
88
81
|
|
|
89
|
-
// Render either form (legacy HTML string or Tiptap JSON) to final HTML.
|
|
90
|
-
// For a string this returns it unchanged, so the legacy path is unaffected.
|
|
91
|
-
const renderedHtml = computed(() => renderRteToHtml(props.text));
|
|
92
|
-
|
|
93
82
|
const isRTEContent = computed(() => {
|
|
94
|
-
return
|
|
83
|
+
return hasRTEContent(props.text);
|
|
95
84
|
});
|
|
96
85
|
|
|
97
86
|
const sanitizeText = computed(() => {
|
|
98
|
-
if (isRTEContent.value && !getPlainTextFromRTE(
|
|
87
|
+
if (isRTEContent.value && !getPlainTextFromRTE(props.text)) {
|
|
99
88
|
return '';
|
|
100
89
|
}
|
|
101
90
|
|
|
102
|
-
return
|
|
91
|
+
return props.text;
|
|
103
92
|
});
|
|
104
93
|
|
|
105
94
|
const fontSize = computed<any>(() => props.textStyle.fontSize);
|
|
@@ -171,7 +160,7 @@ const applyScale = () => {
|
|
|
171
160
|
}
|
|
172
161
|
|
|
173
162
|
const fontSizeMatchInRTEText = [
|
|
174
|
-
...
|
|
163
|
+
...props.text.matchAll(
|
|
175
164
|
/style="(?:[^"]*?)\bfont-size\s*:\s*(?<fontSize>[^;"']+)[^"]*?"/gm,
|
|
176
165
|
),
|
|
177
166
|
].map((x) => parseFloat(x.groups?.fontSize || '1em'));
|
|
@@ -30,15 +30,6 @@ import type { LogoImageModel, LogoTemplateImageModel } from './logo.model';
|
|
|
30
30
|
import cloneDeep from 'lodash.clonedeep';
|
|
31
31
|
import * as uuid from 'uuid';
|
|
32
32
|
import type { BrandPagePaymentConfig } from './payment.model';
|
|
33
|
-
import type { JSONContent } from '@tiptap/vue-3';
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* BP-5787 — a rich-text field is either a legacy inline-HTML string or a
|
|
37
|
-
* Tiptap JSON document. Typing RTE content fields as RichTextValue (instead of
|
|
38
|
-
* `string`) forces every consumer to handle both forms — surfacing at compile
|
|
39
|
-
* time any code that assumes a plain string (e.g. `.replace`, `.length`).
|
|
40
|
-
*/
|
|
41
|
-
export type RichTextValue = string | JSONContent;
|
|
42
33
|
|
|
43
34
|
export * from './brand-page-context.model';
|
|
44
35
|
export * from './payment.model';
|
|
@@ -289,7 +280,7 @@ export interface TextContent extends Content {
|
|
|
289
280
|
/**
|
|
290
281
|
* For BrandPage/BrandContact only
|
|
291
282
|
*/
|
|
292
|
-
text:
|
|
283
|
+
text: string;
|
|
293
284
|
/**
|
|
294
285
|
* For BrandSite
|
|
295
286
|
*/
|
|
@@ -313,9 +304,9 @@ export interface TextContent extends Content {
|
|
|
313
304
|
|
|
314
305
|
export interface ProfileContent extends Content {
|
|
315
306
|
avatarImageUrl: string;
|
|
316
|
-
name:
|
|
317
|
-
title:
|
|
318
|
-
description:
|
|
307
|
+
name: string;
|
|
308
|
+
title: string;
|
|
309
|
+
description: string;
|
|
319
310
|
backgroundColor: BrandPageCssStyle;
|
|
320
311
|
colorReplacements: ColorReplacement[];
|
|
321
312
|
mediaType?: string;
|
|
@@ -336,7 +327,7 @@ export interface MapContent extends Content {
|
|
|
336
327
|
|
|
337
328
|
export interface MailingListContent extends Content {
|
|
338
329
|
acceptedFields?: 'emailOnly' | 'nameAndEmail';
|
|
339
|
-
title:
|
|
330
|
+
title: string;
|
|
340
331
|
buttonText: string;
|
|
341
332
|
titleStyle: TextStyleDeclaration;
|
|
342
333
|
buttonTextStyle: TextStyleDeclaration;
|
|
@@ -395,10 +386,10 @@ export interface FormField {
|
|
|
395
386
|
export interface ContactFormContent extends Content {
|
|
396
387
|
type: 'contact-form';
|
|
397
388
|
displayStyle: string;
|
|
398
|
-
title:
|
|
399
|
-
description:
|
|
389
|
+
title: string;
|
|
390
|
+
description: string;
|
|
400
391
|
buttonText: string;
|
|
401
|
-
successMessage:
|
|
392
|
+
successMessage: string;
|
|
402
393
|
fields: FormField[];
|
|
403
394
|
responseEmail: string;
|
|
404
395
|
imageUrl?: string;
|
|
@@ -418,8 +409,8 @@ export interface ContactFormContent extends Content {
|
|
|
418
409
|
export interface BannerSectionContent extends Content {
|
|
419
410
|
type: 'banner-section';
|
|
420
411
|
imageUrl: string;
|
|
421
|
-
title:
|
|
422
|
-
description:
|
|
412
|
+
title: string;
|
|
413
|
+
description: string;
|
|
423
414
|
buttonText: string | false;
|
|
424
415
|
url: string;
|
|
425
416
|
email?: string;
|
|
@@ -449,8 +440,8 @@ export const MEDIA_TYPES = {
|
|
|
449
440
|
export type MediaType = keyof typeof MEDIA_TYPES;
|
|
450
441
|
|
|
451
442
|
export interface TextMediaItem extends ImageItem {
|
|
452
|
-
title:
|
|
453
|
-
paragraph:
|
|
443
|
+
title: string;
|
|
444
|
+
paragraph: string;
|
|
454
445
|
itemsId: number;
|
|
455
446
|
|
|
456
447
|
buttonText?: string | false;
|
|
@@ -474,8 +465,8 @@ export interface TextShapeItem extends TextMediaItem {
|
|
|
474
465
|
export interface ShapeItem extends ImageItem {
|
|
475
466
|
colorReplacements: ColorReplacement[];
|
|
476
467
|
mediaType: MediaType;
|
|
477
|
-
title?:
|
|
478
|
-
paragraph?:
|
|
468
|
+
title?: string;
|
|
469
|
+
paragraph?: string;
|
|
479
470
|
|
|
480
471
|
// overrides for personalisation
|
|
481
472
|
titleOverride?: string;
|
|
@@ -486,9 +477,9 @@ export interface TestimonialItems {
|
|
|
486
477
|
/** Stable per-item identifier. See ImageItem.id. */
|
|
487
478
|
id: string;
|
|
488
479
|
imageUrl: string;
|
|
489
|
-
name:
|
|
490
|
-
position:
|
|
491
|
-
testimonials:
|
|
480
|
+
name: string;
|
|
481
|
+
position: string;
|
|
482
|
+
testimonials: string;
|
|
492
483
|
colorReplacements: ColorReplacement[];
|
|
493
484
|
mediaType: string;
|
|
494
485
|
size?: string;
|
|
@@ -504,7 +495,7 @@ export interface TestimonialItems {
|
|
|
504
495
|
export interface TextItem {
|
|
505
496
|
/** Stable per-item identifier. See ImageItem.id. */
|
|
506
497
|
id: string;
|
|
507
|
-
text:
|
|
498
|
+
text: string;
|
|
508
499
|
textStyle: TextStyleDeclaration;
|
|
509
500
|
|
|
510
501
|
// overrides for personalisation
|
|
@@ -526,8 +517,8 @@ export interface ImagesSectionContent extends Content, MediaSection {
|
|
|
526
517
|
}
|
|
527
518
|
|
|
528
519
|
export interface MediaSection extends Content {
|
|
529
|
-
title:
|
|
530
|
-
description:
|
|
520
|
+
title: string;
|
|
521
|
+
description: string;
|
|
531
522
|
displayStyle: ImagesSectionContentDisplayStyle;
|
|
532
523
|
items: TextShapeItem[] | TextMediaItem[];
|
|
533
524
|
titleStyle: TextStyleDeclaration;
|
|
@@ -546,7 +537,7 @@ export interface ColorReplacement {
|
|
|
546
537
|
|
|
547
538
|
export interface TestimonialSectionContent extends Content {
|
|
548
539
|
type: 'testimonial-section';
|
|
549
|
-
title:
|
|
540
|
+
title: string;
|
|
550
541
|
displayStyle: string;
|
|
551
542
|
items: TestimonialItems[];
|
|
552
543
|
titleStyle: TextStyleDeclaration;
|
|
@@ -561,8 +552,8 @@ export interface TestimonialSectionContent extends Content {
|
|
|
561
552
|
export interface AboutSectionContent extends Content {
|
|
562
553
|
type: 'about-section';
|
|
563
554
|
imageUrl: string;
|
|
564
|
-
title:
|
|
565
|
-
paragraph:
|
|
555
|
+
title: string;
|
|
556
|
+
paragraph: string;
|
|
566
557
|
displayStyle: string;
|
|
567
558
|
titleStyle: TextStyleDeclaration;
|
|
568
559
|
paragraphStyle: TextStyleDeclaration;
|
|
@@ -591,7 +582,7 @@ export type SocialLinksIconDisplayStyle =
|
|
|
591
582
|
|
|
592
583
|
export interface SocialLinksContent extends Content {
|
|
593
584
|
type: 'social-links';
|
|
594
|
-
title:
|
|
585
|
+
title: string;
|
|
595
586
|
links: LinksItem[];
|
|
596
587
|
iconDisplayStyle: SocialLinksIconDisplayStyle;
|
|
597
588
|
iconFillStyles: BrandPageCssStyle;
|
|
@@ -618,8 +609,8 @@ export interface DividerSectionContent extends Content {
|
|
|
618
609
|
|
|
619
610
|
export interface BookingSectionContent extends Content {
|
|
620
611
|
type: 'booking-section';
|
|
621
|
-
title:
|
|
622
|
-
description:
|
|
612
|
+
title: string;
|
|
613
|
+
description: string;
|
|
623
614
|
buttonText: string;
|
|
624
615
|
url: string;
|
|
625
616
|
email?: string;
|
|
@@ -463,7 +463,7 @@ exports[`migration.util > Banner Contents - text Align and Font Family should no
|
|
|
463
463
|
},
|
|
464
464
|
},
|
|
465
465
|
"userPromptContentKey": undefined,
|
|
466
|
-
"v":
|
|
466
|
+
"v": 18,
|
|
467
467
|
},
|
|
468
468
|
"useOldLogoRenderSize": true,
|
|
469
469
|
}
|
|
@@ -916,7 +916,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
|
|
|
916
916
|
},
|
|
917
917
|
},
|
|
918
918
|
"userPromptContentKey": undefined,
|
|
919
|
-
"v":
|
|
919
|
+
"v": 18,
|
|
920
920
|
},
|
|
921
921
|
"useOldLogoRenderSize": true,
|
|
922
922
|
}
|
|
@@ -1376,7 +1376,7 @@ exports[`migration.util > migrateBrandPageModel should return brand page model w
|
|
|
1376
1376
|
},
|
|
1377
1377
|
},
|
|
1378
1378
|
"userPromptContentKey": undefined,
|
|
1379
|
-
"v":
|
|
1379
|
+
"v": 18,
|
|
1380
1380
|
},
|
|
1381
1381
|
"useOldLogoRenderSize": true,
|
|
1382
1382
|
}
|
|
@@ -21,7 +21,6 @@ import { migration15 } from './migration/migration-15';
|
|
|
21
21
|
import { migration16 } from './migration/migration-16';
|
|
22
22
|
import { migration17 } from './migration/migration-17';
|
|
23
23
|
import { migration18 } from './migration/migration-18';
|
|
24
|
-
import { migration19 } from './migration/migration-19';
|
|
25
24
|
|
|
26
25
|
export interface MigrationData {
|
|
27
26
|
brandPageModel: BrandPageModel;
|
|
@@ -168,17 +167,6 @@ const MIGRATION_TABLE = [
|
|
|
168
167
|
description:
|
|
169
168
|
'BP-5603: backfill stable item ids on all nested items (gallery/section/testimonial/text items, section + footer social links) — the item id is now non-nullable',
|
|
170
169
|
},
|
|
171
|
-
{
|
|
172
|
-
version: 19,
|
|
173
|
-
migrateFn: migration19,
|
|
174
|
-
// runAlways: normalise RTE to JSON on every load. Idempotent (only HTML
|
|
175
|
-
// strings are converted; JSON is skipped), so it also catches content the
|
|
176
|
-
// AI section builders still emit as HTML — converting it on the next read
|
|
177
|
-
// without a new migration or changes to those builders.
|
|
178
|
-
runAlways: true,
|
|
179
|
-
description:
|
|
180
|
-
'BP-5787: convert inline-HTML RTE content to Tiptap JSON on load (lazy migrate-on-read for web-renderer; persisted on next maker save; also normalises AI-built HTML)',
|
|
181
|
-
},
|
|
182
170
|
] as MigrationTableItem[];
|
|
183
171
|
|
|
184
172
|
export const GetLatestModelVersion = () =>
|
|
@@ -87,57 +87,6 @@ describe('style.util', () => {
|
|
|
87
87
|
);
|
|
88
88
|
});
|
|
89
89
|
|
|
90
|
-
test('applyLogoThemeToBrandPageModel preserves var() token refs in JSON RTE content (BP-5787)', () => {
|
|
91
|
-
// Arrange — title stored as Tiptap JSON with a theme-token colour mark.
|
|
92
|
-
const logoTheme = logoWithFlatColorsTheme;
|
|
93
|
-
const titleDoc = {
|
|
94
|
-
type: 'doc',
|
|
95
|
-
content: [
|
|
96
|
-
{
|
|
97
|
-
type: 'heading',
|
|
98
|
-
attrs: { level: 2, textAlign: 'center' },
|
|
99
|
-
content: [
|
|
100
|
-
{
|
|
101
|
-
type: 'text',
|
|
102
|
-
marks: [
|
|
103
|
-
{
|
|
104
|
-
type: 'textStyle',
|
|
105
|
-
attrs: { color: `var(--${STYLE_THEME_KEYS.LogoTextColor})` },
|
|
106
|
-
},
|
|
107
|
-
],
|
|
108
|
-
text: 'Themed',
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
};
|
|
114
|
-
const brandPageModel = new BrandPageModel({
|
|
115
|
-
...BRAND_SITE,
|
|
116
|
-
pages: [
|
|
117
|
-
{
|
|
118
|
-
title: '1',
|
|
119
|
-
slug: '1',
|
|
120
|
-
contents: [
|
|
121
|
-
{
|
|
122
|
-
...createInitialBannerSectionContent(),
|
|
123
|
-
title: titleDoc,
|
|
124
|
-
} as any,
|
|
125
|
-
],
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// Act
|
|
131
|
-
const { model } = applyLogoThemeToBrandPageModel(logoTheme, brandPageModel);
|
|
132
|
-
|
|
133
|
-
// Assert — JSON RTE content is an object, so the var() token is NOT
|
|
134
|
-
// flattened to rgb(); it survives so a new theme can still propagate.
|
|
135
|
-
const title = JSON.stringify((model.pages[0].contents[0] as any).title);
|
|
136
|
-
|
|
137
|
-
expect(title).toContain(`var(--${STYLE_THEME_KEYS.LogoTextColor})`);
|
|
138
|
-
expect(title).not.toContain(logoTheme.logoTextColor.rgb);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
90
|
test('applyLogoThemeToBrandPageModel should apply logo theme color to content color when content color has a color matched theme key', () => {
|
|
142
91
|
// Arrange
|
|
143
92
|
const logoTheme = logoWithFlatColorsTheme;
|
|
@@ -1,56 +1,17 @@
|
|
|
1
1
|
import { ESCAPED_NEWLINE_CHARACTER } from '../../constants/ui';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* BP-5787 — a Tiptap JSON document is an object with `type: 'doc'`.
|
|
5
|
-
* Inlined here (rather than importing from brand-page-maker) to avoid a
|
|
6
|
-
* circular import: rich-text-editor.util imports getRenderText from this file.
|
|
7
|
-
*/
|
|
8
|
-
const isTiptapDoc = (value: unknown): value is { type: 'doc' } =>
|
|
9
|
-
typeof value === 'object' &&
|
|
10
|
-
value !== null &&
|
|
11
|
-
(value as { type?: string }).type === 'doc';
|
|
12
|
-
|
|
13
|
-
const jsonDocPlainText = (doc: unknown): string => {
|
|
14
|
-
const parts: string[] = [];
|
|
15
|
-
|
|
16
|
-
const walk = (node: any): void => {
|
|
17
|
-
if (typeof node?.text === 'string') parts.push(node.text);
|
|
18
|
-
|
|
19
|
-
node?.content?.forEach(walk);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
walk(doc);
|
|
23
|
-
|
|
24
|
-
return parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
25
|
-
};
|
|
26
|
-
|
|
27
3
|
export const getRenderText = (text: string): string => {
|
|
28
|
-
// BP-5787: RTE fields may now be Tiptap JSON objects. Non-strings pass
|
|
29
|
-
// through untouched so the renderer (TextWrapper) handles them via its
|
|
30
|
-
// JSON-aware path, instead of throwing on `.replace`.
|
|
31
|
-
if (typeof text !== 'string') {
|
|
32
|
-
return text as unknown as string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
4
|
return text.replace(
|
|
36
5
|
new RegExp(`(${ESCAPED_NEWLINE_CHARACTER}|\\n)`, 'g'),
|
|
37
6
|
'<br/>',
|
|
38
7
|
);
|
|
39
8
|
};
|
|
40
9
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
10
|
+
export const escapeContentText = (
|
|
11
|
+
text: string | undefined,
|
|
12
|
+
): string | undefined => {
|
|
44
13
|
if (!text) {
|
|
45
|
-
return text
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (isTiptapDoc(text)) {
|
|
49
|
-
return jsonDocPlainText(text);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (typeof text !== 'string') {
|
|
53
|
-
return undefined;
|
|
14
|
+
return text;
|
|
54
15
|
}
|
|
55
16
|
|
|
56
17
|
return text
|
|
@@ -235,11 +235,9 @@ const addProfileInfo = (
|
|
|
235
235
|
profile: ProfileViewModel,
|
|
236
236
|
vcardLines: string[],
|
|
237
237
|
): void => {
|
|
238
|
-
const name = sanitizeForVCard(
|
|
239
|
-
const title = sanitizeForVCard(
|
|
240
|
-
const description = sanitizeForVCard(
|
|
241
|
-
getPlainTextFromRTE(profile.description) || '',
|
|
242
|
-
);
|
|
238
|
+
const name = sanitizeForVCard(profile.name || '');
|
|
239
|
+
const title = sanitizeForVCard(profile.title || '');
|
|
240
|
+
const description = sanitizeForVCard(profile.description || '');
|
|
243
241
|
|
|
244
242
|
if (name) {
|
|
245
243
|
const nameParts = name.trim().split(/\s+/);
|
|
@@ -318,7 +316,7 @@ export const saveContact = (brandPageData: {
|
|
|
318
316
|
}): void => {
|
|
319
317
|
const vcard = generateVCard(brandPageData);
|
|
320
318
|
const profile = findProfileContent(brandPageData);
|
|
321
|
-
const filename = generateVCardFilename(
|
|
319
|
+
const filename = generateVCardFilename(profile?.name);
|
|
322
320
|
|
|
323
321
|
downloadVCard(vcard, filename);
|
|
324
322
|
};
|