feat: add SharePanel component for social sharing with QR code support
Some checks failed
docker-images / resolve-build-targets (push) Successful in 7s
ui-regression / playwright-regression (push) Failing after 13m47s
docker-images / build-and-push (push) Failing after 7s
docker-images / submit-indexnow (push) Has been skipped

- 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:
2026-04-02 14:15:21 +08:00
parent a516be2e91
commit 3628a46ed1
53 changed files with 4390 additions and 91 deletions

View File

@@ -0,0 +1,89 @@
---
interface Props {
pageType: string;
entityId?: string;
postSlug?: string;
}
const props = Astro.props;
const pageType = props.pageType;
const entityId = props.entityId ?? '';
const postSlug = props.postSlug ?? '';
---
<script is:inline define:vars={{ pageType, entityId, postSlug }}>
(() => {
const endpoint = '/api/analytics/content';
const storageKey = `termi:pageview:${pageType}:${entityId || postSlug || 'root'}`;
function ensureSessionId() {
try {
const existing = window.sessionStorage.getItem(storageKey);
if (existing) return existing;
const nextId = crypto.randomUUID();
window.sessionStorage.setItem(storageKey, nextId);
return nextId;
} catch {
return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
}
}
function getReferrerHost() {
try {
return document.referrer ? new URL(document.referrer).host : '';
} catch {
return '';
}
}
function normalizeSource(value) {
const source = String(value || '').trim().toLowerCase();
if (!source) return 'direct';
if (source.includes('chatgpt') || source.includes('openai')) return 'chatgpt-search';
if (source.includes('perplexity')) return 'perplexity';
if (source.includes('copilot') || source.includes('bing')) return 'copilot-bing';
if (source.includes('gemini')) return 'gemini';
if (source.includes('google')) return 'google';
if (source.includes('claude')) return 'claude';
if (source.includes('duckduckgo')) return 'duckduckgo';
if (source.includes('kagi')) return 'kagi';
return source;
}
function buildMetadata() {
const currentUrl = new URL(window.location.href);
const utmSource = currentUrl.searchParams.get('utm_source')?.trim() || '';
const utmMedium = currentUrl.searchParams.get('utm_medium')?.trim() || '';
const utmCampaign = currentUrl.searchParams.get('utm_campaign')?.trim() || '';
const utmTerm = currentUrl.searchParams.get('utm_term')?.trim() || '';
const utmContent = currentUrl.searchParams.get('utm_content')?.trim() || '';
const referrerHost = getReferrerHost();
return {
pageType,
entityId: entityId || undefined,
referrerHost: referrerHost || undefined,
utmSource: utmSource || undefined,
utmMedium: utmMedium || undefined,
utmCampaign: utmCampaign || undefined,
utmTerm: utmTerm || undefined,
utmContent: utmContent || undefined,
landingSource: normalizeSource(utmSource || referrerHost),
};
}
fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
keepalive: true,
body: JSON.stringify({
event_type: 'page_view',
path: `${window.location.pathname}${window.location.search}`,
post_slug: postSlug || undefined,
session_id: ensureSessionId(),
referrer: document.referrer || undefined,
metadata: buildMetadata(),
}),
}).catch(() => undefined);
})();
</script>