@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.
Files changed (163) hide show
  1. package/.env.example +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +286 -0
  4. package/app/api/[[...path]]/route.ts +69 -0
  5. package/app/archives/page.tsx +28 -0
  6. package/app/article/[id]/page.tsx +92 -0
  7. package/app/category/[category]/page.tsx +60 -0
  8. package/app/globals.css +4969 -0
  9. package/app/knowledge/[pId]/[id]/page.tsx +54 -0
  10. package/app/knowledge/[pId]/page.tsx +47 -0
  11. package/app/knowledge/page.tsx +29 -0
  12. package/app/layout.tsx +76 -0
  13. package/app/llms.txt/route.ts +26 -0
  14. package/app/login/page.tsx +17 -0
  15. package/app/nav/[id]/page.tsx +44 -0
  16. package/app/nav/page.tsx +5 -0
  17. package/app/not-found.tsx +21 -0
  18. package/app/page/[id]/page.tsx +57 -0
  19. package/app/page.tsx +61 -0
  20. package/app/register/page.tsx +17 -0
  21. package/app/robots.ts +14 -0
  22. package/app/rss/route.ts +82 -0
  23. package/app/search/page.tsx +46 -0
  24. package/app/sitemap.ts +38 -0
  25. package/app/suggestions/page.tsx +47 -0
  26. package/app/tag/[tag]/page.tsx +58 -0
  27. package/app/tags/page.tsx +18 -0
  28. package/app/template.tsx +6 -0
  29. package/components/article/ArchiveBanner.tsx +40 -0
  30. package/components/article/ArticleActionsBar.tsx +53 -0
  31. package/components/article/ArticleCarousel.tsx +237 -0
  32. package/components/article/ArticleCopyrightBar.tsx +128 -0
  33. package/components/article/ArticleEndBar.tsx +19 -0
  34. package/components/article/ArticleFeedSection.tsx +59 -0
  35. package/components/article/ArticleList.tsx +170 -0
  36. package/components/article/ArticleReadingChrome.tsx +104 -0
  37. package/components/article/ArticleReadingProgress.tsx +75 -0
  38. package/components/article/ArticleRecommend.tsx +362 -0
  39. package/components/article/ArticleTocPanel.tsx +19 -0
  40. package/components/article/ArticleViewClient.tsx +221 -0
  41. package/components/article/CategoryMenu.tsx +67 -0
  42. package/components/article/CmsPageClient.tsx +92 -0
  43. package/components/article/LoadMore.tsx +59 -0
  44. package/components/article/ReadingContent.tsx +51 -0
  45. package/components/auth/AuthPageClient.tsx +314 -0
  46. package/components/comment/CommentEditor.tsx +290 -0
  47. package/components/comment/CommentEmoji.tsx +110 -0
  48. package/components/comment/CommentIconButton.tsx +37 -0
  49. package/components/comment/CommentItem.tsx +135 -0
  50. package/components/comment/CommentSection.tsx +123 -0
  51. package/components/comment/emojis.ts +152 -0
  52. package/components/knowledge/KnowledgeList.tsx +70 -0
  53. package/components/layout/BackToTop.tsx +27 -0
  54. package/components/layout/ConditionalSiteFooter.tsx +29 -0
  55. package/components/layout/ContentStylesLoader.tsx +9 -0
  56. package/components/layout/DoubleColumnLayout.tsx +171 -0
  57. package/components/layout/HeaderSearch.tsx +23 -0
  58. package/components/layout/LocaleSwitcher.tsx +24 -0
  59. package/components/layout/PageContainer.tsx +11 -0
  60. package/components/layout/PageLoadProgress.tsx +169 -0
  61. package/components/layout/SiteFooter.tsx +75 -0
  62. package/components/layout/SiteHeader.tsx +312 -0
  63. package/components/layout/SystemNotification.tsx +176 -0
  64. package/components/layout/ThemeSwitch.tsx +19 -0
  65. package/components/layout/UserAuth.tsx +130 -0
  66. package/components/search/SearchHero.tsx +323 -0
  67. package/components/search/SearchQuickLinks.tsx +285 -0
  68. package/components/shared/DevChunkRecovery.tsx +8 -0
  69. package/components/shared/Link.tsx +30 -0
  70. package/components/shared/SectionHeading.tsx +38 -0
  71. package/components/shared/ThemeImage.tsx +13 -0
  72. package/components/shared/logo.svg +7 -0
  73. package/components/views/ArchivesClient.tsx +137 -0
  74. package/components/views/CategoryClient.tsx +87 -0
  75. package/components/views/HomeClient.tsx +79 -0
  76. package/components/views/KnowledgeBookClient.tsx +127 -0
  77. package/components/views/KnowledgeChapterClient.tsx +175 -0
  78. package/components/views/KnowledgeClient.tsx +69 -0
  79. package/components/views/LoginClient.tsx +80 -0
  80. package/components/views/NavDetailClient.tsx +177 -0
  81. package/components/views/SearchClient.tsx +100 -0
  82. package/components/views/TagArchiveClient.tsx +105 -0
  83. package/components/views/TagsClient.tsx +46 -0
  84. package/components/widgets/AboutUs.tsx +180 -0
  85. package/components/widgets/ArticleActionButton.tsx +95 -0
  86. package/components/widgets/BookmarkWidget.tsx +61 -0
  87. package/components/widgets/CategoriesWidget.tsx +47 -0
  88. package/components/widgets/HomeSidebar.tsx +40 -0
  89. package/components/widgets/LikesWidget.tsx +77 -0
  90. package/components/widgets/ShareWidget.tsx +120 -0
  91. package/components/widgets/TagCloud.tsx +94 -0
  92. package/components/widgets/TagsWidget.tsx +160 -0
  93. package/eslint.config.mjs +80 -0
  94. package/lib/auth/githubOAuth.ts +26 -0
  95. package/lib/auth/sessionUser.ts +41 -0
  96. package/lib/mock-api/auth.ts +129 -0
  97. package/lib/mock-api/config.ts +11 -0
  98. package/lib/mock-api/cors.ts +53 -0
  99. package/lib/mock-api/data.ts +998 -0
  100. package/lib/mock-api/match-route.ts +202 -0
  101. package/lib/mock-api/proxy.ts +28 -0
  102. package/lib/providers/client.ts +58 -0
  103. package/lib/providers/materializeProvider.ts +9 -0
  104. package/lib/providers/server.ts +8 -0
  105. package/lib/reactpress/appearance.ts +69 -0
  106. package/lib/reactpress/bootstrap.ts +48 -0
  107. package/lib/reactpress/coercePublishedPage.ts +12 -0
  108. package/lib/reactpress/contentSeo.ts +123 -0
  109. package/lib/reactpress/env.ts +31 -0
  110. package/lib/reactpress/feedFooterPlacement.ts +16 -0
  111. package/lib/reactpress/fetchSetting.ts +19 -0
  112. package/lib/reactpress/homeFooter.tsx +27 -0
  113. package/lib/reactpress/layout-shell.tsx +19 -0
  114. package/lib/reactpress/materializeBootstrapCatalog.ts +31 -0
  115. package/lib/reactpress/normalizeApiResponse.ts +25 -0
  116. package/lib/reactpress/providers.tsx +199 -0
  117. package/lib/reactpress/resolveSiteNav.ts +56 -0
  118. package/lib/reactpress/seoMetadata.ts +82 -0
  119. package/lib/reactpress/serverLocale.ts +43 -0
  120. package/lib/reactpress/siteMetadata.ts +151 -0
  121. package/lib/reactpress/staticParams.ts +46 -0
  122. package/lib/reactpress/suggestionsContent.ts +48 -0
  123. package/lib/reactpress/useFeedFooterPlacement.ts +22 -0
  124. package/lib/reactpress/useSiteNavConfig.ts +39 -0
  125. package/lib/utils/archiveBanner.ts +8 -0
  126. package/lib/utils/colors.ts +119 -0
  127. package/lib/utils/comment.ts +46 -0
  128. package/lib/utils/headerPosition.ts +55 -0
  129. package/lib/utils/icons.tsx +238 -0
  130. package/lib/utils/reading.ts +16 -0
  131. package/lib/utils/scroll.ts +8 -0
  132. package/lib/utils/tagCloud.ts +222 -0
  133. package/next.config.js +103 -0
  134. package/package.json +98 -0
  135. package/postcss.config.js +5 -0
  136. package/prettier.config.js +10 -0
  137. package/public/apple-touch-icon.png +0 -0
  138. package/public/cli.png +0 -0
  139. package/public/favicon-32.png +0 -0
  140. package/public/favicon.ico +0 -0
  141. package/public/home-dark.png +0 -0
  142. package/public/home-light.png +0 -0
  143. package/public/icon-192.png +0 -0
  144. package/public/icon-512.png +0 -0
  145. package/public/logo-400.png +0 -0
  146. package/public/logo.png +0 -0
  147. package/public/mobile.png +0 -0
  148. package/public/pad.png +0 -0
  149. package/public/site.webmanifest +19 -0
  150. package/scripts/build-mock.mjs +139 -0
  151. package/scripts/dev.mjs +221 -0
  152. package/scripts/ensure-env.mjs +100 -0
  153. package/scripts/remote-dev.mjs +143 -0
  154. package/scripts/resolve-api-env.mjs +114 -0
  155. package/scripts/set-publish-version.mjs +31 -0
  156. package/scripts/smoke-pages.mjs +113 -0
  157. package/scripts/start-mock.mjs +79 -0
  158. package/scripts/verify-toolkit.mjs +37 -0
  159. package/svg.d.ts +6 -0
  160. package/theme.json +123 -0
  161. package/theme.manifest.schema.json +265 -0
  162. package/tsconfig.json +41 -0
  163. 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
@@ -0,0 +1,6 @@
1
+ declare module '*.svg' {
2
+ import type { FC, SVGProps } from 'react'
3
+
4
+ const ReactComponent: FC<SVGProps<SVGSVGElement>>
5
+ export default ReactComponent
6
+ }
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
+ }