web/timeline: add click handlers for matrix URIs

This commit is contained in:
Tulir Asokan 2024-11-09 12:52:22 +01:00
parent c6992b0fca
commit c2d0020c8c
5 changed files with 62 additions and 17 deletions

View file

@ -43,6 +43,7 @@ class ContextFields implements MainScreenContextFields {
) {
this.keybindings = new Keybindings(client.store, this)
client.store.switchRoom = this.setActiveRoom
window.mainScreenContext = this
}
setActiveRoom = (roomID: RoomID | null) => {

View file

@ -27,7 +27,7 @@ export interface MainScreenContextFields {
clickRightPanelOpener: (evt: React.MouseEvent) => void
}
const MainScreenContext = createContext<MainScreenContextFields>({
const stubContext = {
get setActiveRoom(): never {
throw new Error("MainScreenContext used outside main screen")
},
@ -46,6 +46,9 @@ const MainScreenContext = createContext<MainScreenContextFields>({
get clickRightPanelOpener(): never {
throw new Error("MainScreenContext used outside main screen")
},
})
}
const MainScreenContext = createContext<MainScreenContextFields>(stubContext)
window.mainScreenContext = stubContext
export default MainScreenContext

View file

@ -14,43 +14,55 @@
// 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, use } from "react"
import type { UserID } from "@/api/types"
import MainScreenContext from "../MainScreenContext.ts"
import PinnedMessages from "./PinnedMessages.tsx"
import CloseButton from "@/icons/close.svg?react"
import "./RightPanel.css"
export type RightPanelType = "pinned-messages" | "members"
export type RightPanelType = "pinned-messages" | "members" | "user"
export interface RightPanelProps {
type: RightPanelType
interface RightPanelSimpleProps {
type: "pinned-messages" | "members"
}
interface RightPanelUserProps {
type: "user"
userID: UserID
}
export type RightPanelProps = RightPanelUserProps | RightPanelSimpleProps
function getTitle(type: RightPanelType): string {
switch (type) {
case "pinned-messages":
return "Pinned Messages"
case "members":
return "Room Members"
case "user":
return "User Info"
}
}
function renderRightPanelContent({ type }: RightPanelProps): JSX.Element | null {
switch (type) {
function renderRightPanelContent(props: RightPanelProps): JSX.Element | null {
switch (props.type) {
case "pinned-messages":
return <PinnedMessages />
case "members":
return <>Member list is not yet implemented</>
case "user":
return <>{props.userID}</>
}
}
const RightPanel = ({ type, ...rest }: RightPanelProps) => {
const RightPanel = (props: RightPanelProps) => {
return <div className="right-panel">
<div className="right-panel-header">
<div className="panel-name">{getTitle(type)}</div>
<div className="panel-name">{getTitle(props.type)}</div>
<button onClick={use(MainScreenContext).closeRightPanel}><CloseButton/></button>
</div>
<div className={`right-panel-content ${type}`}>
{renderRightPanelContent({ type, ...rest })}
<div className={`right-panel-content ${props.type}`}>
{renderRightPanelContent(props)}
</div>
</div>
}

View file

@ -20,15 +20,42 @@ function isImageElement(elem: EventTarget): elem is HTMLImageElement {
return (elem as HTMLImageElement).tagName === "IMG"
}
const onClickHTML = (evt: React.MouseEvent<HTMLDivElement>) => {
if (isImageElement(evt.target)) {
window.openLightbox({
src: evt.target.src,
alt: evt.target.alt,
function isAnchorElement(elem: EventTarget): elem is HTMLAnchorElement {
return (elem as HTMLAnchorElement).tagName === "A"
}
function onClickMatrixURI(href: string) {
const url = new URL(href)
const pathParts = url.pathname.split("/")
switch (pathParts[0]) {
case "u":
return window.mainScreenContext.setRightPanel({
type: "user",
userID: pathParts[1],
})
} else if ((evt.target as HTMLElement).closest?.("span.hicli-spoiler")?.classList.toggle("spoiler-revealed")) {
case "roomid":
return window.mainScreenContext.setActiveRoom(pathParts[1])
case "r":
return window.client.rpc.resolveAlias(`#${pathParts[1]}`).then(
res => window.mainScreenContext.setActiveRoom(res.room_id),
err => window.alert(`Failed to resolve room alias #${pathParts[1]}: ${err}`),
)
}
}
const onClickHTML = (evt: React.MouseEvent<HTMLDivElement>) => {
const targetElem = evt.target as HTMLElement
if (isImageElement(targetElem)) {
window.openLightbox({
src: targetElem.src,
alt: targetElem.alt,
})
} else if (targetElem.closest?.("span.hicli-spoiler")?.classList.toggle("spoiler-revealed")) {
// When unspoilering, don't trigger links and other clickables inside the spoiler
evt.preventDefault()
} else if (isAnchorElement(targetElem) && targetElem.href.startsWith("matrix:")) {
onClickMatrixURI(targetElem.href)
evt.preventDefault()
}
}

View file

@ -2,10 +2,12 @@
/// <reference types="vite-plugin-svgr/client" />
import type Client from "@/api/client.ts"
import type { MainScreenContextFields } from "@/ui/MainScreenContext.ts"
declare global {
interface Window {
client: Client
mainScreenContext: MainScreenContextFields
openLightbox: (params: { src: string, alt: string }) => void
}
}