@meith/web 0.16.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 (511) hide show
  1. package/LICENSE.md +165 -0
  2. package/app/(auth)/layout.tsx +6 -0
  3. package/app/(auth)/login/page.tsx +106 -0
  4. package/app/(auth)/login/verify/page.tsx +55 -0
  5. package/app/(auth)/register/page.tsx +83 -0
  6. package/app/(auth)/reset/confirm/page.tsx +45 -0
  7. package/app/(auth)/reset/page.tsx +22 -0
  8. package/app/(auth)/verify/page.tsx +44 -0
  9. package/app/(auth)/verify/resend/page.tsx +51 -0
  10. package/app/(board)/[slug]/feed.xml/route.ts +48 -0
  11. package/app/(board)/[slug]/new/page.tsx +112 -0
  12. package/app/(board)/[slug]/page.tsx +403 -0
  13. package/app/(board)/discover/[view]/page.tsx +115 -0
  14. package/app/(board)/discover/page.tsx +5 -0
  15. package/app/(board)/layout.tsx +22 -0
  16. package/app/(board)/member/[id]/page.tsx +187 -0
  17. package/app/(board)/member/[id]/reputation/page.tsx +195 -0
  18. package/app/(board)/member/by-name/[name]/page.tsx +32 -0
  19. package/app/(board)/messages/[id]/page.tsx +103 -0
  20. package/app/(board)/messages/compose/page.tsx +60 -0
  21. package/app/(board)/messages/layout.tsx +5 -0
  22. package/app/(board)/messages/page.tsx +179 -0
  23. package/app/(board)/modcp/forums/page.tsx +59 -0
  24. package/app/(board)/modcp/ip/page.tsx +158 -0
  25. package/app/(board)/modcp/layout.tsx +5 -0
  26. package/app/(board)/modcp/log/page.tsx +98 -0
  27. package/app/(board)/modcp/page.tsx +112 -0
  28. package/app/(board)/moderation/layout.tsx +5 -0
  29. package/app/(board)/moderation/page.tsx +111 -0
  30. package/app/(board)/moderation/reports/page.tsx +193 -0
  31. package/app/(board)/moderation/warn/page.tsx +158 -0
  32. package/app/(board)/notifications/layout.tsx +5 -0
  33. package/app/(board)/notifications/page.tsx +144 -0
  34. package/app/(board)/notifications/preferences/page.tsx +65 -0
  35. package/app/(board)/online/page.tsx +119 -0
  36. package/app/(board)/page.tsx +195 -0
  37. package/app/(board)/plugins/[key]/[[...path]]/page.tsx +72 -0
  38. package/app/(board)/privacy/page.tsx +12 -0
  39. package/app/(board)/report/page.tsx +71 -0
  40. package/app/(board)/rules/page.tsx +12 -0
  41. package/app/(board)/search/[token]/page.tsx +130 -0
  42. package/app/(board)/search/page.tsx +289 -0
  43. package/app/(board)/stats/page.tsx +202 -0
  44. package/app/(board)/subscriptions/layout.tsx +5 -0
  45. package/app/(board)/subscriptions/page.tsx +142 -0
  46. package/app/(board)/terms/page.tsx +12 -0
  47. package/app/(board)/thread/[slug]/edit/page.tsx +107 -0
  48. package/app/(board)/thread/[slug]/feed.xml/route.ts +42 -0
  49. package/app/(board)/thread/[slug]/page.tsx +707 -0
  50. package/app/(board)/thread/[slug]/poll/page.tsx +41 -0
  51. package/app/(board)/thread/[slug]/post/[postId]/history/page.tsx +160 -0
  52. package/app/(board)/thread/[slug]/reply/page.tsx +136 -0
  53. package/app/(board)/unsubscribe/page.tsx +73 -0
  54. package/app/(board)/usercp/avatar/page.tsx +56 -0
  55. package/app/(board)/usercp/contacts/page.tsx +141 -0
  56. package/app/(board)/usercp/email/confirm/page.tsx +52 -0
  57. package/app/(board)/usercp/layout.tsx +23 -0
  58. package/app/(board)/usercp/options/page.tsx +56 -0
  59. package/app/(board)/usercp/page.tsx +71 -0
  60. package/app/(board)/usercp/profile/page.tsx +81 -0
  61. package/app/(board)/usercp/security/page.tsx +238 -0
  62. package/app/(board)/usercp/security/verify/page.tsx +92 -0
  63. package/app/(board)/usercp/signature/page.tsx +67 -0
  64. package/app/admin/antispam/page.tsx +155 -0
  65. package/app/admin/api/marketplace/screenshot/route.ts +68 -0
  66. package/app/admin/api/plugins/[key]/[...path]/route.ts +27 -0
  67. package/app/admin/api-tokens/page.tsx +129 -0
  68. package/app/admin/content/announcements/page.tsx +110 -0
  69. package/app/admin/content/attachments/page.tsx +199 -0
  70. package/app/admin/content/navigation/page.tsx +123 -0
  71. package/app/admin/content/page.tsx +170 -0
  72. package/app/admin/forums/[id]/page.tsx +96 -0
  73. package/app/admin/forums/[id]/permissions/page.tsx +108 -0
  74. package/app/admin/forums/page.tsx +45 -0
  75. package/app/admin/groups/[id]/page.tsx +133 -0
  76. package/app/admin/groups/memberships/page.tsx +56 -0
  77. package/app/admin/groups/page.tsx +104 -0
  78. package/app/admin/groups/promotions/page.tsx +143 -0
  79. package/app/admin/layout.tsx +86 -0
  80. package/app/admin/log/page.tsx +116 -0
  81. package/app/admin/page.tsx +206 -0
  82. package/app/admin/plugins/[key]/[[...path]]/page.tsx +276 -0
  83. package/app/admin/plugins/browse/page.tsx +70 -0
  84. package/app/admin/plugins/page.tsx +165 -0
  85. package/app/admin/security/page.tsx +124 -0
  86. package/app/admin/settings/page.tsx +190 -0
  87. package/app/admin/system/page.tsx +303 -0
  88. package/app/admin/themes/[key]/page.tsx +110 -0
  89. package/app/admin/themes/browse/page.tsx +70 -0
  90. package/app/admin/themes/page.tsx +163 -0
  91. package/app/admin/users/[id]/merge/page.tsx +136 -0
  92. package/app/admin/users/[id]/page.tsx +208 -0
  93. package/app/admin/users/mail/page.tsx +68 -0
  94. package/app/admin/users/page.tsx +260 -0
  95. package/app/admin/users/prune/page.tsx +186 -0
  96. package/app/api/health/route.ts +11 -0
  97. package/app/api/metrics/route.ts +66 -0
  98. package/app/api/plugins/[key]/[...path]/route.ts +27 -0
  99. package/app/api/read/all/route.ts +15 -0
  100. package/app/api/read/forum/[id]/route.ts +29 -0
  101. package/app/api/read/thread/[id]/route.ts +40 -0
  102. package/app/api/ready/route.ts +63 -0
  103. package/app/api/system/tick/route.ts +77 -0
  104. package/app/api/v1/[...path]/route.ts +208 -0
  105. package/app/api/v1/openapi.json/route.ts +12 -0
  106. package/app/apple-icon.ts +11 -0
  107. package/app/atom.xml/route.ts +7 -0
  108. package/app/attachment/[id]/route.ts +10 -0
  109. package/app/attachment/[id]/thumb/route.ts +10 -0
  110. package/app/auth/passkey/options/route.ts +145 -0
  111. package/app/auth/passkey/verify/route.ts +241 -0
  112. package/app/auth/resume/route.ts +55 -0
  113. package/app/auth/sso/[provider]/callback/route.ts +173 -0
  114. package/app/auth/sso/[provider]/go/route.ts +57 -0
  115. package/app/auth/sso/[provider]/route.ts +105 -0
  116. package/app/avatar/[id]/route.ts +31 -0
  117. package/app/brand/icon-192.png/route.ts +7 -0
  118. package/app/brand/icon-512.png/route.ts +7 -0
  119. package/app/brand/icon-maskable.png/route.ts +7 -0
  120. package/app/demo/stripe/[[...path]]/route.ts +27 -0
  121. package/app/error.tsx +15 -0
  122. package/app/feed.xml/route.ts +7 -0
  123. package/app/forumdisplay.php/page.tsx +11 -0
  124. package/app/group/[id]/badge/[scheme]/route.ts +26 -0
  125. package/app/icon.ts +13 -0
  126. package/app/install/page.tsx +221 -0
  127. package/app/jump/page.tsx +28 -0
  128. package/app/layout.tsx +124 -0
  129. package/app/logo/[scheme]/route.ts +24 -0
  130. package/app/manifest.ts +43 -0
  131. package/app/member.php/page.tsx +11 -0
  132. package/app/memberlist.php/page.tsx +11 -0
  133. package/app/not-found.tsx +14 -0
  134. package/app/opengraph-image.ts +11 -0
  135. package/app/redirect/page.tsx +44 -0
  136. package/app/robots.txt/route.ts +35 -0
  137. package/app/showthread.php/page.tsx +11 -0
  138. package/app/sitemap/[page]/route.ts +64 -0
  139. package/app/sitemap.xml/route.ts +31 -0
  140. package/app/twitter-image.ts +11 -0
  141. package/app/viewforum.php/page.tsx +11 -0
  142. package/app/viewtopic.php/page.tsx +11 -0
  143. package/bin/forum-web.mjs +241 -0
  144. package/components.json +21 -0
  145. package/instrumentation.ts +18 -0
  146. package/next.config.mjs +182 -0
  147. package/package.json +99 -0
  148. package/postcss.config.mjs +7 -0
  149. package/proxy.ts +160 -0
  150. package/src/components/account/active-sessions.tsx +87 -0
  151. package/src/components/account/avatar-form.tsx +119 -0
  152. package/src/components/account/credential-proof-form.tsx +76 -0
  153. package/src/components/account/logout-form.tsx +14 -0
  154. package/src/components/account/notification-forms.tsx +157 -0
  155. package/src/components/account/passkey-enrol.tsx +81 -0
  156. package/src/components/account/passkey-proof-button.tsx +47 -0
  157. package/src/components/account/push-device-form.tsx +193 -0
  158. package/src/components/account/relation-forms.tsx +69 -0
  159. package/src/components/account/reputation-forms.tsx +124 -0
  160. package/src/components/account/security-activity.tsx +46 -0
  161. package/src/components/account/sign-in-methods.tsx +166 -0
  162. package/src/components/account/subscription-forms.tsx +151 -0
  163. package/src/components/account/two-factor-forms.tsx +226 -0
  164. package/src/components/account/usercp-forms.tsx +387 -0
  165. package/src/components/account/usercp-nav.tsx +14 -0
  166. package/src/components/account/usercp-shell.tsx +34 -0
  167. package/src/components/admin/admin-forms.tsx +92 -0
  168. package/src/components/admin/admin-nav.tsx +52 -0
  169. package/src/components/admin/admin-undo.tsx +32 -0
  170. package/src/components/admin/api-token-forms.tsx +89 -0
  171. package/src/components/admin/badge-forms.tsx +97 -0
  172. package/src/components/admin/branding-forms.tsx +102 -0
  173. package/src/components/admin/content-forms.tsx +712 -0
  174. package/src/components/admin/form-bits.tsx +11 -0
  175. package/src/components/admin/forum-forms.tsx +524 -0
  176. package/src/components/admin/forum-tree.tsx +454 -0
  177. package/src/components/admin/group-forms.tsx +646 -0
  178. package/src/components/admin/mail-test-card.tsx +90 -0
  179. package/src/components/admin/marketplace-forms.tsx +30 -0
  180. package/src/components/admin/marketplace-listing.tsx +123 -0
  181. package/src/components/admin/navigation-forms.tsx +229 -0
  182. package/src/components/admin/navigation-tree.tsx +442 -0
  183. package/src/components/admin/oklch-picker.tsx +180 -0
  184. package/src/components/admin/plugin-forms.tsx +225 -0
  185. package/src/components/admin/settings-form.tsx +148 -0
  186. package/src/components/admin/system-forms.tsx +169 -0
  187. package/src/components/admin/theme-changes.tsx +354 -0
  188. package/src/components/admin/theme-forms.tsx +636 -0
  189. package/src/components/admin/theme-palette.tsx +119 -0
  190. package/src/components/admin/theme-preview.tsx +461 -0
  191. package/src/components/admin/user-bulk-toolbar.tsx +99 -0
  192. package/src/components/admin/user-forms.tsx +488 -0
  193. package/src/components/auth/auth-page.tsx +45 -0
  194. package/src/components/auth/form-controls.tsx +117 -0
  195. package/src/components/auth/login-form.tsx +47 -0
  196. package/src/components/auth/passkey-client.ts +171 -0
  197. package/src/components/auth/passkey-second-factor.tsx +69 -0
  198. package/src/components/auth/passkey-sign-in.tsx +61 -0
  199. package/src/components/auth/register-form.tsx +136 -0
  200. package/src/components/auth/resend-verification-form.tsx +34 -0
  201. package/src/components/auth/reset-confirm-form.tsx +43 -0
  202. package/src/components/auth/reset-request-form.tsx +35 -0
  203. package/src/components/auth/second-factor-form.tsx +51 -0
  204. package/src/components/auth/sso-buttons.tsx +47 -0
  205. package/src/components/board/live-region.tsx +74 -0
  206. package/src/components/board/search-off-notice.tsx +18 -0
  207. package/src/components/board/section-page.tsx +114 -0
  208. package/src/components/content/attachment-field.tsx +51 -0
  209. package/src/components/content/composer-field.ts +31 -0
  210. package/src/components/content/composer-ids.ts +6 -0
  211. package/src/components/content/composer-intents.tsx +27 -0
  212. package/src/components/content/composer-recovery.tsx +215 -0
  213. package/src/components/content/edit-post-form.tsx +121 -0
  214. package/src/components/content/markdown-editor.tsx +590 -0
  215. package/src/components/content/markdown-syntax.ts +53 -0
  216. package/src/components/content/mention-token.ts +23 -0
  217. package/src/components/content/multiquote-button.tsx +32 -0
  218. package/src/components/content/multiquote-selection.tsx +105 -0
  219. package/src/components/content/multiquote.ts +125 -0
  220. package/src/components/content/new-thread-form.tsx +169 -0
  221. package/src/components/content/poll-edit-form.tsx +96 -0
  222. package/src/components/content/poll.tsx +89 -0
  223. package/src/components/content/quote-in-place.tsx +51 -0
  224. package/src/components/content/reply-form.tsx +113 -0
  225. package/src/components/content/thanks-button.tsx +65 -0
  226. package/src/components/content/thread-rating.tsx +143 -0
  227. package/src/components/install/install-form.tsx +497 -0
  228. package/src/components/legal/legal-document.tsx +22 -0
  229. package/src/components/messages/message-forms.tsx +152 -0
  230. package/src/components/moderation/inline-moderation-form.tsx +158 -0
  231. package/src/components/moderation/modcp-nav.tsx +15 -0
  232. package/src/components/moderation/modcp-shell.tsx +41 -0
  233. package/src/components/moderation/queue-form.tsx +96 -0
  234. package/src/components/moderation/report-forms.tsx +120 -0
  235. package/src/components/moderation/signature-lock-form.tsx +83 -0
  236. package/src/components/moderation/thread-surgery-form.tsx +96 -0
  237. package/src/components/moderation/thread-tools-form.tsx +137 -0
  238. package/src/components/moderation/warning-forms.tsx +125 -0
  239. package/src/components/profile/custom-field.tsx +79 -0
  240. package/src/components/shell/appearance-icons.tsx +35 -0
  241. package/src/components/shell/board-notice.tsx +21 -0
  242. package/src/components/shell/copy-record.ts +20 -0
  243. package/src/components/shell/copy.tsx +17 -0
  244. package/src/components/shell/crash-notice.tsx +86 -0
  245. package/src/components/shell/demo-banner.tsx +49 -0
  246. package/src/components/shell/group-name-style.tsx +6 -0
  247. package/src/components/shell/measure.ts +1 -0
  248. package/src/components/shell/notification-menu.tsx +361 -0
  249. package/src/components/shell/offline-notice.tsx +29 -0
  250. package/src/components/shell/onboarding-banner.tsx +39 -0
  251. package/src/components/shell/page-shell.tsx +167 -0
  252. package/src/components/shell/panel-list.tsx +95 -0
  253. package/src/components/shell/panel-nav.tsx +40 -0
  254. package/src/components/shell/panel-overview.tsx +81 -0
  255. package/src/components/shell/panel-page.tsx +96 -0
  256. package/src/components/shell/panel-pagination.tsx +28 -0
  257. package/src/components/shell/panel-shell.tsx +31 -0
  258. package/src/components/shell/theme-runtime-style.tsx +6 -0
  259. package/src/components/shell/theme-switcher.tsx +97 -0
  260. package/src/components/shell/timezone-probe.tsx +45 -0
  261. package/src/components/shell/view-tabs.tsx +60 -0
  262. package/src/config.ts +13 -0
  263. package/src/server/absent-services.ts +29 -0
  264. package/src/server/admin-actions.ts +171 -0
  265. package/src/server/admin-settings-actions.ts +83 -0
  266. package/src/server/admin-undo-actions.ts +70 -0
  267. package/src/server/admin-undo.ts +75 -0
  268. package/src/server/admin.ts +112 -0
  269. package/src/server/announcements.ts +82 -0
  270. package/src/server/antispam.ts +310 -0
  271. package/src/server/api/content.ts +486 -0
  272. package/src/server/api/http.ts +119 -0
  273. package/src/server/api/members.ts +109 -0
  274. package/src/server/api/messages.ts +113 -0
  275. package/src/server/api/registry.ts +30 -0
  276. package/src/server/api/search.ts +98 -0
  277. package/src/server/api/subscriptions.ts +106 -0
  278. package/src/server/api-auth.ts +66 -0
  279. package/src/server/api-token-actions.ts +85 -0
  280. package/src/server/api-tokens-admin.ts +95 -0
  281. package/src/server/appearance-actions.ts +39 -0
  282. package/src/server/attachment-download.ts +43 -0
  283. package/src/server/attachment-embed.ts +63 -0
  284. package/src/server/attachment-upload-actions.ts +92 -0
  285. package/src/server/attachments.ts +257 -0
  286. package/src/server/auth-actions.ts +479 -0
  287. package/src/server/auth-config.ts +61 -0
  288. package/src/server/auth-events.ts +92 -0
  289. package/src/server/auth-form-state.ts +14 -0
  290. package/src/server/auth-mail.ts +104 -0
  291. package/src/server/avatars.ts +83 -0
  292. package/src/server/board-latest-actions.ts +7 -0
  293. package/src/server/board-latest.tsx +103 -0
  294. package/src/server/board-offline.ts +23 -0
  295. package/src/server/board-url.ts +14 -0
  296. package/src/server/brand-assets.ts +151 -0
  297. package/src/server/brand-mark.tsx +110 -0
  298. package/src/server/branding-actions.ts +53 -0
  299. package/src/server/branding.ts +101 -0
  300. package/src/server/cache-targets.ts +13 -0
  301. package/src/server/code-highlighting.ts +33 -0
  302. package/src/server/container.ts +402 -0
  303. package/src/server/content-actions.ts +459 -0
  304. package/src/server/content-admin-actions.ts +492 -0
  305. package/src/server/content-admin.ts +95 -0
  306. package/src/server/content-security-policy.ts +35 -0
  307. package/src/server/context.ts +32 -0
  308. package/src/server/cookies.ts +139 -0
  309. package/src/server/credential-proof-actions.ts +42 -0
  310. package/src/server/credential-proof.ts +35 -0
  311. package/src/server/current-location.ts +19 -0
  312. package/src/server/demo-stripe.ts +104 -0
  313. package/src/server/demo-uploads.ts +28 -0
  314. package/src/server/demo.ts +80 -0
  315. package/src/server/discovery.ts +104 -0
  316. package/src/server/embeds.ts +113 -0
  317. package/src/server/error-notice.tsx +41 -0
  318. package/src/server/federation-actions.ts +113 -0
  319. package/src/server/federation.ts +138 -0
  320. package/src/server/feed-builder.ts +72 -0
  321. package/src/server/feed-routes.ts +122 -0
  322. package/src/server/fixture-actor-source.ts +62 -0
  323. package/src/server/fixture-forum-repo.ts +59 -0
  324. package/src/server/fixture-member-profile-repo.ts +30 -0
  325. package/src/server/fixture-post-repo.ts +108 -0
  326. package/src/server/fixture-thread-repo.ts +98 -0
  327. package/src/server/form-state-reporter.ts +34 -0
  328. package/src/server/form-values.ts +23 -0
  329. package/src/server/forum-admin-actions.ts +353 -0
  330. package/src/server/forum-admin.ts +98 -0
  331. package/src/server/group-admin-actions.ts +341 -0
  332. package/src/server/group-admin.ts +98 -0
  333. package/src/server/group-badge-actions.ts +61 -0
  334. package/src/server/group-badge.ts +62 -0
  335. package/src/server/group-identity.ts +104 -0
  336. package/src/server/i18n-catalogs.ts +42 -0
  337. package/src/server/i18n.ts +69 -0
  338. package/src/server/image-upload.ts +109 -0
  339. package/src/server/inline-moderation-actions.ts +129 -0
  340. package/src/server/install-actions.ts +124 -0
  341. package/src/server/install.ts +281 -0
  342. package/src/server/legacy-redirect.ts +83 -0
  343. package/src/server/legal.ts +43 -0
  344. package/src/server/location-header.ts +37 -0
  345. package/src/server/mail-brand.ts +43 -0
  346. package/src/server/mail-health.ts +41 -0
  347. package/src/server/mail-send.ts +44 -0
  348. package/src/server/mail-test-actions.ts +67 -0
  349. package/src/server/mail-test.ts +56 -0
  350. package/src/server/markdown-pipeline.ts +31 -0
  351. package/src/server/marketplace-actions.ts +47 -0
  352. package/src/server/marketplace-admin.ts +260 -0
  353. package/src/server/mention-actions.ts +21 -0
  354. package/src/server/mention-search.ts +35 -0
  355. package/src/server/message-actions.ts +110 -0
  356. package/src/server/messages.ts +141 -0
  357. package/src/server/modcp.ts +125 -0
  358. package/src/server/moderation-actions.ts +165 -0
  359. package/src/server/navigation-admin-actions.ts +217 -0
  360. package/src/server/navigation.ts +112 -0
  361. package/src/server/nonce.ts +13 -0
  362. package/src/server/notification-actions.ts +84 -0
  363. package/src/server/notification-audience.ts +10 -0
  364. package/src/server/notification-menu-actions.ts +82 -0
  365. package/src/server/notification-menu.ts +96 -0
  366. package/src/server/notifications.ts +93 -0
  367. package/src/server/onboarding-actions.ts +27 -0
  368. package/src/server/onboarding.ts +89 -0
  369. package/src/server/page-metadata.ts +14 -0
  370. package/src/server/passkey-challenge.ts +42 -0
  371. package/src/server/plugin-admin-actions.ts +232 -0
  372. package/src/server/plugin-admin.ts +276 -0
  373. package/src/server/plugin-host.ts +143 -0
  374. package/src/server/plugin-pages.ts +192 -0
  375. package/src/server/plugin-panel.ts +49 -0
  376. package/src/server/plugin-routes.ts +298 -0
  377. package/src/server/plugin-view.tsx +58 -0
  378. package/src/server/poll-actions.ts +104 -0
  379. package/src/server/poll-scope.ts +52 -0
  380. package/src/server/post-notifications.ts +71 -0
  381. package/src/server/post-scope.ts +86 -0
  382. package/src/server/presence.ts +177 -0
  383. package/src/server/profile-fields.ts +112 -0
  384. package/src/server/push-actions.ts +113 -0
  385. package/src/server/push.ts +40 -0
  386. package/src/server/redirect-back.ts +15 -0
  387. package/src/server/registration.ts +7 -0
  388. package/src/server/relation-actions.ts +89 -0
  389. package/src/server/relations.ts +45 -0
  390. package/src/server/reply-core.ts +173 -0
  391. package/src/server/report-actions.ts +165 -0
  392. package/src/server/report-scope.ts +21 -0
  393. package/src/server/reputation-actions.ts +146 -0
  394. package/src/server/reputation-target.ts +49 -0
  395. package/src/server/reputation.ts +70 -0
  396. package/src/server/request-fingerprint.ts +65 -0
  397. package/src/server/safe-path.ts +16 -0
  398. package/src/server/same-origin.ts +45 -0
  399. package/src/server/search-page.ts +313 -0
  400. package/src/server/search.ts +45 -0
  401. package/src/server/secret-auth.ts +20 -0
  402. package/src/server/see-other.ts +11 -0
  403. package/src/server/seed-board.ts +417 -0
  404. package/src/server/session-actions.ts +78 -0
  405. package/src/server/session-cookies.ts +124 -0
  406. package/src/server/session-key.ts +17 -0
  407. package/src/server/settings.ts +47 -0
  408. package/src/server/signatures.ts +62 -0
  409. package/src/server/sso-handshake.ts +79 -0
  410. package/src/server/stats.ts +63 -0
  411. package/src/server/subscription-actions.ts +161 -0
  412. package/src/server/surgery-actions.ts +180 -0
  413. package/src/server/syndication.ts +77 -0
  414. package/src/server/system-admin-actions.ts +134 -0
  415. package/src/server/system-admin.ts +90 -0
  416. package/src/server/test-container.ts +114 -0
  417. package/src/server/theme-admin-actions.ts +242 -0
  418. package/src/server/theme-admin.ts +203 -0
  419. package/src/server/theme-runtime.ts +89 -0
  420. package/src/server/theme-style.ts +279 -0
  421. package/src/server/theme.ts +52 -0
  422. package/src/server/thread-core.ts +207 -0
  423. package/src/server/thread-rating-actions.ts +55 -0
  424. package/src/server/thread-tool-actions.ts +154 -0
  425. package/src/server/two-factor-actions.ts +178 -0
  426. package/src/server/two-factor.ts +86 -0
  427. package/src/server/upgrade-notice.ts +41 -0
  428. package/src/server/user-admin-actions.ts +572 -0
  429. package/src/server/user-admin.ts +165 -0
  430. package/src/server/usercp-actions.ts +263 -0
  431. package/src/server/usercp-mail.ts +120 -0
  432. package/src/server/viewer-preferences.ts +64 -0
  433. package/src/server/warning-actions.ts +122 -0
  434. package/src/styles/globals.css +969 -0
  435. package/src/theme/contract.fixture.ts +793 -0
  436. package/src/view/account-copy.ts +351 -0
  437. package/src/view/admin-content-copy.ts +66 -0
  438. package/src/view/admin-copy.ts +106 -0
  439. package/src/view/admin-forum-tree-copy.ts +40 -0
  440. package/src/view/admin-group-copy.ts +80 -0
  441. package/src/view/admin-log.ts +58 -0
  442. package/src/view/admin-nav.ts +164 -0
  443. package/src/view/admin-navigation-copy.ts +58 -0
  444. package/src/view/admin-panel-copy.ts +194 -0
  445. package/src/view/admin-settings.ts +173 -0
  446. package/src/view/admin-theme-copy.ts +229 -0
  447. package/src/view/admin-user-copy.ts +96 -0
  448. package/src/view/attachments.ts +51 -0
  449. package/src/view/auth-copy.ts +147 -0
  450. package/src/view/board-index.ts +167 -0
  451. package/src/view/board-latest.ts +101 -0
  452. package/src/view/breadcrumb.ts +50 -0
  453. package/src/view/contacts.ts +85 -0
  454. package/src/view/content-copy.ts +118 -0
  455. package/src/view/contrast.ts +286 -0
  456. package/src/view/copy.ts +101 -0
  457. package/src/view/count.ts +8 -0
  458. package/src/view/discovery-view.ts +74 -0
  459. package/src/view/editor-toolbar.ts +84 -0
  460. package/src/view/error-notice.ts +52 -0
  461. package/src/view/feed.ts +130 -0
  462. package/src/view/forum-cursor.ts +54 -0
  463. package/src/view/forum-display.ts +152 -0
  464. package/src/view/forum-jump.ts +60 -0
  465. package/src/view/forum-notice.ts +24 -0
  466. package/src/view/inline-moderation.ts +87 -0
  467. package/src/view/install-copy.ts +95 -0
  468. package/src/view/legal.ts +58 -0
  469. package/src/view/locale.ts +40 -0
  470. package/src/view/marketplace-panel.ts +51 -0
  471. package/src/view/member-identity.ts +23 -0
  472. package/src/view/member-profile.ts +65 -0
  473. package/src/view/messages.ts +222 -0
  474. package/src/view/metadata.ts +75 -0
  475. package/src/view/modcp-nav.ts +76 -0
  476. package/src/view/moderation-copy.ts +84 -0
  477. package/src/view/moderation-queue.ts +67 -0
  478. package/src/view/navigation-arrange.ts +188 -0
  479. package/src/view/navigation.ts +173 -0
  480. package/src/view/notification-menu.ts +202 -0
  481. package/src/view/notifications.ts +84 -0
  482. package/src/view/onboarding.ts +26 -0
  483. package/src/view/pager.ts +191 -0
  484. package/src/view/panel-nav.ts +203 -0
  485. package/src/view/plugin-panel.ts +42 -0
  486. package/src/view/post-form.ts +61 -0
  487. package/src/view/post-link.ts +29 -0
  488. package/src/view/presence.ts +99 -0
  489. package/src/view/promotion-rules.ts +52 -0
  490. package/src/view/redirect-notice.ts +21 -0
  491. package/src/view/reputation.ts +107 -0
  492. package/src/view/search-controls.ts +103 -0
  493. package/src/view/search-results.ts +291 -0
  494. package/src/view/security-activity.ts +73 -0
  495. package/src/view/setting-groups.ts +43 -0
  496. package/src/view/shell.ts +164 -0
  497. package/src/view/slug-id.ts +12 -0
  498. package/src/view/sso-hand-off.ts +44 -0
  499. package/src/view/sso-notices.ts +48 -0
  500. package/src/view/subscriptions.ts +76 -0
  501. package/src/view/theme-draft.ts +167 -0
  502. package/src/view/theme-preference.ts +30 -0
  503. package/src/view/theme-tokens.ts +333 -0
  504. package/src/view/thread-view.ts +280 -0
  505. package/src/view/time.ts +31 -0
  506. package/src/view/timezone.ts +35 -0
  507. package/src/view/two-factor.ts +6 -0
  508. package/src/view/usercp-nav.ts +103 -0
  509. package/src/view/usercp.ts +196 -0
  510. package/src/view/warnings.ts +128 -0
  511. package/src/view/word-filter.ts +5 -0
@@ -0,0 +1,40 @@
1
+ import type { PanelKind } from '@meith/theme-kit'
2
+ import { requireSlot, slotCopy } from '@meith/theme-kit'
3
+
4
+ import { getActor } from '@/server/context'
5
+ import { currentLocation } from '@/server/current-location'
6
+ import { getTranslator } from '@/server/i18n'
7
+ import { filterView, viewerRef } from '@/server/plugin-view'
8
+ import { currentTheme } from '@/server/theme'
9
+ import { buildPanelNavModel, type PanelCounts, type PanelNav } from '@/view/panel-nav'
10
+
11
+ export interface PanelNavProps {
12
+ readonly panel: PanelKind
13
+ readonly nav: PanelNav
14
+ readonly overviewHref: string
15
+ readonly fallbackHref?: string
16
+ readonly counts?: PanelCounts
17
+ }
18
+
19
+ export async function PanelNavRegion(props: PanelNavProps) {
20
+ const theme = await currentTheme()
21
+ const Nav = requireSlot(theme, 'PanelNav')
22
+ const translator = await getTranslator()
23
+
24
+ const model = buildPanelNavModel({
25
+ panel: props.panel,
26
+ nav: props.nav,
27
+ overviewHref: props.overviewHref,
28
+ location: await currentLocation(),
29
+ fallbackHref: props.fallbackHref,
30
+ counts: props.counts,
31
+ t: translator,
32
+ })
33
+
34
+ return (
35
+ <Nav
36
+ {...(await filterView('view.panel-nav', model, viewerRef(await getActor())))}
37
+ copy={slotCopy(theme, 'PanelNav', translator)}
38
+ />
39
+ )
40
+ }
@@ -0,0 +1,81 @@
1
+ import { buttonVariants, Card, CardContent, Empty, EmptyDescription, EmptyTitle } from '@meith/ui'
2
+
3
+ import type { PanelSectionCopy } from '@/view/panel-nav'
4
+
5
+ export interface WaitingItem {
6
+ readonly count: number
7
+ readonly one: string
8
+ readonly many: string
9
+ readonly href: string
10
+ readonly action: string
11
+ }
12
+
13
+ function PanelWaiting({ item }: { item: WaitingItem }) {
14
+ return (
15
+ <Card className="flex-1 border-moderation-pending/50">
16
+ <CardContent className="flex items-center justify-between gap-4 p-4">
17
+ <div>
18
+ <p className="text-2xl font-semibold tabular-nums text-foreground">{item.count}</p>
19
+ <p className="text-sm text-muted-foreground">{item.count === 1 ? item.one : item.many}</p>
20
+ </div>
21
+ <a href={item.href} className={buttonVariants({ variant: 'outline', size: 'sm' })}>
22
+ {item.action}
23
+ </a>
24
+ </CardContent>
25
+ </Card>
26
+ )
27
+ }
28
+
29
+ export function PanelWaitingList({
30
+ items,
31
+ emptyTitle,
32
+ emptyDescription,
33
+ }: {
34
+ readonly items: readonly WaitingItem[]
35
+ readonly emptyTitle: string
36
+ readonly emptyDescription: string
37
+ }) {
38
+ const waiting = items.filter((item) => item.count > 0)
39
+
40
+ if (waiting.length === 0) {
41
+ return (
42
+ <Card>
43
+ <Empty className="py-8">
44
+ <EmptyTitle>{emptyTitle}</EmptyTitle>
45
+ <EmptyDescription>{emptyDescription}</EmptyDescription>
46
+ </Empty>
47
+ </Card>
48
+ )
49
+ }
50
+
51
+ return (
52
+ <div className="flex flex-col gap-3 sm:flex-row">
53
+ {waiting.map((item) => (
54
+ <PanelWaiting key={item.href} item={item} />
55
+ ))}
56
+ </div>
57
+ )
58
+ }
59
+
60
+ export function PanelSectionGrid({ sections }: { sections: readonly PanelSectionCopy[] }) {
61
+ return (
62
+ <ul className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
63
+ {sections.map((section) => (
64
+ <li key={section.href}>
65
+ <Card className="relative h-full transition-colors hover:bg-muted/50">
66
+ <CardContent className="flex flex-col gap-0.5 p-4">
67
+ <a
68
+ href={section.href}
69
+ className="text-sm font-medium text-foreground underline-offset-2 hover:underline"
70
+ >
71
+ <span className="absolute inset-0" />
72
+ {section.title}
73
+ </a>
74
+ <p className="text-xs text-muted-foreground">{section.blurb}</p>
75
+ </CardContent>
76
+ </Card>
77
+ </li>
78
+ ))}
79
+ </ul>
80
+ )
81
+ }
@@ -0,0 +1,96 @@
1
+ import type { PanelKind } from '@meith/theme-kit'
2
+ import { requireSlot, slotCopy } from '@meith/theme-kit'
3
+
4
+ import { getActor } from '@/server/context'
5
+ import { getTranslator } from '@/server/i18n'
6
+ import { filterView, viewerRef } from '@/server/plugin-view'
7
+ import { currentTheme } from '@/server/theme'
8
+
9
+ export interface PanelPageProps {
10
+ readonly title: string
11
+ readonly panel?: PanelKind
12
+ readonly frame?: 'panel' | 'standalone'
13
+ readonly back?: { readonly href: string; readonly label: string }
14
+ readonly lede?: React.ReactNode
15
+ readonly meta?: React.ReactNode
16
+ readonly actions?: React.ReactNode
17
+ readonly width?: 'reading' | 'wide'
18
+ readonly gap?: 'normal' | 'loose'
19
+ readonly children: React.ReactNode
20
+ }
21
+
22
+ export async function PanelPage({
23
+ title,
24
+ panel,
25
+ frame = 'panel',
26
+ back,
27
+ lede,
28
+ meta,
29
+ actions,
30
+ width = 'reading',
31
+ gap = 'normal',
32
+ children,
33
+ }: PanelPageProps) {
34
+ const theme = await currentTheme()
35
+ const Page = requireSlot(theme, 'PanelPage')
36
+
37
+ const model = await filterView(
38
+ 'view.panel-page',
39
+ {
40
+ title,
41
+ panel: panel ?? null,
42
+ frame,
43
+ back: back ?? null,
44
+ width,
45
+ gap,
46
+ regions: {
47
+ ...(lede === undefined ? {} : { lede }),
48
+ ...(meta === undefined ? {} : { meta }),
49
+ ...(actions === undefined ? {} : { actions }),
50
+ },
51
+ },
52
+ viewerRef(await getActor()),
53
+ )
54
+
55
+ return (
56
+ <Page {...model} copy={slotCopy(theme, 'PanelPage', await getTranslator())}>
57
+ {children}
58
+ </Page>
59
+ )
60
+ }
61
+
62
+ export async function PanelSection({
63
+ title,
64
+ description,
65
+ actions,
66
+ id,
67
+ children,
68
+ }: {
69
+ readonly title: string
70
+ readonly description?: React.ReactNode
71
+ readonly actions?: React.ReactNode
72
+ readonly id: string
73
+ readonly children: React.ReactNode
74
+ }) {
75
+ const theme = await currentTheme()
76
+ const Section = requireSlot(theme, 'PanelSection')
77
+
78
+ const model = await filterView(
79
+ 'view.panel-section',
80
+ {
81
+ title,
82
+ headingId: id,
83
+ regions: {
84
+ ...(description === undefined ? {} : { description }),
85
+ ...(actions === undefined ? {} : { actions }),
86
+ },
87
+ },
88
+ viewerRef(await getActor()),
89
+ )
90
+
91
+ return (
92
+ <Section {...model} copy={slotCopy(theme, 'PanelSection', await getTranslator())}>
93
+ {children}
94
+ </Section>
95
+ )
96
+ }
@@ -0,0 +1,28 @@
1
+ import { requireSlot, slotCopy } from '@meith/theme-kit'
2
+
3
+ import { getActor } from '@/server/context'
4
+ import { getTranslator } from '@/server/i18n'
5
+ import { filterView, viewerRef } from '@/server/plugin-view'
6
+ import { currentTheme } from '@/server/theme'
7
+ import { buildOffsetPager, buildPager, type OffsetPagerInput, type PagerInput } from '@/view/pager'
8
+
9
+ export type PanelPaginationProps = OffsetPagerInput | PagerInput
10
+
11
+ function isNumbered(input: PanelPaginationProps): input is OffsetPagerInput {
12
+ return 'total' in input && typeof input.total === 'number' && 'page' in input
13
+ }
14
+
15
+ export async function PanelPagination(input: PanelPaginationProps) {
16
+ const model = isNumbered(input) ? buildOffsetPager(input) : buildPager(input)
17
+ if (model.previousHref === null && model.nextHref === null) return null
18
+
19
+ const theme = await currentTheme()
20
+ const Pagination = requireSlot(theme, 'Pagination')
21
+
22
+ return (
23
+ <Pagination
24
+ {...(await filterView('view.pagination', model, viewerRef(await getActor())))}
25
+ copy={slotCopy(theme, 'Pagination', await getTranslator())}
26
+ />
27
+ )
28
+ }
@@ -0,0 +1,31 @@
1
+ import type { LinkModel, PanelKind } from '@meith/theme-kit'
2
+ import { requireSlot, slotCopy } from '@meith/theme-kit'
3
+
4
+ import { getActor } from '@/server/context'
5
+ import { getTranslator, tr } from '@/server/i18n'
6
+ import { filterView, viewerRef } from '@/server/plugin-view'
7
+ import { currentTheme } from '@/server/theme'
8
+
9
+ export interface PanelShellProps {
10
+ readonly panel: PanelKind
11
+ readonly nav: React.ReactNode
12
+ readonly links?: readonly LinkModel[]
13
+ readonly children: React.ReactNode
14
+ }
15
+
16
+ export async function PanelShell({ panel, nav, links = [], children }: PanelShellProps) {
17
+ const theme = await currentTheme()
18
+ const Shell = requireSlot(theme, 'PanelShell')
19
+
20
+ const model = await filterView(
21
+ 'view.panel-shell',
22
+ { panel, links, linksLabel: await tr('panel.otherPanels'), regions: { nav } },
23
+ viewerRef(await getActor()),
24
+ )
25
+
26
+ return (
27
+ <Shell {...model} copy={slotCopy(theme, 'PanelShell', await getTranslator())}>
28
+ {children}
29
+ </Shell>
30
+ )
31
+ }
@@ -0,0 +1,6 @@
1
+ import { getBoardThemeStyle } from '@/server/theme-runtime'
2
+
3
+ export async function ThemeRuntimeStyle() {
4
+ const { css } = await getBoardThemeStyle()
5
+ return css === '' ? null : <style id="forum-theme-overrides">{css}</style>
6
+ }
@@ -0,0 +1,97 @@
1
+ import { MonitorIcon, MoonIcon, SunIcon } from '@/components/shell/appearance-icons'
2
+ import { BOARD_MEASURE } from '@/components/shell/measure'
3
+ import { setAppearanceAction } from '@/server/appearance-actions'
4
+ import { getTranslator } from '@/server/i18n'
5
+ import { currentColourScheme, currentThemeKey } from '@/server/theme'
6
+ import { getBoardThemeStyle } from '@/server/theme-runtime'
7
+ import { COLOUR_SCHEMES, type ColourSchemePreference } from '@/view/theme-preference'
8
+
9
+ const SCHEME_ICON: Record<ColourSchemePreference, () => React.ReactNode> = {
10
+ light: SunIcon,
11
+ system: MonitorIcon,
12
+ dark: MoonIcon,
13
+ }
14
+
15
+ const SCHEME_LABEL_KEY: Record<ColourSchemePreference, string> = {
16
+ system: 'appearance.scheme.system',
17
+ light: 'appearance.scheme.light',
18
+ dark: 'appearance.scheme.dark',
19
+ }
20
+
21
+ const CONTROL =
22
+ 'h-8 rounded-md border border-border bg-background px-2 text-xs text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
23
+
24
+ export async function ThemeSwitcher() {
25
+ const [{ choices }, theme, scheme, t] = await Promise.all([
26
+ getBoardThemeStyle(),
27
+ currentThemeKey(),
28
+ currentColourScheme(),
29
+ getTranslator(),
30
+ ])
31
+
32
+ return (
33
+ <div className="border-t border-border bg-card text-card-foreground">
34
+ <div
35
+ className={`${BOARD_MEASURE} flex flex-wrap items-center justify-end gap-x-4 gap-y-2 py-2`}
36
+ >
37
+ <section
38
+ aria-label={t.t('appearance.section')}
39
+ className="flex flex-wrap items-center justify-end gap-x-4 gap-y-2"
40
+ >
41
+ {choices.length > 1 && (
42
+ <form action={setAppearanceAction} className="flex items-center gap-2">
43
+ <label htmlFor="appearance-theme" className="text-xs text-muted-foreground">
44
+ {t.t('appearance.theme')}
45
+ </label>
46
+ <select id="appearance-theme" name="theme" defaultValue={theme} className={CONTROL}>
47
+ {choices.map((choice) => (
48
+ <option key={choice.key} value={choice.key}>
49
+ {choice.title}
50
+ </option>
51
+ ))}
52
+ </select>
53
+ <button
54
+ type="submit"
55
+ className="h-8 rounded-md border border-border px-2.5 text-xs font-medium text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
56
+ >
57
+ {t.t('appearance.apply')}
58
+ </button>
59
+ </form>
60
+ )}
61
+
62
+ <form action={setAppearanceAction}>
63
+ <span
64
+ role="group"
65
+ aria-label={t.t('appearance.schemeGroup')}
66
+ className="inline-flex items-center overflow-hidden rounded-md border border-border text-muted-foreground"
67
+ >
68
+ {COLOUR_SCHEMES.map((option) => {
69
+ const Icon = SCHEME_ICON[option]
70
+ const label = t.t(SCHEME_LABEL_KEY[option])
71
+ const selected = scheme === option
72
+ return (
73
+ <button
74
+ key={option}
75
+ type="submit"
76
+ name="scheme"
77
+ value={option}
78
+ title={label}
79
+ aria-pressed={selected}
80
+ className={`inline-flex h-8 items-center justify-center border-border px-2.5 transition-colors [&:not(:first-child)]:border-l focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring ${
81
+ selected
82
+ ? 'bg-primary text-primary-foreground'
83
+ : 'bg-background hover:bg-accent hover:text-accent-foreground'
84
+ }`}
85
+ >
86
+ <Icon />
87
+ <span className="sr-only">{label}</span>
88
+ </button>
89
+ )
90
+ })}
91
+ </span>
92
+ </form>
93
+ </section>
94
+ </div>
95
+ </div>
96
+ )
97
+ }
@@ -0,0 +1,45 @@
1
+ import { cspNonce } from '@/server/nonce'
2
+ import { getViewerPreferences } from '@/server/viewer-preferences'
3
+ import { TIMEZONE_COOKIE } from '@/view/timezone'
4
+
5
+ const COOKIE_MAX_AGE = 60 * 60 * 24 * 365
6
+
7
+ function probe(applied: string, automatic: boolean): string {
8
+ const name = JSON.stringify(TIMEZONE_COOKIE)
9
+
10
+ return `(function(){try{
11
+ var n=${name},a=${JSON.stringify(applied)},auto=${automatic ? 'true' : 'false'};
12
+ var zone=Intl.DateTimeFormat().resolvedOptions().timeZone;
13
+ if(!zone)return;
14
+ var read=function(){
15
+ var m=document.cookie.match(new RegExp('(^|; )'+n+'=([^;]*)'));
16
+ return m?decodeURIComponent(m[2]):null;
17
+ };
18
+ if(read()!==zone){
19
+ document.cookie=n+'='+encodeURIComponent(zone)+';path=/;max-age=${COOKIE_MAX_AGE};samesite=lax'+(location.protocol==='https:'?';secure':'');
20
+ }
21
+ if(!auto||a===zone||read()!==zone)return;
22
+ var k=n+'.reloaded';
23
+ if(sessionStorage.getItem(k)===zone)return;
24
+ sessionStorage.setItem(k,zone);
25
+ location.reload();
26
+ }catch(e){}})();`
27
+ }
28
+
29
+ export async function TimezoneProbe() {
30
+ let preferences: Awaited<ReturnType<typeof getViewerPreferences>>
31
+ try {
32
+ preferences = await getViewerPreferences()
33
+ } catch {
34
+ return null
35
+ }
36
+
37
+ return (
38
+ <script
39
+ nonce={await cspNonce()}
40
+ dangerouslySetInnerHTML={{
41
+ __html: probe(preferences.timezone, preferences.timezoneIsAutomatic),
42
+ }}
43
+ />
44
+ )
45
+ }
@@ -0,0 +1,60 @@
1
+ import { cn } from '@meith/ui'
2
+
3
+ export interface ViewTab {
4
+ readonly href: string
5
+ readonly label: string
6
+ readonly isCurrent: boolean
7
+ readonly count?: number
8
+ }
9
+
10
+ export function ViewTabs({
11
+ label,
12
+ tabs,
13
+ aside,
14
+ className,
15
+ }: {
16
+ label: string
17
+ tabs: readonly ViewTab[]
18
+ aside?: React.ReactNode
19
+ className?: string
20
+ }) {
21
+ if (tabs.length === 0) return null
22
+
23
+ return (
24
+ <nav aria-label={label} className={cn('flex items-center gap-x-3', className)}>
25
+ <ul className="-my-1.5 flex min-w-0 flex-1 items-center gap-1 overflow-x-auto py-1.5">
26
+ {tabs.map((tab) => (
27
+ <li key={tab.href} className="shrink-0">
28
+ <a
29
+ href={tab.href}
30
+ {...(tab.isCurrent ? { 'aria-current': 'page' as const } : {})}
31
+ className={cn(
32
+ 'inline-flex h-9 items-center gap-1.5 rounded-full px-3.5 text-sm font-medium whitespace-nowrap transition-colors',
33
+ 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',
34
+ tab.isCurrent
35
+ ? 'bg-primary font-semibold text-primary-foreground'
36
+ : 'text-muted-foreground hover:bg-accent hover:text-foreground',
37
+ )}
38
+ >
39
+ {tab.label}
40
+ {tab.count !== undefined && tab.count > 0 && (
41
+ <span
42
+ className={cn(
43
+ 'rounded-full px-1.5 text-xs font-semibold tabular-nums',
44
+ tab.isCurrent ? 'bg-primary-foreground/20' : 'bg-secondary',
45
+ )}
46
+ >
47
+ {tab.count}
48
+ </span>
49
+ )}
50
+ </a>
51
+ </li>
52
+ ))}
53
+ </ul>
54
+
55
+ {aside !== undefined && (
56
+ <span className="shrink-0 text-xs text-muted-foreground">{aside}</span>
57
+ )}
58
+ </nav>
59
+ )
60
+ }
package/src/config.ts ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@meith/web/config` — the surface a board's own `community.config.ts`
3
+ * builds against.
4
+ *
5
+ * A scaffolded board depends on `@meith/web` for the app itself, and imports
6
+ * its board-time registry through this subpath rather than reaching into
7
+ * `@meith/core` directly. That indirection is what lets `@meith/web` change
8
+ * which package actually defines `defineForumConfig` without every board's
9
+ * `community.config.ts` following along.
10
+ */
11
+
12
+ export type { ForumConfig, InstalledPlugin, InstalledTheme, MessageBundle } from '@meith/core'
13
+ export { defineForumConfig } from '@meith/core'
@@ -0,0 +1,29 @@
1
+ export const ABSENT_SERVICES = {
2
+ threadWrites: null,
3
+ postWrites: null,
4
+ moderationQueue: null,
5
+ reports: null,
6
+ threadTools: null,
7
+ threadSurgery: null,
8
+ inlineModeration: null,
9
+ warnings: null,
10
+ warningBans: null,
11
+ modcp: null,
12
+ notifications: null,
13
+ subscriptions: null,
14
+ memberSettings: null,
15
+ profileFields: null,
16
+ messages: null,
17
+ relations: null,
18
+ reputation: null,
19
+ polls: null,
20
+ drafts: null,
21
+ signatures: null,
22
+ adminSessions: null,
23
+ adminLog: null,
24
+ attachments: null,
25
+ avatars: null,
26
+ readState: null,
27
+ threadViews: null,
28
+ scheduler: null,
29
+ } as const