♻️ Simplify text bubble content shape
Remove html and plainText field because it was redundant Closes #386
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 = ({
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
})}
|
||||
</>
|
||||
)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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')
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user