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:
@@ -29,5 +29,6 @@ pnpm test:ui
|
||||
- 本地默认优先走已安装的 `msedge` channel,CI 仍使用 `playwright install chromium`。
|
||||
- 当前已覆盖:
|
||||
- 前台:首页过滤、文章详情、评论、搜索、AI 问答、友链申请、订阅确认/管理/退订
|
||||
- 前台 GEO 能力:llms.txt / llms-full.txt 入口、分享面板、微信扫码分享、AI 摘要块
|
||||
- 后台:登录、导航、评论审核、友链审核
|
||||
- 后台深度回归:分类 CRUD、标签 CRUD、订阅 CRUD / 测试发送 / weekly & monthly digest、文章创建/保存/版本恢复/删除、媒体上传/元数据/替换/删除、站点设置保存/AI 重建索引/Provider 连通性/存储连通性、评测 CRUD / AI 润色、评论画像与黑名单管理
|
||||
- 后台深度回归:分类 CRUD、标签 CRUD、订阅 CRUD / 测试发送 / weekly & monthly digest、文章创建/保存/版本恢复/删除、媒体上传/元数据/替换/删除、站点设置保存 / 微信扫码分享开关 / AI 重建索引 / Provider 连通性 / 存储连通性、评测 CRUD / AI 润色、评论画像与黑名单管理
|
||||
|
||||
@@ -824,6 +824,7 @@ function createSiteSettings() {
|
||||
media_r2_secret_access_key: 'mock-secret',
|
||||
seo_default_og_image: `${MOCK_ORIGIN}/media-files/default-og.svg`,
|
||||
seo_default_twitter_handle: '@initcool',
|
||||
seo_wechat_share_qr_enabled: false,
|
||||
notification_webhook_url: 'https://notify.mock.invalid/termi',
|
||||
notification_channel_type: 'webhook',
|
||||
notification_comment_enabled: true,
|
||||
@@ -2528,8 +2529,19 @@ const server = createServer(async (req, res) => {
|
||||
recent_events: [],
|
||||
providers_last_7d: [{ provider: 'mock-openai', count: 18 }],
|
||||
top_referrers: [{ referrer: 'homepage', count: 44 }],
|
||||
ai_referrers_last_7d: [
|
||||
{ referrer: 'chatgpt-search', count: 21 },
|
||||
{ referrer: 'perplexity', count: 9 },
|
||||
{ referrer: 'copilot-bing', count: 6 },
|
||||
],
|
||||
ai_discovery_page_views_last_7d: 36,
|
||||
popular_posts: getHomePayload().popular_posts,
|
||||
daily_activity: [{ date: '2026-04-01', searches: 9, ai_questions: 5 }],
|
||||
daily_activity: [
|
||||
{ date: '2026-03-29', searches: 6, ai_questions: 3 },
|
||||
{ date: '2026-03-30', searches: 7, ai_questions: 4 },
|
||||
{ date: '2026-03-31', searches: 11, ai_questions: 6 },
|
||||
{ date: '2026-04-01', searches: 9, ai_questions: 5 },
|
||||
],
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -2707,6 +2719,7 @@ const server = createServer(async (req, res) => {
|
||||
mediaR2SecretAccessKey: 'media_r2_secret_access_key',
|
||||
seoDefaultOgImage: 'seo_default_og_image',
|
||||
seoDefaultTwitterHandle: 'seo_default_twitter_handle',
|
||||
seoWechatShareQrEnabled: 'seo_wechat_share_qr_enabled',
|
||||
notificationWebhookUrl: 'notification_webhook_url',
|
||||
notificationChannelType: 'notification_channel_type',
|
||||
notificationCommentEnabled: 'notification_comment_enabled',
|
||||
|
||||
@@ -315,6 +315,10 @@ test('后台可完成媒体库上传/元数据/替换/删除,并执行设置
|
||||
await page.getByRole('link', { name: '设置' }).click()
|
||||
await page.getByTestId('site-settings-site-name').fill('InitCool Deep Regression')
|
||||
await page.getByTestId('site-settings-popup-title').fill('订阅深回归')
|
||||
await page
|
||||
.locator('label', { hasText: '开启文章页微信扫码分享' })
|
||||
.locator('input[type="checkbox"]')
|
||||
.check()
|
||||
await page.getByTestId('site-settings-save').click()
|
||||
await page.getByTestId('site-settings-reindex').click()
|
||||
await page.getByTestId('site-settings-test-provider').click()
|
||||
@@ -324,6 +328,7 @@ test('后台可完成媒体库上传/元数据/替换/删除,并执行设置
|
||||
state = await getDebugState(request)
|
||||
expect(state.site_settings.site_name).toBe('InitCool Deep Regression')
|
||||
expect(state.site_settings.subscription_popup_title).toBe('订阅深回归')
|
||||
expect(state.site_settings.seo_wechat_share_qr_enabled).toBe(true)
|
||||
expect(state.site_settings.ai_chunks_count).toBeGreaterThan(128)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from '@playwright/test'
|
||||
|
||||
import { getDebugState, resetMockState } from './helpers'
|
||||
import { getDebugState, patchAdminSiteSettings, resetMockState } from './helpers'
|
||||
|
||||
test.beforeEach(async ({ request }) => {
|
||||
await resetMockState(request)
|
||||
@@ -100,6 +100,7 @@ test('友链申请与订阅确认/偏好/退订链路可用', async ({ page, req
|
||||
await expect(page.locator('[data-subscribe-status]')).toContainText('订阅')
|
||||
|
||||
await page.locator('[data-subscription-popup-open]').click()
|
||||
await expect(page.locator('[data-subscription-popup-panel]')).toBeVisible()
|
||||
await page.locator('[data-subscription-popup-form] input[name="displayName"]').fill('弹窗订阅用户')
|
||||
await page.locator('[data-subscription-popup-email]').fill('playwright-subscriber@example.com')
|
||||
await page.locator('[data-subscription-popup-form] button[type="submit"]').click()
|
||||
@@ -128,3 +129,49 @@ test('友链申请与订阅确认/偏好/退订链路可用', async ({ page, req
|
||||
await page.getByRole('button', { name: '确认退订' }).click()
|
||||
await expect(page.locator('[data-unsubscribe-status]')).toContainText('成功退订')
|
||||
})
|
||||
|
||||
test('GEO 分享面板、AI 摘要块与 llms 入口可用', async ({ page, request }) => {
|
||||
await patchAdminSiteSettings(request, {
|
||||
seoWechatShareQrEnabled: true,
|
||||
})
|
||||
|
||||
await page.goto('/')
|
||||
await expect(page.locator('head link[rel="alternate"][href$="/llms.txt"]')).toHaveCount(1)
|
||||
await expect(page.locator('head link[rel="alternate"][href$="/llms-full.txt"]')).toHaveCount(1)
|
||||
await expect(page.getByRole('heading', { name: '给 AI 看的站点摘要' })).toBeVisible()
|
||||
await expect(page.getByRole('button', { name: '微信扫码' }).first()).toBeVisible()
|
||||
|
||||
await page.goto('/about')
|
||||
await expect(page.getByRole('heading', { name: '给 AI 看的身份摘要' })).toBeVisible()
|
||||
await expect(page.getByText('身份主页')).toBeVisible()
|
||||
|
||||
await page.goto('/articles')
|
||||
await expect(page.getByRole('heading', { name: '给 AI 看的归档摘要' })).toBeVisible()
|
||||
|
||||
await page.goto('/reviews')
|
||||
await expect(page.getByRole('heading', { name: '给 AI 看的评测摘要' })).toBeVisible()
|
||||
|
||||
await page.goto('/ask')
|
||||
await expect(page.getByRole('heading', { name: '给 AI 看的问答页摘要' })).toBeVisible()
|
||||
|
||||
await page.goto('/friends')
|
||||
await expect(page.getByRole('heading', { name: '给 AI 看的友链网络摘要' })).toBeVisible()
|
||||
|
||||
await page.goto('/articles/playwright-regression-workflow')
|
||||
await page.getByRole('button', { name: '微信扫码' }).first().click()
|
||||
await expect(page.locator('[data-article-wechat-qr-modal]')).toHaveAttribute('aria-hidden', 'false')
|
||||
await expect(page.getByRole('heading', { name: '微信扫码分享' })).toBeVisible()
|
||||
await expect(page.locator('[data-article-qr-download]')).toBeVisible()
|
||||
|
||||
await page.locator('[data-article-wechat-qr-close]').first().click()
|
||||
await expect(page.locator('[data-article-wechat-qr-modal]')).toHaveAttribute('aria-hidden', 'true')
|
||||
|
||||
await page.goto('/categories/frontend-engineering')
|
||||
await expect(page.getByRole('button', { name: '复制摘要' })).toBeVisible()
|
||||
|
||||
await page.goto('/tags/playwright')
|
||||
await expect(page.getByRole('button', { name: '分享摘要' })).toBeVisible()
|
||||
|
||||
await page.goto('/reviews/1')
|
||||
await expect(page.getByRole('button', { name: '复制摘要' })).toBeVisible()
|
||||
})
|
||||
|
||||
@@ -19,6 +19,20 @@ export async function getDebugState(request: APIRequestContext) {
|
||||
return response.json()
|
||||
}
|
||||
|
||||
export async function patchAdminSiteSettings(
|
||||
request: APIRequestContext,
|
||||
payload: Record<string, unknown>,
|
||||
) {
|
||||
const response = await request.patch(`${MOCK_BASE_URL}/api/admin/site-settings`, {
|
||||
headers: {
|
||||
cookie: `${ADMIN_COOKIE.name}=${ADMIN_COOKIE.value}`,
|
||||
},
|
||||
data: payload,
|
||||
})
|
||||
expect(response.ok()).toBeTruthy()
|
||||
return response.json()
|
||||
}
|
||||
|
||||
export async function loginAdmin(page: Page) {
|
||||
await page.goto('/login')
|
||||
await page.getByLabel('用户名').fill('admin')
|
||||
|
||||
Reference in New Issue
Block a user