diff --git a/app/soapbox/components/__tests__/__snapshots__/timeline_queue_button_header-test.js.snap b/app/soapbox/components/__tests__/__snapshots__/timeline_queue_button_header-test.js.snap index ddc783a55..d63691a71 100644 --- a/app/soapbox/components/__tests__/__snapshots__/timeline_queue_button_header-test.js.snap +++ b/app/soapbox/components/__tests__/__snapshots__/timeline_queue_button_header-test.js.snap @@ -25,7 +25,7 @@ exports[` renders correctly 1`] = ` exports[` renders correctly 2`] = `
renders correctly 2`] = ` } />
- Click to see 1 new post +
+ Click to see 1 new post +
`; exports[` renders correctly 3`] = `
renders correctly 3`] = ` } />
- Click to see 9999999 new posts +
+ Click to see 9999999 new posts +
`; diff --git a/app/soapbox/components/attachment_thumbs.js b/app/soapbox/components/attachment_thumbs.js new file mode 100644 index 000000000..438c2061e --- /dev/null +++ b/app/soapbox/components/attachment_thumbs.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { MediaGallery } from 'soapbox/features/ui/util/async-components'; +import { openModal } from 'soapbox/actions/modal'; +import Bundle from 'soapbox/features/ui/components/bundle'; + +export default @connect() +class AttachmentThumbs extends ImmutablePureComponent { + + static propTypes = { + dispatch: PropTypes.func.isRequired, + media: ImmutablePropTypes.list.isRequired, + onClick: PropTypes.func, + }; + + renderLoading() { + return
; + } + + onOpenMedia = (media, index) => { + this.props.dispatch(openModal('MEDIA', { media, index })); + } + + render() { + const { media, onClick } = this.props; + + return ( +
+ + {Component => ( + + )} + + {onClick && ( +
+ )} +
+ ); + } + +} diff --git a/app/soapbox/components/home_column_header.js b/app/soapbox/components/home_column_header.js deleted file mode 100644 index 9ed7fea2a..000000000 --- a/app/soapbox/components/home_column_header.js +++ /dev/null @@ -1,185 +0,0 @@ -'use strict'; - -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import classNames from 'classnames'; -import { injectIntl, defineMessages } from 'react-intl'; -import { Link } from 'react-router-dom'; -import Icon from 'soapbox/components/icon'; -import { fetchLists } from 'soapbox/actions/lists'; -import { createSelector } from 'reselect'; -import { getFeatures } from 'soapbox/utils/features'; - -const messages = defineMessages({ - show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, - hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' }, - homeTitle: { id: 'home_column_header.home', defaultMessage: 'Home' }, - allTitle: { id: 'home_column_header.all', defaultMessage: 'All' }, - fediverseTitle: { id: 'home_column_header.fediverse', defaultMessage: 'Fediverse' }, - listTitle: { id: 'home_column.lists', defaultMessage: 'Lists' }, -}); - -const getOrderedLists = createSelector([state => state.get('lists')], lists => { - if (!lists) { - return lists; - } - - return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))); -}); - -const mapStateToProps = state => { - const instance = state.get('instance'); - const features = getFeatures(instance); - - return { - lists: getOrderedLists(state), - siteTitle: state.getIn(['instance', 'title']), - federating: features.federating, - }; -}; - -class ColumnHeader extends React.PureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - - static propTypes = { - intl: PropTypes.object.isRequired, - dispatch: PropTypes.func.isRequired, - active: PropTypes.bool, - children: PropTypes.node, - activeItem: PropTypes.string, - activeSubItem: PropTypes.string, - lists: ImmutablePropTypes.list, - siteTitle: PropTypes.string, - federating: PropTypes.bool, - }; - - state = { - collapsed: true, - animating: false, - expandedFor: null, //lists, groups, etc. - }; - - componentDidMount() { - this.props.dispatch(fetchLists()); - } - - handleToggleClick = (e) => { - e.stopPropagation(); - this.setState({ collapsed: !this.state.collapsed, animating: true }); - } - - handleTransitionEnd = () => { - this.setState({ animating: false }); - } - - expandLists = () => { - this.setState({ - expandedFor: 'lists', - }); - } - - render() { - const { active, children, intl: { formatMessage }, activeItem, activeSubItem, lists, siteTitle, federating } = this.props; - const { collapsed, animating, expandedFor } = this.state; - - const wrapperClassName = classNames('column-header__wrapper', { - 'active': active, - }); - - const buttonClassName = classNames('column-header', { - 'active': active, - }); - - const collapsibleClassName = classNames('column-header__collapsible', { - 'collapsed': collapsed, - 'animating': animating, - }); - - const collapsibleButtonClassName = classNames('column-header__button', { - 'active': !collapsed, - }); - - const expansionClassName = classNames('column-header column-header__expansion', { - 'open': expandedFor, - }); - - let extraContent, collapseButton; - - if (children) { - extraContent = ( -
- {children} -
- ); - - collapseButton = ; - } - - const collapsedContent = [ - extraContent, - ]; - - let expandedContent = null; - if ((expandedFor === 'lists' || activeItem === 'lists') && lists) { - expandedContent = lists.map(list => - ( - {list.get('title')} - ), - ); - } - - return ( -
-

- - - {formatMessage(messages.homeTitle)} - - - - - {federating ? siteTitle : formatMessage(messages.allTitle)} - - - {federating && - - {formatMessage(messages.fediverseTitle)} - } - -
- {collapseButton} -
-

- - { - expandedContent && -

- {expandedContent} -

- } - -
-
- {(!collapsed || animating) && collapsedContent} -
-
-
- ); - } - -} - -export default injectIntl(connect(mapStateToProps)(ColumnHeader)); diff --git a/app/soapbox/components/material_status.js b/app/soapbox/components/material_status.js index 25fd24e05..6454497c5 100644 --- a/app/soapbox/components/material_status.js +++ b/app/soapbox/components/material_status.js @@ -19,9 +19,9 @@ export default class MaterialStatus extends React.Component { } return ( -
-
- +
+
+
); diff --git a/app/soapbox/components/media_gallery.js b/app/soapbox/components/media_gallery.js index b89dbffdd..7a6b77eea 100644 --- a/app/soapbox/components/media_gallery.js +++ b/app/soapbox/components/media_gallery.js @@ -279,6 +279,7 @@ class MediaGallery extends React.PureComponent { visible: PropTypes.bool, onToggleVisibility: PropTypes.func, displayMedia: PropTypes.string, + compact: PropTypes.bool, }; static defaultProps = { @@ -560,7 +561,7 @@ class MediaGallery extends React.PureComponent { } render() { - const { media, intl, sensitive } = this.props; + const { media, intl, sensitive, compact } = this.props; const { visible } = this.state; const sizeData = this.getSizeData(media.size); @@ -592,7 +593,7 @@ class MediaGallery extends React.PureComponent { } return ( -
+
{spoilerButton}
diff --git a/app/soapbox/components/status.js b/app/soapbox/components/status.js index 5a4aa572d..5a9de370d 100644 --- a/app/soapbox/components/status.js +++ b/app/soapbox/components/status.js @@ -8,7 +8,7 @@ import RelativeTimestamp from './relative_timestamp'; import DisplayName from './display_name'; import StatusContent from './status_content'; import StatusActionBar from './status_action_bar'; -import AttachmentList from './attachment_list'; +import AttachmentThumbs from './attachment_thumbs'; import Card from '../features/status/components/card'; import { injectIntl, FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -93,6 +93,11 @@ class Status extends ImmutablePureComponent { group: ImmutablePropTypes.map, displayMedia: PropTypes.string, allowedEmoji: ImmutablePropTypes.list, + focusable: PropTypes.bool, + }; + + static defaultProps = { + focusable: true, }; // Avoid checking props that are functions (and whose equality will always @@ -336,7 +341,7 @@ class Status extends ImmutablePureComponent { return ( -
+
@@ -384,10 +389,7 @@ class Status extends ImmutablePureComponent { if (size > 0) { if (this.props.muted) { media = ( - + ); } else if (size === 1 && status.getIn(['media_attachments', 0, 'type']) === 'video') { const video = status.getIn(['media_attachments', 0]); @@ -493,7 +495,7 @@ class Status extends ImmutablePureComponent { return ( -
+
{prepend}
diff --git a/app/soapbox/components/status_action_bar.js b/app/soapbox/components/status_action_bar.js index 6c25d1104..4ae7a8539 100644 --- a/app/soapbox/components/status_action_bar.js +++ b/app/soapbox/components/status_action_bar.js @@ -458,7 +458,13 @@ class StatusActionBar extends ImmutablePureComponent { emoji={meEmojiReact} onClick={this.handleLikeButtonClick} /> - {emojiReactCount !== 0 && {emojiReactCount}} + {emojiReactCount !== 0 && ( + (features.exposableReactions && !features.emojiReacts) ? ( + {emojiReactCount} + ) : ( + {emojiReactCount} + ) + )}
{shareButton} diff --git a/app/soapbox/components/status_list.js b/app/soapbox/components/status_list.js index f96ec779f..c843a4b64 100644 --- a/app/soapbox/components/status_list.js +++ b/app/soapbox/components/status_list.js @@ -82,7 +82,6 @@ export default class StatusList extends ImmutablePureComponent { } handleDequeueTimeline = () => { - window.scrollTo({ top: 0, behavior: 'smooth' }); const { onDequeueTimeline, timelineId } = this.props; if (!onDequeueTimeline || !timelineId) return; onDequeueTimeline(timelineId); diff --git a/app/soapbox/components/sub_navigation.js b/app/soapbox/components/sub_navigation.js index 6edf355a4..627dffff4 100644 --- a/app/soapbox/components/sub_navigation.js +++ b/app/soapbox/components/sub_navigation.js @@ -1,7 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; +import { throttle } from 'lodash'; import Icon from 'soapbox/components/icon'; +import classNames from 'classnames'; export default class SubNavigation extends React.PureComponent { @@ -13,6 +15,10 @@ export default class SubNavigation extends React.PureComponent { router: PropTypes.object.isRequired, } + state = { + scrolled: false, + } + handleBackClick = () => { if (window.history && window.history.length === 1) { this.context.router.history.push('/'); @@ -27,11 +33,44 @@ export default class SubNavigation extends React.PureComponent { } } + componentDidMount() { + this.attachScrollListener(); + } + + componentWillUnmount() { + this.detachScrollListener(); + } + + attachScrollListener() { + window.addEventListener('scroll', this.handleScroll); + } + + detachScrollListener() { + window.removeEventListener('scroll', this.handleScroll); + } + + handleScroll = throttle(() => { + if (this.node) { + const { top } = this.node.getBoundingClientRect(); + + if (top <= 50) { + this.setState({ scrolled: true }); + } else { + this.setState({ scrolled: false }); + } + } + }, 150, { trailing: true }); + + setRef = c => { + this.node = c; + } + render() { const { message } = this.props; + const { scrolled } = this.state; return ( -
+
diff --git a/app/soapbox/features/compose/components/reply_indicator.js b/app/soapbox/features/compose/components/reply_indicator.js index c37cff67a..1dac73a20 100644 --- a/app/soapbox/features/compose/components/reply_indicator.js +++ b/app/soapbox/features/compose/components/reply_indicator.js @@ -7,7 +7,7 @@ import DisplayName from '../../../components/display_name'; import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { isRtl } from '../../../rtl'; -import AttachmentList from 'soapbox/components/attachment_list'; +import AttachmentThumbs from 'soapbox/components/attachment_thumbs'; import { NavLink } from 'react-router-dom'; const messages = defineMessages({ @@ -57,7 +57,7 @@ class ReplyIndicator extends ImmutablePureComponent {
{status.get('media_attachments').size > 0 && ( - diff --git a/app/soapbox/features/compose/components/text_character_counter.js b/app/soapbox/features/compose/components/text_character_counter.js new file mode 100644 index 000000000..99a96d1ca --- /dev/null +++ b/app/soapbox/features/compose/components/text_character_counter.js @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { length } from 'stringz'; + +export default class TextCharacterCounter extends React.PureComponent { + + static propTypes = { + text: PropTypes.string.isRequired, + max: PropTypes.number.isRequired, + }; + + checkRemainingText(diff) { + if (diff < 0) { + return {diff}; + } + + return {diff}; + } + + render() { + const diff = this.props.max - length(this.props.text); + return this.checkRemainingText(diff); + } + +} diff --git a/app/soapbox/features/compose/components/character_counter.js b/app/soapbox/features/compose/components/visual_character_counter.js similarity index 66% rename from app/soapbox/features/compose/components/character_counter.js rename to app/soapbox/features/compose/components/visual_character_counter.js index a3daa3867..f7873e82c 100644 --- a/app/soapbox/features/compose/components/character_counter.js +++ b/app/soapbox/features/compose/components/visual_character_counter.js @@ -13,7 +13,7 @@ const messages = defineMessages({ * @param {string} props.text - text to use to measure * @param {number} props.max - max text allowed */ -class CharacterCounter extends React.PureComponent { +class VisualCharacterCounter extends React.PureComponent { render() { const { intl, text, max } = this.props; @@ -22,21 +22,23 @@ class CharacterCounter extends React.PureComponent { const progress = textLength / max; return ( - +
+ +
); } } -CharacterCounter.propTypes = { +VisualCharacterCounter.propTypes = { intl: PropTypes.object.isRequired, text: PropTypes.string.isRequired, max: PropTypes.number.isRequired, }; -export default injectIntl(CharacterCounter); +export default injectIntl(VisualCharacterCounter); diff --git a/app/soapbox/features/compose/containers/search_container.js b/app/soapbox/features/compose/containers/search_container.js index 3a45b4794..e6484216c 100644 --- a/app/soapbox/features/compose/containers/search_container.js +++ b/app/soapbox/features/compose/containers/search_container.js @@ -6,30 +6,41 @@ import { showSearch, } from '../../../actions/search'; import Search from '../components/search'; +import { debounce } from 'lodash'; const mapStateToProps = state => ({ value: state.getIn(['search', 'value']), submitted: state.getIn(['search', 'submitted']), }); -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch, { autoSubmit }) => { - onChange(value) { - dispatch(changeSearch(value)); - }, - - onClear() { - dispatch(clearSearch()); - }, - - onSubmit() { + const debouncedSubmit = debounce(() => { dispatch(submitSearch()); - }, + }, 900); - onShow() { - dispatch(showSearch()); - }, + return { + onChange(value) { + dispatch(changeSearch(value)); -}); + if (autoSubmit) { + debouncedSubmit(); + } + }, + + onClear() { + dispatch(clearSearch()); + }, + + onSubmit() { + dispatch(submitSearch()); + }, + + onShow() { + dispatch(showSearch()); + }, + + }; +}; export default connect(mapStateToProps, mapDispatchToProps)(Search); diff --git a/app/soapbox/features/edit_profile/components/profile_preview.js b/app/soapbox/features/edit_profile/components/profile_preview.js index dbb6881d5..69be6a365 100644 --- a/app/soapbox/features/edit_profile/components/profile_preview.js +++ b/app/soapbox/features/edit_profile/components/profile_preview.js @@ -2,6 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; +import { Link } from 'react-router-dom'; import { getAcct, isVerified } from 'soapbox/utils/accounts'; import StillImage from 'soapbox/components/still_image'; import VerificationBadge from 'soapbox/components/verification_badge'; @@ -13,7 +14,7 @@ const mapStateToProps = state => ({ const ProfilePreview = ({ account, displayFqn }) => (
- +
); diff --git a/app/soapbox/features/favourited_statuses/index.js b/app/soapbox/features/favourited_statuses/index.js index be87190fa..e10455f21 100644 --- a/app/soapbox/features/favourited_statuses/index.js +++ b/app/soapbox/features/favourited_statuses/index.js @@ -11,6 +11,7 @@ import { debounce } from 'lodash'; import MissingIndicator from 'soapbox/components/missing_indicator'; import { fetchAccount, fetchAccountByUsername } from '../../actions/accounts'; import LoadingIndicator from '../../components/loading_indicator'; +import { findAccountByUsername } from 'soapbox/selectors'; const mapStateToProps = (state, { params }) => { const username = params.username || ''; @@ -28,14 +29,13 @@ const mapStateToProps = (state, { params }) => { }; } - const accounts = state.getIn(['accounts']); const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase()); let accountId = -1; if (accountFetchError) { accountId = null; } else { - const account = accounts.find(acct => username.toLowerCase() === acct.getIn(['acct'], '').toLowerCase()); + const account = findAccountByUsername(state, username); accountId = account ? account.getIn(['id'], null) : -1; } diff --git a/app/soapbox/features/favourites/index.js b/app/soapbox/features/favourites/index.js index 1b91a0c55..7bf1852d9 100644 --- a/app/soapbox/features/favourites/index.js +++ b/app/soapbox/features/favourites/index.js @@ -5,19 +5,25 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import LoadingIndicator from '../../components/loading_indicator'; import { fetchFavourites } from '../../actions/interactions'; -import { FormattedMessage } from 'react-intl'; +import { injectIntl, defineMessages, FormattedMessage } from 'react-intl'; import AccountContainer from '../../containers/account_container'; import Column from '../ui/components/column'; import ScrollableList from '../../components/scrollable_list'; +const messages = defineMessages({ + heading: { id: 'column.favourites', defaultMessage: 'Likes' }, +}); + const mapStateToProps = (state, props) => ({ accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId]), }); export default @connect(mapStateToProps) +@injectIntl class Favourites extends ImmutablePureComponent { static propTypes = { + intl: PropTypes.object.isRequired, params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.orderedSet, @@ -37,7 +43,7 @@ class Favourites extends ImmutablePureComponent { } render() { - const { accountIds } = this.props; + const { intl, accountIds } = this.props; if (!accountIds) { return ( @@ -50,7 +56,7 @@ class Favourites extends ImmutablePureComponent { const emptyMessage = ; return ( - + { const username = params.username || ''; const me = state.get('me'); - const accounts = state.getIn(['accounts']); const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase()); let accountId = -1; if (accountFetchError) { accountId = null; } else { - const account = accounts.find(acct => username.toLowerCase() === acct.getIn(['acct'], '').toLowerCase()); + const account = findAccountByUsername(state, username); accountId = account ? account.getIn(['id'], null) : -1; } diff --git a/app/soapbox/features/following/index.js b/app/soapbox/features/following/index.js index 7e480e1a9..c09a742b8 100644 --- a/app/soapbox/features/following/index.js +++ b/app/soapbox/features/following/index.js @@ -17,18 +17,18 @@ import Column from '../ui/components/column'; import ScrollableList from '../../components/scrollable_list'; import MissingIndicator from 'soapbox/components/missing_indicator'; import { getFollowDifference } from 'soapbox/utils/accounts'; +import { findAccountByUsername } from 'soapbox/selectors'; const mapStateToProps = (state, { params, withReplies = false }) => { const username = params.username || ''; const me = state.get('me'); - const accounts = state.getIn(['accounts']); const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() === username.toLowerCase()); let accountId = -1; if (accountFetchError) { accountId = null; } else { - const account = accounts.find(acct => username.toLowerCase() === acct.getIn(['acct'], '').toLowerCase()); + const account = findAccountByUsername(state, username); accountId = account ? account.getIn(['id'], null) : -1; } diff --git a/app/soapbox/features/hashtag_timeline/index.js b/app/soapbox/features/hashtag_timeline/index.js index ea9b30e84..a489c669e 100644 --- a/app/soapbox/features/hashtag_timeline/index.js +++ b/app/soapbox/features/hashtag_timeline/index.js @@ -8,7 +8,6 @@ import { expandHashtagTimeline, clearTimeline } from '../../actions/timelines'; import { FormattedMessage } from 'react-intl'; import { connectHashtagStream } from '../../actions/streaming'; import { isEqual } from 'lodash'; -import ColumnBackButton from '../../components/column_back_button'; const mapStateToProps = (state, props) => ({ hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0, @@ -26,8 +25,10 @@ class HashtagTimeline extends React.PureComponent { }; title = () => { - const title = [this.props.params.id]; + const title = [`#${this.props.params.id}`]; + // TODO: wtf is all this? + // It exists in Mastodon's codebase, but undocumented if (this.additionalFor('any')) { title.push(' ', ); } @@ -43,6 +44,8 @@ class HashtagTimeline extends React.PureComponent { return title; } + // TODO: wtf is this? + // It exists in Mastodon's codebase, but undocumented additionalFor = (mode) => { const { tags } = this.props.params; @@ -108,9 +111,8 @@ class HashtagTimeline extends React.PureComponent { const { id } = this.props.params; return ( - - - + + - {(features.suggestions && isEmpty && !isLoading && !done) ? ( + + {showSuggestions ? ( {Component => } diff --git a/app/soapbox/features/list_timeline/index.js b/app/soapbox/features/list_timeline/index.js index c14df6b23..e45bcbcd6 100644 --- a/app/soapbox/features/list_timeline/index.js +++ b/app/soapbox/features/list_timeline/index.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import StatusListContainer from '../ui/containers/status_list_container'; -import Column from '../../components/column'; +import Column from 'soapbox/features/ui/components/column'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import { connectListStream } from '../../actions/streaming'; import { expandListTimeline } from '../../actions/timelines'; @@ -11,9 +11,6 @@ import { fetchList, deleteList } from '../../actions/lists'; import { openModal } from '../../actions/modal'; import MissingIndicator from '../../components/missing_indicator'; import LoadingIndicator from '../../components/loading_indicator'; -import Icon from 'soapbox/components/icon'; -import HomeColumnHeader from '../../components/home_column_header'; -import { Link } from 'react-router-dom'; import Button from 'soapbox/components/button'; const messages = defineMessages({ @@ -23,7 +20,7 @@ const messages = defineMessages({ const mapStateToProps = (state, props) => ({ list: state.getIn(['lists', props.params.id]), - hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0, + // hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0, }); export default @connect(mapStateToProps) @@ -37,7 +34,7 @@ class ListTimeline extends React.PureComponent { static propTypes = { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, - hasUnread: PropTypes.bool, + // hasUnread: PropTypes.bool, list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]), intl: PropTypes.object.isRequired, }; @@ -97,7 +94,7 @@ class ListTimeline extends React.PureComponent { } render() { - const { hasUnread, list } = this.props; + const { list } = this.props; const { id } = this.props.params; const title = list ? list.get('title') : id; @@ -126,8 +123,8 @@ class ListTimeline extends React.PureComponent { ); return ( - - + + {/*
-
+
*/} { const getStatus = makeGetStatus(); const status = getStatus(state, { @@ -27,9 +31,11 @@ const mapStateToProps = (state, props) => { }; export default @connect(mapStateToProps) +@injectIntl class Reblogs extends ImmutablePureComponent { static propTypes = { + intl: PropTypes.object.isRequired, params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, accountIds: ImmutablePropTypes.orderedSet, @@ -50,7 +56,7 @@ class Reblogs extends ImmutablePureComponent { } render() { - const { accountIds, status } = this.props; + const { intl, accountIds, status } = this.props; if (!accountIds) { return ( @@ -71,7 +77,7 @@ class Reblogs extends ImmutablePureComponent { const emptyMessage = ; return ( - + - + {!pinned &&
diff --git a/app/soapbox/features/scheduled_statuses/components/scheduled_status.js b/app/soapbox/features/scheduled_statuses/components/scheduled_status.js index 4dcc387ab..ecdc5f598 100644 --- a/app/soapbox/features/scheduled_statuses/components/scheduled_status.js +++ b/app/soapbox/features/scheduled_statuses/components/scheduled_status.js @@ -10,7 +10,7 @@ import { Link, NavLink } from 'react-router-dom'; import { getDomain } from 'soapbox/utils/accounts'; import Avatar from 'soapbox/components/avatar'; import DisplayName from 'soapbox/components/display_name'; -import AttachmentList from 'soapbox/components/attachment_list'; +import AttachmentThumbs from 'soapbox/components/attachment_thumbs'; import PollPreview from './poll_preview'; import ScheduledStatusActionBar from './scheduled_status_action_bar'; @@ -67,7 +67,7 @@ class ScheduledStatus extends ImmutablePureComponent { collapsable /> - diff --git a/app/soapbox/features/search/index.js b/app/soapbox/features/search/index.js index 9e6da120d..2e7348769 100644 --- a/app/soapbox/features/search/index.js +++ b/app/soapbox/features/search/index.js @@ -12,12 +12,8 @@ const messages = defineMessages({ const Search = ({ intl }) => (
- -
-
- -
-
+ +
); diff --git a/app/soapbox/features/status/components/status_interaction_bar.js b/app/soapbox/features/status/components/status_interaction_bar.js index 0fea9f97b..b75769d66 100644 --- a/app/soapbox/features/status/components/status_interaction_bar.js +++ b/app/soapbox/features/status/components/status_interaction_bar.js @@ -14,11 +14,10 @@ import { getSoapboxConfig } from 'soapbox/actions/soapbox'; const mapStateToProps = state => { const instance = state.get('instance'); - const features = getFeatures(instance); return { allowedEmoji: getSoapboxConfig(state).get('allowedEmoji'), - reactionList: features.exposableReactions, + features: getFeatures(instance), }; }; @@ -29,7 +28,7 @@ class StatusInteractionBar extends ImmutablePureComponent { status: ImmutablePropTypes.map, me: SoapboxPropTypes.me, allowedEmoji: ImmutablePropTypes.list, - reactionList: PropTypes.bool, + features: PropTypes.object.isRequired, } getNormalizedReacts = () => { @@ -42,7 +41,7 @@ class StatusInteractionBar extends ImmutablePureComponent { ).reverse(); } - getRepost = () => { + getReposts = () => { const { status } = this.props; if (status.get('reblogs_count')) { return ( @@ -58,8 +57,39 @@ class StatusInteractionBar extends ImmutablePureComponent { return ''; } + getFavourites = () => { + const { features, status } = this.props; + + if (status.get('favourites_count')) { + const favourites = ( + <> + + + + + + ); + + if (features.exposableReactions) { + return ( + + {favourites} + + ); + } else { + return ( +
+ {favourites} +
+ ); + } + } + + return ''; + } + getEmojiReacts = () => { - const { status, reactionList } = this.props; + const { status, features } = this.props; const emojiReacts = this.getNormalizedReacts(); const count = emojiReacts.reduce((acc, cur) => ( @@ -81,7 +111,7 @@ class StatusInteractionBar extends ImmutablePureComponent { ); - if (reactionList) { + if (features.exposableReactions) { return {emojiReact}; } @@ -99,13 +129,12 @@ class StatusInteractionBar extends ImmutablePureComponent { }; render() { - const emojiReacts = this.getEmojiReacts(); - const repost = this.getRepost(); + const { features } = this.props; return (
- {emojiReacts} - {repost} + {features.emojiReacts ? this.getEmojiReacts() : this.getFavourites()} + {this.getReposts()}
); } diff --git a/app/soapbox/features/status/index.js b/app/soapbox/features/status/index.js index 7c8e2f999..6e4ef46ee 100644 --- a/app/soapbox/features/status/index.js +++ b/app/soapbox/features/status/index.js @@ -573,7 +573,7 @@ class Status extends ImmutablePureComponent { }; return ( - + {/* Eye icon to show/hide all CWs in a thread. diff --git a/app/soapbox/features/ui/components/better_column.js b/app/soapbox/features/ui/components/better_column.js index 9bcbf2783..6d7e6e609 100644 --- a/app/soapbox/features/ui/components/better_column.js +++ b/app/soapbox/features/ui/components/better_column.js @@ -2,7 +2,6 @@ import React from 'react'; import ColumnHeader from './column_header'; import PropTypes from 'prop-types'; import Column from 'soapbox/components/column'; -import ColumnBackButton from '../../../components/column_back_button_slim'; import DropdownMenu from 'soapbox/containers/dropdown_menu_container'; // Yes, there are 3 types of columns at this point, but this one is better, I swear @@ -29,7 +28,6 @@ export default class BetterColumn extends React.PureComponent {
)} -
{children} diff --git a/app/soapbox/features/ui/components/boost_modal.js b/app/soapbox/features/ui/components/boost_modal.js index 89f5d5d99..97c9e0631 100644 --- a/app/soapbox/features/ui/components/boost_modal.js +++ b/app/soapbox/features/ui/components/boost_modal.js @@ -9,7 +9,7 @@ import RelativeTimestamp from '../../../components/relative_timestamp'; import DisplayName from '../../../components/display_name'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Icon from 'soapbox/components/icon'; -import AttachmentList from 'soapbox/components/attachment_list'; +import AttachmentThumbs from 'soapbox/components/attachment_thumbs'; const messages = defineMessages({ cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Un-repost' }, @@ -88,7 +88,7 @@ class BoostModal extends ImmutablePureComponent { {status.get('media_attachments').size > 0 && ( - diff --git a/app/soapbox/features/ui/components/drawer_loading.js b/app/soapbox/features/ui/components/drawer_loading.js deleted file mode 100644 index 08b0d2347..000000000 --- a/app/soapbox/features/ui/components/drawer_loading.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; - -const DrawerLoading = () => ( -
-
-
-
-
-); - -export default DrawerLoading; diff --git a/app/soapbox/features/ui/components/media_modal.js b/app/soapbox/features/ui/components/media_modal.js index 5cdfabd3b..b93e23edf 100644 --- a/app/soapbox/features/ui/components/media_modal.js +++ b/app/soapbox/features/ui/components/media_modal.js @@ -138,8 +138,17 @@ class MediaModal extends ImmutablePureComponent { const index = this.getIndex(); let pagination = []; - const leftNav = media.size > 1 && ; - const rightNav = media.size > 1 && ; + const leftNav = media.size > 1 && ( + + ); + + const rightNav = media.size > 1 && ( + + ); if (media.size > 1) { pagination = media.map((item, i) => { diff --git a/app/soapbox/features/ui/index.js b/app/soapbox/features/ui/index.js index 27e7d13fd..05f9f79e4 100644 --- a/app/soapbox/features/ui/index.js +++ b/app/soapbox/features/ui/index.js @@ -56,7 +56,7 @@ import { Following, Reblogs, Reactions, - // Favourites, + Favourites, DirectTimeline, Conversations, HashtagTimeline, @@ -259,7 +259,7 @@ class SwitchingColumnsArea extends React.PureComponent { - + @@ -288,6 +288,7 @@ class SwitchingColumnsArea extends React.PureComponent { + diff --git a/app/soapbox/locales/pl.json b/app/soapbox/locales/pl.json index 981c15ffd..c37f6bb31 100644 --- a/app/soapbox/locales/pl.json +++ b/app/soapbox/locales/pl.json @@ -119,7 +119,7 @@ "auth.invalid_credentials": "Nieprawidłowa nazwa użytkownika lub hasło", "auth.logged_out": "Wylogowano.", "backups.actions.create": "Utwórz kopię zapasową", - "backups.empty_message": "Nie znaleziono kopii zapasaowych. {action}", + "backups.empty_message": "Nie znaleziono kopii zapasowych. {action}", "backups.empty_message.action": "Chcesz utworzyć?", "backups.pending": "Oczekująca", "boost_modal.combo": "Naciśnij {combo}, aby pominąć to następnym razem", @@ -525,12 +525,18 @@ "morefollows.followers_label": "…i {count} więcej {count, plural, one {obserwujący(-a)} few {obserwujących} many {obserwujących} other {obserwujących}} na zdalnych stronach.", "morefollows.following_label": "…i {count} więcej {count, plural, one {obserwowany(-a)} few {obserwowanych} many {obserwowanych} other {obserwowanych}} na zdalnych stronach.", "mute_modal.hide_notifications": "Chcesz ukryć powiadomienia od tego użytkownika?", + "navigation.chats": "Czaty", + "navigation.direct_messages": "Wiadomości", + "navigation.home": "Strona główna", + "navigation.notifications": "Powiadomienia", + "navigation.search": "Szukaj", "navigation_bar.account_aliases": "Aliasy kont", "navigation_bar.admin_settings": "Ustawienia administracyjne", "navigation_bar.blocks": "Zablokowani użytkownicy", "navigation_bar.bookmarks": "Zakładki", "navigation_bar.compose": "Utwórz nowy wpis", "navigation_bar.domain_blocks": "Ukryte domeny", + "navigation_bar.export_data": "Eksportuj dane", "navigation_bar.favourites": "Ulubione", "navigation_bar.filters": "Wyciszone słowa", "navigation_bar.follow_requests": "Prośby o śledzenie", @@ -809,10 +815,13 @@ "status.unpin": "Odepnij z profilu", "status_list.queue_label": "Naciśnij aby zobaczyć {count} {count, plural, one {nowy wpis} few {nowe wpisy} many {nowych wpisów} other {nowe wpisy}}", "statuses.tombstone": "Jeden lub więcej z wpisów jest już niedostępny.", + "sub_navigation.back": "Wróć", "suggestions.dismiss": "Odrzuć sugestię", + "tabs_bar.all": "Wszystkie", "tabs_bar.apps": "Aplikacje", "tabs_bar.chats": "Rozmowy", "tabs_bar.dashboard": "Panel administracyjny", + "tabs_bar.fediverse": "Fediwersum", "tabs_bar.header": "Informacje o koncie", "tabs_bar.home": "Strona główna", "tabs_bar.news": "Nowości", diff --git a/app/soapbox/pages/profile_page.js b/app/soapbox/pages/profile_page.js index 8a7e0fdc9..1d526a1b4 100644 --- a/app/soapbox/pages/profile_page.js +++ b/app/soapbox/pages/profile_page.js @@ -17,10 +17,9 @@ import LinkFooter from '../features/ui/components/link_footer'; import { getAcct } from 'soapbox/utils/accounts'; import { displayFqn } from 'soapbox/utils/state'; import { getFeatures } from 'soapbox/utils/features'; -import { makeGetAccount } from '../selectors'; import { Redirect } from 'react-router-dom'; import classNames from 'classnames'; - +import { findAccountByUsername, makeGetAccount } from 'soapbox/selectors'; const mapStateToProps = (state, { params, withReplies = false }) => { const username = params.username || ''; @@ -34,7 +33,7 @@ const mapStateToProps = (state, { params, withReplies = false }) => { if (accountFetchError) { accountId = null; } else { - account = accounts.find(acct => username.toLowerCase() === acct.getIn(['acct'], '').toLowerCase()); + account = findAccountByUsername(state, username); accountId = account ? account.getIn(['id'], null) : -1; accountUsername = account ? account.getIn(['acct'], '') : ''; } diff --git a/app/soapbox/pages/remote_instance_page.js b/app/soapbox/pages/remote_instance_page.js index d5c807a12..7805c6daa 100644 --- a/app/soapbox/pages/remote_instance_page.js +++ b/app/soapbox/pages/remote_instance_page.js @@ -1,7 +1,9 @@ import React from 'react'; import { connect } from 'react-redux'; import ImmutablePureComponent from 'react-immutable-pure-component'; +import Sticky from 'react-stickynode'; import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; +import PrimaryNavigation from 'soapbox/components/primary_navigation'; import { PromoPanel, FeaturesPanel, @@ -36,14 +38,9 @@ class RemoteInstancePage extends ImmutablePureComponent {
- - {Component => } - - {(disclosed || isAdmin) && ( - - {Component => } - - )} + + +
@@ -55,15 +52,25 @@ class RemoteInstancePage extends ImmutablePureComponent {
- {me && ( - - {Component => } + + {me && ( + + {Component => } + + )} + + {Component => } - )} - - {Component => } - - + + {Component => } + + {(disclosed || isAdmin) && ( + + {Component => } + + )} + +
diff --git a/app/soapbox/selectors/index.js b/app/soapbox/selectors/index.js index 60afa8bd8..b382957ee 100644 --- a/app/soapbox/selectors/index.js +++ b/app/soapbox/selectors/index.js @@ -47,6 +47,36 @@ export const makeGetAccount = () => { }); }; +const findAccountsByUsername = (state, username) => { + const accounts = state.get('accounts'); + + return accounts.filter(account => { + return username.toLowerCase() === account.getIn(['acct'], '').toLowerCase(); + }); +}; + +export const findAccountByUsername = (state, username) => { + const accounts = findAccountsByUsername(state, username); + + if (accounts.size > 1) { + const me = state.get('me'); + const meURL = state.getIn(['accounts', me, 'url']); + + return accounts.find(account => { + try { + // If more than one account has the same username, try matching its host + const { host } = new URL(account.get('url')); + const { host: meHost } = new URL(meURL); + return host === meHost; + } catch { + return false; + } + }); + } else { + return accounts.first(); + } +}; + const toServerSideType = columnType => { switch (columnType) { case 'home': diff --git a/app/styles/accounts.scss b/app/styles/accounts.scss index 50e87e600..5c61002f6 100644 --- a/app/styles/accounts.scss +++ b/app/styles/accounts.scss @@ -3,7 +3,9 @@ display: block; text-decoration: none; color: inherit; - box-shadow: 0 0 15px rgba($base-shadow-color, 0.2); + box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.3); + border-radius: 4px; + overflow: hidden; @media screen and (max-width: $no-gap-breakpoint) { box-shadow: none; @@ -13,7 +15,7 @@ &:active, &:focus { .card__bar { - background: var(--foreground-color); + background-color: var(--brand-color--faint); } } } @@ -22,7 +24,6 @@ height: 130px; position: relative; background: var(--background-color); - border-radius: 4px 4px 0 0; .still-image { display: block; @@ -30,7 +31,6 @@ height: 100%; margin: 0; object-fit: cover; - border-radius: 4px 4px 0 0; } @media screen and (max-width: 600px) { @@ -48,12 +48,8 @@ display: flex; justify-content: flex-start; align-items: center; - background: var(--brand-color--faint); - border-radius: 0 0 4px 4px; - - @media screen and (max-width: $no-gap-breakpoint) { - border-radius: 0; - } + background: var(--background-color); + transition: 0.2s; .avatar { flex: 0 0 auto; diff --git a/app/styles/application.scss b/app/styles/application.scss index 84d8978ee..1e0be7267 100644 --- a/app/styles/application.scss +++ b/app/styles/application.scss @@ -58,7 +58,6 @@ @import 'components/react-toggle'; @import 'components/getting-started'; @import 'components/promo-panel'; -@import 'components/drawer'; @import 'components/still-image'; @import 'components/timeline-queue-header'; @import 'components/badge'; diff --git a/app/styles/chats.scss b/app/styles/chats.scss index b093d7386..d241c63da 100644 --- a/app/styles/chats.scss +++ b/app/styles/chats.scss @@ -296,20 +296,22 @@ position: relative; .icon-button { - color: var(--primary-text-color--faint); + color: var(--primary-text-color); position: absolute; right: 10px; - top: calc(50% - 13px); + top: 50%; + transform: translateY(-50%); width: auto; height: auto; background: transparent !important; border: 0; padding: 0; margin: 0; - } - .chat-box__send .icon-button { - top: calc(50% - 9px); + .svg-icon { + width: 18px; + height: 18px; + } } textarea { @@ -332,7 +334,7 @@ .ui--chatroom { padding-bottom: 0; - .columns-area__panels__main .columns-area { + .columns-area__panels__main .columns-area .column { height: calc(100vh - 100px); box-sizing: border-box; overflow: hidden; diff --git a/app/styles/components/account-header.scss b/app/styles/components/account-header.scss index 9234ca7a0..b8dbaf455 100644 --- a/app/styles/components/account-header.scss +++ b/app/styles/components/account-header.scss @@ -313,9 +313,11 @@ display: flex; font-size: 14px; color: var(--primary-text-color--faint); + @media screen and (max-width: 895px) { justify-content: center; flex-wrap: wrap; + display: none; } a { diff --git a/app/styles/components/columns.scss b/app/styles/components/columns.scss index 81233865d..2f9abd13b 100644 --- a/app/styles/components/columns.scss +++ b/app/styles/components/columns.scss @@ -61,6 +61,7 @@ box-sizing: border-box; display: flex; flex-direction: column; + flex: 1 1 100%; } @media screen and (min-width: 631px) { @@ -68,8 +69,7 @@ padding: 0; } - .column, - .drawer { + .column { flex: 0 0 auto; padding: 10px; padding-left: 5px; @@ -85,8 +85,7 @@ } .columns-area > div { - .column, - .drawer { + .column { padding-left: 5px; padding-right: 5px; } @@ -98,12 +97,10 @@ flex-direction: column; width: 100%; margin: 0 auto; - padding: 15px 0; + padding-top: 15px; - .column, - .drawer { + .column { width: 100%; - height: 100%; padding: 0; } @@ -125,11 +122,9 @@ } @media (max-width: 580px) { - padding: 0; - .timeline-compose-block { border-radius: 0; - margin-top: 10px; // Make less claustrophobic + margin-top: -5px; } } @@ -352,6 +347,10 @@ background: transparent; border-radius: 0; box-shadow: none; + + .sub-navigation { + box-shadow: 0 -6px 6px -6px rgba(0, 0, 0, 0.1); + } } @media screen and (max-width: 580px) { @@ -710,6 +709,7 @@ align-items: center; justify-content: center; min-height: 160px; + border-radius: 0 0 10px 10px; @supports (display: grid) { // hack to fix Chrome <57 contain: strict; @@ -727,21 +727,20 @@ text-decoration: underline; } } + + @media screen and (max-width: 580px) { + border-radius: 0; + } } .error-column { flex-direction: column; - border-radius: 0 0 10px 10px; .svg-icon { width: 70px; height: 70px; margin-bottom: 30px; } - - @media screen and (max-width: 580px) { - border-radius: 0; - } } .column-link--transparent .icon-with-badge__badge { @@ -775,6 +774,11 @@ .column__top { display: flex; align-items: center; + border-bottom: 1px solid hsla(var(--primary-text-color_hsl), 0.2); + + .sub-navigation { + border-bottom: 0; + } } .column-header { @@ -856,6 +860,10 @@ .column-list { position: relative; + + &__empty-message { + padding: 0 20px; + } } .column-loading { @@ -882,6 +890,10 @@ .column--transparent { .slist__append { @include standard-panel; + + @media screen and (max-width: 580px) { + border-radius: 0; + } } .sub-navigation ~ .slist .slist__append { diff --git a/app/styles/components/compose-form.scss b/app/styles/components/compose-form.scss index 4345c77ec..cd9e91ec4 100644 --- a/app/styles/components/compose-form.scss +++ b/app/styles/components/compose-form.scss @@ -27,7 +27,7 @@ } } - .compose-form__warning { + &__warning { color: var(--primary-text-color); margin-bottom: 10px; background: var(--brand-color--faint); @@ -66,7 +66,7 @@ right: 5px; } - .compose-form__autosuggest-wrapper { + &__autosuggest-wrapper { position: relative; } @@ -195,7 +195,7 @@ color: var(--primary-text-color--faint); } - .compose-form__modifiers { + &__modifiers { color: var(--primary-text-color); font-family: inherit; font-size: 14px; @@ -327,7 +327,7 @@ } } // end .compose-form .compose-form__modifiers - .compose-form__buttons-wrapper { + &__buttons-wrapper { padding: 10px; background: var(--background-color); display: flex; @@ -380,13 +380,23 @@ } } - .character-counter__wrapper { - align-self: center; - margin: 0 10px 0 auto; + .character-counter { + display: block; + cursor: default; + font-family: var(--font-sans-serif), sans-serif; + font-size: 14px; + font-weight: 600; + color: var(--primary-text-color--faint); + &.character-counter--over { color: $warning-red; } + } + + .character-counter, + .visual-character-counter { + margin-right: 10px; } } - .compose-form__publish { + &__publish { display: flex; justify-content: flex-end; min-width: 0; @@ -396,6 +406,13 @@ overflow: hidden; } } + + &__counter { + display: flex; + align-items: center; + align-self: center; + margin-left: auto; + } } // end .compose-form // Icon tweaks diff --git a/app/styles/components/detailed-status.scss b/app/styles/components/detailed-status.scss index c83c359e7..9178f4160 100644 --- a/app/styles/components/detailed-status.scss +++ b/app/styles/components/detailed-status.scss @@ -71,7 +71,6 @@ .detailed-status__link { color: var(--primary-text-color--faint); - cursor: pointer; text-decoration: none; font-size: 13px; } @@ -159,8 +158,34 @@ } .thread { + @include standard-panel; + border-top-left-radius: 0; + border-top-right-radius: 0; + + @media screen and (max-width: 580px) { + border-radius: 0; + } + &__status { position: relative; + + // Only display line if posts are below + &:last-child .detailed-status__action-bar { + border-bottom: 0; + } + } + + &__descendants .thread__status:first-child { + margin-top: 10px; + } + + &__status--focused:first-child, + &__ancestors &__status:first-child { + margin-top: 10px; + } + + &__descendants &__status:last-child { + margin-bottom: 10px; } &__connector { diff --git a/app/styles/components/drawer.scss b/app/styles/components/drawer.scss deleted file mode 100644 index fe50fb50a..000000000 --- a/app/styles/components/drawer.scss +++ /dev/null @@ -1,80 +0,0 @@ -.drawer { - width: 300px; - box-sizing: border-box; - display: flex; - flex-direction: column; - overflow-y: hidden; -} - -.drawer__tab { - display: block; - flex: 1 1 auto; - padding: 15px 5px 13px; - color: var(--primary-text-color--faint); - text-decoration: none; - text-align: center; - font-size: 16px; - border-bottom: 2px solid transparent; -} - -.column, -.drawer { - flex: 1 1 100%; -} - -.drawer__pager { - box-sizing: border-box; - padding: 0; - flex-grow: 1; - position: relative; - overflow: hidden; - display: flex; -} - -.drawer__inner { - top: 0; - left: 0; - background: var(--foreground-color); - box-sizing: border-box; - padding: 0; - display: flex; - flex-direction: column; - overflow: hidden; - overflow-y: auto; - width: 100%; - height: 100%; -} - -.pseudo-drawer { - background: var(--background-color); - font-size: 13px; - text-align: left; -} - -.drawer__header { - flex: 0 0 auto; - font-size: 16px; - background: var(--brand-color--med); - margin-bottom: 10px; - display: flex; - flex-direction: row; - - a { - transition: background 100ms ease-in; - - &:hover { - background: var(--background-color); - transition: background 200ms ease-out; - } - } -} - -.drawer__backdrop { - cursor: pointer; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: rgba($base-overlay-background, 0.5); -} diff --git a/app/styles/components/emoji-reacts.scss b/app/styles/components/emoji-reacts.scss index c912d21e3..9af8b33ca 100644 --- a/app/styles/components/emoji-reacts.scss +++ b/app/styles/components/emoji-reacts.scss @@ -21,12 +21,19 @@ } } -.emoji-react--reblogs { +.emoji-react--reblogs, +.emoji-react--favourites { vertical-align: middle; display: inline-flex; + margin-right: 10px; .svg-icon { margin-right: 0.2em; + } +} + +.emoji-react--reblogs { + .svg-icon { color: var(--highlight-text-color); svg { @@ -35,6 +42,12 @@ } } +.emoji-react--favourites { + .svg-icon { + color: $gold-star; + } +} + .emoji-reacts { display: inline-flex; flex-direction: row-reverse; diff --git a/app/styles/components/getting-started.scss b/app/styles/components/getting-started.scss index 0aef52bad..4bb228234 100644 --- a/app/styles/components/getting-started.scss +++ b/app/styles/components/getting-started.scss @@ -17,8 +17,7 @@ &__footer { flex: 0 0 auto; - padding: 10px; - padding-top: 20px; + padding: 20px 10px 40px; ul { margin-bottom: 10px; diff --git a/app/styles/components/list-forms.scss b/app/styles/components/list-forms.scss index 0679ac6e1..0212ebd1f 100644 --- a/app/styles/components/list-forms.scss +++ b/app/styles/components/list-forms.scss @@ -14,16 +14,6 @@ border-radius: 8px 8px 0 0; } - .drawer__inner { - border-radius: 0 0 8px 8px; - - &.backdrop { - width: calc(100% - 60px); - box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4); - border-radius: 0 0 0 8px; - } - } - &__accounts { background: var(--background-color); overflow-y: auto; diff --git a/app/styles/components/media-gallery.scss b/app/styles/components/media-gallery.scss index 31d407e3e..d4209b596 100644 --- a/app/styles/components/media-gallery.scss +++ b/app/styles/components/media-gallery.scss @@ -192,3 +192,33 @@ } } } + +$media-compact-size: 50px; + +.media-gallery--compact { + height: $media-compact-size !important; + background: transparent; + + .spoiler-button { + display: none; + } + + .media-gallery__item { + width: $media-compact-size !important; + height: $media-compact-size !important; + inset: auto !important; + margin-right: 5px; + + &-overflow { + font-size: 20px; + } + + &__icons { + font-size: 30px; + } + } + + .media-gallery__file-extension__label { + display: none; + } +} diff --git a/app/styles/components/modal.scss b/app/styles/components/modal.scss index d19081ca4..ca1bb1698 100644 --- a/app/styles/components/modal.scss +++ b/app/styles/components/modal.scss @@ -128,8 +128,9 @@ @media screen and (max-width: 600px) { padding: 30px 2px; } - .fa { - margin-right: 0; + .svg-icon { + width: 24px; + height: 24px; } } @@ -180,7 +181,7 @@ } .media-modal__button { - background-color: var(--primary-text-color); + background-color: #fff; height: 12px; width: 12px; border-radius: 6px; @@ -337,7 +338,7 @@ overflow: hidden; width: 480px; max-width: 90vw; - border-radius: 4px; + border-radius: 10px; border: 1px solid var(--primary-text-color--faint); color: var(--primary-text-color--faint); background: var(--foreground-color); diff --git a/app/styles/components/remote-timeline.scss b/app/styles/components/remote-timeline.scss index e059d7af7..803c315e8 100644 --- a/app/styles/components/remote-timeline.scss +++ b/app/styles/components/remote-timeline.scss @@ -29,9 +29,11 @@ } .pinned-hosts-picker { - margin-left: 10px; + padding: 10px 0 0 10px; display: inline-flex; flex-wrap: wrap; + background: var(--foreground-color); + border-bottom: 1px solid hsla(var(--primary-text-color_hsl), 0.2); .pinned-host { margin-right: 10px; diff --git a/app/styles/components/search.scss b/app/styles/components/search.scss index d452e122c..be71491a5 100644 --- a/app/styles/components/search.scss +++ b/app/styles/components/search.scss @@ -166,9 +166,7 @@ } .search-page { - .drawer__inner:not(:empty) { - min-height: 48px; - } + height: 100%; .search { padding: 10px 15px; @@ -184,14 +182,6 @@ .search__icon .svg-icon { right: 24px; } - - .drawer__pager { - border-radius: 0 0 10px 10px; - - @media screen and (max-width: 450px) { - border-radius: 0; - } - } } .search-results { diff --git a/app/styles/components/status.scss b/app/styles/components/status.scss index 5c7c08ce0..230c514d8 100644 --- a/app/styles/components/status.scss +++ b/app/styles/components/status.scss @@ -143,6 +143,10 @@ .status__prepend-icon-wrapper { left: -26px; position: absolute; + + svg.feather-repeat { + color: var(--highlight-text-color); + } } .status { @@ -710,3 +714,18 @@ a.status-card.compact:hover { padding: 15px 0 10px; } } + +.attachment-thumbs { + position: relative; + + &__clickable-region { + cursor: pointer; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + right: 0; + bottom: 0; + } +} diff --git a/app/styles/components/timeline-queue-header.scss b/app/styles/components/timeline-queue-header.scss index 405c7c5d8..93cf73d6f 100644 --- a/app/styles/components/timeline-queue-header.scss +++ b/app/styles/components/timeline-queue-header.scss @@ -1,35 +1,35 @@ .timeline-queue-header { display: flex; + align-self: center; align-items: center; justify-content: space-evenly; - max-height: 30px; - position: sticky; + height: 30px; + position: fixed; top: 60px; margin: 0 auto; - margin-bottom: 8px; background-color: var(--brand-color); color: #fff; - border-bottom: 1px solid; - border-top: 1px solid; - border-color: var(--brand-color--faint); border-radius: 100px; - transition: max-height 150ms ease; + transition: 150ms ease; overflow: hidden; - opacity: 1; - left: 0; - right: 0; padding: 0 10px; z-index: 500; + .sub-navigation ~ & { + top: calc(60px + 41px); + } + .svg-icon { margin-right: 5px; } &.hidden { - max-height: 0; - opacity: 0; - margin: 0; - border: 0; + transform: scaleY(0); + pointer-events: none; + + .timeline-queue-header__label { + opacity: 0; + } } &__btn { @@ -46,4 +46,8 @@ height: 46px; } } + + &__label { + transition: 150ms ease; + } } diff --git a/app/styles/components/video-player.scss b/app/styles/components/video-player.scss index 3122849a4..57061a2ff 100644 --- a/app/styles/components/video-player.scss +++ b/app/styles/components/video-player.scss @@ -297,7 +297,7 @@ left: 0; top: 50%; transform: translate(0, -50%); - background: var(--brand-color); + background: var(--accent-color); } &__handle { @@ -310,7 +310,7 @@ left: 0; margin-left: -6px; transform: translate(0, -50%); - background: var(--brand-color); + background: var(--accent-color); box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2); opacity: 0; @@ -364,7 +364,7 @@ height: 4px; border-radius: 4px; top: 14px; - background: var(--brand-color); + background: var(--accent-color); } &__buffer { @@ -380,7 +380,7 @@ height: 12px; top: 10px; margin-left: -6px; - background: var(--brand-color); + background: var(--accent-color); box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2); .no-reduce-motion & { diff --git a/app/styles/navigation.scss b/app/styles/navigation.scss index c9498aeff..a0c0c451a 100644 --- a/app/styles/navigation.scss +++ b/app/styles/navigation.scss @@ -113,6 +113,10 @@ justify-content: center; z-index: 999; + &--scrolled { + border-radius: 0 !important; + } + &__content { width: 100%; height: 100%; diff --git a/app/styles/rtl.scss b/app/styles/rtl.scss index 051d0e82d..483ff5449 100644 --- a/app/styles/rtl.scss +++ b/app/styles/rtl.scss @@ -228,8 +228,7 @@ body.rtl { } @media screen and (min-width: 631px) { - .column, - .drawer { + .column { padding-left: 5px; padding-right: 5px; @@ -240,8 +239,7 @@ body.rtl { } .columns-area > div { - .column, - .drawer { + .column { padding-left: 5px; padding-right: 5px; } diff --git a/app/styles/ui.scss b/app/styles/ui.scss index aefadcaaa..34fc45e15 100644 --- a/app/styles/ui.scss +++ b/app/styles/ui.scss @@ -318,7 +318,6 @@ .react-swipeable-view-container { &, .columns-area, - .drawer, .column { height: 100%; } diff --git a/package.json b/package.json index f6544915c..eccf8594c 100644 --- a/package.json +++ b/package.json @@ -116,6 +116,7 @@ "qrcode.react": "^1.0.0", "react": "^16.13.1", "react-color": "^2.18.1", + "react-content-loader": "^6.0.3", "react-datepicker": "^4.1.1", "react-dom": "^16.13.1", "react-helmet": "^6.0.0", diff --git a/yarn.lock b/yarn.lock index 62d92d092..3d75cd47c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7757,6 +7757,11 @@ react-color@^2.18.1: reactcss "^1.2.0" tinycolor2 "^1.4.1" +react-content-loader@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/react-content-loader/-/react-content-loader-6.0.3.tgz#32e28ca7120e0a2552fc26655d0d4448cc1fc0c5" + integrity sha512-CIRgTHze+ls+jGDIfCitw27YkW2XcaMpsYORTUdBxsMFiKuUYMnlvY76dZE4Lsaa9vFXVw+41ieBEK7SJt0nug== + react-datepicker@^4.1.1: version "4.2.1" resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.2.1.tgz#72caf5055bc7c4eb0279c1f6d7624ded053edc4c"