mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-20 10:33:41 -05:00
Implement user ignores
This commit is contained in:
parent
bef3e43de8
commit
6f177e0524
5 changed files with 83 additions and 13 deletions
|
@ -66,6 +66,15 @@ func (h *HiClient) handleJSONCommand(ctx context.Context, req *JSONCommand) (any
|
||||||
return unmarshalAndCall(req.Data, func(params *sendStateEventParams) (id.EventID, error) {
|
return unmarshalAndCall(req.Data, func(params *sendStateEventParams) (id.EventID, error) {
|
||||||
return h.SetState(ctx, params.RoomID, params.EventType, params.StateKey, params.Content)
|
return h.SetState(ctx, params.RoomID, params.EventType, params.StateKey, params.Content)
|
||||||
})
|
})
|
||||||
|
case "get_account_data":
|
||||||
|
return unmarshalAndCall(req.Data, func(params *getAccountDataParams) (*map[string]any, error) {
|
||||||
|
var result map[string]any
|
||||||
|
if params.RoomID != "" {
|
||||||
|
return &result, h.Client.GetRoomAccountData(ctx, params.RoomID, params.Type, &result)
|
||||||
|
} else {
|
||||||
|
return &result, h.Client.GetAccountData(ctx, params.Type, &result)
|
||||||
|
}
|
||||||
|
})
|
||||||
case "set_account_data":
|
case "set_account_data":
|
||||||
return unmarshalAndCall(req.Data, func(params *setAccountDataParams) (bool, error) {
|
return unmarshalAndCall(req.Data, func(params *setAccountDataParams) (bool, error) {
|
||||||
if params.RoomID != "" {
|
if params.RoomID != "" {
|
||||||
|
@ -262,6 +271,11 @@ type sendStateEventParams struct {
|
||||||
Content json.RawMessage `json:"content"`
|
Content json.RawMessage `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type getAccountDataParams struct {
|
||||||
|
RoomID id.RoomID `json:"room_id,omitempty"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
type setAccountDataParams struct {
|
type setAccountDataParams struct {
|
||||||
RoomID id.RoomID `json:"room_id,omitempty"`
|
RoomID id.RoomID `json:"room_id,omitempty"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
|
|
@ -167,6 +167,10 @@ export default abstract class RPCClient {
|
||||||
return this.request("set_state", { room_id, type, state_key, content })
|
return this.request("set_state", { room_id, type, state_key, content })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAccountData(type: EventType, roomID?: RoomID): Promise<unknown> {
|
||||||
|
return this.request("get_account_data", { type, roomID })
|
||||||
|
}
|
||||||
|
|
||||||
setAccountData(type: EventType, content: unknown, room_id?: RoomID): Promise<boolean> {
|
setAccountData(type: EventType, content: unknown, room_id?: RoomID): Promise<boolean> {
|
||||||
return this.request("set_account_data", { type, content, room_id })
|
return this.request("set_account_data", { type, content, room_id })
|
||||||
}
|
}
|
||||||
|
|
1
web/src/icons/block.svg
Normal file
1
web/src/icons/block.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5E6267"><path d="M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q54 0 104-17.5t92-50.5L228-676q-33 42-50.5 92T160-480q0 134 93 227t227 93Zm252-124q33-42 50.5-92T800-480q0-134-93-227t-227-93q-54 0-104 17.5T284-732l448 448Z"/></svg>
|
After Width: | Height: | Size: 477 B |
|
@ -205,7 +205,7 @@ div.right-panel-content.user {
|
||||||
fill: var(--error-color)
|
fill: var(--error-color)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.invite {
|
.positive {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
svg {
|
svg {
|
||||||
fill: var(--primary-color)
|
fill: var(--primary-color)
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
//
|
//
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import { use } from "react"
|
import { use, useEffect, useState } from "react"
|
||||||
import Client from "@/api/client.ts"
|
import Client from "@/api/client.ts"
|
||||||
import { RoomStateStore } from "@/api/statestore"
|
import { RoomStateStore } from "@/api/statestore"
|
||||||
import { MemDBEvent, MemberEventContent, Membership } from "@/api/types"
|
import { MemDBEvent, MemberEventContent, Membership } from "@/api/types"
|
||||||
|
@ -21,6 +21,7 @@ import { ModalContext } from "@/ui/modal"
|
||||||
import { RoomContext } from "@/ui/roomview/roomcontext.ts"
|
import { RoomContext } from "@/ui/roomview/roomcontext.ts"
|
||||||
import ConfirmWithMessageModal from "@/ui/timeline/menu/ConfirmWithMessageModal.tsx"
|
import ConfirmWithMessageModal from "@/ui/timeline/menu/ConfirmWithMessageModal.tsx"
|
||||||
import { getPowerLevels } from "@/ui/timeline/menu/util.ts"
|
import { getPowerLevels } from "@/ui/timeline/menu/util.ts"
|
||||||
|
import Block from "@/icons/block.svg?react"
|
||||||
import Gavel from "@/icons/gavel.svg?react"
|
import Gavel from "@/icons/gavel.svg?react"
|
||||||
import PersonAdd from "@/icons/person-add.svg?react"
|
import PersonAdd from "@/icons/person-add.svg?react"
|
||||||
import PersonRemove from "@/icons/person-remove.svg?react"
|
import PersonRemove from "@/icons/person-remove.svg?react"
|
||||||
|
@ -32,13 +33,62 @@ interface UserModerationProps {
|
||||||
member: MemDBEvent | null;
|
member: MemDBEvent | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IgnoredUsersType {
|
||||||
|
ignored_users: Record<string, object>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserIgnoreButton = ({ userID, client }: { userID: string; client: Client }) => {
|
||||||
|
const [ignoredUsers, setIgnoredUsers] = useState<IgnoredUsersType | null>(null)
|
||||||
|
useEffect(() => {
|
||||||
|
// Get blocked user list
|
||||||
|
client.rpc.getAccountData("m.ignored_user_list").then((data) => {
|
||||||
|
const parsedData = data as IgnoredUsersType
|
||||||
|
if (data !== ignoredUsers || !("ignored_users" in parsedData)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setIgnoredUsers(parsedData)
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error("Failed to get ignored users", e)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const isIgnored = ignoredUsers?.ignored_users[userID]
|
||||||
|
const ignoreUser = () => {
|
||||||
|
const newIgnoredUsers = { ...(ignoredUsers || { ignored_users: {}}) }
|
||||||
|
newIgnoredUsers.ignored_users[userID] = {}
|
||||||
|
client.rpc.setAccountData("m.ignored_user_list", newIgnoredUsers).then(() => {
|
||||||
|
setIgnoredUsers(newIgnoredUsers)
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error("Failed to ignore user", e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const unignoreUser = () => {
|
||||||
|
const newIgnoredUsers = { ...(ignoredUsers || { ignored_users: {}}) }
|
||||||
|
delete newIgnoredUsers.ignored_users[userID]
|
||||||
|
client.rpc.setAccountData("m.ignored_user_list", newIgnoredUsers).then(() => {
|
||||||
|
setIgnoredUsers(newIgnoredUsers)
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error("Failed to unignore user", e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={"moderation-actions " + (isIgnored ? "positive" : "dangerous")}
|
||||||
|
onClick={isIgnored ? unignoreUser : ignoreUser}>
|
||||||
|
<Block/>
|
||||||
|
<span>{isIgnored ? "Unignore" : "Ignore"}</span>
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const UserModeration = ({ userID, client, member }: UserModerationProps) => {
|
const UserModeration = ({ userID, client, member }: UserModerationProps) => {
|
||||||
const roomCtx = use(RoomContext)
|
const roomCtx = use(RoomContext)
|
||||||
if(!roomCtx) {
|
|
||||||
return null // There is no room context, moderation is not an applicable context.
|
|
||||||
}
|
|
||||||
const openModal = use(ModalContext)
|
const openModal = use(ModalContext)
|
||||||
const hasPl = (action: "invite" | "kick" | "ban") => {
|
const hasPl = (action: "invite" | "kick" | "ban") => {
|
||||||
|
if(!roomCtx) {
|
||||||
|
return false // no room context
|
||||||
|
}
|
||||||
const [pls, ownPL] = getPowerLevels(roomCtx.store, client)
|
const [pls, ownPL] = getPowerLevels(roomCtx.store, client)
|
||||||
const actionPL = pls[action] ?? pls.state_default ?? 50
|
const actionPL = pls[action] ?? pls.state_default ?? 50
|
||||||
const otherUserPl = pls.users?.[userID] ?? pls.users_default ?? 0
|
const otherUserPl = pls.users?.[userID] ?? pls.users_default ?? 0
|
||||||
|
@ -88,42 +138,43 @@ const UserModeration = ({ userID, client, member }: UserModerationProps) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const membership = member?.content.membership || "leave"
|
const membership = member?.content.membership || "leave"
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="user-moderation">
|
<div className="user-moderation">
|
||||||
<h4>Moderation</h4>
|
<h4>Moderation</h4>
|
||||||
<div className="moderation-actions">
|
<div className="moderation-actions">
|
||||||
{(["knock", "leave"].includes(membership) || !member) && hasPl("invite") && (
|
{roomCtx && (["knock", "leave"].includes(membership) || !member) && hasPl("invite") && (
|
||||||
<button className="moderation-action invite" onClick={runAction("invite")}>
|
<button className="moderation-action positive" onClick={runAction("invite")}>
|
||||||
<PersonAdd />
|
<PersonAdd />
|
||||||
<span>{membership === "knock" ? "Accept request to join" : "Invite"}</span>
|
<span>{membership === "knock" ? "Accept request to join" : "Invite"}</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{["knock", "invite"].includes(membership) && hasPl("kick") && (
|
{roomCtx && ["knock", "invite"].includes(membership) && hasPl("kick") && (
|
||||||
<button className="moderation-action dangerous" onClick={runAction("leave")}>
|
<button className="moderation-action dangerous" onClick={runAction("leave")}>
|
||||||
<PersonRemove />
|
<PersonRemove />
|
||||||
<span>{membership === "invite" ? "Revoke invitation" : "Reject join request"}</span>
|
<span>{membership === "invite" ? "Revoke invitation" : "Reject join request"}</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{membership === "join" && hasPl("kick") && (
|
{roomCtx && membership === "join" && hasPl("kick") && (
|
||||||
<button className="moderation-action dangerous" onClick={runAction("leave")}>
|
<button className="moderation-action dangerous" onClick={runAction("leave")}>
|
||||||
<PersonRemove />
|
<PersonRemove />
|
||||||
<span>Kick</span>
|
<span>Kick</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{membership !== "ban" && hasPl("ban") && (
|
{roomCtx && membership !== "ban" && hasPl("ban") && (
|
||||||
<button className="moderation-action dangerous" onClick={runAction("ban")}>
|
<button className="moderation-action dangerous" onClick={runAction("ban")}>
|
||||||
<Gavel />
|
<Gavel />
|
||||||
<span>Ban</span>
|
<span>Ban</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{membership === "ban" && hasPl("ban") && (
|
{roomCtx && membership === "ban" && hasPl("ban") && (
|
||||||
<button className="moderation-action invite" onClick={runAction("leave")}>
|
<button className="moderation-action positive" onClick={runAction("leave")}>
|
||||||
<Gavel />
|
<Gavel />
|
||||||
<span>Unban</span>
|
<span>Unban</span>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
<UserIgnoreButton userID={userID} client={client} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue