mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-04 14:30:29 +03:00
Merge 52fde65973 into 46c02b89de
This commit is contained in:
commit
f1b77a41c2
5 changed files with 167 additions and 0 deletions
|
|
@ -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
src/app/components/event-action-button/EventActionButton.tsx
Normal file
14
src/app/components/event-action-button/EventActionButton.tsx
Normal file
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
|
@ -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) => (
|
||||
|
|
@ -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)
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
70
src/app/hooks/usePinnedEventParser.tsx
Normal file
70
src/app/hooks/usePinnedEventParser.tsx
Normal file
|
|
@ -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);
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue