2
0

Highlight newly added replies in thread view (#36237)

This commit is contained in:
diondiondion
2025-09-24 11:27:33 +02:00
committed by GitHub
parent 23a69e3bd7
commit 059bf1e980
3 changed files with 28 additions and 1 deletions

View File

@@ -118,6 +118,7 @@ class Status extends ImmutablePureComponent {
unread: PropTypes.bool, unread: PropTypes.bool,
showThread: PropTypes.bool, showThread: PropTypes.bool,
isQuotedPost: PropTypes.bool, isQuotedPost: PropTypes.bool,
shouldHighlightOnMount: PropTypes.bool,
getScrollPosition: PropTypes.func, getScrollPosition: PropTypes.func,
updateScrollBottom: PropTypes.func, updateScrollBottom: PropTypes.func,
cacheMediaWidth: PropTypes.func, cacheMediaWidth: PropTypes.func,
@@ -567,6 +568,7 @@ class Status extends ImmutablePureComponent {
'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted,
'status--is-quote': isQuotedPost, 'status--is-quote': isQuotedPost,
'status--has-quote': !!status.get('quote'), 'status--has-quote': !!status.get('quote'),
'status--highlighted-entry': this.props.shouldHighlightOnMount,
}) })
} }
data-id={status.get('id')} data-id={status.get('id')}

View File

@@ -5,6 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import classNames from 'classnames'; import classNames from 'classnames';
import { Helmet } from 'react-helmet'; import { Helmet } from 'react-helmet';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { difference } from 'lodash';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
@@ -150,6 +151,11 @@ class Status extends ImmutablePureComponent {
fullscreen: false, fullscreen: false,
showMedia: defaultMediaVisibility(this.props.status), showMedia: defaultMediaVisibility(this.props.status),
loadedStatusId: undefined, loadedStatusId: undefined,
/**
* Holds the ids of newly added replies, excluding the initial load.
* Used to highlight newly added replies in the UI
*/
newRepliesIds: [],
}; };
UNSAFE_componentWillMount () { UNSAFE_componentWillMount () {
@@ -462,6 +468,7 @@ class Status extends ImmutablePureComponent {
previousId={i > 0 ? list[i - 1] : undefined} previousId={i > 0 ? list[i - 1] : undefined}
nextId={list[i + 1] || (ancestors && statusId)} nextId={list[i + 1] || (ancestors && statusId)}
rootId={statusId} rootId={statusId}
shouldHighlightOnMount={this.state.newRepliesIds.includes(id)}
/> />
)); ));
} }
@@ -495,11 +502,20 @@ class Status extends ImmutablePureComponent {
} }
componentDidUpdate (prevProps) { componentDidUpdate (prevProps) {
const { status, ancestorsIds } = this.props; const { status, ancestorsIds, descendantsIds } = this.props;
if (status && (ancestorsIds.length > prevProps.ancestorsIds.length || prevProps.status?.get('id') !== status.get('id'))) { if (status && (ancestorsIds.length > prevProps.ancestorsIds.length || prevProps.status?.get('id') !== status.get('id'))) {
this._scrollStatusIntoView(); this._scrollStatusIntoView();
} }
// Only highlight replies after the initial load
if (prevProps.descendantsIds.length) {
const newRepliesIds = difference(descendantsIds, prevProps.descendantsIds);
if (newRepliesIds.length) {
this.setState({newRepliesIds});
}
}
} }
componentWillUnmount () { componentWillUnmount () {

View File

@@ -1597,6 +1597,15 @@
} }
} }
} }
.no-reduce-motion &--highlighted-entry::before {
content: '';
position: absolute;
inset: 0;
background: rgb(from $ui-highlight-color r g b / 20%);
opacity: 0;
animation: fade 0.7s reverse both 0.3s;
}
} }
.status__relative-time { .status__relative-time {