@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,113 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Smoke-test all theme routes against a running dev:mock server.
|
|
4
|
+
* Usage: node scripts/smoke-pages.mjs [baseUrl]
|
|
5
|
+
*/
|
|
6
|
+
const base = (process.argv[2] || 'http://127.0.0.1:3040').replace(/\/$/, '')
|
|
7
|
+
|
|
8
|
+
const pages = [
|
|
9
|
+
['/', 200],
|
|
10
|
+
['/article/what-is-reactpress/', 200],
|
|
11
|
+
['/article/quick-start/', 200],
|
|
12
|
+
['/article/platform-capabilities/', 200],
|
|
13
|
+
['/article/cli-reference/', 200],
|
|
14
|
+
['/article/deploy-reactpress/', 200],
|
|
15
|
+
['/article/theme-starter/', 200],
|
|
16
|
+
['/category/quick-start/', 200],
|
|
17
|
+
['/category/platform/', 200],
|
|
18
|
+
['/category/cli-deploy/', 200],
|
|
19
|
+
['/tag/reactpress/', 200],
|
|
20
|
+
['/tag/nextjs/', 200],
|
|
21
|
+
['/tags/', 200],
|
|
22
|
+
['/archives/', 200],
|
|
23
|
+
['/search/', 200],
|
|
24
|
+
['/search/?keyword=reactpress', 200],
|
|
25
|
+
['/suggestions/', 200],
|
|
26
|
+
['/knowledge/', 200],
|
|
27
|
+
['/knowledge/reactpress-handbook/', 200],
|
|
28
|
+
['/knowledge/reactpress-handbook/cli-overview/', 200],
|
|
29
|
+
['/knowledge/reactpress-handbook/api-endpoints/', 200],
|
|
30
|
+
['/login/', 200],
|
|
31
|
+
['/register/', 200],
|
|
32
|
+
['/nav/rp-github/', 200],
|
|
33
|
+
['/this-page-does-not-exist/', 404],
|
|
34
|
+
['/rss/', 200],
|
|
35
|
+
['/sitemap.xml', 200],
|
|
36
|
+
['/robots.txt', 200],
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
const apis = [
|
|
40
|
+
['POST', '/api/setting/get'],
|
|
41
|
+
['POST', '/api/view'],
|
|
42
|
+
['GET', '/api/article'],
|
|
43
|
+
['GET', '/api/article/what-is-reactpress'],
|
|
44
|
+
['POST', '/api/article/what-is-reactpress/views'],
|
|
45
|
+
['GET', '/api/article/recommend?pageSize=15'],
|
|
46
|
+
['GET', '/api/category'],
|
|
47
|
+
['GET', '/api/tag'],
|
|
48
|
+
['GET', '/api/page'],
|
|
49
|
+
['GET', '/api/page/suggestions'],
|
|
50
|
+
['POST', '/api/page/suggestions/views'],
|
|
51
|
+
['GET', '/api/knowledge'],
|
|
52
|
+
['GET', '/api/knowledge/reactpress-handbook'],
|
|
53
|
+
['GET', '/api/knowledge/cli-overview'],
|
|
54
|
+
['GET', '/api/search/article?keyword=reactpress'],
|
|
55
|
+
['GET', '/api/comment/host/what-is-reactpress'],
|
|
56
|
+
['POST', '/api/knowledge/reactpress-handbook/views'],
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
async function checkPage(path, expected) {
|
|
60
|
+
const res = await fetch(`${base}${path}`, { redirect: 'follow' })
|
|
61
|
+
const ok = res.status === expected
|
|
62
|
+
return { ok, status: res.status, path, expected }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function checkApi(method, path) {
|
|
66
|
+
const res = await fetch(`${base}${path}`, {
|
|
67
|
+
method,
|
|
68
|
+
headers: method === 'POST' ? { 'Content-Type': 'application/json' } : undefined,
|
|
69
|
+
body: method === 'POST' ? '{}' : undefined,
|
|
70
|
+
})
|
|
71
|
+
let json = null
|
|
72
|
+
try {
|
|
73
|
+
json = await res.json()
|
|
74
|
+
} catch {
|
|
75
|
+
// non-json routes like robots.txt tested separately
|
|
76
|
+
}
|
|
77
|
+
const ok = res.ok && (json === null || json.success !== false)
|
|
78
|
+
return { ok, status: res.status, method, path, success: json?.success }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function main() {
|
|
82
|
+
let failed = 0
|
|
83
|
+
|
|
84
|
+
console.log(`Smoke testing ${base}\n`)
|
|
85
|
+
|
|
86
|
+
console.log('--- Pages ---')
|
|
87
|
+
for (const [path, expected] of pages) {
|
|
88
|
+
const result = await checkPage(path, expected)
|
|
89
|
+
const mark = result.ok ? 'OK' : 'FAIL'
|
|
90
|
+
if (!result.ok) failed += 1
|
|
91
|
+
console.log(`${mark} ${result.status} (expected ${expected}) ${path}`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log('\n--- APIs ---')
|
|
95
|
+
for (const [method, path] of apis) {
|
|
96
|
+
const result = await checkApi(method, path)
|
|
97
|
+
const mark = result.ok ? 'OK' : 'FAIL'
|
|
98
|
+
if (!result.ok) failed += 1
|
|
99
|
+
console.log(`${mark} ${result.status} ${method} ${path}`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (failed) {
|
|
103
|
+
console.log(`\n${failed} check(s) failed`)
|
|
104
|
+
process.exit(1)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log('\nAll checks passed')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main().catch((error) => {
|
|
111
|
+
console.error(error)
|
|
112
|
+
process.exit(1)
|
|
113
|
+
})
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Production server with in-process mock API (after `pnpm build:mock`).
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* pnpm build:mock && pnpm start:mock
|
|
7
|
+
* pnpm start:mock -- --port 3002
|
|
8
|
+
*/
|
|
9
|
+
import { spawn } from 'node:child_process'
|
|
10
|
+
import path from 'node:path'
|
|
11
|
+
import { fileURLToPath } from 'node:url'
|
|
12
|
+
import { DEFAULT_THEME_DEV_PORT } from './resolve-api-env.mjs'
|
|
13
|
+
|
|
14
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
15
|
+
const projectRoot = path.resolve(__dirname, '..')
|
|
16
|
+
|
|
17
|
+
function parseArgs(argv) {
|
|
18
|
+
let port = DEFAULT_THEME_DEV_PORT
|
|
19
|
+
const passthrough = []
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
22
|
+
const arg = argv[i]
|
|
23
|
+
if (arg === '-p' || arg === '--port') {
|
|
24
|
+
port = Number(argv[++i]) || DEFAULT_THEME_DEV_PORT
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
if (arg.startsWith('--port=')) {
|
|
28
|
+
port = Number(arg.slice('--port='.length)) || DEFAULT_THEME_DEV_PORT
|
|
29
|
+
continue
|
|
30
|
+
}
|
|
31
|
+
passthrough.push(arg)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { port, passthrough }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildEnv(port) {
|
|
38
|
+
const siteUrl = (process.env.CLIENT_SITE_URL || `http://localhost:${port}`).replace(/\/$/, '')
|
|
39
|
+
const serverApiUrl = `http://localhost:${port}/api`
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
...process.env,
|
|
43
|
+
INIT_CWD: projectRoot,
|
|
44
|
+
REACTPRESS_ORIGINAL_CWD: projectRoot,
|
|
45
|
+
REACTPRESS_MOCK_API: '1',
|
|
46
|
+
SERVER_API_URL: serverApiUrl,
|
|
47
|
+
REACTPRESS_API_URL: serverApiUrl,
|
|
48
|
+
NEXT_PUBLIC_REACTPRESS_API_URL: '/api',
|
|
49
|
+
CLIENT_SITE_URL: siteUrl,
|
|
50
|
+
NEXT_TELEMETRY_DISABLED: '1',
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function main() {
|
|
55
|
+
const { port, passthrough } = parseArgs(process.argv.slice(2))
|
|
56
|
+
const nextBin = path.join(projectRoot, 'node_modules', 'next', 'dist', 'bin', 'next')
|
|
57
|
+
|
|
58
|
+
console.log('')
|
|
59
|
+
console.log(' ReactPress Theme (production + mock API)')
|
|
60
|
+
console.log(` Local: http://localhost:${port}`)
|
|
61
|
+
console.log(` API: /api (mock routes, same-origin in browser)`)
|
|
62
|
+
console.log('')
|
|
63
|
+
|
|
64
|
+
const child = spawn(process.execPath, [nextBin, 'start', '-p', String(port), ...passthrough], {
|
|
65
|
+
cwd: projectRoot,
|
|
66
|
+
env: buildEnv(port),
|
|
67
|
+
stdio: 'inherit',
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
child.on('exit', (code, signal) => {
|
|
71
|
+
if (signal) {
|
|
72
|
+
process.kill(process.pid, signal)
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
process.exit(code ?? 0)
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Verify installed @fecommunity/reactpress-toolkit includes theme/* exports (3.1.0+).
|
|
4
|
+
*/
|
|
5
|
+
import { createRequire } from 'node:module'
|
|
6
|
+
|
|
7
|
+
const require = createRequire(import.meta.url)
|
|
8
|
+
|
|
9
|
+
const REQUIRED = [
|
|
10
|
+
'@fecommunity/reactpress-toolkit/theme/server',
|
|
11
|
+
'@fecommunity/reactpress-toolkit/theme/next-config',
|
|
12
|
+
'@fecommunity/reactpress-toolkit/ui',
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
const missing = REQUIRED.filter((spec) => {
|
|
16
|
+
try {
|
|
17
|
+
require.resolve(spec)
|
|
18
|
+
return false
|
|
19
|
+
} catch {
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
if (missing.length === 0) {
|
|
25
|
+
console.log('[toolkit] theme/* exports OK')
|
|
26
|
+
process.exit(0)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.error(`[toolkit] @fecommunity/reactpress-toolkit 缺少 theme/* 导出,请升级到 3.1.0+:
|
|
30
|
+
|
|
31
|
+
pnpm add @fecommunity/reactpress-toolkit@^3.1.0
|
|
32
|
+
|
|
33
|
+
缺失路径:
|
|
34
|
+
${missing.map((item) => ` - ${item}`).join('\n')}
|
|
35
|
+
`)
|
|
36
|
+
|
|
37
|
+
process.exit(1)
|
package/svg.d.ts
ADDED
package/theme.json
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./theme.manifest.schema.json",
|
|
3
|
+
"id": "reactpress-theme-starter",
|
|
4
|
+
"name": "ReactPress Theme Starter",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"description": "Tailwind 风格博客主题,Next.js App Router + ReactPress 动态内容,支持深浅色、评论、知识库与网址导航。",
|
|
7
|
+
"author": "ReactPress",
|
|
8
|
+
"authorUri": "https://reactpress.surge.sh/",
|
|
9
|
+
"themeUri": "https://github.com/fecommunity/reactpress-theme-starter",
|
|
10
|
+
"tags": ["博客", "Tailwind", "App Router", "ReactPress"],
|
|
11
|
+
"cover": "public/logo-400.png",
|
|
12
|
+
"requires": ">=3.0.0",
|
|
13
|
+
"supports": {
|
|
14
|
+
"darkMode": true,
|
|
15
|
+
"menus": ["primary"]
|
|
16
|
+
},
|
|
17
|
+
"templates": {
|
|
18
|
+
"404": "app/not-found.tsx",
|
|
19
|
+
"home": "app/page.tsx",
|
|
20
|
+
"single": "app/article/[id]/page.tsx",
|
|
21
|
+
"page": "app/page/[id]/page.tsx",
|
|
22
|
+
"archive-category": "app/category/[category]/page.tsx",
|
|
23
|
+
"archive-tag": "app/tag/[tag]/page.tsx",
|
|
24
|
+
"archives": "app/archives/page.tsx",
|
|
25
|
+
"search": "app/search/page.tsx"
|
|
26
|
+
},
|
|
27
|
+
"appearance": {
|
|
28
|
+
"panels": [
|
|
29
|
+
{
|
|
30
|
+
"id": "basic",
|
|
31
|
+
"title": "基础配置"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "style",
|
|
35
|
+
"title": "样式配置"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"sections": [
|
|
39
|
+
{
|
|
40
|
+
"id": "identity",
|
|
41
|
+
"panel": "basic",
|
|
42
|
+
"title": "站点身份",
|
|
43
|
+
"settings": [
|
|
44
|
+
{
|
|
45
|
+
"id": "displayTitle",
|
|
46
|
+
"type": "text",
|
|
47
|
+
"label": "站点标题",
|
|
48
|
+
"description": "留空则继承后台「站点标题」"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "displayTagline",
|
|
52
|
+
"type": "text",
|
|
53
|
+
"label": "站点副标题",
|
|
54
|
+
"description": "留空则继承后台「站点副标题」"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "siteLogo",
|
|
58
|
+
"type": "image",
|
|
59
|
+
"label": "站点 Logo"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "colors",
|
|
65
|
+
"panel": "style",
|
|
66
|
+
"title": "颜色",
|
|
67
|
+
"groups": [
|
|
68
|
+
{
|
|
69
|
+
"id": "light",
|
|
70
|
+
"title": "浅色模式"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "dark",
|
|
74
|
+
"title": "深色模式"
|
|
75
|
+
}
|
|
76
|
+
],
|
|
77
|
+
"settings": [
|
|
78
|
+
{
|
|
79
|
+
"id": "primaryColor",
|
|
80
|
+
"group": "light",
|
|
81
|
+
"type": "color",
|
|
82
|
+
"label": "主色",
|
|
83
|
+
"default": "#e11d48"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"id": "backgroundColor",
|
|
87
|
+
"group": "light",
|
|
88
|
+
"type": "color",
|
|
89
|
+
"label": "背景色",
|
|
90
|
+
"default": "#ffffff"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"id": "darkPrimaryColor",
|
|
94
|
+
"group": "dark",
|
|
95
|
+
"type": "color",
|
|
96
|
+
"label": "主色",
|
|
97
|
+
"default": "#fb7185"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"id": "darkBackgroundColor",
|
|
101
|
+
"group": "dark",
|
|
102
|
+
"type": "color",
|
|
103
|
+
"label": "背景色",
|
|
104
|
+
"default": "#030712"
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"id": "layout",
|
|
110
|
+
"panel": "basic",
|
|
111
|
+
"title": "布局",
|
|
112
|
+
"settings": [
|
|
113
|
+
{
|
|
114
|
+
"id": "stickyNav",
|
|
115
|
+
"type": "checkbox",
|
|
116
|
+
"label": "固定顶栏",
|
|
117
|
+
"default": "0"
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://reactpress.surge.sh/schemas/theme.manifest.json",
|
|
4
|
+
"title": "Theme Manifest",
|
|
5
|
+
"description": "Portable theme manifest for ReactPress and compatible platforms. Parsed by `parseThemeManifest()`; stored at `themes/{id}/theme.json`. Platform fields are top-level (no vendor namespace). Appearance controls map to WordPress theme_mod storage.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": ["id", "name", "version"],
|
|
9
|
+
"properties": {
|
|
10
|
+
"$schema": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "JSON Schema reference. Use \"../theme.manifest.schema.json\" for editor validation.",
|
|
13
|
+
"examples": ["../theme.manifest.schema.json"]
|
|
14
|
+
},
|
|
15
|
+
"id": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
18
|
+
"description": "Theme slug / Text Domain. Matches directory `themes/{id}/`, kebab-case.",
|
|
19
|
+
"examples": ["twentytwentyfive", "hello-world"]
|
|
20
|
+
},
|
|
21
|
+
"name": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"minLength": 1,
|
|
24
|
+
"description": "Human-readable theme name (WordPress Theme Name).",
|
|
25
|
+
"examples": ["Twenty Twenty-Five", "Hello World"]
|
|
26
|
+
},
|
|
27
|
+
"version": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$",
|
|
30
|
+
"description": "Semantic version (WordPress Version header).",
|
|
31
|
+
"examples": ["3.0.4", "1.0.0"]
|
|
32
|
+
},
|
|
33
|
+
"description": { "type": "string", "description": "Theme description." },
|
|
34
|
+
"author": { "type": "string", "description": "Author name." },
|
|
35
|
+
"authorUri": { "type": "string", "description": "Author homepage URL." },
|
|
36
|
+
"themeUri": { "type": "string", "description": "Theme homepage or documentation URL." },
|
|
37
|
+
"tags": {
|
|
38
|
+
"type": "array",
|
|
39
|
+
"items": { "type": "string", "minLength": 1 },
|
|
40
|
+
"uniqueItems": true,
|
|
41
|
+
"description": "Theme tags for marketplace or admin filters."
|
|
42
|
+
},
|
|
43
|
+
"cover": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "Theme cover image for admin theme list and preview. Path relative to theme root (recommended 1200×900, e.g. cover.png).",
|
|
46
|
+
"examples": ["cover.png", "public/cover.jpg"]
|
|
47
|
+
},
|
|
48
|
+
"requires": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "Minimum host platform version (semver range). WordPress: Requires at least.",
|
|
51
|
+
"examples": [">=3.0.0"]
|
|
52
|
+
},
|
|
53
|
+
"supports": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"description": "Theme feature flags. WordPress: add_theme_support().",
|
|
56
|
+
"properties": {
|
|
57
|
+
"darkMode": { "type": "boolean", "description": "Visitor-facing light/dark toggle." },
|
|
58
|
+
"menus": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"items": { "type": "string", "enum": ["primary", "footer", "social"] },
|
|
61
|
+
"description": "Registered navigation menu locations."
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"additionalProperties": true
|
|
65
|
+
},
|
|
66
|
+
"templates": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"description": "Template hierarchy slug → page file path. WordPress Template Hierarchy.",
|
|
69
|
+
"propertyNames": {
|
|
70
|
+
"enum": [
|
|
71
|
+
"home",
|
|
72
|
+
"single",
|
|
73
|
+
"page",
|
|
74
|
+
"archive-category",
|
|
75
|
+
"archive-tag",
|
|
76
|
+
"archives",
|
|
77
|
+
"search",
|
|
78
|
+
"404"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
"additionalProperties": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"examples": ["pages/index.tsx", "pages/article/[id].tsx"]
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"options": {
|
|
87
|
+
"$ref": "#/definitions/themeOptions"
|
|
88
|
+
},
|
|
89
|
+
"appearance": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"description": "Appearance editor: panels, sections, and theme_mod controls. Stored in `globalSetting.theme.mods[themeId]`.",
|
|
92
|
+
"additionalProperties": false,
|
|
93
|
+
"required": ["sections"],
|
|
94
|
+
"properties": {
|
|
95
|
+
"panels": {
|
|
96
|
+
"type": "array",
|
|
97
|
+
"description": "Sidebar navigation panels (WordPress Customizer panels).",
|
|
98
|
+
"items": { "$ref": "#/definitions/appearancePanel" }
|
|
99
|
+
},
|
|
100
|
+
"sections": {
|
|
101
|
+
"type": "array",
|
|
102
|
+
"minItems": 1,
|
|
103
|
+
"description": "Appearance sections (WordPress Customizer sections).",
|
|
104
|
+
"items": { "$ref": "#/definitions/appearanceSection" }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"definitions": {
|
|
110
|
+
"themeOptions": {
|
|
111
|
+
"type": "object",
|
|
112
|
+
"description": "JSON Schema for structured theme options. Persisted in `globalSetting.config[themeId]`.",
|
|
113
|
+
"required": ["type"],
|
|
114
|
+
"properties": {
|
|
115
|
+
"$schema": { "type": "string", "const": "http://json-schema.org/draft-07/schema#" },
|
|
116
|
+
"title": { "type": "string" },
|
|
117
|
+
"description": { "type": "string" },
|
|
118
|
+
"type": { "const": "object" },
|
|
119
|
+
"properties": {
|
|
120
|
+
"type": "object",
|
|
121
|
+
"additionalProperties": { "$ref": "#/definitions/optionsProperty" }
|
|
122
|
+
},
|
|
123
|
+
"required": { "type": "array", "items": { "type": "string" } },
|
|
124
|
+
"additionalProperties": { "type": "boolean" }
|
|
125
|
+
},
|
|
126
|
+
"additionalProperties": true
|
|
127
|
+
},
|
|
128
|
+
"optionsProperty": {
|
|
129
|
+
"type": "object",
|
|
130
|
+
"properties": {
|
|
131
|
+
"type": {},
|
|
132
|
+
"title": { "type": "string" },
|
|
133
|
+
"description": { "type": "string" },
|
|
134
|
+
"default": {},
|
|
135
|
+
"properties": {
|
|
136
|
+
"type": "object",
|
|
137
|
+
"additionalProperties": { "$ref": "#/definitions/optionsProperty" }
|
|
138
|
+
},
|
|
139
|
+
"items": { "$ref": "#/definitions/optionsProperty" },
|
|
140
|
+
"required": { "type": "array", "items": { "type": "string" } },
|
|
141
|
+
"additionalProperties": { "type": "boolean" },
|
|
142
|
+
"x-ui": {
|
|
143
|
+
"type": "object",
|
|
144
|
+
"description": "Admin UI metadata (schema-only, not persisted).",
|
|
145
|
+
"properties": {
|
|
146
|
+
"widget": {
|
|
147
|
+
"type": "string",
|
|
148
|
+
"examples": ["navLinkList", "urlConfigEditor", "navSearchEditor"]
|
|
149
|
+
},
|
|
150
|
+
"section": { "type": "string" },
|
|
151
|
+
"order": { "type": "number" },
|
|
152
|
+
"scope": { "type": "string", "enum": ["site", "locale"] },
|
|
153
|
+
"hidden": { "type": "boolean" }
|
|
154
|
+
},
|
|
155
|
+
"additionalProperties": false
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
"additionalProperties": true
|
|
159
|
+
},
|
|
160
|
+
"appearancePanel": {
|
|
161
|
+
"type": "object",
|
|
162
|
+
"additionalProperties": false,
|
|
163
|
+
"required": ["id", "title"],
|
|
164
|
+
"properties": {
|
|
165
|
+
"id": {
|
|
166
|
+
"type": "string",
|
|
167
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
168
|
+
"examples": ["basic", "style", "layout"]
|
|
169
|
+
},
|
|
170
|
+
"title": { "type": "string" },
|
|
171
|
+
"description": { "type": "string" }
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"appearanceSection": {
|
|
175
|
+
"type": "object",
|
|
176
|
+
"additionalProperties": false,
|
|
177
|
+
"required": ["id", "title"],
|
|
178
|
+
"properties": {
|
|
179
|
+
"id": {
|
|
180
|
+
"type": "string",
|
|
181
|
+
"pattern": "^[a-z][a-zA-Z0-9]*(?:-[a-z0-9]+)*$",
|
|
182
|
+
"examples": ["identity", "colors", "themeConfiguration"]
|
|
183
|
+
},
|
|
184
|
+
"panel": {
|
|
185
|
+
"type": "string",
|
|
186
|
+
"description": "Parent panel id (`appearance.panels[].id`)."
|
|
187
|
+
},
|
|
188
|
+
"title": { "type": "string" },
|
|
189
|
+
"description": { "type": "string" },
|
|
190
|
+
"embed": {
|
|
191
|
+
"type": "string",
|
|
192
|
+
"enum": ["options"],
|
|
193
|
+
"description": "Embed the theme `options` schema form instead of inline controls."
|
|
194
|
+
},
|
|
195
|
+
"groups": {
|
|
196
|
+
"type": "array",
|
|
197
|
+
"description": "Control groups within the section (e.g. light / dark palettes).",
|
|
198
|
+
"items": { "$ref": "#/definitions/appearanceGroup" }
|
|
199
|
+
},
|
|
200
|
+
"settings": {
|
|
201
|
+
"type": "array",
|
|
202
|
+
"description": "theme_mod controls.",
|
|
203
|
+
"items": { "$ref": "#/definitions/appearanceSetting" }
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"allOf": [
|
|
207
|
+
{
|
|
208
|
+
"if": { "properties": { "embed": { "const": "options" } }, "required": ["embed"] },
|
|
209
|
+
"then": { "not": { "required": ["settings"] } },
|
|
210
|
+
"else": { "required": ["settings"] }
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
},
|
|
214
|
+
"appearanceGroup": {
|
|
215
|
+
"type": "object",
|
|
216
|
+
"additionalProperties": false,
|
|
217
|
+
"required": ["id", "title"],
|
|
218
|
+
"properties": {
|
|
219
|
+
"id": {
|
|
220
|
+
"type": "string",
|
|
221
|
+
"pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$",
|
|
222
|
+
"examples": ["light", "dark"]
|
|
223
|
+
},
|
|
224
|
+
"title": { "type": "string" },
|
|
225
|
+
"description": { "type": "string" }
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
"appearanceSetting": {
|
|
229
|
+
"type": "object",
|
|
230
|
+
"additionalProperties": false,
|
|
231
|
+
"required": ["id", "type", "label"],
|
|
232
|
+
"properties": {
|
|
233
|
+
"id": {
|
|
234
|
+
"type": "string",
|
|
235
|
+
"pattern": "^[a-z][a-zA-Z0-9]*$",
|
|
236
|
+
"description": "theme_mod key (camelCase). Path: `globalSetting.theme.mods[themeId][id]`.",
|
|
237
|
+
"examples": ["displayTitle", "primaryColor", "siteLogo"]
|
|
238
|
+
},
|
|
239
|
+
"type": {
|
|
240
|
+
"type": "string",
|
|
241
|
+
"enum": ["color", "text", "image", "textarea", "checkbox", "select", "noticeList"]
|
|
242
|
+
},
|
|
243
|
+
"label": { "type": "string" },
|
|
244
|
+
"description": { "type": "string" },
|
|
245
|
+
"default": { "type": "string" },
|
|
246
|
+
"group": {
|
|
247
|
+
"type": "string",
|
|
248
|
+
"description": "Parent group id within the section (`appearance.sections[].groups[].id`)."
|
|
249
|
+
},
|
|
250
|
+
"choices": {
|
|
251
|
+
"type": "array",
|
|
252
|
+
"items": {
|
|
253
|
+
"type": "object",
|
|
254
|
+
"required": ["value", "label"],
|
|
255
|
+
"properties": {
|
|
256
|
+
"value": { "type": "string" },
|
|
257
|
+
"label": { "type": "string" }
|
|
258
|
+
},
|
|
259
|
+
"additionalProperties": false
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"incremental": true,
|
|
4
|
+
"target": "ES6",
|
|
5
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
6
|
+
"allowJs": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"strict": false,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"module": "esnext",
|
|
13
|
+
"moduleResolution": "bundler",
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"isolatedModules": true,
|
|
16
|
+
"jsx": "preserve",
|
|
17
|
+
"baseUrl": ".",
|
|
18
|
+
"paths": {
|
|
19
|
+
"@/components/*": ["components/*"],
|
|
20
|
+
"@/lib/*": ["lib/*"]
|
|
21
|
+
},
|
|
22
|
+
"plugins": [
|
|
23
|
+
{
|
|
24
|
+
"name": "next"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"strictNullChecks": false
|
|
28
|
+
},
|
|
29
|
+
"include": [
|
|
30
|
+
"**/*.js",
|
|
31
|
+
"**/*.json",
|
|
32
|
+
"**/*.mjs",
|
|
33
|
+
"**/*.ts",
|
|
34
|
+
"**/*.tsx",
|
|
35
|
+
".next/dev/types/**/*.ts",
|
|
36
|
+
".next/types/**/*.ts",
|
|
37
|
+
"next-env.d.ts",
|
|
38
|
+
".next-dev/types/**/*.ts"
|
|
39
|
+
],
|
|
40
|
+
"exclude": ["node_modules"]
|
|
41
|
+
}
|
package/vercel.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://openapi.vercel.sh/vercel.json",
|
|
3
|
+
"framework": "nextjs",
|
|
4
|
+
"installCommand": "pnpm install --frozen-lockfile",
|
|
5
|
+
"buildCommand": "pnpm run build:mock",
|
|
6
|
+
"env": {
|
|
7
|
+
"REACTPRESS_MOCK_API": "1",
|
|
8
|
+
"REACTPRESS_API_URL": "https://reactpress-theme-starter.vercel.app/api",
|
|
9
|
+
"NEXT_PUBLIC_REACTPRESS_API_URL": "/api",
|
|
10
|
+
"CLIENT_SITE_URL": "https://reactpress-theme-starter.vercel.app"
|
|
11
|
+
}
|
|
12
|
+
}
|