From d65f6c2f8a64dbfd2d8c76a2de19aef808fb5dbf Mon Sep 17 00:00:00 2001 From: Claire Date: Sun, 22 Dec 2024 20:27:32 +0100 Subject: [PATCH] =?UTF-8?q?Replace=20glitch-soc's=20collapsed=20toots=20wi?= =?UTF-8?q?th=20upstream's=20=E2=80=9CRead=20more=E2=80=9D=20(#2916)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove glitch-soc's post collapse feature * Get rid of the infamous `parseClick` * Remove unused CSS * Use upstream's “Read More” implementation * Update translation strings --- .../glitch/components/collapse_button.jsx | 46 --- .../flavours/glitch/components/status.jsx | 281 ++++++------------ .../glitch/components/status_content.jsx | 71 +++-- .../glitch/components/status_header.jsx | 63 ---- .../glitch/components/status_icons.jsx | 19 -- .../glitch/components/status_prepend.jsx | 12 +- .../glitch/containers/status_container.js | 6 + .../components/conversation.jsx | 15 +- .../features/keyboard_shortcuts/index.jsx | 64 +++- .../local_settings/navigation/index.jsx | 10 - .../features/local_settings/page/index.jsx | 97 ------ .../status/components/detailed_status.tsx | 16 - .../flavours/glitch/features/ui/index.jsx | 1 - .../flavours/glitch/locales/en.json | 17 +- .../glitch/reducers/local_settings.js | 13 - .../flavours/glitch/styles/components.scss | 112 +------ .../glitch/styles/glitch_local_settings.scss | 4 - 17 files changed, 203 insertions(+), 644 deletions(-) delete mode 100644 app/javascript/flavours/glitch/components/collapse_button.jsx delete mode 100644 app/javascript/flavours/glitch/components/status_header.jsx diff --git a/app/javascript/flavours/glitch/components/collapse_button.jsx b/app/javascript/flavours/glitch/components/collapse_button.jsx deleted file mode 100644 index 30008f1ff9..0000000000 --- a/app/javascript/flavours/glitch/components/collapse_button.jsx +++ /dev/null @@ -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 ( - - ); -}; - -CollapseButton.propTypes = { - collapsed: PropTypes.bool, - setCollapsed: PropTypes.func.isRequired, -}; diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index 335cda0dc7..168b5f2071 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -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 { - {muted && settings.getIn(['collapsed', 'enabled']) && ( -
- -
- )} -
+ /> ); } @@ -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 = ; + } else { + statusAvatar = ; + } + const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); contentMedia.push(hashtagBar); return (
{(connectReply || connectUp || connectToRoot) &&
} - {(!muted || !isCollapsed) && ( + {(!muted) && ( /* eslint-disable-next-line jsx-a11y/no-static-element-interactions */ -
- +
+ +
+ {statusAvatar} +
+ + +
)} - {(!isCollapsed || !(muted || !settings.getIn(['collapsed', 'show_action_bar']))) && ( - - )} + + {notification && ( { 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 && ( + + ); + const translateButton = renderTranslate && ( ); @@ -427,7 +445,7 @@ class StatusContent extends PureComponent { {extraMedia}
); - } else if (parseClick) { + } else if (this.props.onClick) { return (
{translateButton} + {readMoreButton} {media} {extraMedia}
diff --git a/app/javascript/flavours/glitch/components/status_header.jsx b/app/javascript/flavours/glitch/components/status_header.jsx deleted file mode 100644 index dffffe4fb8..0000000000 --- a/app/javascript/flavours/glitch/components/status_header.jsx +++ /dev/null @@ -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 = ; - } else { - statusAvatar = ; - } - - return ( - -
- {statusAvatar} -
- - -
- ); - } - -} diff --git a/app/javascript/flavours/glitch/components/status_icons.jsx b/app/javascript/flavours/glitch/components/status_icons.jsx index 8ee84fe269..157abe590a 100644 --- a/app/javascript/flavours/glitch/components/status_icons.jsx +++ b/app/javascript/flavours/glitch/components/status_icons.jsx @@ -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') && } - {collapsible && }
); } diff --git a/app/javascript/flavours/glitch/components/status_prepend.jsx b/app/javascript/flavours/glitch/components/status_prepend.jsx index b83767a990..6c1edc76bf 100644 --- a/app/javascript/flavours/glitch/components/status_prepend.jsx +++ b/app/javascript/flavours/glitch/components/status_prepend.jsx @@ -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 = ( - - + ); switch (type) { case 'featured': diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js index 493a01da23..d19cf6dd23 100644 --- a/app/javascript/flavours/glitch/containers/status_container.js +++ b/app/javascript/flavours/glitch/containers/status_container.js @@ -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'])) { diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx index e7fc914b65..5df3af4b7a 100644 --- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx +++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.jsx @@ -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 }) ({ - 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 ( @@ -88,12 +82,6 @@ class KeyboardShortcuts extends ImmutablePureComponent { h - {collapseEnabled && ( - - shift+x - - - )} up, k @@ -134,6 +122,54 @@ class KeyboardShortcuts extends ImmutablePureComponent { esc + + g+h + + + + g+n + + + + g+l + + + + g+t + + + + g+d + + + + g+s + + + + g+f + + + + g+p + + + + g+u + + + + g+b + + + + g+m + + + + g+r + + ? @@ -151,4 +187,4 @@ class KeyboardShortcuts extends ImmutablePureComponent { } -export default connect(mapStateToProps)(injectIntl(KeyboardShortcuts)); +export default injectIntl(KeyboardShortcuts); diff --git a/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx b/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx index 10d4097eef..8d2589aaf7 100644 --- a/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx +++ b/app/javascript/flavours/glitch/features/local_settings/navigation/index.jsx @@ -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)} /> - ), - ({ onChange, settings }) => ( -
-

- - - - - - - -
-

- - - - - - - - - - - - - - - - - - - - - -
-
- ), ({ intl, onChange, settings }) => (

diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.tsx b/app/javascript/flavours/glitch/features/status/components/detailed_status.tsx index c6f0900c5f..8267686b04 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.tsx +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.tsx @@ -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(); - 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)} /> diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx index 01edfda00d..9e803bcc96 100644 --- a/app/javascript/flavours/glitch/features/ui/index.jsx +++ b/app/javascript/flavours/glitch/features/ui/index.jsx @@ -132,7 +132,6 @@ const keyMap = { goToRequests: 'g r', toggleHidden: 'x', bookmark: 'd', - toggleCollapse: 'shift+x', toggleSensitive: 'h', openMedia: 'e', }; diff --git a/app/javascript/flavours/glitch/locales/en.json b/app/javascript/flavours/glitch/locales/en.json index 7a982d9532..3d2a082f5d 100644 --- a/app/javascript/flavours/glitch/locales/en.json +++ b/app/javascript/flavours/glitch/locales/en.json @@ -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" } diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js index b5df6be05b..130047b65f 100644 --- a/app/javascript/flavours/glitch/reducers/local_settings.js +++ b/app/javascript/flavours/glitch/reducers/local_settings.js @@ -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, diff --git a/app/javascript/flavours/glitch/styles/components.scss b/app/javascript/flavours/glitch/styles/components.scss index c61a7471a8..76af86370a 100644 --- a/app/javascript/flavours/glitch/styles/components.scss +++ b/app/javascript/flavours/glitch/styles/components.scss @@ -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; diff --git a/app/javascript/flavours/glitch/styles/glitch_local_settings.scss b/app/javascript/flavours/glitch/styles/glitch_local_settings.scss index b32d54c681..f416de1f3d 100644 --- a/app/javascript/flavours/glitch/styles/glitch_local_settings.scss +++ b/app/javascript/flavours/glitch/styles/glitch_local_settings.scss @@ -121,10 +121,6 @@ text-decoration: none; } } - - #mastodon-settings--collapsed-auto-height { - width: calc(4ch + 20px); - } } .glitch.local-settings__page__item.string,