@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.
- package/LICENSE.md +165 -0
- package/app/(auth)/layout.tsx +6 -0
- package/app/(auth)/login/page.tsx +106 -0
- package/app/(auth)/login/verify/page.tsx +55 -0
- package/app/(auth)/register/page.tsx +83 -0
- package/app/(auth)/reset/confirm/page.tsx +45 -0
- package/app/(auth)/reset/page.tsx +22 -0
- package/app/(auth)/verify/page.tsx +44 -0
- package/app/(auth)/verify/resend/page.tsx +51 -0
- package/app/(board)/[slug]/feed.xml/route.ts +48 -0
- package/app/(board)/[slug]/new/page.tsx +112 -0
- package/app/(board)/[slug]/page.tsx +403 -0
- package/app/(board)/discover/[view]/page.tsx +115 -0
- package/app/(board)/discover/page.tsx +5 -0
- package/app/(board)/layout.tsx +22 -0
- package/app/(board)/member/[id]/page.tsx +187 -0
- package/app/(board)/member/[id]/reputation/page.tsx +195 -0
- package/app/(board)/member/by-name/[name]/page.tsx +32 -0
- package/app/(board)/messages/[id]/page.tsx +103 -0
- package/app/(board)/messages/compose/page.tsx +60 -0
- package/app/(board)/messages/layout.tsx +5 -0
- package/app/(board)/messages/page.tsx +179 -0
- package/app/(board)/modcp/forums/page.tsx +59 -0
- package/app/(board)/modcp/ip/page.tsx +158 -0
- package/app/(board)/modcp/layout.tsx +5 -0
- package/app/(board)/modcp/log/page.tsx +98 -0
- package/app/(board)/modcp/page.tsx +112 -0
- package/app/(board)/moderation/layout.tsx +5 -0
- package/app/(board)/moderation/page.tsx +111 -0
- package/app/(board)/moderation/reports/page.tsx +193 -0
- package/app/(board)/moderation/warn/page.tsx +158 -0
- package/app/(board)/notifications/layout.tsx +5 -0
- package/app/(board)/notifications/page.tsx +144 -0
- package/app/(board)/notifications/preferences/page.tsx +65 -0
- package/app/(board)/online/page.tsx +119 -0
- package/app/(board)/page.tsx +195 -0
- package/app/(board)/plugins/[key]/[[...path]]/page.tsx +72 -0
- package/app/(board)/privacy/page.tsx +12 -0
- package/app/(board)/report/page.tsx +71 -0
- package/app/(board)/rules/page.tsx +12 -0
- package/app/(board)/search/[token]/page.tsx +130 -0
- package/app/(board)/search/page.tsx +289 -0
- package/app/(board)/stats/page.tsx +202 -0
- package/app/(board)/subscriptions/layout.tsx +5 -0
- package/app/(board)/subscriptions/page.tsx +142 -0
- package/app/(board)/terms/page.tsx +12 -0
- package/app/(board)/thread/[slug]/edit/page.tsx +107 -0
- package/app/(board)/thread/[slug]/feed.xml/route.ts +42 -0
- package/app/(board)/thread/[slug]/page.tsx +707 -0
- package/app/(board)/thread/[slug]/poll/page.tsx +41 -0
- package/app/(board)/thread/[slug]/post/[postId]/history/page.tsx +160 -0
- package/app/(board)/thread/[slug]/reply/page.tsx +136 -0
- package/app/(board)/unsubscribe/page.tsx +73 -0
- package/app/(board)/usercp/avatar/page.tsx +56 -0
- package/app/(board)/usercp/contacts/page.tsx +141 -0
- package/app/(board)/usercp/email/confirm/page.tsx +52 -0
- package/app/(board)/usercp/layout.tsx +23 -0
- package/app/(board)/usercp/options/page.tsx +56 -0
- package/app/(board)/usercp/page.tsx +71 -0
- package/app/(board)/usercp/profile/page.tsx +81 -0
- package/app/(board)/usercp/security/page.tsx +238 -0
- package/app/(board)/usercp/security/verify/page.tsx +92 -0
- package/app/(board)/usercp/signature/page.tsx +67 -0
- package/app/admin/antispam/page.tsx +155 -0
- package/app/admin/api/marketplace/screenshot/route.ts +68 -0
- package/app/admin/api/plugins/[key]/[...path]/route.ts +27 -0
- package/app/admin/api-tokens/page.tsx +129 -0
- package/app/admin/content/announcements/page.tsx +110 -0
- package/app/admin/content/attachments/page.tsx +199 -0
- package/app/admin/content/navigation/page.tsx +123 -0
- package/app/admin/content/page.tsx +170 -0
- package/app/admin/forums/[id]/page.tsx +96 -0
- package/app/admin/forums/[id]/permissions/page.tsx +108 -0
- package/app/admin/forums/page.tsx +45 -0
- package/app/admin/groups/[id]/page.tsx +133 -0
- package/app/admin/groups/memberships/page.tsx +56 -0
- package/app/admin/groups/page.tsx +104 -0
- package/app/admin/groups/promotions/page.tsx +143 -0
- package/app/admin/layout.tsx +86 -0
- package/app/admin/log/page.tsx +116 -0
- package/app/admin/page.tsx +206 -0
- package/app/admin/plugins/[key]/[[...path]]/page.tsx +276 -0
- package/app/admin/plugins/browse/page.tsx +70 -0
- package/app/admin/plugins/page.tsx +165 -0
- package/app/admin/security/page.tsx +124 -0
- package/app/admin/settings/page.tsx +190 -0
- package/app/admin/system/page.tsx +303 -0
- package/app/admin/themes/[key]/page.tsx +110 -0
- package/app/admin/themes/browse/page.tsx +70 -0
- package/app/admin/themes/page.tsx +163 -0
- package/app/admin/users/[id]/merge/page.tsx +136 -0
- package/app/admin/users/[id]/page.tsx +208 -0
- package/app/admin/users/mail/page.tsx +68 -0
- package/app/admin/users/page.tsx +260 -0
- package/app/admin/users/prune/page.tsx +186 -0
- package/app/api/health/route.ts +11 -0
- package/app/api/metrics/route.ts +66 -0
- package/app/api/plugins/[key]/[...path]/route.ts +27 -0
- package/app/api/read/all/route.ts +15 -0
- package/app/api/read/forum/[id]/route.ts +29 -0
- package/app/api/read/thread/[id]/route.ts +40 -0
- package/app/api/ready/route.ts +63 -0
- package/app/api/system/tick/route.ts +77 -0
- package/app/api/v1/[...path]/route.ts +208 -0
- package/app/api/v1/openapi.json/route.ts +12 -0
- package/app/apple-icon.ts +11 -0
- package/app/atom.xml/route.ts +7 -0
- package/app/attachment/[id]/route.ts +10 -0
- package/app/attachment/[id]/thumb/route.ts +10 -0
- package/app/auth/passkey/options/route.ts +145 -0
- package/app/auth/passkey/verify/route.ts +241 -0
- package/app/auth/resume/route.ts +55 -0
- package/app/auth/sso/[provider]/callback/route.ts +173 -0
- package/app/auth/sso/[provider]/go/route.ts +57 -0
- package/app/auth/sso/[provider]/route.ts +105 -0
- package/app/avatar/[id]/route.ts +31 -0
- package/app/brand/icon-192.png/route.ts +7 -0
- package/app/brand/icon-512.png/route.ts +7 -0
- package/app/brand/icon-maskable.png/route.ts +7 -0
- package/app/demo/stripe/[[...path]]/route.ts +27 -0
- package/app/error.tsx +15 -0
- package/app/feed.xml/route.ts +7 -0
- package/app/forumdisplay.php/page.tsx +11 -0
- package/app/group/[id]/badge/[scheme]/route.ts +26 -0
- package/app/icon.ts +13 -0
- package/app/install/page.tsx +221 -0
- package/app/jump/page.tsx +28 -0
- package/app/layout.tsx +124 -0
- package/app/logo/[scheme]/route.ts +24 -0
- package/app/manifest.ts +43 -0
- package/app/member.php/page.tsx +11 -0
- package/app/memberlist.php/page.tsx +11 -0
- package/app/not-found.tsx +14 -0
- package/app/opengraph-image.ts +11 -0
- package/app/redirect/page.tsx +44 -0
- package/app/robots.txt/route.ts +35 -0
- package/app/showthread.php/page.tsx +11 -0
- package/app/sitemap/[page]/route.ts +64 -0
- package/app/sitemap.xml/route.ts +31 -0
- package/app/twitter-image.ts +11 -0
- package/app/viewforum.php/page.tsx +11 -0
- package/app/viewtopic.php/page.tsx +11 -0
- package/bin/forum-web.mjs +241 -0
- package/components.json +21 -0
- package/instrumentation.ts +18 -0
- package/next.config.mjs +182 -0
- package/package.json +99 -0
- package/postcss.config.mjs +7 -0
- package/proxy.ts +160 -0
- package/src/components/account/active-sessions.tsx +87 -0
- package/src/components/account/avatar-form.tsx +119 -0
- package/src/components/account/credential-proof-form.tsx +76 -0
- package/src/components/account/logout-form.tsx +14 -0
- package/src/components/account/notification-forms.tsx +157 -0
- package/src/components/account/passkey-enrol.tsx +81 -0
- package/src/components/account/passkey-proof-button.tsx +47 -0
- package/src/components/account/push-device-form.tsx +193 -0
- package/src/components/account/relation-forms.tsx +69 -0
- package/src/components/account/reputation-forms.tsx +124 -0
- package/src/components/account/security-activity.tsx +46 -0
- package/src/components/account/sign-in-methods.tsx +166 -0
- package/src/components/account/subscription-forms.tsx +151 -0
- package/src/components/account/two-factor-forms.tsx +226 -0
- package/src/components/account/usercp-forms.tsx +387 -0
- package/src/components/account/usercp-nav.tsx +14 -0
- package/src/components/account/usercp-shell.tsx +34 -0
- package/src/components/admin/admin-forms.tsx +92 -0
- package/src/components/admin/admin-nav.tsx +52 -0
- package/src/components/admin/admin-undo.tsx +32 -0
- package/src/components/admin/api-token-forms.tsx +89 -0
- package/src/components/admin/badge-forms.tsx +97 -0
- package/src/components/admin/branding-forms.tsx +102 -0
- package/src/components/admin/content-forms.tsx +712 -0
- package/src/components/admin/form-bits.tsx +11 -0
- package/src/components/admin/forum-forms.tsx +524 -0
- package/src/components/admin/forum-tree.tsx +454 -0
- package/src/components/admin/group-forms.tsx +646 -0
- package/src/components/admin/mail-test-card.tsx +90 -0
- package/src/components/admin/marketplace-forms.tsx +30 -0
- package/src/components/admin/marketplace-listing.tsx +123 -0
- package/src/components/admin/navigation-forms.tsx +229 -0
- package/src/components/admin/navigation-tree.tsx +442 -0
- package/src/components/admin/oklch-picker.tsx +180 -0
- package/src/components/admin/plugin-forms.tsx +225 -0
- package/src/components/admin/settings-form.tsx +148 -0
- package/src/components/admin/system-forms.tsx +169 -0
- package/src/components/admin/theme-changes.tsx +354 -0
- package/src/components/admin/theme-forms.tsx +636 -0
- package/src/components/admin/theme-palette.tsx +119 -0
- package/src/components/admin/theme-preview.tsx +461 -0
- package/src/components/admin/user-bulk-toolbar.tsx +99 -0
- package/src/components/admin/user-forms.tsx +488 -0
- package/src/components/auth/auth-page.tsx +45 -0
- package/src/components/auth/form-controls.tsx +117 -0
- package/src/components/auth/login-form.tsx +47 -0
- package/src/components/auth/passkey-client.ts +171 -0
- package/src/components/auth/passkey-second-factor.tsx +69 -0
- package/src/components/auth/passkey-sign-in.tsx +61 -0
- package/src/components/auth/register-form.tsx +136 -0
- package/src/components/auth/resend-verification-form.tsx +34 -0
- package/src/components/auth/reset-confirm-form.tsx +43 -0
- package/src/components/auth/reset-request-form.tsx +35 -0
- package/src/components/auth/second-factor-form.tsx +51 -0
- package/src/components/auth/sso-buttons.tsx +47 -0
- package/src/components/board/live-region.tsx +74 -0
- package/src/components/board/search-off-notice.tsx +18 -0
- package/src/components/board/section-page.tsx +114 -0
- package/src/components/content/attachment-field.tsx +51 -0
- package/src/components/content/composer-field.ts +31 -0
- package/src/components/content/composer-ids.ts +6 -0
- package/src/components/content/composer-intents.tsx +27 -0
- package/src/components/content/composer-recovery.tsx +215 -0
- package/src/components/content/edit-post-form.tsx +121 -0
- package/src/components/content/markdown-editor.tsx +590 -0
- package/src/components/content/markdown-syntax.ts +53 -0
- package/src/components/content/mention-token.ts +23 -0
- package/src/components/content/multiquote-button.tsx +32 -0
- package/src/components/content/multiquote-selection.tsx +105 -0
- package/src/components/content/multiquote.ts +125 -0
- package/src/components/content/new-thread-form.tsx +169 -0
- package/src/components/content/poll-edit-form.tsx +96 -0
- package/src/components/content/poll.tsx +89 -0
- package/src/components/content/quote-in-place.tsx +51 -0
- package/src/components/content/reply-form.tsx +113 -0
- package/src/components/content/thanks-button.tsx +65 -0
- package/src/components/content/thread-rating.tsx +143 -0
- package/src/components/install/install-form.tsx +497 -0
- package/src/components/legal/legal-document.tsx +22 -0
- package/src/components/messages/message-forms.tsx +152 -0
- package/src/components/moderation/inline-moderation-form.tsx +158 -0
- package/src/components/moderation/modcp-nav.tsx +15 -0
- package/src/components/moderation/modcp-shell.tsx +41 -0
- package/src/components/moderation/queue-form.tsx +96 -0
- package/src/components/moderation/report-forms.tsx +120 -0
- package/src/components/moderation/signature-lock-form.tsx +83 -0
- package/src/components/moderation/thread-surgery-form.tsx +96 -0
- package/src/components/moderation/thread-tools-form.tsx +137 -0
- package/src/components/moderation/warning-forms.tsx +125 -0
- package/src/components/profile/custom-field.tsx +79 -0
- package/src/components/shell/appearance-icons.tsx +35 -0
- package/src/components/shell/board-notice.tsx +21 -0
- package/src/components/shell/copy-record.ts +20 -0
- package/src/components/shell/copy.tsx +17 -0
- package/src/components/shell/crash-notice.tsx +86 -0
- package/src/components/shell/demo-banner.tsx +49 -0
- package/src/components/shell/group-name-style.tsx +6 -0
- package/src/components/shell/measure.ts +1 -0
- package/src/components/shell/notification-menu.tsx +361 -0
- package/src/components/shell/offline-notice.tsx +29 -0
- package/src/components/shell/onboarding-banner.tsx +39 -0
- package/src/components/shell/page-shell.tsx +167 -0
- package/src/components/shell/panel-list.tsx +95 -0
- package/src/components/shell/panel-nav.tsx +40 -0
- package/src/components/shell/panel-overview.tsx +81 -0
- package/src/components/shell/panel-page.tsx +96 -0
- package/src/components/shell/panel-pagination.tsx +28 -0
- package/src/components/shell/panel-shell.tsx +31 -0
- package/src/components/shell/theme-runtime-style.tsx +6 -0
- package/src/components/shell/theme-switcher.tsx +97 -0
- package/src/components/shell/timezone-probe.tsx +45 -0
- package/src/components/shell/view-tabs.tsx +60 -0
- package/src/config.ts +13 -0
- package/src/server/absent-services.ts +29 -0
- package/src/server/admin-actions.ts +171 -0
- package/src/server/admin-settings-actions.ts +83 -0
- package/src/server/admin-undo-actions.ts +70 -0
- package/src/server/admin-undo.ts +75 -0
- package/src/server/admin.ts +112 -0
- package/src/server/announcements.ts +82 -0
- package/src/server/antispam.ts +310 -0
- package/src/server/api/content.ts +486 -0
- package/src/server/api/http.ts +119 -0
- package/src/server/api/members.ts +109 -0
- package/src/server/api/messages.ts +113 -0
- package/src/server/api/registry.ts +30 -0
- package/src/server/api/search.ts +98 -0
- package/src/server/api/subscriptions.ts +106 -0
- package/src/server/api-auth.ts +66 -0
- package/src/server/api-token-actions.ts +85 -0
- package/src/server/api-tokens-admin.ts +95 -0
- package/src/server/appearance-actions.ts +39 -0
- package/src/server/attachment-download.ts +43 -0
- package/src/server/attachment-embed.ts +63 -0
- package/src/server/attachment-upload-actions.ts +92 -0
- package/src/server/attachments.ts +257 -0
- package/src/server/auth-actions.ts +479 -0
- package/src/server/auth-config.ts +61 -0
- package/src/server/auth-events.ts +92 -0
- package/src/server/auth-form-state.ts +14 -0
- package/src/server/auth-mail.ts +104 -0
- package/src/server/avatars.ts +83 -0
- package/src/server/board-latest-actions.ts +7 -0
- package/src/server/board-latest.tsx +103 -0
- package/src/server/board-offline.ts +23 -0
- package/src/server/board-url.ts +14 -0
- package/src/server/brand-assets.ts +151 -0
- package/src/server/brand-mark.tsx +110 -0
- package/src/server/branding-actions.ts +53 -0
- package/src/server/branding.ts +101 -0
- package/src/server/cache-targets.ts +13 -0
- package/src/server/code-highlighting.ts +33 -0
- package/src/server/container.ts +402 -0
- package/src/server/content-actions.ts +459 -0
- package/src/server/content-admin-actions.ts +492 -0
- package/src/server/content-admin.ts +95 -0
- package/src/server/content-security-policy.ts +35 -0
- package/src/server/context.ts +32 -0
- package/src/server/cookies.ts +139 -0
- package/src/server/credential-proof-actions.ts +42 -0
- package/src/server/credential-proof.ts +35 -0
- package/src/server/current-location.ts +19 -0
- package/src/server/demo-stripe.ts +104 -0
- package/src/server/demo-uploads.ts +28 -0
- package/src/server/demo.ts +80 -0
- package/src/server/discovery.ts +104 -0
- package/src/server/embeds.ts +113 -0
- package/src/server/error-notice.tsx +41 -0
- package/src/server/federation-actions.ts +113 -0
- package/src/server/federation.ts +138 -0
- package/src/server/feed-builder.ts +72 -0
- package/src/server/feed-routes.ts +122 -0
- package/src/server/fixture-actor-source.ts +62 -0
- package/src/server/fixture-forum-repo.ts +59 -0
- package/src/server/fixture-member-profile-repo.ts +30 -0
- package/src/server/fixture-post-repo.ts +108 -0
- package/src/server/fixture-thread-repo.ts +98 -0
- package/src/server/form-state-reporter.ts +34 -0
- package/src/server/form-values.ts +23 -0
- package/src/server/forum-admin-actions.ts +353 -0
- package/src/server/forum-admin.ts +98 -0
- package/src/server/group-admin-actions.ts +341 -0
- package/src/server/group-admin.ts +98 -0
- package/src/server/group-badge-actions.ts +61 -0
- package/src/server/group-badge.ts +62 -0
- package/src/server/group-identity.ts +104 -0
- package/src/server/i18n-catalogs.ts +42 -0
- package/src/server/i18n.ts +69 -0
- package/src/server/image-upload.ts +109 -0
- package/src/server/inline-moderation-actions.ts +129 -0
- package/src/server/install-actions.ts +124 -0
- package/src/server/install.ts +281 -0
- package/src/server/legacy-redirect.ts +83 -0
- package/src/server/legal.ts +43 -0
- package/src/server/location-header.ts +37 -0
- package/src/server/mail-brand.ts +43 -0
- package/src/server/mail-health.ts +41 -0
- package/src/server/mail-send.ts +44 -0
- package/src/server/mail-test-actions.ts +67 -0
- package/src/server/mail-test.ts +56 -0
- package/src/server/markdown-pipeline.ts +31 -0
- package/src/server/marketplace-actions.ts +47 -0
- package/src/server/marketplace-admin.ts +260 -0
- package/src/server/mention-actions.ts +21 -0
- package/src/server/mention-search.ts +35 -0
- package/src/server/message-actions.ts +110 -0
- package/src/server/messages.ts +141 -0
- package/src/server/modcp.ts +125 -0
- package/src/server/moderation-actions.ts +165 -0
- package/src/server/navigation-admin-actions.ts +217 -0
- package/src/server/navigation.ts +112 -0
- package/src/server/nonce.ts +13 -0
- package/src/server/notification-actions.ts +84 -0
- package/src/server/notification-audience.ts +10 -0
- package/src/server/notification-menu-actions.ts +82 -0
- package/src/server/notification-menu.ts +96 -0
- package/src/server/notifications.ts +93 -0
- package/src/server/onboarding-actions.ts +27 -0
- package/src/server/onboarding.ts +89 -0
- package/src/server/page-metadata.ts +14 -0
- package/src/server/passkey-challenge.ts +42 -0
- package/src/server/plugin-admin-actions.ts +232 -0
- package/src/server/plugin-admin.ts +276 -0
- package/src/server/plugin-host.ts +143 -0
- package/src/server/plugin-pages.ts +192 -0
- package/src/server/plugin-panel.ts +49 -0
- package/src/server/plugin-routes.ts +298 -0
- package/src/server/plugin-view.tsx +58 -0
- package/src/server/poll-actions.ts +104 -0
- package/src/server/poll-scope.ts +52 -0
- package/src/server/post-notifications.ts +71 -0
- package/src/server/post-scope.ts +86 -0
- package/src/server/presence.ts +177 -0
- package/src/server/profile-fields.ts +112 -0
- package/src/server/push-actions.ts +113 -0
- package/src/server/push.ts +40 -0
- package/src/server/redirect-back.ts +15 -0
- package/src/server/registration.ts +7 -0
- package/src/server/relation-actions.ts +89 -0
- package/src/server/relations.ts +45 -0
- package/src/server/reply-core.ts +173 -0
- package/src/server/report-actions.ts +165 -0
- package/src/server/report-scope.ts +21 -0
- package/src/server/reputation-actions.ts +146 -0
- package/src/server/reputation-target.ts +49 -0
- package/src/server/reputation.ts +70 -0
- package/src/server/request-fingerprint.ts +65 -0
- package/src/server/safe-path.ts +16 -0
- package/src/server/same-origin.ts +45 -0
- package/src/server/search-page.ts +313 -0
- package/src/server/search.ts +45 -0
- package/src/server/secret-auth.ts +20 -0
- package/src/server/see-other.ts +11 -0
- package/src/server/seed-board.ts +417 -0
- package/src/server/session-actions.ts +78 -0
- package/src/server/session-cookies.ts +124 -0
- package/src/server/session-key.ts +17 -0
- package/src/server/settings.ts +47 -0
- package/src/server/signatures.ts +62 -0
- package/src/server/sso-handshake.ts +79 -0
- package/src/server/stats.ts +63 -0
- package/src/server/subscription-actions.ts +161 -0
- package/src/server/surgery-actions.ts +180 -0
- package/src/server/syndication.ts +77 -0
- package/src/server/system-admin-actions.ts +134 -0
- package/src/server/system-admin.ts +90 -0
- package/src/server/test-container.ts +114 -0
- package/src/server/theme-admin-actions.ts +242 -0
- package/src/server/theme-admin.ts +203 -0
- package/src/server/theme-runtime.ts +89 -0
- package/src/server/theme-style.ts +279 -0
- package/src/server/theme.ts +52 -0
- package/src/server/thread-core.ts +207 -0
- package/src/server/thread-rating-actions.ts +55 -0
- package/src/server/thread-tool-actions.ts +154 -0
- package/src/server/two-factor-actions.ts +178 -0
- package/src/server/two-factor.ts +86 -0
- package/src/server/upgrade-notice.ts +41 -0
- package/src/server/user-admin-actions.ts +572 -0
- package/src/server/user-admin.ts +165 -0
- package/src/server/usercp-actions.ts +263 -0
- package/src/server/usercp-mail.ts +120 -0
- package/src/server/viewer-preferences.ts +64 -0
- package/src/server/warning-actions.ts +122 -0
- package/src/styles/globals.css +969 -0
- package/src/theme/contract.fixture.ts +793 -0
- package/src/view/account-copy.ts +351 -0
- package/src/view/admin-content-copy.ts +66 -0
- package/src/view/admin-copy.ts +106 -0
- package/src/view/admin-forum-tree-copy.ts +40 -0
- package/src/view/admin-group-copy.ts +80 -0
- package/src/view/admin-log.ts +58 -0
- package/src/view/admin-nav.ts +164 -0
- package/src/view/admin-navigation-copy.ts +58 -0
- package/src/view/admin-panel-copy.ts +194 -0
- package/src/view/admin-settings.ts +173 -0
- package/src/view/admin-theme-copy.ts +229 -0
- package/src/view/admin-user-copy.ts +96 -0
- package/src/view/attachments.ts +51 -0
- package/src/view/auth-copy.ts +147 -0
- package/src/view/board-index.ts +167 -0
- package/src/view/board-latest.ts +101 -0
- package/src/view/breadcrumb.ts +50 -0
- package/src/view/contacts.ts +85 -0
- package/src/view/content-copy.ts +118 -0
- package/src/view/contrast.ts +286 -0
- package/src/view/copy.ts +101 -0
- package/src/view/count.ts +8 -0
- package/src/view/discovery-view.ts +74 -0
- package/src/view/editor-toolbar.ts +84 -0
- package/src/view/error-notice.ts +52 -0
- package/src/view/feed.ts +130 -0
- package/src/view/forum-cursor.ts +54 -0
- package/src/view/forum-display.ts +152 -0
- package/src/view/forum-jump.ts +60 -0
- package/src/view/forum-notice.ts +24 -0
- package/src/view/inline-moderation.ts +87 -0
- package/src/view/install-copy.ts +95 -0
- package/src/view/legal.ts +58 -0
- package/src/view/locale.ts +40 -0
- package/src/view/marketplace-panel.ts +51 -0
- package/src/view/member-identity.ts +23 -0
- package/src/view/member-profile.ts +65 -0
- package/src/view/messages.ts +222 -0
- package/src/view/metadata.ts +75 -0
- package/src/view/modcp-nav.ts +76 -0
- package/src/view/moderation-copy.ts +84 -0
- package/src/view/moderation-queue.ts +67 -0
- package/src/view/navigation-arrange.ts +188 -0
- package/src/view/navigation.ts +173 -0
- package/src/view/notification-menu.ts +202 -0
- package/src/view/notifications.ts +84 -0
- package/src/view/onboarding.ts +26 -0
- package/src/view/pager.ts +191 -0
- package/src/view/panel-nav.ts +203 -0
- package/src/view/plugin-panel.ts +42 -0
- package/src/view/post-form.ts +61 -0
- package/src/view/post-link.ts +29 -0
- package/src/view/presence.ts +99 -0
- package/src/view/promotion-rules.ts +52 -0
- package/src/view/redirect-notice.ts +21 -0
- package/src/view/reputation.ts +107 -0
- package/src/view/search-controls.ts +103 -0
- package/src/view/search-results.ts +291 -0
- package/src/view/security-activity.ts +73 -0
- package/src/view/setting-groups.ts +43 -0
- package/src/view/shell.ts +164 -0
- package/src/view/slug-id.ts +12 -0
- package/src/view/sso-hand-off.ts +44 -0
- package/src/view/sso-notices.ts +48 -0
- package/src/view/subscriptions.ts +76 -0
- package/src/view/theme-draft.ts +167 -0
- package/src/view/theme-preference.ts +30 -0
- package/src/view/theme-tokens.ts +333 -0
- package/src/view/thread-view.ts +280 -0
- package/src/view/time.ts +31 -0
- package/src/view/timezone.ts +35 -0
- package/src/view/two-factor.ts +6 -0
- package/src/view/usercp-nav.ts +103 -0
- package/src/view/usercp.ts +196 -0
- package/src/view/warnings.ts +128 -0
- package/src/view/word-filter.ts +5 -0
|
@@ -0,0 +1,712 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useActionState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { EMPTY_STATE } from '@/server/auth-form-state'
|
|
6
|
+
import {
|
|
7
|
+
createAnnouncementAction,
|
|
8
|
+
createCaptchaQuestionAction,
|
|
9
|
+
createDirectiveAction,
|
|
10
|
+
createPrefixAction,
|
|
11
|
+
createSmileyAction,
|
|
12
|
+
createWordFilterAction,
|
|
13
|
+
deleteAnnouncementAction,
|
|
14
|
+
deleteAttachmentAction,
|
|
15
|
+
deleteCaptchaQuestionAction,
|
|
16
|
+
deleteDirectiveAction,
|
|
17
|
+
deletePrefixAction,
|
|
18
|
+
deleteSmileyAction,
|
|
19
|
+
deleteWordFilterAction,
|
|
20
|
+
updateAnnouncementAction,
|
|
21
|
+
updateCaptchaQuestionAction,
|
|
22
|
+
updateDirectiveAction,
|
|
23
|
+
updateSmileyAction,
|
|
24
|
+
updateWordFilterAction,
|
|
25
|
+
} from '@/server/content-admin-actions'
|
|
26
|
+
|
|
27
|
+
import { FormError, SubmitButton } from '../auth/form-controls'
|
|
28
|
+
import { type Copy, fromCopy } from '../shell/copy'
|
|
29
|
+
import { INPUT, Saved } from './form-bits'
|
|
30
|
+
|
|
31
|
+
export interface WordFilterValues {
|
|
32
|
+
readonly id: number
|
|
33
|
+
readonly pattern: string
|
|
34
|
+
readonly replacement: string
|
|
35
|
+
readonly wholeWord: boolean
|
|
36
|
+
readonly enabled: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function WordFilterRowForm({ filter, copy }: { filter: WordFilterValues; copy: Copy }) {
|
|
40
|
+
const [state, action] = useActionState(updateWordFilterAction, EMPTY_STATE)
|
|
41
|
+
const [removeState, removeAction] = useActionState(deleteWordFilterAction, EMPTY_STATE)
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div className="flex flex-col gap-2 py-3">
|
|
45
|
+
<FormError message={state.error ?? removeState.error} />
|
|
46
|
+
<Saved when={state.notice === 'saved'}>{fromCopy(copy, 'admin.saved')}</Saved>
|
|
47
|
+
|
|
48
|
+
<form action={action} className="flex flex-wrap items-end gap-3" noValidate>
|
|
49
|
+
<input type="hidden" name="id" value={filter.id} />
|
|
50
|
+
|
|
51
|
+
<label className="flex min-w-40 flex-1 flex-col gap-1 text-sm">
|
|
52
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.filter.match')}</span>
|
|
53
|
+
<input name="pattern" defaultValue={filter.pattern} className={INPUT} required />
|
|
54
|
+
</label>
|
|
55
|
+
|
|
56
|
+
<label className="flex min-w-40 flex-1 flex-col gap-1 text-sm">
|
|
57
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.filter.showInstead')}</span>
|
|
58
|
+
<input name="replacement" defaultValue={filter.replacement} className={INPUT} />
|
|
59
|
+
</label>
|
|
60
|
+
|
|
61
|
+
<label className="flex items-center gap-2 text-sm">
|
|
62
|
+
<input
|
|
63
|
+
type="checkbox"
|
|
64
|
+
name="wholeWord"
|
|
65
|
+
value="1"
|
|
66
|
+
defaultChecked={filter.wholeWord}
|
|
67
|
+
className="size-4"
|
|
68
|
+
/>
|
|
69
|
+
<span>{fromCopy(copy, 'adminContent.filter.wholeWords')}</span>
|
|
70
|
+
</label>
|
|
71
|
+
|
|
72
|
+
<label className="flex items-center gap-2 text-sm">
|
|
73
|
+
<input
|
|
74
|
+
type="checkbox"
|
|
75
|
+
name="enabled"
|
|
76
|
+
value="1"
|
|
77
|
+
defaultChecked={filter.enabled}
|
|
78
|
+
className="size-4"
|
|
79
|
+
/>
|
|
80
|
+
<span>{fromCopy(copy, 'adminContent.filter.active')}</span>
|
|
81
|
+
</label>
|
|
82
|
+
|
|
83
|
+
<span className="min-w-24">
|
|
84
|
+
<SubmitButton>{fromCopy(copy, 'adminContent.save')}</SubmitButton>
|
|
85
|
+
</span>
|
|
86
|
+
</form>
|
|
87
|
+
|
|
88
|
+
<form action={removeAction}>
|
|
89
|
+
<input type="hidden" name="id" value={filter.id} />
|
|
90
|
+
<button type="submit" className="text-xs text-muted-foreground hover:underline">
|
|
91
|
+
{fromCopy(copy, 'adminContent.filter.removeThis')}
|
|
92
|
+
</button>
|
|
93
|
+
</form>
|
|
94
|
+
</div>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function NewWordFilterForm({ copy }: { copy: Copy }) {
|
|
99
|
+
const [state, action] = useActionState(createWordFilterAction, EMPTY_STATE)
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<form action={action} className="flex flex-wrap items-end gap-3" noValidate>
|
|
103
|
+
<FormError message={state.error} />
|
|
104
|
+
<Saved when={state.notice === 'saved'}>{fromCopy(copy, 'adminContent.added')}</Saved>
|
|
105
|
+
|
|
106
|
+
<label className="flex min-w-40 flex-1 flex-col gap-1 text-sm">
|
|
107
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.filter.match')}</span>
|
|
108
|
+
<input name="pattern" className={INPUT} required />
|
|
109
|
+
</label>
|
|
110
|
+
|
|
111
|
+
<label className="flex min-w-40 flex-1 flex-col gap-1 text-sm">
|
|
112
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.filter.showInstead')}</span>
|
|
113
|
+
<input name="replacement" className={INPUT} />
|
|
114
|
+
<span className="text-xs text-muted-foreground">
|
|
115
|
+
{fromCopy(copy, 'adminContent.filter.showInsteadHint')}
|
|
116
|
+
</span>
|
|
117
|
+
</label>
|
|
118
|
+
|
|
119
|
+
<label className="flex items-center gap-2 text-sm">
|
|
120
|
+
<input type="checkbox" name="wholeWord" value="1" defaultChecked className="size-4" />
|
|
121
|
+
<span>{fromCopy(copy, 'adminContent.filter.wholeWords')}</span>
|
|
122
|
+
</label>
|
|
123
|
+
|
|
124
|
+
<span className="min-w-28">
|
|
125
|
+
<SubmitButton>{fromCopy(copy, 'adminContent.add')}</SubmitButton>
|
|
126
|
+
</span>
|
|
127
|
+
</form>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface PrefixValues {
|
|
132
|
+
readonly id: number
|
|
133
|
+
readonly label: string
|
|
134
|
+
readonly token: string | null
|
|
135
|
+
readonly displayOrder: number
|
|
136
|
+
readonly forumPathPrefix: string | null
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function DeletePrefixForm({ prefix, copy }: { prefix: PrefixValues; copy: Copy }) {
|
|
140
|
+
const [state, action] = useActionState(deletePrefixAction, EMPTY_STATE)
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<form action={action} className="flex items-center gap-2">
|
|
144
|
+
<FormError message={state.error} />
|
|
145
|
+
<input type="hidden" name="id" value={prefix.id} />
|
|
146
|
+
<button type="submit" className="text-xs text-muted-foreground hover:underline">
|
|
147
|
+
{fromCopy(copy, 'admin.remove')}
|
|
148
|
+
</button>
|
|
149
|
+
</form>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function NewPrefixForm({ copy }: { copy: Copy }) {
|
|
154
|
+
const [state, action] = useActionState(createPrefixAction, EMPTY_STATE)
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<form action={action} className="flex flex-col gap-3" noValidate>
|
|
158
|
+
<FormError message={state.error} />
|
|
159
|
+
<Saved when={state.notice === 'saved'}>{fromCopy(copy, 'adminContent.added')}</Saved>
|
|
160
|
+
|
|
161
|
+
<div className="grid gap-3 sm:grid-cols-2">
|
|
162
|
+
<label className="flex flex-col gap-1 text-sm">
|
|
163
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.prefix.label')}</span>
|
|
164
|
+
<input name="label" className={INPUT} required />
|
|
165
|
+
</label>
|
|
166
|
+
|
|
167
|
+
<label className="flex flex-col gap-1 text-sm">
|
|
168
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.prefix.styleToken')}</span>
|
|
169
|
+
<input name="token" className={INPUT} />
|
|
170
|
+
<span className="text-xs text-muted-foreground">
|
|
171
|
+
{fromCopy(copy, 'adminContent.prefix.styleTokenHint')}
|
|
172
|
+
</span>
|
|
173
|
+
</label>
|
|
174
|
+
|
|
175
|
+
<label className="flex flex-col gap-1 text-sm">
|
|
176
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.prefix.displayOrder')}</span>
|
|
177
|
+
<input type="number" name="displayOrder" min={0} defaultValue={0} className={INPUT} />
|
|
178
|
+
</label>
|
|
179
|
+
|
|
180
|
+
<label className="flex flex-col gap-1 text-sm">
|
|
181
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.prefix.onlyBranch')}</span>
|
|
182
|
+
<input name="forumPathPrefix" className={INPUT} />
|
|
183
|
+
<span className="text-xs text-muted-foreground">
|
|
184
|
+
{fromCopy(copy, 'adminContent.prefix.onlyBranchHint')}
|
|
185
|
+
</span>
|
|
186
|
+
</label>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
<div>
|
|
190
|
+
<SubmitButton>{fromCopy(copy, 'adminContent.prefix.add')}</SubmitButton>
|
|
191
|
+
</div>
|
|
192
|
+
</form>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface SmileyValues {
|
|
197
|
+
readonly id: number
|
|
198
|
+
readonly code: string
|
|
199
|
+
readonly src: string
|
|
200
|
+
readonly alt: string | null
|
|
201
|
+
readonly enabled: boolean
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function SmileyRowForm({ smiley, copy }: { smiley: SmileyValues; copy: Copy }) {
|
|
205
|
+
const [state, action] = useActionState(updateSmileyAction, EMPTY_STATE)
|
|
206
|
+
const [removeState, removeAction] = useActionState(deleteSmileyAction, EMPTY_STATE)
|
|
207
|
+
|
|
208
|
+
return (
|
|
209
|
+
<div className="flex flex-col gap-2 py-3">
|
|
210
|
+
<FormError message={state.error ?? removeState.error} />
|
|
211
|
+
<Saved when={state.notice === 'saved'}>{fromCopy(copy, 'admin.saved')}</Saved>
|
|
212
|
+
|
|
213
|
+
<form action={action} className="flex flex-wrap items-end gap-3" noValidate>
|
|
214
|
+
<input type="hidden" name="id" value={smiley.id} />
|
|
215
|
+
|
|
216
|
+
<img
|
|
217
|
+
src={smiley.src}
|
|
218
|
+
alt={smiley.alt ?? smiley.code}
|
|
219
|
+
className="size-6 shrink-0 self-center"
|
|
220
|
+
/>
|
|
221
|
+
|
|
222
|
+
<label className="flex w-28 flex-col gap-1 text-sm">
|
|
223
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.smiley.code')}</span>
|
|
224
|
+
<input name="code" defaultValue={smiley.code} className={INPUT} required />
|
|
225
|
+
</label>
|
|
226
|
+
|
|
227
|
+
<label className="flex min-w-40 flex-1 flex-col gap-1 text-sm">
|
|
228
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.smiley.image')}</span>
|
|
229
|
+
<input name="src" defaultValue={smiley.src} className={INPUT} required />
|
|
230
|
+
</label>
|
|
231
|
+
|
|
232
|
+
<label className="flex w-32 flex-col gap-1 text-sm">
|
|
233
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.smiley.altText')}</span>
|
|
234
|
+
<input name="alt" defaultValue={smiley.alt ?? ''} className={INPUT} />
|
|
235
|
+
</label>
|
|
236
|
+
|
|
237
|
+
<label className="flex items-center gap-2 text-sm">
|
|
238
|
+
<input
|
|
239
|
+
type="checkbox"
|
|
240
|
+
name="enabled"
|
|
241
|
+
value="1"
|
|
242
|
+
defaultChecked={smiley.enabled}
|
|
243
|
+
className="size-4"
|
|
244
|
+
/>
|
|
245
|
+
<span>{fromCopy(copy, 'adminContent.enabled')}</span>
|
|
246
|
+
</label>
|
|
247
|
+
|
|
248
|
+
<span className="min-w-24">
|
|
249
|
+
<SubmitButton>{fromCopy(copy, 'adminContent.save')}</SubmitButton>
|
|
250
|
+
</span>
|
|
251
|
+
</form>
|
|
252
|
+
|
|
253
|
+
<form action={removeAction}>
|
|
254
|
+
<input type="hidden" name="id" value={smiley.id} />
|
|
255
|
+
<button type="submit" className="text-xs text-destructive hover:underline">
|
|
256
|
+
{fromCopy(copy, 'admin.remove')}
|
|
257
|
+
</button>
|
|
258
|
+
</form>
|
|
259
|
+
</div>
|
|
260
|
+
)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function NewSmileyForm({ copy }: { copy: Copy }) {
|
|
264
|
+
const [state, action] = useActionState(createSmileyAction, EMPTY_STATE)
|
|
265
|
+
|
|
266
|
+
return (
|
|
267
|
+
<form action={action} className="flex flex-wrap items-end gap-3" noValidate>
|
|
268
|
+
<FormError message={state.error} />
|
|
269
|
+
<Saved when={state.notice === 'saved'}>{fromCopy(copy, 'adminContent.added')}</Saved>
|
|
270
|
+
|
|
271
|
+
<label className="flex w-28 flex-col gap-1 text-sm">
|
|
272
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.smiley.code')}</span>
|
|
273
|
+
<input
|
|
274
|
+
name="code"
|
|
275
|
+
className={INPUT}
|
|
276
|
+
placeholder={fromCopy(copy, 'adminContent.smiley.codePlaceholder')}
|
|
277
|
+
required
|
|
278
|
+
/>
|
|
279
|
+
</label>
|
|
280
|
+
|
|
281
|
+
<label className="flex min-w-40 flex-1 flex-col gap-1 text-sm">
|
|
282
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.smiley.image')}</span>
|
|
283
|
+
<input
|
|
284
|
+
name="src"
|
|
285
|
+
className={INPUT}
|
|
286
|
+
placeholder={fromCopy(copy, 'adminContent.smiley.imagePlaceholder')}
|
|
287
|
+
required
|
|
288
|
+
/>
|
|
289
|
+
</label>
|
|
290
|
+
|
|
291
|
+
<label className="flex w-32 flex-col gap-1 text-sm">
|
|
292
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.smiley.altText')}</span>
|
|
293
|
+
<input name="alt" className={INPUT} />
|
|
294
|
+
<span className="text-xs text-muted-foreground">
|
|
295
|
+
{fromCopy(copy, 'adminContent.smiley.altTextHint')}
|
|
296
|
+
</span>
|
|
297
|
+
</label>
|
|
298
|
+
|
|
299
|
+
<span className="min-w-28">
|
|
300
|
+
<SubmitButton>{fromCopy(copy, 'adminContent.add')}</SubmitButton>
|
|
301
|
+
</span>
|
|
302
|
+
</form>
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export interface DirectiveValues {
|
|
307
|
+
readonly id: number
|
|
308
|
+
readonly name: string
|
|
309
|
+
readonly block: boolean
|
|
310
|
+
readonly description: string | null
|
|
311
|
+
readonly enabled: boolean
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function DirectiveRowForm({ directive, copy }: { directive: DirectiveValues; copy: Copy }) {
|
|
315
|
+
const [state, action] = useActionState(updateDirectiveAction, EMPTY_STATE)
|
|
316
|
+
const [removeState, removeAction] = useActionState(deleteDirectiveAction, EMPTY_STATE)
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<div className="flex flex-col gap-2 py-3">
|
|
320
|
+
<FormError message={state.error ?? removeState.error} />
|
|
321
|
+
<Saved when={state.notice === 'saved'}>{fromCopy(copy, 'admin.saved')}</Saved>
|
|
322
|
+
|
|
323
|
+
<form action={action} className="flex flex-wrap items-end gap-3" noValidate>
|
|
324
|
+
<input type="hidden" name="id" value={directive.id} />
|
|
325
|
+
|
|
326
|
+
<label className="flex w-36 flex-col gap-1 text-sm">
|
|
327
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.directive.name')}</span>
|
|
328
|
+
<input name="name" defaultValue={directive.name} className={INPUT} required />
|
|
329
|
+
</label>
|
|
330
|
+
|
|
331
|
+
<label className="flex min-w-40 flex-1 flex-col gap-1 text-sm">
|
|
332
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.directive.note')}</span>
|
|
333
|
+
<input name="description" defaultValue={directive.description ?? ''} className={INPUT} />
|
|
334
|
+
</label>
|
|
335
|
+
|
|
336
|
+
<label className="flex items-center gap-2 text-sm">
|
|
337
|
+
<input
|
|
338
|
+
type="checkbox"
|
|
339
|
+
name="block"
|
|
340
|
+
value="1"
|
|
341
|
+
defaultChecked={directive.block}
|
|
342
|
+
className="size-4"
|
|
343
|
+
/>
|
|
344
|
+
<span>{fromCopy(copy, 'adminContent.directive.block')}</span>
|
|
345
|
+
</label>
|
|
346
|
+
|
|
347
|
+
<label className="flex items-center gap-2 text-sm">
|
|
348
|
+
<input
|
|
349
|
+
type="checkbox"
|
|
350
|
+
name="enabled"
|
|
351
|
+
value="1"
|
|
352
|
+
defaultChecked={directive.enabled}
|
|
353
|
+
className="size-4"
|
|
354
|
+
/>
|
|
355
|
+
<span>{fromCopy(copy, 'adminContent.enabled')}</span>
|
|
356
|
+
</label>
|
|
357
|
+
|
|
358
|
+
<span className="min-w-24">
|
|
359
|
+
<SubmitButton>{fromCopy(copy, 'adminContent.save')}</SubmitButton>
|
|
360
|
+
</span>
|
|
361
|
+
</form>
|
|
362
|
+
|
|
363
|
+
<form action={removeAction}>
|
|
364
|
+
<input type="hidden" name="id" value={directive.id} />
|
|
365
|
+
<button type="submit" className="text-xs text-destructive hover:underline">
|
|
366
|
+
{fromCopy(copy, 'admin.remove')}
|
|
367
|
+
</button>
|
|
368
|
+
</form>
|
|
369
|
+
</div>
|
|
370
|
+
)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function NewDirectiveForm({ copy }: { copy: Copy }) {
|
|
374
|
+
const [state, action] = useActionState(createDirectiveAction, EMPTY_STATE)
|
|
375
|
+
|
|
376
|
+
return (
|
|
377
|
+
<form action={action} className="flex flex-wrap items-end gap-3" noValidate>
|
|
378
|
+
<FormError message={state.error} />
|
|
379
|
+
<Saved when={state.notice === 'saved'}>{fromCopy(copy, 'adminContent.added')}</Saved>
|
|
380
|
+
|
|
381
|
+
<label className="flex w-36 flex-col gap-1 text-sm">
|
|
382
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.directive.name')}</span>
|
|
383
|
+
<input
|
|
384
|
+
name="name"
|
|
385
|
+
className={INPUT}
|
|
386
|
+
placeholder={fromCopy(copy, 'adminContent.directive.namePlaceholder')}
|
|
387
|
+
required
|
|
388
|
+
/>
|
|
389
|
+
<span className="text-xs text-muted-foreground">
|
|
390
|
+
{copy['adminContent.directive.nameHintLead']}
|
|
391
|
+
<code>:::note</code>
|
|
392
|
+
{copy['adminContent.directive.nameHintMid']}
|
|
393
|
+
<code>:note[…]</code>
|
|
394
|
+
{copy['adminContent.directive.nameHintTail']}
|
|
395
|
+
</span>
|
|
396
|
+
</label>
|
|
397
|
+
|
|
398
|
+
<label className="flex min-w-40 flex-1 flex-col gap-1 text-sm">
|
|
399
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.directive.note')}</span>
|
|
400
|
+
<input name="description" className={INPUT} />
|
|
401
|
+
<span className="text-xs text-muted-foreground">
|
|
402
|
+
{fromCopy(copy, 'adminContent.directive.noteHint')}
|
|
403
|
+
</span>
|
|
404
|
+
</label>
|
|
405
|
+
|
|
406
|
+
<label className="flex items-center gap-2 text-sm">
|
|
407
|
+
<input type="checkbox" name="block" value="1" className="size-4" />
|
|
408
|
+
<span>{fromCopy(copy, 'adminContent.directive.block')}</span>
|
|
409
|
+
</label>
|
|
410
|
+
|
|
411
|
+
<span className="min-w-28">
|
|
412
|
+
<SubmitButton>{fromCopy(copy, 'adminContent.add')}</SubmitButton>
|
|
413
|
+
</span>
|
|
414
|
+
</form>
|
|
415
|
+
)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export function DeleteAttachmentForm({ attachmentId, copy }: { attachmentId: number; copy: Copy }) {
|
|
419
|
+
const [state, action] = useActionState(deleteAttachmentAction, EMPTY_STATE)
|
|
420
|
+
|
|
421
|
+
return (
|
|
422
|
+
<form action={action} className="shrink-0">
|
|
423
|
+
<FormError message={state.error} />
|
|
424
|
+
<input type="hidden" name="id" value={attachmentId} />
|
|
425
|
+
<button type="submit" className="text-xs text-destructive hover:underline">
|
|
426
|
+
{fromCopy(copy, 'adminContent.attachment.delete')}
|
|
427
|
+
</button>
|
|
428
|
+
</form>
|
|
429
|
+
)
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export interface AnnouncementValues {
|
|
433
|
+
readonly id: number
|
|
434
|
+
readonly forumId: number | null
|
|
435
|
+
readonly title: string
|
|
436
|
+
readonly message: string
|
|
437
|
+
readonly startsAtInput: string
|
|
438
|
+
readonly endsAtInput: string
|
|
439
|
+
readonly enabled: boolean
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export interface ForumChoice {
|
|
443
|
+
readonly id: number
|
|
444
|
+
readonly label: string
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function AnnouncementFields({
|
|
448
|
+
forums,
|
|
449
|
+
values,
|
|
450
|
+
copy,
|
|
451
|
+
}: {
|
|
452
|
+
forums: readonly ForumChoice[]
|
|
453
|
+
values?: AnnouncementValues
|
|
454
|
+
copy: Copy
|
|
455
|
+
}) {
|
|
456
|
+
return (
|
|
457
|
+
<>
|
|
458
|
+
<label className="flex flex-col gap-1 text-sm">
|
|
459
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.announcement.title')}</span>
|
|
460
|
+
<input name="title" defaultValue={values?.title ?? ''} className={INPUT} required />
|
|
461
|
+
</label>
|
|
462
|
+
|
|
463
|
+
<label className="flex flex-col gap-1 text-sm">
|
|
464
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.announcement.message')}</span>
|
|
465
|
+
<textarea
|
|
466
|
+
name="message"
|
|
467
|
+
rows={5}
|
|
468
|
+
defaultValue={values?.message ?? ''}
|
|
469
|
+
className={INPUT}
|
|
470
|
+
required
|
|
471
|
+
/>
|
|
472
|
+
<span className="text-xs text-muted-foreground">
|
|
473
|
+
{fromCopy(copy, 'adminContent.announcement.messageHint')}
|
|
474
|
+
</span>
|
|
475
|
+
</label>
|
|
476
|
+
|
|
477
|
+
<label className="flex flex-col gap-1 text-sm">
|
|
478
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.announcement.where')}</span>
|
|
479
|
+
<select
|
|
480
|
+
name="forumId"
|
|
481
|
+
defaultValue={
|
|
482
|
+
values?.forumId === null || values === undefined ? '' : String(values.forumId)
|
|
483
|
+
}
|
|
484
|
+
className={INPUT}
|
|
485
|
+
>
|
|
486
|
+
<option value="">{fromCopy(copy, 'adminContent.announcement.wholeBoard')}</option>
|
|
487
|
+
{forums.map((forum) => (
|
|
488
|
+
<option key={forum.id} value={forum.id}>
|
|
489
|
+
{forum.label}
|
|
490
|
+
</option>
|
|
491
|
+
))}
|
|
492
|
+
</select>
|
|
493
|
+
<span className="text-xs text-muted-foreground">
|
|
494
|
+
{fromCopy(copy, 'adminContent.announcement.whereHint')}
|
|
495
|
+
</span>
|
|
496
|
+
</label>
|
|
497
|
+
|
|
498
|
+
<div className="flex flex-wrap gap-3">
|
|
499
|
+
<label className="flex flex-1 flex-col gap-1 text-sm">
|
|
500
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.announcement.from')}</span>
|
|
501
|
+
<input
|
|
502
|
+
type="datetime-local"
|
|
503
|
+
name="startsAt"
|
|
504
|
+
defaultValue={values?.startsAtInput ?? ''}
|
|
505
|
+
className={INPUT}
|
|
506
|
+
/>
|
|
507
|
+
</label>
|
|
508
|
+
|
|
509
|
+
<label className="flex flex-1 flex-col gap-1 text-sm">
|
|
510
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.announcement.until')}</span>
|
|
511
|
+
<input
|
|
512
|
+
type="datetime-local"
|
|
513
|
+
name="endsAt"
|
|
514
|
+
defaultValue={values?.endsAtInput ?? ''}
|
|
515
|
+
className={INPUT}
|
|
516
|
+
/>
|
|
517
|
+
<span className="text-xs text-muted-foreground">
|
|
518
|
+
{fromCopy(copy, 'adminContent.announcement.untilHint')}
|
|
519
|
+
</span>
|
|
520
|
+
</label>
|
|
521
|
+
</div>
|
|
522
|
+
|
|
523
|
+
<p className="text-xs text-muted-foreground">
|
|
524
|
+
{fromCopy(copy, 'adminContent.announcement.timesUtc')}
|
|
525
|
+
</p>
|
|
526
|
+
</>
|
|
527
|
+
)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export function AnnouncementRowForm({
|
|
531
|
+
announcement,
|
|
532
|
+
forums,
|
|
533
|
+
copy,
|
|
534
|
+
}: {
|
|
535
|
+
announcement: AnnouncementValues
|
|
536
|
+
forums: readonly ForumChoice[]
|
|
537
|
+
copy: Copy
|
|
538
|
+
}) {
|
|
539
|
+
const [state, action] = useActionState(updateAnnouncementAction, EMPTY_STATE)
|
|
540
|
+
const [removeState, removeAction] = useActionState(deleteAnnouncementAction, EMPTY_STATE)
|
|
541
|
+
|
|
542
|
+
return (
|
|
543
|
+
<div className="flex flex-col gap-3 py-4">
|
|
544
|
+
<FormError message={state.error ?? removeState.error} />
|
|
545
|
+
<Saved when={state.notice === 'saved'}>{fromCopy(copy, 'admin.saved')}</Saved>
|
|
546
|
+
|
|
547
|
+
<form action={action} className="flex flex-col gap-3" noValidate>
|
|
548
|
+
<input type="hidden" name="id" value={announcement.id} />
|
|
549
|
+
<AnnouncementFields forums={forums} values={announcement} copy={copy} />
|
|
550
|
+
|
|
551
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
552
|
+
<label className="flex items-center gap-2 text-sm">
|
|
553
|
+
<input
|
|
554
|
+
type="checkbox"
|
|
555
|
+
name="enabled"
|
|
556
|
+
value="1"
|
|
557
|
+
defaultChecked={announcement.enabled}
|
|
558
|
+
className="size-4"
|
|
559
|
+
/>
|
|
560
|
+
<span>{fromCopy(copy, 'adminContent.enabled')}</span>
|
|
561
|
+
</label>
|
|
562
|
+
|
|
563
|
+
<span className="min-w-28">
|
|
564
|
+
<SubmitButton>{fromCopy(copy, 'adminContent.save')}</SubmitButton>
|
|
565
|
+
</span>
|
|
566
|
+
</div>
|
|
567
|
+
</form>
|
|
568
|
+
|
|
569
|
+
<form action={removeAction}>
|
|
570
|
+
<input type="hidden" name="id" value={announcement.id} />
|
|
571
|
+
<button type="submit" className="text-xs text-destructive hover:underline">
|
|
572
|
+
{fromCopy(copy, 'admin.remove')}
|
|
573
|
+
</button>
|
|
574
|
+
</form>
|
|
575
|
+
</div>
|
|
576
|
+
)
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export function NewAnnouncementForm({
|
|
580
|
+
forums,
|
|
581
|
+
copy,
|
|
582
|
+
}: {
|
|
583
|
+
forums: readonly ForumChoice[]
|
|
584
|
+
copy: Copy
|
|
585
|
+
}) {
|
|
586
|
+
const [state, action] = useActionState(createAnnouncementAction, EMPTY_STATE)
|
|
587
|
+
|
|
588
|
+
return (
|
|
589
|
+
<form action={action} className="flex flex-col gap-3" noValidate>
|
|
590
|
+
<FormError message={state.error} />
|
|
591
|
+
<Saved when={state.notice === 'saved'}>{fromCopy(copy, 'adminContent.added')}</Saved>
|
|
592
|
+
|
|
593
|
+
<AnnouncementFields forums={forums} copy={copy} />
|
|
594
|
+
|
|
595
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
596
|
+
<label className="flex items-center gap-2 text-sm">
|
|
597
|
+
<input type="checkbox" name="enabled" value="1" defaultChecked className="size-4" />
|
|
598
|
+
<span>{fromCopy(copy, 'adminContent.enabled')}</span>
|
|
599
|
+
</label>
|
|
600
|
+
|
|
601
|
+
<span className="min-w-28">
|
|
602
|
+
<SubmitButton>{fromCopy(copy, 'adminContent.add')}</SubmitButton>
|
|
603
|
+
</span>
|
|
604
|
+
</div>
|
|
605
|
+
</form>
|
|
606
|
+
)
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
export interface CaptchaQuestionValues {
|
|
610
|
+
readonly id: number
|
|
611
|
+
readonly question: string
|
|
612
|
+
readonly answers: readonly string[]
|
|
613
|
+
readonly enabled: boolean
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export function CaptchaQuestionRowForm({
|
|
617
|
+
question,
|
|
618
|
+
copy,
|
|
619
|
+
}: {
|
|
620
|
+
question: CaptchaQuestionValues
|
|
621
|
+
copy: Copy
|
|
622
|
+
}) {
|
|
623
|
+
const [state, action] = useActionState(updateCaptchaQuestionAction, EMPTY_STATE)
|
|
624
|
+
const [removeState, removeAction] = useActionState(deleteCaptchaQuestionAction, EMPTY_STATE)
|
|
625
|
+
|
|
626
|
+
return (
|
|
627
|
+
<div className="flex flex-col gap-2 py-3">
|
|
628
|
+
<FormError message={state.error ?? removeState.error} />
|
|
629
|
+
<Saved when={state.notice === 'saved'}>{fromCopy(copy, 'admin.saved')}</Saved>
|
|
630
|
+
|
|
631
|
+
<form action={action} className="flex flex-col gap-3" noValidate>
|
|
632
|
+
<input type="hidden" name="id" value={question.id} />
|
|
633
|
+
|
|
634
|
+
<label className="flex flex-col gap-1 text-sm">
|
|
635
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.captcha.question')}</span>
|
|
636
|
+
<input name="question" defaultValue={question.question} className={INPUT} required />
|
|
637
|
+
</label>
|
|
638
|
+
|
|
639
|
+
<label className="flex flex-col gap-1 text-sm">
|
|
640
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.captcha.answers')}</span>
|
|
641
|
+
<textarea
|
|
642
|
+
name="answers"
|
|
643
|
+
rows={3}
|
|
644
|
+
defaultValue={question.answers.join('\n')}
|
|
645
|
+
className={INPUT}
|
|
646
|
+
required
|
|
647
|
+
/>
|
|
648
|
+
<span className="text-xs text-muted-foreground">
|
|
649
|
+
{fromCopy(copy, 'adminContent.captcha.answersHint')}
|
|
650
|
+
</span>
|
|
651
|
+
</label>
|
|
652
|
+
|
|
653
|
+
<div className="flex flex-wrap items-center gap-4">
|
|
654
|
+
<label className="flex items-center gap-2 text-sm">
|
|
655
|
+
<input
|
|
656
|
+
type="checkbox"
|
|
657
|
+
name="enabled"
|
|
658
|
+
value="1"
|
|
659
|
+
defaultChecked={question.enabled}
|
|
660
|
+
className="size-4"
|
|
661
|
+
/>
|
|
662
|
+
<span>{fromCopy(copy, 'adminContent.enabled')}</span>
|
|
663
|
+
</label>
|
|
664
|
+
|
|
665
|
+
<span className="min-w-24">
|
|
666
|
+
<SubmitButton>{fromCopy(copy, 'adminContent.save')}</SubmitButton>
|
|
667
|
+
</span>
|
|
668
|
+
</div>
|
|
669
|
+
</form>
|
|
670
|
+
|
|
671
|
+
<form action={removeAction}>
|
|
672
|
+
<input type="hidden" name="id" value={question.id} />
|
|
673
|
+
<button type="submit" className="text-xs text-destructive hover:underline">
|
|
674
|
+
{fromCopy(copy, 'admin.remove')}
|
|
675
|
+
</button>
|
|
676
|
+
</form>
|
|
677
|
+
</div>
|
|
678
|
+
)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
export function NewCaptchaQuestionForm({ copy }: { copy: Copy }) {
|
|
682
|
+
const [state, action] = useActionState(createCaptchaQuestionAction, EMPTY_STATE)
|
|
683
|
+
|
|
684
|
+
return (
|
|
685
|
+
<form action={action} className="flex flex-col gap-3" noValidate>
|
|
686
|
+
<FormError message={state.error} />
|
|
687
|
+
<Saved when={state.notice === 'saved'}>{fromCopy(copy, 'adminContent.added')}</Saved>
|
|
688
|
+
|
|
689
|
+
<label className="flex flex-col gap-1 text-sm">
|
|
690
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.captcha.question')}</span>
|
|
691
|
+
<input
|
|
692
|
+
name="question"
|
|
693
|
+
className={INPUT}
|
|
694
|
+
placeholder={fromCopy(copy, 'adminContent.captcha.questionPlaceholder')}
|
|
695
|
+
required
|
|
696
|
+
/>
|
|
697
|
+
</label>
|
|
698
|
+
|
|
699
|
+
<label className="flex flex-col gap-1 text-sm">
|
|
700
|
+
<span className="font-medium">{fromCopy(copy, 'adminContent.captcha.answers')}</span>
|
|
701
|
+
<textarea name="answers" rows={3} className={INPUT} required />
|
|
702
|
+
<span className="text-xs text-muted-foreground">
|
|
703
|
+
{fromCopy(copy, 'adminContent.captcha.answersHint')}
|
|
704
|
+
</span>
|
|
705
|
+
</label>
|
|
706
|
+
|
|
707
|
+
<span className="min-w-28">
|
|
708
|
+
<SubmitButton>{fromCopy(copy, 'adminContent.add')}</SubmitButton>
|
|
709
|
+
</span>
|
|
710
|
+
</form>
|
|
711
|
+
)
|
|
712
|
+
}
|