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:
parent
2498e6e19f
commit
c1cb2e855a
|
|
@ -1,14 +1,18 @@
|
||||||
|
import {Button} from '@material-ui/core';
|
||||||
import IconButton from '@material-ui/core/IconButton';
|
import IconButton from '@material-ui/core/IconButton';
|
||||||
import {createStyles, Theme, withStyles, WithStyles} from '@material-ui/core/styles';
|
import {createStyles, Theme, withStyles, WithStyles} from '@material-ui/core/styles';
|
||||||
import Typography from '@material-ui/core/Typography';
|
import Typography from '@material-ui/core/Typography';
|
||||||
|
import {ExpandLess, ExpandMore} from '@material-ui/icons';
|
||||||
import Delete from '@material-ui/icons/Delete';
|
import Delete from '@material-ui/icons/Delete';
|
||||||
import React from 'react';
|
import React, {RefObject} from 'react';
|
||||||
import TimeAgo from 'react-timeago';
|
import TimeAgo from 'react-timeago';
|
||||||
import Container from '../common/Container';
|
import Container from '../common/Container';
|
||||||
import * as config from '../config';
|
|
||||||
import {Markdown} from '../common/Markdown';
|
import {Markdown} from '../common/Markdown';
|
||||||
import {RenderMode, contentType} from './extras';
|
import * as config from '../config';
|
||||||
import {IMessageExtras} from '../types';
|
import {IMessageExtras} from '../types';
|
||||||
|
import {contentType, RenderMode} from './extras';
|
||||||
|
|
||||||
|
const PREVIEW_LENGTH = 500;
|
||||||
|
|
||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
|
|
@ -52,7 +56,12 @@ const styles = (theme: Theme) =>
|
||||||
whiteSpace: 'pre-wrap',
|
whiteSpace: 'pre-wrap',
|
||||||
},
|
},
|
||||||
content: {
|
content: {
|
||||||
|
maxHeight: PREVIEW_LENGTH,
|
||||||
wordBreak: 'break-all',
|
wordBreak: 'break-all',
|
||||||
|
overflowY: 'hidden',
|
||||||
|
'&.expanded': {
|
||||||
|
maxHeight: 'none',
|
||||||
|
},
|
||||||
'& p': {
|
'& p': {
|
||||||
margin: 0,
|
margin: 0,
|
||||||
},
|
},
|
||||||
|
|
@ -79,6 +88,11 @@ interface IProps {
|
||||||
height: (height: number) => void;
|
height: (height: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
expanded: boolean;
|
||||||
|
isOverflowing: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const priorityColor = (priority: number) => {
|
const priorityColor = (priority: number) => {
|
||||||
if (priority >= 4 && priority <= 7) {
|
if (priority >= 4 && priority <= 7) {
|
||||||
return 'rgba(230, 126, 34, 0.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 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);
|
this.props.height(this.node ? this.node.getBoundingClientRect().height : 0);
|
||||||
|
|
||||||
private renderContent = () => {
|
private renderContent = () => {
|
||||||
const content = this.props.content;
|
const content = this.props.content;
|
||||||
|
|
||||||
switch (contentType(this.props.extras)) {
|
switch (contentType(this.props.extras)) {
|
||||||
case RenderMode.Markdown:
|
case RenderMode.Markdown:
|
||||||
return <Markdown>{content}</Markdown>;
|
return <Markdown>{content}</Markdown>;
|
||||||
|
|
@ -114,6 +153,7 @@ class Message extends React.PureComponent<IProps & WithStyles<typeof styles>> {
|
||||||
<Container
|
<Container
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
borderLeftColor: priorityColor(priority),
|
borderLeftColor: priorityColor(priority),
|
||||||
borderLeftWidth: 6,
|
borderLeftWidth: 6,
|
||||||
borderLeftStyle: 'solid',
|
borderLeftStyle: 'solid',
|
||||||
|
|
@ -141,10 +181,28 @@ class Message extends React.PureComponent<IProps & WithStyles<typeof styles>> {
|
||||||
<Delete />
|
<Delete />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</div>
|
</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()}
|
{this.renderContent()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</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>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue