feat: P1 features — quick switcher, media gallery, DM previews, knock-to-join, syntax highlighting

P1-1: Quick room switcher (Ctrl+K/Cmd+K) — QuickSwitcher.tsx + ClientNonUIFeatures hotkey
P1-2: Media gallery drawer (images/videos/files) — MediaGallery.tsx + RoomViewHeader toggle
P1-4: DM last message preview + relative timestamp in RoomNavItem when direct=true
P1-7: Code syntax highlighting — TDS tokenizer (syntaxHighlight.ts), custom CSS theme
       (.prism-tds-dark/.prism-tds-light), applied in react-custom-html-parser.tsx
P1-11: Knock-to-join — "Request to Join" in RoomIntro + Pending Requests in MembersDrawer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 19:45:57 -04:00
parent afe957015b
commit d43044ccbf
11 changed files with 1468 additions and 271 deletions
+34 -1
View File
@@ -10,8 +10,9 @@ import {
Spinner,
Text,
as,
color,
} from 'folds';
import { Room } from 'matrix-js-sdk';
import { JoinRule, Room } from 'matrix-js-sdk';
import { useAtomValue } from 'jotai';
import { IRoomCreateContent, Membership, StateEvent } from '../../../types/matrix/room';
import { getMemberDisplayName, getStateEvent } from '../../utils/room';
@@ -42,6 +43,8 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) =>
const mDirects = useAtomValue(mDirectAtom);
const [invitePrompt, setInvitePrompt] = useState(false);
const [viewTopic, setViewTopic] = useState(false);
const [knocked, setKnocked] = useState(false);
const [knockError, setKnockError] = useState<string | undefined>();
const createEvent = getStateEvent(room, StateEvent.RoomCreate);
const avatarMxc = useRoomAvatar(room, mDirects.has(room.roomId));
@@ -168,6 +171,36 @@ export const RoomIntro = as<'div', RoomIntroProps>(({ room, ...props }, ref) =>
<Text size="B300">Join Old Room</Text>
</Button>
))}
{room.getJoinRule() === JoinRule.Knock &&
room.getMyMembership() !== Membership.Join &&
(knocked ? (
<Text size="T300" priority="300">
Request sent waiting for room admin approval
</Text>
) : (
<>
<Button
onClick={() => {
setKnockError(undefined);
mx.knockRoom(room.roomId)
.then(() => setKnocked(true))
.catch((err: Error) =>
setKnockError(err.message ?? 'Failed to send request'),
);
}}
variant="Primary"
size="300"
radii="300"
>
<Text size="B300">Request to Join</Text>
</Button>
{knockError && (
<Text size="T300" style={{ color: color.Critical.Main }}>
{knockError}
</Text>
)}
</>
))}
</Box>
</Box>
</Box>