214 lines
No EOL
11 KiB
JavaScript
214 lines
No EOL
11 KiB
JavaScript
import { Box, Link, Chip, List, ListItem, ListItemText } from '@mui/material';
|
|
import { OpenInNew, Comment, TrendingUp } from '@mui/icons-material';
|
|
import GenericWidget from './GenericWidget';
|
|
|
|
function NewsWidget({ data, sx }) {
|
|
if (!data || data.error) {
|
|
return (
|
|
<GenericWidget className="news-widget" title="News" sx={{ opacity: 0.5, ...sx }}>
|
|
<p className="widget-error">
|
|
{data?.message || 'News data unavailable'}
|
|
</p>
|
|
</GenericWidget>
|
|
);
|
|
}
|
|
|
|
// Handle Reddit data
|
|
if (data.type === 'reddit' && data.posts) {
|
|
const subreddit = data.url?.match(/\/r\/([^\/\.]+)/)?.[1] || 'Reddit';
|
|
|
|
return (
|
|
<GenericWidget className="news-widget" title={`r/${subreddit}`} sx={{ ...sx }}>
|
|
<List dense>
|
|
{data.posts.slice(0, 5).map((post, index) => {
|
|
// Determine if we should show an image
|
|
const hasImage = post.mainImage &&
|
|
!post.mainImage.includes('external-preview') && // Skip Reddit's external preview placeholder
|
|
post.mainImage !== 'spoiler' &&
|
|
post.mainImage !== 'nsfw' &&
|
|
post.mainImage !== 'default';
|
|
|
|
return (
|
|
<ListItem key={index} disablePadding sx={{ mb: 2 }}>
|
|
<ListItemText
|
|
primary={
|
|
<Box>
|
|
{/* Image section - Medium size, above title */}
|
|
{hasImage && (
|
|
<Link
|
|
href={post.permalink || post.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
sx={{ display: 'block', mb: 1 }}
|
|
>
|
|
<Box
|
|
component="img"
|
|
src={post.mainImage}
|
|
alt={post.title}
|
|
onError={(e) => {
|
|
e.target.style.display = 'none';
|
|
}}
|
|
sx={{
|
|
width: '100%',
|
|
maxWidth: 400,
|
|
height: 'auto',
|
|
maxHeight: 250,
|
|
objectFit: 'cover',
|
|
borderRadius: 1,
|
|
display: 'block',
|
|
backgroundColor: 'action.hover',
|
|
cursor: 'pointer',
|
|
transition: 'transform 0.2s',
|
|
'&:hover': {
|
|
transform: 'scale(1.02)'
|
|
}
|
|
}}
|
|
/>
|
|
{post.isGallery && post.galleryImages.length > 1 && (
|
|
<Chip
|
|
label={`Gallery (${post.galleryImages.length} images)`}
|
|
size="small"
|
|
sx={{ mt: -3.5, ml: 1, position: 'relative' }}
|
|
/>
|
|
)}
|
|
</Link>
|
|
)}
|
|
|
|
{/* Title section */}
|
|
<Link
|
|
href={post.permalink || post.url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
sx={{
|
|
textDecoration: 'none',
|
|
color: 'text.primary',
|
|
'&:hover': { textDecoration: 'underline' }
|
|
}}
|
|
>
|
|
<div className="news-title" style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
{post.title}
|
|
<OpenInNew sx={{ fontSize: 14, marginLeft: 0.5 }} />
|
|
</div>
|
|
</Link>
|
|
</Box>
|
|
}
|
|
secondaryTypographyProps={{ component: 'div' }}
|
|
secondary={
|
|
<Box sx={{ display: 'flex', gap: 1, mt: 0.5, flexWrap: 'wrap', alignItems: 'center' }}>
|
|
<Chip
|
|
icon={<TrendingUp />}
|
|
label={`${post.score?.toLocaleString() || 0} upvotes`}
|
|
size="small"
|
|
variant="outlined"
|
|
color="primary"
|
|
/>
|
|
<Chip
|
|
icon={<Comment />}
|
|
label={`${post.comments?.toLocaleString() || 0} comments`}
|
|
size="small"
|
|
variant="outlined"
|
|
/>
|
|
<span className="news-author" style={{ lineHeight: '24px', fontSize: '0.875rem', opacity: 0.7 }}>
|
|
{post.created && (() => {
|
|
const now = Date.now() / 1000; // Convert to seconds
|
|
const diff = now - post.created;
|
|
const hours = Math.floor(diff / 3600);
|
|
const days = Math.floor(hours / 24);
|
|
const timeAgo = days > 0 ? `${days}d ago` : `${hours}h ago`;
|
|
return timeAgo;
|
|
})()} • by {post.author} in r/{post.subreddit}
|
|
</span>
|
|
</Box>
|
|
}
|
|
/>
|
|
</ListItem>
|
|
);
|
|
})}
|
|
</List>
|
|
</GenericWidget>
|
|
);
|
|
}
|
|
|
|
// Handle RSS feed data
|
|
if (data.type === 'rss' && data.items) {
|
|
return (
|
|
<GenericWidget title={data.feedTitle || 'RSS Feed'} sx={{ ...sx }}>
|
|
<List dense>
|
|
{data.items.slice(0, 5).map((item, index) => (
|
|
<ListItem key={index} disablePadding sx={{ mb: 1 }}>
|
|
<ListItemText
|
|
primary={
|
|
<Box sx={{ display: 'flex', alignItems: 'start', gap: 1 }}>
|
|
{(item.thumbnail || item.enclosure?.url || item['media:thumbnail']?.url) && (
|
|
<Link
|
|
href={item.link}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<Box
|
|
component="img"
|
|
src={item.thumbnail || item.enclosure?.url || item['media:thumbnail']?.url}
|
|
alt={item.title}
|
|
sx={{
|
|
width: 70,
|
|
height: 70,
|
|
objectFit: 'cover',
|
|
borderRadius: 1,
|
|
flexShrink: 0
|
|
}}
|
|
/>
|
|
</Link>
|
|
)}
|
|
<Link
|
|
href={item.link}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
sx={{
|
|
textDecoration: 'none',
|
|
color: 'text.primary',
|
|
'&:hover': { textDecoration: 'underline' }
|
|
}}
|
|
>
|
|
<div className="news-title">
|
|
{item.title}
|
|
<OpenInNew sx={{ fontSize: 14 }} />
|
|
</div>
|
|
</Link>
|
|
</Box>
|
|
}
|
|
secondaryTypographyProps={{ component: 'div' }}
|
|
secondary={
|
|
<Box>
|
|
<div className="news-date">
|
|
{item.pubDate && new Date(item.pubDate).toLocaleDateString()}
|
|
</div>
|
|
{item.description && (
|
|
<div className="news-description">
|
|
{item.description.replace(/<[^>]*>?/gm, '')}
|
|
</div>
|
|
)}
|
|
</Box>
|
|
}
|
|
/>
|
|
</ListItem>
|
|
))}
|
|
</List>
|
|
</GenericWidget>
|
|
);
|
|
}
|
|
|
|
|
|
// Default fallback for unknown data types
|
|
return (
|
|
<GenericWidget title="News" sx={{ ...sx }}>
|
|
<p className="widget-info">
|
|
Data format not recognized
|
|
</p>
|
|
<p className="widget-caption">
|
|
{JSON.stringify(data).substring(0, 100)}...
|
|
</p>
|
|
</GenericWidget>
|
|
);
|
|
}
|
|
|
|
export default NewsWidget; |