Add column width customize features

This commit is contained in:
noellabo 2022-09-23 21:06:47 +09:00
parent 2bee3e3fdb
commit c297bf5471
53 changed files with 890 additions and 179 deletions

View file

@ -95,6 +95,7 @@ class Settings::PreferencesController < Settings::BaseController
:setting_confirm_follow_from_bot, :setting_confirm_follow_from_bot,
:setting_default_search_searchability, :setting_default_search_searchability,
:setting_show_reload_button, :setting_show_reload_button,
:setting_default_column_width,
notification_emails: %i(follow follow_request reblog favourite emoji_reaction status_reference mention digest report pending_account trending_tag), notification_emails: %i(follow follow_request reblog favourite emoji_reaction status_reference mention digest report pending_account trending_tag),
interactions: %i(must_be_follower must_be_following must_be_following_dm must_be_dm_to_send_email must_be_following_reference) interactions: %i(must_be_follower must_be_following must_be_following_dm must_be_dm_to_send_email must_be_following_reference)
) )

View file

@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { supportsPassiveEvents } from 'detect-passive-events'; import { supportsPassiveEvents } from 'detect-passive-events';
import { scrollTop } from '../scroll'; import { scrollTop } from '../scroll';
import classNames from 'classnames';
export default class Column extends React.PureComponent { export default class Column extends React.PureComponent {
@ -9,6 +10,7 @@ export default class Column extends React.PureComponent {
children: PropTypes.node, children: PropTypes.node,
label: PropTypes.string, label: PropTypes.string,
bindToDocument: PropTypes.bool, bindToDocument: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
scrollTop () { scrollTop () {
@ -50,10 +52,10 @@ export default class Column extends React.PureComponent {
} }
render () { render () {
const { label, children } = this.props; const { label, columnWidth, children } = this.props;
return ( return (
<div role='region' aria-label={label} className='column' ref={this.setRef}> <div role='region' aria-label={label} className={classNames('column', columnWidth)} ref={this.setRef}>
{children} {children}
</div> </div>
); );

View file

@ -1,10 +1,11 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import classNames from 'classnames'; import classNames from 'classnames';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
import { enableEmptyColumn } from 'mastodon/initial_state'; import { enableEmptyColumn, defaultColumnWidth } from 'mastodon/initial_state';
const messages = defineMessages({ const messages = defineMessages({
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' }, show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
@ -13,7 +14,20 @@ const messages = defineMessages({
moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' }, moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' },
}); });
export default @injectIntl const column_width_message = [
{ id: 'x080', defaultMessage: <FormattedMessage id={'column_width.x080'} defaultMessage='80%' /> },
{ id: 'x100', defaultMessage: <FormattedMessage id={'column_width.x100'} defaultMessage='100%' /> },
{ id: 'x125', defaultMessage: <FormattedMessage id={'column_width.x125'} defaultMessage='125%' /> },
{ id: 'x150', defaultMessage: <FormattedMessage id={'column_width.x150'} defaultMessage='150%' /> },
{ id: 'free', defaultMessage: <FormattedMessage id={'column_width.free'} defaultMessage='Free' /> },
];
const mapStateToProps = (state, { columnWidth }) => ({
columnWidth: columnWidth ?? defaultColumnWidth,
});
export default @connect(mapStateToProps)
@injectIntl
class ColumnHeader extends React.PureComponent { class ColumnHeader extends React.PureComponent {
static contextTypes = { static contextTypes = {
@ -36,6 +50,8 @@ class ColumnHeader extends React.PureComponent {
onClick: PropTypes.func, onClick: PropTypes.func,
appendContent: PropTypes.node, appendContent: PropTypes.node,
collapseIssues: PropTypes.bool, collapseIssues: PropTypes.bool,
columnWidth: PropTypes.string,
onWidthChange: PropTypes.func,
}; };
state = { state = {
@ -88,8 +104,15 @@ class ColumnHeader extends React.PureComponent {
this.props.onPin(); this.props.onPin();
} }
handleChangeWidth = e => {
e.stopPropagation();
if (this.props.onWidthChange) {
this.props.onWidthChange(e.target.value);
}
}
render () { render () {
const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues } = this.props; const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder, appendContent, collapseIssues, columnWidth } = this.props;
const { collapsed, animating } = this.state; const { collapsed, animating } = this.state;
const wrapperClassName = classNames('column-header__wrapper', { const wrapperClassName = classNames('column-header__wrapper', {
@ -146,11 +169,25 @@ class ColumnHeader extends React.PureComponent {
); );
} }
const widthButton = (
<div className='column-settings__row' role='group' key='column-width'>
<div className='column-width' role='group'>
{column_width_message.map(({ id, defaultMessage }) => (
<label key={id} className={classNames('column-width__item', { active: columnWidth === id })}>
<input name='width' type='radio' value={id} checked={columnWidth === id} onChange={this.handleChangeWidth} />
{defaultMessage}
</label>
))}
</div>
</div>
);
const collapsedContent = [ const collapsedContent = [
extraContent, extraContent,
]; ];
if (multiColumn) { if (multiColumn) {
collapsedContent.unshift(widthButton);
collapsedContent.push(moveButtons); collapsedContent.push(moveButtons);
collapsedContent.push(pinButton); collapsedContent.push(pinButton);
} }

View file

@ -17,7 +17,8 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs'; import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
import MissingIndicator from 'mastodon/components/missing_indicator'; import MissingIndicator from 'mastodon/components/missing_indicator';
import TimelineHint from 'mastodon/components/timeline_hint'; import TimelineHint from 'mastodon/components/timeline_hint';
import { new_features_policy } from 'mastodon/initial_state'; import { new_features_policy, defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.account', defaultMessage: 'Account' }, title: { id: 'column.account', defaultMessage: 'Account' },
@ -36,6 +37,7 @@ const mapStateToProps = (state, { params: { accountId } }) => ({
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false), blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true), advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true),
hideRelation: state.getIn(['settings', 'account', 'other', 'hideRelation'], false), hideRelation: state.getIn(['settings', 'account', 'other', 'hideRelation'], false),
columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth),
}); });
const RemoteHint = ({ url }) => ( const RemoteHint = ({ url }) => (
@ -63,6 +65,7 @@ class AccountConversations extends ImmutablePureComponent {
isAccount: PropTypes.bool, isAccount: PropTypes.bool,
suspended: PropTypes.bool, suspended: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
componentWillMount () { componentWillMount () {
@ -93,12 +96,16 @@ class AccountConversations extends ImmutablePureComponent {
this.column.scrollTop(); this.column.scrollTop();
} }
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['account', 'columnWidth'], value));
}
setRef = c => { setRef = c => {
this.column = c; this.column = c;
} }
render () { render () {
const { statusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, hideRelation, intl } = this.props; const { statusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, hideRelation, columnWidth, intl } = this.props;
if (!isAccount) { if (!isAccount) {
return ( return (
@ -128,7 +135,7 @@ class AccountConversations extends ImmutablePureComponent {
} }
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='user' icon='user'
active={false} active={false}
@ -136,6 +143,8 @@ class AccountConversations extends ImmutablePureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={false} pinned={false}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
<ColumnSettingsContainer /> <ColumnSettingsContainer />
</ColumnHeader> </ColumnHeader>

View file

@ -16,8 +16,9 @@ import ScrollContainer from 'mastodon/containers/scroll_container';
import LoadMore from 'mastodon/components/load_more'; import LoadMore from 'mastodon/components/load_more';
import MissingIndicator from 'mastodon/components/missing_indicator'; import MissingIndicator from 'mastodon/components/missing_indicator';
import { openModal } from 'mastodon/actions/modal'; import { openModal } from 'mastodon/actions/modal';
import { new_features_policy } from 'mastodon/initial_state'; import { new_features_policy, defaultColumnWidth } from 'mastodon/initial_state';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { changeSetting } from '../../actions/settings';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.account', defaultMessage: 'Account' }, title: { id: 'column.account', defaultMessage: 'Account' },
@ -32,6 +33,7 @@ const mapStateToProps = (state, props) => ({
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false), blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true), advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true),
hideRelation: state.getIn(['settings', 'account', 'other', 'hideRelation'], false), hideRelation: state.getIn(['settings', 'account', 'other', 'hideRelation'], false),
columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth),
}); });
class LoadMoreMedia extends ImmutablePureComponent { class LoadMoreMedia extends ImmutablePureComponent {
@ -72,6 +74,7 @@ class AccountGallery extends ImmutablePureComponent {
blockedBy: PropTypes.bool, blockedBy: PropTypes.bool,
suspended: PropTypes.bool, suspended: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
state = { state = {
@ -140,12 +143,16 @@ class AccountGallery extends ImmutablePureComponent {
this.column.scrollTop(); this.column.scrollTop();
} }
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['account', 'columnWidth'], value));
}
setRef = c => { setRef = c => {
this.column = c; this.column = c;
} }
render () { render () {
const { attachments, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended, hideRelation, intl } = this.props; const { attachments, isLoading, hasMore, isAccount, multiColumn, blockedBy, suspended, hideRelation, columnWidth, intl } = this.props;
const { width } = this.state; const { width } = this.state;
if (!isAccount) { if (!isAccount) {
@ -179,7 +186,7 @@ class AccountGallery extends ImmutablePureComponent {
} }
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='user' icon='user'
active={false} active={false}
@ -187,6 +194,8 @@ class AccountGallery extends ImmutablePureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={false} pinned={false}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
<ColumnSettingsContainer /> <ColumnSettingsContainer />
</ColumnHeader> </ColumnHeader>

View file

@ -17,9 +17,10 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs'; import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
import MissingIndicator from 'mastodon/components/missing_indicator'; import MissingIndicator from 'mastodon/components/missing_indicator';
import TimelineHint from 'mastodon/components/timeline_hint'; import TimelineHint from 'mastodon/components/timeline_hint';
import { me, new_features_policy } from 'mastodon/initial_state'; import { me, new_features_policy, defaultColumnWidth } from 'mastodon/initial_state';
import { connectTimeline, disconnectTimeline } from 'mastodon/actions/timelines'; import { connectTimeline, disconnectTimeline } from 'mastodon/actions/timelines';
import { fetchFeaturedTags } from '../../actions/featured_tags'; import { fetchFeaturedTags } from '../../actions/featured_tags';
import { changeSetting } from '../../actions/settings';
const emptyList = ImmutableList(); const emptyList = ImmutableList();
@ -54,6 +55,7 @@ const mapStateToProps = (state, { params: { accountId, tagged }, about, withRepl
withoutReblogs, withoutReblogs,
showPostsInAbout, showPostsInAbout,
hideRelation, hideRelation,
columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth),
}; };
}; };
@ -90,6 +92,7 @@ class AccountTimeline extends ImmutablePureComponent {
remote: PropTypes.bool, remote: PropTypes.bool,
remoteUrl: PropTypes.string, remoteUrl: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
static defaultProps = { static defaultProps = {
@ -169,12 +172,16 @@ class AccountTimeline extends ImmutablePureComponent {
this.column.scrollTop(); this.column.scrollTop();
} }
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['account', 'columnWidth'], value));
}
setRef = c => { setRef = c => {
this.column = c; this.column = c;
} }
render () { render () {
const { intl, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, remote, remoteUrl, about, withReplies, posts, advancedMode, hideFeaturedTags, showPostsInAbout, hideRelation } = this.props; const { intl, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy, suspended, isAccount, multiColumn, remote, remoteUrl, about, withReplies, posts, advancedMode, hideFeaturedTags, showPostsInAbout, hideRelation, columnWidth } = this.props;
if (!isAccount) { if (!isAccount) {
return ( return (
@ -210,7 +217,7 @@ class AccountTimeline extends ImmutablePureComponent {
const remoteMessage = (!about && remote) ? <RemoteHint url={remoteUrl} /> : null; const remoteMessage = (!about && remote) ? <RemoteHint url={remoteUrl} /> : null;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='user' icon='user'
active={false} active={false}
@ -218,6 +225,8 @@ class AccountTimeline extends ImmutablePureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={false} pinned={false}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
<ColumnSettingsContainer /> <ColumnSettingsContainer />
</ColumnHeader> </ColumnHeader>

View file

@ -11,6 +11,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import AccountContainer from '../../containers/account_container'; import AccountContainer from '../../containers/account_container';
import { fetchBlocks, expandBlocks } from '../../actions/blocks'; import { fetchBlocks, expandBlocks } from '../../actions/blocks';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import { defaultColumnWidth } from 'mastodon/initial_state';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.blocks', defaultMessage: 'Blocked users' }, heading: { id: 'column.blocks', defaultMessage: 'Blocked users' },
@ -20,6 +21,7 @@ const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'blocks', 'items']), accountIds: state.getIn(['user_lists', 'blocks', 'items']),
hasMore: !!state.getIn(['user_lists', 'blocks', 'next']), hasMore: !!state.getIn(['user_lists', 'blocks', 'next']),
isLoading: state.getIn(['user_lists', 'blocks', 'isLoading'], true), isLoading: state.getIn(['user_lists', 'blocks', 'isLoading'], true),
columnWidth: defaultColumnWidth,
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -34,6 +36,7 @@ class Blocks extends ImmutablePureComponent {
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
componentWillMount () { componentWillMount () {
@ -45,7 +48,7 @@ class Blocks extends ImmutablePureComponent {
}, 300, { leading: true }); }, 300, { leading: true });
render () { render () {
const { intl, accountIds, hasMore, multiColumn, isLoading } = this.props; const { intl, accountIds, hasMore, multiColumn, isLoading, columnWidth } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -58,7 +61,7 @@ class Blocks extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />; const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
return ( return (
<Column bindToDocument={!multiColumn} icon='ban' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='ban' heading={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnBackButtonSlim /> <ColumnBackButtonSlim />
<ScrollableList <ScrollableList
scrollKey='blocks' scrollKey='blocks'

View file

@ -10,16 +10,27 @@ import StatusList from '../../components/status_list';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
}); });
const mapStateToProps = state => ({ const mapStateToProps = (state, { columnId }) => {
statusIds: state.getIn(['status_lists', 'bookmarks', 'items']), const uuid = columnId;
isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true), const columns = state.getIn(['settings', 'columns']);
hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']), const index = columns.findIndex(c => c.get('uuid') === uuid);
}); const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'bookmarked_statuses', 'columnWidth']);
return {
statusIds: state.getIn(['status_lists', 'bookmarks', 'items']),
isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -31,6 +42,7 @@ class Bookmarks extends ImmutablePureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
}; };
@ -66,14 +78,24 @@ class Bookmarks extends ImmutablePureComponent {
this.props.dispatch(expandBookmarkedStatuses()); this.props.dispatch(expandBookmarkedStatuses());
}, 300, { leading: true }) }, 300, { leading: true })
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['bookmarked_statuses', 'columnWidth'], value));
}
}
render () { render () {
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; const { intl, statusIds, columnId, multiColumn, hasMore, isLoading, columnWidth } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses' defaultMessage="You don't have any bookmarked toots yet. When you bookmark one, it will show up here." />; const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses' defaultMessage="You don't have any bookmarked toots yet. When you bookmark one, it will show up here." />;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='bookmark' icon='bookmark'
title={intl.formatMessage(messages.heading)} title={intl.formatMessage(messages.heading)}
@ -82,6 +104,8 @@ class Bookmarks extends ImmutablePureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
showBackButton showBackButton
/> />

View file

@ -13,6 +13,7 @@ import NewCircleForm from './components/new_circle_form';
import Circle from './components/circle'; import Circle from './components/circle';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import { defaultColumnWidth } from 'mastodon/initial_state';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.circles', defaultMessage: 'Circles' }, heading: { id: 'column.circles', defaultMessage: 'Circles' },
@ -29,6 +30,7 @@ const getOrderedCircles = createSelector([state => state.get('circles')], circle
const mapStateToProps = state => ({ const mapStateToProps = state => ({
circles: getOrderedCircles(state), circles: getOrderedCircles(state),
columnWidth: defaultColumnWidth,
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -41,6 +43,7 @@ class Circles extends ImmutablePureComponent {
circles: ImmutablePropTypes.list, circles: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
componentWillMount () { componentWillMount () {
@ -48,7 +51,7 @@ class Circles extends ImmutablePureComponent {
} }
render () { render () {
const { intl, circles, multiColumn } = this.props; const { intl, circles, multiColumn, columnWidth } = this.props;
if (!circles) { if (!circles) {
return ( return (
@ -61,7 +64,7 @@ class Circles extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.circles' defaultMessage="You don't have any circles yet. When you create one, it will show up here." />; const emptyMessage = <FormattedMessage id='empty_column.circles' defaultMessage="You don't have any circles yet. When you create one, it will show up here." />;
return ( return (
<Column bindToDocument={!multiColumn} icon='user-circle' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='user-circle' heading={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnBackButtonSlim /> <ColumnBackButtonSlim />
<NewCircleForm /> <NewCircleForm />

View file

@ -8,12 +8,25 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { connectDirectStream } from '../../actions/streaming'; import { connectDirectStream } from '../../actions/streaming';
import ConversationsListContainer from './containers/conversations_list_container'; import ConversationsListContainer from './containers/conversations_list_container';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.direct', defaultMessage: 'Direct messages' }, title: { id: 'column.direct', defaultMessage: 'Direct messages' },
}); });
export default @connect() const mapStateToProps = (state, { columnId }) => {
const uuid = columnId;
const columns = state.getIn(['settings', 'columns']);
const index = columns.findIndex(c => c.get('uuid') === uuid);
const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'direct', 'columnWidth']);
return {
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps)
@injectIntl @injectIntl
class DirectTimeline extends React.PureComponent { class DirectTimeline extends React.PureComponent {
@ -23,6 +36,7 @@ class DirectTimeline extends React.PureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool, hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
handlePin = () => { handlePin = () => {
@ -69,12 +83,22 @@ class DirectTimeline extends React.PureComponent {
this.props.dispatch(expandConversations({ maxId })); this.props.dispatch(expandConversations({ maxId }));
} }
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['direct', 'columnWidth'], value));
}
}
render () { render () {
const { intl, hasUnread, columnId, multiColumn } = this.props; const { intl, hasUnread, columnId, multiColumn, columnWidth } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='envelope' icon='envelope'
active={hasUnread} active={hasUnread}
@ -84,6 +108,8 @@ class DirectTimeline extends React.PureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
/> />
<ConversationsListContainer <ConversationsListContainer

View file

@ -13,6 +13,8 @@ import RadioButton from 'mastodon/components/radio_button';
import classNames from 'classnames'; import classNames from 'classnames';
import LoadMore from 'mastodon/components/load_more'; import LoadMore from 'mastodon/components/load_more';
import ScrollContainer from 'mastodon/containers/scroll_container'; import ScrollContainer from 'mastodon/containers/scroll_container';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.directory', defaultMessage: 'Browse profiles' }, title: { id: 'column.directory', defaultMessage: 'Browse profiles' },
@ -26,6 +28,7 @@ const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'directory', 'items'], ImmutableList()), accountIds: state.getIn(['user_lists', 'directory', 'items'], ImmutableList()),
isLoading: state.getIn(['user_lists', 'directory', 'isLoading'], true), isLoading: state.getIn(['user_lists', 'directory', 'isLoading'], true),
domain: state.getIn(['meta', 'domain']), domain: state.getIn(['meta', 'domain']),
columnWidth: state.getIn(['settings', 'directory', 'columnWidth'], defaultColumnWidth),
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -43,6 +46,7 @@ class Directory extends React.PureComponent {
columnId: PropTypes.string, columnId: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
domain: PropTypes.string.isRequired, domain: PropTypes.string.isRequired,
params: PropTypes.shape({ params: PropTypes.shape({
order: PropTypes.string, order: PropTypes.string,
@ -123,8 +127,12 @@ class Directory extends React.PureComponent {
dispatch(expandDirectory(this.getParams(this.props, this.state))); dispatch(expandDirectory(this.getParams(this.props, this.state)));
} }
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['directory', 'columnWidth'], value));
}
render () { render () {
const { isLoading, accountIds, intl, columnId, multiColumn, domain } = this.props; const { isLoading, accountIds, intl, columnId, multiColumn, domain, columnWidth } = this.props;
const { order, local } = this.getParams(this.props, this.state); const { order, local } = this.getParams(this.props, this.state);
const pinned = !!columnId; const pinned = !!columnId;
@ -151,7 +159,7 @@ class Directory extends React.PureComponent {
); );
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='address-book-o' icon='address-book-o'
title={intl.formatMessage(messages.title)} title={intl.formatMessage(messages.title)}
@ -160,6 +168,8 @@ class Directory extends React.PureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
/> />
{multiColumn && !pinned ? <ScrollContainer scrollKey='directory'>{scrollableArea}</ScrollContainer> : scrollableArea} {multiColumn && !pinned ? <ScrollContainer scrollKey='directory'>{scrollableArea}</ScrollContainer> : scrollableArea}

View file

@ -11,6 +11,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import DomainContainer from '../../containers/domain_container'; import DomainContainer from '../../containers/domain_container';
import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks'; import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import { defaultColumnWidth } from 'mastodon/initial_state';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' }, heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
@ -20,6 +21,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({ const mapStateToProps = state => ({
domains: state.getIn(['domain_lists', 'blocks', 'items']), domains: state.getIn(['domain_lists', 'blocks', 'items']),
hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']), hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']),
columnWidth: defaultColumnWidth,
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -33,6 +35,7 @@ class Blocks extends ImmutablePureComponent {
domains: ImmutablePropTypes.orderedSet, domains: ImmutablePropTypes.orderedSet,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
componentWillMount () { componentWillMount () {
@ -44,7 +47,7 @@ class Blocks extends ImmutablePureComponent {
}, 300, { leading: true }); }, 300, { leading: true });
render () { render () {
const { intl, domains, hasMore, multiColumn } = this.props; const { intl, domains, hasMore, multiColumn, columnWidth } = this.props;
if (!domains) { if (!domains) {
return ( return (
@ -57,7 +60,7 @@ class Blocks extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no blocked domains yet.' />; const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no blocked domains yet.' />;
return ( return (
<Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnBackButtonSlim /> <ColumnBackButtonSlim />
<ScrollableList <ScrollableList
scrollKey='domain_blocks' scrollKey='domain_blocks'

View file

@ -9,11 +9,15 @@ import { expandDomainTimeline } from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import ColumnSettingsContainer from './containers/column_settings_container'; import ColumnSettingsContainer from './containers/column_settings_container';
import { connectDomainStream } from '../../actions/streaming'; import { connectDomainStream } from '../../actions/streaming';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
const uuid = props.columnId; const uuid = props.columnId;
const columns = state.getIn(['settings', 'columns']); const columns = state.getIn(['settings', 'columns']);
const index = columns.findIndex(c => c.get('uuid') === uuid); const index = columns.findIndex(c => c.get('uuid') === uuid);
const columnWidth = (props.columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'domain', 'columnWidth']);
const onlyMedia = (props.columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'domain', 'other', 'onlyMedia']); const onlyMedia = (props.columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'domain', 'other', 'onlyMedia']);
const withoutMedia = (props.columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutMedia']) : state.getIn(['settings', 'domain', 'other', 'withoutMedia']); const withoutMedia = (props.columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutMedia']) : state.getIn(['settings', 'domain', 'other', 'withoutMedia']);
const withoutBot = (props.columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutBot']) : state.getIn(['settings', 'domain', 'other', 'withoutBot']); const withoutBot = (props.columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutBot']) : state.getIn(['settings', 'domain', 'other', 'withoutBot']);
@ -26,6 +30,7 @@ const mapStateToProps = (state, props) => {
withoutMedia, withoutMedia,
withoutBot, withoutBot,
domain: domain, domain: domain,
columnWidth: columnWidth ?? defaultColumnWidth,
}; };
}; };
@ -50,6 +55,7 @@ class DomainTimeline extends React.PureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool, hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
onlyMedia: PropTypes.bool, onlyMedia: PropTypes.bool,
withoutMedia: PropTypes.bool, withoutMedia: PropTypes.bool,
withoutBot: PropTypes.bool, withoutBot: PropTypes.bool,
@ -109,12 +115,22 @@ class DomainTimeline extends React.PureComponent {
dispatch(expandDomainTimeline(domain, { maxId, onlyMedia, withoutMedia, withoutBot })); dispatch(expandDomainTimeline(domain, { maxId, onlyMedia, withoutMedia, withoutBot }));
} }
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['domain', 'columnWidth'], value));
}
}
render () { render () {
const { hasUnread, columnId, multiColumn, onlyMedia, withoutMedia, withoutBot, domain } = this.props; const { hasUnread, columnId, multiColumn, onlyMedia, withoutMedia, withoutBot, domain, columnWidth } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={domain}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={domain} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='users' icon='users'
active={hasUnread} active={hasUnread}
@ -125,6 +141,8 @@ class DomainTimeline extends React.PureComponent {
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
showBackButton showBackButton
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
<ColumnSettingsContainer columnId={columnId} /> <ColumnSettingsContainer columnId={columnId} />
</ColumnHeader> </ColumnHeader>

View file

@ -10,16 +10,27 @@ import StatusList from '../../components/status_list';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.emoji_reactions', defaultMessage: 'EmojiReactions' }, heading: { id: 'column.emoji_reactions', defaultMessage: 'EmojiReactions' },
}); });
const mapStateToProps = state => ({ const mapStateToProps = (state, { columnId }) => {
statusIds: state.getIn(['status_lists', 'emoji_reactions', 'items']), const uuid = columnId;
isLoading: state.getIn(['status_lists', 'emoji_reactions', 'isLoading'], true), const columns = state.getIn(['settings', 'columns']);
hasMore: !!state.getIn(['status_lists', 'emoji_reactions', 'next']), const index = columns.findIndex(c => c.get('uuid') === uuid);
}); const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'emoji_reactioned_statuses', 'columnWidth']);
return {
statusIds: state.getIn(['status_lists', 'emoji_reactions', 'items']),
isLoading: state.getIn(['status_lists', 'emoji_reactions', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'emoji_reactions', 'next']),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -31,6 +42,7 @@ class EmojiReactions extends ImmutablePureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
}; };
@ -66,14 +78,24 @@ class EmojiReactions extends ImmutablePureComponent {
this.props.dispatch(expandEmojiReactionedStatuses()); this.props.dispatch(expandEmojiReactionedStatuses());
}, 300, { leading: true }) }, 300, { leading: true })
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['emoji_reactioned_statuses', 'columnWidth'], value));
}
}
render () { render () {
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; const { intl, statusIds, columnId, multiColumn, hasMore, isLoading, columnWidth } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.emoji_reactioned_statuses' defaultMessage="You don't have any reaction posts yet. When you reaction one, it will show up here." />; const emptyMessage = <FormattedMessage id='empty_column.emoji_reactioned_statuses' defaultMessage="You don't have any reaction posts yet. When you reaction one, it will show up here." />;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='star' icon='star'
title={intl.formatMessage(messages.heading)} title={intl.formatMessage(messages.heading)}
@ -82,6 +104,8 @@ class EmojiReactions extends ImmutablePureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
showBackButton showBackButton
/> />

View file

@ -16,6 +16,9 @@ import { createSelector } from 'reselect';
import { Map as ImmutableMap } from 'immutable'; import { Map as ImmutableMap } from 'immutable';
import ReactedHeaderContaier from '../reactioned/containers/header_container'; import ReactedHeaderContaier from '../reactioned/containers/header_container';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' }, refresh: { id: 'refresh', defaultMessage: 'Refresh' },
@ -23,12 +26,20 @@ const messages = defineMessages({
const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap())); const customEmojiMap = createSelector([state => state.get('custom_emojis')], items => items.reduce((map, emoji) => map.set(emoji.get('shortcode'), emoji), ImmutableMap()));
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, { columnId, params }) => {
emojiReactions: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'items']), const uuid = columnId;
isLoading: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'isLoading'], true), const columns = state.getIn(['settings', 'columns']);
hasMore: !!state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'next']), const index = columns.findIndex(c => c.get('uuid') === uuid);
emojiMap: customEmojiMap(state), const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'emoji_reactions', 'columnWidth']);
});
return {
emojiReactions: state.getIn(['user_lists', 'emoji_reactioned_by', params.statusId, 'items']),
isLoading: state.getIn(['user_lists', 'emoji_reactioned_by', params.statusId, 'isLoading'], true),
hasMore: !!state.getIn(['user_lists', 'emoji_reactioned_by', params.statusId, 'next']),
emojiMap: customEmojiMap(state),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
class Reaction extends ImmutablePureComponent { class Reaction extends ImmutablePureComponent {
@ -65,6 +76,7 @@ class EmojiReactions extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
emojiReactions: ImmutablePropTypes.list, emojiReactions: ImmutablePropTypes.list,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
emojiMap: ImmutablePropTypes.map.isRequired, emojiMap: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
@ -91,8 +103,18 @@ class EmojiReactions extends ImmutablePureComponent {
this.props.dispatch(expandEmojiReactions(this.props.params.statusId)); this.props.dispatch(expandEmojiReactions(this.props.params.statusId));
}, 300, { leading: true }) }, 300, { leading: true })
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['emoji_reactions', 'columnWidth'], value));
}
}
render () { render () {
const { intl, emojiReactions, multiColumn, emojiMap, hasMore, isLoading } = this.props; const { intl, emojiReactions, multiColumn, emojiMap, hasMore, isLoading, columnWidth } = this.props;
if (!emojiReactions) { if (!emojiReactions) {
return ( return (
@ -105,10 +127,12 @@ class EmojiReactions extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.emoji_reactions' defaultMessage='No one has reactioned this post yet. When someone does, they will show up here.' />; const emptyMessage = <FormattedMessage id='empty_column.emoji_reactions' defaultMessage='No one has reactioned this post yet. When someone does, they will show up here.' />;
return ( return (
<Column bindToDocument={!multiColumn}> <Column bindToDocument={!multiColumn} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
extraButton={( extraButton={(
<button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button> <button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
)} )}

View file

@ -10,16 +10,27 @@ import StatusList from '../../components/status_list';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.favourites', defaultMessage: 'Favourites' }, heading: { id: 'column.favourites', defaultMessage: 'Favourites' },
}); });
const mapStateToProps = state => ({ const mapStateToProps = (state, { columnId }) => {
statusIds: state.getIn(['status_lists', 'favourites', 'items']), const uuid = columnId;
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true), const columns = state.getIn(['settings', 'columns']);
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']), const index = columns.findIndex(c => c.get('uuid') === uuid);
}); const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'favourited_statuses', 'columnWidth']);
return {
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -31,6 +42,7 @@ class Favourites extends ImmutablePureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
}; };
@ -66,14 +78,24 @@ class Favourites extends ImmutablePureComponent {
this.props.dispatch(expandFavouritedStatuses()); this.props.dispatch(expandFavouritedStatuses());
}, 300, { leading: true }) }, 300, { leading: true })
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['favourited_statuses', 'columnWidth'], value));
}
}
render () { render () {
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props; const { intl, statusIds, columnId, multiColumn, hasMore, isLoading, columnWidth } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite posts yet. When you favourite one, it will show up here." />; const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite posts yet. When you favourite one, it will show up here." />;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='star' icon='star'
title={intl.formatMessage(messages.heading)} title={intl.formatMessage(messages.heading)}
@ -82,6 +104,8 @@ class Favourites extends ImmutablePureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
showBackButton showBackButton
/> />

View file

@ -13,16 +13,27 @@ import Icon from 'mastodon/components/icon';
import ColumnHeader from '../../components/column_header'; import ColumnHeader from '../../components/column_header';
import ReactedHeaderContaier from '../reactioned/containers/header_container'; import ReactedHeaderContaier from '../reactioned/containers/header_container';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' }, refresh: { id: 'refresh', defaultMessage: 'Refresh' },
}); });
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, { columnId, params }) => {
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'items']), const uuid = columnId;
isLoading: state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'isLoading'], true), const columns = state.getIn(['settings', 'columns']);
hasMore: !!state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'next']), const index = columns.findIndex(c => c.get('uuid') === uuid);
}); const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'favourites', 'columnWidth']);
return {
accountIds: state.getIn(['user_lists', 'favourited_by', params.statusId, 'items']),
isLoading: state.getIn(['user_lists', 'favourited_by', params.statusId, 'isLoading'], true),
hasMore: !!state.getIn(['user_lists', 'favourited_by', params.statusId, 'next']),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -33,6 +44,7 @@ class Favourites extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
@ -58,8 +70,18 @@ class Favourites extends ImmutablePureComponent {
this.props.dispatch(expandFavourites(this.props.params.statusId)); this.props.dispatch(expandFavourites(this.props.params.statusId));
}, 300, { leading: true }) }, 300, { leading: true })
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['favourites', 'columnWidth'], value));
}
}
render () { render () {
const { intl, accountIds, multiColumn, hasMore, isLoading } = this.props; const { intl, accountIds, multiColumn, hasMore, isLoading, columnWidth } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -72,10 +94,12 @@ class Favourites extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favourited this post yet. When someone does, they will show up here.' />; const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favourited this post yet. When someone does, they will show up here.' />;
return ( return (
<Column bindToDocument={!multiColumn}> <Column bindToDocument={!multiColumn} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
extraButton={( extraButton={(
<button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button> <button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
)} )}

View file

@ -11,7 +11,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import AccountAuthorizeContainer from './containers/account_authorize_container'; import AccountAuthorizeContainer from './containers/account_authorize_container';
import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts'; import { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import { me } from '../../initial_state'; import { me, defaultColumnWidth } from '../../initial_state';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' }, heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' },
@ -23,6 +23,7 @@ const mapStateToProps = state => ({
hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']), hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']),
locked: !!state.getIn(['accounts', me, 'locked']), locked: !!state.getIn(['accounts', me, 'locked']),
domain: state.getIn(['meta', 'domain']), domain: state.getIn(['meta', 'domain']),
columnWidth: defaultColumnWidth,
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -39,6 +40,7 @@ class FollowRequests extends ImmutablePureComponent {
domain: PropTypes.string, domain: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
componentWillMount () { componentWillMount () {
@ -50,7 +52,7 @@ class FollowRequests extends ImmutablePureComponent {
}, 300, { leading: true }); }, 300, { leading: true });
render () { render () {
const { intl, accountIds, hasMore, multiColumn, locked, domain, isLoading } = this.props; const { intl, accountIds, hasMore, multiColumn, locked, domain, isLoading, columnWidth } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -72,7 +74,7 @@ class FollowRequests extends ImmutablePureComponent {
); );
return ( return (
<Column bindToDocument={!multiColumn} icon='user-plus' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='user-plus' heading={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnBackButtonSlim /> <ColumnBackButtonSlim />
<ScrollableList <ScrollableList
scrollKey='follow_requests' scrollKey='follow_requests'

View file

@ -19,7 +19,8 @@ import HeaderContainer from '../account_timeline/containers/header_container';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import MissingIndicator from 'mastodon/components/missing_indicator'; import MissingIndicator from 'mastodon/components/missing_indicator';
import TimelineHint from 'mastodon/components/timeline_hint'; import TimelineHint from 'mastodon/components/timeline_hint';
import { new_features_policy } from 'mastodon/initial_state'; import { new_features_policy, defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.account', defaultMessage: 'Account' }, title: { id: 'column.account', defaultMessage: 'Account' },
@ -34,6 +35,7 @@ const mapStateToProps = (state, props) => ({
isLoading: state.getIn(['user_lists', 'followers', props.params.accountId, 'isLoading'], true), isLoading: state.getIn(['user_lists', 'followers', props.params.accountId, 'isLoading'], true),
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false), blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true), advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true),
columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth),
}); });
const RemoteHint = ({ url }) => ( const RemoteHint = ({ url }) => (
@ -60,6 +62,7 @@ class Followers extends ImmutablePureComponent {
remote: PropTypes.bool, remote: PropTypes.bool,
remoteUrl: PropTypes.string, remoteUrl: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
componentWillMount () { componentWillMount () {
@ -84,12 +87,16 @@ class Followers extends ImmutablePureComponent {
this.column.scrollTop(); this.column.scrollTop();
} }
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['account', 'columnWidth'], value));
}
setRef = c => { setRef = c => {
this.column = c; this.column = c;
} }
render () { render () {
const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl, intl } = this.props; const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl, columnWidth, intl } = this.props;
if (!isAccount) { if (!isAccount) {
return ( return (
@ -120,7 +127,7 @@ class Followers extends ImmutablePureComponent {
const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null; const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='user' icon='user'
active={false} active={false}
@ -128,6 +135,8 @@ class Followers extends ImmutablePureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={false} pinned={false}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
<ColumnSettingsContainer /> <ColumnSettingsContainer />
</ColumnHeader> </ColumnHeader>

View file

@ -19,7 +19,8 @@ import HeaderContainer from '../account_timeline/containers/header_container';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import MissingIndicator from 'mastodon/components/missing_indicator'; import MissingIndicator from 'mastodon/components/missing_indicator';
import TimelineHint from 'mastodon/components/timeline_hint'; import TimelineHint from 'mastodon/components/timeline_hint';
import { new_features_policy } from 'mastodon/initial_state'; import { new_features_policy, defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.account', defaultMessage: 'Account' }, title: { id: 'column.account', defaultMessage: 'Account' },
@ -34,6 +35,7 @@ const mapStateToProps = (state, props) => ({
isLoading: state.getIn(['user_lists', 'following', props.params.accountId, 'isLoading'], true), isLoading: state.getIn(['user_lists', 'following', props.params.accountId, 'isLoading'], true),
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false), blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true), advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true),
columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth),
}); });
const RemoteHint = ({ url }) => ( const RemoteHint = ({ url }) => (
@ -60,6 +62,7 @@ class Following extends ImmutablePureComponent {
remote: PropTypes.bool, remote: PropTypes.bool,
remoteUrl: PropTypes.string, remoteUrl: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
componentWillMount () { componentWillMount () {
@ -84,12 +87,16 @@ class Following extends ImmutablePureComponent {
this.column.scrollTop(); this.column.scrollTop();
} }
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['account', 'columnWidth'], value));
}
setRef = c => { setRef = c => {
this.column = c; this.column = c;
} }
render () { render () {
const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl, intl } = this.props; const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, remote, remoteUrl, columnWidth, intl } = this.props;
if (!isAccount) { if (!isAccount) {
return ( return (
@ -120,7 +127,7 @@ class Following extends ImmutablePureComponent {
const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null; const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='user' icon='user'
active={false} active={false}
@ -128,6 +135,8 @@ class Following extends ImmutablePureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={false} pinned={false}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
<ColumnSettingsContainer /> <ColumnSettingsContainer />
</ColumnHeader> </ColumnHeader>

View file

@ -7,7 +7,7 @@ import { connect } from 'react-redux';
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';
import { me, profile_directory, showTrends, enable_limited_timeline, enableEmptyColumn } from '../../initial_state'; import { me, profile_directory, showTrends, enable_limited_timeline, enableEmptyColumn, defaultColumnWidth } from '../../initial_state';
import { fetchFollowRequests } from 'mastodon/actions/accounts'; import { fetchFollowRequests } from 'mastodon/actions/accounts';
import { fetchFavouriteDomains } from 'mastodon/actions/favourite_domains'; import { fetchFavouriteDomains } from 'mastodon/actions/favourite_domains';
import { fetchFavouriteTags } from 'mastodon/actions/favourite_tags'; import { fetchFavouriteTags } from 'mastodon/actions/favourite_tags';
@ -61,6 +61,7 @@ const mapStateToProps = state => ({
lists: getOrderedLists(state), lists: getOrderedLists(state),
favourite_domains: getOrderedDomains(state), favourite_domains: getOrderedDomains(state),
favourite_tags: getOrderedTags(state), favourite_tags: getOrderedTags(state),
columnWidth: defaultColumnWidth,
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
@ -94,6 +95,7 @@ class GettingStarted extends ImmutablePureComponent {
myAccount: ImmutablePropTypes.map.isRequired, myAccount: ImmutablePropTypes.map.isRequired,
columns: ImmutablePropTypes.list, columns: ImmutablePropTypes.list,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
fetchFollowRequests: PropTypes.func.isRequired, fetchFollowRequests: PropTypes.func.isRequired,
fetchFavouriteDomains: PropTypes.func.isRequired, fetchFavouriteDomains: PropTypes.func.isRequired,
fetchFavouriteTags: PropTypes.func.isRequired, fetchFavouriteTags: PropTypes.func.isRequired,
@ -118,7 +120,7 @@ class GettingStarted extends ImmutablePureComponent {
} }
render () { render () {
const { intl, myAccount, columns, multiColumn, unreadFollowRequests, lists, favourite_domains, favourite_tags } = this.props; const { intl, myAccount, columns, multiColumn, unreadFollowRequests, lists, favourite_domains, favourite_tags, columnWidth } = this.props;
const navItems = []; const navItems = [];
let height = (multiColumn) ? 0 : 60; let height = (multiColumn) ? 0 : 60;
@ -264,7 +266,7 @@ class GettingStarted extends ImmutablePureComponent {
} }
return ( return (
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.menu)}> <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.menu)} columnWidth={columnWidth}>
{multiColumn && <div className='column-header__wrapper'> {multiColumn && <div className='column-header__wrapper'>
<h1 className='column-header'> <h1 className='column-header'>
<button> <button>

View file

@ -13,6 +13,8 @@ import RadioButton from 'mastodon/components/radio_button';
import classNames from 'classnames'; import classNames from 'classnames';
import LoadMore from 'mastodon/components/load_more'; import LoadMore from 'mastodon/components/load_more';
import { ScrollContainer } from 'react-router-scroll-4'; import { ScrollContainer } from 'react-router-scroll-4';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.group_directory', defaultMessage: 'Browse groups' }, title: { id: 'column.group_directory', defaultMessage: 'Browse groups' },
@ -23,6 +25,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({ const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'group_directory', 'items'], ImmutableList()), accountIds: state.getIn(['user_lists', 'group_directory', 'items'], ImmutableList()),
isLoading: state.getIn(['user_lists', 'group_directory', 'isLoading'], true), isLoading: state.getIn(['user_lists', 'group_directory', 'isLoading'], true),
columnWidth: state.getIn(['settings', 'group_directory', 'columnWidth'], defaultColumnWidth),
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -40,6 +43,7 @@ class GroupDirectory extends React.PureComponent {
columnId: PropTypes.string, columnId: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
params: PropTypes.shape({ params: PropTypes.shape({
order: PropTypes.string, order: PropTypes.string,
}), }),
@ -107,8 +111,12 @@ class GroupDirectory extends React.PureComponent {
dispatch(expandGroupDirectory(this.getParams(this.props, this.state))); dispatch(expandGroupDirectory(this.getParams(this.props, this.state)));
} }
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['group_directory', 'columnWidth'], value));
}
render () { render () {
const { isLoading, accountIds, intl, columnId, multiColumn } = this.props; const { isLoading, accountIds, intl, columnId, multiColumn, columnWidth } = this.props;
const { order } = this.getParams(this.props, this.state); const { order } = this.getParams(this.props, this.state);
const pinned = !!columnId; const pinned = !!columnId;
@ -130,7 +138,7 @@ class GroupDirectory extends React.PureComponent {
); );
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='address-book-o' icon='address-book-o'
title={intl.formatMessage(messages.title)} title={intl.formatMessage(messages.title)}
@ -139,6 +147,8 @@ class GroupDirectory extends React.PureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
/> />
{multiColumn && !pinned ? <ScrollContainer scrollKey='group_directory'>{scrollableArea}</ScrollContainer> : scrollableArea} {multiColumn && !pinned ? <ScrollContainer scrollKey='group_directory'>{scrollableArea}</ScrollContainer> : scrollableArea}

View file

@ -15,6 +15,9 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import ColumnSettingsContainer from './containers/column_settings_container'; import ColumnSettingsContainer from './containers/column_settings_container';
import GroupDetail from './components/group_detail'; import GroupDetail from './components/group_detail';
import { connectGroupStream } from '../../actions/streaming'; import { connectGroupStream } from '../../actions/streaming';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.group', defaultMessage: 'Group timeline' }, title: { id: 'column.group', defaultMessage: 'Group timeline' },
@ -32,6 +35,7 @@ const makeMapStateToProps = () => {
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'group', 'other', 'onlyMedia']); const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'group', 'other', 'onlyMedia']);
const withoutMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutMedia']) : state.getIn(['settings', 'group', 'other', 'withoutMedia']); const withoutMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutMedia']) : state.getIn(['settings', 'group', 'other', 'withoutMedia']);
const timelineState = state.getIn(['timelines', `group:${id}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}${tagged ? `:${tagged}` : ''}`]); const timelineState = state.getIn(['timelines', `group:${id}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}${tagged ? `:${tagged}` : ''}`]);
const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'group', 'columnWidth']);
const account = getAccount(state, id); const account = getAccount(state, id);
return { return {
@ -39,6 +43,7 @@ const makeMapStateToProps = () => {
onlyMedia, onlyMedia,
withoutMedia, withoutMedia,
account, account,
columnWidth: columnWidth ?? defaultColumnWidth,
}; };
}; };
@ -66,6 +71,7 @@ class GroupTimeline extends React.PureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool, hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
onlyMedia: PropTypes.bool, onlyMedia: PropTypes.bool,
withoutMedia: PropTypes.bool, withoutMedia: PropTypes.bool,
}; };
@ -138,8 +144,18 @@ class GroupTimeline extends React.PureComponent {
this.setState({ animating: false }); this.setState({ animating: false });
} }
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['group', 'columnWidth'], value));
}
}
render () { render () {
const { intl, hasUnread, columnId, multiColumn, onlyMedia, withoutMedia, params: { id, tagged }, account } = this.props; const { intl, hasUnread, columnId, multiColumn, onlyMedia, withoutMedia, params: { id, tagged }, account, columnWidth } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const { collapsed, animating } = this.state; const { collapsed, animating } = this.state;
@ -179,7 +195,7 @@ class GroupTimeline extends React.PureComponent {
); );
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={title}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={title} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='users' icon='users'
active={hasUnread} active={hasUnread}
@ -190,6 +206,8 @@ class GroupTimeline extends React.PureComponent {
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
extraButton={groupDetailButton} extraButton={groupDetailButton}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
<ColumnSettingsContainer columnId={columnId} /> <ColumnSettingsContainer columnId={columnId} />
</ColumnHeader> </ColumnHeader>

View file

@ -14,16 +14,27 @@ import { isEqual } from 'lodash';
import { fetchHashtag, followHashtag, unfollowHashtag } from 'mastodon/actions/tags'; import { fetchHashtag, followHashtag, unfollowHashtag } from 'mastodon/actions/tags';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
import classNames from 'classnames'; import classNames from 'classnames';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' }, followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' },
unfollowHashtag: { id: 'hashtag.unfollow', defaultMessage: 'Unfollow hashtag' }, unfollowHashtag: { id: 'hashtag.unfollow', defaultMessage: 'Unfollow hashtag' },
}); });
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, { columnId, params }) => {
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0, const uuid = columnId;
tag: state.getIn(['tags', props.params.id]), const columns = state.getIn(['settings', 'columns']);
}); const index = columns.findIndex(c => c.get('uuid') === uuid);
const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'hashtag', 'columnWidth']);
return {
hasUnread: state.getIn(['timelines', `hashtag:${params.id}`, 'unread']) > 0,
tag: state.getIn(['tags', params.id]),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -38,6 +49,7 @@ class HashtagTimeline extends React.PureComponent {
hasUnread: PropTypes.bool, hasUnread: PropTypes.bool,
tag: ImmutablePropTypes.map, tag: ImmutablePropTypes.map,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
intl: PropTypes.object, intl: PropTypes.object,
}; };
@ -166,8 +178,18 @@ class HashtagTimeline extends React.PureComponent {
} }
} }
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['hashtag', 'columnWidth'], value));
}
}
render () { render () {
const { hasUnread, columnId, multiColumn, tag, intl } = this.props; const { hasUnread, columnId, multiColumn, tag, columnWidth, intl } = this.props;
const { id } = this.props.params; const { id } = this.props.params;
const pinned = !!columnId; const pinned = !!columnId;
@ -184,7 +206,7 @@ class HashtagTimeline extends React.PureComponent {
} }
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='hashtag' icon='hashtag'
active={hasUnread} active={hasUnread}
@ -196,6 +218,8 @@ class HashtagTimeline extends React.PureComponent {
multiColumn={multiColumn} multiColumn={multiColumn}
extraButton={followButton} extraButton={followButton}
showBackButton showBackButton
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
{columnId && <ColumnSettingsContainer columnId={columnId} />} {columnId && <ColumnSettingsContainer columnId={columnId} />}
</ColumnHeader> </ColumnHeader>

View file

@ -14,6 +14,9 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/an
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container'; import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
import classNames from 'classnames'; import classNames from 'classnames';
import IconWithBadge from 'mastodon/components/icon_with_badge'; import IconWithBadge from 'mastodon/components/icon_with_badge';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.home', defaultMessage: 'Home' }, title: { id: 'column.home', defaultMessage: 'Home' },
@ -21,14 +24,22 @@ const messages = defineMessages({
hide_announcements: { id: 'home.hide_announcements', defaultMessage: 'Hide announcements' }, hide_announcements: { id: 'home.hide_announcements', defaultMessage: 'Hide announcements' },
}); });
const mapStateToProps = state => ({ const mapStateToProps = (state, { columnId }) => {
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0, const uuid = columnId;
isPartial: state.getIn(['timelines', 'home', 'isPartial']), const columns = state.getIn(['settings', 'columns']);
hasAnnouncements: !state.getIn(['announcements', 'items']).isEmpty(), const index = columns.findIndex(c => c.get('uuid') === uuid);
unreadAnnouncements: state.getIn(['announcements', 'items']).count(item => !item.get('read')), const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'home', 'columnWidth']);
showAnnouncements: state.getIn(['announcements', 'show']),
visibilities: getHomeVisibilities(state), return {
}); hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
isPartial: state.getIn(['timelines', 'home', 'isPartial']),
hasAnnouncements: !state.getIn(['announcements', 'items']).isEmpty(),
unreadAnnouncements: state.getIn(['announcements', 'items']).count(item => !item.get('read')),
showAnnouncements: state.getIn(['announcements', 'show']),
visibilities: getHomeVisibilities(state),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -41,6 +52,7 @@ class HomeTimeline extends React.PureComponent {
isPartial: PropTypes.bool, isPartial: PropTypes.bool,
columnId: PropTypes.string, columnId: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasAnnouncements: PropTypes.bool, hasAnnouncements: PropTypes.bool,
unreadAnnouncements: PropTypes.number, unreadAnnouncements: PropTypes.number,
showAnnouncements: PropTypes.bool, showAnnouncements: PropTypes.bool,
@ -121,8 +133,18 @@ class HomeTimeline extends React.PureComponent {
this.props.dispatch(toggleShowAnnouncements()); this.props.dispatch(toggleShowAnnouncements());
} }
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['home', 'columnWidth'], value));
}
}
render () { render () {
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props; const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements, columnWidth } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
let announcementsButton = null; let announcementsButton = null;
@ -142,7 +164,7 @@ class HomeTimeline extends React.PureComponent {
} }
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='home' icon='home'
active={hasUnread} active={hasUnread}
@ -154,6 +176,8 @@ class HomeTimeline extends React.PureComponent {
multiColumn={multiColumn} multiColumn={multiColumn}
extraButton={announcementsButton} extraButton={announcementsButton}
appendContent={hasAnnouncements && showAnnouncements && <AnnouncementsContainer />} appendContent={hasAnnouncements && showAnnouncements && <AnnouncementsContainer />}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
<ColumnSettingsContainer /> <ColumnSettingsContainer />
</ColumnHeader> </ColumnHeader>

View file

@ -4,24 +4,31 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { defaultColumnWidth } from 'mastodon/initial_state';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' }, heading: { id: 'keyboard_shortcuts.heading', defaultMessage: 'Keyboard Shortcuts' },
}); });
export default @injectIntl const mapStateToProps = state => ({
columnWidth: defaultColumnWidth,
});
export default @connect(mapStateToProps)
@injectIntl
class KeyboardShortcuts extends ImmutablePureComponent { class KeyboardShortcuts extends ImmutablePureComponent {
static propTypes = { static propTypes = {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
render () { render () {
const { intl, multiColumn } = this.props; const { intl, multiColumn, columnWidth } = this.props;
return ( return (
<Column bindToDocument={!multiColumn} icon='question' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='question' heading={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnBackButtonSlim /> <ColumnBackButtonSlim />
<div className='keyboard-shortcuts scrollable optionally-scrollable'> <div className='keyboard-shortcuts scrollable optionally-scrollable'>
<table> <table>

View file

@ -9,15 +9,26 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { getLimitedVisibilities } from 'mastodon/selectors'; import { getLimitedVisibilities } from 'mastodon/selectors';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ColumnSettingsContainer from './containers/column_settings_container'; import ColumnSettingsContainer from './containers/column_settings_container';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.limited', defaultMessage: 'Limited' }, title: { id: 'column.limited', defaultMessage: 'Limited' },
}); });
const mapStateToProps = state => ({ const mapStateToProps = (state, { columnId }) => {
hasUnread: state.getIn(['timelines', 'limited', 'unread']) > 0, const uuid = columnId;
visibilities: getLimitedVisibilities(state), const columns = state.getIn(['settings', 'columns']);
}); const index = columns.findIndex(c => c.get('uuid') === uuid);
const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'limited', 'columnWidth']);
return {
hasUnread: state.getIn(['timelines', 'limited', 'unread']) > 0,
visibilities: getLimitedVisibilities(state),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -30,6 +41,7 @@ class LimitedTimeline extends React.PureComponent {
visibilities: PropTypes.arrayOf(PropTypes.string), visibilities: PropTypes.arrayOf(PropTypes.string),
columnId: PropTypes.string, columnId: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
handlePin = () => { handlePin = () => {
@ -75,12 +87,22 @@ class LimitedTimeline extends React.PureComponent {
} }
} }
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['limited', 'columnWidth'], value));
}
}
render () { render () {
const { intl, hasUnread, columnId, multiColumn } = this.props; const { intl, hasUnread, columnId, multiColumn, columnWidth } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='lock' icon='lock'
active={hasUnread} active={hasUnread}
@ -90,6 +112,8 @@ class LimitedTimeline extends React.PureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
<ColumnSettingsContainer /> <ColumnSettingsContainer />
</ColumnHeader> </ColumnHeader>

View file

@ -16,6 +16,9 @@ import MissingIndicator from '../../components/missing_indicator';
import LoadingIndicator from '../../components/loading_indicator'; import LoadingIndicator from '../../components/loading_indicator';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
import RadioButton from 'mastodon/components/radio_button'; import RadioButton from 'mastodon/components/radio_button';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' }, deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
@ -25,10 +28,18 @@ const messages = defineMessages({
list: { id: 'lists.replies_policy.list', defaultMessage: 'Members of the list' }, list: { id: 'lists.replies_policy.list', defaultMessage: 'Members of the list' },
}); });
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, { columnId, params }) => {
list: state.getIn(['lists', props.params.id]), const uuid = columnId;
hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0, const columns = state.getIn(['settings', 'columns']);
}); const index = columns.findIndex(c => c.get('uuid') === uuid);
const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'list', 'columnWidth']);
return {
list: state.getIn(['lists', params.id]),
hasUnread: state.getIn(['timelines', `list:${params.id}`, 'unread']) > 0,
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -44,6 +55,7 @@ class ListTimeline extends React.PureComponent {
columnId: PropTypes.string, columnId: PropTypes.string,
hasUnread: PropTypes.bool, hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]), list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
@ -140,8 +152,18 @@ class ListTimeline extends React.PureComponent {
dispatch(updateList(id, undefined, false, target.value)); dispatch(updateList(id, undefined, false, target.value));
} }
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['list', 'columnWidth'], value));
}
}
render () { render () {
const { hasUnread, columnId, multiColumn, list, intl } = this.props; const { hasUnread, columnId, multiColumn, list, columnWidth, intl } = this.props;
const { id } = this.props.params; const { id } = this.props.params;
const pinned = !!columnId; const pinned = !!columnId;
const title = list ? list.get('title') : id; const title = list ? list.get('title') : id;
@ -165,7 +187,7 @@ class ListTimeline extends React.PureComponent {
} }
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={title}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={title} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='list-ul' icon='list-ul'
active={hasUnread} active={hasUnread}
@ -175,6 +197,8 @@ class ListTimeline extends React.PureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
<div className='column-settings__row column-header__links'> <div className='column-settings__row column-header__links'>
<button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}> <button className='text-btn column-header__setting-btn' tabIndex='0' onClick={this.handleEditClick}>

View file

@ -13,6 +13,7 @@ import NewListForm from './components/new_list_form';
import List from './components/list'; import List from './components/list';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import { defaultColumnWidth } from 'mastodon/initial_state';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.lists', defaultMessage: 'Lists' }, heading: { id: 'column.lists', defaultMessage: 'Lists' },
@ -29,6 +30,7 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => {
const mapStateToProps = state => ({ const mapStateToProps = state => ({
lists: getOrderedLists(state), lists: getOrderedLists(state),
columnWidth: defaultColumnWidth,
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -41,6 +43,7 @@ class Lists extends ImmutablePureComponent {
lists: ImmutablePropTypes.list, lists: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
componentWillMount () { componentWillMount () {
@ -48,7 +51,7 @@ class Lists extends ImmutablePureComponent {
} }
render () { render () {
const { intl, lists, multiColumn } = this.props; const { intl, lists, multiColumn, columnWidth } = this.props;
if (!lists) { if (!lists) {
return ( return (
@ -61,7 +64,7 @@ class Lists extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />; const emptyMessage = <FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />;
return ( return (
<Column bindToDocument={!multiColumn} icon='list-ul' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='list-ul' heading={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnBackButtonSlim /> <ColumnBackButtonSlim />
<NewListForm /> <NewListForm />

View file

@ -11,12 +11,23 @@ import Column from '../ui/components/column';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import ColumnHeader from '../../components/column_header'; import ColumnHeader from '../../components/column_header';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, { columnId, params }) => {
accountIds: state.getIn(['user_lists', 'mentioned_by', props.params.statusId, 'items']), const uuid = columnId;
isLoading: state.getIn(['user_lists', 'mentioned_by', props.params.statusId, 'isLoading'], true), const columns = state.getIn(['settings', 'columns']);
hasMore: !!state.getIn(['user_lists', 'mentioned_by', props.params.statusId, 'next']), const index = columns.findIndex(c => c.get('uuid') === uuid);
}); const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'mentions', 'columnWidth']);
return {
accountIds: state.getIn(['user_lists', 'mentioned_by', params.statusId, 'items']),
isLoading: state.getIn(['user_lists', 'mentioned_by', params.statusId, 'isLoading'], true),
hasMore: !!state.getIn(['user_lists', 'mentioned_by', params.statusId, 'next']),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -27,6 +38,7 @@ class Mentions extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
@ -48,8 +60,18 @@ class Mentions extends ImmutablePureComponent {
this.props.dispatch(expandMentions(this.props.params.statusId)); this.props.dispatch(expandMentions(this.props.params.statusId));
}, 300, { leading: true }) }, 300, { leading: true })
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['mentions', 'columnWidth'], value));
}
}
render () { render () {
const { accountIds, multiColumn, hasMore, isLoading } = this.props; const { accountIds, multiColumn, hasMore, isLoading, columnWidth } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -62,10 +84,12 @@ class Mentions extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.mentions' defaultMessage='No one has mentioned this toot.' />; const emptyMessage = <FormattedMessage id='empty_column.mentions' defaultMessage='No one has mentioned this toot.' />;
return ( return (
<Column bindToDocument={!multiColumn}> <Column bindToDocument={!multiColumn} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
/> />
<ScrollableList <ScrollableList

View file

@ -11,6 +11,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import AccountContainer from '../../containers/account_container'; import AccountContainer from '../../containers/account_container';
import { fetchMutes, expandMutes } from '../../actions/mutes'; import { fetchMutes, expandMutes } from '../../actions/mutes';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import { defaultColumnWidth } from 'mastodon/initial_state';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.mutes', defaultMessage: 'Muted users' }, heading: { id: 'column.mutes', defaultMessage: 'Muted users' },
@ -20,6 +21,7 @@ const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'mutes', 'items']), accountIds: state.getIn(['user_lists', 'mutes', 'items']),
hasMore: !!state.getIn(['user_lists', 'mutes', 'next']), hasMore: !!state.getIn(['user_lists', 'mutes', 'next']),
isLoading: state.getIn(['user_lists', 'mutes', 'isLoading'], true), isLoading: state.getIn(['user_lists', 'mutes', 'isLoading'], true),
columnWidth: defaultColumnWidth,
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -34,6 +36,7 @@ class Mutes extends ImmutablePureComponent {
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
componentWillMount () { componentWillMount () {
@ -45,7 +48,7 @@ class Mutes extends ImmutablePureComponent {
}, 300, { leading: true }); }, 300, { leading: true });
render () { render () {
const { intl, hasMore, accountIds, multiColumn, isLoading } = this.props; const { intl, hasMore, accountIds, multiColumn, isLoading, columnWidth } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -58,7 +61,7 @@ class Mutes extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />; const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
return ( return (
<Column bindToDocument={!multiColumn} icon='volume-off' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='volume-off' heading={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnBackButtonSlim /> <ColumnBackButtonSlim />
<ScrollableList <ScrollableList
scrollKey='mutes' scrollKey='mutes'

View file

@ -26,6 +26,9 @@ import LoadGap from '../../components/load_gap';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
import compareId from 'mastodon/compare_id'; import compareId from 'mastodon/compare_id';
import NotificationsPermissionBanner from './components/notifications_permission_banner'; import NotificationsPermissionBanner from './components/notifications_permission_banner';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.notifications', defaultMessage: 'Notifications' }, title: { id: 'column.notifications', defaultMessage: 'Notifications' },
@ -53,17 +56,25 @@ const getNotifications = createSelector([
return notifications.filter(item => item === null || allowedType === item.get('type')); return notifications.filter(item => item === null || allowedType === item.get('type'));
}); });
const mapStateToProps = state => ({ const mapStateToProps = (state, { columnId }) => {
showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']), const uuid = columnId;
notifications: getNotifications(state), const columns = state.getIn(['settings', 'columns']);
isLoading: state.getIn(['notifications', 'isLoading'], true), const index = columns.findIndex(c => c.get('uuid') === uuid);
isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0, const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'notifications', 'columnWidth']);
hasMore: state.getIn(['notifications', 'hasMore']),
numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size, return {
lastReadId: state.getIn(['settings', 'notifications', 'showUnread']) ? state.getIn(['notifications', 'readMarkerId']) : '0', showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
canMarkAsRead: state.getIn(['settings', 'notifications', 'showUnread']) && state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0), notifications: getNotifications(state),
needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default' && !state.getIn(['settings', 'notifications', 'dismissPermissionBanner']), isLoading: state.getIn(['notifications', 'isLoading'], true),
}); isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0,
hasMore: state.getIn(['notifications', 'hasMore']),
numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
lastReadId: state.getIn(['settings', 'notifications', 'showUnread']) ? state.getIn(['notifications', 'readMarkerId']) : '0',
canMarkAsRead: state.getIn(['settings', 'notifications', 'showUnread']) && state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default' && !state.getIn(['settings', 'notifications', 'dismissPermissionBanner']),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -78,6 +89,7 @@ class Notifications extends React.PureComponent {
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
isUnread: PropTypes.bool, isUnread: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
numPending: PropTypes.number, numPending: PropTypes.number,
lastReadId: PropTypes.string, lastReadId: PropTypes.string,
@ -174,8 +186,18 @@ class Notifications extends React.PureComponent {
this.props.dispatch(submitMarkers({ immediate: true })); this.props.dispatch(submitMarkers({ immediate: true }));
}; };
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['notifications', 'columnWidth'], value));
}
}
render () { render () {
const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props; const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId, canMarkAsRead, needsNotificationPermission, columnWidth } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />; const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
@ -248,7 +270,7 @@ class Notifications extends React.PureComponent {
} }
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setColumnRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setColumnRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='bell' icon='bell'
active={isUnread} active={isUnread}
@ -259,6 +281,8 @@ class Notifications extends React.PureComponent {
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
extraButton={extraButton} extraButton={extraButton}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
<ColumnSettingsContainer /> <ColumnSettingsContainer />
</ColumnHeader> </ColumnHeader>

View file

@ -8,6 +8,7 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import StatusList from '../../components/status_list'; import StatusList from '../../components/status_list';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { defaultColumnWidth } from 'mastodon/initial_state';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.pins', defaultMessage: 'Pinned toot' }, heading: { id: 'column.pins', defaultMessage: 'Pinned toot' },
@ -16,6 +17,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({ const mapStateToProps = state => ({
statusIds: state.getIn(['status_lists', 'pins', 'items']), statusIds: state.getIn(['status_lists', 'pins', 'items']),
hasMore: !!state.getIn(['status_lists', 'pins', 'next']), hasMore: !!state.getIn(['status_lists', 'pins', 'next']),
columnWidth: defaultColumnWidth,
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -28,6 +30,7 @@ class PinnedStatuses extends ImmutablePureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasMore: PropTypes.bool.isRequired, hasMore: PropTypes.bool.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
componentWillMount () { componentWillMount () {
@ -43,10 +46,10 @@ class PinnedStatuses extends ImmutablePureComponent {
} }
render () { render () {
const { intl, statusIds, hasMore, multiColumn } = this.props; const { intl, statusIds, hasMore, multiColumn, columnWidth } = this.props;
return ( return (
<Column bindToDocument={!multiColumn} icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef}> <Column bindToDocument={!multiColumn} icon='thumb-tack' heading={intl.formatMessage(messages.heading)} ref={this.setRef} columnWidth={columnWidth}>
<ColumnBackButtonSlim /> <ColumnBackButtonSlim />
<StatusList <StatusList
statusIds={statusIds} statusIds={statusIds}

View file

@ -9,6 +9,9 @@ import { expandPublicTimeline } from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import ColumnSettingsContainer from './containers/column_settings_container'; import ColumnSettingsContainer from './containers/column_settings_container';
import { connectPublicStream } from '../../actions/streaming'; import { connectPublicStream } from '../../actions/streaming';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.public', defaultMessage: 'Federated timeline' }, title: { id: 'column.public', defaultMessage: 'Federated timeline' },
@ -22,6 +25,7 @@ const mapStateToProps = (state, { columnId }) => {
const withoutMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutMedia']) : state.getIn(['settings', 'public', 'other', 'withoutMedia']); const withoutMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutMedia']) : state.getIn(['settings', 'public', 'other', 'withoutMedia']);
const withoutBot = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutBot']) : state.getIn(['settings', 'public', 'other', 'withoutBot']); const withoutBot = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'withoutBot']) : state.getIn(['settings', 'public', 'other', 'withoutBot']);
const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']); const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']);
const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'public', 'columnWidth']);
const timelineState = state.getIn(['timelines', `public${onlyRemote ? ':remote' : ''}${withoutBot ? ':nobot' : ':bot'}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}`]); const timelineState = state.getIn(['timelines', `public${onlyRemote ? ':remote' : ''}${withoutBot ? ':nobot' : ':bot'}${withoutMedia ? ':nomedia' : ''}${onlyMedia ? ':media' : ''}`]);
return { return {
@ -30,6 +34,7 @@ const mapStateToProps = (state, { columnId }) => {
withoutMedia, withoutMedia,
withoutBot, withoutBot,
onlyRemote, onlyRemote,
columnWidth: columnWidth ?? defaultColumnWidth,
}; };
}; };
@ -53,6 +58,7 @@ class PublicTimeline extends React.PureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasUnread: PropTypes.bool, hasUnread: PropTypes.bool,
onlyMedia: PropTypes.bool, onlyMedia: PropTypes.bool,
withoutMedia: PropTypes.bool, withoutMedia: PropTypes.bool,
@ -79,6 +85,16 @@ class PublicTimeline extends React.PureComponent {
this.column.scrollTop(); this.column.scrollTop();
} }
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['public', 'columnWidth'], value));
}
}
componentDidMount () { componentDidMount () {
const { dispatch, onlyMedia, withoutMedia, withoutBot, onlyRemote } = this.props; const { dispatch, onlyMedia, withoutMedia, withoutBot, onlyRemote } = this.props;
@ -114,11 +130,11 @@ class PublicTimeline extends React.PureComponent {
} }
render () { render () {
const { intl, columnId, hasUnread, multiColumn, onlyMedia, withoutMedia, withoutBot, onlyRemote } = this.props; const { intl, columnId, hasUnread, multiColumn, onlyMedia, withoutMedia, withoutBot, onlyRemote, columnWidth } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='globe' icon='globe'
active={hasUnread} active={hasUnread}
@ -128,6 +144,8 @@ class PublicTimeline extends React.PureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
<ColumnSettingsContainer columnId={columnId} /> <ColumnSettingsContainer columnId={columnId} />
</ColumnHeader> </ColumnHeader>

View file

@ -13,16 +13,27 @@ import Icon from 'mastodon/components/icon';
import ColumnHeader from '../../components/column_header'; import ColumnHeader from '../../components/column_header';
import ReactedHeaderContaier from '../reactioned/containers/header_container'; import ReactedHeaderContaier from '../reactioned/containers/header_container';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' }, refresh: { id: 'refresh', defaultMessage: 'Refresh' },
}); });
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, { columnId, params }) => {
accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId, 'items']), const uuid = columnId;
isLoading: state.getIn(['user_lists', 'reblogged_by', props.params.statusId, 'isLoading'], true), const columns = state.getIn(['settings', 'columns']);
hasMore: !!state.getIn(['user_lists', 'reblogged_by', props.params.statusId, 'next']), const index = columns.findIndex(c => c.get('uuid') === uuid);
}); const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'reblogs', 'columnWidth']);
return {
accountIds: state.getIn(['user_lists', 'reblogged_by', params.statusId, 'items']),
isLoading: state.getIn(['user_lists', 'reblogged_by', params.statusId, 'isLoading'], true),
hasMore: !!state.getIn(['user_lists', 'reblogged_by', params.statusId, 'next']),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -33,6 +44,7 @@ class Reblogs extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
@ -58,8 +70,18 @@ class Reblogs extends ImmutablePureComponent {
this.props.dispatch(expandReblogs(this.props.params.statusId)); this.props.dispatch(expandReblogs(this.props.params.statusId));
}, 300, { leading: true }) }, 300, { leading: true })
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['reblogs', 'columnWidth'], value));
}
}
render () { render () {
const { intl, accountIds, multiColumn, hasMore, isLoading } = this.props; const { intl, accountIds, multiColumn, hasMore, isLoading, columnWidth } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -72,10 +94,12 @@ class Reblogs extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='status.reblogs.empty' defaultMessage='No one has boosted this toot yet. When someone does, they will show up here.' />; const emptyMessage = <FormattedMessage id='status.reblogs.empty' defaultMessage='No one has boosted this toot yet. When someone does, they will show up here.' />;
return ( return (
<Column bindToDocument={!multiColumn}> <Column bindToDocument={!multiColumn} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
extraButton={( extraButton={(
<button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button> <button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
)} )}

View file

@ -12,17 +12,28 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import ReactedHeaderContaier from '../reactioned/containers/header_container'; import ReactedHeaderContaier from '../reactioned/containers/header_container';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' }, refresh: { id: 'refresh', defaultMessage: 'Refresh' },
heading: { id: 'column.referred_by_statuses', defaultMessage: 'Referred by posts' }, heading: { id: 'column.referred_by_statuses', defaultMessage: 'Referred by posts' },
}); });
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, { columnId, params }) => {
statusIds: state.getIn(['status_status_lists', 'referred_by', props.params.statusId, 'items']), const uuid = columnId;
isLoading: state.getIn(['status_status_lists', 'referred_by', props.params.statusId, 'isLoading'], true), const columns = state.getIn(['settings', 'columns']);
hasMore: !!state.getIn(['status_status_lists', 'referred_by', props.params.statusId, 'next']), const index = columns.findIndex(c => c.get('uuid') === uuid);
}); const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'referred_by_statuses', 'columnWidth']);
return {
statusIds: state.getIn(['status_status_lists', 'referred_by', params.statusId, 'items']),
isLoading: state.getIn(['status_status_lists', 'referred_by', params.statusId, 'isLoading'], true),
hasMore: !!state.getIn(['status_status_lists', 'referred_by', params.statusId, 'next']),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -33,6 +44,7 @@ class ReferredByStatuses extends ImmutablePureComponent {
statusIds: ImmutablePropTypes.list, statusIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
}; };
@ -57,8 +69,18 @@ class ReferredByStatuses extends ImmutablePureComponent {
this.props.dispatch(expandReferredByStatuses(this.props.params.statusId)); this.props.dispatch(expandReferredByStatuses(this.props.params.statusId));
}, 300, { leading: true }) }, 300, { leading: true })
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['referred_by_statuses', 'columnWidth'], value));
}
}
render () { render () {
const { intl, statusIds, multiColumn, hasMore, isLoading } = this.props; const { intl, statusIds, multiColumn, hasMore, isLoading, columnWidth } = this.props;
if (!statusIds) { if (!statusIds) {
return ( return (
@ -71,10 +93,12 @@ class ReferredByStatuses extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.referred_by_statuses' defaultMessage="There are no referred by posts yet. When someone refers a post, it will appear here." />; const emptyMessage = <FormattedMessage id='empty_column.referred_by_statuses' defaultMessage="There are no referred by posts yet. When someone refers a post, it will appear here." />;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
extraButton={( extraButton={(
<button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button> <button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
)} )}

View file

@ -65,6 +65,9 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from
import { textForScreenReader, defaultMediaVisibility } from '../../components/status'; import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
import DetailedHeaderContaier from './containers/header_container'; import DetailedHeaderContaier from './containers/header_container';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
@ -148,8 +151,12 @@ const makeMapStateToProps = () => {
return ImmutableList(contextReference.get(statusId)); return ImmutableList(contextReference.get(statusId));
}); });
const mapStateToProps = (state, props) => { const mapStateToProps = (state, { columnId, params }) => {
const status = getStatus(state, { id: props.params.statusId }); const uuid = columnId;
const columns = state.getIn(['settings', 'columns']);
const index = columns.findIndex(c => c.get('uuid') === uuid);
const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'status', 'columnWidth']);
const status = getStatus(state, { id: params.statusId });
const ancestorsIds = status ? getAncestorsIds(state, { id: status.get('in_reply_to_id') }) : ImmutableList(); const ancestorsIds = status ? getAncestorsIds(state, { id: status.get('in_reply_to_id') }) : ImmutableList();
const descendantsIds = status ? getDescendantsIds(state, { id: status.get('id') }) : ImmutableList(); const descendantsIds = status ? getDescendantsIds(state, { id: status.get('id') }) : ImmutableList();
const referencesIds = status ? getReferencesIds(state, { id: status.get('id') }) : ImmutableList(); const referencesIds = status ? getReferencesIds(state, { id: status.get('id') }) : ImmutableList();
@ -161,10 +168,11 @@ const makeMapStateToProps = () => {
descendantsIds, descendantsIds,
askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0, askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
domain: state.getIn(['meta', 'domain']), domain: state.getIn(['meta', 'domain']),
pictureInPicture: getPictureInPicture(state, { id: props.params.statusId }), pictureInPicture: getPictureInPicture(state, { id: params.statusId }),
emojiMap: customEmojiMap(state), emojiMap: customEmojiMap(state),
referenced: state.getIn(['compose', 'references']).has(id), referenced: state.getIn(['compose', 'references']).has(id),
contextReferenced: state.getIn(['compose', 'context_references']).has(id), contextReferenced: state.getIn(['compose', 'context_references']).has(id),
columnWidth: columnWidth ?? defaultColumnWidth,
}; };
}; };
@ -190,6 +198,7 @@ class Status extends ImmutablePureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
askReplyConfirmation: PropTypes.bool, askReplyConfirmation: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
domain: PropTypes.string.isRequired, domain: PropTypes.string.isRequired,
pictureInPicture: ImmutablePropTypes.contains({ pictureInPicture: ImmutablePropTypes.contains({
inUse: PropTypes.bool, inUse: PropTypes.bool,
@ -561,9 +570,19 @@ class Status extends ImmutablePureComponent {
this.setState({ fullscreen: isFullscreen() }); this.setState({ fullscreen: isFullscreen() });
} }
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['status', 'columnWidth'], value));
}
}
render () { render () {
let ancestors, descendants; let ancestors, descendants;
const { status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture, emojiMap, referenced, contextReferenced } = this.props; const { status, ancestorsIds, descendantsIds, intl, domain, multiColumn, pictureInPicture, emojiMap, referenced, contextReferenced, columnWidth } = this.props;
const { fullscreen } = this.state; const { fullscreen } = this.state;
if (status === null) { if (status === null) {
@ -599,10 +618,12 @@ class Status extends ImmutablePureComponent {
}; };
return ( return (
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.detailedStatus)}> <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.detailedStatus)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
extraButton={( extraButton={(
<button className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button> <button className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button>
)} )}

View file

@ -22,6 +22,9 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen'; import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
import Icon from 'mastodon/components/icon'; import Icon from 'mastodon/components/icon';
import DetailedHeaderContaier from '../status/containers/header_container'; import DetailedHeaderContaier from '../status/containers/header_container';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' }, revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
@ -39,13 +42,18 @@ const makeMapStateToProps = () => {
return ImmutableList(contextReference.get(statusId)); return ImmutableList(contextReference.get(statusId));
}); });
const mapStateToProps = (state, props) => { const mapStateToProps = (state, { columnId, params }) => {
const status = getStatus(state, { id: props.params.statusId }); const uuid = columnId;
const columns = state.getIn(['settings', 'columns']);
const index = columns.findIndex(c => c.get('uuid') === uuid);
const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'status_references', 'columnWidth']);
const status = getStatus(state, { id: params.statusId });
const referencesIds = status ? getReferencesIds(state, { id: status.get('id') }) : ImmutableList(); const referencesIds = status ? getReferencesIds(state, { id: status.get('id') }) : ImmutableList();
return { return {
status, status,
referencesIds, referencesIds,
columnWidth: columnWidth ?? defaultColumnWidth,
}; };
}; };
@ -67,6 +75,7 @@ class StatusReferences extends ImmutablePureComponent {
referencesIds: ImmutablePropTypes.list, referencesIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
state = { state = {
@ -154,8 +163,18 @@ class StatusReferences extends ImmutablePureComponent {
this.setState({ fullscreen: isFullscreen() }); this.setState({ fullscreen: isFullscreen() });
} }
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['status_references', 'columnWidth'], value));
}
}
render () { render () {
const { status, referencesIds, intl, multiColumn } = this.props; const { status, referencesIds, intl, multiColumn, columnWidth } = this.props;
const { fullscreen } = this.state; const { fullscreen } = this.state;
if (status === null) { if (status === null) {
@ -173,10 +192,12 @@ class StatusReferences extends ImmutablePureComponent {
} }
return ( return (
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.detailedStatus)}> <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.detailedStatus)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
extraButton={( extraButton={(
<button className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button> <button className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button>
)} )}

View file

@ -18,7 +18,8 @@ import ColumnSettingsContainer from '../account_timeline/containers/column_setti
import HeaderContainer from '../account_timeline/containers/header_container'; import HeaderContainer from '../account_timeline/containers/header_container';
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import MissingIndicator from 'mastodon/components/missing_indicator'; import MissingIndicator from 'mastodon/components/missing_indicator';
import { new_features_policy } from 'mastodon/initial_state'; import { new_features_policy, defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.account', defaultMessage: 'Account' }, title: { id: 'column.account', defaultMessage: 'Account' },
@ -31,6 +32,7 @@ const mapStateToProps = (state, props) => ({
isLoading: state.getIn(['user_lists', 'subscribing', props.params.accountId, 'isLoading'], true), isLoading: state.getIn(['user_lists', 'subscribing', props.params.accountId, 'isLoading'], true),
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false), blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true), advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true),
columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth),
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -46,6 +48,7 @@ class Subscribing extends ImmutablePureComponent {
blockedBy: PropTypes.bool, blockedBy: PropTypes.bool,
isAccount: PropTypes.bool, isAccount: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
componentWillMount () { componentWillMount () {
@ -70,12 +73,16 @@ class Subscribing extends ImmutablePureComponent {
this.column.scrollTop(); this.column.scrollTop();
} }
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['account', 'columnWidth'], value));
}
setRef = c => { setRef = c => {
this.column = c; this.column = c;
} }
render () { render () {
const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, intl } = this.props; const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, columnWidth, intl } = this.props;
if (!isAccount) { if (!isAccount) {
return ( return (
@ -96,7 +103,7 @@ class Subscribing extends ImmutablePureComponent {
const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='account.subscribes.empty' defaultMessage="This user doesn't subscribe anyone yet." />; const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' /> : <FormattedMessage id='account.subscribes.empty' defaultMessage="This user doesn't subscribe anyone yet." />;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='user' icon='user'
active={false} active={false}
@ -104,6 +111,8 @@ class Subscribing extends ImmutablePureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={false} pinned={false}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
> >
<ColumnSettingsContainer /> <ColumnSettingsContainer />
</ColumnHeader> </ColumnHeader>

View file

@ -11,6 +11,9 @@ import ScrollableList from 'mastodon/components/scrollable_list';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import AccountContainer from 'mastodon/containers/account_container'; import AccountContainer from 'mastodon/containers/account_container';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'suggestions.heading', defaultMessage: 'Suggestions' }, heading: { id: 'suggestions.heading', defaultMessage: 'Suggestions' },
@ -18,10 +21,18 @@ const messages = defineMessages({
dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' }, dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
}); });
const mapStateToProps = state => ({ const mapStateToProps = (state, { columnId }) => {
suggestions: state.getIn(['suggestions', 'items']), const uuid = columnId;
isLoading: state.getIn(['suggestions', 'isLoading'], true), const columns = state.getIn(['settings', 'columns']);
}); const index = columns.findIndex(c => c.get('uuid') === uuid);
const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'suggestions', 'columnWidth']);
return {
suggestions: state.getIn(['suggestions', 'items']),
isLoading: state.getIn(['suggestions', 'isLoading'], true),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -33,6 +44,7 @@ class Suggestions extends ImmutablePureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
}; };
@ -75,14 +87,24 @@ class Suggestions extends ImmutablePureComponent {
this.column = c; this.column = c;
} }
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['suggestions', 'columnWidth'], value));
}
}
render () { render () {
const { intl, suggestions, columnId, multiColumn, isLoading } = this.props; const { intl, suggestions, columnId, multiColumn, isLoading, columnWidth } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.suggestions' defaultMessage='No one has suggestions yet.' />; const emptyMessage = <FormattedMessage id='empty_column.suggestions' defaultMessage='No one has suggestions yet.' />;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='user-plus' icon='user-plus'
title={intl.formatMessage(messages.heading)} title={intl.formatMessage(messages.heading)}
@ -91,6 +113,8 @@ class Suggestions extends ImmutablePureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
showBackButton showBackButton
/> />

View file

@ -12,6 +12,9 @@ import ScrollableList from 'mastodon/components/scrollable_list';
import Hashtag from 'mastodon/components/hashtag'; import Hashtag from 'mastodon/components/hashtag';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'trends.heading', defaultMessage: 'Trends' }, heading: { id: 'trends.heading', defaultMessage: 'Trends' },
@ -19,10 +22,18 @@ const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' }, refresh: { id: 'refresh', defaultMessage: 'Refresh' },
}); });
const mapStateToProps = state => ({ const mapStateToProps = (state, { columnId }) => {
trends: state.getIn(['trends', 'items']), const uuid = columnId;
isLoading: state.getIn(['trends', 'isLoading'], true), const columns = state.getIn(['settings', 'columns']);
}); const index = columns.findIndex(c => c.get('uuid') === uuid);
const columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'trends', 'columnWidth']);
return {
trends: state.getIn(['trends', 'items']),
isLoading: state.getIn(['trends', 'isLoading'], true),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@injectIntl @injectIntl
@ -34,6 +45,7 @@ class Trends extends ImmutablePureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
columnId: PropTypes.string, columnId: PropTypes.string,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
}; };
@ -81,14 +93,24 @@ class Trends extends ImmutablePureComponent {
this.column = c; this.column = c;
} }
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['trends', 'columnWidth'], value));
}
}
render () { render () {
const { intl, trends, columnId, multiColumn, isLoading } = this.props; const { intl, trends, columnId, multiColumn, isLoading, columnWidth } = this.props;
const pinned = !!columnId; const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.trends' defaultMessage='No one has trends yet.' />; const emptyMessage = <FormattedMessage id='empty_column.trends' defaultMessage='No one has trends yet.' />;
return ( return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnHeader <ColumnHeader
icon='line-chart' icon='line-chart'
title={intl.formatMessage(messages.heading)} title={intl.formatMessage(messages.heading)}
@ -97,6 +119,8 @@ class Trends extends ImmutablePureComponent {
onClick={this.handleHeaderClick} onClick={this.handleHeaderClick}
pinned={pinned} pinned={pinned}
multiColumn={multiColumn} multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
showBackButton showBackButton
extraButton={( extraButton={(
<button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button> <button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>

View file

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { scrollTop } from '../../../scroll'; import { scrollTop } from '../../../scroll';
import { isMobile } from '../../../is_mobile'; import { isMobile } from '../../../is_mobile';
import classNames from 'classnames';
export default class Column extends React.PureComponent { export default class Column extends React.PureComponent {
@ -13,6 +14,7 @@ export default class Column extends React.PureComponent {
children: PropTypes.node, children: PropTypes.node,
active: PropTypes.bool, active: PropTypes.bool,
hideHeadingOnMobile: PropTypes.bool, hideHeadingOnMobile: PropTypes.bool,
columnWidth: PropTypes.string,
}; };
handleHeaderClick = () => { handleHeaderClick = () => {
@ -47,7 +49,7 @@ export default class Column extends React.PureComponent {
} }
render () { render () {
const { heading, icon, children, active, hideHeadingOnMobile } = this.props; const { heading, icon, children, active, hideHeadingOnMobile, columnWidth } = this.props;
const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth))); const showHeading = heading && (!hideHeadingOnMobile || (hideHeadingOnMobile && !isMobile(window.innerWidth)));
@ -60,7 +62,7 @@ export default class Column extends React.PureComponent {
ref={this.setRef} ref={this.setRef}
role='region' role='region'
aria-labelledby={columnHeaderId} aria-labelledby={columnHeaderId}
className='column' className={classNames('column', columnWidth)}
onScroll={this.handleScroll} onScroll={this.handleScroll}
> >
{header} {header}

View file

@ -52,5 +52,6 @@ export const maxReferences = initialState?.status_references?.max_references;
export const matchVisibilityOfReferences = getMeta('match_visibility_of_references'); export const matchVisibilityOfReferences = getMeta('match_visibility_of_references');
export const enableEmptyColumn = getMeta('enable_empty_column'); export const enableEmptyColumn = getMeta('enable_empty_column');
export const showReloadButton = getMeta('show_reload_button'); export const showReloadButton = getMeta('show_reload_button');
export const defaultColumnWidth = getMeta('default_column_width');
export default initialState; export default initialState;

View file

@ -140,6 +140,11 @@
"column_subheading.favourite_lists": "Favourite lists", "column_subheading.favourite_lists": "Favourite lists",
"column_subheading.favourite_tags": "Favourite tags", "column_subheading.favourite_tags": "Favourite tags",
"column_subheading.settings": "Settings", "column_subheading.settings": "Settings",
"column_width.free": "Free",
"column_width.x080": "80%",
"column_width.x100": "100%",
"column_width.x125": "125%",
"column_width.x150": "150%",
"community.column_settings.local_only": "Local only", "community.column_settings.local_only": "Local only",
"community.column_settings.media_only": "Media only", "community.column_settings.media_only": "Media only",
"community.column_settings.remote_only": "Remote only", "community.column_settings.remote_only": "Remote only",

View file

@ -140,6 +140,11 @@
"column_subheading.favourite_lists": "お気に入りリスト", "column_subheading.favourite_lists": "お気に入りリスト",
"column_subheading.favourite_tags": "お気に入りハッシュタグ", "column_subheading.favourite_tags": "お気に入りハッシュタグ",
"column_subheading.settings": "設定", "column_subheading.settings": "設定",
"column_width.free": "フリー",
"column_width.x080": "80%",
"column_width.x100": "100%",
"column_width.x125": "125%",
"column_width.x150": "150%",
"community.column_settings.local_only": "ローカルのみ表示", "community.column_settings.local_only": "ローカルのみ表示",
"community.column_settings.media_only": "メディアのみ表示", "community.column_settings.media_only": "メディアのみ表示",
"community.column_settings.remote_only": "リモートのみ表示", "community.column_settings.remote_only": "リモートのみ表示",

View file

@ -2346,6 +2346,57 @@ a.account__display-name {
> .scrollable { > .scrollable {
background: $ui-base-color; background: $ui-base-color;
} }
.layout-multiple-columns &.free {
flex: 1 1 auto;
min-width: 350px;
}
.layout-multiple-columns &.x080 {
width: 350px * 0.8;
.status__action-bar {
zoom: 0.8;
}
}
.layout-multiple-columns &.x125 {
width: 350px * 1.25;
}
.layout-multiple-columns &.x150 {
width: 350px * 1.5;
}
.layout-multiple-columns &.x200 {
width: 350px * 2;
}
}
.column-width {
display: flex;
justify-content: flex-end;
padding-bottom: 8px;
margin-bottom: 10px;
&__item {
flex: 0 0 auto;
min-width: 36px;
padding: 4px;
margin: 0 2px;
border-radius: 4px;
text-align: center;
background-color: $ui-base-color;
}
&__item.active {
color: $primary-text-color;
background-color: $ui-highlight-color;
}
input[type=radio] {
display: none;
}
} }
.ui { .ui {

View file

@ -89,6 +89,7 @@ class UserSettingsDecorator
user.settings['confirm_follow_from_bot'] = confirm_follow_from_bot_preference if change?('setting_confirm_follow_from_bot') user.settings['confirm_follow_from_bot'] = confirm_follow_from_bot_preference if change?('setting_confirm_follow_from_bot')
user.settings['default_search_searchability'] = default_search_searchability_preference if change?('setting_default_search_searchability') user.settings['default_search_searchability'] = default_search_searchability_preference if change?('setting_default_search_searchability')
user.settings['show_reload_button'] = show_reload_button_preference if change?('setting_show_reload_button') user.settings['show_reload_button'] = show_reload_button_preference if change?('setting_show_reload_button')
user.settings['default_column_width'] = default_column_width_preference if change?('setting_default_column_width')
end end
def merged_notification_emails def merged_notification_emails
@ -331,6 +332,10 @@ end
boolean_cast_setting 'setting_show_reload_button' boolean_cast_setting 'setting_show_reload_button'
end end
def default_column_width_preference
settings['setting_default_column_width']
end
def boolean_cast_setting(key) def boolean_cast_setting(key)
ActiveModel::Type::Boolean.new.cast(settings[key]) ActiveModel::Type::Boolean.new.cast(settings[key])
end end

View file

@ -142,7 +142,7 @@ class User < ApplicationRecord
:mobile_customize, :mobile_content_font_size, :mobile_info_font_size, :mobile_content_emoji_reaction_size, :mobile_customize, :mobile_content_font_size, :mobile_info_font_size, :mobile_content_emoji_reaction_size,
:hide_bot_on_public_timeline, :confirm_follow_from_bot, :hide_bot_on_public_timeline, :confirm_follow_from_bot,
:default_search_searchability, :default_search_searchability,
:show_reload_button, :show_reload_button, :default_column_width,
to: :settings, prefix: :setting, allow_nil: false to: :settings, prefix: :setting, allow_nil: false

View file

@ -72,6 +72,7 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:hide_bot_on_public_timeline] = object.current_account.user.setting_hide_bot_on_public_timeline store[:hide_bot_on_public_timeline] = object.current_account.user.setting_hide_bot_on_public_timeline
store[:confirm_follow_from_bot] = object.current_account.user.setting_confirm_follow_from_bot store[:confirm_follow_from_bot] = object.current_account.user.setting_confirm_follow_from_bot
store[:show_reload_button] = object.current_account.user.setting_show_reload_button store[:show_reload_button] = object.current_account.user.setting_show_reload_button
store[:default_column_width] = object.current_account.user.setting_default_column_width
else else
store[:auto_play_gif] = Setting.auto_play_gif store[:auto_play_gif] = Setting.auto_play_gif
store[:display_media] = Setting.display_media store[:display_media] = Setting.display_media

View file

@ -62,6 +62,9 @@
.fields-group .fields-group
= f.input :setting_enable_empty_column, as: :boolean, wrapper: :with_label, fedibird_features: true = f.input :setting_enable_empty_column, as: :boolean, wrapper: :with_label, fedibird_features: true
.fields-group
= f.input :setting_default_column_width, collection: ['x080', 'x100', 'x125', 'x150', 'free'], label_method: lambda { |item| t("simple_form.hints.defaults.setting_default_column_width_#{item}") }, wrapper: :with_label, include_blank: false, fedibird_features: true
%h4= t 'appearance.animations_and_accessibility' %h4= t 'appearance.animations_and_accessibility'
.fields-group .fields-group

View file

@ -57,6 +57,11 @@ en:
setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts) setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts)
setting_compact_reaction: Emoji reaction display to be only the number of cases, except for the detail display setting_compact_reaction: Emoji reaction display to be only the number of cases, except for the detail display
setting_confirm_follow_from_bot: Manually approve followers from bot accounts setting_confirm_follow_from_bot: Manually approve followers from bot accounts
setting_default_column_width_free: Free - Column width changes according to screen
setting_default_column_width_x080: 80% - Fix to 80% of standard width
setting_default_column_width_x100: 100% - Fix to Mastodon standard column width
setting_default_column_width_x125: 125% - Fix to 125% of standard width
setting_default_column_width_x150: 150% - Fix to 150% of standard width
setting_default_search_searchability: For clients that do not support advanced range settings, switch the settings here. Mastodon's standard behavior is "Reacted-users-only". Targeting "Public" makes it easier to discover unknown information, but if the results are noisy, narrowing the search range is effective. setting_default_search_searchability: For clients that do not support advanced range settings, switch the settings here. Mastodon's standard behavior is "Reacted-users-only". Targeting "Public" makes it easier to discover unknown information, but if the results are noisy, narrowing the search range is effective.
setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click
setting_disable_joke_appearance: Disable April Fools' Day and other joke functions setting_disable_joke_appearance: Disable April Fools' Day and other joke functions
@ -219,6 +224,7 @@ en:
setting_content_emoji_reaction_size: Emoji reaction size setting_content_emoji_reaction_size: Emoji reaction size
setting_content_font_size: Content font size setting_content_font_size: Content font size
setting_crop_images: Crop images in non-expanded posts to 16x9 setting_crop_images: Crop images in non-expanded posts to 16x9
setting_default_column_width: Default column width
setting_default_language: Posting language setting_default_language: Posting language
setting_default_privacy: Posting privacy setting_default_privacy: Posting privacy
setting_default_search_searchability: Search range setting_default_search_searchability: Search range

View file

@ -57,6 +57,11 @@ ja:
setting_aggregate_reblogs: 最近ブーストされた投稿が新たにブーストされても表示しません (設定後受信したものにのみ影響) setting_aggregate_reblogs: 最近ブーストされた投稿が新たにブーストされても表示しません (設定後受信したものにのみ影響)
setting_compact_reaction: 詳細表示以外の絵文字リアクション表示を件数のみにする setting_compact_reaction: 詳細表示以外の絵文字リアクション表示を件数のみにする
setting_confirm_follow_from_bot: Botアカウントからのフォローを手動で承認する setting_confirm_follow_from_bot: Botアカウントからのフォローを手動で承認する
setting_default_column_width_free: フリー - 画面に応じてカラム幅が変わります
setting_default_column_width_x080: 80% - 標準の80%の幅に固定します
setting_default_column_width_x100: 100% - Mastodon標準のカラム幅に固定します
setting_default_column_width_x125: 125% - 標準の125%の幅に固定します
setting_default_column_width_x150: 150% - 標準の150%の幅に固定します
setting_default_search_searchability: 範囲の詳細設定に対応していないクライアントでは、ここで設定を切り替えてください。Mastodonの標準動作は『リアクション限定』です。『公開』を対象にすると未知の情報を発見しやすくなりますが、結果にイズが多い場合は検索範囲を狭めると効果的です。 setting_default_search_searchability: 範囲の詳細設定に対応していないクライアントでは、ここで設定を切り替えてください。Mastodonの標準動作は『リアクション限定』です。『公開』を対象にすると未知の情報を発見しやすくなりますが、結果にイズが多い場合は検索範囲を狭めると効果的です。
setting_default_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります setting_default_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります
setting_disable_joke_appearance: エイプリルフール等のジョーク機能を無効にします setting_disable_joke_appearance: エイプリルフール等のジョーク機能を無効にします
@ -219,6 +224,7 @@ ja:
setting_content_emoji_reaction_size: 投稿の絵文字リアクションのサイズ setting_content_emoji_reaction_size: 投稿の絵文字リアクションのサイズ
setting_content_font_size: 投稿のフォントサイズ setting_content_font_size: 投稿のフォントサイズ
setting_crop_images: 投稿の詳細以外では画像を16:9に切り抜く setting_crop_images: 投稿の詳細以外では画像を16:9に切り抜く
setting_default_column_width: デフォルトのカラム幅
setting_default_language: 投稿する言語 setting_default_language: 投稿する言語
setting_default_privacy: 投稿の公開範囲 setting_default_privacy: 投稿の公開範囲
setting_default_search_searchability: 検索の対象とする範囲 setting_default_search_searchability: 検索の対象とする範囲

View file

@ -110,6 +110,7 @@ defaults: &defaults
confirm_follow_from_bot: true confirm_follow_from_bot: true
default_search_searchability: 'private' default_search_searchability: 'private'
show_reload_button: true show_reload_button: true
default_column_width: 'x100'
development: development:
<<: *defaults <<: *defaults