--- import type { CollectionEntry } from 'astro:content'; import { formatDate, processMarkdown, isValidDate } from '../utils/markdown'; import { siteConfig } from '../config'; import { getOptimizedFormat } from '@/utils/images'; import ImageWrapper from './ImageWrapper.astro'; import Icon from './Icon.astro'; interface Props { documentation: CollectionEntry<'docs'>; featured?: boolean; eager?: boolean; context?: 'featured' | 'recent' | 'docs' | 'tags' | 'home'; } const { documentation, featured = false, eager = false, context = 'docs' } = Astro.props; // Get excerpt from markdown processing const { excerpt } = processMarkdown(documentation.body); const { title, description: rawDescription, lastModified, image: rawImage, imageAlt, category, version } = documentation.data; // Process Obsidian bracket syntax in image field (copy from PostCard) const image = (() => { if (!rawImage) return rawImage; // Handle case where rawImage is an array (unquoted YAML syntax) let imageValue = rawImage; if (Array.isArray(rawImage)) { // If it's an array, take the first element imageValue = rawImage[0]; } // Ensure imageValue is a string before calling string methods if (typeof imageValue !== 'string') { console.warn('DocumentationCard: imageValue is not a string:', imageValue); return imageValue; } // Check if it's Obsidian double bracket syntax if (imageValue.startsWith('[[') && imageValue.endsWith(']]')) { // Extract the content inside the double brackets return imageValue.slice(2, -2); } return imageValue; })(); const description = rawDescription || excerpt; // Determine if we should show the cover image // Documentation should always show images when available (not controlled by postOptions) const shouldShowCoverImage = true; // Hide images for docs on homepage for cleaner look const showCoverImage = shouldShowCoverImage && image && context !== 'home'; ---
{version && ( v{version} )}
{image && showCoverImage && (
{ // Clean the image path by removing Obsidian brackets // Remove Obsidian brackets to get the actual image path let cleanImagePath = image; if (typeof cleanImagePath === 'string' && cleanImagePath.startsWith('[[') && cleanImagePath.endsWith(']]')) { cleanImagePath = cleanImagePath.slice(2, -2); } // Remove Obsidian subfolder prefixes (images/, attachments/) if (typeof cleanImagePath === 'string') { // Extract just the filename if it includes attachments/ if (cleanImagePath.includes('attachments/')) { cleanImagePath = cleanImagePath.split('attachments/').pop() || ''; } // Remove images/ prefix if present (Obsidian subfolder setting) else if (cleanImagePath.startsWith('images/')) { cleanImagePath = cleanImagePath.replace('images/', ''); } } // Convert to WebP if applicable (sync-images.js creates WebP versions) return getOptimizedFormat(cleanImagePath || ''); })()} basePath={(() => { // 1. External URLs - no basePath needed if (typeof image === 'string' && image.startsWith('http')) { return ''; } // 2. Clean the image path to check for attachments/ prefix let cleanImagePath = image; if (typeof cleanImagePath === 'string' && cleanImagePath.startsWith('[[') && cleanImagePath.endsWith(']]')) { cleanImagePath = cleanImagePath.slice(2, -2); } // 3. Detect folder-based vs file-based: if image path starts with 'attachments/', // it's a single-file doc (shared attachments folder) if (typeof cleanImagePath === 'string' && cleanImagePath.includes('attachments/')) { // Single-file doc with image in attachments folder // The sync script copies these to /docs/attachments/ return "/docs/attachments/"; } // 4. Folder-based doc - sync script copies images to doc folder root // Safety check: ensure documentation.id exists before using it if (documentation.id) { return `/docs/${documentation.id}/`; } else { // Fallback to attachments directory if documentation.id is undefined return '/docs/attachments/'; } })()} alt={imageAlt || `Cover image for documentation: ${title}`} width={400} height={200} loading={eager ? 'eager' : 'lazy'} fetchpriority={eager ? 'high' : 'auto'} class="w-full h-32 object-cover group-hover:scale-105 transition-transform duration-300" />
)}

{title}

{description && (

{description}

)} {lastModified && (
{isValidDate(lastModified) && ( )}
)}