fix+feat: bug fixes, deleted message placeholder, poll display
Bug fixes: - IncomingCallNotification: track ring setTimeout ID and clear it on cleanup/dismiss — prevents orphaned callbacks after unmount - RoomTimeline: allow redacted m.room.message, m.room.encrypted and m.sticker events past the early-return filter so they hit the existing RedactedContent renderer showing the trash-icon placeholder New features: - PollContent component: read-only display of m.poll.start and org.matrix.msc3381.poll.start events (both stable Matrix 1.7 and MSC3381 unstable content keys); renders poll question + answer options inside the standard Message bubble; registered both as top-level event renderers and inside EncryptedContent callback so encrypted polls also render after decryption - Deleted message placeholder: m.room.message and m.room.encrypted redacted events now show the existing MessageDeletedContent component (trash icon + italic notice) instead of disappearing entirely — matches Element, FluffyChat, Commet, Nheko behaviour
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import { Box, Text } from 'folds';
|
||||
|
||||
type PollTextValue = Array<{ body: string }> | string;
|
||||
|
||||
function extractText(val: PollTextValue | undefined): string {
|
||||
if (!val) return '';
|
||||
if (typeof val === 'string') return val;
|
||||
return val[0]?.body ?? '';
|
||||
}
|
||||
|
||||
type PollAnswer = {
|
||||
'm.id'?: string;
|
||||
id?: string;
|
||||
'm.text'?: PollTextValue;
|
||||
'org.matrix.msc3381.poll.answer'?: { body: string };
|
||||
};
|
||||
|
||||
type PollData = {
|
||||
question?: { body?: string; 'm.text'?: PollTextValue };
|
||||
answers?: PollAnswer[];
|
||||
};
|
||||
|
||||
export function PollContent({ content }: { content: Record<string, unknown> }) {
|
||||
const poll = (
|
||||
content['m.poll'] ?? content['org.matrix.msc3381.poll.start']
|
||||
) as PollData | undefined;
|
||||
|
||||
if (!poll) {
|
||||
return (
|
||||
<Text style={{ opacity: 0.6 }}>
|
||||
<i>Poll (unreadable format)</i>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
const questionText =
|
||||
extractText((poll.question as any)?.['m.text']) ||
|
||||
(poll.question as any)?.body ||
|
||||
'Untitled poll';
|
||||
|
||||
return (
|
||||
<Box direction="Column" gap="200" style={{ maxWidth: '340px', paddingTop: '2px', paddingBottom: '4px' }}>
|
||||
<Box
|
||||
alignItems="Center"
|
||||
gap="100"
|
||||
style={{
|
||||
fontSize: '0.68rem',
|
||||
fontWeight: 700,
|
||||
letterSpacing: '0.12em',
|
||||
textTransform: 'uppercase',
|
||||
opacity: 0.55,
|
||||
marginBottom: '2px',
|
||||
}}
|
||||
>
|
||||
◉ Poll
|
||||
</Box>
|
||||
<Text size="T400" style={{ fontWeight: 600 }}>
|
||||
{questionText}
|
||||
</Text>
|
||||
<Box direction="Column" gap="100" style={{ marginTop: '2px' }}>
|
||||
{(poll.answers ?? []).map((answer, i) => {
|
||||
const text =
|
||||
extractText((answer as any)['m.text']) ||
|
||||
(answer as any)['org.matrix.msc3381.poll.answer']?.body ||
|
||||
`Option ${i + 1}`;
|
||||
const id = answer['m.id'] ?? answer.id ?? String(i);
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
style={{
|
||||
padding: '7px 12px',
|
||||
borderRadius: '8px',
|
||||
background: 'var(--bg-surface-low)',
|
||||
border: '1px solid var(--bg-surface-border)',
|
||||
fontSize: '0.88rem',
|
||||
lineHeight: 1.4,
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
<Text size="T200" style={{ opacity: 0.4, marginTop: '2px' }}>
|
||||
<i>Open in Element to vote</i>
|
||||
</Text>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -5,3 +5,4 @@ export * from './AudioContent';
|
||||
export * from './FileContent';
|
||||
export * from './FallbackContent';
|
||||
export * from './EventContent';
|
||||
export * from './PollContent';
|
||||
|
||||
Reference in New Issue
Block a user