Show "read more" link on overly long in-stream statuses (#8205)

Show "read more" link on overly long in-stream statuses
master
Haelwenn Monnier 6 years ago committed by Eugen Rochko
parent 42ab93c8b2
commit 15fc2b76f9
  1. 2
      app/javascript/mastodon/components/status.js
  2. 40
      app/javascript/mastodon/components/status_content.js
  3. 22
      app/javascript/styles/mastodon/components.scss

@ -285,7 +285,7 @@ class Status extends ImmutablePureComponent {
</a> </a>
</div> </div>
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} /> <StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} collapsable />
{media} {media}

@ -6,6 +6,8 @@ import { FormattedMessage } from 'react-intl';
import Permalink from './permalink'; import Permalink from './permalink';
import classnames from 'classnames'; import classnames from 'classnames';
const MAX_HEIGHT = 322; // 20px * 16 (+ 2px padding at the top)
export default class StatusContent extends React.PureComponent { export default class StatusContent extends React.PureComponent {
static contextTypes = { static contextTypes = {
@ -17,10 +19,12 @@ export default class StatusContent extends React.PureComponent {
expanded: PropTypes.bool, expanded: PropTypes.bool,
onExpandedToggle: PropTypes.func, onExpandedToggle: PropTypes.func,
onClick: PropTypes.func, onClick: PropTypes.func,
collapsable: PropTypes.bool,
}; };
state = { state = {
hidden: true, hidden: true,
collapsed: null, // `collapsed: null` indicates that an element doesn't need collapsing, while `true` or `false` indicates that it does (and is/isn't).
}; };
_updateStatusLinks () { _updateStatusLinks () {
@ -53,6 +57,16 @@ export default class StatusContent extends React.PureComponent {
link.setAttribute('target', '_blank'); link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener'); link.setAttribute('rel', 'noopener');
} }
if (
this.props.collapsable
&& this.props.onClick
&& this.state.collapsed === null
&& node.clientHeight > MAX_HEIGHT
&& this.props.status.get('spoiler_text').length === 0
) {
this.setState({ collapsed: true });
}
} }
componentDidMount () { componentDidMount () {
@ -113,6 +127,11 @@ export default class StatusContent extends React.PureComponent {
} }
} }
handleCollapsedClick = (e) => {
e.preventDefault();
this.setState({ collapsed: !this.state.collapsed });
}
setRef = (c) => { setRef = (c) => {
this.node = c; this.node = c;
} }
@ -132,12 +151,19 @@ export default class StatusContent extends React.PureComponent {
const classNames = classnames('status__content', { const classNames = classnames('status__content', {
'status__content--with-action': this.props.onClick && this.context.router, 'status__content--with-action': this.props.onClick && this.context.router,
'status__content--with-spoiler': status.get('spoiler_text').length > 0, 'status__content--with-spoiler': status.get('spoiler_text').length > 0,
'status__content--collapsed': this.state.collapsed === true,
}); });
if (isRtl(status.get('search_index'))) { if (isRtl(status.get('search_index'))) {
directionStyle.direction = 'rtl'; directionStyle.direction = 'rtl';
} }
const readMoreButton = (
<button className='status__content__read-more-button' onClick={this.props.onClick}>
<FormattedMessage id='status.read_more' defaultMessage='Read more' /><i className='fa fa-fw fa-angle-right' />
</button>
);
if (status.get('spoiler_text').length > 0) { if (status.get('spoiler_text').length > 0) {
let mentionsPlaceholder = ''; let mentionsPlaceholder = '';
@ -167,17 +193,23 @@ export default class StatusContent extends React.PureComponent {
</div> </div>
); );
} else if (this.props.onClick) { } else if (this.props.onClick) {
return ( const output = [
<div <div
ref={this.setRef} ref={this.setRef}
tabIndex='0' tabIndex='0'
className={classNames} className={classNames}
style={directionStyle} style={directionStyle}
dangerouslySetInnerHTML={content}
onMouseDown={this.handleMouseDown} onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp} onMouseUp={this.handleMouseUp}
dangerouslySetInnerHTML={content} />,
/> ];
);
if (this.state.collapsed) {
output.push(readMoreButton);
}
return output;
} else { } else {
return ( return (
<div <div

@ -631,11 +631,13 @@
.status__content, .status__content,
.reply-indicator__content { .reply-indicator__content {
position: relative;
font-size: 15px; font-size: 15px;
line-height: 20px; line-height: 20px;
word-wrap: break-word; word-wrap: break-word;
font-weight: 400; font-weight: 400;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis;
white-space: pre-wrap; white-space: pre-wrap;
padding-top: 2px; padding-top: 2px;
color: $primary-text-color; color: $primary-text-color;
@ -721,6 +723,26 @@
} }
} }
.status__content.status__content--collapsed {
max-height: 20px * 15; // 15 lines is roughly above 500 characters
}
.status__content__read-more-button {
display: block;
font-size: 15px;
line-height: 20px;
color: lighten($ui-highlight-color, 8%);
border: 0;
background: transparent;
padding: 0;
padding-top: 8px;
&:hover,
&:active {
text-decoration: underline;
}
}
.status__content__spoiler-link { .status__content__spoiler-link {
display: inline-block; display: inline-block;
border-radius: 2px; border-radius: 2px;

Loading…
Cancel
Save