Skip to content

Commit

Permalink
feat: apply keepLineBreaksPlugin & htmlToTextPlugin plugins to text r…
Browse files Browse the repository at this point in the history
…endering by default (#2169)

BREAKING CHANGE: apply the remark plugins `keepLineBreaksPlugin`,
`htmlToTextPlugin` as a part of the default message text parsing,
upgrade `unified` libraries

### 🎯 Goal

Apply the remark plugins `keepLineBreaksPlugin`, `htmlToTextPlugin` as a
part of the default message text parsing.

## TODO

- [X] merge #2170 
- [X] include the plugins among the default remark plugins
- [X] migration guide - see below
- [X] remove docs section [Optional remark and rehype
plugins](https://getstream.io/chat/docs/sdk/react/components/core-components/message_list/#optional-remark-and-rehype-plugins)](https://getstream.io/chat/docs/sdk/react/components/core-components/message_list/#overriding-defaults)
- [X] upgrade Jest / install a new test runner (vitest) - new deps are
not found with old Jest 26.6.3
- [X] include migration guide text in docs
- [X] refactor [`customMarkDownRenderers` code in
`renderText`](https://github.com/GetStream/stream-chat-react/blob/f0bc7ba2532760cabb1db01e685a35bd3b0b64c5/src/components/Message/renderText/renderText.tsx#L158)

---------

Co-authored-by: Oliver Lazoroski <oliver.lazoroski@gmail.com>
Co-authored-by: Anton Arnautov <43254280+arnautov-anton@users.noreply.github.com>
  • Loading branch information
3 people authored Nov 27, 2023
1 parent 3dd8dab commit f87de16
Show file tree
Hide file tree
Showing 21 changed files with 1,988 additions and 1,634 deletions.
34 changes: 34 additions & 0 deletions .babelrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// The content of babel.config.js copied here. This is due to the bug in jest - https://github.com/jestjs/jest/issues/11741
// The bug has been resolved with jest 30 - https://github.com/jestjs/jest/commit/983274ac08c67d2a445e111b2dfaf81020f912b2
module.exports = {
env: {
production: {
presets: [
[
'@babel/env',
{
modules: false,
},
],
],
},
test: {
plugins: ['transform-es2015-modules-commonjs'],
presets: [
[
'@babel/preset-env',
{
modules: 'commonjs',
},
],
],
},
},
ignore: ['src/@types/*'],
plugins: [
'@babel/plugin-proposal-class-properties',
'@babel/plugin-transform-runtime',
'babel-plugin-dynamic-import-node',
],
presets: ['@babel/preset-typescript', '@babel/env', '@babel/preset-react'],
};
109 changes: 36 additions & 73 deletions docusaurus/docs/React/components/core-components/message-list.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,42 @@ const App = () => (
);
```

#### Custom element rendering

If you feel like the default output is sufficient, but you'd like to adjust how certain [ReactMarkdown components](https://github.com/remarkjs/react-markdown#appendix-b-components) look like (like `strong` element generated by typing \*\*strong\*\*) you can do so by passing down options to a third argument of the default `renderText` function:

:::note
Types `mention` and `emoji` are special case component types generated by our SDK's custom rehype plugins.
:::

```tsx
import { renderText } from 'stream-chat-react';

const StrongComponent = ({ children }) => <b className='custom-strong-class-name'>{children}</b>;

const MentionComponent = ({ children, node: { mentionedUser } }) => (
<a data-user-id={mentionedUser.id} href={`/user-profile/${mentionedUser.id}`}>
{children}
</a>
);

const App = () => (
<Chat client={client}>
<Channel>
<Window>
<MessageList
renderText={(text, mentionedUsers) =>
renderText(text, mentionedUsers, {
customMarkDownRenderers: { strong: StrongComponent, mention: MentionComponent },
})
}
/>
</Window>
</Channel>
</Chat>
);
```

#### Custom remark and rehype plugins

If you would like to extend the array of plugins used to parse the markdown, you can provide your own lists of remark resp. rehype plugins. The logic that determines what plugins are used and in which order can be specified in custom `getRehypePlugins` and `getRemarkPlugins` functions. These receive the default array of rehype and remark plugins for further customization. Both custom functions ought to be passed to the third `renderText()` parameter. An example follows:
Expand Down Expand Up @@ -182,79 +218,6 @@ const CustomMessageList = () => (
);
```

#### Optional remark and rehype plugins

The SDK provides the following plugins that are not applied to the text parsing by the default `renderText()` implementation. However, these can be included by simply overriding the defaults with `getRemarkPlugins` and `getRehypePlugins` parameters as described in [the section about custom plugins](#custom-remark-and-rehype-plugins).

Currently, there are the following optional remark plugins available in the SDK:

- `htmlToTextPlugin` - keeps the HTML tags in the resulting text string.
- `keepLineBreaksPlugin` - replaces empty lines in text with line breaks ([according to the CommonMark Markdown specification](https://spec.commonmark.org/0.30/#hard-line-breaks), the empty lines - meaning newline characters `\n` - are not transformed to `<br/>` elements).

These can be plugged in as follows:

```tsx
import { UserResponse } from 'stream-chat';
import {
htmlToTextPlugin,
keepLineBreaksPlugin,
MessageList,
MessageListProps,
renderText,
RenderTextPluginConfigurator,
} from 'stream-chat-react';

const getRemarkPlugins: RenderTextPluginConfigurator = (plugins) => [
htmlToTextPlugin,
keepLineBreaksPlugin,
...plugins,
];

function customRenderText(text?: string, mentionedUsers?: UserResponse[]) {
return renderText(text, mentionedUsers, { getRemarkPlugins });
}

export const CustomMessageList = (props: MessageListProps) => (
<MessageList {...props} renderText={customRenderText} />
);
```

#### Custom element rendering

If you feel like the default output is sufficient, but you'd like to adjust how certain [ReactMarkdown components](https://github.com/remarkjs/react-markdown#appendix-b-components) look like (like `strong` element generated by typing \*\*strong\*\*) you can do so by passing down options to a third argument of the default `renderText` function:

:::note
Types `mention` and `emoji` are special case component types generated by our SDK's custom rehype plugins.
:::

```tsx
import { renderText } from 'stream-chat-react';

const StrongComponent = ({ children }) => <b className='custom-strong-class-name'>{children}</b>;

const MentionComponent = ({ children, node: { mentionedUser } }) => (
<a data-user-id={mentionedUser.id} href={`/user-profile/${mentionedUser.id}`}>
{children}
</a>
);

const App = () => (
<Chat client={client}>
<Channel>
<Window>
<MessageList
renderText={(text, mentionedUsers) =>
renderText(text, mentionedUsers, {
customMarkDownRenderers: { strong: StrongComponent, mention: MentionComponent },
})
}
/>
</Window>
</Channel>
</Chat>
);
```

## Props

### additionalMessageInputProps
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
id: message-text-rendering-v11
sidebar_position: 3
title: Message text rendering 11.0.0
keywords: [migration guide, upgrade, message, text rendering, breaking changes, v11]
---

Optional remark plugins `htmlToTextPlugin`, `keepLineBreaksPlugin` introduced with [stream-chat-react@v10.19.0](https://github.com/GetStream/stream-chat-react/releases/tag/v10.19.0) are now included among the default remark plugins. That means that in the messages that originally contained a sequence of multiple newline characters `\n`, these will be replaced with line break elements `<br/>`. The number of line breaks equals count of newline characters minus 1.
The dependencies used to parse the markdown with [`renderText()` function](../../components/core-components/message_list/#rendering-message-text-with-rendertext-function) have been upgraded as a result of that, the following changes had to be performed in stream-chat-react library:

### `ReactMarkdownProps` dropped from `customMarkDownRenderers`

`RenderTextOptions.customMarkDownRenderers`- a mapping of element name and corresponding React component to be rendered. The components are no longer accepting `ReactMarkdownProps`

### User mention renderer props change

The `RenderTextOptions.customMarkDownRenderers.mention` props have been reduced. From now on, only `children` and `node` are passed to the component. And so now `mention` renderer props look as follows:

```ts
import { PropsWithChildren } from 'react';
import type { UserResponse } from 'stream-chat';
import type { DefaultStreamChatGenerics } from 'stream-chat-react';

type MentionProps<
StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics
> = PropsWithChildren<{
node: {
mentionedUser: UserResponse<StreamChatGenerics>;
};
}>;
```

### Adjust custom rehype or remark plugins

If you have implemented your own rehype or remark plugin using `visit` function from the library `unist-util-visit` beware that the `index` and `parent` arguments of the `Visitor` function cannot be `null` but `undefined` instead. You should be notified by Typescript about this and should adjust the type checks accordingly.

If you would like to prevent applying plugins `htmlToTextPlugin`, `keepLineBreaksPlugin`, you can customize your `renderText()` by overriding the remark plugins. The example below will keep the plugin `remarkGfm` and exclude the rest:

```tsx
import remarkGfm from 'remark-gfm';
import { renderText, RenderTextPluginConfigurator } from 'stream-chat-react';


const getRemarkPlugins: RenderTextPluginConfigurator = () => {
return [[remarkGfm, { singleTilde: false }]];
};

const customRenderText = (text, mentionedUsers) =>
renderText(text, mentionedUsers, {
getRemarkPlugins
});
const CustomMessageList = () => (
<MessageList renderText={customRenderText}/>
);
```
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ module.exports = {
},
preset: 'ts-jest',
setupFiles: ['core-js'],
setupFilesAfterEnv: ['<rootDir>/jest.env.setup.js'],
testEnvironment: 'jsdom',
testPathIgnorePatterns: ['/node_modules/', '/examples/', '__snapshots__', '/e2e/'],
testRegex: [
/**
Expand Down
8 changes: 8 additions & 0 deletions jest.env.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* eslint-disable no-undef */
const crypto = require('crypto');

Object.defineProperty(globalThis, 'crypto', {
value: {
getRandomValues: (arr) => crypto.randomBytes(arr.length),
},
});
22 changes: 12 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"dayjs": "^1.10.4",
"emoji-mart": "3.0.1",
"emoji-regex": "^9.2.0",
"hast-util-find-and-replace": "^4.1.2",
"hast-util-find-and-replace": "^5.0.1",
"i18next": "^21.6.14",
"isomorphic-ws": "^4.0.1",
"linkifyjs": "^4.1.0",
Expand All @@ -49,16 +49,16 @@
"react-fast-compare": "^3.2.2",
"react-image-gallery": "1.2.12",
"react-is": "^18.1.0",
"react-markdown": "^8.0.7",
"react-markdown": "^9.0.0",
"react-player": "2.10.1",
"react-popper": "^2.3.0",
"react-textarea-autosize": "^8.3.0",
"react-virtuoso": "^2.16.5",
"remark-gfm": "^3.0.1",
"remark-gfm": "^4.0.0",
"textarea-caret": "^3.1.0",
"tslib": "^2.6.2",
"unist-builder": "^3.0.0",
"unist-util-visit": "^4.1.1"
"unist-util-visit": "^5.0.0"
},
"optionalDependencies": {
"@stream-io/transliterate": "^1.5.5",
Expand All @@ -82,7 +82,7 @@
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-transform-runtime": "^7.12.1",
"@babel/preset-env": "^7.12.7",
"@babel/preset-react": "^7.12.7",
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.12.7",
"@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^16.2.1",
Expand All @@ -98,13 +98,14 @@
"@semantic-release/changelog": "^6.0.2",
"@semantic-release/git": "^10.0.1",
"@stream-io/rollup-plugin-node-builtins": "^2.1.5",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^13.1.1",
"@testing-library/react-hooks": "^8.0.0",
"@types/deep-equal": "^1.0.1",
"@types/dotenv": "^8.2.0",
"@types/emoji-mart": "^3.0.9",
"@types/hast": "^2.3.4",
"@types/jsdom": "^21.1.5",
"@types/linkifyjs": "^2.1.3",
"@types/lodash.debounce": "^4.0.7",
"@types/lodash.throttle": "^4.1.7",
Expand All @@ -120,7 +121,7 @@
"@typescript-eslint/parser": "4.27.0",
"autoprefixer": "^10.0.3",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.3.0",
"babel-jest": "^28.1.3",
"babel-loader": "8.2.2",
"babel-plugin-module-resolver": "^4.1.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
Expand Down Expand Up @@ -152,8 +153,9 @@
"file-loader": "^6.2.0",
"husky": "^4.3.0",
"i18next-parser": "^6.0.0",
"jest": "^26.6.3",
"jest-axe": "^6.0.0",
"jest": "^28.1.3",
"jest-axe": "^8.0.0",
"jest-environment-jsdom": "^28.1.3",
"moment-timezone": "^0.5.43",
"postcss": "^8.1.10",
"postcss-loader": "^4.1.0",
Expand All @@ -172,7 +174,7 @@
"semantic-release": "^19.0.5",
"stream-chat": "^8.13.1",
"style-loader": "^2.0.0",
"ts-jest": "^26.5.1",
"ts-jest": "^28.0.8",
"typescript": "^4.7.4",
"url-loader": "^4.1.1",
"webpack": "4.44.2",
Expand Down
8 changes: 0 additions & 8 deletions src/components/Message/renderText/Emoji.tsx

This file was deleted.

31 changes: 0 additions & 31 deletions src/components/Message/renderText/Mention.tsx

This file was deleted.

Loading

0 comments on commit f87de16

Please sign in to comment.