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:
Archer
2025-09-15 20:02:54 +08:00
committed by GitHub
parent c8934e3d22
commit 2ed1545eb5
187 changed files with 3701 additions and 2221 deletions

View File

@@ -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 />
</>
)}
</>

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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');
}
};