mirror of
https://github.com/dergigi/boris.git
synced 2026-01-26 02:04:58 +01:00
feat: add image preview for large view cards
- Extract YouTube video thumbnails from URLs - Display thumbnail images as background in large preview cards - Add gradient overlay for better text contrast - Fallback to icon placeholder for non-YouTube URLs - Handle multiple YouTube URL formats (watch, youtu.be, shorts) - Gracefully handle missing images with icon fallback
This commit is contained in:
@@ -12,6 +12,7 @@ import ContentWithResolvedProfiles from './ContentWithResolvedProfiles'
|
||||
import { extractUrlsFromContent } from '../services/bookmarkHelpers'
|
||||
import { classifyUrl } from '../utils/helpers'
|
||||
import { ViewMode } from './Bookmarks'
|
||||
import { getPreviewImage } from '../utils/imagePreview'
|
||||
|
||||
interface BookmarkItemProps {
|
||||
bookmark: IndividualBookmark
|
||||
@@ -124,14 +125,22 @@ export const BookmarkItem: React.FC<BookmarkItemProps> = ({ bookmark, index, onS
|
||||
|
||||
// Large preview view rendering
|
||||
if (viewMode === 'large') {
|
||||
const firstUrl = hasUrls ? extractedUrls[0] : null
|
||||
const previewImage = firstUrl ? getPreviewImage(firstUrl, firstUrlClassification?.type || '') : null
|
||||
|
||||
return (
|
||||
<div key={`${bookmark.id}-${index}`} className={`individual-bookmark large ${bookmark.isPrivate ? 'private-bookmark' : ''}`}>
|
||||
{hasUrls && (
|
||||
<div className="large-preview-image" onClick={() => onSelectUrl?.(extractedUrls[0])}>
|
||||
{/* Placeholder for future image preview */}
|
||||
<div className="preview-placeholder">
|
||||
<FontAwesomeIcon icon={getIconForUrlType(extractedUrls[0])} />
|
||||
</div>
|
||||
<div
|
||||
className="large-preview-image"
|
||||
onClick={() => onSelectUrl?.(extractedUrls[0])}
|
||||
style={previewImage ? { backgroundImage: `url(${previewImage})` } : undefined}
|
||||
>
|
||||
{!previewImage && (
|
||||
<div className="preview-placeholder">
|
||||
<FontAwesomeIcon icon={getIconForUrlType(extractedUrls[0])} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -838,16 +838,28 @@ body {
|
||||
width: 100%;
|
||||
height: 180px;
|
||||
background: #1a1a1a;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
transition: all 0.2s ease;
|
||||
border-bottom: 1px solid #333;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.large-preview-image:hover {
|
||||
background: #222;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.large-preview-image::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(to bottom, transparent 60%, rgba(0,0,0,0.3) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.preview-placeholder {
|
||||
|
||||
39
src/utils/imagePreview.ts
Normal file
39
src/utils/imagePreview.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
// Utility to extract preview images from URLs
|
||||
|
||||
export const extractYouTubeVideoId = (url: string): string | null => {
|
||||
// Handle various YouTube URL formats
|
||||
const patterns = [
|
||||
/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\n?#]+)/,
|
||||
/youtube\.com\/shorts\/([^&\n?#]+)/,
|
||||
]
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = url.match(pattern)
|
||||
if (match && match[1]) {
|
||||
return match[1]
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export const getYouTubeThumbnail = (url: string): string | null => {
|
||||
const videoId = extractYouTubeVideoId(url)
|
||||
if (!videoId) return null
|
||||
|
||||
// Use maxresdefault for best quality, falls back to hqdefault if not available
|
||||
return `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`
|
||||
}
|
||||
|
||||
export const getPreviewImage = (url: string, type: string): string | null => {
|
||||
// YouTube videos
|
||||
if (type === 'youtube') {
|
||||
return getYouTubeThumbnail(url)
|
||||
}
|
||||
|
||||
// For other URLs, we would need to fetch OG tags
|
||||
// but CORS will block us on localhost
|
||||
// Return null for now and show placeholder
|
||||
return null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user