[dyad] Added social media tag analysis - wrote 5 file(s)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{metaData.openGraph && metaData.twitter && (
|
||||
<TabsTrigger value="social">
|
||||
{tabColors && <TabIndicator color={tabColors.social} />}
|
||||
Social
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{metaData.faq && metaData.faq.length > 0 && (
|
||||
<TabsTrigger value="faq">
|
||||
{tabColors && <TabIndicator color={tabColors.faq} />}
|
||||
@@ -238,6 +263,15 @@ export function MetaForm() {
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
{metaData.openGraph && metaData.twitter && (
|
||||
<TabsContent value="social">
|
||||
<SocialTab
|
||||
openGraph={metaData.openGraph}
|
||||
twitter={metaData.twitter}
|
||||
/>
|
||||
</TabsContent>
|
||||
)}
|
||||
|
||||
{metaData.faq && metaData.faq.length > 0 && (
|
||||
<TabsContent value="faq">
|
||||
<Card className="w-full shadow-lg rounded-lg">
|
||||
|
||||
53
src/components/social-preview.tsx
Normal file
53
src/components/social-preview.tsx
Normal file
@@ -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 (
|
||||
<div className="border rounded-lg overflow-hidden bg-card shadow-sm max-w-lg mx-auto">
|
||||
<div className="aspect-video bg-muted flex items-center justify-center">
|
||||
{image && !imageError ? (
|
||||
<img
|
||||
src={image}
|
||||
alt={`${platform} preview`}
|
||||
className="w-full h-full object-cover"
|
||||
onError={() => setImageError(true)}
|
||||
/>
|
||||
) : (
|
||||
<ImageOff className="h-10 w-10 text-muted-foreground" />
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4 border-t bg-background">
|
||||
<p className="text-sm text-muted-foreground uppercase flex items-center gap-1.5">
|
||||
<LinkIcon className="h-3 w-3" />
|
||||
{displayUrl}
|
||||
</p>
|
||||
<h3 className="font-semibold text-card-foreground mt-1 truncate">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1 line-clamp-2">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
63
src/components/social-tab.tsx
Normal file
63
src/components/social-tab.tsx
Normal file
@@ -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 (
|
||||
<div className="flex justify-between items-center py-2 border-b text-sm">
|
||||
<span className="text-muted-foreground">{label}</span>
|
||||
<span className="font-medium text-right">{value}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function SocialTab({ openGraph, twitter }: SocialTabProps) {
|
||||
return (
|
||||
<Card className="w-full shadow-lg rounded-lg">
|
||||
<CardContent className="p-6 grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-xl font-bold text-card-foreground">
|
||||
Open Graph
|
||||
</h3>
|
||||
<SocialPreview
|
||||
platform="Open Graph"
|
||||
title={openGraph.title}
|
||||
description={openGraph.description}
|
||||
image={openGraph.image}
|
||||
url={openGraph.url}
|
||||
/>
|
||||
<div className="space-y-1">
|
||||
<DataRow label="og:type" value={openGraph.type} />
|
||||
<DataRow label="og:site_name" value={openGraph.siteName} />
|
||||
<DataRow label="og:url" value={openGraph.url} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
<h3 className="text-xl font-bold text-card-foreground">
|
||||
Twitter Card
|
||||
</h3>
|
||||
<SocialPreview
|
||||
platform="Twitter"
|
||||
title={twitter.title}
|
||||
description={twitter.description}
|
||||
image={twitter.image}
|
||||
url={openGraph.url}
|
||||
/>
|
||||
<div className="space-y-1">
|
||||
<DataRow label="twitter:card" value={twitter.card} />
|
||||
<DataRow label="twitter:site" value={twitter.site} />
|
||||
<DataRow label="twitter:creator" value={twitter.creator} />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user