@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.
Files changed (35) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-r7USgAVD.js → CreatedOnBrandCrowd-de-de-hj_ievMc.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-Cl9PeQVq.js → CreatedOnBrandCrowd-en-us-jAOSpfCw.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-JQgWL1oM.js → CreatedOnBrandCrowd-es-es-DZMVc-y3.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-MyYaZgId.js → CreatedOnBrandCrowd-fr-ca-DvB54H38.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-MyYaZgId.js → CreatedOnBrandCrowd-fr-fr-DvB54H38.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-D6FStQU6.js → CreatedOnBrandCrowd-pt-br-bf-x1xhi.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-D6FStQU6.js → CreatedOnBrandCrowd-pt-pt-bf-x1xhi.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-BL1nZZE6.js → CreatedOnDcom-de-de-B-NY1B44.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-C8R2eB3R.js → CreatedOnDcom-en-us-Cd4C6KQf.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-Cw34v79P.js → CreatedOnDcom-es-es-DONlsQsm.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-hQ7zuwhB.js → CreatedOnDcom-fr-ca-Dsv3FRpZ.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DYO-y4Qs.js → CreatedOnDcom-fr-fr-DOgyvY5C.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-D_-6VWMA.js → CreatedOnDcom-pt-br-DX66MhJS.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-C0bg2qxu.js → CreatedOnDcom-pt-pt-COe2G0bU.js} +1 -1
  15. package/lib/src/brand-page/utils/text.util.d.ts +12 -0
  16. package/lib/src/brand-page/utils/text.util.js +32 -0
  17. package/lib/src/css/library.brand-page-brandCrowd.min.css +39 -36
  18. package/lib/src/css/library.brand-page-brandPage.min.css +39 -36
  19. package/lib/src/css/library.brand-page-designCom.min.css +39 -36
  20. package/lib/src/css/library.brand-page-preview.min.css +39 -36
  21. package/lib/src/index.mjs +1 -1
  22. package/lib/src/{lib-B_8Foa76.js → lib-scDtk-Pi.js} +9846 -9827
  23. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +1 -1
  24. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.js +21 -9
  25. package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +1 -1
  26. package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +1 -1
  27. package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +1 -1
  28. package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +1 -1
  29. package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +1 -1
  30. package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +1 -1
  31. package/package.json +1 -1
  32. package/src/brand-page/utils/text.util.test.ts +60 -1
  33. package/src/brand-page/utils/text.util.ts +40 -0
  34. package/src/brand-page-maker/components/rich-text-editor/RichTextEditor.vue +1 -1
  35. 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, escapeContentText, unescapeContentText, } from '@brand-page/utils';
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;
@@ -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,7 +1,7 @@
1
1
  {
2
2
  "name": "@designcrowd/library.brand-page",
3
3
  "description": "A collection of Maker/Designer applications",
4
- "version": "6.1.1-json-prereq-2",
4
+ "version": "6.1.1-json-prereq-3",
5
5
  "author": "Design.com Eng",
6
6
  "license": "UNLICENSED",
7
7
  "type": "module",
@@ -1,5 +1,9 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { escapeContentText, getRenderText } from './text.util';
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(/&lt;/g, '<')
@@ -389,7 +389,7 @@
389
389
  :options="bubbleMenuOptions"
390
390
  :update-delay="0"
391
391
  :should-show="shouldShow"
392
- class="tw-w-fit"
392
+ class="tw-w-fit tw-max-w-[350px]"
393
393
  >
394
394
  <div class="bubble-menu">
395
395
  <div
@@ -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
- escapeContentText,
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) {