1
0
Fork 0
forked from Mirrors/gomuks

web/menu/roomlist: implement marking as read/unread and leaving room

Fixes #523
This commit is contained in:
Tulir Asokan 2025-02-25 21:58:45 +02:00
parent 1ba4d532c9
commit 62d8d06129
5 changed files with 55 additions and 1 deletions

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M440-440q17 0 28.5-11.5T480-480q0-17-11.5-28.5T440-520q-17 0-28.5 11.5T400-480q0 17 11.5 28.5T440-440ZM280-120v-80l240-40v-445q0-15-9-27t-23-14l-208-34v-80l220 36q44 8 72 41t28 77v512l-320 54Zm-160 0v-80h80v-560q0-34 23.5-57t56.5-23h400q34 0 57 23t23 57v560h80v80H120Zm160-80h400v-560H280v560Z"/></svg>

After

Width:  |  Height:  |  Size: 419 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M694-160 553-302l56-56 85 85 170-170 56 57-226 226ZM80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v280h-80v-280H160v525l46-45h274v80H240L80-80Zm80-240v-480 480Z"/></svg>

After

Width:  |  Height:  |  Size: 300 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M80-80v-720q0-33 23.5-56.5T160-880h404q-4 20-4 40t4 40H160v525l46-45h594v-324q23-5 43-13.5t37-22.5v360q0 33-23.5 56.5T800-240H240L80-80Zm80-720v480-480Zm600 80q-50 0-85-35t-35-85q0-50 35-85t85-35q50 0 85 35t35 85q0 50-35 85t-85 35Z"/></svg>

After

Width:  |  Height:  |  Size: 357 B

View file

@ -16,9 +16,13 @@
import { CSSProperties, use } from "react" import { CSSProperties, use } from "react"
import { RoomListEntry, RoomStateStore, useAccountData } from "@/api/statestore" import { RoomListEntry, RoomStateStore, useAccountData } from "@/api/statestore"
import { RoomID } from "@/api/types" import { RoomID } from "@/api/types"
import { useEventAsState } from "@/util/eventdispatcher.ts"
import ClientContext from "../ClientContext.ts" import ClientContext from "../ClientContext.ts"
import { ModalContext } from "../modal" import { ModalCloseContext, ModalContext } from "../modal"
import SettingsView from "../settings/SettingsView.tsx" import SettingsView from "../settings/SettingsView.tsx"
import DoorOpenIcon from "@/icons/door-open.svg?react"
import MarkReadIcon from "@/icons/mark-read.svg?react"
import MarkUnreadIcon from "@/icons/mark-unread.svg?react"
import NotificationsOffIcon from "@/icons/notifications-off.svg?react" import NotificationsOffIcon from "@/icons/notifications-off.svg?react"
import NotificationsIcon from "@/icons/notifications.svg?react" import NotificationsIcon from "@/icons/notifications.svg?react"
import SettingsIcon from "@/icons/settings.svg?react" import SettingsIcon from "@/icons/settings.svg?react"
@ -36,6 +40,7 @@ const hasNotifyingActions = (actions: unknown) => {
const MuteButton = ({ roomID }: { roomID: RoomID }) => { const MuteButton = ({ roomID }: { roomID: RoomID }) => {
const client = use(ClientContext)! const client = use(ClientContext)!
const closeModal = use(ModalCloseContext)
const roomRules = useAccountData(client.store, "m.push_rules")?.global?.room const roomRules = useAccountData(client.store, "m.push_rules")?.global?.room
const pushRule = Array.isArray(roomRules) ? roomRules.find(rule => rule?.rule_id === roomID) : null const pushRule = Array.isArray(roomRules) ? roomRules.find(rule => rule?.rule_id === roomID) : null
const muted = pushRule?.enabled === true && !hasNotifyingActions(pushRule.actions) const muted = pushRule?.enabled === true && !hasNotifyingActions(pushRule.actions)
@ -44,6 +49,7 @@ const MuteButton = ({ roomID }: { roomID: RoomID }) => {
console.error("Failed to mute room", err) console.error("Failed to mute room", err)
window.alert(`Failed to ${muted ? "unmute" : "mute"} room: ${err}`) window.alert(`Failed to ${muted ? "unmute" : "mute"} room: ${err}`)
}) })
closeModal()
} }
return <button onClick={toggleMute}> return <button onClick={toggleMute}>
{muted ? <NotificationsIcon/> : <NotificationsOffIcon/>} {muted ? <NotificationsIcon/> : <NotificationsOffIcon/>}
@ -51,8 +57,43 @@ const MuteButton = ({ roomID }: { roomID: RoomID }) => {
</button> </button>
} }
const MarkReadButton = ({ room }: { room: RoomStateStore }) => {
const meta = useEventAsState(room.meta)
const client = use(ClientContext)!
const closeModal = use(ModalCloseContext)
const read = !meta.marked_unread && meta.unread_messages === 0
const markRead = () => {
const evt = room.eventsByRowID.get(
room.timeline[room.timeline.length-1]?.event_rowid ?? meta.preview_event_rowid,
)
if (!evt) {
window.alert("Can't mark room as read: last event not found in cache")
return
}
const rrType = room.preferences.send_read_receipts ? "m.read" : "m.read.private"
client.rpc.markRead(room.roomID, evt.event_id, rrType).catch(err => {
console.error("Failed to mark room as read", err)
window.alert(`Failed to mark room as read: ${err}`)
})
closeModal()
}
const markUnread = () => {
client.rpc.setAccountData("m.marked_unread", { unread: true }, room.roomID).catch(err => {
console.error("Failed to mark room as unread", err)
window.alert(`Failed to mark room as unread: ${err}`)
})
closeModal()
}
return <button onClick={read ? markUnread : markRead}>
{read ? <MarkUnreadIcon/> : <MarkReadIcon/>}
Mark {read ? "unread" : "read"}
</button>
}
export const RoomMenu = ({ room, style }: RoomMenuProps) => { export const RoomMenu = ({ room, style }: RoomMenuProps) => {
const openModal = use(ModalContext) const openModal = use(ModalContext)
const closeModal = use(ModalCloseContext)
const client = use(ClientContext)!
const openSettings = () => { const openSettings = () => {
openModal({ openModal({
dimmed: true, dimmed: true,
@ -61,8 +102,17 @@ export const RoomMenu = ({ room, style }: RoomMenuProps) => {
content: <SettingsView room={room} />, content: <SettingsView room={room} />,
}) })
} }
const leaveRoom = () => {
client.rpc.leaveRoom(room.roomID).catch(err => {
console.error("Failed to leave room", err)
window.alert(`Failed to leave room: ${err}`)
})
closeModal()
}
return <div className="context-menu room-list-menu" style={style}> return <div className="context-menu room-list-menu" style={style}>
<MarkReadButton room={room} />
<MuteButton roomID={room.roomID}/> <MuteButton roomID={room.roomID}/>
<button onClick={openSettings}><SettingsIcon /> Settings</button> <button onClick={openSettings}><SettingsIcon /> Settings</button>
<button onClick={leaveRoom}><DoorOpenIcon /> Leave room</button>
</div> </div>
} }

View file

@ -85,6 +85,7 @@ const Entry = ({ room, isActive, hidden }: RoomListEntryProps) => {
const onContextMenu = (evt: React.MouseEvent<HTMLDivElement>) => { const onContextMenu = (evt: React.MouseEvent<HTMLDivElement>) => {
const realRoom = client.store.rooms.get(room.room_id) const realRoom = client.store.rooms.get(room.room_id)
if (!realRoom) { if (!realRoom) {
// TODO implement separate menu for invite rooms
console.error("Room state store not found for", room.room_id) console.error("Room state store not found for", room.room_id)
return return
} }