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, " ", " ")