Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/app/components/event-action-button/EventActionButton.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ComplexStyleRule } from '@vanilla-extract/css';
import { recipe } from '@vanilla-extract/recipes';
import { color, DefaultReset, MainColor } from 'folds';

const getVariant = (variant: MainColor): ComplexStyleRule => ({
vars: {
color: color[variant].Main,
},
selectors: {
'&:hover': {
color: color[variant].MainHover,
},
},
});

export const EventActionButton = recipe({
base: [
DefaultReset,
{
':hover': {
textDecoration: 'underline',
},
cursor: 'pointer',
},
],
variants: {
variant: {
Primary: getVariant('Primary'),
Secondary: getVariant('Secondary'),
Success: getVariant('Success'),
Warning: getVariant('Warning'),
Critical: getVariant('Critical'),
},
},
defaultVariants: {
variant: 'Primary',
},
});
14 changes: 14 additions & 0 deletions src/app/components/event-action-button/EventActionButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from "react";
import { as, MainColor } from "folds";
import classNames from "classnames";
import * as css from "./EventActionButton.css";

export const EventActionButton = as<'button', { variant?: MainColor }>(
({ as: AsCutoutCard = 'button', className, variant = 'Primary', ...props }, ref) => (
<AsCutoutCard
className={classNames(css.EventActionButton({ variant }), className)}
{...props}
ref={ref}
/>
)
);
43 changes: 43 additions & 0 deletions src/app/features/room/RoomTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ import { useAccessiblePowerTagColors, useGetMemberPowerTag } from '../../hooks/u
import { useTheme } from '../../hooks/useTheme';
import { useRoomCreatorsTag } from '../../hooks/useRoomCreatorsTag';
import { usePowerLevelTags } from '../../hooks/usePowerLevelTags';
import { usePinnedEventParser } from '../../hooks/usePinnedEventParser';

const TimelineFloat = as<'div', css.TimelineFloatVariants>(
({ position, className, ...props }, ref) => (
Expand Down Expand Up @@ -532,6 +533,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
[mx, room, linkifyOpts, spoilerClickHandler, mentionClickHandler, useAuthentication]
);
const parseMemberEvent = useMemberEventParser();
const parsePinnedEvent = usePinnedEventParser(room.roomId);

const [timeline, setTimeline] = useState<Timeline>(() =>
eventId ? getEmptyTimeline() : getInitialTimeline(room)
Expand Down Expand Up @@ -1468,6 +1470,47 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
</Event>
);
},
[StateEvent.RoomPinnedEvents]: (mEventId, mEvent, item) => {
const highlighted = focusItem?.index === item && focusItem.highlight;
const parsed = parsePinnedEvent(mEvent);

const timeJSX = (
<Time
ts={mEvent.getTs()}
compact={messageLayout === MessageLayout.Compact}
hour24Clock={hour24Clock}
dateFormatString={dateFormatString}
/>
);

return (
<Event
key={mEvent.getId()}
data-message-item={item}
data-message-id={mEventId}
room={room}
mEvent={mEvent}
highlight={highlighted}
messageSpacing={messageSpacing}
canDelete={canRedact || mEvent.getSender() === mx.getUserId()}
hideReadReceipts={hideActivity}
showDeveloperTools={showDeveloperTools}
>
<EventContent
messageLayout={messageLayout}
time={timeJSX}
iconSrc={Icons.Pin}
content={
<Box grow="Yes" direction="Column">
<Text size="T300" priority="300">
{parsed}
</Text>
</Box>
}
/>
</Event>
);
}
},
(mEventId, mEvent, item) => {
if (!showHiddenEvents) return null;
Expand Down
70 changes: 70 additions & 0 deletions src/app/hooks/usePinnedEventParser.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { MatrixEvent } from 'matrix-js-sdk';
import React, { ReactNode, useCallback } from 'react';
import { RoomPinnedEventsEventContent } from 'matrix-js-sdk/lib/types';
import { getMxIdLocalPart } from '../utils/matrix';
import { EventActionButton } from '../components/event-action-button/EventActionButton';
import { useRoomNavigate } from './useRoomNavigate';
import { singleOrNull } from '../utils/common';

export type PinnedEventParser = (mEvent: MatrixEvent) => ReactNode | null;

export const usePinnedEventParser = (roomId: string): PinnedEventParser => {
const { navigateRoom } = useRoomNavigate();

const navigate = useCallback(
(eventId: string) => {
navigateRoom(roomId, eventId);
},
[navigateRoom, roomId]
);

const pinnedEventParser = (mEvent: MatrixEvent) => {
const { pinned } = mEvent.getContent<RoomPinnedEventsEventContent>();
const prevPinned = (mEvent.getPrevContent() as Partial<RoomPinnedEventsEventContent>).pinned;
const senderId = mEvent.getSender() ?? '';
const senderName = getMxIdLocalPart(senderId);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should use

getMemberDisplayName(room, senderId) ??
    getMxIdLocalPart(senderId) ??
    senderId;

for displayname


const addedPins = pinned.filter((event) => !(prevPinned?.includes(event) ?? false));
const removedPins = prevPinned?.filter((event) => !pinned.includes(event)) ?? [];
const bodyMessages: string[] = [];

// if only one event was added/removed total, show a link to jump to it
const jumpTarget = singleOrNull(addedPins.concat(removedPins));

// if this event didn't change anything, don't show the message at all
if (addedPins.length === 0 && removedPins.length === 0) {
return null;
}

// check for added pins
if (addedPins.length > 0) {
if (addedPins.length === 1) {
bodyMessages.push('pinned a message');
} else {
bodyMessages.push(`pinned ${addedPins.length} messages`);
}
}

// check for removed pins
if (removedPins.length > 0) {
if (removedPins.length === 1) {
bodyMessages.push('unpinned a message');
} else {
bodyMessages.push(`unpinned ${removedPins.length} messages`);
}
}

return (
<>
<b>{senderName}</b>
{` ${bodyMessages.join(' and ')}. `}
{jumpTarget && (
<EventActionButton onClick={() => navigate(jumpTarget)}>
<b>View Message</b>
</EventActionButton>
)}
</>
);
};
return pinnedEventParser;
};
2 changes: 2 additions & 0 deletions src/app/utils/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,5 @@ export const splitWithSpace = (content: string): string[] => {
if (trimmedContent === '') return [];
return trimmedContent.split(' ');
};

export const singleOrNull = <T>(array: T[]): T | null => (array.length === 1 ? array[0] : null);