From a8f5a6c2f4eef681cd3d271b3d072ba8912fa8e0 Mon Sep 17 00:00:00 2001
From: Krishan <33421343+kfiven@users.noreply.github.com>
Date: Thu, 1 Aug 2024 23:42:45 +1000
Subject: [PATCH 01/14] update self deploy instructions after react router
(#1859)
* update self deploy instructions after react router
* List the alternative
* docs to deploy on subdir
---
README.md | 26 ++++++++++++++------------
1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/README.md b/README.md
index 5b54f7a..79e8ccc 100644
--- a/README.md
+++ b/README.md
@@ -19,22 +19,24 @@ A Matrix client focusing primarily on simple, elegant and secure interface. The
## Getting started
-Web app is available at https://app.cinny.in and gets updated on each new release. The `dev` branch is continuously deployed at https://dev.cinny.in but keep in mind that it could have things broken.
+* Web app is available at https://app.cinny.in and gets updated on each new release. The `dev` branch is continuously deployed at https://dev.cinny.in but keep in mind that it could have things broken.
-You can also download our desktop app from [cinny-desktop repository](https://github.com/cinnyapp/cinny-desktop).
+* You can also download our desktop app from [cinny-desktop repository](https://github.com/cinnyapp/cinny-desktop).
-To host Cinny on your own, download tarball of the app from [GitHub release](https://github.com/cinnyapp/cinny/releases/latest).
+* To host Cinny on your own, download tarball of the app from [GitHub release](https://github.com/cinnyapp/cinny/releases/latest).
You can serve the application with a webserver of your choice by simply copying `dist/` directory to the webroot.
-To set default Homeserver on login and register page, place a customized [`config.json`](config.json) in webroot of your choice.
+To set default Homeserver on login, register and Explore Community page, place a customized [`config.json`](config.json) in webroot of your choice.
+You will also need to setup redirects to serve the assests. An example setting of redirects for netlify is done in [`netlify.toml`](netlify.toml). You can also set `hashRouter.enabled = true` in [`config.json`](config.json) if you have trouble setting redirects.
+To deploy on subdirectory, you need to rebuild the app youself after updating the `base` path in [`build.confg.ts`](build.confg.ts). For example, if you want to deploy on `https://cinny.in/app`, then change `base: '/app'`.
-Alternatively you can just pull the [DockerHub image](https://hub.docker.com/r/ajbura/cinny) by:
-```
-docker pull ajbura/cinny
-```
-or [ghcr image](https://github.com/cinnyapp/cinny/pkgs/container/cinny) by:
-```
-docker pull ghcr.io/cinnyapp/cinny:latest
-```
+* Alternatively you can just pull the [DockerHub image](https://hub.docker.com/r/ajbura/cinny) by:
+ ```
+ docker pull ajbura/cinny
+ ```
+ or [ghcr image](https://github.com/cinnyapp/cinny/pkgs/container/cinny) by:
+ ```
+ docker pull ghcr.io/cinnyapp/cinny:latest
+ ```
PGP Public Key to verify tarball
From c62050445bb1f5ceb790871353c46d231322d037 Mon Sep 17 00:00:00 2001
From: Krishan <33421343+kfiven@users.noreply.github.com>
Date: Thu, 1 Aug 2024 23:45:22 +1000
Subject: [PATCH 02/14] Fix typo in readme
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 79e8ccc..5cee6fa 100644
--- a/README.md
+++ b/README.md
@@ -27,7 +27,7 @@ A Matrix client focusing primarily on simple, elegant and secure interface. The
You can serve the application with a webserver of your choice by simply copying `dist/` directory to the webroot.
To set default Homeserver on login, register and Explore Community page, place a customized [`config.json`](config.json) in webroot of your choice.
You will also need to setup redirects to serve the assests. An example setting of redirects for netlify is done in [`netlify.toml`](netlify.toml). You can also set `hashRouter.enabled = true` in [`config.json`](config.json) if you have trouble setting redirects.
-To deploy on subdirectory, you need to rebuild the app youself after updating the `base` path in [`build.confg.ts`](build.confg.ts). For example, if you want to deploy on `https://cinny.in/app`, then change `base: '/app'`.
+To deploy on subdirectory, you need to rebuild the app youself after updating the `base` path in [`build.config.ts`](build.config.ts). For example, if you want to deploy on `https://cinny.in/app`, then change `base: '/app'`.
* Alternatively you can just pull the [DockerHub image](https://hub.docker.com/r/ajbura/cinny) by:
```
From 9cb5c70d5174332eb306b3792a97f9ee7c126ace Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Sat, 3 Aug 2024 19:17:53 +0530
Subject: [PATCH 03/14] add back btn for mobile view (#1861)
---
src/app/components/BackRouteHandler.tsx | 86 +++++++++++++++
src/app/components/page/Page.tsx | 20 ++--
src/app/components/page/style.css.ts | 19 +++-
.../JoinBeforeNavigate.tsx | 28 +++--
src/app/features/lobby/LobbyHeader.tsx | 101 ++++++++++++------
src/app/features/room/RoomViewHeader.tsx | 40 +++++--
src/app/pages/client/explore/Featured.tsx | 19 +++-
src/app/pages/client/explore/Server.tsx | 36 +++++--
src/app/pages/client/home/Search.tsx | 31 ++++--
src/app/pages/client/inbox/Invites.tsx | 30 ++++--
src/app/pages/client/inbox/Notifications.tsx | 29 +++--
src/app/pages/client/space/Search.tsx | 31 ++++--
12 files changed, 370 insertions(+), 100 deletions(-)
create mode 100644 src/app/components/BackRouteHandler.tsx
diff --git a/src/app/components/BackRouteHandler.tsx b/src/app/components/BackRouteHandler.tsx
new file mode 100644
index 0000000..fa3d759
--- /dev/null
+++ b/src/app/components/BackRouteHandler.tsx
@@ -0,0 +1,86 @@
+import { ReactNode, useCallback } from 'react';
+import { matchPath, useLocation, useNavigate } from 'react-router-dom';
+import {
+ getDirectPath,
+ getExplorePath,
+ getHomePath,
+ getInboxPath,
+ getSpacePath,
+} from '../pages/pathUtils';
+import { DIRECT_PATH, EXPLORE_PATH, HOME_PATH, INBOX_PATH, SPACE_PATH } from '../pages/paths';
+
+type BackRouteHandlerProps = {
+ children: (onBack: () => void) => ReactNode;
+};
+export function BackRouteHandler({ children }: BackRouteHandlerProps) {
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ const goBack = useCallback(() => {
+ if (
+ matchPath(
+ {
+ path: HOME_PATH,
+ caseSensitive: true,
+ end: false,
+ },
+ location.pathname
+ )
+ ) {
+ navigate(getHomePath());
+ return;
+ }
+ if (
+ matchPath(
+ {
+ path: DIRECT_PATH,
+ caseSensitive: true,
+ end: false,
+ },
+ location.pathname
+ )
+ ) {
+ navigate(getDirectPath());
+ return;
+ }
+ const spaceMatch = matchPath(
+ {
+ path: SPACE_PATH,
+ caseSensitive: true,
+ end: false,
+ },
+ location.pathname
+ );
+ if (spaceMatch?.params.spaceIdOrAlias) {
+ navigate(getSpacePath(spaceMatch.params.spaceIdOrAlias));
+ return;
+ }
+ if (
+ matchPath(
+ {
+ path: EXPLORE_PATH,
+ caseSensitive: true,
+ end: false,
+ },
+ location.pathname
+ )
+ ) {
+ navigate(getExplorePath());
+ return;
+ }
+ if (
+ matchPath(
+ {
+ path: INBOX_PATH,
+ caseSensitive: true,
+ end: false,
+ },
+ location.pathname
+ )
+ ) {
+ navigate(getInboxPath());
+ }
+ }, [navigate, location]);
+
+ return children(goBack);
+}
diff --git a/src/app/components/page/Page.tsx b/src/app/components/page/Page.tsx
index 4ccb1ec..a8b9ea0 100644
--- a/src/app/components/page/Page.tsx
+++ b/src/app/components/page/Page.tsx
@@ -87,15 +87,17 @@ export const Page = as<'div'>(({ className, ...props }, ref) => (
/>
));
-export const PageHeader = as<'div'>(({ className, ...props }, ref) => (
-
-));
+export const PageHeader = as<'div', css.PageHeaderVariants>(
+ ({ className, balance, ...props }, ref) => (
+
+ )
+);
export const PageContent = as<'div'>(({ className, ...props }, ref) => (
diff --git a/src/app/components/page/style.css.ts b/src/app/components/page/style.css.ts
index 4807a22..23f2da4 100644
--- a/src/app/components/page/style.css.ts
+++ b/src/app/components/page/style.css.ts
@@ -1,4 +1,5 @@
import { style } from '@vanilla-extract/css';
+import { recipe, RecipeVariants } from '@vanilla-extract/recipes';
import { DefaultReset, color, config, toRem } from 'folds';
export const PageNav = style({
@@ -33,11 +34,21 @@ export const PageNavContent = style({
paddingBottom: config.space.S700,
});
-export const PageHeader = style({
- paddingLeft: config.space.S400,
- paddingRight: config.space.S200,
- borderBottomWidth: config.borderWidth.B300,
+export const PageHeader = recipe({
+ base: {
+ paddingLeft: config.space.S400,
+ paddingRight: config.space.S200,
+ borderBottomWidth: config.borderWidth.B300,
+ },
+ variants: {
+ balance: {
+ true: {
+ paddingLeft: config.space.S200,
+ },
+ },
+ },
});
+export type PageHeaderVariants = RecipeVariants;
export const PageContent = style([
DefaultReset,
diff --git a/src/app/features/join-before-navigate/JoinBeforeNavigate.tsx b/src/app/features/join-before-navigate/JoinBeforeNavigate.tsx
index 1cec659..028cd56 100644
--- a/src/app/features/join-before-navigate/JoinBeforeNavigate.tsx
+++ b/src/app/features/join-before-navigate/JoinBeforeNavigate.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Box, Scroll, Text, toRem } from 'folds';
+import { Box, Icon, IconButton, Icons, Scroll, Text, toRem } from 'folds';
import { useAtomValue } from 'jotai';
import { RoomCard } from '../../components/room-card';
import { RoomTopicViewer } from '../../components/room-topic-viewer';
@@ -8,6 +8,8 @@ import { RoomSummaryLoader } from '../../components/RoomSummaryLoader';
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { allRoomsAtom } from '../../state/room-list/roomList';
+import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../components/BackRouteHandler';
type JoinBeforeNavigateProps = { roomIdOrAlias: string; eventId?: string; viaServers?: string[] };
export function JoinBeforeNavigate({
@@ -18,6 +20,7 @@ export function JoinBeforeNavigate({
const mx = useMatrixClient();
const allRooms = useAtomValue(allRoomsAtom);
const { navigateRoom, navigateSpace } = useRoomNavigate();
+ const screenSize = useScreenSizeContext();
const handleView = (roomId: string) => {
if (mx.getRoom(roomId)?.isSpaceRoom()) {
@@ -29,11 +32,24 @@ export function JoinBeforeNavigate({
return (
-
-
-
- {roomIdOrAlias}
-
+
+
+
+ {screenSize === ScreenSize.Mobile && (
+
+ {(onBack) => (
+
+
+
+ )}
+
+ )}
+
+
+
+ {roomIdOrAlias}
+
+
diff --git a/src/app/features/lobby/LobbyHeader.tsx b/src/app/features/lobby/LobbyHeader.tsx
index e01d3ad..fa415bd 100644
--- a/src/app/features/lobby/LobbyHeader.tsx
+++ b/src/app/features/lobby/LobbyHeader.tsx
@@ -31,6 +31,8 @@ import { IPowerLevels, usePowerLevelsAPI } from '../../hooks/usePowerLevels';
import { UseStateProvider } from '../../components/UseStateProvider';
import { LeaveSpacePrompt } from '../../components/leave-space-prompt';
import { stopPropagation } from '../../utils/keyboard';
+import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../components/BackRouteHandler';
type LobbyMenuProps = {
roomId: string;
@@ -123,6 +125,7 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
const space = useSpace();
const setPeopleDrawer = useSetSetting(settingsAtom, 'isPeopleDrawer');
const [menuAnchor, setMenuAnchor] = useState();
+ const screenSize = useScreenSizeContext();
const name = useRoomName(space);
const avatarMxc = useRoomAvatar(space);
@@ -133,42 +136,72 @@ export function LobbyHeader({ showProfile, powerLevels }: LobbyHeaderProps) {
};
return (
-
+
-
-
- {showProfile && (
- <>
-
- {nameInitials(name)}}
- />
-
-
- {name}
-
- >
+ {screenSize === ScreenSize.Mobile ? (
+ <>
+
+
+ {(onBack) => (
+
+
+
+ )}
+
+
+
+ {showProfile && (
+
+ {name}
+
+ )}
+
+ >
+ ) : (
+ <>
+
+
+ {showProfile && (
+ <>
+
+ {nameInitials(name)}}
+ />
+
+
+ {name}
+
+ >
+ )}
+
+ >
+ )}
+
+ {screenSize !== ScreenSize.Mobile && (
+
+ Members
+
+ }
+ >
+ {(triggerRef) => (
+ setPeopleDrawer((drawer) => !drawer)}>
+
+
+ )}
+
)}
-
-
-
- Members
-
- }
- >
- {(triggerRef) => (
- setPeopleDrawer((drawer) => !drawer)}>
-
-
- )}
-
+
+ {screenSize === ScreenSize.Mobile && (
+
+ {(onBack) => (
+
+
+
+
+
+ )}
+
+ )}
-
- (
-
- )}
- />
-
+ {screenSize !== ScreenSize.Mobile && (
+
+ (
+
+ )}
+ />
+
+ )}
{name}
diff --git a/src/app/pages/client/explore/Featured.tsx b/src/app/pages/client/explore/Featured.tsx
index 4838127..f056cbb 100644
--- a/src/app/pages/client/explore/Featured.tsx
+++ b/src/app/pages/client/explore/Featured.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { Box, Icon, Icons, Scroll, Text } from 'folds';
+import { Box, Icon, IconButton, Icons, Scroll, Text } from 'folds';
import { useAtomValue } from 'jotai';
import { useClientConfig } from '../../../hooks/useClientConfig';
import { RoomCard, RoomCardGrid } from '../../../components/room-card';
@@ -9,21 +9,38 @@ import {
Page,
PageContent,
PageContentCenter,
+ PageHeader,
PageHero,
PageHeroSection,
} from '../../../components/page';
import { RoomTopicViewer } from '../../../components/room-topic-viewer';
import * as css from './style.css';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../../components/BackRouteHandler';
export function FeaturedRooms() {
const { featuredCommunities } = useClientConfig();
const { rooms, spaces } = featuredCommunities ?? {};
const allRooms = useAtomValue(allRoomsAtom);
+ const screenSize = useScreenSizeContext();
const { navigateSpace, navigateRoom } = useRoomNavigate();
return (
+ {screenSize === ScreenSize.Mobile && (
+
+
+
+ {(onBack) => (
+
+
+
+ )}
+
+
+
+ )}
diff --git a/src/app/pages/client/explore/Server.tsx b/src/app/pages/client/explore/Server.tsx
index 1a81c22..1f493df 100644
--- a/src/app/pages/client/explore/Server.tsx
+++ b/src/app/pages/client/explore/Server.tsx
@@ -13,6 +13,7 @@ import {
Button,
Chip,
Icon,
+ IconButton,
Icons,
Input,
Line,
@@ -42,6 +43,8 @@ import { allRoomsAtom } from '../../../state/room-list/roomList';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { getMxIdServer } from '../../../utils/matrix';
import { stopPropagation } from '../../../utils/keyboard';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../../components/BackRouteHandler';
const useServerSearchParams = (searchParams: URLSearchParams): ExploreServerPathSearchParams =>
useMemo(
@@ -344,6 +347,7 @@ export function PublicRooms() {
const userServer = userId && getMxIdServer(userId);
const allRooms = useAtomValue(allRoomsAtom);
const { navigateSpace, navigateRoom } = useRoomNavigate();
+ const screenSize = useScreenSizeContext();
const [searchParams] = useSearchParams();
const serverSearchParams = useServerSearchParams(searchParams);
@@ -466,7 +470,7 @@ export function PublicRooms() {
return (
-
+
{isSearch ? (
<>
@@ -482,20 +486,34 @@ export function PublicRooms() {
-
+ {screenSize !== ScreenSize.Mobile && }
Search
-
+
>
) : (
-
-
-
- {server}
-
-
+ <>
+
+ {screenSize === ScreenSize.Mobile && (
+
+ {(onBack) => (
+
+
+
+ )}
+
+ )}
+
+
+ {screenSize !== ScreenSize.Mobile && }
+
+ {server}
+
+
+
+ >
)}
diff --git a/src/app/pages/client/home/Search.tsx b/src/app/pages/client/home/Search.tsx
index af7b1eb..d5ddfb7 100644
--- a/src/app/pages/client/home/Search.tsx
+++ b/src/app/pages/client/home/Search.tsx
@@ -1,21 +1,38 @@
import React, { useRef } from 'react';
-import { Box, Icon, Icons, Text, Scroll } from 'folds';
+import { Box, Icon, Icons, Text, Scroll, IconButton } from 'folds';
import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page';
import { MessageSearch } from '../../../features/message-search';
import { useHomeRooms } from './useHomeRooms';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../../components/BackRouteHandler';
export function HomeSearch() {
const scrollRef = useRef(null);
const rooms = useHomeRooms();
+ const screenSize = useScreenSizeContext();
return (
-
-
-
-
- Message Search
-
+
+
+
+ {screenSize === ScreenSize.Mobile && (
+
+ {(onBack) => (
+
+
+
+ )}
+
+ )}
+
+
+ {screenSize !== ScreenSize.Mobile && }
+
+ Message Search
+
+
+
diff --git a/src/app/pages/client/inbox/Invites.tsx b/src/app/pages/client/inbox/Invites.tsx
index 06e5f6c..1899308 100644
--- a/src/app/pages/client/inbox/Invites.tsx
+++ b/src/app/pages/client/inbox/Invites.tsx
@@ -4,6 +4,7 @@ import {
Box,
Button,
Icon,
+ IconButton,
Icons,
Overlay,
OverlayBackdrop,
@@ -39,6 +40,8 @@ import { RoomTopicViewer } from '../../../components/room-topic-viewer';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { useRoomTopic } from '../../../hooks/useRoomMeta';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../../components/BackRouteHandler';
const COMPACT_CARD_WIDTH = 548;
@@ -205,6 +208,7 @@ export function Invites() {
useCallback(() => containerRef.current, []),
useCallback((width) => setCompact(width <= COMPACT_CARD_WIDTH), [])
);
+ const screenSize = useScreenSizeContext();
const { navigateRoom, navigateSpace } = useRoomNavigate();
@@ -225,12 +229,26 @@ export function Invites() {
return (
-
-
-
-
- Invitations
-
+
+
+
+ {screenSize === ScreenSize.Mobile && (
+
+ {(onBack) => (
+
+
+
+ )}
+
+ )}
+
+
+ {screenSize !== ScreenSize.Mobile && }
+
+ Invitations
+
+
+
diff --git a/src/app/pages/client/inbox/Notifications.tsx b/src/app/pages/client/inbox/Notifications.tsx
index 3425b51..6a8160d 100644
--- a/src/app/pages/client/inbox/Notifications.tsx
+++ b/src/app/pages/client/inbox/Notifications.tsx
@@ -78,6 +78,8 @@ import { UserAvatar } from '../../../components/user-avatar';
import { EncryptedContent } from '../../../features/room/message';
import { useMentionClickHandler } from '../../../hooks/useMentionClickHandler';
import { useSpoilerClickHandler } from '../../../hooks/useSpoilerClickHandler';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../../components/BackRouteHandler';
type RoomNotificationsGroup = {
roomId: string;
@@ -484,6 +486,7 @@ export function Notifications() {
const mx = useMatrixClient();
const [mediaAutoLoad] = useSetting(settingsAtom, 'mediaAutoLoad');
const [urlPreview] = useSetting(settingsAtom, 'urlPreview');
+ const screenSize = useScreenSizeContext();
const { navigateRoom } = useRoomNavigate();
const [searchParams, setSearchParams] = useSearchParams();
@@ -549,12 +552,26 @@ export function Notifications() {
return (
-
-
-
-
- Notification Messages
-
+
+
+
+ {screenSize === ScreenSize.Mobile && (
+
+ {(onBack) => (
+
+
+
+ )}
+
+ )}
+
+
+ {screenSize !== ScreenSize.Mobile && }
+
+ Notification Messages
+
+
+
diff --git a/src/app/pages/client/space/Search.tsx b/src/app/pages/client/space/Search.tsx
index 6e7ac57..017262b 100644
--- a/src/app/pages/client/space/Search.tsx
+++ b/src/app/pages/client/space/Search.tsx
@@ -1,5 +1,5 @@
import React, { useRef } from 'react';
-import { Box, Icon, Icons, Text, Scroll } from 'folds';
+import { Box, Icon, Icons, Text, Scroll, IconButton } from 'folds';
import { useAtomValue } from 'jotai';
import { Page, PageContent, PageContentCenter, PageHeader } from '../../../components/page';
import { MessageSearch } from '../../../features/message-search';
@@ -9,11 +9,14 @@ import { allRoomsAtom } from '../../../state/room-list/roomList';
import { mDirectAtom } from '../../../state/mDirectList';
import { roomToParentsAtom } from '../../../state/room/roomToParents';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
+import { ScreenSize, useScreenSizeContext } from '../../../hooks/useScreenSize';
+import { BackRouteHandler } from '../../../components/BackRouteHandler';
export function SpaceSearch() {
const mx = useMatrixClient();
const scrollRef = useRef(null);
const space = useSpace();
+ const screenSize = useScreenSizeContext();
const mDirects = useAtomValue(mDirectAtom);
const roomToParents = useAtomValue(roomToParentsAtom);
@@ -25,12 +28,26 @@ export function SpaceSearch() {
return (
-
-
-
-
- Message Search
-
+
+
+
+ {screenSize === ScreenSize.Mobile && (
+
+ {(onBack) => (
+
+
+
+ )}
+
+ )}
+
+
+ {screenSize !== ScreenSize.Mobile && }
+
+ Message Search
+
+
+
From 681287c46ae6aab6c3a3697a8a062acbebc519c9 Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Sun, 4 Aug 2024 09:49:37 +0530
Subject: [PATCH 04/14] show unverified tab indicator on sidebar (#1862)
---
src/app/hooks/useDeviceList.js | 31 -----------
src/app/hooks/useDeviceList.ts | 35 +++++++++++++
src/app/pages/client/SidebarNav.tsx | 12 ++++-
.../pages/client/sidebar/UnverifiedTab.css.ts | 24 +++++++++
.../pages/client/sidebar/UnverifiedTab.tsx | 51 +++++++++++++++++++
src/app/pages/client/sidebar/index.ts | 1 +
src/util/matrixUtil.js | 2 +-
7 files changed, 123 insertions(+), 33 deletions(-)
delete mode 100644 src/app/hooks/useDeviceList.js
create mode 100644 src/app/hooks/useDeviceList.ts
create mode 100644 src/app/pages/client/sidebar/UnverifiedTab.css.ts
create mode 100644 src/app/pages/client/sidebar/UnverifiedTab.tsx
diff --git a/src/app/hooks/useDeviceList.js b/src/app/hooks/useDeviceList.js
deleted file mode 100644
index 7daaad1..0000000
--- a/src/app/hooks/useDeviceList.js
+++ /dev/null
@@ -1,31 +0,0 @@
-/* eslint-disable import/prefer-default-export */
-import { useState, useEffect } from 'react';
-import { useMatrixClient } from './useMatrixClient';
-
-export function useDeviceList() {
- const mx = useMatrixClient();
- const [deviceList, setDeviceList] = useState(null);
-
- useEffect(() => {
- let isMounted = true;
-
- const updateDevices = () => mx.getDevices().then((data) => {
- if (!isMounted) return;
- setDeviceList(data.devices || []);
- });
- updateDevices();
-
- const handleDevicesUpdate = (users) => {
- if (users.includes(mx.getUserId())) {
- updateDevices();
- }
- };
-
- mx.on('crypto.devicesUpdated', handleDevicesUpdate);
- return () => {
- mx.removeListener('crypto.devicesUpdated', handleDevicesUpdate);
- isMounted = false;
- };
- }, [mx]);
- return deviceList;
-}
diff --git a/src/app/hooks/useDeviceList.ts b/src/app/hooks/useDeviceList.ts
new file mode 100644
index 0000000..daec7cb
--- /dev/null
+++ b/src/app/hooks/useDeviceList.ts
@@ -0,0 +1,35 @@
+/* eslint-disable import/prefer-default-export */
+import { useState, useEffect } from 'react';
+import { CryptoEvent, IMyDevice } from 'matrix-js-sdk';
+import { CryptoEventHandlerMap } from 'matrix-js-sdk/lib/crypto';
+import { useMatrixClient } from './useMatrixClient';
+
+export function useDeviceList() {
+ const mx = useMatrixClient();
+ const [deviceList, setDeviceList] = useState(null);
+
+ useEffect(() => {
+ let isMounted = true;
+
+ const updateDevices = () =>
+ mx.getDevices().then((data) => {
+ if (!isMounted) return;
+ setDeviceList(data.devices || []);
+ });
+ updateDevices();
+
+ const handleDevicesUpdate: CryptoEventHandlerMap[CryptoEvent.DevicesUpdated] = (users) => {
+ const userId = mx.getUserId();
+ if (userId && users.includes(userId)) {
+ updateDevices();
+ }
+ };
+
+ mx.on(CryptoEvent.DevicesUpdated, handleDevicesUpdate);
+ return () => {
+ mx.removeListener(CryptoEvent.DevicesUpdated, handleDevicesUpdate);
+ isMounted = false;
+ };
+ }, [mx]);
+ return deviceList;
+}
diff --git a/src/app/pages/client/SidebarNav.tsx b/src/app/pages/client/SidebarNav.tsx
index fb6bd74..110e469 100644
--- a/src/app/pages/client/SidebarNav.tsx
+++ b/src/app/pages/client/SidebarNav.tsx
@@ -10,7 +10,15 @@ import {
SidebarItemTooltip,
SidebarItem,
} from '../../components/sidebar';
-import { DirectTab, HomeTab, SpaceTabs, InboxTab, ExploreTab, UserTab } from './sidebar';
+import {
+ DirectTab,
+ HomeTab,
+ SpaceTabs,
+ InboxTab,
+ ExploreTab,
+ UserTab,
+ UnverifiedTab,
+} from './sidebar';
import { openCreateRoom, openSearch } from '../../../client/action/navigation';
export function SidebarNav() {
@@ -65,6 +73,8 @@ export function SidebarNav() {
+
+
diff --git a/src/app/pages/client/sidebar/UnverifiedTab.css.ts b/src/app/pages/client/sidebar/UnverifiedTab.css.ts
new file mode 100644
index 0000000..e8fe8c5
--- /dev/null
+++ b/src/app/pages/client/sidebar/UnverifiedTab.css.ts
@@ -0,0 +1,24 @@
+import { keyframes, style } from '@vanilla-extract/css';
+import { color, toRem } from 'folds';
+
+const pushRight = keyframes({
+ from: {
+ transform: `translateX(${toRem(2)}) scale(1)`,
+ },
+ to: {
+ transform: 'translateX(0) scale(1)',
+ },
+});
+
+export const UnverifiedTab = style({
+ animationName: pushRight,
+ animationDuration: '400ms',
+ animationIterationCount: 30,
+ animationDirection: 'alternate',
+});
+
+export const UnverifiedAvatar = style({
+ backgroundColor: color.Critical.Container,
+ color: color.Critical.OnContainer,
+ borderColor: color.Critical.ContainerLine,
+});
diff --git a/src/app/pages/client/sidebar/UnverifiedTab.tsx b/src/app/pages/client/sidebar/UnverifiedTab.tsx
new file mode 100644
index 0000000..402ffc2
--- /dev/null
+++ b/src/app/pages/client/sidebar/UnverifiedTab.tsx
@@ -0,0 +1,51 @@
+import React from 'react';
+import { Badge, color, Icon, Icons, Text } from 'folds';
+import { openSettings } from '../../../../client/action/navigation';
+import { isCrossVerified } from '../../../../util/matrixUtil';
+import {
+ SidebarAvatar,
+ SidebarItem,
+ SidebarItemBadge,
+ SidebarItemTooltip,
+} from '../../../components/sidebar';
+import { useDeviceList } from '../../../hooks/useDeviceList';
+import { tabText } from '../../../organisms/settings/Settings';
+import { useMatrixClient } from '../../../hooks/useMatrixClient';
+import * as css from './UnverifiedTab.css';
+
+export function UnverifiedTab() {
+ const mx = useMatrixClient();
+ const deviceList = useDeviceList();
+ console.log(deviceList);
+ const unverified = deviceList?.filter(
+ (device) => isCrossVerified(mx, device.device_id) === false
+ );
+ console.log(unverified);
+
+ if (!unverified?.length) return null;
+
+ return (
+
+
+ {(triggerRef) => (
+ openSettings(tabText.SECURITY)}
+ >
+
+
+ )}
+
+
+
+
+ {unverified.length}
+
+
+
+
+ );
+}
diff --git a/src/app/pages/client/sidebar/index.ts b/src/app/pages/client/sidebar/index.ts
index 63c5d4b..5da8780 100644
--- a/src/app/pages/client/sidebar/index.ts
+++ b/src/app/pages/client/sidebar/index.ts
@@ -4,3 +4,4 @@ export * from './SpaceTabs';
export * from './InboxTab';
export * from './ExploreTab';
export * from './UserTab';
+export * from './UnverifiedTab';
diff --git a/src/util/matrixUtil.js b/src/util/matrixUtil.js
index 9f1d942..7664c3a 100644
--- a/src/util/matrixUtil.js
+++ b/src/util/matrixUtil.js
@@ -106,7 +106,7 @@ export function isCrossVerified(mx, deviceId) {
const deviceInfo = mx.getStoredDevice(mx.getUserId(), deviceId);
const deviceTrust = crossSignInfo.checkDeviceTrust(crossSignInfo, deviceInfo, false, true);
return deviceTrust.isCrossSigningVerified();
- } catch {
+ } catch (e) {
// device does not support encryption
return null;
}
From 96222de5bc521ed212d4b63d6a25925fce500c80 Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Sun, 4 Aug 2024 11:06:42 +0530
Subject: [PATCH 05/14] fix page up/down button not working (#1863)
---
src/app/features/room/Room.tsx | 3 ++-
src/app/features/room/RoomView.tsx | 4 ++++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/app/features/room/Room.tsx b/src/app/features/room/Room.tsx
index ee3e702..a4ec716 100644
--- a/src/app/features/room/Room.tsx
+++ b/src/app/features/room/Room.tsx
@@ -13,6 +13,7 @@ import { useKeyDown } from '../../hooks/useKeyDown';
import { markAsRead } from '../../../client/action/notifications';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoomMembers } from '../../hooks/useRoomMembers';
+import { editableActiveElement } from '../../utils/dom';
export function Room() {
const { eventId } = useParams();
@@ -28,7 +29,7 @@ export function Room() {
window,
useCallback(
(evt) => {
- if (isKeyHotkey('escape', evt)) {
+ if (isKeyHotkey('escape', evt) && !editableActiveElement()) {
markAsRead(mx, room.roomId);
}
},
diff --git a/src/app/features/room/RoomView.tsx b/src/app/features/room/RoomView.tsx
index 3eabd52..250afc9 100644
--- a/src/app/features/room/RoomView.tsx
+++ b/src/app/features/room/RoomView.tsx
@@ -25,6 +25,7 @@ const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
if (evt.metaKey || evt.altKey || evt.ctrlKey) {
return false;
}
+
// do not focus on F keys
if (/^F\d+$/.test(code)) return false;
@@ -36,6 +37,9 @@ const shouldFocusMessageField = (evt: KeyboardEvent): boolean => {
code.startsWith('Alt') ||
code.startsWith('Control') ||
code.startsWith('Arrow') ||
+ code.startsWith('Page') ||
+ code.startsWith('End') ||
+ code.startsWith('Home') ||
code === 'Tab' ||
code === 'Space' ||
code === 'Enter' ||
From 8ed78d48fb05c2066ce3eaa317922a99a80110db Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Sun, 4 Aug 2024 11:07:28 +0530
Subject: [PATCH 06/14] fix notification not working for selected room (#1864)
---
src/app/pages/client/ClientNonUIFeatures.tsx | 8 ++++----
src/app/pages/client/sidebar/UnverifiedTab.tsx | 2 --
2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/src/app/pages/client/ClientNonUIFeatures.tsx b/src/app/pages/client/ClientNonUIFeatures.tsx
index 845fceb..ffe33e9 100644
--- a/src/app/pages/client/ClientNonUIFeatures.tsx
+++ b/src/app/pages/client/ClientNonUIFeatures.tsx
@@ -183,17 +183,17 @@ function MessageNotifications() {
removed,
data
) => {
+ if (mx.getSyncState() !== 'SYNCING') return;
+ if (document.hasFocus() && (selectedRoomId === room?.roomId || notificationSelected)) return;
if (
- mx.getSyncState() !== 'SYNCING' ||
- selectedRoomId === room?.roomId ||
- notificationSelected ||
!room ||
!data.liveEvent ||
room.isSpaceRoom() ||
!isNotificationEvent(mEvent) ||
getNotificationType(mx, room.roomId) === NotificationType.Mute
- )
+ ) {
return;
+ }
const sender = mEvent.getSender();
const eventId = mEvent.getId();
diff --git a/src/app/pages/client/sidebar/UnverifiedTab.tsx b/src/app/pages/client/sidebar/UnverifiedTab.tsx
index 402ffc2..919c4a7 100644
--- a/src/app/pages/client/sidebar/UnverifiedTab.tsx
+++ b/src/app/pages/client/sidebar/UnverifiedTab.tsx
@@ -16,11 +16,9 @@ import * as css from './UnverifiedTab.css';
export function UnverifiedTab() {
const mx = useMatrixClient();
const deviceList = useDeviceList();
- console.log(deviceList);
const unverified = deviceList?.filter(
(device) => isCrossVerified(mx, device.device_id) === false
);
- console.log(unverified);
if (!unverified?.length) return null;
From 581211f13e20c87fbfef6410ddf57a13c7a0b49a Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Sun, 4 Aug 2024 11:08:20 +0530
Subject: [PATCH 07/14] fix crash when decoding malformed urls (#1865)
---
src/app/components/editor/input.ts | 3 ++-
src/app/components/url-preview/UrlPreviewCard.tsx | 3 ++-
src/app/pages/auth/AuthLayout.tsx | 5 +++--
src/app/plugins/react-custom-html-parser.tsx | 9 +++++----
src/app/utils/dom.ts | 8 ++++++++
5 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/src/app/components/editor/input.ts b/src/app/components/editor/input.ts
index 29e5bd6..20c56ed 100644
--- a/src/app/components/editor/input.ts
+++ b/src/app/components/editor/input.ts
@@ -25,6 +25,7 @@ import {
parseMatrixToUser,
testMatrixTo,
} from '../../plugins/matrix-to';
+import { tryDecodeURIComponent } from '../../utils/dom';
const markNodeToType: Record = {
b: MarkType.Bold,
@@ -73,7 +74,7 @@ const elementToInlineNode = (node: Element): MentionElement | EmoticonElement |
return createEmoticonElement(src, alt || 'Unknown Emoji');
}
if (node.name === 'a') {
- const href = decodeURIComponent(node.attribs.href);
+ const href = tryDecodeURIComponent(node.attribs.href);
if (typeof href !== 'string') return undefined;
if (testMatrixTo(href)) {
const userMention = parseMatrixToUser(href);
diff --git a/src/app/components/url-preview/UrlPreviewCard.tsx b/src/app/components/url-preview/UrlPreviewCard.tsx
index fc9229f..07c25f8 100644
--- a/src/app/components/url-preview/UrlPreviewCard.tsx
+++ b/src/app/components/url-preview/UrlPreviewCard.tsx
@@ -9,6 +9,7 @@ import {
useIntersectionObserver,
} from '../../hooks/useIntersectionObserver';
import * as css from './UrlPreviewCard.css';
+import { tryDecodeURIComponent } from '../../utils/dom';
const linkStyles = { color: color.Success.Main };
@@ -43,7 +44,7 @@ export const UrlPreviewCard = as<'div', { url: string; ts: number }>(
priority="300"
>
{typeof prev['og:site_name'] === 'string' && `${prev['og:site_name']} | `}
- {decodeURIComponent(url)}
+ {tryDecodeURIComponent(url)}
{prev['og:title']}
diff --git a/src/app/pages/auth/AuthLayout.tsx b/src/app/pages/auth/AuthLayout.tsx
index 2ea9414..3943f42 100644
--- a/src/app/pages/auth/AuthLayout.tsx
+++ b/src/app/pages/auth/AuthLayout.tsx
@@ -29,6 +29,7 @@ import { AutoDiscoveryInfoProvider } from '../../hooks/useAutoDiscoveryInfo';
import { AuthFlowsLoader } from '../../components/AuthFlowsLoader';
import { AuthFlowsProvider } from '../../hooks/useAuthFlows';
import { AuthServerProvider } from '../../hooks/useAuthServer';
+import { tryDecodeURIComponent } from '../../utils/dom';
const currentAuthPath = (pathname: string): string => {
if (matchPath(LOGIN_PATH, pathname)) {
@@ -72,7 +73,7 @@ export function AuthLayout() {
const clientConfig = useClientConfig();
const defaultServer = clientDefaultServer(clientConfig);
- let server: string = urlEncodedServer ? decodeURIComponent(urlEncodedServer) : defaultServer;
+ let server: string = urlEncodedServer ? tryDecodeURIComponent(urlEncodedServer) : defaultServer;
if (!clientAllowedServer(clientConfig, server)) {
server = defaultServer;
@@ -94,7 +95,7 @@ export function AuthLayout() {
// if server is mismatches with path server, update path
useEffect(() => {
- if (!urlEncodedServer || decodeURIComponent(urlEncodedServer) !== server) {
+ if (!urlEncodedServer || tryDecodeURIComponent(urlEncodedServer) !== server) {
navigate(
generatePath(currentAuthPath(location.pathname), {
server: encodeURIComponent(server),
diff --git a/src/app/plugins/react-custom-html-parser.tsx b/src/app/plugins/react-custom-html-parser.tsx
index 1670437..95e2f33 100644
--- a/src/app/plugins/react-custom-html-parser.tsx
+++ b/src/app/plugins/react-custom-html-parser.tsx
@@ -26,6 +26,7 @@ import {
testMatrixTo,
} from './matrix-to';
import { onEnterOrSpace } from '../utils/keyboard';
+import { tryDecodeURIComponent } from '../utils/dom';
const ReactPrism = lazy(() => import('./react-prism/ReactPrism'));
@@ -134,8 +135,8 @@ export const factoryRenderLinkifyWithMention = (
attributes,
content,
}) => {
- if (tagName === 'a' && testMatrixTo(decodeURIComponent(attributes.href))) {
- const mention = mentionRender(decodeURIComponent(attributes.href));
+ if (tagName === 'a' && testMatrixTo(tryDecodeURIComponent(attributes.href))) {
+ const mention = mentionRender(tryDecodeURIComponent(attributes.href));
if (mention) return mention;
}
@@ -325,11 +326,11 @@ export const getReactCustomHtmlParser = (
}
}
- if (name === 'a' && testMatrixTo(decodeURIComponent(props.href))) {
+ if (name === 'a' && testMatrixTo(tryDecodeURIComponent(props.href))) {
const mention = renderMatrixMention(
mx,
roomId,
- decodeURIComponent(props.href),
+ tryDecodeURIComponent(props.href),
makeMentionCustomProps(params.handleMentionClick)
);
if (mention) return mention;
diff --git a/src/app/utils/dom.ts b/src/app/utils/dom.ts
index 1aea675..ab4b8e6 100644
--- a/src/app/utils/dom.ts
+++ b/src/app/utils/dom.ts
@@ -196,3 +196,11 @@ export const setFavicon = (url: string): void => {
if (!favicon) return;
favicon.setAttribute('href', url);
};
+
+export const tryDecodeURIComponent = (encodedURIComponent: string): string => {
+ try {
+ return decodeURIComponent(encodedURIComponent);
+ } catch {
+ return encodedURIComponent;
+ }
+};
From cfe893f358785c9bea2c3bdd6402079fad1fcc3e Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Sun, 4 Aug 2024 15:38:47 +1000
Subject: [PATCH 08/14] Bump docker/setup-buildx-action from 3.5.0 to 3.6.1
(#1850)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.5.0 to 3.6.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3.5.0...v3.6.1)
---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/prod-deploy.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml
index a0a70d1..24a1be0 100644
--- a/.github/workflows/prod-deploy.yml
+++ b/.github/workflows/prod-deploy.yml
@@ -70,7 +70,7 @@ jobs:
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.2.0
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3.5.0
+ uses: docker/setup-buildx-action@v3.6.1
- name: Login to Docker Hub
uses: docker/login-action@v3.3.0
with:
From cabfdd47b5bf1c9a12c6d30a9c28fed6d6682fb9 Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Sun, 4 Aug 2024 11:34:11 +0530
Subject: [PATCH 09/14] fix type to focus not working after room switch (#1866)
---
src/app/features/room/RoomInput.tsx | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx
index 2728a54..8375d2f 100644
--- a/src/app/features/room/RoomInput.tsx
+++ b/src/app/features/room/RoomInput.tsx
@@ -186,9 +186,8 @@ export const RoomInput = forwardRef(
Transforms.insertFragment(editor, msgDraft);
}, [editor, msgDraft]);
- useEffect(() => {
- if (!mobileOrTablet()) ReactEditor.focus(editor);
- return () => {
+ useEffect(
+ () => () => {
if (!isEmptyEditor(editor)) {
const parsedDraft = JSON.parse(JSON.stringify(editor.children));
setMsgDraft(parsedDraft);
@@ -197,8 +196,9 @@ export const RoomInput = forwardRef(
}
resetEditor(editor);
resetEditorHistory(editor);
- };
- }, [roomId, editor, setMsgDraft]);
+ },
+ [roomId, editor, setMsgDraft]
+ );
const handleRemoveUpload = useCallback(
(upload: TUploadContent | TUploadContent[]) => {
From e68c56b334107862ab2eec5635c11c40c050f00b Mon Sep 17 00:00:00 2001
From: Krishan <33421343+kfiven@users.noreply.github.com>
Date: Sun, 4 Aug 2024 20:15:10 +1000
Subject: [PATCH 10/14] Release v4.1.0 (#1867)
---
package-lock.json | 4 ++--
package.json | 2 +-
src/app/pages/auth/AuthFooter.tsx | 2 +-
src/app/pages/client/WelcomePage.tsx | 2 +-
src/client/state/cons.js | 2 +-
5 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 8b00ff0..f5aca0e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "cinny",
- "version": "4.0.3",
+ "version": "4.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cinny",
- "version": "4.0.3",
+ "version": "4.1.0",
"license": "AGPL-3.0-only",
"dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "1.1.6",
diff --git a/package.json b/package.json
index d06b960..dc4a040 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "cinny",
- "version": "4.0.3",
+ "version": "4.1.0",
"description": "Yet another matrix client",
"main": "index.js",
"type": "module",
diff --git a/src/app/pages/auth/AuthFooter.tsx b/src/app/pages/auth/AuthFooter.tsx
index 4483ec1..b6e94d2 100644
--- a/src/app/pages/auth/AuthFooter.tsx
+++ b/src/app/pages/auth/AuthFooter.tsx
@@ -15,7 +15,7 @@ export function AuthFooter() {
target="_blank"
rel="noreferrer"
>
- v4.0.3
+ v4.1.0
Twitter
diff --git a/src/app/pages/client/WelcomePage.tsx b/src/app/pages/client/WelcomePage.tsx
index 8cf6b9f..1e146a0 100644
--- a/src/app/pages/client/WelcomePage.tsx
+++ b/src/app/pages/client/WelcomePage.tsx
@@ -24,7 +24,7 @@ export function WelcomePage() {
target="_blank"
rel="noreferrer noopener"
>
- v4.0.3
+ v4.1.0
}
diff --git a/src/client/state/cons.js b/src/client/state/cons.js
index 157d4a6..f7f65db 100644
--- a/src/client/state/cons.js
+++ b/src/client/state/cons.js
@@ -1,5 +1,5 @@
const cons = {
- version: '4.0.3',
+ version: '4.1.0',
secretKey: {
ACCESS_TOKEN: 'cinny_access_token',
DEVICE_ID: 'cinny_device_id',
From b4ce8a7cab727c51327c9434ef4bdd765fd7778b Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 14 Aug 2024 23:21:11 +1000
Subject: [PATCH 11/14] Bump docker/build-push-action from 6.5.0 to 6.6.1
(#1891)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.5.0 to 6.6.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v6.5.0...v6.6.1)
---
updated-dependencies:
- dependency-name: docker/build-push-action
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/docker-pr.yml | 2 +-
.github/workflows/prod-deploy.yml | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/docker-pr.yml b/.github/workflows/docker-pr.yml
index 7b1e2ab..827ca66 100644
--- a/.github/workflows/docker-pr.yml
+++ b/.github/workflows/docker-pr.yml
@@ -13,7 +13,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4.1.7
- name: Build Docker image
- uses: docker/build-push-action@v6.5.0
+ uses: docker/build-push-action@v6.6.1
with:
context: .
push: false
diff --git a/.github/workflows/prod-deploy.yml b/.github/workflows/prod-deploy.yml
index 24a1be0..d340cb7 100644
--- a/.github/workflows/prod-deploy.yml
+++ b/.github/workflows/prod-deploy.yml
@@ -90,7 +90,7 @@ jobs:
${{ secrets.DOCKER_USERNAME }}/cinny
ghcr.io/${{ github.repository }}
- name: Build and push Docker image
- uses: docker/build-push-action@v6.5.0
+ uses: docker/build-push-action@v6.6.1
with:
context: .
platforms: linux/amd64,linux/arm64
From ac1797344c0b8d0ded6019f33f0c9f17973de746 Mon Sep 17 00:00:00 2001
From: aceArt-GmbH <33117017+aceArt-GmbH@users.noreply.github.com>
Date: Wed, 14 Aug 2024 15:29:34 +0200
Subject: [PATCH 12/14] Add translation support using i18next (#1576)
Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
---
package-lock.json | 134 +++++++++++++++++++++++--
package.json | 4 +
public/locales/de.json | 7 ++
public/locales/en.json | 7 ++
src/app/features/room/RoomTimeline.tsx | 4 +-
src/app/i18n.ts | 31 ++++++
src/index.tsx | 3 +
vite.config.js | 4 +
8 files changed, 184 insertions(+), 10 deletions(-)
create mode 100644 public/locales/de.json
create mode 100644 public/locales/en.json
create mode 100644 src/app/i18n.ts
diff --git a/package-lock.json b/package-lock.json
index f5aca0e..061d3de 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -37,6 +37,9 @@
"formik": "2.4.6",
"html-dom-parser": "4.0.0",
"html-react-parser": "4.2.0",
+ "i18next": "23.12.2",
+ "i18next-browser-languagedetector": "8.0.0",
+ "i18next-http-backend": "2.5.2",
"immer": "9.0.16",
"is-hotkey": "0.2.0",
"jotai": "2.6.0",
@@ -54,6 +57,7 @@
"react-dom": "18.2.0",
"react-error-boundary": "4.0.13",
"react-google-recaptcha": "2.1.0",
+ "react-i18next": "15.0.0",
"react-modal": "3.16.1",
"react-range": "1.8.14",
"react-router-dom": "6.20.0",
@@ -438,11 +442,12 @@
}
},
"node_modules/@babel/runtime": {
- "version": "7.20.6",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
- "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==",
+ "version": "7.25.0",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz",
+ "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==",
+ "license": "MIT",
"dependencies": {
- "regenerator-runtime": "^0.13.11"
+ "regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
@@ -467,11 +472,6 @@
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"dev": true
},
- "node_modules/@babel/runtime/node_modules/regenerator-runtime": {
- "version": "0.13.11",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
- "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
- },
"node_modules/@babel/template": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
@@ -6146,6 +6146,15 @@
"entities": "^4.5.0"
}
},
+ "node_modules/html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "license": "MIT",
+ "dependencies": {
+ "void-elements": "3.1.0"
+ }
+ },
"node_modules/html-react-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-4.2.0.tgz",
@@ -6191,6 +6200,76 @@
"node": ">= 6"
}
},
+ "node_modules/i18next": {
+ "version": "23.12.2",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.12.2.tgz",
+ "integrity": "sha512-XIeh5V+bi8SJSWGL3jqbTEBW5oD6rbP5L+E7dVQh1MNTxxYef0x15rhJVcRb7oiuq4jLtgy2SD8eFlf6P2cmqg==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://locize.com"
+ },
+ {
+ "type": "individual",
+ "url": "https://locize.com/i18next.html"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
+ "node_modules/i18next-browser-languagedetector": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
+ "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
+ "node_modules/i18next-http-backend": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.5.2.tgz",
+ "integrity": "sha512-+K8HbDfrvc1/2X8jpb7RLhI9ZxBDpx3xogYkQwGKlWAUXLSEGXzgdt3EcUjLlBCdMwdQY+K+EUF6oh8oB6rwHw==",
+ "license": "MIT",
+ "dependencies": {
+ "cross-fetch": "4.0.0"
+ }
+ },
+ "node_modules/i18next-http-backend/node_modules/cross-fetch": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
+ "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
+ "license": "MIT",
+ "dependencies": {
+ "node-fetch": "^2.6.12"
+ }
+ },
+ "node_modules/i18next-http-backend/node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@@ -7717,6 +7796,28 @@
"react": ">=16.4.1"
}
},
+ "node_modules/react-i18next": {
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.0.tgz",
+ "integrity": "sha512-2O3IgF4zivg57Q6p6i+ChDgJ371IDcEWbuWC6gvoh5NbkDMs0Q+O7RPr4v61+Se32E0V+LmtwePAeqWZW0bi6g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.24.8",
+ "html-parse-stringify": "^3.0.1"
+ },
+ "peerDependencies": {
+ "i18next": ">= 23.2.3",
+ "react": ">= 16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@@ -7824,6 +7925,12 @@
"node": ">=8.10.0"
}
},
+ "node_modules/regenerator-runtime": {
+ "version": "0.14.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
+ "license": "MIT"
+ },
"node_modules/regexp.prototype.flags": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz",
@@ -9294,6 +9401,15 @@
"@esbuild/win32-x64": "0.19.12"
}
},
+ "node_modules/void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
diff --git a/package.json b/package.json
index dc4a040..8c66068 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,9 @@
"formik": "2.4.6",
"html-dom-parser": "4.0.0",
"html-react-parser": "4.2.0",
+ "i18next": "23.12.2",
+ "i18next-browser-languagedetector": "8.0.0",
+ "i18next-http-backend": "2.5.2",
"immer": "9.0.16",
"is-hotkey": "0.2.0",
"jotai": "2.6.0",
@@ -65,6 +68,7 @@
"react-dom": "18.2.0",
"react-error-boundary": "4.0.13",
"react-google-recaptcha": "2.1.0",
+ "react-i18next": "15.0.0",
"react-modal": "3.16.1",
"react-range": "1.8.14",
"react-router-dom": "6.20.0",
diff --git a/public/locales/de.json b/public/locales/de.json
new file mode 100644
index 0000000..43a3716
--- /dev/null
+++ b/public/locales/de.json
@@ -0,0 +1,7 @@
+{
+ "Organisms": {
+ "RoomCommon": {
+ "changed_room_name": " hat den Raum Name geƤndert"
+ }
+ }
+}
diff --git a/public/locales/en.json b/public/locales/en.json
new file mode 100644
index 0000000..7a2534b
--- /dev/null
+++ b/public/locales/en.json
@@ -0,0 +1,7 @@
+{
+ "Organisms": {
+ "RoomCommon": {
+ "changed_room_name": " changed room name"
+ }
+ }
+}
diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx
index 6e50370..84ce8af 100644
--- a/src/app/features/room/RoomTimeline.tsx
+++ b/src/app/features/room/RoomTimeline.tsx
@@ -46,6 +46,7 @@ import {
} from 'folds';
import { isKeyHotkey } from 'is-hotkey';
import { Opts as LinkifyOpts } from 'linkifyjs';
+import { useTranslation } from 'react-i18next';
import {
decryptFile,
eventWithShortcode,
@@ -958,6 +959,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
},
[editor]
);
+ const { t } = useTranslation();
const renderMatrixEvent = useMatrixEventRenderer<
[string, MatrixEvent, number, EventTimelineSet, boolean]
@@ -1273,7 +1275,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
{senderName}
- {' changed room name'}
+ {t('Organisms.RoomCommon.changed_room_name')}
}
diff --git a/src/app/i18n.ts b/src/app/i18n.ts
new file mode 100644
index 0000000..9e83805
--- /dev/null
+++ b/src/app/i18n.ts
@@ -0,0 +1,31 @@
+import i18n from 'i18next';
+import LanguageDetector from 'i18next-browser-languagedetector';
+import Backend, { HttpBackendOptions } from 'i18next-http-backend';
+import { initReactI18next } from 'react-i18next';
+import { trimTrailingSlash } from './utils/common';
+
+i18n
+ // i18next-http-backend
+ // loads translations from your server
+ // https://github.com/i18next/i18next-http-backend
+ .use(Backend)
+ // detect user language
+ // learn more: https://github.com/i18next/i18next-browser-languageDetector
+ .use(LanguageDetector)
+ // pass the i18n instance to react-i18next.
+ .use(initReactI18next)
+ // init i18next
+ // for all options read: https://www.i18next.com/overview/configuration-options
+ .init({
+ debug: false,
+ fallbackLng: 'en',
+ interpolation: {
+ escapeValue: false, // not needed for react as it escapes by default
+ },
+ load: 'languageOnly',
+ backend: {
+ loadPath: `${trimTrailingSlash(import.meta.env.BASE_URL)}/public/locales/{{lng}}.json`,
+ },
+ });
+
+export default i18n;
diff --git a/src/index.tsx b/src/index.tsx
index 1d86420..a289ed1 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -14,6 +14,9 @@ import settings from './client/state/settings';
import App from './app/pages/App';
+// import i18n (needs to be bundled ;))
+import './app/i18n';
+
document.body.classList.add(configClass, varsClass);
settings.applyTheme();
diff --git a/vite.config.js b/vite.config.js
index 1255f81..7b5ec25 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -35,6 +35,10 @@ const copyFiles = {
src: 'public/res/android',
dest: 'public/',
},
+ {
+ src: 'public/locales',
+ dest: 'public/',
+ },
],
};
From 7e7bee8f48229a7ebac0c64d5f4f744d81eca202 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 14 Aug 2024 23:38:35 +1000
Subject: [PATCH 13/14] Bump actions/upload-artifact from 4.3.4 to 4.3.6
(#1890)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.3.4 to 4.3.6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v4.3.4...v4.3.6)
---
updated-dependencies:
- dependency-name: actions/upload-artifact
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/build-pull-request.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml
index 67ae5f1..ae1097b 100644
--- a/.github/workflows/build-pull-request.yml
+++ b/.github/workflows/build-pull-request.yml
@@ -25,7 +25,7 @@ jobs:
NODE_OPTIONS: '--max_old_space_size=4096'
run: npm run build
- name: Upload artifact
- uses: actions/upload-artifact@v4.3.4
+ uses: actions/upload-artifact@v4.3.6
with:
name: preview
path: dist
@@ -33,7 +33,7 @@ jobs:
- name: Save pr number
run: echo ${PR_NUMBER} > ./pr.txt
- name: Upload pr number
- uses: actions/upload-artifact@v4.3.4
+ uses: actions/upload-artifact@v4.3.6
with:
name: pr
path: ./pr.txt
From 830d05e217fd6640c219d4eb419895ca8669e111 Mon Sep 17 00:00:00 2001
From: greentore <117551249+greentore@users.noreply.github.com>
Date: Thu, 15 Aug 2024 16:52:32 +0200
Subject: [PATCH 14/14] Add basic `m.thread` support (#1349)
* Add basic `m.thread` support
* Fix types
* Update to v4
* Fix auto formatting mess
* Add threaded reply indicators
* Fix reply overflow
* Fix replying to edited threaded replies
* Add thread indicator to room input
* Fix editing encrypted events
* Use `toRem` function for converting units
---------
Co-authored-by: Ajay Bura <32841439+ajbura@users.noreply.github.com>
---
src/app/components/message/Reply.css.ts | 19 +++++
src/app/components/message/Reply.tsx | 81 +++++++++++--------
.../message-search/SearchResultGroup.tsx | 16 ++--
src/app/features/room/RoomInput.tsx | 40 +++++----
src/app/features/room/RoomTimeline.tsx | 33 ++++----
src/app/pages/client/inbox/Notifications.tsx | 13 +--
src/app/state/room/roomInputDrafts.ts | 4 +-
src/app/utils/room.ts | 19 +++--
8 files changed, 140 insertions(+), 85 deletions(-)
diff --git a/src/app/components/message/Reply.css.ts b/src/app/components/message/Reply.css.ts
index 014a284..0679939 100644
--- a/src/app/components/message/Reply.css.ts
+++ b/src/app/components/message/Reply.css.ts
@@ -5,6 +5,25 @@ export const ReplyBend = style({
flexShrink: 0,
});
+export const ThreadIndicator = style({
+ opacity: config.opacity.P300,
+ gap: toRem(2),
+
+ selectors: {
+ 'button&': {
+ cursor: 'pointer',
+ },
+ ':hover&': {
+ opacity: config.opacity.P500,
+ },
+ },
+});
+
+export const ThreadIndicatorIcon = style({
+ width: toRem(14),
+ height: toRem(14),
+});
+
export const Reply = style({
marginBottom: toRem(1),
minWidth: 0,
diff --git a/src/app/components/message/Reply.tsx b/src/app/components/message/Reply.tsx
index 85383cd..82a9d91 100644
--- a/src/app/components/message/Reply.tsx
+++ b/src/app/components/message/Reply.tsx
@@ -1,7 +1,7 @@
import { Box, Icon, Icons, Text, as, color, toRem } from 'folds';
import { EventTimelineSet, MatrixClient, MatrixEvent, Room } from 'matrix-js-sdk';
import { CryptoBackend } from 'matrix-js-sdk/lib/common-crypto/CryptoBackend';
-import React, { ReactNode, useEffect, useMemo, useState } from 'react';
+import React, { MouseEventHandler, ReactNode, useEffect, useMemo, useState } from 'react';
import to from 'await-to-js';
import classNames from 'classnames';
import colorMXID from '../../../util/colorMXID';
@@ -22,6 +22,7 @@ export const ReplyLayout = as<'div', ReplyLayoutProps>(
(
)
);
+export const ThreadIndicator = as<'div'>(({ ...props }, ref) => (
+
+
+ Threaded reply
+
+));
+
type ReplyProps = {
mx: MatrixClient;
room: Room;
- timelineSet?: EventTimelineSet;
- eventId: string;
+ timelineSet?: EventTimelineSet | undefined;
+ replyEventId: string;
+ threadRootId?: string | undefined;
+ onClick?: MouseEventHandler | undefined;
};
-export const Reply = as<'div', ReplyProps>(({ mx, room, timelineSet, eventId, ...props }, ref) => {
+export const Reply = as<'div', ReplyProps>((_, ref) => {
+ const { mx, room, timelineSet, replyEventId, threadRootId, onClick, ...props } = _;
const [replyEvent, setReplyEvent] = useState(
- timelineSet?.findEventById(eventId)
+ timelineSet?.findEventById(replyEventId)
);
const placeholderWidth = useMemo(() => randomNumberBetween(40, 400), []);
@@ -62,7 +73,7 @@ export const Reply = as<'div', ReplyProps>(({ mx, room, timelineSet, eventId, ..
useEffect(() => {
let disposed = false;
const loadEvent = async () => {
- const [err, evt] = await to(mx.fetchRoomEvent(room.roomId, eventId));
+ const [err, evt] = await to(mx.fetchRoomEvent(room.roomId, replyEventId));
const mEvent = new MatrixEvent(evt);
if (disposed) return;
if (err) {
@@ -78,37 +89,43 @@ export const Reply = as<'div', ReplyProps>(({ mx, room, timelineSet, eventId, ..
return () => {
disposed = true;
};
- }, [replyEvent, mx, room, eventId]);
+ }, [replyEvent, mx, room, replyEventId]);
const badEncryption = replyEvent?.getContent().msgtype === 'm.bad.encrypted';
const bodyJSX = body ? scaleSystemEmoji(trimReplyFromBody(body)) : fallbackBody;
return (
-
- {getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender)}
-
- )
- }
- {...props}
- ref={ref}
- >
- {replyEvent !== undefined ? (
-
- {badEncryption ? : bodyJSX}
-
- ) : (
-
+
+ {threadRootId && (
+
)}
-
+
+ {getMemberDisplayName(room, sender) ?? getMxIdLocalPart(sender)}
+
+ )
+ }
+ data-event-id={replyEventId}
+ onClick={onClick}
+ >
+ {replyEvent !== undefined ? (
+
+ {badEncryption ? : bodyJSX}
+
+ ) : (
+
+ )}
+
+
);
});
diff --git a/src/app/features/message-search/SearchResultGroup.tsx b/src/app/features/message-search/SearchResultGroup.tsx
index 2b2a816..84ba3a7 100644
--- a/src/app/features/message-search/SearchResultGroup.tsx
+++ b/src/app/features/message-search/SearchResultGroup.tsx
@@ -148,7 +148,7 @@ export function SearchResultGroup({
}
);
- const handleOpenClick: MouseEventHandler = (evt) => {
+ const handleOpenClick: MouseEventHandler = (evt) => {
const eventId = evt.currentTarget.getAttribute('data-event-id');
if (!eventId) return;
onOpen(room.roomId, eventId);
@@ -183,15 +183,16 @@ export function SearchResultGroup({
event.sender;
const senderAvatarMxc = getMemberAvatarMxc(room, event.sender);
+ const relation = event.content['m.relates_to'];
const mainEventId =
- event.content['m.relates_to']?.rel_type === RelationType.Replace
- ? event.content['m.relates_to'].event_id
- : event.event_id;
+ relation?.rel_type === RelationType.Replace ? relation.event_id : event.event_id;
const getContent = (() =>
event.content['m.new_content'] ?? event.content) as GetContentCallback;
- const replyEventId = event.content['m.relates_to']?.['m.in_reply_to']?.event_id;
+ const replyEventId = relation?.['m.in_reply_to']?.event_id;
+ const threadRootId =
+ relation?.rel_type === RelationType.Thread ? relation.event_id : undefined;
return (
{replyEventId && (
)}
diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx
index 8375d2f..3c78ff3 100644
--- a/src/app/features/room/RoomInput.tsx
+++ b/src/app/features/room/RoomInput.tsx
@@ -10,7 +10,7 @@ import React, {
} from 'react';
import { useAtom, useAtomValue } from 'jotai';
import { isKeyHotkey } from 'is-hotkey';
-import { EventType, IContent, MsgType, Room } from 'matrix-js-sdk';
+import { EventType, IContent, MsgType, RelationType, Room } from 'matrix-js-sdk';
import { ReactEditor } from 'slate-react';
import { Transforms, Editor } from 'slate';
import {
@@ -106,7 +106,7 @@ import { CommandAutocomplete } from './CommandAutocomplete';
import { Command, SHRUG, useCommands } from '../../hooks/useCommands';
import { mobileOrTablet } from '../../utils/user-agent';
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
-import { ReplyLayout } from '../../components/message';
+import { ReplyLayout, ThreadIndicator } from '../../components/message';
import { roomToParentsAtom } from '../../state/room/roomToParents';
interface RoomInputProps {
@@ -310,6 +310,11 @@ export const RoomInput = forwardRef(
event_id: replyDraft.eventId,
},
};
+ if (replyDraft.relation?.rel_type === RelationType.Thread) {
+ content['m.relates_to'].event_id = replyDraft.relation.event_id;
+ content['m.relates_to'].rel_type = RelationType.Thread;
+ content['m.relates_to'].is_falling_back = false;
+ }
}
mx.sendMessage(roomId, content);
resetEditor(editor);
@@ -489,22 +494,25 @@ export const RoomInput = forwardRef(
>
-
+ {replyDraft.relation?.rel_type === RelationType.Thread && }
+
+
+ {getMemberDisplayName(room, replyDraft.userId) ??
+ getMxIdLocalPart(replyDraft.userId) ??
+ replyDraft.userId}
+
+
+ }
+ >
-
- {getMemberDisplayName(room, replyDraft.userId) ??
- getMxIdLocalPart(replyDraft.userId) ??
- replyDraft.userId}
-
+ {trimReplyFromBody(replyDraft.body)}
- }
- >
-
- {trimReplyFromBody(replyDraft.body)}
-
-
+
+
)
diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx
index 84ce8af..01ba14f 100644
--- a/src/app/features/room/RoomTimeline.tsx
+++ b/src/app/features/room/RoomTimeline.tsx
@@ -16,6 +16,7 @@ import {
EventTimeline,
EventTimelineSet,
EventTimelineSetHandlerMap,
+ IContent,
IEncryptedFile,
MatrixClient,
MatrixEvent,
@@ -837,13 +838,13 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
markAsRead(mx, room.roomId);
};
- const handleOpenReply: MouseEventHandler = useCallback(
+ const handleOpenReply: MouseEventHandler = useCallback(
async (evt) => {
- const replyId = evt.currentTarget.getAttribute('data-reply-id');
- if (typeof replyId !== 'string') return;
- const replyTimeline = getEventTimeline(room, replyId);
+ const targetId = evt.currentTarget.getAttribute('data-event-id');
+ if (!targetId) return;
+ const replyTimeline = getEventTimeline(room, targetId);
const absoluteIndex =
- replyTimeline && getEventIdAbsoluteIndex(timeline.linkedTimelines, replyTimeline, replyId);
+ replyTimeline && getEventIdAbsoluteIndex(timeline.linkedTimelines, replyTimeline, targetId);
if (typeof absoluteIndex === 'number') {
scrollToItem(absoluteIndex, {
@@ -858,7 +859,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
});
} else {
setTimeline(getEmptyTimeline());
- loadEventTimeline(replyId);
+ loadEventTimeline(targetId);
}
},
[room, timeline, scrollToItem, loadEventTimeline]
@@ -909,8 +910,9 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const replyEvt = room.findEventById(replyId);
if (!replyEvt) return;
const editedReply = getEditedEvent(replyId, replyEvt, room.getUnfilteredTimelineSet());
- const { body, formatted_body: formattedBody }: Record =
- editedReply?.getContent()['m.new_content'] ?? replyEvt.getContent();
+ const content: IContent = editedReply?.getContent()['m.new_content'] ?? replyEvt.getContent();
+ const { body, formatted_body: formattedBody } = content;
+ const { 'm.relates_to': relation } = replyEvt.getOriginalContent();
const senderId = replyEvt.getSender();
if (senderId && typeof body === 'string') {
setReplyDraft({
@@ -918,6 +920,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
eventId: replyId,
body,
formattedBody,
+ relation,
});
setTimeout(() => ReactEditor.focus(editor), 100);
}
@@ -969,7 +972,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const reactionRelations = getEventReactions(timelineSet, mEventId);
const reactions = reactionRelations && reactionRelations.getSortedAnnotationsByKey();
const hasReactions = reactions && reactions.length > 0;
- const { replyEventId } = mEvent;
+ const { replyEventId, threadRootId } = mEvent;
const highlighted = focusItem?.index === item && focusItem.highlight;
const editedEvent = getEditedEvent(mEventId, mEvent, timelineSet);
@@ -1004,12 +1007,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
reply={
replyEventId && (
)
@@ -1050,7 +1052,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
const reactionRelations = getEventReactions(timelineSet, mEventId);
const reactions = reactionRelations && reactionRelations.getSortedAnnotationsByKey();
const hasReactions = reactions && reactions.length > 0;
- const { replyEventId } = mEvent;
+ const { replyEventId, threadRootId } = mEvent;
const highlighted = focusItem?.index === item && focusItem.highlight;
return (
@@ -1077,12 +1079,11 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli
reply={
replyEventId && (
)
diff --git a/src/app/pages/client/inbox/Notifications.tsx b/src/app/pages/client/inbox/Notifications.tsx
index 6a8160d..aa87821 100644
--- a/src/app/pages/client/inbox/Notifications.tsx
+++ b/src/app/pages/client/inbox/Notifications.tsx
@@ -20,6 +20,7 @@ import {
IRoomEvent,
JoinRule,
Method,
+ RelationType,
Room,
} from 'matrix-js-sdk';
import { useVirtualizer } from '@tanstack/react-virtual';
@@ -352,7 +353,7 @@ function RoomNotificationsGroupComp({
}
);
- const handleOpenClick: MouseEventHandler = (evt) => {
+ const handleOpenClick: MouseEventHandler = (evt) => {
const eventId = evt.currentTarget.getAttribute('data-event-id');
if (!eventId) return;
onOpen(room.roomId, eventId);
@@ -403,7 +404,10 @@ function RoomNotificationsGroupComp({
const senderAvatarMxc = getMemberAvatarMxc(room, event.sender);
const getContent = (() => event.content) as GetContentCallback;
- const replyEventId = event.content['m.relates_to']?.['m.in_reply_to']?.event_id;
+ const relation = event.content['m.relates_to'];
+ const replyEventId = relation?.['m.in_reply_to']?.event_id;
+ const threadRootId =
+ relation?.rel_type === RelationType.Thread ? relation.event_id : undefined;
return (
{replyEventId && (
)}
diff --git a/src/app/state/room/roomInputDrafts.ts b/src/app/state/room/roomInputDrafts.ts
index 60b42fd..33bd060 100644
--- a/src/app/state/room/roomInputDrafts.ts
+++ b/src/app/state/room/roomInputDrafts.ts
@@ -2,6 +2,7 @@ import { atom } from 'jotai';
import { atomFamily } from 'jotai/utils';
import { Descendant } from 'slate';
import { EncryptedAttachmentInfo } from 'browser-encrypt-attachment';
+import { IEventRelation } from 'matrix-js-sdk';
import { TListAtom, createListAtom } from '../list';
import { createUploadAtomFamily } from '../upload';
import { TUploadContent } from '../../utils/matrix';
@@ -39,7 +40,8 @@ export type IReplyDraft = {
userId: string;
eventId: string;
body: string;
- formattedBody?: string;
+ formattedBody?: string | undefined;
+ relation?: IEventRelation | undefined;
};
const createReplyDraftAtom = () => atom(undefined);
export type TReplyDraftAtom = ReturnType;
diff --git a/src/app/utils/room.ts b/src/app/utils/room.ts
index 750dd6c..8cf33a8 100644
--- a/src/app/utils/room.ts
+++ b/src/app/utils/room.ts
@@ -389,13 +389,18 @@ export const getEditedEvent = (
return edits && getLatestEdit(mEvent, edits.getRelations());
};
-export const canEditEvent = (mx: MatrixClient, mEvent: MatrixEvent) =>
- mEvent.getSender() === mx.getUserId() &&
- !mEvent.isRelation() &&
- mEvent.getType() === MessageEvent.RoomMessage &&
- (mEvent.getContent().msgtype === MsgType.Text ||
- mEvent.getContent().msgtype === MsgType.Emote ||
- mEvent.getContent().msgtype === MsgType.Notice);
+export const canEditEvent = (mx: MatrixClient, mEvent: MatrixEvent) => {
+ const content = mEvent.getContent();
+ const relationType = content['m.relates_to']?.rel_type;
+ return (
+ mEvent.getSender() === mx.getUserId() &&
+ (!relationType || relationType === RelationType.Thread) &&
+ mEvent.getType() === MessageEvent.RoomMessage &&
+ (content.msgtype === MsgType.Text ||
+ content.msgtype === MsgType.Emote ||
+ content.msgtype === MsgType.Notice)
+ );
+};
export const getLatestEditableEvt = (
timeline: EventTimeline,