Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(web-channel): markdown on cards #12491

Merged
merged 3 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 8 additions & 2 deletions modules/builtin/src/content-types/card.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const base = require('./_base')
const ActionButton = require('./action_button')
const utils = require('./_utils')

module.exports = {
const Card = {
id: 'builtin_card',
group: 'Built-in Messages',
title: 'card',
Expand Down Expand Up @@ -29,7 +30,8 @@ module.exports = {
type: 'array',
title: 'module.builtin.actionButton',
items: ActionButton.jsonSchema
}
},
...base.useMarkdown
}
},

Expand All @@ -40,3 +42,7 @@ module.exports = {
return utils.extractPayload('card', data)
}
}

Card.jsonSchema.properties.markdown.default = false

module.exports = Card
1 change: 1 addition & 0 deletions modules/channel-web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"multer": "^1.4.2",
"multer-s3": "^2.9.0",
"query-string": "^5.0.1",
"react-children-utilities": "^2.8.0",
"react-ga": "^2.7.0",
"react-intl": "^2.8.0",
"react-linkify": "^0.2.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,15 @@ class Message extends Component<MessageProps> {
}

render_carousel() {
return <Carousel onSendData={this.props.onSendData} carousel={this.props.payload} />
return (
<Carousel
onSendData={this.props.onSendData}
carousel={this.props.payload}
escapeHTML={this.props.store.escapeHTML}
isBotMessage={this.props.isBotMessage}
intl={this.props.intl}
/>
)
}

render_typing() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Slider from 'react-slick'
import '../../../../../../assets/slick/slick-theme.css'
import '../../../../../../assets/slick/slick.css'
import { Renderer } from '../../../typings'
import { ProcessedText } from '../../../utils'

const CarouselSkeleton = () => {
const common = { width: '100%', borderRadius: 5, padding: 10 }
Expand Down Expand Up @@ -55,7 +56,14 @@ export class Carousel extends React.Component<ICarouselProps, ICarouselState> {
return (
<Slider {...settings}>
{elements.map((el, idx) => (
<Card element={el} key={idx} onSendData={this.props.onSendData} />
<Card
element={el}
key={idx}
onSendData={this.props.onSendData}
escapeHTML={this.props.escapeHTML}
isBotMessage={this.props.isBotMessage}
intl={this.props.intl}
/>
))}
</Slider>
)
Expand All @@ -71,15 +79,39 @@ export class Carousel extends React.Component<ICarouselProps, ICarouselState> {
}

export const Card = props => {
const { picture, title, subtitle, buttons } = props.element as Renderer.Card
const { picture, title, subtitle, buttons, markdown } = props.element as Renderer.Card
const { escapeHTML, intl } = props

return (
<div className={'bpw-card-container'}>
{picture && <div className={'bpw-card-picture'} style={{ backgroundImage: `url("${picture}")` }} />}
<div>
<div className={'bpw-card-header'}>
<div className={'bpw-card-title'}>{title}</div>
{subtitle && <div className={'bpw-card-subtitle'}>{subtitle}</div>}
<ProcessedText
wrapperProps={{
className: 'bpw-card-title',
tag: 'div'
}}
isBotMessage={props.isBotMessage}
markdown={markdown}
escapeHTML={escapeHTML}
>
{title}
</ProcessedText>
{subtitle && (
<ProcessedText
wrapperProps={{
className: 'bpw-card-subtitle',
tag: 'div'
}}
isBotMessage={props.isBotMessage}
markdown={markdown}
escapeHTML={escapeHTML}
intl={props.intl}
>
{subtitle}
</ProcessedText>
)}
</div>
<div className={'bpw-card-buttons'}>
{buttons.map((btn: Renderer.CardButton) => {
Expand Down Expand Up @@ -134,6 +166,9 @@ interface ICarouselProps {
carousel: Renderer.Carousel
onSendData: any
style?: object
escapeHTML: boolean
isBotMessage: boolean
intl: any
}

interface ICarouselState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React, { useState } from 'react'
import Linkify from 'react-linkify'

import { Renderer } from '../../../typings'
import { renderUnsafeHTML, isRTLText } from '../../../utils'
import { isRTLText, ProcessedText } from '../../../utils'

/**
* A simple text element with optional markdown
Expand All @@ -22,26 +22,22 @@ export const Text = (props: Renderer.Text) => {
hasShowMore = true
}

const truncateIfRequired = message => {
return hasShowMore && !showMore ? truncate(message, maxLength) : message
}

let message
if (markdown) {
const isUserMessage = !props.isBotMessage
const shouldEscapeHTML = isUserMessage || escapeHTML
const html = renderUnsafeHTML(text, shouldEscapeHTML)

message = <div dangerouslySetInnerHTML={{ __html: truncateIfRequired(html) }} />
} else {
message = <p>{truncateIfRequired(text)}</p>
}

const rtl = isRTLText.test(text)

return (
<Linkify properties={{ target: '_blank' }}>
<div className={classNames({ rtl })}>{message}</div>
<div className={classNames({ rtl })}>
<ProcessedText
isBotMessage={props.isBotMessage}
intl={intl}
maxLength={maxLength}
escapeHTML={escapeHTML}
markdown={markdown}
showMore={showMore}
>
{text}
</ProcessedText>
</div>

{hasShowMore && (
<button type="button" onClick={e => setShowMore(!showMore)} className="bpw-message-read-more">
Expand Down
1 change: 1 addition & 0 deletions modules/channel-web/src/views/lite/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export namespace Renderer {
title: string
subtitle: string
buttons: CardButton[]
markdown: boolean
}

export interface CardButton {
Expand Down
53 changes: 53 additions & 0 deletions modules/channel-web/src/views/lite/utils.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import truncate from 'html-truncate'
import React from 'react'
import Children from 'react-children-utilities'
import ReactGA from 'react-ga'
import snarkdown from 'snarkdown'

Expand Down Expand Up @@ -150,3 +153,53 @@ export const isRTLText = new RegExp(
].join('') +
']'
)

export const ProcessedText = (props: {
markdown?: boolean
escapeHTML?: boolean
isBotMessage: boolean
maxLength?: number
intl?: any
showMore?: boolean
wrapperProps?: any & { tag: string }
children?: string
}) => {
let message
let hasShowMore
let WrapperTag

const {
markdown = false,
escapeHTML = false,
isBotMessage = false,
maxLength,
intl = false,
showMore = false,
wrapperProps = {},
children
} = props
const { tag, ...rest } = wrapperProps

const text = Children.onlyText(children)

if (intl && maxLength && text.length > maxLength) {
hasShowMore = true
}

const truncateIfRequired = message => {
return hasShowMore && !showMore ? truncate(message, maxLength) : message
}

if (markdown) {
const isUserMessage = !isBotMessage
const shouldEscapeHTML = isUserMessage || escapeHTML
const html = renderUnsafeHTML(text, shouldEscapeHTML)
WrapperTag = tag || 'div'
message = <WrapperTag {...rest} dangerouslySetInnerHTML={{ __html: truncateIfRequired(html) }} />
} else {
WrapperTag = tag || 'p'
message = <WrapperTag {...rest}>{truncateIfRequired(text)}</WrapperTag>
}

return message
}
5 changes: 5 additions & 0 deletions modules/channel-web/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,11 @@ raf@^3.4.0:
dependencies:
performance-now "^2.1.0"

react-children-utilities@^2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/react-children-utilities/-/react-children-utilities-2.8.0.tgz#ab6866249bcf1729fe463128de5e15074f43caaa"
integrity sha512-g42oRsZLrFJgCcIdK1lad1CWujNH4gh1Cp1lsMQpHWdDjWQ8gUlaBebgy2iXofyPEpfJ4T/xt4qWrvDkgVCCNg==

react-ga@^2.7.0:
version "2.7.0"
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.7.0.tgz#24328f157f31e8cffbf4de74a3396536679d8d7c"
Expand Down
1 change: 1 addition & 0 deletions packages/bp/src/sdk/botpress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1566,6 +1566,7 @@ declare module 'botpress/sdk' {
subtitle?: string | MultiLangText
image?: string
actions: ActionButton[]
markdown?: boolean
}

export interface LocationContent extends Content {
Expand Down
3 changes: 2 additions & 1 deletion packages/ui-shared-lite/Payloads/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ const renderCarouselPayload = (content: sdk.CarouselContent & CollectFeedback) =
} else {
throw new Error(`Webchat carousel does not support "${a.action}" action-buttons at the moment`)
}
})
}),
markdown: card.markdown
}))
}
}