diff --git a/src/app/components/message/MessageJumpLink.css.ts b/src/app/components/message/MessageJumpLink.css.ts
new file mode 100644
index 00000000..f506af33
--- /dev/null
+++ b/src/app/components/message/MessageJumpLink.css.ts
@@ -0,0 +1,8 @@
+import { style } from '@vanilla-extract/css';
+
+export const messageJumpLink = style({
+ ':hover': {
+ cursor: 'pointer',
+ textDecoration: 'underline',
+ },
+});
diff --git a/src/app/components/message/MessageJumpLink.tsx b/src/app/components/message/MessageJumpLink.tsx
new file mode 100644
index 00000000..3af4513f
--- /dev/null
+++ b/src/app/components/message/MessageJumpLink.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import { messageJumpLink } from "./MessageJumpLink.css";
+import { useRoomNavigate } from "../../hooks/useRoomNavigate";
+
+export function MessageJumpLink({ roomId, eventId }: { roomId: string, eventId: string }) {
+ const { navigateRoom } = useRoomNavigate();
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx
index 2281b59d..13a89e08 100644
--- a/src/app/features/room/RoomTimeline.tsx
+++ b/src/app/features/room/RoomTimeline.tsx
@@ -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(() =>
eventId ? getEmptyTimeline() : getInitialTimeline(room)
@@ -1468,6 +1470,47 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
);
},
+ [StateEvent.RoomPinnedEvents]: (mEventId, mEvent, item) => {
+ const highlighted = focusItem?.index === item && focusItem.highlight;
+ const parsed = parsePinnedEvent(mEvent);
+
+ const timeJSX = (
+
+ );
+
+ return (
+
+
+
+ {parsed}
+
+
+ }
+ />
+
+ );
+ }
},
(mEventId, mEvent, item) => {
if (!showHiddenEvents) return null;
diff --git a/src/app/hooks/usePinnedEventParser.tsx b/src/app/hooks/usePinnedEventParser.tsx
new file mode 100644
index 00000000..48b69648
--- /dev/null
+++ b/src/app/hooks/usePinnedEventParser.tsx
@@ -0,0 +1,57 @@
+import { MatrixEvent } from 'matrix-js-sdk';
+import React, { ReactNode } from 'react';
+import { IRoomPinnedEventsContent } from '../../types/matrix/room';
+import { getMxIdLocalPart } from '../utils/matrix';
+import { MessageJumpLink } from '../components/message/MessageJumpLink';
+
+export type PinnedEventParser = (mEvent: MatrixEvent) => ReactNode;
+
+export const usePinnedEventParser = (roomId: string): PinnedEventParser => {
+ const pinnedEventParser = (mEvent: MatrixEvent) => {
+ const { pinned } = mEvent.getContent();
+ const prevPinned = (mEvent.getPrevContent() as Partial).pinned;
+ const senderId = mEvent.getSender() ?? '';
+ const senderName = getMxIdLocalPart(senderId);
+
+ const addedPins = pinned.filter((pdu) => !(prevPinned?.includes(pdu) ?? false));
+ const removedPins = prevPinned?.filter((pdu) => !pinned.includes(pdu)) ?? [];
+
+ return (
+ <>
+ {senderName}
+ {addedPins.length === 0 && removedPins.length === 0 ? (
+ ' made no changes to the pinned messages'
+ ) : (
+ <>
+ {addedPins.length > 0 && (
+ <>
+ {' pinned '}
+
+ {addedPins.length === 1 ? (
+
+ ) : (
+ `${addedPins.length} messages`
+ )}
+
+ >
+ )}
+ {addedPins.length > 0 && removedPins.length > 0 && 'and'}
+ {removedPins.length > 0 && (
+ <>
+ {' unpinned '}
+
+ {removedPins.length === 1 ? (
+
+ ) : (
+ `${removedPins.length} messages`
+ )}
+
+ >
+ )}
+ >
+ )}
+ >
+ );
+ };
+ return pinnedEventParser;
+};
diff --git a/src/types/matrix/room.ts b/src/types/matrix/room.ts
index b866fd77..ce907cb4 100644
--- a/src/types/matrix/room.ts
+++ b/src/types/matrix/room.ts
@@ -77,6 +77,10 @@ export type IRoomCreateContent = {
};
};
+export type IRoomPinnedEventsContent = {
+ pinned: string[];
+};
+
export type GetContentCallback = () => T;
export type RoomToParents = Map>;