@pushwoosh/dumb-components 1.1.219 → 1.1.220

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.
@@ -3,6 +3,7 @@ import { Transforms } from 'slate';
3
3
  import { useSelected, useSlateStatic } from 'slate-react';
4
4
  import { findPath } from './helpers';
5
5
  import { Chip } from '../../Chip';
6
+ import { isResolvableDc } from '../../shared/dynamicContent';
6
7
  const DynamicContentElementComponent = ({
7
8
  attributes,
8
9
  element,
@@ -14,11 +15,12 @@ const DynamicContentElementComponent = ({
14
15
  const {
15
16
  tagName
16
17
  } = element.dc;
18
+ const isResolvable = isResolvableDc(element.dc);
17
19
  return _jsxs(Chip, {
18
20
  ...attributes,
19
21
  as: "span",
20
22
  contentEditable: false,
21
- "$variant": tagName ? 'primary' : 'danger',
23
+ "$variant": isResolvable ? 'primary' : 'danger',
22
24
  "$selected": selected,
23
25
  "$onClose": event => {
24
26
  event.preventDefault();
@@ -41,7 +41,7 @@ export declare function string2content(value: string): SlateEditorContent;
41
41
  */
42
42
  export declare function content2string(state: SlateEditorContent): string;
43
43
  /**
44
- * Validates if the content has valid dynamic content elements
44
+ * Validates that every dynamic content element is one the sender can substitute
45
45
  * @param value - The Slate editor content to validate
46
46
  * @returns True if all dynamic content elements are valid, false otherwise
47
47
  */
@@ -1,4 +1,4 @@
1
- import { parseDc, splitDynamicContent, stringifyDc } from '../../shared/dynamicContent';
1
+ import { isResolvableDc, parseDc, splitDynamicContent, stringifyDc } from '../../shared/dynamicContent';
2
2
  /**
3
3
  * Checks if a Slate node is a custom element
4
4
  * @param x - The node to check
@@ -127,14 +127,14 @@ export function content2string(state) {
127
127
  }).join('\n');
128
128
  }
129
129
  /**
130
- * Validates if the content has valid dynamic content elements
130
+ * Validates that every dynamic content element is one the sender can substitute
131
131
  * @param value - The Slate editor content to validate
132
132
  * @returns True if all dynamic content elements are valid, false otherwise
133
133
  */
134
134
  export function validateContent(value) {
135
135
  for (const el of value) {
136
136
  if (isElement(el)) {
137
- if (el.type === 'dc' && !el.dc.tagName) {
137
+ if (el.type === 'dc' && !isResolvableDc(el.dc)) {
138
138
  return false;
139
139
  }
140
140
  if (!validateContent(el.children)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushwoosh/dumb-components",
3
- "version": "1.1.219",
3
+ "version": "1.1.220",
4
4
  "description": "React components to build Pushwoosh products",
5
5
  "main": "index.js",
6
6
  "module": "index.js",
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,32 @@
1
+ import { validateContentSting } from '../../../RichMessageEditor/slate/helpers';
2
+ import { isResolvableDc, parseDc } from '../token';
3
+ // The lists below mirror channels/publisher
4
+ // internal/services/dynamiccontentreplacer/constants.go — keep them in sync.
5
+ const caseInsensitiveModifiers = ['capitalizefirst', 'capitalizeallfirst', 'uppercase', 'lowercase', 'regular', 'base64', 'cent', 'dollar', 'comma', 'euro', 'jpy', 'lira'];
6
+ const dateModifiers = ['M-d-y', 'm-d-y', 'M d y', 'M d Y', 'l', 'M d', 'H:i', 'm-d-y H:i'];
7
+ describe('isResolvableDc', () => {
8
+ test.each(caseInsensitiveModifiers)('accepts %s', modifier => {
9
+ expect(isResolvableDc(parseDc(`{Name|${modifier}}`))).toBe(true);
10
+ });
11
+ test.each(caseInsensitiveModifiers)('accepts %s whatever the case', modifier => {
12
+ expect(isResolvableDc(parseDc(`{Name|${modifier.toUpperCase()}}`))).toBe(true);
13
+ });
14
+ test.each(dateModifiers)('accepts the date modifier %s', modifier => {
15
+ expect(isResolvableDc(parseDc(`{Name|${modifier}}`))).toBe(true);
16
+ });
17
+ test('rejects a date modifier in the wrong case, as the sender does', () => {
18
+ expect(isResolvableDc(parseDc('{Name|m d y}'))).toBe(false);
19
+ });
20
+ test.each([['{Name|}', 'no modifier at all'], ['{Name|nope}', 'a modifier outside the list'], ['{|regular}', 'no tag name']])('rejects %s (%s)', token => {
21
+ expect(isResolvableDc(parseDc(token))).toBe(false);
22
+ });
23
+ test('keeps a default value out of the verdict', () => {
24
+ expect(isResolvableDc(parseDc('{WalletPassLink|regular|no pass}'))).toBe(true);
25
+ expect(isResolvableDc(parseDc('{WalletPassLink||no pass}'))).toBe(false);
26
+ });
27
+ });
28
+ describe('validateContentSting', () => {
29
+ test.each([['Tap here!\n{WalletSerialNo|regular}', true], ['Tap here!\n{WalletSerialNo|}', false], ['{WalletSerialNo|regular}{WalletPassLink|}', false], ['Hi {{ user.name | upcase }}, plain liquid is not a token', true], ['no tokens at all', true], ['', true]])('%s -> %s', (content, expected) => {
30
+ expect(validateContentSting(content)).toBe(expected);
31
+ });
32
+ });
@@ -1,5 +1,5 @@
1
1
  export { DynamicContentChip, type DynamicContentChipProps } from './DynamicContentChip';
2
2
  export { DynamicContentText, type DynamicContentTextProps } from './DynamicContentText';
3
- export { parseDc, splitDynamicContent, stringifyDc } from './token';
3
+ export { isResolvableDc, parseDc, splitDynamicContent, stringifyDc } from './token';
4
4
  export { Paragraph } from './styles';
5
5
  export type { DynamicContent, DynamicContentPart, DynamicContentTone } from './types';
@@ -1,4 +1,4 @@
1
1
  export { DynamicContentChip } from './DynamicContentChip';
2
2
  export { DynamicContentText } from './DynamicContentText';
3
- export { parseDc, splitDynamicContent, stringifyDc } from './token';
3
+ export { isResolvableDc, parseDc, splitDynamicContent, stringifyDc } from './token';
4
4
  export { Paragraph } from './styles';
@@ -18,3 +18,5 @@ export declare function splitDynamicContent(text: string): DynamicContentPart[];
18
18
  export declare function parseDc(value: string): DynamicContent;
19
19
  /** Serializes a token back to `{tagName|modifier|defaultValue}`. */
20
20
  export declare function stringifyDc(dc: DynamicContent): string;
21
+ /** Whether the sender substitutes this token, or ships it verbatim (dates are case-sensitive, other modifiers are not). */
22
+ export declare function isResolvableDc(dc: DynamicContent): boolean;
@@ -42,4 +42,14 @@ export function stringifyDc(dc) {
42
42
  defaultValue
43
43
  } = dc;
44
44
  return `{${tagName}|${modifier}${defaultValue ? `|${defaultValue}` : ''}}`;
45
+ }
46
+ const CASE_INSENSITIVE_MODIFIERS = ['capitalizefirst', 'capitalizeallfirst', 'uppercase', 'lowercase', 'regular', 'base64', 'cent', 'dollar', 'comma', 'euro', 'jpy', 'lira'];
47
+ const DATE_MODIFIERS = ['M-d-y', 'm-d-y', 'M d y', 'M d Y', 'l', 'M d', 'H:i', 'm-d-y H:i'];
48
+ /** Whether the sender substitutes this token, or ships it verbatim (dates are case-sensitive, other modifiers are not). */
49
+ export function isResolvableDc(dc) {
50
+ const {
51
+ tagName,
52
+ modifier
53
+ } = dc;
54
+ return Boolean(tagName) && (CASE_INSENSITIVE_MODIFIERS.includes(modifier.toLowerCase()) || DATE_MODIFIERS.includes(modifier));
45
55
  }