Files
bambuddy/frontend/src/components/RichTextEditor.tsx
T
copilot-swe-agent[bot]andcadtoolbox 903f43d71a feat(i18n): update frontend components to use translations
- Add i18n support to AddSmartPlugModal.tsx
- Add i18n support to ConfigureAmsSlotModal.tsx
- Add i18n support to RichTextEditor.tsx
- Add i18n support to ExternalLinksSettings.tsx
- Replace all hardcoded English strings with t() calls

Co-authored-by: cadtoolbox <12723486+cadtoolbox@users.noreply.github.com>
2026-02-10 14:26:51 +00:00

194 lines
5.5 KiB
TypeScript

import { useEditor, EditorContent } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Link from '@tiptap/extension-link';
import Underline from '@tiptap/extension-underline';
import TextAlign from '@tiptap/extension-text-align';
import { TextStyle } from '@tiptap/extension-text-style';
import Color from '@tiptap/extension-color';
import Image from '@tiptap/extension-image';
import {
Bold,
Italic,
Underline as UnderlineIcon,
List,
ListOrdered,
AlignLeft,
AlignCenter,
AlignRight,
Link as LinkIcon,
Unlink,
} from 'lucide-react';
import { useTranslation } from 'react-i18next';
interface RichTextEditorProps {
content: string;
onChange: (html: string) => void;
placeholder?: string;
}
export function RichTextEditor({ content, onChange, placeholder }: RichTextEditorProps) {
const { t } = useTranslation();
const editor = useEditor({
extensions: [
StarterKit.configure({
heading: false,
codeBlock: false,
code: false,
}),
Underline,
Link.configure({
openOnClick: false,
HTMLAttributes: {
target: '_blank',
rel: 'noopener noreferrer',
},
}),
TextAlign.configure({
types: ['paragraph'],
}),
TextStyle,
Color,
Image.configure({
HTMLAttributes: {
style: 'max-width: 100%; height: auto;',
},
}),
],
content,
onUpdate: ({ editor }) => {
onChange(editor.getHTML());
},
editorProps: {
attributes: {
class: 'prose prose-invert prose-sm max-w-none focus:outline-none min-h-[120px] px-3 py-2',
placeholder: placeholder || '',
},
},
});
if (!editor) {
return null;
}
const ToolbarButton = ({
onClick,
isActive = false,
children,
title,
}: {
onClick: () => void;
isActive?: boolean;
children: React.ReactNode;
title: string;
}) => (
<button
type="button"
onClick={onClick}
title={title}
className={`p-1.5 rounded hover:bg-bambu-dark-tertiary transition-colors ${
isActive ? 'bg-bambu-dark-tertiary text-bambu-green' : 'text-bambu-gray'
}`}
>
{children}
</button>
);
const setLink = () => {
const url = window.prompt('Enter URL:');
if (url) {
editor.chain().focus().setLink({ href: url }).run();
}
};
return (
<div className="border border-bambu-dark-tertiary rounded-lg overflow-hidden bg-bambu-dark">
{/* Toolbar */}
<div className="flex items-center gap-0.5 p-1.5 border-b border-bambu-dark-tertiary bg-bambu-dark-secondary">
<ToolbarButton
onClick={() => editor.chain().focus().toggleBold().run()}
isActive={editor.isActive('bold')}
title={t('richTextEditor.bold')}
>
<Bold className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleItalic().run()}
isActive={editor.isActive('italic')}
title={t('richTextEditor.italic')}
>
<Italic className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleUnderline().run()}
isActive={editor.isActive('underline')}
title={t('richTextEditor.underline')}
>
<UnderlineIcon className="w-4 h-4" />
</ToolbarButton>
<div className="w-px h-5 bg-bambu-dark-tertiary mx-1" />
<ToolbarButton
onClick={() => editor.chain().focus().toggleBulletList().run()}
isActive={editor.isActive('bulletList')}
title={t('richTextEditor.bulletList')}
>
<List className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().toggleOrderedList().run()}
isActive={editor.isActive('orderedList')}
title={t('richTextEditor.numberedList')}
>
<ListOrdered className="w-4 h-4" />
</ToolbarButton>
<div className="w-px h-5 bg-bambu-dark-tertiary mx-1" />
<ToolbarButton
onClick={() => editor.chain().focus().setTextAlign('left').run()}
isActive={editor.isActive({ textAlign: 'left' })}
title={t('richTextEditor.alignLeft')}
>
<AlignLeft className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().setTextAlign('center').run()}
isActive={editor.isActive({ textAlign: 'center' })}
title={t('richTextEditor.alignCenter')}
>
<AlignCenter className="w-4 h-4" />
</ToolbarButton>
<ToolbarButton
onClick={() => editor.chain().focus().setTextAlign('right').run()}
isActive={editor.isActive({ textAlign: 'right' })}
title={t('richTextEditor.alignRight')}
>
<AlignRight className="w-4 h-4" />
</ToolbarButton>
<div className="w-px h-5 bg-bambu-dark-tertiary mx-1" />
<ToolbarButton
onClick={setLink}
isActive={editor.isActive('link')}
title={t('richTextEditor.addLink')}
>
<LinkIcon className="w-4 h-4" />
</ToolbarButton>
{editor.isActive('link') && (
<ToolbarButton
onClick={() => editor.chain().focus().unsetLink().run()}
title={t('richTextEditor.removeLink')}
>
<Unlink className="w-4 h-4" />
</ToolbarButton>
)}
</div>
{/* Editor */}
<EditorContent editor={editor} />
</div>
);
}