feat: collapse big messages (#810)

* Updating Message component to add ability to collapse long running messages. Height is broadcast out to parent on height toggle.

See: https://github.com/gotify/server/issues/790

* Cleanup of the Message component including simplifying the read-more expand/collapse functionality.

* fix: cleanup & properly updating the height

---------

Co-authored-by: Jannis Mattheis <contact@jmattheis.de>
This commit is contained in:
Jeremy Gooch 2025-07-06 06:07:57 -05:00 committed by GitHub
parent 2498e6e19f
commit c1cb2e855a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 64 additions and 6 deletions

View File

@ -1,14 +1,18 @@
import {Button} from '@material-ui/core';
import IconButton from '@material-ui/core/IconButton';
import {createStyles, Theme, withStyles, WithStyles} from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import {ExpandLess, ExpandMore} from '@material-ui/icons';
import Delete from '@material-ui/icons/Delete';
import React from 'react';
import React, {RefObject} from 'react';
import TimeAgo from 'react-timeago';
import Container from '../common/Container';
import * as config from '../config';
import {Markdown} from '../common/Markdown';
import {RenderMode, contentType} from './extras';
import * as config from '../config';
import {IMessageExtras} from '../types';
import {contentType, RenderMode} from './extras';
const PREVIEW_LENGTH = 500;
const styles = (theme: Theme) =>
createStyles({
@ -52,7 +56,12 @@ const styles = (theme: Theme) =>
whiteSpace: 'pre-wrap',
},
content: {
maxHeight: PREVIEW_LENGTH,
wordBreak: 'break-all',
overflowY: 'hidden',
'&.expanded': {
maxHeight: 'none',
},
'& p': {
margin: 0,
},
@ -79,6 +88,11 @@ interface IProps {
height: (height: number) => void;
}
interface IState {
expanded: boolean;
isOverflowing: boolean;
}
const priorityColor = (priority: number) => {
if (priority >= 4 && priority <= 7) {
return 'rgba(230, 126, 34, 0.7)';
@ -89,14 +103,39 @@ const priorityColor = (priority: number) => {
}
};
class Message extends React.PureComponent<IProps & WithStyles<typeof styles>> {
class Message extends React.PureComponent<IProps & WithStyles<typeof styles>, IState> {
public state = {expanded: false, isOverflowing: false};
private node: HTMLDivElement | null = null;
private previewRef: RefObject<HTMLDivElement>;
public componentDidMount = () =>
constructor(props: IProps & WithStyles<typeof styles>) {
super(props);
this.previewRef = React.createRef();
}
public componentDidMount = () => {
if (this.previewRef.current) {
this.setState({
isOverflowing:
this.previewRef.current.scrollHeight > this.previewRef.current.clientHeight,
});
}
this.updateHeightInParent();
};
public togglePreviewHeight = () => {
this.setState(
(state) => ({expanded: !state.expanded}),
() => this.updateHeightInParent()
);
};
private updateHeightInParent = () =>
this.props.height(this.node ? this.node.getBoundingClientRect().height : 0);
private renderContent = () => {
const content = this.props.content;
switch (contentType(this.props.extras)) {
case RenderMode.Markdown:
return <Markdown>{content}</Markdown>;
@ -114,6 +153,7 @@ class Message extends React.PureComponent<IProps & WithStyles<typeof styles>> {
<Container
style={{
display: 'flex',
flexWrap: 'wrap',
borderLeftColor: priorityColor(priority),
borderLeftWidth: 6,
borderLeftStyle: 'solid',
@ -141,10 +181,28 @@ class Message extends React.PureComponent<IProps & WithStyles<typeof styles>> {
<Delete />
</IconButton>
</div>
<Typography component="div" className={`${classes.content} content`}>
<Typography
component="div"
ref={this.previewRef}
className={`${classes.content} content ${
this.state.isOverflowing && this.state.expanded ? 'expanded' : ''
}`}>
{this.renderContent()}
</Typography>
</div>
{this.state.isOverflowing && (
<Button
style={{marginTop: 16}}
onClick={() => this.togglePreviewHeight()}
variant="contained"
color="primary"
size="large"
fullWidth={true}
startIcon={this.state.expanded ? <ExpandLess /> : <ExpandMore />}>
{this.state.expanded ? 'Read Less' : 'Read More'}
</Button>
)}
</Container>
</div>
);