UNPKG

@botonic/react

Version:

Build Chatbots using React

184 lines 10.2 kB
import { __rest } from "tslib"; import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { INPUT, isBrowser } from '@botonic/core'; import React, { useContext, useEffect, useState } from 'react'; import Fade from 'react-reveal/Fade'; import styled from 'styled-components'; import { v4 as uuidv4 } from 'uuid'; import { COLORS, SENDERS, WEBCHAT } from '../constants'; import { RequestContext, WebchatContext } from '../contexts'; import { isDev, resolveImage } from '../util/environment'; import { ConditionalWrapper, renderComponent } from '../util/react'; import { Button } from './button'; import { ButtonsDisabler } from './buttons-disabler'; import { getMarkdownStyle, renderLinks, renderMarkdown } from './markdown'; import { Reply } from './reply'; import { MessageTimestamp, resolveMessageTimestamps } from './timestamps'; const MessageContainer = styled.div ` display: flex; justify-content: ${props => (props.isfromuser ? 'flex-end' : 'flex-start')}; position: relative; padding: 0px 6px; `; const BotMessageImageContainer = styled.div ` width: 28px; padding: 12px 4px; flex: none; display: flex; align-items: center; justify-content: center; `; const Blob = styled.div ` position: relative; margin: 8px; border-radius: 8px; background-color: ${props => props.bgcolor}; color: ${props => props.color}; max-width: ${props => props.blob ? props.blobwidth ? props.blobwidth : '60%' : 'calc(100% - 16px)'}; `; const BlobText = styled.div ` padding: ${props => (props.blob ? '8px 12px' : '0px')}; display: flex; flex-direction: column; white-space: pre-line; ${props => props.markdownstyle} `; const BlobTickContainer = styled.div ` position: absolute; box-sizing: border-box; height: 100%; padding: 18px 0px 18px 0px; display: flex; top: 0; align-items: center; `; const BlobTick = styled.div ` position: relative; margin: -${props => props.pointerSize}px 0px; border: ${props => props.pointerSize}px solid ${COLORS.TRANSPARENT}; `; export const Message = props => { const { defaultTyping, defaultDelay } = useContext(RequestContext); let { type = '', blob = true, from = SENDERS.bot, delay = defaultDelay, typing = defaultTyping, children, enabletimestamps = props.enabletimestamps || props.enableTimestamps, json, style, imagestyle = props.imagestyle || props.imageStyle } = props, otherProps = __rest(props, ["type", "blob", "from", "delay", "typing", "children", "enabletimestamps", "json", "style", "imagestyle"]); const isFromUser = from === SENDERS.user; const isFromBot = from === SENDERS.bot; const markdown = props.markdown; const { webchatState, addMessage, updateReplies, getThemeProperty } = useContext(WebchatContext); const [state, setState] = useState({ id: props.id || uuidv4(), }); const [disabled, setDisabled] = useState(false); children = ButtonsDisabler.updateChildrenButtons(children, { parentId: state.id, disabled, setDisabled, }); const replies = React.Children.toArray(children).filter(e => e.type === Reply); const buttons = React.Children.toArray(children).filter(e => e.type === Button); let textChildren = React.Children.toArray(children).filter(e => ![Button, Reply].includes(e.type)); if (isFromUser) textChildren = textChildren.map(e => typeof e === 'string' ? renderLinks(e) : e); const { timestampsEnabled, getFormattedTimestamp, timestampStyle } = resolveMessageTimestamps(getThemeProperty, enabletimestamps); const getEnvAck = () => { if (isDev) return 1; if (!isFromUser) return 1; if (props.ack !== undefined) return props.ack; return 0; }; const ack = getEnvAck(); useEffect(() => { if (isBrowser()) { const decomposedChildren = json; const message = { id: state.id, type, data: decomposedChildren ? decomposedChildren : textChildren, timestamp: props.timestamp || getFormattedTimestamp, markdown, from, buttons: buttons.map(b => (Object.assign({ parentId: b.props.parentId, payload: b.props.payload, path: b.props.path, url: b.props.url, target: b.props.target, webview: b.props.webview && String(b.props.webview), title: b.props.children }, ButtonsDisabler.withDisabledProps(b.props)))), delay, typing, replies: replies.map(r => ({ payload: r.props.payload, path: r.props.path, url: r.props.url, text: r.props.children, })), display: delay + typing == 0, customTypeName: decomposedChildren.customTypeName, ack: ack, }; addMessage(message); } }, []); useEffect(() => { if (isBrowser()) { const msg = webchatState.messagesJSON.find(m => m.id === state.id); if (msg && msg.display && webchatState.messagesJSON.filter(m => !m.display).length == 0) { updateReplies(replies); } } }, [webchatState.messagesJSON]); const brandColor = getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.brandColor, COLORS.BOTONIC_BLUE); const getBgColor = () => { if (!blob) return COLORS.TRANSPARENT; if (isFromUser) { return getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.userMessageBackground, brandColor); } return getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.botMessageBackground, COLORS.SEASHELL_WHITE); }; const getMessageStyle = () => isFromBot ? getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.botMessageStyle) : getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.userMessageStyle); const hasBlobTick = () => getThemeProperty(`message.${from}.blobTick`, true); const renderBrowser = () => { const m = webchatState.messagesJSON.find(m => m.id === state.id); if (!m || !m.display) return _jsx(_Fragment, {}); const getBlobTick = pointerSize => { // to add a border to the blobTick we need to create two triangles and overlap them // that is why the color depends on the pointerSize // https://developpaper.com/realization-code-of-css-drawing-triangle-border-method/ const color = pointerSize == 5 ? getBgColor() : getThemeProperty(`message.${from}.style.borderColor`, COLORS.TRANSPARENT); const containerStyle = Object.assign({}, getThemeProperty(`message.${from}.blobTickStyle`)); const blobTickStyle = {}; if (isFromUser) { containerStyle.right = 0; containerStyle.marginRight = -pointerSize; blobTickStyle.borderRight = 0; blobTickStyle.borderLeftColor = color; } else { containerStyle.left = 0; containerStyle.marginLeft = -pointerSize; blobTickStyle.borderLeft = 0; blobTickStyle.borderRightColor = color; } return (_jsx(BlobTickContainer, Object.assign({ style: containerStyle }, { children: _jsx(BlobTick, { pointerSize: pointerSize, style: blobTickStyle }) }))); }; const BotMessageImage = getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.botMessageImage, getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.brandImage, WEBCHAT.DEFAULTS.LOGO)); const animationsEnabled = getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.enableAnimations, true); const resolveCustomTypeName = () => isFromBot && type === INPUT.CUSTOM ? ` ${m.customTypeName}` : ''; const className = `${type}-${from}${resolveCustomTypeName()}`; return (_jsx(ConditionalWrapper, Object.assign({ condition: animationsEnabled, wrapper: children => _jsx(Fade, { children: children }) }, { children: _jsxs(_Fragment, { children: [_jsxs(MessageContainer, Object.assign({ isfromuser: isFromUser, style: Object.assign({}, getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.messageStyle)) }, { children: [isFromBot && BotMessageImage && (_jsx(BotMessageImageContainer, Object.assign({ style: Object.assign(Object.assign({}, getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.botMessageImageStyle)), imagestyle) }, { children: _jsx("img", { style: { width: '100%' }, src: resolveImage(BotMessageImage) }) }))), _jsxs(Blob, Object.assign({ className: className, bgcolor: getBgColor(), color: isFromUser ? COLORS.SOLID_WHITE : COLORS.SOLID_BLACK, blobwidth: getThemeProperty(WEBCHAT.CUSTOM_PROPERTIES.botMessageBlobWidth), blob: blob, style: Object.assign(Object.assign(Object.assign({}, getMessageStyle()), style), { opacity: ack === 0 ? 0.6 : 1 }) }, otherProps, { children: [markdown ? (_jsx(BlobText, { blob: blob, dangerouslySetInnerHTML: { __html: renderMarkdown(textChildren), }, markdownstyle: getMarkdownStyle(getThemeProperty, isFromUser ? COLORS.SEASHELL_WHITE : brandColor) })) : (_jsx(BlobText, Object.assign({ blob: blob }, { children: textChildren }))), !!buttons.length && (_jsx("div", Object.assign({ className: 'message-buttons-container' }, { children: buttons }))), Boolean(blob) && hasBlobTick() && getBlobTick(6), Boolean(blob) && hasBlobTick() && getBlobTick(5)] }))] })), timestampsEnabled && (_jsx(MessageTimestamp, { timestamp: m.timestamp, style: timestampStyle, isfromuser: isFromUser }))] }) }))); }; const { blob: _blob, json: _json } = props, nodeProps = __rest(props, ["blob", "json"]); const renderNode = () => type === INPUT.CUSTOM ? (_jsx("message", Object.assign({ json: JSON.stringify(_json), typing: typing, delay: delay }, nodeProps))) : (_jsx("message", Object.assign({ typing: typing, delay: delay }, nodeProps, { children: children }))); return renderComponent({ renderBrowser, renderNode }); }; //# sourceMappingURL=message.js.map