Skip to content

Commit

Permalink
feat(react): multichannel Facebook text converter
Browse files Browse the repository at this point in the history
  • Loading branch information
AinaVendrell committed Nov 26, 2020
1 parent 6a95efa commit dfe2b33
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export const MAX_CHARACTERS_FACEBOOK = 640

export class MultichannelFacebook {
constructor() {}

convertText(props, originalText) {
if (originalText.length > MAX_CHARACTERS_FACEBOOK) {
const texts = this.splitText(originalText)
const lastText = texts.pop()
const { propsLastText, propsWithoutChildren } = this.getNewProps(
props,
lastText
)
return { texts, propsLastText, propsWithoutChildren }
}
return { propsLastText: props }
}

splitText(originalText) {
const lines = originalText.split('\n')
const initialText = lines.shift()
const texts = [initialText]
let index = 0
lines.forEach(currentText => {
if (texts[index].length + currentText.length > MAX_CHARACTERS_FACEBOOK) {
index++
texts.push(currentText)
} else {
texts[index] = texts[index].concat('\n', currentText)
}
})
return texts
}

// modifies the props to keep the children only for the last text message, this message will be the one with buttons and replies
getNewProps(props, lastText) {
const propsLastText = { ...props }
propsLastText.children = [lastText]
if (Array.isArray(props.children)) {
props.children
.filter(e => e.type)
.forEach(e => propsLastText.children.push(e))
}
const propsWithoutChildren = { ...props }
delete propsWithoutChildren.children
return { propsLastText, propsWithoutChildren }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import React, { useContext } from 'react'

import { RequestContext } from '../../contexts'
import { Text } from '../text'
import { MultichannelFacebook } from './facebook/facebook'
import { MultichannelButton } from './multichannel-button'
import { MultichannelContext } from './multichannel-context'
import {
elementHasPostback,
elementHasUrl,
getMultichannelButtons,
getMultichannelReplies,
isFacebook,
isMultichannelButton,
isMultichannelReply,
isWhatsapp,
Expand All @@ -21,17 +23,19 @@ export const MultichannelText = props => {

let elements = []

const getText = () => {
let text = undefined
if (typeof props.children == 'string') {
text = props.children
} else if (Array.isArray(props.children)) {
text = props.children[0]
}
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]
return [text].filter(t => t !== '') // to avoid line breaks when the carousel doesn't have title or subtitle
}

const getButtonsAndReplies = () =>
Expand Down Expand Up @@ -89,6 +93,26 @@ export const MultichannelText = props => {
{elements}
</Text>
)
}
if (isFacebook(requestContext)) {
const text = getText(props.children)
const multichannelFacebook = new MultichannelFacebook()
const {
texts,
propsLastText,
propsWithoutChildren,
} = multichannelFacebook.convertText(props, text[0])
return (
<>
{texts &&
texts.map((e, i) => (
<Text key={i} {...propsWithoutChildren}>
{e}
</Text>
))}
<Text {...propsLastText}>{propsLastText.children}</Text>
</>
)
} else {
return <Text {...props}>{props.children}</Text>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,8 @@ export const isWhatsapp = context =>
context.session &&
context.session.user &&
context.session.user.provider == Providers.Messaging.WHATSAPP

export const isFacebook = context =>
context.session &&
context.session.user &&
context.session.user.provider == Providers.Messaging.FACEBOOK
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useContext } from 'react'

import { COMPONENT_TYPE } from '../../constants'
import { RequestContext } from '../../contexts'
import { Text } from '../text'
import { deepMapWithIndex } from './deepmap-with-index'
Expand All @@ -8,31 +9,48 @@ import { MultichannelCarousel } from './multichannel-carousel'
import { MultichannelContext } from './multichannel-context'
import { MultichannelReply } from './multichannel-reply'
import { MultichannelText } from './multichannel-text'
import { isWhatsapp, MULTICHANNEL_WHATSAPP_PROPS } from './multichannel-utils'
import {
isFacebook,
isWhatsapp,
MULTICHANNEL_WHATSAPP_PROPS,
} from './multichannel-utils'

export const Multichannel = props => {
const requestContext = useContext(RequestContext)

if (!isWhatsapp(requestContext)) {
if (!isWhatsapp(requestContext) && !isFacebook(requestContext)) {
return props.children
}
if (isFacebook(requestContext)) {
const newChildren = deepMapWithIndex(props.children, child => {
if (child && child.type && child.type.name === COMPONENT_TYPE.TEXT) {
return (
<MultichannelText {...child.props} key={child.key}>
{child.props.children}
</MultichannelText>
)
}
return child
})
return newChildren
}

let newChildren = deepMapWithIndex(props.children, (child, index) => {
if (child && child.type && child.type.name === 'Button') {
if (child && child.type && child.type.name === COMPONENT_TYPE.BUTTON) {
return (
<MultichannelButton {...child.props} key={child.key}>
{child.props.children}
</MultichannelButton>
)
}
if (child && child.type && child.type.name === 'Reply') {
if (child && child.type && child.type.name === COMPONENT_TYPE.REPLY) {
return (
<MultichannelReply {...child.props} key={child.key}>
{child.props.children}
</MultichannelReply>
)
}
if (child && child.type && child.type.name === 'Text') {
if (child && child.type && child.type.name === COMPONENT_TYPE.TEXT) {
return (
<MultichannelText
{...child.props}
Expand All @@ -45,7 +63,7 @@ export const Multichannel = props => {
</MultichannelText>
)
}
if (child && child.type && child.type.name === 'Carousel') {
if (child && child.type && child.type.name === COMPONENT_TYPE.CAROUSEL) {
return (
<MultichannelCarousel
{...child.props}
Expand Down
7 changes: 7 additions & 0 deletions packages/botonic-react/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,10 @@ export const ROLES = {
VIDEO_MESSAGE: 'video-message',
DOCUMENT_MESSAGE: 'document-message',
}

export const COMPONENT_TYPE = {
TEXT: 'Text',
BUTTON: 'Button',
REPLY: 'Reply',
CAROUSEL: 'Carousel',
}
2 changes: 1 addition & 1 deletion packages/botonic-react/src/util/error-boundary.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { isNode } from '@botonic/core'
import React from 'react'

import { Text } from '../components'
import { Text } from '../components/text'

/**
* Replaces crashed children with the provided fallback component.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { MultichannelFacebook } from '../../../../src/components/multichannel/facebook/facebook'

describe('Multichannel Facebook text converter', () => {
const multichannelFacebook = new MultichannelFacebook()
const button = {
key: '1',
props: {
children: 'Children',
url: 'https://botonic.io/',
},
type: 'Button',
}

const shortText =
'Continetur devenietur id ex denegassem ponderibus imaginatio patrocinio.'

const longText =
'Continetur devenietur id ex denegassem ponderibus imaginatio patrocinio. Falso et se re entis quasi im. Ipsum ne opera at potui ipsis mo. Hic advertisse manifestum uti blandisque objectivae imaginaria assignetur. Re dare dari data ad ex isti ad meas quin. Aeque neque at multo coeco ac. Ullius habens longum necdum negans si ut.\nIm quia odor scio ea. Habet hic duo operi cum fas ullis. Dicunt at attigi re mentem eo longum ne creari videor. Credendam incurrant simplicia tantumque desumptam to de. Rationes ad re quanquam sensisse ac frigoris. Summam eo gustum seriem in vi. Credimus sorbonae ad ac in de cogitare.\nRem summum ope eae notatu sicuti calida causas. Machinam assidere circulum in facultas ab. Haberem volebam tur verarum mallent etc una seu referam ignotae. Heri sic rum ante sine quas fas modi. Nos creasse pendere crescit angelos etc. Is ii veat se suae admi nisi data meas an. Ei probent enatare et naturam. Igni bere meum at vi meae ob ente foco. Progressum expectanti deo advertebam confirmari objectivam age tractandae vix dem. Assentiar im singulari examinare voluntate inhaereat de si colligere me.\nAusi ente me idem utor adeo ob ille. Hominem inferri hos effugio una vel istarum. Gnum ii iste amen ab visu atra. Deo sic olim sese amen. Im co vereor opinio certas. Et legendo caetera disputo saporis exhibet ei. Propositio via explicetur ibi est designabam necessario quo.'

const firstTexts = [
'Continetur devenietur id ex denegassem ponderibus imaginatio patrocinio. Falso et se re entis quasi im. Ipsum ne opera at potui ipsis mo. Hic advertisse manifestum uti blandisque objectivae imaginaria assignetur. Re dare dari data ad ex isti ad meas quin. Aeque neque at multo coeco ac. Ullius habens longum necdum negans si ut.\nIm quia odor scio ea. Habet hic duo operi cum fas ullis. Dicunt at attigi re mentem eo longum ne creari videor. Credendam incurrant simplicia tantumque desumptam to de. Rationes ad re quanquam sensisse ac frigoris. Summam eo gustum seriem in vi. Credimus sorbonae ad ac in de cogitare.',
'Rem summum ope eae notatu sicuti calida causas. Machinam assidere circulum in facultas ab. Haberem volebam tur verarum mallent etc una seu referam ignotae. Heri sic rum ante sine quas fas modi. Nos creasse pendere crescit angelos etc. Is ii veat se suae admi nisi data meas an. Ei probent enatare et naturam. Igni bere meum at vi meae ob ente foco. Progressum expectanti deo advertebam confirmari objectivam age tractandae vix dem. Assentiar im singulari examinare voluntate inhaereat de si colligere me.',
]

const lastText =
'Ausi ente me idem utor adeo ob ille. Hominem inferri hos effugio una vel istarum. Gnum ii iste amen ab visu atra. Deo sic olim sese amen. Im co vereor opinio certas. Et legendo caetera disputo saporis exhibet ei. Propositio via explicetur ibi est designabam necessario quo.'

const expectedPropsWithoutChildren = {
delay: '1',
}

test('TEST: Splits long text', () => {
const props = {
children: longText,
delay: '1',
}
const {
texts,
propsLastText,
propsWithoutChildren,
} = multichannelFacebook.convertText(props, longText)

const expectedPropsLastText = {
children: [lastText],
delay: '1',
}

expect(texts).toEqual(firstTexts)
expect(propsLastText).toEqual(expectedPropsLastText)
expect(propsWithoutChildren).toEqual(expectedPropsWithoutChildren)
})

test('TEST: Splits long text with buttons', () => {
const props = {
children: [longText, button],
delay: '1',
}
const {
texts,
propsLastText,
propsWithoutChildren,
} = multichannelFacebook.convertText(props, longText)

const expectedPropsLastText = {
children: [lastText, button],
delay: '1',
}

expect(texts).toEqual(firstTexts)
expect(propsLastText).toEqual(expectedPropsLastText)
expect(propsWithoutChildren).toEqual(expectedPropsWithoutChildren)
})

test('TEST: Does not split short text', () => {
const props = {
children: shortText,
delay: '1',
}
const {
texts,
propsLastText,
propsWithoutChildren,
} = multichannelFacebook.convertText(props, shortText)
expect(texts).toEqual(undefined)
expect(propsLastText).toEqual(props)
expect(propsWithoutChildren).toEqual(undefined)
})

test('TEST: Does not split short text with buttons', () => {
const props = {
children: [shortText, button],
delay: '1',
}
const {
texts,
propsLastText,
propsWithoutChildren,
} = multichannelFacebook.convertText(props, shortText)
expect(texts).toEqual(undefined)
expect(propsLastText).toEqual(props)
expect(propsWithoutChildren).toEqual(undefined)
})
})

0 comments on commit dfe2b33

Please sign in to comment.