160 lines
5.1 KiB
TypeScript
160 lines
5.1 KiB
TypeScript
import { safeStringify } from '@typebot.io/lib/safeStringify'
|
|
import { isDefined, isNotDefined } from '@typebot.io/lib/utils'
|
|
import { Variable, VariableWithValue } from './types'
|
|
import { createCodeRunner } from './codeRunners'
|
|
|
|
export type ParseVariablesOptions = {
|
|
fieldToParse?: 'value' | 'id'
|
|
isInsideJson?: boolean
|
|
takeLatestIfList?: boolean
|
|
isInsideHtml?: boolean
|
|
}
|
|
|
|
export const defaultParseVariablesOptions: ParseVariablesOptions = {
|
|
fieldToParse: 'value',
|
|
isInsideJson: false,
|
|
takeLatestIfList: false,
|
|
isInsideHtml: false,
|
|
}
|
|
|
|
// {{= inline code =}}
|
|
const inlineCodeRegex = /\{\{=(.+?)=\}\}/g
|
|
|
|
// {{variable}} and ${{{variable}}}
|
|
const variableRegex = /\{\{([^{}]+)\}\}|(\$)\{\{([^{}]+)\}\}/g
|
|
|
|
export const parseVariables =
|
|
(
|
|
variables: Variable[],
|
|
options: ParseVariablesOptions = defaultParseVariablesOptions
|
|
) =>
|
|
(text: string | undefined): string => {
|
|
if (!text || text === '') return ''
|
|
const textWithInlineCodeParsed = text.replace(
|
|
inlineCodeRegex,
|
|
(_full, inlineCodeToEvaluate) => {
|
|
const value = evaluateInlineCode(inlineCodeToEvaluate, { variables })
|
|
return safeStringify(value) ?? value
|
|
}
|
|
)
|
|
|
|
return textWithInlineCodeParsed.replace(
|
|
variableRegex,
|
|
(_full, nameInCurlyBraces, _dollarSign, nameInTemplateLitteral) => {
|
|
const dollarSign = (_dollarSign ?? '') as string
|
|
const matchedVarName = nameInCurlyBraces ?? nameInTemplateLitteral
|
|
const variable = variables.find((variable) => {
|
|
return (
|
|
matchedVarName === variable.name &&
|
|
(options.fieldToParse === 'id' || isDefined(variable.value))
|
|
)
|
|
}) as VariableWithValue | undefined
|
|
if (!variable) return dollarSign + ''
|
|
if (options.fieldToParse === 'id') return dollarSign + variable.id
|
|
const { value } = variable
|
|
if (options.isInsideJson)
|
|
return dollarSign + parseVariableValueInJson(value)
|
|
const parsedValue =
|
|
dollarSign +
|
|
safeStringify(
|
|
options.takeLatestIfList && Array.isArray(value)
|
|
? value[value.length - 1]
|
|
: value
|
|
)
|
|
if (!parsedValue) return dollarSign + ''
|
|
if (options.isInsideHtml) return parseVariableValueInHtml(parsedValue)
|
|
return parsedValue
|
|
}
|
|
)
|
|
}
|
|
|
|
const evaluateInlineCode = (
|
|
code: string,
|
|
{ variables }: { variables: Variable[] }
|
|
) => {
|
|
try {
|
|
return createCodeRunner({ variables })(
|
|
parseVariables(variables, { fieldToParse: 'id' })(code)
|
|
)
|
|
} catch (err) {
|
|
return parseVariables(variables)(code)
|
|
}
|
|
}
|
|
|
|
type VariableToParseInformation = {
|
|
startIndex: number
|
|
endIndex: number
|
|
textToReplace: string
|
|
value: string
|
|
variableId?: string
|
|
}
|
|
|
|
export const getVariablesToParseInfoInText = (
|
|
text: string,
|
|
{
|
|
variables,
|
|
takeLatestIfList,
|
|
}: { variables: Variable[]; takeLatestIfList?: boolean }
|
|
): VariableToParseInformation[] => {
|
|
const variablesToParseInfo: VariableToParseInformation[] = []
|
|
const inlineCodeMatches = [...text.matchAll(inlineCodeRegex)]
|
|
inlineCodeMatches.forEach((match) => {
|
|
if (isNotDefined(match.index) || !match[0].length) return
|
|
const inlineCodeToEvaluate = match[1]
|
|
const evaluatedValue = evaluateInlineCode(inlineCodeToEvaluate, {
|
|
variables,
|
|
})
|
|
variablesToParseInfo.push({
|
|
startIndex: match.index,
|
|
endIndex: match.index + match[0].length,
|
|
textToReplace: match[0],
|
|
value:
|
|
safeStringify(
|
|
takeLatestIfList && Array.isArray(evaluatedValue)
|
|
? evaluatedValue[evaluatedValue.length - 1]
|
|
: evaluatedValue
|
|
) ?? '',
|
|
})
|
|
})
|
|
const textWithInlineCodeParsed = text.replace(
|
|
inlineCodeRegex,
|
|
(_full, inlineCodeToEvaluate) =>
|
|
evaluateInlineCode(inlineCodeToEvaluate, { variables })
|
|
)
|
|
const variableMatches = [...textWithInlineCodeParsed.matchAll(variableRegex)]
|
|
variableMatches.forEach((match) => {
|
|
if (isNotDefined(match.index) || !match[0].length) return
|
|
const matchedVarName = match[1] ?? match[3]
|
|
const variable = variables.find((variable) => {
|
|
return matchedVarName === variable.name && isDefined(variable.value)
|
|
}) as VariableWithValue | undefined
|
|
variablesToParseInfo.push({
|
|
startIndex: match.index,
|
|
endIndex: match.index + match[0].length,
|
|
textToReplace: match[0],
|
|
value:
|
|
safeStringify(
|
|
takeLatestIfList && Array.isArray(variable?.value)
|
|
? variable?.value[variable?.value.length - 1]
|
|
: variable?.value
|
|
) ?? '',
|
|
variableId: variable?.id,
|
|
})
|
|
})
|
|
return variablesToParseInfo.sort((a, b) => a.startIndex - b.startIndex)
|
|
}
|
|
|
|
const parseVariableValueInJson = (value: VariableWithValue['value']) => {
|
|
const stringifiedValue = JSON.stringify(value)
|
|
if (typeof value === 'string') return stringifiedValue.slice(1, -1)
|
|
return stringifiedValue
|
|
}
|
|
|
|
const parseVariableValueInHtml = (
|
|
value: VariableWithValue['value']
|
|
): string => {
|
|
if (typeof value === 'string')
|
|
return value.replace(/</g, '<').replace(/>/g, '>')
|
|
return JSON.stringify(value).replace(/</g, '<').replace(/>/g, '>')
|
|
}
|