Merge branch 'next' into locked-profile-header

profile-avatar-switcher
marcin mikołajczak 2021-10-07 08:52:23 +02:00
commit 20c957d907
36 zmienionych plików z 257 dodań i 174 usunięć

Wyświetl plik

@ -6,18 +6,20 @@ export default class Column extends React.PureComponent {
static propTypes = {
className: PropTypes.string,
transparent: PropTypes.bool,
children: PropTypes.node,
label: PropTypes.string,
};
render() {
const { className, label, children } = this.props;
const { className, label, children, transparent, ...rest } = this.props;
return (
<div
role='region'
aria-label={label}
className={classNames('column', className)}
className={classNames('column', className, { 'column--transparent': transparent })}
{...rest}
>
{children}
</div>

Wyświetl plik

@ -0,0 +1,30 @@
/**
* MaterialStatus: like a Status, but with gaps and rounded corners.
*/
import React from 'react';
import PropTypes from 'prop-types';
import StatusContainer from 'soapbox/containers/status_container';
export default class MaterialStatus extends React.Component {
static propTypes = {
hidden: PropTypes.bool,
}
render() {
// Performance: when hidden, don't render the wrapper divs
if (this.props.hidden) {
return <StatusContainer {...this.props} />;
}
return (
<div className='material-status'>
<div className='material-status__status'>
<StatusContainer {...this.props} />
</div>
</div>
);
}
}

Wyświetl plik

@ -2,7 +2,14 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
class ProgressCircle extends React.PureComponent {
export default class ProgressCircle extends React.PureComponent {
static propTypes = {
progress: PropTypes.number.isRequired,
radius: PropTypes.number,
stroke: PropTypes.number,
title: PropTypes.string,
};
static defaultProps = {
radius: 12,
@ -51,12 +58,3 @@ class ProgressCircle extends React.PureComponent {
}
}
ProgressCircle.propTypes = {
progress: PropTypes.number.isRequired,
radius: PropTypes.number,
stroke: PropTypes.number,
title: PropTypes.text,
};
export default ProgressCircle;

Wyświetl plik

@ -3,7 +3,7 @@ import React from 'react';
import { FormattedMessage, defineMessages } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import StatusContainer from '../containers/status_container';
import MaterialStatus from 'soapbox/components/material_status';
import ImmutablePureComponent from 'react-immutable-pure-component';
import LoadGap from './load_gap';
import ScrollableList from './scrollable_list';
@ -117,7 +117,7 @@ export default class StatusList extends ImmutablePureComponent {
onClick={onLoadMore}
/>
) : (
<StatusContainer
<MaterialStatus
key={statusId}
id={statusId}
onMoveUp={this.handleMoveUp}
@ -132,7 +132,7 @@ export default class StatusList extends ImmutablePureComponent {
if (scrollableContent && featuredStatusIds) {
scrollableContent = featuredStatusIds.map(statusId => (
<StatusContainer
<MaterialStatus
key={`f-${statusId}`}
id={statusId}
featured

Wyświetl plik

@ -231,16 +231,4 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
});
export default @injectIntl
@connect(makeMapStateToProps, mapDispatchToProps)
class StatusContainer extends React.Component {
render() {
return (
<div className='status-container'>
<Status {...this.props} />
</div>
);
}
}
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));

Wyświetl plik

@ -177,7 +177,7 @@ class AccountTimeline extends ImmutablePureComponent {
}
return (
<Column showBackBtn={false}>
<Column transparent>
<div className='account__section-headline'>
<NavLink exact to={`/@${accountUsername}`}>
<FormattedMessage id='account.posts' defaultMessage='Posts' />

Wyświetl plik

@ -55,7 +55,7 @@ class Bookmarks extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.bookmarks' defaultMessage="You don't have any bookmarks yet. When you add one, it will show up here." />;
return (
<Column icon='bookmark' heading={intl.formatMessage(messages.heading)} backBtnSlim>
<Column heading={intl.formatMessage(messages.heading)} transparent>
<StatusList
trackScroll={!pinned}
statusIds={statusIds}

Wyświetl plik

@ -73,7 +73,7 @@ class CommunityTimeline extends React.PureComponent {
const { intl, onlyMedia, timelineId } = this.props;
return (
<Column label={intl.formatMessage(messages.title)}>
<Column label={intl.formatMessage(messages.title)} transparent>
<SubNavigation message={intl.formatMessage(messages.title)} />
<StatusListContainer
scrollKey={`${timelineId}_timeline`}

Wyświetl plik

@ -48,7 +48,7 @@ class DirectTimeline extends React.PureComponent {
const { intl, hasUnread } = this.props;
return (
<Column label={intl.formatMessage(messages.title)}>
<Column label={intl.formatMessage(messages.title)} transparent>
<ColumnHeader
icon='envelope'
active={hasUnread}

Wyświetl plik

@ -98,7 +98,7 @@ class HomeTimeline extends React.PureComponent {
const { done } = this.state;
return (
<Column label={intl.formatMessage(messages.title)}>
<Column label={intl.formatMessage(messages.title)} transparent>
{(features.suggestions && isEmpty && !isLoading && !done) ? (
<BundleContainer fetchComponent={FollowRecommendationsContainer}>
{Component => <Component onDone={this.handleDone} />}

Wyświetl plik

@ -97,7 +97,7 @@ class CommunityTimeline extends React.PureComponent {
const { intl, onlyMedia, timelineId, siteTitle, showExplanationBox, explanationBoxExpanded } = this.props;
return (
<Column label={intl.formatMessage(messages.title)}>
<Column label={intl.formatMessage(messages.title)} transparent>
<SubNavigation message={intl.formatMessage(messages.title)} />
<PinnedHostsPicker />
{showExplanationBox && <div className='explanation-box'>

Wyświetl plik

@ -85,7 +85,7 @@ class RemoteTimeline extends React.PureComponent {
const { intl, hasUnread, onlyMedia, timelineId, instance, pinned } = this.props;
return (
<Column label={intl.formatMessage(messages.title)}>
<Column label={intl.formatMessage(messages.title)} transparent>
<HomeColumnHeader activeItem='fediverse' active={hasUnread} />
<PinnedHostsPicker host={instance} />
{!pinned && <div className='timeline-filter-message'>

Wyświetl plik

@ -0,0 +1,54 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import StatusContainer from 'soapbox/containers/status_container';
import { OrderedSet as ImmutableOrderedSet } from 'immutable';
import classNames from 'classnames';
const mapStateToProps = (state, { id }) => {
return {
replyToId: state.getIn(['statuses', id, 'in_reply_to_id']),
replyCount: state.getIn(['contexts', 'replies', id], ImmutableOrderedSet()).size,
};
};
export default @connect(mapStateToProps)
class ThreadStatus extends React.Component {
static propTypes = {
focusedStatusId: PropTypes.string,
replyToId: PropTypes.string,
replyCount: PropTypes.number,
}
renderConnector() {
const { focusedStatusId, replyToId, replyCount } = this.props;
const isConnectedTop = replyToId && replyToId !== focusedStatusId;
const isConnectedBottom = replyCount > 0;
const isConnected = isConnectedTop || isConnectedBottom;
if (!isConnected) {
return null;
}
return (
<div
className={classNames('thread__connector', {
'thread__connector--top': isConnectedTop,
'thread__connector--bottom': isConnectedBottom,
})}
/>
);
}
render() {
return (
<div className='thread__status'>
{this.renderConnector()}
<StatusContainer {...this.props} />
</div>
);
}
}

Wyświetl plik

@ -37,7 +37,6 @@ import { initMuteModal } from '../../actions/mutes';
import { initReport } from '../../actions/reports';
import { makeGetStatus } from '../../selectors';
// import ColumnHeader from '../../components/column_header';
import StatusContainer from '../../containers/status_container';
import { openModal } from '../../actions/modal';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
@ -49,6 +48,7 @@ import { textForScreenReader, defaultMediaVisibility } from '../../components/st
import { getSettings } from 'soapbox/actions/settings';
import { getSoapboxConfig } from 'soapbox/actions/soapbox';
import { deactivateUserModal, deleteUserModal, deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation';
import ThreadStatus from './components/thread_status';
import SubNavigation from 'soapbox/components/sub_navigation';
const messages = defineMessages({
@ -471,10 +471,13 @@ class Status extends ImmutablePureComponent {
}
renderStatus(id) {
const { status } = this.props;
return (
<StatusContainer
<ThreadStatus
key={id}
id={id}
focusedStatusId={status && status.get('id')}
onMoveUp={this.handleMoveUp}
onMoveDown={this.handleMoveDown}
contextType='thread'
@ -595,53 +598,55 @@ class Status extends ImmutablePureComponent {
/>
*/}
<div ref={this.setRef} className='detailed-status-container'>
<div ref={this.setRef} className='thread'>
{ancestors && (
<div className='detailed-status__ancestors'>{ancestors}</div>
<div className='thread__ancestors'>{ancestors}</div>
)}
<HotKeys handlers={handlers}>
<div ref={this.setStatusRef} className={classNames('focusable', 'detailed-status__wrapper')} tabIndex='0' aria-label={textForScreenReader(intl, status, false)}>
<DetailedStatus
status={status}
onOpenVideo={this.handleOpenVideo}
onOpenMedia={this.handleOpenMedia}
onToggleHidden={this.handleToggleHidden}
domain={domain}
showMedia={this.state.showMedia}
onToggleMediaVisibility={this.handleToggleMediaVisibility}
/>
<div className='thread__status thread__status--focused'>
<HotKeys handlers={handlers}>
<div ref={this.setStatusRef} className={classNames('focusable', 'detailed-status__wrapper')} tabIndex='0' aria-label={textForScreenReader(intl, status, false)}>
<DetailedStatus
status={status}
onOpenVideo={this.handleOpenVideo}
onOpenMedia={this.handleOpenMedia}
onToggleHidden={this.handleToggleHidden}
domain={domain}
showMedia={this.state.showMedia}
onToggleMediaVisibility={this.handleToggleMediaVisibility}
/>
<ActionBar
status={status}
onReply={this.handleReplyClick}
onFavourite={this.handleFavouriteClick}
onEmojiReact={this.handleEmojiReactClick}
onReblog={this.handleReblogClick}
onDelete={this.handleDeleteClick}
onDirect={this.handleDirectClick}
onMention={this.handleMentionClick}
onMute={this.handleMuteClick}
onMuteConversation={this.handleConversationMuteClick}
onBlock={this.handleBlockClick}
onReport={this.handleReport}
onPin={this.handlePin}
onBookmark={this.handleBookmark}
onEmbed={this.handleEmbed}
onDeactivateUser={this.handleDeactivateUser}
onDeleteUser={this.handleDeleteUser}
onToggleStatusSensitivity={this.handleToggleStatusSensitivity}
onDeleteStatus={this.handleDeleteStatus}
allowedEmoji={this.props.allowedEmoji}
emojiSelectorFocused={this.state.emojiSelectorFocused}
handleEmojiSelectorExpand={this.handleEmojiSelectorExpand}
handleEmojiSelectorUnfocus={this.handleEmojiSelectorUnfocus}
/>
</div>
</HotKeys>
<ActionBar
status={status}
onReply={this.handleReplyClick}
onFavourite={this.handleFavouriteClick}
onEmojiReact={this.handleEmojiReactClick}
onReblog={this.handleReblogClick}
onDelete={this.handleDeleteClick}
onDirect={this.handleDirectClick}
onMention={this.handleMentionClick}
onMute={this.handleMuteClick}
onMuteConversation={this.handleConversationMuteClick}
onBlock={this.handleBlockClick}
onReport={this.handleReport}
onPin={this.handlePin}
onBookmark={this.handleBookmark}
onEmbed={this.handleEmbed}
onDeactivateUser={this.handleDeactivateUser}
onDeleteUser={this.handleDeleteUser}
onToggleStatusSensitivity={this.handleToggleStatusSensitivity}
onDeleteStatus={this.handleDeleteStatus}
allowedEmoji={this.props.allowedEmoji}
emojiSelectorFocused={this.state.emojiSelectorFocused}
handleEmojiSelectorExpand={this.handleEmojiSelectorExpand}
handleEmojiSelectorUnfocus={this.handleEmojiSelectorUnfocus}
/>
</div>
</HotKeys>
</div>
{descendants && (
<div className='detailed-status__descendants'>{descendants}</div>
<div className='thread__descendants'>{descendants}</div>
)}
</div>
</Column>

Wyświetl plik

@ -104,7 +104,7 @@ class ActionButton extends ImmutablePureComponent {
// Follow & Unfollow
return (<Button
disabled={account.getIn(['relationship', 'blocked_by'])}
className={classNames('follow-button', {
className={classNames('button--follow', {
'button--destructive': account.getIn(['relationship', 'following']),
})}
onClick={this.handleFollow}

Wyświetl plik

@ -1,11 +1,12 @@
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
export default class Column extends React.PureComponent {
export default class BetterColumn extends React.PureComponent {
static propTypes = {
heading: PropTypes.string,
@ -16,11 +17,11 @@ export default class Column extends React.PureComponent {
};
render() {
const { heading, icon, children, active, menu } = this.props;
const { heading, icon, children, active, menu, ...rest } = this.props;
const columnHeaderId = heading && heading.replace(/ /g, '-');
return (
<div role='region' aria-labelledby={columnHeaderId} className='column column--better'>
<Column aria-labelledby={columnHeaderId} className='column--better' {...rest}>
<div className='column__top'>
{heading && <ColumnHeader icon={icon} active={active} type={heading} columnHeaderId={columnHeaderId} />}
{menu && (
@ -31,7 +32,7 @@ export default class Column extends React.PureComponent {
<ColumnBackButton />
</div>
{children}
</div>
</Column>
);
}

Wyświetl plik

@ -1,8 +1,9 @@
import React from 'react';
import ColumnHeader from './column_header';
import PropTypes from 'prop-types';
import Column from 'soapbox/components/column';
export default class Column extends React.PureComponent {
export default class UIColumn extends React.PureComponent {
static propTypes = {
heading: PropTypes.string,
@ -17,14 +18,14 @@ export default class Column extends React.PureComponent {
}
render() {
const { heading, icon, children, active, showBackBtn } = this.props;
const { heading, icon, children, active, showBackBtn, ...rest } = this.props;
const columnHeaderId = heading && heading.replace(/ /g, '-');
return (
<div role='region' aria-labelledby={columnHeaderId} className='column'>
<Column aria-labelledby={columnHeaderId} {...rest}>
{heading && <ColumnHeader icon={icon} active={active} type={heading} columnHeaderId={columnHeaderId} showBackBtn={showBackBtn} />}
{children}
</div>
</Column>
);
}

Wyświetl plik

@ -30,7 +30,7 @@ class ColumnsArea extends ImmutablePureComponent {
</div>
<div className='columns-area__panels__main'>
<div className='columns-area columns-area--mobile'>
<div className='columns-area'>
{children}
</div>
</div>

Wyświetl plik

@ -27,7 +27,7 @@ class AdminPage extends ImmutablePureComponent {
</div>
<div className='columns-area__panels__main'>
<div className='columns-area columns-area--mobile'>
<div className='columns-area'>
{children}
</div>
</div>

Wyświetl plik

@ -45,7 +45,7 @@ class DefaultPage extends ImmutablePureComponent {
</div>
<div className='columns-area__panels__main'>
<div className='columns-area columns-area--mobile'>
<div className='columns-area'>
{children}
</div>
</div>

Wyświetl plik

@ -16,7 +16,7 @@ export default class DefaultPage extends ImmutablePureComponent {
</div>
<div className='columns-area__panels__main'>
<div className='columns-area columns-area--mobile'>
<div className='columns-area'>
{children}
</div>
</div>

Wyświetl plik

@ -54,7 +54,7 @@ class GroupPage extends ImmutablePureComponent {
</div>
<div className='columns-area__panels__main'>
<div className='columns-area columns-area--mobile'>
<div className='columns-area'>
{children}
</div>
</div>

Wyświetl plik

@ -39,7 +39,7 @@ class GroupsPage extends ImmutablePureComponent {
</div>
<div className='columns-area__panels__main'>
<div className='columns-area columns-area--mobile'>
<div className='columns-area'>
{children}
</div>
</div>

Wyświetl plik

@ -67,8 +67,8 @@ class HomePage extends ImmutablePureComponent {
</div>
</div>
<div className='columns-area__panels__main columns-area__panels__main--transparent'>
<div className='columns-area columns-area--mobile columns-area--transparent'>
<div className='columns-area__panels__main'>
<div className='columns-area'>
{me && <div className='timeline-compose-block' ref={this.composeBlock}>
<Link className='timeline-compose-block__avatar' to={`/@${acct}`}>
<Avatar account={account} size={46} />

Wyświetl plik

@ -107,8 +107,8 @@ class ProfilePage extends ImmutablePureComponent {
</div>
</div>
<div className='columns-area__panels__main columns-area__panels__main--transparent'>
<div className='columns-area columns-area--mobile columns-area--transparent'>
<div className='columns-area__panels__main'>
<div className='columns-area'>
{children}
</div>
</div>

Wyświetl plik

@ -48,7 +48,7 @@ class RemoteInstancePage extends ImmutablePureComponent {
</div>
<div className='columns-area__panels__main'>
<div className='columns-area columns-area--mobile'>
<div className='columns-area'>
{children}
</div>
</div>

Wyświetl plik

@ -45,8 +45,8 @@ class StatusPage extends ImmutablePureComponent {
</div>
</div>
<div className='columns-area__panels__main columns-area__panels__main--transparent'>
<div className='columns-area columns-area--mobile columns-area--transparent'>
<div className='columns-area__panels__main'>
<div className='columns-area'>
{children}
</div>
</div>

Wyświetl plik

@ -463,10 +463,6 @@ a .account__avatar {
}
.account__section-headline {
background: var(--foreground-color);
width: 100%;
display: flex;
button,
a {
flex: none;

Wyświetl plik

@ -366,7 +366,7 @@
max-width: none;
}
.columns-area--mobile .column {
.columns-area .column {
border-radius: 0;
}

Wyświetl plik

@ -143,7 +143,7 @@ a.button {
}
}
.follow-button {
.button--follow {
display: flex;
align-items: center;
justify-content: center;

Wyświetl plik

@ -93,7 +93,7 @@
}
}
.columns-area--mobile {
.columns-area {
display: block;
flex-direction: column;
width: 100%;
@ -126,6 +126,11 @@
@media (max-width: 580px) {
padding: 0;
.timeline-compose-block {
border-radius: 0;
margin-top: 10px; // Make less claustrophobic
}
}
@media screen and (min-width: 630px) {
@ -340,31 +345,21 @@
cursor: default;
}
.columns-area--mobile .column {
.columns-area .column {
@include standard-panel;
&--transparent {
background: transparent;
border-radius: 0;
box-shadow: none;
}
@media screen and (max-width: 580px) {
border-radius: 0;
}
}
.columns-area--transparent .column {
background: transparent;
border-radius: 0;
box-shadow: none;
.column-back-button {
background: transparent;
}
.account__section-headline {
background: transparent;
border: 0;
}
.empty-column-indicator,
.error-column {
background: transparent;
.material-status__status {
border-radius: 0;
}
}
}
@ -817,26 +812,6 @@
}
}
.columns-area--transparent .follow-recommendations-container {
@include standard-panel;
}
// Let transparent columns extend the full width of the screen
@media screen and (max-width: 450px) {
.columns-area__panels__main--transparent {
padding: 0;
}
.columns-area--transparent {
.status__wrapper,
.detailed-status__wrapper,
.timeline-compose-block,
.follow-recommendations-container {
border-radius: 0;
}
}
}
.column-title {
text-align: center;
padding: 40px;
@ -896,3 +871,21 @@
border-radius: 0;
}
}
// Make MaterialStatus flush against SubNavigation
.sub-navigation ~ .slist .item-list > article:first-child .material-status__status {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
// Display background for loading indicator
.column--transparent {
.slist__append {
@include standard-panel;
}
.sub-navigation ~ .slist .slist__append {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}

Wyświetl plik

@ -111,8 +111,6 @@
}
.detailed-status__wrapper {
@include standard-panel;
margin-bottom: 10px;
position: relative;
overflow: hidden;
}
@ -160,9 +158,31 @@
margin-right: 5px;
}
/* Connect the first status to the SubNavigation */
.detailed-status__ancestors .status-container:first-child .status__wrapper,
.detailed-status-container > div:first-child .detailed-status__wrapper {
border-top-left-radius: 0;
border-top-right-radius: 0;
.thread {
&__status {
position: relative;
}
&__connector {
background: hsla(var(--primary-text-color_hsl), 0.2);
position: absolute;
width: 2px;
left: 33px;
display: none;
&--bottom {
height: calc(100% - 8px - 48px - 14px);
top: calc(8px + 48px + 13px);
display: block;
}
@media screen and (min-width: 630px) {
left: 38px;
&--bottom {
height: calc(100% - 15px - 48px);
top: calc(15px + 48px + 9px);
}
}
}
}

Wyświetl plik

@ -164,7 +164,7 @@
.media-gallery__file-extension__label {
display: block;
position: absolute;
color: var(--primary-text-color);
color: #fff;
background: rgba($base-overlay-background, 0.5);
bottom: 6px;
left: 6px;

Wyświetl plik

@ -126,15 +126,6 @@
vertical-align: middle;
}
.status-container {
padding-bottom: 10px;
}
.status__wrapper {
@include standard-panel;
padding: 15px 0 10px;
}
.status__wrapper--filtered {
color: var(--primary-text-color);
border: 0;
@ -179,15 +170,6 @@
margin-top: 8px;
}
&.status-direct:not(.read) {
background: var(--brand-color--med);
border-bottom-color: var(--brand-color--med);
.status__content a {
color: var(--brand-color--hicontrast);
}
}
&.light {
.status__relative-time {
color: var(--primary-text-color--faint);
@ -719,3 +701,12 @@ a.status-card.compact:hover {
}
}
}
.material-status {
padding-bottom: 10px;
&__status {
@include standard-panel;
padding: 15px 0 10px;
}
}

Wyświetl plik

@ -194,6 +194,11 @@
align-items: center;
justify-content: center;
padding: 20px;
border-radius: 10px;
@media screen and (max-width: 580px) {
border-radius: 0;
}
& > div {
width: 100%;

Wyświetl plik

@ -447,7 +447,6 @@
&__append {
flex: 1 1 auto;
position: relative;
min-height: 120px;
padding: 30px 15px;
}
}