'use client'; import React, { useState, useRef, useEffect, useCallback } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Save, X, Eye, Settings, Globe, Github, Image as ImageIcon, Bold, Italic, List, Quote, Code, Link2, ListOrdered, Underline, Strikethrough, Type, Columns } from 'lucide-react'; interface Project { id: string; title: string; description: string; content?: string; category: string; difficulty?: string; tags?: string[]; featured: boolean; published: boolean; github?: string; live?: string; image?: string; createdAt: string; updatedAt: string; } interface GhostEditorProps { isOpen: boolean; onClose: () => void; project?: Project | null; onSave: (projectData: Partial) => void; isCreating: boolean; } export const GhostEditor: React.FC = ({ isOpen, onClose, project, onSave, isCreating }) => { const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); const [content, setContent] = useState(''); const [category, setCategory] = useState('Web Development'); const [tags, setTags] = useState([]); const [github, setGithub] = useState(''); const [live, setLive] = useState(''); const [featured, setFeatured] = useState(false); const [published, setPublished] = useState(false); const [difficulty, setDifficulty] = useState('Intermediate'); // Editor UI state const [viewMode, setViewMode] = useState<'edit' | 'preview' | 'split'>('split'); const [showSettings, setShowSettings] = useState(false); const [wordCount, setWordCount] = useState(0); const [readingTime, setReadingTime] = useState(0); const titleRef = useRef(null); const contentRef = useRef(null); const previewRef = useRef(null); const categories = ['Web Development', 'Full-Stack', 'Web Application', 'Mobile App', 'Design']; const difficulties = ['Beginner', 'Intermediate', 'Advanced', 'Expert']; useEffect(() => { if (project && !isCreating) { setTitle(project.title); setDescription(project.description); setContent(project.content || ''); setCategory(project.category); setTags(project.tags || []); setGithub(project.github || ''); setLive(project.live || ''); setFeatured(project.featured); setPublished(project.published); setDifficulty(project.difficulty || 'Intermediate'); } else { // Reset for new project setTitle(''); setDescription(''); setContent(''); setCategory('Web Development'); setTags([]); setGithub(''); setLive(''); setFeatured(false); setPublished(false); setDifficulty('Intermediate'); } }, [project, isCreating, isOpen]); // Calculate word count and reading time useEffect(() => { const words = content.trim().split(/\s+/).filter(word => word.length > 0).length; setWordCount(words); setReadingTime(Math.ceil(words / 200)); // Average reading speed: 200 words/minute }, [content]); const handleSave = () => { const projectData = { title, description, content, category, tags, github, live, featured, published, difficulty }; onSave(projectData); }; const addTag = (tag: string) => { if (tag.trim() && !tags.includes(tag.trim())) { setTags([...tags, tag.trim()]); } }; const removeTag = (tagToRemove: string) => { setTags(tags.filter(tag => tag !== tagToRemove)); }; const insertMarkdown = useCallback((syntax: string, selectedText: string = '') => { if (!contentRef.current) return; const textarea = contentRef.current; const start = textarea.selectionStart; const end = textarea.selectionEnd; const selection = selectedText || content.substring(start, end); let newText = ''; let cursorOffset = 0; switch (syntax) { case 'bold': newText = `**${selection || 'bold text'}**`; cursorOffset = selection ? newText.length : 2; break; case 'italic': newText = `*${selection || 'italic text'}*`; cursorOffset = selection ? newText.length : 1; break; case 'underline': newText = `${selection || 'underlined text'}`; cursorOffset = selection ? newText.length : 3; break; case 'strikethrough': newText = `~~${selection || 'strikethrough text'}~~`; cursorOffset = selection ? newText.length : 2; break; case 'heading1': newText = `# ${selection || 'Heading 1'}`; cursorOffset = selection ? newText.length : 2; break; case 'heading2': newText = `## ${selection || 'Heading 2'}`; cursorOffset = selection ? newText.length : 3; break; case 'heading3': newText = `### ${selection || 'Heading 3'}`; cursorOffset = selection ? newText.length : 4; break; case 'list': newText = `- ${selection || 'List item'}`; cursorOffset = selection ? newText.length : 2; break; case 'list-ordered': newText = `1. ${selection || 'List item'}`; cursorOffset = selection ? newText.length : 3; break; case 'quote': newText = `> ${selection || 'Quote'}`; cursorOffset = selection ? newText.length : 2; break; case 'code': if (selection.includes('\n')) { newText = `\`\`\`\n${selection || 'code block'}\n\`\`\``; cursorOffset = selection ? newText.length : 4; } else { newText = `\`${selection || 'code'}\``; cursorOffset = selection ? newText.length : 1; } break; case 'link': newText = `[${selection || 'link text'}](url)`; cursorOffset = selection ? newText.length - 4 : newText.length - 4; break; case 'image': newText = `![${selection || 'alt text'}](image-url)`; cursorOffset = selection ? newText.length - 11 : newText.length - 11; break; case 'divider': newText = '\n---\n'; cursorOffset = newText.length; break; default: return; } const newContent = content.substring(0, start) + newText + content.substring(end); setContent(newContent); // Focus and set cursor position setTimeout(() => { textarea.focus(); const newPosition = start + cursorOffset; textarea.setSelectionRange(newPosition, newPosition); }, 0); }, [content]); const autoResizeTextarea = (element: HTMLTextAreaElement) => { element.style.height = 'auto'; element.style.height = element.scrollHeight + 'px'; }; // Render markdown preview const renderMarkdownPreview = (markdown: string) => { // Simple markdown renderer for preview const html = markdown // Headers .replace(/^### (.*$)/gim, '

$1

') .replace(/^## (.*$)/gim, '

$1

') .replace(/^# (.*$)/gim, '

$1

') // Bold and Italic .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') // Underline and Strikethrough .replace(/(.*?)<\/u>/g, '$1') .replace(/~~(.*?)~~/g, '$1') // Code .replace(/```([^`]+)```/g, '
$1
') .replace(/`([^`]+)`/g, '$1') // Lists .replace(/^\- (.*$)/gim, '
  • • $1
  • ') .replace(/^\d+\. (.*$)/gim, '
  • $1
  • ') // Links .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') // Images .replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1') // Quotes .replace(/^> (.*$)/gim, '
    $1
    ') // Dividers .replace(/^---$/gim, '
    ') // Paragraphs .replace(/\n\n/g, '

    ') .replace(/\n/g, '
    '); return `

    ${html}

    `; }; if (!isOpen) return null; return ( {/* Professional Ghost Editor */}
    {/* Top Navigation Bar */}
    {isCreating ? 'New Project' : 'Editing Project'}
    {published ? ( Published ) : ( Draft )} {featured && ( Featured )}
    {/* View Mode Toggle */}
    {/* Rich Text Toolbar */}
    {/* Text Formatting */}
    {/* Headers */}
    {/* Lists */}
    {/* Insert Elements */}
    {/* Stats */}
    {wordCount} words {readingTime} min read
    {/* Main Editor Area */}
    {/* Content Area */}
    {/* Editor Pane */} {(viewMode === 'edit' || viewMode === 'split') && (
    {/* Title & Description */}