Merge branch 'chat-composer' into 'develop'
Chats: move chat attachments into composer See merge request soapbox-pub/soapbox!2229
This commit is contained in:
commit
d44be7fbf8
13 changed files with 290 additions and 60 deletions
|
@ -1,13 +1,32 @@
|
|||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { spring } from 'react-motion';
|
||||
|
||||
import Motion from 'soapbox/features/ui/util/optional-motion';
|
||||
|
||||
interface IProgressBar {
|
||||
progress: number,
|
||||
/** Number between 0 and 1 to represent the percentage complete. */
|
||||
progress: number
|
||||
/** Height of the progress bar. */
|
||||
size?: 'sm' | 'md'
|
||||
}
|
||||
|
||||
/** A horizontal meter filled to the given percentage. */
|
||||
const ProgressBar: React.FC<IProgressBar> = ({ progress }) => (
|
||||
<div className='h-2.5 w-full overflow-hidden rounded-full bg-gray-300 dark:bg-primary-800'>
|
||||
<div className='h-full bg-secondary-500' style={{ width: `${Math.floor(progress * 100)}%` }} />
|
||||
const ProgressBar: React.FC<IProgressBar> = ({ progress, size = 'md' }) => (
|
||||
<div
|
||||
className={clsx('h-2.5 w-full overflow-hidden rounded-lg bg-gray-300 dark:bg-primary-800', {
|
||||
'h-2.5': size === 'md',
|
||||
'h-[6px]': size === 'sm',
|
||||
})}
|
||||
>
|
||||
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress * 100) }}>
|
||||
{({ width }) => (
|
||||
<div
|
||||
className='h-full bg-secondary-500'
|
||||
style={{ width: `${width}%` }}
|
||||
/>
|
||||
)}
|
||||
</Motion>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
|
@ -26,6 +26,8 @@ interface ITextarea extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaElemen
|
|||
hasError?: boolean,
|
||||
/** Whether or not you can resize the teztarea */
|
||||
isResizeable?: boolean,
|
||||
/** Textarea theme. */
|
||||
theme?: 'default' | 'transparent',
|
||||
}
|
||||
|
||||
/** Textarea with custom styles. */
|
||||
|
@ -37,6 +39,7 @@ const Textarea = React.forwardRef(({
|
|||
autoGrow = false,
|
||||
maxRows = 10,
|
||||
minRows = 1,
|
||||
theme = 'default',
|
||||
...props
|
||||
}: ITextarea, ref: React.ForwardedRef<HTMLTextAreaElement>) => {
|
||||
const [rows, setRows] = useState<number>(autoGrow ? 1 : 4);
|
||||
|
@ -72,9 +75,10 @@ const Textarea = React.forwardRef(({
|
|||
ref={ref}
|
||||
rows={rows}
|
||||
onChange={handleChange}
|
||||
className={clsx({
|
||||
'bg-white dark:bg-transparent shadow-sm block w-full sm:text-sm rounded-md text-gray-900 dark:text-gray-100 placeholder:text-gray-600 dark:placeholder:text-gray-600 border-gray-400 dark:border-gray-800 dark:ring-1 dark:ring-gray-800 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500':
|
||||
true,
|
||||
className={clsx('block w-full rounded-md text-gray-900 placeholder:text-gray-600 dark:text-gray-100 dark:placeholder:text-gray-600 sm:text-sm', {
|
||||
'bg-white dark:bg-transparent shadow-sm border-gray-400 dark:border-gray-800 dark:ring-1 dark:ring-gray-800 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500':
|
||||
theme === 'default',
|
||||
'bg-transparent border-0 focus:border-0 focus:ring-0': theme === 'transparent',
|
||||
'font-mono': isCodeEditor,
|
||||
'text-red-600 border-red-600': hasError,
|
||||
'resize-none': !isResizeable,
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { spring } from 'react-motion';
|
||||
|
||||
import { HStack, Icon, Stack, Text } from 'soapbox/components/ui';
|
||||
import Motion from 'soapbox/features/ui/util/optional-motion';
|
||||
import { HStack, Icon, ProgressBar, Stack, Text } from 'soapbox/components/ui';
|
||||
|
||||
interface IUploadProgress {
|
||||
/** Number between 0 and 1 to represent the percentage complete. */
|
||||
progress: number,
|
||||
/** Number between 0 and 100 to represent the percentage complete. */
|
||||
progress: number
|
||||
}
|
||||
|
||||
/** Displays a progress bar for uploading files. */
|
||||
|
@ -24,16 +22,7 @@ const UploadProgress: React.FC<IUploadProgress> = ({ progress }) => {
|
|||
<FormattedMessage id='upload_progress.label' defaultMessage='Uploading…' />
|
||||
</Text>
|
||||
|
||||
<div className='relative h-1.5 w-full rounded-lg bg-gray-200'>
|
||||
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}>
|
||||
{({ width }) =>
|
||||
(<div
|
||||
className='absolute left-0 top-0 h-1.5 rounded-lg bg-primary-600'
|
||||
style={{ width: `${width}%` }}
|
||||
/>)
|
||||
}
|
||||
</Motion>
|
||||
</div>
|
||||
<ProgressBar progress={progress / 100} size='sm' />
|
||||
</Stack>
|
||||
</HStack>
|
||||
);
|
||||
|
|
|
@ -3,13 +3,16 @@ import { defineMessages, IntlShape, useIntl } from 'react-intl';
|
|||
|
||||
import { unblockAccount } from 'soapbox/actions/accounts';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import { Button, Combobox, ComboboxInput, ComboboxList, ComboboxOption, ComboboxPopover, HStack, IconButton, Stack, Text, Textarea } from 'soapbox/components/ui';
|
||||
import { Button, Combobox, ComboboxInput, ComboboxList, ComboboxOption, ComboboxPopover, HStack, IconButton, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useChatContext } from 'soapbox/contexts/chat-context';
|
||||
import UploadButton from 'soapbox/features/compose/components/upload-button';
|
||||
import { search as emojiSearch } from 'soapbox/features/emoji/emoji-mart-search-light';
|
||||
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
|
||||
import { Attachment } from 'soapbox/types/entities';
|
||||
import { textAtCursorMatchesToken } from 'soapbox/utils/suggestions';
|
||||
|
||||
import ChatTextarea from './chat-textarea';
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: { id: 'chat.input.placeholder', defaultMessage: 'Type a message' },
|
||||
send: { id: 'chat.actions.send', defaultMessage: 'Send' },
|
||||
|
@ -39,7 +42,10 @@ interface IChatComposer extends Pick<React.TextareaHTMLAttributes<HTMLTextAreaEl
|
|||
errorMessage: string | undefined
|
||||
onSelectFile: (files: FileList, intl: IntlShape) => void
|
||||
resetFileKey: number | null
|
||||
hasAttachment?: boolean
|
||||
attachments?: Attachment[]
|
||||
onDeleteAttachment?: () => void
|
||||
isUploading?: boolean
|
||||
uploadProgress?: number
|
||||
}
|
||||
|
||||
/** Textarea input for chats. */
|
||||
|
@ -53,7 +59,10 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
|
|||
onSelectFile,
|
||||
resetFileKey,
|
||||
onPaste,
|
||||
hasAttachment,
|
||||
attachments = [],
|
||||
onDeleteAttachment,
|
||||
isUploading,
|
||||
uploadProgress,
|
||||
}, ref) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
@ -68,6 +77,7 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
|
|||
const [suggestions, setSuggestions] = useState<Suggestion>(initialSuggestionState);
|
||||
const isSuggestionsAvailable = suggestions.list.length > 0;
|
||||
|
||||
const hasAttachment = attachments.length > 0;
|
||||
const isOverCharacterLimit = maxCharacterCount && value?.length > maxCharacterCount;
|
||||
const isSubmitDisabled = disabled || isOverCharacterLimit || (value.length === 0 && !hasAttachment);
|
||||
|
||||
|
@ -167,12 +177,9 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
|
|||
)}
|
||||
|
||||
<Stack grow>
|
||||
<Combobox
|
||||
aria-labelledby='demo'
|
||||
onSelect={onSelectComboboxOption}
|
||||
>
|
||||
<Combobox onSelect={onSelectComboboxOption}>
|
||||
<ComboboxInput
|
||||
as={Textarea}
|
||||
as={ChatTextarea}
|
||||
autoFocus
|
||||
ref={ref}
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
|
@ -184,6 +191,10 @@ const ChatComposer = React.forwardRef<HTMLTextAreaElement | null, IChatComposer>
|
|||
autoGrow
|
||||
maxRows={5}
|
||||
disabled={disabled}
|
||||
attachments={attachments}
|
||||
onDeleteAttachment={onDeleteAttachment}
|
||||
isUploading={isUploading}
|
||||
uploadProgress={uploadProgress}
|
||||
/>
|
||||
{isSuggestionsAvailable ? (
|
||||
<ComboboxPopover>
|
||||
|
|
|
@ -195,13 +195,12 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
|
|||
};
|
||||
|
||||
const maybeRenderMedia = (chatMessage: ChatMessageEntity) => {
|
||||
const { attachment } = chatMessage;
|
||||
if (!attachment) return null;
|
||||
if (!chatMessage.media_attachments.size) return null;
|
||||
return (
|
||||
<Bundle fetchComponent={MediaGallery}>
|
||||
{(Component: any) => (
|
||||
<Component
|
||||
media={ImmutableList([attachment])}
|
||||
media={chatMessage.media_attachments}
|
||||
onOpenMedia={onOpenMedia}
|
||||
visible
|
||||
/>
|
||||
|
@ -316,7 +315,7 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
|
|||
space={0.5}
|
||||
className={clsx({
|
||||
'max-w-[85%]': true,
|
||||
'flex-1': chatMessage.attachment,
|
||||
'flex-1': !!chatMessage.media_attachments.size,
|
||||
'order-2': isMyMessage,
|
||||
'order-1': !isMyMessage,
|
||||
})}
|
||||
|
@ -331,8 +330,8 @@ const ChatMessageList: React.FC<IChatMessageList> = ({ chat }) => {
|
|||
className={
|
||||
clsx({
|
||||
'text-ellipsis break-words relative rounded-md py-2 px-3 max-w-full space-y-2 [&_.mention]:underline': true,
|
||||
'rounded-tr-sm': chatMessage.attachment && isMyMessage,
|
||||
'rounded-tl-sm': chatMessage.attachment && !isMyMessage,
|
||||
'rounded-tr-sm': (!!chatMessage.media_attachments.size) && isMyMessage,
|
||||
'rounded-tl-sm': (!!chatMessage.media_attachments.size) && !isMyMessage,
|
||||
'[&_.mention]:text-primary-600 dark:[&_.mention]:text-accent-blue': !isMyMessage,
|
||||
'[&_.mention]:text-white dark:[&_.mention]:white': isMyMessage,
|
||||
'bg-primary-500 text-white': isMyMessage,
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
|
||||
import { ProgressBar } from 'soapbox/components/ui';
|
||||
|
||||
interface IChatPendingUpload {
|
||||
progress: number
|
||||
}
|
||||
|
||||
/** Displays a loading thumbnail for an upload in the chat composer. */
|
||||
const ChatPendingUpload: React.FC<IChatPendingUpload> = ({ progress }) => {
|
||||
return (
|
||||
<div className='relative isolate inline-flex h-24 w-24 items-center justify-center overflow-hidden rounded-lg bg-gray-200 p-4 dark:bg-primary-900'>
|
||||
<ProgressBar progress={progress} size='sm' />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatPendingUpload;
|
58
app/soapbox/features/chats/components/chat-textarea.tsx
Normal file
58
app/soapbox/features/chats/components/chat-textarea.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Textarea } from 'soapbox/components/ui';
|
||||
import { Attachment } from 'soapbox/types/entities';
|
||||
|
||||
import ChatPendingUpload from './chat-pending-upload';
|
||||
import ChatUpload from './chat-upload';
|
||||
|
||||
interface IChatTextarea extends React.ComponentProps<typeof Textarea> {
|
||||
attachments?: Attachment[]
|
||||
onDeleteAttachment?: () => void
|
||||
isUploading?: boolean
|
||||
uploadProgress?: number
|
||||
}
|
||||
|
||||
/** Custom textarea for chats. */
|
||||
const ChatTextarea: React.FC<IChatTextarea> = ({
|
||||
attachments,
|
||||
onDeleteAttachment,
|
||||
isUploading = false,
|
||||
uploadProgress = 0,
|
||||
...rest
|
||||
}) => {
|
||||
return (
|
||||
<div className={`
|
||||
block
|
||||
w-full
|
||||
rounded-md border border-gray-400
|
||||
bg-white text-gray-900
|
||||
shadow-sm placeholder:text-gray-600
|
||||
focus-within:border-primary-500
|
||||
focus-within:ring-1 focus-within:ring-primary-500 dark:border-gray-800 dark:bg-gray-800
|
||||
dark:text-gray-100 dark:ring-1 dark:ring-gray-800 dark:placeholder:text-gray-600 dark:focus-within:border-primary-500
|
||||
dark:focus-within:ring-primary-500 sm:text-sm
|
||||
`}
|
||||
>
|
||||
{(!!attachments?.length || isUploading) && (
|
||||
<div className='flex p-3 pb-0'>
|
||||
{isUploading && (
|
||||
<ChatPendingUpload progress={uploadProgress} />
|
||||
)}
|
||||
|
||||
{attachments?.map(attachment => (
|
||||
<ChatUpload
|
||||
key={attachment.id}
|
||||
attachment={attachment}
|
||||
onDelete={onDeleteAttachment}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Textarea theme='transparent' {...rest} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatTextarea;
|
|
@ -0,0 +1,55 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Icon } from 'soapbox/components/ui';
|
||||
import { MIMETYPE_ICONS } from 'soapbox/components/upload';
|
||||
|
||||
import type { Attachment } from 'soapbox/types/entities';
|
||||
|
||||
const defaultIcon = require('@tabler/icons/paperclip.svg');
|
||||
|
||||
interface IChatUploadPreview {
|
||||
className?: string
|
||||
attachment: Attachment
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a generic preview for an upload depending on its media type.
|
||||
* It fills its container and is expected to be sized by its parent.
|
||||
*/
|
||||
const ChatUploadPreview: React.FC<IChatUploadPreview> = ({ className, attachment }) => {
|
||||
const mimeType = attachment.pleroma.get('mime_type') as string | undefined;
|
||||
|
||||
switch (attachment.type) {
|
||||
case 'image':
|
||||
return (
|
||||
<img
|
||||
className='pointer-events-none h-full w-full object-cover'
|
||||
src={attachment.preview_url}
|
||||
alt=''
|
||||
/>
|
||||
);
|
||||
case 'video':
|
||||
return (
|
||||
<video
|
||||
className='pointer-events-none h-full w-full object-cover'
|
||||
src={attachment.preview_url}
|
||||
autoPlay
|
||||
playsInline
|
||||
controls={false}
|
||||
muted
|
||||
loop
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div className='pointer-events-none flex h-full w-full items-center justify-center'>
|
||||
<Icon
|
||||
className='mx-auto my-12 h-16 w-16 text-gray-800 dark:text-gray-200'
|
||||
src={MIMETYPE_ICONS[mimeType || ''] || defaultIcon}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default ChatUploadPreview;
|
66
app/soapbox/features/chats/components/chat-upload.tsx
Normal file
66
app/soapbox/features/chats/components/chat-upload.tsx
Normal file
|
@ -0,0 +1,66 @@
|
|||
import clsx from 'clsx';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import React from 'react';
|
||||
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import Blurhash from 'soapbox/components/blurhash';
|
||||
import { Icon } from 'soapbox/components/ui';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
|
||||
import ChatUploadPreview from './chat-upload-preview';
|
||||
|
||||
import type { Attachment } from 'soapbox/types/entities';
|
||||
|
||||
interface IChatUpload {
|
||||
attachment: Attachment,
|
||||
onDelete?(): void,
|
||||
}
|
||||
|
||||
/** An attachment uploaded to the chat composer, before sending. */
|
||||
const ChatUpload: React.FC<IChatUpload> = ({ attachment, onDelete }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const clickable = attachment.type !== 'unknown';
|
||||
|
||||
const handleOpenModal = () => {
|
||||
dispatch(openModal('MEDIA', { media: ImmutableList.of(attachment), index: 0 }));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='relative isolate inline-block h-24 w-24 overflow-hidden rounded-lg bg-gray-200 dark:bg-primary-900'>
|
||||
<Blurhash hash={attachment.blurhash} className='absolute inset-0 -z-10 h-full w-full' />
|
||||
|
||||
<div className='absolute right-[6px] top-[6px]'>
|
||||
<RemoveButton onClick={onDelete} />
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={clickable ? handleOpenModal : undefined}
|
||||
className={clsx('h-full w-full', { 'cursor-zoom-in': clickable, 'cursor-default': !clickable })}
|
||||
>
|
||||
<ChatUploadPreview attachment={attachment} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface IRemoveButton {
|
||||
onClick?: React.MouseEventHandler<HTMLButtonElement>
|
||||
}
|
||||
|
||||
/** Floating button to remove an attachment. */
|
||||
const RemoveButton: React.FC<IRemoveButton> = ({ onClick }) => {
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
onClick={onClick}
|
||||
className='flex h-5 w-5 items-center justify-center rounded-full bg-secondary-500 p-1'
|
||||
>
|
||||
<Icon
|
||||
className='h-3 w-3 text-white'
|
||||
src={require('@tabler/icons/x.svg')}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatUpload;
|
|
@ -5,8 +5,6 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||
|
||||
import { uploadMedia } from 'soapbox/actions/media';
|
||||
import { Stack } from 'soapbox/components/ui';
|
||||
import Upload from 'soapbox/components/upload';
|
||||
import UploadProgress from 'soapbox/components/upload-progress';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import { normalizeAttachment } from 'soapbox/normalizers';
|
||||
import { IChat, useChatActions } from 'soapbox/queries/chats';
|
||||
|
@ -164,22 +162,6 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
|||
<ChatMessageList chat={chat} />
|
||||
</div>
|
||||
|
||||
{attachment && (
|
||||
<div className='relative h-48'>
|
||||
<Upload
|
||||
media={attachment}
|
||||
onDelete={handleRemoveFile}
|
||||
withPreview
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isUploading && (
|
||||
<div className='p-4'>
|
||||
<UploadProgress progress={uploadProgress * 100} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ChatComposer
|
||||
ref={inputRef}
|
||||
onKeyDown={handleKeyDown}
|
||||
|
@ -190,7 +172,10 @@ const Chat: React.FC<ChatInterface> = ({ chat, inputRef, className }) => {
|
|||
onSelectFile={handleFiles}
|
||||
resetFileKey={resetFileKey}
|
||||
onPaste={handlePaste}
|
||||
hasAttachment={!!attachment}
|
||||
attachments={attachment ? [attachment] : []}
|
||||
onDeleteAttachment={handleRemoveFile}
|
||||
isUploading={isUploading}
|
||||
uploadProgress={uploadProgress}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
|
|
23
app/soapbox/normalizers/__tests__/chat-message.test.ts
Normal file
23
app/soapbox/normalizers/__tests__/chat-message.test.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { Record as ImmutableRecord } from 'immutable';
|
||||
|
||||
import { normalizeAttachment } from '../attachment';
|
||||
import { normalizeChatMessage } from '../chat-message';
|
||||
|
||||
describe('normalizeChatMessage()', () => {
|
||||
it('upgrades attachment to media_attachments', () => {
|
||||
const message = {
|
||||
id: 'abc',
|
||||
attachment: normalizeAttachment({
|
||||
id: 'def',
|
||||
url: 'https://gleasonator.com/favicon.png',
|
||||
}),
|
||||
};
|
||||
|
||||
const result = normalizeChatMessage(message);
|
||||
|
||||
expect(ImmutableRecord.isRecord(result)).toBe(true);
|
||||
expect(result.id).toEqual('abc');
|
||||
expect(result.media_attachments.first()?.id).toEqual('def');
|
||||
expect(result.media_attachments.first()?.preview_url).toEqual('https://gleasonator.com/favicon.png');
|
||||
});
|
||||
});
|
|
@ -11,7 +11,7 @@ import type { Attachment, Card, Emoji } from 'soapbox/types/entities';
|
|||
|
||||
export const ChatMessageRecord = ImmutableRecord({
|
||||
account_id: '',
|
||||
attachment: null as Attachment | null,
|
||||
media_attachments: ImmutableList<Attachment>(),
|
||||
card: null as Card | null,
|
||||
chat_id: '',
|
||||
content: '',
|
||||
|
@ -24,12 +24,15 @@ export const ChatMessageRecord = ImmutableRecord({
|
|||
});
|
||||
|
||||
const normalizeMedia = (status: ImmutableMap<string, any>) => {
|
||||
const attachments = status.get('media_attachments');
|
||||
const attachment = status.get('attachment');
|
||||
|
||||
if (attachment) {
|
||||
return status.set('attachment', normalizeAttachment(attachment));
|
||||
if (attachments) {
|
||||
return status.set('media_attachments', ImmutableList(attachments.map(normalizeAttachment)));
|
||||
} else if (attachment) {
|
||||
return status.set('media_attachments', ImmutableList([normalizeAttachment(attachment)]));
|
||||
} else {
|
||||
return status;
|
||||
return status.set('media_attachments', ImmutableList());
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -235,7 +235,7 @@ const useChatActions = (chatId: string) => {
|
|||
const createChatMessage = useMutation(
|
||||
(
|
||||
{ chatId, content, mediaId }: { chatId: string, content: string, mediaId?: string },
|
||||
) => api.post<IChatMessage>(`/api/v1/pleroma/chats/${chatId}/messages`, { content, media_id: mediaId }),
|
||||
) => api.post<IChatMessage>(`/api/v1/pleroma/chats/${chatId}/messages`, { content, media_id: mediaId, media_ids: [mediaId] }),
|
||||
{
|
||||
retry: false,
|
||||
onMutate: async (variables) => {
|
||||
|
|
Loading…
Reference in a new issue