@botonic/react
Version:
Build Chatbots using React
184 lines • 10.2 kB
JavaScript
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