Added App Setttings Modal
This commit is contained in:
parent
6cbbdc805f
commit
595c6de32c
|
@ -14,7 +14,7 @@ export function changeLocalSetting(key, value) {
|
||||||
|
|
||||||
export function saveLocalSettings() {
|
export function saveLocalSettings() {
|
||||||
return (_, getState) => {
|
return (_, getState) => {
|
||||||
const localSettings = getState().get('localSettings').toJS();
|
const localSettings = getState().get('local_settings').toJS();
|
||||||
localStorage.setItem('mastodon-settings', JSON.stringify(localSettings));
|
localStorage.setItem('mastodon-settings', JSON.stringify(localSettings));
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,7 @@ export default class StatusOrReblog extends ImmutablePureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
|
settings: ImmutablePropTypes.map,
|
||||||
wrapped: PropTypes.bool,
|
wrapped: PropTypes.bool,
|
||||||
onReply: PropTypes.func,
|
onReply: PropTypes.func,
|
||||||
onFavourite: PropTypes.func,
|
onFavourite: PropTypes.func,
|
||||||
|
@ -47,6 +48,7 @@ export default class StatusOrReblog extends ImmutablePureComponent {
|
||||||
updateOnProps = [
|
updateOnProps = [
|
||||||
'status',
|
'status',
|
||||||
'account',
|
'account',
|
||||||
|
'settings',
|
||||||
'wrapped',
|
'wrapped',
|
||||||
'me',
|
'me',
|
||||||
'boostModal',
|
'boostModal',
|
||||||
|
@ -97,6 +99,7 @@ class Status extends ImmutablePureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
|
settings: ImmutablePropTypes.map,
|
||||||
wrapped: PropTypes.bool,
|
wrapped: PropTypes.bool,
|
||||||
onReply: PropTypes.func,
|
onReply: PropTypes.func,
|
||||||
onFavourite: PropTypes.func,
|
onFavourite: PropTypes.func,
|
||||||
|
@ -126,6 +129,7 @@ class Status extends ImmutablePureComponent {
|
||||||
updateOnProps = [
|
updateOnProps = [
|
||||||
'status',
|
'status',
|
||||||
'account',
|
'account',
|
||||||
|
'settings',
|
||||||
'wrapped',
|
'wrapped',
|
||||||
'me',
|
'me',
|
||||||
'boostModal',
|
'boostModal',
|
||||||
|
@ -140,7 +144,8 @@ class Status extends ImmutablePureComponent {
|
||||||
]
|
]
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
componentWillReceiveProps (nextProps) {
|
||||||
if (nextProps.collapse !== this.props.collapse && nextProps.collapse !== undefined) this.setState({ isCollapsed: !!nextProps.collapse });
|
if (!nextProps.settings.getIn(['collapsed', 'enabled'])) this.collapse(false);
|
||||||
|
else if (nextProps.collapse !== this.props.collapse && nextProps.collapse !== undefined) this.collapse(this.props.collapse);
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate (nextProps, nextState) {
|
shouldComponentUpdate (nextProps, nextState) {
|
||||||
|
@ -165,8 +170,13 @@ class Status extends ImmutablePureComponent {
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const node = this.node;
|
const node = this.node;
|
||||||
|
|
||||||
if (this.props.collapse !== undefined) this.setState({ isCollapsed: !!this.props.collapse });
|
const { collapse, settings, status } = this.props;
|
||||||
else if (node.clientHeight > 400) this.setState({ isCollapsed: true });
|
|
||||||
|
if (collapse !== undefined) this.collapse(collapse);
|
||||||
|
else if (settings.getIn(['collapsed', 'auto', 'all'])) this.collapse();
|
||||||
|
else if (settings.getIn(['collapsed', 'auto', 'lengthy']) && node.clientHeight > 400) this.collapse();
|
||||||
|
else if (settings.getIn(['collapsed', 'auto', 'replies']) && status.get('in_reply_to_id', null) !== null) this.collapse();
|
||||||
|
else if (settings.getIn(['collapsed', 'auto', 'media']) && status.get('media_attachments').size > 0) this.collapse();
|
||||||
|
|
||||||
if (!this.props.intersectionObserverWrapper) {
|
if (!this.props.intersectionObserverWrapper) {
|
||||||
// TODO: enable IntersectionObserver optimization for notification statuses.
|
// TODO: enable IntersectionObserver optimization for notification statuses.
|
||||||
|
@ -186,6 +196,11 @@ class Status extends ImmutablePureComponent {
|
||||||
this.componentMounted = false;
|
this.componentMounted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
collapse = (collapsedOrNot) => {
|
||||||
|
if (collapsedOrNot === undefined) collapsedOrNot = true;
|
||||||
|
if (this.props.settings.getIn(['collapsed', 'enabled'])) this.setState({ isCollapsed: !!collapsedOrNot });
|
||||||
|
}
|
||||||
|
|
||||||
handleIntersection = (entry) => {
|
handleIntersection = (entry) => {
|
||||||
// Edge 15 doesn't support isIntersecting, but we can infer it
|
// Edge 15 doesn't support isIntersecting, but we can infer it
|
||||||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12156111/
|
// https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/12156111/
|
||||||
|
@ -247,20 +262,23 @@ class Status extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
handleCollapsedClick = () => {
|
handleCollapsedClick = () => {
|
||||||
this.setState({ isCollapsed: !this.state.isCollapsed, isExpanded: false });
|
this.collapse(!this.state.isCollapsed);
|
||||||
|
this.setState({ isExpanded: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
let media = null;
|
let media = null;
|
||||||
let mediaType = null;
|
let mediaType = null;
|
||||||
let thumb = null;
|
|
||||||
let statusAvatar;
|
let statusAvatar;
|
||||||
|
|
||||||
// Exclude intersectionObserverWrapper from `other` variable
|
// Exclude intersectionObserverWrapper from `other` variable
|
||||||
// because intersection is managed in here.
|
// because intersection is managed in here.
|
||||||
const { status, account, intersectionObserverWrapper, intl, ...other } = this.props;
|
const { status, account, settings, intersectionObserverWrapper, intl, ...other } = this.props;
|
||||||
const { isExpanded, isIntersecting, isHidden, isCollapsed } = this.state;
|
const { isExpanded, isIntersecting, isHidden, isCollapsed } = this.state;
|
||||||
|
|
||||||
|
|
||||||
|
let background = settings.getIn(['collapsed', 'backgrounds', 'user_backgrounds']) ? status.getIn(['account', 'header']) : null;
|
||||||
|
|
||||||
if (status === null) {
|
if (status === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -280,12 +298,12 @@ class Status extends ImmutablePureComponent {
|
||||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||||
media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} onOpenVideo={this.props.onOpenVideo} />;
|
media = <VideoPlayer media={status.getIn(['media_attachments', 0])} sensitive={status.get('sensitive')} onOpenVideo={this.props.onOpenVideo} />;
|
||||||
mediaType = <i className='fa fa-fw fa-video-camera' aria-hidden='true' />;
|
mediaType = <i className='fa fa-fw fa-video-camera' aria-hidden='true' />;
|
||||||
if (!status.get('sensitive') && !(status.get('spoiler_text').length > 0)) thumb = status.getIn(['media_attachments', 0]).get('preview_url');
|
|
||||||
} else {
|
} else {
|
||||||
media = <MediaGallery media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />;
|
media = <MediaGallery media={status.get('media_attachments')} sensitive={status.get('sensitive')} height={110} onOpenMedia={this.props.onOpenMedia} autoPlayGif={this.props.autoPlayGif} />;
|
||||||
mediaType = status.get('media_attachments').size > 1 ? <i className='fa fa-fw fa-th-large' aria-hidden='true' /> : <i className='fa fa-fw fa-picture-o' aria-hidden='true' />;
|
mediaType = status.get('media_attachments').size > 1 ? <i className='fa fa-fw fa-th-large' aria-hidden='true' /> : <i className='fa fa-fw fa-picture-o' aria-hidden='true' />;
|
||||||
if (!status.get('sensitive') && !(status.get('spoiler_text').length > 0)) thumb = status.getIn(['media_attachments', 0]).get('preview_url');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!status.get('sensitive') && !(status.get('spoiler_text').length > 0) && settings.getIn(['collapsed', 'backgrounds', 'preview_images'])) background = status.getIn(['media_attachments', 0]).get('preview_url');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (account === undefined || account === null) {
|
if (account === undefined || account === null) {
|
||||||
|
@ -295,19 +313,19 @@ class Status extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`status ${this.props.muted ? 'muted' : ''} status-${status.get('visibility')} ${isCollapsed ? 'status-collapsed' : ''}`} data-id={status.get('id')} ref={this.handleRef} style={{ backgroundImage: thumb && isCollapsed ? 'url(' + thumb + ')' : 'none' }}>
|
<div className={`status ${this.props.muted ? 'muted' : ''} status-${status.get('visibility')} ${isCollapsed ? 'status-collapsed' : ''}`} data-id={status.get('id')} ref={this.handleRef} style={{ backgroundImage: background && isCollapsed ? 'url(' + background + ')' : 'none' }}>
|
||||||
<div className='status__info'>
|
<div className='status__info'>
|
||||||
|
|
||||||
<div className='status__info__icons'>
|
<div className='status__info__icons'>
|
||||||
{mediaType}
|
{mediaType}
|
||||||
<IconButton
|
{settings.getIn(['collapsed', 'enabled']) ? <IconButton
|
||||||
className='status__collapse-button'
|
className='status__collapse-button'
|
||||||
animate flip
|
animate flip
|
||||||
active={isCollapsed}
|
active={isCollapsed}
|
||||||
title={isCollapsed ? intl.formatMessage(messages.uncollapse) : intl.formatMessage(messages.collapse)}
|
title={isCollapsed ? intl.formatMessage(messages.uncollapse) : intl.formatMessage(messages.collapse)}
|
||||||
icon='angle-double-up'
|
icon='angle-double-up'
|
||||||
onClick={this.handleCollapsedClick}
|
onClick={this.handleCollapsedClick}
|
||||||
/>
|
/> : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name'>
|
<a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name'>
|
||||||
|
|
|
@ -25,9 +25,9 @@ addLocaleData(localeData);
|
||||||
const store = configureStore();
|
const store = configureStore();
|
||||||
const initialState = JSON.parse(document.getElementById('initial-state').textContent);
|
const initialState = JSON.parse(document.getElementById('initial-state').textContent);
|
||||||
try {
|
try {
|
||||||
initialState.localSettings = JSON.parse(localStorage.getItem('mastodon-settings'));
|
initialState.local_settings = JSON.parse(localStorage.getItem('mastodon-settings'));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
initialState.localSettings = {};
|
initialState.local_settings = {};
|
||||||
}
|
}
|
||||||
store.dispatch(hydrateStore(initialState));
|
store.dispatch(hydrateStore(initialState));
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ const makeMapStateToProps = () => {
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
status: getStatus(state, props.id),
|
status: getStatus(state, props.id),
|
||||||
me: state.getIn(['meta', 'me']),
|
me: state.getIn(['meta', 'me']),
|
||||||
|
settings: state.get('local_settings'),
|
||||||
boostModal: state.getIn(['meta', 'boost_modal']),
|
boostModal: state.getIn(['meta', 'boost_modal']),
|
||||||
deleteModal: state.getIn(['meta', 'delete_modal']),
|
deleteModal: state.getIn(['meta', 'delete_modal']),
|
||||||
autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
|
autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
|
||||||
|
|
|
@ -4,6 +4,7 @@ import NavigationContainer from './containers/navigation_container';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { mountCompose, unmountCompose } from '../../actions/compose';
|
import { mountCompose, unmountCompose } from '../../actions/compose';
|
||||||
|
import { openModal } from '../../actions/modal';
|
||||||
import { changeLocalSetting } from '../../actions/local_settings';
|
import { changeLocalSetting } from '../../actions/local_settings';
|
||||||
import Link from 'react-router-dom/Link';
|
import Link from 'react-router-dom/Link';
|
||||||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||||
|
@ -16,13 +17,13 @@ const messages = defineMessages({
|
||||||
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
|
||||||
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
|
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
|
||||||
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
|
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
|
||||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
|
||||||
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
||||||
layout: state.getIn(['localSettings', 'layout']),
|
layout: state.getIn(['local_settings', 'layout']),
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(mapStateToProps)
|
@connect(mapStateToProps)
|
||||||
|
@ -51,6 +52,10 @@ export default class Compose extends React.PureComponent {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openSettings = () => {
|
||||||
|
this.props.dispatch(openModal('SETTINGS', {}));
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { multiColumn, showSearch, intl, layout } = this.props;
|
const { multiColumn, showSearch, intl, layout } = this.props;
|
||||||
|
|
||||||
|
@ -62,7 +67,7 @@ export default class Compose extends React.PureComponent {
|
||||||
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)}><i role='img' aria-label={intl.formatMessage(messages.start)} className='fa fa-fw fa-asterisk' /></Link>
|
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)}><i role='img' aria-label={intl.formatMessage(messages.start)} className='fa fa-fw fa-asterisk' /></Link>
|
||||||
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)}><i role='img' aria-label={intl.formatMessage(messages.community)} className='fa fa-fw fa-users' /></Link>
|
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)}><i role='img' aria-label={intl.formatMessage(messages.community)} className='fa fa-fw fa-users' /></Link>
|
||||||
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)}><i role='img' aria-label={intl.formatMessage(messages.public)} className='fa fa-fw fa-globe' /></Link>
|
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)}><i role='img' aria-label={intl.formatMessage(messages.public)} className='fa fa-fw fa-globe' /></Link>
|
||||||
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)}><i role='img' aria-label={intl.formatMessage(messages.preferences)} className='fa fa-fw fa-cog' /></a>
|
<a onClick={this.openSettings} role='button' tabIndex='0' className='drawer__tab' title={intl.formatMessage(messages.settings)}><i role='img' aria-label={intl.formatMessage(messages.settings)} className='fa fa-fw fa-cogs' /></a>
|
||||||
<a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)}><i role='img' aria-label={intl.formatMessage(messages.logout)} className='fa fa-fw fa-sign-out' /></a>
|
<a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)}><i role='img' aria-label={intl.formatMessage(messages.logout)} className='fa fa-fw fa-sign-out' /></a>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import ColumnLink from '../ui/components/column_link';
|
||||||
import ColumnSubheading from '../ui/components/column_subheading';
|
import ColumnSubheading from '../ui/components/column_subheading';
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
import { openModal } from '../../actions/modal';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
@ -17,6 +18,7 @@ const messages = defineMessages({
|
||||||
settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' },
|
settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' },
|
||||||
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
|
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
|
||||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||||
|
settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' },
|
||||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||||
sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
sign_out: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
||||||
|
@ -39,8 +41,13 @@ export default class GettingStarted extends ImmutablePureComponent {
|
||||||
me: ImmutablePropTypes.map.isRequired,
|
me: ImmutablePropTypes.map.isRequired,
|
||||||
columns: ImmutablePropTypes.list,
|
columns: ImmutablePropTypes.list,
|
||||||
multiColumn: PropTypes.bool,
|
multiColumn: PropTypes.bool,
|
||||||
|
dispatch: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
openSettings = () => {
|
||||||
|
this.props.dispatch(openModal('SETTINGS', {}));
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, me, columns, multiColumn } = this.props;
|
const { intl, me, columns, multiColumn } = this.props;
|
||||||
|
|
||||||
|
@ -79,27 +86,30 @@ export default class GettingStarted extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile>
|
<Column icon='asterisk' heading={intl.formatMessage(messages.heading)} hideHeadingOnMobile>
|
||||||
<div className='getting-started__wrapper'>
|
<div className='scrollable optionally-scrollable'>
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />
|
<div className='getting-started__wrapper'>
|
||||||
{navItems}
|
<ColumnSubheading text={intl.formatMessage(messages.navigation_subheading)} />
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} />
|
{navItems}
|
||||||
<ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />
|
<ColumnSubheading text={intl.formatMessage(messages.settings_subheading)} />
|
||||||
<ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />
|
<ColumnLink icon='book' text={intl.formatMessage(messages.info)} href='/about/more' />
|
||||||
<ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
|
<ColumnLink icon='cog' text={intl.formatMessage(messages.preferences)} href='/settings/preferences' />
|
||||||
</div>
|
<ColumnLink icon='cogs' text={intl.formatMessage(messages.settings)} onClick={this.openSettings} />
|
||||||
|
<ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className='getting-started__footer scrollable optionally-scrollable'>
|
<div className='getting-started__footer'>
|
||||||
<div className='static-content getting-started'>
|
<div className='static-content getting-started'>
|
||||||
<p>
|
<p>
|
||||||
<a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.faq' defaultMessage='FAQ' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.userguide' defaultMessage='User Guide' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.appsshort' defaultMessage='Apps' /></a>
|
<a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.faq' defaultMessage='FAQ' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.userguide' defaultMessage='User Guide' /></a> • <a href='https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md' rel='noopener' target='_blank'><FormattedMessage id='getting_started.appsshort' defaultMessage='Apps' /></a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='getting_started.open_source_notice'
|
id='getting_started.open_source_notice'
|
||||||
defaultMessage='Glitchsoc is open source software, a friendly fork of {Mastodon}. You can contribute or report issues on GitHub at {github}.'
|
defaultMessage='Glitchsoc is open source software, a friendly fork of {Mastodon}. You can contribute or report issues on GitHub at {github}.'
|
||||||
values={{ github: <a href='https://github.com/glitch-soc/mastodon' rel='noopener' target='_blank'>glitch-soc/mastodon</a>, Mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>Mastodon</a> }}
|
values={{ github: <a href='https://github.com/glitch-soc/mastodon' rel='noopener' target='_blank'>glitch-soc/mastodon</a>, Mastodon: <a href='https://github.com/tootsuite/mastodon' rel='noopener' target='_blank'>Mastodon</a> }}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -12,6 +12,7 @@ export default class Notification extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
notification: ImmutablePropTypes.map.isRequired,
|
notification: ImmutablePropTypes.map.isRequired,
|
||||||
|
settings: ImmutablePropTypes.map.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
renderFollow (account, link) {
|
renderFollow (account, link) {
|
||||||
|
@ -34,7 +35,7 @@ export default class Notification extends ImmutablePureComponent {
|
||||||
return <StatusContainer id={notification.get('status')} withDismiss />;
|
return <StatusContainer id={notification.get('status')} withDismiss />;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFavourite (notification, link) {
|
renderFavourite (notification, settings, link) {
|
||||||
return (
|
return (
|
||||||
<div className='notification notification-favourite'>
|
<div className='notification notification-favourite'>
|
||||||
<div className='notification__message'>
|
<div className='notification__message'>
|
||||||
|
@ -44,12 +45,12 @@ export default class Notification extends ImmutablePureComponent {
|
||||||
<FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
|
<FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted collapse withDismiss />
|
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted collapse={settings.getIn(['collapsed', 'auto', 'notifications'])} withDismiss />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderReblog (notification, link) {
|
renderReblog (notification, settings, link) {
|
||||||
return (
|
return (
|
||||||
<div className='notification notification-reblog'>
|
<div className='notification notification-reblog'>
|
||||||
<div className='notification__message'>
|
<div className='notification__message'>
|
||||||
|
@ -59,13 +60,13 @@ export default class Notification extends ImmutablePureComponent {
|
||||||
<FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
|
<FormattedMessage id='notification.reblog' defaultMessage='{name} boosted your status' values={{ name: link }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted collapse withDismiss />
|
<StatusContainer id={notification.get('status')} account={notification.get('account')} muted collapse={settings.getIn(['collapsed', 'auto', 'notifications'])} withDismiss />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { notification } = this.props;
|
const { notification, settings } = this.props;
|
||||||
const account = notification.get('account');
|
const account = notification.get('account');
|
||||||
const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username');
|
const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username');
|
||||||
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
|
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
|
||||||
|
@ -77,9 +78,9 @@ export default class Notification extends ImmutablePureComponent {
|
||||||
case 'mention':
|
case 'mention':
|
||||||
return this.renderMention(notification);
|
return this.renderMention(notification);
|
||||||
case 'favourite':
|
case 'favourite':
|
||||||
return this.renderFavourite(notification, link);
|
return this.renderFavourite(notification, settings, link);
|
||||||
case 'reblog':
|
case 'reblog':
|
||||||
return this.renderReblog(notification, link);
|
return this.renderReblog(notification, settings, link);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -7,6 +7,7 @@ const makeMapStateToProps = () => {
|
||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
notification: getNotification(state, props.notification, props.accountId),
|
notification: getNotification(state, props.notification, props.accountId),
|
||||||
|
settings: state.get('local_settings'),
|
||||||
});
|
});
|
||||||
|
|
||||||
return mapStateToProps;
|
return mapStateToProps;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Link from 'react-router-dom/Link';
|
import Link from 'react-router-dom/Link';
|
||||||
|
|
||||||
const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => {
|
const ColumnLink = ({ icon, text, to, onClick, href, method, hideOnMobile }) => {
|
||||||
if (href) {
|
if (href) {
|
||||||
return (
|
return (
|
||||||
<a href={href} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}>
|
<a href={href} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}>
|
||||||
|
@ -10,13 +10,20 @@ const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => {
|
||||||
{text}
|
{text}
|
||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
} else {
|
} else if (to) {
|
||||||
return (
|
return (
|
||||||
<Link to={to} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`}>
|
<Link to={to} className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`}>
|
||||||
<i className={`fa fa-fw fa-${icon} column-link__icon`} />
|
<i className={`fa fa-fw fa-${icon} column-link__icon`} />
|
||||||
{text}
|
{text}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<a onClick={onClick} role='button' tabIndex='0' className={`column-link ${hideOnMobile ? 'hidden-on-mobile' : ''}`} data-method={method}>
|
||||||
|
<i className={`fa fa-fw fa-${icon} column-link__icon`} />
|
||||||
|
{text}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -24,6 +31,7 @@ ColumnLink.propTypes = {
|
||||||
icon: PropTypes.string.isRequired,
|
icon: PropTypes.string.isRequired,
|
||||||
text: PropTypes.string.isRequired,
|
text: PropTypes.string.isRequired,
|
||||||
to: PropTypes.string,
|
to: PropTypes.string,
|
||||||
|
onClick: PropTypes.func,
|
||||||
href: PropTypes.string,
|
href: PropTypes.string,
|
||||||
method: PropTypes.string,
|
method: PropTypes.string,
|
||||||
hideOnMobile: PropTypes.bool,
|
hideOnMobile: PropTypes.bool,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import VideoModal from './video_modal';
|
||||||
import BoostModal from './boost_modal';
|
import BoostModal from './boost_modal';
|
||||||
import ConfirmationModal from './confirmation_modal';
|
import ConfirmationModal from './confirmation_modal';
|
||||||
import ReportModal from './report_modal';
|
import ReportModal from './report_modal';
|
||||||
|
import SettingsModal from '../containers/settings_modal_container';
|
||||||
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
import TransitionMotion from 'react-motion/lib/TransitionMotion';
|
||||||
import spring from 'react-motion/lib/spring';
|
import spring from 'react-motion/lib/spring';
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ const MODAL_COMPONENTS = {
|
||||||
'BOOST': BoostModal,
|
'BOOST': BoostModal,
|
||||||
'CONFIRM': ConfirmationModal,
|
'CONFIRM': ConfirmationModal,
|
||||||
'REPORT': ReportModal,
|
'REPORT': ReportModal,
|
||||||
|
'SETTINGS': SettingsModal,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class ModalRoot extends React.PureComponent {
|
export default class ModalRoot extends React.PureComponent {
|
||||||
|
|
|
@ -0,0 +1,212 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
class SettingsItem extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
settings: ImmutablePropTypes.map.isRequired,
|
||||||
|
item: PropTypes.array.isRequired,
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
dependsOn: PropTypes.array,
|
||||||
|
dependsOnNot: PropTypes.array,
|
||||||
|
children: PropTypes.element.isRequired,
|
||||||
|
onChange: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange = (e) => {
|
||||||
|
const { item, onChange } = this.props;
|
||||||
|
onChange(item, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { settings, item, id, children, dependsOn, dependsOnNot } = this.props;
|
||||||
|
let enabled = true;
|
||||||
|
|
||||||
|
if (dependsOn) {
|
||||||
|
for (let i = 0; i < dependsOn.length; i++) {
|
||||||
|
enabled = enabled && settings.getIn(dependsOn[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dependsOnNot) {
|
||||||
|
for (let i = 0; i < dependsOnNot.length; i++) {
|
||||||
|
enabled = enabled && !settings.getIn(dependsOnNot[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label htmlFor={id}>
|
||||||
|
<input
|
||||||
|
id={id}
|
||||||
|
type='checkbox'
|
||||||
|
checked={settings.getIn(item)}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
disabled={!enabled}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SettingsModal extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
settings: ImmutablePropTypes.map.isRequired,
|
||||||
|
toggleSetting: PropTypes.func.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
currentIndex: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
General = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1><FormattedMessage id='settings.general' defaultMessage='General' /></h1>
|
||||||
|
<SettingsItem
|
||||||
|
settings={this.props.settings}
|
||||||
|
item={['stretch']}
|
||||||
|
id='mastodon-settings--stretch'
|
||||||
|
onChange={this.props.toggleSetting}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='settings.wide_view' defaultMessage='Wide view (Desktop mode only)' />
|
||||||
|
</SettingsItem>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CollapsedStatuses = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1><FormattedMessage id='settings.collapsed_statuses' defaultMessage='Collapsed toots' /></h1>
|
||||||
|
<SettingsItem
|
||||||
|
settings={this.props.settings}
|
||||||
|
item={['collapsed', 'enabled']}
|
||||||
|
id='mastodon-settings--collapsed-enabled'
|
||||||
|
onChange={this.props.toggleSetting}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='settings.enable_collapsed' defaultMessage='Enable collapsed toots' />
|
||||||
|
</SettingsItem>
|
||||||
|
<section>
|
||||||
|
<h2><FormattedMessage id='settings.auto_collapse' defaultMessage='Automatic collapsing' /></h2>
|
||||||
|
<SettingsItem
|
||||||
|
settings={this.props.settings}
|
||||||
|
item={['collapsed', 'auto', 'all']}
|
||||||
|
id='mastodon-settings--collapsed-auto-all'
|
||||||
|
onChange={this.props.toggleSetting}
|
||||||
|
dependsOn={[['collapsed', 'enabled']]}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='settings.auto_collapse_all' defaultMessage='Everything' />
|
||||||
|
</SettingsItem>
|
||||||
|
<SettingsItem
|
||||||
|
settings={this.props.settings}
|
||||||
|
item={['collapsed', 'auto', 'notifications']}
|
||||||
|
id='mastodon-settings--collapsed-auto-notifications'
|
||||||
|
onChange={this.props.toggleSetting}
|
||||||
|
dependsOn={[['collapsed', 'enabled']]}
|
||||||
|
dependsOnNot={[['collapsed', 'auto', 'all']]}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='settings.auto_collapse_notifications' defaultMessage='Notifications' />
|
||||||
|
</SettingsItem>
|
||||||
|
<SettingsItem
|
||||||
|
settings={this.props.settings}
|
||||||
|
item={['collapsed', 'auto', 'lengthy']}
|
||||||
|
id='mastodon-settings--collapsed-auto-lengthy'
|
||||||
|
onChange={this.props.toggleSetting}
|
||||||
|
dependsOn={[['collapsed', 'enabled']]}
|
||||||
|
dependsOnNot={[['collapsed', 'auto', 'all']]}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='settings.auto_collapse_lengthy' defaultMessage='Lengthy toots' />
|
||||||
|
</SettingsItem>
|
||||||
|
<SettingsItem
|
||||||
|
settings={this.props.settings}
|
||||||
|
item={['collapsed', 'auto', 'replies']}
|
||||||
|
id='mastodon-settings--collapsed-auto-replies'
|
||||||
|
onChange={this.props.toggleSetting}
|
||||||
|
dependsOn={[['collapsed', 'enabled']]}
|
||||||
|
dependsOnNot={[['collapsed', 'auto', 'all']]}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='settings.auto_collapse_replies' defaultMessage='Replies' />
|
||||||
|
</SettingsItem>
|
||||||
|
<SettingsItem
|
||||||
|
settings={this.props.settings}
|
||||||
|
item={['collapsed', 'auto', 'media']}
|
||||||
|
id='mastodon-settings--collapsed-auto-media'
|
||||||
|
onChange={this.props.toggleSetting}
|
||||||
|
dependsOn={[['collapsed', 'enabled']]}
|
||||||
|
dependsOnNot={[['collapsed', 'auto', 'all']]}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='settings.auto_collapse_media' defaultMessage='Toots with media' />
|
||||||
|
</SettingsItem>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<h2><FormattedMessage id='settings.image_backgrounds' defaultMessage='Image backgrounds' /></h2>
|
||||||
|
<SettingsItem
|
||||||
|
settings={this.props.settings}
|
||||||
|
item={['collapsed', 'backgrounds', 'user_backgrounds']}
|
||||||
|
id='mastodon-settings--collapsed-user-backgrouns'
|
||||||
|
onChange={this.props.toggleSetting}
|
||||||
|
dependsOn={[['collapsed', 'enabled']]}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='settings.image_backgrounds_users' defaultMessage='Give collapsed toots an image background' />
|
||||||
|
</SettingsItem>
|
||||||
|
<SettingsItem
|
||||||
|
settings={this.props.settings}
|
||||||
|
item={['collapsed', 'backgrounds', 'preview_images']}
|
||||||
|
id='mastodon-settings--collapsed-preview-images'
|
||||||
|
onChange={this.props.toggleSetting}
|
||||||
|
dependsOn={[['collapsed', 'enabled']]}
|
||||||
|
>
|
||||||
|
<FormattedMessage id='settings.image_backgrounds_media' defaultMessage='Preview collapsed toot media' />
|
||||||
|
</SettingsItem>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateTo = (e) =>
|
||||||
|
this.setState({ currentIndex: +e.currentTarget.getAttribute('data-mastodon-navigation_index') });
|
||||||
|
|
||||||
|
render () {
|
||||||
|
|
||||||
|
const { General, CollapsedStatuses, navigateTo } = this;
|
||||||
|
const { onClose } = this.props;
|
||||||
|
const { currentIndex } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='modal-root__modal settings-modal'>
|
||||||
|
|
||||||
|
<nav className='settings-modal__navigation'>
|
||||||
|
<a onClick={navigateTo} role='button' data-mastodon-navigation_index='0' tabIndex='0' className={`settings-modal__navigation-item${currentIndex === 0 ? ' active' : ''}`}>
|
||||||
|
<FormattedMessage id='settings.general' defaultMessage='General' />
|
||||||
|
</a>
|
||||||
|
<a onClick={navigateTo} role='button' data-mastodon-navigation_index='1' tabIndex='0' className={`settings-modal__navigation-item${currentIndex === 1 ? ' active' : ''}`}>
|
||||||
|
<FormattedMessage id='settings.collapsed_statuses' defaultMessage='Collapsed toots' />
|
||||||
|
</a>
|
||||||
|
<a href='/settings/preferences' className='settings-modal__navigation-item'>
|
||||||
|
<i className='fa fa-fw fa-cogs' /> <FormattedMessage id='settings.preferences' defaultMessage='User preferences' />
|
||||||
|
</a>
|
||||||
|
<a onClick={onClose} role='button' tabIndex='0' className='settings-modal__navigation-close'>
|
||||||
|
<FormattedMessage id='settings.close' defaultMessage='Close' />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div className='settings-modal__content'>
|
||||||
|
{
|
||||||
|
[
|
||||||
|
<General />,
|
||||||
|
<CollapsedStatuses />,
|
||||||
|
][currentIndex] || <General />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { changeLocalSetting } from '../../../actions/local_settings';
|
||||||
|
import { closeModal } from '../../../actions/modal';
|
||||||
|
import SettingsModal from '../components/settings_modal';
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
settings: state.get('local_settings'),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
toggleSetting (setting, e) {
|
||||||
|
dispatch(changeLocalSetting(setting, e.target.checked));
|
||||||
|
},
|
||||||
|
onClose () {
|
||||||
|
dispatch(closeModal());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(SettingsModal);
|
|
@ -73,7 +73,8 @@ class WrappedRoute extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
layout: state.getIn(['localSettings', 'layout']),
|
layout: state.getIn(['local_settings', 'layout']),
|
||||||
|
isWide: state.getIn(['local_settings', 'stretch']),
|
||||||
});
|
});
|
||||||
|
|
||||||
@connect(mapStateToProps)
|
@connect(mapStateToProps)
|
||||||
|
@ -83,6 +84,7 @@ export default class UI extends React.PureComponent {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
layout: PropTypes.string,
|
layout: PropTypes.string,
|
||||||
|
isWide: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
@ -179,7 +181,7 @@ export default class UI extends React.PureComponent {
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { width, draggingOver } = this.state;
|
const { width, draggingOver } = this.state;
|
||||||
const { children, layout } = this.props;
|
const { children, layout, isWide } = this.props;
|
||||||
|
|
||||||
const columnsClass = layout => {
|
const columnsClass = layout => {
|
||||||
switch (layout) {
|
switch (layout) {
|
||||||
|
@ -193,7 +195,7 @@ export default class UI extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'ui ' + columnsClass(layout)} ref={this.setRef}>
|
<div className={'ui ' + columnsClass(layout) + (isWide ? ' wide' : '')} ref={this.setRef}>
|
||||||
<TabsBar />
|
<TabsBar />
|
||||||
<ColumnsAreaContainer singleColumn={isMobile(width, layout)}>
|
<ColumnsAreaContainer singleColumn={isMobile(width, layout)}>
|
||||||
<WrappedSwitch>
|
<WrappedSwitch>
|
||||||
|
|
|
@ -188,10 +188,6 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"descriptors": [
|
"descriptors": [
|
||||||
{
|
|
||||||
"defaultMessage": "{name} boosted",
|
|
||||||
"id": "status.reblogged_by"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"defaultMessage": "Collapse",
|
"defaultMessage": "Collapse",
|
||||||
"id": "status.collapse"
|
"id": "status.collapse"
|
||||||
|
@ -199,6 +195,10 @@
|
||||||
{
|
{
|
||||||
"defaultMessage": "Uncollapse",
|
"defaultMessage": "Uncollapse",
|
||||||
"id": "status.uncollapse"
|
"id": "status.uncollapse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "{name} boosted",
|
||||||
|
"id": "status.reblogged_by"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"path": "app/javascript/mastodon/components/status.json"
|
"path": "app/javascript/mastodon/components/status.json"
|
||||||
|
@ -652,8 +652,8 @@
|
||||||
"id": "navigation_bar.community_timeline"
|
"id": "navigation_bar.community_timeline"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Preferences",
|
"defaultMessage": "App settings",
|
||||||
"id": "navigation_bar.preferences"
|
"id": "navigation_bar.app_settings"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Logout",
|
"defaultMessage": "Logout",
|
||||||
|
@ -667,13 +667,13 @@
|
||||||
"defaultMessage": "Mobile",
|
"defaultMessage": "Mobile",
|
||||||
"id": "layout.mobile"
|
"id": "layout.mobile"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"defaultMessage": "Desktop",
|
|
||||||
"id": "layout.desktop"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"defaultMessage": "Auto",
|
"defaultMessage": "Auto",
|
||||||
"id": "layout.auto"
|
"id": "layout.auto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Desktop",
|
||||||
|
"id": "layout.desktop"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"path": "app/javascript/mastodon/features/compose/index.json"
|
"path": "app/javascript/mastodon/features/compose/index.json"
|
||||||
|
@ -743,6 +743,10 @@
|
||||||
"defaultMessage": "Preferences",
|
"defaultMessage": "Preferences",
|
||||||
"id": "navigation_bar.preferences"
|
"id": "navigation_bar.preferences"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "App settings",
|
||||||
|
"id": "navigation_bar.app_settings"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Follow requests",
|
"defaultMessage": "Follow requests",
|
||||||
"id": "navigation_bar.follow_requests"
|
"id": "navigation_bar.follow_requests"
|
||||||
|
@ -1073,7 +1077,7 @@
|
||||||
"id": "onboarding.page_one.welcome"
|
"id": "onboarding.page_one.welcome"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "{domain} is an 'instance' of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
"defaultMessage": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
|
||||||
"id": "onboarding.page_one.federation"
|
"id": "onboarding.page_one.federation"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1121,7 +1125,7 @@
|
||||||
"id": "onboarding.page_six.almost_done"
|
"id": "onboarding.page_six.almost_done"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "{domain} runs on Glitchsoc, a friendly fork of {Mastodon}. Glitchsoc is fully compatible with any Mastodon instance or app. You can report bugs, request features, or contribute to the code on {github}.",
|
"defaultMessage": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
|
||||||
"id": "onboarding.page_six.github"
|
"id": "onboarding.page_six.github"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1168,6 +1172,71 @@
|
||||||
],
|
],
|
||||||
"path": "app/javascript/mastodon/features/ui/components/report_modal.json"
|
"path": "app/javascript/mastodon/features/ui/components/report_modal.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"descriptors": [
|
||||||
|
{
|
||||||
|
"defaultMessage": "General",
|
||||||
|
"id": "settings.general"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Wide view (Desktop mode only)",
|
||||||
|
"id": "settings.wide_view"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Collapsed toots",
|
||||||
|
"id": "settings.collapsed_statuses"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Enable collapsed toots",
|
||||||
|
"id": "settings.enable_collapsed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Automatic collapsing",
|
||||||
|
"id": "settings.auto_collapse"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Everything",
|
||||||
|
"id": "settings.auto_collapse_all"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Notifications",
|
||||||
|
"id": "settings.auto_collapse_notifications"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Lengthy toots",
|
||||||
|
"id": "settings.auto_collapse_lengthy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Replies",
|
||||||
|
"id": "settings.auto_collapse_replies"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Toots with media",
|
||||||
|
"id": "settings.auto_collapse_media"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Image backgrounds",
|
||||||
|
"id": "settings.image_backgrounds"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Give collapsed toots an image background",
|
||||||
|
"id": "settings.image_backgrounds_users"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Preview collapsed toot media",
|
||||||
|
"id": "settings.image_backgrounds_media"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "User preferences",
|
||||||
|
"id": "settings.global_settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defaultMessage": "Close",
|
||||||
|
"id": "settings.close"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"path": "app/javascript/mastodon/features/ui/components/settings_modal.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"descriptors": [
|
"descriptors": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -87,6 +87,7 @@
|
||||||
"loading_indicator.label": "Loading...",
|
"loading_indicator.label": "Loading...",
|
||||||
"media_gallery.toggle_visible": "Toggle visibility",
|
"media_gallery.toggle_visible": "Toggle visibility",
|
||||||
"missing_indicator.label": "Not found",
|
"missing_indicator.label": "Not found",
|
||||||
|
"navigation_bar.app_settings": "App settings",
|
||||||
"navigation_bar.blocks": "Blocked users",
|
"navigation_bar.blocks": "Blocked users",
|
||||||
"navigation_bar.community_timeline": "Local timeline",
|
"navigation_bar.community_timeline": "Local timeline",
|
||||||
"navigation_bar.edit_profile": "Edit profile",
|
"navigation_bar.edit_profile": "Edit profile",
|
||||||
|
@ -146,6 +147,21 @@
|
||||||
"report.target": "Reporting {target}",
|
"report.target": "Reporting {target}",
|
||||||
"search.placeholder": "Search",
|
"search.placeholder": "Search",
|
||||||
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
|
||||||
|
"settings.auto_collapse": "Automatic collapsing",
|
||||||
|
"settings.auto_collapse_all": "Everything",
|
||||||
|
"settings.auto_collapse_lengthy": "Lengthy toots",
|
||||||
|
"settings.auto_collapse_media": "Toots with media",
|
||||||
|
"settings.auto_collapse_notifications": "Notifications",
|
||||||
|
"settings.auto_collapse_replies": "Replies",
|
||||||
|
"settings.close": "Close",
|
||||||
|
"settings.collapsed_statuses": "Collapsed toots",
|
||||||
|
"settings.enable_collapsed": "Enable collapsed toots",
|
||||||
|
"settings.general": "General",
|
||||||
|
"settings.global_settings": "User preferences",
|
||||||
|
"settings.image_backgrounds": "Image backgrounds",
|
||||||
|
"settings.image_backgrounds_media": "Preview collapsed toot media",
|
||||||
|
"settings.image_backgrounds_users": "Give collapsed toots an image background",
|
||||||
|
"settings.wide_view": "Wide view (Desktop mode only)",
|
||||||
"status.cannot_reblog": "This post cannot be boosted",
|
"status.cannot_reblog": "This post cannot be boosted",
|
||||||
"status.collapse": "Collapse",
|
"status.collapse": "Collapse",
|
||||||
"status.delete": "Delete",
|
"status.delete": "Delete",
|
||||||
|
|
|
@ -14,7 +14,7 @@ import relationships from './relationships';
|
||||||
import search from './search';
|
import search from './search';
|
||||||
import notifications from './notifications';
|
import notifications from './notifications';
|
||||||
import settings from './settings';
|
import settings from './settings';
|
||||||
import localSettings from './local_settings';
|
import local_settings from './local_settings';
|
||||||
import status_lists from './status_lists';
|
import status_lists from './status_lists';
|
||||||
import cards from './cards';
|
import cards from './cards';
|
||||||
import reports from './reports';
|
import reports from './reports';
|
||||||
|
@ -37,7 +37,7 @@ export default combineReducers({
|
||||||
search,
|
search,
|
||||||
notifications,
|
notifications,
|
||||||
settings,
|
settings,
|
||||||
localSettings,
|
local_settings,
|
||||||
cards,
|
cards,
|
||||||
reports,
|
reports,
|
||||||
contexts,
|
contexts,
|
||||||
|
|
|
@ -3,7 +3,22 @@ import { STORE_HYDRATE } from '../actions/store';
|
||||||
import Immutable from 'immutable';
|
import Immutable from 'immutable';
|
||||||
|
|
||||||
const initialState = Immutable.Map({
|
const initialState = Immutable.Map({
|
||||||
layout: 'auto',
|
layout : 'auto',
|
||||||
|
stretch : true,
|
||||||
|
collapsed : {
|
||||||
|
enabled : true,
|
||||||
|
auto : {
|
||||||
|
all : false,
|
||||||
|
notifications : true,
|
||||||
|
lengthy : true,
|
||||||
|
replies : false,
|
||||||
|
media : false,
|
||||||
|
},
|
||||||
|
backgrounds : {
|
||||||
|
user_backgrounds : false,
|
||||||
|
preview_images : false,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const hydrate = (state, localSettings) => state.mergeDeep(localSettings);
|
const hydrate = (state, localSettings) => state.mergeDeep(localSettings);
|
||||||
|
@ -11,7 +26,7 @@ const hydrate = (state, localSettings) => state.mergeDeep(localSettings);
|
||||||
export default function localSettings(state = initialState, action) {
|
export default function localSettings(state = initialState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case STORE_HYDRATE:
|
case STORE_HYDRATE:
|
||||||
return hydrate(state, action.state.get('localSettings'));
|
return hydrate(state, action.state.get('local_settings'));
|
||||||
case LOCAL_SETTING_CHANGE:
|
case LOCAL_SETTING_CHANGE:
|
||||||
return state.setIn(action.key, action.value);
|
return state.setIn(action.key, action.value);
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1351,6 +1351,10 @@
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
||||||
|
.wide & {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include limited-single-column('screen and (max-width: 360px)', $parent: null) {
|
@include limited-single-column('screen and (max-width: 360px)', $parent: null) {
|
||||||
|
@ -1367,6 +1371,12 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
.wide & {
|
||||||
|
flex: auto;
|
||||||
|
min-width: 330px;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
> .scrollable {
|
> .scrollable {
|
||||||
background: $ui-base-color;
|
background: $ui-base-color;
|
||||||
}
|
}
|
||||||
|
@ -1387,6 +1397,12 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.wide & {
|
||||||
|
flex: 1 1 200px;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__tab {
|
.drawer__tab {
|
||||||
|
@ -1399,11 +1415,12 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
border-bottom: 2px solid transparent;
|
border-bottom: 2px solid transparent;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.column,
|
.column,
|
||||||
.drawer {
|
.drawer {
|
||||||
flex: 1 1 100%;
|
|
||||||
@supports(display: grid) { // hack to fix Chrome <57
|
@supports(display: grid) { // hack to fix Chrome <57
|
||||||
contain: strict;
|
contain: strict;
|
||||||
}
|
}
|
||||||
|
@ -1419,20 +1436,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include single-column('screen and (max-width: 1024px)', $parent: null) {
|
:root { // Overrides .wide stylings for mobile view
|
||||||
.column,
|
@include single-column('screen and (max-width: 1024px)', $parent: null) {
|
||||||
.drawer {
|
.column,
|
||||||
width: 100%;
|
.drawer {
|
||||||
padding: 0;
|
flex: auto;
|
||||||
}
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.columns-area {
|
.columns-area {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search__input,
|
.search__input,
|
||||||
.autosuggest-textarea__textarea {
|
.autosuggest-textarea__textarea {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1443,7 +1465,6 @@
|
||||||
|
|
||||||
.column,
|
.column,
|
||||||
.drawer {
|
.drawer {
|
||||||
flex: 0 0 auto;
|
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
|
@ -1771,6 +1792,8 @@
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: lighten($ui-base-color, 11%);
|
background: lighten($ui-base-color, 11%);
|
||||||
|
@ -3312,6 +3335,85 @@ button.icon-button.active i.fa-retweet {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-modal {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
background: $ui-secondary-color;
|
||||||
|
color: $ui-base-color;
|
||||||
|
border-radius: 8px;
|
||||||
|
height: 80vh;
|
||||||
|
width: 80vw;
|
||||||
|
max-width: 740px;
|
||||||
|
max-height: 450px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 24px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal__navigation {
|
||||||
|
background: $primary-text-color;
|
||||||
|
color: $ui-base-color;
|
||||||
|
width: 200px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.settings-modal__navigation-item, .settings-modal__navigation-close {
|
||||||
|
display: block;
|
||||||
|
padding: 15px 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal__navigation-item {
|
||||||
|
background: $primary-text-color;
|
||||||
|
color: inherit;
|
||||||
|
border-bottom: 1px $ui-primary-color solid;
|
||||||
|
transition: background .3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $ui-secondary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: $ui-highlight-color;
|
||||||
|
color: $primary-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal__navigation-close {
|
||||||
|
background: $error-value-color;
|
||||||
|
color: $primary-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal__content {
|
||||||
|
display: block;
|
||||||
|
flex: auto;
|
||||||
|
padding: 15px 20px 15px 20px;
|
||||||
|
width: 360px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.onboard-sliders {
|
.onboard-sliders {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
max-width: 30px;
|
max-width: 30px;
|
||||||
|
|
|
@ -1,19 +1,5 @@
|
||||||
@import 'application';
|
@import 'application';
|
||||||
|
|
||||||
@include multi-columns('screen and (min-width: 1300px)', $parent: null) {
|
|
||||||
.column {
|
|
||||||
flex-grow: 1 !important;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.drawer {
|
|
||||||
flex-grow: 1 !important;
|
|
||||||
flex-basis: 200px !important;
|
|
||||||
min-width: 268px;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.muted {
|
.muted {
|
||||||
.status__content p, .status__content a {
|
.status__content p, .status__content a {
|
||||||
color: lighten($ui-base-color, 35%);
|
color: lighten($ui-base-color, 35%);
|
||||||
|
|
Loading…
Reference in New Issue