@botonic/react
Version:
Build Chatbots using React
312 lines (278 loc) • 9.42 kB
JSX
import { INPUT, isFacebook, isWhatsapp } from '@botonic/core'
import React, { useContext } from 'react'
import { RequestContext } from '../../contexts'
import { WhatsappButtonList, WhatsappCTAUrlButton } from '..'
import { Text } from '../text'
import { MultichannelFacebook } from './facebook/facebook'
import { MultichannelButton } from './multichannel-button'
import { MultichannelContext } from './multichannel-context'
import {
buttonTypes,
elementHasPostback,
elementHasUrl,
elementHasWebview,
getButtonType,
getMultichannelButtons,
getMultichannelReplies,
} from './multichannel-utils'
import {
DEFAULT_WHATSAPP_MAX_BUTTON_SEPARATOR,
MENU_BUTTON_WHATSAPP_BUTTON_LIST,
MULTICHANNEL_WHATSAPP_PROPS,
WHATSAPP_LIST_MAX_BUTTONS,
WHATSAPP_MAX_BUTTONS,
} from './whatsapp/constants'
import { convertToMarkdownMeta } from './whatsapp/markdown-meta'
export const MultichannelText = props => {
const requestContext = useContext(RequestContext)
const multichannelContext = useContext(MultichannelContext)
const postbackButtonsAsText = props.buttonsAsText ?? true
let elements = []
const getText = children => {
children = Array.isArray(children) ? children : [children]
const text = children
.filter(e => e && !e.type)
.map(e => {
if (Array.isArray(e)) return getText(e)
else return String(e)
})
.join('')
if (text == undefined) {
return []
}
return [text].filter(t => t !== '') // to avoid line breaks when the carousel doesn't have title or subtitle
}
const getButtonsAndReplies = () =>
[].concat(
getMultichannelButtons(React.Children.toArray(props.children)),
getMultichannelReplies(React.Children.toArray(props.children))
)
const getWhatsappButtons = () => {
const postbackButtons = []
const urlButtons = []
const webviewButtons = []
for (const button of getButtonsAndReplies()) {
if (elementHasUrl(button)) urlButtons.push(button)
else if (elementHasWebview(button)) webviewButtons.push(button)
else if (elementHasPostback(button)) postbackButtons.push(button)
}
return { postbackButtons, urlButtons, webviewButtons }
}
const getDefaultIndex = () => {
if (props.indexMode == undefined) {
return undefined
}
if (multichannelContext.currentIndex != null) {
return multichannelContext.currentIndex
}
return props.indexMode === 'letter' ? 'a' : 1
}
const regenerateMultichannelButtons = (newLineFirstButton = true) => {
const generator = (multichannelButton, i) => {
const type = getButtonType(multichannelButton)
const asText =
type === buttonTypes.POSTBACK ? postbackButtonsAsText : true
const newline =
multichannelContext.messageSeparator == null &&
!newLineFirstButton &&
i === 0
? ''
: '\n'
return (
<MultichannelButton
key={`${type}${i}`}
newline={newline}
asText={asText}
{...multichannelButton.props}
>
{multichannelButton.props.children}
</MultichannelButton>
)
}
return generator
}
const splitInWhatsappListButtons = postbackButtons => {
const messages = []
for (
let i = 0;
i < postbackButtons.length;
i += WHATSAPP_LIST_MAX_BUTTONS
) {
messages.push(postbackButtons.slice(i, i + WHATSAPP_LIST_MAX_BUTTONS))
}
return messages
}
if (isWhatsapp(requestContext.session)) {
const texts = getText(props.children)
const { postbackButtons, urlButtons, webviewButtons } = getWhatsappButtons()
const textElements = texts.map(text => {
const textWithMarkdown = convertToMarkdownMeta(text)
return (props.newline || '') + textWithMarkdown
})
const webviewButtonElements = webviewButtons.map(
regenerateMultichannelButtons(false)
)
const buttonsTextSeparator =
props.buttonsTextSeparator || DEFAULT_WHATSAPP_MAX_BUTTON_SEPARATOR
const exceedWhatsAppMaxButtonNumber =
!postbackButtonsAsText && postbackButtons.length > WHATSAPP_MAX_BUTTONS
if (exceedWhatsAppMaxButtonNumber) {
const menuButtonTextWhatsappList =
props.menuButtonTextWhatsappList || MENU_BUTTON_WHATSAPP_BUTTON_LIST
const urlButtonElements = urlButtons.map(
regenerateMultichannelButtons(!!texts.length)
)
const postbackButtonElements = postbackButtons.map(
regenerateMultichannelButtons(!!texts.length || !!urlButtons.length)
)
const messagesPostbackButtonList = splitInWhatsappListButtons(
postbackButtonElements
)
const messages = messagesPostbackButtonList.map(
(postbackButtons, index) => {
if (postbackButtons.length <= WHATSAPP_MAX_BUTTONS) {
return {
type: INPUT.TEXT,
children: [...buttonsTextSeparator, ...postbackButtons],
}
}
const rows = postbackButtons.map(postbackButton => {
const row = {
id: postbackButton.props.path
? `__PATH_PAYLOAD__${postbackButton.props.path}`
: postbackButton.props.payload,
title: postbackButton.props.children,
}
return row
})
const whatsAppButtonListProps = {
body: index === 0 ? texts.join('') : buttonsTextSeparator,
button: menuButtonTextWhatsappList,
sections: [{ rows }],
}
return {
type: INPUT.WHATSAPP_BUTTON_LIST,
props: whatsAppButtonListProps,
}
}
)
const messageWithUrlButtonElements = (
<Text
key={`msg-with-url-button`}
{...MULTICHANNEL_WHATSAPP_PROPS}
{...props}
>
{urlButtonElements}
</Text>
)
const messageWithWebviewButtonElements = (
<Text
key={`msg-with-webview-button`}
{...MULTICHANNEL_WHATSAPP_PROPS}
{...props}
>
{buttonsTextSeparator}
{webviewButtonElements}
</Text>
)
return (
<>
{messages.map((message, i) => {
if (message.type === INPUT.WHATSAPP_BUTTON_LIST)
return (
<WhatsappButtonList
key={`msg-${i}-whatsapp-list`}
{...message.props}
/>
)
return (
<Text
key={`msg-${i}-with-postback-buttons`}
{...MULTICHANNEL_WHATSAPP_PROPS}
{...props}
>
{message.children}
</Text>
)
})}
{urlButtonElements.length > 0 && messageWithUrlButtonElements}
{webviewButtonElements.length > 0 && messageWithWebviewButtonElements}
</>
)
}
multichannelContext.currentIndex = getDefaultIndex()
const postbackButtonElements = postbackButtons.map(
regenerateMultichannelButtons(!!texts.length)
)
const urlButtonElements = urlButtons.map(
regenerateMultichannelButtons(!!texts.length || !!postbackButtons.length)
)
elements = [].concat(
[...textElements],
[...postbackButtonElements],
[...urlButtonElements]
)
if (postbackButtonElements.length === 0) {
// Use texts[0] instead of textElements[0] as a body to avoid apply the markdownMeta function twice.
// If the markdownMeta function is applied twice, bold is replaced by italics.
const body = texts[0] || ''
if (urlButtonElements.length === 1) {
return (
<WhatsappCTAUrlButton
body={body}
displayText={urlButtonElements[0].props.children}
url={urlButtonElements[0].props.url}
/>
)
}
if (webviewButtonElements.length === 1) {
return (
<WhatsappCTAUrlButton
body={body}
displayText={webviewButtonElements[0].props.children}
webview={webviewButtonElements[0].props.webview}
params={webviewButtonElements[0].props.params}
/>
)
}
}
if (multichannelContext.messageSeparator != null) {
return elements
}
const messages = [
<Text key={0} {...MULTICHANNEL_WHATSAPP_PROPS} {...props}>
{elements}
</Text>,
]
if (webviewButtonElements.length) {
messages.push(
<Text key={1} {...MULTICHANNEL_WHATSAPP_PROPS} {...props}>
{buttonsTextSeparator}
{webviewButtonElements}
</Text>
)
}
return <>{messages}</>
}
if (isFacebook(requestContext.session)) {
const text = getText(props.children)
const multichannelFacebook = new MultichannelFacebook()
const { texts, propsLastText, propsWithoutChildren } =
multichannelFacebook.convertText(props, text[0])
const [lastText, ...buttonsAndReplies] = propsLastText.children
return (
<>
{texts?.map((message, i) => (
<Text key={i} {...propsWithoutChildren}>
{convertToMarkdownMeta(message)}
</Text>
))}
<Text {...propsLastText}>
{convertToMarkdownMeta(lastText)}
{buttonsAndReplies}
</Text>
</>
)
}
return <Text {...props}>{props.children}</Text>
}