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
pull/2918/head
Claire 2024-12-22 20:27:32 +01:00 committed by GitHub
parent 28751ff042
commit d65f6c2f8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 203 additions and 644 deletions

View File

@ -1,46 +0,0 @@
import PropTypes from 'prop-types';
import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import ExpandLessIcon from '@/material-icons/400-24px/expand_less.svg?react';
import { IconButton } from './icon_button';
const messages = defineMessages({
collapse: { id: 'status.collapse', defaultMessage: 'Collapse' },
uncollapse: { id: 'status.uncollapse', defaultMessage: 'Uncollapse' },
});
export const CollapseButton = ({ collapsed, setCollapsed }) => {
const intl = useIntl();
const handleCollapsedClick = useCallback((e) => {
if (e.button === 0) {
setCollapsed(!collapsed);
e.preventDefault();
e.stopPropagation();
}
}, [collapsed, setCollapsed]);
return (
<IconButton
className='status__collapse-button'
animate
active={collapsed}
title={
collapsed ?
intl.formatMessage(messages.uncollapse) :
intl.formatMessage(messages.collapse)
}
icon='angle-double-up'
iconComponent={ExpandLessIcon}
onClick={handleCollapsedClick}
/>
);
};
CollapseButton.propTypes = {
collapsed: PropTypes.bool,
setCollapsed: PropTypes.func.isRequired,
};

View File

@ -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,42 +696,39 @@ 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')}
@ -835,7 +736,7 @@ class Status extends ImmutablePureComponent {
onFilter={matchedFilters ? this.handleFilterClick : null}
{...other}
/>
)}
{notification && (
<NotificationOverlayContainer
notification={notification}

View File

@ -9,6 +9,7 @@ import { withRouter } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
import ImageIcon from '@/material-icons/400-24px/image.svg?react';
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
import LinkIcon from '@/material-icons/400-24px/link.svg?react';
@ -20,9 +21,10 @@ import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity
import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
import { Permalink } from './permalink';
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
const textMatchesTarget = (text, origin, host) => {
return (text === origin || text === host
|| text.startsWith(origin + '/') || text.startsWith(host + '/')
@ -134,14 +136,14 @@ class StatusContent extends PureComponent {
status: ImmutablePropTypes.map.isRequired,
statusContent: PropTypes.string,
expanded: PropTypes.bool,
collapsed: PropTypes.bool,
onExpandedToggle: PropTypes.func,
onTranslate: PropTypes.func,
media: PropTypes.node,
extraMedia: PropTypes.node,
mediaIcons: PropTypes.arrayOf(PropTypes.string),
parseClick: PropTypes.func,
disabled: PropTypes.bool,
onClick: PropTypes.func,
collapsible: PropTypes.bool,
onCollapsedToggle: PropTypes.func,
onUpdate: PropTypes.func,
tagLinks: PropTypes.bool,
rewriteMentions: PropTypes.string,
@ -170,16 +172,21 @@ class StatusContent extends PureComponent {
return;
}
const { status, onCollapsedToggle } = this.props;
const links = node.querySelectorAll('a');
let link, mention;
for (var i = 0; i < links.length; ++i) {
let link = links[i];
link = links[i];
if (link.classList.contains('status-link')) {
continue;
}
link.classList.add('status-link');
let mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));
mention = this.props.status.get('mentions').find(item => link.href === item.get('url'));
if (mention) {
link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
@ -195,7 +202,6 @@ class StatusContent extends PureComponent {
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
} else {
link.addEventListener('click', this.onLinkClick.bind(this), false);
link.setAttribute('title', link.href);
link.classList.add('unhandled-link');
@ -228,6 +234,18 @@ class StatusContent extends PureComponent {
}
}
}
if (status.get('collapsed', null) === null && onCollapsedToggle) {
const { collapsible, onClick } = this.props;
const collapsed =
collapsible
&& onClick
&& node.clientHeight > MAX_HEIGHT
&& status.get('spoiler_text').length === 0;
onCollapsedToggle(collapsed);
}
}
handleMouseEnter = ({ currentTarget }) => {
@ -265,23 +283,19 @@ class StatusContent extends PureComponent {
if (this.props.onUpdate) this.props.onUpdate();
}
onLinkClick = (e) => {
if (this.props.collapsed) {
if (this.props.parseClick) this.props.parseClick(e);
}
};
onMentionClick = (mention, e) => {
if (this.props.parseClick) {
this.props.parseClick(e, `/@${mention.get('acct')}`);
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.props.history.push(`/@${mention.get('acct')}`);
}
};
onHashtagClick = (hashtag, e) => {
hashtag = hashtag.replace(/^#/, '');
if (this.props.parseClick) {
this.props.parseClick(e, `/tags/${hashtag}`);
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.props.history.push(`/tags/${hashtag}`);
}
};
@ -290,9 +304,7 @@ class StatusContent extends PureComponent {
};
handleMouseUp = (e) => {
const { parseClick, disabled } = this.props;
if (disabled || !this.startXY) {
if (!this.startXY) {
return;
}
@ -307,8 +319,8 @@ class StatusContent extends PureComponent {
element = element.parentNode;
}
if (deltaX + deltaY < 5 && e.button === 0 && parseClick) {
parseClick(e);
if (deltaX + deltaY < 5 && e.button === 0 && this.props.onClick) {
this.props.onClick(e);
}
this.startXY = null;
@ -338,14 +350,13 @@ class StatusContent extends PureComponent {
media,
extraMedia,
mediaIcons,
parseClick,
disabled,
tagLinks,
rewriteMentions,
intl,
statusContent,
} = this.props;
const renderReadMore = this.props.onClick && status.get('collapsed');
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const contentLocale = intl.locale.replace(/[_-].*/, '');
const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
@ -355,10 +366,17 @@ class StatusContent extends PureComponent {
const spoilerHtml = status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml');
const language = status.getIn(['translation', 'language']) || status.get('language');
const classNames = classnames('status__content', {
'status__content--with-action': parseClick && !disabled,
'status__content--with-action': this.props.onClick && this.props.history,
'status__content--collapsed': renderReadMore,
'status__content--with-spoiler': status.get('spoiler_text').length > 0,
});
const readMoreButton = renderReadMore && (
<button className='status__content__read-more-button' onClick={this.props.onClick} key='read-more'>
<FormattedMessage id='status.read_more' defaultMessage='Read more' /><Icon id='angle-right' icon={ChevronRightIcon} />
</button>
);
const translateButton = renderTranslate && (
<TranslateButton onClick={this.handleTranslate} translation={status.get('translation')} />
);
@ -427,7 +445,7 @@ class StatusContent extends PureComponent {
{extraMedia}
</div>
);
} else if (parseClick) {
} else if (this.props.onClick) {
return (
<div
className={classNames}
@ -446,6 +464,7 @@ class StatusContent extends PureComponent {
lang={language}
/>
{translateButton}
{readMoreButton}
{media}
{extraMedia}
</div>

View File

@ -1,63 +0,0 @@
// Package imports.
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
// Mastodon imports.
import { Avatar } from './avatar';
import { AvatarOverlay } from './avatar_overlay';
import { DisplayName } from './display_name';
export default class StatusHeader extends PureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
friend: ImmutablePropTypes.map,
avatarSize: PropTypes.number,
parseClick: PropTypes.func.isRequired,
};
handleAccountClick = (e) => {
const { status, parseClick } = this.props;
parseClick(e, `/@${status.getIn(['account', 'acct'])}`);
e.stopPropagation();
};
// Rendering.
render () {
const {
status,
friend,
avatarSize,
} = this.props;
const account = status.get('account');
let statusAvatar;
if (friend === undefined || friend === null) {
statusAvatar = <Avatar account={account} size={avatarSize} />;
} else {
statusAvatar = <AvatarOverlay account={account} friend={friend} />;
}
return (
<a
href={account.get('url')}
className='status__display-name'
target='_blank'
onClick={this.handleAccountClick}
rel='noopener noreferrer'
title={status.getIn(['account', 'acct'])}
data-hover-card-account={status.getIn(['account', 'id'])}
>
<div className='status__avatar'>
{statusAvatar}
</div>
<DisplayName account={account} />
</a>
);
}
}

View File

@ -16,12 +16,9 @@ import MusicNoteIcon from '@/material-icons/400-24px/music_note.svg?react';
import { Icon } from 'flavours/glitch/components/icon';
import { languages } from 'flavours/glitch/initial_state';
import { CollapseButton } from './collapse_button';
import { VisibilityIcon } from './visibility_icon';
const messages = defineMessages({
collapse: { id: 'status.collapse', defaultMessage: 'Collapse' },
uncollapse: { id: 'status.uncollapse', defaultMessage: 'Uncollapse' },
inReplyTo: { id: 'status.in_reply_to', defaultMessage: 'This toot is a reply' },
previewCard: { id: 'status.has_preview_card', defaultMessage: 'Features an attached preview card' },
pictures: { id: 'status.has_pictures', defaultMessage: 'Features attached pictures' },
@ -53,22 +50,10 @@ class StatusIcons extends PureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
mediaIcons: PropTypes.arrayOf(PropTypes.string),
collapsible: PropTypes.bool,
collapsed: PropTypes.bool,
setCollapsed: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
settings: ImmutablePropTypes.map.isRequired,
};
// Handles clicks on collapsed button
handleCollapsedClick = (e) => {
const { collapsed, setCollapsed } = this.props;
if (e.button === 0) {
setCollapsed(!collapsed);
e.preventDefault();
}
};
renderIcon (mediaIcon) {
const { intl } = this.props;
@ -114,9 +99,6 @@ class StatusIcons extends PureComponent {
const {
status,
mediaIcons,
collapsible,
collapsed,
setCollapsed,
settings,
intl,
} = this.props;
@ -142,7 +124,6 @@ class StatusIcons extends PureComponent {
/>}
{settings.get('media') && !!mediaIcons && mediaIcons.map(icon => this.renderIcon(icon))}
{settings.get('visibility') && <VisibilityIcon visibility={status.get('visibility')} />}
{collapsible && <CollapseButton collapsed={collapsed} setCollapsed={setCollapsed} />}
</div>
);
}

View File

@ -15,27 +15,23 @@ import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
import { Icon } from 'flavours/glitch/components/icon';
import { me } from 'flavours/glitch/initial_state';
import { Permalink } from './permalink';
export default class StatusPrepend extends PureComponent {
static propTypes = {
type: PropTypes.string.isRequired,
account: ImmutablePropTypes.map.isRequired,
parseClick: PropTypes.func.isRequired,
notificationId: PropTypes.number,
children: PropTypes.node,
};
handleClick = (e) => {
const { account, parseClick } = this.props;
parseClick(e, `/@${account.get('acct')}`);
};
Message = () => {
const { type, account } = this.props;
let link = (
<a
<Permalink
onClick={this.handleClick}
to={`/@${account.get('acct')}`}
href={account.get('url')}
className='status__display-name'
data-hover-card-account={account.get('id')}
@ -47,7 +43,7 @@ export default class StatusPrepend extends PureComponent {
}}
/>
</bdi>
</a>
</Permalink>
);
switch (type) {
case 'featured':

View File

@ -26,6 +26,7 @@ import {
unmuteStatus,
deleteStatus,
toggleStatusSpoilers,
toggleStatusCollapse,
editStatus,
translateStatus,
undoStatusTranslation,
@ -191,6 +192,11 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
dispatch(toggleStatusSpoilers(status.get('id')));
},
onToggleCollapsed (status, isCollapsed) {
dispatch(toggleStatusCollapse(status.get('id'), isCollapsed));
},
deployPictureInPicture (status, type, mediaProps) {
dispatch((_, getState) => {
if (getState().getIn(['local_settings', 'media', 'pop_in_player'])) {

View File

@ -63,19 +63,6 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
const sharedCWState = useSelector(state => state.getIn(['state', 'content_warnings', 'shared_state']));
const [expanded, setExpanded] = useState(undefined);
const parseClick = useCallback((e, destination) => {
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) {
if (destination === undefined) {
if (unread) {
dispatch(markConversationRead(id));
}
destination = `/statuses/${lastStatus.get('id')}`;
}
history.push(destination);
e.preventDefault();
}
}, [dispatch, history, unread, id, lastStatus]);
const handleMouseEnter = useCallback(({ currentTarget }) => {
if (autoPlayGif) {
return;
@ -215,7 +202,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
<StatusContent
status={lastStatus}
parseClick={parseClick}
onClick={handleClick}
expanded={sharedCWState ? lastStatus.get('hidden') : expanded}
onExpandedToggle={handleShowMore}
collapsible

View File

@ -5,7 +5,6 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
import Column from 'flavours/glitch/components/column';
@ -15,20 +14,15 @@ const messages = defineMessages({
heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' },
});
const mapStateToProps = state => ({
collapseEnabled: state.getIn(['local_settings', 'collapsed', 'enabled']),
});
class KeyboardShortcuts extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
collapseEnabled: PropTypes.bool,
};
render () {
const { intl, collapseEnabled, multiColumn } = this.props;
const { intl, multiColumn } = this.props;
return (
<Column>
@ -88,12 +82,6 @@ class KeyboardShortcuts extends ImmutablePureComponent {
<td><kbd>h</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.toggle_sensitivity' defaultMessage='to show/hide media' /></td>
</tr>
{collapseEnabled && (
<tr>
<td><kbd>shift</kbd>+<kbd>x</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.toggle_collapse' defaultMessage='to collapse/uncollapse toots' /></td>
</tr>
)}
<tr>
<td><kbd>up</kbd>, <kbd>k</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.up' defaultMessage='to move up in the list' /></td>
@ -134,6 +122,54 @@ class KeyboardShortcuts extends ImmutablePureComponent {
<td><kbd>esc</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.unfocus' defaultMessage='to un-focus compose textarea/search' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>h</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.home' defaultMessage='to open home timeline' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>n</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.notifications' defaultMessage='to open notifications column' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>l</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.local' defaultMessage='to open local timeline' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>t</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.federated' defaultMessage='to open federated timeline' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>d</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.direct' defaultMessage='to open direct messages column' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>s</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.start' defaultMessage='to open "get started" column' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>f</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.favourites' defaultMessage='to open favorites list' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>p</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.pinned' defaultMessage='to open pinned posts list' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>u</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.my_profile' defaultMessage='to open your profile' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>b</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.blocked' defaultMessage='to open blocked users list' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>m</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.muted' defaultMessage='to open muted users list' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>r</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.requests' defaultMessage='to open follow requests list' /></td>
</tr>
<tr>
<td><kbd>?</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.legend' defaultMessage='to display this legend' /></td>
@ -151,4 +187,4 @@ class KeyboardShortcuts extends ImmutablePureComponent {
}
export default connect(mapStateToProps)(injectIntl(KeyboardShortcuts));
export default injectIntl(KeyboardShortcuts);

View File

@ -6,7 +6,6 @@ import { injectIntl, defineMessages } from 'react-intl';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
import ExpandLessIcon from '@/material-icons/400-24px/expand_less.svg?react';
import ImageIcon from '@/material-icons/400-24px/image.svg?react';
import ManufacturingIcon from '@/material-icons/400-24px/manufacturing.svg?react';
import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react';
@ -19,7 +18,6 @@ const messages = defineMessages({
general: { id: 'settings.general', defaultMessage: 'General' },
compose: { id: 'settings.compose_box_opts', defaultMessage: 'Compose box' },
content_warnings: { id: 'settings.content_warnings', defaultMessage: 'Content Warnings' },
collapsed: { id: 'settings.collapsed_statuses', defaultMessage: 'Collapsed toots' },
media: { id: 'settings.media', defaultMessage: 'Media' },
preferences: { id: 'settings.preferences', defaultMessage: 'Preferences' },
close: { id: 'settings.close', defaultMessage: 'Close' },
@ -64,14 +62,6 @@ class LocalSettingsNavigation extends PureComponent {
iconComponent={WarningIcon}
title={intl.formatMessage(messages.content_warnings)}
/>
<LocalSettingsNavigationItem
active={index === 3}
index={3}
onNavigate={onNavigate}
icon='angle-double-up'
iconComponent={ExpandLessIcon}
title={intl.formatMessage(messages.collapsed)}
/>
<LocalSettingsNavigationItem
active={index === 4}
index={4}

View File

@ -320,103 +320,6 @@ class LocalSettingsPage extends PureComponent {
</section>
</div>
),
({ onChange, settings }) => (
<div className='glitch local-settings__page collapsed'>
<h1><FormattedMessage id='settings.collapsed_statuses' defaultMessage='Collapsed toots' /></h1>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'enabled']}
id='mastodon-settings--collapsed-enabled'
onChange={onChange}
>
<FormattedMessage id='settings.enable_collapsed' defaultMessage='Enable collapsed toots' />
<span className='hint'><FormattedMessage id='settings.enable_collapsed_hint' defaultMessage='Collapsed posts have parts of their contents hidden to take up less screen space. This is distinct from the Content Warning feature' /></span>
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'show_action_bar']}
id='mastodon-settings--collapsed-show-action-bar'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
>
<FormattedMessage id='settings.show_action_bar' defaultMessage='Show action buttons in collapsed toots' />
</LocalSettingsPageItem>
<section>
<h2><FormattedMessage id='settings.auto_collapse' defaultMessage='Automatic collapsing' /></h2>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'all']}
id='mastodon-settings--collapsed-auto-all'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
>
<FormattedMessage id='settings.auto_collapse_all' defaultMessage='Everything' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'notifications']}
id='mastodon-settings--collapsed-auto-notifications'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_notifications' defaultMessage='Notifications' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'lengthy']}
id='mastodon-settings--collapsed-auto-lengthy'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_lengthy' defaultMessage='Lengthy toots' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'reblogs']}
id='mastodon-settings--collapsed-auto-reblogs'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_reblogs' defaultMessage='Boosts' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'replies']}
id='mastodon-settings--collapsed-auto-replies'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_replies' defaultMessage='Replies' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'media']}
id='mastodon-settings--collapsed-auto-media'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
>
<FormattedMessage id='settings.auto_collapse_media' defaultMessage='Toots with media' />
</LocalSettingsPageItem>
<LocalSettingsPageItem
settings={settings}
item={['collapsed', 'auto', 'height']}
id='mastodon-settings--collapsed-auto-height'
placeholder='400'
onChange={onChange}
dependsOn={[['collapsed', 'enabled']]}
dependsOnNot={[['collapsed', 'auto', 'all']]}
inputProps={{ type: 'number', min: '200', max: '999' }}
>
<FormattedMessage id='settings.auto_collapse_height' defaultMessage='Height (in pixels) for a toot to be considered lengthy' />
</LocalSettingsPageItem>
</section>
</div>
),
({ intl, onChange, settings }) => (
<div className='glitch local-settings__page media'>
<h1><FormattedMessage id='settings.media' defaultMessage='Media' /></h1>

View File

@ -19,7 +19,6 @@ import { getHashtagBarForStatus } from 'flavours/glitch/components/hashtag_bar';
import { IconLogo } from 'flavours/glitch/components/logo';
import { Permalink } from 'flavours/glitch/components/permalink';
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
import { useAppHistory } from 'flavours/glitch/components/router';
import { VisibilityIcon } from 'flavours/glitch/components/visibility_icon';
import PollContainer from 'flavours/glitch/containers/poll_container';
import { useAppSelector } from 'flavours/glitch/store';
@ -75,7 +74,6 @@ export const DetailedStatus: React.FC<{
const properStatus = status?.get('reblog') ?? status;
const [height, setHeight] = useState(0);
const nodeRef = useRef<HTMLDivElement>();
const history = useAppHistory();
const rewriteMentions = useAppSelector(
(state) => state.local_settings.get('rewrite_mentions', false) as boolean,
@ -142,18 +140,6 @@ export const DetailedStatus: React.FC<{
if (onTranslate) onTranslate(status);
}, [onTranslate, status]);
const parseClick = useCallback(
(e: React.MouseEvent, destination: string) => {
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) {
e.preventDefault();
history.push(destination);
}
e.stopPropagation();
},
[history],
);
if (!properStatus) {
return null;
}
@ -405,8 +391,6 @@ export const DetailedStatus: React.FC<{
onUpdate={handleChildUpdate}
tagLinks={tagMisleadingLinks}
rewriteMentions={rewriteMentions}
parseClick={parseClick}
disabled
{...(statusContentProps as any)}
/>

View File

@ -132,7 +132,6 @@ const keyMap = {
goToRequests: 'g r',
toggleHidden: 'x',
bookmark: 'd',
toggleCollapse: 'shift+x',
toggleSensitive: 'h',
openMedia: 'e',
};

View File

@ -47,7 +47,6 @@
"home.settings": "Column settings",
"keyboard_shortcuts.bookmark": "to bookmark",
"keyboard_shortcuts.secondary_toot": "to send toot using secondary privacy setting",
"keyboard_shortcuts.toggle_collapse": "to collapse/uncollapse toots",
"moved_to_warning": "This account is marked as moved to {moved_to_link}, and may thus not accept new follows.",
"navigation_bar.app_settings": "App settings",
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
@ -61,16 +60,7 @@
"notifications.marked_clear": "Clear selected notifications",
"notifications.marked_clear_confirmation": "Are you sure you want to permanently clear all selected notifications?",
"settings.always_show_spoilers_field": "Always enable the Content Warning field",
"settings.auto_collapse": "Automatic collapsing",
"settings.auto_collapse_all": "Everything",
"settings.auto_collapse_height": "Height (in pixels) for a toot to be considered lengthy",
"settings.auto_collapse_lengthy": "Lengthy toots",
"settings.auto_collapse_media": "Toots with media",
"settings.auto_collapse_notifications": "Notifications",
"settings.auto_collapse_reblogs": "Boosts",
"settings.auto_collapse_replies": "Replies",
"settings.close": "Close",
"settings.collapsed_statuses": "Collapsed toots",
"settings.compose_box_opts": "Compose box",
"settings.confirm_before_clearing_draft": "Show confirmation dialog before overwriting the message being composed",
"settings.confirm_boost_missing_media_description": "Show confirmation dialog before boosting toots lacking media descriptions",
@ -84,8 +74,6 @@
"settings.content_warnings_shared_state_hint": "Reproduce upstream Mastodon behavior by having the Content Warning button affect all copies of a post at once. This will prevent automatic collapsing of any copy of a toot with unfolded CW",
"settings.content_warnings_unfold_opts": "Auto-unfolding options",
"settings.deprecated_setting": "This setting is now controlled from Mastodon's {settings_page_link}",
"settings.enable_collapsed": "Enable collapsed toots",
"settings.enable_collapsed_hint": "Collapsed posts have parts of their contents hidden to take up less screen space. This is distinct from the Content Warning feature",
"settings.enable_content_warnings_auto_unfold": "Automatically unfold content-warnings",
"settings.general": "General",
"settings.hicolor_privacy_icons": "High color privacy icons",
@ -115,7 +103,6 @@
"settings.rewrite_mentions_no": "Do not rewrite mentions",
"settings.rewrite_mentions_username": "Rewrite with username",
"settings.shared_settings_link": "user preferences",
"settings.show_action_bar": "Show action buttons in collapsed toots",
"settings.show_content_type_choice": "Show content-type choice when authoring toots",
"settings.show_published_toast": "Display toast when publishing/saving a post",
"settings.show_reply_counter": "Display an estimate of the reply count",
@ -136,7 +123,6 @@
"settings.tag_misleading_links.hint": "Add a visual indication with the link target host to every link not mentioning it explicitly",
"settings.wide_view": "Wide view (Desktop mode only)",
"settings.wide_view_hint": "Stretches columns to better fill the available space.",
"status.collapse": "Collapse",
"status.filtered": "Filtered",
"status.has_audio": "Features attached audio files",
"status.has_pictures": "Features attached pictures",
@ -146,6 +132,5 @@
"status.in_reply_to": "This toot is a reply",
"status.is_poll": "This toot is a poll",
"status.local_only": "Only visible from your instance",
"status.show_filter_reason": "Show anyway",
"status.uncollapse": "Uncollapse"
"status.show_filter_reason": "Show anyway"
}

View File

@ -26,19 +26,6 @@ const initialState = ImmutableMap({
media_outside: false,
shared_state : false,
}),
collapsed : ImmutableMap({
enabled : true,
auto : ImmutableMap({
all : false,
notifications : true,
lengthy : true,
reblogs : false,
replies : false,
media : false,
height : 400,
}),
show_action_bar : true,
}),
media : ImmutableMap({
letterbox : true,
fullwidth : true,

View File

@ -1349,6 +1349,11 @@ body > [data-popper-placement] {
}
}
.status__content.status__content--collapsed .status__content__text {
max-height: 20px * 15; // 15 lines is roughly above 500 characters
overflow: hidden;
}
.status__content__read-more-button,
.status__content__translate-button {
display: flex;
@ -1553,46 +1558,6 @@ body > [data-popper-placement] {
}
}
.status__wrapper.collapsed {
.status {
background-position: center;
background-size: cover;
user-select: none;
min-height: 0;
}
.display-name:hover .display-name__html {
text-decoration: none;
}
.status__content {
height: 20px;
overflow: hidden;
text-overflow: ellipsis;
padding-top: 0;
mask-image: linear-gradient(rgb(0 0 0 / 100%), transparent);
a:hover {
text-decoration: none;
}
}
.notification__message {
margin-bottom: 0;
white-space: nowrap;
}
}
.notification__message-collapse-button {
text-align: end;
flex-grow: 2;
.status__collapse-button .icon {
width: 24px;
height: 24px;
}
}
.status__relative-time {
display: block;
font-size: 14px;
@ -1661,73 +1626,6 @@ body > [data-popper-placement] {
}
}
.status__collapse-button {
// compensate for large padding built into the icon
margin: -4px;
}
.status__collapse-button.active > .icon {
transform: rotate(-180deg);
}
.no-reduce-motion .status__collapse-button {
&.activate {
& > .icon {
animation: spring-flip-in 1s linear;
}
}
&.deactivate {
& > .icon {
animation: spring-flip-out 1s linear;
}
}
}
@keyframes spring-flip-in {
0% {
transform: rotate(0deg);
}
30% {
transform: rotate(-242.4deg);
}
60% {
transform: rotate(-158.35deg);
}
90% {
transform: rotate(-187.5deg);
}
100% {
transform: rotate(-180deg);
}
}
@keyframes spring-flip-out {
0% {
transform: rotate(-180deg);
}
30% {
transform: rotate(62.4deg);
}
60% {
transform: rotate(-21.635deg);
}
90% {
transform: rotate(7.5deg);
}
100% {
transform: rotate(0deg);
}
}
.status-check-box__status {
display: block;
box-sizing: border-box;

View File

@ -121,10 +121,6 @@
text-decoration: none;
}
}
#mastodon-settings--collapsed-auto-height {
width: calc(4ch + 20px);
}
}
.glitch.local-settings__page__item.string,