mirror of
https://github.com/whippyshou/mastodon
synced 2024-11-30 15:58:20 +09:00
add local only timeline
This commit is contained in:
parent
96a814d31e
commit
25cc000a97
@ -142,7 +142,7 @@ const excludeTypesFromFilter = filter => {
|
||||
'admin.report',
|
||||
]);
|
||||
|
||||
return allTypes.filterNot(item => filter === 'direct'? item ==='mention' :item === filter ).toJS();
|
||||
return allTypes.filterNot(item => filter === 'direct'? item ==='mention':item === filter ).toJS();
|
||||
};
|
||||
|
||||
const noOp = () => {};
|
||||
|
@ -0,0 +1,44 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call,
|
||||
@typescript-eslint/no-unsafe-return,
|
||||
@typescript-eslint/no-unsafe-assignment,
|
||||
@typescript-eslint/no-unsafe-member-access
|
||||
-- the settings store is not yet typed */
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { changeSetting } from '../../../actions/settings';
|
||||
import SettingToggle from '../../notifications/components/setting_toggle';
|
||||
|
||||
export const ColumnSettings: React.FC = () => {
|
||||
const settings = useAppSelector((state) => state.settings.get('local'));
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const onChange = useCallback(
|
||||
(key: string, checked: boolean) => {
|
||||
dispatch(changeSetting(['local', ...key], checked));
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle
|
||||
prefix='local_timeline'
|
||||
settings={settings}
|
||||
settingPath={['shows', 'media']}
|
||||
onChange={onChange}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='local.column_settings.show_media'
|
||||
defaultMessage='Show Media'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -0,0 +1,14 @@
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
|
||||
import { title } from 'mastodon/initial_state';
|
||||
|
||||
export const ExplorePrompt = () => (
|
||||
<DismissableBanner id='local.explore_prompt'>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='local.explore_prompt.body'
|
||||
defaultMessage="Your home feed will have a mix of posts from the hashtags you've chosen to follow, the people you've chosen to follow, and the posts they boost. If that feels too quiet, you may want to:" values={{ title }}
|
||||
/>
|
||||
</p>
|
||||
</DismissableBanner>
|
||||
);
|
195
app/javascript/mastodon/features/local_timeline/index.jsx
Normal file
195
app/javascript/mastodon/features/local_timeline/index.jsx
Normal file
@ -0,0 +1,195 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
|
||||
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
|
||||
import { expandHomeTimeline } from '../../actions/timelines';
|
||||
import Column from '../../components/column';
|
||||
import ColumnHeader from '../../components/column_header';
|
||||
import LocalStatusListContainer from '../ui/containers/local_status_list_container';
|
||||
|
||||
import { ColumnSettings } from './components/column_settings';
|
||||
import { ExplorePrompt } from './components/explore_prompt';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column.local', defaultMessage: 'local' },
|
||||
});
|
||||
|
||||
const getHomeFeedSpeed = createSelector([
|
||||
state => state.getIn(['timelines', 'home', 'items'], ImmutableList()),
|
||||
state => state.getIn(['timelines', 'home', 'pendingItems'], ImmutableList()),
|
||||
state => state.get('statuses'),
|
||||
], (statusIds, pendingStatusIds, statusMap) => {
|
||||
const recentStatusIds = pendingStatusIds.size > 0 ? pendingStatusIds : statusIds;
|
||||
const statuses = recentStatusIds.filter(id => id !== null).map(id => statusMap.get(id)).filter(status => status?.get('account') !== me).take(20);
|
||||
|
||||
if (statuses.isEmpty()) {
|
||||
return {
|
||||
gap: 0,
|
||||
newest: new Date(0),
|
||||
};
|
||||
}
|
||||
|
||||
const datetimes = statuses.map(status => status.get('created_at', 0));
|
||||
const oldest = new Date(datetimes.min());
|
||||
const newest = new Date(datetimes.max());
|
||||
const averageGap = (newest - oldest) / (1000 * (statuses.size + 1)); // Average gap between posts on first page in seconds
|
||||
|
||||
return {
|
||||
gap: averageGap,
|
||||
newest,
|
||||
};
|
||||
});
|
||||
|
||||
const homeTooSlow = createSelector([
|
||||
state => state.getIn(['timelines', 'home', 'isLoading']),
|
||||
state => state.getIn(['timelines', 'home', 'isPartial']),
|
||||
getHomeFeedSpeed,
|
||||
], (isLoading, isPartial, speed) =>
|
||||
!isLoading && !isPartial // Only if the home feed has finished loading
|
||||
&& (
|
||||
(speed.gap > (30 * 60) // If the average gap between posts is more than 30 minutes
|
||||
|| (Date.now() - speed.newest) > (1000 * 3600)) // If the most recent post is from over an hour ago
|
||||
)
|
||||
);
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
|
||||
isPartial: state.getIn(['timelines', 'home', 'isPartial']),
|
||||
tooSlow: homeTooSlow(state),
|
||||
});
|
||||
|
||||
class HomeTimeline extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasUnread: PropTypes.bool,
|
||||
isPartial: PropTypes.bool,
|
||||
columnId: PropTypes.string,
|
||||
multiColumn: PropTypes.bool,
|
||||
tooSlow: PropTypes.bool,
|
||||
};
|
||||
|
||||
handlePin = () => {
|
||||
const { columnId, dispatch } = this.props;
|
||||
|
||||
if (columnId) {
|
||||
dispatch(removeColumn(columnId));
|
||||
} else {
|
||||
dispatch(addColumn('HOME', {}));
|
||||
}
|
||||
};
|
||||
|
||||
handleMove = (dir) => {
|
||||
const { columnId, dispatch } = this.props;
|
||||
dispatch(moveColumn(columnId, dir));
|
||||
};
|
||||
|
||||
handleHeaderClick = () => {
|
||||
this.column.scrollTop();
|
||||
};
|
||||
|
||||
setRef = c => {
|
||||
this.column = c;
|
||||
};
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
this.props.dispatch(expandHomeTimeline({ maxId }));
|
||||
};
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
this._checkIfReloadNeeded(prevProps.isPartial, this.props.isPartial);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this._stopPolling();
|
||||
}
|
||||
|
||||
_checkIfReloadNeeded (wasPartial, isPartial) {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
if (wasPartial === isPartial) {
|
||||
return;
|
||||
} else if (!wasPartial && isPartial) {
|
||||
this.polling = setInterval(() => {
|
||||
dispatch(expandHomeTimeline());
|
||||
}, 3000);
|
||||
} else if (wasPartial && !isPartial) {
|
||||
this._stopPolling();
|
||||
}
|
||||
}
|
||||
|
||||
_stopPolling () {
|
||||
if (this.polling) {
|
||||
clearInterval(this.polling);
|
||||
this.polling = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, columnId, multiColumn, tooSlow } = this.props;
|
||||
const pinned = !!columnId;
|
||||
const { signedIn } = this.context.identity;
|
||||
const banners = [];
|
||||
|
||||
|
||||
if (tooSlow) {
|
||||
banners.push(<ExplorePrompt key='explore-prompt' />);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='home'
|
||||
active={hasUnread}
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onPin={this.handlePin}
|
||||
onMove={this.handleMove}
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={pinned}
|
||||
multiColumn={multiColumn}
|
||||
>
|
||||
<ColumnSettings />
|
||||
</ColumnHeader>
|
||||
|
||||
{signedIn ? (
|
||||
<LocalStatusListContainer
|
||||
prepend={banners}
|
||||
alwaysPrepend
|
||||
trackScroll={!pinned}
|
||||
scrollKey={`home_timeline-${columnId}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
timelineId='home'
|
||||
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty! Follow more people to fill it up.' />}
|
||||
bindToDocument={!multiColumn}
|
||||
/>
|
||||
) : <NotSignedInIndicator />}
|
||||
|
||||
<Helmet>
|
||||
<title>{intl.formatMessage(messages.title)}</title>
|
||||
<meta name='robots' content='noindex' />
|
||||
</Helmet>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(HomeTimeline));
|
@ -1,74 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { initBoostModal } from '../../../actions/boosts';
|
||||
import { mentionCompose } from '../../../actions/compose';
|
||||
import {
|
||||
reblog,
|
||||
favourite,
|
||||
unreblog,
|
||||
unfavourite,
|
||||
} from '../../../actions/interactions';
|
||||
import {
|
||||
hideStatus,
|
||||
revealStatus,
|
||||
} from '../../../actions/statuses';
|
||||
import { boostModal } from '../../../initial_state';
|
||||
import { makeGetNotification, makeGetStatus, makeGetReport } from '../../../selectors';
|
||||
import Notification from '../components/notification';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getNotification = makeGetNotification();
|
||||
const getStatus = makeGetStatus();
|
||||
const getReport = makeGetReport();
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const notification = getNotification(state, props.notification, props.accountId);
|
||||
return {
|
||||
notification: notification ,
|
||||
status: notification.get('status') ? getStatus(state, { id: notification.get('status'), contextType: 'notifications' }) : null,
|
||||
report: notification.get('report') ? getReport(state, notification.get('report'), notification.getIn(['report', 'target_account', 'id'])) : null,
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onMention: (account, router) => {
|
||||
dispatch(mentionCompose(account, router));
|
||||
},
|
||||
|
||||
onModalReblog (status, privacy) {
|
||||
dispatch(reblog(status, privacy));
|
||||
},
|
||||
|
||||
onReblog (status, e) {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
} else {
|
||||
if (e.shiftKey || !boostModal) {
|
||||
this.onModalReblog(status);
|
||||
} else {
|
||||
dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onFavourite (status) {
|
||||
if (status.get('favourited')) {
|
||||
dispatch(unfavourite(status));
|
||||
} else {
|
||||
dispatch(favourite(status));
|
||||
}
|
||||
},
|
||||
|
||||
onToggleHidden (status) {
|
||||
if (status.get('hidden')) {
|
||||
dispatch(revealStatus(status.get('id')));
|
||||
} else {
|
||||
dispatch(hideStatus(status.get('id')));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(Notification);
|
@ -1,74 +0,0 @@
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { initBoostModal } from '../../../actions/boosts';
|
||||
import { mentionCompose } from '../../../actions/compose';
|
||||
import {
|
||||
reblog,
|
||||
favourite,
|
||||
unreblog,
|
||||
unfavourite,
|
||||
} from '../../../actions/interactions';
|
||||
import {
|
||||
hideStatus,
|
||||
revealStatus,
|
||||
} from '../../../actions/statuses';
|
||||
import { boostModal } from '../../../initial_state';
|
||||
import { makeGetNotification, makeGetStatus, makeGetReport } from '../../../selectors';
|
||||
import Notification from '../components/notification';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getNotification = makeGetNotification();
|
||||
const getStatus = makeGetStatus();
|
||||
const getReport = makeGetReport();
|
||||
|
||||
const mapStateToProps = (state, props) => {
|
||||
const notification = getNotification(state, props.notification, props.accountId);
|
||||
return {
|
||||
notification: (notification.get('status') && getStatus(state, { id: notification.get('status'), contextType: 'notifications' }).get('visibility')!=='direct') || notification.get('report') ? notification : null,
|
||||
status: notification.get('status') ? getStatus(state, { id: notification.get('status'), contextType: 'notifications' }) : null,
|
||||
report: notification.get('report') ? getReport(state, notification.get('report'), notification.getIn(['report', 'target_account', 'id'])) : null,
|
||||
};
|
||||
};
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onMention: (account, router) => {
|
||||
dispatch(mentionCompose(account, router));
|
||||
},
|
||||
|
||||
onModalReblog (status, privacy) {
|
||||
dispatch(reblog(status, privacy));
|
||||
},
|
||||
|
||||
onReblog (status, e) {
|
||||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
} else {
|
||||
if (e.shiftKey || !boostModal) {
|
||||
this.onModalReblog(status);
|
||||
} else {
|
||||
dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onFavourite (status) {
|
||||
if (status.get('favourited')) {
|
||||
dispatch(unfavourite(status));
|
||||
} else {
|
||||
dispatch(favourite(status));
|
||||
}
|
||||
},
|
||||
|
||||
onToggleHidden (status) {
|
||||
if (status.get('hidden')) {
|
||||
dispatch(revealStatus(status.get('id')));
|
||||
} else {
|
||||
dispatch(hideStatus(status.get('id')));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(Notification);
|
@ -45,9 +45,9 @@ class StatusCheckBox extends PureComponent {
|
||||
|
||||
const visibilityIconInfo = {
|
||||
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
||||
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
||||
'unlisted': { icon: 'cloud', text: intl.formatMessage(messages.unlisted_short) },
|
||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||
'direct': { icon: 'explore', text: intl.formatMessage(messages.direct_short) },
|
||||
};
|
||||
|
||||
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
|
||||
|
@ -76,7 +76,7 @@ class NavigationPanel extends Component {
|
||||
</>
|
||||
)}
|
||||
{(signedIn) && (
|
||||
<ColumnLink transparent to='/public/local' isActive={this.isFirehoseActive} icon='hashtag' text={intl.formatMessage(messages.firehose)} />
|
||||
<ColumnLink transparent to='/local' isActive={this.isFirehoseActive} icon='hashtag' text={intl.formatMessage(messages.firehose)} />
|
||||
)}
|
||||
|
||||
{!signedIn && (
|
||||
|
@ -0,0 +1,66 @@
|
||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { scrollTopTimeline, loadPending } from '../../../actions/timelines';
|
||||
import StatusList from '../../../components/status_list';
|
||||
|
||||
const makeGetStatusIds = (pending = false) => createSelector([
|
||||
(state) => state.getIn(['settings', 'local'], ImmutableMap()),
|
||||
(state, { type }) => state.getIn(['timelines', type, pending ? 'pendingItems' : 'items'], ImmutableList()),
|
||||
(state) => state.get('statuses'),
|
||||
(type) => type
|
||||
], (columnSettings, statusIds, statuses,type) => {
|
||||
return statusIds.filter(id => {
|
||||
if (id === null) return true;
|
||||
|
||||
const statusForId = statuses.get(id);
|
||||
|
||||
let showStatus = true;
|
||||
|
||||
if (statusForId.get('visibility') === 'direct')
|
||||
return false;
|
||||
if (statusForId.get('in_reply_to_id')) {
|
||||
showStatus = showStatus && statusForId.get('in_reply_to_account_id') === statusForId.get('account')
|
||||
}
|
||||
if (columnSettings.getIn(['shows', 'media']) === true) {
|
||||
showStatus = showStatus && statusForId.get('media_attachments').size>0;
|
||||
}
|
||||
|
||||
return showStatus;
|
||||
});
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getStatusIds = makeGetStatusIds();
|
||||
const getPendingStatusIds = makeGetStatusIds(true);
|
||||
|
||||
const mapStateToProps = (state, { timelineId }) => ({
|
||||
statusIds: getStatusIds(state, { type: timelineId }),
|
||||
lastId: state.getIn(['timelines', timelineId, 'items'])?.last(),
|
||||
isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
|
||||
isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
|
||||
hasMore: state.getIn(['timelines', timelineId, 'hasMore']),
|
||||
numPending: getPendingStatusIds(state, { type: timelineId }).size,
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, { timelineId }) => ({
|
||||
|
||||
onScrollToTop: debounce(() => {
|
||||
dispatch(scrollTopTimeline(timelineId, true));
|
||||
}, 100),
|
||||
|
||||
onScroll: debounce(() => {
|
||||
dispatch(scrollTopTimeline(timelineId, false));
|
||||
}, 100),
|
||||
|
||||
onLoadPending: () => dispatch(loadPending(timelineId)),
|
||||
|
||||
});
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(StatusList);
|
@ -12,7 +12,8 @@ const makeGetStatusIds = (pending = false) => createSelector([
|
||||
(state, { type }) => state.getIn(['settings', type], ImmutableMap()),
|
||||
(state, { type }) => state.getIn(['timelines', type, pending ? 'pendingItems' : 'items'], ImmutableList()),
|
||||
(state) => state.get('statuses'),
|
||||
], (columnSettings, statusIds, statuses) => {
|
||||
(type) => type
|
||||
], (columnSettings, statusIds, statuses,type) => {
|
||||
return statusIds.filter(id => {
|
||||
if (id === null) return true;
|
||||
|
||||
|
@ -40,6 +40,7 @@ import {
|
||||
AccountTimeline,
|
||||
AccountGallery,
|
||||
HomeTimeline,
|
||||
LocalTimeline,
|
||||
Followers,
|
||||
Following,
|
||||
Reblogs,
|
||||
@ -199,7 +200,12 @@ class SwitchingColumnsArea extends PureComponent {
|
||||
<Redirect from='/timelines/public/local' to='/public/local' exact />
|
||||
<WrappedRoute path='/public' exact component={Firehose} componentParams={{ feedType: 'public' }} content={children} />
|
||||
<WrappedRoute path='/public/local' exact component={Firehose} componentParams={{ feedType: 'community' }} content={children} />
|
||||
<WrappedRoute path='/public/remote' exact component={Firehose} componentParams={{ feedType: 'public:remote' }} content={children} />
|
||||
|
||||
|
||||
<Redirect from='/timelines/local' to='/local' exact />
|
||||
<WrappedRoute path='/local' exact component={LocalTimeline} content={children} />
|
||||
|
||||
|
||||
<WrappedRoute path={['/conversations', '/timelines/direct']} component={DirectTimeline} content={children} />
|
||||
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
|
||||
<WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
|
||||
|
@ -14,6 +14,9 @@ export function HomeTimeline () {
|
||||
return import(/* webpackChunkName: "features/home_timeline" */'../../home_timeline');
|
||||
}
|
||||
|
||||
export function LocalTimeline () {
|
||||
return import(/* webpackChunkName: "features/home_timeline" */'../../local_timeline');
|
||||
}
|
||||
export function PublicTimeline () {
|
||||
return import(/* webpackChunkName: "features/public_timeline" */'../../public_timeline');
|
||||
}
|
||||
|
@ -117,6 +117,7 @@
|
||||
"column.firehose": "Live feeds",
|
||||
"column.follow_requests": "Follow requests",
|
||||
"column.home": "Home",
|
||||
"column.local": "feed",
|
||||
"column.lists": "Lists",
|
||||
"column.mutes": "Muted users",
|
||||
"column.notifications": "Notifications",
|
||||
@ -239,6 +240,7 @@
|
||||
"empty_column.followed_tags": "You have not followed any hashtags yet. When you do, they will show up here.",
|
||||
"empty_column.hashtag": "There is nothing in this hashtag yet.",
|
||||
"empty_column.home": "Your home timeline is empty! Follow more people to fill it up.",
|
||||
"empty_column.local": "Your home timeline is empty! Follow more people to fill it up.",
|
||||
"empty_column.list": "There is nothing in this list yet. When members of this list publish new posts, they will appear here.",
|
||||
"empty_column.lists": "You don't have any lists yet. When you create one, it will show up here.",
|
||||
"empty_column.mutes": "You haven't muted any users yet.",
|
||||
@ -309,8 +311,14 @@
|
||||
"home.column_settings.basic": "Basic",
|
||||
"home.column_settings.show_reblogs": "Show boosts",
|
||||
"home.column_settings.show_replies": "Show replies",
|
||||
"local.column_settings.basic": "local",
|
||||
"local.column_settings.show_media": "Show Media",
|
||||
"home.explore_prompt.body": "Your home feed will have a mix of posts from the hashtags you've chosen to follow, the people you've chosen to follow, and the posts they boost. If that feels too quiet, you may want to:",
|
||||
"home.explore_prompt.title": "This is your home base within Mastodon.",
|
||||
|
||||
"local.explore_prompt.body": "Your home feed will have a mix of posts from the hashtags you've chosen to follow, the people you've chosen to follow, and the posts they boost. If that feels too quiet, you may want to:",
|
||||
"local.explore_prompt.title": "This is your home base within Mastodon.",
|
||||
|
||||
"home.hide_announcements": "Hide announcements",
|
||||
"home.pending_critical_update.body": "Please update your Mastodon server as soon as possible!",
|
||||
"home.pending_critical_update.link": "See updates",
|
||||
|
@ -117,6 +117,7 @@
|
||||
"column.firehose": "실시간 피드",
|
||||
"column.follow_requests": "팔로우 요청",
|
||||
"column.home": "홈",
|
||||
"column.local": "실시간 피드",
|
||||
"column.lists": "리스트",
|
||||
"column.mutes": "뮤트한 사용자",
|
||||
"column.notifications": "알림",
|
||||
@ -239,6 +240,7 @@
|
||||
"empty_column.followed_tags": "아직 아무 해시태그도 팔로우하고 있지 않습니다. 해시태그를 팔로우하면, 여기에 표시됩니다.",
|
||||
"empty_column.hashtag": "이 해시태그는 아직 사용되지 않았습니다.",
|
||||
"empty_column.home": "당신의 홈 타임라인은 비어있습니다! 더 많은 사람들을 팔로우 하여 채워보세요.",
|
||||
"empty_column.local": "당신의 실시간 피드는 비어있습니다! 더 많은 사람들을 팔로우 하여 채워보세요.",
|
||||
"empty_column.list": "리스트에 아직 아무것도 없습니다. 리스트의 누군가가 게시물을 올리면 여기에 나타납니다.",
|
||||
"empty_column.lists": "아직 리스트가 없습니다. 리스트를 만들면 여기에 나타납니다.",
|
||||
"empty_column.mutes": "아직 아무도 뮤트하지 않았습니다.",
|
||||
@ -309,8 +311,14 @@
|
||||
"home.column_settings.basic": "기본",
|
||||
"home.column_settings.show_reblogs": "부스트 표시",
|
||||
"home.column_settings.show_replies": "답글 표시",
|
||||
"local.column_settings.basic": "실시간 피드",
|
||||
"local.column_settings.show_media": "미디어만",
|
||||
"home.explore_prompt.body": "홈 피드에는 내가 팔로우한 해시태그 그리고 팔로우한 사람과 부스트가 함께 나타납니다. 너무 고요하게 느껴진다면, 다음 것들을 살펴볼 수 있습니다.",
|
||||
"home.explore_prompt.title": "이곳은 마스토돈의 내 본거지입니다.",
|
||||
|
||||
"local.explore_prompt.body": "실시간 피드에는 {title}에 있는 사람들의 답장이 아닌 최근 게시글을 살펴볼 수 있습니다.",
|
||||
"local.explore_prompt.title": "이곳은 마스토돈의 내 본거지입니다.",
|
||||
|
||||
"home.hide_announcements": "공지사항 숨기기",
|
||||
"home.pending_critical_update.body": "서둘러 마스토돈 서버를 업데이트 하세요!",
|
||||
"home.pending_critical_update.link": "업데이트 보기",
|
||||
|
@ -20,7 +20,7 @@ const initialState = ImmutableMap({
|
||||
|
||||
home: ImmutableMap({
|
||||
shows: ImmutableMap({
|
||||
reblog: true,
|
||||
media: true,
|
||||
reply: true,
|
||||
}),
|
||||
|
||||
@ -29,6 +29,17 @@ const initialState = ImmutableMap({
|
||||
}),
|
||||
}),
|
||||
|
||||
local: ImmutableMap({
|
||||
shows: ImmutableMap({
|
||||
media: false
|
||||
}),
|
||||
|
||||
regex: ImmutableMap({
|
||||
body: '',
|
||||
}),
|
||||
}),
|
||||
|
||||
|
||||
notifications: ImmutableMap({
|
||||
alerts: ImmutableMap({
|
||||
follow: false,
|
||||
|
@ -1551,21 +1551,21 @@ body.embed .detailed-status__favorites {
|
||||
body.embed .detailed-status__link > .fa-reply + span::after,
|
||||
.layout-multiple-columns .detailed-status__link > .fa-reply + span::after {
|
||||
color: var(--color-dim);
|
||||
content: 'Replies';
|
||||
content: '답장';
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
body.embed .detailed-status__link > .fa-retweet + span::after,
|
||||
.layout-multiple-columns .detailed-status__link > .fa-retweet + span::after {
|
||||
color: var(--color-dim);
|
||||
content: 'Boosts';
|
||||
content: '부스트';
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
body.embed .detailed-status__link > .fa-star + span::after,
|
||||
.layout-multiple-columns .detailed-status__link > .fa-star + span::after {
|
||||
color: var(--color-dim);
|
||||
content: 'Favourites';
|
||||
content: '좋아요';
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
@ -2961,6 +2961,11 @@ body.embed .button.logo-button:hover,
|
||||
order: 5;
|
||||
}
|
||||
|
||||
|
||||
.layout-multiple-columns .column-link[href="/local"] {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
.layout-multiple-columns .column-link[href="/public"] {
|
||||
order: 6;
|
||||
}
|
||||
|
@ -1584,21 +1584,21 @@ body.embed > .activity-stream {
|
||||
body.embed .detailed-status__link > .fa-reply + span::after,
|
||||
.layout-single-column .detailed-status__link > .fa-reply + span::after {
|
||||
color: var(--color-dim);
|
||||
content: 'Replies';
|
||||
content: '답장';
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
body.embed .detailed-status__link > .fa-retweet + span::after,
|
||||
.layout-single-column .detailed-status__link > .fa-retweet + span::after {
|
||||
color: var(--color-dim);
|
||||
content: 'Boosts';
|
||||
content: '부스트';
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
body.embed .detailed-status__link > .fa-star + span::after,
|
||||
.layout-single-column .detailed-status__link > .fa-star + span::after {
|
||||
color: var(--color-dim);
|
||||
content: 'Favourites';
|
||||
content: '좋아요';
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
@ -2997,6 +2997,10 @@ body.embed .button.logo-button:hover,
|
||||
order: 5;
|
||||
}
|
||||
|
||||
.layout-single-column .column-link[href="/local"] {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
.layout-single-column .column-link[href="/public"] {
|
||||
order: 6;
|
||||
}
|
||||
|
@ -1551,21 +1551,21 @@ body.embed .detailed-status__favorites {
|
||||
body.embed .detailed-status__link > .fa-reply + span::after,
|
||||
.layout-multiple-columns .detailed-status__link > .fa-reply + span::after {
|
||||
color: var(--color-dim);
|
||||
content: 'Replies';
|
||||
content: '답장';
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
body.embed .detailed-status__link > .fa-retweet + span::after,
|
||||
.layout-multiple-columns .detailed-status__link > .fa-retweet + span::after {
|
||||
color: var(--color-dim);
|
||||
content: 'Boosts';
|
||||
content: '부스트';
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
body.embed .detailed-status__link > .fa-star + span::after,
|
||||
.layout-multiple-columns .detailed-status__link > .fa-star + span::after {
|
||||
color: var(--color-dim);
|
||||
content: 'Favourites';
|
||||
content: '좋아요';
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
@ -2961,6 +2961,12 @@ body.embed .button.logo-button:hover,
|
||||
order: 5;
|
||||
}
|
||||
|
||||
|
||||
.layout-multiple-columns .column-link[href="/local"] {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
|
||||
.layout-multiple-columns .column-link[href="/public"] {
|
||||
order: 6;
|
||||
}
|
||||
|
@ -1590,21 +1590,21 @@ body.embed > .activity-stream {
|
||||
body.embed .detailed-status__link > .fa-reply + span::after,
|
||||
.layout-single-column .detailed-status__link > .fa-reply + span::after {
|
||||
color: var(--color-dim);
|
||||
content: 'Replies';
|
||||
content: '답장';
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
body.embed .detailed-status__link > .fa-retweet + span::after,
|
||||
.layout-single-column .detailed-status__link > .fa-retweet + span::after {
|
||||
color: var(--color-dim);
|
||||
content: 'Boosts';
|
||||
content: '부스트';
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
body.embed .detailed-status__link > .fa-star + span::after,
|
||||
.layout-single-column .detailed-status__link > .fa-star + span::after {
|
||||
color: var(--color-dim);
|
||||
content: 'Favourites';
|
||||
content: '좋아요';
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
@ -3002,6 +3002,10 @@ body.embed .button.logo-button:hover,
|
||||
order: 5;
|
||||
}
|
||||
|
||||
.layout-single-column .column-link[href="/local"] {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
.layout-single-column .column-link[href="/public"] {
|
||||
order: 6;
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
%p.hint= t 'appearance.advanced_web_interface_hint'
|
||||
|
||||
.fields-group
|
||||
= ff.input :'web.advanced_layout', wrapper: :with_label, hint: false, label: I18n.t('simple_form.labels.defaults.setting_advanced_layout')
|
||||
%h4= t 'appearance.animations_and_accessibility'
|
||||
|
||||
.fields-group
|
||||
|
@ -23,6 +23,7 @@ Rails.application.routes.draw do
|
||||
/publish
|
||||
/follow_requests
|
||||
/direct_messages
|
||||
/local
|
||||
/blocks
|
||||
/domain_blocks
|
||||
/mutes
|
||||
|
Loading…
Reference in New Issue
Block a user