[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 canonical = $('link[rel="canonical"]').attr("href") || null;
|
||||||
const robots = $('meta[name="robots"]').attr("content") || 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 faqData: FaqItem[] = [];
|
||||||
const schemaData: any[] = [];
|
const schemaData: any[] = [];
|
||||||
$('script[type="application/ld+json"]').each((i, el) => {
|
$('script[type="application/ld+json"]').each((i, el) => {
|
||||||
@@ -177,6 +199,8 @@ export async function extractMetaData(url: string, keyword?: string) {
|
|||||||
image,
|
image,
|
||||||
canonical,
|
canonical,
|
||||||
robots,
|
robots,
|
||||||
|
openGraph,
|
||||||
|
twitter,
|
||||||
faq: faqData.length > 0 ? faqData : null,
|
faq: faqData.length > 0 ? faqData : null,
|
||||||
schema: schemaData.length > 0 ? schemaData : null,
|
schema: schemaData.length > 0 ? schemaData : null,
|
||||||
headlines: headlines.length > 0 ? headlines : null,
|
headlines: headlines.length > 0 ? headlines : null,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { getLengthIndicatorColor, type IndicatorColor } from "@/lib/analysis";
|
|||||||
import { SchemaDisplay } from "./schema-display";
|
import { SchemaDisplay } from "./schema-display";
|
||||||
import { MetaFormInputs } from "./meta-form-inputs";
|
import { MetaFormInputs } from "./meta-form-inputs";
|
||||||
import { AnalysisTab } from "./analysis-tab";
|
import { AnalysisTab } from "./analysis-tab";
|
||||||
|
import { SocialTab } from "./social-tab";
|
||||||
import type { MetaData } from "@/lib/types";
|
import type { MetaData } from "@/lib/types";
|
||||||
|
|
||||||
export function MetaForm() {
|
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
|
// FAQ Tab
|
||||||
let faqColor: IndicatorColor = "gray";
|
let faqColor: IndicatorColor = "gray";
|
||||||
if (metaData.faq && metaData.faq.length > 0) {
|
if (metaData.faq && metaData.faq.length > 0) {
|
||||||
@@ -106,6 +124,7 @@ export function MetaForm() {
|
|||||||
analysis: analysisColor,
|
analysis: analysisColor,
|
||||||
headlines: headlinesColor,
|
headlines: headlinesColor,
|
||||||
images: imagesColor,
|
images: imagesColor,
|
||||||
|
social: socialColor,
|
||||||
faq: faqColor,
|
faq: faqColor,
|
||||||
schema: schemaColor,
|
schema: schemaColor,
|
||||||
};
|
};
|
||||||
@@ -181,6 +200,12 @@ export function MetaForm() {
|
|||||||
Images
|
Images
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
)}
|
)}
|
||||||
|
{metaData.openGraph && metaData.twitter && (
|
||||||
|
<TabsTrigger value="social">
|
||||||
|
{tabColors && <TabIndicator color={tabColors.social} />}
|
||||||
|
Social
|
||||||
|
</TabsTrigger>
|
||||||
|
)}
|
||||||
{metaData.faq && metaData.faq.length > 0 && (
|
{metaData.faq && metaData.faq.length > 0 && (
|
||||||
<TabsTrigger value="faq">
|
<TabsTrigger value="faq">
|
||||||
{tabColors && <TabIndicator color={tabColors.faq} />}
|
{tabColors && <TabIndicator color={tabColors.faq} />}
|
||||||
@@ -238,6 +263,15 @@ export function MetaForm() {
|
|||||||
</TabsContent>
|
</TabsContent>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{metaData.openGraph && metaData.twitter && (
|
||||||
|
<TabsContent value="social">
|
||||||
|
<SocialTab
|
||||||
|
openGraph={metaData.openGraph}
|
||||||
|
twitter={metaData.twitter}
|
||||||
|
/>
|
||||||
|
</TabsContent>
|
||||||
|
)}
|
||||||
|
|
||||||
{metaData.faq && metaData.faq.length > 0 && (
|
{metaData.faq && metaData.faq.length > 0 && (
|
||||||
<TabsContent value="faq">
|
<TabsContent value="faq">
|
||||||
<Card className="w-full shadow-lg rounded-lg">
|
<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";
|
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 {
|
export interface MetaData {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
@@ -12,4 +30,6 @@ export interface MetaData {
|
|||||||
images?: ImageAltData[] | null;
|
images?: ImageAltData[] | null;
|
||||||
canonical?: string | null;
|
canonical?: string | null;
|
||||||
robots?: string | null;
|
robots?: string | null;
|
||||||
|
openGraph?: OpenGraphData;
|
||||||
|
twitter?: TwitterData;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user