fix(web): jinja2 editor

This commit is contained in:
zhaoying
2026-04-07 15:24:53 +08:00
parent 0b5a030e46
commit 9a4a614fc8
5 changed files with 60 additions and 44 deletions

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing
* @Date: 2026-02-02 15:17:31
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-02 16:06:03
* @Last Modified time: 2026-04-07 15:14:02
*/
/**
* RbMarkdown Component
@@ -97,10 +97,6 @@ const buildComponents = (onFormSubmit?: (values: Record<string, any>) => void) =
const ctx = useContext(FormContext)
return <RbButton {...props} onClick={() => ctx?.onSubmit?.(ctx.values)}>{[children]}</RbButton>
},
table: ({ children, ...props }: any) => <div className="rb:overflow-x-auto rb:max-w-full"><table className="rb:border rb:border-[#D9D9D9] rb:mb-2" {...props}>{children}</table></div>,
tr: ({ children, ...props }: any) => <tr className="rb:border rb:border-[#D9D9D9]" {...props}>{children}</tr>,
th: ({ children, ...props }: any) => <th className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left rb:font-bold" {...props}>{children}</th>,
td: ({ children, ...props }: any) => <td className="rb:border rb:border-[#D9D9D9] rb:px-2 rb:py-1 rb:text-left" {...props}>{children}</td>,
input: ({ children, ...props }: any) => {
const ctx = useContext(FormContext)
const handleChange = useCallback((val: any) => {

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-04-02 15:15:36
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-02 15:15:36
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-07 14:48:00
*/
import { type FC, useEffect, useMemo } from 'react';
import { LexicalComposer } from '@lexical/react/LexicalComposer';
@@ -12,7 +12,7 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
import { type Suggestion } from './plugin/AutocompletePlugin';
import CharacterCountPlugin from './plugin/CharacterCountPlugin';
import Jinjia2CharacterCountPlugin from './plugin/Jinjia2CharacterCountPlugin';
import Jinja2InitialValuePlugin from './plugin/Jinja2InitialValuePlugin';
import Jinja2AutocompletePlugin from './plugin/Jinja2AutocompletePlugin';
import Jinja2HighlightPlugin from './plugin/Jinja2HighlightPlugin';
@@ -89,7 +89,7 @@ export interface Jinja2EditorProps {
const Jinja2Editor: FC<Jinja2EditorProps> = ({
placeholder = '请输入内容...',
value = '',
value,
onChange,
options = [],
variant = 'borderless',
@@ -174,8 +174,8 @@ const Jinja2Editor: FC<Jinja2EditorProps> = ({
<Jinja2HighlightPlugin />
<LineNumberPlugin />
<Jinja2AutocompletePlugin options={options} />
<CharacterCountPlugin setCount={() => {}} onChange={onChange} waitForInit />
<Jinja2InitialValuePlugin value={value} />
<Jinjia2CharacterCountPlugin setCount={() => {}} />
<Jinja2InitialValuePlugin value={value} onChange={onChange} />
<Jinja2BlurPlugin />
</div>
</LexicalComposer>

View File

@@ -1,8 +1,8 @@
/*
* @Author: ZhaoYing
* @Date: 2026-04-02 17:10:59
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-02 17:10:59
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-07 14:50:14
*/
import { useEffect, useState, useRef, type FC } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
@@ -161,7 +161,7 @@ const Jinja2AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) =>
{Object.entries(groupedSuggestions).map(([nodeId, nodeOptions]) => (
<div key={nodeId}>
<Flex align="center" gap={4} className="rb:px-3! rb:text-[12px] rb:py-1.25! rb:font-medium rb:text-[#5B6167]">
{nodeOptions[0]?.nodeData?.icon && <img src={nodeOptions[0].nodeData.icon} className="rb:size-3" alt="" />}
{nodeOptions[0]?.nodeData?.icon && <div className={`rb:size-3 rb:bg-cover ${nodeOptions[0].nodeData.icon}`} />}
{nodeOptions[0]?.nodeData?.name || nodeId}
</Flex>
{nodeOptions.map((option) => {

View File

@@ -6,53 +6,50 @@
*/
import { useEffect, useRef } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $getRoot, $createParagraphNode, $createTextNode } from 'lexical';
import { $getRoot, $createParagraphNode, $createTextNode, $isParagraphNode } from 'lexical';
interface Jinja2InitialValuePluginProps {
value: string;
value?: string;
onChange?: (value: string) => void;
}
const Jinja2InitialValuePlugin: React.FC<Jinja2InitialValuePluginProps> = ({ value }) => {
const Jinja2InitialValuePlugin: React.FC<Jinja2InitialValuePluginProps> = ({ value, onChange }) => {
const [editor] = useLexicalComposerContext();
const prevValueRef = useRef<string>('');
const isUserInputRef = useRef(false);
const internalValueRef = useRef<string | undefined>(undefined);
const onChangeRef = useRef(onChange);
onChangeRef.current = onChange;
useEffect(() => {
return editor.registerUpdateListener(({ editorState, tags }) => {
if (tags.has('programmatic')) return;
if (internalValueRef.current === undefined) return;
editorState.read(() => {
const textContent = $getRoot().getTextContent();
if (textContent !== prevValueRef.current) {
isUserInputRef.current = true;
prevValueRef.current = textContent;
const paragraphs = $getRoot().getChildren()
.filter($isParagraphNode)
.map(p => p.getChildren().map(n => n.getTextContent()).join(''));
const text = paragraphs.join('\n');
if (text !== internalValueRef.current) {
internalValueRef.current = text;
onChangeRef.current?.(text);
}
});
});
}, [editor]);
useEffect(() => {
if (value === prevValueRef.current) return;
if (value === undefined) return;
if (value === internalValueRef.current) return;
if (isUserInputRef.current) {
prevValueRef.current = value;
isUserInputRef.current = false;
return;
}
prevValueRef.current = value;
isUserInputRef.current = false;
queueMicrotask(() => {
editor.update(() => {
const root = $getRoot();
root.clear();
value.split('\n').forEach((line) => {
const paragraph = $createParagraphNode();
paragraph.append($createTextNode(line));
root.append(paragraph);
});
}, { tag: 'programmatic' });
});
internalValueRef.current = value;
editor.update(() => {
const root = $getRoot();
root.clear();
value.split('\n').forEach((line) => {
const paragraph = $createParagraphNode();
paragraph.append($createTextNode(line));
root.append(paragraph);
});
}, { tag: 'programmatic' });
}, [value, editor]);
return null;

View File

@@ -0,0 +1,23 @@
import { useEffect } from 'react';
import { $getRoot, $isParagraphNode } from 'lexical';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
const Jinjia2CharacterCountPlugin = ({ setCount }: { setCount: (count: number) => void }) => {
const [editor] = useLexicalComposerContext();
useEffect(() => {
return editor.registerUpdateListener(({ editorState }) => {
editorState.read(() => {
const root = $getRoot();
const paragraphs = root.getChildren()
.filter($isParagraphNode)
.map(p => p.getChildren().map(n => n.getTextContent()).join(''));
setCount(paragraphs.join('\n').length);
});
});
}, [editor, setCount]);
return null;
}
export default Jinjia2CharacterCountPlugin