'use client';
import React, { useState, useRef, useEffect, useCallback } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import {
Save,
X,
Eye,
EyeOff,
Settings,
Link as LinkIcon,
Tag,
Calendar,
Globe,
Github,
Image as ImageIcon,
Bold,
Italic,
List,
Hash,
Quote,
Code,
Zap,
Type,
Columns,
PanelLeft,
PanelRight,
Monitor,
Smartphone,
Tablet,
Undo,
Redo,
AlignLeft,
AlignCenter,
AlignRight,
Link2,
ListOrdered,
Underline,
Strikethrough,
GripVertical
} 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 ResizableGhostEditorProps {
project?: Project | null;
onSave: (projectData: any) => void;
onClose: () => void;
isCreating: boolean;
}
export const ResizableGhostEditor: React.FC = ({
project,
onSave,
onClose,
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 [showPreview, setShowPreview] = useState(true);
const [showSettings, setShowSettings] = useState(false);
const [previewWidth, setPreviewWidth] = useState(50); // Percentage
const [wordCount, setWordCount] = useState(0);
const [readingTime, setReadingTime] = useState(0);
const [isResizing, setIsResizing] = useState(false);
const titleRef = useRef(null);
const contentRef = useRef(null);
const previewRef = useRef(null);
const resizeRef = 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]);
// 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]);
// Handle resizing
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!isResizing) return;
const containerWidth = window.innerWidth - (showSettings ? 320 : 0); // Account for settings sidebar
const newWidth = Math.max(20, Math.min(80, (e.clientX / containerWidth) * 100));
setPreviewWidth(100 - newWidth); // Invert since we're setting editor width
};
const handleMouseUp = () => {
setIsResizing(false);
};
if (isResizing) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isResizing, showSettings]);
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 = ``;
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';
};
// Enhanced markdown renderer with proper white text
const renderMarkdownPreview = (markdown: string) => {
let html = markdown
// Headers - WHITE TEXT
.replace(/^### (.*$)/gim, '$1 ')
.replace(/^## (.*$)/gim, '$1 ')
.replace(/^# (.*$)/gim, '$1 ')
// Bold and Italic - WHITE TEXT
.replace(/\*\*(.*?)\*\*/g, '$1 ')
.replace(/\*(.*?)\*/g, '$1 ')
// Underline and Strikethrough - WHITE TEXT
.replace(/(.*?)<\/u>/g, '$1 ')
.replace(/~~(.*?)~~/g, '$1')
// Code
.replace(/```([^`]+)```/g, '$1 ')
.replace(/`([^`]+)`/g, '$1')
// Lists - WHITE TEXT
.replace(/^\- (.*$)/gim, '• $1 ')
.replace(/^\d+\. (.*$)/gim, '$1 ')
// Links
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1 ')
// Images
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, ' ')
// Quotes - WHITE TEXT
.replace(/^> (.*$)/gim, '$1 ')
// Dividers
.replace(/^---$/gim, ' ')
// Paragraphs - WHITE TEXT
.replace(/\n\n/g, '
')
.replace(/\n/g, ' ');
return `
`;
};
return (
{/* Professional Ghost Editor */}
{/* Top Navigation Bar */}
{isCreating ? 'New Project' : 'Editing Project'}
{published ? (
Published
) : (
Draft
)}
{featured && (
Featured
)}
{/* Controls */}
{/* Preview Toggle */}
setShowPreview(!showPreview)}
className={`p-2 rounded transition-colors ${
showPreview ? 'bg-blue-600 text-white' : 'text-gray-400 hover:text-white hover:bg-gray-700'
}`}
title="Toggle Preview"
>
{showPreview ? : }
setShowSettings(!showSettings)}
className={`p-2 rounded-lg transition-colors ${
showSettings ? 'bg-blue-600 text-white' : 'text-gray-400 hover:text-white hover:bg-gray-700'
}`}
>
Save
{/* Rich Text Toolbar */}
{/* Text Formatting */}
insertMarkdown('bold')}
className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors"
title="Bold (Ctrl+B)"
>
insertMarkdown('italic')}
className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors"
title="Italic (Ctrl+I)"
>
insertMarkdown('underline')}
className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors"
title="Underline"
>
insertMarkdown('strikethrough')}
className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors"
title="Strikethrough"
>
{/* Headers */}
insertMarkdown('heading1')}
className="px-2 py-1 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors text-sm font-bold"
title="Heading 1"
>
H1
insertMarkdown('heading2')}
className="px-2 py-1 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors text-sm font-bold"
title="Heading 2"
>
H2
insertMarkdown('heading3')}
className="px-2 py-1 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors text-sm font-bold"
title="Heading 3"
>
H3
{/* Lists */}
insertMarkdown('list')}
className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors"
title="Bullet List"
>
insertMarkdown('list-ordered')}
className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors"
title="Numbered List"
>
{/* Insert Elements */}
insertMarkdown('link')}
className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors"
title="Insert Link"
>
insertMarkdown('image')}
className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors"
title="Insert Image"
>
insertMarkdown('code')}
className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors"
title="Code Block"
>
insertMarkdown('quote')}
className="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded transition-colors"
title="Quote"
>
{/* Stats */}
{wordCount} words
{readingTime} min read
{showPreview && (
Preview: {previewWidth}%
)}
{/* Main Editor Area */}
{/* Content Area */}
{/* Editor Pane */}
{/* Title & Description */}
{/* Content Editor */}
setContent(e.target.value)}
placeholder="Start writing your story...
Use Markdown for formatting:
**Bold text** or *italic text*
# Large heading
## Medium heading
### Small heading
- Bullet points
1. Numbered lists
> Quotes
`code`
[Links](https://example.com)
"
className="w-full h-full text-lg text-white bg-transparent border-none outline-none placeholder-gray-600 resize-none font-mono leading-relaxed focus:ring-0"
style={{ minHeight: '500px' }}
/>
{/* Resize Handle */}
{showPreview && (
setIsResizing(true)}
>
)}
{/* Preview Pane */}
{showPreview && (
{/* Preview Header */}
{title || 'Project title...'}
{description || 'Brief description of your project...'}
{/* Preview Content */}
Start writing to see the preview...'
}}
/>
)}
{/* Settings Sidebar */}
{showSettings && (
Project Settings
{/* Status */}
Publication
Published
setPublished(!published)}
className={`w-12 h-6 rounded-full transition-colors relative ${
published ? 'bg-green-600' : 'bg-gray-600'
}`}
>
Featured
setFeatured(!featured)}
className={`w-12 h-6 rounded-full transition-colors relative ${
featured ? 'bg-purple-600' : 'bg-gray-600'
}`}
>
{/* Category & Difficulty */}
Classification
Category
setCategory(e.target.value)}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{categories.map(cat => (
{cat}
))}
Difficulty
setDifficulty(e.target.value)}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{difficulties.map(diff => (
{diff}
))}
{/* Links */}
{/* Tags */}
Tags
{
if (e.key === 'Enter') {
e.preventDefault();
addTag(e.currentTarget.value);
e.currentTarget.value = '';
}
}}
className="w-full px-3 py-2 bg-gray-700 border border-gray-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
{tags.length > 0 && (
{tags.map((tag) => (
{tag}
removeTag(tag)}
className="text-blue-200 hover:text-white"
>
))}
)}
)}
);
};