diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index f1dba566be..cd8423b2f4 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -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 ( @@ -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 {
{prepend} -
+
+ {(connectReply || connectUp || connectToRoot) &&
} + {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
diff --git a/app/javascript/mastodon/containers/status_container.jsx b/app/javascript/mastodon/containers/status_container.jsx index 580f409e94..f483166540 100644 --- a/app/javascript/mastodon/containers/status_container.jsx +++ b/app/javascript/mastodon/containers/status_container.jsx @@ -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), }); diff --git a/app/javascript/mastodon/features/status/index.jsx b/app/javascript/mastodon/features/status/index.jsx index 900b19c315..6f03362096 100644 --- a/app/javascript/mastodon/features/status/index.jsx +++ b/app/javascript/mastodon/features/status/index.jsx @@ -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) => ( 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) { diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 7c8d700adb..248738f6b3 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -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 {