@meith/web 0.35.1 → 0.36.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/app/(board)/member/[id]/reputation/page.tsx +2 -4
- package/app/(board)/plugins/[key]/[[...path]]/page.tsx +2 -1
- package/app/(board)/search/page.tsx +2 -1
- package/app/(board)/unsubscribe/page.tsx +2 -2
- package/app/admin/layout.tsx +9 -1
- package/app/admin/plugins/[key]/[[...path]]/page.tsx +1 -3
- package/app/install/page.tsx +2 -4
- package/app/layout.tsx +2 -0
- package/package.json +50 -50
- package/src/components/account/active-sessions.tsx +2 -2
- package/src/components/account/avatar-form.tsx +3 -2
- package/src/components/account/feed-token.tsx +5 -5
- package/src/components/account/notification-forms.tsx +4 -6
- package/src/components/account/push-device-form.tsx +3 -2
- package/src/components/account/reputation-forms.tsx +5 -4
- package/src/components/account/sign-in-methods.tsx +2 -2
- package/src/components/account/subscription-forms.tsx +6 -8
- package/src/components/account/two-factor-forms.tsx +3 -4
- package/src/components/account/usercp-forms.tsx +48 -59
- package/src/components/admin/admin-forms.tsx +27 -18
- package/src/components/admin/form-bits.tsx +2 -3
- package/src/components/admin/mail-test-card.tsx +3 -1
- package/src/components/admin/theme-forms.tsx +4 -5
- package/src/components/auth/form-controls.tsx +22 -11
- package/src/components/content/edit-post-form.tsx +3 -2
- package/src/components/content/new-thread-form.tsx +4 -4
- package/src/components/messages/message-forms.tsx +9 -10
- package/src/components/moderation/inline-moderation-form.tsx +3 -2
- package/src/components/moderation/queue-form.tsx +3 -3
- package/src/components/moderation/report-forms.tsx +3 -3
- package/src/components/moderation/thread-surgery-form.tsx +4 -4
- package/src/components/moderation/thread-tools-form.tsx +4 -4
- package/src/components/moderation/warning-forms.tsx +8 -8
- package/src/components/shell/install-action.tsx +3 -2
- package/src/components/shell/nav-disclosure-enhancer.tsx +3 -0
- package/src/components/shell/scheme-toggle.tsx +1 -1
- package/src/components/shell/theme-switcher.tsx +9 -8
- package/src/components/shell/view-tabs.tsx +1 -65
- package/src/server/upgrade-notice.ts +1 -1
- package/src/styles/globals.css +63 -0
- package/src/view/copy.ts +2 -0
- package/src/view/panel-nav.ts +8 -10
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { useActionState } from 'react'
|
|
4
4
|
|
|
5
|
+
import { buttonVariants, controlVariants } from '@meith/ui'
|
|
6
|
+
|
|
5
7
|
import { EMPTY_STATE } from '@/server/auth-form-state'
|
|
6
8
|
import { threadToolAction } from '@/server/thread-tool-actions'
|
|
7
9
|
|
|
@@ -9,10 +11,8 @@ import { FormError, PendingButton } from '../auth/form-controls'
|
|
|
9
11
|
import { ConfirmDialog } from '../shell/confirm-dialog'
|
|
10
12
|
import { type Copy, fromCopy } from '../shell/copy'
|
|
11
13
|
|
|
12
|
-
const BUTTON =
|
|
13
|
-
|
|
14
|
-
const SELECT =
|
|
15
|
-
'h-8 min-w-0 max-w-full rounded-md border border-border bg-background px-2 text-xs focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
14
|
+
const BUTTON = buttonVariants({ variant: 'outline', size: 'sm' })
|
|
15
|
+
const SELECT = controlVariants({ size: 'sm', className: 'w-auto max-w-full' })
|
|
16
16
|
const SUMMARY =
|
|
17
17
|
'flex cursor-pointer list-none items-center gap-2 rounded-lg px-4 py-3 text-xs font-medium text-muted-foreground select-none hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring [&::-webkit-details-marker]:hidden'
|
|
18
18
|
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
import { useActionState } from 'react'
|
|
4
4
|
|
|
5
|
+
import { buttonVariants, controlVariants } from '@meith/ui'
|
|
6
|
+
|
|
5
7
|
import { EMPTY_STATE } from '@/server/auth-form-state'
|
|
6
8
|
import { issueWarningAction, revokeWarningAction } from '@/server/warning-actions'
|
|
7
9
|
|
|
8
10
|
import { FormError, PendingButton } from '../auth/form-controls'
|
|
9
11
|
import { type Copy, fromCopy } from '../shell/copy'
|
|
10
12
|
|
|
11
|
-
const FIELD =
|
|
12
|
-
'w-full rounded-md border border-input bg-card px-3 py-2 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
13
|
+
const FIELD = controlVariants()
|
|
13
14
|
|
|
14
|
-
const BUTTON =
|
|
15
|
-
'inline-flex h-9 items-center justify-center rounded-md bg-primary px-4 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
15
|
+
const BUTTON = buttonVariants({ variant: 'primary', size: 'default' })
|
|
16
16
|
|
|
17
17
|
export interface WarningTypeOption {
|
|
18
18
|
readonly id: number
|
|
@@ -45,7 +45,7 @@ export function IssueWarningForm({
|
|
|
45
45
|
<input type="hidden" name="userId" value={userId} />
|
|
46
46
|
{postId !== null && <input type="hidden" name="postId" value={postId} />}
|
|
47
47
|
|
|
48
|
-
<label className="flex flex-col gap-
|
|
48
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
49
49
|
<span className="font-medium">{fromCopy(copy, 'moderationForm.warn.reason')}</span>
|
|
50
50
|
<select name="typeId" className={FIELD} defaultValue="">
|
|
51
51
|
<option value="">{fromCopy(copy, 'moderationForm.warn.somethingElse')}</option>
|
|
@@ -58,20 +58,20 @@ export function IssueWarningForm({
|
|
|
58
58
|
</label>
|
|
59
59
|
|
|
60
60
|
<div className="grid gap-4 sm:grid-cols-[1fr_8rem]">
|
|
61
|
-
<label className="flex flex-col gap-
|
|
61
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
62
62
|
<span className="font-medium">{fromCopy(copy, 'moderationForm.warn.titleLabel')}</span>
|
|
63
63
|
<input type="text" name="title" maxLength={150} className={FIELD} />
|
|
64
64
|
<span className="text-xs text-muted-foreground">
|
|
65
65
|
{fromCopy(copy, 'moderationForm.warn.titleHint')}
|
|
66
66
|
</span>
|
|
67
67
|
</label>
|
|
68
|
-
<label className="flex flex-col gap-
|
|
68
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
69
69
|
<span className="font-medium">{fromCopy(copy, 'moderationForm.warn.points')}</span>
|
|
70
70
|
<input type="number" name="points" min={1} max={100} className={FIELD} />
|
|
71
71
|
</label>
|
|
72
72
|
</div>
|
|
73
73
|
|
|
74
|
-
<label className="flex flex-col gap-
|
|
74
|
+
<label className="flex min-w-0 flex-col gap-2 text-sm">
|
|
75
75
|
<span className="font-medium">{fromCopy(copy, 'moderationForm.warn.what')}</span>
|
|
76
76
|
<textarea name="reason" rows={4} maxLength={2000} className={FIELD} required />
|
|
77
77
|
<span className="text-xs text-muted-foreground">
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState } from 'react'
|
|
4
4
|
|
|
5
|
+
import { buttonVariants } from '@meith/ui'
|
|
6
|
+
|
|
5
7
|
import { INSTALL_BANNER_COOKIE, INSTALL_BANNER_COOKIE_MAX_AGE } from '@/view/install-banner'
|
|
6
8
|
|
|
7
9
|
interface InstallPrompt extends Event {
|
|
@@ -23,8 +25,7 @@ function isInstallPrompt(event: Event): event is InstallPrompt {
|
|
|
23
25
|
return 'prompt' in event && 'userChoice' in event
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
const BUTTON =
|
|
27
|
-
'inline-flex h-8 items-center rounded-md border border-border bg-background px-3 text-xs font-medium text-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
28
|
+
const BUTTON = buttonVariants({ variant: 'outline', size: 'sm' })
|
|
28
29
|
|
|
29
30
|
export function InstallAction({ label, how }: { label: string; how: string }) {
|
|
30
31
|
const [pending, setPending] = useState<InstallPrompt | null>(null)
|
|
@@ -19,6 +19,9 @@ export function NavDisclosureEnhancer() {
|
|
|
19
19
|
if (event.key !== 'Escape') return
|
|
20
20
|
|
|
21
21
|
for (const details of document.querySelectorAll<HTMLDetailsElement>(OPEN_DISCLOSURES)) {
|
|
22
|
+
if (details.contains(document.activeElement)) {
|
|
23
|
+
details.querySelector('summary')?.focus()
|
|
24
|
+
}
|
|
22
25
|
details.open = false
|
|
23
26
|
}
|
|
24
27
|
}
|
|
@@ -55,7 +55,7 @@ export function SchemeToggle({
|
|
|
55
55
|
title={labels[option]}
|
|
56
56
|
aria-pressed={selected}
|
|
57
57
|
onClick={() => applySchemeImmediately(option)}
|
|
58
|
-
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 ${
|
|
58
|
+
className={`inline-flex h-8 pointer-coarse:min-h-11 pointer-coarse:min-w-11 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 ${
|
|
59
59
|
selected
|
|
60
60
|
? 'bg-primary text-primary-foreground'
|
|
61
61
|
: 'bg-background hover:bg-accent hover:text-accent-foreground'
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { buttonVariants, cn, controlVariants } from '@meith/ui'
|
|
2
|
+
|
|
1
3
|
import { PendingButton } from '@/components/auth/form-controls'
|
|
2
4
|
import { MonitorIcon, MoonIcon, SunIcon } from '@/components/shell/appearance-icons'
|
|
3
5
|
import { SchemeToggle } from '@/components/shell/scheme-toggle'
|
|
@@ -13,9 +15,6 @@ const SCHEME_LABEL_KEY: Record<ColourSchemePreference, string> = {
|
|
|
13
15
|
dark: 'appearance.scheme.dark',
|
|
14
16
|
}
|
|
15
17
|
|
|
16
|
-
const CONTROL =
|
|
17
|
-
'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'
|
|
18
|
-
|
|
19
18
|
export async function ThemeSwitcher() {
|
|
20
19
|
const [{ choices }, theme, scheme, t] = await Promise.all([
|
|
21
20
|
getBoardThemeStyle(),
|
|
@@ -43,17 +42,19 @@ export async function ThemeSwitcher() {
|
|
|
43
42
|
<label htmlFor="appearance-theme" className="text-xs text-muted-foreground">
|
|
44
43
|
{t.t('appearance.theme')}
|
|
45
44
|
</label>
|
|
46
|
-
<select
|
|
45
|
+
<select
|
|
46
|
+
id="appearance-theme"
|
|
47
|
+
name="theme"
|
|
48
|
+
defaultValue={theme}
|
|
49
|
+
className={cn(controlVariants({ size: 'sm' }), 'w-auto')}
|
|
50
|
+
>
|
|
47
51
|
{choices.map((choice) => (
|
|
48
52
|
<option key={choice.key} value={choice.key}>
|
|
49
53
|
{choice.title}
|
|
50
54
|
</option>
|
|
51
55
|
))}
|
|
52
56
|
</select>
|
|
53
|
-
<PendingButton
|
|
54
|
-
showWorking
|
|
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
|
+
<PendingButton showWorking className={buttonVariants({ variant: 'outline', size: 'sm' })}>
|
|
57
58
|
{t.t('appearance.apply')}
|
|
58
59
|
</PendingButton>
|
|
59
60
|
</form>
|
|
@@ -1,65 +1 @@
|
|
|
1
|
-
|
|
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
|
|
25
|
-
aria-label={label}
|
|
26
|
-
className={cn('flex flex-wrap items-center gap-x-3 gap-y-2', className)}
|
|
27
|
-
>
|
|
28
|
-
<ul className="inline-flex max-w-full items-center gap-1 overflow-x-auto rounded-lg border border-border bg-surface p-1">
|
|
29
|
-
{tabs.map((tab) => (
|
|
30
|
-
<li key={tab.href} className="shrink-0">
|
|
31
|
-
<a
|
|
32
|
-
href={tab.href}
|
|
33
|
-
{...(tab.isCurrent ? { 'aria-current': 'page' as const } : {})}
|
|
34
|
-
className={cn(
|
|
35
|
-
'inline-flex h-8 items-center gap-1.5 rounded-md px-3 text-sm whitespace-nowrap transition-colors',
|
|
36
|
-
'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',
|
|
37
|
-
tab.isCurrent
|
|
38
|
-
? 'bg-card font-semibold text-primary shadow-sm'
|
|
39
|
-
: 'font-medium text-muted-foreground hover:text-foreground',
|
|
40
|
-
)}
|
|
41
|
-
>
|
|
42
|
-
{tab.label}
|
|
43
|
-
{tab.count !== undefined && tab.count > 0 && (
|
|
44
|
-
<span
|
|
45
|
-
className={cn(
|
|
46
|
-
'rounded px-1.5 text-xs font-semibold tabular-nums',
|
|
47
|
-
tab.isCurrent
|
|
48
|
-
? 'bg-secondary text-secondary-foreground'
|
|
49
|
-
: 'bg-muted text-muted-foreground',
|
|
50
|
-
)}
|
|
51
|
-
>
|
|
52
|
-
{tab.count}
|
|
53
|
-
</span>
|
|
54
|
-
)}
|
|
55
|
-
</a>
|
|
56
|
-
</li>
|
|
57
|
-
))}
|
|
58
|
-
</ul>
|
|
59
|
-
|
|
60
|
-
{aside !== undefined && (
|
|
61
|
-
<span className="shrink-0 text-xs text-muted-foreground">{aside}</span>
|
|
62
|
-
)}
|
|
63
|
-
</nav>
|
|
64
|
-
)
|
|
65
|
-
}
|
|
1
|
+
export { type NavTab as ViewTab, NavTabs as ViewTabs } from '@meith/ui'
|
|
@@ -17,7 +17,7 @@ import { planUpgrade, type UpgradeState, upgradeNotice } from '@meith/upgrade'
|
|
|
17
17
|
|
|
18
18
|
import { activeDefinitions } from './plugin-host'
|
|
19
19
|
|
|
20
|
-
export const CODE_VERSION = '0.
|
|
20
|
+
export const CODE_VERSION = '0.36.0'
|
|
21
21
|
|
|
22
22
|
export interface UpgradeApplied {
|
|
23
23
|
readonly plugins: readonly string[]
|
package/src/styles/globals.css
CHANGED
|
@@ -436,7 +436,12 @@
|
|
|
436
436
|
border-color: var(--border);
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
+
html {
|
|
440
|
+
scrollbar-gutter: stable;
|
|
441
|
+
}
|
|
442
|
+
|
|
439
443
|
body {
|
|
444
|
+
-webkit-font-smoothing: antialiased;
|
|
440
445
|
background-color: var(--background);
|
|
441
446
|
color: var(--foreground);
|
|
442
447
|
}
|
|
@@ -465,6 +470,7 @@
|
|
|
465
470
|
h3,
|
|
466
471
|
h4 {
|
|
467
472
|
text-wrap: balance;
|
|
473
|
+
overflow-wrap: anywhere;
|
|
468
474
|
}
|
|
469
475
|
|
|
470
476
|
/*
|
|
@@ -1000,3 +1006,60 @@ select:not([multiple], [size]):disabled {
|
|
|
1000
1006
|
font-size: 1rem;
|
|
1001
1007
|
}
|
|
1002
1008
|
}
|
|
1009
|
+
|
|
1010
|
+
[data-slot="navigation-drawer"] {
|
|
1011
|
+
translate: 100% 0;
|
|
1012
|
+
transition:
|
|
1013
|
+
translate 180ms ease-out,
|
|
1014
|
+
display 180ms allow-discrete,
|
|
1015
|
+
overlay 180ms allow-discrete;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
[dir="rtl"] [data-slot="navigation-drawer"] {
|
|
1019
|
+
translate: -100% 0;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
[data-slot="navigation-drawer"]:popover-open {
|
|
1023
|
+
translate: 0 0;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
@starting-style {
|
|
1027
|
+
[data-slot="navigation-drawer"]:popover-open {
|
|
1028
|
+
translate: 100% 0;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
[dir="rtl"] [data-slot="navigation-drawer"]:popover-open {
|
|
1032
|
+
translate: -100% 0;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
@media (width < 64rem) {
|
|
1037
|
+
html:has([data-slot="navigation-drawer"]:popover-open) {
|
|
1038
|
+
overflow: hidden;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
@media (width >= 64rem) {
|
|
1043
|
+
[data-slot="navigation-drawer"]:popover-open {
|
|
1044
|
+
display: none;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1049
|
+
[data-slot="navigation-drawer"] {
|
|
1050
|
+
transition: none;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
[data-panel-nav-trigger] {
|
|
1055
|
+
display: none;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
body:has(#panel-usercp-navigation) [data-panel-nav-trigger="usercp"],
|
|
1059
|
+
body:has(#panel-modcp-navigation) [data-panel-nav-trigger="modcp"] {
|
|
1060
|
+
display: inline-flex;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
body:has(#panel-usercp-navigation, #panel-modcp-navigation) [data-main-navigation] > button {
|
|
1064
|
+
display: none;
|
|
1065
|
+
}
|
package/src/view/copy.ts
CHANGED
package/src/view/panel-nav.ts
CHANGED
|
@@ -178,16 +178,14 @@ export function buildPanelNavModel(input: {
|
|
|
178
178
|
isRecord: false,
|
|
179
179
|
isOpen,
|
|
180
180
|
isOverview: section.href === input.overviewHref,
|
|
181
|
-
children:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}))
|
|
190
|
-
: [],
|
|
181
|
+
children: visibleChildren(section, deepest).map((child) => ({
|
|
182
|
+
href: child.href,
|
|
183
|
+
title: panelText(t, child),
|
|
184
|
+
icon: null,
|
|
185
|
+
count: countFor(input.counts, child.href),
|
|
186
|
+
current: currentFor(input.location, child.href, deepest),
|
|
187
|
+
isRecord: child.record === true,
|
|
188
|
+
})),
|
|
191
189
|
}
|
|
192
190
|
})
|
|
193
191
|
|