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 stringspull/2918/head
parent
28751ff042
commit
d65f6c2f8a
|
@ -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,
|
||||
};
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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'])) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
|
||||
|
|
|
@ -132,7 +132,6 @@ const keyMap = {
|
|||
goToRequests: 'g r',
|
||||
toggleHidden: 'x',
|
||||
bookmark: 'd',
|
||||
toggleCollapse: 'shift+x',
|
||||
toggleSensitive: 'h',
|
||||
openMedia: 'e',
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -121,10 +121,6 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
#mastodon-settings--collapsed-auto-height {
|
||||
width: calc(4ch + 20px);
|
||||
}
|
||||
}
|
||||
|
||||
.glitch.local-settings__page__item.string,
|
||||
|
|
Loading…
Reference in New Issue