Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 58 additions & 32 deletions src/app/features/create-post/CreatePost.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import React, {
useState,
} from 'react';
import { useAtomValue } from 'jotai';
import { IContent, MatrixError, MsgType, Room } from 'matrix-js-sdk';
import { IContent, JoinRule, MatrixError, MsgType, Room } from 'matrix-js-sdk';
import FocusTrap from 'focus-trap-react';
import { isKeyHotkey } from 'is-hotkey';
import {
Expand Down Expand Up @@ -55,7 +55,7 @@ import {
roomUploadAtomFamily,
} from '../../state/room/roomInputDrafts';
import { UploadCardRenderer } from '../../components/upload-card';
import { useFeedRooms } from '../feed';
import { useSpacesWithPostsRoom } from '../feed';
import {
getAudioMsgContent,
getFileMsgContent,
Expand Down Expand Up @@ -85,24 +85,32 @@ export function CreatePostForm({ defaultRoomId, onCreate }: CreatePostFormProps)
const alive = useAlive();
const editor = useEditor();
const useAuthentication = useMediaAuthentication();
const feedRoomIds = useFeedRooms();
const spacesWithPostsRoom = useSpacesWithPostsRoom();
const isComposing = useComposingCheck();

const [isMarkdown] = useSetting(settingsAtom, 'isMarkdown');
const [globalToolbar] = useSetting(settingsAtom, 'editorToolbar');
const [toolbar, setToolbar] = useState(globalToolbar);

const feedRooms = useMemo(
const spaces = useMemo(
() =>
feedRoomIds.map((roomId) => mx.getRoom(roomId)).filter((room): room is Room => room !== null),
[mx, feedRoomIds]
spacesWithPostsRoom
.map(({ spaceId }) => mx.getRoom(spaceId))
.filter((room): room is Room => room !== null),
[mx, spacesWithPostsRoom]
);

const [roomId, setRoomId] = useState<string | undefined>(
(defaultRoomId && feedRoomIds.includes(defaultRoomId) ? defaultRoomId : undefined) ??
feedRoomIds[0]
const [spaceId, setSpaceId] = useState<string | undefined>(
(defaultRoomId &&
spacesWithPostsRoom.find(
(space) => space.spaceId === defaultRoomId || space.postsRoomId === defaultRoomId
)?.spaceId) ??
spacesWithPostsRoom[0]?.spaceId
);
const selectedRoom = roomId ? mx.getRoom(roomId) : undefined;
const selectedSpace = spaceId ? mx.getRoom(spaceId) : undefined;
const postsRoomId = spacesWithPostsRoom.find((space) => space.spaceId === spaceId)?.postsRoomId;
const selectedRoom = postsRoomId ? mx.getRoom(postsRoomId) : undefined;
const isPublicRoom = selectedRoom?.getJoinRule() === JoinRule.Public;
const [menuAnchor, setMenuAnchor] = useState<RectCords>();

const [selectedFiles, setSelectedFiles] = useState<TUploadItem[]>([]);
Expand Down Expand Up @@ -162,7 +170,7 @@ export function CreatePostForm({ defaultRoomId, onCreate }: CreatePostFormProps)

const sendAttachments = useCallback(
async (successUploads: UploadSuccess[], caption?: { body: string; formattedBody?: string }) => {
if (!roomId) return;
if (!postsRoomId) return;
const contentsPromises = successUploads.map(async (upload) => {
const fileItem = selectedFiles.find((f) => f.file === upload.file);
if (!fileItem) throw new Error('Broken upload');
Expand All @@ -184,19 +192,21 @@ export function CreatePostForm({ defaultRoomId, onCreate }: CreatePostFormProps)
}
}
await Promise.all(
contents.map((content) => mx.sendMessage(roomId, { ...content, 'm.post': true } as any))
contents.map((content) =>
mx.sendMessage(postsRoomId, { ...content, 'm.post': true } as any)
)
);
successUploads.forEach((upload) => roomUploadAtomFamily.remove(upload.file));
setSelectedFiles((items) =>
items.filter((item) => !successUploads.some((u) => u.file === item.file))
);
},
[mx, roomId, selectedFiles]
[mx, postsRoomId, selectedFiles]
);

const [createState, create] = useAsyncCallback<void, Error | MatrixError, []>(
useCallback(async () => {
if (!roomId) throw new Error('Select a room to share to');
if (!postsRoomId) throw new Error('Select a space to share to');

const plainText = toPlainText(editor.children, isMarkdown).trim();
const hasText = plainText !== '';
Expand Down Expand Up @@ -230,7 +240,7 @@ export function CreatePostForm({ defaultRoomId, onCreate }: CreatePostFormProps)
}

if (hasText && !mergeCaptionIntoAttachment) {
const mentionData = getMentions(mx, roomId, editor);
const mentionData = getMentions(mx, postsRoomId, editor);
const mMentions = getMentionContent(Array.from(mentionData.users), mentionData.room);

const content: IContent = {
Expand All @@ -244,12 +254,21 @@ export function CreatePostForm({ defaultRoomId, onCreate }: CreatePostFormProps)
content.formatted_body = customHtml;
}

await mx.sendMessage(roomId, content as any);
await mx.sendMessage(postsRoomId, content as any);
}

resetEditor(editor);
resetEditorHistory(editor);
}, [mx, roomId, editor, isMarkdown, selectedFiles, uploads, uploadsPending, sendAttachments])
}, [
mx,
postsRoomId,
editor,
isMarkdown,
selectedFiles,
uploads,
uploadsPending,
sendAttachments,
])
);
const loading = createState.status === AsyncStatus.Loading;
const error = createState.status === AsyncStatus.Error ? createState.error : undefined;
Expand Down Expand Up @@ -279,8 +298,8 @@ export function CreatePostForm({ defaultRoomId, onCreate }: CreatePostFormProps)
setMenuAnchor(evt.currentTarget.getBoundingClientRect());
};

const handleRoomSelect = (id: string) => {
setRoomId(id);
const handleSpaceSelect = (id: string) => {
setSpaceId(id);
setMenuAnchor(undefined);
};

Expand All @@ -307,13 +326,13 @@ export function CreatePostForm({ defaultRoomId, onCreate }: CreatePostFormProps)
type="button"
variant="SurfaceVariant"
radii="400"
before={selectedRoom ? renderRoomAvatar(selectedRoom) : undefined}
before={selectedSpace ? renderRoomAvatar(selectedSpace) : undefined}
after={<Icon size="100" src={Icons.ChevronBottom} />}
onClick={handleOpenMenu}
disabled={loading}
>
<Text size="T400" truncate>
{selectedRoom ? selectedRoom.name : 'Select a room'}
{selectedSpace ? selectedSpace.name : 'Select a space'}
</Text>
</Chip>
<PopOut
Expand All @@ -332,24 +351,24 @@ export function CreatePostForm({ defaultRoomId, onCreate }: CreatePostFormProps)
>
<Menu style={{ maxHeight: '30vh', width: toRem(220), overflowY: 'auto' }}>
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
{feedRooms.length === 0 && (
{spaces.length === 0 && (
<Text size="T300" style={{ padding: config.space.S200 }}>
No rooms available
No spaces available
</Text>
)}
{feedRooms.map((room) => (
{spaces.map((space) => (
<MenuItem
key={room.roomId}
key={space.roomId}
type="button"
size="300"
radii="300"
variant={room.roomId === roomId ? 'Success' : 'Surface'}
aria-pressed={room.roomId === roomId}
before={renderRoomAvatar(room)}
onClick={() => handleRoomSelect(room.roomId)}
variant={space.roomId === spaceId ? 'Success' : 'Surface'}
aria-pressed={space.roomId === spaceId}
before={renderRoomAvatar(space)}
onClick={() => handleSpaceSelect(space.roomId)}
>
<Text truncate size="T400">
{room.name}
{space.name}
</Text>
</MenuItem>
))}
Expand All @@ -358,6 +377,13 @@ export function CreatePostForm({ defaultRoomId, onCreate }: CreatePostFormProps)
</FocusTrap>
}
/>
{selectedRoom && (
<Text size="T200" priority="300">
{isPublicRoom
? 'This will be shared publicly'
: 'This will only be visible to members of this room'}
</Text>
)}
</Box>
<Box grow="Yes" shrink="No" direction="Column" gap="100">
<Text size="L400">Content</Text>
Expand Down Expand Up @@ -388,7 +414,7 @@ export function CreatePostForm({ defaultRoomId, onCreate }: CreatePostFormProps)
size="300"
radii="300"
onClick={() => pickFile('*')}
disabled={!roomId}
disabled={!postsRoomId}
aria-label="Attach media or files"
>
<Icon src={Icons.PlusCircle} />
Expand Down Expand Up @@ -439,7 +465,7 @@ export function CreatePostForm({ defaultRoomId, onCreate }: CreatePostFormProps)
size="500"
variant="Primary"
radii="400"
disabled={loading || !roomId || uploadsPending}
disabled={loading || !postsRoomId || uploadsPending}
before={loading && <Spinner variant="Primary" fill="Solid" size="200" />}
onClick={submit}
>
Expand Down
39 changes: 38 additions & 1 deletion src/app/features/feed/useFeedRooms.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { useCallback } from 'react';
import { useCallback, useMemo } from 'react';
import { useAtomValue } from 'jotai';
import {
RoomSelector,
useRecursiveChildRoomScopeFactory,
useSelectedRooms,
useSpaceChildren,
useSpaces,
} from '../../state/hooks/roomList';
import { allRoomsAtom } from '../../state/room-list/roomList';
import { mDirectAtom } from '../../state/mDirectList';
Expand All @@ -29,3 +30,39 @@ export const useSpaceFeedRooms = (spaceId: string): string[] => {
useRecursiveChildRoomScopeFactory(mx, mDirects, roomToParents)
);
};

const POSTS_ROOM_SUFFIX = '-posts';

export type SpaceWithPostsRoom = {
spaceId: string;
postsRoomId: string;
};

/**
* Spaces that contain at least one room whose name ends with "-posts",
* paired with the id of the first such room (the post destination for that space).
*/
export const useSpacesWithPostsRoom = (): SpaceWithPostsRoom[] => {
const mx = useMatrixClient();
const allRooms = useAtomValue(allRoomsAtom);
const mDirects = useAtomValue(mDirectAtom);
const roomToParents = useAtomValue(roomToParentsAtom);
const spaceIds = useSpaces(mx, allRoomsAtom);
const childRoomSelectorFactory = useRecursiveChildRoomScopeFactory(mx, mDirects, roomToParents);

return useMemo(
() =>
spaceIds.reduce<SpaceWithPostsRoom[]>((spacesWithPostsRoom, spaceId) => {
const childRoomSelector = childRoomSelectorFactory(spaceId);
const postsRoomId = allRooms.find(
(roomId) =>
childRoomSelector(roomId) && mx.getRoom(roomId)?.name.endsWith(POSTS_ROOM_SUFFIX)
);
if (postsRoomId) {
spacesWithPostsRoom.push({ spaceId, postsRoomId });
}
return spacesWithPostsRoom;
}, []),
[mx, allRooms, spaceIds, childRoomSelectorFactory]
);
};