Skip to content
This repository was archived by the owner on May 21, 2026. It is now read-only.

Commit 9863f2f

Browse files
committed
fix: favicon
1 parent bd2f18c commit 9863f2f

1 file changed

Lines changed: 138 additions & 0 deletions

File tree

lib/routes/caicai/blog.ts

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import { load } from 'cheerio';
2+
import type { Context } from 'hono';
3+
4+
import type { Route } from '@/types';
5+
import cache from '@/utils/cache';
6+
import ofetch from '@/utils/ofetch';
7+
import { parseDate } from '@/utils/parse-date';
8+
9+
export const route: Route = {
10+
path: '/blog/:lang?',
11+
categories: ['blog'],
12+
example: '/caicai/blog',
13+
parameters: {
14+
lang: {
15+
description: 'Language',
16+
options: [
17+
{ value: 'en', label: 'English' },
18+
{ value: 'zh', label: '中文' },
19+
],
20+
default: 'en',
21+
},
22+
},
23+
radar: [
24+
{
25+
source: ['www.caicai.me/blogs'],
26+
},
27+
{
28+
source: ['www.caicai.me/zh/blogs'],
29+
target: '/blog/zh',
30+
},
31+
],
32+
name: 'Blog',
33+
maintainers: ['TonyRL'],
34+
handler,
35+
url: 'www.caicai.me/blogs',
36+
};
37+
38+
const baseUrl = 'https://www.caicai.me';
39+
40+
const renderAnnotation = (rich) =>
41+
rich
42+
.map((t) => {
43+
let s = t.content;
44+
const a = t.annotations;
45+
if (a.code) {
46+
s = `<code>${s}</code>`;
47+
}
48+
if (a.bold) {
49+
s = `<strong>${s}</strong>`;
50+
}
51+
if (a.italic) {
52+
s = `<em>${s}</em>`;
53+
}
54+
if (a.strikethrough) {
55+
s = `<s>${s}</s>`;
56+
}
57+
if (a.underline) {
58+
s = `<u>${s}</u>`;
59+
}
60+
if (t.href) {
61+
s = `<a href="${t.href}">${s}</a>`;
62+
}
63+
return s;
64+
})
65+
.join('');
66+
67+
const renderBlocks = (blocks) =>
68+
blocks
69+
.map((b, i) => {
70+
switch (b.type) {
71+
case 'paragraph':
72+
return b.text.length ? `<p>${renderAnnotation(b.text)}</p>` : '';
73+
case 'heading_2':
74+
return b.text.length ? `<h2>${renderAnnotation(b.text)}</h2>` : '';
75+
case 'heading_3':
76+
return b.text.length ? `<h3>${renderAnnotation(b.text)}</h3>` : '';
77+
case 'bulleted_list_item': {
78+
if (!b.text.length) {
79+
return '';
80+
}
81+
const open = blocks[i - 1]?.type === 'bulleted_list_item' ? '' : '<ul>';
82+
const close = blocks[i + 1]?.type === 'bulleted_list_item' ? '' : '</ul>';
83+
return `${open}<li>${renderAnnotation(b.text)}</li>${close}`;
84+
}
85+
case 'image':
86+
return `<img src="${new URL(b.url, baseUrl).href}">`;
87+
case 'divider':
88+
return '<hr>';
89+
default:
90+
return '';
91+
}
92+
})
93+
.join('');
94+
95+
async function handler(ctx: Context) {
96+
const { lang = 'en' } = ctx.req.param();
97+
const prefix = lang === 'en' ? '' : `/${lang}`;
98+
const link = `${baseUrl}${prefix}/blogs`;
99+
100+
const response = await ofetch(link);
101+
const $ = load(response);
102+
103+
const nextData = JSON.parse($('script#__NEXT_DATA__').text());
104+
105+
const list = nextData.props.pageProps.posts.map((post) => ({
106+
title: post.frontMatter.title,
107+
link: `${baseUrl}${prefix}/blogs/${post.slug}`,
108+
description: post.frontMatter.excerpt,
109+
pubDate: parseDate(post.frontMatter.dateIso),
110+
category: [post.frontMatter.group],
111+
image: new URL(post.frontMatter.cover_image, baseUrl).href,
112+
}));
113+
114+
const items = await Promise.all(
115+
list.map((item) =>
116+
cache.tryGet(item.link, async () => {
117+
const response = await ofetch(item.link);
118+
const $ = load(response);
119+
120+
const { pageProps } = JSON.parse($('script#__NEXT_DATA__').text()).props;
121+
const blocks = lang === 'en' ? pageProps.blocks : pageProps.chineseBlocks;
122+
123+
item.description = renderBlocks(blocks);
124+
125+
return item;
126+
})
127+
)
128+
);
129+
130+
return {
131+
title: $('head title').text(),
132+
description: $('head meta[name="description"]').attr('content'),
133+
link,
134+
language: lang === 'en' ? ('en' as const) : ('zh-CN' as const),
135+
image: `${baseUrl}/favicon.ico`,
136+
item: items,
137+
};
138+
}

0 commit comments

Comments
 (0)