@meith/web 0.31.0 → 0.33.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 (174) hide show
  1. package/app/(board)/[slug]/feed.xml/route.ts +5 -3
  2. package/app/(board)/[slug]/new/page.tsx +5 -0
  3. package/app/(board)/[slug]/page.tsx +7 -1
  4. package/app/(board)/discover/[view]/page.tsx +6 -1
  5. package/app/(board)/member/[id]/page.tsx +3 -5
  6. package/app/(board)/member/[id]/reputation/page.tsx +8 -15
  7. package/app/(board)/members/page.tsx +3 -8
  8. package/app/(board)/messages/[id]/page.tsx +3 -7
  9. package/app/(board)/messages/page.tsx +3 -5
  10. package/app/(board)/modcp/ip/page.tsx +3 -5
  11. package/app/(board)/modcp/plugins/[key]/[[...path]]/page.tsx +84 -0
  12. package/app/(board)/moderation/reports/page.tsx +53 -15
  13. package/app/(board)/moderation/warn/page.tsx +3 -6
  14. package/app/(board)/notifications/page.tsx +5 -10
  15. package/app/(board)/notifications/preferences/page.tsx +8 -0
  16. package/app/(board)/online/page.tsx +12 -6
  17. package/app/(board)/staff/page.tsx +4 -5
  18. package/app/(board)/stats/page.tsx +10 -3
  19. package/app/(board)/subscriptions/page.tsx +5 -10
  20. package/app/(board)/thread/[slug]/edit/page.tsx +21 -0
  21. package/app/(board)/thread/[slug]/feed.xml/route.ts +6 -3
  22. package/app/(board)/thread/[slug]/page.tsx +49 -11
  23. package/app/(board)/thread/[slug]/reply/page.tsx +5 -0
  24. package/app/(board)/unsubscribe/page.tsx +8 -11
  25. package/app/(board)/usercp/contacts/page.tsx +3 -5
  26. package/app/(board)/usercp/drafts/page.tsx +70 -0
  27. package/app/(board)/usercp/options/page.tsx +8 -1
  28. package/app/(board)/usercp/security/page.tsx +16 -0
  29. package/app/admin/antispam/page.tsx +4 -12
  30. package/app/admin/content/attachments/page.tsx +3 -6
  31. package/app/admin/content/page.tsx +3 -13
  32. package/app/admin/forums/[id]/permissions/page.tsx +9 -12
  33. package/app/admin/groups/page.tsx +7 -9
  34. package/app/admin/layout.tsx +23 -8
  35. package/app/admin/page.tsx +12 -6
  36. package/app/admin/plugins/[key]/[[...path]]/page.tsx +6 -7
  37. package/app/admin/plugins/page.tsx +3 -6
  38. package/app/admin/users/[id]/page.tsx +5 -11
  39. package/app/admin/users/prune/page.tsx +4 -12
  40. package/app/admin/webhooks/page.tsx +144 -0
  41. package/app/api/subscribe/thread/[id]/route.ts +53 -0
  42. package/app/api/unsubscribe/thread/[id]/route.ts +42 -0
  43. package/app/atom.xml/route.ts +2 -2
  44. package/app/auth/sso/[provider]/callback/route.ts +10 -0
  45. package/app/feed.xml/route.ts +2 -2
  46. package/app/offline/route.ts +98 -0
  47. package/next.config.mjs +1 -0
  48. package/package.json +49 -48
  49. package/public/sw.js +42 -2
  50. package/src/components/account/draft-forms.tsx +39 -0
  51. package/src/components/account/feed-token.tsx +95 -0
  52. package/src/components/account/notification-forms.tsx +37 -2
  53. package/src/components/account/relation-forms.tsx +3 -2
  54. package/src/components/account/subscription-forms.tsx +20 -6
  55. package/src/components/account/usercp-forms.tsx +44 -0
  56. package/src/components/admin/admin-forms.tsx +57 -9
  57. package/src/components/admin/api-token-forms.tsx +11 -7
  58. package/src/components/admin/ban-filter-forms.tsx +15 -11
  59. package/src/components/admin/content-forms.tsx +31 -14
  60. package/src/components/admin/forum-forms.tsx +241 -129
  61. package/src/components/admin/forum-tree.tsx +5 -7
  62. package/src/components/admin/navigation-forms.tsx +43 -26
  63. package/src/components/admin/navigation-tree.tsx +8 -13
  64. package/src/components/admin/oklch-picker.tsx +2 -1
  65. package/src/components/admin/theme-changes.tsx +3 -2
  66. package/src/components/admin/theme-forms.tsx +3 -2
  67. package/src/components/admin/webhook-forms.tsx +137 -0
  68. package/src/components/auth/register-form.tsx +4 -7
  69. package/src/components/board/live-region.tsx +3 -1
  70. package/src/components/content/attachment-field.tsx +93 -22
  71. package/src/components/content/combobox-keys.ts +38 -0
  72. package/src/components/content/edit-post-form.tsx +75 -1
  73. package/src/components/content/markdown-editor.tsx +29 -86
  74. package/src/components/content/mention-combobox.tsx +137 -0
  75. package/src/components/content/multiquote-button.tsx +15 -12
  76. package/src/components/content/new-thread-form.tsx +95 -17
  77. package/src/components/content/poll-edit-form.tsx +61 -39
  78. package/src/components/content/poll-vote-form.tsx +91 -0
  79. package/src/components/content/poll.tsx +12 -48
  80. package/src/components/content/progressive-marker.tsx +13 -0
  81. package/src/components/content/recipient-segment.ts +39 -0
  82. package/src/components/content/reply-form.tsx +9 -1
  83. package/src/components/content/thanks-button.tsx +12 -7
  84. package/src/components/messages/message-forms.tsx +53 -43
  85. package/src/components/messages/recipient-field.tsx +88 -0
  86. package/src/components/moderation/inline-moderation-form.tsx +2 -0
  87. package/src/components/moderation/modcp-shell.tsx +17 -3
  88. package/src/components/moderation/queue-form.tsx +3 -6
  89. package/src/components/moderation/report-forms.tsx +24 -5
  90. package/src/components/moderation/thread-tools-form.tsx +2 -0
  91. package/src/components/shell/board-notice.tsx +22 -1
  92. package/src/components/shell/confirm-dialog.tsx +93 -0
  93. package/src/components/shell/nav-disclosure-enhancer.tsx +36 -0
  94. package/src/components/shell/notice-toast.tsx +40 -0
  95. package/src/components/shell/notification-menu.tsx +5 -3
  96. package/src/components/shell/page-shell.tsx +3 -0
  97. package/src/components/shell/scheme-toggle.tsx +72 -0
  98. package/src/components/shell/theme-switcher.tsx +16 -36
  99. package/src/components/shell/view-tabs.tsx +1 -1
  100. package/src/server/admin-actions.ts +118 -49
  101. package/src/server/admin-settings-actions.ts +5 -0
  102. package/src/server/admin.ts +70 -2
  103. package/src/server/api/search.ts +8 -2
  104. package/src/server/api-token-actions.ts +4 -0
  105. package/src/server/appearance-actions.ts +21 -0
  106. package/src/server/attachments.ts +35 -0
  107. package/src/server/auth-actions.ts +2 -0
  108. package/src/server/auth-form-state.ts +28 -0
  109. package/src/server/auto-watch.ts +31 -0
  110. package/src/server/ban-filter-admin-actions.ts +5 -0
  111. package/src/server/confirm.ts +19 -0
  112. package/src/server/content-actions.ts +129 -19
  113. package/src/server/content-admin-actions.ts +31 -0
  114. package/src/server/cookies.ts +18 -0
  115. package/src/server/discovery.ts +11 -1
  116. package/src/server/feed-routes.ts +13 -4
  117. package/src/server/feed-token-actions.ts +61 -0
  118. package/src/server/feed-token.ts +78 -0
  119. package/src/server/fixture-post-repo.ts +18 -0
  120. package/src/server/forum-admin-actions.ts +23 -16
  121. package/src/server/forum-admin.ts +10 -1
  122. package/src/server/inline-moderation-actions.ts +6 -0
  123. package/src/server/message-actions.ts +9 -0
  124. package/src/server/navigation-admin-actions.ts +6 -0
  125. package/src/server/plugin-admin.ts +7 -2
  126. package/src/server/plugin-pages.ts +50 -0
  127. package/src/server/plugin-panel.ts +34 -1
  128. package/src/server/plugin-routes.ts +8 -0
  129. package/src/server/plugin-view.tsx +46 -7
  130. package/src/server/poll-actions.ts +13 -1
  131. package/src/server/presence.ts +23 -0
  132. package/src/server/push-actions.ts +9 -0
  133. package/src/server/reply-core.ts +8 -1
  134. package/src/server/report-actions.ts +13 -2
  135. package/src/server/reputation-actions.ts +12 -1
  136. package/src/server/search-page.ts +8 -2
  137. package/src/server/search.ts +18 -5
  138. package/src/server/session-cookies.ts +18 -0
  139. package/src/server/subscription-actions.ts +9 -0
  140. package/src/server/thread-core.ts +10 -2
  141. package/src/server/thread-tool-actions.ts +6 -0
  142. package/src/server/unread-goto.ts +28 -0
  143. package/src/server/upgrade-notice.ts +1 -1
  144. package/src/server/usercp-actions.ts +31 -1
  145. package/src/server/viewer-preferences.ts +5 -0
  146. package/src/server/webhook-actions.ts +98 -0
  147. package/src/server/webhooks-admin.ts +122 -0
  148. package/src/styles/globals.css +1 -1
  149. package/src/theme/contract.fixture.ts +36 -5
  150. package/src/view/account-copy.ts +39 -0
  151. package/src/view/admin-copy.ts +4 -9
  152. package/src/view/admin-nav.ts +6 -0
  153. package/src/view/admin-panel-copy.ts +29 -1
  154. package/src/view/attachments.ts +33 -1
  155. package/src/view/board-index.ts +31 -2
  156. package/src/view/board-title.ts +1 -0
  157. package/src/view/content-copy.ts +22 -12
  158. package/src/view/copy.ts +17 -0
  159. package/src/view/discovery-view.ts +2 -1
  160. package/src/view/drafts.ts +45 -0
  161. package/src/view/editor-toolbar.ts +14 -0
  162. package/src/view/forum-display.ts +39 -7
  163. package/src/view/modcp-nav.ts +32 -0
  164. package/src/view/moderation-copy.ts +20 -0
  165. package/src/view/notifications.ts +1 -0
  166. package/src/view/plugin-panel.ts +13 -0
  167. package/src/view/poll-vote.ts +46 -0
  168. package/src/view/post-link.ts +8 -1
  169. package/src/view/progressive-enhancement.ts +5 -0
  170. package/src/view/shell.ts +2 -1
  171. package/src/view/subscriptions.ts +1 -0
  172. package/src/view/thread-view.ts +41 -2
  173. package/src/view/usercp-nav.ts +6 -0
  174. package/src/view/usercp.ts +14 -0
@@ -2,7 +2,12 @@
2
2
 
3
3
  import { useActionState } from 'react'
4
4
 
5
- import { adminSignInAction, adminSignOutAction } from '@/server/admin-actions'
5
+ import {
6
+ adminAbandonSecondFactorAction,
7
+ adminSignInAction,
8
+ adminSignOutAction,
9
+ adminVerifySecondFactorAction,
10
+ } from '@/server/admin-actions'
6
11
  import { EMPTY_STATE } from '@/server/auth-form-state'
7
12
 
8
13
  import { FormError, PendingButton } from '../auth/form-controls'
@@ -51,14 +56,6 @@ export function AdminSignInForm({
51
56
  />
52
57
  </label>
53
58
 
54
- <OtpField
55
- label={fromCopy(copy, 'adminPanel.twoFactorCode')}
56
- name="code"
57
- required={false}
58
- hint={fromCopy(copy, 'adminPanel.twoFactorCodeHint')}
59
- recovery={otpRecoveryFromCopy(copy)}
60
- />
61
-
62
59
  <PendingButton
63
60
  showWorking
64
61
  className="inline-flex h-10 items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
@@ -73,6 +70,57 @@ export function AdminSignInForm({
73
70
  )
74
71
  }
75
72
 
73
+ export function AdminSecondFactorForm({
74
+ recoveryCodesLeft,
75
+ copy,
76
+ }: {
77
+ recoveryCodesLeft: number
78
+ copy: Copy
79
+ }) {
80
+ const [state, action] = useActionState(adminVerifySecondFactorAction, EMPTY_STATE)
81
+
82
+ return (
83
+ <div className="flex w-full max-w-sm flex-col gap-4 rounded-lg border border-border bg-card p-6">
84
+ <h1 className="text-xl font-semibold tracking-tight">
85
+ {fromCopy(copy, 'adminPanel.twoFactor.title')}
86
+ </h1>
87
+
88
+ <p className="text-sm text-muted-foreground">{fromCopy(copy, 'adminPanel.twoFactor.lede')}</p>
89
+
90
+ <form action={action} className="flex flex-col gap-4" noValidate>
91
+ <FormError message={state.error} />
92
+
93
+ <OtpField
94
+ label={fromCopy(copy, 'adminPanel.twoFactorCode')}
95
+ name="code"
96
+ hint={
97
+ recoveryCodesLeft > 0
98
+ ? fromCopy(copy, 'adminPanel.twoFactor.recoveryHint')
99
+ : fromCopy(copy, 'adminPanel.twoFactor.recoveryHintExhausted')
100
+ }
101
+ recovery={otpRecoveryFromCopy(copy)}
102
+ />
103
+
104
+ <PendingButton
105
+ showWorking
106
+ className="inline-flex h-10 items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
107
+ >
108
+ {fromCopy(copy, 'adminPanel.twoFactor.submit')}
109
+ </PendingButton>
110
+ </form>
111
+
112
+ <form action={adminAbandonSecondFactorAction}>
113
+ <PendingButton
114
+ showWorking
115
+ className="text-sm text-muted-foreground hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
116
+ >
117
+ {fromCopy(copy, 'adminPanel.twoFactor.cancel')}
118
+ </PendingButton>
119
+ </form>
120
+ </div>
121
+ )
122
+ }
123
+
76
124
  export function AdminSignOutForm({ copy }: { copy: Copy }) {
77
125
  return (
78
126
  <form action={adminSignOutAction}>
@@ -6,6 +6,7 @@ import { issueApiTokenAction, revokeApiTokenAction } from '@/server/api-token-ac
6
6
  import { EMPTY_STATE } from '@/server/auth-form-state'
7
7
 
8
8
  import { FormError, SubmitButton } from '../auth/form-controls'
9
+ import { ConfirmDialog } from '../shell/confirm-dialog'
9
10
  import { type Copy, fromCopy } from '../shell/copy'
10
11
 
11
12
  export function IssueTokenForm({ scopes, copy }: { scopes: readonly string[]; copy: Copy }) {
@@ -78,12 +79,15 @@ export function RevokeTokenForm({ tokenId, copy }: { tokenId: number; copy: Copy
78
79
  const [state, action] = useActionState(revokeApiTokenAction, EMPTY_STATE)
79
80
 
80
81
  return (
81
- <form action={action}>
82
- <input type="hidden" name="tokenId" value={tokenId} />
83
- <SubmitButton>{fromCopy(copy, 'adminPanel.token.revoke')}</SubmitButton>
84
- {state.error !== undefined && (
85
- <span className="ml-2 text-xs text-destructive">{state.error}</span>
86
- )}
87
- </form>
82
+ <>
83
+ <form action={action}>
84
+ <input type="hidden" name="tokenId" value={tokenId} />
85
+ <SubmitButton>{fromCopy(copy, 'adminPanel.token.revoke')}</SubmitButton>
86
+ {state.error !== undefined && (
87
+ <span className="ml-2 text-xs text-destructive">{state.error}</span>
88
+ )}
89
+ </form>
90
+ <ConfirmDialog confirm={state.confirm} action={action} />
91
+ </>
88
92
  )
89
93
  }
@@ -6,6 +6,7 @@ import { EMPTY_STATE } from '@/server/auth-form-state'
6
6
  import { addBanFilterAction, removeBanFilterAction } from '@/server/ban-filter-admin-actions'
7
7
 
8
8
  import { FormError, PendingButton, SubmitButton } from '../auth/form-controls'
9
+ import { ConfirmDialog } from '../shell/confirm-dialog'
9
10
  import { type Copy, formatFromCopy, fromCopy } from '../shell/copy'
10
11
  import { INPUT, Saved } from './form-bits'
11
12
 
@@ -89,16 +90,19 @@ export function RemoveBanFilterForm({
89
90
  const [state, action] = useActionState(removeBanFilterAction, EMPTY_STATE)
90
91
 
91
92
  return (
92
- <form action={action} className="flex flex-col items-end gap-1">
93
- <FormError message={state.error} />
94
- <input type="hidden" name="id" value={id} />
95
- <PendingButton
96
- showWorking
97
- className="text-xs text-destructive hover:underline"
98
- aria-label={formatFromCopy(copy, 'adminBanFilter.removeFilter', { pattern })}
99
- >
100
- {fromCopy(copy, 'admin.remove')}
101
- </PendingButton>
102
- </form>
93
+ <div className="flex flex-col items-end gap-1">
94
+ <form action={action} className="flex flex-col items-end gap-1">
95
+ <FormError message={state.error} />
96
+ <input type="hidden" name="id" value={id} />
97
+ <PendingButton
98
+ showWorking
99
+ className="text-xs text-destructive hover:underline"
100
+ aria-label={formatFromCopy(copy, 'adminBanFilter.removeFilter', { pattern })}
101
+ >
102
+ {fromCopy(copy, 'admin.remove')}
103
+ </PendingButton>
104
+ </form>
105
+ <ConfirmDialog confirm={state.confirm} action={action} />
106
+ </div>
103
107
  )
104
108
  }
@@ -25,6 +25,7 @@ import {
25
25
  } from '@/server/content-admin-actions'
26
26
 
27
27
  import { FormError, PendingButton, SubmitButton } from '../auth/form-controls'
28
+ import { ConfirmDialog } from '../shell/confirm-dialog'
28
29
  import { type Copy, fromCopy } from '../shell/copy'
29
30
  import { INPUT, Saved } from './form-bits'
30
31
 
@@ -91,6 +92,8 @@ export function WordFilterRowForm({ filter, copy }: { filter: WordFilterValues;
91
92
  {fromCopy(copy, 'adminContent.filter.removeThis')}
92
93
  </PendingButton>
93
94
  </form>
95
+
96
+ <ConfirmDialog confirm={removeState.confirm} action={removeAction} />
94
97
  </div>
95
98
  )
96
99
  }
@@ -140,13 +143,16 @@ export function DeletePrefixForm({ prefix, copy }: { prefix: PrefixValues; copy:
140
143
  const [state, action] = useActionState(deletePrefixAction, EMPTY_STATE)
141
144
 
142
145
  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
- <PendingButton showWorking className="text-xs text-muted-foreground hover:underline">
147
- {fromCopy(copy, 'admin.remove')}
148
- </PendingButton>
149
- </form>
146
+ <>
147
+ <form action={action} className="flex items-center gap-2">
148
+ <FormError message={state.error} />
149
+ <input type="hidden" name="id" value={prefix.id} />
150
+ <PendingButton showWorking className="text-xs text-muted-foreground hover:underline">
151
+ {fromCopy(copy, 'admin.remove')}
152
+ </PendingButton>
153
+ </form>
154
+ <ConfirmDialog confirm={state.confirm} action={action} />
155
+ </>
150
156
  )
151
157
  }
152
158
 
@@ -256,6 +262,8 @@ export function SmileyRowForm({ smiley, copy }: { smiley: SmileyValues; copy: Co
256
262
  {fromCopy(copy, 'admin.remove')}
257
263
  </PendingButton>
258
264
  </form>
265
+
266
+ <ConfirmDialog confirm={removeState.confirm} action={removeAction} />
259
267
  </div>
260
268
  )
261
269
  }
@@ -366,6 +374,8 @@ export function DirectiveRowForm({ directive, copy }: { directive: DirectiveValu
366
374
  {fromCopy(copy, 'admin.remove')}
367
375
  </PendingButton>
368
376
  </form>
377
+
378
+ <ConfirmDialog confirm={removeState.confirm} action={removeAction} />
369
379
  </div>
370
380
  )
371
381
  }
@@ -419,13 +429,16 @@ export function DeleteAttachmentForm({ attachmentId, copy }: { attachmentId: num
419
429
  const [state, action] = useActionState(deleteAttachmentAction, EMPTY_STATE)
420
430
 
421
431
  return (
422
- <form action={action} className="shrink-0">
423
- <FormError message={state.error} />
424
- <input type="hidden" name="id" value={attachmentId} />
425
- <PendingButton showWorking className="text-xs text-destructive hover:underline">
426
- {fromCopy(copy, 'adminContent.attachment.delete')}
427
- </PendingButton>
428
- </form>
432
+ <div className="shrink-0">
433
+ <form action={action}>
434
+ <FormError message={state.error} />
435
+ <input type="hidden" name="id" value={attachmentId} />
436
+ <PendingButton showWorking className="text-xs text-destructive hover:underline">
437
+ {fromCopy(copy, 'adminContent.attachment.delete')}
438
+ </PendingButton>
439
+ </form>
440
+ <ConfirmDialog confirm={state.confirm} action={action} />
441
+ </div>
429
442
  )
430
443
  }
431
444
 
@@ -572,6 +585,8 @@ export function AnnouncementRowForm({
572
585
  {fromCopy(copy, 'admin.remove')}
573
586
  </PendingButton>
574
587
  </form>
588
+
589
+ <ConfirmDialog confirm={removeState.confirm} action={removeAction} />
575
590
  </div>
576
591
  )
577
592
  }
@@ -674,6 +689,8 @@ export function CaptchaQuestionRowForm({
674
689
  {fromCopy(copy, 'admin.remove')}
675
690
  </PendingButton>
676
691
  </form>
692
+
693
+ <ConfirmDialog confirm={removeState.confirm} action={removeAction} />
677
694
  </div>
678
695
  )
679
696
  }