import React, { useState } from 'react'; import ReactMarkdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'; import { EmojieIcon, EditIcon, ReplyIcon, MoreIcon, DeleteIcon, PinIcon, } from '../assets/icons'; import { getEmojiUrl, AllEmojis } from '../assets/emojis'; import Tooltip from './Tooltip'; import Avatar from './Avatar'; const fireIcon = getEmojiUrl('nature', 'fire'); const heartIcon = getEmojiUrl('symbols', 'heart'); const thumbsupIcon = getEmojiUrl('people', 'thumbsup'); const ICON_COLOR_DEFAULT = 'color-mix(in oklab, hsl(240 4.294% 68.039% / 1) 100%, #000 0%)'; const USER_COLORS = ['#5865F2', '#EBA7CD', '#57F287', '#FEE75C', '#EB459E', '#ED4245']; export const getUserColor = (name) => { let hash = 0; for (let i = 0; i < name.length; i++) { hash = name.charCodeAt(i) + ((hash << 5) - hash); } return USER_COLORS[Math.abs(hash) % USER_COLORS.length]; }; export const extractUrls = (text) => { const urlRegex = /(https?:\/\/[^\s]+)/g; return text.match(urlRegex) || []; }; export const formatMentions = (text) => { if (!text) return ''; return text.replace(/@(\w+)/g, '[@$1](mention://$1)'); }; export const formatEmojis = (text) => { if (!text) return ''; return text.replace(/:([a-zA-Z0-9_]+):/g, (match, name) => { const emoji = AllEmojis.find(e => e.name === name); return emoji ? `![${match}](${emoji.src})` : match; }); }; export const parseAttachment = (content) => { if (!content || !content.startsWith('{')) return null; try { const parsed = JSON.parse(content); return parsed.type === 'attachment' ? parsed : null; } catch (e) { return null; } }; export const parseSystemMessage = (content) => { if (!content || !content.startsWith('{')) return null; try { const parsed = JSON.parse(content); return parsed.type === 'system' ? parsed : null; } catch (e) { return null; } }; const VIDEO_EXT_RE = /\.(mp4|webm|ogg|mov)(\?[^\s]*)?$/i; const isVideoUrl = (url) => VIDEO_EXT_RE.test(url); const getReactionIcon = (name) => { switch (name) { case 'thumbsup': return thumbsupIcon; case 'heart': return heartIcon; case 'fire': return fireIcon; default: return heartIcon; } }; const ColoredIcon = ({ src, color, size = '24px', style = {} }) => (
); const isNewDay = (current, previous) => { if (!previous) return true; return current.getDate() !== previous.getDate() || current.getMonth() !== previous.getMonth() || current.getFullYear() !== previous.getFullYear(); }; const markdownComponents = { a: ({ node, ...props }) => { if (props.href && props.href.startsWith('mention://')) return {props.children}; return { e.preventDefault(); window.cryptoAPI.openExternal(props.href); }} style={{ color: '#00b0f4', cursor: 'pointer', textDecoration: 'none' }} onMouseOver={(e) => e.target.style.textDecoration = 'underline'} onMouseOut={(e) => e.target.style.textDecoration = 'none'} />; }, code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || ''); return !inline && match ? {String(children).replace(/\n$/, '')} : {children}; }, p: ({ node, ...props }) =>

, h1: ({ node, ...props }) =>

, h2: ({ node, ...props }) =>

, h3: ({ node, ...props }) =>

, ul: ({ node, ...props }) =>