feat: extract WysiwygEditor into reusable component and add advanced formatting options
Build and Push Multi-Platform Images / build-and-push (push) Successful in 23s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 23s
This commit is contained in:
@@ -0,0 +1,218 @@
|
||||
import { forwardRef, useImperativeHandle } from 'react';
|
||||
import { useEditor, EditorContent } from '@tiptap/react';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
import Underline from '@tiptap/extension-underline';
|
||||
import TextAlign from '@tiptap/extension-text-align';
|
||||
import { Select } from 'antd';
|
||||
import {
|
||||
AlignCenterOutlined,
|
||||
AlignLeftOutlined,
|
||||
AlignRightOutlined,
|
||||
BoldOutlined,
|
||||
ItalicOutlined,
|
||||
OrderedListOutlined,
|
||||
RedoOutlined,
|
||||
StrikethroughOutlined,
|
||||
UnderlineOutlined,
|
||||
UndoOutlined,
|
||||
UnorderedListOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
export interface WysiwygEditorHandle {
|
||||
getHTML: () => string;
|
||||
getText: () => string;
|
||||
setContent: (html: string) => void;
|
||||
}
|
||||
|
||||
interface WysiwygEditorProps {
|
||||
minHeight?: number;
|
||||
}
|
||||
|
||||
function ToolBtn({
|
||||
active,
|
||||
disabled,
|
||||
onClick,
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
onClick: () => void;
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
title={title}
|
||||
disabled={disabled}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
if (!disabled) onClick();
|
||||
}}
|
||||
className={`pm-tool-btn${active ? ' pm-tool-btn--active' : ''}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function Divider() {
|
||||
return <span className="pm-tool-divider" />;
|
||||
}
|
||||
|
||||
const HEADING_OPTIONS = [
|
||||
{ value: 'p', label: 'Normal' },
|
||||
{ value: '1', label: 'Überschrift 1' },
|
||||
{ value: '2', label: 'Überschrift 2' },
|
||||
{ value: '3', label: 'Überschrift 3' },
|
||||
];
|
||||
|
||||
export const WysiwygEditor = forwardRef<WysiwygEditorHandle, WysiwygEditorProps>(
|
||||
({ minHeight = 200 }, ref) => {
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
StarterKit,
|
||||
Underline,
|
||||
TextAlign.configure({ types: ['heading', 'paragraph'] }),
|
||||
],
|
||||
content: '',
|
||||
});
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
getHTML: () => editor?.getHTML() ?? '',
|
||||
getText: () => editor?.getText() ?? '',
|
||||
setContent: (html: string) => {
|
||||
editor?.commands.setContent(html);
|
||||
},
|
||||
}),
|
||||
[editor],
|
||||
);
|
||||
|
||||
if (!editor) return null;
|
||||
|
||||
const headingLevel = editor.isActive('heading', { level: 1 })
|
||||
? '1'
|
||||
: editor.isActive('heading', { level: 2 })
|
||||
? '2'
|
||||
: editor.isActive('heading', { level: 3 })
|
||||
? '3'
|
||||
: 'p';
|
||||
|
||||
return (
|
||||
<div className="pm-editor-wrap">
|
||||
<div className="pm-toolbar">
|
||||
<ToolBtn
|
||||
title="Rückgängig (Strg+Z)"
|
||||
onClick={() => editor.chain().focus().undo().run()}
|
||||
>
|
||||
<UndoOutlined />
|
||||
</ToolBtn>
|
||||
<ToolBtn
|
||||
title="Wiederholen (Strg+Y)"
|
||||
onClick={() => editor.chain().focus().redo().run()}
|
||||
>
|
||||
<RedoOutlined />
|
||||
</ToolBtn>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Select
|
||||
size="small"
|
||||
value={headingLevel}
|
||||
style={{ width: 128 }}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onChange={(v) => {
|
||||
if (v === 'p') {
|
||||
editor.chain().focus().setParagraph().run();
|
||||
} else {
|
||||
editor.chain().focus().setHeading({ level: parseInt(v) as 1 | 2 | 3 }).run();
|
||||
}
|
||||
}}
|
||||
options={HEADING_OPTIONS}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
|
||||
<ToolBtn
|
||||
active={editor.isActive('bold')}
|
||||
onClick={() => editor.chain().focus().toggleBold().run()}
|
||||
title="Fett (Strg+B)"
|
||||
>
|
||||
<BoldOutlined />
|
||||
</ToolBtn>
|
||||
<ToolBtn
|
||||
active={editor.isActive('italic')}
|
||||
onClick={() => editor.chain().focus().toggleItalic().run()}
|
||||
title="Kursiv (Strg+I)"
|
||||
>
|
||||
<ItalicOutlined />
|
||||
</ToolBtn>
|
||||
<ToolBtn
|
||||
active={editor.isActive('underline')}
|
||||
onClick={() => editor.chain().focus().toggleUnderline().run()}
|
||||
title="Unterstrichen (Strg+U)"
|
||||
>
|
||||
<UnderlineOutlined />
|
||||
</ToolBtn>
|
||||
<ToolBtn
|
||||
active={editor.isActive('strike')}
|
||||
onClick={() => editor.chain().focus().toggleStrike().run()}
|
||||
title="Durchgestrichen"
|
||||
>
|
||||
<StrikethroughOutlined />
|
||||
</ToolBtn>
|
||||
|
||||
<Divider />
|
||||
|
||||
<ToolBtn
|
||||
active={editor.isActive('bulletList')}
|
||||
onClick={() => editor.chain().focus().toggleBulletList().run()}
|
||||
title="Aufzählungsliste"
|
||||
>
|
||||
<UnorderedListOutlined />
|
||||
</ToolBtn>
|
||||
<ToolBtn
|
||||
active={editor.isActive('orderedList')}
|
||||
onClick={() => editor.chain().focus().toggleOrderedList().run()}
|
||||
title="Nummerierte Liste"
|
||||
>
|
||||
<OrderedListOutlined />
|
||||
</ToolBtn>
|
||||
|
||||
<Divider />
|
||||
|
||||
<ToolBtn
|
||||
active={editor.isActive({ textAlign: 'left' })}
|
||||
onClick={() => editor.chain().focus().setTextAlign('left').run()}
|
||||
title="Linksbündig"
|
||||
>
|
||||
<AlignLeftOutlined />
|
||||
</ToolBtn>
|
||||
<ToolBtn
|
||||
active={editor.isActive({ textAlign: 'center' })}
|
||||
onClick={() => editor.chain().focus().setTextAlign('center').run()}
|
||||
title="Zentriert"
|
||||
>
|
||||
<AlignCenterOutlined />
|
||||
</ToolBtn>
|
||||
<ToolBtn
|
||||
active={editor.isActive({ textAlign: 'right' })}
|
||||
onClick={() => editor.chain().focus().setTextAlign('right').run()}
|
||||
title="Rechtsbündig"
|
||||
>
|
||||
<AlignRightOutlined />
|
||||
</ToolBtn>
|
||||
</div>
|
||||
|
||||
<div className="pm-document-surround" style={{ minHeight: minHeight + 32 }}>
|
||||
<div className="pm-document-page" style={{ minHeight }}>
|
||||
<EditorContent editor={editor} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user