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
@@ -42,9 +42,42 @@ import {
import { onEnterOrSpace } from '../utils/keyboard';
import { copyToClipboard, tryDecodeURIComponent } from '../utils/dom';
import { useTimeoutToggle } from '../hooks/useTimeoutToggle';
import { tokenize, tokenStyle } from '../utils/syntaxHighlight';
const ReactPrism = lazy(() => import('./react-prism/ReactPrism'));
/** Languages handled by the custom TDS tokenizer. */
const TDS_TOKENIZER_LANGS = new Set([
'js',
'javascript',
'ts',
'typescript',
'jsx',
'tsx',
'py',
'python',
'rs',
'rust',
]);
/**
* Renders a code string as an array of coloured <span> elements using the
* lightweight TDS tokenizer. Falls back to a plain text node when the
* language is not in the supported set.
*/
function renderTokenizedCode(code: string, lang: string): React.ReactNode {
const normalised = lang.toLowerCase().replace(/^language-/, '');
if (!TDS_TOKENIZER_LANGS.has(normalised)) return code;
const tokens = tokenize(code, normalised);
return tokens.map((tok, idx) => (
<span key={idx} style={tok.type !== 'plain' ? tokenStyle(tok.type) : undefined}>
{tok.text}
</span>
));
}
const EMOJI_REG_G = new RegExp(`${URL_NEG_LB}(${EMOJI_PATTERN})`, 'g');
export const LINKIFY_OPTS: LinkifyOpts = {
@@ -420,6 +453,18 @@ export const getReactCustomHtmlParser = (
if (lang === 'language-rs') lang = 'language-rust';
else if (lang === 'language-js') lang = 'language-javascript';
else if (lang === 'language-ts') lang = 'language-typescript';
// Use lightweight TDS tokenizer for supported languages to render
// coloured <span> elements with inline TDS CSS variable styles.
const normLang = (lang ?? '').toLowerCase().replace(/^language-/, '');
if (TDS_TOKENIZER_LANGS.has(normLang)) {
return (
<code {...props} className={lang}>
{renderTokenizedCode(codeReact, normLang)}
</code>
);
}
return (
<ErrorBoundary fallback={<code {...props}>{codeReact}</code>}>
<Suspense fallback={<code {...props}>{codeReact}</code>}>