Skip to content

Commit

Permalink
feat: add unfurl for bookmark
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-guoba committed Feb 23, 2024
1 parent d15164d commit 3ef8e18
Show file tree
Hide file tree
Showing 10 changed files with 369 additions and 12 deletions.
3 changes: 1 addition & 2 deletions app/(blog)/article/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// import Head from "next/head";
import Link from "next/link";
import { Fragment } from "react";
// import Text from "@/app/ui/text";
import { Metadata, ResolvingMetadata } from "next";

import { renderBlock } from "@/app/notion/render";
Expand All @@ -10,7 +9,7 @@ import {
queryPageBySlug,
retrieveBlockChildren,
retrievePage,
} from "@/app/api/notion";
} from "@/app/notion/api";
import Shell from "@/components/shells/shell";
import React from "react";
import { cn } from "@/lib/utils";
Expand Down
3 changes: 1 addition & 2 deletions app/(lobby)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// import Image from 'next/image'
import Link from 'next/link';

import { QueryDatabase } from '@/app/api/notion';
import { QueryDatabase } from '@/app/notion/api';
import '@/app/styles/globals.css'
import Text from '../notion/text';
import Shell from '@/components/shells/shell';
import { PageHeader, PageHeaderHeading, PageHeaderDescription } from '@/components/page-header';
import { Separator } from '@/components/ui/separator';
Expand Down
64 changes: 64 additions & 0 deletions app/api/unfurl/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { unfurl } from "unfurl.js";
import { NextRequest, NextResponse } from "next/server";

type ErrorResponse = { error: string };
type SuccessResponse = {
title?: string | null;
description?: string | null;
favicon?: string | null;
imageSrc?: string | null;
};

const CACHE_RESULT_SECONDS = 60 * 60 * 24; // 1 day

export async function GET(
req: NextRequest,
res: NextResponse<SuccessResponse | ErrorResponse>
) {
const searchParams = req.nextUrl.searchParams;
const url = searchParams.get("url");

console.log('query url', url);

if (!url || typeof url !== "string") {
return NextResponse.json(
{
error: "Please enter title",
},
{
status: 400,
}
);
}

return unfurl(url)
.then((unfurlResponse) => {

console.log(unfurlResponse)

const response = {
title: unfurlResponse.title ?? null,
description: unfurlResponse.description ?? null,
favicon: unfurlResponse.favicon ?? null,
imageSrc: unfurlResponse.open_graph?.images?.[0]?.url ?? null,
};

const res = NextResponse.json(response);
res.headers.set(
"Cache-Control",
`public, max-age=${CACHE_RESULT_SECONDS}`
);
return res;
})
.catch((error) => {
console.error(error);
return NextResponse.json(
{
error: "Internal server error",
},
{
status: 500,
}
);
});
}
123 changes: 121 additions & 2 deletions app/notion/_components/bookmark.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
"use client";

import { cn } from "@/lib/utils";
import RichText from "../text";
import { Icons } from "@/components/icons";
import { UrlData, useUnfurlUrl } from "@/lib/unfurl";
import { Skeleton } from "@/components/ui/skeleton";

import Image from "next/image";
import Link from "next/link";

import { PlaceholderImage } from "@/components/placeholder-image";

import { AspectRatio } from "@/components/ui/aspect-ratio";
import {
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";

interface BookmarkBlockProps {
block: any;
Expand All @@ -17,8 +34,11 @@ export function BookmarkRender({ block, className }: BookmarkBlockProps) {
}

return (
<div className={cn(className, "inline-flex w-full items-center flex-wrap")} title="bookmark">
<Icons.bookmark className="h-6 w-6 p-[0.8] text-gray-600"/>
<div
className={cn(className, "inline-flex w-full items-center flex-wrap")}
title="bookmark"
>
<Icons.bookmark className="h-6 w-6 p-[0.8] text-gray-600" />
<a
id={id}
href={url}
Expand All @@ -37,3 +57,102 @@ export function BookmarkRender({ block, className }: BookmarkBlockProps) {
</div>
);
}

function LoadingSkeleton() {
return (
<div className="flex flex-col space-y-3">
<Skeleton className="h-[125px] w-[250px] rounded-xl" />
<div className="space-y-2">
<Skeleton className="h-4 w-[250px]" />
<Skeleton className="h-4 w-[200px]" />
</div>
</div>
);
}

function UnfurledBookmarkPreview({
block,
data,
className,
}: {
block: any;
data: UrlData | null;
className?: string | undefined;
}) {
const {
id,
bookmark: { caption, url },
} = block;

const image = data?.imageSrc;
const title = data?.title;
const desc = data?.description;
const icon = data?.favicon;

return (
<div key={id} className={className}>
<Link href={url}>
<div className="border border-gray-200 flex overflow-hidden w-full max-w-full">
{/* <span className="sr-only">{url}</span> */}

<div className="flex-[4_1_180px] space-y-2 p-4">
<CardHeader className="p-0">
<CardContent className="line-clamp-1 font-normal p-0 text-sm">
{title}
</CardContent>
</CardHeader>
<CardDescription className="text-xs">{desc}</CardDescription>
<CardContent className="text-xs p-0">
{url}
</CardContent>
</div>
<div className="flex-[1_1_180px] relative hidden md:flex max-h-32">
{image ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={image}
alt={url}
className="object-cover"
// priority={i <= 1}
/>
) : (
<PlaceholderImage asChild />
)}
</div>
</div>
</Link>

{caption && caption.length > 0 ? (
<div className="px-1.5 font-light">
<RichText title={caption} />
</div>
) : null}
</div>
);
}

export function BookmarkPreviewRender({
block,
className,
}: BookmarkBlockProps) {
const {
id,
bookmark: { caption, url },
} = block;
const { status, data } = useUnfurlUrl(url);

console.log(status, data);
if (status == "error") {
return <BookmarkRender block={block} className={className} />;
}
if (status == "success") {
return (
<UnfurledBookmarkPreview
block={block}
className={className}
data={data}
/>
);
}
return <LoadingSkeleton />;
}
File renamed without changes.
4 changes: 2 additions & 2 deletions app/notion/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import React from 'react';
import { CodeRender } from './_components/code';
import { CalloutRender } from './_components/callout';
import { ImageRender } from './_components/image';
import { BookmarkRender } from './_components/bookmark';
import { BookmarkPreviewRender, BookmarkRender } from './_components/bookmark';
import { FileRender } from './_components/file';
import { ColumnListRender, ColumnRender } from './_components/column';
import { QuoteRender } from './_components/quote';
Expand Down Expand Up @@ -138,7 +138,7 @@ export function renderBlock(block: any, level: number = 1) {
return <PDFRender block={block} className='mt-1.5'></PDFRender>;

case 'bookmark':
return <BookmarkRender block={block} className='mt-1.5'></BookmarkRender>;
return <BookmarkPreviewRender block={block} className='mt-1.5'></BookmarkPreviewRender>;

case 'link_preview':
return <LinkPreviewRender block={block} className='mt-1.5'></LinkPreviewRender>;
Expand Down
40 changes: 40 additions & 0 deletions lib/unfurl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useEffect, useState } from "react";

type RequestStatus = "iddle" | "loading" | "success" | "error";

export type UrlData = {
title: string | null;
description: string | null;
favicon: string | null;
imageSrc: string | null;
};

export function useUnfurlUrl(url: string) {
const [status, setStatus] = useState<RequestStatus>("iddle");
const [data, setData] = useState<null | UrlData>(null);
useEffect(() => {
if (url) {
setStatus("loading");
const encoded = encodeURIComponent(url);
fetch(`/api/unfurl?url=${encoded}`)
.then(async (res) => {
if (res.ok) {
const data = await res.json();
setData(data);
setStatus("success");
} else {
setStatus("error");
}
})
.catch((error) => {
console.error(error);

setStatus("error");
});
} else {
setStatus("error");
}
}, [url]);

return { status, data };
}
8 changes: 8 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ const nextConfig = {
{
protocol: "https",
hostname: "*.s3.us-west-2.amazonaws.com",
},
{
protocol: "https",
hostname: "radix-ui.com",
},
{
protocol: "https",
hostname: "*.medium.com",
}
],
// unoptimized: true,
Expand Down
Loading

0 comments on commit 3ef8e18

Please sign in to comment.