@meith/theme-phasebook 0.6.0 → 0.8.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/package.json +4 -4
- package/src/copy.ts +276 -0
- package/src/index.ts +2 -2
- package/src/messages/en.json +149 -0
- package/src/messages/index.ts +5 -0
- package/src/shared.tsx +5 -5
- package/src/slots/announcement.tsx +13 -3
- package/src/slots/board-index.tsx +12 -5
- package/src/slots/board-stats.tsx +14 -12
- package/src/slots/discovery-view.tsx +13 -7
- package/src/slots/error-notice.tsx +17 -6
- package/src/slots/footer.tsx +15 -4
- package/src/slots/forum-display.tsx +23 -14
- package/src/slots/forum-row.tsx +10 -7
- package/src/slots/header.tsx +17 -4
- package/src/slots/latest-posts.tsx +12 -8
- package/src/slots/latest-threads.tsx +18 -9
- package/src/slots/member-profile.tsx +15 -13
- package/src/slots/navigation.tsx +6 -3
- package/src/slots/notice.tsx +10 -8
- package/src/slots/pagination.tsx +13 -10
- package/src/slots/panel-nav.tsx +30 -8
- package/src/slots/panel-page.tsx +3 -8
- package/src/slots/post-actions.tsx +17 -10
- package/src/slots/post-bit.tsx +21 -16
- package/src/slots/post-form.tsx +7 -3
- package/src/slots/redirect-notice.tsx +15 -6
- package/src/slots/search-form.tsx +15 -8
- package/src/slots/search-results.tsx +18 -17
- package/src/slots/shell.tsx +4 -3
- package/src/slots/subforum-list.tsx +9 -5
- package/src/slots/thread-row.tsx +16 -13
- package/src/slots/thread-view.tsx +33 -10
- package/src/slots/user-panel.tsx +18 -14
- package/src/slots/who-is-online.tsx +33 -16
- package/src/theme.ts +61 -1
|
@@ -1,8 +1,29 @@
|
|
|
1
|
-
import type { ThreadViewModel } from '@meith/theme-kit'
|
|
1
|
+
import type { SlotCopy, ThreadViewModel } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
count,
|
|
6
|
+
FEED,
|
|
7
|
+
MUTED_LINK,
|
|
8
|
+
NUMERIC,
|
|
9
|
+
PAGE,
|
|
10
|
+
PILL,
|
|
11
|
+
PILL_PRIMARY,
|
|
12
|
+
Prefix,
|
|
13
|
+
plural,
|
|
14
|
+
Tag,
|
|
15
|
+
} from '../shared'
|
|
16
|
+
|
|
17
|
+
export function ThreadView({
|
|
18
|
+
thread,
|
|
19
|
+
forum,
|
|
20
|
+
replyHref,
|
|
21
|
+
markReadAction,
|
|
22
|
+
regions,
|
|
23
|
+
copy,
|
|
24
|
+
}: ThreadViewModel & { copy: SlotCopy }) {
|
|
25
|
+
const c = (key: string) => fromSlotCopy(copy, `phasebook.threadView.${key}`)
|
|
4
26
|
|
|
5
|
-
export function ThreadView({ thread, forum, replyHref, markReadAction, regions }: ThreadViewModel) {
|
|
6
27
|
return (
|
|
7
28
|
<div className={`${PAGE} py-4 sm:py-6`}>
|
|
8
29
|
<div className={`${FEED} flex flex-col gap-4`}>
|
|
@@ -19,13 +40,15 @@ export function ThreadView({ thread, forum, replyHref, markReadAction, regions }
|
|
|
19
40
|
|
|
20
41
|
<div className="mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1">
|
|
21
42
|
{thread.prefix !== null && <Prefix prefix={thread.prefix} />}
|
|
22
|
-
{thread.isSticky && <Tag token="thread-pinned">
|
|
23
|
-
{thread.isLocked && <Tag token="thread-locked">
|
|
24
|
-
{thread.isMoved && <Tag token="thread-moved">
|
|
43
|
+
{thread.isSticky && <Tag token="thread-pinned">{c('pinned')}</Tag>}
|
|
44
|
+
{thread.isLocked && <Tag token="thread-locked">{c('locked')}</Tag>}
|
|
45
|
+
{thread.isMoved && <Tag token="thread-moved">{c('moved')}</Tag>}
|
|
25
46
|
|
|
26
47
|
<span className={`text-xs text-muted-foreground ${NUMERIC}`}>
|
|
27
|
-
{count(thread.replyCount)}
|
|
28
|
-
{
|
|
48
|
+
{count(thread.replyCount)}{' '}
|
|
49
|
+
{plural(thread.replyCount, c('reply.one'), c('reply.other'))} ·{' '}
|
|
50
|
+
{count(thread.viewCount)}{' '}
|
|
51
|
+
{plural(thread.viewCount, c('view.one'), c('view.other'))}
|
|
29
52
|
</span>
|
|
30
53
|
</div>
|
|
31
54
|
</div>
|
|
@@ -34,13 +57,13 @@ export function ThreadView({ thread, forum, replyHref, markReadAction, regions }
|
|
|
34
57
|
{markReadAction !== null && (
|
|
35
58
|
<form action={markReadAction} method="post">
|
|
36
59
|
<button type="submit" className={PILL}>
|
|
37
|
-
|
|
60
|
+
{c('markRead')}
|
|
38
61
|
</button>
|
|
39
62
|
</form>
|
|
40
63
|
)}
|
|
41
64
|
{replyHref !== null && (
|
|
42
65
|
<a href={replyHref} className={PILL_PRIMARY}>
|
|
43
|
-
|
|
66
|
+
{c('replyAction')}
|
|
44
67
|
</a>
|
|
45
68
|
)}
|
|
46
69
|
</div>
|
package/src/slots/user-panel.tsx
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import type { CountModel, SlotCopy, UserPanelModel } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
1
3
|
import { Menu } from '@meith/ui/menu'
|
|
2
|
-
import type { UserPanelModel } from '@meith/theme-kit'
|
|
3
4
|
|
|
4
|
-
import { Circle, ICON_BUTTON, MUTED_LINK, PILL, PILL_PRIMARY
|
|
5
|
+
import { Circle, count, ICON_BUTTON, MUTED_LINK, PILL, PILL_PRIMARY } from '../shared'
|
|
5
6
|
|
|
6
7
|
function BellIcon() {
|
|
7
8
|
return (
|
|
@@ -63,10 +64,10 @@ function IconButton({
|
|
|
63
64
|
)
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
function CountBadge({ value, label }: { value:
|
|
67
|
+
function CountBadge({ value, label }: { value: CountModel; label: string }) {
|
|
67
68
|
return (
|
|
68
69
|
<span className="absolute -top-0.5 -right-0.5 inline-flex min-w-5 items-center justify-center rounded-full bg-destructive px-1 text-xs font-semibold text-destructive-foreground ring-2 ring-card">
|
|
69
|
-
<span aria-hidden="true">{value > 99 ? '99+' : value}</span>
|
|
70
|
+
<span aria-hidden="true">{value.value > 99 ? '99+' : value.label}</span>
|
|
70
71
|
<span className="sr-only">
|
|
71
72
|
{count(value)} {label}
|
|
72
73
|
</span>
|
|
@@ -82,7 +83,10 @@ export function UserPanel({
|
|
|
82
83
|
notificationsHref,
|
|
83
84
|
messagesHref,
|
|
84
85
|
children,
|
|
85
|
-
|
|
86
|
+
copy,
|
|
87
|
+
}: UserPanelModel & { copy: SlotCopy }) {
|
|
88
|
+
const c = (key: string) => fromSlotCopy(copy, `phasebook.userPanel.${key}`)
|
|
89
|
+
|
|
86
90
|
if (viewer.isGuest) {
|
|
87
91
|
return (
|
|
88
92
|
<div className="flex items-center gap-2">
|
|
@@ -95,7 +99,7 @@ export function UserPanel({
|
|
|
95
99
|
)
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
const name = viewer.username ?? '
|
|
102
|
+
const name = viewer.username ?? c('signedIn')
|
|
99
103
|
|
|
100
104
|
return (
|
|
101
105
|
<div className="flex items-center gap-2">
|
|
@@ -106,23 +110,23 @@ export function UserPanel({
|
|
|
106
110
|
}}
|
|
107
111
|
/>
|
|
108
112
|
|
|
109
|
-
{unreadNotifications > 0 && (
|
|
110
|
-
<IconButton href={notificationsHref ?? null} title=
|
|
113
|
+
{unreadNotifications.value > 0 && (
|
|
114
|
+
<IconButton href={notificationsHref ?? null} title={c('notifications')}>
|
|
111
115
|
<BellIcon />
|
|
112
|
-
<CountBadge value={unreadNotifications} label=
|
|
116
|
+
<CountBadge value={unreadNotifications} label={c('unreadNotifications')} />
|
|
113
117
|
</IconButton>
|
|
114
118
|
)}
|
|
115
119
|
|
|
116
|
-
{unreadMessages > 0 && (
|
|
117
|
-
<IconButton href={messagesHref ?? null} title=
|
|
120
|
+
{unreadMessages.value > 0 && (
|
|
121
|
+
<IconButton href={messagesHref ?? null} title={c('messages')}>
|
|
118
122
|
<MessageIcon />
|
|
119
|
-
<CountBadge value={unreadMessages} label=
|
|
123
|
+
<CountBadge value={unreadMessages} label={c('unreadMessages')} />
|
|
120
124
|
</IconButton>
|
|
121
125
|
)}
|
|
122
126
|
|
|
123
127
|
<div data-account="menu" className="flex min-w-0 items-center">
|
|
124
128
|
<Menu
|
|
125
|
-
label=
|
|
129
|
+
label={c('accountAriaLabel')}
|
|
126
130
|
items={links}
|
|
127
131
|
trigger={
|
|
128
132
|
<>
|
|
@@ -138,7 +142,7 @@ export function UserPanel({
|
|
|
138
142
|
<nav
|
|
139
143
|
data-account="plain"
|
|
140
144
|
style={{ display: 'none' }}
|
|
141
|
-
aria-label=
|
|
145
|
+
aria-label={c('accountAriaLabel')}
|
|
142
146
|
className="flex-wrap items-center gap-x-3 gap-y-1 text-xs"
|
|
143
147
|
>
|
|
144
148
|
<span className="font-semibold text-foreground">{name}</span>
|
|
@@ -1,10 +1,24 @@
|
|
|
1
|
-
import type { OnlineMemberModel, WhoIsOnlineModel } from '@meith/theme-kit'
|
|
1
|
+
import type { OnlineMemberModel, SlotCopy, WhoIsOnlineModel } from '@meith/theme-kit'
|
|
2
|
+
import { fromSlotCopy } from '@meith/theme-kit'
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
Circle,
|
|
6
|
+
count,
|
|
7
|
+
MUTED_LINK,
|
|
8
|
+
NUMERIC,
|
|
9
|
+
OnlineDot,
|
|
10
|
+
plural,
|
|
11
|
+
Rail,
|
|
12
|
+
Stamp,
|
|
13
|
+
Tag,
|
|
14
|
+
UserRef,
|
|
15
|
+
} from '../shared'
|
|
4
16
|
|
|
5
17
|
const VISIBLE_NAMES = 8
|
|
6
18
|
|
|
7
|
-
function Row({ member }: { member: OnlineMemberModel }) {
|
|
19
|
+
function Row({ member, copy }: { member: OnlineMemberModel; copy: SlotCopy }) {
|
|
20
|
+
const c = (key: string) => fromSlotCopy(copy, `phasebook.whoIsOnline.${key}`)
|
|
21
|
+
|
|
8
22
|
return (
|
|
9
23
|
<li className="flex items-center gap-2.5 rounded-lg px-2 py-1.5 transition-colors hover:bg-accent">
|
|
10
24
|
<span className="relative shrink-0">
|
|
@@ -25,7 +39,7 @@ function Row({ member }: { member: OnlineMemberModel }) {
|
|
|
25
39
|
)}
|
|
26
40
|
</span>
|
|
27
41
|
|
|
28
|
-
{member.isInvisible && <Tag>
|
|
42
|
+
{member.isInvisible && <Tag>{c('invisible')}</Tag>}
|
|
29
43
|
</li>
|
|
30
44
|
)
|
|
31
45
|
}
|
|
@@ -33,44 +47,47 @@ function Row({ member }: { member: OnlineMemberModel }) {
|
|
|
33
47
|
export function WhoIsOnline({
|
|
34
48
|
guestCount,
|
|
35
49
|
members,
|
|
50
|
+
memberCount,
|
|
36
51
|
total,
|
|
37
52
|
recordCount,
|
|
38
53
|
recordAt,
|
|
39
54
|
fullListHref,
|
|
40
|
-
|
|
55
|
+
copy,
|
|
56
|
+
}: WhoIsOnlineModel & { copy: SlotCopy }) {
|
|
41
57
|
const shown = members.slice(0, VISIBLE_NAMES)
|
|
42
58
|
const hidden = members.length - shown.length
|
|
43
59
|
|
|
60
|
+
const c = (key: string) => fromSlotCopy(copy, `phasebook.whoIsOnline.${key}`)
|
|
61
|
+
|
|
44
62
|
return (
|
|
45
63
|
<Rail
|
|
46
|
-
title=
|
|
64
|
+
title={c('title')}
|
|
47
65
|
titleId="who-is-online-heading"
|
|
48
66
|
action={
|
|
49
67
|
<a href={fullListHref} className={`text-xs font-semibold ${MUTED_LINK}`}>
|
|
50
|
-
|
|
68
|
+
{c('seeAll')}
|
|
51
69
|
</a>
|
|
52
70
|
}
|
|
53
71
|
>
|
|
54
72
|
<p className={`px-3 text-xs text-muted-foreground ${NUMERIC}`}>
|
|
55
|
-
{count(total)} online
|
|
56
|
-
|
|
73
|
+
{count(total)} {c('online')} {count(memberCount)}{' '}
|
|
74
|
+
{plural(memberCount, c('member.one'), c('member.other'))}, {count(guestCount)}{' '}
|
|
75
|
+
{plural(guestCount, c('guest.one'), c('guest.other'))}
|
|
57
76
|
</p>
|
|
58
77
|
|
|
59
|
-
{
|
|
78
|
+
{memberCount.value === 0 ? (
|
|
60
79
|
<p className="px-3 pt-2 pb-3 text-xs text-muted-foreground">
|
|
61
|
-
{guestCount === 0
|
|
62
|
-
? 'Nobody is reading the board right now.'
|
|
63
|
-
: 'Only guests are reading the board right now.'}
|
|
80
|
+
{guestCount.value === 0 ? c('nobody') : c('onlyGuests')}
|
|
64
81
|
</p>
|
|
65
82
|
) : (
|
|
66
83
|
<ul className="px-1 pt-1 pb-1">
|
|
67
84
|
{shown.map((member) => (
|
|
68
|
-
<Row key={member.userId ?? member.username} member={member} />
|
|
85
|
+
<Row key={member.userId ?? member.username} member={member} copy={copy} />
|
|
69
86
|
))}
|
|
70
87
|
{hidden > 0 && (
|
|
71
88
|
<li className="px-2 py-1.5">
|
|
72
89
|
<a href={fullListHref} className={`text-xs font-semibold ${MUTED_LINK}`}>
|
|
73
|
-
and {
|
|
90
|
+
{c('and')} {hidden} {c('more')}
|
|
74
91
|
</a>
|
|
75
92
|
</li>
|
|
76
93
|
)}
|
|
@@ -79,7 +96,7 @@ export function WhoIsOnline({
|
|
|
79
96
|
|
|
80
97
|
{recordAt !== null && (
|
|
81
98
|
<p className={`border-t border-border px-3 py-2 text-xs text-muted-foreground ${NUMERIC}`}>
|
|
82
|
-
|
|
99
|
+
{c('record')} {count(recordCount)} {c('on')} <Stamp at={recordAt} />
|
|
83
100
|
</p>
|
|
84
101
|
)}
|
|
85
102
|
</Rail>
|
package/src/theme.ts
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
import { defaultTheme } from '@meith/theme-default'
|
|
2
2
|
import { defineTheme } from '@meith/theme-kit'
|
|
3
3
|
|
|
4
|
+
import {
|
|
5
|
+
announcementCopy,
|
|
6
|
+
boardIndexCopy,
|
|
7
|
+
boardStatsCopy,
|
|
8
|
+
discoveryViewCopy,
|
|
9
|
+
errorNoticeCopy,
|
|
10
|
+
footerCopy,
|
|
11
|
+
forumDisplayCopy,
|
|
12
|
+
forumRowCopy,
|
|
13
|
+
headerCopy,
|
|
14
|
+
latestPostsCopy,
|
|
15
|
+
latestThreadsCopy,
|
|
16
|
+
memberProfileCopy,
|
|
17
|
+
navigationCopy,
|
|
18
|
+
noticeCopy,
|
|
19
|
+
paginationCopy,
|
|
20
|
+
panelNavCopy,
|
|
21
|
+
postActionsCopy,
|
|
22
|
+
postBitCopy,
|
|
23
|
+
postFormCopy,
|
|
24
|
+
redirectNoticeCopy,
|
|
25
|
+
searchFormCopy,
|
|
26
|
+
searchResultsCopy,
|
|
27
|
+
shellCopy,
|
|
28
|
+
subforumListCopy,
|
|
29
|
+
threadRowCopy,
|
|
30
|
+
threadViewCopy,
|
|
31
|
+
userPanelCopy,
|
|
32
|
+
whoIsOnlineCopy,
|
|
33
|
+
} from './copy'
|
|
4
34
|
import { Announcement } from './slots/announcement'
|
|
5
35
|
import { AuthPage } from './slots/auth-page'
|
|
6
36
|
import { BoardIndex } from './slots/board-index'
|
|
@@ -9,8 +39,8 @@ import { CategoryBlock } from './slots/category-block'
|
|
|
9
39
|
import { DiscoveryView } from './slots/discovery-view'
|
|
10
40
|
import { ErrorNotice } from './slots/error-notice'
|
|
11
41
|
import { Footer } from './slots/footer'
|
|
12
|
-
import { ForumJump } from './slots/forum-jump'
|
|
13
42
|
import { ForumDisplay } from './slots/forum-display'
|
|
43
|
+
import { ForumJump } from './slots/forum-jump'
|
|
14
44
|
import { ForumRow } from './slots/forum-row'
|
|
15
45
|
import { Header } from './slots/header'
|
|
16
46
|
import { LatestPosts } from './slots/latest-posts'
|
|
@@ -86,4 +116,34 @@ export const phasebookTheme = defineTheme({
|
|
|
86
116
|
RedirectNotice,
|
|
87
117
|
ErrorNotice,
|
|
88
118
|
},
|
|
119
|
+
copy: {
|
|
120
|
+
Announcement: announcementCopy,
|
|
121
|
+
BoardIndex: boardIndexCopy,
|
|
122
|
+
BoardStats: boardStatsCopy,
|
|
123
|
+
DiscoveryView: discoveryViewCopy,
|
|
124
|
+
ErrorNotice: errorNoticeCopy,
|
|
125
|
+
Footer: footerCopy,
|
|
126
|
+
ForumDisplay: forumDisplayCopy,
|
|
127
|
+
ForumRow: forumRowCopy,
|
|
128
|
+
Header: headerCopy,
|
|
129
|
+
LatestPosts: latestPostsCopy,
|
|
130
|
+
LatestThreads: latestThreadsCopy,
|
|
131
|
+
MemberProfile: memberProfileCopy,
|
|
132
|
+
Navigation: navigationCopy,
|
|
133
|
+
Notice: noticeCopy,
|
|
134
|
+
Pagination: paginationCopy,
|
|
135
|
+
PanelNav: panelNavCopy,
|
|
136
|
+
PostActions: postActionsCopy,
|
|
137
|
+
PostBit: postBitCopy,
|
|
138
|
+
PostForm: postFormCopy,
|
|
139
|
+
RedirectNotice: redirectNoticeCopy,
|
|
140
|
+
SearchForm: searchFormCopy,
|
|
141
|
+
SearchResults: searchResultsCopy,
|
|
142
|
+
Shell: shellCopy,
|
|
143
|
+
SubforumList: subforumListCopy,
|
|
144
|
+
ThreadRow: threadRowCopy,
|
|
145
|
+
ThreadView: threadViewCopy,
|
|
146
|
+
UserPanel: userPanelCopy,
|
|
147
|
+
WhoIsOnline: whoIsOnlineCopy,
|
|
148
|
+
},
|
|
89
149
|
})
|