Skip to content

Commit

Permalink
메타데이터를 이용하도록 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
shinwonse committed Nov 20, 2024
1 parent a7a1eed commit e7f599e
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 36 deletions.
53 changes: 53 additions & 0 deletions src/app/api/bookmark/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as cheerio from 'cheerio';
import { NextResponse } from 'next/server';

interface MetaData {
title: string;
description: string;
image: string;
favicon: string;
domain: string;
}

async function getMetadata(url: string): Promise<MetaData> {
try {
const response = await fetch(url);
const html = await response.text();
const $ = cheerio.load(html);
const domain = new URL(url).hostname;

return {
title: $('meta[property="og:title"]').attr('content') || $('title').text() || domain,
description:
$('meta[property="og:description"]').attr('content') ||
$('meta[name="description"]').attr('content') ||
'',
image: $('meta[property="og:image"]').attr('content') || '',
favicon:
$('link[rel="icon"]').attr('href') ||
$('link[rel="shortcut icon"]').attr('href') ||
`https://${domain}/favicon.ico`,
domain,
};
} catch (error) {
return {
title: url,
description: '',
image: '',
favicon: '',
domain: new URL(url).hostname,
};
}
}

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const url = searchParams.get('url');

if (!url) {
return NextResponse.json({ error: 'URL is required' }, { status: 400 });
}

const metadata = await getMetadata(url);
return NextResponse.json(metadata);
}
32 changes: 26 additions & 6 deletions src/styles/post.scss
Original file line number Diff line number Diff line change
Expand Up @@ -135,20 +135,40 @@
@apply my-4 border rounded-lg overflow-hidden transition-all hover:shadow-md;

.bookmark-link {
@apply no-underline block p-4;
@apply no-underline block;
}

.bookmark-content {
@apply flex items-center gap-3;
@apply flex items-start gap-4 p-4;
}

.bookmark-icon {
@apply text-xl;
.bookmark-info {
@apply flex-1 min-w-0;
}

.bookmark-url {
.bookmark-title {
@apply text-lg font-semibold mb-1 truncate;
}

.bookmark-description {
@apply text-sm text-neutral-600 dark:text-neutral-400
truncate hover:text-blue-500 dark:hover:text-blue-400;
line-clamp-2 mb-2;
}

.bookmark-domain {
@apply flex items-center gap-2 text-sm text-neutral-500;
}

.bookmark-favicon {
@apply w-4 h-4;
}

.bookmark-thumbnail {
@apply w-24 h-24 flex-shrink-0;
}

.bookmark-image {
@apply w-full h-full object-cover;
}
}
}
123 changes: 93 additions & 30 deletions src/utils/rehypeBookmark.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import type { Element } from 'hast';
import { visit } from 'unist-util-visit';

async function fetchMetadata(url: string) {
try {
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';
const apiUrl = `${baseUrl}/api/bookmark?url=${encodeURIComponent(url)}`;

const response = await fetch(apiUrl);
console.log('response', response);
return await response.json();
} catch (error) {
console.error('fetchMetadata error', error);
return null;
}
}

export function rehypeBookmark() {
return (tree: any) => {
return async (tree: any) => {
const promises: Promise<void>[] = [];

visit(tree, 'element', (node: Element) => {
// <p><a>bookmark</a></p> 구조 확인
if (
node.tagName === 'p' &&
node.children?.[0]?.type === 'element' &&
Expand All @@ -13,45 +28,93 @@ export function rehypeBookmark() {
node.children[0].children[0].value === 'bookmark'
) {
const link = node.children[0];
const href = link.properties?.href;
const href = link.properties?.href as string;

promises.push(
(async () => {
const metadata = await fetchMetadata(href);
if (!metadata) return;

// 북마크 컴포넌트로 변환
node.tagName = 'div';
node.properties = { className: ['bookmark-card'] };
node.children = [
{
type: 'element',
tagName: 'a',
properties: {
href,
target: '_blank',
rel: 'noopener noreferrer',
className: ['bookmark-link'],
},
children: [
node.tagName = 'div';
node.properties = { className: ['bookmark-card'] };
node.children = [
{
type: 'element',
tagName: 'div',
properties: { className: ['bookmark-content'] },
tagName: 'a',
properties: {
href,
target: '_blank',
rel: 'noopener noreferrer',
className: ['bookmark-link'],
},
children: [
{
type: 'element',
tagName: 'div',
properties: { className: ['bookmark-icon'] },
children: [{ type: 'text', value: '🔖' }],
},
{
type: 'element',
tagName: 'div',
properties: { className: ['bookmark-url'] },
children: [{ type: 'text', value: String(href) }],
properties: { className: ['bookmark-content'] },
children: [
{
type: 'element',
tagName: 'div',
properties: { className: ['bookmark-info'] },
children: [
{
type: 'element',
tagName: 'div',
properties: { className: ['bookmark-title'] },
children: [{ type: 'text', value: metadata.title }],
},
{
type: 'element',
tagName: 'div',
properties: { className: ['bookmark-description'] },
children: [{ type: 'text', value: metadata.description }],
},
{
type: 'element',
tagName: 'div',
properties: { className: ['bookmark-domain'] },
children: [
{
type: 'element',
tagName: 'img',
properties: {
src: metadata.favicon,
alt: '',
className: ['bookmark-favicon'],
},
},
{ type: 'text', value: metadata.domain },
],
},
],
},
metadata.image && {
type: 'element',
tagName: 'div',
properties: { className: ['bookmark-thumbnail'] },
children: [
{
type: 'element',
tagName: 'img',
properties: {
src: metadata.image,
alt: metadata.title,
className: ['bookmark-image'],
},
},
],
},
].filter(Boolean),
},
],
},
],
},
];
];
})(),
);
}
});

await Promise.all(promises);
};
}

0 comments on commit e7f599e

Please sign in to comment.