@meith/theme-raidframe 0.31.0 → 0.32.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 +2 -0
- package/src/messages/en.json +2 -0
- package/src/slots/editor-toolbar.tsx +23 -6
- package/src/slots/header.tsx +95 -17
- package/src/slots/thread-row.tsx +7 -1
- package/src/slots/thread-view.tsx +20 -1
- package/src/theme.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meith/theme-raidframe",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "A gaming theme for Meith — guild, raid and match boards — built on the default theme's slots.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@meith/theme-default": "^0.
|
|
24
|
-
"@meith/theme-kit": "^0.
|
|
25
|
-
"@meith/ui": "^0.
|
|
23
|
+
"@meith/theme-default": "^0.32.0",
|
|
24
|
+
"@meith/theme-kit": "^0.32.0",
|
|
25
|
+
"@meith/ui": "^0.32.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"react": "^19.2.0"
|
package/src/copy.ts
CHANGED
|
@@ -264,6 +264,8 @@ export function threadViewCopy(t: Translator): SlotCopy {
|
|
|
264
264
|
'raidframe.threadView.replies',
|
|
265
265
|
'raidframe.threadView.views',
|
|
266
266
|
'raidframe.threadView.markRead',
|
|
267
|
+
'raidframe.threadView.watch',
|
|
268
|
+
'raidframe.threadView.watching',
|
|
267
269
|
])
|
|
268
270
|
}
|
|
269
271
|
|
package/src/messages/en.json
CHANGED
|
@@ -133,6 +133,8 @@
|
|
|
133
133
|
"raidframe.threadView.postReply": "post reply",
|
|
134
134
|
"raidframe.threadView.replies": "replies",
|
|
135
135
|
"raidframe.threadView.views": "views",
|
|
136
|
+
"raidframe.threadView.watch": "watch",
|
|
137
|
+
"raidframe.threadView.watching": "watching",
|
|
136
138
|
"raidframe.userPanel.accountLabel": "Your account",
|
|
137
139
|
"raidframe.userPanel.new": "new",
|
|
138
140
|
"raidframe.userPanel.pm": "pm",
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
applyEditorTag,
|
|
5
|
+
applyInsertion,
|
|
5
6
|
type EditorTag,
|
|
7
|
+
type EditorToolbarButtonModel,
|
|
6
8
|
type EditorToolbarModel,
|
|
7
9
|
type SlotCopy,
|
|
8
10
|
} from '@meith/theme-kit'
|
|
@@ -14,20 +16,34 @@ const GLYPHS: Readonly<Record<EditorTag, string>> = {
|
|
|
14
16
|
italic: 'I',
|
|
15
17
|
strikethrough: 'S',
|
|
16
18
|
link: 'LINK',
|
|
19
|
+
image: 'IMG',
|
|
17
20
|
quote: 'QUOTE',
|
|
18
21
|
code: 'CODE',
|
|
19
22
|
spoiler: 'SPLR',
|
|
20
23
|
bulletedList: 'UL',
|
|
21
24
|
numberedList: 'OL',
|
|
25
|
+
taskList: 'TASK',
|
|
22
26
|
heading: 'H',
|
|
27
|
+
table: 'TBL',
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
const BUTTON =
|
|
26
31
|
'inline-flex min-w-8 items-center justify-center border border-border bg-secondary px-2 py-1.5 font-mono text-[0.6875rem] font-semibold uppercase tracking-[0.1em] text-secondary-foreground hover:border-primary/70 hover:text-primary'
|
|
27
32
|
|
|
28
|
-
function
|
|
33
|
+
function runButton(textareaId: string, button: EditorToolbarButtonModel): void {
|
|
29
34
|
const field = document.getElementById(textareaId)
|
|
30
|
-
if (field instanceof HTMLTextAreaElement)
|
|
35
|
+
if (!(field instanceof HTMLTextAreaElement)) return
|
|
36
|
+
|
|
37
|
+
if (button.tag !== null) {
|
|
38
|
+
applyEditorTag(field, button.tag, button.placeholder)
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (button.insertion !== null) applyInsertion(field, button.insertion)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function glyphFor(button: EditorToolbarButtonModel): string {
|
|
46
|
+
return button.tag === null ? button.label.charAt(0).toUpperCase() : GLYPHS[button.tag]
|
|
31
47
|
}
|
|
32
48
|
|
|
33
49
|
export function EditorToolbar({
|
|
@@ -39,18 +55,19 @@ export function EditorToolbar({
|
|
|
39
55
|
return (
|
|
40
56
|
<>
|
|
41
57
|
<div role="group" aria-label={groupLabel} className="flex flex-wrap gap-1 px-4 py-3">
|
|
42
|
-
{buttons.map((button) => (
|
|
58
|
+
{buttons.map((button, index) => (
|
|
43
59
|
<button
|
|
44
|
-
|
|
60
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: a plugin's button carries no id of its own, so the index disambiguates two sharing a tag or label; the list is server-rendered in order and never reordered on the client
|
|
61
|
+
key={`${button.tag ?? button.label}-${index}`}
|
|
45
62
|
type="button"
|
|
46
63
|
onMouseDown={(event) => event.preventDefault()}
|
|
47
|
-
onClick={() =>
|
|
64
|
+
onClick={() => runButton(textareaId, button)}
|
|
48
65
|
title={button.title}
|
|
49
66
|
aria-label={button.label}
|
|
50
67
|
{...(button.keyShortcut === null ? {} : { 'aria-keyshortcuts': button.keyShortcut })}
|
|
51
68
|
className={BUTTON}
|
|
52
69
|
>
|
|
53
|
-
<span aria-hidden="true">{
|
|
70
|
+
<span aria-hidden="true">{glyphFor(button)}</span>
|
|
54
71
|
</button>
|
|
55
72
|
))}
|
|
56
73
|
|
package/src/slots/header.tsx
CHANGED
|
@@ -41,7 +41,7 @@ function Submenu({ items }: { items: HeaderModel['navigation'][number]['submenu'
|
|
|
41
41
|
<a
|
|
42
42
|
href={child.href}
|
|
43
43
|
{...linkTarget(child)}
|
|
44
|
-
className="
|
|
44
|
+
className="flex items-center px-3 py-1.5 text-muted-foreground pointer-coarse:min-h-11 hover:bg-muted hover:text-foreground"
|
|
45
45
|
>
|
|
46
46
|
{child.label}
|
|
47
47
|
</a>
|
|
@@ -51,6 +51,71 @@ function Submenu({ items }: { items: HeaderModel['navigation'][number]['submenu'
|
|
|
51
51
|
)
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
function Chevron({ className }: { className: string }) {
|
|
55
|
+
return (
|
|
56
|
+
<svg
|
|
57
|
+
aria-hidden="true"
|
|
58
|
+
viewBox="0 0 12 12"
|
|
59
|
+
className={className}
|
|
60
|
+
fill="none"
|
|
61
|
+
stroke="currentColor"
|
|
62
|
+
strokeWidth="1.5"
|
|
63
|
+
strokeLinecap="round"
|
|
64
|
+
strokeLinejoin="round"
|
|
65
|
+
>
|
|
66
|
+
<path d="m4.5 3 3 3-3 3" />
|
|
67
|
+
</svg>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function MobileNavItem({ item }: { item: HeaderModel['navigation'][number] }) {
|
|
72
|
+
if (item.submenu === undefined || item.submenu.length === 0) {
|
|
73
|
+
return (
|
|
74
|
+
<li>
|
|
75
|
+
<a
|
|
76
|
+
href={item.href}
|
|
77
|
+
{...linkTarget(item)}
|
|
78
|
+
className="flex min-h-11 items-center px-3.5 font-mono text-[0.6875rem] font-semibold tracking-[0.14em] text-muted-foreground uppercase hover:bg-secondary hover:text-primary"
|
|
79
|
+
>
|
|
80
|
+
{item.label}
|
|
81
|
+
</a>
|
|
82
|
+
</li>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<li>
|
|
88
|
+
<details data-nav-disclosure name="raidframe-header-submenu" className="group/submenu">
|
|
89
|
+
<summary className="flex min-h-11 cursor-default list-none items-center justify-between gap-2 px-3.5 font-mono text-[0.6875rem] font-semibold tracking-[0.14em] text-muted-foreground uppercase select-none [&::-webkit-details-marker]:hidden [&::marker]:content-none hover:bg-secondary hover:text-primary">
|
|
90
|
+
<a
|
|
91
|
+
href={item.href}
|
|
92
|
+
{...linkTarget(item)}
|
|
93
|
+
className="min-w-0 flex-1 truncate normal-case tracking-normal hover:underline"
|
|
94
|
+
>
|
|
95
|
+
{item.label}
|
|
96
|
+
</a>
|
|
97
|
+
<span className="flex size-11 shrink-0 items-center justify-center">
|
|
98
|
+
<Chevron className="size-3 transition-transform group-open/submenu:rotate-90" />
|
|
99
|
+
</span>
|
|
100
|
+
</summary>
|
|
101
|
+
<ul className="ml-3 flex flex-col gap-0.5 border-l border-border py-1 pl-2.5">
|
|
102
|
+
{item.submenu.map((child) => (
|
|
103
|
+
<li key={child.href}>
|
|
104
|
+
<a
|
|
105
|
+
href={child.href}
|
|
106
|
+
{...linkTarget(child)}
|
|
107
|
+
className="flex min-h-11 items-center px-3.5 font-mono text-[0.6875rem] font-semibold tracking-[0.14em] text-muted-foreground uppercase hover:bg-secondary hover:text-primary"
|
|
108
|
+
>
|
|
109
|
+
{child.label}
|
|
110
|
+
</a>
|
|
111
|
+
</li>
|
|
112
|
+
))}
|
|
113
|
+
</ul>
|
|
114
|
+
</details>
|
|
115
|
+
</li>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
54
119
|
export function Header({
|
|
55
120
|
boardTitle,
|
|
56
121
|
homeHref,
|
|
@@ -77,22 +142,35 @@ export function Header({
|
|
|
77
142
|
<div className={RULE} aria-hidden="true" />
|
|
78
143
|
|
|
79
144
|
{navigation.length > 0 && (
|
|
80
|
-
<nav
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
{item.
|
|
92
|
-
</
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
145
|
+
<nav aria-label={c('sectionsAriaLabel')} className="border-b border-border bg-card/60">
|
|
146
|
+
<div data-nav-view="desktop" className="hidden flex-wrap lg:flex">
|
|
147
|
+
{navigation.map((item) => (
|
|
148
|
+
<span key={item.href} className="group relative">
|
|
149
|
+
<a
|
|
150
|
+
href={item.href}
|
|
151
|
+
{...linkTarget(item)}
|
|
152
|
+
className="block border-t-2 border-r border-t-transparent border-r-border px-3.5 py-2 font-mono text-[0.6875rem] font-semibold tracking-[0.14em] text-muted-foreground uppercase pointer-coarse:flex pointer-coarse:min-h-11 pointer-coarse:items-center hover:border-t-primary hover:bg-secondary hover:text-primary"
|
|
153
|
+
>
|
|
154
|
+
{item.label}
|
|
155
|
+
</a>
|
|
156
|
+
<Submenu items={item.submenu} />
|
|
157
|
+
</span>
|
|
158
|
+
))}
|
|
159
|
+
</div>
|
|
160
|
+
|
|
161
|
+
<div data-nav-view="mobile" className="px-4 py-1 lg:hidden">
|
|
162
|
+
<details data-nav-disclosure className="group">
|
|
163
|
+
<summary className="flex min-h-11 cursor-default list-none items-center gap-2 font-mono text-[0.6875rem] font-semibold tracking-[0.14em] text-foreground uppercase select-none [&::-webkit-details-marker]:hidden [&::marker]:content-none">
|
|
164
|
+
<Chevron className="size-3 shrink-0 transition-transform group-open:rotate-90" />
|
|
165
|
+
{c('sectionsAriaLabel')}
|
|
166
|
+
</summary>
|
|
167
|
+
<ul className="flex flex-col gap-0.5 pb-2">
|
|
168
|
+
{navigation.map((item) => (
|
|
169
|
+
<MobileNavItem key={item.href} item={item} />
|
|
170
|
+
))}
|
|
171
|
+
</ul>
|
|
172
|
+
</details>
|
|
173
|
+
</div>
|
|
96
174
|
</nav>
|
|
97
175
|
)}
|
|
98
176
|
</header>
|
package/src/slots/thread-row.tsx
CHANGED
|
@@ -5,7 +5,12 @@ import { MICRO, NUMERIC, ReadPip, Stamp, UserRef } from '../shared'
|
|
|
5
5
|
|
|
6
6
|
const TAG = 'border px-1.5 py-px font-mono text-[0.625rem] font-bold uppercase tracking-[0.12em]'
|
|
7
7
|
|
|
8
|
-
export function ThreadRow({
|
|
8
|
+
export function ThreadRow({
|
|
9
|
+
thread,
|
|
10
|
+
select,
|
|
11
|
+
regions,
|
|
12
|
+
copy,
|
|
13
|
+
}: ThreadRowSlotModel & { copy: SlotCopy }) {
|
|
9
14
|
const c = (key: string) => fromSlotCopy(copy, `raidframe.threadRow.${key}`)
|
|
10
15
|
|
|
11
16
|
const hidden = thread.visibility === 'deleted' || thread.visibility === 'unapproved'
|
|
@@ -65,6 +70,7 @@ export function ThreadRow({ thread, select, copy }: ThreadRowSlotModel & { copy:
|
|
|
65
70
|
{c('deleted')}
|
|
66
71
|
</span>
|
|
67
72
|
)}
|
|
73
|
+
{regions?.pluginBadges}
|
|
68
74
|
</div>
|
|
69
75
|
|
|
70
76
|
<p className={`${MICRO} mt-1 normal-case`}>
|
|
@@ -10,6 +10,7 @@ export function ThreadView({
|
|
|
10
10
|
forum,
|
|
11
11
|
replyHref,
|
|
12
12
|
markReadAction,
|
|
13
|
+
watch,
|
|
13
14
|
regions,
|
|
14
15
|
copy,
|
|
15
16
|
}: ThreadViewModel & { copy: SlotCopy }) {
|
|
@@ -22,6 +23,21 @@ export function ThreadView({
|
|
|
22
23
|
</a>
|
|
23
24
|
)
|
|
24
25
|
|
|
26
|
+
const watchToggle =
|
|
27
|
+
watch == null ? null : (
|
|
28
|
+
<form action={watch.action} method="post">
|
|
29
|
+
<button type="submit" className={BUTTON}>
|
|
30
|
+
{watch.subscribed ? (
|
|
31
|
+
<>
|
|
32
|
+
{c('watching')} <span aria-hidden="true">✓</span>
|
|
33
|
+
</>
|
|
34
|
+
) : (
|
|
35
|
+
c('watch')
|
|
36
|
+
)}
|
|
37
|
+
</button>
|
|
38
|
+
</form>
|
|
39
|
+
)
|
|
40
|
+
|
|
25
41
|
return (
|
|
26
42
|
<div className="flex flex-col gap-4 p-4">
|
|
27
43
|
<div className="border border-border bg-card shadow-elevation">
|
|
@@ -53,7 +69,10 @@ export function ThreadView({
|
|
|
53
69
|
</p>
|
|
54
70
|
</div>
|
|
55
71
|
|
|
56
|
-
|
|
72
|
+
<div className="flex shrink-0 flex-wrap items-center gap-2">
|
|
73
|
+
{watchToggle}
|
|
74
|
+
{reply}
|
|
75
|
+
</div>
|
|
57
76
|
</div>
|
|
58
77
|
<div className={RULE} aria-hidden="true" />
|
|
59
78
|
</div>
|
package/src/theme.ts
CHANGED