diff --git a/components/ChannelView.js b/components/ChannelView.js index ee8c7fb..d30e5e7 100644 --- a/components/ChannelView.js +++ b/components/ChannelView.js @@ -7,6 +7,7 @@ import { MessageSquare, Pencil, X, Send, Clock, Search, Camera, ExternalLink } from 'lucide-react'; import PhotoSearchModal from './PhotoSearchModal'; +import PostPreview from './PostPreview'; const GOAL_LABELS = { educational: 'Обучение', news: 'Новости', @@ -387,7 +388,8 @@ export default function ChannelView({ channel }) { {/* Result */} {post && ( -
+
+

Результат @@ -586,7 +588,17 @@ export default function ChannelView({ channel }) { ))}

+
{/* конец левой колонки */} + {/* Правая колонка — превью */} +
+
+
{/* конец грида */} )} {/* Photo search modal */} diff --git a/components/PostPreview.js b/components/PostPreview.js new file mode 100644 index 0000000..45b84f8 --- /dev/null +++ b/components/PostPreview.js @@ -0,0 +1,325 @@ +'use client'; + +/** + * PostPreview — рендерит пост так, как он будет выглядеть в TG / VK / MAX. + * Props: + * text — текст поста (Markdown-разметка TG) + * imageUrl — URL картинки (опционально) + * platform — 'telegram' | 'vk' | 'max' + * channelName — название канала (для шапки) + */ + +import { useState } from 'react'; +import { Eye, EyeOff, MessageCircle, Heart, Share2, Bookmark, + ThumbsUp, BarChart2, AlertCircle } from 'lucide-react'; + +// ── Лимиты платформ ─────────────────────────────────────────────────────────── +const LIMITS = { + telegram: { text: 4096, caption: 1024, label: 'Telegram' }, + vk: { text: 16384, caption: 2048, label: 'ВКонтакте' }, + max: { text: 4096, caption: 1024, label: 'MAX' }, +}; + +// ── Парсер разметки → HTML ──────────────────────────────────────────────────── + +function parseTgMarkdown(text) { + // Экранируем HTML-спецсимволы + let s = text + .replace(/&/g, '&') + .replace(//g, '>'); + + // **bold** или __bold__ + s = s.replace(/\*\*(.+?)\*\*/gs, '$1'); + s = s.replace(/__(.+?)__/gs, '$1'); + // _italic_ или *italic* + s = s.replace(/(?$1'); + s = s.replace(/(?$1'); + // `code` + s = s.replace(/`([^`]+)`/g, '$1'); + // ```block``` + s = s.replace(/```[\w]*\n?([\s\S]*?)```/g, '
$1
'); + // [text](url) + s = s.replace(/\[([^\]]+)\]\((https?:\/\/[^\)]+)\)/g, + '$1'); + // Переносы строк →
+ s = s.replace(/\n/g, '
'); + + return s; +} + +function parseVkMarkdown(text) { + // VK не поддерживает Markdown — убираем разметку, оставляем текст + let s = text + .replace(/\*\*(.+?)\*\*/gs, '$1') + .replace(/__(.+?)__/gs, '$1') + .replace(/(?/g, '>') + .replace(/\n/g, '
'); + return s; +} + +function renderMarkdown(text, platform) { + if (!text) return ''; + if (platform === 'vk') return parseVkMarkdown(text); + return parseTgMarkdown(text); // telegram + max +} + +// ── Счётчик символов ────────────────────────────────────────────────────────── + +function CharCounter({ text, imageUrl, platform }) { + const limits = LIMITS[platform] || LIMITS.telegram; + const limit = imageUrl ? limits.caption : limits.text; + const len = (text || '').length; + const pct = Math.min(len / limit, 1); + const over = len > limit; + + const color = over ? 'text-red-400' : pct > 0.85 ? 'text-yellow-400' : 'text-text-mute'; + + return ( +
+ {over && } + {len} / {limit}{imageUrl ? ' (caption)' : ''} + {over && превышен лимит} +
+ ); +} + +// ── TG Preview ──────────────────────────────────────────────────────────────── + +function TelegramPreview({ text, imageUrl, channelName }) { + const html = renderMarkdown(text, 'telegram'); + + return ( +
+ {/* Header */} +
+
+ {(channelName || 'Z').slice(0, 1).toUpperCase()} +
+
+
{channelName || 'Канал'}
+
только что
+
+
+ + {/* Контент */} +
+ {imageUrl && ( + { e.target.style.display = 'none'; }} + /> + )} +
Текст поста появится здесь…' }} + /> +
+ + {/* Footer */} +
+
+ 1.2K + 14 +
+
+ + +
+
+ + {/* TG inline styles */} + +
+ ); +} + +// ── VK Preview ──────────────────────────────────────────────────────────────── + +function VkPreview({ text, imageUrl, channelName }) { + const html = renderMarkdown(text, 'vk'); + + return ( +
+ {/* Header */} +
+
+ {(channelName || 'K').slice(0, 1).toUpperCase()} +
+
+
{channelName || 'Сообщество'}
+
только что
+
+
···
+
+ + {/* Контент */} +
+
Текст поста появится здесь…' }} + /> + {imageUrl && ( + { e.target.style.display = 'none'; }} + /> + )} +
+ + {/* Footer */} +
+ + + + + 891 + +
+
+ ); +} + +// ── MAX Preview ─────────────────────────────────────────────────────────────── + +function MaxPreview({ text, imageUrl, channelName }) { + const html = renderMarkdown(text, 'max'); + + return ( +
+ {/* Header */} +
+
+ {(channelName || 'M').slice(0, 1).toUpperCase()} +
+
+
{channelName || 'Канал MAX'}
+
только что
+
+
+ + {/* Контент */} +
+ {imageUrl && ( + { e.target.style.display = 'none'; }} + /> + )} +
Текст поста появится здесь…' }} + /> +
+ + {/* Footer */} +
+ 432 + 18 + Опрос +
+
+ ); +} + +// ── Главный экспорт ─────────────────────────────────────────────────────────── + +const PLATFORM_ORDER = ['telegram', 'vk', 'max']; +const PLATFORM_LABELS = { telegram: 'TG', vk: 'VK', max: 'MAX' }; + +export default function PostPreview({ text, imageUrl, platform: defaultPlatform = 'telegram', channelName }) { + const [visible, setVisible] = useState(true); + const [platform, setPlatform] = useState(defaultPlatform); + + if (!visible) { + return ( + + ); + } + + return ( +
+ {/* Тулбар */} +
+ Превью +
+ {/* Переключатель платформы */} +
+ {PLATFORM_ORDER.map(p => ( + + ))} +
+ {/* Скрыть */} + +
+
+ + {/* Счётчик символов */} + + + {/* Превью платформы */} + {platform === 'telegram' && ( + + )} + {platform === 'vk' && ( + + )} + {platform === 'max' && ( + + )} + + {/* Лимит */} +
+ {LIMITS[platform].label}: текст до {LIMITS[platform].text.toLocaleString()} симв. + {', caption (с фото) до '}{LIMITS[platform].caption.toLocaleString()} симв. +
+
+ ); +}