diff --git a/web/src/ui/timeline/content/ACLBody.tsx b/web/src/ui/timeline/content/ACLBody.tsx index 9a136fb..ee56158 100644 --- a/web/src/ui/timeline/content/ACLBody.tsx +++ b/web/src/ui/timeline/content/ACLBody.tsx @@ -13,15 +13,70 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -// import { ACLEventContent } from "@/api/types" +import { Fragment, JSX } from "react" +import { ACLEventContent } from "@/api/types" +import { listDiff } from "@/util/diff.ts" +import { humanJoinReact, joinReact } from "@/util/reactjoin.tsx" import EventContentProps from "./props.ts" +function joinServers(arr: string[]): JSX.Element[] { + return humanJoinReact(arr.map(item => {item})) +} + +function makeACLChangeString( + addedAllow: string[], removedAllow: string[], + addedDeny: string[], removedDeny: string[], + prevAllowIP: boolean, newAllowIP: boolean, +) { + const parts = [] + if (addedDeny.length > 0) { + parts.push(<>Servers matching {joinServers(addedDeny)} are now banned.) + } + if (removedDeny.length > 0) { + parts.push(<>Servers matching {joinServers(removedDeny)} were removed from the ban list.) + } + if (addedAllow.length > 0) { + parts.push(<>Servers matching {joinServers(addedAllow)} are now allowed.) + } + if (removedAllow.length > 0) { + parts.push(<>Servers matching {joinServers(removedAllow)} were removed from the allowed list.) + } + if (prevAllowIP !== newAllowIP) { + parts.push( + <>Participating from a server using an IP literal hostname is now {newAllowIP ? "allowed" : "banned"}., + ) + } + return joinReact(parts) +} + +function ensureArray(val: unknown): string[] { + return Array.isArray(val) ? val : [] +} + const ACLBody = ({ event, sender }: EventContentProps) => { - // const content = event.content as ACLEventContent - // const prevContent = event.unsigned.prev_content as ACLEventContent | undefined - // TODO diff content and prevContent + const content = event.content as ACLEventContent + const prevContent = event.unsigned.prev_content as ACLEventContent | undefined + const [addedAllow, removedAllow] = listDiff(ensureArray(content.allow), ensureArray(prevContent?.allow)) + const [addedDeny, removedDeny] = listDiff(ensureArray(content.deny), ensureArray(prevContent?.deny)) + const prevAllowIP = prevContent?.allow_ip_literals ?? true + const newAllowIP = content.allow_ip_literals ?? true + if ( + prevAllowIP === newAllowIP + && !addedAllow.length && !removedAllow.length + && !addedDeny.length && !removedDeny.length + ) { + return
+ {sender?.content.displayname ?? event.sender} sent a server ACL event with no changes +
+ } + let changeString = makeACLChangeString(addedAllow, removedAllow, addedDeny, removedDeny, prevAllowIP, newAllowIP) + if (ensureArray(content.allow).length === 0) { + changeString = [ + 🎉 All servers are banned from participating! This room can no longer be used. + ] + } return
- {sender?.content.displayname ?? event.sender} changed the server ACLs + {sender?.content.displayname ?? event.sender} changed the server ACLs: {changeString}
} diff --git a/web/src/ui/timeline/content/PinnedEventsBody.tsx b/web/src/ui/timeline/content/PinnedEventsBody.tsx index da67aca..50e03a8 100644 --- a/web/src/ui/timeline/content/PinnedEventsBody.tsx +++ b/web/src/ui/timeline/content/PinnedEventsBody.tsx @@ -19,7 +19,7 @@ import { oxfordHumanJoin } from "@/util/join.ts" import EventContentProps from "./props.ts" function renderPinChanges(content: PinnedEventsContent, prevContent?: PinnedEventsContent): string { - const { added, removed } = listDiff(content.pinned ?? [], prevContent?.pinned ?? []) + const [added, removed] = listDiff(content.pinned ?? [], prevContent?.pinned ?? []) if (added.length) { if (removed.length) { return `pinned ${oxfordHumanJoin(added)} and unpinned ${oxfordHumanJoin(removed)}` diff --git a/web/src/util/diff.ts b/web/src/util/diff.ts index e4011f1..3a31ddc 100644 --- a/web/src/util/diff.ts +++ b/web/src/util/diff.ts @@ -15,19 +15,19 @@ // along with this program. If not, see . const minSizeForSet = 10 -export function listDiff(newArr: T[], oldArr: T[]): { added: T[], removed: T[] } { +export function listDiff(newArr: T[], oldArr: T[]): [added: T[], removed: T[]] { if (oldArr.length < minSizeForSet && newArr.length < minSizeForSet) { - return { - removed: oldArr.filter(item => !newArr.includes(item)), - added: newArr.filter(item => !oldArr.includes(item)), - } + return [ + newArr.filter(item => !oldArr.includes(item)), + oldArr.filter(item => !newArr.includes(item)), + ] } const oldSet = new Set(oldArr) const newSet = new Set(newArr) - return { - removed: oldArr.filter(item => !newSet.has(item)), - added: newArr.filter(item => !oldSet.has(item)), - } + return [ + newArr.filter(item => !oldSet.has(item)), + oldArr.filter(item => !newSet.has(item)), + ] } export function objectDiff( diff --git a/web/src/util/reactjoin.tsx b/web/src/util/reactjoin.tsx new file mode 100644 index 0000000..e784759 --- /dev/null +++ b/web/src/util/reactjoin.tsx @@ -0,0 +1,31 @@ +// gomuks - A Matrix client written in Go. +// Copyright (C) 2024 Tulir Asokan +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +import { Fragment, JSX } from "react" + +export function humanJoinReact( + arr: (string | JSX.Element)[], + sep: string | JSX.Element = ", ", + lastSep: string | JSX.Element = " and ", +): JSX.Element[] { + return arr.map((elem, idx) => + + {elem} + {idx < arr.length - 1 ? (idx === arr.length - 2 ? lastSep : sep) : null} + ) +} + +export const oxfordHumanJoinReact = (arr: (string | JSX.Element)[]) => humanJoinReact(arr, ", ", ", and ") +export const joinReact = (arr: (string | JSX.Element)[]) => humanJoinReact(arr, " ", " ")