Files
cinny/src/app/features/room/RoomView.tsx
T

153 lines
5.3 KiB
TypeScript
Raw Normal View History

2025-04-17 02:26:09 -05:00
import React, { useCallback, useRef } from 'react';
import { Box, Text, config } from 'folds'; // Assuming 'folds' is a UI library
import { EventType, Room } from 'matrix-js-sdk';
2024-07-18 18:50:20 +05:30
import { ReactEditor } from 'slate-react';
import { isKeyHotkey } from 'is-hotkey';
import { useStateEvent } from '../../hooks/useStateEvent';
import { StateEvent } from '../../../types/matrix/room';
import { usePowerLevelsAPI, usePowerLevelsContext } from '../../hooks/usePowerLevels';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useEditor } from '../../components/editor';
import { RoomInputPlaceholder } from './RoomInputPlaceholder';
import { RoomTimeline } from './RoomTimeline';
import { RoomViewTyping } from './RoomViewTyping';
import { RoomTombstone } from './RoomTombstone';
import { RoomInput } from './RoomInput';
2025-02-26 21:44:53 +11:00
import { RoomViewFollowing, RoomViewFollowingPlaceholder } from './RoomViewFollowing';
import { Page } from '../../components/page';
import { RoomViewHeader } from './RoomViewHeader';
import { useKeyDown } from '../../hooks/useKeyDown';
import { editableActiveElement } from '../../utils/dom';
import navigation from '../../../client/state/navigation';
import { settingsAtom } from '../../state/settings';
import { useSetting } from '../../state/hooks/settings';
import { useAccessibleTagColors, usePowerLevelTags } from '../../hooks/usePowerLevelTags';
import { useTheme } from '../../hooks/useTheme';
2024-07-18 18:50:20 +05:30
2025-02-26 21:42:42 +11:00
const FN_KEYS_REGEX = /^F\d+$/;
/**
* Determines if a keyboard event should trigger focusing the message input field.
* @param evt - The KeyboardEvent.
* @returns True if the input should be focused, false otherwise.
*/
2024-07-18 18:50:20 +05:30
const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
const { code } = evt;
if (evt.metaKey || evt.altKey || evt.ctrlKey) {
return false;
}
2024-08-04 11:06:42 +05:30
2025-02-26 21:42:42 +11:00
if (FN_KEYS_REGEX.test(code)) return false;
2024-07-18 18:50:20 +05:30
if (
code.startsWith('OS') ||
code.startsWith('Meta') ||
code.startsWith('Shift') ||
code.startsWith('Alt') ||
code.startsWith('Control') ||
code.startsWith('Arrow') ||
2024-08-04 11:06:42 +05:30
code.startsWith('Page') ||
code.startsWith('End') ||
code.startsWith('Home') ||
2024-07-18 18:50:20 +05:30
code === 'Tab' ||
2025-04-17 02:26:09 -05:00
code === 'Space' ||
code === 'Enter' ||
2024-07-18 18:50:20 +05:30
code === 'NumLock' ||
code === 'ScrollLock'
) {
return false;
}
return true;
};
export function RoomView({ room, eventId }: { room: Room; eventId?: string }) {
2024-07-18 18:50:20 +05:30
const roomInputRef = useRef<HTMLDivElement>(null);
2025-04-17 02:26:09 -05:00
const roomViewRef = useRef<HTMLDivElement>(null);
2025-02-26 21:44:53 +11:00
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const { roomId } = room;
const editor = useEditor();
const mx = useMatrixClient();
const tombstoneEvent = useStateEvent(room, StateEvent.RoomTombstone);
const powerLevels = usePowerLevelsContext();
const { getPowerLevel, canSendEvent } = usePowerLevelsAPI(powerLevels);
const myUserId = mx.getUserId();
const canMessage = myUserId
? canSendEvent(EventType.RoomMessage, getPowerLevel(myUserId))
: false;
const [powerLevelTags, getPowerLevelTag] = usePowerLevelTags(room, powerLevels);
const theme = useTheme();
const accessibleTagColors = useAccessibleTagColors(theme.kind, powerLevelTags);
2025-04-18 03:00:31 -05:00
2024-07-18 18:50:20 +05:30
useKeyDown(
window,
useCallback(
(evt) => {
if (editableActiveElement()) return;
if (document.querySelector('.ReactModalPortal > *') || navigation.isRawModalVisible) {
return;
2024-07-18 18:50:20 +05:30
}
2025-04-12 03:40:13 -05:00
if (shouldFocusMessageField(evt) || isKeyHotkey('mod+v', evt)) {
if (editor) {
ReactEditor.focus(editor);
2025-04-12 03:40:13 -05:00
}
2024-07-18 18:50:20 +05:30
}
},
2025-04-18 03:00:31 -05:00
[editor]
2024-07-18 18:50:20 +05:30
)
);
return (
<Page ref={roomViewRef}>
2025-06-18 17:27:34 -05:00
{!room.isCallRoom() && <RoomViewHeader />}
<Box grow="Yes" direction="Column" style={{ flex: 1, overflow: 'hidden', minHeight: 0 }}>
<RoomTimeline
2025-04-17 02:26:09 -05:00
key={roomId}
room={room}
eventId={eventId}
roomInputRef={roomInputRef}
editor={editor}
getPowerLevelTag={getPowerLevelTag}
accessibleTagColors={accessibleTagColors}
/>
<RoomViewTyping room={room} />
</Box>
<Box shrink="No" direction="Column">
<div style={{ padding: `0 ${config.space.S400}` }}>
{' '}
{tombstoneEvent ? (
<RoomTombstone
roomId={roomId}
body={tombstoneEvent.getContent().body}
replacementRoomId={tombstoneEvent.getContent().replacement_room}
/>
) : (
<>
2025-04-12 03:40:13 -05:00
{canMessage ? (
<RoomInput
room={room}
editor={editor}
roomId={roomId}
2025-04-17 02:26:09 -05:00
fileDropContainerRef={roomViewRef}
ref={roomInputRef}
getPowerLevelTag={getPowerLevelTag}
accessibleTagColors={accessibleTagColors}
/>
2025-04-12 03:40:13 -05:00
) : (
<RoomInputPlaceholder
style={{ padding: config.space.S200 }}
alignItems="Center"
justifyContent="Center"
>
<Text align="Center">You do not have permission to post in this room</Text>
</RoomInputPlaceholder>
)}
</>
)}
</div>
2025-02-26 21:44:53 +11:00
{hideActivity ? <RoomViewFollowingPlaceholder /> : <RoomViewFollowing room={room} />}
</Box>
</Page>
);
}