diff --git a/ci/bundle.json b/ci/bundle.json index c5d0c57..3df9ecf 100644 --- a/ci/bundle.json +++ b/ci/bundle.json @@ -34,6 +34,7 @@ "db", "engagement", "eventLeases.js", + "eventRewards.js", "eventWorld.js", "index.js", "ingest.js", diff --git a/client/src/routes/admin/Visibility.jsx b/client/src/routes/admin/Visibility.jsx index 428d2fc..041018e 100644 --- a/client/src/routes/admin/Visibility.jsx +++ b/client/src/routes/admin/Visibility.jsx @@ -19,6 +19,12 @@ // lists each server's clan board: a server whose clans cannot be read, one at // the game's 100-clan ceiling (D55), and one running the uMod Clans plugin, // whose clans are a separate system and never Teams (D47). +// +// Phase 13b adds a third (D104, D106): whether a published news post is also +// said in each server's in-game chat. Off by default, because core sends every +// post to every registered leg — without a switch, the day this module updated, +// every post would start appearing in every server's chat. It lives here because +// this is the one page that lists every server with a setting of its own. import { useCallback, useEffect, useState } from 'react' @@ -87,6 +93,7 @@ export default function Visibility() { const [fleet, setFleet] = useState('staff') const [clanRoster, setClanRoster] = useState('members') const [servers, setServers] = useState({}) + const [news, setNews] = useState({}) const [busy, setBusy] = useState(false) const [error, setError] = useState('') const [saved, setSaved] = useState(false) @@ -98,6 +105,7 @@ export default function Visibility() { setFleet(state.presence.fleet) setClanRoster((state.clans && state.clans.roster) || 'members') setServers(Object.fromEntries(state.presence.servers.map((s) => [s.id, s.override || INHERIT]))) + setNews(Object.fromEntries(((state.news && state.news.servers) || []).map((s) => [s.id, Boolean(s.on)]))) }, []) useEffect(() => { @@ -114,7 +122,9 @@ export default function Visibility() { const dirtyServers = rows.filter((s) => (servers[s.id] ?? INHERIT) !== (s.override || INHERIT)) const clans = data.clans || { audiences: [], roster: 'members', servers: [] } const dirtyClans = clanRoster !== clans.roster - const dirty = dirtyFleet || dirtyServers.length > 0 || dirtyClans + const newsRows = (data.news && data.news.servers) || [] + const dirtyNews = newsRows.filter((s) => Boolean(news[s.id]) !== Boolean(s.on)) + const dirty = dirtyFleet || dirtyServers.length > 0 || dirtyClans || dirtyNews.length > 0 const effective = (id) => servers[id] || fleet const widened = fleet !== 'staff' || rows.some((s) => effective(s.id) !== 'staff') @@ -131,6 +141,7 @@ export default function Visibility() { if (dirtyServers.length) { body.servers = Object.fromEntries(dirtyServers.map((s) => [s.id, servers[s.id] || null])) } + if (dirtyNews.length) body.news = Object.fromEntries(dirtyNews.map((s) => [s.id, Boolean(news[s.id])])) load(await api.adminVisibility.save(body)) setSaved(true) setReloads((n) => n + 1) @@ -225,6 +236,43 @@ export default function Visibility() { + +

+ When a news post is published, its title is said in the chat of every server switched on + here. A server that is down when a post is published is skipped rather than told late. +

+ {newsRows.length === 0 && ( +

No servers are configured yet.

+ )} + {newsRows.map((s) => ( + + ))} +
+