mirror of
https://github.com/tulir/gomuks.git
synced 2025-04-19 18:13:41 -05:00
web/{timeline,composer}: render m.room.tombstone
events (#608)
This commit is contained in:
parent
5f50cf8e77
commit
769d60c459
5 changed files with 103 additions and 2 deletions
|
@ -102,6 +102,11 @@ export interface RoomNameEventContent {
|
||||||
name?: string
|
name?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RoomCanonicalAliasEventContent {
|
||||||
|
alias?: RoomAlias | null
|
||||||
|
alt_aliases?: RoomAlias[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface RoomTopicEventContent {
|
export interface RoomTopicEventContent {
|
||||||
topic?: string
|
topic?: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,11 @@ div.message-composer {
|
||||||
text-wrap: auto !important;
|
text-wrap: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.tombstoned {
|
||||||
|
min-height: unset;
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
> div.input-area {
|
> div.input-area {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -13,9 +13,19 @@
|
||||||
//
|
//
|
||||||
// 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 React, { CSSProperties, use, useCallback, useEffect, useLayoutEffect, useReducer, useRef, useState } from "react"
|
import React, {
|
||||||
|
CSSProperties,
|
||||||
|
JSX,
|
||||||
|
use,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useReducer,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react"
|
||||||
import { ScaleLoader } from "react-spinners"
|
import { ScaleLoader } from "react-spinners"
|
||||||
import { useRoomEvent } from "@/api/statestore"
|
import { useRoomEvent, useRoomState } from "@/api/statestore"
|
||||||
import type {
|
import type {
|
||||||
EventID,
|
EventID,
|
||||||
MediaMessageEventContent,
|
MediaMessageEventContent,
|
||||||
|
@ -28,6 +38,7 @@ import type {
|
||||||
import { PartialEmoji, emojiToMarkdown } from "@/util/emoji"
|
import { PartialEmoji, emojiToMarkdown } from "@/util/emoji"
|
||||||
import { isMobileDevice } from "@/util/ismobile.ts"
|
import { isMobileDevice } from "@/util/ismobile.ts"
|
||||||
import { escapeMarkdown } from "@/util/markdown.ts"
|
import { escapeMarkdown } from "@/util/markdown.ts"
|
||||||
|
import { getServerName } from "@/util/validation.ts"
|
||||||
import ClientContext from "../ClientContext.ts"
|
import ClientContext from "../ClientContext.ts"
|
||||||
import EmojiPicker from "../emojipicker/EmojiPicker.tsx"
|
import EmojiPicker from "../emojipicker/EmojiPicker.tsx"
|
||||||
import GIFPicker from "../emojipicker/GIFPicker.tsx"
|
import GIFPicker from "../emojipicker/GIFPicker.tsx"
|
||||||
|
@ -576,6 +587,35 @@ const MessageComposer = () => {
|
||||||
const inlineButtons = state.text === "" || window.innerWidth > 720
|
const inlineButtons = state.text === "" || window.innerWidth > 720
|
||||||
const showSendButton = canSend || window.innerWidth > 720
|
const showSendButton = canSend || window.innerWidth > 720
|
||||||
const disableClearMedia = editing && state.media?.msgtype === "m.sticker"
|
const disableClearMedia = editing && state.media?.msgtype === "m.sticker"
|
||||||
|
const tombstoneEvent = useRoomState(room, "m.room.tombstone", "")
|
||||||
|
if (tombstoneEvent !== null) {
|
||||||
|
const content = tombstoneEvent.content
|
||||||
|
const hasReplacement = content.replacement_room?.startsWith("!")
|
||||||
|
let link: JSX.Element | null = null
|
||||||
|
if (hasReplacement) {
|
||||||
|
const via = getServerName(tombstoneEvent.sender)
|
||||||
|
const handleNavigate = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
window.mainScreenContext.setActiveRoom(content.replacement_room, {
|
||||||
|
via: [via],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const url = `matrix:roomid/${content.replacement_room.slice(1)}?via=${via}`
|
||||||
|
link = <a href={url} onClick={handleNavigate}>
|
||||||
|
Join the new one here
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
let body = content.body
|
||||||
|
if (!body) {
|
||||||
|
body = hasReplacement ? "This room has been replaced." : "This room has been shut down."
|
||||||
|
}
|
||||||
|
if (!body.endsWith(".")) {
|
||||||
|
body += "."
|
||||||
|
}
|
||||||
|
return <div className="message-composer tombstoned" ref={composerRef}>
|
||||||
|
{body} {link}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
return <>
|
return <>
|
||||||
{Autocompleter && autocomplete && <div className="autocompletions-wrapper"><Autocompleter
|
{Autocompleter && autocomplete && <div className="autocompletions-wrapper"><Autocompleter
|
||||||
params={autocomplete}
|
params={autocomplete}
|
||||||
|
|
46
web/src/ui/timeline/content/RoomTombstoneBody.tsx
Normal file
46
web/src/ui/timeline/content/RoomTombstoneBody.tsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// gomuks - A Matrix client written in Go.
|
||||||
|
// Copyright (C) 2024 Nexus Nicholson
|
||||||
|
//
|
||||||
|
// 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 <https://www.gnu.org/licenses/>.
|
||||||
|
import { JSX } from "react"
|
||||||
|
import { TombstoneEventContent } from "@/api/types"
|
||||||
|
import EventContentProps from "./props.ts"
|
||||||
|
|
||||||
|
const RoomTombstoneBody = ({ event, sender }: EventContentProps) => {
|
||||||
|
const content = event.content as TombstoneEventContent
|
||||||
|
const end = content.body?.length > 0 ? ` with the message: ${content.body}` : "."
|
||||||
|
const onClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||||
|
e.preventDefault()
|
||||||
|
window.mainScreenContext.setActiveRoom(content.replacement_room)
|
||||||
|
}
|
||||||
|
let description: JSX.Element
|
||||||
|
if (content.replacement_room?.length && content.replacement_room.startsWith("!")) {
|
||||||
|
description = (
|
||||||
|
<span>
|
||||||
|
replaced this room with
|
||||||
|
<a onClick={onClick} href={"matrix:roomid/" + content.replacement_room.slice(1)}
|
||||||
|
className="hicli-matrix-uri">
|
||||||
|
{content.replacement_room}
|
||||||
|
</a>{end}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
description = <span>shut down this room{end}</span>
|
||||||
|
}
|
||||||
|
return <div className="room-tombstone-body">
|
||||||
|
{sender?.content.displayname ?? event.sender} {description}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RoomTombstoneBody
|
|
@ -12,6 +12,7 @@ import PowerLevelBody from "./PowerLevelBody.tsx"
|
||||||
import RedactedBody from "./RedactedBody.tsx"
|
import RedactedBody from "./RedactedBody.tsx"
|
||||||
import RoomAvatarBody from "./RoomAvatarBody.tsx"
|
import RoomAvatarBody from "./RoomAvatarBody.tsx"
|
||||||
import RoomNameBody from "./RoomNameBody.tsx"
|
import RoomNameBody from "./RoomNameBody.tsx"
|
||||||
|
import RoomTombstoneBody from "./RoomTombstoneBody.tsx"
|
||||||
import TextMessageBody from "./TextMessageBody.tsx"
|
import TextMessageBody from "./TextMessageBody.tsx"
|
||||||
import UnknownMessageBody from "./UnknownMessageBody.tsx"
|
import UnknownMessageBody from "./UnknownMessageBody.tsx"
|
||||||
import EventContentProps from "./props.ts"
|
import EventContentProps from "./props.ts"
|
||||||
|
@ -31,6 +32,7 @@ export { default as RedactedBody } from "./RedactedBody.tsx"
|
||||||
export { default as RoomAvatarBody } from "./RoomAvatarBody.tsx"
|
export { default as RoomAvatarBody } from "./RoomAvatarBody.tsx"
|
||||||
export { default as RoomNameBody } from "./RoomNameBody.tsx"
|
export { default as RoomNameBody } from "./RoomNameBody.tsx"
|
||||||
export { default as TextMessageBody } from "./TextMessageBody.tsx"
|
export { default as TextMessageBody } from "./TextMessageBody.tsx"
|
||||||
|
export { default as RoomTombstoneBody } from "./RoomTombstoneBody.tsx"
|
||||||
export { default as UnknownMessageBody } from "./UnknownMessageBody.tsx"
|
export { default as UnknownMessageBody } from "./UnknownMessageBody.tsx"
|
||||||
export type { default as EventContentProps } from "./props.ts"
|
export type { default as EventContentProps } from "./props.ts"
|
||||||
|
|
||||||
|
@ -51,6 +53,8 @@ export function getBodyType(evt: MemDBEvent, forReply = false): React.FunctionCo
|
||||||
return PinnedEventsBody
|
return PinnedEventsBody
|
||||||
case "m.room.power_levels":
|
case "m.room.power_levels":
|
||||||
return PowerLevelBody
|
return PowerLevelBody
|
||||||
|
case "m.room.tombstone":
|
||||||
|
return RoomTombstoneBody
|
||||||
}
|
}
|
||||||
} else if (evt.state_key !== undefined) {
|
} else if (evt.state_key !== undefined) {
|
||||||
// State events which must have a non-empty state key
|
// State events which must have a non-empty state key
|
||||||
|
@ -120,6 +124,7 @@ export function isSmallEvent(bodyType: React.FunctionComponent<EventContentProps
|
||||||
case PolicyRuleBody:
|
case PolicyRuleBody:
|
||||||
case PinnedEventsBody:
|
case PinnedEventsBody:
|
||||||
case PowerLevelBody:
|
case PowerLevelBody:
|
||||||
|
case RoomTombstoneBody:
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|
Loading…
Add table
Reference in a new issue