diff --git a/src/App.tsx b/src/App.tsx index 5b9a82b..fb22c04 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -42,6 +42,7 @@ import code, { PageMetadata, StructuredDataOptions, BrandingOptions, + SocialPreviewOptions, SeoOptions, AnalyticsOptions, CachingOptions, @@ -155,6 +156,13 @@ export default function App() { twitterHandle: "", faviconUrl: "", }); + const [socialPreview, setSocialPreview] = useState({ + defaultImage: "", + imageWidth: 1200, + imageHeight: 630, + twitterCardType: "summary_large_image", + locale: "", + }); const [seo, setSeo] = useState({ aiAttribution: "", }); @@ -279,6 +287,17 @@ export default function App() { setCopied(false); } + function handleSocialPreviewChange( + field: keyof SocialPreviewOptions, + value: string | number, + ): void { + setSocialPreview({ + ...socialPreview, + [field]: value, + }); + setCopied(false); + } + function handleSeoChange(field: keyof SeoOptions, value: string): void { setSeo({ ...seo, @@ -442,6 +461,7 @@ export default function App() { pageMetadata, structuredData, branding, + socialPreview, seo, analytics, caching, @@ -930,6 +950,99 @@ export default function App() { /> + + + Social Preview + + + handleSocialPreviewChange("defaultImage", e.target.value) + } + value={socialPreview.defaultImage} + variant="outlined" + size="small" + /> + + + handleSocialPreviewChange( + "imageWidth", + Number(e.target.value), + ) + } + value={socialPreview.imageWidth} + variant="outlined" + size="small" + sx={{ flex: 1 }} + /> + + handleSocialPreviewChange( + "imageHeight", + Number(e.target.value), + ) + } + value={socialPreview.imageHeight} + variant="outlined" + size="small" + sx={{ flex: 1 }} + /> + + + + Twitter Card Type + + + + + handleSocialPreviewChange("locale", e.target.value) + } + value={socialPreview.locale} + variant="outlined" + size="small" + /> + + ; structuredData: StructuredDataOptions; branding: BrandingOptions; + socialPreview: SocialPreviewOptions; seo: SeoOptions; analytics: AnalyticsOptions; caching: CachingOptions; @@ -111,6 +120,7 @@ export default function code(data: CodeData): string { pageMetadata, structuredData, branding, + socialPreview, seo, analytics, caching, @@ -170,6 +180,16 @@ ${slugs const TWITTER_HANDLE = '${branding?.twitterHandle || ""}'; const FAVICON_URL = '${branding?.faviconUrl || ""}'; + /* + * Step 3.3.1: social preview configuration (optional) + * Enhance Open Graph and Twitter Card meta tags for better link previews + */ + const DEFAULT_OG_IMAGE = '${socialPreview?.defaultImage || ""}'; + const OG_IMAGE_WIDTH = ${socialPreview?.imageWidth || 1200}; + const OG_IMAGE_HEIGHT = ${socialPreview?.imageHeight || 630}; + const TWITTER_CARD_TYPE = '${socialPreview?.twitterCardType || "summary_large_image"}'; + const OG_LOCALE = '${socialPreview?.locale || ""}'; + /* * Step 3.4: SEO configuration (optional) * AI attribution for proper citation in AI-generated content @@ -582,10 +602,11 @@ ${ element.setAttribute('content', pageDescription); } } - // Set custom OG image if specified (Issue #11) - if (ogImage && (element.getAttribute('property') === 'og:image' + // Set custom OG image if specified, fallback to default (Issue #11, #34) + const effectiveOgImage = ogImage || DEFAULT_OG_IMAGE; + if (effectiveOgImage && (element.getAttribute('property') === 'og:image' || element.getAttribute('name') === 'twitter:image')) { - element.setAttribute('content', ogImage); + element.setAttribute('content', effectiveOgImage); } // Set canonical URL for og:url and twitter:url (Issue #9) if (element.getAttribute('property') === 'og:url' @@ -666,6 +687,22 @@ ${ element.append(\`\`, { html: true }); } + // Add enhanced Open Graph and Twitter Card tags (Issue #34) + element.append(\`\`, { html: true }); + element.append(\`\`, { html: true }); + + // Add OG image dimensions if image is set + const effectiveOgImage = this.metadata.ogImage || DEFAULT_OG_IMAGE; + if (effectiveOgImage) { + element.append(\`\`, { html: true }); + element.append(\`\`, { html: true }); + } + + // Add locale if configured + if (OG_LOCALE !== '') { + element.append(\`\`, { html: true }); + } + // Add AI crawler attribution meta tags (Issue #13) if (AI_ATTRIBUTION !== '') { element.append(\`\`, { html: true });