feat: add SharePanel component for social sharing with QR code support
- Implemented SharePanel component in `SharePanel.astro` for sharing content on social media platforms. - Integrated QR code generation for WeChat sharing using the `qrcode` library. - Added localization support for English and Chinese languages. - Created utility functions in `seo.ts` for building article summaries and FAQs. - Introduced API routes for serving IndexNow key and generating full LLM catalog and summaries. - Enhanced SEO capabilities with structured data for articles and pages.
This commit is contained in:
@@ -1,18 +1,23 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import DiscoveryBrief from '../../components/seo/DiscoveryBrief.astro';
|
||||
import PageViewTracker from '../../components/seo/PageViewTracker.astro';
|
||||
import SharePanel from '../../components/seo/SharePanel.astro';
|
||||
import TerminalWindow from '../../components/ui/TerminalWindow.astro';
|
||||
import CommandPrompt from '../../components/ui/CommandPrompt.astro';
|
||||
import StatsList from '../../components/StatsList.astro';
|
||||
import TechStackList from '../../components/TechStackList.astro';
|
||||
import { api, DEFAULT_SITE_SETTINGS } from '../../lib/api/client';
|
||||
import { getI18n } from '../../lib/i18n';
|
||||
import { buildDiscoveryHighlights, buildFaqJsonLd, buildPageFaqs } from '../../lib/seo';
|
||||
|
||||
export const prerender = false;
|
||||
|
||||
let siteSettings = DEFAULT_SITE_SETTINGS;
|
||||
let systemStats = [];
|
||||
let techStack = [];
|
||||
const { t } = getI18n(Astro);
|
||||
const { locale, t } = getI18n(Astro);
|
||||
const isEnglish = locale.startsWith('en');
|
||||
|
||||
try {
|
||||
const [settings, posts, tags, friendLinks] = await Promise.all([
|
||||
@@ -42,13 +47,95 @@ try {
|
||||
}
|
||||
|
||||
const ownerInitial = siteSettings.ownerName.charAt(0) || 'T';
|
||||
const siteBaseUrl = (siteSettings.siteUrl || new URL(Astro.request.url).origin).replace(/\/$/, '');
|
||||
const aboutCanonicalUrl = new URL('/about', siteBaseUrl).toString();
|
||||
const sharePanelCopy = isEnglish
|
||||
? {
|
||||
badge: 'profile source',
|
||||
title: 'Share the profile page',
|
||||
description:
|
||||
'Use this page as the canonical identity and capability profile so social sharing and AI search can cite one stable source.',
|
||||
}
|
||||
: {
|
||||
badge: '身份主页',
|
||||
title: '分享这张身份名片页',
|
||||
description: '把这页当成统一的身份与能力来源分发出去,方便社交回流,也方便 AI 搜索引用到同一个规范地址。',
|
||||
};
|
||||
const aboutHighlights = buildDiscoveryHighlights([
|
||||
siteSettings.ownerTitle,
|
||||
siteSettings.ownerBio,
|
||||
siteSettings.location || '',
|
||||
siteSettings.techStack.slice(0, 4).join(' / '),
|
||||
]);
|
||||
const aboutFaqs = buildPageFaqs({
|
||||
locale,
|
||||
pageTitle: t('about.pageTitle'),
|
||||
summary: siteSettings.ownerBio || siteSettings.siteDescription,
|
||||
primaryLabel: t('about.pageTitle'),
|
||||
primaryUrl: aboutCanonicalUrl,
|
||||
relatedLinks: [
|
||||
{ label: t('nav.articles'), url: `${siteBaseUrl}/articles` },
|
||||
{ label: t('nav.timeline'), url: `${siteBaseUrl}/timeline` },
|
||||
{ label: t('nav.ask'), url: `${siteBaseUrl}/ask` },
|
||||
],
|
||||
signals: aboutHighlights,
|
||||
});
|
||||
const aboutFaqJsonLd = buildFaqJsonLd(aboutFaqs);
|
||||
const aboutJsonLd = [
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'AboutPage',
|
||||
name: `${siteSettings.ownerName} / ${siteSettings.siteName}`,
|
||||
description: siteSettings.siteDescription,
|
||||
url: aboutCanonicalUrl,
|
||||
inLanguage: locale,
|
||||
},
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'ProfilePage',
|
||||
name: siteSettings.ownerName,
|
||||
url: aboutCanonicalUrl,
|
||||
mainEntity: {
|
||||
'@type': 'Person',
|
||||
name: siteSettings.ownerName,
|
||||
jobTitle: siteSettings.ownerTitle,
|
||||
description: siteSettings.ownerBio,
|
||||
image: siteSettings.ownerAvatarUrl || undefined,
|
||||
sameAs: [
|
||||
siteSettings.social.github,
|
||||
siteSettings.social.twitter,
|
||||
].filter(Boolean),
|
||||
},
|
||||
},
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: [
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 1,
|
||||
name: siteSettings.siteName,
|
||||
item: siteBaseUrl,
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 2,
|
||||
name: t('about.pageTitle'),
|
||||
item: aboutCanonicalUrl,
|
||||
},
|
||||
],
|
||||
},
|
||||
aboutFaqJsonLd,
|
||||
];
|
||||
---
|
||||
|
||||
<BaseLayout
|
||||
title={`${t('about.pageTitle')} - ${siteSettings.siteShortName}`}
|
||||
description={siteSettings.siteDescription}
|
||||
siteSettings={siteSettings}
|
||||
jsonLd={aboutJsonLd.filter(Boolean)}
|
||||
>
|
||||
<PageViewTracker pageType="about" entityId="about" />
|
||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<TerminalWindow title="~/about" class="w-full">
|
||||
<div class="mb-6 px-4">
|
||||
@@ -77,6 +164,31 @@ const ownerInitial = siteSettings.ownerName.charAt(0) || 'T';
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ml-4 mt-4">
|
||||
<SharePanel
|
||||
shareTitle={`${siteSettings.ownerName} / ${t('about.pageTitle')}`}
|
||||
summary={siteSettings.ownerBio || siteSettings.siteDescription}
|
||||
canonicalUrl={aboutCanonicalUrl}
|
||||
badge={sharePanelCopy.badge}
|
||||
kicker="geo / profile"
|
||||
title={sharePanelCopy.title}
|
||||
description={sharePanelCopy.description}
|
||||
stats={systemStats.slice(0, 4)}
|
||||
wechatShareQrEnabled={siteSettings.seo.wechatShareQrEnabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="ml-4 mt-4">
|
||||
<DiscoveryBrief
|
||||
badge={isEnglish ? 'profile brief' : '身份摘要'}
|
||||
kicker="geo / profile"
|
||||
title={isEnglish ? 'AI-readable profile brief' : '给 AI 看的身份摘要'}
|
||||
summary={siteSettings.ownerBio || siteSettings.siteDescription}
|
||||
highlights={aboutHighlights}
|
||||
faqs={aboutFaqs}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-4">
|
||||
|
||||
Reference in New Issue
Block a user