From 2d66641167b32a211fe5bb127d3ca228a8da379f Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 16:03:03 -0500
Subject: [PATCH 01/73] A bit more idiomatic than my previous attempt
---
src/app/features/room/message/Message.tsx | 358 ++++++----------------
1 file changed, 97 insertions(+), 261 deletions(-)
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index ae971ab8..39d172db 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -36,6 +36,7 @@ import { MatrixEvent, Room } from 'matrix-js-sdk';
import { Relations } from 'matrix-js-sdk/lib/models/relations';
import classNames from 'classnames';
import { RoomPinnedEventsEventContent } from 'matrix-js-sdk/lib/types';
+import { useLongPress } from 'use-long-press';
import {
AvatarBase,
BubbleLayout,
@@ -79,6 +80,9 @@ import { StateEvent } from '../../../../types/matrix/room';
import { getTagIconSrc, PowerLevelTag } from '../../../hooks/usePowerLevelTags';
import { PowerIcon } from '../../../components/power';
import colorMXID from '../../../../util/colorMXID';
+import { MessageDropdownMenu, MessageOptionsMenu } from '../MessageOptionsMenu';
+import { BottomSheetMenu } from '../MobileClickMenu';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void;
@@ -714,12 +718,15 @@ export const Message = as<'div', MessageProps>(
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const senderId = mEvent.getSender() ?? '';
- const [hover, setHover] = useState(false);
- const { hoverProps } = useHover({ onHoverChange: setHover });
- const { focusWithinProps } = useFocusWithin({ onFocusWithinChange: setHover });
+ const { hoverProps, isHovered } = useHover({});
+ const { focusWithinProps, isFocusWithin } = useFocusWithin({});
const [menuAnchor, setMenuAnchor] = useState();
const [emojiBoardAnchor, setEmojiBoardAnchor] = useState();
-
+ const screenSize = useScreenSizeContext();
+ const isMobile = screenSize === ScreenSize.Mobile;
+ const [isDesktopOptionsActive, setDesktopOptionsActive] = useState(false);
+ const showDesktopOptions = !isMobile && (isHovered || isFocusWithin || isDesktopOptionsActive);
+ const [isMobileSheetOpen, setMobileSheetOpen] = useState(false);
const senderDisplayName =
getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId;
const senderAvatarMxc = getMemberAvatarMxc(room, senderId);
@@ -733,6 +740,18 @@ export const Message = as<'div', MessageProps>(
const usernameColor = legacyUsernameColor ? colorMXID(senderId) : tagColor;
+ const longPressBinder = useLongPress(
+ () => {
+ if (isMobile) {
+ setMobileSheetOpen(true);
+ }
+ },
+ {
+ threshold: 400,
+ cancelOnMovement: true,
+ }
+ );
+
const headerJSX = !collapse && (
(
{tagIconSrc && }
- {messageLayout === MessageLayout.Modern && hover && (
+ {messageLayout === MessageLayout.Modern && isHovered && (
<>
{senderId}
@@ -833,263 +852,80 @@ export const Message = as<'div', MessageProps>(
});
};
- const handleOpenMenu: MouseEventHandler = (evt) => {
- const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
- setMenuAnchor(target.getBoundingClientRect());
- };
-
- const closeMenu = () => {
- setMenuAnchor(undefined);
- };
-
- const handleOpenEmojiBoard: MouseEventHandler = (evt) => {
- const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
- setEmojiBoardAnchor(target.getBoundingClientRect());
- };
- const handleAddReactions: MouseEventHandler = () => {
- const rect = menuAnchor;
- closeMenu();
- // open it with timeout because closeMenu
- // FocusTrap will return focus from emojiBoard
-
- setTimeout(() => {
- setEmojiBoardAnchor(rect);
- }, 100);
- };
-
return (
-
- {!edit && (hover || !!menuAnchor || !!emojiBoardAnchor) && (
-
-
-
+ <>
+
+ {!edit && (isHovered || !!menuAnchor || !!emojiBoardAnchor) && (
+
+ )}
+ {messageLayout === MessageLayout.Compact && (
+
+ {msgContentJSX}
+
+ )}
+ {messageLayout === MessageLayout.Bubble && (
+
+ {headerJSX}
+ {msgContentJSX}
+
+ )}
+ {messageLayout !== MessageLayout.Compact && messageLayout !== MessageLayout.Bubble && (
+
+ {headerJSX}
+ {msgContentJSX}
+
+ )}
+
+
+ {isMobile && (
+ setMobileSheetOpen(false)} isOpen={isMobileSheetOpen}>
+ setMobileSheetOpen(false)}
+ mEvent={mEvent}
+ eventId={mEvent.getId()}
+ room={room}
+ mx={mx}
+ relations={relations}
+ canSendReaction={canSendReaction}
+ canEdit={canEditEvent(mx, mEvent)}
+ canDelete={canDelete || mEvent?.getSender() === mx.getUserId()}
+ canPinEvent={canPinEvent}
+ hideReadReceipts={hideReadReceipts}
+ onReactionToggle={onReactionToggle}
+ onReplyClick={onReplyClick}
+ onEditId={onEditId}
+ handleAddReactions={null}
+ />
+
)}
- {messageLayout === MessageLayout.Compact && (
-
- {msgContentJSX}
-
- )}
- {messageLayout === MessageLayout.Bubble && (
-
- {headerJSX}
- {msgContentJSX}
-
- )}
- {messageLayout !== MessageLayout.Compact && messageLayout !== MessageLayout.Bubble && (
-
- {headerJSX}
- {msgContentJSX}
-
- )}
-
+ >
);
}
);
@@ -1155,7 +991,7 @@ export const Event = as<'div', EventProps>(
highlight={highlight}
selected={!!menuAnchor}
{...props}
- {...hoverProps}
+ {...hoverProps} // Impacts hover
{...focusWithinProps}
ref={ref}
>
From 5d3033aa96e20f5441cbc0d890b7a8fe9b4d56e5 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 16:04:56 -0500
Subject: [PATCH 02/73] move t and handleEdit
---
src/app/features/room/RoomTimeline.tsx | 44 ++++++++------------------
1 file changed, 13 insertions(+), 31 deletions(-)
diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx
index 05caf4b0..7b58ee6b 100644
--- a/src/app/features/room/RoomTimeline.tsx
+++ b/src/app/features/room/RoomTimeline.tsx
@@ -435,6 +435,7 @@ export function RoomTimeline({
accessibleTagColors,
}: RoomTimelineProps) {
const mx = useMatrixClient();
+ const { t } = useTranslation();
const useAuthentication = useMediaAuthentication();
const [hideActivity] = useSetting(settingsAtom, 'hideActivity');
const [messageLayout] = useSetting(settingsAtom, 'messageLayout');
@@ -592,15 +593,8 @@ export function RoomTimeline({
room,
useCallback(
(mEvt: MatrixEvent) => {
- // if user is at bottom of timeline
- // keep paginating timeline and conditionally mark as read
- // otherwise we update timeline without paginating
- // so timeline can be updated with evt like: edits, reactions etc
if (atBottomRef.current) {
if (document.hasFocus() && (!unreadInfo || mEvt.getSender() === mx.getUserId())) {
- // Check if the document is in focus (user is actively viewing the app),
- // and either there are no unread messages or the latest message is from the current user.
- // If either condition is met, trigger the markAsRead function to send a read receipt.
requestAnimationFrame(() => markAsRead(mx, mEvt.getRoomId()!, hideActivity));
}
@@ -668,13 +662,11 @@ export function RoomTimeline({
}, [room, liveTimelineLinked])
);
- // Stay at bottom when room editor resize
useResizeObserver(
useMemo(() => {
let mounted = false;
return (entries) => {
if (!mounted) {
- // skip initial mounting call
mounted = true;
return;
}
@@ -742,8 +734,6 @@ export function RoomTimeline({
if (inFocus && atBottomRef.current) {
if (unreadInfo?.inLiveTimeline) {
handleOpenEvent(unreadInfo.readUptoEventId, false, (scrolled) => {
- // the unread event is already in view
- // so, try mark as read;
if (!scrolled) {
tryAutoMarkAsRead();
}
@@ -757,7 +747,6 @@ export function RoomTimeline({
)
);
- // Handle up arrow edit
useKeyDown(
window,
useCallback(
@@ -788,7 +777,6 @@ export function RoomTimeline({
}
}, [eventId, loadEventTimeline]);
- // Scroll to bottom on initial timeline load
useLayoutEffect(() => {
const scrollEl = scrollRef.current;
if (scrollEl) {
@@ -796,8 +784,6 @@ export function RoomTimeline({
}
}, []);
- // if live timeline is linked and unreadInfo change
- // Scroll to last read message
useLayoutEffect(() => {
const { readUptoEventId, inLiveTimeline, scrollTo } = unreadInfo ?? {};
if (readUptoEventId && inLiveTimeline && scrollTo) {
@@ -815,7 +801,6 @@ export function RoomTimeline({
}
}, [room, unreadInfo, scrollToItem]);
- // scroll to focused message
useLayoutEffect(() => {
if (focusItem && focusItem.scrollTo) {
scrollToItem(focusItem.index, {
@@ -834,7 +819,6 @@ export function RoomTimeline({
}, 2000);
}, [alive, focusItem, scrollToItem]);
- // scroll to bottom of timeline
const scrollToBottomCount = scrollToBottomRef.current.count;
useLayoutEffect(() => {
if (scrollToBottomCount > 0) {
@@ -844,14 +828,24 @@ export function RoomTimeline({
}
}, [scrollToBottomCount]);
- // Remove unreadInfo on mark as read
useEffect(() => {
if (!unread) {
setUnreadInfo(undefined);
}
}, [unread]);
- // scroll out of view msg editor in view.
+ const handleEdit = useCallback(
+ (editEvtId?: string) => {
+ if (editEvtId) {
+ setEditId(editEvtId);
+ return;
+ }
+ setEditId(undefined);
+ ReactEditor.focus(editor);
+ },
+ [editor]
+ );
+
useEffect(() => {
if (editId) {
const editMsgElement =
@@ -982,18 +976,6 @@ export function RoomTimeline({
},
[mx, room]
);
- const handleEdit = useCallback(
- (editEvtId?: string) => {
- if (editEvtId) {
- setEditId(editEvtId);
- return;
- }
- setEditId(undefined);
- ReactEditor.focus(editor);
- },
- [editor]
- );
- const { t } = useTranslation();
const renderMatrixEvent = useMatrixEventRenderer<
[string, MatrixEvent, number, EventTimelineSet, boolean]
From e2bbc429149b4bc027788975a3dc97c1311c6eea Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 16:05:33 -0500
Subject: [PATCH 03/73] Pull out menu options and the dropdown into their own
components for reuse
---
src/app/features/room/MessageOptionsMenu.tsx | 273 +++++++++++++++++++
1 file changed, 273 insertions(+)
create mode 100644 src/app/features/room/MessageOptionsMenu.tsx
diff --git a/src/app/features/room/MessageOptionsMenu.tsx b/src/app/features/room/MessageOptionsMenu.tsx
new file mode 100644
index 00000000..43e5b751
--- /dev/null
+++ b/src/app/features/room/MessageOptionsMenu.tsx
@@ -0,0 +1,273 @@
+import { Box, Icon, IconButton, Icons, Line, Menu, MenuItem, PopOut, RectCords, Text } from 'folds';
+import React, { MouseEventHandler, useEffect, useState } from 'react';
+
+import FocusTrap from 'focus-trap-react';
+import { EmojiBoard } from '../../components/emoji-board';
+import { stopPropagation } from '../../utils/keyboard';
+import * as css from './message/styles.css';
+
+import {
+ MessageAllReactionItem,
+ MessageCopyLinkItem,
+ MessageDeleteItem,
+ MessagePinItem,
+ MessageQuickReactions,
+ MessageReadReceiptItem,
+ MessageReportItem,
+ MessageSourceCodeItem,
+} from './message/Message';
+
+export function MessageDropdownMenu({
+ mEvent,
+ room,
+ mx,
+ relations,
+ eventId,
+ canSendReaction,
+ canEdit,
+ canDelete,
+ canPinEvent,
+ hideReadReceipts,
+ onReactionToggle,
+ onReplyClick,
+ onEditId,
+ handleAddReactions,
+ closeMenu,
+}) {
+ return (
+
+ {canSendReaction && (
+ {
+ onReactionToggle(eventId, key, shortcode);
+ closeMenu();
+ }}
+ />
+ )}
+
+ {canSendReaction && (
+ }
+ radii="300"
+ onClick={handleAddReactions}
+ >
+
+ Add Reaction
+
+
+ )}
+ {relations && (
+
+ )}
+ }
+ radii="300"
+ data-event-id={eventId}
+ onClick={(evt) => {
+ onReplyClick(evt);
+ closeMenu();
+ }}
+ >
+
+ Reply
+
+
+ {canEdit && onEditId && (
+ }
+ radii="300"
+ data-event-id={eventId}
+ onClick={() => {
+ onEditId(eventId);
+ closeMenu();
+ }}
+ >
+
+ Edit Message
+
+
+ )}
+ {!hideReadReceipts && (
+
+ )}
+
+
+ {canPinEvent && }
+
+ {/* Redact and Report actions */}
+ {((!mEvent.isRedacted() && canDelete) || mEvent.getSender() !== mx.getUserId()) && (
+ <>
+
+
+ {!mEvent.isRedacted() && canDelete && (
+
+ )}
+ {mEvent.getSender() !== mx.getUserId() && (
+
+ )}
+
+ >
+ )}
+
+ );
+}
+
+export function MessageOptionsMenu({
+ mEvent,
+ room,
+ mx,
+ relations,
+ imagePackRooms,
+ canSendReaction,
+ canEdit,
+ canDelete,
+ canPinEvent,
+ hideReadReceipts,
+ onReactionToggle,
+ onReplyClick,
+ onEditId,
+ onActiveStateChange,
+}) {
+ const [menuAnchor, setMenuAnchor] = useState();
+ const [emojiBoardAnchor, setEmojiBoardAnchor] = useState();
+
+ useEffect(() => {
+ onActiveStateChange?.(!!menuAnchor || !!emojiBoardAnchor);
+ }, [emojiBoardAnchor, menuAnchor, onActiveStateChange]);
+
+ const eventId = mEvent.getId();
+ if (!eventId) return null;
+
+ const closeMenu = () => {
+ setMenuAnchor(undefined);
+ };
+
+ const handleOpenMenu: MouseEventHandler = (evt) => {
+ const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
+ setMenuAnchor(target.getBoundingClientRect());
+ };
+
+ const handleOpenEmojiBoard: MouseEventHandler = (evt) => {
+ const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
+ setEmojiBoardAnchor(target.getBoundingClientRect());
+ };
+
+ const handleAddReactions: MouseEventHandler = () => {
+ const rect = menuAnchor;
+ closeMenu();
+ // Use a timeout to allow the first menu to close before opening the next
+ setTimeout(() => {
+ setEmojiBoardAnchor(rect);
+ }, 100);
+ };
+ return (
+
+
+
+ {canSendReaction && (
+ {
+ onReactionToggle(eventId, key);
+ setEmojiBoardAnchor(undefined);
+ }}
+ onCustomEmojiSelect={(mxc, shortcode) => {
+ onReactionToggle(eventId, mxc, shortcode);
+ setEmojiBoardAnchor(undefined);
+ }}
+ requestClose={() => setEmojiBoardAnchor(undefined)}
+ />
+ }
+ >
+
+
+
+
+ )}
+
+
+
+ {canEdit && onEditId && (
+ onEditId(eventId)}
+ variant="SurfaceVariant"
+ size="300"
+ radii="300"
+ >
+
+
+ )}
+ setMenuAnchor(undefined),
+ clickOutsideDeactivates: true,
+ isKeyForward: (evt) => evt.key === 'ArrowDown',
+ isKeyBackward: (evt) => evt.key === 'ArrowUp',
+ escapeDeactivates: stopPropagation,
+ }}
+ >
+
+
+ }
+ >
+
+
+
+
+
+
+
+ );
+}
From 41538561ee6039b02d89f90e1dca48bcc5772c83 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 16:06:15 -0500
Subject: [PATCH 04/73] Add a placeholder class for now (move to
MessageOptionsMenu later)
---
src/app/features/room/MobileClickMenu.tsx | 61 +++++++++++++++++++++++
1 file changed, 61 insertions(+)
create mode 100644 src/app/features/room/MobileClickMenu.tsx
diff --git a/src/app/features/room/MobileClickMenu.tsx b/src/app/features/room/MobileClickMenu.tsx
new file mode 100644
index 00000000..48489079
--- /dev/null
+++ b/src/app/features/room/MobileClickMenu.tsx
@@ -0,0 +1,61 @@
+/* eslint-disable react/destructuring-assignment */
+import React from 'react';
+import { Box, Icon, Icons, Text } from 'folds';
+import classNames from 'classnames';
+import * as css from './RoomTimeline.css';
+
+export function BottomSheetMenu({
+ isOpen,
+ onClose,
+ children,
+}: {
+ isOpen: boolean;
+ onClose: () => void;
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
+
+export function MenuItemButton({
+ icon,
+ label,
+ onClick,
+ destructive = false,
+}: {
+ icon: string;
+ label: string;
+ onClick: () => void;
+ destructive?: boolean;
+}) {
+ return (
+
+
+
+ {label}
+
+
+ );
+}
From 64ea5f262b475b11a836dd90a4f4b76afd5d09b8 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 16:09:01 -0500
Subject: [PATCH 05/73] from earlier attempt to remove highlighting on the POC
component (not the current one at all)
---
.../components/message/layout/layout.css.ts | 82 ++++++++++++++++++-
1 file changed, 80 insertions(+), 2 deletions(-)
diff --git a/src/app/components/message/layout/layout.css.ts b/src/app/components/message/layout/layout.css.ts
index a9b3f35f..a61c7f8d 100644
--- a/src/app/components/message/layout/layout.css.ts
+++ b/src/app/components/message/layout/layout.css.ts
@@ -174,16 +174,94 @@ export const MessageTextBody = recipe({
jumboEmoji: {
true: {
fontSize: '1.504em',
- lineHeight: '1.4962em',
+ lineHeight: 1.1,
},
},
emote: {
true: {
- color: color.Success.Main,
fontStyle: 'italic',
},
},
},
+
+ '@media': {
+ 'screen and (max-width: 768px)': {
+ base: {
+ userSelect: 'none',
+ WebkitUserSelect: 'none',
+ MozUserSelect: 'none',
+ msUserSelect: 'none',
+ },
+ },
+ },
});
export type MessageTextBodyVariants = RecipeVariants;
+
+export const menuBackdrop = style({
+ userSelect: 'none',
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+ transition: 'opacity 0.3s ease-in-out',
+ opacity: 0,
+});
+
+export const menuBackdropOpen = style({
+ opacity: 1,
+});
+
+export const menuSheet = style({
+ userSelect: 'none',
+ position: 'fixed',
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: color.Background.Container,
+ borderTopLeftRadius: config.radii.R400,
+ borderTopRightRadius: config.radii.R400,
+ padding: config.space.S500,
+ transform: 'translateY(100%)',
+ transition: 'transform 0.3s ease-out',
+ boxShadow: '0 -2px 10px rgba(0,0,0,0.1)',
+});
+
+export const menuSheetOpen = style({
+ transform: 'translateY(0)',
+});
+
+export const menuItem = style({
+ userSelect: 'none',
+ width: '100%',
+ background: 'none',
+ border: 'none',
+ padding: `${config.space.S300} ${config.space.S100}`,
+ textAlign: 'left',
+ cursor: 'pointer',
+ borderRadius: config.radii.R300,
+ color: color.Primary.ContainerActive,
+
+ outline: 'none',
+
+ WebkitTapHighlightColor: 'transparent',
+
+ WebkitUserSelect: 'none',
+ MozUserSelect: 'none',
+ msUserSelect: 'none',
+
+ selectors: {
+ '&:hover': {
+ backgroundColor: color.Background.ContainerHover,
+ },
+ '&:focus-visible': {
+ backgroundColor: color.Background.ContainerHover,
+ },
+ },
+});
+
+export const menuItemDestructive = style({
+ color: color.Critical.ContainerActive,
+});
From d8a51b41e0a7b9baeed466118b7418f9f8a357bd Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 16:09:12 -0500
Subject: [PATCH 06/73] Add use-long-press
---
package.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 3c1cef8c..7622dbc8 100644
--- a/package.json
+++ b/package.json
@@ -80,7 +80,8 @@
"slate-history": "0.110.3",
"slate-react": "0.112.1",
"tippy.js": "6.3.7",
- "ua-parser-js": "1.0.35"
+ "ua-parser-js": "1.0.35",
+ "use-long-press": "3.3.0"
},
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
From df7b71b768c9edd0b5721e9b1e4cddd595b0c7ea Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 16:09:30 -0500
Subject: [PATCH 07/73] update package lock
---
package-lock.json | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/package-lock.json b/package-lock.json
index 7dd2bf0a..3d2e8d05 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -69,7 +69,8 @@
"slate-history": "0.110.3",
"slate-react": "0.112.1",
"tippy.js": "6.3.7",
- "ua-parser-js": "1.0.35"
+ "ua-parser-js": "1.0.35",
+ "use-long-press": "3.3.0"
},
"devDependencies": {
"@esbuild-plugins/node-globals-polyfill": "0.2.3",
@@ -11311,6 +11312,15 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-long-press": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/use-long-press/-/use-long-press-3.3.0.tgz",
+ "integrity": "sha512-Yedz46ILxsb1BTS6kUzpV/wyEZPUlJDq+8Oat0LP1eOZQHbS887baJHJbIGENqCo8wTKNxmoTHLdY8lU/e+wvw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
From 770732beb4e17f95880152f19f0e4748e3e38456 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 20:04:12 -0500
Subject: [PATCH 08/73] Pull out the dupes and just pass them parameter bloat
is fine here as it shouldn't grow too much more
---
src/app/features/room/MessageOptionsMenu.tsx | 34 ++++++--------------
1 file changed, 10 insertions(+), 24 deletions(-)
diff --git a/src/app/features/room/MessageOptionsMenu.tsx b/src/app/features/room/MessageOptionsMenu.tsx
index 43e5b751..52c44cde 100644
--- a/src/app/features/room/MessageOptionsMenu.tsx
+++ b/src/app/features/room/MessageOptionsMenu.tsx
@@ -130,9 +130,17 @@ export function MessageOptionsMenu({
onReplyClick,
onEditId,
onActiveStateChange,
+ closeMenu,
+ menuAnchor,
+ emojiBoardAnchor,
+ handleOpenEmojiBoard,
+ handleOpenMenu,
+ handleAddReactions,
+ setMenuAnchor,
+ setEmojiBoardAnchor,
}) {
- const [menuAnchor, setMenuAnchor] = useState();
- const [emojiBoardAnchor, setEmojiBoardAnchor] = useState();
+ // const [menuAnchor, setMenuAnchor] = useState();
+ // const [emojiBoardAnchor, setEmojiBoardAnchor] = useState();
useEffect(() => {
onActiveStateChange?.(!!menuAnchor || !!emojiBoardAnchor);
@@ -141,28 +149,6 @@ export function MessageOptionsMenu({
const eventId = mEvent.getId();
if (!eventId) return null;
- const closeMenu = () => {
- setMenuAnchor(undefined);
- };
-
- const handleOpenMenu: MouseEventHandler = (evt) => {
- const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
- setMenuAnchor(target.getBoundingClientRect());
- };
-
- const handleOpenEmojiBoard: MouseEventHandler = (evt) => {
- const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
- setEmojiBoardAnchor(target.getBoundingClientRect());
- };
-
- const handleAddReactions: MouseEventHandler = () => {
- const rect = menuAnchor;
- closeMenu();
- // Use a timeout to allow the first menu to close before opening the next
- setTimeout(() => {
- setEmojiBoardAnchor(rect);
- }, 100);
- };
return (
From d093fc35efa463fb68969664788036932585aeef Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 20:05:20 -0500
Subject: [PATCH 09/73] readd these and pass them
---
src/app/features/room/message/Message.tsx | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index 39d172db..b999d3a8 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -731,6 +731,28 @@ export const Message = as<'div', MessageProps>(
getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId;
const senderAvatarMxc = getMemberAvatarMxc(room, senderId);
+ const closeMenu = () => {
+ setMenuAnchor(undefined);
+ };
+
+ const handleOpenMenu: MouseEventHandler = (evt) => {
+ const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
+ setMenuAnchor(target.getBoundingClientRect());
+ };
+
+ const handleOpenEmojiBoard: MouseEventHandler = (evt) => {
+ const target = evt.currentTarget.parentElement?.parentElement ?? evt.currentTarget;
+ setEmojiBoardAnchor(target.getBoundingClientRect());
+ };
+
+ const handleAddReactions: MouseEventHandler = () => {
+ const rect = menuAnchor;
+ closeMenu();
+ setTimeout(() => {
+ setEmojiBoardAnchor(rect);
+ }, 100);
+ };
+
const tagColor = powerLevelTag?.color
? accessibleTagColors?.get(powerLevelTag.color)
: undefined;
From db0525ff75a275371c018eaa82d22534ef5c1589 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 20:06:03 -0500
Subject: [PATCH 10/73] Add sheet state
---
src/app/features/room/message/Message.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index b999d3a8..dc58061e 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -730,6 +730,7 @@ export const Message = as<'div', MessageProps>(
const senderDisplayName =
getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId;
const senderAvatarMxc = getMemberAvatarMxc(room, senderId);
+ const [view, setView] = useState('options');
const closeMenu = () => {
setMenuAnchor(undefined);
From f2a7a42756570891c81a673c615dd44747d98c23 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 20:06:14 -0500
Subject: [PATCH 11/73] add methods for sheet management
---
src/app/features/room/message/Message.tsx | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index dc58061e..9aa054ef 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -754,6 +754,16 @@ export const Message = as<'div', MessageProps>(
}, 100);
};
+ const handleClose = () => {
+ setView('options');
+ closeMenu();
+ };
+
+ const onEmojiSelect = (key, shortcode) => {
+ onReactionToggle(mEvent.getId(), key, shortcode);
+ handleClose();
+ };
+
const tagColor = powerLevelTag?.color
? accessibleTagColors?.get(powerLevelTag.color)
: undefined;
From e3375f62413a70127c8524bd92e8c487d14e5b54 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 20:06:45 -0500
Subject: [PATCH 12/73] pass moved params thru for component
---
src/app/features/room/message/Message.tsx | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index 9aa054ef..0d21f9dc 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -916,6 +916,14 @@ export const Message = as<'div', MessageProps>(
onReplyClick={onReplyClick}
onEditId={onEditId}
onActiveStateChange={setDesktopOptionsActive}
+ handleAddReactions={handleAddReactions}
+ closeMenu={closeMenu}
+ emojiBoardAnchor={emojiBoardAnchor}
+ menuAnchor={menuAnchor}
+ handleOpenEmojiBoard={handleOpenEmojiBoard}
+ setEmojiBoardAnchor={setEmojiBoardAnchor}
+ setMenuAnchor={setMenuAnchor}
+ handleOpenMenu={handleOpenMenu}
/>
)}
{messageLayout === MessageLayout.Compact && (
From b25011e0e5b1da23803996464c3f0f4029737508 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 20:07:18 -0500
Subject: [PATCH 13/73] restructure to use Emojiboard here or our
MessageDropdownMenu
---
src/app/features/room/message/Message.tsx | 68 +++++++++++++++++------
1 file changed, 51 insertions(+), 17 deletions(-)
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index 0d21f9dc..148b75aa 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -947,23 +947,57 @@ export const Message = as<'div', MessageProps>(
{isMobile && (
setMobileSheetOpen(false)} isOpen={isMobileSheetOpen}>
- setMobileSheetOpen(false)}
- mEvent={mEvent}
- eventId={mEvent.getId()}
- room={room}
- mx={mx}
- relations={relations}
- canSendReaction={canSendReaction}
- canEdit={canEditEvent(mx, mEvent)}
- canDelete={canDelete || mEvent?.getSender() === mx.getUserId()}
- canPinEvent={canPinEvent}
- hideReadReceipts={hideReadReceipts}
- onReactionToggle={onReactionToggle}
- onReplyClick={onReplyClick}
- onEditId={onEditId}
- handleAddReactions={null}
- />
+ {view === 'options' ? (
+ {
+ closeMenu();
+ setMobileSheetOpen(false);
+ }}
+ mEvent={mEvent}
+ eventId={mEvent.getId()}
+ room={room}
+ mx={mx}
+ relations={relations}
+ canSendReaction={canSendReaction}
+ canEdit={canEditEvent(mx, mEvent)}
+ canDelete={canDelete || mEvent?.getSender() === mx.getUserId()}
+ canPinEvent={canPinEvent}
+ hideReadReceipts={hideReadReceipts}
+ onReactionToggle={onReactionToggle}
+ onReplyClick={onReplyClick}
+ onEditId={onEditId}
+ handleAddReactions={() => setView('emoji')}
+ />
+ ) : (
+
+
+ setView('options')}>
+
+
+
+ Add Reaction
+
+
+ {
+ onReactionToggle(mEvent.getId(), key);
+ setEmojiBoardAnchor(undefined);
+ closeMenu();
+ setMobileSheetOpen(false);
+ }}
+ onCustomEmojiSelect={(mxc, shortcode) => {
+ onReactionToggle(mEvent.getId(), mxc, shortcode);
+ setEmojiBoardAnchor(undefined);
+ closeMenu();
+ setMobileSheetOpen(false);
+ }}
+ requestClose={() => setEmojiBoardAnchor(undefined)}
+ />
+
+ )}
)}
>
From 5b31ce62886a09196872f965cbc2ba775c7f174d Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 22:03:08 -0500
Subject: [PATCH 14/73] Add imports from sheets file to merge in
---
src/app/features/room/MessageOptionsMenu.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/app/features/room/MessageOptionsMenu.tsx b/src/app/features/room/MessageOptionsMenu.tsx
index 52c44cde..c6dfe23e 100644
--- a/src/app/features/room/MessageOptionsMenu.tsx
+++ b/src/app/features/room/MessageOptionsMenu.tsx
@@ -1,7 +1,9 @@
import { Box, Icon, IconButton, Icons, Line, Menu, MenuItem, PopOut, RectCords, Text } from 'folds';
-import React, { MouseEventHandler, useEffect, useState } from 'react';
+import React, { MouseEventHandler, useEffect } from 'react';
import FocusTrap from 'focus-trap-react';
+import classNames from 'classnames';
+import { MatrixClient, MatrixEvent, Relations, Room } from 'matrix-js-sdk';
import { EmojiBoard } from '../../components/emoji-board';
import { stopPropagation } from '../../utils/keyboard';
import * as css from './message/styles.css';
From 5466e440a99e1f3d304f12809096dc691f7d73b1 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 22:03:15 -0500
Subject: [PATCH 15/73] Add Base props
---
src/app/features/room/MessageOptionsMenu.tsx | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/src/app/features/room/MessageOptionsMenu.tsx b/src/app/features/room/MessageOptionsMenu.tsx
index c6dfe23e..9b7fdd2c 100644
--- a/src/app/features/room/MessageOptionsMenu.tsx
+++ b/src/app/features/room/MessageOptionsMenu.tsx
@@ -19,6 +19,24 @@ import {
MessageSourceCodeItem,
} from './message/Message';
+type BaseOptionProps = {
+ mEvent: MatrixEvent;
+ room: Room;
+ mx: MatrixClient;
+ relations: Relations | undefined;
+ eventId: string;
+ canSendReaction: boolean | undefined;
+ canEdit: boolean | undefined;
+ canDelete: boolean | undefined;
+ canPinEvent: boolean | undefined;
+ hideReadReceipts: boolean | undefined;
+ onReactionToggle: (targetEventId: string, key: string, shortcode?: string | undefined) => void;
+ onReplyClick: MouseEventHandler;
+ onEditId: ((eventId?: string | undefined) => void) | undefined;
+ handleAddReactions: MouseEventHandler;
+ closeMenu: () => void;
+};
+
export function MessageDropdownMenu({
mEvent,
room,
From c8319b440268c685d59081cda1a77be5f783c368 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 22:03:26 -0500
Subject: [PATCH 16/73] Add ExtendedProps
---
src/app/features/room/MessageOptionsMenu.tsx | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/src/app/features/room/MessageOptionsMenu.tsx b/src/app/features/room/MessageOptionsMenu.tsx
index 9b7fdd2c..267b824b 100644
--- a/src/app/features/room/MessageOptionsMenu.tsx
+++ b/src/app/features/room/MessageOptionsMenu.tsx
@@ -135,6 +135,17 @@ export function MessageDropdownMenu({
);
}
+type ExtendedOptionsProps = BaseOptionProps & {
+ imagePackRooms: Room[] | undefined;
+ onActiveStateChange: React.Dispatch>;
+ menuAnchor: RectCords | undefined;
+ emojiBoardAnchor: RectCords | undefined;
+ handleOpenEmojiBoard: MouseEventHandler;
+ handleOpenMenu: MouseEventHandler;
+ setMenuAnchor: React.Dispatch>;
+ setEmojiBoardAnchor: React.Dispatch>;
+};
+
export function MessageOptionsMenu({
mEvent,
room,
From 511a16eedc6ca2f704fb5bf7623ad3d0a8ea5695 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 22:03:54 -0500
Subject: [PATCH 17/73] Remove comment from when we embedded the anchors
---
src/app/features/room/MessageOptionsMenu.tsx | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/src/app/features/room/MessageOptionsMenu.tsx b/src/app/features/room/MessageOptionsMenu.tsx
index 267b824b..013903a6 100644
--- a/src/app/features/room/MessageOptionsMenu.tsx
+++ b/src/app/features/room/MessageOptionsMenu.tsx
@@ -169,10 +169,7 @@ export function MessageOptionsMenu({
handleAddReactions,
setMenuAnchor,
setEmojiBoardAnchor,
-}) {
- // const [menuAnchor, setMenuAnchor] = useState();
- // const [emojiBoardAnchor, setEmojiBoardAnchor] = useState();
-
+}: ExtendedOptionsProps) {
useEffect(() => {
onActiveStateChange?.(!!menuAnchor || !!emojiBoardAnchor);
}, [emojiBoardAnchor, menuAnchor, onActiveStateChange]);
From 460d77d40c4c0012a0b14aeb947ed1320294c8b5 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 22:04:06 -0500
Subject: [PATCH 18/73] Add bottom sheet menu in
---
src/app/features/room/MessageOptionsMenu.tsx | 54 ++++++++++++++++++++
1 file changed, 54 insertions(+)
diff --git a/src/app/features/room/MessageOptionsMenu.tsx b/src/app/features/room/MessageOptionsMenu.tsx
index 013903a6..885d7b89 100644
--- a/src/app/features/room/MessageOptionsMenu.tsx
+++ b/src/app/features/room/MessageOptionsMenu.tsx
@@ -285,3 +285,57 @@ export function MessageOptionsMenu({
);
}
+
+export function BottomSheetMenu({
+ isOpen,
+ onClose,
+ children,
+}: {
+ isOpen: boolean;
+ onClose: () => void;
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
+
+export function MenuItemButton({
+ label,
+ onClick,
+ destructive = false,
+}: {
+ label: string;
+ onClick: () => void;
+ destructive?: boolean;
+}) {
+ return (
+
+
+
+ {label}
+
+
+ );
+}
From dc296d379f78f21df8ce6b74c7bd3c89b411c252 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 22:04:47 -0500
Subject: [PATCH 19/73] Add prop usage
---
src/app/features/room/MessageOptionsMenu.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/features/room/MessageOptionsMenu.tsx b/src/app/features/room/MessageOptionsMenu.tsx
index 885d7b89..149fe303 100644
--- a/src/app/features/room/MessageOptionsMenu.tsx
+++ b/src/app/features/room/MessageOptionsMenu.tsx
@@ -53,7 +53,7 @@ export function MessageDropdownMenu({
onEditId,
handleAddReactions,
closeMenu,
-}) {
+}: BaseOptionProps) {
return (
{canSendReaction && (
From 05b481187151af1479db682c518610076b8884b3 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 22:04:59 -0500
Subject: [PATCH 20/73] Remove mobile click menu file
---
src/app/features/room/MobileClickMenu.tsx | 61 -----------------------
1 file changed, 61 deletions(-)
delete mode 100644 src/app/features/room/MobileClickMenu.tsx
diff --git a/src/app/features/room/MobileClickMenu.tsx b/src/app/features/room/MobileClickMenu.tsx
deleted file mode 100644
index 48489079..00000000
--- a/src/app/features/room/MobileClickMenu.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-/* eslint-disable react/destructuring-assignment */
-import React from 'react';
-import { Box, Icon, Icons, Text } from 'folds';
-import classNames from 'classnames';
-import * as css from './RoomTimeline.css';
-
-export function BottomSheetMenu({
- isOpen,
- onClose,
- children,
-}: {
- isOpen: boolean;
- onClose: () => void;
- children: React.ReactNode;
-}) {
- return (
-
- );
-}
-
-export function MenuItemButton({
- icon,
- label,
- onClick,
- destructive = false,
-}: {
- icon: string;
- label: string;
- onClick: () => void;
- destructive?: boolean;
-}) {
- return (
-
-
-
- {label}
-
-
- );
-}
From 2b2332955860c88f68a82117fd17c769ec493c95 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 22:05:11 -0500
Subject: [PATCH 21/73] remove wrongful addition
---
.../components/message/layout/layout.css.ts | 68 -------------------
1 file changed, 68 deletions(-)
diff --git a/src/app/components/message/layout/layout.css.ts b/src/app/components/message/layout/layout.css.ts
index a61c7f8d..73631129 100644
--- a/src/app/components/message/layout/layout.css.ts
+++ b/src/app/components/message/layout/layout.css.ts
@@ -197,71 +197,3 @@ export const MessageTextBody = recipe({
});
export type MessageTextBodyVariants = RecipeVariants;
-
-export const menuBackdrop = style({
- userSelect: 'none',
- position: 'fixed',
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
- transition: 'opacity 0.3s ease-in-out',
- opacity: 0,
-});
-
-export const menuBackdropOpen = style({
- opacity: 1,
-});
-
-export const menuSheet = style({
- userSelect: 'none',
- position: 'fixed',
- left: 0,
- right: 0,
- bottom: 0,
- backgroundColor: color.Background.Container,
- borderTopLeftRadius: config.radii.R400,
- borderTopRightRadius: config.radii.R400,
- padding: config.space.S500,
- transform: 'translateY(100%)',
- transition: 'transform 0.3s ease-out',
- boxShadow: '0 -2px 10px rgba(0,0,0,0.1)',
-});
-
-export const menuSheetOpen = style({
- transform: 'translateY(0)',
-});
-
-export const menuItem = style({
- userSelect: 'none',
- width: '100%',
- background: 'none',
- border: 'none',
- padding: `${config.space.S300} ${config.space.S100}`,
- textAlign: 'left',
- cursor: 'pointer',
- borderRadius: config.radii.R300,
- color: color.Primary.ContainerActive,
-
- outline: 'none',
-
- WebkitTapHighlightColor: 'transparent',
-
- WebkitUserSelect: 'none',
- MozUserSelect: 'none',
- msUserSelect: 'none',
-
- selectors: {
- '&:hover': {
- backgroundColor: color.Background.ContainerHover,
- },
- '&:focus-visible': {
- backgroundColor: color.Background.ContainerHover,
- },
- },
-});
-
-export const menuItemDestructive = style({
- color: color.Critical.ContainerActive,
-});
From d26a901ac9f913eff311011f8aeab108fbab674f Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 22:05:25 -0500
Subject: [PATCH 22/73] add mobilesheet styling here
---
src/app/features/room/message/styles.css.ts | 70 ++++++++++++++++++++-
1 file changed, 69 insertions(+), 1 deletion(-)
diff --git a/src/app/features/room/message/styles.css.ts b/src/app/features/room/message/styles.css.ts
index b87cb505..d87ea501 100644
--- a/src/app/features/room/message/styles.css.ts
+++ b/src/app/features/room/message/styles.css.ts
@@ -1,5 +1,5 @@
import { style } from '@vanilla-extract/css';
-import { DefaultReset, config, toRem } from 'folds';
+import { DefaultReset, color, config, toRem } from 'folds';
export const MessageBase = style({
position: 'relative',
@@ -48,3 +48,71 @@ export const ReactionsContainer = style({
export const ReactionsTooltipText = style({
wordBreak: 'break-word',
});
+
+export const menuBackdrop = style({
+ userSelect: 'none',
+ position: 'fixed',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+ transition: 'opacity 0.3s ease-in-out',
+ opacity: 0,
+});
+
+export const menuBackdropOpen = style({
+ opacity: 1,
+});
+
+export const menuSheet = style({
+ userSelect: 'none',
+ position: 'fixed',
+ left: 0,
+ right: 0,
+ bottom: 0,
+ backgroundColor: color.Background.Container,
+ borderTopLeftRadius: config.radii.R400,
+ borderTopRightRadius: config.radii.R400,
+ padding: config.space.S500,
+ transform: 'translateY(100%)',
+ transition: 'transform 0.3s ease-out',
+ boxShadow: '0 -2px 10px rgba(0,0,0,0.1)',
+});
+
+export const menuSheetOpen = style({
+ transform: 'translateY(0)',
+});
+
+export const menuItem = style({
+ userSelect: 'none',
+ width: '100%',
+ background: 'none',
+ border: 'none',
+ padding: `${config.space.S300} ${config.space.S100}`,
+ textAlign: 'left',
+ cursor: 'pointer',
+ borderRadius: config.radii.R300,
+ color: color.Primary.ContainerActive,
+
+ outline: 'none',
+
+ WebkitTapHighlightColor: 'transparent',
+
+ WebkitUserSelect: 'none',
+ MozUserSelect: 'none',
+ msUserSelect: 'none',
+
+ selectors: {
+ '&:hover': {
+ backgroundColor: color.Background.ContainerHover,
+ },
+ '&:focus-visible': {
+ backgroundColor: color.Background.ContainerHover,
+ },
+ },
+});
+
+export const menuItemDestructive = style({
+ color: color.Critical.ContainerActive,
+});
From c8f490adfb241f87df8d573e8fbc0e04a27edd10 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 22:20:04 -0500
Subject: [PATCH 23/73] Add draggablemessage
---
.../room/message/DraggableMessage.tsx | 100 ++++++++++++++++++
1 file changed, 100 insertions(+)
create mode 100644 src/app/features/room/message/DraggableMessage.tsx
diff --git a/src/app/features/room/message/DraggableMessage.tsx b/src/app/features/room/message/DraggableMessage.tsx
new file mode 100644
index 00000000..4476d5c7
--- /dev/null
+++ b/src/app/features/room/message/DraggableMessage.tsx
@@ -0,0 +1,100 @@
+import React from 'react';
+import { useSpring, animated } from '@react-spring/web';
+import { useDrag } from 'react-use-gesture';
+import { Icon, Icons } from 'folds';
+
+const DraggableMessageStyles = {
+ container: {
+ position: 'relative',
+ overflow: 'hidden',
+ width: '100%',
+ },
+ replyIconContainer: {
+ position: 'absolute',
+ top: 0,
+ bottom: 0,
+ right: 0,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: '80px',
+ },
+ messageContent: {
+ position: 'relative',
+ touchAction: 'pan-y',
+ backgroundColor: 'var(--folds-color-Background-Main)',
+ width: '100%',
+ },
+};
+
+export function DraggableMessage({ children, onReply }) {
+ const REPLY_THRESHOLD = 80;
+
+ const [{ x, iconScale }, api] = useSpring(() => ({
+ x: 0,
+ iconScale: 0.5,
+ config: { tension: 250, friction: 25 },
+ }));
+
+ const bind = useDrag(
+ ({ down, movement: [mx], direction: [xDir], vxvy: [vx], cancel }) => {
+ if (!down && Math.abs(xDir) < 0.7) {
+ cancel();
+ }
+
+ const xTarget = down ? Math.min(0, mx) : 0;
+ let scaleTarget = down
+ ? 0.5 + Math.min(Math.abs(mx), REPLY_THRESHOLD) / (REPLY_THRESHOLD * 2)
+ : 0.5;
+
+ if (mx < -REPLY_THRESHOLD) {
+ onReply();
+ }
+
+ /*
+ if (!down) {
+ if (mx < -REPLY_THRESHOLD && vx < -0.5) {
+ onReply();
+ }
+ } else {
+ if (mx < -REPLY_THRESHOLD) {
+ scaleTarget = 1;
+ }
+ }
+*/
+ api.start({
+ x: xTarget,
+ iconScale: scaleTarget,
+ });
+ },
+ {
+ axis: 'x',
+ filterTaps: true,
+ threshold: 10,
+ }
+ );
+
+ return (
+
+
`scale(${s})`),
+ opacity: iconScale.to((s) => (s - 0.5) * 2),
+ }}
+ >
+
+
+
+
+ {children}
+
+
+ );
+}
From e8205439518b43113034f0acabb2aa98d22a74fe Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 23:05:06 -0500
Subject: [PATCH 24/73] Add edit handling too (not perfect, but useful later)
---
.../room/message/DraggableMessage.tsx | 121 ++++++++++++------
1 file changed, 79 insertions(+), 42 deletions(-)
diff --git a/src/app/features/room/message/DraggableMessage.tsx b/src/app/features/room/message/DraggableMessage.tsx
index 4476d5c7..f172afe7 100644
--- a/src/app/features/room/message/DraggableMessage.tsx
+++ b/src/app/features/room/message/DraggableMessage.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import { useSpring, animated } from '@react-spring/web';
import { useDrag } from 'react-use-gesture';
import { Icon, Icons } from 'folds';
+import { MatrixClient, MatrixEvent } from 'matrix-js-sdk';
const DraggableMessageStyles = {
container: {
@@ -9,7 +10,7 @@ const DraggableMessageStyles = {
overflow: 'hidden',
width: '100%',
},
- replyIconContainer: {
+ iconContainer: {
position: 'absolute',
top: 0,
bottom: 0,
@@ -17,7 +18,7 @@ const DraggableMessageStyles = {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
- width: '80px',
+ width: '150px',
},
messageContent: {
position: 'relative',
@@ -25,46 +26,76 @@ const DraggableMessageStyles = {
backgroundColor: 'var(--folds-color-Background-Main)',
width: '100%',
},
+ icon: {
+ position: 'absolute',
+ },
};
-export function DraggableMessage({ children, onReply }) {
+export function DraggableMessage({
+ children,
+ onReply,
+ onEdit,
+ event,
+ mx,
+}: {
+ children: React.ReactNode;
+ onReply: () => void;
+ onEdit: () => void;
+ event: MatrixEvent;
+ mx: MatrixClient;
+}) {
+ const canEdit = mx.getUserId() === event.getSender();
const REPLY_THRESHOLD = 80;
+ const EDIT_THRESHOLD = canEdit ? 250 : Infinity;
- const [{ x, iconScale }, api] = useSpring(() => ({
+ const [{ x, replyOpacity, editOpacity, iconScale }, api] = useSpring(() => ({
x: 0,
+ replyOpacity: 0,
+ editOpacity: 0,
iconScale: 0.5,
config: { tension: 250, friction: 25 },
}));
const bind = useDrag(
- ({ down, movement: [mx], direction: [xDir], vxvy: [vx], cancel }) => {
- if (!down && Math.abs(xDir) < 0.7) {
- cancel();
- }
-
- const xTarget = down ? Math.min(0, mx) : 0;
- let scaleTarget = down
- ? 0.5 + Math.min(Math.abs(mx), REPLY_THRESHOLD) / (REPLY_THRESHOLD * 2)
- : 0.5;
-
- if (mx < -REPLY_THRESHOLD) {
- onReply();
- }
-
- /*
+ ({ down, movement: [x], vxvy: [vx] }) => {
if (!down) {
- if (mx < -REPLY_THRESHOLD && vx < -0.5) {
+ const finalDistance = Math.abs(x);
+
+ if (finalDistance > EDIT_THRESHOLD) {
+ onEdit();
+ } else if (finalDistance > REPLY_THRESHOLD) {
onReply();
}
- } else {
- if (mx < -REPLY_THRESHOLD) {
- scaleTarget = 1;
- }
}
-*/
+
+ const xTarget = down ? Math.min(0, x) : 0;
+ const distance = Math.abs(xTarget);
+
+ let newReplyOpacity = 0;
+ let newEditOpacity = 0;
+ let newScale = 1.0;
+
+ if (canEdit && distance > REPLY_THRESHOLD) {
+ newReplyOpacity = 0;
+ newEditOpacity = 1;
+ if (down && distance > EDIT_THRESHOLD) {
+ newScale = 1.1;
+ }
+ } else {
+ newReplyOpacity = 1;
+ newEditOpacity = 0;
+ newScale = 0.5 + (distance / REPLY_THRESHOLD) * 0.5;
+ }
+
+ if (distance < 5) {
+ newReplyOpacity = 0;
+ }
+
api.start({
x: xTarget,
- iconScale: scaleTarget,
+ replyOpacity: newReplyOpacity,
+ editOpacity: newEditOpacity,
+ iconScale: newScale,
});
},
{
@@ -76,23 +107,29 @@ export function DraggableMessage({ children, onReply }) {
return (
-
`scale(${s})`),
- opacity: iconScale.to((s) => (s - 0.5) * 2),
- }}
- >
-
-
+
+
`scale(${s})`),
+ }}
+ >
+
+
-
+ `scale(${s})`),
+ }}
+ >
+
+
+
+
+
{children}
From 41b036550fc2379a9302cb721fe7ee4262f0c03f Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 23:05:16 -0500
Subject: [PATCH 25/73] Remove event id refs
---
src/app/features/room/MessageOptionsMenu.tsx | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/src/app/features/room/MessageOptionsMenu.tsx b/src/app/features/room/MessageOptionsMenu.tsx
index 149fe303..98c4f680 100644
--- a/src/app/features/room/MessageOptionsMenu.tsx
+++ b/src/app/features/room/MessageOptionsMenu.tsx
@@ -24,7 +24,6 @@ type BaseOptionProps = {
room: Room;
mx: MatrixClient;
relations: Relations | undefined;
- eventId: string;
canSendReaction: boolean | undefined;
canEdit: boolean | undefined;
canDelete: boolean | undefined;
@@ -42,7 +41,6 @@ export function MessageDropdownMenu({
room,
mx,
relations,
- eventId,
canSendReaction,
canEdit,
canDelete,
@@ -59,7 +57,7 @@ export function MessageDropdownMenu({
{canSendReaction && (
{
- onReactionToggle(eventId, key, shortcode);
+ onReactionToggle(mEvent.getId() ?? '', key, shortcode);
closeMenu();
}}
/>
@@ -84,7 +82,7 @@ export function MessageDropdownMenu({
size="300"
after={}
radii="300"
- data-event-id={eventId}
+ data-event-id={mEvent.getId()}
onClick={(evt) => {
onReplyClick(evt);
closeMenu();
@@ -99,9 +97,9 @@ export function MessageDropdownMenu({
size="300"
after={}
radii="300"
- data-event-id={eventId}
+ data-event-id={mEvent.getId()}
onClick={() => {
- onEditId(eventId);
+ onEditId(mEvent.getId());
closeMenu();
}}
>
@@ -111,7 +109,7 @@ export function MessageDropdownMenu({
)}
{!hideReadReceipts && (
-
+
)}
@@ -255,7 +253,6 @@ export function MessageOptionsMenu({
room={room}
mx={mx}
relations={relations}
- eventId={eventId}
canSendReaction={canSendReaction}
canEdit={canEdit}
canDelete={canDelete}
From 7b84264b2639c2601c2a77543ee71910c22b7b45 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 23:06:20 -0500
Subject: [PATCH 26/73] Add draggable handling
---
src/app/features/room/message/Message.tsx | 114 +++++++++++++++++-----
1 file changed, 92 insertions(+), 22 deletions(-)
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index 148b75aa..a2f169f1 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -80,9 +80,9 @@ import { StateEvent } from '../../../../types/matrix/room';
import { getTagIconSrc, PowerLevelTag } from '../../../hooks/usePowerLevelTags';
import { PowerIcon } from '../../../components/power';
import colorMXID from '../../../../util/colorMXID';
-import { MessageDropdownMenu, MessageOptionsMenu } from '../MessageOptionsMenu';
-import { BottomSheetMenu } from '../MobileClickMenu';
+import { MessageDropdownMenu, MessageOptionsMenu, BottomSheetMenu } from '../MessageOptionsMenu';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+import { DraggableMessage } from './DraggableMessage';
export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void;
@@ -719,13 +719,13 @@ export const Message = as<'div', MessageProps>(
const useAuthentication = useMediaAuthentication();
const senderId = mEvent.getSender() ?? '';
const { hoverProps, isHovered } = useHover({});
- const { focusWithinProps, isFocusWithin } = useFocusWithin({});
+ const { focusWithinProps } = useFocusWithin({});
const [menuAnchor, setMenuAnchor] = useState();
const [emojiBoardAnchor, setEmojiBoardAnchor] = useState();
const screenSize = useScreenSizeContext();
const isMobile = screenSize === ScreenSize.Mobile;
const [isDesktopOptionsActive, setDesktopOptionsActive] = useState(false);
- const showDesktopOptions = !isMobile && (isHovered || isFocusWithin || isDesktopOptionsActive);
+ //const showDesktopOptions = !isMobile && (isHovered || isFocusWithin || isDesktopOptionsActive);
const [isMobileSheetOpen, setMobileSheetOpen] = useState(false);
const senderDisplayName =
getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId;
@@ -764,6 +764,10 @@ export const Message = as<'div', MessageProps>(
handleClose();
};
+ // TODO: Remove this and clean it up later...
+ const button = document.createElement('button');
+ button.setAttribute('data-event-id', mEvent.getId());
+
const tagColor = powerLevelTag?.color
? accessibleTagColors?.get(powerLevelTag.color)
: undefined;
@@ -926,23 +930,90 @@ export const Message = as<'div', MessageProps>(
handleOpenMenu={handleOpenMenu}
/>
)}
- {messageLayout === MessageLayout.Compact && (
-
- {msgContentJSX}
-
- )}
- {messageLayout === MessageLayout.Bubble && (
-
- {headerJSX}
- {msgContentJSX}
-
- )}
- {messageLayout !== MessageLayout.Compact && messageLayout !== MessageLayout.Bubble && (
-
- {headerJSX}
- {msgContentJSX}
-
- )}
+ {messageLayout === MessageLayout.Compact &&
+ (!isMobile ? (
+
+ {msgContentJSX}
+
+ ) : (
+ {
+ const mockTargetElement = document.createElement('button');
+ mockTargetElement.setAttribute('data-event-id', mEvent.getId());
+ const mockEvent = {
+ currentTarget: mockTargetElement,
+ };
+
+ onReplyClick(mockEvent);
+ }}
+ onEdit={() => {
+ onEditId(mEvent.getId());
+ }}
+ mx={mx}
+ >
+
+ {msgContentJSX}
+
+
+ ))}
+ {messageLayout === MessageLayout.Bubble &&
+ (!isMobile ? (
+
+ {msgContentJSX}
+
+ ) : (
+ {
+ const mockTargetElement = document.createElement('button');
+ mockTargetElement.setAttribute('data-event-id', mEvent.getId());
+ const mockEvent = {
+ currentTarget: mockTargetElement,
+ };
+
+ onReplyClick(mockEvent);
+ }}
+ onEdit={() => {
+ onEditId(mEvent.getId());
+ }}
+ mx={mx}
+ >
+
+ {msgContentJSX}
+
+
+ ))}
+ {messageLayout !== MessageLayout.Compact &&
+ messageLayout !== MessageLayout.Bubble &&
+ (!isMobile ? (
+
+ {headerJSX}
+ {msgContentJSX}
+
+ ) : (
+ {
+ const mockTargetElement = document.createElement('button');
+ mockTargetElement.setAttribute('data-event-id', mEvent.getId());
+ const mockEvent = {
+ currentTarget: mockTargetElement,
+ };
+
+ onReplyClick(mockEvent);
+ }}
+ onEdit={() => {
+ onEditId(mEvent.getId());
+ }}
+ mx={mx}
+ >
+
+ {headerJSX}
+ {msgContentJSX}
+
+
+ ))}
{isMobile && (
@@ -954,7 +1025,6 @@ export const Message = as<'div', MessageProps>(
setMobileSheetOpen(false);
}}
mEvent={mEvent}
- eventId={mEvent.getId()}
room={room}
mx={mx}
relations={relations}
From c4d0a7a8209b91b0df4901e9f85e8e8cbdf70a40 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 23:20:09 -0500
Subject: [PATCH 27/73] Fix ref issue causing the menu to remain open unless an
option is selected
---
src/app/features/room/MessageOptionsMenu.tsx | 45 +++++++++++---------
1 file changed, 24 insertions(+), 21 deletions(-)
diff --git a/src/app/features/room/MessageOptionsMenu.tsx b/src/app/features/room/MessageOptionsMenu.tsx
index 98c4f680..f5372485 100644
--- a/src/app/features/room/MessageOptionsMenu.tsx
+++ b/src/app/features/room/MessageOptionsMenu.tsx
@@ -1,5 +1,5 @@
import { Box, Icon, IconButton, Icons, Line, Menu, MenuItem, PopOut, RectCords, Text } from 'folds';
-import React, { MouseEventHandler, useEffect } from 'react';
+import React, { forwardRef, MouseEventHandler, useEffect } from 'react';
import FocusTrap from 'focus-trap-react';
import classNames from 'classnames';
@@ -36,24 +36,27 @@ type BaseOptionProps = {
closeMenu: () => void;
};
-export function MessageDropdownMenu({
- mEvent,
- room,
- mx,
- relations,
- canSendReaction,
- canEdit,
- canDelete,
- canPinEvent,
- hideReadReceipts,
- onReactionToggle,
- onReplyClick,
- onEditId,
- handleAddReactions,
- closeMenu,
-}: BaseOptionProps) {
- return (
-
+export const MessageDropdownMenu = forwardRef(
+ (
+ {
+ mEvent,
+ room,
+ mx,
+ relations,
+ canSendReaction,
+ canEdit,
+ canDelete,
+ canPinEvent,
+ hideReadReceipts,
+ onReactionToggle,
+ onReplyClick,
+ onEditId,
+ handleAddReactions,
+ closeMenu,
+ },
+ ref
+ ) => (
+
{canSendReaction && (
{
@@ -130,8 +133,8 @@ export function MessageDropdownMenu({
>
)}
- );
-}
+ )
+);
type ExtendedOptionsProps = BaseOptionProps & {
imagePackRooms: Room[] | undefined;
From 8396771e3fc84b561288ebb3037c6760b3d1fb75 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 23:21:28 -0500
Subject: [PATCH 28/73] remove comment
---
src/app/features/room/MessageOptionsMenu.tsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/app/features/room/MessageOptionsMenu.tsx b/src/app/features/room/MessageOptionsMenu.tsx
index f5372485..c4e145c6 100644
--- a/src/app/features/room/MessageOptionsMenu.tsx
+++ b/src/app/features/room/MessageOptionsMenu.tsx
@@ -118,7 +118,6 @@ export const MessageDropdownMenu = forwardRef(
{canPinEvent && }
- {/* Redact and Report actions */}
{((!mEvent.isRedacted() && canDelete) || mEvent.getSender() !== mx.getUserId()) && (
<>
From 1fe7f014d6577f02791214ccc6f030f436a882a0 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 23:27:42 -0500
Subject: [PATCH 29/73] bump package.json to include some new react pieces
---
package.json | 2 ++
1 file changed, 2 insertions(+)
diff --git a/package.json b/package.json
index 7622dbc8..0083b8de 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3",
"@fontsource/inter": "4.5.14",
+ "@react-spring/web": "10.0.1",
"@tanstack/react-query": "5.24.1",
"@tanstack/react-query-devtools": "5.24.1",
"@tanstack/react-virtual": "3.2.0",
@@ -74,6 +75,7 @@
"react-modal": "3.16.1",
"react-range": "1.8.14",
"react-router-dom": "6.20.0",
+ "react-use-gesture": "9.1.3",
"sanitize-html": "2.12.1",
"slate": "0.112.0",
"slate-dom": "0.112.2",
From c9fd33b2dc9a5619fc634f87cd9d97733c0d122e Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 15 Jun 2025 23:27:52 -0500
Subject: [PATCH 30/73] update package lock
---
package-lock.json | 84 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 84 insertions(+)
diff --git a/package-lock.json b/package-lock.json
index 3d2e8d05..d05ca387 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3",
"@fontsource/inter": "4.5.14",
+ "@react-spring/web": "10.0.1",
"@tanstack/react-query": "5.24.1",
"@tanstack/react-query-devtools": "5.24.1",
"@tanstack/react-virtual": "3.2.0",
@@ -63,6 +64,7 @@
"react-modal": "3.16.1",
"react-range": "1.8.14",
"react-router-dom": "6.20.0",
+ "react-use-gesture": "9.1.3",
"sanitize-html": "2.12.1",
"slate": "0.112.0",
"slate-dom": "0.112.2",
@@ -3098,6 +3100,78 @@
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
}
},
+ "node_modules/@react-spring/animated": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-10.0.1.tgz",
+ "integrity": "sha512-BGL3hA66Y8Qm3KmRZUlfG/mFbDPYajgil2/jOP0VXf2+o2WPVmcDps/eEgdDqgf5Pv9eBbyj7LschLMuSjlW3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-spring/shared": "~10.0.1",
+ "@react-spring/types": "~10.0.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@react-spring/core": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-10.0.1.tgz",
+ "integrity": "sha512-KaMMsN1qHuVTsFpg/5ajAVye7OEqhYbCq0g4aKM9bnSZlDBBYpO7Uf+9eixyXN8YEbF+YXaYj9eoWDs+npZ+sA==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-spring/animated": "~10.0.1",
+ "@react-spring/shared": "~10.0.1",
+ "@react-spring/types": "~10.0.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-spring/donate"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@react-spring/rafz": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-10.0.1.tgz",
+ "integrity": "sha512-UrzG/d6Is+9i0aCAjsjWRqIlFFiC4lFqFHrH63zK935z2YDU95TOFio4VKGISJ5SG0xq4ULy7c1V3KU+XvL+Yg==",
+ "license": "MIT"
+ },
+ "node_modules/@react-spring/shared": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-10.0.1.tgz",
+ "integrity": "sha512-KR2tmjDShPruI/GGPfAZOOLvDgkhFseabjvxzZFFggJMPkyICLjO0J6mCIoGtdJSuHywZyc4Mmlgi+C88lS00g==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-spring/rafz": "~10.0.1",
+ "@react-spring/types": "~10.0.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@react-spring/types": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-10.0.1.tgz",
+ "integrity": "sha512-Fk1wYVAKL+ZTYK+4YFDpHf3Slsy59pfFFvnnTfRjQQFGlyIo4VejPtDs3CbDiuBjM135YztRyZjIH2VbycB+ZQ==",
+ "license": "MIT"
+ },
+ "node_modules/@react-spring/web": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-10.0.1.tgz",
+ "integrity": "sha512-FgQk02OqFrYyJBTTnBTWAU0WPzkHkKXauc6aeexcvATvLapUxwnfGuLlsLYF8BYjEVfkivPT04ziAue6zyRBtQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@react-spring/animated": "~10.0.1",
+ "@react-spring/core": "~10.0.1",
+ "@react-spring/shared": "~10.0.1",
+ "@react-spring/types": "~10.0.1"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/@react-stately/calendar": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/@react-stately/calendar/-/calendar-3.7.0.tgz",
@@ -9848,6 +9922,16 @@
"react-dom": ">=16.8"
}
},
+ "node_modules/react-use-gesture": {
+ "version": "9.1.3",
+ "resolved": "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-9.1.3.tgz",
+ "integrity": "sha512-CdqA2SmS/fj3kkS2W8ZU8wjTbVBAIwDWaRprX7OKaj7HlGwBasGEFggmk5qNklknqk9zK/h8D355bEJFTpqEMg==",
+ "deprecated": "This package is no longer maintained. Please use @use-gesture/react instead",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">= 16.8.0"
+ }
+ },
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
From fb73ebbb3b86fbb8737b6ed098eff41c9a835771 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 03:33:15 -0500
Subject: [PATCH 31/73] Started on adding the context menu for rooms in the nav
on mobile
---
src/app/features/room-nav/RoomNavItem.tsx | 214 ++++++++++++++++------
1 file changed, 159 insertions(+), 55 deletions(-)
diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx
index bdb81418..a9bec223 100644
--- a/src/app/features/room-nav/RoomNavItem.tsx
+++ b/src/app/features/room-nav/RoomNavItem.tsx
@@ -19,6 +19,8 @@ import {
} from 'folds';
import { useFocusWithin, useHover } from 'react-aria';
import FocusTrap from 'focus-trap-react';
+import { useParams } from 'react-router-dom';
+import { useLongPress } from 'use-long-press';
import { NavItem, NavItemContent, NavItemOptions, NavLink } from '../../components/nav';
import { UnreadBadge, UnreadBadgeCenter } from '../../components/unread-badge';
import { RoomAvatar, RoomIcon } from '../../components/room-avatar';
@@ -49,6 +51,10 @@ import {
RoomNotificationMode,
} from '../../hooks/useRoomsNotificationPreferences';
import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationSwitcher';
+import { useCallState } from '../../pages/client/call/CallProvider';
+import { useRoomNavigate } from '../../hooks/useRoomNavigate';
+import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
+import { BottomSheetMenu } from '../room/MessageOptionsMenu';
type RoomNavItemMenuProps = {
room: Room;
@@ -65,6 +71,8 @@ const RoomNavItemMenu = forwardRef(
const canInvite = canDoAction('invite', getPowerLevel(mx.getUserId() ?? ''));
const openRoomSettings = useOpenRoomSettings();
const space = useSpaceOptionally();
+ const screenSize = useScreenSizeContext();
+ const isMobile = screenSize === ScreenSize.Mobile;
const handleMarkAsRead = () => {
markAsRead(mx, room.roomId, hideActivity);
@@ -89,7 +97,7 @@ const RoomNavItemMenu = forwardRef(
};
return (
-
+
+ )}
+ {relations && (
+
+ )}
+ }
+ radii="300"
+ data-event-id={mEvent.getId()}
+ onClick={(evt) => {
+ onReplyClick(evt);
+ closeMenu();
+ }}
+ >
+
+ Reply
+
+
+ {canEdit && onEditId && (
+ }
+ radii="300"
+ data-event-id={mEvent.getId()}
+ onClick={() => {
+ onEditId(mEvent.getId());
+ closeMenu();
+ }}
+ >
+
+ Edit Message
+
+
+ )}
+ {!hideReadReceipts && (
+
+ )}
+
+
+ {canPinEvent && }
+
+ {((!mEvent.isRedacted() && canDelete) || mEvent.getSender() !== mx.getUserId()) && (
+ <>
+
+
+ {!mEvent.isRedacted() && canDelete && (
+
+ )}
+ {mEvent.getSender() !== mx.getUserId() && (
+
+ )}
+
+ >
+ )}
+
+ )
+);
+
+type ExtendedOptionsProps = BaseOptionProps & {
+ imagePackRooms: Room[] | undefined;
+ onActiveStateChange: React.Dispatch>;
+ menuAnchor: RectCords | undefined;
+ emojiBoardAnchor: RectCords | undefined;
+ handleOpenEmojiBoard: MouseEventHandler;
+ handleOpenMenu: MouseEventHandler;
+ setMenuAnchor: React.Dispatch>;
+ setEmojiBoardAnchor: React.Dispatch>;
+ isMobileSheetOpen;
+ setMobileSheetOpen;
+};
+
+export function MessageOptionsMenu({
+ mEvent,
+ room,
+ mx,
+ relations,
+ imagePackRooms,
+ canSendReaction,
+ canEdit,
+ canDelete,
+ canPinEvent,
+ hideReadReceipts,
+ onReactionToggle,
+ onReplyClick,
+ onEditId,
+ onActiveStateChange,
+ closeMenu,
+ menuAnchor,
+ emojiBoardAnchor,
+ handleOpenEmojiBoard,
+ handleOpenMenu,
+ handleAddReactions,
+ setMenuAnchor,
+ setEmojiBoardAnchor,
+ isMobileSheetOpen,
+ setMobileSheetOpen,
+}: ExtendedOptionsProps) {
+ useEffect(() => {
+ onActiveStateChange?.(!!menuAnchor || !!emojiBoardAnchor);
+ }, [emojiBoardAnchor, menuAnchor, onActiveStateChange]);
+
+ const screenSize = useScreenSizeContext();
+ const isMobile = screenSize === ScreenSize.Mobile;
+ const [view, setView] = useState('options');
+
+ const eventId = mEvent.getId();
+ if (!eventId) return null;
+
+ const optionProps: BaseOptionProps = {
+ mEvent,
+ room,
+ mx,
+ relations,
+ canSendReaction,
+ canEdit,
+ canDelete,
+ canPinEvent,
+ hideReadReceipts,
+ onReactionToggle,
+ onReplyClick,
+ onEditId,
+ handleAddReactions,
+ closeMenu,
+ };
+
+ if (isMobile) {
+ return (
+ setMobileSheetOpen(false)} isOpen={isMobileSheetOpen}>
+ {view === 'options' ? (
+ {
+ closeMenu();
+ setMobileSheetOpen(false);
+ }}
+ handleAddReactions={() => setView('emoji')}
+ />
+ ) : (
+
+
+ setView('options')}>
+
+
+
+ Add Reaction
+
+
+ {
+ onReactionToggle(mEvent.getId(), key);
+ setEmojiBoardAnchor(undefined);
+ closeMenu();
+ setMobileSheetOpen(false);
+ }}
+ onCustomEmojiSelect={(mxc, shortcode) => {
+ onReactionToggle(mEvent.getId(), mxc, shortcode);
+ setEmojiBoardAnchor(undefined);
+ closeMenu();
+ setMobileSheetOpen(false);
+ }}
+ requestClose={() => setEmojiBoardAnchor(undefined)}
+ />
+
+ )}
+
+ );
+ }
+
+ return (
+
+
+
+ {canSendReaction && (
+ {
+ onReactionToggle(eventId, key);
+ setEmojiBoardAnchor(undefined);
+ }}
+ onCustomEmojiSelect={(mxc, shortcode) => {
+ onReactionToggle(eventId, mxc, shortcode);
+ setEmojiBoardAnchor(undefined);
+ }}
+ requestClose={() => setEmojiBoardAnchor(undefined)}
+ />
+ }
+ >
+
+
+
+
+ )}
+
+
+
+ {canEdit && onEditId && (
+ onEditId(eventId)}
+ variant="SurfaceVariant"
+ size="300"
+ radii="300"
+ >
+
+
+ )}
+ setMenuAnchor(undefined),
+ clickOutsideDeactivates: true,
+ isKeyForward: (evt) => evt.key === 'ArrowDown',
+ isKeyBackward: (evt) => evt.key === 'ArrowUp',
+ escapeDeactivates: stopPropagation,
+ }}
+ >
+
+
+ }
+ >
+
+
+
+
+
+
+
+ );
+}
From 1cf4dae929e4b323a0393374d2112470b0b81304 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 13:05:37 -0500
Subject: [PATCH 35/73] move file
---
.../room/message/DraggableMessage.tsx | 137 ------------------
1 file changed, 137 deletions(-)
delete mode 100644 src/app/features/room/message/DraggableMessage.tsx
diff --git a/src/app/features/room/message/DraggableMessage.tsx b/src/app/features/room/message/DraggableMessage.tsx
deleted file mode 100644
index f172afe7..00000000
--- a/src/app/features/room/message/DraggableMessage.tsx
+++ /dev/null
@@ -1,137 +0,0 @@
-import React from 'react';
-import { useSpring, animated } from '@react-spring/web';
-import { useDrag } from 'react-use-gesture';
-import { Icon, Icons } from 'folds';
-import { MatrixClient, MatrixEvent } from 'matrix-js-sdk';
-
-const DraggableMessageStyles = {
- container: {
- position: 'relative',
- overflow: 'hidden',
- width: '100%',
- },
- iconContainer: {
- position: 'absolute',
- top: 0,
- bottom: 0,
- right: 0,
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- width: '150px',
- },
- messageContent: {
- position: 'relative',
- touchAction: 'pan-y',
- backgroundColor: 'var(--folds-color-Background-Main)',
- width: '100%',
- },
- icon: {
- position: 'absolute',
- },
-};
-
-export function DraggableMessage({
- children,
- onReply,
- onEdit,
- event,
- mx,
-}: {
- children: React.ReactNode;
- onReply: () => void;
- onEdit: () => void;
- event: MatrixEvent;
- mx: MatrixClient;
-}) {
- const canEdit = mx.getUserId() === event.getSender();
- const REPLY_THRESHOLD = 80;
- const EDIT_THRESHOLD = canEdit ? 250 : Infinity;
-
- const [{ x, replyOpacity, editOpacity, iconScale }, api] = useSpring(() => ({
- x: 0,
- replyOpacity: 0,
- editOpacity: 0,
- iconScale: 0.5,
- config: { tension: 250, friction: 25 },
- }));
-
- const bind = useDrag(
- ({ down, movement: [x], vxvy: [vx] }) => {
- if (!down) {
- const finalDistance = Math.abs(x);
-
- if (finalDistance > EDIT_THRESHOLD) {
- onEdit();
- } else if (finalDistance > REPLY_THRESHOLD) {
- onReply();
- }
- }
-
- const xTarget = down ? Math.min(0, x) : 0;
- const distance = Math.abs(xTarget);
-
- let newReplyOpacity = 0;
- let newEditOpacity = 0;
- let newScale = 1.0;
-
- if (canEdit && distance > REPLY_THRESHOLD) {
- newReplyOpacity = 0;
- newEditOpacity = 1;
- if (down && distance > EDIT_THRESHOLD) {
- newScale = 1.1;
- }
- } else {
- newReplyOpacity = 1;
- newEditOpacity = 0;
- newScale = 0.5 + (distance / REPLY_THRESHOLD) * 0.5;
- }
-
- if (distance < 5) {
- newReplyOpacity = 0;
- }
-
- api.start({
- x: xTarget,
- replyOpacity: newReplyOpacity,
- editOpacity: newEditOpacity,
- iconScale: newScale,
- });
- },
- {
- axis: 'x',
- filterTaps: true,
- threshold: 10,
- }
- );
-
- return (
-
-
-
`scale(${s})`),
- }}
- >
-
-
-
-
`scale(${s})`),
- }}
- >
-
-
-
-
-
- {children}
-
-
- );
-}
From 1ec2c2d27b7ab9d57f7fea7bcb49cd74acd2fc90 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 13:06:06 -0500
Subject: [PATCH 36/73] Change behavior based on if is mobile or not and move
to be a component and not a feature
---
.../message/behavior/DraggableMessage.tsx | 117 ++++++++++++++++++
1 file changed, 117 insertions(+)
create mode 100644 src/app/components/message/behavior/DraggableMessage.tsx
diff --git a/src/app/components/message/behavior/DraggableMessage.tsx b/src/app/components/message/behavior/DraggableMessage.tsx
new file mode 100644
index 00000000..0d303277
--- /dev/null
+++ b/src/app/components/message/behavior/DraggableMessage.tsx
@@ -0,0 +1,117 @@
+import React from 'react';
+import { useSpring, animated } from '@react-spring/web';
+import { useDrag } from 'react-use-gesture';
+import { Icon, Icons } from 'folds';
+import { MatrixClient, MatrixEvent } from 'matrix-js-sdk';
+import { container, iconContainer, messageContent, icon } from './style.css';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+
+export function DraggableMessage({
+ children,
+ onReply,
+ onEdit,
+ event,
+ mx,
+}: {
+ children: React.ReactNode;
+ onReply: () => void;
+ onEdit: () => void;
+ event: MatrixEvent;
+ mx: MatrixClient;
+}) {
+ const screenSize = useScreenSizeContext();
+ const isMobile = screenSize === ScreenSize.Mobile;
+
+ const canEdit = mx.getUserId() === event.getSender();
+ const REPLY_THRESHOLD = 80;
+ const EDIT_THRESHOLD = canEdit ? 250 : Infinity;
+
+ const [{ x, replyOpacity, editOpacity, iconScale }, api] = useSpring(() => ({
+ x: 0,
+ replyOpacity: 0,
+ editOpacity: 0,
+ iconScale: 0.5,
+ config: { tension: 250, friction: 25 },
+ }));
+
+ const bind = useDrag(
+ ({ down, movement: [mvx], vxvy: [vx] }) => {
+ if (!down) {
+ const finalDistance = Math.abs(mvx);
+
+ if (finalDistance > EDIT_THRESHOLD) {
+ onEdit();
+ } else if (finalDistance > REPLY_THRESHOLD) {
+ onReply();
+ }
+ }
+
+ const xTarget = down ? Math.min(0, mvx) : 0;
+ const distance = Math.abs(xTarget);
+
+ let newReplyOpacity = 0;
+ let newEditOpacity = 0;
+ let newScale = 1.0;
+
+ if (canEdit && distance > REPLY_THRESHOLD) {
+ newReplyOpacity = 0;
+ newEditOpacity = 1;
+ if (down && distance > EDIT_THRESHOLD) {
+ newScale = 1.1;
+ }
+ } else {
+ newReplyOpacity = 1;
+ newEditOpacity = 0;
+ newScale = 0.5 + (distance / REPLY_THRESHOLD) * 0.5;
+ }
+
+ if (distance < 5) {
+ newReplyOpacity = 0;
+ }
+
+ api.start({
+ x: xTarget,
+ replyOpacity: newReplyOpacity,
+ editOpacity: newEditOpacity,
+ iconScale: newScale,
+ });
+ },
+ {
+ axis: 'x',
+ filterTaps: true,
+ threshold: 10,
+ }
+ );
+ if (isMobile) {
+ return (
+
+
+
`scale(${s})`),
+ }}
+ >
+
+
+
+
`scale(${s})`),
+ }}
+ >
+
+
+
+
+
+ {children}
+
+
+ );
+ }
+ return {children}
;
+}
From 3e814fc294438cf029bfbd50b33ce34be3f18f34 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 13:06:32 -0500
Subject: [PATCH 37/73] Add style for draggable
---
.../components/message/behavior/style.css.ts | 29 +++++++++++++++++++
1 file changed, 29 insertions(+)
create mode 100644 src/app/components/message/behavior/style.css.ts
diff --git a/src/app/components/message/behavior/style.css.ts b/src/app/components/message/behavior/style.css.ts
new file mode 100644
index 00000000..39df357c
--- /dev/null
+++ b/src/app/components/message/behavior/style.css.ts
@@ -0,0 +1,29 @@
+import { style } from '@vanilla-extract/css';
+
+export const container = style({
+ position: 'relative',
+ overflow: 'hidden',
+ width: '100%',
+});
+
+export const iconContainer = style({
+ position: 'absolute',
+ top: 0,
+ bottom: 0,
+ right: 0,
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: '150px',
+});
+
+export const messageContent = style({
+ position: 'relative',
+ touchAction: 'pan-y',
+ backgroundColor: 'var(--folds-color-Background-Main)',
+ width: '100%',
+});
+
+export const icon = style({
+ position: 'absolute',
+});
From c94074423d2ec349a678b47f88f4d4429a7dc6b6 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 13:08:14 -0500
Subject: [PATCH 38/73] remove unused imports
---
src/app/features/room/message/Message.tsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index a2f169f1..4042c993 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -66,7 +66,6 @@ import * as css from './styles.css';
import { EventReaders } from '../../../components/event-readers';
import { TextViewer } from '../../../components/text-viewer';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
-import { EmojiBoard } from '../../../components/emoji-board';
import { ReactionViewer } from '../reaction-viewer';
import { MessageEditor } from './MessageEditor';
import { UserAvatar } from '../../../components/user-avatar';
@@ -80,7 +79,7 @@ import { StateEvent } from '../../../../types/matrix/room';
import { getTagIconSrc, PowerLevelTag } from '../../../hooks/usePowerLevelTags';
import { PowerIcon } from '../../../components/power';
import colorMXID from '../../../../util/colorMXID';
-import { MessageDropdownMenu, MessageOptionsMenu, BottomSheetMenu } from '../MessageOptionsMenu';
+import { MessageOptionsMenu } from './MessageOptionsMenu';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
import { DraggableMessage } from './DraggableMessage';
From 1a7e59641b539d29ad2e6e0594a49eb0c226646d Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 13:08:24 -0500
Subject: [PATCH 39/73] file moved
---
src/app/features/room/message/Message.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index 4042c993..3f6b46b1 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -81,7 +81,7 @@ import { PowerIcon } from '../../../components/power';
import colorMXID from '../../../../util/colorMXID';
import { MessageOptionsMenu } from './MessageOptionsMenu';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
-import { DraggableMessage } from './DraggableMessage';
+import { DraggableMessage } from '../../../components/message/behavior/DraggableMessage';
export type ReactionHandler = (keyOrMxc: string, shortcode: string) => void;
From 4a1446ca74bda0e2de2541d9cb7dc3b04b2219d4 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 13:09:20 -0500
Subject: [PATCH 40/73] Remove unused
---
src/app/features/room/message/Message.tsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index 3f6b46b1..cff0f03b 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -724,12 +724,10 @@ export const Message = as<'div', MessageProps>(
const screenSize = useScreenSizeContext();
const isMobile = screenSize === ScreenSize.Mobile;
const [isDesktopOptionsActive, setDesktopOptionsActive] = useState(false);
- //const showDesktopOptions = !isMobile && (isHovered || isFocusWithin || isDesktopOptionsActive);
const [isMobileSheetOpen, setMobileSheetOpen] = useState(false);
const senderDisplayName =
getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId;
const senderAvatarMxc = getMemberAvatarMxc(room, senderId);
- const [view, setView] = useState('options');
const closeMenu = () => {
setMenuAnchor(undefined);
From 5dd67009cba815d1d7a9a3d127339f044d73c1c3 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 13:09:45 -0500
Subject: [PATCH 41/73] remove unused methods
---
src/app/features/room/message/Message.tsx | 10 ----------
1 file changed, 10 deletions(-)
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index cff0f03b..7a462861 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -751,16 +751,6 @@ export const Message = as<'div', MessageProps>(
}, 100);
};
- const handleClose = () => {
- setView('options');
- closeMenu();
- };
-
- const onEmojiSelect = (key, shortcode) => {
- onReactionToggle(mEvent.getId(), key, shortcode);
- handleClose();
- };
-
// TODO: Remove this and clean it up later...
const button = document.createElement('button');
button.setAttribute('data-event-id', mEvent.getId());
From 2cdc4a49694a168b1be59e9ce5e66bc624db4f9d Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 13:10:40 -0500
Subject: [PATCH 42/73] Reduce reuse and clean up as a result of handling the
behavior in OptionsMenu
---
src/app/features/room/message/Message.tsx | 281 +++++++++-------------
1 file changed, 113 insertions(+), 168 deletions(-)
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index 7a462861..bef5ea13 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -877,187 +877,132 @@ export const Message = as<'div', MessageProps>(
};
return (
- <>
-
- {!edit && (isHovered || !!menuAnchor || !!emojiBoardAnchor) && (
-
+ {!edit && (isMobileSheetOpen || isHovered || !!menuAnchor || !!emojiBoardAnchor) && (
+
+ )}
+ {messageLayout === MessageLayout.Compact &&
+ (!isMobile ? (
+
+ {msgContentJSX}
+
+ ) : (
+ {
+ const mockTargetElement = document.createElement('button');
+ mockTargetElement.setAttribute('data-event-id', mEvent.getId());
+ const mockEvent = {
+ currentTarget: mockTargetElement,
+ };
+
+ onReplyClick(mockEvent);
+ }}
+ onEdit={() => {
+ onEditId(mEvent.getId());
+ }}
mx={mx}
- relations={relations}
- imagePackRooms={imagePackRooms}
- canSendReaction={canSendReaction}
- canEdit={canEditEvent(mx, mEvent)}
- canDelete={canDelete}
- canPinEvent={canPinEvent}
- hideReadReceipts={hideReadReceipts}
- onReactionToggle={onReactionToggle}
- onReplyClick={onReplyClick}
- onEditId={onEditId}
- onActiveStateChange={setDesktopOptionsActive}
- handleAddReactions={handleAddReactions}
- closeMenu={closeMenu}
- emojiBoardAnchor={emojiBoardAnchor}
- menuAnchor={menuAnchor}
- handleOpenEmojiBoard={handleOpenEmojiBoard}
- setEmojiBoardAnchor={setEmojiBoardAnchor}
- setMenuAnchor={setMenuAnchor}
- handleOpenMenu={handleOpenMenu}
- />
- )}
- {messageLayout === MessageLayout.Compact &&
- (!isMobile ? (
+ >
{msgContentJSX}
- ) : (
- {
- const mockTargetElement = document.createElement('button');
- mockTargetElement.setAttribute('data-event-id', mEvent.getId());
- const mockEvent = {
- currentTarget: mockTargetElement,
- };
+
+ ))}
+ {messageLayout === MessageLayout.Bubble &&
+ (!isMobile ? (
+
+ {msgContentJSX}
+
+ ) : (
+ {
+ const mockTargetElement = document.createElement('button');
+ mockTargetElement.setAttribute('data-event-id', mEvent.getId());
+ const mockEvent = {
+ currentTarget: mockTargetElement,
+ };
- onReplyClick(mockEvent);
- }}
- onEdit={() => {
- onEditId(mEvent.getId());
- }}
- mx={mx}
- >
-
- {msgContentJSX}
-
-
- ))}
- {messageLayout === MessageLayout.Bubble &&
- (!isMobile ? (
+ onReplyClick(mockEvent);
+ }}
+ onEdit={() => {
+ onEditId(mEvent.getId());
+ }}
+ mx={mx}
+ >
{msgContentJSX}
- ) : (
- {
- const mockTargetElement = document.createElement('button');
- mockTargetElement.setAttribute('data-event-id', mEvent.getId());
- const mockEvent = {
- currentTarget: mockTargetElement,
- };
+
+ ))}
+ {messageLayout !== MessageLayout.Compact &&
+ messageLayout !== MessageLayout.Bubble &&
+ (!isMobile ? (
+
+ {headerJSX}
+ {msgContentJSX}
+
+ ) : (
+ {
+ const mockTargetElement = document.createElement('button');
+ mockTargetElement.setAttribute('data-event-id', mEvent.getId());
+ const mockEvent = {
+ currentTarget: mockTargetElement,
+ };
- onReplyClick(mockEvent);
- }}
- onEdit={() => {
- onEditId(mEvent.getId());
- }}
- mx={mx}
- >
-
- {msgContentJSX}
-
-
- ))}
- {messageLayout !== MessageLayout.Compact &&
- messageLayout !== MessageLayout.Bubble &&
- (!isMobile ? (
+ onReplyClick(mockEvent);
+ }}
+ onEdit={() => {
+ onEditId(mEvent.getId());
+ }}
+ mx={mx}
+ >
{headerJSX}
{msgContentJSX}
- ) : (
- {
- const mockTargetElement = document.createElement('button');
- mockTargetElement.setAttribute('data-event-id', mEvent.getId());
- const mockEvent = {
- currentTarget: mockTargetElement,
- };
-
- onReplyClick(mockEvent);
- }}
- onEdit={() => {
- onEditId(mEvent.getId());
- }}
- mx={mx}
- >
-
- {headerJSX}
- {msgContentJSX}
-
-
- ))}
-
-
- {isMobile && (
- setMobileSheetOpen(false)} isOpen={isMobileSheetOpen}>
- {view === 'options' ? (
- {
- closeMenu();
- setMobileSheetOpen(false);
- }}
- mEvent={mEvent}
- room={room}
- mx={mx}
- relations={relations}
- canSendReaction={canSendReaction}
- canEdit={canEditEvent(mx, mEvent)}
- canDelete={canDelete || mEvent?.getSender() === mx.getUserId()}
- canPinEvent={canPinEvent}
- hideReadReceipts={hideReadReceipts}
- onReactionToggle={onReactionToggle}
- onReplyClick={onReplyClick}
- onEditId={onEditId}
- handleAddReactions={() => setView('emoji')}
- />
- ) : (
-
-
- setView('options')}>
-
-
-
- Add Reaction
-
-
- {
- onReactionToggle(mEvent.getId(), key);
- setEmojiBoardAnchor(undefined);
- closeMenu();
- setMobileSheetOpen(false);
- }}
- onCustomEmojiSelect={(mxc, shortcode) => {
- onReactionToggle(mEvent.getId(), mxc, shortcode);
- setEmojiBoardAnchor(undefined);
- closeMenu();
- setMobileSheetOpen(false);
- }}
- requestClose={() => setEmojiBoardAnchor(undefined)}
- />
-
- )}
-
- )}
- >
+
+ ))}
+
);
}
);
From 4085732d39b50fe06a0dc2c39f67b3ae5a7abf5d Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 13:11:04 -0500
Subject: [PATCH 43/73] still trying to find a place for this to live
---
.../room/message/MobileContextMenu.tsx | 32 +++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 src/app/features/room/message/MobileContextMenu.tsx
diff --git a/src/app/features/room/message/MobileContextMenu.tsx b/src/app/features/room/message/MobileContextMenu.tsx
new file mode 100644
index 00000000..3711b72c
--- /dev/null
+++ b/src/app/features/room/message/MobileContextMenu.tsx
@@ -0,0 +1,32 @@
+import classNames from 'classnames';
+import React from 'react';
+import * as css from './styles.css';
+
+export function BottomSheetMenu({
+ isOpen,
+ onClose,
+ children,
+}: {
+ isOpen: boolean;
+ onClose: () => void;
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
From d13c2458468d0706d69c2ebc4e6fae01dabd85d0 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 17:47:13 -0500
Subject: [PATCH 44/73] Category buttons shouldn't be highlightable
---
src/app/features/room-nav/styles.css.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/app/features/room-nav/styles.css.ts b/src/app/features/room-nav/styles.css.ts
index c1097380..8b96eebf 100644
--- a/src/app/features/room-nav/styles.css.ts
+++ b/src/app/features/room-nav/styles.css.ts
@@ -4,6 +4,7 @@ import { config } from 'folds';
export const CategoryButton = style({
flexGrow: 1,
userSelect: 'none',
+ WebkitUserSelect: 'none',
});
export const CategoryButtonIcon = style({
opacity: config.opacity.P400,
From ccb41c47a3f6e2446586195b59f3a673d5f5a510 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 20:13:07 -0500
Subject: [PATCH 45/73] Update CSS to disable text select on mobile (might be
better suited elsewhere)
---
src/app/features/room/message/styles.css.ts | 76 +++------------------
1 file changed, 8 insertions(+), 68 deletions(-)
diff --git a/src/app/features/room/message/styles.css.ts b/src/app/features/room/message/styles.css.ts
index d87ea501..e0545bd6 100644
--- a/src/app/features/room/message/styles.css.ts
+++ b/src/app/features/room/message/styles.css.ts
@@ -3,6 +3,14 @@ import { DefaultReset, color, config, toRem } from 'folds';
export const MessageBase = style({
position: 'relative',
+ '@media': {
+ 'screen and (max-width: 768px)': {
+ userSelect: 'none',
+ WebkitUserSelect: 'none',
+ MozUserSelect: 'none',
+ msUserSelect: 'none',
+ },
+ },
});
export const MessageOptionsBase = style([
@@ -48,71 +56,3 @@ export const ReactionsContainer = style({
export const ReactionsTooltipText = style({
wordBreak: 'break-word',
});
-
-export const menuBackdrop = style({
- userSelect: 'none',
- position: 'fixed',
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
- backgroundColor: 'rgba(0, 0, 0, 0.5)',
- transition: 'opacity 0.3s ease-in-out',
- opacity: 0,
-});
-
-export const menuBackdropOpen = style({
- opacity: 1,
-});
-
-export const menuSheet = style({
- userSelect: 'none',
- position: 'fixed',
- left: 0,
- right: 0,
- bottom: 0,
- backgroundColor: color.Background.Container,
- borderTopLeftRadius: config.radii.R400,
- borderTopRightRadius: config.radii.R400,
- padding: config.space.S500,
- transform: 'translateY(100%)',
- transition: 'transform 0.3s ease-out',
- boxShadow: '0 -2px 10px rgba(0,0,0,0.1)',
-});
-
-export const menuSheetOpen = style({
- transform: 'translateY(0)',
-});
-
-export const menuItem = style({
- userSelect: 'none',
- width: '100%',
- background: 'none',
- border: 'none',
- padding: `${config.space.S300} ${config.space.S100}`,
- textAlign: 'left',
- cursor: 'pointer',
- borderRadius: config.radii.R300,
- color: color.Primary.ContainerActive,
-
- outline: 'none',
-
- WebkitTapHighlightColor: 'transparent',
-
- WebkitUserSelect: 'none',
- MozUserSelect: 'none',
- msUserSelect: 'none',
-
- selectors: {
- '&:hover': {
- backgroundColor: color.Background.ContainerHover,
- },
- '&:focus-visible': {
- backgroundColor: color.Background.ContainerHover,
- },
- },
-});
-
-export const menuItemDestructive = style({
- color: color.Critical.ContainerActive,
-});
From c269e841f6739f1524a5f4b2d3a5f9c1240fc7b5 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 20:15:26 -0500
Subject: [PATCH 46/73] restructure to use different rendering strat
---
.../room/message/MessageOptionsMenu.tsx | 112 ++++++++++--------
1 file changed, 64 insertions(+), 48 deletions(-)
diff --git a/src/app/features/room/message/MessageOptionsMenu.tsx b/src/app/features/room/message/MessageOptionsMenu.tsx
index d0c7277b..bc229ede 100644
--- a/src/app/features/room/message/MessageOptionsMenu.tsx
+++ b/src/app/features/room/message/MessageOptionsMenu.tsx
@@ -31,7 +31,11 @@ import {
MessageSourceCodeItem,
} from './Message';
import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
-import { BottomSheetMenu } from './MobileContextMenu';
+import MobileContextMenu from '../../../molecules/mobile-context-menu/MobileContextMenu';
+
+const EmojiBoard = lazy(() =>
+ import('../../../components/emoji-board').then((module) => ({ default: module.EmojiBoard }))
+);
type BaseOptionProps = {
mEvent: MatrixEvent;
@@ -85,7 +89,10 @@ export const MessageDropdownMenu = forwardRef(
size="300"
after={}
radii="300"
- onClick={handleAddReactions}
+ onClick={(e) => {
+ handleAddReactions(e);
+ closeMenu();
+ }}
>
Add Reaction
@@ -93,7 +100,7 @@ export const MessageDropdownMenu = forwardRef(
)}
{relations && (
-
+ {}} />
)}
(
type ExtendedOptionsProps = BaseOptionProps & {
imagePackRooms: Room[] | undefined;
- onActiveStateChange: React.Dispatch>;
menuAnchor: RectCords | undefined;
emojiBoardAnchor: RectCords | undefined;
handleOpenEmojiBoard: MouseEventHandler;
handleOpenMenu: MouseEventHandler;
setMenuAnchor: React.Dispatch>;
setEmojiBoardAnchor: React.Dispatch>;
- isMobileSheetOpen;
- setMobileSheetOpen;
+ isOptionsMenuOpen;
+ setOptionsMenuOpen;
+ isEmojiBoardOpen;
+ setEmojiBoardOpen;
};
export function MessageOptionsMenu({
@@ -176,7 +184,6 @@ export function MessageOptionsMenu({
onReactionToggle,
onReplyClick,
onEditId,
- onActiveStateChange,
closeMenu,
menuAnchor,
emojiBoardAnchor,
@@ -185,13 +192,11 @@ export function MessageOptionsMenu({
handleAddReactions,
setMenuAnchor,
setEmojiBoardAnchor,
- isMobileSheetOpen,
- setMobileSheetOpen,
+ isOptionsMenuOpen,
+ setOptionsMenuOpen,
+ isEmojiBoardOpen,
+ setEmojiBoardOpen,
}: ExtendedOptionsProps) {
- useEffect(() => {
- onActiveStateChange?.(!!menuAnchor || !!emojiBoardAnchor);
- }, [emojiBoardAnchor, menuAnchor, onActiveStateChange]);
-
const screenSize = useScreenSizeContext();
const isMobile = screenSize === ScreenSize.Mobile;
const [view, setView] = useState('options');
@@ -218,47 +223,58 @@ export function MessageOptionsMenu({
if (isMobile) {
return (
- setMobileSheetOpen(false)} isOpen={isMobileSheetOpen}>
- {view === 'options' ? (
- {
+ <>
+ {isOptionsMenuOpen && (
+ {
closeMenu();
- setMobileSheetOpen(false);
}}
- handleAddReactions={() => setView('emoji')}
- />
- ) : (
-
-
- setView('options')}>
-
-
-
- Add Reaction
-
-
- {
- onReactionToggle(mEvent.getId(), key);
- setEmojiBoardAnchor(undefined);
+ isOpen={isOptionsMenuOpen}
+ >
+ {
closeMenu();
- setMobileSheetOpen(false);
}}
- onCustomEmojiSelect={(mxc, shortcode) => {
- onReactionToggle(mEvent.getId(), mxc, shortcode);
- setEmojiBoardAnchor(undefined);
- closeMenu();
- setMobileSheetOpen(false);
- }}
- requestClose={() => setEmojiBoardAnchor(undefined)}
+ handleAddReactions={handleAddReactions}
/>
-
+
)}
-
+
+ {isEmojiBoardOpen && (
+ {
+ closeMenu();
+ setEmojiBoardOpen(false);
+ }}
+ isOpen={isEmojiBoardOpen}
+ >
+ Loading}>
+ {
+ onReactionToggle(mEvent.getId(), key);
+ setEmojiBoardAnchor(undefined);
+ setEmojiBoardOpen(false);
+ (document.activeElement as HTMLElement)?.blur();
+ }}
+ onCustomEmojiSelect={(mxc, shortcode) => {
+ onReactionToggle(mEvent.getId(), mxc, shortcode);
+ setEmojiBoardAnchor(undefined);
+ setEmojiBoardOpen(false);
+ (document.activeElement as HTMLElement)?.blur();
+ }}
+ requestClose={() => {
+ setEmojiBoardAnchor(undefined);
+ setEmojiBoardOpen(false);
+ }}
+ />
+
+
+ )}
+ >
);
}
From 49259c37b01a026a8a1ecd65b703a8f25e7e284c Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 20:15:55 -0500
Subject: [PATCH 47/73] simplify by placing check in draggable base
---
src/app/features/room/message/Message.tsx | 204 +++++++++++-----------
1 file changed, 98 insertions(+), 106 deletions(-)
diff --git a/src/app/features/room/message/Message.tsx b/src/app/features/room/message/Message.tsx
index bef5ea13..eea14245 100644
--- a/src/app/features/room/message/Message.tsx
+++ b/src/app/features/room/message/Message.tsx
@@ -721,15 +721,18 @@ export const Message = as<'div', MessageProps>(
const { focusWithinProps } = useFocusWithin({});
const [menuAnchor, setMenuAnchor] = useState();
const [emojiBoardAnchor, setEmojiBoardAnchor] = useState();
+ const [isDesktopOptionsActive, setDesktopOptionsActive] = useState(false);
+ const [isOptionsMenuOpen, setOptionsMenuOpen] = useState(false);
+ const [isEmojiBoardOpen, setEmojiBoardOpen] = useState(false);
+
const screenSize = useScreenSizeContext();
const isMobile = screenSize === ScreenSize.Mobile;
- const [isDesktopOptionsActive, setDesktopOptionsActive] = useState(false);
- const [isMobileSheetOpen, setMobileSheetOpen] = useState(false);
const senderDisplayName =
getMemberDisplayName(room, senderId) ?? getMxIdLocalPart(senderId) ?? senderId;
const senderAvatarMxc = getMemberAvatarMxc(room, senderId);
const closeMenu = () => {
+ setOptionsMenuOpen(false);
setMenuAnchor(undefined);
};
@@ -745,10 +748,10 @@ export const Message = as<'div', MessageProps>(
const handleAddReactions: MouseEventHandler = () => {
const rect = menuAnchor;
- closeMenu();
+ setEmojiBoardOpen(true);
setTimeout(() => {
setEmojiBoardAnchor(rect);
- }, 100);
+ }, 150);
};
// TODO: Remove this and clean it up later...
@@ -767,7 +770,7 @@ export const Message = as<'div', MessageProps>(
const longPressBinder = useLongPress(
() => {
if (isMobile) {
- setMobileSheetOpen(true);
+ setOptionsMenuOpen(true);
}
},
{
@@ -883,125 +886,114 @@ export const Message = as<'div', MessageProps>(
space={messageSpacing}
collapse={collapse}
highlight={highlight}
- selected={isDesktopOptionsActive || isMobileSheetOpen}
+ selected={isDesktopOptionsActive || isOptionsMenuOpen || isEmojiBoardOpen}
{...props}
{...hoverProps}
- {...focusWithinProps}
+ {...(!isMobile ? focusWithinProps : {})}
{...(isMobile ? longPressBinder() : {})}
ref={ref}
>
- {!edit && (isMobileSheetOpen || isHovered || !!menuAnchor || !!emojiBoardAnchor) && (
-
+ )}
+ {messageLayout === MessageLayout.Compact && (
+ {
+ const mockTargetElement = document.createElement('button');
+ mockTargetElement.setAttribute('data-event-id', mEvent.getId());
+ const mockEvent = {
+ currentTarget: mockTargetElement,
+ };
+
+ onReplyClick(mockEvent);
+ }}
+ onEdit={() => {
+ onEditId(mEvent.getId());
+ }}
mx={mx}
- relations={relations}
- imagePackRooms={imagePackRooms}
- canSendReaction={canSendReaction}
- canEdit={canEditEvent(mx, mEvent)}
- canDelete={canDelete}
- canPinEvent={canPinEvent}
- hideReadReceipts={hideReadReceipts}
- onReactionToggle={onReactionToggle}
- onReplyClick={onReplyClick}
- onEditId={onEditId}
- onActiveStateChange={setDesktopOptionsActive}
- handleAddReactions={handleAddReactions}
- closeMenu={closeMenu}
- emojiBoardAnchor={emojiBoardAnchor}
- menuAnchor={menuAnchor}
- handleOpenEmojiBoard={handleOpenEmojiBoard}
- setEmojiBoardAnchor={setEmojiBoardAnchor}
- setMenuAnchor={setMenuAnchor}
- handleOpenMenu={handleOpenMenu}
- setMobileSheetOpen={setMobileSheetOpen}
- isMobileSheetOpen={isMobileSheetOpen}
- />
- )}
- {messageLayout === MessageLayout.Compact &&
- (!isMobile ? (
+ >
{msgContentJSX}
- ) : (
- {
- const mockTargetElement = document.createElement('button');
- mockTargetElement.setAttribute('data-event-id', mEvent.getId());
- const mockEvent = {
- currentTarget: mockTargetElement,
- };
+
+ )}
+ {messageLayout === MessageLayout.Bubble && (
+ {
+ const mockTargetElement = document.createElement('button');
+ mockTargetElement.setAttribute('data-event-id', mEvent.getId());
+ const mockEvent = {
+ currentTarget: mockTargetElement,
+ };
- onReplyClick(mockEvent);
- }}
- onEdit={() => {
- onEditId(mEvent.getId());
- }}
- mx={mx}
- >
-
- {msgContentJSX}
-
-
- ))}
- {messageLayout === MessageLayout.Bubble &&
- (!isMobile ? (
+ onReplyClick(mockEvent);
+ }}
+ onEdit={() => {
+ onEditId(mEvent.getId());
+ }}
+ mx={mx}
+ >
{msgContentJSX}
- ) : (
- {
- const mockTargetElement = document.createElement('button');
- mockTargetElement.setAttribute('data-event-id', mEvent.getId());
- const mockEvent = {
- currentTarget: mockTargetElement,
- };
+
+ )}
+ {messageLayout !== MessageLayout.Compact && messageLayout !== MessageLayout.Bubble && (
+ {
+ const mockTargetElement = document.createElement('button');
+ mockTargetElement.setAttribute('data-event-id', mEvent.getId());
+ const mockEvent = {
+ currentTarget: mockTargetElement,
+ };
- onReplyClick(mockEvent);
- }}
- onEdit={() => {
- onEditId(mEvent.getId());
- }}
- mx={mx}
- >
-
- {msgContentJSX}
-
-
- ))}
- {messageLayout !== MessageLayout.Compact &&
- messageLayout !== MessageLayout.Bubble &&
- (!isMobile ? (
+ onReplyClick(mockEvent);
+ }}
+ onEdit={() => {
+ onEditId(mEvent.getId());
+ }}
+ mx={mx}
+ >
{headerJSX}
{msgContentJSX}
- ) : (
- {
- const mockTargetElement = document.createElement('button');
- mockTargetElement.setAttribute('data-event-id', mEvent.getId());
- const mockEvent = {
- currentTarget: mockTargetElement,
- };
-
- onReplyClick(mockEvent);
- }}
- onEdit={() => {
- onEditId(mEvent.getId());
- }}
- mx={mx}
- >
-
- {headerJSX}
- {msgContentJSX}
-
-
- ))}
+
+ )}
);
}
From 9074f4135a562d32709c26ad9dccc9f9dd51ad46 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 20:16:14 -0500
Subject: [PATCH 48/73] update imports
---
src/app/features/room/message/MessageOptionsMenu.tsx | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/app/features/room/message/MessageOptionsMenu.tsx b/src/app/features/room/message/MessageOptionsMenu.tsx
index bc229ede..2fc508bf 100644
--- a/src/app/features/room/message/MessageOptionsMenu.tsx
+++ b/src/app/features/room/message/MessageOptionsMenu.tsx
@@ -11,12 +11,10 @@ import {
RectCords,
Text,
} from 'folds';
-import React, { forwardRef, MouseEventHandler, useEffect, useState } from 'react';
+import React, { forwardRef, lazy, MouseEventHandler, Suspense, useEffect, useState } from 'react';
import FocusTrap from 'focus-trap-react';
-import classNames from 'classnames';
import { MatrixClient, MatrixEvent, Relations, Room } from 'matrix-js-sdk';
-import { EmojiBoard } from '../../../components/emoji-board';
import { stopPropagation } from '../../../utils/keyboard';
import * as css from './styles.css';
From b8ec0d4ba0eef9c5ddd5cfe43fa5e01530edaf2f Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 20:16:32 -0500
Subject: [PATCH 49/73] remove no longer needed css
---
src/app/components/message/layout/layout.css.ts | 11 -----------
1 file changed, 11 deletions(-)
diff --git a/src/app/components/message/layout/layout.css.ts b/src/app/components/message/layout/layout.css.ts
index 73631129..290e8466 100644
--- a/src/app/components/message/layout/layout.css.ts
+++ b/src/app/components/message/layout/layout.css.ts
@@ -183,17 +183,6 @@ export const MessageTextBody = recipe({
},
},
},
-
- '@media': {
- 'screen and (max-width: 768px)': {
- base: {
- userSelect: 'none',
- WebkitUserSelect: 'none',
- MozUserSelect: 'none',
- msUserSelect: 'none',
- },
- },
- },
});
export type MessageTextBodyVariants = RecipeVariants;
From a1805a9ca90989d2cdc753a3d761b0db87ac53c4 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 20:16:55 -0500
Subject: [PATCH 50/73] Use new menu component on roomnav
---
src/app/features/room-nav/RoomNavItem.tsx | 303 +++++++++++-----------
1 file changed, 153 insertions(+), 150 deletions(-)
diff --git a/src/app/features/room-nav/RoomNavItem.tsx b/src/app/features/room-nav/RoomNavItem.tsx
index ac902136..886301ee 100644
--- a/src/app/features/room-nav/RoomNavItem.tsx
+++ b/src/app/features/room-nav/RoomNavItem.tsx
@@ -56,7 +56,7 @@ import { RoomNotificationModeSwitcher } from '../../components/RoomNotificationS
import { useCallState } from '../../pages/client/call/CallProvider';
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
-import { BottomSheetMenu } from '../room/MessageOptionsMenu';
+import { MobileContextMenu } from '../../molecules/mobile-context-menu/MobileContextMenu';
type RoomNavItemMenuProps = {
room: Room;
@@ -99,7 +99,7 @@ const RoomNavItemMenu = forwardRef(
};
return (
-
+
{
- if (isMobile) {
- setMobileSheetOpen(true);
- }
- },
- {
- threshold: 400,
- cancelOnMovement: true,
- }
- );
-
const handleContextMenu: MouseEventHandler = (evt) => {
evt.preventDefault();
@@ -302,148 +290,163 @@ export function RoomNavItem({
setViewedCallRoomId(room.roomId);
};
+ const handleCloseMenu = () => {
+ setMenuAnchor(undefined);
+ setMobileSheetOpen(false);
+ };
+
const optionsVisible = !isMobile && (hover || !!menuAnchor);
+ const longPressBinder = useLongPress(
+ () => {
+ if (isMobile) {
+ setMobileSheetOpen(true);
+ }
+ },
+ {
+ threshold: 400,
+ cancelOnMovement: true,
+ }
+ );
+
+ const menuContent = (
+
+ );
+
return (
- <>
-
-
-
-
- {showAvatar ? (
- (
-
- {nameInitials(room.name)}
-
- )}
- />
- ) : (
-
- )}
-
-
-
- {room.name}
-
-
- {!optionsVisible && !unread && !selected && typingMember.length > 0 && (
-
-
-
- )}
- {!optionsVisible && unread && (
-
- 0} count={unread.total} />
-
- )}
- {!optionsVisible && notificationMode !== RoomNotificationMode.Unset && (
-
+
+
+
+
+ {showAvatar ? (
+ (
+
+ {nameInitials(room.name)}
+
+ )}
+ />
+ ) : (
+
)}
+
+
+
+ {room.name}
+
-
- {optionsVisible && (
-
- setMenuAnchor(undefined),
- clickOutsideDeactivates: true,
- isKeyForward: (evt) => evt.key === 'ArrowDown',
- isKeyBackward: (evt) => evt.key === 'ArrowUp',
- escapeDeactivates: stopPropagation,
- }}
- >
- setMenuAnchor(undefined)}
- notificationMode={notificationMode}
- />
-
- }
- >
- {room.isCallRoom() && (
-
- Open chat
-
- }
- >
- {(triggerRef) => (
-
-
-
-
-
- )}
-
- )}
- 0 && (
+
+
+
+ )}
+ {!optionsVisible && unread && (
+
+ 0} count={unread.total} />
+
+ )}
+ {!optionsVisible && notificationMode !== RoomNotificationMode.Unset && (
+
+ )}
+
+
+ {optionsVisible && (
+
+ setMenuAnchor(undefined),
+ clickOutsideDeactivates: true,
+ isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
+ isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
+ escapeDeactivates: stopPropagation,
+ }}
>
-
-
-
-
- )}
-
- {isMobile && (
- setMobileSheetOpen(false)}>
- setMobileSheetOpen(false)}
- notificationMode={notificationMode}
- />
-
+ {menuContent}
+
+ }
+ >
+ {room.isCallRoom() && (
+
+ Open chat
+
+ }
+ >
+ {(triggerRef) => (
+
+
+
+
+
+ )}
+
+ )}
+
+
+
+
+
)}
- >
+ {isMobile && (
+
+ {menuContent}
+
+ )}
+
);
}
From e2e3ae63e36c5f7a58aafea7e235d2b8f0920575 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 20:17:20 -0500
Subject: [PATCH 51/73] add mobilecontextmenu
---
.../mobile-context-menu/MobileContextMenu.jsx | 62 +++++++++++++++++++
1 file changed, 62 insertions(+)
create mode 100644 src/app/molecules/mobile-context-menu/MobileContextMenu.jsx
diff --git a/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx b/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx
new file mode 100644
index 00000000..e2915e7c
--- /dev/null
+++ b/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx
@@ -0,0 +1,62 @@
+import React, { useEffect } from 'react';
+import { useSpring, animated } from '@react-spring/web';
+import { useDrag } from 'react-use-gesture';
+import './MobileContextMenu.scss';
+
+export function MobileContextMenu({ isOpen, onClose, children }) {
+ const { innerHeight } = window;
+
+ const [{ y }, api] = useSpring(() => ({
+ y: innerHeight,
+ config: { tension: 250, friction: 25 },
+ }));
+
+ useEffect(() => {
+ api.start({ y: isOpen ? 0 : innerHeight });
+ }, [api, innerHeight, isOpen]);
+
+ const bind = useDrag(
+ ({ last, movement: [, my], velocities: [, vy] }) => {
+ if (last) {
+ if (my > innerHeight / 4) {
+ onClose();
+ } else {
+ api.start({ y: 0 });
+ }
+ } else {
+ api.start({ y: Math.max(my, 0), immediate: true });
+ }
+ },
+ {
+ from: () => [0, y.get()],
+ filterTaps: true,
+ bounds: { top: 0 },
+ rubberband: true,
+ }
+ );
+ if (!isOpen) return null;
+
+ return (
+ <>
+
+
+
+
+ {children}
+
+ >
+ );
+}
+
+export default MobileContextMenu;
From a572e6c352decd3e946e025dbcdb0c8520881eb6 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 20:17:29 -0500
Subject: [PATCH 52/73] css for mobile context menu
---
.../MobileContextMenu.scss | 34 +++++++++++++++++++
1 file changed, 34 insertions(+)
create mode 100644 src/app/molecules/mobile-context-menu/MobileContextMenu.scss
diff --git a/src/app/molecules/mobile-context-menu/MobileContextMenu.scss b/src/app/molecules/mobile-context-menu/MobileContextMenu.scss
new file mode 100644
index 00000000..f42e1f75
--- /dev/null
+++ b/src/app/molecules/mobile-context-menu/MobileContextMenu.scss
@@ -0,0 +1,34 @@
+.bottom-sheet-backdrop {
+ position: fixed;
+ inset: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ z-index: 1000;
+ touch-action: none;
+}
+
+.bottom-sheet-container {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1001;
+ display: flex;
+ flex-direction: column;
+ max-height: 90vh;
+ border-radius: 16px 16px 0 0;
+ box-shadow: 0px -4px B16px rgba(0, 0, 0, 0.15);
+}
+
+.bottom-sheet-grabber {
+ flex-shrink: 0;
+ width: 40px;
+ height: 5px;
+ border-radius: 2.5px;
+ background-color: #ccc;
+ margin: 8px auto;
+}
+
+.bottom-sheet-content {
+ overflow-y: auto;
+ padding: 0 1rem 1rem 1rem;
+}
From 01c3b23284a83b2c563139c29b534923b9d45d98 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 20:26:07 -0500
Subject: [PATCH 53/73] revert
---
src/app/features/room-nav/styles.css.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/app/features/room-nav/styles.css.ts b/src/app/features/room-nav/styles.css.ts
index 8b96eebf..b5701a6f 100644
--- a/src/app/features/room-nav/styles.css.ts
+++ b/src/app/features/room-nav/styles.css.ts
@@ -3,8 +3,6 @@ import { config } from 'folds';
export const CategoryButton = style({
flexGrow: 1,
- userSelect: 'none',
- WebkitUserSelect: 'none',
});
export const CategoryButtonIcon = style({
opacity: config.opacity.P400,
From f9ff976624713fd99d3b7840bbe78ba218177893 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 20:26:24 -0500
Subject: [PATCH 54/73] disable highlight on nav buttons (for webkit)
---
src/app/components/virtualizer/style.css.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/app/components/virtualizer/style.css.ts b/src/app/components/virtualizer/style.css.ts
index 962550cc..1be1fac6 100644
--- a/src/app/components/virtualizer/style.css.ts
+++ b/src/app/components/virtualizer/style.css.ts
@@ -4,6 +4,7 @@ import { DefaultReset } from 'folds';
export const VirtualTile = style([
DefaultReset,
{
+ WebkitUserSelect: 'none',
position: 'absolute',
width: '100%',
left: 0,
From 699bae67203e0974720c86f7e11c906d14edc2bc Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Tue, 17 Jun 2025 20:27:11 -0500
Subject: [PATCH 55/73] You basically should never be copying from this anyway
---
src/app/components/virtualizer/style.css.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/app/components/virtualizer/style.css.ts b/src/app/components/virtualizer/style.css.ts
index 1be1fac6..c93636a8 100644
--- a/src/app/components/virtualizer/style.css.ts
+++ b/src/app/components/virtualizer/style.css.ts
@@ -5,6 +5,8 @@ export const VirtualTile = style([
DefaultReset,
{
WebkitUserSelect: 'none',
+ MozUserSelect: 'none',
+ userSelect: 'none',
position: 'absolute',
width: '100%',
left: 0,
From 46958d88c98ec0f8b4ee31b116eabd153d88000a Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 01:11:11 -0500
Subject: [PATCH 56/73] add imports
---
src/app/pages/client/sidebar/SpaceTabs.tsx | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx
index 011741ee..68445c69 100644
--- a/src/app/pages/client/sidebar/SpaceTabs.tsx
+++ b/src/app/pages/client/sidebar/SpaceTabs.tsx
@@ -39,6 +39,8 @@ import {
import { autoScrollForElements } from '@atlaskit/pragmatic-drag-and-drop-auto-scroll/element';
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
import FocusTrap from 'focus-trap-react';
+import { useLongPress } from 'use-long-press';
+import { createPortal } from 'react-dom';
import {
useOrphanSpaces,
useRecursiveChildScopeFactory,
@@ -91,6 +93,7 @@ import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { useSetting } from '../../../state/hooks/settings';
import { settingsAtom } from '../../../state/settings';
import { useOpenSpaceSettings } from '../../../state/hooks/spaceSettings';
+import { MobileContextMenu } from '../../../molecules/mobile-context-menu/MobileContextMenu';
type SpaceMenuProps = {
room: Room;
From 37638ff81826f091be6d375bd4e3931da2f51361 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 01:11:25 -0500
Subject: [PATCH 57/73] remove width cap to allow scaling in mobile
---
src/app/pages/client/sidebar/SpaceTabs.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx
index 68445c69..305bfa64 100644
--- a/src/app/pages/client/sidebar/SpaceTabs.tsx
+++ b/src/app/pages/client/sidebar/SpaceTabs.tsx
@@ -145,7 +145,7 @@ const SpaceMenu = forwardRef(
};
return (
-
+
Date: Wed, 18 Jun 2025 01:12:17 -0500
Subject: [PATCH 58/73] update useDraggableItem to allow function passing to
impact drag behavior
---
src/app/pages/client/sidebar/SpaceTabs.tsx | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx
index 305bfa64..32c65bcd 100644
--- a/src/app/pages/client/sidebar/SpaceTabs.tsx
+++ b/src/app/pages/client/sidebar/SpaceTabs.tsx
@@ -224,7 +224,8 @@ const useDraggableItem = (
item: SidebarDraggable,
targetRef: RefObject,
onDragging: (item?: SidebarDraggable) => void,
- dragHandleRef?: RefObject
+ dragHandleRef?: RefObject,
+ onActualDragStart?: () => void
): boolean => {
const [dragging, setDragging] = useState(false);
@@ -241,13 +242,16 @@ const useDraggableItem = (
onDragStart: () => {
setDragging(true);
onDragging?.(item);
+ if (typeof onActualDragStart === 'function') {
+ onActualDragStart();
+ }
},
onDrop: () => {
setDragging(false);
onDragging?.(undefined);
},
});
- }, [targetRef, dragHandleRef, item, onDragging]);
+ }, [targetRef, dragHandleRef, item, onDragging, onActualDragStart]);
return dragging;
};
From 81a4113ec35f12454cdcf094d2c6b723b26287a7 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 01:12:30 -0500
Subject: [PATCH 59/73] add our mobile checks
---
src/app/pages/client/sidebar/SpaceTabs.tsx | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx
index 32c65bcd..71a87f9f 100644
--- a/src/app/pages/client/sidebar/SpaceTabs.tsx
+++ b/src/app/pages/client/sidebar/SpaceTabs.tsx
@@ -395,6 +395,11 @@ function SpaceTab({
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const targetRef = useRef(null);
+ const screenSize = useScreenSizeContext();
+ const isMobile = screenSize === ScreenSize.Mobile;
+ const [isMobileSheetOpen, setMobileSheetOpen] = useState(false);
+
+ const [menuAnchor, setMenuAnchor] = useState();
const spaceDraggable: SidebarDraggable = useMemo(
() =>
From 868e4a8a90a81a6ab268a5e1dbddc5fd746bdc36 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 01:13:25 -0500
Subject: [PATCH 60/73] swap to set useDraggableItem as a variable so we can
refer to it in our long press
---
src/app/pages/client/sidebar/SpaceTabs.tsx | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx
index 71a87f9f..32a6debd 100644
--- a/src/app/pages/client/sidebar/SpaceTabs.tsx
+++ b/src/app/pages/client/sidebar/SpaceTabs.tsx
@@ -412,7 +412,21 @@ function SpaceTab({
[folder, space]
);
- useDraggableItem(spaceDraggable, targetRef, onDragging);
+ const handleDragStart = useCallback(() => {
+ if (isMobileSheetOpen) {
+ setMenuAnchor(undefined);
+ setMobileSheetOpen(false);
+ }
+ }, [isMobileSheetOpen]);
+
+ const isDragging = useDraggableItem(
+ spaceDraggable,
+ targetRef,
+ onDragging,
+ undefined,
+ handleDragStart
+ );
+
const dropState = useDropTarget(spaceDraggable, targetRef);
const dropType = dropState?.type;
From 6c6fd89911e1e8bfe193ceea6891fe3b088a75d7 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 01:13:33 -0500
Subject: [PATCH 61/73] move menu anchor
---
src/app/pages/client/sidebar/SpaceTabs.tsx | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx
index 32a6debd..01b00c45 100644
--- a/src/app/pages/client/sidebar/SpaceTabs.tsx
+++ b/src/app/pages/client/sidebar/SpaceTabs.tsx
@@ -430,8 +430,6 @@ function SpaceTab({
const dropState = useDropTarget(spaceDraggable, targetRef);
const dropType = dropState?.type;
- const [menuAnchor, setMenuAnchor] = useState();
-
const handleContextMenu: MouseEventHandler = (evt) => {
evt.preventDefault();
const cords = evt.currentTarget.getBoundingClientRect();
From 550534ae31a3802f487b56687d65f29da9241a67 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 01:13:39 -0500
Subject: [PATCH 62/73] add long press
---
src/app/pages/client/sidebar/SpaceTabs.tsx | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx
index 01b00c45..2f3055ce 100644
--- a/src/app/pages/client/sidebar/SpaceTabs.tsx
+++ b/src/app/pages/client/sidebar/SpaceTabs.tsx
@@ -439,6 +439,18 @@ function SpaceTab({
});
};
+ const longPressBinder = useLongPress(
+ () => {
+ if (isMobile && !isDragging) {
+ setMobileSheetOpen(true);
+ }
+ },
+ {
+ threshold: 400,
+ cancelOnMovement: true,
+ }
+ );
+
return (
{(unread) => (
From 7cd2a4a143cd4532cf505b072971f0568686e3a4 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 01:13:50 -0500
Subject: [PATCH 63/73] add prop for listening to long presses
---
src/app/pages/client/sidebar/SpaceTabs.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx
index 2f3055ce..5ebbfbab 100644
--- a/src/app/pages/client/sidebar/SpaceTabs.tsx
+++ b/src/app/pages/client/sidebar/SpaceTabs.tsx
@@ -462,6 +462,7 @@ function SpaceTab({
data-drop-above={dropType === 'reorder-above'}
data-drop-below={dropType === 'reorder-below'}
data-inside-folder={!!folder}
+ {...(isMobile ? longPressBinder() : {})}
>
{(triggerRef) => (
From 69c916f1b89207f11065ec44939ef8b25ad4d1b6 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 01:14:13 -0500
Subject: [PATCH 64/73] add mobile context menu
---
src/app/pages/client/sidebar/SpaceTabs.tsx | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx
index 5ebbfbab..a66a9d41 100644
--- a/src/app/pages/client/sidebar/SpaceTabs.tsx
+++ b/src/app/pages/client/sidebar/SpaceTabs.tsx
@@ -516,6 +516,21 @@ function SpaceTab({
}
/>
)}
+ {createPortal(
+ {
+ setMobileSheetOpen(false);
+ }}
+ isOpen={isMobileSheetOpen}
+ >
+ setMobileSheetOpen(false)}
+ onUnpin={onUnpin}
+ />
+ ,
+ document.body
+ )}
)}
From 94eacffd99baf74a9d4e0c6130521a7a30422b86 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 01:31:29 -0500
Subject: [PATCH 65/73] no highlighting on elements that should never be
highlighted
---
src/app/components/sidebar/Sidebar.css.ts | 25 ++++++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/src/app/components/sidebar/Sidebar.css.ts b/src/app/components/sidebar/Sidebar.css.ts
index c3686223..59856a98 100644
--- a/src/app/components/sidebar/Sidebar.css.ts
+++ b/src/app/components/sidebar/Sidebar.css.ts
@@ -9,7 +9,9 @@ export const Sidebar = style([
width: toRem(66),
backgroundColor: color.Background.Container,
borderRight: `${config.borderWidth.B300} solid ${color.Background.ContainerLine}`,
-
+ MozUserSelect: 'none',
+ WebkitUserSelect: 'none',
+ userSelect: 'none',
display: 'flex',
flexDirection: 'column',
color: color.Background.OnContainer,
@@ -19,6 +21,9 @@ export const Sidebar = style([
export const SidebarStack = style([
DefaultReset,
{
+ MozUserSelect: 'none',
+ WebkitUserSelect: 'none',
+ userSelect: 'none',
width: '100%',
display: 'flex',
flexDirection: 'column',
@@ -68,6 +73,9 @@ export const SidebarItem = recipe({
base: [
DefaultReset,
{
+ MozUserSelect: 'none',
+ WebkitUserSelect: 'none',
+ userSelect: 'none',
minWidth: toRem(42),
display: 'flex',
alignItems: 'center',
@@ -101,6 +109,9 @@ export const SidebarItem = recipe({
],
variants: {
active: {
+ MozUserSelect: 'none',
+ WebkitUserSelect: 'none',
+ userSelect: 'none',
true: {
selectors: {
'&::before': {
@@ -148,6 +159,9 @@ export type SidebarItemBadgeVariants = RecipeVariants;
export const SidebarAvatar = recipe({
base: [
{
+ MozUserSelect: 'none',
+ WebkitUserSelect: 'none',
+ userSelect: 'none',
selectors: {
'button&': {
cursor: 'pointer',
@@ -158,6 +172,9 @@ export const SidebarAvatar = recipe({
variants: {
size: {
'200': {
+ MozUserSelect: 'none',
+ WebkitUserSelect: 'none',
+ userSelect: 'none',
width: toRem(16),
height: toRem(16),
fontSize: toRem(10),
@@ -165,10 +182,16 @@ export const SidebarAvatar = recipe({
letterSpacing: config.letterSpacing.T200,
},
'300': {
+ MozUserSelect: 'none',
+ WebkitUserSelect: 'none',
+ userSelect: 'none',
width: toRem(34),
height: toRem(34),
},
'400': {
+ MozUserSelect: 'none',
+ WebkitUserSelect: 'none',
+ userSelect: 'none',
width: toRem(42),
height: toRem(42),
},
From 1e4fccb826e1279f70f90c6e8ac55866655cb3dc Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 19:57:05 -0500
Subject: [PATCH 66/73] Fixes the drag jitter on Android and only the grabber
working after the virtual keyboard appears once
---
.../mobile-context-menu/MobileContextMenu.jsx | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx b/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx
index e2915e7c..41ef7a3c 100644
--- a/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx
+++ b/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx
@@ -5,6 +5,14 @@ import './MobileContextMenu.scss';
export function MobileContextMenu({ isOpen, onClose, children }) {
const { innerHeight } = window;
+ useEffect(() => {
+ if (isOpen) {
+ document.body.style.overscrollBehavior = 'contain';
+ }
+ return () => {
+ document.body.style.overscrollBehavior = 'auto';
+ };
+ }, [isOpen]);
const [{ y }, api] = useSpring(() => ({
y: innerHeight,
@@ -49,11 +57,13 @@ export function MobileContextMenu({ isOpen, onClose, children }) {
{...bind()}
style={{
y,
- touchAction: 'pan-y',
+ touchAction: 'none',
}}
>
- {children}
+
+ {children}
+
>
);
From 791381c978331e760b4ad7e2275343312b2aed95 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 20:03:11 -0500
Subject: [PATCH 67/73] swallow events under the menu if we actually drag
---
src/app/molecules/mobile-context-menu/MobileContextMenu.jsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx b/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx
index 41ef7a3c..fd809fa0 100644
--- a/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx
+++ b/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx
@@ -24,9 +24,11 @@ export function MobileContextMenu({ isOpen, onClose, children }) {
}, [api, innerHeight, isOpen]);
const bind = useDrag(
- ({ last, movement: [, my], velocities: [, vy] }) => {
+ ({ last, movement: [, my], event }) => {
if (last) {
if (my > innerHeight / 4) {
+ event.preventDefault();
+ event.stopPropagation();
onClose();
} else {
api.start({ y: 0 });
From 9e7b37dcd1f14f30b53f85ba103db30567480905 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 20:53:10 -0500
Subject: [PATCH 68/73] Clear the UI up to accurate reflect the thresholds
---
.../message/behavior/DraggableMessage.tsx | 24 +++++++++----------
1 file changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/app/components/message/behavior/DraggableMessage.tsx b/src/app/components/message/behavior/DraggableMessage.tsx
index 0d303277..31394d4e 100644
--- a/src/app/components/message/behavior/DraggableMessage.tsx
+++ b/src/app/components/message/behavior/DraggableMessage.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState } from 'react';
import { useSpring, animated } from '@react-spring/web';
import { useDrag } from 'react-use-gesture';
import { Icon, Icons } from 'folds';
@@ -23,19 +23,20 @@ export function DraggableMessage({
const isMobile = screenSize === ScreenSize.Mobile;
const canEdit = mx.getUserId() === event.getSender();
- const REPLY_THRESHOLD = 80;
- const EDIT_THRESHOLD = canEdit ? 250 : Infinity;
+ const REPLY_THRESHOLD = 30;
+ const EDIT_THRESHOLD = canEdit ? 180 : Infinity;
- const [{ x, replyOpacity, editOpacity, iconScale }, api] = useSpring(() => ({
+ const [isEditVisible, setEditVisible] = useState(false);
+
+ const [{ x, replyOpacity, iconScale }, api] = useSpring(() => ({
x: 0,
replyOpacity: 0,
- editOpacity: 0,
iconScale: 0.5,
config: { tension: 250, friction: 25 },
}));
const bind = useDrag(
- ({ down, movement: [mvx], vxvy: [vx] }) => {
+ ({ down, movement: [mvx] }) => {
if (!down) {
const finalDistance = Math.abs(mvx);
@@ -49,19 +50,18 @@ export function DraggableMessage({
const xTarget = down ? Math.min(0, mvx) : 0;
const distance = Math.abs(xTarget);
+ setEditVisible(canEdit && distance >= EDIT_THRESHOLD);
+
let newReplyOpacity = 0;
- let newEditOpacity = 0;
let newScale = 1.0;
- if (canEdit && distance > REPLY_THRESHOLD) {
+ if (canEdit && (distance < REPLY_THRESHOLD || distance >= EDIT_THRESHOLD)) {
newReplyOpacity = 0;
- newEditOpacity = 1;
if (down && distance > EDIT_THRESHOLD) {
newScale = 1.1;
}
} else {
newReplyOpacity = 1;
- newEditOpacity = 0;
newScale = 0.5 + (distance / REPLY_THRESHOLD) * 0.5;
}
@@ -72,7 +72,6 @@ export function DraggableMessage({
api.start({
x: xTarget,
replyOpacity: newReplyOpacity,
- editOpacity: newEditOpacity,
iconScale: newScale,
});
},
@@ -82,6 +81,7 @@ export function DraggableMessage({
threshold: 10,
}
);
+
if (isMobile) {
return (
@@ -99,7 +99,7 @@ export function DraggableMessage({
`scale(${s})`),
}}
>
From a6898e1484e8947ba2f7503bb510c9f7207a0f91 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 21:49:32 -0500
Subject: [PATCH 69/73] Fix the crash caused by the emoji board load
---
.../room/message/MessageOptionsMenu.tsx | 33 ++++++++++---------
1 file changed, 17 insertions(+), 16 deletions(-)
diff --git a/src/app/features/room/message/MessageOptionsMenu.tsx b/src/app/features/room/message/MessageOptionsMenu.tsx
index 2fc508bf..c5f85ada 100644
--- a/src/app/features/room/message/MessageOptionsMenu.tsx
+++ b/src/app/features/room/message/MessageOptionsMenu.tsx
@@ -197,7 +197,6 @@ export function MessageOptionsMenu({
}: ExtendedOptionsProps) {
const screenSize = useScreenSizeContext();
const isMobile = screenSize === ScreenSize.Mobile;
- const [view, setView] = useState('options');
const eventId = mEvent.getId();
if (!eventId) return null;
@@ -247,7 +246,7 @@ export function MessageOptionsMenu({
}}
isOpen={isEmojiBoardOpen}
>
- Loading}>
+ }>
{
- onReactionToggle(eventId, key);
- setEmojiBoardAnchor(undefined);
- }}
- onCustomEmojiSelect={(mxc, shortcode) => {
- onReactionToggle(eventId, mxc, shortcode);
- setEmojiBoardAnchor(undefined);
- }}
- requestClose={() => setEmojiBoardAnchor(undefined)}
- />
+ }>
+ {
+ onReactionToggle(eventId, key);
+ setEmojiBoardAnchor(undefined);
+ }}
+ onCustomEmojiSelect={(mxc, shortcode) => {
+ onReactionToggle(eventId, mxc, shortcode);
+ setEmojiBoardAnchor(undefined);
+ }}
+ requestClose={() => setEmojiBoardAnchor(undefined)}
+ />
+
}
>
Date: Wed, 18 Jun 2025 21:56:28 -0500
Subject: [PATCH 70/73] Add bounds to prevent swiping the other direction for
the same effect without visuals
---
src/app/components/message/behavior/DraggableMessage.tsx | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/app/components/message/behavior/DraggableMessage.tsx b/src/app/components/message/behavior/DraggableMessage.tsx
index 31394d4e..3bd035a1 100644
--- a/src/app/components/message/behavior/DraggableMessage.tsx
+++ b/src/app/components/message/behavior/DraggableMessage.tsx
@@ -47,7 +47,7 @@ export function DraggableMessage({
}
}
- const xTarget = down ? Math.min(0, mvx) : 0;
+ const xTarget = down ? mvx : 0;
const distance = Math.abs(xTarget);
setEditVisible(canEdit && distance >= EDIT_THRESHOLD);
@@ -79,6 +79,7 @@ export function DraggableMessage({
axis: 'x',
filterTaps: true,
threshold: 10,
+ bounds: { right: 0 },
}
);
From 622521ca0aa693300548c8b10fa241d1b9f6d201 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Wed, 18 Jun 2025 21:58:18 -0500
Subject: [PATCH 71/73] bump the reply threshold
---
src/app/components/message/behavior/DraggableMessage.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/app/components/message/behavior/DraggableMessage.tsx b/src/app/components/message/behavior/DraggableMessage.tsx
index 3bd035a1..2a1c1588 100644
--- a/src/app/components/message/behavior/DraggableMessage.tsx
+++ b/src/app/components/message/behavior/DraggableMessage.tsx
@@ -23,7 +23,7 @@ export function DraggableMessage({
const isMobile = screenSize === ScreenSize.Mobile;
const canEdit = mx.getUserId() === event.getSender();
- const REPLY_THRESHOLD = 30;
+ const REPLY_THRESHOLD = 50;
const EDIT_THRESHOLD = canEdit ? 180 : Infinity;
const [isEditVisible, setEditVisible] = useState(false);
@@ -55,7 +55,7 @@ export function DraggableMessage({
let newReplyOpacity = 0;
let newScale = 1.0;
- if (canEdit && (distance < REPLY_THRESHOLD || distance >= EDIT_THRESHOLD)) {
+ if (canEdit && (distance <= REPLY_THRESHOLD || distance >= EDIT_THRESHOLD)) {
newReplyOpacity = 0;
if (down && distance > EDIT_THRESHOLD) {
newScale = 1.1;
From 4963142ed4d76afb3ea4c79b864edacf1839b982 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 22 Jun 2025 03:35:21 -0500
Subject: [PATCH 72/73] Prevent the desktop context menu from appearing on
mobile
---
src/app/pages/client/sidebar/SpaceTabs.tsx | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/app/pages/client/sidebar/SpaceTabs.tsx b/src/app/pages/client/sidebar/SpaceTabs.tsx
index a66a9d41..98f4d600 100644
--- a/src/app/pages/client/sidebar/SpaceTabs.tsx
+++ b/src/app/pages/client/sidebar/SpaceTabs.tsx
@@ -433,10 +433,12 @@ function SpaceTab({
const handleContextMenu: MouseEventHandler = (evt) => {
evt.preventDefault();
const cords = evt.currentTarget.getBoundingClientRect();
- setMenuAnchor((currentState) => {
- if (currentState) return undefined;
- return cords;
- });
+ if (!isMobile) {
+ setMenuAnchor((currentState) => {
+ if (currentState) return undefined;
+ return cords;
+ });
+ }
};
const longPressBinder = useLongPress(
From a4c5d1a6f91a27e69ee87dff5a57201adec4be99 Mon Sep 17 00:00:00 2001
From: Gigiaj
Date: Sun, 22 Jun 2025 03:35:58 -0500
Subject: [PATCH 73/73] Completely revise the animations for the context menu
to be compatible with Firefox for Android (worked everywhere else amusingly)
---
.../mobile-context-menu/MobileContextMenu.jsx | 67 +++++++++++--------
.../MobileContextMenu.scss | 39 +++++++----
2 files changed, 67 insertions(+), 39 deletions(-)
diff --git a/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx b/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx
index fd809fa0..b9beda87 100644
--- a/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx
+++ b/src/app/molecules/mobile-context-menu/MobileContextMenu.jsx
@@ -1,10 +1,21 @@
-import React, { useEffect } from 'react';
-import { useSpring, animated } from '@react-spring/web';
+import React, { useState, useEffect } from 'react';
import { useDrag } from 'react-use-gesture';
import './MobileContextMenu.scss';
export function MobileContextMenu({ isOpen, onClose, children }) {
- const { innerHeight } = window;
+ const getInnerHeight = () => (typeof window !== 'undefined' ? window.innerHeight : 0);
+ const [y, setY] = useState(getInnerHeight());
+ const [isDragging, setIsDragging] = useState(false);
+
+ useEffect(() => {
+ const timer = setTimeout(() => {
+ setY(isOpen ? 0 : getInnerHeight());
+ }, 10);
+
+ return () => clearTimeout(timer);
+ }, [isOpen]);
+
+
useEffect(() => {
if (isOpen) {
document.body.style.overscrollBehavior = 'contain';
@@ -14,51 +25,53 @@ export function MobileContextMenu({ isOpen, onClose, children }) {
};
}, [isOpen]);
- const [{ y }, api] = useSpring(() => ({
- y: innerHeight,
- config: { tension: 250, friction: 25 },
- }));
-
- useEffect(() => {
- api.start({ y: isOpen ? 0 : innerHeight });
- }, [api, innerHeight, isOpen]);
-
const bind = useDrag(
- ({ last, movement: [, my], event }) => {
+ ({ last, movement: [, my], down }) => {
+ if (down && !isDragging) {
+ setIsDragging(true);
+ }
+
+ const newY = Math.max(my, 0);
+ setY(newY);
+
if (last) {
- if (my > innerHeight / 4) {
- event.preventDefault();
- event.stopPropagation();
+ setIsDragging(false);
+ if (my > getInnerHeight() / 4) {
onClose();
} else {
- api.start({ y: 0 });
+ setY(0);
}
- } else {
- api.start({ y: Math.max(my, 0), immediate: true });
}
},
{
- from: () => [0, y.get()],
+ from: () => [0, y],
filterTaps: true,
bounds: { top: 0 },
rubberband: true,
}
);
- if (!isOpen) return null;
+
+ if (!isOpen && y >= getInnerHeight()) return null;
+ const containerClasses = [
+ 'bottom-sheet-container',
+ !isDragging ? 'is-transitioning' : '',
+ ].join(' ');
+
+ const backdropOpacity = y > 0 ? 1 - y / getInnerHeight() : 1;
return (
<>
-
-
@@ -66,7 +79,7 @@ export function MobileContextMenu({ isOpen, onClose, children }) {
{children}
-
+
>
);
}
diff --git a/src/app/molecules/mobile-context-menu/MobileContextMenu.scss b/src/app/molecules/mobile-context-menu/MobileContextMenu.scss
index f42e1f75..326385da 100644
--- a/src/app/molecules/mobile-context-menu/MobileContextMenu.scss
+++ b/src/app/molecules/mobile-context-menu/MobileContextMenu.scss
@@ -1,34 +1,49 @@
.bottom-sheet-backdrop {
position: fixed;
- inset: 0;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
background-color: rgba(0, 0, 0, 0.5);
- z-index: 1000;
- touch-action: none;
+ // The backdrop fade will also be smoother with a transition
+ transition: opacity 300ms ease-out;
+ z-index: 999;
}
.bottom-sheet-container {
position: fixed;
- left: 0;
- right: 0;
bottom: 0;
- z-index: 1001;
+ left: 0;
+ width: 100%;
+ // Set transform from the component's style prop
+ // The 'will-change' property is a performance hint for the browser
+ will-change: transform;
+ z-index: 1000;
+
+ // Your existing styles for the sheet itself
+ border-top-left-radius: 16px;
+ border-top-right-radius: 16px;
+ box-shadow: 0 -2px A10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
- max-height: 90vh;
- border-radius: 16px 16px 0 0;
- box-shadow: 0px -4px B16px rgba(0, 0, 0, 0.15);
+
+ // This is the magic: apply a transition only when this class is present
+ &.is-transitioning {
+ transition: transform 300ms ease-out;
+ }
}
+// Your other styles remain the same
.bottom-sheet-grabber {
- flex-shrink: 0;
width: 40px;
height: 5px;
- border-radius: 2.5px;
background-color: #ccc;
+ border-radius: 2.5px;
margin: 8px auto;
+ cursor: grab;
}
.bottom-sheet-content {
+ padding: 16px;
overflow-y: auto;
- padding: 0 1rem 1rem 1rem;
}