refactor: extract jinja render's form

This commit is contained in:
zhaoying
2026-01-20 10:42:13 +08:00
parent cd1a50a1d1
commit 804d87bca2
6 changed files with 279 additions and 151 deletions

View File

@@ -16,6 +16,7 @@ import InitialValuePlugin from './plugin/InitialValuePlugin';
import CommandPlugin from './plugin/CommandPlugin';
import Jinja2HighlightPlugin from './plugin/Jinja2HighlightPlugin';
import LineNumberPlugin from './plugin/LineNumberPlugin';
import BlurPlugin from './plugin/BlurPlugin';
import { VariableNode } from './nodes/VariableNode'
interface LexicalEditorProps {
@@ -113,8 +114,10 @@ const Editor: FC<LexicalEditorProps> =({
display: flex;
align-items: flex-start;
}
.editor-content-with-numbers {
.editor-content-wrapper {
flex: 1;
}
.editor-content-with-numbers {
white-space: pre-wrap;
}
.editor-content-with-numbers p {
@@ -174,18 +177,20 @@ const Editor: FC<LexicalEditorProps> =({
<div className="line-numbers">
<div>1</div>
</div>
<ContentEditable
className="editor-content-with-numbers"
style={{
minHeight: minheight,
padding: '4px 0',
outline: 'none',
resize: 'none',
fontSize: fontSize,
lineHeight: lineHeight,
border: 'none',
}}
/>
<div className="editor-content-wrapper">
<ContentEditable
className="editor-content-with-numbers"
style={{
minHeight: minheight,
padding: '4px 0',
outline: 'none',
resize: 'none',
fontSize: fontSize,
lineHeight: lineHeight,
border: 'none',
}}
/>
</div>
</div>
) : (
<ContentEditable
@@ -207,8 +212,8 @@ const Editor: FC<LexicalEditorProps> =({
style={{
minHeight: placeHolderMinheight,
position: 'absolute',
top: variant === 'borderless' ? '0' : '6px',
left: enableJinja2 ? '59px' : (variant === 'borderless' ? '0' : '11px'),
top: enableJinja2 ? '4px' : variant === 'borderless' ? '0' : '6px',
left: enableJinja2 ? '16px' : (variant === 'borderless' ? '0' : '11px'),
color: '#A8A9AA',
fontSize: fontSize,
lineHeight: placeHolderMinheight,
@@ -227,6 +232,7 @@ const Editor: FC<LexicalEditorProps> =({
<AutocompletePlugin options={options} enableJinja2={enableJinja2} />
<CharacterCountPlugin setCount={(count) => { setCount(count) }} onChange={onChange} />
<InitialValuePlugin value={value} options={options} enableJinja2={enableJinja2} />
{enableJinja2 && <BlurPlugin />}
</div>
</LexicalComposer>
);

View File

@@ -36,7 +36,7 @@ const VariableComponent: React.FC<{ nodeKey: NodeKey; data: Suggestion }> = ({
return (
<span
onClick={handleClick}
className={clsx('rb:border rb:rounded-md rb:bg-white rb:text-[12px] rb:inline-flex rb:items-center rb:py-0.5 rb:px-1.5 rb:mx-0.5 rb:cursor-pointer', {
className={clsx('rb:border rb:rounded-md rb:bg-white rb:text-[10px] rb:inline-flex rb:items-center rb:py-0 rb:px-1.5 rb:mx-0.5 rb:cursor-pointer', {
'rb:border-[#155EEF]': isSelected,
'rb:border-[#DFE4ED]': !isSelected
})}

View File

@@ -1,6 +1,6 @@
import { useEffect, useState, type FC } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $getSelection, $isRangeSelection } from 'lexical';
import { $getSelection, $isRangeSelection, $isTextNode } from 'lexical';
import { INSERT_VARIABLE_COMMAND } from '../commands';
import type { NodeProperties } from '../../../types'
@@ -96,7 +96,9 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }>
const textAfter = nodeText.substring(anchorOffset);
const newText = textBefore + `{{${suggestion.value}}}` + textAfter;
anchorNode.setTextContent(newText);
if ($isTextNode(anchorNode)) {
anchorNode.setTextContent(newText);
}
// 设置光标位置到插入文本之后
const newOffset = textBefore.length + `{{${suggestion.value}}}`.length;
@@ -129,6 +131,8 @@ const AutocompletePlugin: FC<{ options: Suggestion[], enableJinja2?: boolean }>
}
return (
<div
data-autocomplete-popup="true"
onMouseDown={(e) => e.preventDefault()}
style={{
position: 'fixed',
top: popupPosition.top,

View File

@@ -0,0 +1,33 @@
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { useEffect } from 'react';
import { $setSelection } from 'lexical';
export default function BlurPlugin() {
const [editor] = useLexicalComposerContext();
useEffect(() => {
return editor.registerRootListener((rootElement) => {
if (rootElement) {
const handleBlur = (e: FocusEvent) => {
// 检查是否点击了自动完成弹窗
const target = e.target as HTMLElement;
console.log('target', target)
if (target?.closest('[data-autocomplete-popup="true"]')) {
return;
}
editor.update(() => {
$setSelection(null);
});
};
rootElement.addEventListener('blur', handleBlur);
return () => {
rootElement.removeEventListener('blur', handleBlur);
};
}
});
}, [editor]);
return null;
}