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 * @Author: ZhaoYing
* @Date: 2026-02-02 15:17:31 * @Date: 2026-02-02 15:17:31
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-02 16:06:03 * @Last Modified time: 2026-04-07 15:14:02
*/ */
/** /**
* RbMarkdown Component * RbMarkdown Component
@@ -97,10 +97,6 @@ const buildComponents = (onFormSubmit?: (values: Record<string, any>) => void) =
const ctx = useContext(FormContext) const ctx = useContext(FormContext)
return <RbButton {...props} onClick={() => ctx?.onSubmit?.(ctx.values)}>{[children]}</RbButton> 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) => { input: ({ children, ...props }: any) => {
const ctx = useContext(FormContext) const ctx = useContext(FormContext)
const handleChange = useCallback((val: any) => { const handleChange = useCallback((val: any) => {

View File

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

View File

@@ -1,8 +1,8 @@
/* /*
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-04-02 17:10:59 * @Date: 2026-04-02 17:10:59
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-04-02 17:10:59 * @Last Modified time: 2026-04-07 14:50:14
*/ */
import { useEffect, useState, useRef, type FC } from 'react'; import { useEffect, useState, useRef, type FC } from 'react';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
@@ -161,7 +161,7 @@ const Jinja2AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) =>
{Object.entries(groupedSuggestions).map(([nodeId, nodeOptions]) => ( {Object.entries(groupedSuggestions).map(([nodeId, nodeOptions]) => (
<div key={nodeId}> <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]"> <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} {nodeOptions[0]?.nodeData?.name || nodeId}
</Flex> </Flex>
{nodeOptions.map((option) => { {nodeOptions.map((option) => {

View File

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