Compare commits

...

9 Commits

Author SHA1 Message Date
Krishan
abd713d693 Release v4.9.1 (#2446) 2025-08-17 21:08:35 +10:00
Krishan
802357b7a0 Rename the PL 150 to Manager (#2443)
Manager seem more appropriate than Co-Founder. As Co-founder essentially have same power to Founder.
2025-08-17 16:20:17 +05:30
Ajay Bura
c5d4530947 Add new join with address prompt (#2442) 2025-08-16 21:40:39 +10:00
Ajay Bura
367397fdd4 Fix type error when accessing FileList (#2441) 2025-08-16 21:35:34 +10:00
Ajay Bura
63fa60e7f4 Open user profile at around mouse anchor (#2440) 2025-08-16 21:34:46 +10:00
Ajay Bura
544a06964d Hide block user button for own profile (#2439) 2025-08-16 21:32:09 +10:00
Ajay Bura
50583f9474 Fix room v12 mention pills (#2438) 2025-08-16 21:30:52 +10:00
Ajay Bura
1ad7fe8deb Fix missing creators support using via (#2431)
* add additional_creators in IRoomCreateContent type

* use creators in getViaServers

* consider creators in guessing perfect parent
2025-08-16 21:30:02 +10:00
Ajay Bura
752a19a4e7 Open tombstone space as space (#2428) 2025-08-16 21:27:37 +10:00
20 changed files with 290 additions and 62 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "cinny", "name": "cinny",
"version": "4.9.0", "version": "4.9.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "cinny", "name": "cinny",
"version": "4.9.0", "version": "4.9.1",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@atlaskit/pragmatic-drag-and-drop": "1.1.6", "@atlaskit/pragmatic-drag-and-drop": "1.1.6",

View File

@@ -1,6 +1,6 @@
{ {
"name": "cinny", "name": "cinny",
"version": "4.9.0", "version": "4.9.1",
"description": "Yet another matrix client", "description": "Yet another matrix client",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",

View File

@@ -23,6 +23,7 @@ import { UserAvatar } from '../user-avatar';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { useOpenUserRoomProfile } from '../../state/hooks/userRoomProfile'; import { useOpenUserRoomProfile } from '../../state/hooks/userRoomProfile';
import { useSpaceOptionally } from '../../hooks/useSpace'; import { useSpaceOptionally } from '../../hooks/useSpace';
import { getMouseEventCords } from '../../utils/dom';
export type EventReadersProps = { export type EventReadersProps = {
room: Room; room: Room;
@@ -83,7 +84,7 @@ export const EventReaders = as<'div', EventReadersProps>(
room.roomId, room.roomId,
space?.roomId, space?.roomId,
readerId, readerId,
event.currentTarget.getBoundingClientRect(), getMouseEventCords(event.nativeEvent),
'Bottom' 'Bottom'
); );
}} }}

View File

@@ -0,0 +1,131 @@
import React, { FormEventHandler, useState } from 'react';
import FocusTrap from 'focus-trap-react';
import {
Dialog,
Overlay,
OverlayCenter,
OverlayBackdrop,
Header,
config,
Box,
Text,
IconButton,
Icon,
Icons,
Button,
Input,
color,
} from 'folds';
import { stopPropagation } from '../../utils/keyboard';
import { isRoomAlias, isRoomId } from '../../utils/matrix';
import { parseMatrixToRoom, parseMatrixToRoomEvent, testMatrixTo } from '../../plugins/matrix-to';
import { tryDecodeURIComponent } from '../../utils/dom';
type JoinAddressProps = {
onOpen: (roomIdOrAlias: string, via?: string[], eventId?: string) => void;
onCancel: () => void;
};
export function JoinAddressPrompt({ onOpen, onCancel }: JoinAddressProps) {
const [invalid, setInvalid] = useState(false);
const handleSubmit: FormEventHandler<HTMLFormElement> = (evt) => {
evt.preventDefault();
setInvalid(false);
const target = evt.target as HTMLFormElement | undefined;
const addressInput = target?.addressInput as HTMLInputElement | undefined;
const address = addressInput?.value.trim();
if (!address) return;
if (isRoomId(address) || isRoomAlias(address)) {
onOpen(address);
return;
}
if (testMatrixTo(address)) {
const decodedAddress = tryDecodeURIComponent(address);
const toRoom = parseMatrixToRoom(decodedAddress);
if (toRoom) {
onOpen(toRoom.roomIdOrAlias, toRoom.viaServers);
return;
}
const toEvent = parseMatrixToRoomEvent(decodedAddress);
if (toEvent) {
onOpen(toEvent.roomIdOrAlias, toEvent.viaServers, toEvent.eventId);
return;
}
}
setInvalid(true);
};
return (
<Overlay open backdrop={<OverlayBackdrop />}>
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
onDeactivate: onCancel,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Dialog variant="Surface">
<Header
style={{
padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
}}
variant="Surface"
size="500"
>
<Box grow="Yes">
<Text size="H4">Join with Address</Text>
</Box>
<IconButton size="300" onClick={onCancel} radii="300">
<Icon src={Icons.Cross} />
</IconButton>
</Header>
<Box
as="form"
onSubmit={handleSubmit}
style={{ padding: config.space.S400, paddingTop: 0 }}
direction="Column"
gap="400"
>
<Box direction="Column" gap="200">
<Text priority="400" size="T300">
Enter public address to join the community. Addresses looks like:
</Text>
<Text as="ul" size="T200" priority="300" style={{ paddingLeft: config.space.S400 }}>
<li>#community:server</li>
<li>https://matrix.to/#/#community:server</li>
<li>https://matrix.to/#/!xYzAj?via=server</li>
</Text>
</Box>
<Box direction="Column" gap="100">
<Text size="L400">Address</Text>
<Input
size="500"
autoFocus
name="addressInput"
variant="Background"
placeholder="#community:server"
required
/>
{invalid && (
<Text size="T200" style={{ color: color.Critical.Main }}>
<b>Invalid Address</b>
</Text>
)}
</Box>
<Button type="submit" variant="Primary">
<Text size="B400">Open</Text>
</Button>
</Box>
</Dialog>
</FocusTrap>
</OverlayCenter>
</Overlay>
);
}

View File

@@ -0,0 +1 @@
export * from './JoinAddressPrompt';

View File

@@ -124,8 +124,8 @@ export function UserRoomProfile({ userId }: UserRoomProfileProps) {
{server && <ServerChip server={server} />} {server && <ServerChip server={server} />}
<ShareChip userId={userId} /> <ShareChip userId={userId} />
{creator ? <CreatorChip /> : <PowerChip userId={userId} />} {creator ? <CreatorChip /> : <PowerChip userId={userId} />}
<MutualRoomsChip userId={userId} /> {userId !== myUserId && <MutualRoomsChip userId={userId} />}
<OptionsChip userId={userId} /> {userId !== myUserId && <OptionsChip userId={userId} />}
</Box> </Box>
</Box> </Box>
{ignored && <IgnoredUserAlert />} {ignored && <IgnoredUserAlert />}

View File

@@ -55,6 +55,7 @@ import {
import { useSpaceOptionally } from '../../../hooks/useSpace'; import { useSpaceOptionally } from '../../../hooks/useSpace';
import { useFlattenPowerTagMembers, useGetMemberPowerTag } from '../../../hooks/useMemberPowerTag'; import { useFlattenPowerTagMembers, useGetMemberPowerTag } from '../../../hooks/useMemberPowerTag';
import { useRoomCreators } from '../../../hooks/useRoomCreators'; import { useRoomCreators } from '../../../hooks/useRoomCreators';
import { getMouseEventCords } from '../../../utils/dom';
const SEARCH_OPTIONS: UseAsyncSearchOptions = { const SEARCH_OPTIONS: UseAsyncSearchOptions = {
limit: 1000, limit: 1000,
@@ -145,7 +146,7 @@ export function Members({ requestClose }: MembersProps) {
const btn = evt.currentTarget as HTMLButtonElement; const btn = evt.currentTarget as HTMLButtonElement;
const userId = btn.getAttribute('data-user-id'); const userId = btn.getAttribute('data-user-id');
if (userId) { if (userId) {
openProfile(room.roomId, space?.roomId, userId, btn.getBoundingClientRect()); openProfile(room.roomId, space?.roomId, userId, getMouseEventCords(evt.nativeEvent));
} }
}; };

View File

@@ -27,6 +27,7 @@ import { UserAvatar } from '../../../components/user-avatar';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication'; import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import { useOpenUserRoomProfile } from '../../../state/hooks/userRoomProfile'; import { useOpenUserRoomProfile } from '../../../state/hooks/userRoomProfile';
import { useSpaceOptionally } from '../../../hooks/useSpace'; import { useSpaceOptionally } from '../../../hooks/useSpace';
import { getMouseEventCords } from '../../../utils/dom';
export type ReactionViewerProps = { export type ReactionViewerProps = {
room: Room; room: Room;
@@ -136,7 +137,7 @@ export const ReactionViewer = as<'div', ReactionViewerProps>(
room.roomId, room.roomId,
space?.roomId, space?.roomId,
senderId, senderId,
event.currentTarget.getBoundingClientRect(), getMouseEventCords(event.nativeEvent),
'Bottom' 'Bottom'
); );
}} }}

View File

@@ -51,7 +51,7 @@ const DEFAULT_TAGS: PowerLevelTags = {
color: '#ff6a00', color: '#ff6a00',
}, },
150: { 150: {
name: 'Co-Founder', name: 'Manager',
color: '#ff6a7f', color: '#ff6a7f',
}, },
101: { 101: {

View File

@@ -15,7 +15,7 @@ export function AuthFooter() {
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
v4.9.0 v4.9.1
</Text> </Text>
<Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer"> <Text as="a" size="T300" href="https://twitter.com/cinnyapp" target="_blank" rel="noreferrer">
Twitter Twitter

View File

@@ -24,7 +24,7 @@ export function WelcomePage() {
target="_blank" target="_blank"
rel="noreferrer noopener" rel="noreferrer noopener"
> >
v4.9.0 v4.9.1
</a> </a>
</span> </span>
} }

View File

@@ -30,10 +30,12 @@ import {
NavLink, NavLink,
} from '../../../components/nav'; } from '../../../components/nav';
import { import {
encodeSearchParamValueArray,
getExplorePath, getExplorePath,
getHomeCreatePath, getHomeCreatePath,
getHomeRoomPath, getHomeRoomPath,
getHomeSearchPath, getHomeSearchPath,
withSearchParam,
} from '../../pathUtils'; } from '../../pathUtils';
import { getCanonicalAliasOrRoomId } from '../../../utils/matrix'; import { getCanonicalAliasOrRoomId } from '../../../utils/matrix';
import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom'; import { useSelectedRoom } from '../../../hooks/router/useSelectedRoom';
@@ -49,7 +51,6 @@ import { makeNavCategoryId } from '../../../state/closedNavCategories';
import { roomToUnreadAtom } from '../../../state/room/roomToUnread'; import { roomToUnreadAtom } from '../../../state/room/roomToUnread';
import { useCategoryHandler } from '../../../hooks/useCategoryHandler'; import { useCategoryHandler } from '../../../hooks/useCategoryHandler';
import { useNavToActivePathMapper } from '../../../hooks/useNavToActivePathMapper'; import { useNavToActivePathMapper } from '../../../hooks/useNavToActivePathMapper';
import { openJoinAlias } from '../../../../client/action/navigation';
import { PageNav, PageNavHeader, PageNavContent } from '../../../components/page'; import { PageNav, PageNavHeader, PageNavContent } from '../../../components/page';
import { useRoomsUnread } from '../../../state/hooks/unread'; import { useRoomsUnread } from '../../../state/hooks/unread';
import { markAsRead } from '../../../../client/action/notifications'; import { markAsRead } from '../../../../client/action/notifications';
@@ -61,6 +62,9 @@ import {
getRoomNotificationMode, getRoomNotificationMode,
useRoomsNotificationPreferencesContext, useRoomsNotificationPreferencesContext,
} from '../../../hooks/useRoomsNotificationPreferences'; } from '../../../hooks/useRoomsNotificationPreferences';
import { UseStateProvider } from '../../../components/UseStateProvider';
import { JoinAddressPrompt } from '../../../components/join-address-prompt';
import { _RoomSearchParams } from '../../paths';
type HomeMenuProps = { type HomeMenuProps = {
requestClose: () => void; requestClose: () => void;
@@ -77,11 +81,6 @@ const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, re
requestClose(); requestClose();
}; };
const handleJoinAddress = () => {
openJoinAlias();
requestClose();
};
return ( return (
<Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}> <Menu ref={ref} style={{ maxWidth: toRem(160), width: '100vw' }}>
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}> <Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
@@ -96,16 +95,6 @@ const HomeMenu = forwardRef<HTMLDivElement, HomeMenuProps>(({ requestClose }, re
Mark as Read Mark as Read
</Text> </Text>
</MenuItem> </MenuItem>
<MenuItem
onClick={handleJoinAddress}
size="300"
radii="300"
after={<Icon size="100" src={Icons.Link} />}
>
<Text style={{ flexGrow: 1 }} as="span" size="T300" truncate>
Join with Address
</Text>
</MenuItem>
</Box> </Box>
</Menu> </Menu>
); );
@@ -268,22 +257,44 @@ export function Home() {
</NavItemContent> </NavItemContent>
</NavButton> </NavButton>
</NavItem> </NavItem>
<NavItem variant="Background" radii="400"> <UseStateProvider initial={false}>
<NavButton onClick={() => openJoinAlias()}> {(open, setOpen) => (
<NavItemContent> <>
<Box as="span" grow="Yes" alignItems="Center" gap="200"> <NavItem variant="Background" radii="400">
<Avatar size="200" radii="400"> <NavButton onClick={() => setOpen(true)}>
<Icon src={Icons.Link} size="100" /> <NavItemContent>
</Avatar> <Box as="span" grow="Yes" alignItems="Center" gap="200">
<Box as="span" grow="Yes"> <Avatar size="200" radii="400">
<Text as="span" size="Inherit" truncate> <Icon src={Icons.Link} size="100" />
Join with Address </Avatar>
</Text> <Box as="span" grow="Yes">
</Box> <Text as="span" size="Inherit" truncate>
</Box> Join with Address
</NavItemContent> </Text>
</NavButton> </Box>
</NavItem> </Box>
</NavItemContent>
</NavButton>
</NavItem>
{open && (
<JoinAddressPrompt
onCancel={() => setOpen(false)}
onOpen={(roomIdOrAlias, viaServers, eventId) => {
setOpen(false);
const path = getHomeRoomPath(roomIdOrAlias, eventId);
navigate(
viaServers
? withSearchParam<_RoomSearchParams>(path, {
viaServers: encodeSearchParamValueArray(viaServers),
})
: path
);
}}
/>
)}
</>
)}
</UseStateProvider>
<NavItem variant="Background" radii="400" aria-selected={searchSelected}> <NavItem variant="Background" radii="400" aria-selected={searchSelected}>
<NavLink to={getHomeSearchPath()}> <NavLink to={getHomeSearchPath()}>
<NavItemContent> <NavItemContent>

View File

@@ -7,15 +7,22 @@ import { stopPropagation } from '../../../utils/keyboard';
import { SequenceCard } from '../../../components/sequence-card'; import { SequenceCard } from '../../../components/sequence-card';
import { SettingTile } from '../../../components/setting-tile'; import { SettingTile } from '../../../components/setting-tile';
import { ContainerColor } from '../../../styles/ContainerColor.css'; import { ContainerColor } from '../../../styles/ContainerColor.css';
import { openJoinAlias } from '../../../../client/action/navigation'; import {
import { getCreatePath } from '../../pathUtils'; encodeSearchParamValueArray,
getCreatePath,
getSpacePath,
withSearchParam,
} from '../../pathUtils';
import { useCreateSelected } from '../../../hooks/router/useCreateSelected'; import { useCreateSelected } from '../../../hooks/router/useCreateSelected';
import { JoinAddressPrompt } from '../../../components/join-address-prompt';
import { _RoomSearchParams } from '../../paths';
export function CreateTab() { export function CreateTab() {
const createSelected = useCreateSelected(); const createSelected = useCreateSelected();
const navigate = useNavigate(); const navigate = useNavigate();
const [menuCords, setMenuCords] = useState<RectCords>(); const [menuCords, setMenuCords] = useState<RectCords>();
const [joinAddress, setJoinAddress] = useState(false);
const handleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => { const handleMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
setMenuCords(menuCords ? undefined : evt.currentTarget.getBoundingClientRect()); setMenuCords(menuCords ? undefined : evt.currentTarget.getBoundingClientRect());
@@ -27,7 +34,7 @@ export function CreateTab() {
}; };
const handleJoinWithAddress = () => { const handleJoinWithAddress = () => {
openJoinAlias(); setJoinAddress(true);
setMenuCords(undefined); setMenuCords(undefined);
}; };
@@ -103,6 +110,22 @@ export function CreateTab() {
> >
<Icon src={Icons.Plus} /> <Icon src={Icons.Plus} />
</SidebarAvatar> </SidebarAvatar>
{joinAddress && (
<JoinAddressPrompt
onCancel={() => setJoinAddress(false)}
onOpen={(roomIdOrAlias, viaServers) => {
setJoinAddress(false);
const path = getSpacePath(roomIdOrAlias);
navigate(
viaServers
? withSearchParam<_RoomSearchParams>(path, {
viaServers: encodeSearchParamValueArray(viaServers),
})
: path
);
}}
/>
)}
</PopOut> </PopOut>
)} )}
</SidebarItemTooltip> </SidebarItemTooltip>

View File

@@ -297,7 +297,7 @@ function SpaceHeader() {
type SpaceTombstoneProps = { roomId: string; replacementRoomId: string }; type SpaceTombstoneProps = { roomId: string; replacementRoomId: string };
export function SpaceTombstone({ roomId, replacementRoomId }: SpaceTombstoneProps) { export function SpaceTombstone({ roomId, replacementRoomId }: SpaceTombstoneProps) {
const mx = useMatrixClient(); const mx = useMatrixClient();
const { navigateRoom } = useRoomNavigate(); const { navigateSpace } = useRoomNavigate();
const [joinState, handleJoin] = useAsyncCallback( const [joinState, handleJoin] = useAsyncCallback(
useCallback(() => { useCallback(() => {
@@ -311,8 +311,8 @@ export function SpaceTombstone({ roomId, replacementRoomId }: SpaceTombstoneProp
const replacementRoom = mx.getRoom(replacementRoomId); const replacementRoom = mx.getRoom(replacementRoomId);
const handleOpen = () => { const handleOpen = () => {
if (replacementRoom) navigateRoom(replacementRoom.roomId); if (replacementRoom) navigateSpace(replacementRoom.roomId);
if (joinState.status === AsyncStatus.Success) navigateRoom(joinState.data.roomId); if (joinState.status === AsyncStatus.Success) navigateSpace(joinState.data.roomId);
}; };
return ( return (

View File

@@ -42,9 +42,9 @@ const MATRIX_TO = /^https?:\/\/matrix\.to\S*$/;
export const testMatrixTo = (href: string): boolean => MATRIX_TO.test(href); export const testMatrixTo = (href: string): boolean => MATRIX_TO.test(href);
const MATRIX_TO_USER = /^https?:\/\/matrix\.to\/#\/(@[^:\s]+:[^?/\s]+)\/?$/; const MATRIX_TO_USER = /^https?:\/\/matrix\.to\/#\/(@[^:\s]+:[^?/\s]+)\/?$/;
const MATRIX_TO_ROOM = /^https?:\/\/matrix\.to\/#\/([#!][^:\s]+:[^?/\s]+)\/?(\?[\S]*)?$/; const MATRIX_TO_ROOM = /^https?:\/\/matrix\.to\/#\/([#!][^?/\s]+)\/?(\?[\S]*)?$/;
const MATRIX_TO_ROOM_EVENT = const MATRIX_TO_ROOM_EVENT =
/^https?:\/\/matrix\.to\/#\/([#!][^:\s]+:[^?/\s]+)\/(\$[^?/\s]+)\/?(\?[\S]*)?$/; /^https?:\/\/matrix\.to\/#\/([#!][^?/\s]+)\/(\$[^?/\s]+)\/?(\?[\S]*)?$/;
export const parseMatrixToUser = (href: string): string | undefined => { export const parseMatrixToUser = (href: string): string | undefined => {
const match = href.match(MATRIX_TO_USER); const match = href.match(MATRIX_TO_USER);

View File

@@ -1,11 +1,19 @@
import { Room } from 'matrix-js-sdk'; import { Room } from 'matrix-js-sdk';
import { IPowerLevels } from '../hooks/usePowerLevels'; import { IPowerLevels } from '../hooks/usePowerLevels';
import { getMxIdServer } from '../utils/matrix'; import { creatorsSupported, getMxIdServer } from '../utils/matrix';
import { StateEvent } from '../../types/matrix/room'; import { IRoomCreateContent, StateEvent } from '../../types/matrix/room';
import { getStateEvent } from '../utils/room'; import { getStateEvent } from '../utils/room';
export const getViaServers = (room: Room): string[] => { export const getViaServers = (room: Room): string[] => {
const getHighestPowerUserId = (): string | undefined => { const getHighestPowerUserId = (): string | undefined => {
const creatorEvent = getStateEvent(room, StateEvent.RoomCreate);
if (
creatorEvent &&
creatorsSupported(creatorEvent.getContent<IRoomCreateContent>().room_version)
) {
return creatorEvent.getSender();
}
const powerLevels = getStateEvent(room, StateEvent.RoomPowerLevels)?.getContent<IPowerLevels>(); const powerLevels = getStateEvent(room, StateEvent.RoomPowerLevels)?.getContent<IPowerLevels>();
if (!powerLevels) return undefined; if (!powerLevels) return undefined;

View File

@@ -43,6 +43,17 @@ export const canFitInScrollView = (
export type FilesOrFile<T extends boolean | undefined = undefined> = T extends true ? File[] : File; export type FilesOrFile<T extends boolean | undefined = undefined> = T extends true ? File[] : File;
export const getFilesFromFileList = (fileList: FileList): File[] => {
const files: File[] = [];
for (let i = 0; i < fileList.length; i += 1) {
const file: File | undefined = fileList[i];
if (file instanceof File) files.push(file);
}
return files;
};
export const selectFile = <M extends boolean | undefined = undefined>( export const selectFile = <M extends boolean | undefined = undefined>(
accept: string, accept: string,
multiple?: M multiple?: M
@@ -58,7 +69,7 @@ export const selectFile = <M extends boolean | undefined = undefined>(
if (!fileList) { if (!fileList) {
resolve(undefined); resolve(undefined);
} else { } else {
const files: File[] = [...fileList].filter((file) => file); const files: File[] = getFilesFromFileList(fileList);
resolve((multiple ? files : files[0]) as FilesOrFile<M>); resolve((multiple ? files : files[0]) as FilesOrFile<M>);
} }
input.removeEventListener('change', changeHandler); input.removeEventListener('change', changeHandler);
@@ -70,7 +81,7 @@ export const selectFile = <M extends boolean | undefined = undefined>(
export const getDataTransferFiles = (dataTransfer: DataTransfer): File[] | undefined => { export const getDataTransferFiles = (dataTransfer: DataTransfer): File[] | undefined => {
const fileList = dataTransfer.files; const fileList = dataTransfer.files;
const files = [...fileList].filter((file) => file); const files: File[] = getFilesFromFileList(fileList);
if (files.length === 0) return undefined; if (files.length === 0) return undefined;
return files; return files;
}; };
@@ -224,3 +235,10 @@ export const notificationPermission = (permission: NotificationPermission) => {
} }
return false; return false;
}; };
export const getMouseEventCords = (event: MouseEvent) => ({
x: event.clientX,
y: event.clientY,
width: 0,
height: 0,
});

View File

@@ -20,6 +20,7 @@ import {
import { CryptoBackend } from 'matrix-js-sdk/lib/common-crypto/CryptoBackend'; import { CryptoBackend } from 'matrix-js-sdk/lib/common-crypto/CryptoBackend';
import { AccountDataEvent } from '../../types/matrix/accountData'; import { AccountDataEvent } from '../../types/matrix/accountData';
import { import {
IRoomCreateContent,
Membership, Membership,
MessageEvent, MessageEvent,
NotificationType, NotificationType,
@@ -43,7 +44,7 @@ export const getStateEvents = (room: Room, eventType: StateEvent): MatrixEvent[]
export const getAccountData = ( export const getAccountData = (
mx: MatrixClient, mx: MatrixClient,
eventType: AccountDataEvent eventType: AccountDataEvent
): MatrixEvent | undefined => mx.getAccountData(eventType); ): MatrixEvent | undefined => mx.getAccountData(eventType as any);
export const getMDirects = (mDirectEvent: MatrixEvent): Set<string> => { export const getMDirects = (mDirectEvent: MatrixEvent): Set<string> => {
const roomIds = new Set<string>(); const roomIds = new Set<string>();
@@ -480,6 +481,23 @@ export const bannedInRooms = (mx: MatrixClient, rooms: string[], otherUserId: st
return banned; return banned;
}); });
export const getAllVersionsRoomCreator = (room: Room): Set<string> => {
const creators = new Set<string>();
const createEvent = getStateEvent(room, StateEvent.RoomCreate);
const createContent = createEvent?.getContent<IRoomCreateContent>();
const creator = createEvent?.getSender();
if (typeof creator === 'string') creators.add(creator);
if (createContent && Array.isArray(createContent.additional_creators)) {
createContent.additional_creators.forEach((c) => {
if (typeof c === 'string') creators.add(c);
});
}
return creators;
};
export const guessPerfectParent = ( export const guessPerfectParent = (
mx: MatrixClient, mx: MatrixClient,
roomId: string, roomId: string,
@@ -490,15 +508,29 @@ export const guessPerfectParent = (
} }
const getSpecialUsers = (rId: string): string[] => { const getSpecialUsers = (rId: string): string[] => {
const specialUsers: Set<string> = new Set();
const r = mx.getRoom(rId); const r = mx.getRoom(rId);
const powerLevels = if (!r) return [];
r && getStateEvent(r, StateEvent.RoomPowerLevels)?.getContent<IPowerLevelsContent>();
getAllVersionsRoomCreator(r).forEach((c) => specialUsers.add(c));
const powerLevels = getStateEvent(
r,
StateEvent.RoomPowerLevels
)?.getContent<IPowerLevelsContent>();
const { users_default: usersDefault, users } = powerLevels ?? {}; const { users_default: usersDefault, users } = powerLevels ?? {};
if (typeof users !== 'object') return [];
const defaultPower = typeof usersDefault === 'number' ? usersDefault : 0; const defaultPower = typeof usersDefault === 'number' ? usersDefault : 0;
return Object.keys(users).filter((userId) => users[userId] > defaultPower);
if (typeof users === 'object')
Object.keys(users).forEach((userId) => {
if (users[userId] > defaultPower) {
specialUsers.add(userId);
}
});
return Array.from(specialUsers);
}; };
let perfectParent: string | undefined; let perfectParent: string | undefined;

View File

@@ -1,5 +1,5 @@
const cons = { const cons = {
version: '4.9.0', version: '4.9.1',
secretKey: { secretKey: {
ACCESS_TOKEN: 'cinny_access_token', ACCESS_TOKEN: 'cinny_access_token',
DEVICE_ID: 'cinny_device_id', DEVICE_ID: 'cinny_device_id',

View File

@@ -70,6 +70,7 @@ export type IRoomCreateContent = {
['m.federate']?: boolean; ['m.federate']?: boolean;
room_version: string; room_version: string;
type?: string; type?: string;
additional_creators?: string[];
predecessor?: { predecessor?: {
event_id?: string; event_id?: string;
room_id: string; room_id: string;