@designcrowd/library.brand-page 6.1.1-json-prereq-2 → 6.1.1-json-prereq-3
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-r7USgAVD.js → CreatedOnBrandCrowd-de-de-hj_ievMc.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-en-us-Cl9PeQVq.js → CreatedOnBrandCrowd-en-us-jAOSpfCw.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-es-es-JQgWL1oM.js → CreatedOnBrandCrowd-es-es-DZMVc-y3.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-fr-MyYaZgId.js → CreatedOnBrandCrowd-fr-ca-DvB54H38.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-fr-ca-MyYaZgId.js → CreatedOnBrandCrowd-fr-fr-DvB54H38.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-pt-D6FStQU6.js → CreatedOnBrandCrowd-pt-br-bf-x1xhi.js} +1 -1
- package/lib/src/{CreatedOnBrandCrowd-pt-br-D6FStQU6.js → CreatedOnBrandCrowd-pt-pt-bf-x1xhi.js} +1 -1
- package/lib/src/{CreatedOnDcom-de-de-BL1nZZE6.js → CreatedOnDcom-de-de-B-NY1B44.js} +1 -1
- package/lib/src/{CreatedOnDcom-en-us-C8R2eB3R.js → CreatedOnDcom-en-us-Cd4C6KQf.js} +1 -1
- package/lib/src/{CreatedOnDcom-es-es-Cw34v79P.js → CreatedOnDcom-es-es-DONlsQsm.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-ca-hQ7zuwhB.js → CreatedOnDcom-fr-ca-Dsv3FRpZ.js} +1 -1
- package/lib/src/{CreatedOnDcom-fr-fr-DYO-y4Qs.js → CreatedOnDcom-fr-fr-DOgyvY5C.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-br-D_-6VWMA.js → CreatedOnDcom-pt-br-DX66MhJS.js} +1 -1
- package/lib/src/{CreatedOnDcom-pt-pt-C0bg2qxu.js → CreatedOnDcom-pt-pt-COe2G0bU.js} +1 -1
- package/lib/src/brand-page/utils/text.util.d.ts +12 -0
- package/lib/src/brand-page/utils/text.util.js +32 -0
- package/lib/src/css/library.brand-page-brandCrowd.min.css +39 -36
- package/lib/src/css/library.brand-page-brandPage.min.css +39 -36
- package/lib/src/css/library.brand-page-designCom.min.css +39 -36
- package/lib/src/css/library.brand-page-preview.min.css +39 -36
- package/lib/src/index.mjs +1 -1
- package/lib/src/{lib-B_8Foa76.js → lib-scDtk-Pi.js} +9846 -9827
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +21 -9
- package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +1 -1
- package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +1 -1
- package/package.json +1 -1
- package/src/brand-page/utils/text.util.test.ts +60 -1
- package/src/brand-page/utils/text.util.ts +40 -0
- package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +1 -1
- package/src/shared/content-settings/components/GenericTextInput.mixin.ts +25 -10
|
@@ -83,13 +83,13 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
83
83
|
_oldValue: string;
|
|
84
84
|
isDirty: boolean;
|
|
85
85
|
emitChangeDebounce: DebounceHandle | undefined;
|
|
86
|
-
lengthError: boolean;
|
|
87
86
|
burstIsHydration: boolean | undefined;
|
|
88
87
|
pendingWriteIsHydration: boolean;
|
|
89
88
|
}, {
|
|
90
89
|
errorMessage(): string | undefined;
|
|
91
90
|
isErrorVisible(): boolean;
|
|
92
91
|
isValid(): boolean | undefined;
|
|
92
|
+
lengthError(): boolean;
|
|
93
93
|
label(): string;
|
|
94
94
|
text: {
|
|
95
95
|
get(): string | false;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defineComponent } from 'vue';
|
|
2
2
|
import debounce from 'lodash.debounce';
|
|
3
3
|
import { TextInput, Textarea } from '@designcrowd/fe-shared-lib';
|
|
4
|
-
import { encodeContentText,
|
|
4
|
+
import { encodeContentText, getContentTextLength, unescapeContentText, } from '@brand-page/utils';
|
|
5
5
|
import { genericTextInputTr } from '@src/composables/useTranslate';
|
|
6
6
|
const DEBOUNCE_WAIT_MS = 100;
|
|
7
7
|
export default defineComponent({
|
|
@@ -98,7 +98,6 @@ export default defineComponent({
|
|
|
98
98
|
_oldValue: '',
|
|
99
99
|
isDirty: false,
|
|
100
100
|
emitChangeDebounce: undefined,
|
|
101
|
-
lengthError: false,
|
|
102
101
|
// Whether every write in the current debounce burst was an RTE
|
|
103
102
|
// hydration write-back (see UpdateItemValuePayload.isHydrationWrite).
|
|
104
103
|
// undefined = no burst pending; any user write downgrades it to false.
|
|
@@ -118,6 +117,26 @@ export default defineComponent({
|
|
|
118
117
|
isValid() {
|
|
119
118
|
return this.settings?.isValid;
|
|
120
119
|
},
|
|
120
|
+
// Derived from the current value + maxLength (not hand-synced in a watcher):
|
|
121
|
+
// stays correct when maxLength or showRichTextEditor change and on first
|
|
122
|
+
// render, and flips the instant `_value` updates rather than after the emit
|
|
123
|
+
// round-trips back through the `value` prop. `>=` (not `>`) because the RTE
|
|
124
|
+
// MaxLength extension hard-gates the length, so "reached the cap" is the
|
|
125
|
+
// signal to surface. Length is measured with getContentTextLength so it
|
|
126
|
+
// matches the editor's own char count (ProseMirror textContent) — NOT
|
|
127
|
+
// escapeContentText, whose space-join over-counts by one per mark boundary
|
|
128
|
+
// (e.g. hyperlinking a word), reporting length+1 at the cap (BP-5787).
|
|
129
|
+
lengthError() {
|
|
130
|
+
const value = this._value;
|
|
131
|
+
const maxLength = this.maxLength;
|
|
132
|
+
if (maxLength == null || value === false || value == null) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
const length = this.showRichTextEditor
|
|
136
|
+
? getContentTextLength(value)
|
|
137
|
+
: value.length;
|
|
138
|
+
return length >= maxLength;
|
|
139
|
+
},
|
|
121
140
|
label() {
|
|
122
141
|
return this.name.replace(/./, (c) => c.toUpperCase());
|
|
123
142
|
},
|
|
@@ -173,13 +192,6 @@ export default defineComponent({
|
|
|
173
192
|
return;
|
|
174
193
|
}
|
|
175
194
|
this._value = newVal;
|
|
176
|
-
if (this.showRichTextEditor) {
|
|
177
|
-
const textValue = escapeContentText(this._value);
|
|
178
|
-
this.lengthError = textValue && textValue.length >= this.maxLength;
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
this.lengthError = this._value.length >= this.maxLength;
|
|
182
|
-
}
|
|
183
195
|
},
|
|
184
196
|
isToggled(newVal) {
|
|
185
197
|
if (newVal) {
|
|
@@ -80,13 +80,13 @@ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import
|
|
|
80
80
|
_oldValue: string;
|
|
81
81
|
isDirty: boolean;
|
|
82
82
|
emitChangeDebounce: import("lodash.debounce").DebounceHandle | undefined;
|
|
83
|
-
lengthError: boolean;
|
|
84
83
|
burstIsHydration: boolean | undefined;
|
|
85
84
|
pendingWriteIsHydration: boolean;
|
|
86
85
|
}, {
|
|
87
86
|
errorMessage(): string | undefined;
|
|
88
87
|
isErrorVisible(): boolean;
|
|
89
88
|
isValid(): boolean | undefined;
|
|
89
|
+
lengthError(): boolean;
|
|
90
90
|
label(): string;
|
|
91
91
|
text: {
|
|
92
92
|
get(): string | false;
|
|
@@ -80,13 +80,13 @@ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import
|
|
|
80
80
|
_oldValue: string;
|
|
81
81
|
isDirty: boolean;
|
|
82
82
|
emitChangeDebounce: import("lodash.debounce").DebounceHandle | undefined;
|
|
83
|
-
lengthError: boolean;
|
|
84
83
|
burstIsHydration: boolean | undefined;
|
|
85
84
|
pendingWriteIsHydration: boolean;
|
|
86
85
|
}, {
|
|
87
86
|
errorMessage(): string | undefined;
|
|
88
87
|
isErrorVisible(): boolean;
|
|
89
88
|
isValid(): boolean | undefined;
|
|
89
|
+
lengthError(): boolean;
|
|
90
90
|
label(): string;
|
|
91
91
|
text: {
|
|
92
92
|
get(): string | false;
|
|
@@ -80,13 +80,13 @@ declare const _default: import("vue").DefineComponent<{}, {}, {}, {}, {}, import
|
|
|
80
80
|
_oldValue: string;
|
|
81
81
|
isDirty: boolean;
|
|
82
82
|
emitChangeDebounce: import("lodash.debounce").DebounceHandle | undefined;
|
|
83
|
-
lengthError: boolean;
|
|
84
83
|
burstIsHydration: boolean | undefined;
|
|
85
84
|
pendingWriteIsHydration: boolean;
|
|
86
85
|
}, {
|
|
87
86
|
errorMessage(): string | undefined;
|
|
88
87
|
isErrorVisible(): boolean;
|
|
89
88
|
isValid(): boolean | undefined;
|
|
89
|
+
lengthError(): boolean;
|
|
90
90
|
label(): string;
|
|
91
91
|
text: {
|
|
92
92
|
get(): string | false;
|
|
@@ -94,13 +94,13 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
94
94
|
_oldValue: string;
|
|
95
95
|
isDirty: boolean;
|
|
96
96
|
emitChangeDebounce: import("lodash.debounce").DebounceHandle | undefined;
|
|
97
|
-
lengthError: boolean;
|
|
98
97
|
burstIsHydration: boolean | undefined;
|
|
99
98
|
pendingWriteIsHydration: boolean;
|
|
100
99
|
}, {
|
|
101
100
|
errorMessage(): string | undefined;
|
|
102
101
|
isErrorVisible(): boolean;
|
|
103
102
|
isValid(): boolean | undefined;
|
|
103
|
+
lengthError(): boolean;
|
|
104
104
|
label(): string;
|
|
105
105
|
text: {
|
|
106
106
|
get(): string | false;
|
|
@@ -87,13 +87,13 @@ declare const _default: import("vue").DefineComponent<{}, {}, Record<string, any
|
|
|
87
87
|
_oldValue: string;
|
|
88
88
|
isDirty: boolean;
|
|
89
89
|
emitChangeDebounce: import("lodash.debounce").DebounceHandle | undefined;
|
|
90
|
-
lengthError: boolean;
|
|
91
90
|
burstIsHydration: boolean | undefined;
|
|
92
91
|
pendingWriteIsHydration: boolean;
|
|
93
92
|
}, {
|
|
94
93
|
errorMessage(): string | undefined;
|
|
95
94
|
isErrorVisible(): boolean;
|
|
96
95
|
isValid(): boolean | undefined;
|
|
96
|
+
lengthError(): boolean;
|
|
97
97
|
label(): string;
|
|
98
98
|
text: {
|
|
99
99
|
get(): string | false;
|
package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts
CHANGED
|
@@ -83,13 +83,13 @@ declare const _default: import("vue").DefineComponent<{}, {}, Record<string, any
|
|
|
83
83
|
_oldValue: string;
|
|
84
84
|
isDirty: boolean;
|
|
85
85
|
emitChangeDebounce: import("lodash.debounce").DebounceHandle | undefined;
|
|
86
|
-
lengthError: boolean;
|
|
87
86
|
burstIsHydration: boolean | undefined;
|
|
88
87
|
pendingWriteIsHydration: boolean;
|
|
89
88
|
}, {
|
|
90
89
|
errorMessage(): string | undefined;
|
|
91
90
|
isErrorVisible(): boolean;
|
|
92
91
|
isValid(): boolean | undefined;
|
|
92
|
+
lengthError(): boolean;
|
|
93
93
|
label(): string;
|
|
94
94
|
text: {
|
|
95
95
|
get(): string | false;
|
package/package.json
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
escapeContentText,
|
|
4
|
+
getContentTextLength,
|
|
5
|
+
getRenderText,
|
|
6
|
+
} from './text.util';
|
|
3
7
|
|
|
4
8
|
/**
|
|
5
9
|
* BP-5787 — escapeContentText / getRenderText must accept either a legacy
|
|
@@ -45,3 +49,58 @@ describe('getRenderText (BP-5787 JSON-safe)', () => {
|
|
|
45
49
|
expect(getRenderText('a\nb')).toBe('a<br/>b');
|
|
46
50
|
});
|
|
47
51
|
});
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* BP-5787 — length limiting must count the editor's own char count (ProseMirror
|
|
55
|
+
* textContent), NOT escapeContentText's space-joined plain text. A mark boundary
|
|
56
|
+
* (e.g. hyperlinking a word) splits one text node into two; escapeContentText
|
|
57
|
+
* inserts a phantom space at the split and reports length+1, which tripped the
|
|
58
|
+
* "max reached" indicator at exactly-the-cap content. getContentTextLength
|
|
59
|
+
* concatenates node text with no separator, matching the MaxLength gate.
|
|
60
|
+
*/
|
|
61
|
+
describe('getContentTextLength (BP-5787 gate-matching length)', () => {
|
|
62
|
+
const linkedDoc = {
|
|
63
|
+
type: 'doc',
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: 'paragraph',
|
|
67
|
+
content: [
|
|
68
|
+
{ type: 'text', text: 'hello' },
|
|
69
|
+
{
|
|
70
|
+
type: 'text',
|
|
71
|
+
text: 'world',
|
|
72
|
+
marks: [{ type: 'link', attrs: { href: 'https://x.com' } }],
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
it('counts split (hyperlinked) nodes without a phantom space', () => {
|
|
80
|
+
// escapeContentText over-counts this as 11 ("hello world"); the editor's
|
|
81
|
+
// textContent is "helloworld" = 10.
|
|
82
|
+
expect(getContentTextLength(linkedDoc)).toBe(10);
|
|
83
|
+
expect(escapeContentText(linkedDoc)).toHaveLength(11);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('matches plain single-node text', () => {
|
|
87
|
+
const plain = {
|
|
88
|
+
type: 'doc',
|
|
89
|
+
content: [
|
|
90
|
+
{ type: 'paragraph', content: [{ type: 'text', text: 'helloworld' }] },
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
expect(getContentTextLength(plain)).toBe(10);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('counts a legacy HTML string with tags stripped', () => {
|
|
98
|
+
expect(getContentTextLength('<p>hello<a href="#">world</a></p>')).toBe(10);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('returns 0 for empty / non-content values', () => {
|
|
102
|
+
expect(getContentTextLength('')).toBe(0);
|
|
103
|
+
expect(getContentTextLength(undefined)).toBe(0);
|
|
104
|
+
expect(getContentTextLength(false)).toBe(0);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -58,6 +58,46 @@ export const escapeContentText = (text: unknown): string | undefined => {
|
|
|
58
58
|
.replace(/(\r\n|\r|\n)/g, ESCAPED_NEWLINE_CHARACTER);
|
|
59
59
|
};
|
|
60
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Character length of RTE content as the *editor* counts it — ProseMirror
|
|
63
|
+
* `doc.textContent`: text nodes concatenated with NO separator. This is the
|
|
64
|
+
* metric the MaxLength extension gates on, so length indicators must use it too.
|
|
65
|
+
*
|
|
66
|
+
* Do NOT use escapeContentText/jsonDocPlainText for length limiting: those
|
|
67
|
+
* join text nodes with a space for readable plain-text extraction, so they
|
|
68
|
+
* over-count by one character per inline mark boundary. Converting a word to a
|
|
69
|
+
* hyperlink (or bolding it, etc.) splits its text node, making the counter
|
|
70
|
+
* report length+1 for content the editor considers exactly at the cap (BP-5787).
|
|
71
|
+
*/
|
|
72
|
+
export const getContentTextLength = (value: unknown): number => {
|
|
73
|
+
if (!value) {
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isTiptapDoc(value)) {
|
|
78
|
+
let length = 0;
|
|
79
|
+
|
|
80
|
+
const walk = (node: any): void => {
|
|
81
|
+
if (typeof node?.text === 'string') {
|
|
82
|
+
length += node.text.length;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
node?.content?.forEach(walk);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
walk(value);
|
|
89
|
+
|
|
90
|
+
return length;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (typeof value !== 'string') {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Legacy inline-HTML string: count visible text with tags stripped.
|
|
98
|
+
return value.replace(/(<([^>]+)>)/gi, '').length;
|
|
99
|
+
};
|
|
100
|
+
|
|
61
101
|
export const decodeHtmlEntities = (text: string): string => {
|
|
62
102
|
return text
|
|
63
103
|
.replace(/</g, '<')
|
|
@@ -6,7 +6,7 @@ import { TextInput, Textarea } from '@designcrowd/fe-shared-lib';
|
|
|
6
6
|
import type { ContentItemFieldSettings } from '../models';
|
|
7
7
|
import {
|
|
8
8
|
encodeContentText,
|
|
9
|
-
|
|
9
|
+
getContentTextLength,
|
|
10
10
|
unescapeContentText,
|
|
11
11
|
} from '@brand-page/utils';
|
|
12
12
|
import { genericTextInputTr } from '@src/composables/useTranslate';
|
|
@@ -107,7 +107,6 @@ export default defineComponent({
|
|
|
107
107
|
_oldValue: '' as string,
|
|
108
108
|
isDirty: false,
|
|
109
109
|
emitChangeDebounce: undefined as DebounceHandle | undefined,
|
|
110
|
-
lengthError: false,
|
|
111
110
|
// Whether every write in the current debounce burst was an RTE
|
|
112
111
|
// hydration write-back (see UpdateItemValuePayload.isHydrationWrite).
|
|
113
112
|
// undefined = no burst pending; any user write downgrades it to false.
|
|
@@ -130,6 +129,30 @@ export default defineComponent({
|
|
|
130
129
|
return this.settings?.isValid;
|
|
131
130
|
},
|
|
132
131
|
|
|
132
|
+
// Derived from the current value + maxLength (not hand-synced in a watcher):
|
|
133
|
+
// stays correct when maxLength or showRichTextEditor change and on first
|
|
134
|
+
// render, and flips the instant `_value` updates rather than after the emit
|
|
135
|
+
// round-trips back through the `value` prop. `>=` (not `>`) because the RTE
|
|
136
|
+
// MaxLength extension hard-gates the length, so "reached the cap" is the
|
|
137
|
+
// signal to surface. Length is measured with getContentTextLength so it
|
|
138
|
+
// matches the editor's own char count (ProseMirror textContent) — NOT
|
|
139
|
+
// escapeContentText, whose space-join over-counts by one per mark boundary
|
|
140
|
+
// (e.g. hyperlinking a word), reporting length+1 at the cap (BP-5787).
|
|
141
|
+
lengthError(): boolean {
|
|
142
|
+
const value = this._value;
|
|
143
|
+
const maxLength = this.maxLength;
|
|
144
|
+
|
|
145
|
+
if (maxLength == null || value === false || value == null) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const length = this.showRichTextEditor
|
|
150
|
+
? getContentTextLength(value)
|
|
151
|
+
: value.length;
|
|
152
|
+
|
|
153
|
+
return length >= maxLength;
|
|
154
|
+
},
|
|
155
|
+
|
|
133
156
|
label(): string {
|
|
134
157
|
return this.name.replace(/./, (c: string) => c.toUpperCase());
|
|
135
158
|
},
|
|
@@ -193,14 +216,6 @@ export default defineComponent({
|
|
|
193
216
|
}
|
|
194
217
|
|
|
195
218
|
this._value = newVal;
|
|
196
|
-
|
|
197
|
-
if (this.showRichTextEditor) {
|
|
198
|
-
const textValue = escapeContentText(this._value);
|
|
199
|
-
|
|
200
|
-
this.lengthError = textValue && textValue.length >= this.maxLength;
|
|
201
|
-
} else {
|
|
202
|
-
this.lengthError = this._value.length >= this.maxLength;
|
|
203
|
-
}
|
|
204
219
|
},
|
|
205
220
|
|
|
206
221
|
isToggled(newVal: boolean) {
|