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 { displayMedia } from '../initial_state';
|
||||||
|
|
||||||
import AttachmentList from './attachment_list';
|
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 { getHashtagBarForStatus } from './hashtag_bar';
|
||||||
import StatusActionBar from './status_action_bar';
|
import StatusActionBar from './status_action_bar';
|
||||||
import StatusContent from './status_content';
|
import StatusContent from './status_content';
|
||||||
import StatusHeader from './status_header';
|
|
||||||
import StatusIcons from './status_icons';
|
import StatusIcons from './status_icons';
|
||||||
import StatusPrepend from './status_prepend';
|
import StatusPrepend from './status_prepend';
|
||||||
|
|
||||||
|
@ -99,6 +100,7 @@ class Status extends ImmutablePureComponent {
|
||||||
onEmbed: PropTypes.func,
|
onEmbed: PropTypes.func,
|
||||||
onHeightChange: PropTypes.func,
|
onHeightChange: PropTypes.func,
|
||||||
onToggleHidden: PropTypes.func,
|
onToggleHidden: PropTypes.func,
|
||||||
|
onToggleCollapsed: PropTypes.func,
|
||||||
onTranslate: PropTypes.func,
|
onTranslate: PropTypes.func,
|
||||||
onInteractionModal: PropTypes.func,
|
onInteractionModal: PropTypes.func,
|
||||||
muted: PropTypes.bool,
|
muted: PropTypes.bool,
|
||||||
|
@ -127,8 +129,6 @@ class Status extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
isCollapsed: false,
|
|
||||||
autoCollapsed: false,
|
|
||||||
isExpanded: undefined,
|
isExpanded: undefined,
|
||||||
showMedia: defaultMediaVisibility(this.props.status, this.props.settings) && !(this.context?.hideMediaByDefault),
|
showMedia: defaultMediaVisibility(this.props.status, this.props.settings) && !(this.context?.hideMediaByDefault),
|
||||||
revealBehindCW: undefined,
|
revealBehindCW: undefined,
|
||||||
|
@ -156,19 +156,10 @@ class Status extends ImmutablePureComponent {
|
||||||
|
|
||||||
updateOnStates = [
|
updateOnStates = [
|
||||||
'isExpanded',
|
'isExpanded',
|
||||||
'isCollapsed',
|
|
||||||
'showMedia',
|
'showMedia',
|
||||||
'forceFilter',
|
'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) {
|
static getDerivedStateFromProps(nextProps, prevState) {
|
||||||
let update = {};
|
let update = {};
|
||||||
let updated = false;
|
let updated = false;
|
||||||
|
@ -183,30 +174,12 @@ class Status extends ImmutablePureComponent {
|
||||||
updated = true;
|
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.
|
// 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.
|
// It's used in the thread view when unfolding/re-folding all CWs at once.
|
||||||
if (nextProps.expanded !== prevState.expandedProp &&
|
if (nextProps.expanded !== prevState.expandedProp &&
|
||||||
nextProps.expanded !== undefined
|
nextProps.expanded !== undefined
|
||||||
) {
|
) {
|
||||||
update.isExpanded = nextProps.expanded;
|
update.isExpanded = nextProps.expanded;
|
||||||
if (nextProps.expanded) update.isCollapsed = false;
|
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,63 +199,13 @@ class Status extends ImmutablePureComponent {
|
||||||
return updated ? update : null;
|
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 () {
|
componentDidMount () {
|
||||||
const { node } = this;
|
const { node } = this;
|
||||||
const {
|
|
||||||
status,
|
|
||||||
settings,
|
|
||||||
collapse,
|
|
||||||
muted,
|
|
||||||
prepend,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
// Prevent a crash when node is undefined. Not completely sure why this
|
// Prevent a crash when node is undefined. Not completely sure why this
|
||||||
// happens, might be because status === null.
|
// happens, might be because status === null.
|
||||||
if (node === undefined) return;
|
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
|
// Hack to fix timeline jumps when a preview card is fetched
|
||||||
this.setState({
|
this.setState({
|
||||||
showCard: !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card') && this.props.settings.get('inline_preview_cards'),
|
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 { muted, hidden, status, settings } = this.props;
|
||||||
|
|
||||||
const doShowCard = !muted && !hidden && status && status.get('card') && settings.get('inline_preview_cards');
|
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 (doShowCard) this.setState({ showCard: true });
|
||||||
if (this.state.autoCollapsed) this.setState({ autoCollapsed: false });
|
|
||||||
return this.props.getScrollPosition();
|
return this.props.getScrollPosition();
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState, snapshot) {
|
componentDidUpdate(prevProps, _prevState, snapshot) {
|
||||||
if (snapshot !== null && this.props.updateScrollBottom && this.node.offsetTop < snapshot.top) {
|
if (snapshot !== null && this.props.updateScrollBottom && this.node.offsetTop < snapshot.top) {
|
||||||
this.props.updateScrollBottom(snapshot.height - 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) => {
|
setExpansion = (value) => {
|
||||||
if (this.props.settings.getIn(['content_warnings', 'shared_state']) && this.props.status.get('hidden') === value) {
|
if (this.props.settings.getIn(['content_warnings', 'shared_state']) && this.props.status.get('hidden') === value) {
|
||||||
this.props.onToggleHidden(this.props.status);
|
this.props.onToggleHidden(this.props.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ isExpanded: value });
|
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 = () => {
|
handleToggleMediaVisibility = () => {
|
||||||
this.setState({ showMedia: !this.state.showMedia });
|
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 = () => {
|
handleExpandedToggle = () => {
|
||||||
if (this.props.settings.getIn(['content_warnings', 'shared_state'])) {
|
if (this.props.settings.getIn(['content_warnings', 'shared_state'])) {
|
||||||
this.props.onToggleHidden(this.props.status);
|
this.props.onToggleHidden(this.props.status);
|
||||||
|
@ -466,12 +359,34 @@ class Status extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleHotkeyOpen = () => {
|
handleHotkeyOpen = () => {
|
||||||
|
if (this.props.onClick) {
|
||||||
|
this.props.onClick();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { history } = this.props;
|
||||||
const status = this.props.status;
|
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 = () => {
|
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 => {
|
handleHotkeyMoveUp = e => {
|
||||||
|
@ -482,13 +397,6 @@ class Status extends ImmutablePureComponent {
|
||||||
this.props.onMoveDown(this.props.containerId || this.props.id, e.target.getAttribute('data-featured'));
|
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 = () => {
|
handleHotkeyToggleSensitive = () => {
|
||||||
this.handleToggleMediaVisibility();
|
this.handleToggleMediaVisibility();
|
||||||
};
|
};
|
||||||
|
@ -506,6 +414,10 @@ class Status extends ImmutablePureComponent {
|
||||||
this.node = c;
|
this.node = c;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleCollapsedToggle = isCollapsed => {
|
||||||
|
this.props.onToggleCollapsed(this.props.status, isCollapsed);
|
||||||
|
};
|
||||||
|
|
||||||
handleTranslate = () => {
|
handleTranslate = () => {
|
||||||
this.props.onTranslate(this.props.status);
|
this.props.onTranslate(this.props.status);
|
||||||
};
|
};
|
||||||
|
@ -525,16 +437,10 @@ class Status extends ImmutablePureComponent {
|
||||||
render () {
|
render () {
|
||||||
const { intl, hidden, featured, unfocusable, unread, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46 } = this.props;
|
const { intl, hidden, featured, unfocusable, unread, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46 } = this.props;
|
||||||
|
|
||||||
const {
|
|
||||||
parseClick,
|
|
||||||
setCollapsed,
|
|
||||||
} = this;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
status,
|
status,
|
||||||
account,
|
account,
|
||||||
settings,
|
settings,
|
||||||
collapsed,
|
|
||||||
muted,
|
muted,
|
||||||
intersectionObserverWrapper,
|
intersectionObserverWrapper,
|
||||||
onOpenVideo,
|
onOpenVideo,
|
||||||
|
@ -543,7 +449,6 @@ class Status extends ImmutablePureComponent {
|
||||||
history,
|
history,
|
||||||
...other
|
...other
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { isCollapsed } = this.state;
|
|
||||||
let attachments = null;
|
let attachments = null;
|
||||||
|
|
||||||
// Depending on user settings, some media are considered as parts of the
|
// Depending on user settings, some media are considered as parts of the
|
||||||
|
@ -555,6 +460,7 @@ class Status extends ImmutablePureComponent {
|
||||||
let extraMediaIcons = [];
|
let extraMediaIcons = [];
|
||||||
let media = contentMedia;
|
let media = contentMedia;
|
||||||
let mediaIcons = contentMediaIcons;
|
let mediaIcons = contentMediaIcons;
|
||||||
|
let statusAvatar;
|
||||||
|
|
||||||
if (settings.getIn(['content_warnings', 'media_outside'])) {
|
if (settings.getIn(['content_warnings', 'media_outside'])) {
|
||||||
media = extraMedia;
|
media = extraMedia;
|
||||||
|
@ -578,7 +484,6 @@ class Status extends ImmutablePureComponent {
|
||||||
moveDown: this.handleHotkeyMoveDown,
|
moveDown: this.handleHotkeyMoveDown,
|
||||||
toggleHidden: this.handleExpandedToggle,
|
toggleHidden: this.handleExpandedToggle,
|
||||||
bookmark: this.handleHotkeyBookmark,
|
bookmark: this.handleHotkeyBookmark,
|
||||||
toggleCollapse: this.handleHotkeyCollapse,
|
|
||||||
toggleSensitive: this.handleHotkeyToggleSensitive,
|
toggleSensitive: this.handleHotkeyToggleSensitive,
|
||||||
openMedia: this.handleHotkeyOpenMedia,
|
openMedia: this.handleHotkeyOpenMedia,
|
||||||
};
|
};
|
||||||
|
@ -650,7 +555,7 @@ class Status extends ImmutablePureComponent {
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||||
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
|
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
|
||||||
hidden={isCollapsed || !isExpanded}
|
hidden={!isExpanded}
|
||||||
onOpenMedia={this.handleOpenMedia}
|
onOpenMedia={this.handleOpenMedia}
|
||||||
cacheWidth={this.props.cacheMediaWidth}
|
cacheWidth={this.props.cacheMediaWidth}
|
||||||
defaultWidth={this.props.cachedMediaWidth}
|
defaultWidth={this.props.cachedMediaWidth}
|
||||||
|
@ -707,7 +612,7 @@ class Status extends ImmutablePureComponent {
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||||
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
|
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
|
||||||
preventPlayback={isCollapsed || !isExpanded}
|
preventPlayback={!isExpanded}
|
||||||
onOpenVideo={this.handleOpenVideo}
|
onOpenVideo={this.handleOpenVideo}
|
||||||
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
|
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
|
||||||
visible={this.state.showMedia}
|
visible={this.state.showMedia}
|
||||||
|
@ -754,15 +659,8 @@ class Status extends ImmutablePureComponent {
|
||||||
<StatusPrepend
|
<StatusPrepend
|
||||||
type={this.props.prepend}
|
type={this.props.prepend}
|
||||||
account={account}
|
account={account}
|
||||||
parseClick={parseClick}
|
|
||||||
notificationId={this.props.notificationId}
|
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') });
|
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);
|
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
||||||
contentMedia.push(hashtagBar);
|
contentMedia.push(hashtagBar);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
||||||
<div
|
<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}
|
{...selectorAttribs}
|
||||||
tabIndex={unfocusable ? null : 0}
|
tabIndex={unfocusable ? null : 0}
|
||||||
data-featured={featured ? 'true' : null}
|
data-featured={featured ? 'true' : null}
|
||||||
|
@ -792,50 +696,47 @@ class Status extends ImmutablePureComponent {
|
||||||
>
|
>
|
||||||
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
{(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 */
|
/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */
|
||||||
<header onClick={this.parseClick} className='status__info'>
|
<header onClick={this.handleClick} className='status__info'>
|
||||||
<StatusHeader
|
<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'>
|
||||||
status={status}
|
<div className='status__avatar'>
|
||||||
friend={account}
|
{statusAvatar}
|
||||||
collapsed={isCollapsed}
|
</div>
|
||||||
parseClick={parseClick}
|
|
||||||
avatarSize={avatarSize}
|
<DisplayName account={status.get('account')} />
|
||||||
/>
|
</a>
|
||||||
<StatusIcons
|
<StatusIcons
|
||||||
status={status}
|
status={status}
|
||||||
mediaIcons={contentMediaIcons.concat(extraMediaIcons)}
|
mediaIcons={contentMediaIcons.concat(extraMediaIcons)}
|
||||||
collapsible={!muted && settings.getIn(['collapsed', 'enabled'])}
|
|
||||||
collapsed={isCollapsed}
|
|
||||||
setCollapsed={setCollapsed}
|
|
||||||
settings={settings.get('status_icons')}
|
settings={settings.get('status_icons')}
|
||||||
/>
|
/>
|
||||||
</header>
|
</header>
|
||||||
)}
|
)}
|
||||||
<StatusContent
|
<StatusContent
|
||||||
status={status}
|
status={status}
|
||||||
|
onClick={this.handleClick}
|
||||||
|
onTranslate={this.handleTranslate}
|
||||||
|
collapsible
|
||||||
media={contentMedia}
|
media={contentMedia}
|
||||||
extraMedia={extraMedia}
|
extraMedia={extraMedia}
|
||||||
mediaIcons={contentMediaIcons}
|
mediaIcons={contentMediaIcons}
|
||||||
expanded={isExpanded}
|
expanded={isExpanded}
|
||||||
onExpandedToggle={this.handleExpandedToggle}
|
onExpandedToggle={this.handleExpandedToggle}
|
||||||
onTranslate={this.handleTranslate}
|
onCollapsedToggle={this.handleCollapsedToggle}
|
||||||
parseClick={parseClick}
|
|
||||||
disabled={!history}
|
|
||||||
tagLinks={settings.get('tag_misleading_links')}
|
tagLinks={settings.get('tag_misleading_links')}
|
||||||
rewriteMentions={settings.get('rewrite_mentions')}
|
rewriteMentions={settings.get('rewrite_mentions')}
|
||||||
{...statusContentProps}
|
{...statusContentProps}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{(!isCollapsed || !(muted || !settings.getIn(['collapsed', 'show_action_bar']))) && (
|
<StatusActionBar
|
||||||
<StatusActionBar
|
status={status}
|
||||||
status={status}
|
account={status.get('account')}
|
||||||
account={status.get('account')}
|
showReplyCount={settings.get('show_reply_count')}
|
||||||
showReplyCount={settings.get('show_reply_count')}
|
onFilter={matchedFilters ? this.handleFilterClick : null}
|
||||||
onFilter={matchedFilters ? this.handleFilterClick : null}
|
{...other}
|
||||||
{...other}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{notification && (
|
{notification && (
|
||||||
<NotificationOverlayContainer
|
<NotificationOverlayContainer
|
||||||
notification={notification}
|
notification={notification}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { withRouter } from 'react-router-dom';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { connect } from 'react-redux';
|
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 ImageIcon from '@/material-icons/400-24px/image.svg?react';
|
||||||
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
|
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
|
||||||
import LinkIcon from '@/material-icons/400-24px/link.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 { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
|
||||||
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
|
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
|
||||||
|
|
||||||
|
|
||||||
import { Permalink } from './permalink';
|
import { Permalink } from './permalink';
|
||||||
|
|
||||||
|
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
|
||||||
|
|
||||||
const textMatchesTarget = (text, origin, host) => {
|
const textMatchesTarget = (text, origin, host) => {
|
||||||
return (text === origin || text === host
|
return (text === origin || text === host
|
||||||
|| text.startsWith(origin + '/') || text.startsWith(host + '/')
|
|| text.startsWith(origin + '/') || text.startsWith(host + '/')
|
||||||
|
@ -134,14 +136,14 @@ class StatusContent extends PureComponent {
|
||||||
status: ImmutablePropTypes.map.isRequired,
|
status: ImmutablePropTypes.map.isRequired,
|
||||||
statusContent: PropTypes.string,
|
statusContent: PropTypes.string,
|
||||||
expanded: PropTypes.bool,
|
expanded: PropTypes.bool,
|
||||||
collapsed: PropTypes.bool,
|
|
||||||
onExpandedToggle: PropTypes.func,
|
onExpandedToggle: PropTypes.func,
|
||||||
onTranslate: PropTypes.func,
|
onTranslate: PropTypes.func,
|
||||||
media: PropTypes.node,
|
media: PropTypes.node,
|
||||||
extraMedia: PropTypes.node,
|
extraMedia: PropTypes.node,
|
||||||
mediaIcons: PropTypes.arrayOf(PropTypes.string),
|
mediaIcons: PropTypes.arrayOf(PropTypes.string),
|
||||||
parseClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
disabled: PropTypes.bool,
|
collapsible: PropTypes.bool,
|
||||||
|
onCollapsedToggle: PropTypes.func,
|
||||||
onUpdate: PropTypes.func,
|
onUpdate: PropTypes.func,
|
||||||
tagLinks: PropTypes.bool,
|
tagLinks: PropTypes.bool,
|
||||||
rewriteMentions: PropTypes.string,
|
rewriteMentions: PropTypes.string,
|
||||||
|
@ -170,16 +172,21 @@ class StatusContent extends PureComponent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { status, onCollapsedToggle } = this.props;
|
||||||
const links = node.querySelectorAll('a');
|
const links = node.querySelectorAll('a');
|
||||||
|
|
||||||
|
let link, mention;
|
||||||
|
|
||||||
for (var i = 0; i < links.length; ++i) {
|
for (var i = 0; i < links.length; ++i) {
|
||||||
let link = links[i];
|
link = links[i];
|
||||||
|
|
||||||
if (link.classList.contains('status-link')) {
|
if (link.classList.contains('status-link')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
link.classList.add('status-link');
|
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) {
|
if (mention) {
|
||||||
link.addEventListener('click', this.onMentionClick.bind(this, mention), false);
|
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] === '#')) {
|
} 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);
|
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
|
||||||
} else {
|
} else {
|
||||||
link.addEventListener('click', this.onLinkClick.bind(this), false);
|
|
||||||
link.setAttribute('title', link.href);
|
link.setAttribute('title', link.href);
|
||||||
link.classList.add('unhandled-link');
|
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 }) => {
|
handleMouseEnter = ({ currentTarget }) => {
|
||||||
|
@ -265,23 +283,19 @@ class StatusContent extends PureComponent {
|
||||||
if (this.props.onUpdate) this.props.onUpdate();
|
if (this.props.onUpdate) this.props.onUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
onLinkClick = (e) => {
|
|
||||||
if (this.props.collapsed) {
|
|
||||||
if (this.props.parseClick) this.props.parseClick(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMentionClick = (mention, e) => {
|
onMentionClick = (mention, e) => {
|
||||||
if (this.props.parseClick) {
|
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||||
this.props.parseClick(e, `/@${mention.get('acct')}`);
|
e.preventDefault();
|
||||||
|
this.props.history.push(`/@${mention.get('acct')}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onHashtagClick = (hashtag, e) => {
|
onHashtagClick = (hashtag, e) => {
|
||||||
hashtag = hashtag.replace(/^#/, '');
|
hashtag = hashtag.replace(/^#/, '');
|
||||||
|
|
||||||
if (this.props.parseClick) {
|
if (this.props.history && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||||
this.props.parseClick(e, `/tags/${hashtag}`);
|
e.preventDefault();
|
||||||
|
this.props.history.push(`/tags/${hashtag}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -290,9 +304,7 @@ class StatusContent extends PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleMouseUp = (e) => {
|
handleMouseUp = (e) => {
|
||||||
const { parseClick, disabled } = this.props;
|
if (!this.startXY) {
|
||||||
|
|
||||||
if (disabled || !this.startXY) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,8 +319,8 @@ class StatusContent extends PureComponent {
|
||||||
element = element.parentNode;
|
element = element.parentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deltaX + deltaY < 5 && e.button === 0 && parseClick) {
|
if (deltaX + deltaY < 5 && e.button === 0 && this.props.onClick) {
|
||||||
parseClick(e);
|
this.props.onClick(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.startXY = null;
|
this.startXY = null;
|
||||||
|
@ -338,14 +350,13 @@ class StatusContent extends PureComponent {
|
||||||
media,
|
media,
|
||||||
extraMedia,
|
extraMedia,
|
||||||
mediaIcons,
|
mediaIcons,
|
||||||
parseClick,
|
|
||||||
disabled,
|
|
||||||
tagLinks,
|
tagLinks,
|
||||||
rewriteMentions,
|
rewriteMentions,
|
||||||
intl,
|
intl,
|
||||||
statusContent,
|
statusContent,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
const renderReadMore = this.props.onClick && status.get('collapsed');
|
||||||
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
|
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
|
||||||
const contentLocale = intl.locale.replace(/[_-].*/, '');
|
const contentLocale = intl.locale.replace(/[_-].*/, '');
|
||||||
const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
|
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 spoilerHtml = status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml');
|
||||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||||
const classNames = classnames('status__content', {
|
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,
|
'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 && (
|
const translateButton = renderTranslate && (
|
||||||
<TranslateButton onClick={this.handleTranslate} translation={status.get('translation')} />
|
<TranslateButton onClick={this.handleTranslate} translation={status.get('translation')} />
|
||||||
);
|
);
|
||||||
|
@ -427,7 +445,7 @@ class StatusContent extends PureComponent {
|
||||||
{extraMedia}
|
{extraMedia}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (parseClick) {
|
} else if (this.props.onClick) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames}
|
className={classNames}
|
||||||
|
@ -446,6 +464,7 @@ class StatusContent extends PureComponent {
|
||||||
lang={language}
|
lang={language}
|
||||||
/>
|
/>
|
||||||
{translateButton}
|
{translateButton}
|
||||||
|
{readMoreButton}
|
||||||
{media}
|
{media}
|
||||||
{extraMedia}
|
{extraMedia}
|
||||||
</div>
|
</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 { Icon } from 'flavours/glitch/components/icon';
|
||||||
import { languages } from 'flavours/glitch/initial_state';
|
import { languages } from 'flavours/glitch/initial_state';
|
||||||
|
|
||||||
import { CollapseButton } from './collapse_button';
|
|
||||||
import { VisibilityIcon } from './visibility_icon';
|
import { VisibilityIcon } from './visibility_icon';
|
||||||
|
|
||||||
const messages = defineMessages({
|
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' },
|
inReplyTo: { id: 'status.in_reply_to', defaultMessage: 'This toot is a reply' },
|
||||||
previewCard: { id: 'status.has_preview_card', defaultMessage: 'Features an attached preview card' },
|
previewCard: { id: 'status.has_preview_card', defaultMessage: 'Features an attached preview card' },
|
||||||
pictures: { id: 'status.has_pictures', defaultMessage: 'Features attached pictures' },
|
pictures: { id: 'status.has_pictures', defaultMessage: 'Features attached pictures' },
|
||||||
|
@ -53,22 +50,10 @@ class StatusIcons extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
status: ImmutablePropTypes.map.isRequired,
|
status: ImmutablePropTypes.map.isRequired,
|
||||||
mediaIcons: PropTypes.arrayOf(PropTypes.string),
|
mediaIcons: PropTypes.arrayOf(PropTypes.string),
|
||||||
collapsible: PropTypes.bool,
|
|
||||||
collapsed: PropTypes.bool,
|
|
||||||
setCollapsed: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
settings: ImmutablePropTypes.map.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) {
|
renderIcon (mediaIcon) {
|
||||||
const { intl } = this.props;
|
const { intl } = this.props;
|
||||||
|
|
||||||
|
@ -114,9 +99,6 @@ class StatusIcons extends PureComponent {
|
||||||
const {
|
const {
|
||||||
status,
|
status,
|
||||||
mediaIcons,
|
mediaIcons,
|
||||||
collapsible,
|
|
||||||
collapsed,
|
|
||||||
setCollapsed,
|
|
||||||
settings,
|
settings,
|
||||||
intl,
|
intl,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -142,7 +124,6 @@ class StatusIcons extends PureComponent {
|
||||||
/>}
|
/>}
|
||||||
{settings.get('media') && !!mediaIcons && mediaIcons.map(icon => this.renderIcon(icon))}
|
{settings.get('media') && !!mediaIcons && mediaIcons.map(icon => this.renderIcon(icon))}
|
||||||
{settings.get('visibility') && <VisibilityIcon visibility={status.get('visibility')} />}
|
{settings.get('visibility') && <VisibilityIcon visibility={status.get('visibility')} />}
|
||||||
{collapsible && <CollapseButton collapsed={collapsed} setCollapsed={setCollapsed} />}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,27 +15,23 @@ import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
import { me } from 'flavours/glitch/initial_state';
|
import { me } from 'flavours/glitch/initial_state';
|
||||||
|
|
||||||
|
import { Permalink } from './permalink';
|
||||||
|
|
||||||
export default class StatusPrepend extends PureComponent {
|
export default class StatusPrepend extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
parseClick: PropTypes.func.isRequired,
|
|
||||||
notificationId: PropTypes.number,
|
notificationId: PropTypes.number,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClick = (e) => {
|
|
||||||
const { account, parseClick } = this.props;
|
|
||||||
parseClick(e, `/@${account.get('acct')}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
Message = () => {
|
Message = () => {
|
||||||
const { type, account } = this.props;
|
const { type, account } = this.props;
|
||||||
let link = (
|
let link = (
|
||||||
<a
|
<Permalink
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
|
to={`/@${account.get('acct')}`}
|
||||||
href={account.get('url')}
|
href={account.get('url')}
|
||||||
className='status__display-name'
|
className='status__display-name'
|
||||||
data-hover-card-account={account.get('id')}
|
data-hover-card-account={account.get('id')}
|
||||||
|
@ -47,7 +43,7 @@ export default class StatusPrepend extends PureComponent {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</bdi>
|
</bdi>
|
||||||
</a>
|
</Permalink>
|
||||||
);
|
);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'featured':
|
case 'featured':
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
unmuteStatus,
|
unmuteStatus,
|
||||||
deleteStatus,
|
deleteStatus,
|
||||||
toggleStatusSpoilers,
|
toggleStatusSpoilers,
|
||||||
|
toggleStatusCollapse,
|
||||||
editStatus,
|
editStatus,
|
||||||
translateStatus,
|
translateStatus,
|
||||||
undoStatusTranslation,
|
undoStatusTranslation,
|
||||||
|
@ -191,6 +192,11 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
|
||||||
dispatch(toggleStatusSpoilers(status.get('id')));
|
dispatch(toggleStatusSpoilers(status.get('id')));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onToggleCollapsed (status, isCollapsed) {
|
||||||
|
dispatch(toggleStatusCollapse(status.get('id'), isCollapsed));
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
deployPictureInPicture (status, type, mediaProps) {
|
deployPictureInPicture (status, type, mediaProps) {
|
||||||
dispatch((_, getState) => {
|
dispatch((_, getState) => {
|
||||||
if (getState().getIn(['local_settings', 'media', 'pop_in_player'])) {
|
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 sharedCWState = useSelector(state => state.getIn(['state', 'content_warnings', 'shared_state']));
|
||||||
const [expanded, setExpanded] = useState(undefined);
|
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 }) => {
|
const handleMouseEnter = useCallback(({ currentTarget }) => {
|
||||||
if (autoPlayGif) {
|
if (autoPlayGif) {
|
||||||
return;
|
return;
|
||||||
|
@ -215,7 +202,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
|
||||||
|
|
||||||
<StatusContent
|
<StatusContent
|
||||||
status={lastStatus}
|
status={lastStatus}
|
||||||
parseClick={parseClick}
|
onClick={handleClick}
|
||||||
expanded={sharedCWState ? lastStatus.get('hidden') : expanded}
|
expanded={sharedCWState ? lastStatus.get('hidden') : expanded}
|
||||||
onExpandedToggle={handleShowMore}
|
onExpandedToggle={handleShowMore}
|
||||||
collapsible
|
collapsible
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
||||||
import Column from 'flavours/glitch/components/column';
|
import Column from 'flavours/glitch/components/column';
|
||||||
|
@ -15,20 +14,15 @@ const messages = defineMessages({
|
||||||
heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' },
|
heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
collapseEnabled: state.getIn(['local_settings', 'collapsed', 'enabled']),
|
|
||||||
});
|
|
||||||
|
|
||||||
class KeyboardShortcuts extends ImmutablePureComponent {
|
class KeyboardShortcuts extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
collapseEnabled: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, collapseEnabled, multiColumn } = this.props;
|
const { intl, multiColumn } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column>
|
<Column>
|
||||||
|
@ -88,12 +82,6 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
||||||
<td><kbd>h</kbd></td>
|
<td><kbd>h</kbd></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.toggle_sensitivity' defaultMessage='to show/hide media' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.toggle_sensitivity' defaultMessage='to show/hide media' /></td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td><kbd>up</kbd>, <kbd>k</kbd></td>
|
<td><kbd>up</kbd>, <kbd>k</kbd></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.up' defaultMessage='to move up in the list' /></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><kbd>esc</kbd></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.unfocus' defaultMessage='to un-focus compose textarea/search' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.unfocus' defaultMessage='to un-focus compose textarea/search' /></td>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td><kbd>?</kbd></td>
|
<td><kbd>?</kbd></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.legend' defaultMessage='to display this legend' /></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 CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||||
import EditIcon from '@/material-icons/400-24px/edit.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 ImageIcon from '@/material-icons/400-24px/image.svg?react';
|
||||||
import ManufacturingIcon from '@/material-icons/400-24px/manufacturing.svg?react';
|
import ManufacturingIcon from '@/material-icons/400-24px/manufacturing.svg?react';
|
||||||
import SettingsIcon from '@/material-icons/400-24px/settings-fill.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' },
|
general: { id: 'settings.general', defaultMessage: 'General' },
|
||||||
compose: { id: 'settings.compose_box_opts', defaultMessage: 'Compose box' },
|
compose: { id: 'settings.compose_box_opts', defaultMessage: 'Compose box' },
|
||||||
content_warnings: { id: 'settings.content_warnings', defaultMessage: 'Content Warnings' },
|
content_warnings: { id: 'settings.content_warnings', defaultMessage: 'Content Warnings' },
|
||||||
collapsed: { id: 'settings.collapsed_statuses', defaultMessage: 'Collapsed toots' },
|
|
||||||
media: { id: 'settings.media', defaultMessage: 'Media' },
|
media: { id: 'settings.media', defaultMessage: 'Media' },
|
||||||
preferences: { id: 'settings.preferences', defaultMessage: 'Preferences' },
|
preferences: { id: 'settings.preferences', defaultMessage: 'Preferences' },
|
||||||
close: { id: 'settings.close', defaultMessage: 'Close' },
|
close: { id: 'settings.close', defaultMessage: 'Close' },
|
||||||
|
@ -64,14 +62,6 @@ class LocalSettingsNavigation extends PureComponent {
|
||||||
iconComponent={WarningIcon}
|
iconComponent={WarningIcon}
|
||||||
title={intl.formatMessage(messages.content_warnings)}
|
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
|
<LocalSettingsNavigationItem
|
||||||
active={index === 4}
|
active={index === 4}
|
||||||
index={4}
|
index={4}
|
||||||
|
|
|
@ -320,103 +320,6 @@ class LocalSettingsPage extends PureComponent {
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</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 }) => (
|
({ intl, onChange, settings }) => (
|
||||||
<div className='glitch local-settings__page media'>
|
<div className='glitch local-settings__page media'>
|
||||||
<h1><FormattedMessage id='settings.media' defaultMessage='Media' /></h1>
|
<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 { IconLogo } from 'flavours/glitch/components/logo';
|
||||||
import { Permalink } from 'flavours/glitch/components/permalink';
|
import { Permalink } from 'flavours/glitch/components/permalink';
|
||||||
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
|
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 { VisibilityIcon } from 'flavours/glitch/components/visibility_icon';
|
||||||
import PollContainer from 'flavours/glitch/containers/poll_container';
|
import PollContainer from 'flavours/glitch/containers/poll_container';
|
||||||
import { useAppSelector } from 'flavours/glitch/store';
|
import { useAppSelector } from 'flavours/glitch/store';
|
||||||
|
@ -75,7 +74,6 @@ export const DetailedStatus: React.FC<{
|
||||||
const properStatus = status?.get('reblog') ?? status;
|
const properStatus = status?.get('reblog') ?? status;
|
||||||
const [height, setHeight] = useState(0);
|
const [height, setHeight] = useState(0);
|
||||||
const nodeRef = useRef<HTMLDivElement>();
|
const nodeRef = useRef<HTMLDivElement>();
|
||||||
const history = useAppHistory();
|
|
||||||
|
|
||||||
const rewriteMentions = useAppSelector(
|
const rewriteMentions = useAppSelector(
|
||||||
(state) => state.local_settings.get('rewrite_mentions', false) as boolean,
|
(state) => state.local_settings.get('rewrite_mentions', false) as boolean,
|
||||||
|
@ -142,18 +140,6 @@ export const DetailedStatus: React.FC<{
|
||||||
if (onTranslate) onTranslate(status);
|
if (onTranslate) onTranslate(status);
|
||||||
}, [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) {
|
if (!properStatus) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -405,8 +391,6 @@ export const DetailedStatus: React.FC<{
|
||||||
onUpdate={handleChildUpdate}
|
onUpdate={handleChildUpdate}
|
||||||
tagLinks={tagMisleadingLinks}
|
tagLinks={tagMisleadingLinks}
|
||||||
rewriteMentions={rewriteMentions}
|
rewriteMentions={rewriteMentions}
|
||||||
parseClick={parseClick}
|
|
||||||
disabled
|
|
||||||
{...(statusContentProps as any)}
|
{...(statusContentProps as any)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,6 @@ const keyMap = {
|
||||||
goToRequests: 'g r',
|
goToRequests: 'g r',
|
||||||
toggleHidden: 'x',
|
toggleHidden: 'x',
|
||||||
bookmark: 'd',
|
bookmark: 'd',
|
||||||
toggleCollapse: 'shift+x',
|
|
||||||
toggleSensitive: 'h',
|
toggleSensitive: 'h',
|
||||||
openMedia: 'e',
|
openMedia: 'e',
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,7 +47,6 @@
|
||||||
"home.settings": "Column settings",
|
"home.settings": "Column settings",
|
||||||
"keyboard_shortcuts.bookmark": "to bookmark",
|
"keyboard_shortcuts.bookmark": "to bookmark",
|
||||||
"keyboard_shortcuts.secondary_toot": "to send toot using secondary privacy setting",
|
"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.",
|
"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.app_settings": "App settings",
|
||||||
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
|
"navigation_bar.keyboard_shortcuts": "Keyboard shortcuts",
|
||||||
|
@ -61,16 +60,7 @@
|
||||||
"notifications.marked_clear": "Clear selected notifications",
|
"notifications.marked_clear": "Clear selected notifications",
|
||||||
"notifications.marked_clear_confirmation": "Are you sure you want to permanently clear all 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.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.close": "Close",
|
||||||
"settings.collapsed_statuses": "Collapsed toots",
|
|
||||||
"settings.compose_box_opts": "Compose box",
|
"settings.compose_box_opts": "Compose box",
|
||||||
"settings.confirm_before_clearing_draft": "Show confirmation dialog before overwriting the message being composed",
|
"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",
|
"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_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.content_warnings_unfold_opts": "Auto-unfolding options",
|
||||||
"settings.deprecated_setting": "This setting is now controlled from Mastodon's {settings_page_link}",
|
"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.enable_content_warnings_auto_unfold": "Automatically unfold content-warnings",
|
||||||
"settings.general": "General",
|
"settings.general": "General",
|
||||||
"settings.hicolor_privacy_icons": "High color privacy icons",
|
"settings.hicolor_privacy_icons": "High color privacy icons",
|
||||||
|
@ -115,7 +103,6 @@
|
||||||
"settings.rewrite_mentions_no": "Do not rewrite mentions",
|
"settings.rewrite_mentions_no": "Do not rewrite mentions",
|
||||||
"settings.rewrite_mentions_username": "Rewrite with username",
|
"settings.rewrite_mentions_username": "Rewrite with username",
|
||||||
"settings.shared_settings_link": "user preferences",
|
"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_content_type_choice": "Show content-type choice when authoring toots",
|
||||||
"settings.show_published_toast": "Display toast when publishing/saving a post",
|
"settings.show_published_toast": "Display toast when publishing/saving a post",
|
||||||
"settings.show_reply_counter": "Display an estimate of the reply count",
|
"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.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": "Wide view (Desktop mode only)",
|
||||||
"settings.wide_view_hint": "Stretches columns to better fill the available space.",
|
"settings.wide_view_hint": "Stretches columns to better fill the available space.",
|
||||||
"status.collapse": "Collapse",
|
|
||||||
"status.filtered": "Filtered",
|
"status.filtered": "Filtered",
|
||||||
"status.has_audio": "Features attached audio files",
|
"status.has_audio": "Features attached audio files",
|
||||||
"status.has_pictures": "Features attached pictures",
|
"status.has_pictures": "Features attached pictures",
|
||||||
|
@ -146,6 +132,5 @@
|
||||||
"status.in_reply_to": "This toot is a reply",
|
"status.in_reply_to": "This toot is a reply",
|
||||||
"status.is_poll": "This toot is a poll",
|
"status.is_poll": "This toot is a poll",
|
||||||
"status.local_only": "Only visible from your instance",
|
"status.local_only": "Only visible from your instance",
|
||||||
"status.show_filter_reason": "Show anyway",
|
"status.show_filter_reason": "Show anyway"
|
||||||
"status.uncollapse": "Uncollapse"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,19 +26,6 @@ const initialState = ImmutableMap({
|
||||||
media_outside: false,
|
media_outside: false,
|
||||||
shared_state : 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({
|
media : ImmutableMap({
|
||||||
letterbox : true,
|
letterbox : true,
|
||||||
fullwidth : 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__read-more-button,
|
||||||
.status__content__translate-button {
|
.status__content__translate-button {
|
||||||
display: flex;
|
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 {
|
.status__relative-time {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 14px;
|
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 {
|
.status-check-box__status {
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
|
@ -121,10 +121,6 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#mastodon-settings--collapsed-auto-height {
|
|
||||||
width: calc(4ch + 20px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.glitch.local-settings__page__item.string,
|
.glitch.local-settings__page__item.string,
|
||||||
|
|
Loading…
Reference in New Issue