Skip to content
Merged

v9.4.0 #1700

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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ The `MessageInput` component accepts an `overrideSubmitHandler` prop, which allo
conclusion of the underlying `textarea` element's [`handleSubmit`](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/hooks/useSubmitHandler.ts)
function.

:::note
You do not have to implement your custom submit handler, if the only thing you need is to pass custom message data to the underlying API call. In that case you can use the [`handleSubmit`](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/hooks/useSubmitHandler.ts) function from the [`MessageInputContext`](../contexts/message-input-context.mdx). The `handleSubmit` function allows you to pass custom message data through its second parameter `customMessageData`. This applies to sending a new message as well as updating an existing one. In order for this to work, you will have to implement custom message input components and pass them to [`Channel`](../core-components/channel.mdx) props `EditMessageInput` or `Input` respectively.
:::

The `overrideSubmitHandler` function receives two arguments, the message to be sent and the `cid` (channel type prepended to channel id)
for the currently active channel. The message object is of the following type:

Expand Down
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1",
"lodash.uniqby": "^4.7.0",
"mdast-util-find-and-replace": "1.1.1",
"mdast-util-find-and-replace": "^2.2.1",
"nanoid": "^3.3.4",
"pretty-bytes": "^5.4.1",
"prop-types": "^15.7.2",
Expand Down Expand Up @@ -103,6 +103,7 @@
"@types/lodash.isequal": "^4.5.5",
"@types/lodash.throttle": "^4.1.6",
"@types/lodash.uniqby": "^4.7.6",
"@types/mdast": "^3.0.10",
"@types/moment": "^2.13.0",
"@types/react": "^18.0.8",
"@types/react-dom": "^18.0.3",
Expand Down Expand Up @@ -208,7 +209,8 @@
"e2e-container": "./e2e/scripts/run_in_container.sh"
},
"resolutions": {
"ast-types": "^0.14.0"
"ast-types": "^0.14.0",
"@types/unist": "^2.0.6"
},
"browserslist": [
">0.2%",
Expand Down
142 changes: 142 additions & 0 deletions src/__tests__/__snapshots__/utils.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renderText handles the special case where user name matches to an e-mail pattern - 1 1`] = `
<p>
Hello
<span
className="str-chat__message-mention"
>
@username@email.com
</span>
, is
<a
className=""
href="mailto:username@email.com"
rel="nofollow noreferrer noopener"
target="_blank"
>
username@email.com
</a>
your @primary e-mail?
</p>
`;

exports[`renderText handles the special case where user name matches to an e-mail pattern - 2 1`] = `
<p>
<a
className=""
href="mailto:username@email.com"
rel="nofollow noreferrer noopener"
target="_blank"
>
username@email.com
</a>

<span
className="str-chat__message-mention"
>
@username@email.com
</span>
is this the right address?
</p>
`;

exports[`renderText handles the special case where user name matches to an e-mail pattern - 3 1`] = `
<p>
<span
className="str-chat__message-mention"
>
@username@email.com
</span>

<span
className="str-chat__message-mention"
>
@username@email.com
</span>

<span
className="str-chat__message-mention"
>
@username@email.com
</span>

<span
className="str-chat__message-mention"
>
@username@email.com
</span>
</p>
`;

exports[`renderText handles the special case where user name matches to an e-mail pattern - 4 1`] = `
<p>
<span
className="str-chat__message-mention"
>
@username@email.com
</span>

<span
className="str-chat__message-mention"
>
@username@email.com
</span>

<a
className=""
href="mailto:username@email.com"
rel="nofollow noreferrer noopener"
target="_blank"
>
username@email.com
</a>

<span
className="str-chat__message-mention"
>
@username@email.com
</span>
</p>
`;

exports[`renderText renders custom mention 1`] = `
<p>
<span
className="my-mention"
>
@username@email.com
</span>

<span
className="my-mention"
>
@username@email.com
</span>

<a
className=""
href="mailto:username@email.com"
rel="nofollow noreferrer noopener"
target="_blank"
>
username@email.com
</a>

<span
className="my-mention"
>
@username@email.com
</span>
</p>
`;

exports[`renderText renders standard markdown text 1`] = `
<p>
Hi, shall we meet on
<strong>
Tuesday
</strong>
?
</p>
`;
63 changes: 63 additions & 0 deletions src/__tests__/utils.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { renderText } from '../utils';

describe(`renderText`, () => {
it('handles the special case where user name matches to an e-mail pattern - 1', () => {
const Markdown = renderText(
'Hello @username@email.com, is username@email.com your @primary e-mail?',
[{ id: 'id-username@email.com', name: 'username@email.com' }],
);
const tree = renderer.create(Markdown).toJSON();
expect(tree).toMatchSnapshot();
});

it('handles the special case where user name matches to an e-mail pattern - 2', () => {
const Markdown = renderText(
'username@email.com @username@email.com is this the right address?',
[{ id: 'id-username@email.com', name: 'username@email.com' }],
);
const tree = renderer.create(Markdown).toJSON();
expect(tree).toMatchSnapshot();
});

it('handles the special case where user name matches to an e-mail pattern - 3', () => {
const Markdown = renderText(
'@username@email.com @username@email.com @username@email.com @username@email.com',
[{ id: 'id-username@email.com', name: 'username@email.com' }],
);
const tree = renderer.create(Markdown).toJSON();
expect(tree).toMatchSnapshot();
});

it('handles the special case where user name matches to an e-mail pattern - 4', () => {
const Markdown = renderText(
'@username@email.com @username@email.com username@email.com @username@email.com',
[{ id: 'id-username@email.com', name: 'username@email.com' }],
);
const tree = renderer.create(Markdown).toJSON();
expect(tree).toMatchSnapshot();
});

it('renders custom mention', () => {
const Markdown = renderText(
'@username@email.com @username@email.com username@email.com @username@email.com',
[{ id: 'id-username@email.com', name: 'username@email.com' }],
{
customMarkDownRenderers: {
mention: function MyMention(props) {
return <span className='my-mention'>{props.children}</span>;
},
},
},
);
const tree = renderer.create(Markdown).toJSON();
expect(tree).toMatchSnapshot();
});

it('renders standard markdown text', () => {
const Markdown = renderText('Hi, shall we meet on **Tuesday**?', []);
const tree = renderer.create(Markdown).toJSON();
expect(tree).toMatchSnapshot();
});
});
52 changes: 52 additions & 0 deletions src/components/MessageInput/__tests__/MessageInput.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Channel } from '../../Channel/Channel';
import { MessageActionsBox } from '../../MessageActions';

import { MessageProvider } from '../../../context/MessageContext';
import { useMessageInputContext } from '../../../context/MessageInputContext';
import { useChatContext } from '../../../context/ChatContext';
import {
dispatchMessageDeletedEvent,
Expand Down Expand Up @@ -664,6 +665,57 @@ function axeNoViolations(container) {
await axeNoViolations(container);
});

it('should allow to send custom message data', async () => {
const customMessageData = { customX: 'customX' };
const CustomInputForm = () => {
const { handleChange, handleSubmit, value } = useMessageInputContext();
return (
<form>
<input onChange={handleChange} placeholder={inputPlaceholder} value={value} />
<button
onClick={(event) => {
handleSubmit(event, customMessageData);
}}
type='submit'
>
Send
</button>
</form>
);
};

const messageInputProps =
componentName === 'EditMessageForm'
? {
messageInputProps: {
message: {
text: `abc`,
},
},
}
: {};

const renderComponent = makeRenderFn(CustomInputForm);
const { container, submit } = await renderComponent(messageInputProps);

fireEvent.change(await screen.findByPlaceholderText(inputPlaceholder), {
target: {
value: 'Some text',
},
});

await act(() => submit());

await waitFor(() => {
const calledMock = componentName === 'EditMessageForm' ? editMock : submitMock;
expect(calledMock).toHaveBeenCalledWith(
expect.stringMatching(/.+:.+/),
expect.objectContaining(customMessageData),
);
});
await axeNoViolations(container);
});

it('Should use overrideSubmitHandler prop if it is defined', async () => {
const overrideMock = jest.fn().mockImplementation(() => Promise.resolve());
const customMessageData = undefined;
Expand Down
1 change: 1 addition & 0 deletions src/components/MessageInput/hooks/useSubmitHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export const useSubmitHandler = <
await editMessage(({
...message,
...updatedMessage,
...customMessageData,
} as unknown) as UpdatedMessage<StreamChatGenerics>);

clearEditingState?.();
Expand Down
27 changes: 22 additions & 5 deletions src/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import React, { PropsWithChildren } from 'react';
import emojiRegex from 'emoji-regex';
import * as linkify from 'linkifyjs';
import { nanoid } from 'nanoid';
//@ts-expect-error
import findAndReplace from 'mdast-util-find-and-replace';
import { findAndReplace, ReplaceFunction } from 'mdast-util-find-and-replace';
import RootReactMarkdown, { NodeType } from 'react-markdown';
import ReactMarkdown from 'react-markdown/with-html';
import uniqBy from 'lodash.uniqby';

import type { UserResponse } from 'stream-chat';

import type { Root } from 'mdast';
import type { DefaultStreamChatGenerics } from './types/types';

export const isOnlyEmojis = (text?: string) => {
Expand Down Expand Up @@ -119,7 +118,7 @@ export const emojiMarkdownPlugin = () => {
}

const transform = <T extends unknown>(markdownAST: T) => {
findAndReplace(markdownAST, emojiRegex(), replace);
findAndReplace(markdownAST as Root, emojiRegex(), replace as ReplaceFunction);
return markdownAST;
};

Expand Down Expand Up @@ -156,7 +155,7 @@ export const mentionsMarkdownPlugin = <
mentioned_usernames.map((username) => `@${username}`).join('|'),
'g',
);
findAndReplace(markdownAST, mentionedUsersRegex, replace);
findAndReplace(markdownAST as Root, mentionedUsersRegex, replace as ReplaceFunction);
return markdownAST;
};

Expand Down Expand Up @@ -213,6 +212,24 @@ export const renderText = <
if (noParsingNeeded.length > 0 || linkIsInBlock) return;

try {
// special case for mentions:
// it could happen that a user's name matches with an e-mail format pattern.
// in that case, we check whether the found e-mail is actually a mention
// by naively checking for an existence of @ sign in front of it.
if (type === 'email' && mentioned_users) {
const emailMatchesWithName = mentioned_users.some((u) => u.name === value);
if (emailMatchesWithName) {
newText = newText.replace(new RegExp(escapeRegExp(value), 'g'), (match, position) => {
const isMention = newText.charAt(position - 1) === '@';
// in case of mention, we leave the match in its original form,
// and we let `mentionsMarkdownPlugin` to do its job
return isMention ? match : `[${match}](${encodeDecode(href)})`;
});

return;
}
}

const displayLink = type === 'email' ? value : formatUrlForDisplay(href);

newText = newText.replace(
Expand Down
Loading