[dyad] Implemented headline tree structure - wrote 4 file(s), deleted 1 file(s)
This commit is contained in:
@@ -7,10 +7,12 @@ interface FaqItem {
|
||||
answer: string;
|
||||
}
|
||||
|
||||
interface HeadlineItem {
|
||||
export interface HeadlineNode {
|
||||
tag: string;
|
||||
text: string;
|
||||
length: number;
|
||||
level: number;
|
||||
children: HeadlineNode[];
|
||||
}
|
||||
|
||||
export async function extractMetaData(url: string) {
|
||||
@@ -79,17 +81,35 @@ export async function extractMetaData(url: string) {
|
||||
}
|
||||
});
|
||||
|
||||
const headlines: HeadlineItem[] = [];
|
||||
const headlines: HeadlineNode[] = [];
|
||||
const path: HeadlineNode[] = [];
|
||||
|
||||
$("h1, h2, h3, h4, h5, h6").each((i, el) => {
|
||||
const tag = $(el).prop("tagName").toLowerCase();
|
||||
const text = $(el).text().trim();
|
||||
if (text) {
|
||||
headlines.push({
|
||||
tag,
|
||||
text,
|
||||
length: text.length,
|
||||
});
|
||||
if (!text) return;
|
||||
|
||||
const level = parseInt(tag.replace("h", ""), 10);
|
||||
|
||||
const node: HeadlineNode = {
|
||||
tag,
|
||||
text,
|
||||
length: text.length,
|
||||
level,
|
||||
children: [],
|
||||
};
|
||||
|
||||
while (path.length > 0 && path[path.length - 1].level >= level) {
|
||||
path.pop();
|
||||
}
|
||||
|
||||
if (path.length === 0) {
|
||||
headlines.push(node);
|
||||
} else {
|
||||
path[path.length - 1].children.push(node);
|
||||
}
|
||||
|
||||
path.push(node);
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
53
src/components/headline-tree.tsx
Normal file
53
src/components/headline-tree.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
"use client";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import type { HeadlineNode } from "@/app/actions";
|
||||
|
||||
interface HeadlineTreeProps {
|
||||
headlines: HeadlineNode[];
|
||||
}
|
||||
|
||||
const HeadlineNodeDisplay = ({
|
||||
node,
|
||||
level,
|
||||
}: {
|
||||
node: HeadlineNode;
|
||||
level: number;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div
|
||||
className="flex items-center gap-3 py-2.5 border-b"
|
||||
style={{ paddingLeft: `${level * 24}px` }}
|
||||
>
|
||||
<Badge
|
||||
variant="secondary"
|
||||
className="uppercase w-12 flex-shrink-0 justify-center font-mono"
|
||||
>
|
||||
{node.tag}
|
||||
</Badge>
|
||||
<p className="font-medium flex-grow text-foreground">{node.text}</p>
|
||||
<p className="text-sm text-muted-foreground flex-shrink-0 w-10 text-right">
|
||||
{node.length}
|
||||
</p>
|
||||
</div>
|
||||
{node.children && node.children.length > 0 && (
|
||||
<div>
|
||||
{node.children.map((child, index) => (
|
||||
<HeadlineNodeDisplay key={index} node={child} level={level + 1} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function HeadlineTree({ headlines }: HeadlineTreeProps) {
|
||||
return (
|
||||
<div className="w-full border rounded-lg overflow-hidden">
|
||||
{headlines.map((headline, index) => (
|
||||
<HeadlineNodeDisplay key={index} node={headline} level={0} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface HeadlinesDisplayProps {
|
||||
headlines: {
|
||||
tag: string;
|
||||
text: string;
|
||||
length: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export function HeadlinesDisplay({ headlines }: HeadlinesDisplayProps) {
|
||||
return (
|
||||
<div className="border rounded-lg max-h-80 overflow-y-auto relative">
|
||||
<Table>
|
||||
<TableHeader className="sticky top-0 bg-muted/95 backdrop-blur-sm">
|
||||
<TableRow>
|
||||
<TableHead className="w-[80px]">Tag</TableHead>
|
||||
<TableHead>Text</TableHead>
|
||||
<TableHead className="text-right w-[100px]">Length</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{headlines.map((headline, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell>
|
||||
<Badge variant="secondary" className="uppercase">
|
||||
{headline.tag}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">{headline.text}</TableCell>
|
||||
<TableCell className="text-right">{headline.length}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,20 +6,20 @@ import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Globe, Edit, Check, Loader2, X, ImageOff } from "lucide-react";
|
||||
import { extractMetaData } from "@/app/actions";
|
||||
import { extractMetaData, type HeadlineNode } from "@/app/actions";
|
||||
import { LengthIndicator } from "./length-indicator";
|
||||
import { CopyButton } from "./copy-button";
|
||||
import { SerpPreview } from "./serp-preview";
|
||||
import { ResultsSkeleton } from "./results-skeleton";
|
||||
import { FaqDisplay } from "./faq-display";
|
||||
import { HeadlinesDisplay } from "./headlines-display";
|
||||
import { HeadlineTree } from "./headline-tree";
|
||||
|
||||
interface MetaData {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string | null;
|
||||
faq?: { question: string; answer: string }[] | null;
|
||||
headlines?: { tag: string; text: string; length: number }[] | null;
|
||||
headlines?: HeadlineNode[] | null;
|
||||
}
|
||||
|
||||
export function MetaForm() {
|
||||
@@ -283,11 +283,11 @@ export function MetaForm() {
|
||||
<Card className="w-full shadow-lg rounded-lg">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xl text-card-foreground">
|
||||
Headlines
|
||||
Headlines Structure
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<HeadlinesDisplay headlines={metaData.headlines} />
|
||||
<HeadlineTree headlines={metaData.headlines} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
@@ -61,13 +61,17 @@ export function ResultsSkeleton() {
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<Skeleton className="h-6 w-32" />
|
||||
<Skeleton className="h-6 w-40" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="border rounded-lg p-2 space-y-2">
|
||||
<Skeleton className="h-8 w-full" />
|
||||
<Skeleton className="h-8 w-full" />
|
||||
<Skeleton className="h-8 w-full" />
|
||||
<div className="ml-6">
|
||||
<Skeleton className="h-8 w-[90%]" />
|
||||
</div>
|
||||
<div className="ml-6">
|
||||
<Skeleton className="h-8 w-[90%]" />
|
||||
</div>
|
||||
<Skeleton className="h-8 w-full" />
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user