Show "read more" link on overly long in-stream statuses (#8205)
Show "read more" link on overly long in-stream statuses
This commit is contained in:
parent
42ab93c8b2
commit
15fc2b76f9
3 changed files with 59 additions and 5 deletions
|
@ -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…
Reference in a new issue