♻️ Simplify text bubble content shape

Remove html and plainText field because it was redundant

Closes #386
This commit is contained in:
Baptiste Arnaud
2023-04-13 17:04:21 +02:00
parent 2cbf8348c3
commit e0a9824913
70 changed files with 545 additions and 1030 deletions

View File

@@ -1,28 +1,25 @@
import { Flex } from '@chakra-ui/react'
import { useTypebot } from '@/features/editor/providers/TypebotProvider'
import { TextBubbleBlock } from '@typebot.io/schemas'
import React from 'react'
import { isEmpty } from '@typebot.io/lib'
import { parseVariableHtmlTags } from '@/features/variables/helpers/parseVariableHtmlTags'
import { PlateBlock } from './plate/PlateBlock'
type Props = {
block: TextBubbleBlock
}
export const TextBubbleContent = ({ block }: Props) => {
const { typebot } = useTypebot()
const isEmpty = block.content.richText.length === 0
return (
<Flex
w="90%"
flexDir={'column'}
opacity={block.content.html === '' ? '0.5' : '1'}
opacity={isEmpty ? '0.5' : '1'}
className="slate-html-container"
color={isEmpty(block.content.plainText) ? 'gray.500' : 'inherit'}
dangerouslySetInnerHTML={{
__html: isEmpty(block.content.plainText)
? `<p>Click to edit...</p>`
: parseVariableHtmlTags(block.content.html, typebot?.variables ?? []),
}}
/>
color={isEmpty ? 'gray.500' : 'inherit'}
>
{block.content.richText.map((element, idx) => (
<PlateBlock key={idx} element={element} />
))}
</Flex>
)
}

View File

@@ -3,14 +3,8 @@ import React, { useEffect, useRef, useState } from 'react'
import { Plate, PlateProvider, usePlateEditorRef } from '@udecode/plate-core'
import { editorStyle, platePlugins } from '@/lib/plate'
import { BaseEditor, BaseSelection, Transforms } from 'slate'
import {
defaultTextBubbleContent,
TextBubbleContent,
Variable,
} from '@typebot.io/schemas'
import { Variable } from '@typebot.io/schemas'
import { ReactEditor } from 'slate-react'
import { serializeHtml } from '@udecode/plate-serializer-html'
import { parseHtmlStringToPlainText } from '../utils'
import { VariableSearchInput } from '@/components/inputs/VariableSearchInput'
import { colors } from '@/lib/theme'
import { useOutsideClick } from '@/hooks/useOutsideClick'
@@ -20,7 +14,7 @@ import { TextEditorToolBar } from './TextEditorToolBar'
type TextBubbleEditorContentProps = {
id: string
textEditorValue: TElement[]
onClose: (newContent: TextBubbleContent) => void
onClose: (newContent: TElement[]) => void
}
const TextBubbleEditorContent = ({
@@ -37,7 +31,7 @@ const TextBubbleEditorContent = ({
const textEditorRef = useRef<HTMLDivElement>(null)
const closeEditor = () => onClose(convertValueToBlockContent(textEditorValue))
const closeEditor = () => onClose(textEditorValue)
useOutsideClick({
ref: textEditorRef,
@@ -67,18 +61,6 @@ const TextBubbleEditorContent = ({
}
}
const convertValueToBlockContent = (value: TElement[]): TextBubbleContent => {
if (value.length === 0) defaultTextBubbleContent
const html = serializeHtml(editor, {
nodes: value,
})
return {
html,
richText: value,
plainText: parseHtmlStringToPlainText(html),
}
}
const handleVariableSelected = (variable?: Variable) => {
setIsVariableDropdownOpen(false)
if (!rememberedSelection.current || !variable) return
@@ -170,7 +152,7 @@ const TextBubbleEditorContent = ({
type TextBubbleEditorProps = {
id: string
initialValue: TElement[]
onClose: (newContent: TextBubbleContent) => void
onClose: (newContent: TElement[]) => void
}
export const TextBubbleEditor = ({

View File

@@ -0,0 +1,27 @@
import { TElement, TText, TDescendant } from '@udecode/plate-common'
import { PlateText } from './PlateText'
export const PlateBlock = ({ element }: { element: TElement | TText }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if (element.text) return <PlateText {...(element as any)} />
switch (element.type) {
case 'a': {
return (
<a href={element.url as string} target="_blank" className="slate-a">
{(element.children as TDescendant[])?.map((child, idx) => (
<PlateBlock key={idx} element={child} />
))}
</a>
)
}
default: {
return (
<div>
{(element.children as TDescendant[])?.map((child, idx) => (
<PlateBlock key={idx} element={child} />
))}
</div>
)
}
}
}

View File

@@ -0,0 +1,38 @@
export const PlateText = ({
text,
bold,
italic,
underline,
}: {
text: string
bold?: boolean
italic?: boolean
underline?: boolean
}) => {
let className = ''
if (bold) className += 'slate-bold'
if (italic) className += ' slate-italic'
if (underline) className += ' slate-underline'
if (className)
return (
<span className={className}>
<PlateTextContent text={text} />
</span>
)
return <PlateTextContent text={text} />
}
const PlateTextContent = ({ text }: { text: string }) => (
<>
{text.split(/\{\{(.*?\}\})/g).map((str, idx) => {
if (str.endsWith('}}')) {
return (
<span className="slate-variable" key={idx}>
{str.trim().slice(0, -2)}
</span>
)
}
return str
})}
</>
)

View File

@@ -1,17 +0,0 @@
import { Parser } from 'htmlparser2'
import { isNotEmpty } from '@typebot.io/lib'
export const parseHtmlStringToPlainText = (html: string): string => {
let plainText = ''
const parser = new Parser({
onopentag(name) {
if (name === 'div' && isNotEmpty(plainText)) plainText += '\n'
},
ontext(text) {
plainText += `${text}`
},
})
parser.write(html)
parser.end()
return plainText
}

View File

@@ -120,12 +120,12 @@ test.describe.parallel('Google sheets integration', () => {
await page.getByPlaceholder('Type a value...').nth(-1).fill('test@test.com')
await page.click('text=Select a column')
await page.click('text="First name"')
await page.getByRole('menuitem', { name: 'First name' }).click()
await createNewVar(page, 'First name')
await page.click('text=Add a value')
await page.click('text=Select a column')
await page.click('text="Last name"')
await page.getByRole('menuitem', { name: 'Last name' }).click()
await createNewVar(page, 'Last name')
await page.click('text=Preview')

View File

@@ -13,7 +13,6 @@ import {
DraggableBlock,
Block,
BlockWithOptions,
TextBubbleContent,
TextBubbleBlock,
LogicBlockType,
} from '@typebot.io/schemas'
@@ -39,6 +38,7 @@ import { hasDefaultConnector } from '@/features/typebot/helpers/hasDefaultConnec
import { setMultipleRefs } from '@/helpers/setMultipleRefs'
import { TargetEndpoint } from '../../endpoints/TargetEndpoint'
import { SettingsModal } from './SettingsModal'
import { TElement } from '@udecode/plate-common'
export const BlockNode = ({
block,
@@ -72,7 +72,7 @@ export const BlockNode = ({
openedBlockId === block.id
)
const [isEditing, setIsEditing] = useState<boolean>(
isTextBubbleBlock(block) && block.content.plainText === ''
isTextBubbleBlock(block) && block.content.richText.length === 0
)
const blockRef = useRef<HTMLDivElement | null>(null)
@@ -134,8 +134,8 @@ export const BlockNode = ({
})
}
const handleCloseEditor = (content: TextBubbleContent) => {
const updatedBlock = { ...block, content } as Block
const handleCloseEditor = (content: TElement[]) => {
const updatedBlock = { ...block, content: { richText: content } }
updateBlock(indices, updatedBlock)
setIsEditing(false)
}

View File

@@ -1,18 +0,0 @@
import { Variable } from '@typebot.io/schemas'
export const parseVariableHtmlTags = (
content: string,
variables: Variable[]
) => {
const varNames = variables.map((variable) => variable.name)
return content.replace(/\{\{(.*?)\}\}/g, (fullMatch, foundVar) => {
if (content.includes(`href="{{${foundVar}}}"`)) return fullMatch
if (varNames.some((val) => foundVar === val)) {
return `<span style="background-color:#ff8b1a; color:#ffffff; padding: 0.125rem 0.25rem; border-radius: 0.35rem">${fullMatch.replace(
/{{|}}/g,
''
)}</span>`
}
return fullMatch
})
}