mirror of
https://github.com/FlowiseAI/Flowise.git
synced 2026-10-12 02:02:17 +00:00
fix(ui): allow XML tag for markdown rich input (#6095)
* fix(ui): allow XML tag for rich input - Introduced utilities for escaping and unescaping non-standard XML/HTML tags to preserve them during markdown processing. - Updated RichInput and ExpandRichInputDialog components to utilize the new XML tag utilities for handling custom tags. - Added Jest configuration for the UI package and created tests for the XML tag utilities to ensure functionality. - Updated package.json to include Jest as a dependency and added a test script. * address code review comments from Gemini * allow html tag in markdown editor * add test case for markdown format * update dependency in ui
This commit is contained in:
@@ -247,8 +247,11 @@ export const resolveVariables = async (
|
||||
// If value is not a string, return as is
|
||||
if (typeof value !== 'string') return value
|
||||
|
||||
// Convert legacy HTML content to markdown, preserving any markdown syntax within
|
||||
if (/<[a-z][a-z0-9]*[^>]*>/i.test(value)) {
|
||||
// Convert legacy HTML content to markdown, preserving any markdown syntax within.
|
||||
// Legacy content from old getHTML() starts with a TipTap block tag (e.g. <p>text</p>).
|
||||
// Anchor with ^ to avoid matching intentional HTML/XML tags in user prompts
|
||||
// (e.g. <instruction><div>...</div></instruction>).
|
||||
if (/^\s*<(?:p|div|h[1-6]|ul|ol|blockquote|pre|table)\b/i.test(value)) {
|
||||
const turndownService = new TurndownService()
|
||||
// Disable escaping so markdown characters (e.g. ###, -, *) inside HTML are preserved as-is
|
||||
turndownService.escape = (str: string) => str
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
roots: ['<rootDir>/src'],
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['<rootDir>/src/**/*.test.js'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/build/'],
|
||||
moduleNameMapper: {
|
||||
'\\.(css|less|scss|sass|svg|png|jpg|jpeg|gif|webp)$': '<rootDir>/src/__mocks__/styleMock.js',
|
||||
'^@/(.*)$': '<rootDir>/src/$1'
|
||||
},
|
||||
transform: {
|
||||
'^.+\\.jsx?$': 'babel-jest'
|
||||
},
|
||||
transformIgnorePatterns: ['/node_modules/']
|
||||
}
|
||||
@@ -26,6 +26,7 @@
|
||||
"@mui/x-tree-view": "^7.25.0",
|
||||
"@reduxjs/toolkit": "^2.2.7",
|
||||
"@tabler/icons-react": "^3.30.0",
|
||||
"@tiptap/core": "^3.20.4",
|
||||
"@tiptap/extension-code-block-lowlight": "^3.20.4",
|
||||
"@tiptap/extension-mention": "^3.20.4",
|
||||
"@tiptap/extension-placeholder": "^3.20.4",
|
||||
@@ -81,11 +82,20 @@
|
||||
"dev": "vite",
|
||||
"start": "vite",
|
||||
"build": "vite build",
|
||||
"test": "jest",
|
||||
"clean": "rimraf build",
|
||||
"nuke": "rimraf build node_modules .turbo"
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "current"
|
||||
}
|
||||
}
|
||||
],
|
||||
"@babel/preset-react"
|
||||
]
|
||||
},
|
||||
@@ -108,6 +118,7 @@
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^12.8.3",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"jest": "^29.7.0",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"react-scripts": "^5.0.1",
|
||||
"rimraf": "^5.0.5",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = {}
|
||||
@@ -23,15 +23,10 @@ import { common, createLowlight } from 'lowlight'
|
||||
import { suggestionOptions } from '@/ui-component/input/suggestionOption'
|
||||
import { getAvailableNodesForVariable } from '@/utils/genericHelper'
|
||||
import { CustomMention } from '@/utils/customMention'
|
||||
import { isHtmlContent, escapeXmlTags, unescapeXmlEntities, unescapeXmlTags } from '@/utils/xmlTagUtils'
|
||||
|
||||
const lowlight = createLowlight(common)
|
||||
|
||||
// Detect if content is legacy HTML (from old getHTML() storage) vs markdown
|
||||
const isHtmlContent = (content) => {
|
||||
if (!content || typeof content !== 'string') return false
|
||||
return /<(?:p|div|span|h[1-6]|ul|ol|li|br|code|pre|blockquote|table|strong|em)\b/i.test(content)
|
||||
}
|
||||
|
||||
// Store
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
|
||||
|
||||
@@ -221,7 +216,7 @@ const ExpandRichInputDialog = ({ show, dialogProps, onCancel, onInputHintDialogC
|
||||
onUpdate: ({ editor }) => {
|
||||
if (!isSwitchingRef.current) {
|
||||
try {
|
||||
setInputValue(editor.getMarkdown())
|
||||
setInputValue(unescapeXmlTags(editor.getMarkdown()))
|
||||
} catch {
|
||||
setInputValue(editor.getHTML())
|
||||
}
|
||||
@@ -239,12 +234,13 @@ const ExpandRichInputDialog = ({ show, dialogProps, onCancel, onInputHintDialogC
|
||||
if (isHtmlContent(inputValue)) {
|
||||
editor.commands.setContent(inputValue)
|
||||
try {
|
||||
setInputValue(editor.getMarkdown())
|
||||
setInputValue(unescapeXmlTags(editor.getMarkdown()))
|
||||
} catch {
|
||||
// keep original value if conversion fails
|
||||
}
|
||||
} else {
|
||||
editor.commands.setContent(inputValue, { contentType: 'markdown' })
|
||||
editor.commands.setContent(escapeXmlTags(inputValue), { contentType: 'markdown' })
|
||||
editor.commands.setContent(unescapeXmlEntities(editor.getJSON()))
|
||||
}
|
||||
isSwitchingRef.current = false
|
||||
}
|
||||
@@ -265,13 +261,17 @@ const ExpandRichInputDialog = ({ show, dialogProps, onCancel, onInputHintDialogC
|
||||
|
||||
if (newMode === 'preview' && editor) {
|
||||
isSwitchingRef.current = true
|
||||
const contentType = isHtmlContent(inputValue) ? 'html' : 'markdown'
|
||||
editor.commands.setContent(inputValue, { contentType })
|
||||
if (isHtmlContent(inputValue)) {
|
||||
editor.commands.setContent(inputValue, { contentType: 'html' })
|
||||
} else {
|
||||
editor.commands.setContent(escapeXmlTags(inputValue), { contentType: 'markdown' })
|
||||
editor.commands.setContent(unescapeXmlEntities(editor.getJSON()))
|
||||
}
|
||||
isSwitchingRef.current = false
|
||||
setTimeout(() => editor.commands.focus(), 50)
|
||||
} else if (newMode === 'raw' && editor) {
|
||||
try {
|
||||
setInputValue(editor.getMarkdown())
|
||||
setInputValue(unescapeXmlTags(editor.getMarkdown()))
|
||||
} catch {
|
||||
setInputValue(editor.getHTML())
|
||||
}
|
||||
|
||||
@@ -13,15 +13,10 @@ import { common, createLowlight } from 'lowlight'
|
||||
import { suggestionOptions } from './suggestionOption'
|
||||
import { getAvailableNodesForVariable } from '@/utils/genericHelper'
|
||||
import { CustomMention } from '@/utils/customMention'
|
||||
import { isHtmlContent, escapeXmlTags, unescapeXmlEntities, unescapeXmlTags } from '@/utils/xmlTagUtils'
|
||||
|
||||
const lowlight = createLowlight(common)
|
||||
|
||||
// Detect if content is legacy HTML (from old getHTML() storage) vs markdown
|
||||
const isHtmlContent = (content) => {
|
||||
if (!content || typeof content !== 'string') return false
|
||||
return /<(?:p|div|span|h[1-6]|ul|ol|li|br|code|pre|blockquote|table|strong|em)\b/i.test(content)
|
||||
}
|
||||
|
||||
// define your extension array
|
||||
const extensions = (
|
||||
availableNodesForVariable,
|
||||
@@ -154,7 +149,7 @@ export const RichInput = ({ inputParam, value, nodes, edges, nodeId, onChange, d
|
||||
onUpdate: ({ editor }) => {
|
||||
if (useMarkdown) {
|
||||
try {
|
||||
onChange(editor.getMarkdown())
|
||||
onChange(unescapeXmlTags(editor.getMarkdown()))
|
||||
} catch {
|
||||
onChange(editor.getHTML())
|
||||
}
|
||||
@@ -173,7 +168,10 @@ export const RichInput = ({ inputParam, value, nodes, edges, nodeId, onChange, d
|
||||
if (!useMarkdown || isHtmlContent(value)) {
|
||||
editor.commands.setContent(value)
|
||||
} else {
|
||||
editor.commands.setContent(value, { contentType: 'markdown' })
|
||||
// Step 1: Escape XML tags to entities so marked treats them as text
|
||||
editor.commands.setContent(escapeXmlTags(value), { contentType: 'markdown' })
|
||||
// Step 2: Decode entities in the ProseMirror doc for proper display
|
||||
editor.commands.setContent(unescapeXmlEntities(editor.getJSON()))
|
||||
}
|
||||
}
|
||||
}, [editor]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* Utilities for preserving XML/HTML tags in prompt text through TipTap's markdown roundtrip.
|
||||
*
|
||||
* Problem: When content like `<question>text</question>` is parsed by marked (via @tiptap/markdown),
|
||||
* the lexer tokenizes tags as HTML tokens. TipTap's parseHTMLToken then calls generateJSON which
|
||||
* creates DOM elements — unrecognized tags are stripped and only inner text survives.
|
||||
*
|
||||
* Solution: Three-step process:
|
||||
* 1. escapeXmlTags: Convert all tags to HTML entities before markdown parsing
|
||||
* so marked treats them as text, not HTML tokens.
|
||||
* 2. unescapeXmlEntities: After TipTap builds the ProseMirror document, walk the JSON tree
|
||||
* and decode </> back to </> in text nodes for proper visual display.
|
||||
* 3. unescapeXmlTags: After getMarkdown(), reverse any remaining entity-escaped tags
|
||||
* in the serialized output (safety net — typically a no-op).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Detect if content is legacy HTML from old getHTML() storage vs markdown.
|
||||
* Legacy content always starts with a block-level tag like <p>.
|
||||
* Anchored with ^ to avoid matching intentional HTML tags inside user prompts.
|
||||
*
|
||||
* @example
|
||||
* isHtmlContent('<p>some text</p>') // → true (legacy getHTML output)
|
||||
* isHtmlContent('<instruction>text</instruction>') // → false (user prompt)
|
||||
*
|
||||
* @param {string} content - Content to check
|
||||
* @returns {boolean} True if content looks like legacy HTML
|
||||
*/
|
||||
export const isHtmlContent = (content) => {
|
||||
if (!content || typeof content !== 'string') return false
|
||||
return /^\s*<(?:p|div|h[1-6]|ul|ol|blockquote|pre|table)\b/i.test(content)
|
||||
}
|
||||
|
||||
/**
|
||||
* Regex matching opening, closing, and self-closing XML/HTML tags.
|
||||
* Captures: (1) optional slash, (2) tag name, (3) optional attributes, (4) optional self-close slash
|
||||
*/
|
||||
const XML_TAG_REGEX = /<(\/?)([a-zA-Z][a-zA-Z0-9_.-]*)(\s[^>]*)?(\/?)>/g
|
||||
|
||||
/**
|
||||
* Escape all XML/HTML tags to HTML entities so marked doesn't parse them as HTML.
|
||||
* In prompt editing context, users want tags preserved literally, not rendered.
|
||||
*
|
||||
* @example
|
||||
* escapeXmlTags('<instructions>Be helpful</instructions>')
|
||||
* // → '<instructions>Be helpful</instructions>'
|
||||
*
|
||||
* escapeXmlTags('<div><question>text</question></div>')
|
||||
* // → '<div><question>text</question></div>'
|
||||
*
|
||||
* @param {string} text - Raw markdown/text content
|
||||
* @returns {string} Content with tags escaped to HTML entities
|
||||
*/
|
||||
export function escapeXmlTags(text) {
|
||||
if (!text || typeof text !== 'string') return text
|
||||
return text.replace(XML_TAG_REGEX, (match, slash, tagName, attrs, selfClose) => {
|
||||
return `<${slash}${tagName}${attrs || ''}${selfClose}>`
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescape XML tag entities in ProseMirror JSON text nodes.
|
||||
* Call this after setContent() to fix the visual display in the editor.
|
||||
* Mutates the JSON in-place and returns it.
|
||||
*
|
||||
* @example
|
||||
* const json = { type: 'doc', content: [
|
||||
* { type: 'paragraph', content: [{ type: 'text', text: '<question>What?</question>' }] }
|
||||
* ]}
|
||||
* unescapeXmlEntities(json)
|
||||
* // json.content[0].content[0].text → '<question>What?</question>'
|
||||
*
|
||||
* @param {object} json - ProseMirror document JSON from editor.getJSON()
|
||||
* @returns {object} The same JSON with decoded entities in text nodes
|
||||
*/
|
||||
export function unescapeXmlEntities(json) {
|
||||
if (json.text) {
|
||||
json.text = unescapeXmlTags(json.text)
|
||||
}
|
||||
if (json.content) {
|
||||
json.content.forEach(unescapeXmlEntities)
|
||||
}
|
||||
return json
|
||||
}
|
||||
|
||||
/**
|
||||
* Unescape all entity-escaped XML/HTML tags after markdown serialization.
|
||||
*
|
||||
* @example
|
||||
* unescapeXmlTags('<question>text</question>')
|
||||
* // → '<question>text</question>'
|
||||
*
|
||||
* unescapeXmlTags('<div>text</div>')
|
||||
* // → '<div>text</div>'
|
||||
*
|
||||
* unescapeXmlTags('<question>text</question>')
|
||||
* // → '<question>text</question>' (raw tags pass through unchanged)
|
||||
*
|
||||
* @param {string} text - Markdown output from TipTap
|
||||
* @returns {string} Content with tags restored to angle brackets
|
||||
*/
|
||||
export function unescapeXmlTags(text) {
|
||||
if (!text || typeof text !== 'string') return text
|
||||
return text.replace(/<(\/?)([a-zA-Z][a-zA-Z0-9_.-]*)(\s.*?)?(\/?)>/g, (match, slash, tagName, attrs, selfClose) => {
|
||||
return `<${slash}${tagName}${attrs || ''}${selfClose}>`
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
import { isHtmlContent, escapeXmlTags, unescapeXmlEntities, unescapeXmlTags } from './xmlTagUtils'
|
||||
|
||||
describe('xmlTagUtils', () => {
|
||||
describe('isHtmlContent', () => {
|
||||
it('should detect legacy getHTML() output starting with <p>', () => {
|
||||
expect(isHtmlContent('<p>some text</p>')).toBe(true)
|
||||
})
|
||||
|
||||
it('should detect legacy output starting with <div>', () => {
|
||||
expect(isHtmlContent('<div>content</div>')).toBe(true)
|
||||
})
|
||||
|
||||
it('should detect legacy output with leading whitespace', () => {
|
||||
expect(isHtmlContent(' <p>text</p>')).toBe(true)
|
||||
})
|
||||
|
||||
it('should NOT detect user prompts starting with custom tags', () => {
|
||||
expect(isHtmlContent('<instruction>Help me</instruction>')).toBe(false)
|
||||
})
|
||||
|
||||
it('should NOT detect prompts that contain HTML tags mid-content', () => {
|
||||
expect(isHtmlContent('<instruction><div>Test</div></instruction>')).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for plain text', () => {
|
||||
expect(isHtmlContent('just plain text')).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for empty/null/undefined', () => {
|
||||
expect(isHtmlContent('')).toBe(false)
|
||||
expect(isHtmlContent(null)).toBe(false)
|
||||
expect(isHtmlContent(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('escapeXmlTags', () => {
|
||||
it('should escape opening and closing tags to entities', () => {
|
||||
expect(escapeXmlTags('<question>text</question>')).toBe('<question>text</question>')
|
||||
})
|
||||
|
||||
it('should escape self-closing tags', () => {
|
||||
expect(escapeXmlTags('<my-separator />')).toBe('<my-separator />')
|
||||
})
|
||||
|
||||
it('should escape tags with attributes', () => {
|
||||
expect(escapeXmlTags('<context type="user">hello</context>')).toBe('<context type="user">hello</context>')
|
||||
})
|
||||
|
||||
it('should escape standard HTML tags too', () => {
|
||||
expect(escapeXmlTags('<div><p>text</p></div>')).toBe('<div><p>text</p></div>')
|
||||
})
|
||||
|
||||
it('should escape all tags in mixed content', () => {
|
||||
const input = '# Heading\n<question>What is {{name}}?</question>\n<p>paragraph</p>'
|
||||
const expected = '# Heading\n<question>What is {{name}}?</question>\n<p>paragraph</p>'
|
||||
expect(escapeXmlTags(input)).toBe(expected)
|
||||
})
|
||||
|
||||
it('should handle nested tags', () => {
|
||||
const input = '<outer><inner>text</inner></outer>'
|
||||
const expected = '<outer><inner>text</inner></outer>'
|
||||
expect(escapeXmlTags(input)).toBe(expected)
|
||||
})
|
||||
|
||||
it('should return empty/null/undefined as-is', () => {
|
||||
expect(escapeXmlTags('')).toBe('')
|
||||
expect(escapeXmlTags(null)).toBe(null)
|
||||
expect(escapeXmlTags(undefined)).toBe(undefined)
|
||||
})
|
||||
|
||||
it('should handle text with no tags', () => {
|
||||
expect(escapeXmlTags('just plain text')).toBe('just plain text')
|
||||
})
|
||||
|
||||
it('should handle tags with dots and hyphens in names', () => {
|
||||
expect(escapeXmlTags('<my.tag>text</my.tag>')).toBe('<my.tag>text</my.tag>')
|
||||
expect(escapeXmlTags('<my-tag>text</my-tag>')).toBe('<my-tag>text</my-tag>')
|
||||
})
|
||||
|
||||
it('should not double-escape already-escaped content', () => {
|
||||
const alreadyEscaped = '<question>text</question>'
|
||||
expect(escapeXmlTags(alreadyEscaped)).toBe(alreadyEscaped)
|
||||
})
|
||||
})
|
||||
|
||||
describe('unescapeXmlEntities', () => {
|
||||
it('should unescape entities in text nodes', () => {
|
||||
const json = {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: '<question>What?</question>' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
unescapeXmlEntities(json)
|
||||
expect(json.content[0].content[0].text).toBe('<question>What?</question>')
|
||||
})
|
||||
|
||||
it('should unescape standard HTML tag entities too', () => {
|
||||
const json = {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: '<div>content</div>' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
unescapeXmlEntities(json)
|
||||
expect(json.content[0].content[0].text).toBe('<div>content</div>')
|
||||
})
|
||||
|
||||
it('should handle nested content', () => {
|
||||
const json = {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: '<outer>' }]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: '<inner>text</inner>' }]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
content: [{ type: 'text', text: '</outer>' }]
|
||||
}
|
||||
]
|
||||
}
|
||||
unescapeXmlEntities(json)
|
||||
expect(json.content[0].content[0].text).toBe('<outer>')
|
||||
expect(json.content[1].content[0].text).toBe('<inner>text</inner>')
|
||||
expect(json.content[2].content[0].text).toBe('</outer>')
|
||||
})
|
||||
|
||||
it('should not modify nodes without entities', () => {
|
||||
const json = {
|
||||
type: 'doc',
|
||||
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'plain text' }] }]
|
||||
}
|
||||
unescapeXmlEntities(json)
|
||||
expect(json.content[0].content[0].text).toBe('plain text')
|
||||
})
|
||||
|
||||
it('should return the same object for chaining', () => {
|
||||
const json = { type: 'doc', content: [] }
|
||||
expect(unescapeXmlEntities(json)).toBe(json)
|
||||
})
|
||||
})
|
||||
|
||||
describe('unescapeXmlTags', () => {
|
||||
it('should unescape entity-escaped tags', () => {
|
||||
expect(unescapeXmlTags('<question>text</question>')).toBe('<question>text</question>')
|
||||
})
|
||||
|
||||
it('should unescape standard HTML tags too', () => {
|
||||
expect(unescapeXmlTags('<div>text</div>')).toBe('<div>text</div>')
|
||||
})
|
||||
|
||||
it('should unescape tags with attributes', () => {
|
||||
expect(unescapeXmlTags('<context type="user">hello</context>')).toBe('<context type="user">hello</context>')
|
||||
})
|
||||
|
||||
it('should unescape tags with attributes containing ampersands', () => {
|
||||
expect(unescapeXmlTags('<datasource url="foo&bar=1">text</datasource>')).toBe(
|
||||
'<datasource url="foo&bar=1">text</datasource>'
|
||||
)
|
||||
})
|
||||
|
||||
it('should handle mixed content', () => {
|
||||
const input = '# Heading\n<question>text</question>\nsome markdown'
|
||||
const expected = '# Heading\n<question>text</question>\nsome markdown'
|
||||
expect(unescapeXmlTags(input)).toBe(expected)
|
||||
})
|
||||
|
||||
it('should return empty/null/undefined as-is', () => {
|
||||
expect(unescapeXmlTags('')).toBe('')
|
||||
expect(unescapeXmlTags(null)).toBe(null)
|
||||
expect(unescapeXmlTags(undefined)).toBe(undefined)
|
||||
})
|
||||
|
||||
it('should pass through raw (unescaped) tags unchanged', () => {
|
||||
expect(unescapeXmlTags('<question>text</question>')).toBe('<question>text</question>')
|
||||
})
|
||||
})
|
||||
|
||||
describe('roundtrip (escape → unescape)', () => {
|
||||
const cases = [
|
||||
'<question>What is the answer?</question>',
|
||||
'<context type="system">You are helpful</context>',
|
||||
'<instructions>\n- Step 1\n- Step 2\n</instructions>',
|
||||
'<outer><inner>nested</inner></outer>',
|
||||
'# Title\n<question>{{input}}</question>\n**bold** text',
|
||||
'<my-component />',
|
||||
'No tags here, just **markdown**',
|
||||
'<div>standard html preserved</div>',
|
||||
'<example>This has <strong>standard</strong> HTML inside</example>'
|
||||
]
|
||||
|
||||
cases.forEach((input) => {
|
||||
it(`should roundtrip: ${input.substring(0, 50)}...`, () => {
|
||||
const escaped = escapeXmlTags(input)
|
||||
const restored = unescapeXmlTags(escaped)
|
||||
expect(restored).toBe(input)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('markdown formatting preservation', () => {
|
||||
it('should preserve heading syntax', () => {
|
||||
const input = '## My Task\nDo something useful'
|
||||
expect(escapeXmlTags(input)).toBe(input)
|
||||
expect(unescapeXmlTags(input)).toBe(input)
|
||||
})
|
||||
|
||||
it('should preserve bold and italic syntax', () => {
|
||||
const input = 'This is **bold** and *italic* and ***both***'
|
||||
expect(escapeXmlTags(input)).toBe(input)
|
||||
expect(unescapeXmlTags(input)).toBe(input)
|
||||
})
|
||||
|
||||
it('should preserve unordered list syntax', () => {
|
||||
const input = '- Step 1\n- Step 2\n- Step 3'
|
||||
expect(escapeXmlTags(input)).toBe(input)
|
||||
expect(unescapeXmlTags(input)).toBe(input)
|
||||
})
|
||||
|
||||
it('should preserve ordered list syntax', () => {
|
||||
const input = '1. First\n2. Second\n3. Third'
|
||||
expect(escapeXmlTags(input)).toBe(input)
|
||||
expect(unescapeXmlTags(input)).toBe(input)
|
||||
})
|
||||
|
||||
it('should preserve code blocks', () => {
|
||||
const input = '```python\nprint("hello")\n```'
|
||||
expect(escapeXmlTags(input)).toBe(input)
|
||||
expect(unescapeXmlTags(input)).toBe(input)
|
||||
})
|
||||
|
||||
it('should preserve inline code', () => {
|
||||
const input = 'Use `console.log()` for debugging'
|
||||
expect(escapeXmlTags(input)).toBe(input)
|
||||
expect(unescapeXmlTags(input)).toBe(input)
|
||||
})
|
||||
|
||||
it('should preserve markdown links [text](url)', () => {
|
||||
const input = 'See [the docs](https://example.com/path?q=1&r=2) for details'
|
||||
expect(escapeXmlTags(input)).toBe(input)
|
||||
expect(unescapeXmlTags(input)).toBe(input)
|
||||
})
|
||||
|
||||
it('should roundtrip HTML <a> links', () => {
|
||||
const input = 'Visit <a href="https://flowiseai.com">Flowise</a> for docs'
|
||||
const escaped = escapeXmlTags(input)
|
||||
expect(escaped).toContain('<a href="https://flowiseai.com">')
|
||||
expect(unescapeXmlTags(escaped)).toBe(input)
|
||||
})
|
||||
|
||||
it('should preserve complex pasted markdown with XML tags', () => {
|
||||
const input =
|
||||
'## System Prompt\n\n' +
|
||||
'<instructions>\n' +
|
||||
'- Be helpful\n' +
|
||||
'- Be **concise**\n' +
|
||||
'</instructions>\n\n' +
|
||||
'See [docs](https://example.com) for more info.\n' +
|
||||
'Also visit <a href="https://flowiseai.com">Flowise</a>.\n\n' +
|
||||
'```\ncode block\n```'
|
||||
const escaped = escapeXmlTags(input)
|
||||
expect(escaped).not.toBe(input)
|
||||
const restored = unescapeXmlTags(escaped)
|
||||
expect(restored).toBe(input)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Simulates the full editor save/reload cycle as it happens in RichInput:
|
||||
*
|
||||
* LOAD (saved value → editor):
|
||||
* 1. escapeXmlTags(savedValue) — entities prevent marked from stripping tags
|
||||
* 2. setContent(escaped, 'markdown') — marked parses entities as text nodes
|
||||
* 3. unescapeXmlEntities(editor.getJSON()) — fix "<" → "<" in ProseMirror JSON
|
||||
* 4. setContent(decodedJson) — editor now displays <question> correctly
|
||||
*
|
||||
* SAVE (editor → saved value):
|
||||
* 5. editor.getMarkdown() — serializes text nodes with raw "<"
|
||||
* 6. unescapeXmlTags(markdown) — safety net for any remaining entities
|
||||
* 7. onChange(result) — written to flow JSON
|
||||
*/
|
||||
describe('full editor save/reload cycle', () => {
|
||||
function simulateEditorCycle(userInput) {
|
||||
// --- LOAD ---
|
||||
// Step 1: escape XML tags to entities
|
||||
const escaped = escapeXmlTags(userInput)
|
||||
// Steps 2-3: marked creates text nodes with entities, then unescapeXmlEntities fixes them
|
||||
const mockJson = {
|
||||
type: 'doc',
|
||||
content: [{ type: 'paragraph', content: [{ type: 'text', text: escaped }] }]
|
||||
}
|
||||
unescapeXmlEntities(mockJson)
|
||||
// Step 4: verify editor would display proper angle brackets (no entities)
|
||||
const editorDisplayText = mockJson.content[0].content[0].text
|
||||
expect(editorDisplayText).not.toMatch(/<|>/)
|
||||
|
||||
// --- SAVE ---
|
||||
// Step 5: getMarkdown() outputs text node content as-is (no escaping)
|
||||
const markdownOutput = editorDisplayText
|
||||
// Step 6: unescape safety net
|
||||
return unescapeXmlTags(markdownOutput)
|
||||
}
|
||||
|
||||
it('should preserve XML-tagged prompt', () => {
|
||||
const userInput = '<question>What is the answer?</question>'
|
||||
expect(simulateEditorCycle(userInput)).toBe(userInput)
|
||||
})
|
||||
|
||||
it('should preserve tags with attributes', () => {
|
||||
const userInput = '<context type="system">You are helpful</context>'
|
||||
expect(simulateEditorCycle(userInput)).toBe(userInput)
|
||||
})
|
||||
|
||||
it('should preserve multiline structured prompt', () => {
|
||||
const userInput = '<instructions>\n- Step 1\n- Step 2\n</instructions>'
|
||||
expect(simulateEditorCycle(userInput)).toBe(userInput)
|
||||
})
|
||||
|
||||
it('should preserve nested XML tags', () => {
|
||||
const userInput = '<outer><inner>nested</inner></outer>'
|
||||
expect(simulateEditorCycle(userInput)).toBe(userInput)
|
||||
})
|
||||
|
||||
it('should preserve XML tags mixed with markdown and variables', () => {
|
||||
const userInput = '# Title\n<question>{{input}}</question>\n**bold** text'
|
||||
expect(simulateEditorCycle(userInput)).toBe(userInput)
|
||||
})
|
||||
|
||||
it('should preserve standard HTML tags too', () => {
|
||||
const userInput = '<div>content in a div</div>'
|
||||
expect(simulateEditorCycle(userInput)).toBe(userInput)
|
||||
})
|
||||
|
||||
it('should handle mixed custom and standard tags', () => {
|
||||
const userInput = '<example>This has <strong>bold</strong> HTML inside</example>'
|
||||
expect(simulateEditorCycle(userInput)).toBe(userInput)
|
||||
})
|
||||
|
||||
it('should leave plain markdown unchanged', () => {
|
||||
const userInput = 'No tags here, just **markdown**'
|
||||
expect(simulateEditorCycle(userInput)).toBe(userInput)
|
||||
})
|
||||
})
|
||||
})
|
||||
Generated
+57
-59
@@ -812,7 +812,7 @@ importers:
|
||||
version: 6.11.0
|
||||
'@google-cloud/logging-winston':
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.0([email protected])
|
||||
version: 6.0.0([email protected])([email protected])
|
||||
'@keyv/redis':
|
||||
specifier: ^4.2.0
|
||||
version: 4.3.3
|
||||
@@ -986,7 +986,7 @@ importers:
|
||||
version: 1.2.0
|
||||
multer-cloud-storage:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0
|
||||
version: 4.0.0([email protected])
|
||||
multer-s3:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.1(@aws-sdk/[email protected])
|
||||
@@ -1222,6 +1222,9 @@ importers:
|
||||
'@tabler/icons-react':
|
||||
specifier: ^3.30.0
|
||||
version: 3.31.0([email protected])
|
||||
'@tiptap/core':
|
||||
specifier: ^3.20.4
|
||||
version: 3.20.4(@tiptap/[email protected])
|
||||
'@tiptap/extension-code-block-lowlight':
|
||||
specifier: ^3.20.4
|
||||
version: 3.20.4(@tiptap/[email protected](@tiptap/[email protected]))(@tiptap/[email protected](@tiptap/[email protected](@tiptap/[email protected]))(@tiptap/[email protected]))(@tiptap/[email protected])([email protected])([email protected])
|
||||
@@ -1266,10 +1269,10 @@ importers:
|
||||
version: 16.4.5
|
||||
flowise-embed:
|
||||
specifier: latest
|
||||
version: 3.1.0
|
||||
version: 3.1.2
|
||||
flowise-embed-react:
|
||||
specifier: latest
|
||||
version: 3.1.1(@types/[email protected])([email protected].0)([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected])
|
||||
version: 3.1.2(@types/[email protected])([email protected].2)([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected])
|
||||
flowise-react-json-view:
|
||||
specifier: '*'
|
||||
version: 1.21.7(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])
|
||||
@@ -1391,9 +1394,12 @@ importers:
|
||||
'@vitejs/plugin-react':
|
||||
specifier: ^4.2.0
|
||||
version: 4.2.1([email protected](@types/[email protected])([email protected])([email protected]))
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0(@types/[email protected])([email protected])([email protected](@swc/[email protected])(@types/[email protected])([email protected]))
|
||||
pretty-quick:
|
||||
specifier: ^3.1.3
|
||||
version: 3.3.1(prettier@3.2.5)
|
||||
version: 3.3.1(prettier@2.8.8)
|
||||
react-scripts:
|
||||
specifier: ^5.0.1
|
||||
version: 5.0.1(@babel/[email protected](@babel/[email protected]))(@babel/[email protected](@babel/[email protected]))(@swc/[email protected])(@types/[email protected])([email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected](@swc/[email protected])(@types/[email protected])([email protected]))([email protected])([email protected])([email protected])([email protected])
|
||||
@@ -2109,6 +2115,7 @@ packages:
|
||||
'@aws-sdk/[email protected]':
|
||||
resolution: {integrity: sha512-BGU92StrWF0EJj8jX5EFvRkX9z4/CVIZfON0nWow8gb5ouKwz47o1rO9CP/k2b3F6g134/0XqwXvrUgIWfjJeA==}
|
||||
engines: {node: '>= 14.0.0'}
|
||||
deprecated: Please update your @aws-sdk client to a more recent version, such as https://github.com/aws/aws-sdk-js-v3/releases/tag/v3.982.0, if using browser-based WebSocket bidirectional streaming.
|
||||
|
||||
'@aws-sdk/[email protected]':
|
||||
resolution: {integrity: sha512-OH68BRF0rt9nDloq4zsfeHI0G21lj11a66qosaljtEP66PWm7tQ06feKbFkXHT5E1K3QhJW3nVyK8v2fEBY5fg==}
|
||||
@@ -3578,7 +3585,7 @@ packages:
|
||||
resolution: {integrity: sha512-C28o4U1T4dPe8avLv1xMQ4MtSE6G4skbVA3VfVZIrpTqklvO+homKB1uFEtNbuZlaKg+Znv8sjmGQUInTH17gA==}
|
||||
|
||||
'@codemirror/[email protected]':
|
||||
resolution: {integrity: sha512-Kx9BCSOLKmqNXEvmViuzsBQJ2VEa/wWwOATNpixOa+suttTV3rDnAUtAIt5ObAUFjXvZakWfFfF/EbxELnGLzQ==, tarball: https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.14.0.tgz}
|
||||
resolution: {integrity: sha512-Kx9BCSOLKmqNXEvmViuzsBQJ2VEa/wWwOATNpixOa+suttTV3rDnAUtAIt5ObAUFjXvZakWfFfF/EbxELnGLzQ==}
|
||||
peerDependencies:
|
||||
'@codemirror/language': ^6.0.0
|
||||
'@codemirror/state': ^6.0.0
|
||||
@@ -3601,16 +3608,16 @@ packages:
|
||||
resolution: {integrity: sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==}
|
||||
|
||||
'@codemirror/[email protected]':
|
||||
resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==, tarball: https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz}
|
||||
resolution: {integrity: sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==}
|
||||
|
||||
'@codemirror/[email protected]':
|
||||
resolution: {integrity: sha512-Hgke565YcO4fd9pe2uLYxnMufHO5rQwRr+AAhFq8ABuhkrjyX8R5p5s+hZUTdV60O0dMRjxKhBLxz8pu/MkUVA==}
|
||||
|
||||
'@codemirror/[email protected]':
|
||||
resolution: {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==, tarball: https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz}
|
||||
resolution: {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==}
|
||||
|
||||
'@codemirror/[email protected]':
|
||||
resolution: {integrity: sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==, tarball: https://registry.npmjs.org/@codemirror/language/-/language-6.10.1.tgz}
|
||||
resolution: {integrity: sha512-5GrXzrhq6k+gL5fjkAwt90nYDmjlzTIJV8THnxNFtNKWotMIlzzN+CpqxqwXOECnUdOndmSeWntVrVcv5axWRQ==}
|
||||
|
||||
'@codemirror/[email protected]':
|
||||
resolution: {integrity: sha512-+5YyicIaaAZKU8K43IQi8TBy6mF6giGeWAH7N96Z5LC30Wm5JMjqxOYIE9mxwMG1NbhT2mA3l9hA4uuKUM3E5g==}
|
||||
@@ -3619,7 +3626,7 @@ packages:
|
||||
resolution: {integrity: sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==}
|
||||
|
||||
'@codemirror/[email protected]':
|
||||
resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==, tarball: https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz}
|
||||
resolution: {integrity: sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==}
|
||||
|
||||
'@codemirror/[email protected]':
|
||||
resolution: {integrity: sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==}
|
||||
@@ -5863,7 +5870,7 @@ packages:
|
||||
resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==}
|
||||
|
||||
'@lezer/[email protected]':
|
||||
resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==, tarball: https://registry.npmjs.org/@lezer/common/-/common-1.2.1.tgz}
|
||||
resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==}
|
||||
|
||||
'@lezer/[email protected]':
|
||||
resolution: {integrity: sha512-7JhxupKuMBaWQKjQoLtzhGj83DdnZY9MckEOG5+/iLKNK2ZJqKc6hf6uc0HjwCX7Qlok44jBNqZhHKDhEhZYLA==}
|
||||
@@ -5878,16 +5885,16 @@ packages:
|
||||
resolution: {integrity: sha512-5IBr8LIO3xJdJH1e9aj/ZNLE4LSbdsx25wFmGRAZsj2zSmwAYjx26JyU/BYOCpRQlu1jcv1z3vy4NB9+UkfRow==}
|
||||
|
||||
'@lezer/[email protected]':
|
||||
resolution: {integrity: sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==, tarball: https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz}
|
||||
resolution: {integrity: sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==}
|
||||
|
||||
'@lezer/[email protected]':
|
||||
resolution: {integrity: sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==, tarball: https://registry.npmjs.org/@lezer/lr/-/lr-1.4.0.tgz}
|
||||
resolution: {integrity: sha512-Wst46p51km8gH0ZUmeNrtpRYmdlRHUpN1DQd3GFAyKANi8WVz8c2jHYTf1CVScFaCjQw1iO3ZZdqGDxQPRErTg==}
|
||||
|
||||
'@lezer/[email protected]':
|
||||
resolution: {integrity: sha512-ErbEQ15eowmJUyT095e9NJc3BI9yZ894fjSDtHftD0InkfUBGgnKSU6dvan9jqsZuNHg2+ag/1oyDRxNsENupQ==}
|
||||
|
||||
'@lezer/[email protected]':
|
||||
resolution: {integrity: sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==, tarball: https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz}
|
||||
resolution: {integrity: sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==}
|
||||
|
||||
'@llamaindex/[email protected]':
|
||||
resolution: {integrity: sha512-8HBSiAZkmX1RvpEM2czEVKqMUCKk7uvMSiDpMGWlEj3MUKBYCh+r8E2TtVhZfU4TunEI7nJRMcVBfXDyFz6Lpw==}
|
||||
@@ -9251,10 +9258,10 @@ packages:
|
||||
resolution: {integrity: sha512-rMVl/WrRtN/XtRiLEd/Bnb6TYQqDilZVWi8TC5YpKN6J6uK00zOxlJ7nopm3SwRL8FqzJSybNdMZMnbEKaoYQg==}
|
||||
|
||||
'@uiw/[email protected]':
|
||||
resolution: {integrity: sha512-319zklfinRpKxs9OIowhIt3kDYDe2uTg7Xx5tpYO9lHnU1GiJRQZflXUqxroLqZU1Zfx7pjXtFtVstL3sTuhqw==, tarball: https://registry.npmjs.org/@uiw/codemirror-theme-vscode/-/codemirror-theme-vscode-4.21.24.tgz}
|
||||
resolution: {integrity: sha512-319zklfinRpKxs9OIowhIt3kDYDe2uTg7Xx5tpYO9lHnU1GiJRQZflXUqxroLqZU1Zfx7pjXtFtVstL3sTuhqw==}
|
||||
|
||||
'@uiw/[email protected]':
|
||||
resolution: {integrity: sha512-InY24KWP8YArDBACWHKFZ6ZU+WCvRHf3ZB2cCVxMVN35P1ANUmRzpAP2ernZQ5OIriL1/A/kXgD0Zg3Y65PNgg==, tarball: https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.21.24.tgz}
|
||||
resolution: {integrity: sha512-InY24KWP8YArDBACWHKFZ6ZU+WCvRHf3ZB2cCVxMVN35P1ANUmRzpAP2ernZQ5OIriL1/A/kXgD0Zg3Y65PNgg==}
|
||||
peerDependencies:
|
||||
'@codemirror/language': '>=6.0.0'
|
||||
'@codemirror/state': '>=6.0.0'
|
||||
@@ -9484,6 +9491,7 @@ packages:
|
||||
'@xmldom/[email protected]':
|
||||
resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
deprecated: this version has critical issues, please update to the latest version
|
||||
|
||||
'@xtuc/[email protected]':
|
||||
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
|
||||
@@ -9865,7 +9873,7 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==, tarball: https://registry.npmjs.org/asap/-/asap-2.0.6.tgz}
|
||||
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==}
|
||||
@@ -10124,7 +10132,7 @@ packages:
|
||||
resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==, tarball: https://registry.npmjs.org/base16/-/base16-1.0.0.tgz}
|
||||
resolution: {integrity: sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
|
||||
@@ -10144,6 +10152,7 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
deprecated: Security vulnerability fixed in 5.2.0, please upgrade
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==}
|
||||
@@ -12401,13 +12410,13 @@ packages:
|
||||
resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==, tarball: https://registry.npmjs.org/fbemitter/-/fbemitter-3.0.0.tgz}
|
||||
resolution: {integrity: sha512-KWKaceCwKQU0+HPoop6gn4eOHk50bBv/VxjJtGMfwmJt3D29JpN4H4eisCtIPA+a8GVBam+ldMMpMjJUvpDyHw==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==, tarball: https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz}
|
||||
resolution: {integrity: sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==, tarball: https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz}
|
||||
resolution: {integrity: sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
|
||||
@@ -12543,20 +12552,20 @@ packages:
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
|
||||
|
||||
[email protected].1:
|
||||
resolution: {integrity: sha512-86fL3Z/OLPtTpcL5mgd+pZWaJcOON9L5gOc2NSzMCXeXOXeLtgktfixbeYXZ+22L2pJdD5bDuLByE0GZ04DTcA==}
|
||||
[email protected].2:
|
||||
resolution: {integrity: sha512-ugzG31lKAw1hX5oAQ3R8MLff+jFpAYtL55yCUEz4VVN4hgI0c4qsatCWl4WM21xvTJ7SONxxFBzxUawwaG1VoQ==}
|
||||
peerDependencies:
|
||||
flowise-embed: '*'
|
||||
react: 18.x
|
||||
|
||||
[email protected].0:
|
||||
resolution: {integrity: sha512-znZuKTlnEp+YSXzDvCc1x8Z3F4+oKicoLCWokDn3+7DLMSRxXYqfVhr3yVZFfPE8M1akt6szzx57i71Iqksrlg==}
|
||||
[email protected].2:
|
||||
resolution: {integrity: sha512-Uo7cqTHUy5Oz93LPy10kiTWpmKL39aJVe6NNKiKSfjajaql2tpS6GBiKv0dTCsOyssbPziCRUCuagOmt4kwYWg==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-Er/leiIzJZ691kWqXkjCWv29g0G5qWA4juBOcGZ1P5kBLnkKFxbWc5x8JsSPcHuJQIiGNX/dYEzp2ZrOV3YYMg==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-oFjwtSLJkUWk6waLh8heCQ4o9b60FJRA2X8LefaZp5WaJvj/Rr2HILjk+ocf1JkfTcq8jc6t2jfIybg4leWsaQ==, tarball: https://registry.npmjs.org/flowise-react-json-view/-/flowise-react-json-view-1.21.7.tgz}
|
||||
resolution: {integrity: sha512-oFjwtSLJkUWk6waLh8heCQ4o9b60FJRA2X8LefaZp5WaJvj/Rr2HILjk+ocf1JkfTcq8jc6t2jfIybg4leWsaQ==}
|
||||
peerDependencies:
|
||||
react: ^17.0.0 || ^16.3.0 || ^15.5.4
|
||||
react-dom: ^17.0.0 || ^16.3.0 || ^15.5.4
|
||||
@@ -12565,7 +12574,7 @@ packages:
|
||||
resolution: {integrity: sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw==, tarball: https://registry.npmjs.org/flux/-/flux-4.0.4.tgz}
|
||||
resolution: {integrity: sha512-NCj3XlayA2UsapRpM7va6wU1+9rE5FIL7qoMcmxWHRzbp0yujihMBm9BBHZ1MDIk5h5o2Bl6eGiCe8rYELAmYw==}
|
||||
peerDependencies:
|
||||
react: ^15.0.2 || ^16.0.0 || ^17.0.0
|
||||
|
||||
@@ -14682,7 +14691,7 @@ packages:
|
||||
resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==, tarball: https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz}
|
||||
resolution: {integrity: sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
||||
@@ -14691,7 +14700,7 @@ packages:
|
||||
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==, tarball: https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz}
|
||||
resolution: {integrity: sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
|
||||
@@ -17003,7 +17012,7 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==, tarball: https://registry.npmjs.org/promise/-/promise-7.3.1.tgz}
|
||||
resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==}
|
||||
@@ -17160,7 +17169,7 @@ packages:
|
||||
deprecated: < 24.15.0 is no longer supported
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==, tarball: https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz}
|
||||
resolution: {integrity: sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==}
|
||||
@@ -17272,7 +17281,7 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ==, tarball: https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.6.0.tgz}
|
||||
resolution: {integrity: sha512-yvh/7CArceR/jNATXOKDlvTnPKPmGZz7zsenQ3jUwLzHkNUR0CvY3yGYJbWJ/nnxsL8Sgmt5cO3/SILVuPO6TQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-ENNuxG07yO+OuX1ChRje3ieefPRz6yrIpHmebQlaFQgzcAHbUfVeTINpOpoI9bSRSObeYo/OdHsporeToZ7fcg==}
|
||||
@@ -17346,7 +17355,7 @@ packages:
|
||||
resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==, tarball: https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz}
|
||||
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ==}
|
||||
@@ -17446,7 +17455,7 @@ packages:
|
||||
react: '>= 0.14.0'
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==, tarball: https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz}
|
||||
resolution: {integrity: sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
@@ -19263,7 +19272,7 @@ packages:
|
||||
resolution: {integrity: sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==, tarball: https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz}
|
||||
resolution: {integrity: sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
|
||||
@@ -19469,12 +19478,12 @@ packages:
|
||||
resolution: {integrity: sha512-K4+koEq4II9FqKKdLyMwfVFiWvTLJsdsIihXCprumjlOwpviO44E4hAhLYBLb6CEVTZh9hXXMTQHIT+Hwv5BPw==}
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==, tarball: https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz}
|
||||
resolution: {integrity: sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==}
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==, tarball: https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz}
|
||||
resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
@@ -19483,7 +19492,7 @@ packages:
|
||||
optional: true
|
||||
|
||||
[email protected]:
|
||||
resolution: {integrity: sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==, tarball: https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz}
|
||||
resolution: {integrity: sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
@@ -20121,7 +20130,7 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
|
||||
xlsx@https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz:
|
||||
resolution: {integrity: sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==, tarball: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz}
|
||||
resolution: {tarball: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz}
|
||||
version: 0.20.3
|
||||
engines: {node: '>=0.8'}
|
||||
hasBin: true
|
||||
@@ -25493,7 +25502,7 @@ snapshots:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@google-cloud/[email protected]':
|
||||
'@google-cloud/[email protected]([email protected])':
|
||||
dependencies:
|
||||
'@google-cloud/projectify': 4.0.0
|
||||
'@google-cloud/promisify': 4.1.0
|
||||
@@ -25508,9 +25517,9 @@ snapshots:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@google-cloud/[email protected]([email protected])':
|
||||
'@google-cloud/[email protected]([email protected])([email protected])':
|
||||
dependencies:
|
||||
'@google-cloud/logging': 11.2.0
|
||||
'@google-cloud/logging': 11.2.0([email protected])
|
||||
google-auth-library: 9.15.1([email protected])
|
||||
lodash.mapvalues: 4.6.0
|
||||
winston: 3.12.0
|
||||
@@ -25519,9 +25528,9 @@ snapshots:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@google-cloud/[email protected]':
|
||||
'@google-cloud/[email protected]([email protected])':
|
||||
dependencies:
|
||||
'@google-cloud/common': 5.0.2
|
||||
'@google-cloud/common': 5.0.2([email protected])
|
||||
'@google-cloud/paginator': 5.0.2
|
||||
'@google-cloud/projectify': 4.0.0
|
||||
'@google-cloud/promisify': 4.1.0
|
||||
@@ -35683,10 +35692,10 @@ snapshots:
|
||||
|
||||
[email protected]: {}
|
||||
|
||||
[email protected].1(@types/[email protected])([email protected].0)([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected]):
|
||||
[email protected].2(@types/[email protected])([email protected].2)([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected]):
|
||||
dependencies:
|
||||
'@ladle/react': 2.5.1(@types/[email protected])([email protected]([email protected]))([email protected])([email protected])([email protected])([email protected])
|
||||
flowise-embed: 3.1.0
|
||||
flowise-embed: 3.1.2
|
||||
react: 18.2.0
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
@@ -35700,7 +35709,7 @@ snapshots:
|
||||
- terser
|
||||
- typescript
|
||||
|
||||
[email protected].0:
|
||||
[email protected].2:
|
||||
dependencies:
|
||||
'@babel/core': 7.24.0
|
||||
'@microsoft/fetch-event-source': 2.0.1
|
||||
@@ -39719,7 +39728,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
[email protected]:
|
||||
[email protected]([email protected]):
|
||||
dependencies:
|
||||
'@google-cloud/storage': 7.16.0([email protected])
|
||||
'@types/express': 5.0.1
|
||||
@@ -41314,17 +41323,6 @@ snapshots:
|
||||
prettier: 2.8.8
|
||||
tslib: 2.6.2
|
||||
|
||||
[email protected]([email protected]):
|
||||
dependencies:
|
||||
execa: 4.1.0
|
||||
find-up: 4.1.0
|
||||
ignore: 5.3.1
|
||||
mri: 1.2.0
|
||||
picocolors: 1.0.0
|
||||
picomatch: 3.0.1
|
||||
prettier: 3.2.5
|
||||
tslib: 2.6.2
|
||||
|
||||
[email protected]([email protected]):
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
|
||||
Reference in New Issue
Block a user