Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- `Gemfile.lock`:
  Not a real conflict, just a glitch-soc-only dependency too close to a
  dependency that got updated upstream. Updated as well.
- `app/models/status.rb`:
  Not a real conflict, just a change too close to glitch-soc-changed code
  for optionally showing boosts in public timelines.
  Applied upstream changes.
- `app/views/layouts/application.html.haml`:
  Upstream a new, static CSS file, conflict due to glitch-soc's theming
  system, include the file regardless of the theme.
- `config/initializers/content_security_policy.rb`:
  Upstream dropped 'unsafe-inline' from the 'style-src' directive, but
  both files are very different. Removed 'unsafe-inline' as well.
This commit is contained in:
Thibaut Girka
2020-05-10 15:15:39 +02:00
68 changed files with 2287 additions and 1421 deletions

View File

@ -73,7 +73,7 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
export const connectPublicStream = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`);
export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) => connectTimelineStream(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`);
export const connectHashtagStream = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept);
export const connectDirectStream = () => connectTimelineStream('direct', 'direct');
export const connectListStream = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);

View File

@ -107,7 +107,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
};
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done);
export const expandPublicTimeline = ({ maxId, onlyMedia, onlyRemote } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia }, done);
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });

View File

@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, FormattedMessage } from 'react-intl';
import SettingToggle from '../../notifications/components/setting_toggle';
export default @injectIntl
class ColumnSettings extends React.PureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
};
render () {
const { settings, onChange } = this.props;
return (
<div>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
<SettingToggle settings={settings} settingPath={['other', 'onlyRemote']} onChange={onChange} label={<FormattedMessage id='community.column_settings.remote_only' defaultMessage='Remote only' />} />
</div>
</div>
);
}
}

View File

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import ColumnSettings from '../../community_timeline/components/column_settings';
import ColumnSettings from '../components/column_settings';
import { changeSetting } from '../../../actions/settings';
import { changeColumnParams } from '../../../actions/columns';

View File

@ -19,11 +19,13 @@ const mapStateToProps = (state, { columnId }) => {
const columns = state.getIn(['settings', 'columns']);
const index = columns.findIndex(c => c.get('uuid') === uuid);
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']);
const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]);
return {
hasUnread: !!timelineState && timelineState.get('unread') > 0,
onlyMedia,
onlyRemote,
};
};
@ -47,15 +49,16 @@ class PublicTimeline extends React.PureComponent {
multiColumn: PropTypes.bool,
hasUnread: PropTypes.bool,
onlyMedia: PropTypes.bool,
onlyRemote: PropTypes.bool,
};
handlePin = () => {
const { columnId, dispatch, onlyMedia } = this.props;
const { columnId, dispatch, onlyMedia, onlyRemote } = this.props;
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('PUBLIC', { other: { onlyMedia } }));
dispatch(addColumn(onlyRemote ? 'REMOTE' : 'PUBLIC', { other: { onlyMedia, onlyRemote } }));
}
}
@ -69,19 +72,19 @@ class PublicTimeline extends React.PureComponent {
}
componentDidMount () {
const { dispatch, onlyMedia } = this.props;
const { dispatch, onlyMedia, onlyRemote } = this.props;
dispatch(expandPublicTimeline({ onlyMedia }));
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote }));
}
componentDidUpdate (prevProps) {
if (prevProps.onlyMedia !== this.props.onlyMedia) {
const { dispatch, onlyMedia } = this.props;
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) {
const { dispatch, onlyMedia, onlyRemote } = this.props;
this.disconnect();
dispatch(expandPublicTimeline({ onlyMedia }));
this.disconnect = dispatch(connectPublicStream({ onlyMedia }));
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
this.disconnect = dispatch(connectPublicStream({ onlyMedia, onlyRemote }));
}
}
@ -97,13 +100,13 @@ class PublicTimeline extends React.PureComponent {
}
handleLoadMore = maxId => {
const { dispatch, onlyMedia } = this.props;
const { dispatch, onlyMedia, onlyRemote } = this.props;
dispatch(expandPublicTimeline({ maxId, onlyMedia }));
dispatch(expandPublicTimeline({ maxId, onlyMedia, onlyRemote }));
}
render () {
const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia } = this.props;
const { intl, shouldUpdateScroll, columnId, hasUnread, multiColumn, onlyMedia, onlyRemote } = this.props;
const pinned = !!columnId;
return (
@ -122,7 +125,7 @@ class PublicTimeline extends React.PureComponent {
</ColumnHeader>
<StatusListContainer
timelineId={`public${onlyMedia ? ':media' : ''}`}
timelineId={`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`}
onLoadMore={this.handleLoadMore}
trackScroll={!pinned}
scrollKey={`public_timeline-${columnId}`}

View File

@ -5,30 +5,21 @@ import ColumnHeader from '../column_header';
describe('<Column />', () => {
describe('<ColumnHeader /> click handler', () => {
const originalRaf = global.requestAnimationFrame;
beforeEach(() => {
global.requestAnimationFrame = jest.fn();
});
afterAll(() => {
global.requestAnimationFrame = originalRaf;
});
it('runs the scroll animation if the column contains scrollable content', () => {
const wrapper = mount(
<Column heading='notifications'>
<div className='scrollable' />
</Column>,
);
const scrollToMock = jest.fn();
wrapper.find(Column).find('.scrollable').getDOMNode().scrollTo = scrollToMock;
wrapper.find(ColumnHeader).find('button').simulate('click');
expect(global.requestAnimationFrame.mock.calls.length).toEqual(1);
expect(scrollToMock).toHaveBeenCalledWith({ behavior: 'smooth', top: 0 });
});
it('does not try to scroll if there is no scrollable content', () => {
const wrapper = mount(<Column heading='notifications' />);
wrapper.find(ColumnHeader).find('button').simulate('click');
expect(global.requestAnimationFrame.mock.calls.length).toEqual(0);
});
});
});

View File

@ -37,6 +37,7 @@ const componentMap = {
'HOME': HomeTimeline,
'NOTIFICATIONS': Notifications,
'PUBLIC': PublicTimeline,
'REMOTE': PublicTimeline,
'COMMUNITY': CommunityTimeline,
'HASHTAG': HashtagTimeline,
'DIRECT': DirectTimeline,

View File

@ -543,12 +543,6 @@ $small-breakpoint: 960px;
flex: 0 0 auto;
}
&__avatar {
width: 44px;
height: 44px;
background-size: 44px 44px;
}
.display-name {
font-size: 15px;
@ -749,12 +743,6 @@ $small-breakpoint: 960px;
display: flex;
align-items: center;
}
.account__avatar {
width: 44px;
height: 44px;
background-size: 44px 44px;
}
}
&__counters__wrapper {

View File

@ -1318,8 +1318,13 @@
.account__avatar {
@include avatar-radius;
display: block;
position: relative;
width: 36px;
height: 36px;
background-size: 36px 36px;
&-inline {
display: inline-block;
vertical-align: middle;

View File

@ -93,12 +93,6 @@
display: flex;
align-items: center;
}
.account__avatar {
width: 44px;
height: 44px;
background-size: 44px 44px;
}
}
.trends__item {