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_default_search_searchability,
: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),
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 { supportsPassiveEvents } from 'detect-passive-events';
import { scrollTop } from '../scroll';
import classNames from 'classnames';
export default class Column extends React.PureComponent {
@ -9,6 +10,7 @@ export default class Column extends React.PureComponent {
children: PropTypes.node,
label: PropTypes.string,
bindToDocument: PropTypes.bool,
columnWidth: PropTypes.string,
};
scrollTop () {
@ -50,10 +52,10 @@ export default class Column extends React.PureComponent {
}
render () {
const { label, children } = this.props;
const { label, columnWidth, children } = this.props;
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}
</div>
);

View file

@ -1,10 +1,11 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { createPortal } from 'react-dom';
import classNames from 'classnames';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import Icon from 'mastodon/components/icon';
import { enableEmptyColumn } from 'mastodon/initial_state';
import { enableEmptyColumn, defaultColumnWidth } from 'mastodon/initial_state';
const messages = defineMessages({
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' },
});
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 {
static contextTypes = {
@ -36,6 +50,8 @@ class ColumnHeader extends React.PureComponent {
onClick: PropTypes.func,
appendContent: PropTypes.node,
collapseIssues: PropTypes.bool,
columnWidth: PropTypes.string,
onWidthChange: PropTypes.func,
};
state = {
@ -88,8 +104,15 @@ class ColumnHeader extends React.PureComponent {
this.props.onPin();
}
handleChangeWidth = e => {
e.stopPropagation();
if (this.props.onWidthChange) {
this.props.onWidthChange(e.target.value);
}
}
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 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 = [
extraContent,
];
if (multiColumn) {
collapsedContent.unshift(widthButton);
collapsedContent.push(moveButtons);
collapsedContent.push(pinButton);
}

View file

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

View file

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

View file

@ -17,9 +17,10 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
import MissingIndicator from 'mastodon/components/missing_indicator';
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 { fetchFeaturedTags } from '../../actions/featured_tags';
import { changeSetting } from '../../actions/settings';
const emptyList = ImmutableList();
@ -54,6 +55,7 @@ const mapStateToProps = (state, { params: { accountId, tagged }, about, withRepl
withoutReblogs,
showPostsInAbout,
hideRelation,
columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth),
};
};
@ -90,6 +92,7 @@ class AccountTimeline extends ImmutablePureComponent {
remote: PropTypes.bool,
remoteUrl: PropTypes.string,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
};
static defaultProps = {
@ -169,12 +172,16 @@ class AccountTimeline extends ImmutablePureComponent {
this.column.scrollTop();
}
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['account', 'columnWidth'], value));
}
setRef = c => {
this.column = c;
}
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) {
return (
@ -210,7 +217,7 @@ class AccountTimeline extends ImmutablePureComponent {
const remoteMessage = (!about && remote) ? <RemoteHint url={remoteUrl} /> : null;
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
icon='user'
active={false}
@ -218,6 +225,8 @@ class AccountTimeline extends ImmutablePureComponent {
onClick={this.handleHeaderClick}
pinned={false}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
>
<ColumnSettingsContainer />
</ColumnHeader>

View file

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

View file

@ -10,16 +10,27 @@ import StatusList from '../../components/status_list';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({
heading: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' },
});
const mapStateToProps = state => ({
statusIds: state.getIn(['status_lists', 'bookmarks', 'items']),
isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']),
});
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', '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)
@injectIntl
@ -31,6 +42,7 @@ class Bookmarks extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasMore: PropTypes.bool,
isLoading: PropTypes.bool,
};
@ -66,14 +78,24 @@ class Bookmarks extends ImmutablePureComponent {
this.props.dispatch(expandBookmarkedStatuses());
}, 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 () {
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading, columnWidth } = this.props;
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." />;
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
icon='bookmark'
title={intl.formatMessage(messages.heading)}
@ -82,6 +104,8 @@ class Bookmarks extends ImmutablePureComponent {
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
showBackButton
/>

View file

@ -13,6 +13,7 @@ import NewCircleForm from './components/new_circle_form';
import Circle from './components/circle';
import { createSelector } from 'reselect';
import ScrollableList from '../../components/scrollable_list';
import { defaultColumnWidth } from 'mastodon/initial_state';
const messages = defineMessages({
heading: { id: 'column.circles', defaultMessage: 'Circles' },
@ -29,6 +30,7 @@ const getOrderedCircles = createSelector([state => state.get('circles')], circle
const mapStateToProps = state => ({
circles: getOrderedCircles(state),
columnWidth: defaultColumnWidth,
});
export default @connect(mapStateToProps)
@ -41,6 +43,7 @@ class Circles extends ImmutablePureComponent {
circles: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
};
componentWillMount () {
@ -48,7 +51,7 @@ class Circles extends ImmutablePureComponent {
}
render () {
const { intl, circles, multiColumn } = this.props;
const { intl, circles, multiColumn, columnWidth } = this.props;
if (!circles) {
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." />;
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 />
<NewCircleForm />

View file

@ -8,12 +8,25 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { connectDirectStream } from '../../actions/streaming';
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({
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
class DirectTimeline extends React.PureComponent {
@ -23,6 +36,7 @@ class DirectTimeline extends React.PureComponent {
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
};
handlePin = () => {
@ -69,12 +83,22 @@ class DirectTimeline extends React.PureComponent {
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 () {
const { intl, hasUnread, columnId, multiColumn } = this.props;
const { intl, hasUnread, columnId, multiColumn, columnWidth } = this.props;
const pinned = !!columnId;
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
icon='envelope'
active={hasUnread}
@ -84,6 +108,8 @@ class DirectTimeline extends React.PureComponent {
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
/>
<ConversationsListContainer

View file

@ -13,6 +13,8 @@ import RadioButton from 'mastodon/components/radio_button';
import classNames from 'classnames';
import LoadMore from 'mastodon/components/load_more';
import ScrollContainer from 'mastodon/containers/scroll_container';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
const messages = defineMessages({
title: { id: 'column.directory', defaultMessage: 'Browse profiles' },
@ -26,6 +28,7 @@ const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'directory', 'items'], ImmutableList()),
isLoading: state.getIn(['user_lists', 'directory', 'isLoading'], true),
domain: state.getIn(['meta', 'domain']),
columnWidth: state.getIn(['settings', 'directory', 'columnWidth'], defaultColumnWidth),
});
export default @connect(mapStateToProps)
@ -43,6 +46,7 @@ class Directory extends React.PureComponent {
columnId: PropTypes.string,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
domain: PropTypes.string.isRequired,
params: PropTypes.shape({
order: PropTypes.string,
@ -123,8 +127,12 @@ class Directory extends React.PureComponent {
dispatch(expandDirectory(this.getParams(this.props, this.state)));
}
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['directory', 'columnWidth'], value));
}
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 pinned = !!columnId;
@ -151,7 +159,7 @@ class Directory extends React.PureComponent {
);
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
icon='address-book-o'
title={intl.formatMessage(messages.title)}
@ -160,6 +168,8 @@ class Directory extends React.PureComponent {
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
/>
{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 { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_blocks';
import ScrollableList from '../../components/scrollable_list';
import { defaultColumnWidth } from 'mastodon/initial_state';
const messages = defineMessages({
heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
@ -20,6 +21,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
domains: state.getIn(['domain_lists', 'blocks', 'items']),
hasMore: !!state.getIn(['domain_lists', 'blocks', 'next']),
columnWidth: defaultColumnWidth,
});
export default @connect(mapStateToProps)
@ -33,6 +35,7 @@ class Blocks extends ImmutablePureComponent {
domains: ImmutablePropTypes.orderedSet,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
};
componentWillMount () {
@ -44,7 +47,7 @@ class Blocks extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
const { intl, domains, hasMore, multiColumn } = this.props;
const { intl, domains, hasMore, multiColumn, columnWidth } = this.props;
if (!domains) {
return (
@ -57,7 +60,7 @@ class Blocks extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no blocked domains yet.' />;
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 />
<ScrollableList
scrollKey='domain_blocks'

View file

@ -9,11 +9,15 @@ import { expandDomainTimeline } from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import ColumnSettingsContainer from './containers/column_settings_container';
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 uuid = props.columnId;
const columns = state.getIn(['settings', 'columns']);
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 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']);
@ -26,6 +30,7 @@ const mapStateToProps = (state, props) => {
withoutMedia,
withoutBot,
domain: domain,
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
@ -50,6 +55,7 @@ class DomainTimeline extends React.PureComponent {
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
onlyMedia: PropTypes.bool,
withoutMedia: PropTypes.bool,
withoutBot: PropTypes.bool,
@ -109,12 +115,22 @@ class DomainTimeline extends React.PureComponent {
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 () {
const { hasUnread, columnId, multiColumn, onlyMedia, withoutMedia, withoutBot, domain } = this.props;
const { hasUnread, columnId, multiColumn, onlyMedia, withoutMedia, withoutBot, domain, columnWidth } = this.props;
const pinned = !!columnId;
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={domain}>
<Column bindToDocument={!multiColumn} ref={this.setRef} label={domain} columnWidth={columnWidth}>
<ColumnHeader
icon='users'
active={hasUnread}
@ -125,6 +141,8 @@ class DomainTimeline extends React.PureComponent {
pinned={pinned}
multiColumn={multiColumn}
showBackButton
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
>
<ColumnSettingsContainer columnId={columnId} />
</ColumnHeader>

View file

@ -10,16 +10,27 @@ import StatusList from '../../components/status_list';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({
heading: { id: 'column.emoji_reactions', defaultMessage: 'EmojiReactions' },
});
const mapStateToProps = state => ({
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']),
});
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', '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)
@injectIntl
@ -31,6 +42,7 @@ class EmojiReactions extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasMore: PropTypes.bool,
isLoading: PropTypes.bool,
};
@ -66,14 +78,24 @@ class EmojiReactions extends ImmutablePureComponent {
this.props.dispatch(expandEmojiReactionedStatuses());
}, 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 () {
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading, columnWidth } = this.props;
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." />;
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
icon='star'
title={intl.formatMessage(messages.heading)}
@ -82,6 +104,8 @@ class EmojiReactions extends ImmutablePureComponent {
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
showBackButton
/>

View file

@ -16,6 +16,9 @@ import { createSelector } from 'reselect';
import { Map as ImmutableMap } from 'immutable';
import ReactedHeaderContaier from '../reactioned/containers/header_container';
import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({
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 mapStateToProps = (state, props) => ({
emojiReactions: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'items']),
isLoading: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'isLoading'], true),
hasMore: !!state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'next']),
emojiMap: customEmojiMap(state),
});
const mapStateToProps = (state, { columnId, params }) => {
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', '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 {
@ -65,6 +76,7 @@ class EmojiReactions extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired,
emojiReactions: ImmutablePropTypes.list,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
emojiMap: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
hasMore: PropTypes.bool,
@ -91,8 +103,18 @@ class EmojiReactions extends ImmutablePureComponent {
this.props.dispatch(expandEmojiReactions(this.props.params.statusId));
}, 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 () {
const { intl, emojiReactions, multiColumn, emojiMap, hasMore, isLoading } = this.props;
const { intl, emojiReactions, multiColumn, emojiMap, hasMore, isLoading, columnWidth } = this.props;
if (!emojiReactions) {
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.' />;
return (
<Column bindToDocument={!multiColumn}>
<Column bindToDocument={!multiColumn} columnWidth={columnWidth}>
<ColumnHeader
showBackButton
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
extraButton={(
<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 ImmutablePureComponent from 'react-immutable-pure-component';
import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({
heading: { id: 'column.favourites', defaultMessage: 'Favourites' },
});
const mapStateToProps = state => ({
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
});
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', '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)
@injectIntl
@ -31,6 +42,7 @@ class Favourites extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasMore: PropTypes.bool,
isLoading: PropTypes.bool,
};
@ -66,14 +78,24 @@ class Favourites extends ImmutablePureComponent {
this.props.dispatch(expandFavouritedStatuses());
}, 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 () {
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading, columnWidth } = this.props;
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." />;
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
icon='star'
title={intl.formatMessage(messages.heading)}
@ -82,6 +104,8 @@ class Favourites extends ImmutablePureComponent {
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
showBackButton
/>

View file

@ -13,16 +13,27 @@ import Icon from 'mastodon/components/icon';
import ColumnHeader from '../../components/column_header';
import ReactedHeaderContaier from '../reactioned/containers/header_container';
import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
});
const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'items']),
isLoading: state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'isLoading'], true),
hasMore: !!state.getIn(['user_lists', 'favourited_by', props.params.statusId, 'next']),
});
const mapStateToProps = (state, { columnId, params }) => {
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', '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)
@injectIntl
@ -33,6 +44,7 @@ class Favourites extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
intl: PropTypes.object.isRequired,
hasMore: PropTypes.bool,
isLoading: PropTypes.bool,
@ -58,8 +70,18 @@ class Favourites extends ImmutablePureComponent {
this.props.dispatch(expandFavourites(this.props.params.statusId));
}, 300, { leading: true })
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['favourites', 'columnWidth'], value));
}
}
render () {
const { intl, accountIds, multiColumn, hasMore, isLoading } = this.props;
const { intl, accountIds, multiColumn, hasMore, isLoading, columnWidth } = this.props;
if (!accountIds) {
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.' />;
return (
<Column bindToDocument={!multiColumn}>
<Column bindToDocument={!multiColumn} columnWidth={columnWidth}>
<ColumnHeader
showBackButton
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
extraButton={(
<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 { fetchFollowRequests, expandFollowRequests } from '../../actions/accounts';
import ScrollableList from '../../components/scrollable_list';
import { me } from '../../initial_state';
import { me, defaultColumnWidth } from '../../initial_state';
const messages = defineMessages({
heading: { id: 'column.follow_requests', defaultMessage: 'Follow requests' },
@ -23,6 +23,7 @@ const mapStateToProps = state => ({
hasMore: !!state.getIn(['user_lists', 'follow_requests', 'next']),
locked: !!state.getIn(['accounts', me, 'locked']),
domain: state.getIn(['meta', 'domain']),
columnWidth: defaultColumnWidth,
});
export default @connect(mapStateToProps)
@ -39,6 +40,7 @@ class FollowRequests extends ImmutablePureComponent {
domain: PropTypes.string,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
};
componentWillMount () {
@ -50,7 +52,7 @@ class FollowRequests extends ImmutablePureComponent {
}, 300, { leading: true });
render () {
const { intl, accountIds, hasMore, multiColumn, locked, domain, isLoading } = this.props;
const { intl, accountIds, hasMore, multiColumn, locked, domain, isLoading, columnWidth } = this.props;
if (!accountIds) {
return (
@ -72,7 +74,7 @@ class FollowRequests extends ImmutablePureComponent {
);
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 />
<ScrollableList
scrollKey='follow_requests'

View file

@ -19,7 +19,8 @@ import HeaderContainer from '../account_timeline/containers/header_container';
import ScrollableList from '../../components/scrollable_list';
import MissingIndicator from 'mastodon/components/missing_indicator';
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({
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),
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true),
columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth),
});
const RemoteHint = ({ url }) => (
@ -60,6 +62,7 @@ class Followers extends ImmutablePureComponent {
remote: PropTypes.bool,
remoteUrl: PropTypes.string,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
};
componentWillMount () {
@ -84,12 +87,16 @@ class Followers extends ImmutablePureComponent {
this.column.scrollTop();
}
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['account', 'columnWidth'], value));
}
setRef = c => {
this.column = c;
}
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) {
return (
@ -120,7 +127,7 @@ class Followers extends ImmutablePureComponent {
const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;
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
icon='user'
active={false}
@ -128,6 +135,8 @@ class Followers extends ImmutablePureComponent {
onClick={this.handleHeaderClick}
pinned={false}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
>
<ColumnSettingsContainer />
</ColumnHeader>

View file

@ -19,7 +19,8 @@ import HeaderContainer from '../account_timeline/containers/header_container';
import ScrollableList from '../../components/scrollable_list';
import MissingIndicator from 'mastodon/components/missing_indicator';
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({
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),
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true),
columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth),
});
const RemoteHint = ({ url }) => (
@ -60,6 +62,7 @@ class Following extends ImmutablePureComponent {
remote: PropTypes.bool,
remoteUrl: PropTypes.string,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
};
componentWillMount () {
@ -84,12 +87,16 @@ class Following extends ImmutablePureComponent {
this.column.scrollTop();
}
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['account', 'columnWidth'], value));
}
setRef = c => {
this.column = c;
}
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) {
return (
@ -120,7 +127,7 @@ class Following extends ImmutablePureComponent {
const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;
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
icon='user'
active={false}
@ -128,6 +135,8 @@ class Following extends ImmutablePureComponent {
onClick={this.handleHeaderClick}
pinned={false}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
>
<ColumnSettingsContainer />
</ColumnHeader>

View file

@ -7,7 +7,7 @@ import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
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 { fetchFavouriteDomains } from 'mastodon/actions/favourite_domains';
import { fetchFavouriteTags } from 'mastodon/actions/favourite_tags';
@ -61,6 +61,7 @@ const mapStateToProps = state => ({
lists: getOrderedLists(state),
favourite_domains: getOrderedDomains(state),
favourite_tags: getOrderedTags(state),
columnWidth: defaultColumnWidth,
});
const mapDispatchToProps = dispatch => ({
@ -94,6 +95,7 @@ class GettingStarted extends ImmutablePureComponent {
myAccount: ImmutablePropTypes.map.isRequired,
columns: ImmutablePropTypes.list,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
fetchFollowRequests: PropTypes.func.isRequired,
fetchFavouriteDomains: PropTypes.func.isRequired,
fetchFavouriteTags: PropTypes.func.isRequired,
@ -118,7 +120,7 @@ class GettingStarted extends ImmutablePureComponent {
}
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 = [];
let height = (multiColumn) ? 0 : 60;
@ -264,7 +266,7 @@ class GettingStarted extends ImmutablePureComponent {
}
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'>
<h1 className='column-header'>
<button>

View file

@ -13,6 +13,8 @@ import RadioButton from 'mastodon/components/radio_button';
import classNames from 'classnames';
import LoadMore from 'mastodon/components/load_more';
import { ScrollContainer } from 'react-router-scroll-4';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
const messages = defineMessages({
title: { id: 'column.group_directory', defaultMessage: 'Browse groups' },
@ -23,6 +25,7 @@ const messages = defineMessages({
const mapStateToProps = state => ({
accountIds: state.getIn(['user_lists', 'group_directory', 'items'], ImmutableList()),
isLoading: state.getIn(['user_lists', 'group_directory', 'isLoading'], true),
columnWidth: state.getIn(['settings', 'group_directory', 'columnWidth'], defaultColumnWidth),
});
export default @connect(mapStateToProps)
@ -40,6 +43,7 @@ class GroupDirectory extends React.PureComponent {
columnId: PropTypes.string,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
params: PropTypes.shape({
order: PropTypes.string,
}),
@ -107,8 +111,12 @@ class GroupDirectory extends React.PureComponent {
dispatch(expandGroupDirectory(this.getParams(this.props, this.state)));
}
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['group_directory', 'columnWidth'], value));
}
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 pinned = !!columnId;
@ -130,7 +138,7 @@ class GroupDirectory extends React.PureComponent {
);
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
icon='address-book-o'
title={intl.formatMessage(messages.title)}
@ -139,6 +147,8 @@ class GroupDirectory extends React.PureComponent {
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
/>
{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 GroupDetail from './components/group_detail';
import { connectGroupStream } from '../../actions/streaming';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({
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 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 columnWidth = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'columnWidth']) : state.getIn(['settings', 'group', 'columnWidth']);
const account = getAccount(state, id);
return {
@ -39,6 +43,7 @@ const makeMapStateToProps = () => {
onlyMedia,
withoutMedia,
account,
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
@ -66,6 +71,7 @@ class GroupTimeline extends React.PureComponent {
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
onlyMedia: PropTypes.bool,
withoutMedia: PropTypes.bool,
};
@ -138,8 +144,18 @@ class GroupTimeline extends React.PureComponent {
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 () {
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 { collapsed, animating } = this.state;
@ -179,7 +195,7 @@ class GroupTimeline extends React.PureComponent {
);
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={title}>
<Column bindToDocument={!multiColumn} ref={this.setRef} label={title} columnWidth={columnWidth}>
<ColumnHeader
icon='users'
active={hasUnread}
@ -190,6 +206,8 @@ class GroupTimeline extends React.PureComponent {
pinned={pinned}
multiColumn={multiColumn}
extraButton={groupDetailButton}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
>
<ColumnSettingsContainer columnId={columnId} />
</ColumnHeader>

View file

@ -14,16 +14,27 @@ import { isEqual } from 'lodash';
import { fetchHashtag, followHashtag, unfollowHashtag } from 'mastodon/actions/tags';
import Icon from 'mastodon/components/icon';
import classNames from 'classnames';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({
followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' },
unfollowHashtag: { id: 'hashtag.unfollow', defaultMessage: 'Unfollow hashtag' },
});
const mapStateToProps = (state, props) => ({
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,
tag: state.getIn(['tags', props.params.id]),
});
const mapStateToProps = (state, { columnId, params }) => {
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', '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)
@injectIntl
@ -38,6 +49,7 @@ class HashtagTimeline extends React.PureComponent {
hasUnread: PropTypes.bool,
tag: ImmutablePropTypes.map,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
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 () {
const { hasUnread, columnId, multiColumn, tag, intl } = this.props;
const { hasUnread, columnId, multiColumn, tag, columnWidth, intl } = this.props;
const { id } = this.props.params;
const pinned = !!columnId;
@ -184,7 +206,7 @@ class HashtagTimeline extends React.PureComponent {
}
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}>
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`} columnWidth={columnWidth}>
<ColumnHeader
icon='hashtag'
active={hasUnread}
@ -196,6 +218,8 @@ class HashtagTimeline extends React.PureComponent {
multiColumn={multiColumn}
extraButton={followButton}
showBackButton
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
>
{columnId && <ColumnSettingsContainer columnId={columnId} />}
</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 classNames from 'classnames';
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({
title: { id: 'column.home', defaultMessage: 'Home' },
@ -21,14 +24,22 @@ const messages = defineMessages({
hide_announcements: { id: 'home.hide_announcements', defaultMessage: 'Hide announcements' },
});
const mapStateToProps = state => ({
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),
});
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', 'home', 'columnWidth']);
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)
@injectIntl
@ -41,6 +52,7 @@ class HomeTimeline extends React.PureComponent {
isPartial: PropTypes.bool,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasAnnouncements: PropTypes.bool,
unreadAnnouncements: PropTypes.number,
showAnnouncements: PropTypes.bool,
@ -121,8 +133,18 @@ class HomeTimeline extends React.PureComponent {
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 () {
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements, columnWidth } = this.props;
const pinned = !!columnId;
let announcementsButton = null;
@ -142,7 +164,7 @@ class HomeTimeline extends React.PureComponent {
}
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
icon='home'
active={hasUnread}
@ -154,6 +176,8 @@ class HomeTimeline extends React.PureComponent {
multiColumn={multiColumn}
extraButton={announcementsButton}
appendContent={hasAnnouncements && showAnnouncements && <AnnouncementsContainer />}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
>
<ColumnSettingsContainer />
</ColumnHeader>

View file

@ -4,24 +4,31 @@ import ColumnBackButtonSlim from '../../components/column_back_button_slim';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { defaultColumnWidth } from 'mastodon/initial_state';
const messages = defineMessages({
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 {
static propTypes = {
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
};
render () {
const { intl, multiColumn } = this.props;
const { intl, multiColumn, columnWidth } = this.props;
return (
<Column bindToDocument={!multiColumn} icon='question' heading={intl.formatMessage(messages.heading)}>
<Column bindToDocument={!multiColumn} icon='question' heading={intl.formatMessage(messages.heading)} columnWidth={columnWidth}>
<ColumnBackButtonSlim />
<div className='keyboard-shortcuts scrollable optionally-scrollable'>
<table>

View file

@ -9,15 +9,26 @@ import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { getLimitedVisibilities } from 'mastodon/selectors';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
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({
title: { id: 'column.limited', defaultMessage: 'Limited' },
});
const mapStateToProps = state => ({
hasUnread: state.getIn(['timelines', 'limited', 'unread']) > 0,
visibilities: getLimitedVisibilities(state),
});
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', 'limited', 'columnWidth']);
return {
hasUnread: state.getIn(['timelines', 'limited', 'unread']) > 0,
visibilities: getLimitedVisibilities(state),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps)
@injectIntl
@ -30,6 +41,7 @@ class LimitedTimeline extends React.PureComponent {
visibilities: PropTypes.arrayOf(PropTypes.string),
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
};
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 () {
const { intl, hasUnread, columnId, multiColumn } = this.props;
const { intl, hasUnread, columnId, multiColumn, columnWidth } = this.props;
const pinned = !!columnId;
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
icon='lock'
active={hasUnread}
@ -90,6 +112,8 @@ class LimitedTimeline extends React.PureComponent {
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
>
<ColumnSettingsContainer />
</ColumnHeader>

View file

@ -16,6 +16,9 @@ import MissingIndicator from '../../components/missing_indicator';
import LoadingIndicator from '../../components/loading_indicator';
import Icon from 'mastodon/components/icon';
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({
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' },
});
const mapStateToProps = (state, props) => ({
list: state.getIn(['lists', props.params.id]),
hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
});
const mapStateToProps = (state, { columnId, params }) => {
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', '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)
@injectIntl
@ -44,6 +55,7 @@ class ListTimeline extends React.PureComponent {
columnId: PropTypes.string,
hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
intl: PropTypes.object.isRequired,
};
@ -140,8 +152,18 @@ class ListTimeline extends React.PureComponent {
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 () {
const { hasUnread, columnId, multiColumn, list, intl } = this.props;
const { hasUnread, columnId, multiColumn, list, columnWidth, intl } = this.props;
const { id } = this.props.params;
const pinned = !!columnId;
const title = list ? list.get('title') : id;
@ -165,7 +187,7 @@ class ListTimeline extends React.PureComponent {
}
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={title}>
<Column bindToDocument={!multiColumn} ref={this.setRef} label={title} columnWidth={columnWidth}>
<ColumnHeader
icon='list-ul'
active={hasUnread}
@ -175,6 +197,8 @@ class ListTimeline extends React.PureComponent {
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
>
<div className='column-settings__row column-header__links'>
<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 { createSelector } from 'reselect';
import ScrollableList from '../../components/scrollable_list';
import { defaultColumnWidth } from 'mastodon/initial_state';
const messages = defineMessages({
heading: { id: 'column.lists', defaultMessage: 'Lists' },
@ -29,6 +30,7 @@ const getOrderedLists = createSelector([state => state.get('lists')], lists => {
const mapStateToProps = state => ({
lists: getOrderedLists(state),
columnWidth: defaultColumnWidth,
});
export default @connect(mapStateToProps)
@ -41,6 +43,7 @@ class Lists extends ImmutablePureComponent {
lists: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
};
componentWillMount () {
@ -48,7 +51,7 @@ class Lists extends ImmutablePureComponent {
}
render () {
const { intl, lists, multiColumn } = this.props;
const { intl, lists, multiColumn, columnWidth } = this.props;
if (!lists) {
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." />;
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 />
<NewListForm />

View file

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

View file

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

View file

@ -26,6 +26,9 @@ import LoadGap from '../../components/load_gap';
import Icon from 'mastodon/components/icon';
import compareId from 'mastodon/compare_id';
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({
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
@ -53,17 +56,25 @@ const getNotifications = createSelector([
return notifications.filter(item => item === null || allowedType === item.get('type'));
});
const mapStateToProps = state => ({
showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
notifications: getNotifications(state),
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']),
});
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', 'notifications', 'columnWidth']);
return {
showFilterBar: state.getIn(['settings', 'notifications', 'quickFilter', 'show']),
notifications: getNotifications(state),
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)
@injectIntl
@ -78,6 +89,7 @@ class Notifications extends React.PureComponent {
isLoading: PropTypes.bool,
isUnread: PropTypes.bool,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasMore: PropTypes.bool,
numPending: PropTypes.number,
lastReadId: PropTypes.string,
@ -174,8 +186,18 @@ class Notifications extends React.PureComponent {
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 () {
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 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 (
<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
icon='bell'
active={isUnread}
@ -259,6 +281,8 @@ class Notifications extends React.PureComponent {
pinned={pinned}
multiColumn={multiColumn}
extraButton={extraButton}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
>
<ColumnSettingsContainer />
</ColumnHeader>

View file

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

View file

@ -9,6 +9,9 @@ import { expandPublicTimeline } from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import ColumnSettingsContainer from './containers/column_settings_container';
import { connectPublicStream } from '../../actions/streaming';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({
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 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 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' : ''}`]);
return {
@ -30,6 +34,7 @@ const mapStateToProps = (state, { columnId }) => {
withoutMedia,
withoutBot,
onlyRemote,
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
@ -53,6 +58,7 @@ class PublicTimeline extends React.PureComponent {
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasUnread: PropTypes.bool,
onlyMedia: PropTypes.bool,
withoutMedia: PropTypes.bool,
@ -79,6 +85,16 @@ class PublicTimeline extends React.PureComponent {
this.column.scrollTop();
}
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['public', 'columnWidth'], value));
}
}
componentDidMount () {
const { dispatch, onlyMedia, withoutMedia, withoutBot, onlyRemote } = this.props;
@ -114,11 +130,11 @@ class PublicTimeline extends React.PureComponent {
}
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;
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
icon='globe'
active={hasUnread}
@ -128,6 +144,8 @@ class PublicTimeline extends React.PureComponent {
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
>
<ColumnSettingsContainer columnId={columnId} />
</ColumnHeader>

View file

@ -13,16 +13,27 @@ import Icon from 'mastodon/components/icon';
import ColumnHeader from '../../components/column_header';
import ReactedHeaderContaier from '../reactioned/containers/header_container';
import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
});
const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'reblogged_by', props.params.statusId, 'items']),
isLoading: state.getIn(['user_lists', 'reblogged_by', props.params.statusId, 'isLoading'], true),
hasMore: !!state.getIn(['user_lists', 'reblogged_by', props.params.statusId, 'next']),
});
const mapStateToProps = (state, { columnId, params }) => {
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', '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)
@injectIntl
@ -33,6 +44,7 @@ class Reblogs extends ImmutablePureComponent {
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
intl: PropTypes.object.isRequired,
hasMore: PropTypes.bool,
isLoading: PropTypes.bool,
@ -58,8 +70,18 @@ class Reblogs extends ImmutablePureComponent {
this.props.dispatch(expandReblogs(this.props.params.statusId));
}, 300, { leading: true })
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['reblogs', 'columnWidth'], value));
}
}
render () {
const { intl, accountIds, multiColumn, hasMore, isLoading } = this.props;
const { intl, accountIds, multiColumn, hasMore, isLoading, columnWidth } = this.props;
if (!accountIds) {
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.' />;
return (
<Column bindToDocument={!multiColumn}>
<Column bindToDocument={!multiColumn} columnWidth={columnWidth}>
<ColumnHeader
showBackButton
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
extraButton={(
<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 ReactedHeaderContaier from '../reactioned/containers/header_container';
import { debounce } from 'lodash';
import { defaultColumnWidth } from 'mastodon/initial_state';
import { changeSetting } from '../../actions/settings';
import { changeColumnParams } from '../../actions/columns';
const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
heading: { id: 'column.referred_by_statuses', defaultMessage: 'Referred by posts' },
});
const mapStateToProps = (state, props) => ({
statusIds: state.getIn(['status_status_lists', 'referred_by', props.params.statusId, 'items']),
isLoading: state.getIn(['status_status_lists', 'referred_by', props.params.statusId, 'isLoading'], true),
hasMore: !!state.getIn(['status_status_lists', 'referred_by', props.params.statusId, 'next']),
});
const mapStateToProps = (state, { columnId, params }) => {
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', '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)
@injectIntl
@ -33,6 +44,7 @@ class ReferredByStatuses extends ImmutablePureComponent {
statusIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
hasMore: PropTypes.bool,
isLoading: PropTypes.bool,
};
@ -57,8 +69,18 @@ class ReferredByStatuses extends ImmutablePureComponent {
this.props.dispatch(expandReferredByStatuses(this.props.params.statusId));
}, 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 () {
const { intl, statusIds, multiColumn, hasMore, isLoading } = this.props;
const { intl, statusIds, multiColumn, hasMore, isLoading, columnWidth } = this.props;
if (!statusIds) {
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." />;
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
showBackButton
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
extraButton={(
<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 Icon from 'mastodon/components/icon';
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({
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
@ -148,8 +151,12 @@ const makeMapStateToProps = () => {
return ImmutableList(contextReference.get(statusId));
});
const mapStateToProps = (state, props) => {
const status = getStatus(state, { id: props.params.statusId });
const mapStateToProps = (state, { columnId, params }) => {
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 descendantsIds = status ? getDescendantsIds(state, { id: status.get('id') }) : ImmutableList();
const referencesIds = status ? getReferencesIds(state, { id: status.get('id') }) : ImmutableList();
@ -161,10 +168,11 @@ const makeMapStateToProps = () => {
descendantsIds,
askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
domain: state.getIn(['meta', 'domain']),
pictureInPicture: getPictureInPicture(state, { id: props.params.statusId }),
pictureInPicture: getPictureInPicture(state, { id: params.statusId }),
emojiMap: customEmojiMap(state),
referenced: state.getIn(['compose', '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,
askReplyConfirmation: PropTypes.bool,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
domain: PropTypes.string.isRequired,
pictureInPicture: ImmutablePropTypes.contains({
inUse: PropTypes.bool,
@ -561,9 +570,19 @@ class Status extends ImmutablePureComponent {
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 () {
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;
if (status === null) {
@ -599,10 +618,12 @@ class Status extends ImmutablePureComponent {
};
return (
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.detailedStatus)}>
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.detailedStatus)} columnWidth={columnWidth}>
<ColumnHeader
showBackButton
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
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>
)}

View file

@ -22,6 +22,9 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
import Icon from 'mastodon/components/icon';
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({
revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
@ -39,13 +42,18 @@ const makeMapStateToProps = () => {
return ImmutableList(contextReference.get(statusId));
});
const mapStateToProps = (state, props) => {
const status = getStatus(state, { id: props.params.statusId });
const mapStateToProps = (state, { columnId, params }) => {
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();
return {
status,
referencesIds,
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
@ -67,6 +75,7 @@ class StatusReferences extends ImmutablePureComponent {
referencesIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
};
state = {
@ -154,8 +163,18 @@ class StatusReferences extends ImmutablePureComponent {
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 () {
const { status, referencesIds, intl, multiColumn } = this.props;
const { status, referencesIds, intl, multiColumn, columnWidth } = this.props;
const { fullscreen } = this.state;
if (status === null) {
@ -173,10 +192,12 @@ class StatusReferences extends ImmutablePureComponent {
}
return (
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.detailedStatus)}>
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.detailedStatus)} columnWidth={columnWidth}>
<ColumnHeader
showBackButton
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
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>
)}

View file

@ -18,7 +18,8 @@ import ColumnSettingsContainer from '../account_timeline/containers/column_setti
import HeaderContainer from '../account_timeline/containers/header_container';
import ScrollableList from '../../components/scrollable_list';
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({
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),
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
advancedMode: state.getIn(['settings', 'account', 'other', 'advancedMode'], new_features_policy === 'conservative' ? false : true),
columnWidth: state.getIn(['settings', 'account', 'columnWidth'], defaultColumnWidth),
});
export default @connect(mapStateToProps)
@ -46,6 +48,7 @@ class Subscribing extends ImmutablePureComponent {
blockedBy: PropTypes.bool,
isAccount: PropTypes.bool,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
};
componentWillMount () {
@ -70,12 +73,16 @@ class Subscribing extends ImmutablePureComponent {
this.column.scrollTop();
}
handleWidthChange = (value) => {
this.props.dispatch(changeSetting(['account', 'columnWidth'], value));
}
setRef = c => {
this.column = c;
}
render () {
const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, intl } = this.props;
const { accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, columnWidth, intl } = this.props;
if (!isAccount) {
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." />;
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
icon='user'
active={false}
@ -104,6 +111,8 @@ class Subscribing extends ImmutablePureComponent {
onClick={this.handleHeaderClick}
pinned={false}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
>
<ColumnSettingsContainer />
</ColumnHeader>

View file

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

View file

@ -12,6 +12,9 @@ import ScrollableList from 'mastodon/components/scrollable_list';
import Hashtag from 'mastodon/components/hashtag';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
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({
heading: { id: 'trends.heading', defaultMessage: 'Trends' },
@ -19,10 +22,18 @@ const messages = defineMessages({
refresh: { id: 'refresh', defaultMessage: 'Refresh' },
});
const mapStateToProps = state => ({
trends: state.getIn(['trends', 'items']),
isLoading: state.getIn(['trends', 'isLoading'], true),
});
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', 'trends', 'columnWidth']);
return {
trends: state.getIn(['trends', 'items']),
isLoading: state.getIn(['trends', 'isLoading'], true),
columnWidth: columnWidth ?? defaultColumnWidth,
};
};
export default @connect(mapStateToProps)
@injectIntl
@ -34,6 +45,7 @@ class Trends extends ImmutablePureComponent {
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
multiColumn: PropTypes.bool,
columnWidth: PropTypes.string,
isLoading: PropTypes.bool,
};
@ -81,14 +93,24 @@ class Trends extends ImmutablePureComponent {
this.column = c;
}
handleWidthChange = (value) => {
const { columnId, dispatch } = this.props;
if (columnId) {
dispatch(changeColumnParams(columnId, 'columnWidth', value));
} else {
dispatch(changeSetting(['trends', 'columnWidth'], value));
}
}
render () {
const { intl, trends, columnId, multiColumn, isLoading } = this.props;
const { intl, trends, columnId, multiColumn, isLoading, columnWidth } = this.props;
const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.trends' defaultMessage='No one has trends yet.' />;
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
icon='line-chart'
title={intl.formatMessage(messages.heading)}
@ -97,6 +119,8 @@ class Trends extends ImmutablePureComponent {
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
columnWidth={columnWidth}
onWidthChange={this.handleWidthChange}
showBackButton
extraButton={(
<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 { scrollTop } from '../../../scroll';
import { isMobile } from '../../../is_mobile';
import classNames from 'classnames';
export default class Column extends React.PureComponent {
@ -13,6 +14,7 @@ export default class Column extends React.PureComponent {
children: PropTypes.node,
active: PropTypes.bool,
hideHeadingOnMobile: PropTypes.bool,
columnWidth: PropTypes.string,
};
handleHeaderClick = () => {
@ -47,7 +49,7 @@ export default class Column extends React.PureComponent {
}
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)));
@ -60,7 +62,7 @@ export default class Column extends React.PureComponent {
ref={this.setRef}
role='region'
aria-labelledby={columnHeaderId}
className='column'
className={classNames('column', columnWidth)}
onScroll={this.handleScroll}
>
{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 enableEmptyColumn = getMeta('enable_empty_column');
export const showReloadButton = getMeta('show_reload_button');
export const defaultColumnWidth = getMeta('default_column_width');
export default initialState;

View file

@ -140,6 +140,11 @@
"column_subheading.favourite_lists": "Favourite lists",
"column_subheading.favourite_tags": "Favourite tags",
"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.media_only": "Media only",
"community.column_settings.remote_only": "Remote only",

View file

@ -140,6 +140,11 @@
"column_subheading.favourite_lists": "お気に入りリスト",
"column_subheading.favourite_tags": "お気に入りハッシュタグ",
"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.media_only": "メディアのみ表示",
"community.column_settings.remote_only": "リモートのみ表示",

View file

@ -2346,6 +2346,57 @@ a.account__display-name {
> .scrollable {
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 {

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['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['default_column_width'] = default_column_width_preference if change?('setting_default_column_width')
end
def merged_notification_emails
@ -331,6 +332,10 @@ end
boolean_cast_setting 'setting_show_reload_button'
end
def default_column_width_preference
settings['setting_default_column_width']
end
def boolean_cast_setting(key)
ActiveModel::Type::Boolean.new.cast(settings[key])
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,
:hide_bot_on_public_timeline, :confirm_follow_from_bot,
:default_search_searchability,
:show_reload_button,
:show_reload_button, :default_column_width,
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[: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[:default_column_width] = object.current_account.user.setting_default_column_width
else
store[:auto_play_gif] = Setting.auto_play_gif
store[:display_media] = Setting.display_media

View file

@ -62,6 +62,9 @@
.fields-group
= 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'
.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_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_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_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
@ -219,6 +224,7 @@ en:
setting_content_emoji_reaction_size: Emoji reaction size
setting_content_font_size: Content font size
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_privacy: Posting privacy
setting_default_search_searchability: Search range

View file

@ -57,6 +57,11 @@ ja:
setting_aggregate_reblogs: 最近ブーストされた投稿が新たにブーストされても表示しません (設定後受信したものにのみ影響)
setting_compact_reaction: 詳細表示以外の絵文字リアクション表示を件数のみにする
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_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります
setting_disable_joke_appearance: エイプリルフール等のジョーク機能を無効にします
@ -219,6 +224,7 @@ ja:
setting_content_emoji_reaction_size: 投稿の絵文字リアクションのサイズ
setting_content_font_size: 投稿のフォントサイズ
setting_crop_images: 投稿の詳細以外では画像を16:9に切り抜く
setting_default_column_width: デフォルトのカラム幅
setting_default_language: 投稿する言語
setting_default_privacy: 投稿の公開範囲
setting_default_search_searchability: 検索の対象とする範囲

View file

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