This commit is contained in:
2026-04-25 16:36:34 +08:00
commit db90e7579b
1876 changed files with 189777 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
import { isArray, ObjectMark, RemirrorJSON } from 'remirror';
/**
* 转换成BBCode
*/
export function transformToBBCode(json: RemirrorJSON): string {
if (json.type === 'doc') {
return (json.content ?? []).map(transformToBBCode).join('\n');
}
if (json.type === 'paragraph') {
return (json.content ?? []).map(transformToBBCode).join('');
}
if (json.type === 'text') {
let text = json.text ?? '';
if (isArray(json.marks)) {
(json.marks ?? []).forEach((mark) => {
if (typeof mark === 'string') {
mark = { type: mark };
}
text = applyMarks(mark, text);
});
}
return text;
}
return '';
}
/**
* 为text增加mark包裹
*/
function applyMarks(mark: ObjectMark, text: string): string {
if (mark.type === 'bold') {
return `[b]${text}[/b]`;
}
if (mark.type === 'underline') {
return `[u]${text}[/u]`;
}
if (mark.type === 'italic') {
return `[i]${text}[/i]`;
}
if (mark.type === 'code') {
return `[code]${text}[/code]`;
}
return text;
}

View File

@@ -0,0 +1,12 @@
.remirror-editor-wrapper {
height: 100%;
}
.tailchat-rich-editor {
height: 100%;
outline: 0;
}
.tailchat-rich-editor p {
margin: 0;
}

View File

@@ -0,0 +1,44 @@
import React from 'react';
import {
Remirror,
useRemirror,
OnChangeJSON,
EditorComponent,
} from '@remirror/react';
import { useMemoizedFn } from 'ahooks';
import type { RemirrorJSON } from 'remirror';
import { Toolbar } from './toolbar';
import { extensions } from './extensions';
import { transformToBBCode } from './bbcode';
import './editor.css';
interface RichEditorProps extends React.PropsWithChildren {
initContent: string;
onChange: (bbcode: string) => void;
}
export const RichEditor: React.FC<RichEditorProps> = React.memo((props) => {
const { manager, state } = useRemirror({
extensions,
content: props.initContent,
stringHandler: 'html',
selection: 'end',
});
const handleChange = useMemoizedFn((json: RemirrorJSON) => {
props.onChange(transformToBBCode(json));
});
return (
<Remirror
classNames={['tailchat-rich-editor']}
manager={manager}
initialContent={state}
>
<Toolbar />
<EditorComponent />
<OnChangeJSON onChange={handleChange} />
{props.children}
</Remirror>
);
});
RichEditor.displayName = 'RichEditor';

View File

@@ -0,0 +1,16 @@
import {
BoldExtension,
CodeExtension,
ItalicExtension,
UnderlineExtension,
} from 'remirror/extensions';
/**
* 富文本编辑器使用的拓展
*/
export const extensions = () => [
new BoldExtension(),
new ItalicExtension(),
new UnderlineExtension(),
new CodeExtension(),
];

View File

@@ -0,0 +1,24 @@
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { RichEditor } from './editor';
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
title: 'Tailchat/RichEditor',
component: RichEditor,
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
argTypes: {},
} as ComponentMeta<typeof RichEditor>;
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template: ComponentStory<typeof RichEditor> = (args) => (
<div style={{ height: 1000, width: '100%' }}>
<RichEditor {...args} />
</div>
);
export const Default = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Default.args = {
initContent: '<p>Hi <strong>Friend</strong></p>',
};

View File

@@ -0,0 +1,8 @@
import React from 'react';
/**
* 富文本编辑器
*/
export const RichEditor = React.lazy(() =>
import('./editor').then((module) => ({ default: module.RichEditor }))
);

View File

@@ -0,0 +1,26 @@
import React from 'react';
import {
FloatingToolbar,
CommandButtonGroup,
ToggleBoldButton,
ToggleItalicButton,
ToggleUnderlineButton,
ToggleCodeButton,
} from '@remirror/react';
/**
* 菜单
*/
export const Toolbar: React.FC = React.memo(() => {
return (
<FloatingToolbar>
<CommandButtonGroup>
<ToggleBoldButton />
<ToggleItalicButton />
<ToggleUnderlineButton />
<ToggleCodeButton />
</CommandButtonGroup>
</FloatingToolbar>
);
});
Toolbar.displayName = 'Toolbar';