219 lines
5.8 KiB
TypeScript
219 lines
5.8 KiB
TypeScript
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>
|
|
);
|
|
},
|
|
);
|