fix(web): JinjaRender's form bugfix
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
import { TextNode, $createTextNode } from 'lexical';
|
import { TextNode, $createTextNode, $getSelection, $isRangeSelection } from 'lexical';
|
||||||
|
|
||||||
const Jinja2HighlightPlugin = () => {
|
const Jinja2HighlightPlugin = () => {
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
@@ -18,6 +18,16 @@ const Jinja2HighlightPlugin = () => {
|
|||||||
const parent = textNode.getParent();
|
const parent = textNode.getParent();
|
||||||
if (!parent) return;
|
if (!parent) return;
|
||||||
|
|
||||||
|
// Preserve selection
|
||||||
|
const selection = $getSelection();
|
||||||
|
let selectionOffset = null;
|
||||||
|
if ($isRangeSelection(selection)) {
|
||||||
|
const anchor = selection.anchor;
|
||||||
|
if (anchor.getNode() === textNode) {
|
||||||
|
selectionOffset = anchor.offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const tokens = tokenizeJinja2(text);
|
const tokens = tokenizeJinja2(text);
|
||||||
|
|
||||||
// Skip if no meaningful tokenization (only one text token)
|
// Skip if no meaningful tokenization (only one text token)
|
||||||
@@ -85,6 +95,19 @@ const Jinja2HighlightPlugin = () => {
|
|||||||
for (let i = 1; i < newNodes.length; i++) {
|
for (let i = 1; i < newNodes.length; i++) {
|
||||||
newNodes[i - 1].insertAfter(newNodes[i]);
|
newNodes[i - 1].insertAfter(newNodes[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore selection
|
||||||
|
if (selectionOffset !== null && $isRangeSelection(selection)) {
|
||||||
|
let currentOffset = 0;
|
||||||
|
for (const node of newNodes) {
|
||||||
|
const nodeLength = node.getTextContent().length;
|
||||||
|
if (currentOffset + nodeLength >= selectionOffset) {
|
||||||
|
node.select(selectionOffset - currentOffset, selectionOffset - currentOffset);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
currentOffset += nodeLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [editor]);
|
}, [editor]);
|
||||||
|
|||||||
@@ -31,13 +31,11 @@ const JinjaRender: FC<JinjaRenderProps> = ({ selectedNode, options, templateOpti
|
|||||||
const form = Form.useFormInstance()
|
const form = Form.useFormInstance()
|
||||||
const values = Form.useWatch([], form) || {}
|
const values = Form.useWatch([], form) || {}
|
||||||
|
|
||||||
console.log('JinjaRender values', values)
|
|
||||||
|
|
||||||
const prevMappingNamesRef = useRef<string[]>([])
|
const prevMappingNamesRef = useRef<string[]>([])
|
||||||
const prevTemplateVarsRef = useRef<string[]>([])
|
const prevTemplateVarsRef = useRef<string[]>([])
|
||||||
const syncTimeoutRef = useRef<number | null>(null)
|
|
||||||
const isSyncingRef = useRef(false)
|
const isSyncingRef = useRef(false)
|
||||||
const lastSyncSourceRef = useRef<'mapping' | 'template' | null>(null)
|
const lastSyncSourceRef = useRef<'mapping' | 'template' | null>(null)
|
||||||
|
const editorKeyRef = useRef(0)
|
||||||
|
|
||||||
// Reset refs when node changes
|
// Reset refs when node changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -68,46 +66,39 @@ const JinjaRender: FC<JinjaRenderProps> = ({ selectedNode, options, templateOpti
|
|||||||
|
|
||||||
if (JSON.stringify(prevNames) === JSON.stringify(currentMappingNames)) return
|
if (JSON.stringify(prevNames) === JSON.stringify(currentMappingNames)) return
|
||||||
|
|
||||||
if (syncTimeoutRef.current) clearTimeout(syncTimeoutRef.current)
|
let updatedTemplate = String(form.getFieldValue('template') || '')
|
||||||
const activeElement = document.activeElement as HTMLElement
|
|
||||||
|
|
||||||
syncTimeoutRef.current = setTimeout(() => {
|
prevNames.forEach((oldName, index) => {
|
||||||
let updatedTemplate = String(form.getFieldValue('template') || '')
|
const newName = currentMappingNames[index]
|
||||||
|
if (newName && oldName !== newName) {
|
||||||
prevNames.forEach((oldName, index) => {
|
updatedTemplate = updatedTemplate.replace(
|
||||||
const newName = currentMappingNames[index]
|
new RegExp(`{{\\s*${oldName}\\s*}}`, 'g'),
|
||||||
if (newName && oldName !== newName) {
|
`{{${newName}}}`
|
||||||
updatedTemplate = updatedTemplate.replace(
|
)
|
||||||
new RegExp(`{{\\s*${oldName}\\s*}}`, 'g'),
|
|
||||||
`{{${newName}}}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (updatedTemplate !== form.getFieldValue('template')) {
|
|
||||||
isSyncingRef.current = true
|
|
||||||
lastSyncSourceRef.current = 'mapping'
|
|
||||||
|
|
||||||
prevTemplateVarsRef.current = extractTemplateVars(updatedTemplate)
|
|
||||||
prevMappingNamesRef.current = currentMappingNames
|
|
||||||
form.setFieldValue('template', updatedTemplate)
|
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
|
||||||
activeElement?.focus?.()
|
|
||||||
setTimeout(() => {
|
|
||||||
isSyncingRef.current = false
|
|
||||||
lastSyncSourceRef.current = null
|
|
||||||
}, 50)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
prevMappingNamesRef.current = currentMappingNames
|
|
||||||
}
|
}
|
||||||
}, 0)
|
})
|
||||||
|
|
||||||
|
|
||||||
|
if (updatedTemplate !== form.getFieldValue('template')) {
|
||||||
|
isSyncingRef.current = true
|
||||||
|
lastSyncSourceRef.current = 'mapping'
|
||||||
|
|
||||||
|
prevTemplateVarsRef.current = extractTemplateVars(updatedTemplate)
|
||||||
|
prevMappingNamesRef.current = currentMappingNames
|
||||||
|
form.setFieldValue('template', updatedTemplate)
|
||||||
|
editorKeyRef.current++
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
isSyncingRef.current = false
|
||||||
|
lastSyncSourceRef.current = null
|
||||||
|
}, 0)
|
||||||
|
} else {
|
||||||
|
prevMappingNamesRef.current = currentMappingNames
|
||||||
|
}
|
||||||
}, [values?.mapping, selectedNode?.data?.type, form])
|
}, [values?.mapping, selectedNode?.data?.type, form])
|
||||||
|
|
||||||
// Sync mapping when template variables change
|
// Sync mapping when template variables change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('values?.template', values?.template)
|
|
||||||
if (
|
if (
|
||||||
isSyncingRef.current ||
|
isSyncingRef.current ||
|
||||||
lastSyncSourceRef.current === 'template' ||
|
lastSyncSourceRef.current === 'template' ||
|
||||||
@@ -155,11 +146,10 @@ const JinjaRender: FC<JinjaRenderProps> = ({ selectedNode, options, templateOpti
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Remove unused mappings and duplicates
|
// Remove duplicates only
|
||||||
const seenNames = new Set<string>()
|
const seenNames = new Set<string>()
|
||||||
const finalMapping = updatedMapping.filter(item => {
|
const finalMapping = updatedMapping.filter(item => {
|
||||||
const isUsed = templateVars.some(v => item.name === v || item.value === `{{${v}}}`)
|
if (!item.name || seenNames.has(item.name)) return false
|
||||||
if (!isUsed || !item.name || seenNames.has(item.name)) return false
|
|
||||||
seenNames.add(item.name)
|
seenNames.add(item.name)
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
@@ -190,6 +180,7 @@ const JinjaRender: FC<JinjaRenderProps> = ({ selectedNode, options, templateOpti
|
|||||||
|
|
||||||
<Form.Item name="template">
|
<Form.Item name="template">
|
||||||
<MessageEditor
|
<MessageEditor
|
||||||
|
key={editorKeyRef.current}
|
||||||
title={t('workflow.config.jinja-render.template')}
|
title={t('workflow.config.jinja-render.template')}
|
||||||
isArray={false}
|
isArray={false}
|
||||||
parentName="template"
|
parentName="template"
|
||||||
|
|||||||
Reference in New Issue
Block a user