Files
project_vollidioten_website/components/MarkdownEditor.tsx
Lars Behrends 065a6e657d feat: add world map functionality and admin map management
- Added world map page with interactive marker display
- Implemented admin map management for marker CRUD operations
- Added map layers and markers seed data to database
- Integrated new routes for map functionality
- Updated database configuration for production environment
- Added documentation page route
- Enhanced package.json with required dependencies for map features
2026-01-02 05:08:07 +01:00

128 lines
4.0 KiB
TypeScript

import React, { useState, useRef } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { Icons } from './IconSet';
interface MarkdownEditorProps {
value: string;
onChange: (value: string) => void;
className?: string;
}
const MarkdownEditor: React.FC<MarkdownEditorProps> = ({ value, onChange, className = '' }) => {
const textareaRef = useRef<HTMLTextAreaElement>(null);
const insertText = (before: string, after: string = '', placeholder: string = 'text') => {
const textarea = textareaRef.current;
if (!textarea) return;
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = value.substring(start, end);
const textToInsert = selectedText || placeholder;
const newText = value.substring(0, start) + before + textToInsert + after + value.substring(end);
onChange(newText);
// Set cursor position after the inserted text
setTimeout(() => {
const newCursorPos = start + before.length + textToInsert.length + after.length;
textarea.setSelectionRange(newCursorPos, newCursorPos);
textarea.focus();
}, 0);
};
const formatButtons = [
{
icon: 'H',
label: 'Überschrift',
action: () => insertText('# ', ''),
className: 'text-lg font-bold'
},
{
icon: <Icons.Hammer className="w-3 h-3" />,
label: 'Fett',
action: () => insertText('**', '**', 'fetter Text'),
},
{
icon: <Icons.Tag className="w-3 h-3" />,
label: 'Kursiv',
action: () => insertText('*', '*', 'kursiver Text'),
},
{
icon: <Icons.Scroll className="w-3 h-3" />,
label: 'Liste',
action: () => insertText('- ', ''),
},
{
icon: <Icons.Crown className="w-3 h-3" />,
label: 'Nummerierte Liste',
action: () => insertText('1. ', ''),
},
{
icon: <Icons.Shield className="w-3 h-3" />,
label: 'Zitat',
action: () => insertText('> ', ''),
},
{
icon: '🔗',
label: 'Link',
action: () => insertText('[', '](url)', 'Link-Text'),
},
{
icon: '📷',
label: 'Bild',
action: () => insertText('![', '](url)', 'Alt-Text'),
},
];
return (
<div className={`border border-border rounded-lg overflow-hidden ${className}`}>
{/* Toolbar */}
<div className="bg-surfaceHighlight border-b border-border p-2 flex flex-wrap gap-1">
{formatButtons.map((button, index) => (
<button
key={index}
type="button"
onClick={button.action}
className="p-1.5 hover:bg-white/10 rounded text-textMuted hover:text-textMain transition-colors text-sm flex items-center justify-center min-w-[32px] h-8"
title={button.label}
>
{typeof button.icon === 'string' ? (
<span className={button.className}>{button.icon}</span>
) : (
button.icon
)}
</button>
))}
</div>
{/* Editor and Preview */}
<div className="flex h-64 md:h-96">
{/* Textarea */}
<textarea
ref={textareaRef}
value={value}
onChange={(e) => onChange(e.target.value)}
className="w-1/2 h-full bg-[#0b0b0d] border-0 border-r border-border p-4 text-sm font-mono text-gray-300 focus:outline-none resize-none"
placeholder="Schreibe dein Journal hier... Verwende die Toolbar für Formatierung."
spellCheck={false}
/>
{/* Preview */}
<div className="w-1/2 h-full bg-[#0b0b0d] p-4 text-sm text-gray-300 overflow-auto">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{value || 'Hier erscheint die Vorschau deiner Markdown-Formatierung.'}
</ReactMarkdown>
</div>
</div>
{/* Footer */}
<div className="bg-surfaceHighlight border-t border-border px-4 py-2 text-xs text-textMuted">
Markdown-Formatierung unterstützt. Verwende die Toolbar für schnelle Formatierung.
</div>
</div>
);
};
export default MarkdownEditor;