@@ 68,6 68,9 @@ class Status extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map,
account: ImmutablePropTypes.map,
+ previousId: PropTypes.string,
+ nextInReplyToId: PropTypes.string,
+ rootId: PropTypes.string,
onClick: PropTypes.func,
onReply: PropTypes.func,
onFavourite: PropTypes.func,
@@ 309,10 312,7 @@ class Status extends ImmutablePureComponent {
};
render () {
- let media = null;
- let statusAvatar, prepend, rebloggedByText;
-
- const { intl, hidden, featured, unread, showThread, scrollKey, pictureInPicture } = this.props;
+ const { intl, hidden, featured, unread, showThread, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId } = this.props;
let { status, account, ...other } = this.props;
@@ 334,6 334,8 @@ class Status extends ImmutablePureComponent {
openMedia: this.handleHotkeyOpenMedia,
};
+ let media, statusAvatar, prepend, rebloggedByText;
+
if (hidden) {
return (
<HotKeys handlers={handlers}>
@@ 345,7 347,11 @@ class Status extends ImmutablePureComponent {
);
}
+ const connectUp = previousId && previousId === status.get('in_reply_to_id');
+ const connectToRoot = rootId && rootId === status.get('in_reply_to_id');
+ const connectReply = nextInReplyToId && nextInReplyToId === status.get('id');
const matchedFilters = status.get('matched_filters');
+
if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) {
const minHandlers = this.props.muted ? {} : {
moveUp: this.handleHotkeyMoveUp,
@@ 519,7 525,9 @@ class Status extends ImmutablePureComponent {
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
{prepend}
- <div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted })} data-id={status.get('id')}>
+ <div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
+ {(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
+
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div onClick={this.handleClick} className='status__info'>
<a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
@@ 67,6 67,7 @@ const makeMapStateToProps = () => {
const mapStateToProps = (state, props) => ({
status: getStatus(state, props),
+ nextInReplyToId: props.nextId ? state.getIn(['statuses', props.nextId, 'in_reply_to_id']) : null,
pictureInPicture: getPictureInPicture(state, props),
});
@@ 529,14 529,19 @@ class Status extends ImmutablePureComponent {
}
}
- renderChildren (list) {
- return list.map(id => (
+ renderChildren (list, ancestors) {
+ const { params: { statusId } } = this.props;
+
+ return list.map((id, i) => (
<StatusContainer
key={id}
id={id}
onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown}
contextType='thread'
+ previousId={i > 0 && list.get(i - 1)}
+ nextId={list.get(i + 1) || (ancestors && statusId)}
+ rootId={statusId}
/>
));
}
@@ 590,7 595,7 @@ class Status extends ImmutablePureComponent {
}
if (ancestorsIds && ancestorsIds.size > 0) {
- ancestors = <>{this.renderChildren(ancestorsIds)}</>;
+ ancestors = <>{this.renderChildren(ancestorsIds, true)}</>;
}
if (descendantsIds && descendantsIds.size > 0) {
@@ 1145,6 1145,58 @@ body > [data-popper-placement] {
}
}
}
+
+ &--in-thread {
+ border-bottom: 0;
+
+ .status__content,
+ .status__action-bar,
+ .media-gallery,
+ .video,
+ .audio,
+ .attachment-list {
+ margin-left: 46px + 10px;
+ width: calc(100% - (46px + 10px));
+ }
+ }
+
+ &--first-in-thread {
+ border-top: 1px solid lighten($ui-base-color, 8%);
+ }
+
+ &__line {
+ height: 16px - 4px;
+ border-inline-start: 2px solid lighten($ui-base-color, 8%);
+ width: 0;
+ position: absolute;
+ top: 0;
+ inset-inline-start: 16px + ((46px - 2px) / 2);
+
+ &--full {
+ top: 0;
+ height: 100%;
+
+ &::before {
+ content: '';
+ display: block;
+ position: absolute;
+ top: 16px - 4px;
+ height: 46px + 4px + 4px;
+ width: 2px;
+ background: $ui-base-color;
+ inset-inline-start: -2px;
+ }
+ }
+
+ &--first {
+ top: 16px + 46px + 4px;
+ height: calc(100% - (16px + 46px + 4px));
+
+ &::before {
+ display: none;
+ }
+ }
+ }
}
.status__relative-time {
@@ 1293,6 1345,7 @@ body > [data-popper-placement] {
.detailed-status {
background: lighten($ui-base-color, 4%);
padding: 16px;
+ border-top: 1px solid lighten($ui-base-color, 8%);
&--flex {
display: flex;
@@ 1729,6 1782,7 @@ a.account__display-name {
.status__avatar {
width: 46px;
height: 46px;
+ box-shadow: 0 0 0 2px $ui-base-color;
}
.muted {