diff --git a/src/app/actions.ts b/src/app/actions.ts
index f16d9da..80f068d 100644
--- a/src/app/actions.ts
+++ b/src/app/actions.ts
@@ -21,6 +21,13 @@ export interface ImageAltData {
size: number | null;
}
+export interface LinkData {
+ href: string;
+ text: string;
+ type: "internal" | "external" | "anchor" | "other";
+ rel: string;
+}
+
export async function extractMetaData(url: string, keyword?: string) {
if (!url) {
return { error: "URL is required." };
@@ -175,7 +182,6 @@ export async function extractMetaData(url: string, keyword?: string) {
const imageSizePromises = imageSrcs.map(async (img) => {
try {
- // Use a HEAD request for efficiency
const res = await fetch(img.src, { method: "HEAD" });
if (res.ok) {
const contentLength = res.headers.get("content-length");
@@ -192,6 +198,33 @@ export async function extractMetaData(url: string, keyword?: string) {
const imageAltData: ImageAltData[] = await Promise.all(imageSizePromises);
+ const links: LinkData[] = [];
+ const pageUrl = new URL(formattedUrl);
+
+ $("a").each((i, el) => {
+ const href = $(el).attr("href");
+ if (!href) return;
+
+ const text = $(el).text().trim();
+ const rel = $(el).attr("rel") || "";
+ let type: LinkData["type"] = "external";
+ let absoluteUrl = href;
+
+ try {
+ const linkUrl = new URL(href, formattedUrl);
+ absoluteUrl = linkUrl.href;
+ if (linkUrl.hostname === pageUrl.hostname) {
+ type = "internal";
+ }
+ } catch (e) {
+ if (href.startsWith("#")) type = "anchor";
+ else if (href.startsWith("mailto:") || href.startsWith("tel:"))
+ type = "other";
+ }
+
+ links.push({ href: absoluteUrl, text, type, rel });
+ });
+
return {
data: {
title,
@@ -207,6 +240,7 @@ export async function extractMetaData(url: string, keyword?: string) {
keyword: trimmedKeyword || null,
keywordCount,
images: imageAltData.length > 0 ? imageAltData : null,
+ links: links.length > 0 ? links : null,
},
};
} catch (error) {
diff --git a/src/components/links-display.tsx b/src/components/links-display.tsx
new file mode 100644
index 0000000..67a6356
--- /dev/null
+++ b/src/components/links-display.tsx
@@ -0,0 +1,134 @@
+"use client";
+
+import { useState, useMemo } from "react";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { ExternalLink, Link as LinkIcon } from "lucide-react";
+import type { LinkData } from "@/app/actions";
+
+interface LinksDisplayProps {
+ links: LinkData[];
+}
+
+const StatCard = ({ title, value }: { title: string; value: number }) => (
+