mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-19 18:14:38 +00:00
V4.12.4 features (#5626)
* fix: push again, user select option button and form input radio content overflow (#5601) * fix: push again, user select option button and form input radio content overflow * fix: use useCallback instead of useMemo, fix unnecessary delete * fix: Move the variable inside the component * fix: do not pass valueLabel to MySelect * ui * del collection api adapt * refactor: inherit permission (#5529) * refactor: permission update conflict check function * refactor(permission): app collaborator update api * refactor(permission): support app update collaborator * feat: support fe permission conflict check * refactor(permission): app permission * refactor(permission): dataset permission * refactor(permission): team permission * chore: fe adjust * fix: type error * fix: audit pagiation * fix: tc * chore: initv4130 * fix: app/dataset auth logic * chore: move code * refactor(permission): remove selfPermission * fix: mock * fix: test * fix: app & dataset auth * fix: inherit * test(inheritPermission): test syncChildrenPermission * prompt editor add list plugin (#5620) * perf: search result (#5608) * fix: table size (#5598) * temp: list value * backspace * optimize code --------- Co-authored-by: Archer <545436317@qq.com> Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com> * fix: fe & member list (#5619) * chore: initv4130 * fix: MemberItemCard * fix: MemberItemCard * chore: fe adjust & init script * perf: test code * doc * fix debug variables (#5617) * perf: search result (#5608) * fix: table size (#5598) * fix debug variables * fix --------- Co-authored-by: Archer <545436317@qq.com> Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com> * perf: member ui * fix: inherit bug (#5624) * refactor(permission): remove getClbsWithInfo, which is useless * fix: app list privateApp * fix: get infos * perf(fe): remove delete icon when it is disable in MemberItemCard * fix: dataset private dataset * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Archer <545436317@qq.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * perf: auto coupon * chore: upgrade script & get infos avatar (#5625) * fix: get infos * chore: initv4130 * feat: support WecomRobot publish, and fix AesKey can not save bug (#5526) * feat: resolve conflicts * fix: add param 'show_publish_wecom' * feat: abstract out WecomCrypto type * doc: wecom robot document * fix: solve instability in AI output * doc: update some pictures * feat: remove functions from request.ts to chat.ts and toolCall.ts * doc: wecom robot doc update * fix * delete unused code * doc: update version and prompt * feat: remove wecom crypto, delete wecom code in workflow * feat: delete unused codes --------- Co-authored-by: heheer <zhiyu44@qq.com> * remove test * rename init shell * feat: collection page store * reload sandbox * pysandbox * remove log * chore: remove useless code (#5629) * chore: remove useless code * fix: checkConflict * perf: support hidden type for RoleList * fix: copy node * update doc * fix(permission): some bug (#5632) * fix: app/dataset list * fix: inherit bug * perf: del app;i18n;save chat * fix: test * i18n * fix: sumper overflow return OwnerRoleVal (#5633) * remove invalid code * fix: scroll * fix: objectId * update next * update package * object id * mock redis * feat: add redis append to resolve wecom stream response (#5643) * feat: resolve conflicts * fix: add param 'show_publish_wecom' * feat: abstract out WecomCrypto type * doc: wecom robot document * fix: solve instability in AI output * doc: update some pictures * feat: remove functions from request.ts to chat.ts and toolCall.ts * doc: wecom robot doc update * fix * delete unused code * doc: update version and prompt * feat: remove wecom crypto, delete wecom code in workflow * feat: delete unused codes * feat: add redis append method --------- Co-authored-by: heheer <zhiyu44@qq.com> * cache per * fix(test): init team sub when creating mocked user (#5646) * fix: button is not vertically centered (#5647) * doc * fix: gridFs objectId (#5649) --------- Co-authored-by: Zeng Qingwen <143274079+fishwww-ww@users.noreply.github.com> Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com> Co-authored-by: heheer <heheer@sealos.io> Co-authored-by: 伍闲犬 <whoeverimf5@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: heheer <zhiyu44@qq.com>
This commit is contained in:
@@ -15,6 +15,7 @@ import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
|
||||
import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
|
||||
import { ListPlugin } from '@lexical/react/LexicalListPlugin';
|
||||
import { CheckListPlugin } from '@lexical/react/LexicalCheckListPlugin';
|
||||
import { TabIndentationPlugin } from '@lexical/react/LexicalTabIndentationPlugin';
|
||||
import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
|
||||
import { HeadingNode, QuoteNode } from '@lexical/rich-text';
|
||||
import { ListItemNode, ListNode } from '@lexical/list';
|
||||
@@ -219,7 +220,6 @@ export default function Editor({
|
||||
)}
|
||||
{variableLabels.length > 0 && <VariablePickerPlugin variables={variables} />}
|
||||
<OnBlurPlugin onBlur={onBlur} />
|
||||
<ListDisplayFixPlugin />
|
||||
<OnChangePlugin
|
||||
onChange={(editorState, editor) => {
|
||||
const rootElement = editor.getRootElement();
|
||||
@@ -232,11 +232,12 @@ export default function Editor({
|
||||
|
||||
{isRichText && (
|
||||
<>
|
||||
{/* <ListPlugin />
|
||||
<ListDisplayFixPlugin />
|
||||
<TabIndentationPlugin />
|
||||
<ListPlugin />
|
||||
<CheckListPlugin />
|
||||
<ListExitPlugin /> */}
|
||||
<TabToSpacesPlugin />
|
||||
{/* <MarkdownPlugin /> */}
|
||||
<ListExitPlugin />
|
||||
<MarkdownPlugin />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
@@ -70,7 +70,7 @@ export default function ListExitPlugin(): JSX.Element | null {
|
||||
}
|
||||
|
||||
const anchorNode = selection.anchor.getNode();
|
||||
const listItemNode = anchorNode.getParent();
|
||||
const listItemNode = $isListItemNode(anchorNode) ? anchorNode : anchorNode.getParent();
|
||||
|
||||
if ($isListItemNode(listItemNode)) {
|
||||
// Check if cursor is at the beginning of an empty list item
|
||||
|
@@ -23,3 +23,88 @@ export type EditorVariableLabelPickerType = {
|
||||
};
|
||||
|
||||
export type FormPropsType = Omit<BoxProps, 'onChange' | 'onBlur'>;
|
||||
|
||||
// Lexical editor node types
|
||||
export type BaseEditorNode = {
|
||||
type: string;
|
||||
version: number;
|
||||
};
|
||||
|
||||
export type TextEditorNode = BaseEditorNode & {
|
||||
type: 'text';
|
||||
text: string;
|
||||
detail: number;
|
||||
format: number;
|
||||
mode: string;
|
||||
style: string;
|
||||
};
|
||||
|
||||
export type LineBreakEditorNode = BaseEditorNode & {
|
||||
type: 'linebreak';
|
||||
};
|
||||
|
||||
export type VariableLabelEditorNode = BaseEditorNode & {
|
||||
type: 'variableLabel';
|
||||
variableKey: string;
|
||||
};
|
||||
|
||||
export type VariableEditorNode = BaseEditorNode & {
|
||||
type: 'Variable';
|
||||
variableKey: string;
|
||||
};
|
||||
|
||||
export type TabEditorNode = BaseEditorNode & {
|
||||
type: 'tab';
|
||||
};
|
||||
|
||||
export type ChildEditorNode =
|
||||
| TextEditorNode
|
||||
| LineBreakEditorNode
|
||||
| VariableLabelEditorNode
|
||||
| VariableEditorNode
|
||||
| TabEditorNode;
|
||||
|
||||
export type ParagraphEditorNode = BaseEditorNode & {
|
||||
type: 'paragraph';
|
||||
children: ChildEditorNode[];
|
||||
direction: string;
|
||||
format: string;
|
||||
indent: number;
|
||||
};
|
||||
|
||||
export type ListItemEditorNode = BaseEditorNode & {
|
||||
type: 'listitem';
|
||||
children: Array<ChildEditorNode | ListEditorNode>;
|
||||
direction: string | null;
|
||||
format: string;
|
||||
indent: number;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export type ListEditorNode = BaseEditorNode & {
|
||||
type: 'list';
|
||||
children: ListItemEditorNode[];
|
||||
direction: string | null;
|
||||
format: string;
|
||||
indent: number;
|
||||
listType: 'bullet' | 'number';
|
||||
start: number;
|
||||
tag: 'ul' | 'ol';
|
||||
};
|
||||
|
||||
export type EditorState = {
|
||||
root: {
|
||||
type: 'root';
|
||||
children: Array<ParagraphEditorNode | ListEditorNode>;
|
||||
direction: string;
|
||||
format: string;
|
||||
indent: number;
|
||||
} & BaseEditorNode;
|
||||
};
|
||||
|
||||
export type ListItemInfo = {
|
||||
type: 'bullet' | 'number';
|
||||
text: string;
|
||||
indent: number;
|
||||
numberValue?: number;
|
||||
};
|
||||
|
@@ -6,12 +6,19 @@
|
||||
*
|
||||
*/
|
||||
|
||||
import type { DecoratorNode, Klass, LexicalEditor, LexicalNode } from 'lexical';
|
||||
import type { Klass, LexicalEditor, LexicalNode } from 'lexical';
|
||||
import type { EntityMatch } from '@lexical/text';
|
||||
import { $createTextNode, $isTextNode, TextNode } from 'lexical';
|
||||
import { useCallback } from 'react';
|
||||
import type { VariableLabelNode } from './plugins/VariableLabelPlugin/node';
|
||||
import type { VariableNode } from './plugins/VariablePlugin/node';
|
||||
import type {
|
||||
ListItemEditorNode,
|
||||
ListEditorNode,
|
||||
ParagraphEditorNode,
|
||||
EditorState,
|
||||
ListItemInfo
|
||||
} from './type';
|
||||
|
||||
export function registerLexicalTextEntity<T extends TextNode | VariableLabelNode | VariableNode>(
|
||||
editor: LexicalEditor,
|
||||
@@ -175,31 +182,148 @@ export function registerLexicalTextEntity<T extends TextNode | VariableLabelNode
|
||||
return [removePlainTextTransform, removeReverseNodeTransform];
|
||||
}
|
||||
|
||||
export function textToEditorState(text = '') {
|
||||
const paragraph = typeof text === 'string' ? text?.split('\n') : [''];
|
||||
// text to editor state
|
||||
const parseTextLine = (line: string) => {
|
||||
const trimmed = line.trimStart();
|
||||
const indentLevel = Math.floor((line.length - trimmed.length) / 2);
|
||||
|
||||
const bulletMatch = trimmed.match(/^- (.*)$/);
|
||||
if (bulletMatch) {
|
||||
return { type: 'bullet', text: bulletMatch[1], indent: indentLevel };
|
||||
}
|
||||
|
||||
const numberMatch = trimmed.match(/^(\d+)\. (.*)$/);
|
||||
if (numberMatch) {
|
||||
return {
|
||||
type: 'number',
|
||||
text: numberMatch[2],
|
||||
indent: indentLevel,
|
||||
numberValue: parseInt(numberMatch[1])
|
||||
};
|
||||
}
|
||||
|
||||
return { type: 'paragraph', text: trimmed, indent: indentLevel };
|
||||
};
|
||||
|
||||
const buildListStructure = (items: ListItemInfo[]) => {
|
||||
const result: ListEditorNode[] = [];
|
||||
|
||||
let i = 0;
|
||||
while (i < items.length) {
|
||||
const currentListType = items[i].type;
|
||||
const currentIndent = items[i].indent;
|
||||
const currentListItems: ListItemEditorNode[] = [];
|
||||
|
||||
// Collect consecutive items of the same type
|
||||
while (i < items.length && items[i].type === currentListType) {
|
||||
const listItem: ListItemEditorNode = {
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: items[i].text,
|
||||
type: 'text' as const,
|
||||
version: 1
|
||||
}
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'listitem' as const,
|
||||
version: 1,
|
||||
value: items[i].numberValue || 1
|
||||
};
|
||||
|
||||
// Collect nested items
|
||||
const nestedItems: ListItemInfo[] = [];
|
||||
let j = i + 1;
|
||||
while (j < items.length && items[j].indent > currentIndent) {
|
||||
nestedItems.push(items[j]);
|
||||
j++;
|
||||
}
|
||||
|
||||
// recursively build nested lists and add them to the current item's children
|
||||
if (nestedItems.length > 0) {
|
||||
const nestedLists = buildListStructure(nestedItems);
|
||||
listItem.children.push(...nestedLists);
|
||||
}
|
||||
|
||||
currentListItems.push(listItem);
|
||||
i = j;
|
||||
}
|
||||
|
||||
result.push({
|
||||
children: currentListItems,
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'list' as const,
|
||||
version: 1,
|
||||
listType: currentListType,
|
||||
start: 1,
|
||||
tag: currentListType === 'bullet' ? 'ul' : ('ol' as const)
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const textToEditorState = (text = '') => {
|
||||
const lines = text.split('\n');
|
||||
const children: Array<ParagraphEditorNode | ListEditorNode> = [];
|
||||
|
||||
let i = 0;
|
||||
while (i < lines.length) {
|
||||
const parsed = parseTextLine(lines[i]);
|
||||
|
||||
if (parsed.type === 'paragraph') {
|
||||
children.push({
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: parsed.text,
|
||||
type: 'text',
|
||||
version: 1
|
||||
}
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: parsed.indent,
|
||||
type: 'paragraph',
|
||||
version: 1
|
||||
});
|
||||
i++;
|
||||
} else {
|
||||
const listItems: ListItemInfo[] = [];
|
||||
|
||||
while (i < lines.length) {
|
||||
const currentParsed = parseTextLine(lines[i]);
|
||||
if (currentParsed.type === 'paragraph') {
|
||||
break;
|
||||
}
|
||||
listItems.push({
|
||||
type: currentParsed.type as 'bullet' | 'number',
|
||||
text: currentParsed.text,
|
||||
indent: currentParsed.indent,
|
||||
numberValue: currentParsed.numberValue
|
||||
});
|
||||
i++;
|
||||
}
|
||||
|
||||
// build nested lists and add to children
|
||||
const lists = buildListStructure(listItems) as ListEditorNode[];
|
||||
children.push(...lists);
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
root: {
|
||||
children: paragraph.map((p) => {
|
||||
return {
|
||||
children: [
|
||||
{
|
||||
detail: 0,
|
||||
format: 0,
|
||||
mode: 'normal',
|
||||
style: '',
|
||||
text: p,
|
||||
type: 'text',
|
||||
version: 1
|
||||
}
|
||||
],
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
type: 'paragraph',
|
||||
version: 1
|
||||
};
|
||||
}),
|
||||
children: children,
|
||||
direction: 'ltr',
|
||||
format: '',
|
||||
indent: 0,
|
||||
@@ -207,30 +331,9 @@ export function textToEditorState(text = '') {
|
||||
version: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const varRegex = /\{\{([a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
|
||||
export const getVars = (value: string) => {
|
||||
if (!value) return [];
|
||||
const keys =
|
||||
value
|
||||
.match(varRegex)
|
||||
?.map((item) => {
|
||||
return item.replace('{{', '').replace('}}', '');
|
||||
})
|
||||
.filter((key) => key.length <= 10) || [];
|
||||
const keyObj: Record<string, boolean> = {};
|
||||
// remove duplicate keys
|
||||
const res: string[] = [];
|
||||
keys.forEach((key) => {
|
||||
if (keyObj[key]) return;
|
||||
|
||||
keyObj[key] = true;
|
||||
res.push(key);
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
// menu text match
|
||||
export type MenuTextMatch = {
|
||||
leadOffset: number;
|
||||
matchingString: string;
|
||||
@@ -266,22 +369,102 @@ export function useBasicTypeaheadTriggerMatch(
|
||||
);
|
||||
}
|
||||
|
||||
export function editorStateToText(editor: LexicalEditor) {
|
||||
const editorStateTextString: string[] = [];
|
||||
const paragraphs = editor.getEditorState().toJSON().root.children;
|
||||
paragraphs.forEach((paragraph: any) => {
|
||||
const children = paragraph.children || [];
|
||||
const paragraphText: string[] = [];
|
||||
children.forEach((child: any) => {
|
||||
if (child.type === 'linebreak') {
|
||||
paragraphText.push('\n');
|
||||
} else if (child.text) {
|
||||
paragraphText.push(child.text);
|
||||
} else if (child.type === 'variableLabel' || child.type === 'Variable') {
|
||||
paragraphText.push(child.variableKey);
|
||||
}
|
||||
// editor state to text
|
||||
const processListItem = ({
|
||||
listItem,
|
||||
listType,
|
||||
index,
|
||||
indentLevel
|
||||
}: {
|
||||
listItem: ListItemEditorNode;
|
||||
listType: 'bullet' | 'number';
|
||||
index: number;
|
||||
indentLevel: number;
|
||||
}) => {
|
||||
const results = [];
|
||||
|
||||
const itemText: string[] = [];
|
||||
const nestedLists: ListEditorNode[] = [];
|
||||
|
||||
// Separate text and nested lists
|
||||
listItem.children.forEach((child) => {
|
||||
if (child.type === 'linebreak') {
|
||||
itemText.push('\n');
|
||||
} else if (child.type === 'text') {
|
||||
itemText.push(child.text);
|
||||
} else if (child.type === 'tab') {
|
||||
itemText.push(' ');
|
||||
} else if (child.type === 'variableLabel' || child.type === 'Variable') {
|
||||
itemText.push(child.variableKey);
|
||||
} else if (child.type === 'list') {
|
||||
nestedLists.push(child);
|
||||
}
|
||||
});
|
||||
|
||||
// Add prefix and indent
|
||||
const itemTextString = itemText.join('').trim();
|
||||
const indent = ' '.repeat(indentLevel);
|
||||
const prefix = listType === 'bullet' ? '- ' : `${index + 1}. `;
|
||||
results.push(indent + prefix + itemTextString);
|
||||
|
||||
// Handle nested lists
|
||||
nestedLists.forEach((nestedList) => {
|
||||
const nestedResults = processList({
|
||||
list: nestedList,
|
||||
indentLevel: indentLevel + 1
|
||||
});
|
||||
editorStateTextString.push(paragraphText.join(''));
|
||||
results.push(...nestedResults);
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
const processList = ({ list, indentLevel = 0 }: { list: ListEditorNode; indentLevel?: number }) => {
|
||||
const results: string[] = [];
|
||||
|
||||
list.children.forEach((listItem, index: number) => {
|
||||
if (listItem.type === 'listitem') {
|
||||
const itemResults = processListItem({
|
||||
listItem,
|
||||
listType: list.listType,
|
||||
index,
|
||||
indentLevel
|
||||
});
|
||||
results.push(...itemResults);
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
};
|
||||
export const editorStateToText = (editor: LexicalEditor) => {
|
||||
const editorStateTextString: string[] = [];
|
||||
const editorState = editor.getEditorState().toJSON() as EditorState;
|
||||
const paragraphs = editorState.root.children;
|
||||
|
||||
paragraphs.forEach((paragraph) => {
|
||||
if (paragraph.type === 'list') {
|
||||
const listResults = processList({ list: paragraph });
|
||||
editorStateTextString.push(...listResults);
|
||||
} else if (paragraph.type === 'paragraph') {
|
||||
const children = paragraph.children;
|
||||
const paragraphText: string[] = [];
|
||||
|
||||
const indentSpaces = ' '.repeat(paragraph.indent || 0);
|
||||
|
||||
children.forEach((child) => {
|
||||
if (child.type === 'linebreak') {
|
||||
paragraphText.push('\n');
|
||||
} else if (child.type === 'text') {
|
||||
paragraphText.push(child.text);
|
||||
} else if (child.type === 'tab') {
|
||||
paragraphText.push(' ');
|
||||
} else if (child.type === 'variableLabel' || child.type === 'Variable') {
|
||||
paragraphText.push(child.variableKey);
|
||||
}
|
||||
});
|
||||
|
||||
const finalText = paragraphText.join('');
|
||||
editorStateTextString.push(indentSpaces + finalText);
|
||||
}
|
||||
});
|
||||
return editorStateTextString.join('\n');
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user