@meith/web 0.33.3 → 0.33.4
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)/thread/[slug]/page.tsx +1 -0
- package/package.json +49 -49
- package/src/components/moderation/thread-surgery-form.tsx +6 -5
- package/src/components/moderation/thread-tools-form.tsx +80 -57
- package/src/components/shell/header-peek-enhancer.tsx +58 -0
- package/src/components/shell/page-shell.tsx +2 -0
- package/src/server/upgrade-notice.ts +1 -1
- package/src/styles/globals.css +3 -3
- package/src/view/navigation.ts +16 -7
|
@@ -644,6 +644,7 @@ export default async function ThreadPage({
|
|
|
644
644
|
rights={toolRights}
|
|
645
645
|
moveTargets={moveTargets}
|
|
646
646
|
heading={threadToolsHeading(appointment.isForumModerator, await getTranslator())}
|
|
647
|
+
open={toolNotice !== undefined}
|
|
647
648
|
>
|
|
648
649
|
<ThreadSurgeryForm
|
|
649
650
|
copy={moderationFormsCopy(await getTranslator())}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meith/web",
|
|
3
|
-
"version": "0.33.
|
|
3
|
+
"version": "0.33.4",
|
|
4
4
|
"description": "The board itself: the Next.js app, and the forum-web bin that materializes it into an external board workspace.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -43,54 +43,54 @@
|
|
|
43
43
|
"react-dom": "19.2.8",
|
|
44
44
|
"tailwindcss": "^4.3.3",
|
|
45
45
|
"typescript": "7.0.2",
|
|
46
|
-
"@meith/accounts": "0.33.
|
|
47
|
-
"@meith/admin": "0.33.
|
|
48
|
-
"@meith/antispam": "0.33.
|
|
49
|
-
"@meith/api": "0.33.
|
|
50
|
-
"@meith/attachments": "0.33.
|
|
51
|
-
"@meith/authorization": "0.33.
|
|
52
|
-
"@meith/avatars": "0.33.
|
|
53
|
-
"@meith/board-digest": "0.33.
|
|
54
|
-
"@meith/core": "0.33.
|
|
55
|
-
"@meith/db": "0.33.
|
|
56
|
-
"@meith/demo": "0.33.
|
|
57
|
-
"@meith/
|
|
58
|
-
"@meith/
|
|
59
|
-
"@meith/events": "0.33.
|
|
60
|
-
"@meith/forums": "0.33.
|
|
61
|
-
"@meith/groups": "0.33.
|
|
62
|
-
"@meith/i18n": "0.33.
|
|
63
|
-
"@meith/import": "0.33.
|
|
64
|
-
"@meith/install": "0.33.
|
|
65
|
-
"@meith/mail": "0.33.
|
|
66
|
-
"@meith/markdown": "0.33.
|
|
67
|
-
"@meith/marketplace": "0.33.
|
|
68
|
-
"@meith/messages": "0.33.
|
|
69
|
-
"@meith/moderation": "0.33.
|
|
70
|
-
"@meith/notifications": "0.33.
|
|
71
|
-
"@meith/plugin-calendar": "0.33.
|
|
72
|
-
"@meith/plugin-dues": "0.33.
|
|
73
|
-
"@meith/plugin-kit": "0.33.
|
|
74
|
-
"@meith/polls": "0.33.
|
|
75
|
-
"@meith/posts": "0.33.
|
|
76
|
-
"@meith/profile-fields": "0.33.
|
|
77
|
-
"@meith/
|
|
78
|
-
"@meith/
|
|
79
|
-
"@meith/runtime": "0.33.
|
|
80
|
-
"@meith/search": "0.33.
|
|
81
|
-
"@meith/settings": "0.33.
|
|
82
|
-
"@meith/signatures": "0.33.
|
|
83
|
-
"@meith/subscriptions": "0.33.
|
|
84
|
-
"@meith/tasks": "0.33.
|
|
85
|
-
"@meith/theme-
|
|
86
|
-
"@meith/theme-
|
|
87
|
-
"@meith/theme-kit": "0.33.
|
|
88
|
-
"@meith/theme-midnight": "0.33.
|
|
89
|
-
"@meith/theme-phasebook": "0.33.
|
|
90
|
-
"@meith/theme-raidframe": "0.33.
|
|
91
|
-
"@meith/threads": "0.33.
|
|
92
|
-
"@meith/ui": "0.33.
|
|
93
|
-
"@meith/upgrade": "0.33.
|
|
46
|
+
"@meith/accounts": "0.33.4",
|
|
47
|
+
"@meith/admin": "0.33.4",
|
|
48
|
+
"@meith/antispam": "0.33.4",
|
|
49
|
+
"@meith/api": "0.33.4",
|
|
50
|
+
"@meith/attachments": "0.33.4",
|
|
51
|
+
"@meith/authorization": "0.33.4",
|
|
52
|
+
"@meith/avatars": "0.33.4",
|
|
53
|
+
"@meith/board-digest": "0.33.4",
|
|
54
|
+
"@meith/core": "0.33.4",
|
|
55
|
+
"@meith/db": "0.33.4",
|
|
56
|
+
"@meith/demo": "0.33.4",
|
|
57
|
+
"@meith/drivers": "0.33.4",
|
|
58
|
+
"@meith/drafts": "0.33.4",
|
|
59
|
+
"@meith/events": "0.33.4",
|
|
60
|
+
"@meith/forums": "0.33.4",
|
|
61
|
+
"@meith/groups": "0.33.4",
|
|
62
|
+
"@meith/i18n": "0.33.4",
|
|
63
|
+
"@meith/import": "0.33.4",
|
|
64
|
+
"@meith/install": "0.33.4",
|
|
65
|
+
"@meith/mail": "0.33.4",
|
|
66
|
+
"@meith/markdown": "0.33.4",
|
|
67
|
+
"@meith/marketplace": "0.33.4",
|
|
68
|
+
"@meith/messages": "0.33.4",
|
|
69
|
+
"@meith/moderation": "0.33.4",
|
|
70
|
+
"@meith/notifications": "0.33.4",
|
|
71
|
+
"@meith/plugin-calendar": "0.33.4",
|
|
72
|
+
"@meith/plugin-dues": "0.33.4",
|
|
73
|
+
"@meith/plugin-kit": "0.33.4",
|
|
74
|
+
"@meith/polls": "0.33.4",
|
|
75
|
+
"@meith/posts": "0.33.4",
|
|
76
|
+
"@meith/profile-fields": "0.33.4",
|
|
77
|
+
"@meith/relations": "0.33.4",
|
|
78
|
+
"@meith/reputation": "0.33.4",
|
|
79
|
+
"@meith/runtime": "0.33.4",
|
|
80
|
+
"@meith/search": "0.33.4",
|
|
81
|
+
"@meith/settings": "0.33.4",
|
|
82
|
+
"@meith/signatures": "0.33.4",
|
|
83
|
+
"@meith/subscriptions": "0.33.4",
|
|
84
|
+
"@meith/tasks": "0.33.4",
|
|
85
|
+
"@meith/theme-clubhouse": "0.33.4",
|
|
86
|
+
"@meith/theme-default": "0.33.4",
|
|
87
|
+
"@meith/theme-kit": "0.33.4",
|
|
88
|
+
"@meith/theme-midnight": "0.33.4",
|
|
89
|
+
"@meith/theme-phasebook": "0.33.4",
|
|
90
|
+
"@meith/theme-raidframe": "0.33.4",
|
|
91
|
+
"@meith/threads": "0.33.4",
|
|
92
|
+
"@meith/ui": "0.33.4",
|
|
93
|
+
"@meith/upgrade": "0.33.4"
|
|
94
94
|
},
|
|
95
95
|
"scripts": {
|
|
96
96
|
"dev": "next dev",
|
|
@@ -11,7 +11,8 @@ import { type Copy, formatFromCopy, fromCopy } from '../shell/copy'
|
|
|
11
11
|
const BUTTON =
|
|
12
12
|
'inline-flex h-8 items-center rounded-md border border-border px-3 text-xs font-medium hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
13
13
|
const FIELD =
|
|
14
|
-
'h-8 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
|
+
'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'
|
|
15
|
+
const LABEL = 'flex min-w-0 max-w-full items-center text-xs'
|
|
15
16
|
|
|
16
17
|
export interface SplitPoint {
|
|
17
18
|
readonly id: number
|
|
@@ -39,9 +40,9 @@ export function ThreadSurgeryForm({
|
|
|
39
40
|
<form action={splitAction} className="flex flex-col gap-2">
|
|
40
41
|
<div className="flex flex-wrap items-center gap-2">
|
|
41
42
|
<input type="hidden" name="threadId" value={threadId} />
|
|
42
|
-
<label className=
|
|
43
|
+
<label className={LABEL}>
|
|
43
44
|
<span className="sr-only">{fromCopy(copy, 'moderationForm.surgery.fromSr')}</span>
|
|
44
|
-
<select name="fromPostId" className={FIELD}>
|
|
45
|
+
<select name="fromPostId" className={`${FIELD} sm:max-w-64`}>
|
|
45
46
|
{splitPoints.map((point) => (
|
|
46
47
|
<option key={point.id} value={point.id}>
|
|
47
48
|
{formatFromCopy(copy, 'moderationForm.surgery.splitPoint', {
|
|
@@ -52,7 +53,7 @@ export function ThreadSurgeryForm({
|
|
|
52
53
|
))}
|
|
53
54
|
</select>
|
|
54
55
|
</label>
|
|
55
|
-
<label className=
|
|
56
|
+
<label className={LABEL}>
|
|
56
57
|
<span className="sr-only">{fromCopy(copy, 'moderationForm.surgery.titleSr')}</span>
|
|
57
58
|
<input
|
|
58
59
|
type="text"
|
|
@@ -76,7 +77,7 @@ export function ThreadSurgeryForm({
|
|
|
76
77
|
<form action={mergeAction} className="flex flex-col gap-2">
|
|
77
78
|
<div className="flex flex-wrap items-center gap-2">
|
|
78
79
|
<input type="hidden" name="threadId" value={threadId} />
|
|
79
|
-
<label className=
|
|
80
|
+
<label className={LABEL}>
|
|
80
81
|
<span className="sr-only">{fromCopy(copy, 'moderationForm.surgery.mergeSr')}</span>
|
|
81
82
|
<input
|
|
82
83
|
type="number"
|
|
@@ -11,6 +11,27 @@ import { type Copy, fromCopy } from '../shell/copy'
|
|
|
11
11
|
|
|
12
12
|
const BUTTON =
|
|
13
13
|
'inline-flex h-8 items-center rounded-md border border-border px-3 text-xs font-medium hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
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'
|
|
16
|
+
const SUMMARY =
|
|
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
|
+
|
|
19
|
+
function Chevron() {
|
|
20
|
+
return (
|
|
21
|
+
<svg
|
|
22
|
+
aria-hidden="true"
|
|
23
|
+
viewBox="0 0 12 12"
|
|
24
|
+
className="size-3 shrink-0 transition-transform group-open/tools:rotate-90"
|
|
25
|
+
fill="none"
|
|
26
|
+
stroke="currentColor"
|
|
27
|
+
strokeWidth="1.5"
|
|
28
|
+
strokeLinecap="round"
|
|
29
|
+
strokeLinejoin="round"
|
|
30
|
+
>
|
|
31
|
+
<path d="m4.5 3 3 3-3 3" />
|
|
32
|
+
</svg>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
14
35
|
|
|
15
36
|
export interface MoveOption {
|
|
16
37
|
readonly id: number
|
|
@@ -25,6 +46,7 @@ export function ThreadToolsForm({
|
|
|
25
46
|
moveTargets,
|
|
26
47
|
heading,
|
|
27
48
|
copy,
|
|
49
|
+
open = false,
|
|
28
50
|
children,
|
|
29
51
|
}: {
|
|
30
52
|
threadId: number
|
|
@@ -34,74 +56,75 @@ export function ThreadToolsForm({
|
|
|
34
56
|
moveTargets: readonly MoveOption[]
|
|
35
57
|
heading: string
|
|
36
58
|
copy: Copy
|
|
59
|
+
open?: boolean
|
|
37
60
|
children?: React.ReactNode
|
|
38
61
|
}) {
|
|
39
62
|
const [state, action] = useActionState(threadToolAction, EMPTY_STATE)
|
|
40
63
|
|
|
41
64
|
return (
|
|
42
|
-
<section
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<form action={action} className="flex flex-wrap items-center gap-3">
|
|
50
|
-
<input type="hidden" name="threadId" value={threadId} />
|
|
65
|
+
<section aria-label={heading} className="rounded-lg border border-border bg-secondary">
|
|
66
|
+
<details className="group/tools" open={open || state.error !== undefined ? true : undefined}>
|
|
67
|
+
<summary className={SUMMARY}>
|
|
68
|
+
<Chevron />
|
|
69
|
+
{heading}
|
|
70
|
+
</summary>
|
|
51
71
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
? fromCopy(copy, 'moderationForm.tool.unlock')
|
|
56
|
-
: fromCopy(copy, 'moderationForm.tool.lock')}
|
|
57
|
-
</PendingButton>
|
|
58
|
-
)}
|
|
59
|
-
{rights.stick && (
|
|
60
|
-
<PendingButton name="tool" value={isSticky ? 'unstick' : 'stick'} className={BUTTON}>
|
|
61
|
-
{isSticky
|
|
62
|
-
? fromCopy(copy, 'moderationForm.tool.unpin')
|
|
63
|
-
: fromCopy(copy, 'moderationForm.tool.pin')}
|
|
64
|
-
</PendingButton>
|
|
65
|
-
)}
|
|
66
|
-
{rights.delete && (
|
|
67
|
-
<PendingButton
|
|
68
|
-
name="tool"
|
|
69
|
-
value="delete"
|
|
70
|
-
className={`${BUTTON} border-destructive/40 text-destructive`}
|
|
71
|
-
>
|
|
72
|
-
{fromCopy(copy, 'moderationForm.tool.deleteThread')}
|
|
73
|
-
</PendingButton>
|
|
74
|
-
)}
|
|
72
|
+
<div className="flex flex-col gap-2 px-4 pb-3">
|
|
73
|
+
<form action={action} className="flex flex-wrap items-center gap-2">
|
|
74
|
+
<input type="hidden" name="threadId" value={threadId} />
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
))}
|
|
89
|
-
</select>
|
|
90
|
-
</label>
|
|
91
|
-
<PendingButton name="tool" value="move" className={BUTTON}>
|
|
92
|
-
{fromCopy(copy, 'moderationForm.tool.move')}
|
|
76
|
+
{rights.lock && (
|
|
77
|
+
<PendingButton name="tool" value={isLocked ? 'unlock' : 'lock'} className={BUTTON}>
|
|
78
|
+
{isLocked
|
|
79
|
+
? fromCopy(copy, 'moderationForm.tool.unlock')
|
|
80
|
+
: fromCopy(copy, 'moderationForm.tool.lock')}
|
|
81
|
+
</PendingButton>
|
|
82
|
+
)}
|
|
83
|
+
{rights.stick && (
|
|
84
|
+
<PendingButton name="tool" value={isSticky ? 'unstick' : 'stick'} className={BUTTON}>
|
|
85
|
+
{isSticky
|
|
86
|
+
? fromCopy(copy, 'moderationForm.tool.unpin')
|
|
87
|
+
: fromCopy(copy, 'moderationForm.tool.pin')}
|
|
93
88
|
</PendingButton>
|
|
94
|
-
|
|
95
|
-
|
|
89
|
+
)}
|
|
90
|
+
{rights.delete && (
|
|
91
|
+
<PendingButton
|
|
92
|
+
name="tool"
|
|
93
|
+
value="delete"
|
|
94
|
+
className={`${BUTTON} border-destructive/40 text-destructive`}
|
|
95
|
+
>
|
|
96
|
+
{fromCopy(copy, 'moderationForm.tool.deleteThread')}
|
|
96
97
|
</PendingButton>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
{rights.move && moveTargets.length > 0 && (
|
|
101
|
+
<span className="flex min-w-0 max-w-full basis-full flex-wrap items-center gap-2 sm:basis-auto">
|
|
102
|
+
<label className="flex min-w-0 max-w-full flex-1 items-center text-xs sm:flex-none">
|
|
103
|
+
<span className="sr-only">{fromCopy(copy, 'moderationForm.moveTo')}</span>
|
|
104
|
+
<select name="toForumId" className={`${SELECT} w-full sm:w-auto sm:max-w-56`}>
|
|
105
|
+
{moveTargets.map((forum) => (
|
|
106
|
+
<option key={forum.id} value={forum.id}>
|
|
107
|
+
{forum.title}
|
|
108
|
+
</option>
|
|
109
|
+
))}
|
|
110
|
+
</select>
|
|
111
|
+
</label>
|
|
112
|
+
<PendingButton name="tool" value="move" className={BUTTON}>
|
|
113
|
+
{fromCopy(copy, 'moderationForm.tool.move')}
|
|
114
|
+
</PendingButton>
|
|
115
|
+
<PendingButton name="tool" value="copy" className={BUTTON}>
|
|
116
|
+
{fromCopy(copy, 'moderationForm.tool.copy')}
|
|
117
|
+
</PendingButton>
|
|
118
|
+
</span>
|
|
119
|
+
)}
|
|
120
|
+
</form>
|
|
121
|
+
|
|
122
|
+
{children}
|
|
100
123
|
|
|
101
|
-
|
|
102
|
-
|
|
124
|
+
<FormError message={state.error} />
|
|
125
|
+
</div>
|
|
126
|
+
</details>
|
|
103
127
|
|
|
104
|
-
<FormError message={state.error} />
|
|
105
128
|
<ConfirmDialog confirm={state.confirm} action={action} />
|
|
106
129
|
</section>
|
|
107
130
|
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
|
|
5
|
+
const PEEKING_HEADER = '[data-header-peek]'
|
|
6
|
+
|
|
7
|
+
const SETTLE = 8
|
|
8
|
+
|
|
9
|
+
function peek(header: HTMLElement): () => void {
|
|
10
|
+
let last = window.scrollY
|
|
11
|
+
let ticking = false
|
|
12
|
+
|
|
13
|
+
function settle() {
|
|
14
|
+
ticking = false
|
|
15
|
+
const y = Math.max(0, window.scrollY)
|
|
16
|
+
const delta = y - last
|
|
17
|
+
|
|
18
|
+
if (y <= header.offsetHeight) {
|
|
19
|
+
header.removeAttribute('data-peek')
|
|
20
|
+
} else if (delta > SETTLE) {
|
|
21
|
+
header.setAttribute('data-peek', '')
|
|
22
|
+
} else if (delta < -SETTLE) {
|
|
23
|
+
header.removeAttribute('data-peek')
|
|
24
|
+
} else {
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
last = y
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function onScroll() {
|
|
31
|
+
if (ticking) return
|
|
32
|
+
ticking = true
|
|
33
|
+
window.requestAnimationFrame(settle)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function onFocusIn() {
|
|
37
|
+
header.removeAttribute('data-peek')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
window.addEventListener('scroll', onScroll, { passive: true })
|
|
41
|
+
header.addEventListener('focusin', onFocusIn)
|
|
42
|
+
|
|
43
|
+
return () => {
|
|
44
|
+
window.removeEventListener('scroll', onScroll)
|
|
45
|
+
header.removeEventListener('focusin', onFocusIn)
|
|
46
|
+
header.removeAttribute('data-peek')
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function HeaderPeekEnhancer() {
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const header = document.querySelector<HTMLElement>(PEEKING_HEADER)
|
|
53
|
+
if (header === null) return
|
|
54
|
+
return peek(header)
|
|
55
|
+
}, [])
|
|
56
|
+
|
|
57
|
+
return null
|
|
58
|
+
}
|
|
@@ -3,6 +3,7 @@ import { currentRequestId } from '@meith/core/logger'
|
|
|
3
3
|
import { requireSlot, slotCopy } from '@meith/theme-kit'
|
|
4
4
|
|
|
5
5
|
import { LogoutForm } from '@/components/account/logout-form'
|
|
6
|
+
import { HeaderPeekEnhancer } from '@/components/shell/header-peek-enhancer'
|
|
6
7
|
import { InstallBanner } from '@/components/shell/install-banner'
|
|
7
8
|
import { NavDisclosureEnhancer } from '@/components/shell/nav-disclosure-enhancer'
|
|
8
9
|
import { NotificationMenu } from '@/components/shell/notification-menu'
|
|
@@ -154,6 +155,7 @@ export async function PageShell({ actor, children }: { actor: Actor; children: R
|
|
|
154
155
|
</Header>
|
|
155
156
|
|
|
156
157
|
<NavDisclosureEnhancer />
|
|
158
|
+
<HeaderPeekEnhancer />
|
|
157
159
|
|
|
158
160
|
{await boardRegion('header.notice', actor)}
|
|
159
161
|
|
|
@@ -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.33.
|
|
20
|
+
export const CODE_VERSION = '0.33.4'
|
|
21
21
|
|
|
22
22
|
export interface UpgradeApplied {
|
|
23
23
|
readonly plugins: readonly string[]
|
package/src/styles/globals.css
CHANGED
|
@@ -486,11 +486,11 @@
|
|
|
486
486
|
* A permalink to `#post-12` otherwise lands the post flush against the top
|
|
487
487
|
* of the viewport, with its own header cropped and the previous post's footer
|
|
488
488
|
* nowhere — the reader cannot tell they arrived at the right one. The
|
|
489
|
-
* default theme's header is sticky
|
|
490
|
-
*
|
|
489
|
+
* default theme's header is sticky and 3.5rem tall, so the margin clears it
|
|
490
|
+
* with room to spare.
|
|
491
491
|
*/
|
|
492
492
|
:target {
|
|
493
|
-
scroll-margin-block-start:
|
|
493
|
+
scroll-margin-block-start: 5rem;
|
|
494
494
|
}
|
|
495
495
|
|
|
496
496
|
::selection {
|
package/src/view/navigation.ts
CHANGED
|
@@ -22,27 +22,36 @@ export interface BuiltInNavigationItem {
|
|
|
22
22
|
readonly messageKey: string
|
|
23
23
|
readonly href: string
|
|
24
24
|
readonly audience: NavigationAudience
|
|
25
|
+
readonly shown: boolean
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export const BUILT_IN_NAVIGATION: readonly BuiltInNavigationItem[] = [
|
|
28
|
-
{ key: 'home', messageKey: 'nav.home', href: '/', audience: 'all' },
|
|
29
|
-
{
|
|
29
|
+
{ key: 'home', messageKey: 'nav.home', href: '/', audience: 'all', shown: true },
|
|
30
|
+
{
|
|
31
|
+
key: 'new-posts',
|
|
32
|
+
messageKey: 'nav.newPosts',
|
|
33
|
+
href: '/discover/new',
|
|
34
|
+
audience: 'all',
|
|
35
|
+
shown: true,
|
|
36
|
+
},
|
|
30
37
|
{
|
|
31
38
|
key: 'unanswered',
|
|
32
39
|
messageKey: 'nav.unanswered',
|
|
33
40
|
href: '/discover/unanswered',
|
|
34
41
|
audience: 'all',
|
|
42
|
+
shown: false,
|
|
35
43
|
},
|
|
36
44
|
{
|
|
37
45
|
key: 'my-posts',
|
|
38
46
|
messageKey: 'nav.myPosts',
|
|
39
47
|
href: '/discover/participated',
|
|
40
48
|
audience: 'members',
|
|
49
|
+
shown: false,
|
|
41
50
|
},
|
|
42
|
-
{ key: 'search', messageKey: 'nav.search', href: '/search', audience: 'all' },
|
|
43
|
-
{ key: 'online', messageKey: 'nav.online', href: '/online', audience: 'all' },
|
|
44
|
-
{ key: 'members', messageKey: 'nav.members', href: '/members', audience: 'all' },
|
|
45
|
-
{ key: 'staff', messageKey: 'nav.staff', href: '/staff', audience: 'all' },
|
|
51
|
+
{ key: 'search', messageKey: 'nav.search', href: '/search', audience: 'all', shown: true },
|
|
52
|
+
{ key: 'online', messageKey: 'nav.online', href: '/online', audience: 'all', shown: false },
|
|
53
|
+
{ key: 'members', messageKey: 'nav.members', href: '/members', audience: 'all', shown: false },
|
|
54
|
+
{ key: 'staff', messageKey: 'nav.staff', href: '/staff', audience: 'all', shown: false },
|
|
46
55
|
]
|
|
47
56
|
|
|
48
57
|
const SEARCH_KEY = 'search'
|
|
@@ -64,7 +73,7 @@ export function defaultNavigationItems(): readonly NavigationItemRow[] {
|
|
|
64
73
|
displayOrder: index * 10,
|
|
65
74
|
audience: item.audience,
|
|
66
75
|
newTab: false,
|
|
67
|
-
enabled:
|
|
76
|
+
enabled: item.shown,
|
|
68
77
|
visibleToGroups: [],
|
|
69
78
|
}))
|
|
70
79
|
}
|