ajaxter-chat 3.0.4 → 3.0.6

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.
@@ -1,55 +1,134 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- export const HomeScreen = ({ config, onNavigate }) => {
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { SlideNavMenu } from '../SlideNavMenu';
4
+ export const HomeScreen = ({ config, onNavigate, tickets }) => {
5
+ var _a;
6
+ const [menuOpen, setMenuOpen] = useState(false);
3
7
  const showSupport = config.chatType === 'SUPPORT' || config.chatType === 'BOTH';
4
8
  const showChat = config.chatType === 'CHAT' || config.chatType === 'BOTH';
5
- const cards = [
6
- showSupport && {
7
- key: 'support',
8
- icon: '🛠',
9
- title: 'Need Support',
10
- subtitle: 'We typically reply in a few minutes',
11
- onClick: () => onNavigate('support'),
12
- },
13
- showChat && {
14
- key: 'conversation',
15
- icon: '💬',
16
- title: 'New Conversation',
17
- subtitle: 'With your colleague',
18
- onClick: () => onNavigate('conversation'),
19
- },
20
- {
21
- key: 'ticket',
22
- icon: '🎫',
23
- title: 'Raise Ticket',
24
- subtitle: 'For major changes',
25
- onClick: () => onNavigate('ticket'),
26
- },
27
- ].filter(Boolean);
28
- return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%' }, children: [_jsxs("div", { style: {
29
- background: `linear-gradient(145deg, ${config.primaryColor}, ${config.primaryColor}dd)`,
30
- padding: '36px 24px 52px',
9
+ const viewerIsDev = config.viewerType === 'developer';
10
+ const continueItems = tickets.slice(0, 2);
11
+ const handleCallUs = () => {
12
+ var _a;
13
+ const raw = (_a = config.supportPhone) === null || _a === void 0 ? void 0 : _a.trim();
14
+ if (!raw)
15
+ return;
16
+ window.location.href = `tel:${raw.replace(/\s/g, '')}`;
17
+ };
18
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', position: 'relative', overflow: 'hidden', background: '#fafbfc' }, children: [_jsx(SlideNavMenu, { open: menuOpen, onClose: () => setMenuOpen(false), primaryColor: config.primaryColor, chatType: config.chatType, viewerType: (_a = config.viewerType) !== null && _a !== void 0 ? _a : 'user', onSelect: onNavigate }), _jsx("div", { style: {
31
19
  flexShrink: 0,
32
- position: 'relative',
33
- overflow: 'hidden',
34
- }, children: [_jsx("div", { style: { position: 'absolute', top: -40, right: -40, width: 140, height: 140, borderRadius: '50%', background: 'rgba(255,255,255,0.07)' } }), _jsx("div", { style: { position: 'absolute', bottom: -20, left: -20, width: 90, height: 90, borderRadius: '50%', background: 'rgba(255,255,255,0.05)' } }), _jsx("h1", { style: { margin: '0 0 8px', fontSize: 26, fontWeight: 800, color: '#fff', letterSpacing: '-0.03em' }, children: config.welcomeTitle }), _jsx("p", { style: { margin: 0, fontSize: 14, color: 'rgba(255,255,255,0.85)', lineHeight: 1.6 }, children: config.welcomeSubtitle })] }), _jsx("div", { style: { flex: 1, overflowY: 'auto', padding: '0 16px 20px', marginTop: -28, display: 'flex', flexDirection: 'column', gap: 10 }, children: cards.map((card, i) => (_jsxs("button", { onClick: card.onClick, style: {
35
- width: '100%', background: '#fff', border: 'none', borderRadius: 14,
36
- padding: '18px 20px', display: 'flex', alignItems: 'center',
37
- justifyContent: 'space-between', cursor: 'pointer', textAlign: 'left',
38
- boxShadow: '0 2px 14px rgba(0,0,0,0.10)',
39
- animation: `cw-fadeUp 0.35s ease both`,
40
- animationDelay: `${i * 0.08}s`,
41
- transition: 'transform 0.15s, box-shadow 0.15s',
42
- }, onMouseEnter: e => {
43
- e.currentTarget.style.transform = 'translateY(-2px)';
44
- e.currentTarget.style.boxShadow = '0 8px 24px rgba(0,0,0,0.14)';
45
- }, onMouseLeave: e => {
46
- e.currentTarget.style.transform = 'translateY(0)';
47
- e.currentTarget.style.boxShadow = '0 2px 14px rgba(0,0,0,0.10)';
48
- }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 14 }, children: [_jsx("div", { style: {
49
- width: 44, height: 44, borderRadius: 12,
50
- backgroundColor: `${config.primaryColor}14`,
51
- display: 'flex', alignItems: 'center', justifyContent: 'center',
52
- fontSize: 20, flexShrink: 0,
53
- }, children: card.icon }), _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 700, fontSize: 15, color: '#1a2332', marginBottom: 2 }, children: card.title }), _jsx("div", { style: { fontSize: 12, color: '#7b8fa1' }, children: card.subtitle })] })] }), _jsx(SendArrow, { color: config.primaryColor })] }, card.key))) })] }));
20
+ padding: '14px 16px 10px',
21
+ display: 'flex',
22
+ alignItems: 'center',
23
+ gap: 12,
24
+ background: '#fff',
25
+ borderBottom: '1px solid #eef0f5',
26
+ }, children: _jsxs("button", { type: "button", "aria-label": "Open menu", onClick: () => setMenuOpen(true), style: {
27
+ width: 40,
28
+ height: 40,
29
+ borderRadius: 10,
30
+ border: 'none',
31
+ background: '#f1f5f9',
32
+ cursor: 'pointer',
33
+ display: 'flex',
34
+ flexDirection: 'column',
35
+ alignItems: 'center',
36
+ justifyContent: 'center',
37
+ gap: 5,
38
+ flexShrink: 0,
39
+ }, children: [_jsx("span", { style: { width: 18, height: 2, background: '#334155', borderRadius: 1 } }), _jsx("span", { style: { width: 18, height: 2, background: '#334155', borderRadius: 1 } }), _jsx("span", { style: { width: 18, height: 2, background: '#334155', borderRadius: 1 } })] }) }), _jsxs("div", { className: "cw-scroll", style: { flex: 1, overflowY: 'auto', padding: '20px 18px 28px' }, children: [_jsx("h1", { style: {
40
+ margin: '0 0 8px',
41
+ fontSize: 24,
42
+ fontWeight: 800,
43
+ color: '#0f172a',
44
+ letterSpacing: '-0.03em',
45
+ lineHeight: 1.2,
46
+ }, children: config.welcomeTitle }), _jsx("p", { style: { margin: '0 0 28px', fontSize: 14, color: '#64748b', lineHeight: 1.55 }, children: config.welcomeSubtitle }), _jsx("h2", { style: { margin: '0 0 12px', fontSize: 15, fontWeight: 800, color: '#0f172a' }, children: "Continue Conversations" }), _jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 28 }, children: continueItems.length > 0 ? (continueItems.map(t => (_jsx("button", { type: "button", onClick: () => onNavigate('ticket'), style: {
47
+ width: '100%',
48
+ textAlign: 'left',
49
+ padding: '14px 16px',
50
+ borderRadius: 14,
51
+ border: 'none',
52
+ background: '#e0f2fe',
53
+ color: '#0369a1',
54
+ fontSize: 14,
55
+ fontWeight: 600,
56
+ cursor: 'pointer',
57
+ boxShadow: '0 1px 3px rgba(0,0,0,0.06)',
58
+ }, children: t.title }, t.id)))) : (_jsxs(_Fragment, { children: [_jsx("div", { style: {
59
+ padding: '14px 16px',
60
+ borderRadius: 14,
61
+ background: '#e0f2fe',
62
+ color: '#64748b',
63
+ fontSize: 14,
64
+ fontWeight: 500,
65
+ }, children: "No open tickets yet" }), _jsx("div", { style: {
66
+ padding: '14px 16px',
67
+ borderRadius: 14,
68
+ background: '#e0f2fe',
69
+ color: '#64748b',
70
+ fontSize: 14,
71
+ fontWeight: 500,
72
+ }, children: "Start via Raise ticket below" })] })) }), _jsx("h2", { style: { margin: '0 0 12px', fontSize: 15, fontWeight: 800, color: '#0f172a' }, children: viewerIsDev ? 'Support tools' : 'Talk to our experts' }), showSupport && (_jsxs("button", { type: "button", onClick: () => onNavigate('support'), style: {
73
+ width: '100%',
74
+ display: 'flex',
75
+ alignItems: 'center',
76
+ justifyContent: 'center',
77
+ gap: 10,
78
+ padding: '14px 18px',
79
+ marginBottom: showChat ? 10 : 14,
80
+ borderRadius: 14,
81
+ border: 'none',
82
+ background: '#ede9fe',
83
+ color: '#5b21b6',
84
+ fontSize: 15,
85
+ fontWeight: 700,
86
+ cursor: 'pointer',
87
+ boxShadow: '0 2px 8px rgba(91,33,182,0.12)',
88
+ }, children: [_jsx("span", { style: { fontSize: 18 }, children: "\uD83D\uDC64" }), viewerIsDev ? 'Provide Support' : 'Support'] })), showChat && showSupport && (_jsx("button", { type: "button", onClick: () => onNavigate('conversation'), style: {
89
+ width: '100%',
90
+ padding: '12px 16px',
91
+ marginBottom: 14,
92
+ borderRadius: 12,
93
+ border: '1.5px solid #e9d5ff',
94
+ background: '#fff',
95
+ color: '#6d28d9',
96
+ fontSize: 14,
97
+ fontWeight: 600,
98
+ cursor: 'pointer',
99
+ }, children: viewerIsDev ? 'Chat with a developer' : 'New Conversation' })), showChat && !showSupport && (_jsxs("button", { type: "button", onClick: () => onNavigate('conversation'), style: {
100
+ width: '100%',
101
+ display: 'flex',
102
+ alignItems: 'center',
103
+ justifyContent: 'center',
104
+ gap: 10,
105
+ padding: '14px 18px',
106
+ marginBottom: 14,
107
+ borderRadius: 14,
108
+ border: 'none',
109
+ background: '#ede9fe',
110
+ color: '#5b21b6',
111
+ fontSize: 15,
112
+ fontWeight: 700,
113
+ cursor: 'pointer',
114
+ }, children: [_jsx("span", { style: { fontSize: 18 }, children: "\uD83D\uDCAC" }), "New Conversation"] })), _jsxs("div", { style: {
115
+ borderRadius: 18,
116
+ padding: '22px 20px 20px',
117
+ background: 'linear-gradient(145deg, #fce7f3 0%, #e9d5ff 45%, #ddd6fe 100%)',
118
+ position: 'relative',
119
+ overflow: 'hidden',
120
+ }, children: [_jsx("div", { style: { position: 'absolute', top: -20, right: -20, width: 100, height: 100, borderRadius: '50%', background: 'rgba(255,255,255,0.35)' } }), _jsx("p", { style: { margin: '0 0 16px', fontSize: 15, fontWeight: 700, color: '#4c1d95', lineHeight: 1.45, position: 'relative' }, children: "Need specialized help? Our teams are ready to assist you with any questions." }), _jsxs("button", { type: "button", onClick: handleCallUs, disabled: !config.supportPhone, style: {
121
+ display: 'inline-flex',
122
+ alignItems: 'center',
123
+ gap: 8,
124
+ padding: '10px 18px',
125
+ borderRadius: 12,
126
+ border: 'none',
127
+ background: config.supportPhone ? config.primaryColor : '#94a3b8',
128
+ color: '#fff',
129
+ fontSize: 14,
130
+ fontWeight: 700,
131
+ cursor: config.supportPhone ? 'pointer' : 'not-allowed',
132
+ position: 'relative',
133
+ }, children: [_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: _jsx("path", { d: "M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 10.8a19.79 19.79 0 01-3.07-8.68A2 2 0 012 0h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.09 7.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 14.92v2z", fill: "#fff" }) }), "Call Us"] })] })] })] }));
54
134
  };
55
- const SendArrow = ({ color }) => (_jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: _jsx("path", { d: "M5 12h14M12 5l7 7-7 7", stroke: color, strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round" }) }));
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import { ChatType, UserListContext } from '../types';
3
+ export interface SlideNavMenuProps {
4
+ open: boolean;
5
+ onClose: () => void;
6
+ primaryColor: string;
7
+ chatType: ChatType;
8
+ /** When `developer`, relabels the first two entries for staff */
9
+ viewerType?: 'user' | 'developer';
10
+ onSelect: (ctx: UserListContext | 'ticket') => void;
11
+ /** When set, shows “Back to home” at the bottom (e.g. chat screen) */
12
+ onBackHome?: () => void;
13
+ }
14
+ export declare const SlideNavMenu: React.FC<SlideNavMenuProps>;
@@ -0,0 +1,74 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ export const SlideNavMenu = ({ open, onClose, primaryColor, chatType, viewerType = 'user', onSelect, onBackHome, }) => {
3
+ const showSupport = chatType === 'SUPPORT' || chatType === 'BOTH';
4
+ const showChat = chatType === 'CHAT' || chatType === 'BOTH';
5
+ const isStaff = viewerType === 'developer';
6
+ const items = [
7
+ showSupport ? { key: 'support', icon: '🛠', title: isStaff ? 'Provide Support' : 'Need Support' } : null,
8
+ showChat ? { key: 'conversation', icon: '💬', title: isStaff ? 'Chat with developer' : 'New Conversation' } : null,
9
+ { key: 'ticket', icon: '🎫', title: 'Raise ticket' },
10
+ ];
11
+ if (!open)
12
+ return null;
13
+ return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", "aria-label": "Close menu", onClick: onClose, style: {
14
+ position: 'absolute',
15
+ inset: 0,
16
+ zIndex: 200,
17
+ background: 'rgba(15,23,42,0.45)',
18
+ border: 'none',
19
+ cursor: 'pointer',
20
+ animation: 'cw-fadeIn 0.2s ease',
21
+ } }), _jsx("style", { children: `@keyframes cw-fadeIn { from { opacity: 0; } to { opacity: 1; } }` }), _jsxs("nav", { style: {
22
+ position: 'absolute',
23
+ top: 0,
24
+ left: 0,
25
+ bottom: 0,
26
+ width: 'min(300px, 88%)',
27
+ zIndex: 210,
28
+ background: '#fff',
29
+ boxShadow: '8px 0 32px rgba(0,0,0,0.12)',
30
+ display: 'flex',
31
+ flexDirection: 'column',
32
+ padding: '20px 0 16px',
33
+ animation: 'cw-slideNavIn 0.28s cubic-bezier(0.22, 1, 0.36, 1)',
34
+ }, children: [_jsx("style", { children: `@keyframes cw-slideNavIn { from { transform: translateX(-100%); } to { transform: translateX(0); } }` }), _jsx("div", { style: { padding: '0 20px 16px', borderBottom: '1px solid #eef0f5' }, children: _jsx("p", { style: { margin: 0, fontSize: 13, fontWeight: 700, color: '#64748b', letterSpacing: '0.04em' }, children: "Menu" }) }), _jsx("div", { style: { flex: 1, overflowY: 'auto', padding: '12px 12px' }, children: items.filter(Boolean).map(item => {
35
+ const it = item;
36
+ return (_jsxs("button", { type: "button", onClick: () => {
37
+ onSelect(it.key);
38
+ onClose();
39
+ }, style: {
40
+ width: '100%',
41
+ display: 'flex',
42
+ alignItems: 'center',
43
+ gap: 12,
44
+ padding: '14px 14px',
45
+ marginBottom: 6,
46
+ border: 'none',
47
+ borderRadius: 12,
48
+ background: '#f8fafc',
49
+ cursor: 'pointer',
50
+ textAlign: 'left',
51
+ fontSize: 15,
52
+ fontWeight: 600,
53
+ color: '#1e293b',
54
+ transition: 'background 0.15s',
55
+ }, onMouseEnter: e => {
56
+ e.currentTarget.style.background = `${primaryColor}12`;
57
+ }, onMouseLeave: e => {
58
+ e.currentTarget.style.background = '#f8fafc';
59
+ }, children: [_jsx("span", { style: { fontSize: 20 }, children: it.icon }), it.title] }, it.key));
60
+ }) }), onBackHome && (_jsx("div", { style: { padding: '0 12px', borderTop: '1px solid #eef0f5', paddingTop: 12 }, children: _jsx("button", { type: "button", onClick: () => {
61
+ onBackHome();
62
+ onClose();
63
+ }, style: {
64
+ width: '100%',
65
+ padding: '12px 14px',
66
+ border: '1.5px solid #e2e8f0',
67
+ borderRadius: 12,
68
+ background: '#fff',
69
+ fontSize: 14,
70
+ fontWeight: 600,
71
+ color: '#475569',
72
+ cursor: 'pointer',
73
+ }, children: "\u2190 Back to home" }) }))] })] }));
74
+ };
@@ -4,6 +4,8 @@ interface UserListScreenProps {
4
4
  context: UserListContext;
5
5
  users: ChatUser[];
6
6
  primaryColor: string;
7
+ /** `developer` = staff using the widget (lists customers vs teammates) */
8
+ viewerType?: 'user' | 'developer';
7
9
  onBack: () => void;
8
10
  onSelectUser: (user: ChatUser) => void;
9
11
  }
@@ -1,8 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { avatarColor, initials } from '../../utils/chat';
3
- export const UserListScreen = ({ context, users, primaryColor, onBack, onSelectUser, }) => {
4
- const title = context === 'support' ? 'Need Support' : 'New Conversation';
5
- const subtitle = context === 'support' ? 'Choose a support agent' : 'Choose a colleague';
3
+ export const UserListScreen = ({ context, users, primaryColor, viewerType = 'user', onBack, onSelectUser, }) => {
4
+ const isStaff = viewerType === 'developer';
5
+ const title = context === 'support'
6
+ ? (isStaff ? 'Provide Support' : 'Need Support')
7
+ : (isStaff ? 'Developers' : 'New Conversation');
8
+ const subtitle = context === 'support'
9
+ ? (isStaff ? 'All chat users — choose who to help' : 'Choose a support agent')
10
+ : (isStaff ? 'Chat with another developer or coordinate handoff' : 'Choose a colleague');
6
11
  return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', height: '100%', animation: 'cw-slideIn 0.22s ease' }, children: [_jsxs("div", { style: { background: `linear-gradient(135deg,${primaryColor},${primaryColor}cc)`, padding: '14px 18px', display: 'flex', alignItems: 'center', gap: 12, flexShrink: 0 }, children: [_jsx(BackBtn, { onClick: onBack }), _jsxs("div", { children: [_jsx("div", { style: { fontWeight: 700, fontSize: 16, color: '#fff' }, children: title }), _jsx("div", { style: { fontSize: 12, color: 'rgba(255,255,255,0.8)' }, children: subtitle })] })] }), _jsx("div", { style: { flex: 1, overflowY: 'auto' }, children: users.length === 0 ? (_jsx(Empty, {})) : users.map((u, i) => (_jsxs("button", { onClick: () => onSelectUser(u), style: {
7
12
  width: '100%', padding: '13px 18px', display: 'flex',
8
13
  alignItems: 'center', gap: 13, background: 'transparent',
package/dist/index.d.ts CHANGED
@@ -9,6 +9,7 @@ export { CallScreen } from './components/CallScreen';
9
9
  export { MaintenanceView } from './components/MaintenanceView';
10
10
  export { BottomTabs } from './components/Tabs/BottomTabs';
11
11
  export { EmojiPicker } from './components/EmojiPicker';
12
+ export { SlideNavMenu } from './components/SlideNavMenu';
12
13
  export { useChat } from './hooks/useChat';
13
14
  export { useWebRTC } from './hooks/useWebRTC';
14
15
  export { useRemoteConfig } from './hooks/useRemoteConfig';
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ export { CallScreen } from './components/CallScreen';
9
9
  export { MaintenanceView } from './components/MaintenanceView';
10
10
  export { BottomTabs } from './components/Tabs/BottomTabs';
11
11
  export { EmojiPicker } from './components/EmojiPicker';
12
+ export { SlideNavMenu } from './components/SlideNavMenu';
12
13
  export { useChat } from './hooks/useChat';
13
14
  export { useWebRTC } from './hooks/useWebRTC';
14
15
  export { useRemoteConfig } from './hooks/useRemoteConfig';
@@ -8,6 +8,21 @@ export interface WidgetConfig {
8
8
  buttonPosition: 'bottom-right' | 'bottom-left';
9
9
  welcomeTitle: string;
10
10
  welcomeSubtitle: string;
11
+ /** Shown in footer (e.g. branch / location name) */
12
+ branch?: string;
13
+ /** Optional label above branch (e.g. "Answers by") */
14
+ footerPoweredBy?: string;
15
+ /** Shown on home “Call Us” (tel: link) */
16
+ supportPhone?: string;
17
+ /**
18
+ * Who is using the widget. `developer` = support staff: “Need Support” becomes “Provide Support”
19
+ * and lists customers; “New Conversation” lists other developers (excl. viewerUid).
20
+ */
21
+ viewerType?: 'user' | 'developer';
22
+ /** Current user id when viewerType is developer — excluded from developer pick lists */
23
+ viewerUid?: string;
24
+ /** Display name for transfer notes (optional) */
25
+ viewerName?: string;
11
26
  allowVoiceMessage: boolean;
12
27
  allowAttachment: boolean;
13
28
  allowEmoji: boolean;
@@ -54,7 +69,11 @@ export interface ChatMessage {
54
69
  status: 'sent' | 'delivered' | 'read';
55
70
  attachmentName?: string;
56
71
  attachmentSize?: string;
72
+ /** Blob URL for attachment download (local send) */
73
+ attachmentUrl?: string;
57
74
  voiceDuration?: number;
75
+ /** Blob URL for in-bubble audio playback (local recording) */
76
+ voiceUrl?: string;
58
77
  }
59
78
  export interface Ticket {
60
79
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ajaxter-chat",
3
- "version": "3.0.4",
3
+ "version": "3.0.6",
4
4
  "description": "Drawer-based chat widget with support chat, tickets, WebRTC calling, voice messages, block list, and transcript download.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,6 +9,12 @@
9
9
  "buttonPosition": "bottom-right",
10
10
  "welcomeTitle": "Hi there 👋",
11
11
  "welcomeSubtitle": "Need help? Start a conversation:",
12
+ "viewerType": "user",
13
+ "viewerUid": "",
14
+ "viewerName": "",
15
+ "branch": "Mumbai HQ",
16
+ "footerPoweredBy": "Answers by",
17
+ "supportPhone": "+919876543210",
12
18
  "allowVoiceMessage": true,
13
19
  "allowAttachment": true,
14
20
  "allowEmoji": true,