[dyad] Implemented headline tree structure - wrote 4 file(s), deleted 1 file(s)

This commit is contained in:
[dyad]
2026-01-20 14:09:44 +01:00
parent 9d3e230173
commit 06219f7883
5 changed files with 93 additions and 64 deletions

View File

@@ -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({
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 {

View 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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
)}

View File

@@ -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>