Commit b217c535 authored by Takeshi Umeda's avatar Takeshi Umeda
Browse files

Remove local timeline for fedibird.com

parent bfadecc6
......@@ -39,7 +39,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
any: params[:any],
all: params[:all],
none: params[:none],
local: truthy_param?(:local),
local: false,
remote: truthy_param?(:remote),
only_media: truthy_param?(:only_media)
)
......@@ -50,7 +50,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
end
def pagination_params(core_params)
params.slice(:local, :limit, :only_media).permit(:local, :limit, :only_media).merge(core_params)
params.slice(:limit, :only_media).permit(:limit, :only_media).merge(core_params)
end
def next_path
......
......@@ -43,7 +43,7 @@ class TagsController < ApplicationController
end
def set_local
@local = truthy_param?(:local)
@local = false
end
def set_statuses
......
......@@ -196,7 +196,6 @@ export function submitCompose(routerHistory) {
}
if (response.data.in_reply_to_id === null && response.data.visibility === 'public') {
insertIfOnline('community');
insertIfOnline('public');
insertIfOnline(`account:${response.data.account.id}`);
}
......
......@@ -117,14 +117,6 @@ const refreshHomeTimelineAndNotification = (dispatch, done) => {
export const connectUserStream = () =>
connectTimelineStream('home', 'user', {}, { fallback: refreshHomeTimelineAndNotification });
/**
* @param {Object} options
* @param {boolean} [options.onlyMedia]
* @return {function(): void}
*/
export const connectCommunityStream = ({ onlyMedia } = {}) =>
connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
/**
* @param {string} domain
* @param {Object} options
......@@ -146,12 +138,11 @@ export const connectPublicStream = ({ onlyMedia, onlyRemote } = {}) =>
/**
* @param {string} columnId
* @param {string} tagName
* @param {boolean} onlyLocal
* @param {function(object): boolean} accept
* @return {function(): void}
*/
export const connectHashtagStream = (columnId, tagName, onlyLocal, accept) =>
connectTimelineStream(`hashtag:${columnId}${onlyLocal ? ':local' : ''}`, `hashtag${onlyLocal ? ':local' : ''}`, { tag: tagName }, { accept });
export const connectHashtagStream = (columnId, tagName, accept) =>
connectTimelineStream(`hashtag:${columnId}`, `hashtag`, { tag: tagName }, { accept });
/**
* @return {function(): void}
......
......@@ -121,19 +121,17 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
export const expandPublicTimeline = ({ maxId, onlyMedia, onlyRemote } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia }, done);
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
export const expandDomainTimeline = (domain, { maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`domain${onlyMedia ? ':media' : ''}:${domain}`, '/api/v1/timelines/public', { local: false, domain: domain, max_id: maxId, only_media: !!onlyMedia }, done);
export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 });
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
export const expandHashtagTimeline = (hashtag, { maxId, tags, local } = {}, done = noOp) => {
return expandTimeline(`hashtag:${hashtag}${local ? ':local' : ''}`, `/api/v1/timelines/tag/${hashtag}`, {
export const expandHashtagTimeline = (hashtag, { maxId, tags } = {}, done = noOp) => {
return expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, {
max_id: maxId,
any: parseTags(tags, 'any'),
all: parseTags(tags, 'all'),
none: parseTags(tags, 'none'),
local: local,
}, done);
};
......
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, FormattedMessage } from 'react-intl';
import SettingToggle from '../../notifications/components/setting_toggle';
export default @injectIntl
class ColumnSettings extends React.PureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
};
render () {
const { settings, onChange } = this.props;
return (
<div>
<div className='column-settings__row'>
<SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media only' />} />
</div>
</div>
);
}
}
import { connect } from 'react-redux';
import ColumnSettings from '../components/column_settings';
import { changeSetting } from '../../../actions/settings';
import { changeColumnParams } from '../../../actions/columns';
const mapStateToProps = (state, { columnId }) => {
const uuid = columnId;
const columns = state.getIn(['settings', 'columns']);
const index = columns.findIndex(c => c.get('uuid') === uuid);
return {
settings: (uuid && index >= 0) ? columns.get(index).get('params') : state.getIn(['settings', 'community']),
};
};
const mapDispatchToProps = (dispatch, { columnId }) => {
return {
onChange (key, checked) {
if (columnId) {
dispatch(changeColumnParams(columnId, key, checked));
} else {
dispatch(changeSetting(['community', ...key], checked));
}
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
import React from 'react';
import { connect } from 'react-redux';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import StatusListContainer from '../ui/containers/status_list_container';
import Column from '../../components/column';
import ColumnHeader from '../../components/column_header';
import { expandCommunityTimeline } from '../../actions/timelines';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import ColumnSettingsContainer from './containers/column_settings_container';
import { connectCommunityStream } from '../../actions/streaming';
const messages = defineMessages({
title: { id: 'column.community', defaultMessage: 'Local timeline' },
});
const mapStateToProps = (state, { columnId }) => {
const uuid = columnId;
const columns = state.getIn(['settings', 'columns']);
const index = columns.findIndex(c => c.get('uuid') === uuid);
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'community', 'other', 'onlyMedia']);
const timelineState = state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`]);
return {
hasUnread: !!timelineState && timelineState.get('unread') > 0,
onlyMedia,
};
};
export default @connect(mapStateToProps)
@injectIntl
class CommunityTimeline extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
};
static defaultProps = {
onlyMedia: false,
};
static propTypes = {
dispatch: PropTypes.func.isRequired,
shouldUpdateScroll: PropTypes.func,
columnId: PropTypes.string,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
multiColumn: PropTypes.bool,
onlyMedia: PropTypes.bool,
};
handlePin = () => {
const { columnId, dispatch, onlyMedia } = this.props;
if (columnId) {
dispatch(removeColumn(columnId));
} else {
dispatch(addColumn('COMMUNITY', { other: { onlyMedia } }));
}
}
handleMove = (dir) => {
const { columnId, dispatch } = this.props;
dispatch(moveColumn(columnId, dir));
}
handleHeaderClick = () => {
this.column.scrollTop();
}
componentDidMount () {
const { dispatch, onlyMedia } = this.props;
dispatch(expandCommunityTimeline({ onlyMedia }));
this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
}
componentDidUpdate (prevProps) {
if (prevProps.onlyMedia !== this.props.onlyMedia) {
const { dispatch, onlyMedia } = this.props;
this.disconnect();
dispatch(expandCommunityTimeline({ onlyMedia }));
this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
}
}
componentWillUnmount () {
if (this.disconnect) {
this.disconnect();
this.disconnect = null;
}
}
setRef = c => {
this.column = c;
}
handleLoadMore = maxId => {
const { dispatch, onlyMedia } = this.props;
dispatch(expandCommunityTimeline({ maxId, onlyMedia }));
}
render () {
const { intl, shouldUpdateScroll, hasUnread, columnId, multiColumn, onlyMedia } = this.props;
const pinned = !!columnId;
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
<ColumnHeader
icon='users'
active={hasUnread}
title={intl.formatMessage(messages.title)}
onPin={this.handlePin}
onMove={this.handleMove}
onClick={this.handleHeaderClick}
pinned={pinned}
multiColumn={multiColumn}
>
<ColumnSettingsContainer columnId={columnId} />
</ColumnHeader>
<StatusListContainer
trackScroll={!pinned}
scrollKey={`community_timeline-${columnId}`}
timelineId={`community${onlyMedia ? ':media' : ''}`}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
shouldUpdateScroll={shouldUpdateScroll}
bindToDocument={!multiColumn}
/>
</Column>
);
}
}
......@@ -23,7 +23,7 @@ const messages = defineMessages({
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' },
......@@ -104,9 +104,6 @@ class Compose extends React.PureComponent {
{!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'COMMUNITY') && (
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'PUBLIC') && (
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
)}
......
......@@ -20,7 +20,6 @@ const messages = defineMessages({
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' },
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' },
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
......@@ -98,11 +97,10 @@ class GettingStarted extends ImmutablePureComponent {
if (multiColumn) {
navItems.push(
<ColumnSubheading key='header-discover' text={intl.formatMessage(messages.discover)} />,
<ColumnLink key='community_timeline' icon='users' text={intl.formatMessage(messages.community_timeline)} to='/timelines/public/local' />,
<ColumnLink key='public_timeline' icon='globe' text={intl.formatMessage(messages.public_timeline)} to='/timelines/public' />,
);
height += 34 + 48*2;
height += 34 + 48;
if (profile_directory) {
navItems.push(
......
......@@ -5,7 +5,6 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import Toggle from 'react-toggle';
import AsyncSelect from 'react-select/async';
import { NonceProvider } from 'react-select';
import SettingToggle from '../../notifications/components/setting_toggle';
const messages = defineMessages({
placeholder: { id: 'hashtag.column_settings.select.placeholder', defaultMessage: 'Enter hashtags…' },
......@@ -112,10 +111,6 @@ class ColumnSettings extends React.PureComponent {
{this.modeSelect('none')}
</div>
)}
<div className='column-settings__row'>
<SettingToggle settings={settings} settingPath={['local']} onChange={onChange} label={<FormattedMessage id='community.column_settings.local_only' defaultMessage='Local only' />} />
</div>
</div>
);
}
......
......@@ -12,7 +12,7 @@ import { connectHashtagStream } from '../../actions/streaming';
import { isEqual } from 'lodash';
const mapStateToProps = (state, props) => ({
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}${props.params.local ? ':local' : ''}`, 'unread']) > 0,
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}`, 'unread']) > 0,
});
export default @connect(mapStateToProps)
......@@ -76,13 +76,13 @@ class HashtagTimeline extends React.PureComponent {
this.column.scrollTop();
}
_subscribe (dispatch, id, tags = {}, local) {
_subscribe (dispatch, id, tags = {}) {
let any = (tags.any || []).map(tag => tag.value);
let all = (tags.all || []).map(tag => tag.value);
let none = (tags.none || []).map(tag => tag.value);
[id, ...any].map(tag => {
this.disconnects.push(dispatch(connectHashtagStream(id, tag, local, status => {
this.disconnects.push(dispatch(connectHashtagStream(id, tag, status => {
let tags = status.tags.map(tag => tag.name);
return all.filter(tag => tags.includes(tag)).length === all.length &&
......@@ -98,21 +98,21 @@ class HashtagTimeline extends React.PureComponent {
componentDidMount () {
const { dispatch } = this.props;
const { id, tags, local } = this.props.params;
const { id, tags } = this.props.params;
this._subscribe(dispatch, id, tags, local);
dispatch(expandHashtagTimeline(id, { tags, local }));
this._subscribe(dispatch, id, tags);
dispatch(expandHashtagTimeline(id, { tags }));
}
componentWillReceiveProps (nextProps) {
const { dispatch, params } = this.props;
const { id, tags, local } = nextProps.params;
const { id, tags } = nextProps.params;
if (id !== params.id || !isEqual(tags, params.tags) || !isEqual(local, params.local)) {
if (id !== params.id || !isEqual(tags, params.tags)) {
this._unsubscribe();
this._subscribe(dispatch, id, tags, local);
dispatch(clearTimeline(`hashtag:${id}${local ? ':local' : ''}`));
dispatch(expandHashtagTimeline(id, { tags, local }));
this._subscribe(dispatch, id, tags);
dispatch(clearTimeline(`hashtag:${id}`));
dispatch(expandHashtagTimeline(id, { tags }));
}
}
......@@ -125,13 +125,13 @@ class HashtagTimeline extends React.PureComponent {
}
handleLoadMore = maxId => {
const { id, tags, local } = this.props.params;
this.props.dispatch(expandHashtagTimeline(id, { maxId, tags, local }));
const { id, tags } = this.props.params;
this.props.dispatch(expandHashtagTimeline(id, { maxId, tags }));
}
render () {
const { shouldUpdateScroll, hasUnread, columnId, multiColumn } = this.props;
const { id, local } = this.props.params;
const { id } = this.props.params;
const pinned = !!columnId;
return (
......@@ -153,7 +153,7 @@ class HashtagTimeline extends React.PureComponent {
<StatusListContainer
trackScroll={!pinned}
scrollKey={`hashtag_timeline-${columnId}`}
timelineId={`hashtag:${id}${local ? ':local' : ''}`}
timelineId={`hashtag:${id}`}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.hashtag' defaultMessage='There is nothing in this hashtag yet.' />}
shouldUpdateScroll={shouldUpdateScroll}
......
......@@ -45,11 +45,6 @@ const FrameFederation = ({ onNext }) => (
<p><FormattedMessage id='introduction.federation.home.text' defaultMessage='Posts from people you follow will appear in your home feed. You can follow anyone on any server!' /></p>
</div>
<div>
<h3><FormattedMessage id='introduction.federation.local.headline' defaultMessage='Local' /></h3>
<p><FormattedMessage id='introduction.federation.local.text' defaultMessage='Public posts from people on the same server as you will appear in the local timeline.' /></p>
</div>
<div>
<h3><FormattedMessage id='introduction.federation.federated.headline' defaultMessage='Federated' /></h3>
<p><FormattedMessage id='introduction.federation.federated.text' defaultMessage='Public posts from other servers of the fediverse will appear in the federated timeline.' /></p>
......
......@@ -112,10 +112,6 @@ class KeyboardShortcuts extends ImmutablePureComponent {
<td><kbd>g</kbd>+<kbd>n</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.notifications' defaultMessage='to open notifications column' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>l</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.local' defaultMessage='to open local timeline' /></td>
</tr>
<tr>
<td><kbd>g</kbd>+<kbd>t</kbd></td>
<td><FormattedMessage id='keyboard_shortcuts.federated' defaultMessage='to open federated timeline' /></td>
......
......@@ -24,25 +24,20 @@ class HashtagTimeline extends React.PureComponent {
isLoading: PropTypes.bool.isRequired,
hasMore: PropTypes.bool.isRequired,
hashtag: PropTypes.string.isRequired,
local: PropTypes.bool.isRequired,
};
static defaultProps = {
local: false,
};
componentDidMount () {
const { dispatch, hashtag, local } = this.props;
const { dispatch, hashtag } = this.props;
dispatch(expandHashtagTimeline(hashtag, { local }));
dispatch(expandHashtagTimeline(hashtag));
}
handleLoadMore = () => {
const { dispatch, hashtag, local, statusIds } = this.props;
const { dispatch, hashtag, statusIds } = this.props;
const maxId = statusIds.last();
if (maxId) {
dispatch(expandHashtagTimeline(hashtag, { maxId, local }));
dispatch(expandHashtagTimeline(hashtag, { maxId }));
}
}
......
......@@ -2,15 +2,15 @@ import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
import { expandPublicTimeline } from 'mastodon/actions/timelines';
import Masonry from 'react-masonry-infinite';
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import DetailedStatusContainer from 'mastodon/features/status/containers/detailed_status_container';
import { debounce } from 'lodash';
import LoadingIndicator from 'mastodon/components/loading_indicator';
const mapStateToProps = (state, { local }) => {
const timeline = state.getIn(['timelines', local ? 'community' : 'public'], ImmutableMap());
const mapStateToProps = (state) => {
const timeline = state.getIn(['timelines', 'public'], ImmutableMap());
return {
statusIds: timeline.get('items', ImmutableList()),
......@@ -27,7 +27,6 @@ class PublicTimeline extends React.PureComponent {
statusIds: ImmutablePropTypes.list.isRequired,
isLoading: PropTypes.bool.isRequired,
hasMore: PropTypes.bool.isRequired,
local: PropTypes.bool,
};
componentDidMount () {
......@@ -35,23 +34,20 @@ class PublicTimeline extends React.PureComponent {
}
componentDidUpdate (prevProps) {
if (prevProps.local !== this.props.local) {
this._connect();
}
}
_connect () {
const { dispatch, local } = this.props;
const { dispatch } = this.props;
dispatch(local ? expandCommunityTimeline() : expandPublicTimeline());
dispatch(expandPublicTimeline());
}
handleLoadMore = () => {
const { dispatch, statusIds, local } = this.props;
const { dispatch, statusIds } = this.props;
const maxId = statusIds.last();
if (maxId) {
dispatch(local ? expandCommunityTimeline({ maxId }) : expandPublicTimeline({ maxId }));
dispatch(expandPublicTimeline({ maxId }));
}
}
......
......@@ -18,7 +18,6 @@ import {
Compose,
Notifications,
HomeTimeline,
CommunityTimeline,
PublicTimeline,
DomainTimeline,
HashtagTimeline,