mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-14 08:19:05 +00:00
Replace glitch-soc's collapsed toots with upstream's “Read more” (#2916)
* Remove glitch-soc's post collapse feature * Get rid of the infamous `parseClick` * Remove unused CSS * Use upstream's “Read More” implementation * Update translation strings
This commit is contained in:
@@ -24,11 +24,12 @@ import { SensitiveMediaContext } from '../features/ui/util/sensitive_media_conte
|
||||
import { displayMedia } from '../initial_state';
|
||||
|
||||
import AttachmentList from './attachment_list';
|
||||
import { CollapseButton } from './collapse_button';
|
||||
import { Avatar } from './avatar';
|
||||
import { AvatarOverlay } from './avatar_overlay';
|
||||
import { DisplayName } from './display_name';
|
||||
import { getHashtagBarForStatus } from './hashtag_bar';
|
||||
import StatusActionBar from './status_action_bar';
|
||||
import StatusContent from './status_content';
|
||||
import StatusHeader from './status_header';
|
||||
import StatusIcons from './status_icons';
|
||||
import StatusPrepend from './status_prepend';
|
||||
|
||||
@@ -99,6 +100,7 @@ class Status extends ImmutablePureComponent {
|
||||
onEmbed: PropTypes.func,
|
||||
onHeightChange: PropTypes.func,
|
||||
onToggleHidden: PropTypes.func,
|
||||
onToggleCollapsed: PropTypes.func,
|
||||
onTranslate: PropTypes.func,
|
||||
onInteractionModal: PropTypes.func,
|
||||
muted: PropTypes.bool,
|
||||
@@ -127,8 +129,6 @@ class Status extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
state = {
|
||||
isCollapsed: false,
|
||||
autoCollapsed: false,
|
||||
isExpanded: undefined,
|
||||
showMedia: defaultMediaVisibility(this.props.status, this.props.settings) && !(this.context?.hideMediaByDefault),
|
||||
revealBehindCW: undefined,
|
||||
@@ -156,19 +156,10 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
updateOnStates = [
|
||||
'isExpanded',
|
||||
'isCollapsed',
|
||||
'showMedia',
|
||||
'forceFilter',
|
||||
];
|
||||
|
||||
// If our settings have changed to disable collapsed statuses, then we
|
||||
// need to make sure that we uncollapse every one. We do that by watching
|
||||
// for changes to `settings.collapsed.enabled` in
|
||||
// `getderivedStateFromProps()`.
|
||||
|
||||
// We also need to watch for changes on the `collapse` prop---if this
|
||||
// changes to anything other than `undefined`, then we need to collapse or
|
||||
// uncollapse our status accordingly.
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
let update = {};
|
||||
let updated = false;
|
||||
@@ -183,30 +174,12 @@ class Status extends ImmutablePureComponent {
|
||||
updated = true;
|
||||
}
|
||||
|
||||
// Update state based on new props
|
||||
if (!nextProps.settings.getIn(['collapsed', 'enabled'])) {
|
||||
if (prevState.isCollapsed) {
|
||||
update.isCollapsed = false;
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle uncollapsing toots when the shared CW state is expanded
|
||||
if (nextProps.settings.getIn(['content_warnings', 'shared_state']) &&
|
||||
nextProps.status?.get('spoiler_text')?.length && nextProps.status?.get('hidden') === false &&
|
||||
prevState.statusPropHidden !== false && prevState.isCollapsed
|
||||
) {
|
||||
update.isCollapsed = false;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
// The “expanded” prop is used to one-off change the local state.
|
||||
// It's used in the thread view when unfolding/re-folding all CWs at once.
|
||||
if (nextProps.expanded !== prevState.expandedProp &&
|
||||
nextProps.expanded !== undefined
|
||||
) {
|
||||
update.isExpanded = nextProps.expanded;
|
||||
if (nextProps.expanded) update.isCollapsed = false;
|
||||
updated = true;
|
||||
}
|
||||
|
||||
@@ -226,63 +199,13 @@ class Status extends ImmutablePureComponent {
|
||||
return updated ? update : null;
|
||||
}
|
||||
|
||||
// When mounting, we just check to see if our status should be collapsed,
|
||||
// and collapse it if so. We don't need to worry about whether collapsing
|
||||
// is enabled here, because `setCollapsed()` already takes that into
|
||||
// account.
|
||||
|
||||
// The cases where a status should be collapsed are:
|
||||
//
|
||||
// - The `collapse` prop has been set to `true`
|
||||
// - The user has decided in local settings to collapse all statuses.
|
||||
// - The user has decided to collapse all notifications ('muted'
|
||||
// statuses).
|
||||
// - The user has decided to collapse long statuses and the status is
|
||||
// over the user set value (default 400 without media, or 610px with).
|
||||
// - The status is a reply and the user has decided to collapse all
|
||||
// replies.
|
||||
// - The status contains media and the user has decided to collapse all
|
||||
// statuses with media.
|
||||
// - The status is a reblog the user has decided to collapse all
|
||||
// statuses which are reblogs.
|
||||
componentDidMount () {
|
||||
const { node } = this;
|
||||
const {
|
||||
status,
|
||||
settings,
|
||||
collapse,
|
||||
muted,
|
||||
prepend,
|
||||
} = this.props;
|
||||
|
||||
// Prevent a crash when node is undefined. Not completely sure why this
|
||||
// happens, might be because status === null.
|
||||
if (node === undefined) return;
|
||||
|
||||
const autoCollapseSettings = settings.getIn(['collapsed', 'auto']);
|
||||
|
||||
// Don't autocollapse if CW state is shared and status is explicitly revealed,
|
||||
// as it could cause surprising changes when receiving notifications
|
||||
if (settings.getIn(['content_warnings', 'shared_state']) && status.get('spoiler_text').length && !status.get('hidden')) return;
|
||||
|
||||
let autoCollapseHeight = parseInt(autoCollapseSettings.get('height'));
|
||||
if (status.get('media_attachments').size && !muted) {
|
||||
autoCollapseHeight += 210;
|
||||
}
|
||||
|
||||
if (collapse ||
|
||||
autoCollapseSettings.get('all') ||
|
||||
(autoCollapseSettings.get('notifications') && muted) ||
|
||||
(autoCollapseSettings.get('lengthy') && node.clientHeight > autoCollapseHeight) ||
|
||||
(autoCollapseSettings.get('reblogs') && prepend === 'reblogged_by') ||
|
||||
(autoCollapseSettings.get('replies') && status.get('in_reply_to_id', null) !== null) ||
|
||||
(autoCollapseSettings.get('media') && !(status.get('spoiler_text').length) && status.get('media_attachments').size > 0)
|
||||
) {
|
||||
this.setCollapsed(true);
|
||||
// Hack to fix timeline jumps on second rendering when auto-collapsing
|
||||
this.setState({ autoCollapsed: true });
|
||||
}
|
||||
|
||||
// Hack to fix timeline jumps when a preview card is fetched
|
||||
this.setState({
|
||||
showCard: !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card') && this.props.settings.get('inline_preview_cards'),
|
||||
@@ -297,16 +220,15 @@ class Status extends ImmutablePureComponent {
|
||||
const { muted, hidden, status, settings } = this.props;
|
||||
|
||||
const doShowCard = !muted && !hidden && status && status.get('card') && settings.get('inline_preview_cards');
|
||||
if (this.state.autoCollapsed || (doShowCard && !this.state.showCard)) {
|
||||
if (doShowCard && !this.state.showCard) {
|
||||
if (doShowCard) this.setState({ showCard: true });
|
||||
if (this.state.autoCollapsed) this.setState({ autoCollapsed: false });
|
||||
return this.props.getScrollPosition();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||
componentDidUpdate(prevProps, _prevState, snapshot) {
|
||||
if (snapshot !== null && this.props.updateScrollBottom && this.node.offsetTop < snapshot.top) {
|
||||
this.props.updateScrollBottom(snapshot.height - snapshot.top);
|
||||
}
|
||||
@@ -335,72 +257,43 @@ class Status extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// `setCollapsed()` sets the value of `isCollapsed` in our state, that is,
|
||||
// whether the toot is collapsed or not.
|
||||
|
||||
// `setCollapsed()` automatically checks for us whether toot collapsing
|
||||
// is enabled, so we don't have to.
|
||||
setCollapsed = (value) => {
|
||||
if (this.props.settings.getIn(['collapsed', 'enabled'])) {
|
||||
if (value) {
|
||||
this.setExpansion(false);
|
||||
}
|
||||
this.setState({ isCollapsed: value });
|
||||
} else {
|
||||
this.setState({ isCollapsed: false });
|
||||
}
|
||||
};
|
||||
|
||||
setExpansion = (value) => {
|
||||
if (this.props.settings.getIn(['content_warnings', 'shared_state']) && this.props.status.get('hidden') === value) {
|
||||
this.props.onToggleHidden(this.props.status);
|
||||
}
|
||||
|
||||
this.setState({ isExpanded: value });
|
||||
if (value) {
|
||||
this.setCollapsed(false);
|
||||
}
|
||||
};
|
||||
|
||||
// `parseClick()` takes a click event and responds appropriately.
|
||||
// If our status is collapsed, then clicking on it should uncollapse it.
|
||||
// If `Shift` is held, then clicking on it should collapse it.
|
||||
// Otherwise, we open the url handed to us in `destination`, if
|
||||
// applicable.
|
||||
parseClick = (e, destination) => {
|
||||
const { status, history } = this.props;
|
||||
const { isCollapsed } = this.state;
|
||||
if (!history) return;
|
||||
|
||||
if (e.button !== 0 || e.ctrlKey || e.altKey || e.metaKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCollapsed) this.setCollapsed(false);
|
||||
else if (e.shiftKey) {
|
||||
this.setCollapsed(true);
|
||||
document.getSelection().removeAllRanges();
|
||||
} else if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
return;
|
||||
} else {
|
||||
if (destination === undefined) {
|
||||
destination = `/@${
|
||||
status.getIn(['reblog', 'account', 'acct'], status.getIn(['account', 'acct']))
|
||||
}/${
|
||||
status.getIn(['reblog', 'id'], status.get('id'))
|
||||
}`;
|
||||
}
|
||||
history.push(destination);
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
handleToggleMediaVisibility = () => {
|
||||
this.setState({ showMedia: !this.state.showMedia });
|
||||
};
|
||||
|
||||
handleClick = e => {
|
||||
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
this.handleHotkeyOpen();
|
||||
};
|
||||
|
||||
handleAccountClick = (e, proper = true) => {
|
||||
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
this._openProfile(proper);
|
||||
};
|
||||
|
||||
handleExpandedToggle = () => {
|
||||
if (this.props.settings.getIn(['content_warnings', 'shared_state'])) {
|
||||
this.props.onToggleHidden(this.props.status);
|
||||
@@ -466,12 +359,34 @@ class Status extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
handleHotkeyOpen = () => {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick();
|
||||
return;
|
||||
}
|
||||
|
||||
const { history } = this.props;
|
||||
const status = this.props.status;
|
||||
this.props.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
|
||||
history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
|
||||
};
|
||||
|
||||
handleHotkeyOpenProfile = () => {
|
||||
this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
this._openProfile();
|
||||
};
|
||||
|
||||
_openProfile = () => {
|
||||
const { history } = this.props;
|
||||
const status = this.props.status;
|
||||
|
||||
if (!history) {
|
||||
return;
|
||||
}
|
||||
|
||||
history.push(`/@${status.getIn(['account', 'acct'])}`);
|
||||
};
|
||||
|
||||
handleHotkeyMoveUp = e => {
|
||||
@@ -482,13 +397,6 @@ class Status extends ImmutablePureComponent {
|
||||
this.props.onMoveDown(this.props.containerId || this.props.id, e.target.getAttribute('data-featured'));
|
||||
};
|
||||
|
||||
handleHotkeyCollapse = () => {
|
||||
if (!this.props.settings.getIn(['collapsed', 'enabled']))
|
||||
return;
|
||||
|
||||
this.setCollapsed(!this.state.isCollapsed);
|
||||
};
|
||||
|
||||
handleHotkeyToggleSensitive = () => {
|
||||
this.handleToggleMediaVisibility();
|
||||
};
|
||||
@@ -506,6 +414,10 @@ class Status extends ImmutablePureComponent {
|
||||
this.node = c;
|
||||
};
|
||||
|
||||
handleCollapsedToggle = isCollapsed => {
|
||||
this.props.onToggleCollapsed(this.props.status, isCollapsed);
|
||||
};
|
||||
|
||||
handleTranslate = () => {
|
||||
this.props.onTranslate(this.props.status);
|
||||
};
|
||||
@@ -525,16 +437,10 @@ class Status extends ImmutablePureComponent {
|
||||
render () {
|
||||
const { intl, hidden, featured, unfocusable, unread, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46 } = this.props;
|
||||
|
||||
const {
|
||||
parseClick,
|
||||
setCollapsed,
|
||||
} = this;
|
||||
|
||||
const {
|
||||
status,
|
||||
account,
|
||||
settings,
|
||||
collapsed,
|
||||
muted,
|
||||
intersectionObserverWrapper,
|
||||
onOpenVideo,
|
||||
@@ -543,7 +449,6 @@ class Status extends ImmutablePureComponent {
|
||||
history,
|
||||
...other
|
||||
} = this.props;
|
||||
const { isCollapsed } = this.state;
|
||||
let attachments = null;
|
||||
|
||||
// Depending on user settings, some media are considered as parts of the
|
||||
@@ -555,6 +460,7 @@ class Status extends ImmutablePureComponent {
|
||||
let extraMediaIcons = [];
|
||||
let media = contentMedia;
|
||||
let mediaIcons = contentMediaIcons;
|
||||
let statusAvatar;
|
||||
|
||||
if (settings.getIn(['content_warnings', 'media_outside'])) {
|
||||
media = extraMedia;
|
||||
@@ -578,7 +484,6 @@ class Status extends ImmutablePureComponent {
|
||||
moveDown: this.handleHotkeyMoveDown,
|
||||
toggleHidden: this.handleExpandedToggle,
|
||||
bookmark: this.handleHotkeyBookmark,
|
||||
toggleCollapse: this.handleHotkeyCollapse,
|
||||
toggleSensitive: this.handleHotkeyToggleSensitive,
|
||||
openMedia: this.handleHotkeyOpenMedia,
|
||||
};
|
||||
@@ -650,7 +555,7 @@ class Status extends ImmutablePureComponent {
|
||||
sensitive={status.get('sensitive')}
|
||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
|
||||
hidden={isCollapsed || !isExpanded}
|
||||
hidden={!isExpanded}
|
||||
onOpenMedia={this.handleOpenMedia}
|
||||
cacheWidth={this.props.cacheMediaWidth}
|
||||
defaultWidth={this.props.cachedMediaWidth}
|
||||
@@ -707,7 +612,7 @@ class Status extends ImmutablePureComponent {
|
||||
sensitive={status.get('sensitive')}
|
||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
|
||||
preventPlayback={isCollapsed || !isExpanded}
|
||||
preventPlayback={!isExpanded}
|
||||
onOpenVideo={this.handleOpenVideo}
|
||||
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
|
||||
visible={this.state.showMedia}
|
||||
@@ -754,15 +659,8 @@ class Status extends ImmutablePureComponent {
|
||||
<StatusPrepend
|
||||
type={this.props.prepend}
|
||||
account={account}
|
||||
parseClick={parseClick}
|
||||
notificationId={this.props.notificationId}
|
||||
>
|
||||
{muted && settings.getIn(['collapsed', 'enabled']) && (
|
||||
<div className='notification__message-collapse-button'>
|
||||
<CollapseButton collapsed={isCollapsed} setCollapsed={setCollapsed} />
|
||||
</div>
|
||||
)}
|
||||
</StatusPrepend>
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -770,13 +668,19 @@ class Status extends ImmutablePureComponent {
|
||||
rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: account.get('acct') });
|
||||
}
|
||||
|
||||
if (account === undefined || account === null) {
|
||||
statusAvatar = <Avatar account={status.get('account')} size={avatarSize} />;
|
||||
} else {
|
||||
statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
|
||||
}
|
||||
|
||||
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
||||
contentMedia.push(hashtagBar);
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
||||
<div
|
||||
className={classNames('status__wrapper', 'focusable', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, collapsed: isCollapsed })}
|
||||
className={classNames('status__wrapper', 'focusable', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread })}
|
||||
{...selectorAttribs}
|
||||
tabIndex={unfocusable ? null : 0}
|
||||
data-featured={featured ? 'true' : null}
|
||||
@@ -792,50 +696,47 @@ class Status extends ImmutablePureComponent {
|
||||
>
|
||||
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
||||
|
||||
{(!muted || !isCollapsed) && (
|
||||
{(!muted) && (
|
||||
/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */
|
||||
<header onClick={this.parseClick} className='status__info'>
|
||||
<StatusHeader
|
||||
status={status}
|
||||
friend={account}
|
||||
collapsed={isCollapsed}
|
||||
parseClick={parseClick}
|
||||
avatarSize={avatarSize}
|
||||
/>
|
||||
<header onClick={this.handleClick} className='status__info'>
|
||||
<a onClick={this.handleAccountClick} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} data-hover-card-account={status.getIn(['account', 'id'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
|
||||
<div className='status__avatar'>
|
||||
{statusAvatar}
|
||||
</div>
|
||||
|
||||
<DisplayName account={status.get('account')} />
|
||||
</a>
|
||||
<StatusIcons
|
||||
status={status}
|
||||
mediaIcons={contentMediaIcons.concat(extraMediaIcons)}
|
||||
collapsible={!muted && settings.getIn(['collapsed', 'enabled'])}
|
||||
collapsed={isCollapsed}
|
||||
setCollapsed={setCollapsed}
|
||||
settings={settings.get('status_icons')}
|
||||
/>
|
||||
</header>
|
||||
)}
|
||||
<StatusContent
|
||||
status={status}
|
||||
onClick={this.handleClick}
|
||||
onTranslate={this.handleTranslate}
|
||||
collapsible
|
||||
media={contentMedia}
|
||||
extraMedia={extraMedia}
|
||||
mediaIcons={contentMediaIcons}
|
||||
expanded={isExpanded}
|
||||
onExpandedToggle={this.handleExpandedToggle}
|
||||
onTranslate={this.handleTranslate}
|
||||
parseClick={parseClick}
|
||||
disabled={!history}
|
||||
onCollapsedToggle={this.handleCollapsedToggle}
|
||||
tagLinks={settings.get('tag_misleading_links')}
|
||||
rewriteMentions={settings.get('rewrite_mentions')}
|
||||
{...statusContentProps}
|
||||
/>
|
||||
|
||||
{(!isCollapsed || !(muted || !settings.getIn(['collapsed', 'show_action_bar']))) && (
|
||||
<StatusActionBar
|
||||
status={status}
|
||||
account={status.get('account')}
|
||||
showReplyCount={settings.get('show_reply_count')}
|
||||
onFilter={matchedFilters ? this.handleFilterClick : null}
|
||||
{...other}
|
||||
/>
|
||||
)}
|
||||
<StatusActionBar
|
||||
status={status}
|
||||
account={status.get('account')}
|
||||
showReplyCount={settings.get('show_reply_count')}
|
||||
onFilter={matchedFilters ? this.handleFilterClick : null}
|
||||
{...other}
|
||||
/>
|
||||
|
||||
{notification && (
|
||||
<NotificationOverlayContainer
|
||||
notification={notification}
|
||||
|
||||
Reference in New Issue
Block a user