@fecommunity/reactpress-theme-starter 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +22 -0
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/app/api/[[...path]]/route.ts +69 -0
- package/app/archives/page.tsx +28 -0
- package/app/article/[id]/page.tsx +92 -0
- package/app/category/[category]/page.tsx +60 -0
- package/app/globals.css +4969 -0
- package/app/knowledge/[pId]/[id]/page.tsx +54 -0
- package/app/knowledge/[pId]/page.tsx +47 -0
- package/app/knowledge/page.tsx +29 -0
- package/app/layout.tsx +76 -0
- package/app/llms.txt/route.ts +26 -0
- package/app/login/page.tsx +17 -0
- package/app/nav/[id]/page.tsx +44 -0
- package/app/nav/page.tsx +5 -0
- package/app/not-found.tsx +21 -0
- package/app/page/[id]/page.tsx +57 -0
- package/app/page.tsx +61 -0
- package/app/register/page.tsx +17 -0
- package/app/robots.ts +14 -0
- package/app/rss/route.ts +82 -0
- package/app/search/page.tsx +46 -0
- package/app/sitemap.ts +38 -0
- package/app/suggestions/page.tsx +47 -0
- package/app/tag/[tag]/page.tsx +58 -0
- package/app/tags/page.tsx +18 -0
- package/app/template.tsx +6 -0
- package/components/article/ArchiveBanner.tsx +40 -0
- package/components/article/ArticleActionsBar.tsx +53 -0
- package/components/article/ArticleCarousel.tsx +237 -0
- package/components/article/ArticleCopyrightBar.tsx +128 -0
- package/components/article/ArticleEndBar.tsx +19 -0
- package/components/article/ArticleFeedSection.tsx +59 -0
- package/components/article/ArticleList.tsx +170 -0
- package/components/article/ArticleReadingChrome.tsx +104 -0
- package/components/article/ArticleReadingProgress.tsx +75 -0
- package/components/article/ArticleRecommend.tsx +362 -0
- package/components/article/ArticleTocPanel.tsx +19 -0
- package/components/article/ArticleViewClient.tsx +221 -0
- package/components/article/CategoryMenu.tsx +67 -0
- package/components/article/CmsPageClient.tsx +92 -0
- package/components/article/LoadMore.tsx +59 -0
- package/components/article/ReadingContent.tsx +51 -0
- package/components/auth/AuthPageClient.tsx +314 -0
- package/components/comment/CommentEditor.tsx +290 -0
- package/components/comment/CommentEmoji.tsx +110 -0
- package/components/comment/CommentIconButton.tsx +37 -0
- package/components/comment/CommentItem.tsx +135 -0
- package/components/comment/CommentSection.tsx +123 -0
- package/components/comment/emojis.ts +152 -0
- package/components/knowledge/KnowledgeList.tsx +70 -0
- package/components/layout/BackToTop.tsx +27 -0
- package/components/layout/ConditionalSiteFooter.tsx +29 -0
- package/components/layout/ContentStylesLoader.tsx +9 -0
- package/components/layout/DoubleColumnLayout.tsx +171 -0
- package/components/layout/HeaderSearch.tsx +23 -0
- package/components/layout/LocaleSwitcher.tsx +24 -0
- package/components/layout/PageContainer.tsx +11 -0
- package/components/layout/PageLoadProgress.tsx +169 -0
- package/components/layout/SiteFooter.tsx +75 -0
- package/components/layout/SiteHeader.tsx +312 -0
- package/components/layout/SystemNotification.tsx +176 -0
- package/components/layout/ThemeSwitch.tsx +19 -0
- package/components/layout/UserAuth.tsx +130 -0
- package/components/search/SearchHero.tsx +323 -0
- package/components/search/SearchQuickLinks.tsx +285 -0
- package/components/shared/DevChunkRecovery.tsx +8 -0
- package/components/shared/Link.tsx +30 -0
- package/components/shared/SectionHeading.tsx +38 -0
- package/components/shared/ThemeImage.tsx +13 -0
- package/components/shared/logo.svg +7 -0
- package/components/views/ArchivesClient.tsx +137 -0
- package/components/views/CategoryClient.tsx +87 -0
- package/components/views/HomeClient.tsx +79 -0
- package/components/views/KnowledgeBookClient.tsx +127 -0
- package/components/views/KnowledgeChapterClient.tsx +175 -0
- package/components/views/KnowledgeClient.tsx +69 -0
- package/components/views/LoginClient.tsx +80 -0
- package/components/views/NavDetailClient.tsx +177 -0
- package/components/views/SearchClient.tsx +100 -0
- package/components/views/TagArchiveClient.tsx +105 -0
- package/components/views/TagsClient.tsx +46 -0
- package/components/widgets/AboutUs.tsx +180 -0
- package/components/widgets/ArticleActionButton.tsx +95 -0
- package/components/widgets/BookmarkWidget.tsx +61 -0
- package/components/widgets/CategoriesWidget.tsx +47 -0
- package/components/widgets/HomeSidebar.tsx +40 -0
- package/components/widgets/LikesWidget.tsx +77 -0
- package/components/widgets/ShareWidget.tsx +120 -0
- package/components/widgets/TagCloud.tsx +94 -0
- package/components/widgets/TagsWidget.tsx +160 -0
- package/eslint.config.mjs +80 -0
- package/lib/auth/githubOAuth.ts +26 -0
- package/lib/auth/sessionUser.ts +41 -0
- package/lib/mock-api/auth.ts +129 -0
- package/lib/mock-api/config.ts +11 -0
- package/lib/mock-api/cors.ts +53 -0
- package/lib/mock-api/data.ts +998 -0
- package/lib/mock-api/match-route.ts +202 -0
- package/lib/mock-api/proxy.ts +28 -0
- package/lib/providers/client.ts +58 -0
- package/lib/providers/materializeProvider.ts +9 -0
- package/lib/providers/server.ts +8 -0
- package/lib/reactpress/appearance.ts +69 -0
- package/lib/reactpress/bootstrap.ts +48 -0
- package/lib/reactpress/coercePublishedPage.ts +12 -0
- package/lib/reactpress/contentSeo.ts +123 -0
- package/lib/reactpress/env.ts +31 -0
- package/lib/reactpress/feedFooterPlacement.ts +16 -0
- package/lib/reactpress/fetchSetting.ts +19 -0
- package/lib/reactpress/homeFooter.tsx +27 -0
- package/lib/reactpress/layout-shell.tsx +19 -0
- package/lib/reactpress/materializeBootstrapCatalog.ts +31 -0
- package/lib/reactpress/normalizeApiResponse.ts +25 -0
- package/lib/reactpress/providers.tsx +199 -0
- package/lib/reactpress/resolveSiteNav.ts +56 -0
- package/lib/reactpress/seoMetadata.ts +82 -0
- package/lib/reactpress/serverLocale.ts +43 -0
- package/lib/reactpress/siteMetadata.ts +151 -0
- package/lib/reactpress/staticParams.ts +46 -0
- package/lib/reactpress/suggestionsContent.ts +48 -0
- package/lib/reactpress/useFeedFooterPlacement.ts +22 -0
- package/lib/reactpress/useSiteNavConfig.ts +39 -0
- package/lib/utils/archiveBanner.ts +8 -0
- package/lib/utils/colors.ts +119 -0
- package/lib/utils/comment.ts +46 -0
- package/lib/utils/headerPosition.ts +55 -0
- package/lib/utils/icons.tsx +238 -0
- package/lib/utils/reading.ts +16 -0
- package/lib/utils/scroll.ts +8 -0
- package/lib/utils/tagCloud.ts +222 -0
- package/next.config.js +103 -0
- package/package.json +98 -0
- package/postcss.config.js +5 -0
- package/prettier.config.js +10 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/cli.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/home-dark.png +0 -0
- package/public/home-light.png +0 -0
- package/public/icon-192.png +0 -0
- package/public/icon-512.png +0 -0
- package/public/logo-400.png +0 -0
- package/public/logo.png +0 -0
- package/public/mobile.png +0 -0
- package/public/pad.png +0 -0
- package/public/site.webmanifest +19 -0
- package/scripts/build-mock.mjs +139 -0
- package/scripts/dev.mjs +221 -0
- package/scripts/ensure-env.mjs +100 -0
- package/scripts/remote-dev.mjs +143 -0
- package/scripts/resolve-api-env.mjs +114 -0
- package/scripts/set-publish-version.mjs +31 -0
- package/scripts/smoke-pages.mjs +113 -0
- package/scripts/start-mock.mjs +79 -0
- package/scripts/verify-toolkit.mjs +37 -0
- package/svg.d.ts +6 -0
- package/theme.json +123 -0
- package/theme.manifest.schema.json +265 -0
- package/tsconfig.json +41 -0
- package/vercel.json +12 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
type MockUser = {
|
|
2
|
+
id: string
|
|
3
|
+
name: string
|
|
4
|
+
password: string
|
|
5
|
+
email: string
|
|
6
|
+
avatar: string
|
|
7
|
+
role: string
|
|
8
|
+
status: string
|
|
9
|
+
type: string
|
|
10
|
+
createAt: string
|
|
11
|
+
updateAt: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const now = () => new Date().toISOString()
|
|
15
|
+
|
|
16
|
+
const mockUsers: MockUser[] = [
|
|
17
|
+
{
|
|
18
|
+
id: 'mock-user-demo',
|
|
19
|
+
name: 'demo',
|
|
20
|
+
password: 'demo123',
|
|
21
|
+
email: 'demo@example.com',
|
|
22
|
+
avatar: '',
|
|
23
|
+
role: 'visitor',
|
|
24
|
+
status: 'active',
|
|
25
|
+
type: 'local',
|
|
26
|
+
createAt: now(),
|
|
27
|
+
updateAt: now(),
|
|
28
|
+
},
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
function findUserByName(name: string) {
|
|
32
|
+
return mockUsers.find((user) => user.name === name.trim())
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function toPublicUser(user: MockUser, token: string) {
|
|
36
|
+
const { password: _password, ...rest } = user
|
|
37
|
+
return { ...rest, token: `mock-token-${token}` }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function jsonError(message: string, status = 400): Response {
|
|
41
|
+
return Response.json({ success: false, msg: message, statusCode: status }, { status })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function mockAuthLogin(body: unknown): Response {
|
|
45
|
+
const payload = body && typeof body === 'object' ? (body as Record<string, unknown>) : {}
|
|
46
|
+
const name = typeof payload.name === 'string' ? payload.name.trim() : ''
|
|
47
|
+
const password = typeof payload.password === 'string' ? payload.password : ''
|
|
48
|
+
|
|
49
|
+
if (!name || !password) {
|
|
50
|
+
return jsonError('Please enter username and password')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const user = findUserByName(name)
|
|
54
|
+
if (!user || user.password !== password) {
|
|
55
|
+
return jsonError('Invalid username or password')
|
|
56
|
+
}
|
|
57
|
+
if (user.status === 'locked') {
|
|
58
|
+
return jsonError('Account is locked')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return Response.json({ success: true, data: toPublicUser(user, user.name) })
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function mockAuthRegister(body: unknown): Response {
|
|
65
|
+
const payload = body && typeof body === 'object' ? (body as Record<string, unknown>) : {}
|
|
66
|
+
const name = typeof payload.name === 'string' ? payload.name.trim() : ''
|
|
67
|
+
const password = typeof payload.password === 'string' ? payload.password : ''
|
|
68
|
+
const email = typeof payload.email === 'string' ? payload.email.trim() : ''
|
|
69
|
+
|
|
70
|
+
if (!name || !password) {
|
|
71
|
+
return jsonError('Please enter username and password')
|
|
72
|
+
}
|
|
73
|
+
if (findUserByName(name)) {
|
|
74
|
+
return jsonError('Username already exists')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const user: MockUser = {
|
|
78
|
+
id: `mock-user-${Date.now()}`,
|
|
79
|
+
name,
|
|
80
|
+
password,
|
|
81
|
+
email,
|
|
82
|
+
avatar: '',
|
|
83
|
+
role: 'visitor',
|
|
84
|
+
status: 'active',
|
|
85
|
+
type: 'local',
|
|
86
|
+
createAt: now(),
|
|
87
|
+
updateAt: now(),
|
|
88
|
+
}
|
|
89
|
+
mockUsers.push(user)
|
|
90
|
+
|
|
91
|
+
const { password: _password, ...rest } = user
|
|
92
|
+
return Response.json({ success: true, data: rest }, { status: 201 })
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function mockAuthGithub(body: unknown): Response {
|
|
96
|
+
const code =
|
|
97
|
+
body && typeof body === 'object'
|
|
98
|
+
? typeof (body as Record<string, unknown>).code === 'string'
|
|
99
|
+
? (body as Record<string, string>).code
|
|
100
|
+
: ''
|
|
101
|
+
: typeof body === 'string'
|
|
102
|
+
? body
|
|
103
|
+
: ''
|
|
104
|
+
|
|
105
|
+
if (!code.trim()) {
|
|
106
|
+
return jsonError('Please enter a GitHub authorization code')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const user: MockUser = {
|
|
110
|
+
id: 'mock-user-github',
|
|
111
|
+
name: 'Github User',
|
|
112
|
+
password: '',
|
|
113
|
+
email: 'github@example.com',
|
|
114
|
+
avatar: 'https://avatars.githubusercontent.com/u/9919?s=96&v=4',
|
|
115
|
+
role: 'visitor',
|
|
116
|
+
status: 'active',
|
|
117
|
+
type: 'github',
|
|
118
|
+
createAt: now(),
|
|
119
|
+
updateAt: now(),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const existing = findUserByName(user.name)
|
|
123
|
+
const resolved = existing ?? user
|
|
124
|
+
if (!existing) {
|
|
125
|
+
mockUsers.push(user)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return Response.json({ success: true, data: toPublicUser(resolved, resolved.name) })
|
|
129
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function isMockApiEnabled(): boolean {
|
|
2
|
+
return process.env.REACTPRESS_MOCK_API === '1'
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function resolveUpstreamApiBase(): string {
|
|
6
|
+
return (
|
|
7
|
+
process.env.REACTPRESS_API_URL?.trim() ||
|
|
8
|
+
process.env.SERVER_API_URL?.trim() ||
|
|
9
|
+
'http://localhost:3002/api'
|
|
10
|
+
).replace(/\/$/, '')
|
|
11
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const ALLOWED_METHODS = 'GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD'
|
|
2
|
+
|
|
3
|
+
function resolveAllowedOrigin(request: Request): string | null {
|
|
4
|
+
const origin = request.headers.get('Origin')?.trim()
|
|
5
|
+
if (!origin) return null
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const url = new URL(origin)
|
|
9
|
+
if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
|
|
10
|
+
return origin
|
|
11
|
+
}
|
|
12
|
+
} catch {
|
|
13
|
+
// ignore invalid Origin
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function corsPreflightResponse(request: Request): Response {
|
|
20
|
+
const origin = resolveAllowedOrigin(request)
|
|
21
|
+
const headers = new Headers({
|
|
22
|
+
'Access-Control-Allow-Methods': ALLOWED_METHODS,
|
|
23
|
+
'Access-Control-Allow-Headers':
|
|
24
|
+
request.headers.get('Access-Control-Request-Headers') ??
|
|
25
|
+
'Authorization, Content-Type, Accept',
|
|
26
|
+
'Access-Control-Max-Age': '86400',
|
|
27
|
+
Vary: 'Origin',
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
if (origin) {
|
|
31
|
+
headers.set('Access-Control-Allow-Origin', origin)
|
|
32
|
+
headers.set('Access-Control-Allow-Credentials', 'true')
|
|
33
|
+
} else {
|
|
34
|
+
headers.set('Access-Control-Allow-Origin', '*')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return new Response(null, { status: 204, headers })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function withCors(request: Request, response: Response): Response {
|
|
41
|
+
const origin = resolveAllowedOrigin(request)
|
|
42
|
+
if (!origin) return response
|
|
43
|
+
|
|
44
|
+
const headers = new Headers(response.headers)
|
|
45
|
+
headers.set('Access-Control-Allow-Origin', origin)
|
|
46
|
+
headers.set('Access-Control-Allow-Credentials', 'true')
|
|
47
|
+
headers.set('Vary', 'Origin')
|
|
48
|
+
return new Response(response.body, {
|
|
49
|
+
status: response.status,
|
|
50
|
+
statusText: response.statusText,
|
|
51
|
+
headers,
|
|
52
|
+
})
|
|
53
|
+
}
|