✨ Automatically parse markdown from variables in text bubbles
Closes #539
This commit is contained in:
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/js",
|
||||
"version": "0.1.33",
|
||||
"version": "0.1.34",
|
||||
"description": "Javascript library to display typebots on your website",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
@ -14,7 +14,9 @@
|
||||
"dependencies": {
|
||||
"@stripe/stripe-js": "1.54.1",
|
||||
"@udecode/plate-common": "^21.1.5",
|
||||
"dompurify": "^3.0.6",
|
||||
"eventsource-parser": "^1.0.0",
|
||||
"marked": "^9.0.3",
|
||||
"solid-element": "1.7.1",
|
||||
"solid-js": "1.7.8"
|
||||
},
|
||||
@ -24,11 +26,12 @@
|
||||
"@rollup/plugin-node-resolve": "15.1.0",
|
||||
"@rollup/plugin-terser": "0.4.3",
|
||||
"@rollup/plugin-typescript": "11.1.2",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@typebot.io/bot-engine": "workspace:*",
|
||||
"@typebot.io/env": "workspace:*",
|
||||
"@typebot.io/lib": "workspace:*",
|
||||
"@typebot.io/schemas": "workspace:*",
|
||||
"@typebot.io/tsconfig": "workspace:*",
|
||||
"@typebot.io/bot-engine": "workspace:*",
|
||||
"@types/dompurify": "^3.0.3",
|
||||
"autoprefixer": "10.4.14",
|
||||
"babel-preset-solid": "1.7.7",
|
||||
"clsx": "2.0.0",
|
||||
|
@ -94,14 +94,10 @@ textarea {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.slate-a {
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.slate-html-container > div {
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.slate-bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -1,21 +1,32 @@
|
||||
import { streamingMessage } from '@/utils/streamingMessageSignal'
|
||||
import { createEffect, createSignal } from 'solid-js'
|
||||
import { marked } from 'marked'
|
||||
import domPurify from 'dompurify'
|
||||
|
||||
type Props = {
|
||||
streamingMessageId: string
|
||||
}
|
||||
|
||||
marked.use({
|
||||
renderer: {
|
||||
link: (href, _title, text) => {
|
||||
return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>`
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export const StreamingBubble = (props: Props) => {
|
||||
let ref: HTMLDivElement | undefined
|
||||
const [content, setContent] = createSignal<string>('')
|
||||
|
||||
createEffect(() => {
|
||||
if (streamingMessage()?.id === props.streamingMessageId)
|
||||
setContent(streamingMessage()?.content ?? '')
|
||||
setContent(
|
||||
domPurify.sanitize(marked.parse(streamingMessage()?.content ?? ''))
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<div class="flex flex-col animate-fade-in" ref={ref}>
|
||||
<div class="flex flex-col animate-fade-in">
|
||||
<div class="flex w-full items-center">
|
||||
<div class="flex relative items-start typebot-host-bubble">
|
||||
<div
|
||||
@ -28,11 +39,10 @@ export const StreamingBubble = (props: Props) => {
|
||||
/>
|
||||
<div
|
||||
class={
|
||||
'overflow-hidden text-fade-in mx-4 my-2 whitespace-pre-wrap slate-html-container relative text-ellipsis opacity-100 h-full'
|
||||
'flex flex-col overflow-hidden text-fade-in mx-4 my-2 relative text-ellipsis h-full gap-6'
|
||||
}
|
||||
>
|
||||
{content()}
|
||||
</div>
|
||||
innerHTML={content()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { TypingBubble } from '@/components'
|
||||
import type { TextBubbleContent, TypingEmulation } from '@typebot.io/schemas'
|
||||
import { For, createSignal, onCleanup, onMount } from 'solid-js'
|
||||
import { PlateBlock } from './plate/PlateBlock'
|
||||
import { PlateElement } from './plate/PlateBlock'
|
||||
import { computePlainText } from '../helpers/convertRichTextToPlainText'
|
||||
import { clsx } from 'clsx'
|
||||
import { isMobile } from '@/utils/isMobileSignal'
|
||||
@ -70,7 +70,7 @@ export const TextBubble = (props: Props) => {
|
||||
}}
|
||||
>
|
||||
<For each={props.content.richText}>
|
||||
{(element) => <PlateBlock element={element} />}
|
||||
{(element) => <PlateElement element={element} />}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,30 +1,89 @@
|
||||
import type { TElement, TText, TDescendant } from '@udecode/plate-common'
|
||||
import { PlateText, PlateTextProps } from './PlateText'
|
||||
import { For, Match, Show, Switch } from 'solid-js'
|
||||
import { For, Match, Switch, JSXElement } from 'solid-js'
|
||||
import { isDefined } from '@typebot.io/lib/utils'
|
||||
import clsx from 'clsx'
|
||||
|
||||
type Props = { element: TElement | TText }
|
||||
type Props = {
|
||||
element: TElement | TText
|
||||
isUniqueChild?: boolean
|
||||
inElement?: boolean
|
||||
}
|
||||
|
||||
export const PlateBlock = (props: Props) => (
|
||||
<Show
|
||||
when={!props.element.text}
|
||||
fallback={<PlateText {...(props.element as PlateTextProps)} />}
|
||||
>
|
||||
<Switch
|
||||
fallback={
|
||||
<div>
|
||||
<For each={props.element.children as TDescendant[]}>
|
||||
{(child) => <PlateBlock element={child} />}
|
||||
</For>
|
||||
export const PlateElement = (props: Props) => (
|
||||
<Switch>
|
||||
<Match when={isDefined(props.element.text)}>
|
||||
<PlateText
|
||||
{...(props.element as PlateTextProps)}
|
||||
isUniqueChild={props.isUniqueChild ?? false}
|
||||
/>
|
||||
</Match>
|
||||
<Match when={props.element.type}>
|
||||
<Switch>
|
||||
<Match when={props.element.type === 'a'}>
|
||||
<a
|
||||
href={props.element.url as string}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<For each={props.element.children as TDescendant[]}>
|
||||
{(child) => (
|
||||
<PlateElement
|
||||
element={child}
|
||||
isUniqueChild={
|
||||
(props.element.children as TDescendant[])?.length === 1
|
||||
}
|
||||
inElement={true}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</a>
|
||||
</Match>
|
||||
<Match when={props.element.type !== 'a'}>
|
||||
<ElementRoot
|
||||
element={props.element as TElement}
|
||||
inElement={props.inElement ?? false}
|
||||
>
|
||||
<For each={props.element.children as TDescendant[]}>
|
||||
{(child) => (
|
||||
<PlateElement
|
||||
element={child}
|
||||
isUniqueChild={
|
||||
(props.element.children as TDescendant[])?.length === 1
|
||||
}
|
||||
inElement={true}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</ElementRoot>
|
||||
</Match>
|
||||
</Switch>
|
||||
</Match>
|
||||
</Switch>
|
||||
)
|
||||
|
||||
type ElementRootProps = {
|
||||
element: TElement
|
||||
inElement: boolean
|
||||
children: JSXElement
|
||||
}
|
||||
|
||||
const ElementRoot = (props: ElementRootProps) => {
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={props.inElement}>
|
||||
<span data-element-type={props.element.type}>{props.children}</span>
|
||||
</Match>
|
||||
<Match when={!props.inElement}>
|
||||
<div
|
||||
data-element-type={props.element.type}
|
||||
class={clsx(
|
||||
props.element.type === 'variable' && 'flex flex-col gap-6'
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Match when={props.element.type === 'a'}>
|
||||
<a href={props.element.url as string} target="_blank" class="slate-a">
|
||||
<For each={props.element.children as TDescendant[]}>
|
||||
{(child) => <PlateBlock element={child} />}
|
||||
</For>
|
||||
</a>
|
||||
</Match>
|
||||
</Switch>
|
||||
</Show>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { isEmpty } from '@typebot.io/lib'
|
||||
import { Show } from 'solid-js'
|
||||
|
||||
export type PlateTextProps = {
|
||||
text: string
|
||||
isUniqueChild: boolean
|
||||
bold?: boolean
|
||||
italic?: boolean
|
||||
underline?: boolean
|
||||
@ -20,11 +22,10 @@ const computeClassNames = (
|
||||
}
|
||||
|
||||
export const PlateText = (props: PlateTextProps) => (
|
||||
<Show
|
||||
when={computeClassNames(props.bold, props.italic, props.underline)}
|
||||
keyed
|
||||
fallback={<>{props.text}</>}
|
||||
>
|
||||
{(className) => <span class={className}>{props.text}</span>}
|
||||
</Show>
|
||||
<span class={computeClassNames(props.bold, props.italic, props.underline)}>
|
||||
{props.text}
|
||||
<Show when={props.isUniqueChild && isEmpty(props.text)}>
|
||||
<br />
|
||||
</Show>
|
||||
</span>
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/nextjs",
|
||||
"version": "0.1.33",
|
||||
"version": "0.1.34",
|
||||
"description": "Convenient library to display typebots on your Next.js website",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@typebot.io/react",
|
||||
"version": "0.1.33",
|
||||
"version": "0.1.34",
|
||||
"description": "Convenient library to display typebots on your React app",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
|
Reference in New Issue
Block a user