@designcrowd/library.brand-page 6.0.6-json-prereq-7 → 6.0.6-tracker-1
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-SPVLPi8n.js → CreatedOnBrandCrowd-de-de-DD0OBnij.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-CvtrCPHd.js → CreatedOnBrandCrowd-en-us-DysrpsmM.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-BSsac7jB.js → CreatedOnBrandCrowd-es-es-B6twGr9h.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-BauENV2a.js → CreatedOnBrandCrowd-fr-ca-yxl0ih_4.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-BauENV2a.js → CreatedOnBrandCrowd-fr-fr-yxl0ih_4.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-Be0hawmr.js → CreatedOnBrandCrowd-pt-br-BtXDrIcx.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-Be0hawmr.js → CreatedOnBrandCrowd-pt-pt-BtXDrIcx.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-CTjE2Tmn.js → CreatedOnDcom-de-de-ho_ROy1L.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-DnZRp5Pg.js → CreatedOnDcom-en-us-C91p9kls.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-J-9qvjy8.js → CreatedOnDcom-es-es-B3FYmObE.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-BDNHqm4G.js → CreatedOnDcom-fr-ca-D40m7fPx.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-B_2-ceuO.js → CreatedOnDcom-fr-fr-Cd57oYyc.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-76lJVrAY.js → CreatedOnDcom-pt-br-Du57mWxG.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-TQp3CNcu.js → CreatedOnDcom-pt-pt-fYoPZWwr.js} +1 -1
- 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 +99 -20
- 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 +137 -141
- package/lib/src/{lib-Bh9Y5_-x.js → lib-lMNy66QO.js} +43580 -44184
- 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 -5
- package/lib/src/metadata-builder/helpers/rte.helpers.d.ts +1 -1
- 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/tracker.d.ts +2 -0
- package/lib/src/tracker.js +12 -0
- package/lib/src/tracker.mjs +12 -0
- package/lib/src/useEventTracker-DmQ6AkNn.js +356 -0
- package/lib/src/utils/minify-brand-page-metadata.js +1 -4
- package/package.json +16 -1
- 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 +106 -22
- 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 -6
- 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/tracker.ts +28 -0
- package/src/utils/minify-brand-page-metadata.ts +1 -4
- package/lib/src/brand-page/utils/migration/migration-18.d.ts +0 -2
- package/lib/src/brand-page/utils/migration/migration-18.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-18.test.ts +0 -72
- package/src/brand-page/utils/migration/migration-18.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-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
|
@@ -8,7 +8,18 @@ import {
|
|
|
8
8
|
watch,
|
|
9
9
|
} from 'vue';
|
|
10
10
|
import { Editor } from '@tiptap/vue-3';
|
|
11
|
-
import
|
|
11
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
12
|
+
import Underline from '@tiptap/extension-underline';
|
|
13
|
+
import TextStyle from '@tiptap/extension-text-style';
|
|
14
|
+
import TextAlign from '@tiptap/extension-text-align';
|
|
15
|
+
import FontFamily from '@tiptap/extension-font-family';
|
|
16
|
+
import ColorExtension from '@tiptap/extension-color';
|
|
17
|
+
import { FontSize } from './extensions/FontSize';
|
|
18
|
+
import { LetterSpacing } from './extensions/LetterSpacing';
|
|
19
|
+
import { LineHeight } from './extensions/LineHeight';
|
|
20
|
+
import Highlight from '@tiptap/extension-highlight';
|
|
21
|
+
import Placeholder from '@tiptap/extension-placeholder';
|
|
22
|
+
import Link from '@tiptap/extension-link';
|
|
12
23
|
import type { StyleTextFontFamilyOption } from '@shared/style-settings/models';
|
|
13
24
|
import { createFormattingActions } from './formatting';
|
|
14
25
|
import { useTextAlign } from './textAlign';
|
|
@@ -20,7 +31,7 @@ import {
|
|
|
20
31
|
emToPx,
|
|
21
32
|
isStyleChanged,
|
|
22
33
|
pxToEm,
|
|
23
|
-
|
|
34
|
+
sanitiseClientSideRTE,
|
|
24
35
|
skipStyleDetection,
|
|
25
36
|
} from '@brand-page-maker/utils';
|
|
26
37
|
import type { HyperlinkType, TextStyles } from '@brand-page';
|
|
@@ -38,7 +49,9 @@ import {
|
|
|
38
49
|
import { useUiContentSettingsStore } from '@shared/content-settings/store';
|
|
39
50
|
import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
|
|
40
51
|
import { AllSelection } from 'prosemirror-state';
|
|
41
|
-
import {
|
|
52
|
+
import { MaxLength } from './extensions/MaxLength';
|
|
53
|
+
import { AutoLink } from './extensions/AutoLink';
|
|
54
|
+
import { isHrefPageId, useHyperlink } from './hyperlink';
|
|
42
55
|
import { useBubbleMenu } from './bubbleMenu';
|
|
43
56
|
import {
|
|
44
57
|
hasLogoColorVar,
|
|
@@ -116,7 +129,44 @@ export const useExternalRichTextEditor = (): {
|
|
|
116
129
|
} => {
|
|
117
130
|
const externalEditor = new Editor({
|
|
118
131
|
editable: false,
|
|
119
|
-
extensions:
|
|
132
|
+
extensions: [
|
|
133
|
+
StarterKit,
|
|
134
|
+
Underline,
|
|
135
|
+
TextStyle.configure({ mergeNestedSpanStyles: true }),
|
|
136
|
+
TextAlign.configure({
|
|
137
|
+
types: ['heading', 'paragraph'],
|
|
138
|
+
}),
|
|
139
|
+
FontFamily.configure({
|
|
140
|
+
types: ['textStyle'],
|
|
141
|
+
}),
|
|
142
|
+
FontSize,
|
|
143
|
+
LetterSpacing,
|
|
144
|
+
LineHeight,
|
|
145
|
+
ColorExtension.configure({
|
|
146
|
+
types: ['textStyle'],
|
|
147
|
+
}),
|
|
148
|
+
Highlight.configure({
|
|
149
|
+
multicolor: true,
|
|
150
|
+
}),
|
|
151
|
+
Link.configure({
|
|
152
|
+
openOnClick: false,
|
|
153
|
+
defaultProtocol: 'https',
|
|
154
|
+
autolink: true,
|
|
155
|
+
HTMLAttributes: (attrs) => {
|
|
156
|
+
const href = attrs.href;
|
|
157
|
+
const isExternal =
|
|
158
|
+
href && (href.startsWith('http://') || href.startsWith('https://'));
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
rel: 'noopener noreferrer',
|
|
162
|
+
target: isExternal ? '_blank' : null,
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
isAllowedUri: (url, ctx) =>
|
|
166
|
+
ctx.defaultValidate(url) || isHrefPageId(url),
|
|
167
|
+
}),
|
|
168
|
+
AutoLink,
|
|
169
|
+
],
|
|
120
170
|
});
|
|
121
171
|
|
|
122
172
|
return {
|
|
@@ -168,13 +218,54 @@ export const useRichTextEditor = (
|
|
|
168
218
|
const editor = new Editor({
|
|
169
219
|
content: model.value ?? '',
|
|
170
220
|
editable: !props.readOnly,
|
|
171
|
-
extensions:
|
|
172
|
-
|
|
173
|
-
|
|
221
|
+
extensions: [
|
|
222
|
+
StarterKit.configure({
|
|
223
|
+
bulletList: false,
|
|
224
|
+
orderedList: false,
|
|
225
|
+
listItem: false,
|
|
226
|
+
horizontalRule: false,
|
|
227
|
+
codeBlock: false,
|
|
228
|
+
code: false,
|
|
229
|
+
}),
|
|
230
|
+
Underline,
|
|
231
|
+
TextStyle.configure({ mergeNestedSpanStyles: true }),
|
|
232
|
+
TextAlign.configure({
|
|
233
|
+
types: ['heading', 'paragraph'],
|
|
234
|
+
}),
|
|
235
|
+
FontFamily.configure({
|
|
236
|
+
types: ['textStyle'],
|
|
237
|
+
}),
|
|
238
|
+
FontSize,
|
|
239
|
+
LetterSpacing,
|
|
240
|
+
LineHeight,
|
|
241
|
+
ColorExtension.configure({
|
|
242
|
+
types: ['textStyle'],
|
|
243
|
+
}),
|
|
244
|
+
Highlight.configure({
|
|
245
|
+
multicolor: true,
|
|
246
|
+
}),
|
|
247
|
+
Placeholder.configure({
|
|
174
248
|
placeholder: props.placeholder,
|
|
175
|
-
|
|
176
|
-
},
|
|
177
|
-
|
|
249
|
+
}),
|
|
250
|
+
MaxLength.configure({ maxLength: props.maxLength }),
|
|
251
|
+
Link.configure({
|
|
252
|
+
openOnClick: false,
|
|
253
|
+
defaultProtocol: 'https',
|
|
254
|
+
autolink: true,
|
|
255
|
+
HTMLAttributes: (attrs) => {
|
|
256
|
+
const href = attrs.href;
|
|
257
|
+
const isExternal =
|
|
258
|
+
href && (href.startsWith('http://') || href.startsWith('https://'));
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
target: isExternal ? '_blank' : null,
|
|
262
|
+
};
|
|
263
|
+
},
|
|
264
|
+
isAllowedUri: (url, ctx) =>
|
|
265
|
+
ctx.defaultValidate(url) || isHrefPageId(url),
|
|
266
|
+
}),
|
|
267
|
+
AutoLink,
|
|
268
|
+
],
|
|
178
269
|
editorProps: {
|
|
179
270
|
handleDOMEvents: {
|
|
180
271
|
drop(view, event: DragEvent) {
|
|
@@ -385,13 +476,7 @@ export const useRichTextEditor = (
|
|
|
385
476
|
|
|
386
477
|
const isExternalMutation = updateRichTextEditor.value || appliedSinceLast;
|
|
387
478
|
|
|
388
|
-
|
|
389
|
-
// seen as "changed" against the editor's HTML (which would re-run
|
|
390
|
-
// setContent and reset the user's just-applied styles).
|
|
391
|
-
const hasChanged =
|
|
392
|
-
JSON.stringify(editor.getJSON()) !== JSON.stringify(newValue);
|
|
393
|
-
|
|
394
|
-
if (isExternalMutation && hasChanged) {
|
|
479
|
+
if (isExternalMutation && editor.getHTML() !== newValue) {
|
|
395
480
|
editor.commands.setContent(newValue, false);
|
|
396
481
|
updateRichTextEditor.value = false;
|
|
397
482
|
//text style type is not switching when reordering
|
|
@@ -414,12 +499,11 @@ export const useRichTextEditor = (
|
|
|
414
499
|
}
|
|
415
500
|
});
|
|
416
501
|
|
|
417
|
-
// Update model when editor content changes
|
|
418
|
-
// JSON (BP-5787). getJSON() is already in hand (the editor holds the doc) and
|
|
419
|
-
// is sanitised natively — no getHTML(), no DOMPurify, no extra editor — so
|
|
420
|
-
// the per-keystroke path allocates nothing heavy.
|
|
502
|
+
// Update model when editor content changes
|
|
421
503
|
editor.on('update', () => {
|
|
422
|
-
|
|
504
|
+
const sanitizedData = sanitiseClientSideRTE(editor.getHTML());
|
|
505
|
+
|
|
506
|
+
emit('update:modelValue', sanitizedData.sanitizedHtml);
|
|
423
507
|
});
|
|
424
508
|
|
|
425
509
|
editor.on('selectionUpdate', () => {
|
|
@@ -7,8 +7,6 @@ export * from './url.util';
|
|
|
7
7
|
export * from './slug.util';
|
|
8
8
|
export * from './text.util';
|
|
9
9
|
export * from './rich-text-editor.util';
|
|
10
|
-
export * from './rte-render.util';
|
|
11
|
-
export * from './rte-sanitize.util';
|
|
12
10
|
|
|
13
11
|
export const isColorMatched = (color: string | null | undefined): boolean => {
|
|
14
12
|
if (!color) return false;
|
|
@@ -76,7 +76,7 @@ describe('hasHTML', () => {
|
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
describe('getUpdatedHTMLFromEditor', () => {
|
|
79
|
-
it('applies style commands and returns updated
|
|
79
|
+
it('applies style commands and returns updated HTML', () => {
|
|
80
80
|
// Mock editor
|
|
81
81
|
const runMock = vi.fn();
|
|
82
82
|
const chain = {
|
|
@@ -119,7 +119,7 @@ describe('getUpdatedHTMLFromEditor', () => {
|
|
|
119
119
|
},
|
|
120
120
|
},
|
|
121
121
|
chain: () => chain as any,
|
|
122
|
-
|
|
122
|
+
getHTML: () => '<div>styled</div>',
|
|
123
123
|
} as unknown as Editor;
|
|
124
124
|
|
|
125
125
|
const newStyle = {
|
|
@@ -137,8 +137,7 @@ describe('getUpdatedHTMLFromEditor', () => {
|
|
|
137
137
|
|
|
138
138
|
expect(externalEditor.commands.setContent).toHaveBeenCalledWith('foo');
|
|
139
139
|
expect(runMock).toHaveBeenCalled();
|
|
140
|
-
|
|
141
|
-
expect(result).toEqual({ type: 'doc', content: [{ type: 'paragraph' }] });
|
|
140
|
+
expect(result).toBe('<div>styled</div>');
|
|
142
141
|
});
|
|
143
142
|
});
|
|
144
143
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChainedCommands, Editor
|
|
1
|
+
import type { ChainedCommands, Editor } from '@tiptap/vue-3';
|
|
2
2
|
import type { EditorState, Transaction, Selection } from 'prosemirror-state';
|
|
3
3
|
import {
|
|
4
4
|
STYLE_THEME_KEYS,
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
type RequestPaymentContent,
|
|
20
20
|
type SupportMeContent,
|
|
21
21
|
type ShopContent,
|
|
22
|
-
type RichTextValue,
|
|
23
22
|
} from '@brand-page';
|
|
24
23
|
import type { PageSettingsState } from '@shared/page-settings/store';
|
|
25
24
|
import type { StyleSettingsState } from '@shared/style-settings/store';
|
|
@@ -244,38 +243,8 @@ export const skipStyleDetection = (chain: ChainedCommands, marks?: Mark[]) =>
|
|
|
244
243
|
return true;
|
|
245
244
|
});
|
|
246
245
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
* document is an object with `type: 'doc'`.
|
|
250
|
-
*/
|
|
251
|
-
export const isTiptapDocument = (value: unknown): value is JSONContent =>
|
|
252
|
-
typeof value === 'object' &&
|
|
253
|
-
value !== null &&
|
|
254
|
-
(value as JSONContent).type === 'doc';
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Extract visible plain text from a Tiptap JSON document (concatenated text
|
|
258
|
-
* nodes). Dependency-free — no editor instantiation.
|
|
259
|
-
*/
|
|
260
|
-
export const getRteJsonPlainText = (doc: JSONContent): string => {
|
|
261
|
-
const parts: string[] = [];
|
|
262
|
-
|
|
263
|
-
const walk = (node: JSONContent): void => {
|
|
264
|
-
if (typeof node.text === 'string') parts.push(node.text);
|
|
265
|
-
|
|
266
|
-
node.content?.forEach(walk);
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
walk(doc);
|
|
270
|
-
|
|
271
|
-
return parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
// Accepts a legacy inline-HTML string or a Tiptap JSON document (BP-5787).
|
|
275
|
-
export const hasRTEContent = (content: unknown): boolean => {
|
|
276
|
-
if (isTiptapDocument(content)) return true;
|
|
277
|
-
|
|
278
|
-
if (typeof content !== 'string' || content.length === 0) return false;
|
|
246
|
+
export const hasRTEContent = (content: string): boolean => {
|
|
247
|
+
if (!content || content.length === 0) return false;
|
|
279
248
|
|
|
280
249
|
const trimmed = content.trim();
|
|
281
250
|
const tagWrapRegex = /^<([A-Za-z][A-Za-z0-9-]*)\b[^>]*>[\s\S]*<\/\1>$/;
|
|
@@ -283,11 +252,7 @@ export const hasRTEContent = (content: unknown): boolean => {
|
|
|
283
252
|
return tagWrapRegex.test(trimmed);
|
|
284
253
|
};
|
|
285
254
|
|
|
286
|
-
export const getPlainTextFromRTE = (content:
|
|
287
|
-
if (isTiptapDocument(content)) {
|
|
288
|
-
return getRteJsonPlainText(content);
|
|
289
|
-
}
|
|
290
|
-
|
|
255
|
+
export const getPlainTextFromRTE = (content: string): string => {
|
|
291
256
|
if (typeof content !== 'string') {
|
|
292
257
|
return '';
|
|
293
258
|
}
|
|
@@ -756,20 +721,16 @@ export const switchAllContentToRTE = (
|
|
|
756
721
|
|
|
757
722
|
export const getUpdatedHTMLFromEditor = (
|
|
758
723
|
externalEditor: Editor,
|
|
759
|
-
contentText:
|
|
724
|
+
contentText: string,
|
|
760
725
|
newContentStyle: TextStyleDeclaration,
|
|
761
726
|
) => {
|
|
762
727
|
if (!contentText) {
|
|
763
728
|
return contentText;
|
|
764
729
|
}
|
|
765
730
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
typeof contentText === 'string' &&
|
|
770
|
-
contentText.includes(ESCAPED_NEWLINE_CHARACTER)
|
|
771
|
-
? getRenderText(contentText)
|
|
772
|
-
: contentText;
|
|
731
|
+
const content = contentText.includes(ESCAPED_NEWLINE_CHARACTER)
|
|
732
|
+
? getRenderText(contentText)
|
|
733
|
+
: contentText;
|
|
773
734
|
|
|
774
735
|
externalEditor.commands.setContent(content);
|
|
775
736
|
|
|
@@ -827,9 +788,9 @@ export const getUpdatedHTMLFromEditor = (
|
|
|
827
788
|
chain = newContentStyle?.fontStyle ? chain.setItalic() : chain.unsetItalic();
|
|
828
789
|
|
|
829
790
|
chain.run();
|
|
791
|
+
const html = externalEditor.getHTML();
|
|
830
792
|
|
|
831
|
-
|
|
832
|
-
return externalEditor.getJSON();
|
|
793
|
+
return html;
|
|
833
794
|
};
|
|
834
795
|
|
|
835
796
|
// Helper to build stored marks from a style declaration
|
|
@@ -953,7 +914,7 @@ export const sanitiseClientSideRTE = (html: string): SanitizationResult => {
|
|
|
953
914
|
/**
|
|
954
915
|
* Validates if an href value is safe and returns a sanitized version
|
|
955
916
|
*/
|
|
956
|
-
|
|
917
|
+
const validateAndSanitizeHref = (href: string): string | null => {
|
|
957
918
|
if (!href || typeof href !== 'string') return null;
|
|
958
919
|
|
|
959
920
|
// Trim whitespace and decode entities
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isURL, isEmail, isTel } from '@utils/validator.util';
|
|
2
2
|
import type { ItemFieldType, Validator } from '@brand-page-maker/models';
|
|
3
|
-
import { YOUTUBE_URL_RE
|
|
3
|
+
import { YOUTUBE_URL_RE } from '@brand-page-maker/utils';
|
|
4
4
|
import { validatorTr } from '@src/composables/useTranslate';
|
|
5
5
|
|
|
6
6
|
const addressValueValidator: Validator = {
|
|
@@ -76,15 +76,7 @@ export const dummyValidator: Validator = {
|
|
|
76
76
|
|
|
77
77
|
const nonEmptyStringValidator: Validator = {
|
|
78
78
|
validate: (name, value: any) => {
|
|
79
|
-
|
|
80
|
-
// An RTE field's stored value used to be inline HTML — an empty editor was
|
|
81
|
-
// "<p></p>", a NON-empty string, so the required check always passed for RTE
|
|
82
|
-
// fields (it only ever failed for genuinely empty PLAIN inputs). Now RTE
|
|
83
|
-
// fields are Tiptap JSON docs; treat any doc as "present" so behaviour
|
|
84
|
-
// stays identical. A plain string is still required to be non-empty.
|
|
85
|
-
const isValid = isTiptapDocument(value)
|
|
86
|
-
? true
|
|
87
|
-
: typeof value === 'string' && value.length > 0;
|
|
79
|
+
const isValid = typeof value === 'string' && value.length > 0;
|
|
88
80
|
|
|
89
81
|
return {
|
|
90
82
|
isValid,
|
package/src/lib.ts
CHANGED
|
@@ -85,15 +85,6 @@ export {
|
|
|
85
85
|
recalculateHeaderLogoSizes,
|
|
86
86
|
} from './brand-page/utils/logo.util';
|
|
87
87
|
export { sanitizeRTEHtml } from './brand-page-maker/utils';
|
|
88
|
-
// BP-5787: RTE JSON storage boundary helpers (render/normalise/sanitise/extract).
|
|
89
|
-
export {
|
|
90
|
-
renderRteToHtml,
|
|
91
|
-
toRteJson,
|
|
92
|
-
isTiptapDocument,
|
|
93
|
-
sanitizeRteJson,
|
|
94
|
-
getRteJsonPlainText,
|
|
95
|
-
type RichTextValue,
|
|
96
|
-
} from './brand-page-maker/utils';
|
|
97
88
|
export { getPageSlugFromPageTitle } from './brand-page-maker/utils/slug.util';
|
|
98
89
|
|
|
99
90
|
// AI-pipeline preview (longclaw per-turn snapshots): web-renderer's
|
|
@@ -6,36 +6,7 @@ import {
|
|
|
6
6
|
type BuilderResult,
|
|
7
7
|
type MetadataBuilderRequest,
|
|
8
8
|
} from '../models';
|
|
9
|
-
import {
|
|
10
|
-
hasRTEContent,
|
|
11
|
-
replaceTextInRTE,
|
|
12
|
-
isTiptapDocument,
|
|
13
|
-
renderRteToHtml,
|
|
14
|
-
toRteJson,
|
|
15
|
-
} from '../../brand-page-maker/utils';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* BP-5787 — replace the text of an RTE field while preserving formatting, for
|
|
19
|
-
* either storage form. JSON is rendered to HTML, edited via the existing
|
|
20
|
-
* HTML-preserving replace, then converted back to JSON; plain/legacy strings
|
|
21
|
-
* keep the original behaviour.
|
|
22
|
-
*/
|
|
23
|
-
const updateRteFieldValue = (existing: unknown, newText: string): unknown => {
|
|
24
|
-
const wasJson = isTiptapDocument(existing);
|
|
25
|
-
const existingHtml = wasJson ? renderRteToHtml(existing) : existing;
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
existingHtml &&
|
|
29
|
-
typeof existingHtml === 'string' &&
|
|
30
|
-
hasRTEContent(existingHtml)
|
|
31
|
-
) {
|
|
32
|
-
const updatedHtml = replaceTextInRTE(existingHtml, newText);
|
|
33
|
-
|
|
34
|
-
return wasJson ? toRteJson(updatedHtml) : updatedHtml;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return newText;
|
|
38
|
-
};
|
|
9
|
+
import { hasRTEContent, replaceTextInRTE } from '../../brand-page-maker/utils';
|
|
39
10
|
|
|
40
11
|
export interface UpdateTextPayload {
|
|
41
12
|
contentId: string;
|
|
@@ -65,7 +36,12 @@ const updateTextField = (target: any, fieldName: string, newText: string) => {
|
|
|
65
36
|
|
|
66
37
|
if (Array.isArray(array) && array[+index]) {
|
|
67
38
|
const existingText = array[+index][property];
|
|
68
|
-
const updatedValue =
|
|
39
|
+
const updatedValue =
|
|
40
|
+
existingText &&
|
|
41
|
+
typeof existingText === 'string' &&
|
|
42
|
+
hasRTEContent(existingText)
|
|
43
|
+
? replaceTextInRTE(existingText, newText)
|
|
44
|
+
: newText;
|
|
69
45
|
|
|
70
46
|
// Create a new array with the updated item to trigger reactivity
|
|
71
47
|
const newArray = [...array];
|
|
@@ -78,7 +54,17 @@ const updateTextField = (target: any, fieldName: string, newText: string) => {
|
|
|
78
54
|
}
|
|
79
55
|
} else {
|
|
80
56
|
// Simple field
|
|
81
|
-
|
|
57
|
+
const existingText = target[fieldName];
|
|
58
|
+
|
|
59
|
+
if (
|
|
60
|
+
existingText &&
|
|
61
|
+
typeof existingText === 'string' &&
|
|
62
|
+
hasRTEContent(existingText)
|
|
63
|
+
) {
|
|
64
|
+
target[fieldName] = replaceTextInRTE(existingText, newText);
|
|
65
|
+
} else {
|
|
66
|
+
target[fieldName] = newText;
|
|
67
|
+
}
|
|
82
68
|
}
|
|
83
69
|
};
|
|
84
70
|
|
|
@@ -68,19 +68,17 @@ const mergeStyles = (
|
|
|
68
68
|
return merged;
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const updateRTEText = (text: unknown, style: TextStyleDeclaration): unknown => {
|
|
74
|
-
if (!hasRTEContent(text)) {
|
|
71
|
+
const updateRTEText = (text: string, style: TextStyleDeclaration): string => {
|
|
72
|
+
if (!text || typeof text !== 'string' || !hasRTEContent(text)) {
|
|
75
73
|
return text;
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
const { externalEditor } = useExternalRichTextEditor();
|
|
79
|
-
const
|
|
77
|
+
const updatedText = applyStyleToRTEText(externalEditor, text, style);
|
|
80
78
|
|
|
81
79
|
externalEditor.destroy();
|
|
82
80
|
|
|
83
|
-
return
|
|
81
|
+
return updatedText;
|
|
84
82
|
};
|
|
85
83
|
|
|
86
84
|
const findStyleKey = (target: Content, property: string): string | null => {
|
|
@@ -11,23 +11,18 @@ import {
|
|
|
11
11
|
|
|
12
12
|
export const applyStyleToRTEText = (
|
|
13
13
|
externalEditor: Editor,
|
|
14
|
-
contentText:
|
|
14
|
+
contentText: string,
|
|
15
15
|
newContentStyle: TextStyleDeclaration,
|
|
16
16
|
) => {
|
|
17
17
|
if (!contentText) {
|
|
18
18
|
return contentText;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const content =
|
|
25
|
-
typeof contentText === 'string' &&
|
|
26
|
-
contentText.includes(ESCAPED_NEWLINE_CHARACTER)
|
|
27
|
-
? getRenderText(contentText)
|
|
28
|
-
: contentText;
|
|
21
|
+
const content = contentText.includes(ESCAPED_NEWLINE_CHARACTER)
|
|
22
|
+
? getRenderText(contentText)
|
|
23
|
+
: contentText;
|
|
29
24
|
|
|
30
|
-
externalEditor.commands.setContent(content
|
|
25
|
+
externalEditor.commands.setContent(content);
|
|
31
26
|
|
|
32
27
|
let chain = externalEditor.chain().selectAll();
|
|
33
28
|
|
|
@@ -82,8 +77,9 @@ export const applyStyleToRTEText = (
|
|
|
82
77
|
: chain.unsetItalic();
|
|
83
78
|
|
|
84
79
|
chain.run();
|
|
80
|
+
const html = externalEditor.getHTML();
|
|
85
81
|
|
|
86
|
-
return
|
|
82
|
+
return html;
|
|
87
83
|
};
|
|
88
84
|
|
|
89
85
|
/**
|
|
@@ -159,23 +159,7 @@ export default defineComponent({
|
|
|
159
159
|
},
|
|
160
160
|
},
|
|
161
161
|
watch: {
|
|
162
|
-
value(newVal: string | undefined
|
|
163
|
-
// BP-5787: RTE values are Tiptap JSON objects. The parent (TextItemsField)
|
|
164
|
-
// re-clones its items array (cloneDeep) on ANY change — including a sibling
|
|
165
|
-
// field's style sync — yielding a NEW object reference with identical text
|
|
166
|
-
// content. For objects Vue fires this watcher on reference change, so that
|
|
167
|
-
// spurious re-clone would overwrite `_value` and clobber an in-flight edit
|
|
168
|
-
// (a mark applied but not yet debounced to the store), silently dropping
|
|
169
|
-
// the user's formatting. Strings never hit this (same content = same
|
|
170
|
-
// reference = no fire), so mirror that: ignore re-clones whose content is
|
|
171
|
-
// unchanged from the previous prop value.
|
|
172
|
-
if (
|
|
173
|
-
this.showRichTextEditor &&
|
|
174
|
-
JSON.stringify(newVal) === JSON.stringify(oldVal)
|
|
175
|
-
) {
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
|
|
162
|
+
value(newVal: string | undefined) {
|
|
179
163
|
this._value = newVal;
|
|
180
164
|
|
|
181
165
|
if (this.showRichTextEditor) {
|
|
@@ -21,9 +21,6 @@ import {
|
|
|
21
21
|
replaceTextInRTE,
|
|
22
22
|
RTE_ALLOWED_SECTION_TYPES,
|
|
23
23
|
isRTEManagedStyleField,
|
|
24
|
-
isTiptapDocument,
|
|
25
|
-
renderRteToHtml,
|
|
26
|
-
toRteJson,
|
|
27
24
|
} from '@brand-page-maker/utils';
|
|
28
25
|
import { isDefaultContentText } from '@shared/content-settings/utils';
|
|
29
26
|
import { useUiLogoSettingsStore } from '../../../logo-settings/store';
|
|
@@ -324,20 +321,10 @@ export default defineComponent({
|
|
|
324
321
|
this.isToggled = isToggled;
|
|
325
322
|
},
|
|
326
323
|
onUseGeneratedText(generatedText: string) {
|
|
327
|
-
let formattedText
|
|
324
|
+
let formattedText = generatedText;
|
|
328
325
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
// (cold AI path, shared editor); string fields use it directly.
|
|
332
|
-
if (this.showRichTextEditor) {
|
|
333
|
-
if (isTiptapDocument(this.value)) {
|
|
334
|
-
formattedText =
|
|
335
|
-
toRteJson(
|
|
336
|
-
replaceTextInRTE(renderRteToHtml(this.value), generatedText),
|
|
337
|
-
) ?? generatedText;
|
|
338
|
-
} else if (typeof this.value === 'string') {
|
|
339
|
-
formattedText = replaceTextInRTE(this.value, generatedText);
|
|
340
|
-
}
|
|
326
|
+
if (this.showRichTextEditor && typeof this.value === 'string') {
|
|
327
|
+
formattedText = replaceTextInRTE(this.value, generatedText);
|
|
341
328
|
}
|
|
342
329
|
|
|
343
330
|
this.contentSettingsStore.updateItemValue({
|
package/src/tracker.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Lightweight subpath entry: `@designcrowd/library.brand-page/tracker`.
|
|
2
|
+
// Re-exports ONLY the analytics surface (useEventTracker + event constants),
|
|
3
|
+
// which depends solely on `vue`. This lets consumers import the tracker without
|
|
4
|
+
// pulling the full library graph (maker, TipTap, renderer). See
|
|
5
|
+
// docs/plans/multi-entrypoint-tree-shaking.md and the /tracker proposal.
|
|
6
|
+
//
|
|
7
|
+
// IMPORTANT: useEventTracker holds module-scoped pub/sub state (LISTENERS,
|
|
8
|
+
// GLOBAL_EVENT_PROPS). It MUST resolve to a single shared instance across this
|
|
9
|
+
// entry and the root `.` entry, or subscribers registered here won't receive
|
|
10
|
+
// events published via the root. The multi-entry build hoists it into a shared
|
|
11
|
+
// chunk (both `index` and `tracker` import it) — verified at build time.
|
|
12
|
+
export {
|
|
13
|
+
useEventTracker,
|
|
14
|
+
EVENTS,
|
|
15
|
+
SELECT_LOCATIONS,
|
|
16
|
+
UPGRADE_CTA_LOCATIONS,
|
|
17
|
+
AI_CTA_LOCATIONS,
|
|
18
|
+
TRIGGER_TYPE,
|
|
19
|
+
PUBLISH_MODAL_CONTINUATION_EVENTS,
|
|
20
|
+
GENERATE_TEXT_CONTINUATION_EVENTS,
|
|
21
|
+
PUBLISH_OPTION_MODAL_CONTINUATION_EVENTS,
|
|
22
|
+
} from './brand-page-maker/composables/useEventTracker';
|
|
23
|
+
export type {
|
|
24
|
+
EventTracker,
|
|
25
|
+
BpEventTrackerEvent,
|
|
26
|
+
BpGlobalEventProps,
|
|
27
|
+
BpUpsellEventProps,
|
|
28
|
+
} from './brand-page-maker/composables/useEventTracker';
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
} from '../brand-page/models';
|
|
11
11
|
import type { Content, ContentType } from '../brand-page/models/content.model';
|
|
12
12
|
import type { TextStyleDeclaration } from '../brand-page/models/styles.model';
|
|
13
|
-
import { hasRTEContent
|
|
13
|
+
import { hasRTEContent } from '../brand-page-maker/utils';
|
|
14
14
|
import { useExternalRichTextEditor } from '../brand-page-maker/composables/rich-text-editor/useRichTextEditor';
|
|
15
15
|
import type { Editor, JSONContent } from '@tiptap/vue-3';
|
|
16
16
|
import {
|
|
@@ -101,9 +101,6 @@ const convertText = (
|
|
|
101
101
|
value: unknown,
|
|
102
102
|
editor: Editor,
|
|
103
103
|
): RichTextValue | undefined => {
|
|
104
|
-
// Already Tiptap JSON (BP-5787 storage) — keep as-is.
|
|
105
|
-
if (isTiptapDocument(value)) return value;
|
|
106
|
-
|
|
107
104
|
if (typeof value !== 'string' || !value) return undefined;
|
|
108
105
|
|
|
109
106
|
if (hasRTEContent(value)) {
|