diff --git a/src/app/actions.ts b/src/app/actions.ts index 42f7050..f16d9da 100644 --- a/src/app/actions.ts +++ b/src/app/actions.ts @@ -58,6 +58,28 @@ export async function extractMetaData(url: string, keyword?: string) { const canonical = $('link[rel="canonical"]').attr("href") || null; const robots = $('meta[name="robots"]').attr("content") || null; + // Social Tags + const openGraph = { + title: $('meta[property="og:title"]').attr("content") || title, + description: + $('meta[property="og:description"]').attr("content") || description, + image: $('meta[property="og:image"]').attr("content") || image, + url: $('meta[property="og:url"]').attr("content") || null, + siteName: $('meta[property="og:site_name"]').attr("content") || null, + type: $('meta[property="og:type"]').attr("content") || null, + }; + + const twitter = { + card: $('meta[name="twitter:card"]').attr("content") || null, + title: $('meta[name="twitter:title"]').attr("content") || openGraph.title, + description: + $('meta[name="twitter:description"]').attr("content") || + openGraph.description, + image: $('meta[name="twitter:image"]').attr("content") || openGraph.image, + site: $('meta[name="twitter:site"]').attr("content") || null, + creator: $('meta[name="twitter:creator"]').attr("content") || null, + }; + const faqData: FaqItem[] = []; const schemaData: any[] = []; $('script[type="application/ld+json"]').each((i, el) => { @@ -177,6 +199,8 @@ export async function extractMetaData(url: string, keyword?: string) { image, canonical, robots, + openGraph, + twitter, faq: faqData.length > 0 ? faqData : null, schema: schemaData.length > 0 ? schemaData : null, headlines: headlines.length > 0 ? headlines : null, diff --git a/src/components/meta-form.tsx b/src/components/meta-form.tsx index d6b5787..4aff96b 100644 --- a/src/components/meta-form.tsx +++ b/src/components/meta-form.tsx @@ -13,6 +13,7 @@ import { getLengthIndicatorColor, type IndicatorColor } from "@/lib/analysis"; import { SchemaDisplay } from "./schema-display"; import { MetaFormInputs } from "./meta-form-inputs"; import { AnalysisTab } from "./analysis-tab"; +import { SocialTab } from "./social-tab"; import type { MetaData } from "@/lib/types"; export function MetaForm() { @@ -90,6 +91,23 @@ export function MetaForm() { } } + // Social Tab + let socialColor: IndicatorColor = "gray"; + if (metaData.openGraph && metaData.twitter) { + const og = metaData.openGraph; + const tw = metaData.twitter; + const hasOgBasics = og.title && og.description && og.image; + const hasTwBasics = tw.card && tw.title && tw.description && tw.image; + + if (hasOgBasics && hasTwBasics) { + socialColor = "green"; + } else if (hasOgBasics || (tw.card && tw.title)) { + socialColor = "yellow"; + } else { + socialColor = "red"; + } + } + // FAQ Tab let faqColor: IndicatorColor = "gray"; if (metaData.faq && metaData.faq.length > 0) { @@ -106,6 +124,7 @@ export function MetaForm() { analysis: analysisColor, headlines: headlinesColor, images: imagesColor, + social: socialColor, faq: faqColor, schema: schemaColor, }; @@ -181,6 +200,12 @@ export function MetaForm() { Images )} + {metaData.openGraph && metaData.twitter && ( + + {tabColors && } + Social + + )} {metaData.faq && metaData.faq.length > 0 && ( {tabColors && } @@ -238,6 +263,15 @@ export function MetaForm() { )} + {metaData.openGraph && metaData.twitter && ( + + + + )} + {metaData.faq && metaData.faq.length > 0 && ( diff --git a/src/components/social-preview.tsx b/src/components/social-preview.tsx new file mode 100644 index 0000000..85a99a3 --- /dev/null +++ b/src/components/social-preview.tsx @@ -0,0 +1,53 @@ +"use client"; + +import * as React from "react"; +import { ImageOff, Link as LinkIcon } from "lucide-react"; + +interface SocialPreviewProps { + platform: "Open Graph" | "Twitter"; + title: string; + description: string; + image: string | null; + url: string | null; +} + +export function SocialPreview({ + platform, + title, + description, + image, + url, +}: SocialPreviewProps) { + const [imageError, setImageError] = React.useState(false); + + const displayUrl = url ? new URL(url).hostname : "example.com"; + + return ( + + + {image && !imageError ? ( + setImageError(true)} + /> + ) : ( + + )} + + + + + {displayUrl} + + + {title} + + + {description} + + + + ); +} \ No newline at end of file diff --git a/src/components/social-tab.tsx b/src/components/social-tab.tsx new file mode 100644 index 0000000..23bca49 --- /dev/null +++ b/src/components/social-tab.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { Card, CardContent } from "@/components/ui/card"; +import { SocialPreview } from "./social-preview"; +import type { OpenGraphData, TwitterData } from "@/lib/types"; + +interface SocialTabProps { + openGraph: OpenGraphData; + twitter: TwitterData; +} + +const DataRow = ({ label, value }: { label: string; value: string | null }) => { + if (!value) return null; + return ( + + {label} + {value} + + ); +}; + +export function SocialTab({ openGraph, twitter }: SocialTabProps) { + return ( + + + + + Open Graph + + + + + + + + + + + Twitter Card + + + + + + + + + + + ); +} \ No newline at end of file diff --git a/src/lib/types.ts b/src/lib/types.ts index 5e3ad0b..66388b0 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,5 +1,23 @@ import type { HeadlineNode, ImageAltData } from "@/app/actions"; +export interface OpenGraphData { + title: string; + description: string; + image: string | null; + url: string | null; + siteName: string | null; + type: string | null; +} + +export interface TwitterData { + card: string | null; + title: string; + description: string; + image: string | null; + site: string | null; + creator: string | null; +} + export interface MetaData { title: string; description: string; @@ -12,4 +30,6 @@ export interface MetaData { images?: ImageAltData[] | null; canonical?: string | null; robots?: string | null; + openGraph?: OpenGraphData; + twitter?: TwitterData; } \ No newline at end of file
+ + {displayUrl} +
+ {description} +