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:
@@ -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>}>
|
||||
|
||||
@@ -22,6 +22,54 @@
|
||||
--prism-regex: #fd971f;
|
||||
}
|
||||
|
||||
/* ── Lotus Terminal Design System syntax theme ─────────────────────────────
|
||||
Applied when the lotus-terminal-theme body class is active.
|
||||
Maps Prism token roles to TDS accent variables:
|
||||
keyword → --lt-accent-cyan (language control flow)
|
||||
selector → --lt-accent-green (strings / inserted text)
|
||||
boolean → --lt-accent-orange (numbers / booleans)
|
||||
atrule → --lt-accent-purple (functions / class names)
|
||||
comment → dimmed italic (comments use opacity)
|
||||
property → --lt-accent-orange (properties / tags)
|
||||
regex → --lt-accent-amber (regex / important)
|
||||
─────────────────────────────────────────────────────────────────────── */
|
||||
.prism-tds-dark {
|
||||
--prism-comment: rgba(0, 255, 136, 0.4);
|
||||
--prism-punctuation: rgba(196, 217, 238, 0.65);
|
||||
--prism-property: var(--lt-accent-orange, #ff6b00);
|
||||
--prism-boolean: var(--lt-accent-orange, #ff6b00);
|
||||
--prism-selector: var(--lt-accent-green, #00ff88);
|
||||
--prism-operator: rgba(196, 217, 238, 0.8);
|
||||
--prism-atrule: var(--lt-accent-purple, #bf5fff);
|
||||
--prism-keyword: var(--lt-accent-cyan, #00d4ff);
|
||||
--prism-regex: var(--lt-accent-amber, #ffb300);
|
||||
}
|
||||
|
||||
.prism-tds-light {
|
||||
--prism-comment: rgba(0, 109, 53, 0.55);
|
||||
--prism-punctuation: rgba(45, 61, 86, 0.7);
|
||||
--prism-property: var(--lt-accent-orange, #c44e00);
|
||||
--prism-boolean: var(--lt-accent-orange, #c44e00);
|
||||
--prism-selector: var(--lt-accent-green, #006d35);
|
||||
--prism-operator: rgba(45, 61, 86, 0.85);
|
||||
--prism-atrule: var(--lt-accent-purple, #6b2fb8);
|
||||
--prism-keyword: var(--lt-accent-cyan, #0062b8);
|
||||
--prism-regex: var(--lt-accent-amber, #8a5a00);
|
||||
}
|
||||
|
||||
/* Comment tokens get italic treatment in TDS themes */
|
||||
.prism-tds-dark code .token.comment,
|
||||
.prism-tds-dark code .token.prolog,
|
||||
.prism-tds-dark code .token.doctype,
|
||||
.prism-tds-dark code .token.cdata,
|
||||
.prism-tds-light code .token.comment,
|
||||
.prism-tds-light code .token.prolog,
|
||||
.prism-tds-light code .token.doctype,
|
||||
.prism-tds-light code .token.cdata {
|
||||
font-style: italic;
|
||||
opacity: 0.65;
|
||||
}
|
||||
|
||||
code .token.comment,
|
||||
code .token.prolog,
|
||||
code .token.doctype,
|
||||
|
||||
Reference in New Issue
Block a user