@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
|
@@ -1,25 +1,72 @@
|
|
|
1
1
|
import { onBeforeUnmount, computed, watch, } from 'vue';
|
|
2
2
|
import { Editor } from '@tiptap/vue-3';
|
|
3
|
-
import
|
|
3
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
4
|
+
import Underline from '@tiptap/extension-underline';
|
|
5
|
+
import TextStyle from '@tiptap/extension-text-style';
|
|
6
|
+
import TextAlign from '@tiptap/extension-text-align';
|
|
7
|
+
import FontFamily from '@tiptap/extension-font-family';
|
|
8
|
+
import ColorExtension from '@tiptap/extension-color';
|
|
9
|
+
import { FontSize } from './extensions/FontSize';
|
|
10
|
+
import { LetterSpacing } from './extensions/LetterSpacing';
|
|
11
|
+
import { LineHeight } from './extensions/LineHeight';
|
|
12
|
+
import Highlight from '@tiptap/extension-highlight';
|
|
13
|
+
import Placeholder from '@tiptap/extension-placeholder';
|
|
14
|
+
import Link from '@tiptap/extension-link';
|
|
4
15
|
import { createFormattingActions } from './formatting';
|
|
5
16
|
import { useTextAlign } from './textAlign';
|
|
6
17
|
import { useFont } from './font';
|
|
7
18
|
import { useSpacing } from './spacing';
|
|
8
19
|
import Color from 'color';
|
|
9
|
-
import { buildInitialMarks, emToPx, isStyleChanged, pxToEm,
|
|
20
|
+
import { buildInitialMarks, emToPx, isStyleChanged, pxToEm, sanitiseClientSideRTE, skipStyleDetection, } from '@brand-page-maker/utils';
|
|
10
21
|
import { HYPERLINK_TYPE, } from '@brand-page';
|
|
11
22
|
import { letterSpacingValueConvertor, colorConvertor, backgroundColorConvertor, } from '@brand-page-maker/convertors';
|
|
12
23
|
import { useUiContentSettingsStore } from '@shared/content-settings/store';
|
|
13
24
|
import { useHistoryStore } from '@brand-page-maker/stores/history/history-store';
|
|
14
25
|
import { AllSelection } from 'prosemirror-state';
|
|
15
|
-
import {
|
|
26
|
+
import { MaxLength } from './extensions/MaxLength';
|
|
27
|
+
import { AutoLink } from './extensions/AutoLink';
|
|
28
|
+
import { isHrefPageId, useHyperlink } from './hyperlink';
|
|
16
29
|
import { useBubbleMenu } from './bubbleMenu';
|
|
17
30
|
import { hasLogoColorVar, isLogoColor, getRTEColor, } from '@brand-page-maker/utils/rich-text-editor.util';
|
|
18
31
|
import { useEventTracker, EVENTS } from '../useEventTracker';
|
|
19
32
|
export const useExternalRichTextEditor = () => {
|
|
20
33
|
const externalEditor = new Editor({
|
|
21
34
|
editable: false,
|
|
22
|
-
extensions:
|
|
35
|
+
extensions: [
|
|
36
|
+
StarterKit,
|
|
37
|
+
Underline,
|
|
38
|
+
TextStyle.configure({ mergeNestedSpanStyles: true }),
|
|
39
|
+
TextAlign.configure({
|
|
40
|
+
types: ['heading', 'paragraph'],
|
|
41
|
+
}),
|
|
42
|
+
FontFamily.configure({
|
|
43
|
+
types: ['textStyle'],
|
|
44
|
+
}),
|
|
45
|
+
FontSize,
|
|
46
|
+
LetterSpacing,
|
|
47
|
+
LineHeight,
|
|
48
|
+
ColorExtension.configure({
|
|
49
|
+
types: ['textStyle'],
|
|
50
|
+
}),
|
|
51
|
+
Highlight.configure({
|
|
52
|
+
multicolor: true,
|
|
53
|
+
}),
|
|
54
|
+
Link.configure({
|
|
55
|
+
openOnClick: false,
|
|
56
|
+
defaultProtocol: 'https',
|
|
57
|
+
autolink: true,
|
|
58
|
+
HTMLAttributes: (attrs) => {
|
|
59
|
+
const href = attrs.href;
|
|
60
|
+
const isExternal = href && (href.startsWith('http://') || href.startsWith('https://'));
|
|
61
|
+
return {
|
|
62
|
+
rel: 'noopener noreferrer',
|
|
63
|
+
target: isExternal ? '_blank' : null,
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
isAllowedUri: (url, ctx) => ctx.defaultValidate(url) || isHrefPageId(url),
|
|
67
|
+
}),
|
|
68
|
+
AutoLink,
|
|
69
|
+
],
|
|
23
70
|
});
|
|
24
71
|
return {
|
|
25
72
|
externalEditor,
|
|
@@ -30,13 +77,51 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
|
|
|
30
77
|
const editor = new Editor({
|
|
31
78
|
content: model.value ?? '',
|
|
32
79
|
editable: !props.readOnly,
|
|
33
|
-
extensions:
|
|
34
|
-
|
|
35
|
-
|
|
80
|
+
extensions: [
|
|
81
|
+
StarterKit.configure({
|
|
82
|
+
bulletList: false,
|
|
83
|
+
orderedList: false,
|
|
84
|
+
listItem: false,
|
|
85
|
+
horizontalRule: false,
|
|
86
|
+
codeBlock: false,
|
|
87
|
+
code: false,
|
|
88
|
+
}),
|
|
89
|
+
Underline,
|
|
90
|
+
TextStyle.configure({ mergeNestedSpanStyles: true }),
|
|
91
|
+
TextAlign.configure({
|
|
92
|
+
types: ['heading', 'paragraph'],
|
|
93
|
+
}),
|
|
94
|
+
FontFamily.configure({
|
|
95
|
+
types: ['textStyle'],
|
|
96
|
+
}),
|
|
97
|
+
FontSize,
|
|
98
|
+
LetterSpacing,
|
|
99
|
+
LineHeight,
|
|
100
|
+
ColorExtension.configure({
|
|
101
|
+
types: ['textStyle'],
|
|
102
|
+
}),
|
|
103
|
+
Highlight.configure({
|
|
104
|
+
multicolor: true,
|
|
105
|
+
}),
|
|
106
|
+
Placeholder.configure({
|
|
36
107
|
placeholder: props.placeholder,
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
|
|
108
|
+
}),
|
|
109
|
+
MaxLength.configure({ maxLength: props.maxLength }),
|
|
110
|
+
Link.configure({
|
|
111
|
+
openOnClick: false,
|
|
112
|
+
defaultProtocol: 'https',
|
|
113
|
+
autolink: true,
|
|
114
|
+
HTMLAttributes: (attrs) => {
|
|
115
|
+
const href = attrs.href;
|
|
116
|
+
const isExternal = href && (href.startsWith('http://') || href.startsWith('https://'));
|
|
117
|
+
return {
|
|
118
|
+
target: isExternal ? '_blank' : null,
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
isAllowedUri: (url, ctx) => ctx.defaultValidate(url) || isHrefPageId(url),
|
|
122
|
+
}),
|
|
123
|
+
AutoLink,
|
|
124
|
+
],
|
|
40
125
|
editorProps: {
|
|
41
126
|
handleDOMEvents: {
|
|
42
127
|
drop(view, event) {
|
|
@@ -154,11 +239,7 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
|
|
|
154
239
|
const appliedSinceLast = historyStore.snapshotAppliedToken !== lastAppliedToken;
|
|
155
240
|
lastAppliedToken = historyStore.snapshotAppliedToken;
|
|
156
241
|
const isExternalMutation = updateRichTextEditor.value || appliedSinceLast;
|
|
157
|
-
|
|
158
|
-
// seen as "changed" against the editor's HTML (which would re-run
|
|
159
|
-
// setContent and reset the user's just-applied styles).
|
|
160
|
-
const hasChanged = JSON.stringify(editor.getJSON()) !== JSON.stringify(newValue);
|
|
161
|
-
if (isExternalMutation && hasChanged) {
|
|
242
|
+
if (isExternalMutation && editor.getHTML() !== newValue) {
|
|
162
243
|
editor.commands.setContent(newValue, false);
|
|
163
244
|
updateRichTextEditor.value = false;
|
|
164
245
|
//text style type is not switching when reordering
|
|
@@ -186,13 +267,11 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
|
|
|
186
267
|
});
|
|
187
268
|
}
|
|
188
269
|
});
|
|
189
|
-
// Update model when editor content changes
|
|
190
|
-
// JSON (BP-5787). getJSON() is already in hand (the editor holds the doc) and
|
|
191
|
-
// is sanitised natively — no getHTML(), no DOMPurify, no extra editor — so
|
|
192
|
-
// the per-keystroke path allocates nothing heavy.
|
|
270
|
+
// Update model when editor content changes
|
|
193
271
|
editor.on('update', () => {
|
|
272
|
+
const sanitizedData = sanitiseClientSideRTE(editor.getHTML());
|
|
194
273
|
// An update fired while the editor is unfocused is TipTap rewriting the
|
|
195
|
-
// model back into its canonical
|
|
274
|
+
// model back into its canonical HTML during mount rehydration or a
|
|
196
275
|
// programmatic setContent — not a user edit. Tag the emit so the write
|
|
197
276
|
// folds into the current undo head instead of pushing a phantom entry
|
|
198
277
|
// (BP-5810). The flag travels WITH the value through the debounced
|
|
@@ -206,7 +285,7 @@ export const useRichTextEditor = (model, props, emit, fontSettings, fontStyleDro
|
|
|
206
285
|
// the RTE only mounts inside an already-open settings drawer and the user
|
|
207
286
|
// focuses it before formatting, so this doesn't occur — revisit if a
|
|
208
287
|
// consumer ever drives toolbar actions on an unfocused editor.
|
|
209
|
-
emit('update:modelValue',
|
|
288
|
+
emit('update:modelValue', sanitizedData.sanitizedHtml, {
|
|
210
289
|
isHydrationWrite: !editor.isFocused,
|
|
211
290
|
});
|
|
212
291
|
});
|
|
@@ -5,6 +5,4 @@ export * from './url.util';
|
|
|
5
5
|
export * from './slug.util';
|
|
6
6
|
export * from './text.util';
|
|
7
7
|
export * from './rich-text-editor.util';
|
|
8
|
-
export * from './rte-render.util';
|
|
9
|
-
export * from './rte-sanitize.util';
|
|
10
8
|
export declare const isColorMatched: (color: string | null | undefined) => boolean;
|
|
@@ -6,8 +6,6 @@ export * from './url.util';
|
|
|
6
6
|
export * from './slug.util';
|
|
7
7
|
export * from './text.util';
|
|
8
8
|
export * from './rich-text-editor.util';
|
|
9
|
-
export * from './rte-render.util';
|
|
10
|
-
export * from './rte-sanitize.util';
|
|
11
9
|
export const isColorMatched = (color) => {
|
|
12
10
|
if (!color)
|
|
13
11
|
return false;
|
|
@@ -1,6 +1,6 @@
|
|
|
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
|
-
import { type Content, type TextStyleDeclaration, type TextStyleType
|
|
3
|
+
import { type Content, type TextStyleDeclaration, type TextStyleType } from '@brand-page';
|
|
4
4
|
import type { PageSettingsState } from '@shared/page-settings/store';
|
|
5
5
|
import type { StyleSettingsState } from '@shared/style-settings/store';
|
|
6
6
|
import type { Mark } from 'prosemirror-model';
|
|
@@ -9,22 +9,12 @@ export declare const RTE_ALLOWED_SECTION_TYPES: string[];
|
|
|
9
9
|
export declare const isRTEManagedStyleField: (sectionType: string, fieldName: string) => boolean;
|
|
10
10
|
export declare const isStyleChanged: (tr: Transaction) => boolean;
|
|
11
11
|
export declare const skipStyleDetection: (chain: ChainedCommands, marks?: Mark[]) => ChainedCommands;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* document is an object with `type: 'doc'`.
|
|
15
|
-
*/
|
|
16
|
-
export declare const isTiptapDocument: (value: unknown) => value is JSONContent;
|
|
17
|
-
/**
|
|
18
|
-
* Extract visible plain text from a Tiptap JSON document (concatenated text
|
|
19
|
-
* nodes). Dependency-free — no editor instantiation.
|
|
20
|
-
*/
|
|
21
|
-
export declare const getRteJsonPlainText: (doc: JSONContent) => string;
|
|
22
|
-
export declare const hasRTEContent: (content: unknown) => boolean;
|
|
23
|
-
export declare const getPlainTextFromRTE: (content: unknown) => string;
|
|
12
|
+
export declare const hasRTEContent: (content: string) => boolean;
|
|
13
|
+
export declare const getPlainTextFromRTE: (content: string) => string;
|
|
24
14
|
export declare const replaceTextInRTE: (existingHtml: string, newText: string) => string;
|
|
25
15
|
export declare const transformToRTE: (uiPageSettingStore: PageSettingsState, externalEditor: Editor, uiStyleSettingsStore?: StyleSettingsState, newContent?: Content, type?: TextStyleType, newStyle?: TextStyleDeclaration) => void;
|
|
26
16
|
export declare const switchAllContentToRTE: (uiPageSettingStore: PageSettingsState, externalEditor: Editor, uiStyleSettingsStore?: StyleSettingsState) => void;
|
|
27
|
-
export declare const getUpdatedHTMLFromEditor: (externalEditor: Editor, contentText:
|
|
17
|
+
export declare const getUpdatedHTMLFromEditor: (externalEditor: Editor, contentText: string, newContentStyle: TextStyleDeclaration) => string;
|
|
28
18
|
export declare const buildInitialMarks: (state: EditorState, style: TextStyleDeclaration) => any[];
|
|
29
19
|
export declare const retainStyleOnEmptyLine: (editor: Editor, selection: Selection) => void;
|
|
30
20
|
export interface SanitizationResult {
|
|
@@ -32,10 +22,6 @@ export interface SanitizationResult {
|
|
|
32
22
|
removedElements: string[];
|
|
33
23
|
}
|
|
34
24
|
export declare const sanitiseClientSideRTE: (html: string) => SanitizationResult;
|
|
35
|
-
/**
|
|
36
|
-
* Validates if an href value is safe and returns a sanitized version
|
|
37
|
-
*/
|
|
38
|
-
export declare const validateAndSanitizeHref: (href: string) => string | null;
|
|
39
25
|
/**
|
|
40
26
|
* Sanitize HTML string using DOMPurify.
|
|
41
27
|
* Only allow tags and attributes that correspond to the enabled Tiptap extensions.
|
|
@@ -175,41 +175,14 @@ export const skipStyleDetection = (chain, marks) => chain.command(({ tr }) => {
|
|
|
175
175
|
}
|
|
176
176
|
return true;
|
|
177
177
|
});
|
|
178
|
-
/**
|
|
179
|
-
* BP-5787 — canonical discriminator for the JSON RTE model: a ProseMirror
|
|
180
|
-
* document is an object with `type: 'doc'`.
|
|
181
|
-
*/
|
|
182
|
-
export const isTiptapDocument = (value) => typeof value === 'object' &&
|
|
183
|
-
value !== null &&
|
|
184
|
-
value.type === 'doc';
|
|
185
|
-
/**
|
|
186
|
-
* Extract visible plain text from a Tiptap JSON document (concatenated text
|
|
187
|
-
* nodes). Dependency-free — no editor instantiation.
|
|
188
|
-
*/
|
|
189
|
-
export const getRteJsonPlainText = (doc) => {
|
|
190
|
-
const parts = [];
|
|
191
|
-
const walk = (node) => {
|
|
192
|
-
if (typeof node.text === 'string')
|
|
193
|
-
parts.push(node.text);
|
|
194
|
-
node.content?.forEach(walk);
|
|
195
|
-
};
|
|
196
|
-
walk(doc);
|
|
197
|
-
return parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
198
|
-
};
|
|
199
|
-
// Accepts a legacy inline-HTML string or a Tiptap JSON document (BP-5787).
|
|
200
178
|
export const hasRTEContent = (content) => {
|
|
201
|
-
if (
|
|
202
|
-
return true;
|
|
203
|
-
if (typeof content !== 'string' || content.length === 0)
|
|
179
|
+
if (!content || content.length === 0)
|
|
204
180
|
return false;
|
|
205
181
|
const trimmed = content.trim();
|
|
206
182
|
const tagWrapRegex = /^<([A-Za-z][A-Za-z0-9-]*)\b[^>]*>[\s\S]*<\/\1>$/;
|
|
207
183
|
return tagWrapRegex.test(trimmed);
|
|
208
184
|
};
|
|
209
185
|
export const getPlainTextFromRTE = (content) => {
|
|
210
|
-
if (isTiptapDocument(content)) {
|
|
211
|
-
return getRteJsonPlainText(content);
|
|
212
|
-
}
|
|
213
186
|
if (typeof content !== 'string') {
|
|
214
187
|
return '';
|
|
215
188
|
}
|
|
@@ -501,10 +474,7 @@ export const getUpdatedHTMLFromEditor = (externalEditor, contentText, newContent
|
|
|
501
474
|
if (!contentText) {
|
|
502
475
|
return contentText;
|
|
503
476
|
}
|
|
504
|
-
|
|
505
|
-
// handles both; only the newline-escaping shim applies to strings.
|
|
506
|
-
const content = typeof contentText === 'string' &&
|
|
507
|
-
contentText.includes(ESCAPED_NEWLINE_CHARACTER)
|
|
477
|
+
const content = contentText.includes(ESCAPED_NEWLINE_CHARACTER)
|
|
508
478
|
? getRenderText(contentText)
|
|
509
479
|
: contentText;
|
|
510
480
|
externalEditor.commands.setContent(content);
|
|
@@ -548,8 +518,8 @@ export const getUpdatedHTMLFromEditor = (externalEditor, contentText, newContent
|
|
|
548
518
|
: chain.unsetBold();
|
|
549
519
|
chain = newContentStyle?.fontStyle ? chain.setItalic() : chain.unsetItalic();
|
|
550
520
|
chain.run();
|
|
551
|
-
|
|
552
|
-
return
|
|
521
|
+
const html = externalEditor.getHTML();
|
|
522
|
+
return html;
|
|
553
523
|
};
|
|
554
524
|
// Helper to build stored marks from a style declaration
|
|
555
525
|
export const buildInitialMarks = (state, style) => {
|
|
@@ -628,7 +598,7 @@ export const sanitiseClientSideRTE = (html) => {
|
|
|
628
598
|
/**
|
|
629
599
|
* Validates if an href value is safe and returns a sanitized version
|
|
630
600
|
*/
|
|
631
|
-
|
|
601
|
+
const validateAndSanitizeHref = (href) => {
|
|
632
602
|
if (!href || typeof href !== 'string')
|
|
633
603
|
return null;
|
|
634
604
|
// Trim whitespace and decode entities
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isURL, isEmail, isTel } from '@utils/validator.util';
|
|
2
|
-
import { YOUTUBE_URL_RE
|
|
2
|
+
import { YOUTUBE_URL_RE } from '@brand-page-maker/utils';
|
|
3
3
|
import { validatorTr } from '@src/composables/useTranslate';
|
|
4
4
|
const addressValueValidator = {
|
|
5
5
|
validate: (name, value) => {
|
|
@@ -62,15 +62,7 @@ export const dummyValidator = {
|
|
|
62
62
|
};
|
|
63
63
|
const nonEmptyStringValidator = {
|
|
64
64
|
validate: (name, value) => {
|
|
65
|
-
|
|
66
|
-
// An RTE field's stored value used to be inline HTML — an empty editor was
|
|
67
|
-
// "<p></p>", a NON-empty string, so the required check always passed for RTE
|
|
68
|
-
// fields (it only ever failed for genuinely empty PLAIN inputs). Now RTE
|
|
69
|
-
// fields are Tiptap JSON docs; treat any doc as "present" so behaviour
|
|
70
|
-
// stays identical. A plain string is still required to be non-empty.
|
|
71
|
-
const isValid = isTiptapDocument(value)
|
|
72
|
-
? true
|
|
73
|
-
: typeof value === 'string' && value.length > 0;
|
|
65
|
+
const isValid = typeof value === 'string' && value.length > 0;
|
|
74
66
|
return {
|
|
75
67
|
isValid,
|
|
76
68
|
message: isValid ? '' : validatorTr(`emptyStringMessage`),
|
package/lib/src/index.d.ts
CHANGED
|
@@ -28,7 +28,6 @@ export * as brandPageUtils from './brand-page/utils';
|
|
|
28
28
|
export type { CriticalResourceURLs } from './brand-page/utils/view-model.util';
|
|
29
29
|
export { getOptimalLogoSize, calculateOptimizedLogoDimensions, applyLogoImageBase64UrlToBrandPageTemplateModel, recalculateLogoContentDimensions, recalculateHeaderLogoSizes, } from './brand-page/utils/logo.util';
|
|
30
30
|
export { sanitizeRTEHtml } from './brand-page-maker/utils';
|
|
31
|
-
export { renderRteToHtml, toRteJson, isTiptapDocument, sanitizeRteJson, getRteJsonPlainText, type RichTextValue, } from './brand-page-maker/utils';
|
|
32
31
|
export { getPageSlugFromPageTitle } from './brand-page-maker/utils/slug.util';
|
|
33
32
|
export { executeToolCallsAsync } from './metadata-builder/ai-metadata-executor';
|
|
34
33
|
export type { ParsedToolCall } from './metadata-builder/parsers/ai-toolcall-parser';
|